diff --git a/.github/.well-known/funding-manifest-urls b/.github/.well-known/funding-manifest-urls new file mode 100644 index 0000000000..856e91df9f --- /dev/null +++ b/.github/.well-known/funding-manifest-urls @@ -0,0 +1 @@ +https://jmonkeyengine.org/funding.json \ No newline at end of file diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..5e829187ab --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +open_collective: jmonkeyengine diff --git a/.github/actions/tools/bintray.sh b/.github/actions/tools/bintray.sh deleted file mode 100644 index 0c7d4e6ddf..0000000000 --- a/.github/actions/tools/bintray.sh +++ /dev/null @@ -1,93 +0,0 @@ -#!/bin/bash - -# bintray_createPackage [REPO] [PACKAGE] [USER] [PASSWORD] [GIT REPO] [LICENSE] -function bintray_createPackage { - repo="$1" - package="$2" - user="$3" - password="$4" - srcrepo="$5" - license="$6" - - repoUrl="https://api.bintray.com/packages/$repo" - if [ "`curl -u$user:$password -H Content-Type:application/json -H Accept:application/json \ - --write-out %{http_code} --silent --output /dev/null -X GET \"$repoUrl/$package\"`" != "200" ]; - then - - if [ "$srcrepo" != "" -a "$license" != "" ]; - then - echo "Package does not exist... create." - data="{ - \"name\": \"${package}\", - \"labels\": [], - \"licenses\": [\"${license}\"], - \"vcs_url\": \"${srcrepo}\" - }" - - - curl -u$user:$password -H "Content-Type:application/json" -H "Accept:application/json" -X POST \ - -d "${data}" "$repoUrl" - else - echo "Package does not exist... you need to specify a repo and license for it to be created." - fi - else - echo "The package already exists. Skip." - fi -} - -# uploadFile file destination [REPO] "content" [PACKAGE] [USER] [PASSWORD] [SRCREPO] [LICENSE] -function bintray_uploadFile { - file="$1" - dest="$2" - - echo "Upload $file to $dest" - - repo="$3" - type="$4" - package="$5" - - user="$6" - password="$7" - - srcrepo="$8" - license="$9" - publish="${10}" - - bintray_createPackage $repo $package $user $password $srcrepo $license - - url="https://api.bintray.com/$type/$repo/$package/$dest" - if [ "$publish" = "true" ]; then url="$url;publish=1"; fi - - curl -T "$file" -u$user:$password "$url" - -} - -function bintray_uploadAll { - path="$1" - destpath="$2" - repo="$3" - type="$4" - package="$5" - - user="$6" - password="$7" - - srcrepo="$8" - license="$9" - publish="${10}" - - cdir="$PWD" - cd "$path" - - files="`find . -type f -print`" - IFS=" -" - set -f - for f in $files; do - destfile="$destpath/${f:2}" - bintray_uploadFile $f $destfile $repo $type $package $user $password $srcrepo $license $publish - done - set +f - unset IFS - cd "$cdir" -} diff --git a/.github/actions/tools/minio.sh b/.github/actions/tools/minio.sh new file mode 100644 index 0000000000..356fbf14d3 --- /dev/null +++ b/.github/actions/tools/minio.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +# minio_uploadFile +# +# Upload the specified file to the specified MinIO instance. +function minio_uploadFile { + file="$1" + dest="$2" + url="$3" + access="$4" + secret="$5" + + echo "Install MinIO client" + wget --quiet https://dl.min.io/client/mc/release/linux-amd64/mc + chmod +x ./mc + + echo "Add an alias for the MinIO instance to the MinIO configuration file" + ./mc alias set objects "$url" "$access" "$secret" + + echo "Upload $file to $url/$dest" + ./mc cp "$file" "objects/$dest" +} diff --git a/.github/actions/tools/uploadToCentral.sh b/.github/actions/tools/uploadToCentral.sh new file mode 100755 index 0000000000..12a36ac5e2 --- /dev/null +++ b/.github/actions/tools/uploadToCentral.sh @@ -0,0 +1,68 @@ +#! /bin/bash +set -euo pipefail + +## Upload a deployment +## from the "org.jmonkeyengine" namespace in Sonatype's OSSRH staging area +## to Sonatype's Central Publisher Portal +## so the deployment can be tested and then published or dropped. + +## IMPORTANT: The upload request must originate +## from the IP address used to stage the deployment to the staging area! + +# The required -p and -u flags on the command line +# specify the password and username components of a "user token" +# generated using the web interface at https://central.sonatype.com/account + +while getopts p:u: flag +do + case "${flag}" in + p) centralPassword=${OPTARG};; + u) centralUsername=${OPTARG};; + esac +done + +# Combine both components into a base64 "user token" +# suitable for the Authorization header of a POST request: + +token=$(printf %s:%s "${centralUsername}" "${centralPassword}" | base64) + +# Send a POST request to upload the deployment: + +server='ossrh-staging-api.central.sonatype.com' +endpoint='/manual/upload/defaultRepository/org.jmonkeyengine' +url="https://${server}${endpoint}" + +statusCode=$(curl "${url}" \ + --no-progress-meter \ + --output postData1.txt \ + --write-out '%{response_code}' \ + --request POST \ + --header 'accept: */*' \ + --header "Authorization: Bearer ${token}" \ + --data '') + +echo "Status code = ${statusCode}" +echo 'Received data:' +cat postData1.txt +echo '[EOF]' + +# Retry if the default repo isn't found (status=400). + +if [ "${statusCode}" == "400" ]; then + echo "Will retry after 30 seconds." + sleep 30 + + statusCode2=$(curl "${url}" \ + --no-progress-meter \ + --output postData2.txt \ + --write-out '%{response_code}' \ + --request POST \ + --header 'accept: */*' \ + --header "Authorization: Bearer ${token}" \ + --data '') + + echo "Status code = ${statusCode2}" + echo 'Received data:' + cat postData2.txt + echo '[EOF]' +fi diff --git a/.github/actions/tools/uploadToMaven.sh b/.github/actions/tools/uploadToMaven.sh index 1c817a6152..51dc2da533 100644 --- a/.github/actions/tools/uploadToMaven.sh +++ b/.github/actions/tools/uploadToMaven.sh @@ -2,16 +2,10 @@ ############################################# # # Usage -# uploadAllToMaven path/of/dist/maven https://api.bintray.com/maven/riccardo/sandbox-maven/ riccardo $BINTRAY_PASSWORD gitrepo license -# Note: gitrepo and license are needed only when uploading to bintray if you want to create missing packages automatically -# gitrepo must be a valid source repository -# license must be a license supported by bintray eg "BSD 3-Clause" -# or -# uploadAllToMaven path/of/dist/maven $GITHUB_PACKAGE_REPOSITORY user password +# uploadAllToMaven path/of/dist/maven $GITHUB_PACKAGE_REPOSITORY user password # ############################################# root="`dirname ${BASH_SOURCE[0]}`" -source $root/bintray.sh set -e function uploadToMaven { @@ -34,29 +28,6 @@ function uploadToMaven { auth="-H \"Authorization: token $password\"" fi - - if [[ $repourl == https\:\/\/api.bintray.com\/* ]]; - then - package="`dirname $destfile`" - version="`basename $package`" - package="`dirname $package`" - package="`basename $package`" - - if [ "$user" = "" -o "$password" = "" ]; - then - echo "Error! You need username and password to upload to bintray" - exit 1 - fi - echo "Detected bintray" - - bintrayRepo="${repourl/https\:\/\/api.bintray.com\/maven/}" - echo "Create package on $bintrayRepo" - - bintray_createPackage $bintrayRepo $package $user $password $srcrepo $license - - repourl="$repourl/$package" - fi - cmd="curl -T \"$file\" $auth \ \"$repourl/$destfile\" \ -vvv" diff --git a/.github/workflows/bounty.yml b/.github/workflows/bounty.yml new file mode 100644 index 0000000000..99689a7d5d --- /dev/null +++ b/.github/workflows/bounty.yml @@ -0,0 +1,93 @@ +name: Bounty detector + +on: + issues: + types: [labeled] + +permissions: + issues: write + pull-requests: read + +jobs: + notify: + runs-on: ubuntu-latest + if: startsWith(github.event.label.name, 'diff:') + steps: + - name: Comment bounty info + uses: actions/github-script@v7 + env: + FORUM_URL: "https://hub.jmonkeyengine.org/t/bounty-program-trial-starts-today/49394/" + RESERVE_HOURS: "48" + TIMER_SVG_BASE: "https://jme-bounty-reservation-indicator.rblb.workers.dev/timer.svg" + with: + script: | + const issue = context.payload.issue; + const actor = context.actor; + const issueOwner = issue.user?.login; + if (!issueOwner) return; + + const forumUrl = process.env.FORUM_URL || "TBD"; + const reserveHours = Number(process.env.RESERVE_HOURS || "48"); + const svgBase = process.env.TIMER_SVG_BASE || ""; + + // "previous contributor" = has at least one merged PR authored in this repo + const repoFull = `${context.repo.owner}/${context.repo.repo}`; + const q = `repo:${repoFull} type:pr author:${issueOwner} is:merged`; + + let isPreviousContributor = false; + try { + const search = await github.rest.search.issuesAndPullRequests({ q, per_page: 1 }); + isPreviousContributor = (search.data.total_count ?? 0) > 0; + } catch (e) { + isPreviousContributor = false; + } + + // Reserve only if previous contributor AND labeler is NOT the issue owner + const shouldReserve = isPreviousContributor && (actor !== issueOwner); + + const lines = []; + lines.push(`## 💰 This issue has a bounty`); + lines.push(`Resolve it to receive a reward.`); + lines.push(`For details (amount, rules, eligibility), see: ${forumUrl}`); + lines.push(""); + + lines.push(`If you want to start working on this, **comment on this issue** with your intent.`); + lines.push(`If accepted by a maintainer, the issue will be **assigned** to you.`); + lines.push(""); + + if (shouldReserve && svgBase) { + const reservedUntil = new Date(Date.now() + reserveHours * 60 * 60 * 1000); + const reservedUntilIso = reservedUntil.toISOString(); + + const svgUrl = + `${svgBase}` + + `?until=${encodeURIComponent(reservedUntilIso)}` + + `&user=${encodeURIComponent(issueOwner)}` + + `&theme=dark`; + + lines.push(`![bounty reservation](${svgUrl})`); + lines.push(""); + } + + // Avoid duplicate comments for the same label + const comments = await github.rest.issues.listComments({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + per_page: 100, + }); + + const already = comments.data.some(c => + c.user?.login === "github-actions[bot]" && + typeof c.body === "string" && + c.body.includes("This issue has a bounty") + ); + + if (already) return; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: lines.join("\n"), + }); diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml new file mode 100644 index 0000000000..6ff2d099fa --- /dev/null +++ b/.github/workflows/format.yml @@ -0,0 +1,21 @@ +name: auto-format +on: + push: + +jobs: + format: + runs-on: ubuntu-latest + if: ${{ false }} + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Prettify code + uses: creyD/prettier_action@v4.3 + with: + prettier_options: --tab-width 4 --print-width 110 --write **/**/*.java + prettier_version: "2.8.8" + only_changed: True + commit_message: "auto-format" + prettier_plugins: "prettier-plugin-java" diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8782a24e63..a46920fb0b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,37 +1,36 @@ ###################################################################################### -# JME CI/CD +# JME CI/CD ###################################################################################### # Quick overview of what is going on in this script: # - Build natives for android -# - Build natives for linux arm -# - Build natives for windows,mac,linux x86_64 and x86 # - Merge the natives, build the engine, create the zip release, maven artifacts, javadoc and native snapshot -# - (only when there is a change in the native code) Deploy the native snapshot to bintray -# - (only when building a release) Deploy everything else to github releases, github packet registry and bintray +# - (only when native code changes) Deploy the natives snapshot to the MinIO instance +# - (only when building a release) Deploy everything else to github releases and Sonatype # - (only when building a release) Update javadoc.jmonkeyengine.org # Note: -# All the actions/upload-artifact and actions/download-artifact steps are used to pass +# All the actions/upload-artifact and actions/download-artifact steps are used to pass # stuff between jobs, github actions has some sort of storage that is local to the # running workflow, we use it to store the result of each job since the filesystem # is not maintained between jobs. ################# CONFIGURATIONS ##################################################### -# >> Configure BINTRAY RELEASE & NATIVE SNAPSHOT -# Configure the following secrets/variables (customize the values with your own) -# BINTRAY_GENERIC_REPO=riccardoblsandbox/jmonkeyengine-files -# BINTRAY_MAVEN_REPO=riccardoblsandbox/jmonkeyengine -# BINTRAY_USER=riccardo -# BINTRAY_APIKEY=XXXXXX -# BINTRAY_LICENSE="BSD 3-Clause" +# >> Configure MINIO NATIVES SNAPSHOT +# OBJECTS_KEY=XXXXXX +# >> Configure SONATYPE RELEASE +# CENTRAL_PASSWORD=XXXXXX +# CENTRAL_USERNAME=XXXXXX +# >> Configure SIGNING +# SIGNING_KEY=XXXXXX +# SIGNING_PASSWORD=XXXXXX # >> Configure PACKAGE REGISTRY RELEASE -# Nothing to do here, everything is autoconfigured to work with the account/org that +# Nothing to do here, everything is autoconfigured to work with the account/org that # is running the build. # >> Configure JAVADOC # JAVADOC_GHPAGES_REPO="riccardoblsandbox/javadoc.jmonkeyengine.org.git" -# Generate a deloy key +# Generate a deploy key # ssh-keygen -t rsa -b 4096 -C "actions@users.noreply.github.com" -f javadoc_deploy # Set # JAVADOC_GHPAGES_DEPLOY_PRIVKEY="......." -# In github repo -> Settings, use javadoc_deploy.pub as Deploy key with write access +# In github repo -> Settings, use javadoc_deploy.pub as Deploy key with write access ###################################################################################### # Resources: # - Github actions docs: https://help.github.com/en/articles/about-github-actions @@ -47,180 +46,174 @@ on: push: branches: - master - - newbuild - - v3.3.* - - v3.2 - - v3.2.* + - v3.7 + - v3.6 + - v3.5 + - v3.4 + - v3.3 + - ios-2024_2 pull_request: release: types: [published] - + jobs: - - # Builds the natives on linux arm - BuildLinuxArmNatives: - name: Build natives for linux (arm) - runs-on: ubuntu-18.04 + ScreenshotTests: + name: Run Screenshot Tests + runs-on: ubuntu-latest container: - image: riccardoblb/buildenv-jme3:linuxArm - + image: ghcr.io/onemillionworlds/opengl-docker-image:v1 + permissions: + contents: read steps: + - uses: actions/checkout@v4 + - name: Start xvfb + run: | + Xvfb :99 -ac -screen 0 1024x768x16 & + export DISPLAY=:99 + echo "DISPLAY=:99" >> $GITHUB_ENV + - name: Report GL/Vulkan + run: | + set -x + echo "DISPLAY=$DISPLAY" + glxinfo | grep -E "OpenGL version|OpenGL renderer|OpenGL vendor" || true + vulkaninfo --summary || true + echo "VK_ICD_FILENAMES=$VK_ICD_FILENAMES" + echo "MESA_LOADER_DRIVER_OVERRIDE=$MESA_LOADER_DRIVER_OVERRIDE" + echo "GALLIUM_DRIVER=$GALLIUM_DRIVER" + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + - name: Test with Gradle Wrapper + run: | + ./gradlew :jme3-screenshot-test:screenshotTest + - name: Upload Test Reports + uses: actions/upload-artifact@master + if: always() + with: + name: screenshot-test-report + retention-days: 30 + path: | + **/build/reports/** + **/build/changed-images/** + **/build/test-results/** + + # Build iOS natives + BuildIosNatives: + name: Build natives for iOS + runs-on: macOS-14 + + steps: + - name: Check default JAVAs + run: echo $JAVA_HOME --- $JAVA_HOME_8_X64 --- $JAVA_HOME_11_X64 --- $JAVA_HOME_17_X64 --- $JAVA_HOME_21_X64 --- + + - name: Setup the java environment + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '11.0.26+4' + + - name: Setup the XCode version to 15.1.0 + uses: maxim-lobanov/setup-xcode@v1 + with: + xcode-version: '15.1.0' + - name: Clone the repo - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - - name: Build + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Build run: | - # Build - # Note: since this is crossbuild we use the buildForPlatforms filter to tell - # the buildscript wich platforms it should build for. - gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildForPlatforms=LinuxArm,LinuxArmHF,LinuxArm64 -PbuildNativeProjects=true \ - :jme3-bullet-native:assemble + ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ + :jme3-ios-native:build - name: Upload natives uses: actions/upload-artifact@master with: - name: linuxarm-natives - path: build/native + name: ios-natives + path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework # Build the natives on android BuildAndroidNatives: name: Build natives for android - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest container: - image: riccardoblb/buildenv-jme3:android - + image: ghcr.io/cirruslabs/android-sdk:36-ndk + steps: - name: Clone the repo - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - - name: Build - run: | - gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ - :jme3-android-native:assemble \ - :jme3-bullet-native-android:assemble - - - name: Upload natives - uses: actions/upload-artifact@master + - name: Setup Java 11 + uses: actions/setup-java@v4 with: - name: android-natives - path: build/native + distribution: temurin + java-version: '11' - # Build the natives - BuildNatives: - strategy: - fail-fast: true - matrix: - os: [ubuntu-18.04,windows-2019,macOS-latest] - jdk: [8.x.x] - include: - - os: ubuntu-18.04 - osName: linux - - os: windows-2019 - osName: windows - - os: macOS-latest - osName: mac - - name: Build natives for ${{ matrix.osName }} - runs-on: ${{ matrix.os }} - steps: - - - name: Clone the repo - uses: actions/checkout@v1 - with: - fetch-depth: 1 - - - name: Prepare java environment - uses: actions/setup-java@v1 - with: - java-version: ${{ matrix.jdk }} - architecture: x64 - - - name: Build Natives - shell: bash - env: - OS_NAME: ${{ matrix.osName }} + - name: Check java version + run: java -version + + - name: Install CMake run: | - # Install dependencies - if [ "$OS_NAME" = "mac" ]; - then - echo "Prepare mac" - - elif [ "$OS_NAME" = "linux" ]; - then - echo "Prepare linux" - sudo apt-get update - sudo apt-get install -y gcc-multilib g++-multilib - else - echo "Prepare windows" - fi - - # Build - gradle -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true -Dmaven.repo.local="$PWD/dist/maven" \ - build \ - :jme3-bullet-native:build - - # Upload natives to be used later by the BuildJMonkey job + apt-get update + apt-get install -y cmake + cmake --version + + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 + + - name: Build + run: | + export ANDROID_NDK="$ANDROID_SDK_ROOT/ndk/$ANDROID_NDK_VERSION" + ./gradlew -PuseCommitHashAsVersionName=true --no-daemon -PbuildNativeProjects=true \ + :jme3-android-native:assemble + - name: Upload natives uses: actions/upload-artifact@master with: - name: ${{ matrix.osName }}-natives + name: android-natives path: build/native - - # Build the engine, we only deploy from ubuntu-18.04 jdk8 - BuildJMonkey: - needs: [BuildNatives,BuildAndroidNatives] + # Build the engine, we only deploy from ubuntu-latest jdk21 + BuildJMonkey: + needs: [BuildAndroidNatives, BuildIosNatives] name: Build on ${{ matrix.osName }} jdk${{ matrix.jdk }} - runs-on: ${{ matrix.os }} + runs-on: ${{ matrix.os }} strategy: - fail-fast: true + fail-fast: false matrix: - os: [ubuntu-18.04,windows-2019,macOS-latest] - jdk: [8.x.x,11.x.x] + os: [ubuntu-latest,windows-latest,macOS-latest] + jdk: [11, 17, 21] include: - - os: ubuntu-18.04 + - os: ubuntu-latest osName: linux deploy: true - - os: windows-2019 + - os: windows-latest osName: windows + deploy: false - os: macOS-latest - osName: mac - - jdk: 11.x.x - deploy: false + osName: mac + deploy: false + - jdk: 11 + deploy: false + - jdk: 17 + deploy: false - steps: + steps: - name: Clone the repo - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - + - name: Setup the java environment - uses: actions/setup-java@v1 + uses: actions/setup-java@v4 with: + distribution: 'temurin' java-version: ${{ matrix.jdk }} - architecture: x64 - - - name: Download natives for linux - uses: actions/download-artifact@master - with: - name: linux-natives - path: build/native - - - name: Download natives for windows - uses: actions/download-artifact@master - with: - name: windows-natives - path: build/native - - - name: Download natives for mac - uses: actions/download-artifact@master - with: - name: mac-natives - path: build/native - name: Download natives for android uses: actions/download-artifact@master @@ -228,43 +221,56 @@ jobs: name: android-natives path: build/native - - name: Download natives for linux (arm) + - name: Download natives for iOS uses: actions/download-artifact@master with: - name: linuxarm-natives - path: build/native - + name: ios-natives + path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + + - name: Validate the Gradle wrapper + uses: gradle/actions/wrapper-validation@v3 - name: Build Engine shell: bash run: | - # Build - gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true build - + # Normal build plus ZIP distribution and merged javadoc + ./gradlew -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true \ + build createZipDistribution mergedJavadoc + if [ "${{ matrix.deploy }}" = "true" ]; - then + then # We are going to need "zip" sudo apt-get update sudo apt-get install -y zip - # Create the zip release and the javadoc - gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true mergedJavadoc createZipDistribution - # We prepare the release for deploy mkdir -p ./dist/release/ mv build/distributions/*.zip dist/release/ - - # Create the maven artifacts - mkdir -p ./dist/maven/ - gradle -PuseCommitHashAsVersionName=true -PskipPrebuildLibraries=true install -Dmaven.repo.local="$PWD/dist/maven" + + # Install maven artifacts to ./dist/maven and sign them if possible + if [ "${{ secrets.SIGNING_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable signing:" + echo "SIGNING_KEY, SIGNING_PASSWORD" + + ./gradlew publishMavenPublicationToDistRepository \ + -PskipPrebuildLibraries=true -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + else + ./gradlew publishMavenPublicationToDistRepository \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PskipPrebuildLibraries=true -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + fi # Zip the natives into a single archive (we are going to use this to deploy native snapshots) echo "Create native zip" cdir="$PWD" cd "build/native" - zip -r "$cdir/dist/jme3-natives.zip" * + zip -r "$cdir/dist/jme3-natives.zip" * cd "$cdir" echo "Done" - fi + fi # Used later by DeploySnapshot - name: Upload merged natives @@ -273,38 +279,38 @@ jobs: with: name: natives path: dist/jme3-natives.zip - + # Upload maven artifacts to be used later by the deploy job - name: Upload maven artifacts if: matrix.deploy==true uses: actions/upload-artifact@master with: name: maven - path: dist/maven + path: dist/maven - name: Upload javadoc if: matrix.deploy==true uses: actions/upload-artifact@master with: name: javadoc - path: dist/javadoc - - # Upload release archive to be used later by the deploy job + path: dist/javadoc + + # Upload release archive to be used later by the deploy job - name: Upload release if: github.event_name == 'release' && matrix.deploy==true uses: actions/upload-artifact@master with: name: release - path: dist/release + path: dist/release # This job deploys the native snapshot. # 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: @@ -316,7 +322,7 @@ jobs: then git clone --single-branch --branch "$branch" https://github.com/${GITHUB_REPOSITORY}.git . fi - + - name: Download merged natives uses: actions/download-artifact@master with: @@ -325,7 +331,7 @@ jobs: - name: Deploy natives snapshot run: | - source .github/actions/tools/bintray.sh + source .github/actions/tools/minio.sh NATIVE_CHANGES="yes" branch="${GITHUB_REF//refs\/heads\//}" if [ "$branch" != "" ]; @@ -334,7 +340,7 @@ jobs: then nativeSnapshot=`cat "natives-snapshot.properties"` nativeSnapshot="${nativeSnapshot#*=}" - + # We deploy ONLY if GITHUB_SHA (the current commit hash) is newer than $nativeSnapshot if [ "`git rev-list --count $nativeSnapshot..$GITHUB_SHA`" = "0" ]; then @@ -342,10 +348,7 @@ jobs: else # We check if the native code changed. echo "Detect changes" - NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet-native/)" - if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-android-native/)"; fi - if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet-native-android/)"; fi - if [ "$NATIVE_CHANGES" = "" ];then NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-bullet/)"; fi + NATIVE_CHANGES="$(git diff-tree --name-only "$GITHUB_SHA" "$nativeSnapshot" -- jme3-android-native/)" fi fi @@ -354,31 +357,28 @@ jobs: then echo "No changes, skip." else - if [ "${{ secrets.BINTRAY_GENERIC_REPO }}" = "" ]; - then - echo "Configure the following secrets to enable native snapshot deployment" - echo "BINTRAY_GENERIC_REPO, BINTRAY_USER, BINTRAY_APIKEY" + if [ "${{ secrets.OBJECTS_KEY }}" = "" ]; + then + echo "Configure the OBJECTS_KEY secret to enable natives snapshot deployment to MinIO" else - # Deploy snapshot - bintray_uploadFile dist/jme3-natives.zip \ - $GITHUB_SHA/$GITHUB_SHA/jme3-natives.zip \ - ${{ secrets.BINTRAY_GENERIC_REPO }} "content" "natives" \ - ${{ secrets.BINTRAY_USER }} \ - ${{ secrets.BINTRAY_APIKEY }} \ - "https://github.com/${GITHUB_REPOSITORY}" \ - "${{ secrets.BINTRAY_LICENSE }}" "true" - - # We reference the snapshot by writing its commit hash in natives-snapshot.properties + # Deploy natives snapshot to a MinIO instance using function in minio.sh + minio_uploadFile dist/jme3-natives.zip \ + native-snapshots/$GITHUB_SHA/jme3-natives.zip \ + https://objects.jmonkeyengine.org \ + jmonkeyengine \ + ${{ secrets.OBJECTS_KEY }} + + # We reference the snapshot by writing its commit hash in natives-snapshot.properties echo "natives.snapshot=$GITHUB_SHA" > natives-snapshot.properties - + # We commit the updated natives-snapshot.properties git config --global user.name "Github Actions" git config --global user.email "actions@users.noreply.github.com" - + git add natives-snapshot.properties - + git commit -m "[skip ci] update natives snapshot" - + # Pull rebase from the remote repo, just in case there was a push in the meantime git pull -q --rebase @@ -387,39 +387,127 @@ jobs: # Push (git -c http.extraheader="AUTHORIZATION: basic $header" push origin "$branch" || true) - + fi 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@v4 + with: + fetch-depth: 1 + + # Setup jdk 21 used for building Maven-style artifacts + - name: Setup the java environment + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + + - name: Download natives for android + uses: actions/download-artifact@master + with: + name: android-natives + path: build/native + + - name: Download natives for iOS + uses: actions/download-artifact@master + with: + name: ios-natives + path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + + - name: Rebuild the maven artifacts and upload them to Sonatype's maven-snapshots repo + run: | + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + else + ./gradlew publishMavenPublicationToSNAPSHOTRepository \ + -PcentralPassword=${{ secrets.CENTRAL_PASSWORD }} \ + -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + fi + + # This job deploys the release - DeployRelease: + 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 ... + steps: + + # We need to clone everything again for uploadToCentral.sh ... - name: Clone the repo - uses: actions/checkout@v1 + uses: actions/checkout@v4 with: fetch-depth: 1 - + + # Setup jdk 21 used for building Sonatype artifacts + - name: Setup the java environment + uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + # Download all the stuff... - name: Download maven artifacts uses: actions/download-artifact@master with: name: maven path: dist/maven - + - name: Download release uses: actions/download-artifact@master with: name: release - path: dist/release - - - name: Deploy to github releases + path: dist/release + + - name: Download natives for android + uses: actions/download-artifact@master + with: + name: android-natives + path: build/native + + - name: Download natives for iOS + uses: actions/download-artifact@master + with: + name: ios-natives + path: jme3-ios-native/template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + + - name: Rebuild the maven artifacts and upload them to Sonatype's Central Publisher Portal + run: | + if [ "${{ secrets.CENTRAL_PASSWORD }}" = "" ]; + then + echo "Configure the following secrets to enable uploading to Sonatype:" + echo "CENTRAL_PASSWORD, CENTRAL_USERNAME, SIGNING_KEY, SIGNING_PASSWORD" + else + ./gradlew publishMavenPublicationToCentralRepository \ + -PcentralPassword=${{ secrets.CENTRAL_PASSWORD }} \ + -PcentralUsername=${{ secrets.CENTRAL_USERNAME }} \ + -PsigningKey='${{ secrets.SIGNING_KEY }}' \ + -PsigningPassword='${{ secrets.SIGNING_PASSWORD }}' \ + -PuseCommitHashAsVersionName=true \ + --console=plain --stacktrace + .github/actions/tools/uploadToCentral.sh \ + -p '${{ secrets.CENTRAL_PASSWORD }}' \ + -u '${{ secrets.CENTRAL_USERNAME }}' + fi + + - name: Deploy to GitHub Releases run: | # We need to get the release id (yeah, it's not the same as the tag) echo "${GITHUB_EVENT_PATH}" @@ -436,33 +524,22 @@ jobs: -H "Content-Type: application/zip" \ --data-binary @"$filename" \ "$url" - - - name: Deploy to bintray + + - name: Deploy to github package registry run: | source .github/actions/tools/uploadToMaven.sh - if [ "${{ secrets.BINTRAY_MAVEN_REPO }}" = "" ]; - then - echo "Configure the following secrets to enable bintray deployment" - echo "BINTRAY_MAVEN_REPO, BINTRAY_USER, BINTRAY_APIKEY" - else - uploadAllToMaven dist/maven/ https://api.bintray.com/maven/${{ secrets.BINTRAY_MAVEN_REPO }} ${{ secrets.BINTRAY_USER }} ${{ secrets.BINTRAY_APIKEY }} "https://github.com/${GITHUB_REPOSITORY}" "${{ secrets.BINTRAY_LICENSE }}" - fi - - # - name: Deploy to github package registry - # run: | - # source .github/actions/tools/uploadToMaven.sh - # registry="https://maven.pkg.github.com/$GITHUB_REPOSITORY" - # echo "Deploy to github package registry $registry" - # uploadAllToMaven dist/maven/ $registry "token" ${{ secrets.GITHUB_TOKEN }} - + registry="https://maven.pkg.github.com/$GITHUB_REPOSITORY" + echo "Deploy to github package registry $registry" + uploadAllToMaven dist/maven/ $registry "token" ${{ secrets.GITHUB_TOKEN }} + # Deploy the javadoc - DeployJavaDoc: + DeployJavaDoc: needs: [BuildJMonkey] name: Deploy Javadoc - runs-on: ubuntu-18.04 + runs-on: ubuntu-latest if: github.event_name == 'release' - steps: - + steps: + # We are going to need a deploy key for this, since we need # to push to a different repo - name: Set ssh key @@ -477,16 +554,16 @@ jobs: branch="gh-pages" export GIT_SSH_COMMAND="ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -i $HOME/.ssh/deploy.key" git clone --single-branch --branch "$branch" git@github.com:${{ secrets.JAVADOC_GHPAGES_REPO }} . - + # Download the javadoc in the new directory "newdoc" - name: Download javadoc uses: actions/download-artifact@master with: name: javadoc path: newdoc - + # The actual deploy - - name: Deploy to github pages + - name: Deploy to github pages run: | set -f IFS=$'\n' @@ -508,10 +585,10 @@ jobs: # if there isn't an index.txt we create one (we need this to list the versions) if [ ! -f "index.txt" ]; then echo "" > index.txt ; fi index="`cat index.txt`" - + # Check if this version is already in index.txt addNew=true - for v in $index; + for v in $index; do if [ "$v" = "$version" ]; then @@ -538,11 +615,11 @@ jobs: # Commit the changes git config --global user.name "Github Actions" git config --global user.email "actions@users.noreply.github.com" - - git add . - git commit -m "$version" - branch="gh-pages" - git push origin "$branch" --force + git add . || true + git commit -m "$version" || true + + branch="gh-pages" + git push origin "$branch" --force || true fi diff --git a/.github/workflows/screenshot-test-comment.yml b/.github/workflows/screenshot-test-comment.yml new file mode 100644 index 0000000000..5b4ae992e9 --- /dev/null +++ b/.github/workflows/screenshot-test-comment.yml @@ -0,0 +1,119 @@ +name: Screenshot Test PR Comment + +# This workflow is designed to safely comment on PRs from forks +# It uses pull_request_target which has higher permissions than pull_request +# Security note: This workflow does NOT check out or execute code from the PR +# It only monitors the status of the ScreenshotTests job and posts comments +# (If this commenting was done in the main worflow it would not have the permissions +# to create a comment) + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +jobs: + monitor-screenshot-tests: + name: Monitor Screenshot Tests and Comment + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + pull-requests: write + contents: read + steps: + - name: Wait for GitHub to register the workflow run + run: sleep 120 + + - name: Wait for Screenshot Tests to complete + uses: lewagon/wait-on-check-action@v1.3.1 + with: + ref: ${{ github.event.pull_request.head.sha }} + check-name: 'Run Screenshot Tests' + repo-token: ${{ secrets.GITHUB_TOKEN }} + wait-interval: 10 + allowed-conclusions: success,skipped,failure + - name: Check Screenshot Tests status + id: check-status + uses: actions/github-script@v6 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const { owner, repo } = context.repo; + const ref = '${{ github.event.pull_request.head.sha }}'; + + // Get workflow runs for the PR + const runs = await github.rest.actions.listWorkflowRunsForRepo({ + owner, + repo, + head_sha: ref + }); + + // Find the ScreenshotTests job + let screenshotTestRun = null; + for (const run of runs.data.workflow_runs) { + if (run.name === 'Build jMonkeyEngine') { + const jobs = await github.rest.actions.listJobsForWorkflowRun({ + owner, + repo, + run_id: run.id + }); + + for (const job of jobs.data.jobs) { + if (job.name === 'Run Screenshot Tests') { + screenshotTestRun = job; + break; + } + } + + if (screenshotTestRun) break; + } + } + + if (!screenshotTestRun) { + console.log('Screenshot test job not found'); + return; + } + + // Check if the job failed + if (screenshotTestRun.conclusion === 'failure') { + core.setOutput('failed', 'true'); + } else { + core.setOutput('failed', 'false'); + } + - name: Find Existing Comment + uses: peter-evans/find-comment@v3 + id: existingCommentId + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: Screenshot tests have failed. + + - name: Comment on PR if tests fail + if: steps.check-status.outputs.failed == 'true' + uses: peter-evans/create-or-update-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + body: | + 🖼️ **Screenshot tests have failed.** + + The purpose of these tests is to ensure that changes introduced in this PR don't break visual features. They are visual unit tests. + + 📄 **Where to find the report:** + - Go to the (failed run) > Summary > Artifacts > screenshot-test-report + - Download the zip and open jme3-screenshot-tests/build/reports/ScreenshotDiffReport.html + + ⚠️ **If you didn't expect to change anything visual:** + Fix your changes so the screenshot tests pass. + + ✅ **If you did mean to change things:** + Review the replacement images in jme3-screenshot-tests/build/changed-images to make sure they really are improvements and then replace and commit the replacement images at jme3-screenshot-tests/src/test/resources. + + ✨ **If you are creating entirely new tests:** + Find the new images in jme3-screenshot-tests/build/changed-images and commit the new images at jme3-screenshot-tests/src/test/resources. + + **Note;** it is very important that the committed reference images are created on the build pipeline, locally created images are not reliable. Similarly tests will fail locally but you can look at the report to check they are "visually similar". + + See https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/jme3-screenshot-tests/README.md for more information + + Contact @richardTingle (aka richtea) for guidance if required + edit-mode: replace + comment-id: ${{ steps.existingCommentId.outputs.comment-id }} diff --git a/.gitignore b/.gitignore index 1e5bf3f071..121fd33ac3 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ **/.classpath **/.settings **/.project -**/.vscode +**/.vscode/** **/out/ /.gradle/ /.nb-gradle/ @@ -14,6 +14,7 @@ /.classpath /.project /.settings +/local.properties *.dll *.so *.jnilib @@ -45,4 +46,8 @@ .travis.yml appveyor.yml javadoc_deploy -javadoc_deploy.pub \ No newline at end of file +javadoc_deploy.pub +!.vscode/settings.json +!.vscode/JME_style.xml +!.vscode/extensions.json +joysticks-*.txt \ No newline at end of file diff --git a/.vscode/JME_style.xml b/.vscode/JME_style.xml new file mode 100644 index 0000000000..b4014b077e --- /dev/null +++ b/.vscode/JME_style.xml @@ -0,0 +1,310 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..a1538208b3 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,6 @@ +{ + "recommendations": [ + "vscjava.vscode-java-pack", + "slevesque.shader" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000000..89d691dd52 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,15 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic", + "java.compile.nullAnalysis.mode": "automatic", + "java.refactor.renameFromFileExplorer": "prompt", + "java.format.settings.url": "./.vscode/JME_style.xml", + "editor.formatOnPaste": true, + "editor.formatOnType": false, + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "modifications" , + + "prettier.tabWidth": 4, + "prettier.printWidth": 110, + "prettier.enable": true, + "prettier.resolveGlobalModules": true +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 47fd3f7002..c1cf2bbb68 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,14 +13,92 @@ Check out the [Projects](https://github.com/jMonkeyEngine/jmonkeyengine/projects When you're ready to submit your code, just make a [pull request](https://help.github.com/articles/using-pull-requests). -- Do not commit your code until you have received proper feedback. - In your commit log message, please refer back to the originating forum thread (example) for a ‘full circle’ reference. Also please [reference related issues](https://help.github.com/articles/closing-issues-via-commit-messages) by typing the issue hashtag. - When committing, always be sure to run an update before you commit. If there is a conflict between the latest revision and your patch after the update, then it is your responsibility to track down the update that caused the conflict and determine the issue (and fix it). In the case where the breaking commit has no thread linked (and one cannot be found in the forum), then the contributor should contact an administrator and wait for feedback before committing. - If your code is committed and it introduces new functionality, please edit the wiki accordingly. We can easily roll back to previous revisions, so just do your best; point us to it and we’ll see if it sticks! p.s. We will try hold ourselves to a [certain standard](http://www.defmacro.org/2013/04/03/issue-etiquette.html) when it comes to GitHub etiquette. If at any point we fail to uphold this standard, let us know. -#### Core Contributors +There are many ways +to submit a pull request (PR) to the "jmonkeyengine" project repository, +depending on your knowledge of Git and which tools you prefer. + +
+ + Click to view step-by-step instructions for a reusable setup + using a web browser and a command-line tool such as Bash. + + +The setup described here allows you to reuse the same local repo for many PRs. + +#### Prerequisites + +These steps need only be done once... + +1. You'll need a personal account on https://github.com/ . + The "Sign up" and "Sign in" buttons are in the upper-right corner. +2. Create a GitHub access token, if you don't already have one: + + Browse to https://github.com/settings/tokens + + Click on the "Generate new token" button in the upper right. + + Follow the instructions. + + When specifying the scope of the token, check the box labeled "repo". + + Copy the generated token to a secure location from which you can + easily paste it into your command-line tool. +3. Create your personal fork of the "jmonkeyengine" repository at GitHub, + if you don't already have one: + + Browse to https://github.com/jMonkeyEngine/jmonkeyengine + + Click on the "Fork" button (upper right) + + Follow the instructions. + + If offered a choice of locations, choose your personal account. +4. Clone the fork to your development system: + + `git clone https://github.com/` ***yourGitHubUserName*** `/jmonkeyengine.git` + + As of 2021, this step consumes about 1.3 GBytes of filesystem storage. +5. Create a local branch for tracking the project repository: + + `cd jmonkeyengine` + + `git remote add project https://github.com/jMonkeyEngine/jmonkeyengine.git` + + `git fetch project` + + `git checkout -b project-master project/master` + +#### PR process + +1. Create a temporary, up-to-date, local branch for your PR changes: + + `git checkout project-master` + + `git pull` + + `git checkout -b tmpBranch` (replace "tmpBranch" with a descriptive name) +2. Make your changes in the working tree. +3. Test your changes. + Testing should, at a minimum, include building the Engine from scratch: + + `./gradlew clean build` +4. Add and commit your changes to your temporary local branch. +5. Push the PR commits to your fork at GitHub: + + `git push --set-upstream origin ` ***tmpBranchName*** + + Type your GitHub user name at the "Username" prompt. + + Paste your access token (from prerequisite step 2) at the "Password" prompt. +6. Initiate the pull request: + + Browse to [https://github.com/ ***yourGitHubUserName*** /jmonkeyengine]() + + Click on the "Compare & pull request" button at the top. + + The "base repository:" should be "jMonkeyEngine/jmonkeyengine". + + The "base:" should be "master". + + The "head repository:" should be your personal fork at GitHub. + + The "compare:" should be the name of your temporary branch. +7. Fill in the textboxes for the PR name and PR description, and + click on the "Create pull request" button. + +To amend an existing PR: + + `git checkout tmpBranch` + + Repeat steps 2 through 5. + +To submit another PR using the existing local repository, +repeat the PR process using a new temporary branch with a different name. + +If you have an integrated development environment (IDE), +it may provide an interface to Git that's more intuitive than a command line. +
+ +Generic instructions for creating GitHub pull requests can be found at +https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request + +### Core Contributors Developers in the Contributors team can push directly to Main instead of submitting pull requests, however for new features it is often a good idea to do a pull request as a means to get a last code review. @@ -33,10 +111,44 @@ Developers in the Contributors team can push directly to Main instead of submitt - In general, library changes that plausibly might break existing apps appear only in major releases, not minor ones. -## Building the engine +## How to build the Engine from source -1. Install [Gradle](http://www.gradle.org/) -2. Navigate to the project directory and run 'gradle build' from command line to build the engine. +### Prerequisites + +These steps need only be done once... + +1. Install a Java Development Kit (JDK), if you don't already have one. +2. Set the JAVA_HOME environment variable: + + using Bash: `export JAVA_HOME="` *path to your JDK* `"` + + using PowerShell: `$env:JAVA_HOME = '` *path to your JDK* `'` + + using Windows Command Prompt: `set JAVA_HOME="` *path to your JDK* `"` + + Tip: The path names a directory containing "bin" and "lib" subdirectories. + On Linux it might be something like "/usr/lib/jvm/java-17-openjdk-amd64" + + Tip: You may be able to skip this step + if the JDK binaries are in your system path. +3. Clone the project repository from GitHub: + + `git clone https://github.com/jmonkeyengine/jmonkeyengine.git` + + `cd jmonkeyengine` + + As of 2021, this step consumes about 1.3 GBytes of filesystem storage. + +### Build command + +Run the Gradle wrapper: ++ using Bash or PowerShell: `./gradlew build` ++ using Windows Command Prompt: `.\gradlew build` + +After a successful build, +snapshot jars will be found in the "*/build/libs" subfolders. + +### Related Gradle tasks + +You can install the Maven artifacts to your local repository: + + using Bash or PowerShell: `./gradlew install` + + using Windows Command Prompt: `.\gradlew install` + +You can restore the project to a pristine state: + + using Bash or PowerShell: `./gradlew clean` + + using Windows Command Prompt: `.\gradlew clean` ## Best Practices @@ -49,9 +161,29 @@ Developers in the Contributors team can push directly to Main instead of submitt general testing tips? WIP +### Coding Style + ++ Our preferred style for Java source code is + [Google style](https://google.github.io/styleguide/javaguide.html) with the following 8 modifications: + 1. No blank line before a `package` statement. (Section 3) + 2. Logical ordering of class contents is encouraged but not required. (Section 3.4.2) + 3. Block indentation of +4 spaces instead of +2. (Section 4.2) + 4. Column limit of 110 instead of 100. (Section 4.4) + 5. Continuation line indentation of +8 spaces instead of +4. (Section 4.5.2) + 6. Commented-out code need not be indented at the same level as surrounding code. (Section 4.8.6.1) + 7. The names of test classes need not end in "Test". (Section 5.2.2) + 8. No trailing whitespace. ++ Any pull request that adds new Java source files shall apply our preferred style to those files. ++ Any pull request that has style improvement as its primary purpose + shall apply our preferred style, or specific aspect(s) thereof, to every file it modifies. ++ Any pull request that modifies a pre-existing source file AND + doesn't have style improvement as it's primary purpose shall either: + 1. conform to the prevailing style of that file OR + 2. apply our preferred style, but only to the portions modified for the PR's primary purpose. + ### Code Quality -We generally abide by the standard Java Code Conventions. Besides that, just make an effort to write elegant code: +Make an effort to write elegant code: 1. Handles errors gracefully 2. Only reinvents the wheel when there is a measurable benefit in doing so. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000000..bb12c971e2 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,30 @@ +Copyright (c) 2009-2025 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 f7f679677e..923eeb6480 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,58 @@ -jMonkeyEngine +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. 3.2.4 is the latest stable version of the jMonkeyEngine 3 SDK, a complete game development suite. We'll release 3.2.x updates until the major 3.3 release arrives. +jMonkeyEngine is a 3-D game engine for adventurous Java developers. It’s open-source, cross-platform, and cutting-edge. +v3.8.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) + - [jME powered games on IndieDB](https://www.indiedb.com/engines/jmonkeyengine/games) - [Boardtastic 2](https://boardtastic-2.fileplanet.com/apk) - [Attack of the Gelatinous Blob](https://attack-gelatinous-blob.softwareandgames.com/) - - [Mythruna](http://mythruna.com/) + - [Mythruna](https://mythruna.com/) - [PirateHell (on Steam)](https://store.steampowered.com/app/321080/Pirate_Hell/) - - [3089 (on Steam)](http://store.steampowered.com/app/263360/) - - [3079 (on Steam)](http://store.steampowered.com/app/259620/) + - [3089 (on Steam)](https://store.steampowered.com/app/263360/3089__Futuristic_Action_RPG/) + - [3079 (on Steam)](https://store.steampowered.com/app/259620/3079__Block_Action_RPG/) - [Lightspeed Frontier (on Steam)](https://store.steampowered.com/app/548650/Lightspeed_Frontier/) - [Skullstone](http://www.skullstonegame.com/) - [Spoxel (on Steam)](https://store.steampowered.com/app/746880/Spoxel/) - - [Nine Circles of Hell](https://store.steampowered.com/app/1200600/Nine_Circles_of_Hell/) - -## Getting started + - [Nine Circles of Hell (on Steam)](https://store.steampowered.com/app/1200600/Nine_Circles_of_Hell/) + - [Leap](https://gamejolt.com/games/leap/313308) + - [Jumping Jack Flag](http://timealias.bplaced.net/jack/) + - [PapaSpace Flight Simulation](https://www.papaspace.at/) + - [Cubic Nightmare (on Itch)](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) + - [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) + - [Depthris (on Itch)](https://codewalker.itch.io/depthris) + - [Stranded (on Itch)](https://tgiant.itch.io/stranded) + - [The Afflicted Forests (Coming Soon to Steam)](https://www.indiedb.com/games/the-afflicted-forests) + - [Star Colony: Beyond Horizons (on Google Play)](https://play.google.com/store/apps/details?id=game.colony.ColonyBuilder) + - [High Impact (on Steam)](https://store.steampowered.com/app/3059050/High_Impact/) + +## Getting Started Go to https://github.com/jMonkeyEngine/sdk/releases to download the jMonkeyEngine SDK. -[Read the wiki](https://jmonkeyengine.github.io/wiki) for a complete install guide. Power up with some SDK Plugins and AssetPacks and you are off to the races. At this point you're gonna want to [join the forum](http://hub.jmonkeyengine.org/) so our tribe can grow stronger. +Read [the wiki](https://jmonkeyengine.github.io/wiki) for the installation guide and tutorials. +Join [the discussion forum](https://hub.jmonkeyengine.org/) to participate in our community, +get your questions answered, and share your projects. -Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION, it will break constantly during development of the stable jME versions! +Note: The master branch on GitHub is a development version of the engine and is NOT MEANT TO BE USED IN PRODUCTION. ### Technology Stack - - Java - - NetBeans Platform - - Gradle + - windowed, multi-platform IDE derived from NetBeans + - libraries for GUI, networking, physics, SFX, terrain, importing assets, etc. + - platform-neutral core library for scene graph, animation, rendering, math, etc. + - LWJGL v2/v3 (to access GLFW, OpenAL, OpenGL, and OpenVR) or Android or iOS + - Java Virtual Machine (v8 or higher) -Plus a bunch of awesome libraries & tight integrations like Bullet, Blender, NiftyGUI and other goodies. - ### Documentation Did you miss it? Don't sweat it, [here it is again](https://jmonkeyengine.github.io/wiki). @@ -47,5 +63,49 @@ Read our [contribution guide](https://github.com/jMonkeyEngine/jmonkeyengine/blo ### License -New BSD (3-clause) License. In other words, you do whatever makes you happy! - +[New BSD (3-clause) License](https://github.com/jMonkeyEngine/jmonkeyengine/blob/master/LICENSE.md) + +### How to Build the Engine from Source + +1. Install a Java Development Kit (JDK), + if you don't already have one. +2. Point the `JAVA_HOME` environment variable to your JDK installation: + (In other words, set it to the path of a directory/folder + containing a "bin" that contains a Java executable. + That path might look something like + "C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot" + or "/usr/lib/jvm/java-17-openjdk-amd64/" or + "/Library/Java/JavaVirtualMachines/zulu-17.jdk/Contents/Home" .) + + using Bash or Zsh: `export JAVA_HOME="` *path to installation* `"` + + using Fish: `set -g JAVA_HOME "` *path to installation* `"` + + using Windows Command Prompt: `set JAVA_HOME="` *path to installation* `"` + + using PowerShell: `$env:JAVA_HOME = '` *path to installation* `'` +3. Download and extract the engine source code from GitHub: + + using Git: + + `git clone https://github.com/jMonkeyEngine/jmonkeyengine.git` + + `cd jmonkeyengine` + + `git checkout -b latest v3.7.0-stable` (unless you plan to do development) + + using a web browser: + + browse to [the latest release](https://github.com/jMonkeyEngine/jmonkeyengine/releases/latest) + + follow the "Source code (zip)" link at the bottom of the page + + save the ZIP file + + extract the contents of the saved ZIP file + + `cd` to the extracted directory/folder +4. Run the Gradle wrapper: + + using Bash or Fish or PowerShell or Zsh: `./gradlew build` + + using Windows Command Prompt: `.\gradlew build` + +After a successful build, +fresh JARs will be found in "*/build/libs". + +You can install the JARs to your local Maven repository: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew install` ++ using Windows Command Prompt: `.\gradlew install` + +You can run the "jme3-examples" app: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew run` ++ using Windows Command Prompt: `.\gradlew run` + +You can restore the project to a pristine state: ++ using Bash or Fish or PowerShell or Zsh: `./gradlew clean` ++ using Windows Command Prompt: `.\gradlew clean` diff --git a/bintray.gradle b/bintray.gradle deleted file mode 100644 index 0deafc8cf6..0000000000 --- a/bintray.gradle +++ /dev/null @@ -1,29 +0,0 @@ -// -// This file is to be applied to some subproject. -// - -apply plugin: 'com.jfrog.bintray' - -bintray { - user = bintray_user - key = bintray_api_key - configurations = ['archives'] - dryRun = false - pkg { - repo = 'org.jmonkeyengine' - userOrg = 'jmonkeyengine' - name = project.name - desc = POM_DESCRIPTION - websiteUrl = POM_URL - licenses = ['BSD New'] - vcsUrl = POM_SCM_URL - labels = ['jmonkeyengine'] - } -} - -bintrayUpload.dependsOn(writeFullPom) - -bintrayUpload.onlyIf { - (bintray_api_key.length() > 0) && - !(version ==~ /.*SNAPSHOT/) -} diff --git a/build.gradle b/build.gradle index db97fb4756..d4bd67720d 100644 --- a/build.gradle +++ b/build.gradle @@ -3,35 +3,62 @@ import java.nio.file.StandardCopyOption; buildscript { repositories { + mavenCentral() google() - jcenter() + maven { + url "https://plugins.gradle.org/m2/" + } } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4' + classpath libs.android.build.gradle + classpath libs.gradle.retrolambda + classpath libs.spotbugs.gradle.plugin } } allprojects { repositories { + mavenCentral() google() - jcenter() + } + tasks.withType(Jar) { + duplicatesStrategy = 'include' } } +// Set the license for IDEs that understand this +ext.license = file("$rootDir/source-file-header-template.txt") + apply plugin: 'base' +apply plugin: 'com.github.spotbugs' apply from: file('version.gradle') +apply plugin: 'me.tatarka.retrolambda' + // This is applied to all sub projects subprojects { if(!project.name.equals('jme3-android-examples')) { apply from: rootProject.file('common.gradle') - if (!project.name.equals('jme3-testdata')) { - apply from: rootProject.file('bintray.gradle') - } } else { apply from: rootProject.file('common-android-app.gradle') } + + if (!project.name.endsWith("-native") && enableSpotBugs != "false" ) { + apply plugin: 'com.github.spotbugs' + + // Currently we only warn about issues and try to fix them as we go, but those aren't mission critical. + spotbugs { + ignoreFailures = true + toolVersion = '4.8.6' + } + + tasks.withType(com.github.spotbugs.snom.SpotBugsTask ) { + reports { + html.enabled = !project.hasProperty("xml-reports") + xml.enabled = project.hasProperty("xml-reports") + } + } + } } task run(dependsOn: ':jme3-examples:run') { @@ -46,25 +73,27 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e File sourceFolder = mkdir("$buildDir/libDist/sources") File javadocFolder = mkdir("$buildDir/libDist/javadoc") subprojects.each {project -> - if(project.ext.mainClass == ''){ + if(!project.hasProperty('mainClassName')){ project.tasks.withType(Jar).each {archiveTask -> - if(archiveTask.classifier == "sources"){ + String classifier = archiveTask.archiveClassifier.get() + String ext = archiveTask.archiveExtension.get() + if (classifier == "sources") { copy { from archiveTask.archivePath into sourceFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + classifier + '.' + ext} } - } else if(archiveTask.classifier == "javadoc"){ + } else if (classifier == "javadoc") { copy { from archiveTask.archivePath into javadocFolder - rename {project.name + '-' + archiveTask.classifier +'.'+ archiveTask.extension} + rename {project.name + '-' + classifier + '.' + ext} } } else{ copy { from archiveTask.archivePath into libFolder - rename {project.name + '.' + archiveTask.extension} + rename {project.name + '.' + ext} } } } @@ -74,7 +103,9 @@ task libDist(dependsOn: subprojects.build, description: 'Builds and copies the e } task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"Package the nightly zip distribution"){ - archiveName "jME" + jmeFullVersion + ".zip" + archiveFileName = provider { + "jME" + jmeFullVersion + ".zip" + } into("/") { from {"./dist"} } @@ -86,7 +117,7 @@ task createZipDistribution(type:Zip,dependsOn:["dist","libDist"], description:"P task copyLibs(type: Copy){ // description 'Copies the engine dependencies to build/libDist' from { - subprojects*.configurations*.compile*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() + subprojects*.configurations*.implementation*.copyRecursive({ !(it instanceof ProjectDependency); })*.resolve() } into "$buildDir/libDist/lib-ext" //buildDir.path + '/' + libsDirName + '/lib' @@ -96,6 +127,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") @@ -108,18 +155,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*.allJava - } - classpath = files(subprojects.collect {project -> - project.sourceSets*.compileClasspath}) - // source { - // subprojects*.sourceSets*.main*.allSource - // } - 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') @@ -158,29 +195,29 @@ task configureAndroidNDK { gradle.rootProject.ext.set("usePrebuildNatives", buildNativeProjects!="true"); -if(skipPrebuildLibraries!="true"&&buildNativeProjects!="true"){ +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { String rootPath = rootProject.projectDir.absolutePath - Properties nativesSnasphotProp = new Properties() - File nativesSnasphotPropF=new File("${rootPath}/natives-snapshot.properties"); - - if(nativesSnasphotPropF.exists()){ + Properties nativesSnapshotProp = new Properties() + File nativesSnapshotPropF = new File("${rootPath}/natives-snapshot.properties"); - nativesSnasphotPropF.withInputStream { nativesSnasphotProp.load(it) } + if (nativesSnapshotPropF.exists()) { - String nativesSnasphot=nativesSnasphotProp.getProperty("natives.snapshot"); - String nativesUrl=PREBUILD_NATIVES_URL.replace('${natives.snapshot}',nativesSnasphot) - println "Use natives snapshot: "+nativesUrl + nativesSnapshotPropF.withInputStream { nativesSnapshotProp.load(it) } - String nativesZipFile="${rootPath}" + File.separator + "build"+ File.separator +nativesSnasphot+"-natives.zip" - String nativesPath="${rootPath}" + File.separator + "build"+ File.separator +"native" + String nativesSnapshot = nativesSnapshotProp.getProperty("natives.snapshot"); + String nativesUrl = PREBUILD_NATIVES_URL.replace('${natives.snapshot}', nativesSnapshot) + println "Use natives snapshot: " + nativesUrl + + String nativesZipFile = "${rootPath}" + File.separator + "build" + File.separator + nativesSnapshot + "-natives.zip" + String nativesPath = "${rootPath}" + File.separator + "build" + File.separator + "native" task getNativesZipFile { outputs.file nativesZipFile doFirst { File target = file(nativesZipFile); - println("Download natives from "+nativesUrl+" to "+nativesZipFile); + println("Download natives from " + nativesUrl + " to " + nativesZipFile); target.getParentFile().mkdirs(); ant.get(src: nativesUrl, dest: target); } @@ -192,60 +229,27 @@ if(skipPrebuildLibraries!="true"&&buildNativeProjects!="true"){ dependsOn getNativesZipFile doFirst { - for(File src : zipTree(nativesZipFile)){ - String srcRel=src.getAbsolutePath().substring((int)(nativesZipFile.length()+1)); - srcRel=srcRel.substring(srcRel.indexOf( File.separator)+1); + for (File src : zipTree(nativesZipFile)) { + String srcRel = src.getAbsolutePath().substring((int) (nativesZipFile.length() + 1)); + srcRel = srcRel.substring(srcRel.indexOf(File.separator) + 1); - File dest=new File(nativesPath+File.separator+srcRel); + File dest = new File(nativesPath + File.separator + srcRel); boolean doCopy = !(dest.exists() && dest.lastModified() > src.lastModified()) if (doCopy) { - println("Copy "+src+" "+dest); + println("Copy " + src + " " + dest); dest.getParentFile().mkdirs(); Files.copy(src.toPath(), dest.toPath(), StandardCopyOption.REPLACE_EXISTING); } } } } - build.dependsOn extractPrebuiltNatives + + assemble.dependsOn extractPrebuiltNatives } } - - - - -//class IncrementalReverseTask extends DefaultTask { -// @InputDirectory -// def File inputDir -// -// @OutputDirectory -// def File outputDir -// -// @Input -// def inputProperty -// -// @TaskAction -// void execute(IncrementalTaskInputs inputs) { -// println inputs.incremental ? "CHANGED inputs considered out of date" : "ALL inputs considered out of date" -// inputs.outOfDate { change -> -// println "out of date: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.text = change.file.text.reverse() -// } -// -// inputs.removed { change -> -// println "removed: ${change.file.name}" -// def targetFile = new File(outputDir, change.file.name) -// targetFile.delete() -// } -// } -//} - -//allprojects { -// tasks.withType(JavaExec) { -// enableAssertions = true // false by default -// } -// tasks.withType(Test) { -// enableAssertions = true // true by default -// } -//} +retrolambda { + javaVersion JavaVersion.VERSION_1_7 + incremental true + jvmArgs '-noverify' +} diff --git a/common.gradle b/common.gradle index 21ffc4d015..042d88e3b5 100644 --- a/common.gradle +++ b/common.gradle @@ -2,21 +2,35 @@ // This file is to be applied to every subproject. // -apply plugin: 'java' +apply plugin: 'java-library' apply plugin: 'groovy' -apply plugin: 'maven' +apply plugin: 'maven-publish' +apply plugin: 'signing' +apply plugin: 'eclipse' +apply plugin: 'checkstyle' +eclipse.jdt.file.withProperties { props -> + props.setProperty "org.eclipse.jdt.core.circularClasspath", "warning" +} group = 'org.jmonkeyengine' version = jmeFullVersion -sourceCompatibility = '1.8' -[compileJava, compileTestJava]*.options*.encoding = 'UTF-8' +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks.withType(JavaCompile) { // compile-time options: + //options.compilerArgs << '-Xlint:deprecation' // to show deprecation warnings + options.compilerArgs << '-Xlint:unchecked' + options.encoding = 'UTF-8' + if (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_1_10)) { + options.release = 8 + } +} repositories { mavenCentral() - maven { - url "http://nifty-gui.sourceforge.net/nifty-maven-repo" - } flatDir { dirs rootProject.file('lib') } @@ -24,13 +38,11 @@ repositories { dependencies { // Adding dependencies here will add the dependencies to each subproject. - testCompile group: 'junit', name: 'junit', version: '4.12' - testCompile group: 'org.mockito', name: 'mockito-core', version: '3.0.0' - testCompile group: 'org.easytesting', name: 'fest-assert-core', version: '2.0M10' - testCompile 'org.codehaus.groovy:groovy-all:2.5.8' + testImplementation libs.junit4 + testImplementation libs.mokito.core + testImplementation libs.groovy.test } - // Uncomment if you want to see the status of every test that is run and // the test output. /* @@ -44,7 +56,9 @@ test { jar { manifest { attributes 'Implementation-Title': 'jMonkeyEngine', - 'Implementation-Version': jmeFullVersion + 'Implementation-Version': jmeFullVersion, + 'Automatic-Module-Name': "${project.name.replace("-", ".")}", + 'Created-By': "${JavaVersion.current()} (${System.getProperty("java.vendor")})" } } @@ -58,10 +72,7 @@ javadoc { options.use = "true" options.charSet = "UTF-8" options.encoding = "UTF-8" - //disable doclint for JDK8, more quiet output - if (JavaVersion.current().isJava8Compatible()){ - options.addStringOption('Xdoclint:none', '-quiet') - } + source = sourceSets.main.allJava // main only, exclude tests } test { @@ -71,12 +82,12 @@ test { } task sourcesJar(type: Jar, dependsOn: classes, description: 'Creates a jar from the source files.') { - classifier = 'sources' + archiveClassifier = 'sources' from sourceSets*.allSource } task javadocJar(type: Jar, dependsOn: javadoc, description: 'Creates a jar from the javadoc files.') { - classifier = 'javadoc' + archiveClassifier = 'javadoc' from javadoc.destinationDir } @@ -105,27 +116,115 @@ ext.pomConfig = { } } -// workaround to be able to use same custom pom with 'maven' and 'bintray' plugin -task writeFullPom { - ext.pomFile = "$mavenPomDir/${project.name}-${project.version}.pom" - outputs.file pomFile - doLast { - pom { - project pomConfig - }.writeTo(pomFile) - } -} - -assemble.dependsOn(writeFullPom) -install.dependsOn(writeFullPom) -uploadArchives.dependsOn(writeFullPom) - artifacts { archives jar archives sourcesJar if (buildJavaDoc == "true") { archives javadocJar } - archives writeFullPom.outputs.files[0] } +publishing { + publications { + maven(MavenPublication) { + artifact javadocJar + artifact sourcesJar + from components.java + pom { + description = POM_DESCRIPTION + developers { + developer { + id = 'jMonkeyEngine' + name = 'jMonkeyEngine Team' + } + } + inceptionYear = POM_INCEPTION_YEAR + licenses { + license { + distribution = POM_LICENSE_DISTRIBUTION + name = POM_LICENSE_NAME + url = POM_LICENSE_URL + } + } + name = POM_NAME + scm { + connection = POM_SCM_CONNECTION + developerConnection = POM_SCM_DEVELOPER_CONNECTION + url = POM_SCM_URL + } + url = POM_URL + } + version project.version + } + } + + repositories { + maven { + name = 'Dist' + url = gradle.rootProject.projectDir.absolutePath + '/dist/maven' + } + + // Uploading to Sonatype relies on the existence of 2 properties + // (centralUsername and centralPassword) + // which should be set using -P options on the command line. + + maven { + // for uploading release builds to the default repo in Sonatype's OSSRH staging area + credentials { + username = gradle.rootProject.hasProperty('centralUsername') ? centralUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('centralPassword') ? centralPassword : 'Unknown password' + } + name = 'Central' + url = 'https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/' + } + maven { + // for uploading snapshot builds to Sonatype's maven-snapshots repo + credentials { + username = gradle.rootProject.hasProperty('centralUsername') ? centralUsername : 'Unknown user' + password = gradle.rootProject.hasProperty('centralPassword') ? centralPassword : 'Unknown password' + } + name = 'SNAPSHOT' + url = 'https://central.sonatype.com/repository/maven-snapshots/' + } + } +} + +publishToMavenLocal.doLast { + println 'published ' + project.getName() + "-${jmeFullVersion} to mavenLocal" +} +task('install') { + dependsOn 'publishToMavenLocal' +} + +signing { + def signingKey = gradle.rootProject.findProperty('signingKey') + def signingPassword = gradle.rootProject.findProperty('signingPassword') + useInMemoryPgpKeys(signingKey, signingPassword) + + sign configurations.archives + sign publishing.publications.maven +} +tasks.withType(Sign) { + onlyIf { gradle.rootProject.hasProperty('signingKey') } +} + +checkstyle { + toolVersion libs.versions.checkstyle.get() + configFile file("${gradle.rootProject.rootDir}/config/checkstyle/checkstyle.xml") +} + +checkstyleMain { + source ='src/main/java' +} + +checkstyleTest { + source ='src/test/java' +} + +tasks.withType(Checkstyle) { + reports { + xml.required.set(false) + html.required.set(true) + } + include("**/com/jme3/renderer/**/*.java") +} \ No newline at end of file diff --git a/config/checkstyle/checkstyle-suppressions.xml b/config/checkstyle/checkstyle-suppressions.xml new file mode 100644 index 0000000000..919d0a916f --- /dev/null +++ b/config/checkstyle/checkstyle-suppressions.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 0000000000..927c3779c9 --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,361 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 035c547130..42b02b8708 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Version number: Major.Minor.SubMinor (e.g. 3.3.0) -jmeVersion = 3.4.0 +jmeVersion = 3.9.0 -# Leave empty to autogenerate +# Leave empty to autogenerate # (use -PjmeVersionName="myVersion" from commandline to specify a custom version name ) jmeVersionName = @@ -22,16 +22,14 @@ buildAndroidExamples = false buildForPlatforms = Linux64,Linux32,Windows64,Windows32,Mac64 # Forcefully ignore prebuilt libraries skipPrebuildLibraries=false + +# Enable spotbugs +enableSpotBugs=false + # Path to android NDK for building native libraries #ndkPath=/Users/normenhansen/Documents/Code-Import/android-ndk-r7 ndkPath = /opt/android-ndk-r16b -# Path for downloading native Bullet -# 2.88+ -bulletUrl = https://github.com/bulletphysics/bullet3/archive/28039903b14c2aec28beff5b3609c9308b17e76a.zip -bulletFolder = bullet3-28039903b14c2aec28beff5b3609c9308b17e76a -bulletZipFile = bullet3.zip - # POM settings POM_NAME=jMonkeyEngine POM_DESCRIPTION=jMonkeyEngine is a 3-D game engine for adventurous Java developers @@ -44,8 +42,4 @@ POM_LICENSE_URL=http://opensource.org/licenses/BSD-3-Clause POM_LICENSE_DISTRIBUTION=repo POM_INCEPTION_YEAR=2009 -# Bintray settings to override in $HOME/.gradle/gradle.properties or ENV or commandline -bintray_user= -bintray_api_key= - -PREBUILD_NATIVES_URL=https://dl.bintray.com/jmonkeyengine/files/${natives.snapshot}/jme3-natives.zip +PREBUILD_NATIVES_URL=https://objects.jmonkeyengine.org/native-snapshots/${natives.snapshot}/jme3-natives.zip diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000000..1244e46c51 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,49 @@ +## catalog of libraries and plugins used to build the jmonkeyengine project + +[versions] + +checkstyle = "9.3" +lwjgl3 = "3.4.0" +nifty = "1.4.3" + +[libraries] + +android-build-gradle = "com.android.tools.build:gradle:4.2.0" +android-support-appcompat = "com.android.support:appcompat-v7:28.0.0" +androidx-annotation = "androidx.annotation:annotation:1.3.0" +androidx-lifecycle-common = "androidx.lifecycle:lifecycle-common:2.4.0" +gradle-git = "org.ajoberstar:gradle-git:1.2.0" +gradle-retrolambda = "me.tatarka:gradle-retrolambda:3.7.1" +groovy-test = "org.codehaus.groovy:groovy-test:3.0.24" +gson = "com.google.code.gson:gson:2.9.1" +j-ogg-vorbis = "com.github.stephengold:j-ogg-vorbis:1.0.6" +jbullet = "com.github.stephengold:jbullet:1.0.3" +jinput = "net.java.jinput:jinput:2.0.9" +jna = "net.java.dev.jna:jna:5.10.0" +jnaerator-runtime = "com.nativelibs4java:jnaerator-runtime:0.12" +junit4 = "junit:junit:4.13.2" +lwjgl2 = "org.jmonkeyengine:lwjgl:2.9.5" +lwjgl3-awt = "org.lwjglx:lwjgl3-awt:0.2.3" + +lwjgl3-base = { module = "org.lwjgl:lwjgl", version.ref = "lwjgl3" } +lwjgl3-glfw = { module = "org.lwjgl:lwjgl-glfw", version.ref = "lwjgl3" } +lwjgl3-jawt = { module = "org.lwjgl:lwjgl-jawt", version.ref = "lwjgl3" } +lwjgl3-jemalloc = { module = "org.lwjgl:lwjgl-jemalloc", version.ref = "lwjgl3" } +lwjgl3-openal = { module = "org.lwjgl:lwjgl-openal", version.ref = "lwjgl3" } +lwjgl3-opencl = { module = "org.lwjgl:lwjgl-opencl", version.ref = "lwjgl3" } +lwjgl3-opengl = { module = "org.lwjgl:lwjgl-opengl", version.ref = "lwjgl3" } +lwjgl3-sdl = { module = "org.lwjgl:lwjgl-sdl", version.ref = "lwjgl3" } + +mokito-core = "org.mockito:mockito-core:3.12.4" + +nifty = { module = "com.github.nifty-gui:nifty", version.ref = "nifty" } +nifty-default-controls = { module = "com.github.nifty-gui:nifty-default-controls", version.ref = "nifty" } +nifty-examples = { module = "com.github.nifty-gui:nifty-examples", version.ref = "nifty" } +nifty-style-black = { module = "com.github.nifty-gui:nifty-style-black", version.ref = "nifty" } + +spotbugs-gradle-plugin = "com.github.spotbugs.snom:spotbugs-gradle-plugin:6.0.18" +vecmath = "javax.vecmath:vecmath:1.5.2" + +[bundles] + +[plugins] diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7a3265ee94..e6441136f3 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 76e4690af0..a4413138c9 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-bin.zip +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index cccdd3d517..b740cf1339 100755 --- a/gradlew +++ b/gradlew @@ -1,78 +1,127 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - 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=SC2039,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=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + 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. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index e95643d6a2..7101f8e467 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,25 +25,29 @@ 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% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,48 +55,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :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-examples/build.gradle b/jme3-android-examples/build.gradle index 17be807ebb..c17664e1dd 100644 --- a/jme3-android-examples/build.gradle +++ b/jme3-android-examples/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 28 - buildToolsVersion "28.0.3" + buildToolsVersion "30.0.2" lintOptions { // Fix nifty gui referencing "java.awt" package. @@ -25,11 +25,6 @@ android { } } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - sourceSets { main { java { @@ -45,20 +40,18 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:28.0.0' - - compile project(':jme3-core') - compile project(':jme3-android') - compile project(':jme3-android-native') - compile project(':jme3-effects') - compile project(':jme3-bullet') - compile project(':jme3-bullet-native-android') - compile project(':jme3-networking') - compile project(':jme3-niftygui') - compile project(':jme3-plugins') - compile project(':jme3-terrain') - compile fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*']) -// compile project(':jme3-examples') + implementation fileTree(dir: 'libs', include: ['*.jar']) + testImplementation libs.junit4 + implementation libs.android.support.appcompat + + implementation project(':jme3-core') + implementation project(':jme3-android') + implementation project(':jme3-android-native') + implementation project(':jme3-effects') + implementation project(':jme3-jbullet') + implementation project(':jme3-networking') + implementation project(':jme3-niftygui') + implementation project(':jme3-plugins') + implementation project(':jme3-terrain') + implementation fileTree(dir: '../jme3-examples/build/libs', include: ['*.jar'], exclude: ['*sources*.*']) } diff --git a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java index 3805435d9a..b38bba76c5 100644 --- a/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java +++ b/jme3-android-examples/src/main/java/jme3test/android/TestAndroidSensors.java @@ -38,7 +38,7 @@ public class TestAndroidSensors extends SimpleApplication implements ActionListe private Geometry geomZero = null; // Map of joysticks saved with the joyId as the key - private IntMap joystickMap = new IntMap(); + private IntMap joystickMap = new IntMap<>(); // flag to allow for the joystick axis to be calibrated on startup private boolean initialCalibrationComplete = false; // mappings used for onAnalog @@ -228,7 +228,7 @@ public void simpleUpdate(float tpf) { } } - + @Override public void onAction(String string, boolean pressed, float tpf) { if (string.equalsIgnoreCase("MouseClick") && pressed) { // Calibrate the axis (set new zero position) if the axis @@ -256,7 +256,7 @@ public void onAction(String string, boolean pressed, float tpf) { } } - + @Override public void onAnalog(String string, float value, float tpf) { logger.log(Level.INFO, "onAnalog for {0}, value: {1}, tpf: {2}", new Object[]{string, value, tpf}); diff --git a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java index 4d38087964..cbdd59d4e5 100644 --- a/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java +++ b/jme3-android-examples/src/main/java/org/jmonkeyengine/jme3androidexamples/CustomArrayAdapter.java @@ -23,11 +23,11 @@ public class CustomArrayAdapter extends ArrayAdapter { private int selectedPosition = -1; /* Background Color of selected item */ private int selectedBackgroundColor = 0xffff00; - /* Background Color of non selected item */ + /* Background Color of non-selected items */ private int nonselectedBackgroundColor = 0x000000; /* Background Drawable Resource ID of selected item */ private int selectedBackgroundResource = 0; - /* Background Drawable Resource ID of non selected items */ + /* Background Drawable Resource ID of non-selected items */ private int nonselectedBackgroundResource = 0; /* Variables to support list filtering */ @@ -53,7 +53,7 @@ public void setSelectedBackgroundColor(int selectedBackgroundColor) { this.selectedBackgroundColor = selectedBackgroundColor; } - /** Setter for non selected background color */ + /** Setter for non-selected background color */ public void setNonSelectedBackgroundColor(int nonselectedBackgroundColor) { this.nonselectedBackgroundColor = nonselectedBackgroundColor; } @@ -63,7 +63,7 @@ public void setSelectedBackgroundResource(int selectedBackgroundResource) { this.selectedBackgroundResource = selectedBackgroundResource; } - /** Setter for non selected background resource id*/ + /** Setter for non-selected background resource id*/ public void setNonSelectedBackgroundResource(int nonselectedBackgroundResource) { this.nonselectedBackgroundResource = nonselectedBackgroundResource; } @@ -125,13 +125,13 @@ protected FilterResults performFiltering(CharSequence constraint){ String prefix = constraint.toString().toLowerCase(); Log.i(TAG, "performFiltering: entries size: " + entries.size()); if (prefix == null || prefix.length() == 0){ - ArrayList list = new ArrayList(entries); + ArrayList list = new ArrayList<>(entries); results.values = list; results.count = list.size(); Log.i(TAG, "clearing filter with size: " + list.size()); }else{ - final ArrayList list = new ArrayList(entries); - final ArrayList nlist = new ArrayList(); + final ArrayList list = new ArrayList<>(entries); + final ArrayList nlist = new ArrayList<>(); int count = list.size(); for (int i = 0; i classNames = new ArrayList(); - private List exclusions = new ArrayList(); + private List classNames = new ArrayList<>(); + private List exclusions = new ArrayList<>(); private String rootPackage; /* ListView that displays the test application class names. */ @@ -263,7 +263,7 @@ private boolean checkClassName(String className) { boolean include = true; /* check to see if the class in inside the rootPackage package */ if (className.startsWith(rootPackage)) { - /* check to see if the class contains any of the exlusion strings */ + /* check to see if the class contains any of the exclusion strings */ for (int i = 0; i < exclusions.size(); i++) { if (className.contains(exclusions.get(i))) { Log.d(TAG, "Skipping Class " + className + ". Includes exclusion string: " + exclusions.get(i) + "."); @@ -286,7 +286,7 @@ private boolean checkClassName(String className) { private boolean checkClassType(String className) { boolean include = true; try { - Class clazz = (Class) Class.forName(className); + Class clazz = Class.forName(className); if (Application.class.isAssignableFrom(clazz)) { Log.d(TAG, "Class " + className + " is a jME Application"); } else { @@ -364,7 +364,7 @@ public void onTextChanged(CharSequence cs, int startPos, int beforePos, int coun setSelection(-1); } - public void afterTextChanged(Editable edtbl) { + public void afterTextChanged(Editable editable) { } @Override diff --git a/jme3-android-native/.gitignore b/jme3-android-native/.gitignore index 9010f892c2..a75c2dfd25 100644 --- a/jme3-android-native/.gitignore +++ b/jme3-android-native/.gitignore @@ -1,2 +1,2 @@ -# The headers are autogenerated and nobody should try to commit them... +# The headers are autogenerated, and nobody should try to commit them. src/native/headers diff --git a/jme3-android-native/bufferallocator.gradle b/jme3-android-native/bufferallocator.gradle new file mode 100644 index 0000000000..d10335a7f2 --- /dev/null +++ b/jme3-android-native/bufferallocator.gradle @@ -0,0 +1,62 @@ +// 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) +} +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibsBufferAllocator.dependsOn(rootProject.extractPrebuiltNatives) +} + +// ndkExists is a boolean from the build.gradle in the root project +// buildNativeProjects is a string set to "true" +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 50e6f94178..5ec15daea1 100644 --- a/jme3-android-native/build.gradle +++ b/jme3-android-native/build.gradle @@ -2,18 +2,6 @@ // for this project. This initialization is applied in the "build.gradle" // of the root project. -// NetBeans will automatically add "run" and "debug" tasks relying on the -// "mainClass" property. You may however define the property prior executing -// tasks by passing a "-PmainClass=" argument. -// -// Note however, that you may define your own "run" and "debug" task if you -// prefer. In this case NetBeans will not add these tasks but you may rely on -// your own implementation. - -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - sourceSets { main { java { @@ -30,20 +18,18 @@ dependencies { // // You can read more about how to add dependency here: // http://www.gradle.org/docs/current/userguide/dependency_management.html#sec:how_to_declare_your_dependencies - compile project(':jme3-android') + api project(':jme3-android') } ext { // stores the native project classpath to be used in each native // build to generate native header files - projectClassPath = configurations.runtime.asFileTree.matching { + projectClassPath = configurations.runtimeClasspath.asFileTree.matching { exclude ".gradle" }.asPath } -//println "projectClassPath = " + projectClassPath // add each native lib build file apply from: file('openalsoft.gradle') -// apply from: file('stb_image.gradle') -// apply from: file('tremor.gradle') apply from: file('decode.gradle') +apply from: file('bufferallocator.gradle') diff --git a/jme3-android-native/decode.gradle b/jme3-android-native/decode.gradle index 07d93a04af..d6a3842f02 100644 --- a/jme3-android-native/decode.gradle +++ b/jme3-android-native/decode.gradle @@ -1,5 +1,5 @@ String tremorZipFile = "TremorAndroid.zip" -String stbiUrl = 'https://raw.githubusercontent.com/nothings/stb/master/stb_image.h' +String stbiUrl = 'https://raw.githubusercontent.com/jMonkeyEngine/stb/0224a44a10564a214595797b4c88323f79a5f934/stb_image.h' // Working directories for the ndk build. String decodeBuildDir = "${buildDir}" + File.separator + 'decode' @@ -83,6 +83,9 @@ task copyPreCompiledLibs(type: Copy) { from sourceDir into outputDir } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledLibs.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" @@ -96,7 +99,7 @@ if (ndkExists && buildNativeProjects == "true") { jar.into("lib") { from decodeBuildLibsDir } -// Helper class to wrap ant dowload task +// Helper class to wrap ant download task class MyDownload extends DefaultTask { @Input String sourceUrl diff --git a/jme3-android-native/openalsoft.gradle b/jme3-android-native/openalsoft.gradle index d8e5e836e2..0a14d4b429 100644 --- a/jme3-android-native/openalsoft.gradle +++ b/jme3-android-native/openalsoft.gradle @@ -1,11 +1,12 @@ -// OpenAL Soft r1.16 -String openALSoftUrl = 'http://repo.or.cz/w/openal-soft.git/snapshot/e5016f814a265ed592a88acea95cf912c4bfdf12.zip' +// OpenAL Soft r1.21.1 +// TODO: update URL to jMonkeyEngine fork once it's updated with latest kcat's changes +String openALSoftUrl = 'https://github.com/kcat/openal-soft/archive/1.24.3.zip' String openALSoftZipFile = 'OpenALSoft.zip' // OpenAL Soft directory the download is extracted into // Typically, the downloaded OpenAL Soft zip file will extract to a directory // called "openal-soft" -String openALSoftFolder = 'openal-soft-e5016f8' +String openALSoftFolder = 'openal-soft-1.24.3' //Working directories for the ndk build. String openalsoftBuildDir = "${buildDir}" + File.separator + 'openalsoft' @@ -56,7 +57,7 @@ task copyOpenALSoft(type: Copy) { into outputDir } copyOpenALSoft.dependsOn { - def openALSoftUnzipDir = new File(project.projectDir.absolutePath + File.separator + openALSoftFolder) + def openALSoftUnzipDir = new File(openalsoftBuildDir + File.separator + openALSoftFolder) // println "openALSoftUnzipDir path: " + openALSoftUnzipDir.absolutePath // println "openALSoftUnzipDir exists: " + openALSoftUnzipDir.isDirectory() if (!openALSoftUnzipDir.isDirectory()) { @@ -80,13 +81,103 @@ task copyJmeOpenALSoft(type: Copy, dependsOn: [copyOpenALSoft, copyJmeHeadersOpe from sourceDir into outputDir } +// rootProject.ndkCommandPath must be set to your ndk-build wrapper or full ndk path +def ndkPath = new File(rootProject.ndkCommandPath).getParent() +def cmakeToolchain = "${ndkPath}/build/cmake/android.toolchain.cmake" + +// 1) list your ABIs here +def openalAbis = [ + "armeabi-v7a", + "arm64-v8a", + "x86", + "x86_64" +] + +// 2) for each ABI, register a configure/build pair +openalAbis.each { abi -> + + // configure task + tasks.register("configureOpenAlSoft_${abi}", Exec) { + group = "external-native" + description = "Generate CMake build files for OpenAL-Soft [$abi]" + + workingDir file("$openalsoftBuildDir/$openALSoftFolder") + commandLine = [ + "cmake", + "-S", ".", + "-B", "cmake-build-${abi}", + "-G", "Unix Makefiles", // or Ninja + "-DCMAKE_TOOLCHAIN_FILE=${cmakeToolchain}", + "-DANDROID_PLATFORM=android-21", + "-DANDROID_ABI=${abi}", + "-DCMAKE_BUILD_TYPE=Release", + "-DALSOFT_UTILS=OFF", + "-DALSOFT_EXAMPLES=OFF", + "-DALSOFT_TESTS=OFF", + "-DALSOFT_BACKEND_OPENSL=ON", + '-DALSOFT_SHARED=OFF', + '-DBUILD_SHARED_LIBS=OFF', + '-DALSOFT_STATIC=ON', + '-DLIBTYPE=STATIC', + '-DCMAKE_CXX_FLAGS=-stdlib=libc++' + ] + + dependsOn copyOpenALSoft + } + + // build task + tasks.register("buildOpenAlSoft_${abi}", Exec) { + group = "external-native" + description = "Compile OpenAL-Soft into libopenalsoft.a for [$abi]" + + dependsOn "configureOpenAlSoft_${abi}" + workingDir file("$openalsoftBuildDir/$openALSoftFolder") + commandLine = [ + "cmake", + "--build", "cmake-build-${abi}", + "--config", "Release" + ] + } +} + +// 3) optional: aggregate tasks +tasks.register("configureOpenAlSoftAll") { + group = "external-native" + description = "Configure OpenAL-Soft for all ABIs" + dependsOn openalAbis.collect { "configureOpenAlSoft_${it}" } +} + +tasks.register("buildOpenAlSoftAll") { + group = "external-native" + description = "Build OpenAL-Soft for all ABIs" + dependsOn openalAbis.collect { "buildOpenAlSoft_${it}" } +} -task buildOpenAlSoftNativeLib(type: Exec, dependsOn: copyJmeOpenALSoft) { -// println "openalsoft build dir: " + openalsoftBuildDir -// println "ndkCommandPath: " + project.ndkCommandPath +task buildOpenAlSoftNativeLib(type: Exec) { + group = "external-native" + description = "Runs ndk-build on your JNI code, linking in the prebuilt OpenAL-Soft .a files" + + dependsOn copyJmeOpenALSoft, buildOpenAlSoftAll + + // where your Android.mk lives workingDir openalsoftBuildDir + + // call the NDK build script executable rootProject.ndkCommandPath - args "-j" + Runtime.runtime.availableProcessors() + + // pass in all ABIs (so ndk-build will rebuild your shared .so for each one), + // and pass in a custom var OPENALSOFT_BUILD_DIR so your Android.mk can find + // the cmake-build- folders. + args( + // let ndk-build know which ABIs to build for + "APP_ABI=armeabi-v7a,arm64-v8a,x86,x86_64", + + // pass in the path to the CMake output root + "OPENALSOFT_BUILD_ROOT=${openalsoftBuildDir}/${openALSoftFolder}", + + // parallel jobs + "-j${Runtime.runtime.availableProcessors()}" + ) } task updatePreCompiledOpenAlSoftLibs(type: Copy, dependsOn: buildOpenAlSoftNativeLib) { @@ -110,6 +201,9 @@ task copyPreCompiledOpenAlSoftLibs(type: Copy) { from sourceDir into outputDir } +if (skipPrebuildLibraries != "true" && buildNativeProjects != "true") { + copyPreCompiledOpenAlSoftLibs.dependsOn(rootProject.extractPrebuiltNatives) +} // ndkExists is a boolean from the build.gradle in the root project // buildNativeProjects is a string set to "true" @@ -123,7 +217,7 @@ if (ndkExists && buildNativeProjects == "true") { jar.into("lib") { from openalsoftBuildLibsDir } -// Helper class to wrap ant dowload task +// Helper class to wrap ant download task class MyDownload extends DefaultTask { @Input String sourceUrl @@ -136,3 +230,4 @@ class MyDownload extends DefaultTask { ant.get(src: sourceUrl, dest: target) } } + 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..de0aea4497 --- /dev/null +++ b/jme3-android-native/src/native/jme_bufferallocator/Application.mk @@ -0,0 +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. + +## +# 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 := armeabi-v7a,arm64-v8a,x86,x86_64 +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true + 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/Android.mk b/jme3-android-native/src/native/jme_decode/Android.mk index ee1ff8e4a6..1f2a2dca2d 100644 --- a/jme3-android-native/src/native/jme_decode/Android.mk +++ b/jme3-android-native/src/native/jme_decode/Android.mk @@ -10,7 +10,7 @@ LOCAL_C_INCLUDES:= \ $(LOCAL_PATH) \ $(LOCAL_PATH)/Tremor -LOCAL_CFLAGS := -std=gnu99 -DLIMIT_TO_64kHz +LOCAL_CFLAGS := -std=gnu99 -DLIMIT_TO_64kHz -O0 LOCAL_LDLIBS := -lz -llog -Wl,-s ifeq ($(TARGET_ARCH),arm) diff --git a/jme3-android-native/src/native/jme_decode/Application.mk b/jme3-android-native/src/native/jme_decode/Application.mk index fbc028f847..9651f8ee88 100644 --- a/jme3-android-native/src/native/jme_decode/Application.mk +++ b/jme3-android-native/src/native/jme_decode/Application.mk @@ -1,3 +1,5 @@ APP_PLATFORM := android-9 APP_OPTIM := release -APP_ABI := all \ No newline at end of file +APP_ABI := armeabi-v7a,arm64-v8a,x86,x86_64 +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true + 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 2e62dee6b8..b00ff9a7cc 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 @@ -1,6 +1,7 @@ #include #include #include +#include #include "Tremor/ivorbisfile.h" @@ -110,13 +111,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 +145,7 @@ static long FileDesc_tell(void *datasource) static ov_callbacks FileDescCallbacks = { FileDesc_read, FileDesc_seek, - FileDesc_close, + FileDesc_clear, FileDesc_tell }; @@ -157,10 +163,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 +177,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 +195,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 +252,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 +294,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; @@ -298,7 +304,7 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readFully wrapper->env = env; char err[512]; - void* byteBufferPtr = (*env)->GetDirectBufferAddress(env, buf); + unsigned char* byteBufferPtr = (unsigned char*)(*env)->GetDirectBufferAddress(env, buf); jlong byteBufferCap = (*env)->GetDirectBufferCapacity(env, buf); int offset = 0; @@ -306,7 +312,7 @@ JNIEXPORT void JNICALL Java_com_jme3_audio_plugins_NativeVorbisFile_readFully while (remaining > 0) { - long result = ov_read(ovf, byteBufferPtr + offset, remaining, &bitstream); + long result = ov_read(ovf, (void*)(byteBufferPtr + offset), remaining, &bitstream); LOGI("ov_read(%d, %d) = %ld", offset, remaining, result); @@ -330,19 +336,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-native/src/native/jme_openalsoft/Android.mk b/jme3-android-native/src/native/jme_openalsoft/Android.mk index 23d14b3ba1..d1f38c1864 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Android.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Android.mk @@ -1,70 +1,54 @@ -TARGET_PLATFORM := android-9 +# jni/Android.mk LOCAL_PATH := $(call my-dir) -include $(CLEAR_VARS) - -LOCAL_MODULE := openalsoftjme +# require the path to cmake-build- +ifndef OPENALSOFT_BUILD_ROOT +$(error OPENALSOFT_BUILD_ROOT not set! pass it via ndk-build OPENALSOFT_BUILD_ROOT=/path/to/cmake-build-root) +endif -LOCAL_C_INCLUDES += $(LOCAL_PATH) $(LOCAL_PATH)/include \ - $(LOCAL_PATH)/OpenAL32/Include $(LOCAL_PATH)/Alc +# assemble the path to this ABI's .a +OPENAL_PREBUILT_DIR := $(OPENALSOFT_BUILD_ROOT)/cmake-build-$(TARGET_ARCH_ABI) -LOCAL_CFLAGS := -std=c99 -ffast-math -DAL_BUILD_LIBRARY -DAL_ALEXT_PROTOTYPES -LOCAL_LDLIBS := -lOpenSLES -llog -Wl,-s +# ----------------------------------------------------------------------------- +# 1) prebuilt static library +include $(CLEAR_VARS) +LOCAL_MODULE := openalsoft_prebuilt +LOCAL_SRC_FILES := $(OPENAL_PREBUILT_DIR)/libopenal.a +LOCAL_EXPORT_C_INCLUDES := $(OPENALSOFT_BUILD_ROOT)/include +include $(PREBUILT_STATIC_LIBRARY) -LOCAL_SRC_FILES := Alc/backends/opensl.c \ - Alc/backends/loopback.c \ - Alc/backends/wave.c \ - Alc/backends/base.c \ - Alc/backends/null.c \ - Alc/ALc.c \ - Alc/helpers.c \ - Alc/bs2b.c \ - Alc/alcRing.c \ - Alc/effects/chorus.c \ - Alc/effects/flanger.c \ - Alc/effects/dedicated.c \ - Alc/effects/reverb.c \ - Alc/effects/distortion.c \ - Alc/effects/autowah.c \ - Alc/effects/equalizer.c \ - Alc/effects/modulator.c \ - Alc/effects/echo.c \ - Alc/effects/compressor.c \ - Alc/effects/null.c \ - Alc/alcConfig.c \ - Alc/ALu.c \ - Alc/mixer_c.c \ - Alc/panning.c \ - Alc/hrtf.c \ - Alc/mixer.c \ - Alc/midi/soft.c \ - Alc/midi/sf2load.c \ - Alc/midi/dummy.c \ - Alc/midi/fluidsynth.c \ - Alc/midi/base.c \ - common/uintmap.c \ - common/atomic.c \ - common/threads.c \ - common/rwlock.c \ - OpenAL32/alBuffer.c \ - OpenAL32/alPreset.c \ - OpenAL32/alListener.c \ - OpenAL32/alEffect.c \ - OpenAL32/alExtension.c \ - OpenAL32/alThunk.c \ - OpenAL32/alMidi.c \ - OpenAL32/alSoundfont.c \ - OpenAL32/alFontsound.c \ - OpenAL32/alAuxEffectSlot.c \ - OpenAL32/alError.c \ - OpenAL32/alFilter.c \ - OpenAL32/alSource.c \ - OpenAL32/alState.c \ - OpenAL32/sample_cvt.c \ - com_jme3_audio_android_AndroidAL.c \ - com_jme3_audio_android_AndroidALC.c \ - com_jme3_audio_android_AndroidEFX.c +# ----------------------------------------------------------------------------- +# 2) your JNI wrapper +include $(CLEAR_VARS) +LOCAL_MODULE := openalsoftjme +LOCAL_SRC_FILES := \ + com_jme3_audio_android_AndroidAL.c \ + com_jme3_audio_android_AndroidALC.c \ + com_jme3_audio_android_AndroidEFX.c + +LOCAL_C_INCLUDES += \ + $(LOCAL_PATH) \ + $(LOCAL_PATH)/include \ + $(LOCAL_PATH)/alc \ + $(LOCAL_PATH)/common + +LOCAL_CPP_FEATURES := exceptions rtti +LOCAL_CFLAGS := -ffast-math \ + -DAL_ALEXT_PROTOTYPES \ + -fcommon \ + -O0 \ + -DRESTRICT="" + +LOCAL_LDLIBS := -lOpenSLES -llog -Wl,-s -lc++_static -lc++abi +ifeq ($(TARGET_ARCH_ABI),arm64-v8a) + LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384" +endif +ifeq ($(TARGET_ARCH_ABI),x86_64) + LOCAL_LDFLAGS += "-Wl,-z,max-page-size=16384" +endif +LOCAL_STATIC_LIBRARIES := openalsoft_prebuilt +# (or LOCAL_WHOLE_STATIC_LIBRARIES if you need every object pulled in) include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-android-native/src/native/jme_openalsoft/Application.mk b/jme3-android-native/src/native/jme_openalsoft/Application.mk index fbc028f847..58561d1d14 100644 --- a/jme3-android-native/src/native/jme_openalsoft/Application.mk +++ b/jme3-android-native/src/native/jme_openalsoft/Application.mk @@ -1,3 +1,6 @@ -APP_PLATFORM := android-9 +APP_PLATFORM := android-19 APP_OPTIM := release -APP_ABI := all \ No newline at end of file +APP_ABI := armeabi-v7a,arm64-v8a,x86,x86_64 +APP_STL := c++_static +APP_SUPPORT_FLEXIBLE_PAGE_SIZES := true + diff --git a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c b/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c index c919f951a5..d1c1000372 100644 --- a/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c +++ b/jme3-android-native/src/native/jme_openalsoft/com_jme3_audio_android_AndroidALC.c @@ -74,14 +74,11 @@ static void CloseAL() static ALCdevice* GetALCDevice() { - ALCdevice *device; - ALCcontext *ctx; + ALCcontext *ctx = alcGetCurrentContext(); - ctx = alcGetCurrentContext(); - - if (ctx != NULL) + if (ctx != NULL) { - device = alcGetContextsDevice(ctx); + ALCdevice *device = alcGetContextsDevice(ctx); if (device != NULL) { diff --git a/jme3-android-native/src/native/jme_openalsoft/config.h b/jme3-android-native/src/native/jme_openalsoft/config.h index f9de995283..e0f5bc73e4 100644 --- a/jme3-android-native/src/native/jme_openalsoft/config.h +++ b/jme3-android-native/src/native/jme_openalsoft/config.h @@ -2,18 +2,18 @@ #define AL_API __attribute__((visibility("protected"))) #define ALC_API __attribute__((visibility("protected"))) -/* Define to the library version */ -#define ALSOFT_VERSION "1.16.0" - -#ifdef IN_IDE_PARSER -/* KDevelop's parser doesn't recognize the C99-standard restrict keyword, but - * recent versions (at least 4.5.1) do recognize GCC's __restrict. */ -#define restrict __restrict -#endif - /* Define any available alignment declaration */ #define ALIGN(x) __attribute__((aligned(x))) +/* Define a built-in call indicating an aligned data pointer */ +#define ASSUME_ALIGNED(x, y) __builtin_assume_aligned(x, y) + +/* Define if HRTF data is embedded in the library */ +/* #undef ALSOFT_EMBED_HRTF_DATA */ + +/* Define if we have the sysconf function */ +#define HAVE_SYSCONF + /* Define if we have the C11 aligned_alloc function */ /* #undef HAVE_ALIGNED_ALLOC */ @@ -23,17 +23,21 @@ /* Define if we have the _aligned_malloc function */ /* #undef HAVE__ALIGNED_MALLOC */ +/* Define if we have the proc_pidpath function */ +/* #undef HAVE_PROC_PIDPATH */ + +/* Define if we have the getopt function */ +#define HAVE_GETOPT + /* Define if we have SSE CPU extensions */ /* #undef HAVE_SSE */ /* #undef HAVE_SSE2 */ +/* #undef HAVE_SSE3 */ /* #undef HAVE_SSE4_1 */ /* Define if we have ARM Neon CPU extensions */ /* #undef HAVE_NEON */ -/* Define if we have FluidSynth support */ -/* #undef HAVE_FLUIDSYNTH */ - /* Define if we have the ALSA backend */ /* #undef HAVE_ALSA */ @@ -49,8 +53,8 @@ /* Define if we have the QSA backend */ /* #undef HAVE_QSA */ -/* Define if we have the MMDevApi backend */ -/* #undef HAVE_MMDEVAPI */ +/* Define if we have the WASAPI backend */ +/* #undef HAVE_WASAPI */ /* Define if we have the DSound backend */ /* #undef HAVE_DSOUND */ @@ -64,6 +68,9 @@ /* Define if we have the PulseAudio backend */ /* #undef HAVE_PULSEAUDIO */ +/* Define if we have the JACK backend */ +/* #undef HAVE_JACK */ + /* Define if we have the CoreAudio backend */ /* #undef HAVE_COREAUDIO */ @@ -73,15 +80,33 @@ /* Define if we have the Wave Writer backend */ #define HAVE_WAVE +/* Define if we have the SDL2 backend */ +/* #undef HAVE_SDL2 */ + /* Define if we have the stat function */ #define HAVE_STAT /* Define if we have the lrintf function */ #define HAVE_LRINTF +/* Define if we have the modff function */ +#define HAVE_MODFF + +/* Define if we have the log2f function */ +#define HAVE_LOG2F + +/* Define if we have the cbrtf function */ +#define HAVE_CBRTF + +/* Define if we have the copysignf function */ +#define HAVE_COPYSIGNF + /* Define if we have the strtof function */ /* #undef HAVE_STRTOF */ +/* Define if we have the strnlen function */ +#define HAVE_STRNLEN + /* Define if we have the __int64 type */ /* #undef HAVE___INT64 */ @@ -91,9 +116,6 @@ /* Define to the size of a long long int type */ #define SIZEOF_LONG_LONG 8 -/* Define if we have C99 variable-length array support */ -#define HAVE_C99_VLA - /* Define if we have C99 _Bool support */ #define HAVE_C99_BOOL @@ -101,10 +123,10 @@ #define HAVE_C11_STATIC_ASSERT /* Define if we have C11 _Alignas support */ -/* #undef HAVE_C11_ALIGNAS */ +#define HAVE_C11_ALIGNAS /* Define if we have C11 _Atomic support */ -/* #undef HAVE_C11_ATOMIC */ +#define HAVE_C11_ATOMIC /* Define if we have GCC's destructor attribute */ #define HAVE_GCC_DESTRUCTOR @@ -119,7 +141,7 @@ #define HAVE_STDBOOL_H /* Define if we have stdalign.h */ -/* #undef HAVE_STDALIGN_H */ +#define HAVE_STDALIGN_H /* Define if we have windows.h */ /* #undef HAVE_WINDOWS_H */ @@ -130,17 +152,11 @@ /* Define if we have pthread_np.h */ /* #undef HAVE_PTHREAD_NP_H */ -/* Define if we have alloca.h */ -/* #undef HAVE_ALLOCA_H */ - /* Define if we have malloc.h */ #define HAVE_MALLOC_H -/* Define if we have ftw.h */ -/* #undef HAVE_FTW_H */ - -/* Define if we have io.h */ -/* #undef HAVE_IO_H */ +/* Define if we have dirent.h */ +#define HAVE_DIRENT_H /* Define if we have strings.h */ #define HAVE_STRINGS_H @@ -175,24 +191,30 @@ /* Define if we have the __cpuid() intrinsic */ /* #undef HAVE_CPUID_INTRINSIC */ +/* Define if we have the _BitScanForward64() intrinsic */ +/* #undef HAVE_BITSCANFORWARD64_INTRINSIC */ + +/* Define if we have the _BitScanForward() intrinsic */ +/* #undef HAVE_BITSCANFORWARD_INTRINSIC */ + /* Define if we have _controlfp() */ /* #undef HAVE__CONTROLFP */ /* Define if we have __control87_2() */ /* #undef HAVE___CONTROL87_2 */ -/* Define if we have ftw() */ -/* #undef HAVE_FTW */ - -/* Define if we have _wfindfirst() */ -/* #undef HAVE__WFINDFIRST */ - /* Define if we have pthread_setschedparam() */ #define HAVE_PTHREAD_SETSCHEDPARAM /* Define if we have pthread_setname_np() */ #define HAVE_PTHREAD_SETNAME_NP +/* Define if pthread_setname_np() only accepts one parameter */ +/* #undef PTHREAD_SETNAME_NP_ONE_PARAM */ + +/* Define if pthread_setname_np() accepts three parameters */ +/* #undef PTHREAD_SETNAME_NP_THREE_PARAMS */ + /* Define if we have pthread_set_name_np() */ /* #undef HAVE_PTHREAD_SET_NAME_NP */ @@ -200,4 +222,4 @@ /* #undef HAVE_PTHREAD_MUTEXATTR_SETKIND_NP */ /* Define if we have pthread_mutex_timedlock() */ -/* #undef HAVE_PTHREAD_MUTEX_TIMEDLOCK */ \ No newline at end of file +/* #undef HAVE_PTHREAD_MUTEX_TIMEDLOCK */ diff --git a/jme3-android-native/src/native/jme_openalsoft/version.h b/jme3-android-native/src/native/jme_openalsoft/version.h new file mode 100644 index 0000000000..94ce874a45 --- /dev/null +++ b/jme3-android-native/src/native/jme_openalsoft/version.h @@ -0,0 +1,10 @@ +/* Define to the library version */ +#define ALSOFT_VERSION "1.21.1" +#define ALSOFT_VERSION_NUM 1,21,1,0 + +/* Define the branch being built */ +#define ALSOFT_GIT_BRANCH "HEAD" + +/* Define the hash of the head commit */ +#define ALSOFT_GIT_COMMIT_HASH "ae4eacf1" + diff --git a/jme3-android/build.gradle b/jme3-android/build.gradle index 98a2b37a0a..4fa73b50ca 100644 --- a/jme3-android/build.gradle +++ b/jme3-android/build.gradle @@ -1,12 +1,10 @@ apply plugin: 'java' -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') - compile project(':jme3-plugins') + //added annotations used by JmeSurfaceView. + compileOnly libs.androidx.annotation + compileOnly libs.androidx.lifecycle.common + api project(':jme3-core') compileOnly 'android:android' } diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java index b6d0913d16..66d5abc0a9 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarness.java @@ -75,8 +75,8 @@ public class AndroidHarness extends Activity implements TouchListener, DialogInt protected int eglDepthBits = 16; /** - * Sets the number of samples to use for multisampling.
- * Leave 0 (default) to disable multisampling.
+ * Sets the number of samples to use for multisampling.
+ * Leave 0 (default) to disable multisampling.
* Set to 2 or 4 to enable multisampling. */ protected int eglSamples = 0; @@ -190,6 +190,7 @@ public Object onRetainNonConfigurationInstance() { } @Override + @SuppressWarnings("unchecked") public void onCreate(Bundle savedInstanceState) { initializeLogHandler(); @@ -211,7 +212,7 @@ public void onCreate(Bundle savedInstanceState) { logger.log(Level.FINE, "Using Retained App"); this.app = data.app; } else { - // Discover the screen reolution + // Discover the screen resolution //TODO try to find a better way to get a hand on the resolution WindowManager wind = this.getWindowManager(); Display disp = wind.getDefaultDisplay(); @@ -239,9 +240,8 @@ public void onCreate(Bundle savedInstanceState) { // Create application instance try { if (app == null) { - @SuppressWarnings("unchecked") - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); } app.setSettings(settings); @@ -359,9 +359,10 @@ public void run() { * Called by the android alert dialog, terminate the activity and OpenGL * rendering * - * @param dialog - * @param whichButton + * @param dialog ignored + * @param whichButton the button index */ + @Override public void onClick(DialogInterface dialog, int whichButton) { if (whichButton != -2) { if (app != null) { @@ -473,6 +474,7 @@ protected void initializeLogHandler() { handler.setLevel(Level.ALL); } + @Override public void initialize() { app.initialize(); if (handleExitHook) { @@ -488,10 +490,17 @@ public void initialize() { } } + @Override public void reshape(int width, int height) { app.reshape(width, height); } + @Override + public void rescale(float x, float y) { + app.rescale(x, y); + } + + @Override public void update() { app.update(); // call to remove the splash screen, if present. @@ -503,10 +512,12 @@ public void update() { } } + @Override public void requestClose(boolean esc) { app.requestClose(esc); } + @Override public void destroy() { if (app != null) { app.destroy(); @@ -516,6 +527,7 @@ public void destroy() { } } + @Override public void gainFocus() { logger.fine("gainFocus"); if (view != null) { @@ -547,6 +559,7 @@ public void gainFocus() { } } + @Override public void loseFocus() { logger.fine("loseFocus"); if (app != null) { diff --git a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java index ed8976955f..11de175389 100644 --- a/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java +++ b/jme3-android/src/main/java/com/jme3/app/AndroidHarnessFragment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -103,8 +103,8 @@ public class AndroidHarnessFragment extends Fragment implements protected int eglDepthBits = 16; /** - * Sets the number of samples to use for multisampling.
- * Leave 0 (default) to disable multisampling.
+ * Sets the number of samples to use for multisampling.
+ * Leave 0 (default) to disable multisampling.
* Set to 2 or 4 to enable multisampling. */ protected int eglSamples = 0; @@ -128,9 +128,9 @@ public class AndroidHarnessFragment extends Fragment implements * If the surfaceview is rectangular, the longest side (width or height) * will have the resolution set to a maximum of maxResolutionDimension. * The other direction will be set to a value that maintains the aspect - * ratio of the surfaceview.
+ * ratio of the surfaceview.
* Any value less than 0 (default = -1) will result in the surfaceview having the - * same resolution as the view layout (ie. no max resolution). + * same resolution as the view layout (i.e. no max resolution). */ protected int maxResolutionDimension = -1; @@ -229,9 +229,10 @@ public void onAttach(Activity activity) { * other methods. View related objects should not be reused, but rather * created and destroyed along with the Activity. * - * @param savedInstanceState + * @param savedInstanceState the saved instance state */ @Override + @SuppressWarnings("unchecked") public void onCreate(Bundle savedInstanceState) { initializeLogHandler(); logger.fine("onCreate"); @@ -257,9 +258,8 @@ public void onCreate(Bundle savedInstanceState) { // Create application instance try { if (app == null) { - @SuppressWarnings("unchecked") - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); } app.setSettings(settings); @@ -283,9 +283,9 @@ public void onCreate(Bundle savedInstanceState) { * by the Activity's layout parameters for this Fragment. For jME, we also * update the application reference to the new view. * - * @param inflater - * @param container - * @param savedInstanceState + * @param inflater ignored + * @param container ignored + * @param savedInstanceState ignored * @return the new view */ @Override @@ -313,7 +313,7 @@ public void onStart() { } /** - * When the Fragment resumes (ie. after app resumes or device screen turned + * When the Fragment resumes (i.e. after app resumes or device screen turned * back on), call the gainFocus() in the jME application. */ @Override @@ -325,7 +325,7 @@ public void onResume() { } /** - * When the Fragment pauses (ie. after home button pressed on the device + * When the Fragment pauses (i.e. after home button pressed on the device * or device screen turned off) , call the loseFocus() in the jME application. */ @Override @@ -432,8 +432,8 @@ public void run() { * Called by the android alert dialog, terminate the activity and OpenGL * rendering * - * @param dialog - * @param whichButton + * @param dialog ignored + * @param whichButton the button index */ @Override public void onClick(DialogInterface dialog, int whichButton) { @@ -572,6 +572,11 @@ public void reshape(int width, int height) { app.reshape(width, height); } + @Override + public void rescale(float x, float y) { + app.rescale(x, y); + } + @Override public void update() { app.update(); @@ -674,8 +679,10 @@ public void onLayoutChange(View v, int newHeight = bottom-top; if (viewWidth != newWidth || viewHeight != newHeight) { - logger.log(Level.FINE, "SurfaceView layout changed: old width: {0}, old height: {1}, new width: {2}, new height: {3}", - new Object[]{viewWidth, viewHeight, newWidth, newHeight}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "SurfaceView layout changed: old width: {0}, old height: {1}, new width: {2}, new height: {3}", + new Object[]{viewWidth, viewHeight, newWidth, newHeight}); + } viewWidth = newWidth; viewHeight = newHeight; @@ -684,10 +691,10 @@ public void onLayoutChange(View v, if (viewWidth > viewHeight && viewWidth > maxResolutionDimension) { // landscape fixedSizeWidth = maxResolutionDimension; - fixedSizeHeight = (int)(maxResolutionDimension * ((float)viewHeight / (float)viewWidth)); + fixedSizeHeight = (int)(maxResolutionDimension * (viewHeight / (float)viewWidth)); } else if (viewHeight > viewWidth && viewHeight > maxResolutionDimension) { // portrait - fixedSizeWidth = (int)(maxResolutionDimension * ((float)viewWidth / (float)viewHeight)); + fixedSizeWidth = (int)(maxResolutionDimension * (viewWidth / (float)viewHeight)); fixedSizeHeight = maxResolutionDimension; } else if (viewWidth == viewHeight && viewWidth > maxResolutionDimension) { fixedSizeWidth = maxResolutionDimension; @@ -695,8 +702,10 @@ public void onLayoutChange(View v, } // set the surfaceview resolution if the size != current view size if (fixedSizeWidth != viewWidth || fixedSizeHeight != viewHeight) { - logger.log(Level.FINE, "setting surfaceview resolution to width: {0}, height: {1}", - new Object[]{fixedSizeWidth, fixedSizeHeight}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "setting surfaceview resolution to width: {0}, height: {1}", + new Object[]{fixedSizeWidth, fixedSizeHeight}); + } view.getHolder().setFixedSize(fixedSizeWidth, fixedSizeHeight); } } diff --git a/jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java b/jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java index 4faa8aa898..3c4e95bf68 100644 --- a/jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java +++ b/jme3-android/src/main/java/com/jme3/app/DefaultAndroidProfiler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 jMonkeyEngine + * Copyright (c) 2014-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -82,6 +82,7 @@ public class DefaultAndroidProfiler implements AppProfiler { private int androidApiLevel = Build.VERSION.SDK_INT; + @Override public void appStep(AppStep appStep) { if (androidApiLevel >= 18) { switch(appStep) { @@ -140,6 +141,7 @@ public void appSubStep(String... additionalInfo) { } + @Override public void vpStep(VpStep vpStep, ViewPort vp, RenderQueue.Bucket bucket) { if (androidApiLevel >= 18) { switch (vpStep) { diff --git a/jme3-android/src/main/java/com/jme3/app/state/MjpegFileWriter.java b/jme3-android/src/main/java/com/jme3/app/state/MjpegFileWriter.java index 9a8361f440..16039fb176 100644 --- a/jme3-android/src/main/java/com/jme3/app/state/MjpegFileWriter.java +++ b/jme3-android/src/main/java/com/jme3/app/state/MjpegFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -126,10 +126,12 @@ public void finishAVI() throws Exception { int fileSize = (int)aviFile.length(); logger.log(Level.INFO, "fileSize: {0}", fileSize); int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length); - logger.log(Level.INFO, "listSize: {0}", listSize); - logger.log(Level.INFO, "aviFile canWrite: {0}", aviFile.canWrite()); - logger.log(Level.INFO, "aviFile AbsolutePath: {0}", aviFile.getAbsolutePath()); - logger.log(Level.INFO, "aviFile numFrames: {0}", numFrames); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "listSize: {0}", listSize); + logger.log(Level.INFO, "aviFile canWrite: {0}", aviFile.canWrite()); + logger.log(Level.INFO, "aviFile AbsolutePath: {0}", aviFile.getAbsolutePath()); + logger.log(Level.INFO, "aviFile numFrames: {0}", numFrames); + } RandomAccessFile raf = new RandomAccessFile(aviFile, "rw"); @@ -457,7 +459,7 @@ private class AVIIndexList { public byte[] fcc = new byte[]{'i', 'd', 'x', '1'}; public int cb = 0; - public List ind = new ArrayList(); + public List ind = new ArrayList<>(); public AVIIndexList() { } @@ -478,7 +480,7 @@ public byte[] toBytes() throws Exception { baos.write(fcc); baos.write(intBytes(swapInt(cb))); for (int i = 0; i < ind.size(); i++) { - AVIIndex in = (AVIIndex) ind.get(i); + AVIIndex in = ind.get(i); baos.write(in.toBytes()); } diff --git a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java index f89a49879d..cc25a40d55 100644 --- a/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java +++ b/jme3-android/src/main/java/com/jme3/app/state/VideoRecorderAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ /** * A Video recording AppState that records the screen output into an AVI file with - * M-JPEG content. The file should be playable on any OS in any video player.
+ * M-JPEG content. The file should be playable on any OS in any video player.
* The video recording starts when the state is attached and stops when it is detached * or the application is quit. You can set the fileName of the file to be written when the * state is detached, else the old file will be overwritten. If you specify no file @@ -73,6 +73,7 @@ public class VideoRecorderAppState extends AbstractAppState { private Application app; private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { + @Override public Thread newThread(Runnable r) { Thread th = new Thread(r); th.setName("jME3 Video Processor"); @@ -131,9 +132,14 @@ public VideoRecorderAppState(File file, float quality) { } /** - * This constructor allows you to specify the output file of the video as well as the quality + * This constructor allows you to specify the output file of the video as + * well as the quality. + * * @param file the video file - * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + * @param quality the quality of the jpegs in the video stream (0.0 smallest + * file - 1.0 largest file) + * @param framerate the frame rate of the resulting video, the application + * will be locked to this framerate */ public VideoRecorderAppState(File file, float quality, int framerate) { this.file = file; @@ -221,12 +227,11 @@ private class VideoProcessor implements SceneProcessor { private int width; private int height; private RenderManager renderManager; - private boolean isInitilized = false; + private boolean isInitialized = false; private LinkedBlockingQueue freeItems; - private LinkedBlockingQueue usedItems = new LinkedBlockingQueue(); + private LinkedBlockingQueue usedItems = new LinkedBlockingQueue<>(); private MjpegFileWriter writer; private boolean fastMode = true; - private AppProfiler prof; public void addImage(Renderer renderer, FrameBuffer out) { if (freeItems == null) { @@ -239,6 +244,7 @@ public void addImage(Renderer renderer, FrameBuffer out) { renderer.readFrameBufferWithFormat(out, item.buffer, Image.Format.BGRA8); executor.submit(new Callable() { + @Override public Void call() throws Exception { if (fastMode) { item.data = item.buffer.array(); @@ -260,13 +266,14 @@ public Void call() throws Exception { } } + @Override public void initialize(RenderManager rm, ViewPort viewPort) { logger.log(Level.INFO, "initialize in VideoProcessor"); this.camera = viewPort.getCamera(); this.width = camera.getWidth(); this.height = camera.getHeight(); this.renderManager = rm; - this.isInitilized = true; + this.isInitialized = true; if (freeItems == null) { freeItems = new LinkedBlockingQueue(); for (int i = 0; i < numCpus; i++) { @@ -275,13 +282,16 @@ public void initialize(RenderManager rm, ViewPort viewPort) { } } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { - return this.isInitilized; + return this.isInitialized; } + @Override public void preFrame(float tpf) { if (null == writer) { try { @@ -292,14 +302,17 @@ public void preFrame(float tpf) { } } + @Override public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { numFrames++; addImage(renderManager.getRenderer(), out); } + @Override public void cleanup() { logger.log(Level.INFO, "cleanup in VideoProcessor"); logger.log(Level.INFO, "VideoProcessor numFrames: {0}", numFrames); @@ -317,7 +330,7 @@ public void cleanup() { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } } @@ -332,22 +345,27 @@ public IsoTimer(float framerate) { this.ticks = 0; } + @Override public long getTime() { return (long) (this.ticks * (1.0f / this.framerate) * 1000f); } + @Override public long getResolution() { return 1000L; } + @Override public float getFrameRate() { return this.framerate; } + @Override public float getTimePerFrame() { - return (float) (1.0f / this.framerate); + return 1.0f / this.framerate; } + @Override public void update() { long time = System.currentTimeMillis(); long difference = time - lastTime; @@ -357,13 +375,14 @@ public void update() { Thread.sleep(difference); } catch (InterruptedException ex) { } - } else { + } else if (logger.isLoggable(Level.INFO)) { logger.log(Level.INFO, "actual tpf(ms): {0}, 1/framerate(ms): {1}", new Object[]{difference, (1.0f / this.framerate) * 1000.0f}); } this.ticks++; } + @Override public void reset() { this.ticks = 0; } diff --git a/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java b/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java index 7c7200bb92..d0ffda860a 100644 --- a/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java +++ b/jme3-android/src/main/java/com/jme3/asset/plugins/AndroidLocator.java @@ -17,6 +17,7 @@ public class AndroidLocator implements AssetLocator { public AndroidLocator() { } + @Override public void setRootPath(String rootPath) { this.rootPath = rootPath; } @@ -25,11 +26,12 @@ public void setRootPath(String rootPath) { public AssetInfo locate(AssetManager manager, AssetKey key) { String assetPath = rootPath + key.getName(); // Fix path issues + assetPath = assetPath.replace("//", "/"); if (assetPath.startsWith("/")) { // Remove leading / assetPath = assetPath.substring(1); } - assetPath = assetPath.replace("//", "/"); + // Not making this a property and storing for future use in case the view stored in JmeAndroidSystem // is replaced due to device orientation change. Not sure it is necessary to do this yet, but am for now. diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidAL.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAL.java index 2a0634c919..3dc8b4bbb2 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidAL.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidAL.java @@ -10,44 +10,64 @@ public final class AndroidAL implements AL { public AndroidAL() { } + @Override public native String alGetString(int parameter); + @Override public native int alGenSources(); + @Override public native int alGetError(); + @Override public native void alDeleteSources(int numSources, IntBuffer sources); + @Override public native void alGenBuffers(int numBuffers, IntBuffer buffers); + @Override public native void alDeleteBuffers(int numBuffers, IntBuffer buffers); + @Override public native void alSourceStop(int source); + @Override public native void alSourcei(int source, int param, int value); + @Override public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency); + @Override public native void alSourcePlay(int source); + @Override public native void alSourcePause(int source); + @Override public native void alSourcef(int source, int param, float value); + @Override public native void alSource3f(int source, int param, float value1, float value2, float value3); + @Override public native int alGetSourcei(int source, int param); + @Override public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers); + @Override public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers); + @Override public native void alListener(int param, FloatBuffer data); + @Override public native void alListenerf(int param, float value); + @Override public native void alListener3f(int param, float value1, float value2, float value3); + @Override public native void alSource3i(int source, int param, int value1, int value2, int value3); } diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidALC.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidALC.java index a2f0a4eb62..f05cfd03d0 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidALC.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidALC.java @@ -12,19 +12,27 @@ public final class AndroidALC implements ALC { public AndroidALC() { } + @Override public native void createALC(); + @Override public native void destroyALC(); + @Override public native boolean isCreated(); + @Override public native String alcGetString(int parameter); + @Override public native boolean alcIsExtensionPresent(String extension); + @Override public native void alcGetInteger(int param, IntBuffer buffer, int size); + @Override public native void alcDevicePauseSOFT(); + @Override public native void alcDeviceResumeSOFT(); } diff --git a/jme3-android/src/main/java/com/jme3/audio/android/AndroidEFX.java b/jme3-android/src/main/java/com/jme3/audio/android/AndroidEFX.java index 271d2d507e..3fa6e5e6aa 100644 --- a/jme3-android/src/main/java/com/jme3/audio/android/AndroidEFX.java +++ b/jme3-android/src/main/java/com/jme3/audio/android/AndroidEFX.java @@ -8,25 +8,36 @@ public class AndroidEFX implements EFX { public AndroidEFX() { } + @Override public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers); + @Override public native void alGenEffects(int numEffects, IntBuffer buffers); + @Override public native void alEffecti(int effect, int param, int value); + @Override public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value); + @Override public native void alDeleteEffects(int numEffects, IntBuffer buffers); + @Override public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers); + @Override public native void alGenFilters(int numFilters, IntBuffer buffers); + @Override public native void alFilteri(int filter, int param, int value); + @Override public native void alFilterf(int filter, int param, float value); + @Override public native void alDeleteFilters(int numFilters, IntBuffer buffers); + @Override public native void alEffectf(int effect, int param, float value); } diff --git a/jme3-android/src/main/java/com/jme3/audio/android/package-info.java b/jme3-android/src/main/java/com/jme3/audio/android/package-info.java new file mode 100644 index 0000000000..dbfa1c5d25 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/audio/android/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio support for Android devices + */ +package com.jme3.audio.android; 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 c4daf9d7b4..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 @@ -46,6 +77,7 @@ public long skip(long n) throws IOException { throw new IOException("Not supported for audio streams"); } + @Override public void setTime(float time) { try { file.seekTime(time); @@ -56,7 +88,7 @@ public void setTime(float time) { @Override public void close() throws IOException { - file.close(); + file.clearResources(); afd.close(); } } @@ -70,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(); @@ -106,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/input/android/AndroidGestureProcessor.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java index e905a852b5..c38c7663af 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidGestureProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +66,7 @@ public AndroidGestureProcessor(AndroidTouchInput touchInput) { @Override public boolean onDown(MotionEvent event) { - // start of all GestureListeners. Not really a gesture by itself + // The start of all GestureListeners. Not really a gesture by itself, // so we don't create an event. // However, reset the scaleInProgress here since this is the beginning // of a series of gesture events. @@ -116,11 +116,11 @@ public void onLongPress(MotionEvent event) { @Override public boolean onScroll(MotionEvent startEvent, MotionEvent endEvent, float distX, float distY) { - // if not scaleInProgess, send scroll events. This is to avoid sending + // if not scaleInProgress, send scroll events. This is to avoid sending // scroll events when one of the fingers is lifted just before the other one. // Avoids sending the scroll for that brief period of time. // Return true so that the next event doesn't accumulate the distX and distY values. - // Apparantly, both distX and distY are negative. + // Apparently, both distX and distY are negative. // Negate distX to get the real value, but leave distY negative to compensate // for the fact that jME has y=0 at bottom where Android has y=0 at top. if (!touchInput.getScaleDetector().isInProgress()) { diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java index b52cad7d2e..5c3d303ab5 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ /** * AndroidInput is the main class that connects the Android system * inputs to jME. It receives the inputs from the Android View and passes them - * to the appropriate classes based on the source of the input.
+ * to the appropriate classes based on the source of the input.
* This class is to be extended when new functionality is released in Android. * * @author iwgeric diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java index ad11cd9343..5ee0f757b2 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidInputHandler14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ /** * AndroidInputHandler14 extends AndroidInputHandler to - * add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).
+ * add the onHover and onGenericMotion events that where added in Android rev 14 (Android 4.0).
* The onGenericMotion events are the main interface to Joystick axes. They * were actually released in Android rev 12. * diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java index 6eb2a949f9..fbbdc8c831 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,15 +51,15 @@ /** * Main class that manages various joystick devices. Joysticks can be many forms * including a simulated joystick to communicate the device orientation as well - * as physical gamepads.
+ * as physical gamepads.
* This class manages all the joysticks and feeds the inputs from each back * to jME's InputManager. * * This handler also supports the joystick.rumble(rumbleAmount) method. In this * case, when joystick.rumble(rumbleAmount) is called, the Android device will vibrate - * if the device has a built in vibrate motor. + * if the device has a built-in vibrate motor. * - * Because Andorid does not allow for the user to define the intensity of the + * Because Android does not allow for the user to define the intensity of the * vibration, the rumble amount (ie strength) is converted into vibration pulses * The stronger the strength amount, the shorter the delay between pulses. If * amount is 1, then the vibration stays on the whole time. If amount is 0.5, @@ -83,14 +83,14 @@ public class AndroidJoyInput implements JoyInput { public static boolean disableSensors = false; protected AndroidInputHandler inputHandler; - protected List joystickList = new ArrayList(); + protected List joystickList = new ArrayList<>(); // private boolean dontSendHistory = false; // Internal private boolean initialized = false; private RawInputListener listener = null; - private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue eventQueue = new ConcurrentLinkedQueue<>(); private AndroidSensorJoyInput sensorJoyInput; private Vibrator vibrator = null; private boolean vibratorActive = false; @@ -209,7 +209,9 @@ public void setJoyRumble(int joyId, float amount) { @Override public Joystick[] loadJoysticks(InputManager inputManager) { - logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "loading joysticks for {0}", this.getClass().getName()); + } if (!disableSensors) { joystickList.add(sensorJoyInput.loadJoystick(joystickList.size(), inputManager)); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java index 00478aea1c..050a00e292 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoyInput14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ /** * AndroidJoyInput14 extends AndroidJoyInput - * to include support for physical joysticks/gamepads.
+ * to include support for physical joysticks/gamepads. * * @author iwgeric */ diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java index 1ed98977cd..6011e9d0a7 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidJoystickJoyInput14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -63,9 +64,8 @@ public class AndroidJoystickJoyInput14 { private static final Logger logger = Logger.getLogger(AndroidJoystickJoyInput14.class.getName()); - private boolean loaded = false; private AndroidJoyInput joyInput; - private Map joystickIndex = new HashMap(); + private Map joystickIndex = new HashMap<>(); private static int[] AndroidGamepadButtons = { // Dpad buttons @@ -109,16 +109,18 @@ public void destroy() { public List loadJoysticks(int joyId, InputManager inputManager) { logger.log(Level.INFO, "loading Joystick devices"); - ArrayList joysticks = new ArrayList(); + ArrayList joysticks = new ArrayList<>(); joysticks.clear(); joystickIndex.clear(); - ArrayList gameControllerDeviceIds = new ArrayList(); + ArrayList gameControllerDeviceIds = new ArrayList<>(); int[] deviceIds = InputDevice.getDeviceIds(); for (int deviceId : deviceIds) { InputDevice dev = InputDevice.getDevice(deviceId); int sources = dev.getSources(); - logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "deviceId[{0}] sources: {1}", new Object[]{deviceId, sources}); + } // Verify that the device has gamepad buttons, control sticks, or both. if (((sources & InputDevice.SOURCE_GAMEPAD) == InputDevice.SOURCE_GAMEPAD) || @@ -127,9 +129,9 @@ public List loadJoysticks(int joyId, InputManager inputManager) { if (!gameControllerDeviceIds.contains(deviceId)) { gameControllerDeviceIds.add(deviceId); logger.log(Level.FINE, "Attempting to create joystick for device: {0}", dev); - // Create an AndroidJoystick and store the InputDevice so we - // can later correspond the input from the InputDevice to the - // appropriate jME Joystick event + // Create an AndroidJoystick and store the InputDevice, so we + // can later convert the input from the InputDevice to the + // appropriate jME Joystick event. AndroidJoystick joystick = new AndroidJoystick(inputManager, joyInput, dev, @@ -144,10 +146,14 @@ public List loadJoysticks(int joyId, InputManager inputManager) { // type reported by Android into the jME Joystick axis List motionRanges = dev.getMotionRanges(); for (MotionRange motionRange: motionRanges) { - logger.log(Level.INFO, "motion range: {0}", motionRange.toString()); - logger.log(Level.INFO, "axis: {0}", motionRange.getAxis()); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "motion range: {0}", motionRange); + logger.log(Level.INFO, "axis: {0}", motionRange.getAxis()); + } JoystickAxis axis = joystick.addAxis(motionRange); - logger.log(Level.INFO, "added axis: {0}", axis); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "added axis: {0}", axis); + } } // InputDevice has a method for determining if a keyCode is @@ -158,8 +164,10 @@ public List loadJoysticks(int joyId, InputManager inputManager) { // buttons being configured that don't exist on the specific // device, but I haven't found a better way yet. for (int keyCode: AndroidGamepadButtons) { - logger.log(Level.INFO, "button[{0}]: {1}", - new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)}); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "button[{0}]: {1}", + new Object[]{keyCode, KeyCharacterMap.deviceHasKey(keyCode)}); + } if (KeyCharacterMap.deviceHasKey(keyCode)) { // add button even though we aren't sure if the button // actually exists on this InputDevice @@ -173,13 +181,12 @@ public List loadJoysticks(int joyId, InputManager inputManager) { } } - - loaded = true; return joysticks; } public boolean onGenericMotion(MotionEvent event) { boolean consumed = false; + float rawValue, value; // logger.log(Level.INFO, "onGenericMotion event: {0}", event); event.getDeviceId(); event.getSource(); @@ -188,7 +195,8 @@ public boolean onGenericMotion(MotionEvent event) { if (joystick != null) { for (int androidAxis: joystick.getAndroidAxes()) { String axisName = MotionEvent.axisToString(androidAxis); - float value = event.getAxisValue(androidAxis); + rawValue = event.getAxisValue(androidAxis); + value = JoystickCompatibilityMappings.remapAxisRange(joystick.getAxis(androidAxis), rawValue); int action = event.getAction(); if (action == MotionEvent.ACTION_MOVE) { // logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}", @@ -197,7 +205,7 @@ public boolean onGenericMotion(MotionEvent event) { if (axis != null) { // logger.log(Level.INFO, "MOVE axis num: {0}, axisName: {1}, value: {2}, deadzone: {3}", // new Object[]{androidAxis, axisName, value, axis.getDeadZone()}); - JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value); + JoyAxisEvent axisEvent = new JoyAxisEvent(axis, value, rawValue); joyInput.addEvent(axisEvent); consumed = true; } else { @@ -245,8 +253,8 @@ protected class AndroidJoystick extends AbstractJoystick { private JoystickAxis yAxis; private JoystickAxis povX; private JoystickAxis povY; - private Map axisIndex = new HashMap(); - private Map buttonIndex = new HashMap(); + private Map axisIndex = new HashMap<>(); + private Map buttonIndex = new HashMap<>(); public AndroidJoystick( InputManager inputManager, JoyInput joyInput, InputDevice device, int joyId, String name ) { @@ -319,8 +327,8 @@ protected JoystickButton addButton( int keyCode ) { original = JoystickButton.BUTTON_11; } - String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); - if( logicalId == null ? original != null : !logicalId.equals(original) ) { + String logicalId = JoystickCompatibilityMappings.remapButton( getName(), original ); + if (logger.isLoggable(Level.FINE) && !Objects.equals(logicalId, original)) { logger.log(Level.FINE, "Remapped: {0} to: {1}", new Object[]{original, logicalId}); } @@ -350,8 +358,8 @@ protected JoystickAxis addAxis(MotionRange motionRange) { } else if (motionRange.getAxis() == MotionEvent.AXIS_HAT_Y) { original = JoystickAxis.POV_Y; } - String logicalId = JoystickCompatibilityMappings.remapComponent( getName(), original ); - if( logicalId == null ? original != null : !logicalId.equals(original) ) { + String logicalId = JoystickCompatibilityMappings.remapAxis( getName(), original ); + if (logger.isLoggable(Level.FINE) && !Objects.equals(logicalId, original)) { logger.log(Level.FINE, "Remapped: {0} to: {1}", new Object[]{original, logicalId}); } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java index 32d1e00086..7886a2540b 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidKeyMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -143,6 +143,12 @@ public class AndroidKeyMapping { 0x0,//mute }; + /** + * A private constructor to inhibit instantiation of this class. + */ + private AndroidKeyMapping() { + } + public static int getJmeKey(int androidKey) { if (androidKey > ANDROID_TO_JME.length) { return androidKey; diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java index 4232e60827..d0fab80311 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidSensorJoyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -78,7 +78,7 @@ public class AndroidSensorJoyInput implements SensorEventListener { private AndroidJoyInput joyInput; private SensorManager sensorManager = null; private WindowManager windowManager = null; - private IntMap sensors = new IntMap(); + private IntMap sensors = new IntMap<>(); private int lastRotation = 0; private boolean loaded = false; @@ -96,7 +96,7 @@ private class SensorData { int sensorAccuracy = -1; float[] lastValues; final Object valuesLock = new Object(); - ArrayList axes = new ArrayList(); + ArrayList axes = new ArrayList<>(); boolean enabled = false; boolean haveData = false; @@ -155,16 +155,20 @@ private boolean registerListener(int sensorType) { SensorData sensorData = sensors.get(sensorType); if (sensorData != null) { if (sensorData.enabled) { - logger.log(Level.FINE, "Sensor Already Active: SensorType: {0}, active: {1}", - new Object[]{sensorType, sensorData.enabled}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Sensor Already Active: SensorType: {0}, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + } return true; } sensorData.haveData = false; if (sensorData.sensor != null) { if (sensorManager.registerListener(this, sensorData.sensor, sensorData.androidSensorSpeed)) { sensorData.enabled = true; - logger.log(Level.FINE, "SensorType: {0}, actived: {1}", - new Object[]{sensorType, sensorData.enabled}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "SensorType: {0}, enabled: {1}", + new Object[]{sensorType, sensorData.enabled}); + } return true; } else { sensorData.enabled = false; @@ -183,8 +187,10 @@ private void unRegisterListener(int sensorType) { } sensorData.enabled = false; sensorData.haveData = false; - logger.log(Level.FINE, "SensorType: {0} deactivated, active: {1}", - new Object[]{sensorType, sensorData.enabled}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "SensorType: {0} deactivated, active: {1}", + new Object[]{sensorType, sensorData.enabled}); + } } } @@ -276,7 +282,7 @@ private boolean remapCoordinates(float[] inR, float[] outR) { * Surface.ROTATION_270 = device in rotated 270deg counterclockwise * * When the Manifest locks the orientation, this value will not change during - * gametime, but if the orientation of the screen is based off the sensor, + * game time, but if the orientation of the screen is based off the sensor, * this value will change as the device is rotated. * @return Current device rotation amount */ @@ -374,7 +380,7 @@ private boolean updateOrientation() { sensorData.haveData = true; } else { if (axis.isChanged()) { - joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); + joyInput.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue(), axis.getJoystickAxisValue())); } } } @@ -409,9 +415,11 @@ public Joystick loadJoystick(int joyId, InputManager inputManager) { "AndroidSensorsJoystick"); List availSensors = sensorManager.getSensorList(Sensor.TYPE_ALL); - for (Sensor sensor: availSensors) { - logger.log(Level.FINE, "{0} Sensor is available, Type: {1}, Vendor: {2}, Version: {3}", - new Object[]{sensor.getName(), sensor.getType(), sensor.getVendor(), sensor.getVersion()}); + if (logger.isLoggable(Level.FINE)) { + for (Sensor sensor : availSensors) { + logger.log(Level.FINE, "{0} Sensor is available, Type: {1}, Vendor: {2}, Version: {3}", + new Object[]{sensor.getName(), sensor.getType(), sensor.getVendor(), sensor.getVersion()}); + } } // manually create orientation sensor data since orientation is not a physical sensor @@ -553,7 +561,7 @@ public void onSensorChanged(SensorEvent se) { sensorData.haveData = true; } else { if (axis.isChanged()) { - JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue()); + JoyAxisEvent event = new JoyAxisEvent(axis, axis.getJoystickAxisValue(), axis.getJoystickAxisValue()); // logger.log(Level.INFO, "adding JoyAxisEvent: {0}", event); joyInput.addEvent(event); // joyHandler.addEvent(new JoyAxisEvent(axis, axis.getJoystickAxisValue())); @@ -561,7 +569,7 @@ public void onSensorChanged(SensorEvent se) { } } } - } else if (sensorData != null) { + } else { if (!sensorData.haveData) { sensorData.haveData = true; } @@ -575,10 +583,12 @@ public void onAccuracyChanged(Sensor sensor, int i) { int sensorType = sensor.getType(); SensorData sensorData = sensors.get(sensorType); if (sensorData != null) { - logger.log(Level.FINE, "onAccuracyChanged for {0}: accuracy: {1}", - new Object[]{sensor.getName(), i}); - logger.log(Level.FINE, "MaxRange: {0}, Resolution: {1}", - new Object[]{sensor.getMaximumRange(), sensor.getResolution()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "onAccuracyChanged for {0}: accuracy: {1}", + new Object[]{sensor.getName(), i}); + logger.log(Level.FINE, "MaxRange: {0}, Resolution: {1}", + new Object[]{sensor.getMaximumRange(), sensor.getResolution()}); + } sensorData.sensorAccuracy = i; } } @@ -705,8 +715,10 @@ protected boolean isChanged() { @Override public void calibrateCenter() { zeroRawValue = lastRawValue; - logger.log(Level.FINE, "Calibrating axis {0} to {1}", - new Object[]{getName(), zeroRawValue}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Calibrating axis {0} to {1}", + new Object[]{getName(), zeroRawValue}); + } } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java index 0a7fc38381..47e687dc52 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ * AndroidTouchInput is the base class that receives touch inputs from the * Android system and creates the TouchEvents for jME. This class is designed * to handle the base touch events for Android rev 9 (Android 2.3). This is - * extended by other classes to add features that were introducted after + * extended by other classes to add features that were introduced after * Android rev 9. * * @author iwgeric @@ -69,11 +69,10 @@ public class AndroidTouchInput implements TouchInput { private boolean mouseEventsInvertX = false; private boolean mouseEventsInvertY = false; private boolean keyboardEventsEnabled = false; - private boolean dontSendHistory = false; protected int numPointers = 0; - final private HashMap lastPositions = new HashMap(); - final private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + final private HashMap lastPositions = new HashMap<>(); + final private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue<>(); private final static int MAX_TOUCH_EVENTS = 1024; private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); private float scaleX = 1f; @@ -131,12 +130,14 @@ public void loadSettings(AppSettings settings) { // view width and height are 0 until the view is displayed on the screen if (androidInput.getView().getWidth() != 0 && androidInput.getView().getHeight() != 0) { - scaleX = (float)settings.getWidth() / (float)androidInput.getView().getWidth(); - scaleY = (float)settings.getHeight() / (float)androidInput.getView().getHeight(); + scaleX = settings.getWidth() / (float)androidInput.getView().getWidth(); + scaleY = settings.getHeight() / (float)androidInput.getView().getHeight(); } - logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", - new Object[]{scaleX, scaleY}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}", + new Object[]{scaleX, scaleY}); + } } @@ -162,7 +163,7 @@ public boolean onTouch(MotionEvent event) { boolean bWasHandled = false; TouchEvent touch = null; // System.out.println("native : " + event.getAction()); - int action = getAction(event); + getAction(event); int pointerIndex = getPointerIndex(event); int pointerId = getPointerId(event); Vector2f lastPos = lastPositions.get(pointerId); @@ -352,9 +353,9 @@ public boolean onKey(KeyEvent event) { // logger.log(Level.FINE, "creating KeyInputEvent: {0}", kie); } - // consume all keys ourself except Volume Up/Down and Menu + // Consume all keys ourselves except Volume Up/Down and Menu. // Don't do Menu so that typical Android Menus can be created and used - // by the user in MainActivity + // by the user in MainActivity. if ((event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) || (event.getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) || (event.getKeyCode() == KeyEvent.KEYCODE_MENU)) { @@ -368,7 +369,7 @@ public boolean onKey(KeyEvent event) { - // ----------------------------------------- + // ----------------------------------------- // JME3 Input interface @Override public void initialize() { @@ -469,7 +470,7 @@ public boolean isSimulateKeyboard() { @Override public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; + // not implemented } } diff --git a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java index 7b5e987b59..e8a74c4953 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java +++ b/jme3-android/src/main/java/com/jme3/input/android/AndroidTouchInput14.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,7 @@ */ public class AndroidTouchInput14 extends AndroidTouchInput { private static final Logger logger = Logger.getLogger(AndroidTouchInput14.class.getName()); - final private HashMap lastHoverPositions = new HashMap(); + final private HashMap lastHoverPositions = new HashMap<>(); public AndroidTouchInput14(AndroidInputHandler androidInput) { super(androidInput); diff --git a/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java b/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java index 400a3bd38d..2bea5a8cd9 100644 --- a/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java +++ b/jme3-android/src/main/java/com/jme3/input/android/TouchEventPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -87,7 +87,7 @@ public TouchEvent getNextFreeEvent() { TouchEvent evt = null; int curSize = eventPool.size(); while (curSize > 0) { - evt = (TouchEvent)eventPool.pop(); + evt = eventPool.pop(); if (evt.isConsumed()) { break; } else { diff --git a/jme3-android/src/main/java/com/jme3/input/android/package-info.java b/jme3-android/src/main/java/com/jme3/input/android/package-info.java new file mode 100644 index 0000000000..8c7d4de449 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/input/android/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * user-input classes specific to Android devices + */ +package com.jme3.input.android; diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java index ced4260ea5..701da81518 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/AndroidGL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,7 +45,9 @@ public class AndroidGL implements GL, GL2, GLES_30, GLExt, GLFbo { IntBuffer tmpBuff = BufferUtils.createIntBuffer(1); + IntBuffer tmpBuff16 = BufferUtils.createIntBuffer(16); + @Override public void resetStats() { } @@ -86,10 +88,12 @@ private static void checkLimit(Buffer buffer) { } } + @Override public void glActiveTexture(int texture) { GLES20.glActiveTexture(texture); } + @Override public void glAttachShader(int program, int shader) { GLES20.glAttachShader(program, shader); } @@ -99,144 +103,184 @@ public void glBeginQuery(int target, int query) { GLES30.glBeginQuery(target, query); } + @Override public void glBindBuffer(int target, int buffer) { GLES20.glBindBuffer(target, buffer); } + @Override public void glBindTexture(int target, int texture) { GLES20.glBindTexture(target, texture); } + @Override public void glBlendFunc(int sfactor, int dfactor) { GLES20.glBlendFunc(sfactor, dfactor); } + @Override public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) { GLES20.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); } + @Override public void glBufferData(int target, FloatBuffer data, int usage) { GLES20.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferData(int target, ShortBuffer data, int usage) { GLES20.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferData(int target, ByteBuffer data, int usage) { GLES20.glBufferData(target, getLimitBytes(data), data, usage); } - public void glBufferData(int target, long data_size, int usage) { - GLES20.glBufferData(target, (int) data_size, null, usage); + @Override + public void glBufferData(int target, long dataSize, int usage) { + GLES20.glBufferData(target, (int) dataSize, null, usage); } + @Override public void glBufferSubData(int target, long offset, FloatBuffer data) { GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glBufferSubData(int target, long offset, ShortBuffer data) { GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glBufferSubData(int target, long offset, ByteBuffer data) { GLES20.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glGetBufferSubData(int target, long offset, ByteBuffer data) { throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); } + @Override + public void glGetBufferSubData(int target, long offset, IntBuffer data) { + throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); + } + + @Override public void glClear(int mask) { GLES20.glClear(mask); } + @Override public void glClearColor(float red, float green, float blue, float alpha) { GLES20.glClearColor(red, green, blue, alpha); } + @Override public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) { GLES20.glColorMask(red, green, blue, alpha); } + @Override public void glCompileShader(int shader) { GLES20.glCompileShader(shader); } + @Override public void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, ByteBuffer data) { GLES20.glCompressedTexImage2D(target, level, internalformat, width, height, 0, getLimitBytes(data), data); } + @Override public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data) { GLES20.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, getLimitBytes(data), data); } + @Override public int glCreateProgram() { return GLES20.glCreateProgram(); } + @Override public int glCreateShader(int shaderType) { return GLES20.glCreateShader(shaderType); } + @Override public void glCullFace(int mode) { GLES20.glCullFace(mode); } + @Override public void glDeleteBuffers(IntBuffer buffers) { checkLimit(buffers); GLES20.glDeleteBuffers(buffers.limit(), buffers); } + @Override public void glDeleteProgram(int program) { GLES20.glDeleteProgram(program); } + @Override public void glDeleteShader(int shader) { GLES20.glDeleteShader(shader); } + @Override public void glDeleteTextures(IntBuffer textures) { checkLimit(textures); GLES20.glDeleteTextures(textures.limit(), textures); } + @Override public void glDepthFunc(int func) { GLES20.glDepthFunc(func); } + @Override public void glDepthMask(boolean flag) { GLES20.glDepthMask(flag); } + @Override public void glDepthRange(double nearVal, double farVal) { GLES20.glDepthRangef((float)nearVal, (float)farVal); } + @Override public void glDetachShader(int program, int shader) { GLES20.glDetachShader(program, shader); } + @Override public void glDisable(int cap) { GLES20.glDisable(cap); } + @Override public void glDisableVertexAttribArray(int index) { GLES20.glDisableVertexAttribArray(index); } + @Override public void glDrawArrays(int mode, int first, int count) { GLES20.glDrawArrays(mode, first, count); } + @Override public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) { GLES20.glDrawElements(mode, count, type, (int)indices); } + @Override public void glEnable(int cap) { GLES20.glEnable(cap); } + @Override public void glEnableVertexAttribArray(int index) { GLES20.glEnableVertexAttribArray(index); } @@ -246,11 +290,13 @@ public void glEndQuery(int target) { GLES30.glEndQuery(target); } + @Override public void glGenBuffers(IntBuffer buffers) { checkLimit(buffers); GLES20.glGenBuffers(buffers.limit(), buffers); } + @Override public void glGenTextures(IntBuffer textures) { checkLimit(textures); GLES20.glGenTextures(textures.limit(), textures); @@ -261,29 +307,41 @@ public void glGenQueries(int num, IntBuffer buff) { GLES30.glGenQueries(num, buff); } + @Override public int glGetAttribLocation(int program, String name) { return GLES20.glGetAttribLocation(program, name); } + @Override public void glGetBoolean(int pname, ByteBuffer params) { // GLES20.glGetBoolean(pname, params); throw new UnsupportedOperationException("Today is not a good day for this"); } + @Override public int glGetError() { return GLES20.glGetError(); } + @Override + public void glGetFloat(int parameterId, FloatBuffer storeValues) { + checkLimit(storeValues); + GLES20.glGetFloatv(parameterId, storeValues); + } + + @Override public void glGetInteger(int pname, IntBuffer params) { checkLimit(params); GLES20.glGetIntegerv(pname, params); } + @Override public void glGetProgram(int program, int pname, IntBuffer params) { checkLimit(params); GLES20.glGetProgramiv(program, pname, params); } + @Override public String glGetProgramInfoLog(int program, int maxLength) { return GLES20.glGetProgramInfoLog(program); } @@ -303,51 +361,63 @@ public int glGetQueryObjectiv(int query, int pname) { return buff.get(0); } + @Override public void glGetShader(int shader, int pname, IntBuffer params) { checkLimit(params); GLES20.glGetShaderiv(shader, pname, params); } + @Override public String glGetShaderInfoLog(int shader, int maxLength) { return GLES20.glGetShaderInfoLog(shader); } + @Override public String glGetString(int name) { return GLES20.glGetString(name); } + @Override public int glGetUniformLocation(int program, String name) { return GLES20.glGetUniformLocation(program, name); } + @Override public boolean glIsEnabled(int cap) { return GLES20.glIsEnabled(cap); } + @Override public void glLineWidth(float width) { GLES20.glLineWidth(width); } + @Override public void glLinkProgram(int program) { GLES20.glLinkProgram(program); } + @Override public void glPixelStorei(int pname, int param) { GLES20.glPixelStorei(pname, param); } + @Override public void glPolygonOffset(float factor, float units) { GLES20.glPolygonOffset(factor, units); } + @Override public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) { GLES20.glReadPixels(x, y, width, height, format, type, data); } + @Override public void glScissor(int x, int y, int width, int height) { GLES20.glScissor(x, y, width, height); } + @Override public void glShaderSource(int shader, String[] string, IntBuffer length) { if (string.length != 1) { throw new UnsupportedOperationException("Today is not a good day"); @@ -355,186 +425,231 @@ public void glShaderSource(int shader, String[] string, IntBuffer length) { GLES20.glShaderSource(shader, string[0]); } + @Override public void glStencilFuncSeparate(int face, int func, int ref, int mask) { GLES20.glStencilFuncSeparate(face, func, ref, mask); } + @Override public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass) { GLES20.glStencilOpSeparate(face, sfail, dpfail, dppass); } + @Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) { GLES20.glTexImage2D(target, level, internalFormat, width, height, 0, format, type, data); } + @Override public void glTexParameterf(int target, int pname, float param) { GLES20.glTexParameterf(target, pname, param); } + @Override public void glTexParameteri(int target, int pname, int param) { GLES20.glTexParameteri(target, pname, param); } + @Override public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { GLES20.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data); } + @Override public void glUniform1(int location, FloatBuffer value) { GLES20.glUniform1fv(location, getLimitCount(value, 1), value); } + @Override public void glUniform1(int location, IntBuffer value) { GLES20.glUniform1iv(location, getLimitCount(value, 1), value); } + @Override public void glUniform1f(int location, float v0) { GLES20.glUniform1f(location, v0); } + @Override public void glUniform1i(int location, int v0) { GLES20.glUniform1i(location, v0); } + @Override public void glUniform2(int location, IntBuffer value) { GLES20.glUniform2iv(location, getLimitCount(value, 2), value); } + @Override public void glUniform2(int location, FloatBuffer value) { GLES20.glUniform2fv(location, getLimitCount(value, 2), value); } + @Override public void glUniform2f(int location, float v0, float v1) { GLES20.glUniform2f(location, v0, v1); } + @Override public void glUniform3(int location, IntBuffer value) { GLES20.glUniform3iv(location, getLimitCount(value, 3), value); } + @Override public void glUniform3(int location, FloatBuffer value) { GLES20.glUniform3fv(location, getLimitCount(value, 3), value); } + @Override public void glUniform3f(int location, float v0, float v1, float v2) { GLES20.glUniform3f(location, v0, v1, v2); } + @Override public void glUniform4(int location, FloatBuffer value) { GLES20.glUniform4fv(location, getLimitCount(value, 4), value); } + @Override public void glUniform4(int location, IntBuffer value) { GLES20.glUniform4iv(location, getLimitCount(value, 4), value); } + @Override public void glUniform4f(int location, float v0, float v1, float v2, float v3) { GLES20.glUniform4f(location, v0, v1, v2, v3); } + @Override public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) { GLES20.glUniformMatrix3fv(location, getLimitCount(value, 3 * 3), transpose, value); } + @Override public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) { GLES20.glUniformMatrix4fv(location, getLimitCount(value, 4 * 4), transpose, value); } + @Override public void glUseProgram(int program) { GLES20.glUseProgram(program); } + @Override public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) { GLES20.glVertexAttribPointer(index, size, type, normalized, stride, (int)pointer); } + @Override public void glViewport(int x, int y, int width, int height) { GLES20.glViewport(x, y, width, height); } + @Override public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { GLES30.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } + @Override public void glBufferData(int target, IntBuffer data, int usage) { GLES20.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferSubData(int target, long offset, IntBuffer data) { GLES20.glBufferSubData(target, (int)offset, getLimitBytes(data), data); } + @Override public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { GLES30.glDrawArraysInstanced(mode, first, count, primcount); } + @Override public void glDrawBuffers(IntBuffer bufs) { GLES30.glDrawBuffers(bufs.limit(), bufs); } - public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { - GLES30.glDrawElementsInstanced(mode, indices_count, type, (int)indices_buffer_offset, primcount); + @Override + public void glDrawElementsInstancedARB(int mode, int indicesCount, int type, long indicesBufferOffset, int primcount) { + GLES30.glDrawElementsInstanced(mode, indicesCount, type, (int)indicesBufferOffset, primcount); } + @Override public void glGetMultisample(int pname, int index, FloatBuffer val) { GLES31.glGetMultisamplefv(pname, index, val); } + @Override public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { GLES30.glRenderbufferStorageMultisample(target, samples, internalformat, width, height); } - public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { - GLES31.glTexStorage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); + @Override + public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedSampleLocations) { + GLES31.glTexStorage2DMultisample(target, samples, internalformat, width, height, fixedSampleLocations); } + @Override public void glVertexAttribDivisorARB(int index, int divisor) { GLES30.glVertexAttribDivisor(index, divisor); } + @Override public void glBindFramebufferEXT(int param1, int param2) { GLES20.glBindFramebuffer(param1, param2); } + @Override public void glBindRenderbufferEXT(int param1, int param2) { GLES20.glBindRenderbuffer(param1, param2); } + @Override public int glCheckFramebufferStatusEXT(int param1) { return GLES20.glCheckFramebufferStatus(param1); } + @Override public void glDeleteFramebuffersEXT(IntBuffer param1) { checkLimit(param1); GLES20.glDeleteFramebuffers(param1.limit(), param1); } + @Override public void glDeleteRenderbuffersEXT(IntBuffer param1) { checkLimit(param1); GLES20.glDeleteRenderbuffers(param1.limit(), param1); } + @Override public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { GLES20.glFramebufferRenderbuffer(param1, param2, param3, param4); } + @Override public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { GLES20.glFramebufferTexture2D(param1, param2, param3, param4, param5); } + @Override public void glGenFramebuffersEXT(IntBuffer param1) { checkLimit(param1); GLES20.glGenFramebuffers(param1.limit(), param1); } + @Override public void glGenRenderbuffersEXT(IntBuffer param1) { checkLimit(param1); GLES20.glGenRenderbuffers(param1.limit(), param1); } + @Override public void glGenerateMipmapEXT(int param1) { GLES20.glGenerateMipmap(param1); } + @Override public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { GLES20.glRenderbufferStorage(param1, param2, param3, param4); } @@ -570,46 +685,78 @@ public void glFramebufferTextureLayerEXT(int target, int attachment, int texture GLES30.glFramebufferTextureLayer(target, attachment, texture, level, layer); } + @Override public void glAlphaFunc(int func, float ref) { } + @Override public void glPointSize(float size) { } + @Override public void glPolygonMode(int face, int mode) { } // Wrapper to DrawBuffers as there's no DrawBuffer method in GLES + @Override public void glDrawBuffer(int mode) { - tmpBuff.clear(); - tmpBuff.put(0, mode); - tmpBuff.rewind(); - glDrawBuffers(tmpBuff); + int nBuffers = (mode - GLFbo.GL_COLOR_ATTACHMENT0_EXT) + 1; + if (nBuffers <= 0 || nBuffers > 16) { + throw new IllegalArgumentException("Draw buffer outside range: " + Integer.toHexString(mode)); + } + tmpBuff16.clear(); + for (int i = 0; i < nBuffers - 1; i++) { + tmpBuff16.put(GL.GL_NONE); + } + tmpBuff16.put(mode); + tmpBuff16.flip(); + glDrawBuffers(tmpBuff16); } + @Override public void glReadBuffer(int mode) { GLES30.glReadBuffer(mode); } + @Override public void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, ByteBuffer data) { GLES30.glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, getLimitBytes(data), data); } + @Override public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, ByteBuffer data) { GLES30.glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, getLimitBytes(data), data); } + @Override public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer data) { GLES30.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data); } + @Override public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer data) { GLES30.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data); } + @Override + public void glBindVertexArray(int array) { + GLES30.glBindVertexArray(array); + } + + @Override + public void glDeleteVertexArrays(IntBuffer arrays) { + GLES30.glDeleteVertexArrays(arrays.limit(),arrays); + } + + @Override + public void glGenVertexArrays(IntBuffer arrays) { + GLES30.glGenVertexArrays(arrays.limit(),arrays); + + } + } diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java b/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java index 5f9c842927..b4d824b940 100644 --- a/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java +++ b/jme3-android/src/main/java/com/jme3/renderer/android/RendererUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,12 @@ public class RendererUtil { */ public static boolean ENABLE_ERROR_CHECKING = true; + /** + * A private constructor to inhibit instantiation of this class. + */ + private RendererUtil() { + } + /** * Checks for an OpenGL error and throws a {@link RendererException} if * there is one. Ignores the value of @@ -71,14 +77,14 @@ public static void checkGLErrorForced() { /** * Checks for an EGL error and throws a {@link RendererException} if there * is one. Ignores the value of {@link RendererUtil#ENABLE_ERROR_CHECKING}. + * + * @param egl (not null) */ public static void checkEGLError(EGL10 egl) { int error = egl.eglGetError(); if (error != EGL10.EGL_SUCCESS) { String errorMessage; switch (error) { - case EGL10.EGL_SUCCESS: - return; case EGL10.EGL_NOT_INITIALIZED: errorMessage = "EGL is not initialized, or could not be " + "initialized, for the specified EGL display connection. "; diff --git a/jme3-android/src/main/java/com/jme3/renderer/android/package-info.java b/jme3-android/src/main/java/com/jme3/renderer/android/package-info.java new file mode 100644 index 0000000000..5e09bddca6 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/renderer/android/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * graphics-rendering code specific to Android devices + */ +package com.jme3.renderer.android; diff --git a/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java b/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java index 4bccf537aa..8f023c6266 100644 --- a/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java +++ b/jme3-android/src/main/java/com/jme3/system/android/AndroidConfigChooser.java @@ -35,70 +35,70 @@ public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { EGLConfig[] configs = getConfigs(egl, display); // First try to find an exact match, but allowing a higher stencil - EGLConfig choosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); - if (choosenConfig == null && requestedConfig.d > 16) { + EGLConfig chosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); + if (chosenConfig == null && requestedConfig.d > 16) { logger.log(Level.INFO, "EGL configuration not found, reducing depth"); requestedConfig.d = 16; - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, false, false, false, true); } - if (choosenConfig == null) { + if (chosenConfig == null) { logger.log(Level.INFO, "EGL configuration not found, allowing higher RGB"); - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); } - if (choosenConfig == null && requestedConfig.a > 0) { + if (chosenConfig == null && requestedConfig.a > 0) { logger.log(Level.INFO, "EGL configuration not found, allowing higher alpha"); - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); } - if (choosenConfig == null && requestedConfig.s > 0) { + if (chosenConfig == null && requestedConfig.s > 0) { logger.log(Level.INFO, "EGL configuration not found, allowing higher samples"); - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); } - if (choosenConfig == null && requestedConfig.a > 0) { + if (chosenConfig == null && requestedConfig.a > 0) { logger.log(Level.INFO, "EGL configuration not found, reducing alpha"); requestedConfig.a = 1; - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); } - if (choosenConfig == null && requestedConfig.s > 0) { + if (chosenConfig == null && requestedConfig.s > 0) { logger.log(Level.INFO, "EGL configuration not found, reducing samples"); requestedConfig.s = 1; if (requestedConfig.a > 0) { - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, true, true); } else { - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, true, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, true, true); } } - if (choosenConfig == null && requestedConfig.getBitsPerPixel() > 16) { + if (chosenConfig == null && requestedConfig.getBitsPerPixel() > 16) { logger.log(Level.INFO, "EGL configuration not found, setting to RGB565"); requestedConfig.r = 5; requestedConfig.g = 6; requestedConfig.b = 5; - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); - if (choosenConfig == null) { + if (chosenConfig == null) { logger.log(Level.INFO, "EGL configuration not found, allowing higher alpha"); - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, true, false, true); } } - if (choosenConfig == null) { + if (chosenConfig == null) { logger.log(Level.INFO, "EGL configuration not found, looking for best config with >= 16 bit Depth"); - //failsafe, should pick best config with at least 16 depth + // failsafe: pick the best config with depth >= 16 requestedConfig = new Config(0, 0, 0, 0, 16, 0, 0); - choosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); + chosenConfig = chooseConfig(egl, display, configs, requestedConfig, true, false, false, true); } - if (choosenConfig != null) { + if (chosenConfig != null) { logger.fine("GLSurfaceView asks for egl config, returning: "); - logEGLConfig(choosenConfig, display, egl, Level.FINE); + logEGLConfig(chosenConfig, display, egl, Level.FINE); - storeSelectedConfig(egl, display, choosenConfig); - return choosenConfig; + storeSelectedConfig(egl, display, chosenConfig); + return chosenConfig; } else { logger.severe("No EGL Config found"); return null; @@ -118,11 +118,15 @@ private Config getRequestedConfig() { g = 6; b = 5; } - logger.log(Level.FINE, "Requested Display Config:"); - logger.log(Level.FINE, "RGB: {0}, alpha: {1}, depth: {2}, samples: {3}, stencil: {4}", - new Object[]{settings.getBitsPerPixel(), - settings.getAlphaBits(), settings.getDepthBits(), - settings.getSamples(), settings.getStencilBits()}); + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Requested Display Config:"); + logger.log(Level.FINE, "RGB: {0}, alpha: {1}, depth: {2}, samples: {3}, stencil: {4}", + new Object[]{settings.getBitsPerPixel(), + settings.getAlphaBits(), settings.getDepthBits(), + settings.getSamples(), settings.getStencilBits()}); + } + return new Config( r, g, b, settings.getAlphaBits(), @@ -214,8 +218,10 @@ private EGLConfig chooseConfig( int st = eglGetConfigAttribSafe(egl, display, config, EGL10.EGL_STENCIL_SIZE); - logger.log(Level.FINE, "Checking Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", - new Object[]{r, g, b, a, d, s, st}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Checking Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", + new Object[]{r, g, b, a, d, s, st}); + } if (higherRGB && r < requestedConfig.r) { continue; } if (!higherRGB && r != requestedConfig.r) { continue; } @@ -243,8 +249,10 @@ private EGLConfig chooseConfig( kr = r; kg = g; kb = b; ka = a; kd = d; ks = s; kst = st; keptConfig = config; - logger.log(Level.FINE, "Keeping Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", - new Object[]{r, g, b, a, d, s, st}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Keeping Config r: {0}, g: {1}, b: {2}, alpha: {3}, depth: {4}, samples: {5}, stencil: {6}", + new Object[]{r, g, b, a, d, s, st}); + } } } 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 efaf4f3bbb..dc0cfe5588 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.res.Resources; + import java.io.File; import java.io.IOException; import java.io.OutputStream; @@ -35,10 +37,22 @@ 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() { - return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Android.cfg"); + return Resources.getResource("com/jme3/asset/Android.cfg"); } @Override @@ -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) { @@ -166,7 +162,7 @@ public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { // When created this way, the directory is automatically removed by the Android // system when the app is uninstalled. // The directory is also accessible by a PC connected to the device - // so the files can be copied to the PC (ie. screenshots) + // so the files can be copied to the PC (i.e. screenshots) storageFolder = storageFolders.get(type); if (storageFolder == null) { String state = Environment.getExternalStorageState(); @@ -180,10 +176,12 @@ public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { default: break; } - if (storageFolder != null) { - logger.log(Level.FINE, "Base Storage Folder Path: {0}", storageFolder.getAbsolutePath()); - } else { - logger.log(Level.FINE, "Base Storage Folder not found!"); + if (logger.isLoggable(Level.FINE)) { + if (storageFolder != null) { + logger.log(Level.FINE, "Base Storage Folder Path: {0}", storageFolder.getAbsolutePath()); + } else { + logger.log(Level.FINE, "Base Storage Folder not found!"); + } } return storageFolder; } @@ -204,6 +202,7 @@ public static String getAudioRendererType() { public void showSoftKeyboard(final boolean show) { view.getHandler().post(new Runnable() { + @Override public void run() { InputMethodManager manager = (InputMethodManager)view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 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 7ea954eac4..70911d855a 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-2018 jMonkeyEngine + * Copyright (c) 2009-2025 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; @@ -52,16 +55,10 @@ import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; import com.jme3.renderer.android.AndroidGL; -import com.jme3.renderer.opengl.GL; -import com.jme3.renderer.opengl.GLES_30; -import com.jme3.renderer.opengl.GLDebugES; -import com.jme3.renderer.opengl.GLExt; -import com.jme3.renderer.opengl.GLFbo; -import com.jme3.renderer.opengl.GLRenderer; -import com.jme3.renderer.opengl.GLTracer; +import com.jme3.renderer.opengl.*; import com.jme3.system.*; -import com.jme3.util.AndroidBufferAllocator; import com.jme3.util.BufferAllocatorFactory; +import com.jme3.util.PrimitiveAllocator; import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -81,19 +78,18 @@ public class OGLESContext implements JmeContext, GLSurfaceView.Renderer, SoftTex protected SystemListener listener; protected boolean autoFlush = true; protected AndroidInputHandler androidInput; - protected long minFrameDuration = 0; // No FPS cap + protected long minFrameDuration = 0; // No FPS cap protected long lastUpdateTime = 0; static { final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION; if (System.getProperty(implementation) == null) { - System.setProperty(implementation, AndroidBufferAllocator.class.getName()); + System.setProperty(implementation, PrimitiveAllocator.class.getName()); } } - public OGLESContext() { - } + public OGLESContext() {} @Override public Type getType() { @@ -107,6 +103,7 @@ public Type getType() { * GLSurfaceView. Only one GLSurfaceView can be created at this time. The * given configType specifies how to determine the display configuration. * + * @param context (not null) * @return GLSurfaceView The newly created view */ public GLSurfaceView createView(Context context) { @@ -116,9 +113,11 @@ public GLSurfaceView createView(Context context) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { // below 4.0, check OpenGL ES 2.0 support. if (info.reqGlEsVersion < 0x20000) { - throw new UnsupportedOperationException("OpenGL ES 2.0 or better is not supported on this device"); + throw new UnsupportedOperationException( + "OpenGL ES 2.0 or better is not supported on this device" + ); } - } else if (Build.VERSION.SDK_INT < 9){ + } else if (Build.VERSION.SDK_INT < 9) { throw new UnsupportedOperationException("jME3 requires Android 2.3 or later"); } @@ -128,7 +127,7 @@ public GLSurfaceView createView(Context context) { if (androidInput == null) { if (Build.VERSION.SDK_INT >= 14) { androidInput = new AndroidInputHandler14(); - } else if (Build.VERSION.SDK_INT >= 9){ + } else if (Build.VERSION.SDK_INT >= 9) { androidInput = new AndroidInputHandler(); } } @@ -138,7 +137,7 @@ public GLSurfaceView createView(Context context) { // setEGLContextClientVersion must be set before calling setRenderer // this means it cannot be set in AndroidConfigChooser (too late) // use proper openGL ES version - view.setEGLContextClientVersion(info.reqGlEsVersion>>16); + view.setEGLContextClientVersion(info.reqGlEsVersion >> 16); view.setFocusableInTouchMode(true); view.setFocusable(true); @@ -201,21 +200,35 @@ protected void initInThread() { logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); // Setup unhandled Exception Handler - Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Exception thrown in " + thread.toString(), thrown); - } - }); + Thread + .currentThread() + .setUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Exception thrown in " + thread.toString(), thrown); + } + } + ); timer = new NanoTimer(); - Object gl = new AndroidGL(); + GL gl = new AndroidGL(); if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugES((GL) gl, (GLExt) gl, (GLFbo) gl); + gl = + (GL) GLDebug.createProxy( + gl, + gl, + GL.class, + GL2.class, + GLES_30.class, + GLFbo.class, + GLExt.class + ); } if (settings.getBoolean("GraphicsTrace")) { - gl = GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class); + gl = (GL) GLTracer.createGlesTracer(gl, GL.class, GLES_30.class, GLFbo.class, GLExt.class); } - renderer = new GLRenderer((GL)gl, (GLExt)gl, (GLFbo)gl); + renderer = new GLRenderer(gl, (GLExt) gl, (GLFbo) gl); renderer.initialize(); JmeSystem.setSoftTextDialogInput(this); @@ -234,10 +247,19 @@ protected void deinitInThread() { } listener.destroy(); - + // releases the view holder from the Android Input Resources + // releasing the view enables the context instance to be + // reclaimed by the GC. + // if not released; it leads to a weak reference leak + // disabling the destruction of the Context View Holder. + androidInput.setView(null); + + // nullifying the references + // signals their memory to be reclaimed listener = null; renderer = null; timer = null; + androidInput = null; // do android specific cleaning here logger.fine("Display destroyed."); @@ -254,13 +276,23 @@ public void setSettings(AppSettings settings) { } if (settings.getFrameRate() > 0) { - minFrameDuration = (long)(1000d / (double)settings.getFrameRate()); // ms + minFrameDuration = (long) (1000d / settings.getFrameRate()); // ms logger.log(Level.FINE, "Setting min tpf: {0}ms", minFrameDuration); } else { minFrameDuration = 0; } } + /** + * 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; @@ -302,8 +334,7 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { @@ -318,11 +349,17 @@ public void setAutoFlushFrames(boolean enabled) { // SystemListener:reshape @Override public void onSurfaceChanged(GL10 gl, int width, int height) { - logger.log(Level.FINE, "GL Surface changed, width: {0} height: {1}", new Object[]{width, height}); + if (logger.isLoggable(Level.FINE)) { + logger.log( + Level.FINE, + "GL Surface changed, width: {0} height: {1}", + new Object[] { width, height } + ); + } // update the application settings with the new resolution settings.setResolution(width, height); - // reload settings in androidInput so the correct touch event scaling can be - // calculated in case the surface resolution is different than the view + // Reload settings in androidInput so the correct touch event scaling can be + // calculated in case the surface resolution is different than the view. androidInput.loadSettings(settings); // if the application has already been initialized (ie renderable is set) // then call reshape so the app can adjust to the new resolution. @@ -360,16 +397,14 @@ public void onDrawFrame(GL10 gl) { // Enforce a FPS cap if (updateDelta < minFrameDuration) { -// logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}", -// new Object[]{lastUpdateTime, updateDelta, minTimePerFrame}); + // logger.log(Level.INFO, "lastUpdateTime: {0}, updateDelta: {1}, minTimePerFrame: {2}", + // new Object[]{lastUpdateTime, updateDelta, minTimePerFrame}); try { Thread.sleep(minFrameDuration - updateDelta); - } catch (InterruptedException e) { - } + } catch (InterruptedException e) {} } lastUpdateTime = System.currentTimeMillis(); - } } @@ -390,8 +425,7 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { @@ -409,76 +443,173 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } - public void requestDialog(final int id, final String title, final String initialValue, final SoftTextDialogInputListener listener) { - logger.log(Level.FINE, "requestDialog: title: {0}, initialValue: {1}", - new Object[]{title, initialValue}); + @Override + public void requestDialog( + final int id, + final String title, + final String initialValue, + final SoftTextDialogInputListener listener + ) { + if (logger.isLoggable(Level.FINE)) { + logger.log( + Level.FINE, + "requestDialog: title: {0}, initialValue: {1}", + new Object[] { title, initialValue } + ); + } final View view = JmeAndroidSystem.getView(); - view.getHandler().post(new Runnable() { - @Override - public void run() { - - final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext()); - final EditText editTextDialogInput = new EditText(view.getContext()); - editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); - editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); - editTextDialogInput.setPadding(20, 20, 20, 20); - editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); - //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); - - editTextDialogInput.setText(initialValue); - - switch (id) { - case SoftTextDialogInput.TEXT_ENTRY_DIALOG: - - editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); - break; + view + .getHandler() + .post( + new Runnable() { + @Override + public void run() { + final FrameLayout layoutTextDialogInput = new FrameLayout(view.getContext()); + final EditText editTextDialogInput = new EditText(view.getContext()); + editTextDialogInput.setWidth(LayoutParams.FILL_PARENT); + editTextDialogInput.setHeight(LayoutParams.FILL_PARENT); + editTextDialogInput.setPadding(20, 20, 20, 20); + editTextDialogInput.setGravity(Gravity.FILL_HORIZONTAL); + //editTextDialogInput.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI); + + editTextDialogInput.setText(initialValue); + + switch (id) { + case SoftTextDialogInput.TEXT_ENTRY_DIALOG: + editTextDialogInput.setInputType(InputType.TYPE_CLASS_TEXT); + break; + case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: + editTextDialogInput.setInputType( + InputType.TYPE_CLASS_NUMBER | + InputType.TYPE_NUMBER_FLAG_DECIMAL | + InputType.TYPE_NUMBER_FLAG_SIGNED + ); + break; + case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: + editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); + break; + default: + break; + } + + layoutTextDialogInput.addView(editTextDialogInput); + + AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()) + .setTitle(title) + .setView(layoutTextDialogInput) + .setPositiveButton( + "OK", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked OK, send COMPLETE action + * and text */ + listener.onSoftText( + SoftTextDialogInputListener.COMPLETE, + editTextDialogInput.getText().toString() + ); + } + } + ) + .setNegativeButton( + "Cancel", + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int whichButton) { + /* User clicked CANCEL, send CANCEL action + * and text */ + listener.onSoftText( + SoftTextDialogInputListener.CANCEL, + editTextDialogInput.getText().toString() + ); + } + } + ) + .create(); + + dialogTextInput.show(); + } + } + ); + } - case SoftTextDialogInput.NUMERIC_ENTRY_DIALOG: + @Override + public com.jme3.opencl.Context getOpenCLContext() { + logger.warning("OpenCL is not yet supported on android"); + return null; + } - editTextDialogInput.setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL | InputType.TYPE_NUMBER_FLAG_SIGNED); - break; + /** + * 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; + } - case SoftTextDialogInput.NUMERIC_KEYPAD_DIALOG: + /** + * 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; + } - editTextDialogInput.setInputType(InputType.TYPE_CLASS_PHONE); - break; + /** + * 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"); + } - default: - break; - } + /** + * 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"); + } - layoutTextDialogInput.addView(editTextDialogInput); - - AlertDialog dialogTextInput = new AlertDialog.Builder(view.getContext()).setTitle(title).setView(layoutTextDialogInput).setPositiveButton("OK", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked OK, send COMPLETE action - * and text */ - listener.onSoftText(SoftTextDialogInputListener.COMPLETE, editTextDialogInput.getText().toString()); - } - }).setNegativeButton("Cancel", - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int whichButton) { - /* User clicked CANCEL, send CANCEL action - * and text */ - listener.onSoftText(SoftTextDialogInputListener.CANCEL, editTextDialogInput.getText().toString()); - } - }).create(); - - dialogTextInput.show(); - } - }); + /** + * 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; } @Override - public com.jme3.opencl.Context getOpenCLContext() { - logger.warning("OpenCL is not yet supported on android"); + public Displays getDisplays() { + // TODO Auto-generated method stub return null; } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidBufferImageLoader.java b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidBufferImageLoader.java index bf2abb7a4d..4dc13519da 100644 --- a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidBufferImageLoader.java +++ b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidBufferImageLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,16 +41,18 @@ import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; import java.io.IOException; -import java.io.InputStream; +import java.io.BufferedInputStream; import java.nio.ByteBuffer; /** * Loads textures using Android's Bitmap class, but does not have the * RGBA8 alpha bug. + * + * See below link for supported image formats: + * https://developer.android.com/guide/topics/media/media-formats#image-formats * * @author Kirill Vainer */ -@Deprecated public class AndroidBufferImageLoader implements AssetLoader { private final byte[] tempData = new byte[16 * 1024]; @@ -67,10 +69,10 @@ private static void convertARGBtoABGR(int[] src, int srcOff, int[] dst, int dstO } } + @Override public Object load(AssetInfo assetInfo) throws IOException { - Bitmap bitmap = null; + Bitmap bitmap; Image.Format format; - InputStream in = null; int bpp; BitmapFactory.Options options = new BitmapFactory.Options(); @@ -82,17 +84,20 @@ public Object load(AssetInfo assetInfo) throws IOException { options.inInputShareable = true; options.inPurgeable = true; options.inSampleSize = 1; - - try { - in = assetInfo.openStream(); - bitmap = BitmapFactory.decodeStream(in, null, options); + // Do not premultiply alpha channel as it is not intended + // to be directly drawn by the android view system. + options.inPremultiplied = false; + + // TODO: It is more GC friendly to reuse the Bitmap class instead of recycling + // it on every image load. Android has introduced inBitmap option For this purpose. + // However, there are certain restrictions with how inBitmap can be used. + // See https://developer.android.com/topic/performance/graphics/manage-memory#inBitmap. + + try (final BufferedInputStream bin = new BufferedInputStream(assetInfo.openStream())) { + bitmap = BitmapFactory.decodeStream(bin, null, options); if (bitmap == null) { throw new IOException("Failed to load image: " + assetInfo.getKey().getName()); } - } finally { - if (in != null) { - in.close(); - } } switch (bitmap.getConfig()) { @@ -159,6 +164,7 @@ public Object load(AssetInfo assetInfo) throws IOException { bitmap.recycle(); Image image = new Image(format, width, height, data, ColorSpace.sRGB); + return image; } } diff --git a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java index 7267d3501b..c56e51d0f0 100644 --- a/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java +++ b/jme3-android/src/main/java/com/jme3/texture/plugins/AndroidNativeImageLoader.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.texture.plugins; import com.jme3.asset.AssetInfo; @@ -9,8 +40,6 @@ /** * Native image loader to deal with filetypes that support alpha channels. - * The Android Bitmap class premultiplies the channels by the alpha when - * loading. This loader does not. * * @author iwgeric * @author Kirill Vainer @@ -25,16 +54,11 @@ public class AndroidNativeImageLoader implements AssetLoader { private static native Image load(InputStream in, boolean flipY, byte[] tmpArray) throws IOException; + @Override public Image load(AssetInfo info) throws IOException { boolean flip = ((TextureKey) info.getKey()).isFlipY(); - InputStream in = null; - try { - in = info.openStream(); - return load(info.openStream(), flip, tmpArray); - } finally { - if (in != null){ - in.close(); - } + try (final InputStream in = info.openStream()) { + return load(in, flip, tmpArray); } } } 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 73e203da54..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 @@ -79,14 +81,14 @@ public class AndroidBufferAllocator implements BufferAllocator { } } - @Override /** - * This function search the inner direct buffer of the android specific wrapped buffer classes + * Searches the inner direct buffer of the Android-specific wrapped buffer classes * and destroys it using the reflection allocator method. * * @param toBeDestroyed The direct buffer that will be "cleaned". * */ + @Override public void destroyDirectBuffer(Buffer toBeDestroyed) { // If it is a wrapped buffer, get it's inner direct buffer field and destroy it Field field = fieldIndex.get(toBeDestroyed.getClass()); diff --git a/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java b/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java index 9966be9555..69d907b8a7 100644 --- a/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java +++ b/jme3-android/src/main/java/com/jme3/util/AndroidLogHandler.java @@ -90,6 +90,9 @@ static int getAndroidLevel(Level level) { * Returns the short logger tag for the given logger name. * Traditionally loggers are named by fully-qualified Java classes; this * method attempts to return a concise identifying part of such names. + * + * @param loggerName the logger name, or null for anonymous + * @return the short logger tag */ public static String loggerNameToTag(String loggerName) { // Anonymous logger. 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/util/AndroidScreenshots.java b/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java index 8c742618ac..389822161f 100644 --- a/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java +++ b/jme3-android/src/main/java/com/jme3/util/AndroidScreenshots.java @@ -8,6 +8,12 @@ public final class AndroidScreenshots { private static final Logger logger = Logger.getLogger(AndroidScreenshots.class.getName()); + /** + * A private constructor to inhibit instantiation of this class. + */ + private AndroidScreenshots() { + } + /** * Convert OpenGL GLES20.GL_RGBA to Bitmap.Config.ARGB_8888 and store result * in a Bitmap diff --git a/jme3-android/src/main/java/com/jme3/util/RingBuffer.java b/jme3-android/src/main/java/com/jme3/util/RingBuffer.java index 1d3c22d7e5..f4b50c24d5 100644 --- a/jme3-android/src/main/java/com/jme3/util/RingBuffer.java +++ b/jme3-android/src/main/java/com/jme3/util/RingBuffer.java @@ -49,6 +49,7 @@ public T pop() { return item; } + @Override public Iterator iterator() { return new RingBufferIterator(); } @@ -58,14 +59,17 @@ private class RingBufferIterator implements Iterator { private int i = 0; + @Override public boolean hasNext() { return i < count; } + @Override public void remove() { throw new UnsupportedOperationException(); } + @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); 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/view/surfaceview/JmeSurfaceView.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java new file mode 100644 index 0000000000..67d73f4b5c --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/JmeSurfaceView.java @@ -0,0 +1,1029 @@ +/* + * Copyright (c) 2009-2025 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.view.surfaceview; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.content.DialogInterface; +import android.content.pm.ConfigurationInfo; +import android.opengl.GLSurfaceView; +import android.os.Handler; +import android.util.AttributeSet; +import android.widget.RelativeLayout; +import android.widget.Toast; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.Lifecycle; +import androidx.lifecycle.LifecycleEventObserver; +import androidx.lifecycle.LifecycleOwner; +import com.jme3.app.LegacyApplication; +import com.jme3.asset.AssetLoader; +import com.jme3.audio.AudioNode; +import com.jme3.audio.AudioRenderer; +import com.jme3.input.JoyInput; +import com.jme3.input.android.AndroidSensorJoyInput; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.system.SystemListener; +import com.jme3.system.android.JmeAndroidSystem; +import com.jme3.system.android.OGLESContext; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A RelativeLayout class holder that wraps a {@link GLSurfaceView} as a renderer UI component and uses {@link OGLESContext} as a renderer context to render + * a jme game on an android view for custom xml designs. + * The main idea of {@link JmeSurfaceView} class is to start a jMonkeyEngine application in a {@link SystemListener} context on a GL_ES thread, + * then the game is rendered and updated through a {@link GLSurfaceView} component with a delay of user's choice using a {@link Handler}, during the delay, + * the user has the ability to handle a couple of actions asynchronously as displaying a progress bar on a SplashScreen or an image or even play a preface game music of choice. + * + * @author pavl_g. + */ +public class JmeSurfaceView extends RelativeLayout implements SystemListener, DialogInterface.OnClickListener, LifecycleEventObserver { + + private static final Logger jmeSurfaceViewLogger = Logger.getLogger(JmeSurfaceView.class.getName()); + /*AppSettings attributes*/ + protected String audioRendererType = AppSettings.ANDROID_OPENAL_SOFT; + /*using {@link LegacyApplication} instead of {@link SimpleApplication} to include all classes extends LegacyApplication*/ + private LegacyApplication legacyApplication; + private AppSettings appSettings; + private int eglBitsPerPixel = 24; + private int eglAlphaBits = 0; + private int eglDepthBits = 16; + private int eglSamples = 0; + private int eglStencilBits = 0; + private int frameRate = -1; + private boolean emulateKeyBoard = true; + private boolean emulateMouse = true; + private boolean useJoyStickEvents = true; + private boolean isGLThreadPaused; + /*Late-init instances -- nullable objects*/ + private GLSurfaceView glSurfaceView; + private OGLESContext oglesContext; + private ConfigurationInfo configurationInfo; + private OnRendererCompleted onRendererCompleted; + private OnRendererStarted onRendererStarted; + private OnExceptionThrown onExceptionThrown; + private OnLayoutDrawn onLayoutDrawn; + /*Global Objects*/ + private Handler handler = new Handler(); + private RendererThread rendererThread = new RendererThread(); + private StringWriter crashLogWriter = new StringWriter(150); + /*Global flags*/ + private boolean showErrorDialog = true; + private boolean bindAppState = true; + private boolean showEscExitPrompt = true; + private boolean exitOnEscPressed = true; + /*Destruction policy flag*/ + private DestructionPolicy destructionPolicy = DestructionPolicy.DESTROY_WHEN_FINISH; + /*extra messages/data*/ + private String crashLog = ""; + private String glEsVersion = ""; + + /** + * Instantiates a default surface view holder without XML attributes. + * On instantiating this surface view, the holder is bound directly to the + * parent context life cycle. + * + * @param context the parent context. + */ + public JmeSurfaceView(@NonNull Context context) { + super(context); + //binds the view component to the holder activity life cycle + bindAppStateToActivityLifeCycle(bindAppState); + } + + /** + * Instantiates a surface view holder with XML attributes from an XML document. + * On instantiating this surface view, the holder is bound directly to the + * parent context life cycle. + * + * @param context the parent context. + * @param attrs a collection of attributes describes the tags in an XML document. + * @see android.content.res.Resources.Theme#obtainAttributes(AttributeSet, int[]) + */ + public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + //binds the view component to the holder activity life cycle + bindAppStateToActivityLifeCycle(bindAppState); + } + + /** + * Instantiates a surface view holder with XML attributes and a default style attribute. + * On instantiating this surface view, the holder is bound directly to the + * parent context life cycle. + * + * @param context the parent context. + * @param attrs a collection of attributes describes the tags in an XML document. + * @param defStyleAttr an attribute in the current theme that contains a + * reference to a style resource that supplies + * defaults values. Can be 0 to not look for defaults. + * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) + */ + public JmeSurfaceView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + //binds the view component to the holder activity life cycle + bindAppStateToActivityLifeCycle(bindAppState); + } + + /** + * Instantiates a surface view holder with XML attributes, default style attribute and a default style resource. + * On instantiating this surface view, the holder is bound directly to the + * parent context life cycle. + * + * @param context the parent context. + * @param attrs a collection of attributes describes the tags in an XML document. + * @param defStyleAttr an attribute in the current theme that contains defaults. Can be 0 to not look for defaults. + * @param defStyleRes a resource identifier of a style resource that + * supplies default values, used only if defStyleAttr is 0 or can not be found in the theme. + * Can be 0 to not look for defaults. + * @see android.content.res.Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int) + */ + public JmeSurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + //binds the view component to the holder activity life cycle + bindAppStateToActivityLifeCycle(bindAppState); + } + + /** + * Starts the jmeRenderer on a GlSurfaceView attached to a RelativeLayout. + * + * @param delayMillis delays the attachment of the surface view to the UI (RelativeLayout). + */ + public void startRenderer(int delayMillis) { + delayMillis = Math.max(0, delayMillis); + /*gets the device configuration attributes from the activity manager*/ + configurationInfo = ((ActivityManager) getContext().getSystemService(Context.ACTIVITY_SERVICE)).getDeviceConfigurationInfo(); + glEsVersion = "GL_ES Version : " + configurationInfo.getGlEsVersion(); + /*sanity check the app instance*/ + if (legacyApplication == null) { + throw new IllegalStateException("Cannot build a SurfaceView for a null app, make sure to use setLegacyApplication() to pass in your app !"); + } + /*initialize App Settings and start the Game*/ + appSettings = new AppSettings(true); + appSettings.setAudioRenderer(audioRendererType); + appSettings.setResolution(JmeSurfaceView.this.getLayoutParams().width, JmeSurfaceView.this.getLayoutParams().height); + appSettings.setAlphaBits(eglAlphaBits); + appSettings.setDepthBits(eglDepthBits); + appSettings.setSamples(eglSamples); + appSettings.setStencilBits(eglStencilBits); + appSettings.setBitsPerPixel(eglBitsPerPixel); + appSettings.setEmulateKeyboard(emulateKeyBoard); + appSettings.setEmulateMouse(emulateMouse); + appSettings.setUseJoysticks(useJoyStickEvents); + /*fetch and sanity check the static memory*/ + if (GameState.getLegacyApplication() != null) { + this.legacyApplication = GameState.getLegacyApplication(); + jmeSurfaceViewLogger.log(Level.INFO, "Old game state has been assigned as the current game state, skipping the first update"); + } else { + legacyApplication.setSettings(appSettings); + jmeSurfaceViewLogger.log(Level.INFO, "Starting a new Game State"); + /*start jme game context*/ + legacyApplication.start(); + /*fire the onStart() listener*/ + if (onRendererStarted != null) { + onRendererStarted.onRenderStart(legacyApplication, this); + } + } + /*attach the game to JmE OpenGL.Renderer context*/ + oglesContext = (OGLESContext) legacyApplication.getContext(); + /*create a glSurfaceView that will hold the renderer thread*/ + glSurfaceView = oglesContext.createView(JmeSurfaceView.this.getContext()); + /*set the current view as the system engine thread view for future uses*/ + JmeAndroidSystem.setView(JmeSurfaceView.this); + /*set JME system Listener to initialize game, update, requestClose and destroy on closure*/ + oglesContext.setSystemListener(JmeSurfaceView.this); + /*set the glSurfaceView to fit the widget*/ + glSurfaceView.setLayoutParams(new LayoutParams(JmeSurfaceView.this.getLayoutParams().width, JmeSurfaceView.this.getLayoutParams().height)); + if (GameState.getLegacyApplication() != null) { + addGlSurfaceView(); + } else { + /*post delay the attachment of the surface view on the UI*/ + handler.postDelayed(rendererThread, delayMillis); + } + } + + private void removeGLSurfaceView() { + ((Activity) getContext()).runOnUiThread(() -> JmeSurfaceView.this.removeView(glSurfaceView)); + } + + @Override + public void handleError(String errorMsg, Throwable throwable) { + throwable.printStackTrace(); + showErrorDialog(throwable, throwable.getClass().getName()); + if (onExceptionThrown != null) { + onExceptionThrown.onExceptionThrown(throwable); + } + } + + /** + * A state change observer to the holder Activity life cycle, used to keep this android view up-to-date with the holder activity life cycle. + * + * @param source the life cycle source, aka the observable object. + * @param event the fired event by the observable object, which is dispatched and sent to the observers. + */ + @Override + public void onStateChanged(@NonNull LifecycleOwner source, @NonNull Lifecycle.Event event) { + switch (event) { + case ON_DESTROY: + // activity is off the foreground stack + // activity is being destructed completely as a result of Activity#finish() + // this is a killable automata state! + jmeSurfaceViewLogger.log(Level.INFO, "Hosting Activity has been destructed."); + break; + case ON_PAUSE: + // activity is still on the foreground stack but not + // on the topmost level or before transition to stopped/hidden or destroyed state + // as a result of dispatch to Activity#finish() + // activity is no longer visible and is out of foreground + if (((Activity) getContext()).isFinishing()) { + if (destructionPolicy == DestructionPolicy.DESTROY_WHEN_FINISH) { + legacyApplication.stop(!isGLThreadPaused()); + } else if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) { + jmeSurfaceViewLogger.log(Level.INFO, "Context stops, but game is still running."); + } + } else { + loseFocus(); + } + break; + case ON_RESUME: + // activity is back to the topmost of the + // foreground stack + gainFocus(); + break; + case ON_STOP: + // activity is out off the foreground stack or being destructed by a finishing dispatch + // this is a killable automata state! + break; + } + } + + @Override + public void initialize() { + /*Invoking can be delayed by delaying the draw of GlSurfaceView component on the screen*/ + if (legacyApplication == null) { + return; + } + legacyApplication.initialize(); + /*log for display*/ + jmeSurfaceViewLogger.log(Level.INFO, "JmeGame started in GLThread Asynchronously......."); + } + + @Override + public void reshape(int width, int height) { + if (legacyApplication == null) { + return; + } + legacyApplication.reshape(width, height); + jmeSurfaceViewLogger.log(Level.INFO, "Requested reshaping from the system listener"); + } + + @Override + public void rescale(float x, float y) { + if (legacyApplication == null) { + return; + } + legacyApplication.rescale(x, y); + jmeSurfaceViewLogger.log(Level.INFO, "Requested rescaling from the system listener"); + } + + @Override + public void update() { + /*Invoking can be delayed by delaying the draw of GlSurfaceView component on the screen*/ + if (legacyApplication == null || glSurfaceView == null) { + return; + } + legacyApplication.update(); + if (!GameState.isFirstUpdatePassed()) { + ((Activity) getContext()).runOnUiThread(() -> { + jmeSurfaceViewLogger.log(Level.INFO, "User delay finishes with 0 errors"); + if (onRendererCompleted != null) { + onRendererCompleted.onRenderCompletion(legacyApplication, legacyApplication.getContext().getSettings()); + } + }); + GameState.setFirstUpdatePassed(true); + } + } + + @Override + public void requestClose(boolean esc) { + /*skip if it's not enabled or the input is null*/ + if (legacyApplication == null || (!isExitOnEscPressed())) { + return; + } + if (isShowEscExitPrompt()) { + final AlertDialog alertDialog = new AlertDialog.Builder(getContext()).create(); + alertDialog.setTitle("Exit Prompt"); + alertDialog.setMessage("Are you sure you want to quit ?"); + alertDialog.setCancelable(false); + alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "No", (dialogInterface, i) -> alertDialog.dismiss()); + alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Yes", (dialogInterface, i) -> legacyApplication.requestClose(esc)); + alertDialog.show(); + } else { + legacyApplication.requestClose(esc); + } + } + + @Override + public void gainFocus() { + /*skip the block if the instances are nullptr*/ + if (legacyApplication == null || glSurfaceView == null) { + return; + } + glSurfaceView.onResume(); + /*resume the audio*/ + final AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.resumeAll(); + } + /*resume the sensors (aka joysticks)*/ + if (legacyApplication.getContext() != null) { + final JoyInput joyInput = legacyApplication.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + final AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.resumeSensors(); + } + } + legacyApplication.gainFocus(); + } + setGLThreadPaused(false); + jmeSurfaceViewLogger.log(Level.INFO, "Game returns from the idle mode"); + } + + @Override + public void loseFocus() { + /*skip the block if the invoking instances are nullptr*/ + if (legacyApplication == null || glSurfaceView == null) { + return; + } + glSurfaceView.onPause(); + /*pause the audio*/ + legacyApplication.loseFocus(); + final AudioRenderer audioRenderer = legacyApplication.getAudioRenderer(); + if (audioRenderer != null) { + audioRenderer.pauseAll(); + } + /*pause the sensors (aka joysticks)*/ + if (legacyApplication.getContext() != null) { + final JoyInput joyInput = legacyApplication.getContext().getJoyInput(); + if (joyInput != null) { + if (joyInput instanceof AndroidSensorJoyInput) { + final AndroidSensorJoyInput androidJoyInput = (AndroidSensorJoyInput) joyInput; + androidJoyInput.pauseSensors(); + } + } + } + setGLThreadPaused(true); + jmeSurfaceViewLogger.log(Level.INFO, "Game goes idle"); + } + + @Override + public void destroy() { + if (glSurfaceView != null) { + removeGLSurfaceView(); + } + if (legacyApplication != null) { + legacyApplication.destroy(); + } + /*help the Dalvik Garbage collector to destruct the objects, by releasing their references*/ + /*context instances*/ + legacyApplication = null; + appSettings = null; + oglesContext = null; + configurationInfo = null; + /*extra data instances*/ + crashLogWriter = null; + crashLog = null; + /*nullifying helper instances and flags*/ + rendererThread = null; + destructionPolicy = null; + audioRendererType = null; + handler = null; + glEsVersion = null; + /*nullifying the event handlers*/ + onRendererStarted = null; + onRendererCompleted = null; + onExceptionThrown = null; + onLayoutDrawn = null; + GameState.setLegacyApplication(null); + GameState.setFirstUpdatePassed(false); + JmeAndroidSystem.setView(null); + jmeSurfaceViewLogger.log(Level.INFO, "Context and Game have been destructed."); + } + + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case DialogInterface.BUTTON_NEGATIVE: + dialog.dismiss(); + ((Activity) getContext()).finish(); + break; + case DialogInterface.BUTTON_POSITIVE: + dialog.dismiss(); + break; + case DialogInterface.BUTTON_NEUTRAL: + /*copy crash log button*/ + final ClipboardManager clipboardManager = (ClipboardManager) getContext().getSystemService(Context.CLIPBOARD_SERVICE); + final ClipData clipData = ClipData.newPlainText("Crash Log", crashLog); + clipboardManager.setPrimaryClip(clipData); + Toast.makeText(getContext(), "Crash Log copied to clipboard", Toast.LENGTH_SHORT).show(); + break; + } + } + + /** + * Adds the glSurfaceView to the screen immediately, saving the current app instance. + */ + protected void addGlSurfaceView() { + /*jme Renderer joins the UIThread at that point*/ + JmeSurfaceView.this.addView(glSurfaceView); + /*dispatch the layout drawn event*/ + if (onLayoutDrawn != null) { + onLayoutDrawn.onLayoutDrawn(legacyApplication, this); + } + /*set the static memory to hold the game state, only if the destruction policy uses KEEP_WHEN_FINISHED policy*/ + if (destructionPolicy == DestructionPolicy.KEEP_WHEN_FINISH) { + GameState.setLegacyApplication(legacyApplication); + } else { + GameState.setLegacyApplication(null); + } + } + + /** + * Displays an error dialog with a throwable title(error/exception), message and 3 buttons. + * 1st button is : EXIT to exit the activity and terminates the app. + * 2nd button is : DISMISS to dismiss the dialog and ignore the exception. + * 3rd button is : CopyCrashLog to copy the crash log to the clipboard. + * + * @param throwable the throwable stack. + * @param title the message title. + */ + protected void showErrorDialog(Throwable throwable, String title) { + if (!isShowErrorDialog()) { + return; + } + ((Activity) getContext()).runOnUiThread(() -> { + throwable.printStackTrace(new PrintWriter(crashLogWriter)); + crashLog = glEsVersion + "\n" + crashLogWriter.toString(); + + final AlertDialog alertDialog = new AlertDialog.Builder(getContext()).create(); + alertDialog.setTitle(glEsVersion + ", " + title); + alertDialog.setMessage(crashLog); + alertDialog.setCancelable(false); + alertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, "Exit", this); + alertDialog.setButton(DialogInterface.BUTTON_POSITIVE, "Dismiss", this); + alertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, "Copy crash log", this); + alertDialog.show(); + }); + } + + /** + * Binds/Unbinds the game life cycle to the holder activity life cycle. + * Unbinding the game life cycle, would disable {@link JmeSurfaceView#gainFocus()}, {@link JmeSurfaceView#loseFocus()} + * and {@link JmeSurfaceView#destroy()} from being invoked by the System Listener. + * The Default value is : true, and the view component is pre-bounded to its activity lifeCycle when initialized. + * + * @param condition true if you want to bind them, false otherwise. + */ + public void bindAppStateToActivityLifeCycle(final boolean condition) { + this.bindAppState = condition; + if (condition) { + /*register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/ + if (getContext() instanceof LifecycleOwner) { + ((LifecycleOwner) getContext()).getLifecycle().addObserver(JmeSurfaceView.this); + jmeSurfaceViewLogger.log(Level.INFO, "Command binding SurfaceView to the Activity Lifecycle."); + } + } else { + /*un-register this Ui Component as an observer to the context of jmeSurfaceView only if this context is a LifeCycleOwner*/ + if (getContext() instanceof LifecycleOwner) { + ((LifecycleOwner) getContext()).getLifecycle().removeObserver(JmeSurfaceView.this); + jmeSurfaceViewLogger.log(Level.INFO, "Command removing SurfaceView from the Activity Lifecycle."); + } + } + } + + /** + * Gets the current destruction policy. + * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. + * + * @return the destruction policy, either {@link DestructionPolicy#DESTROY_WHEN_FINISH} or {@link DestructionPolicy#KEEP_WHEN_FINISH}. + * @see DestructionPolicy + * @see GameState + */ + public DestructionPolicy getDestructionPolicy() { + return destructionPolicy; + } + + /** + * Sets the current destruction policy, destruction policy {@link DestructionPolicy#KEEP_WHEN_FINISH} ensures that we protect the app state + * using {@link GameState#legacyApplication} static memory when the activity finishes, while + * {@link DestructionPolicy#DESTROY_WHEN_FINISH} destroys the game context with the activity onDestroy(). + * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. + * + * @param destructionPolicy a destruction policy to set. + * @see DestructionPolicy + * @see GameState + */ + public void setDestructionPolicy(DestructionPolicy destructionPolicy) { + this.destructionPolicy = destructionPolicy; + } + + /** + * Checks whether the current game application life cycle is bound to the activity life cycle. + * + * @return true it matches the condition, false otherwise. + */ + public boolean isAppStateBoundToActivityLifeCycle() { + return bindAppState; + } + + /** + * Checks whether the system would show an exit prompt dialog when the esc keyboard input is invoked. + * + * @return ture if the exit prompt dialog is activated on exit, false otherwise. + */ + public boolean isShowEscExitPrompt() { + return showEscExitPrompt; + } + + /** + * Determines whether to show an exit prompt dialog when the esc keyboard button is invoked. + * + * @param showEscExitPrompt true to show the exit prompt dialog before exiting, false otherwise. + */ + public void setShowEscExitPrompt(boolean showEscExitPrompt) { + this.showEscExitPrompt = showEscExitPrompt; + } + + /** + * Checks whether the exit on esc press is activated. + * + * @return true if the exit on escape is activated, false otherwise. + */ + public boolean isExitOnEscPressed() { + return exitOnEscPressed; + } + + /** + * Determines whether the system would exit on pressing the keyboard esc button. + * + * @param exitOnEscPressed true to activate exiting on Esc button press, false otherwise. + */ + public void setExitOnEscPressed(boolean exitOnEscPressed) { + this.exitOnEscPressed = exitOnEscPressed; + } + + /** + * Gets the jme app instance. + * + * @return legacyApplication instance representing your game enclosure. + */ + public LegacyApplication getLegacyApplication() { + return legacyApplication; + } + + /** + * Sets the jme game instance that will be engaged into the {@link SystemListener}. + * + * @param legacyApplication your jme game instance. + */ + public void setLegacyApplication(@NonNull LegacyApplication legacyApplication) { + this.legacyApplication = legacyApplication; + } + + /** + * Gets the game window settings. + * + * @return app settings instance. + */ + public AppSettings getAppSettings() { + return appSettings; + } + + /** + * Sets the appSettings instance. + * + * @param appSettings the custom appSettings instance + */ + public void setAppSettings(@NonNull AppSettings appSettings) { + this.appSettings = appSettings; + } + + /** + * Gets the bits/pixel for Embedded gL + * + * @return integer representing it. + */ + public int getEglBitsPerPixel() { + return eglBitsPerPixel; + } + + /** + * Sets the memory representing each pixel in bits. + * + * @param eglBitsPerPixel the bits for each pixel. + */ + public void setEglBitsPerPixel(int eglBitsPerPixel) { + this.eglBitsPerPixel = eglBitsPerPixel; + } + + /** + * Gets the Embedded gL alpha(opacity) bits. + * + * @return integer representing it. + */ + public int getEglAlphaBits() { + return eglAlphaBits; + } + + /** + * Sets the memory representing the alpha of embedded gl in bits. + * + * @param eglAlphaBits the alpha bits. + */ + public void setEglAlphaBits(int eglAlphaBits) { + this.eglAlphaBits = eglAlphaBits; + } + + /** + * Gets the memory representing the EGL depth in bits. + * + * @return the depth bits. + */ + public int getEglDepthBits() { + return eglDepthBits; + } + + /** + * Sets the EGL depth in bits. + * The depth buffer or Z-buffer is basically coupled with stencil buffer, + * usually 8bits stencilBuffer + 24bits depthBuffer = 32bits shared memory. + * + * @param eglDepthBits the depth bits. + * @see JmeSurfaceView#setEglStencilBits(int) + */ + public void setEglDepthBits(int eglDepthBits) { + this.eglDepthBits = eglDepthBits; + } + + /** + * Gets the number of samples to use for multi-sampling. + * + * @return number of samples to use for multi-sampling. + */ + public int getEglSamples() { + return eglSamples; + } + + /** + * Sets the number of samples to use for multi-sampling. + * Leave 0 (default) to disable multi-sampling. + * Set to 2 or 4 to enable multi-sampling. + * + * @param eglSamples embedded gl samples bits to set. + */ + public void setEglSamples(int eglSamples) { + this.eglSamples = eglSamples; + } + + /** + * Gets the number of stencil buffer bits. + * Default is : 0. + * + * @return the stencil buffer bits. + */ + public int getEglStencilBits() { + return eglStencilBits; + } + + /** + * Sets the number of stencil buffer bits. + * Stencil buffer is used in depth-based shadow maps and shadow rendering as it limits rendering, + * it's coupled with Z-buffer or depth buffer, usually 8bits stencilBuffer + 24bits depthBuffer = 32bits shared memory. + * (default = 0) + * + * @param eglStencilBits the desired number of stencil bits. + * @see JmeSurfaceView#setEglDepthBits(int) + */ + public void setEglStencilBits(int eglStencilBits) { + this.eglStencilBits = eglStencilBits; + } + + /** + * Gets the limited FrameRate level for egl INFO. + * Default is : -1, for a device based limited value (determined by hardware). + * + * @return the limit frameRate in integers. + */ + public int getFrameRate() { + return frameRate; + } + + /** + * Limits the frame rate (fps) in the second. + * Default is : -1, for a device based limited value (determined by hardware). + * + * @param frameRate the limitation in integers. + */ + public void setFrameRate(int frameRate) { + this.frameRate = frameRate; + } + + /** + * Gets the audio renderer in String. + * Default is : {@link AppSettings#ANDROID_OPENAL_SOFT}. + * + * @return string representing audio renderer framework. + */ + public String getAudioRendererType() { + return audioRendererType; + } + + /** + * Sets the audioRenderer type. + * Default is : {@link AppSettings#ANDROID_OPENAL_SOFT}. + * + * @param audioRendererType string representing audioRenderer type. + */ + public void setAudioRendererType(String audioRendererType) { + this.audioRendererType = audioRendererType; + } + + /** + * Checks if the keyboard interfacing is enabled. + * Default is : true. + * + * @return true if the keyboard interfacing is enabled. + */ + public boolean isEmulateKeyBoard() { + return emulateKeyBoard; + } + + /** + * Enables keyboard interfacing. + * Default is : true. + * + * @param emulateKeyBoard true to enable keyboard interfacing. + */ + public void setEmulateKeyBoard(boolean emulateKeyBoard) { + this.emulateKeyBoard = emulateKeyBoard; + } + + /** + * Checks whether the mouse interfacing is enabled or not. + * Default is : true. + * + * @return true if the mouse interfacing is enabled. + */ + public boolean isEmulateMouse() { + return emulateMouse; + } + + /** + * Enables mouse interfacing. + * Default is : true. + * + * @param emulateMouse true to enable the mouse interfacing. + */ + public void setEmulateMouse(boolean emulateMouse) { + this.emulateMouse = emulateMouse; + } + + /** + * Checks whether joystick interfacing is enabled or not. + * Default is : true. + * + * @return true if the joystick interfacing is enabled. + */ + public boolean isUseJoyStickEvents() { + return useJoyStickEvents; + } + + /** + * Enables joystick interfacing for a jme-game + * + * @param useJoyStickEvents true to enable the joystick interfacing. + */ + public void setUseJoyStickEvents(boolean useJoyStickEvents) { + this.useJoyStickEvents = useJoyStickEvents; + } + + /** + * Checks whether the GLThread is paused or not. + * + * @return true/false + */ + public boolean isGLThreadPaused() { + return isGLThreadPaused; + } + + /** + * Sets GL Thread paused. + * + * @param GLThreadPaused true if you want to pause the GLThread. + */ + protected void setGLThreadPaused(boolean GLThreadPaused) { + isGLThreadPaused = GLThreadPaused; + } + + /** + * Sets the listener for the completion of rendering, ie : when the GL thread holding the {@link JmeSurfaceView} + * joins the UI thread, after asynchronous rendering. + * + * @param onRendererCompleted an instance of the interface {@link OnRendererCompleted}. + */ + public void setOnRendererCompleted(OnRendererCompleted onRendererCompleted) { + this.onRendererCompleted = onRendererCompleted; + } + + /** + * Sets the listener that will fire when an exception is thrown. + * + * @param onExceptionThrown an instance of the interface {@link OnExceptionThrown}. + */ + public void setOnExceptionThrown(OnExceptionThrown onExceptionThrown) { + this.onExceptionThrown = onExceptionThrown; + } + + /** + * Sets the listener that will fire after initializing the game. + * + * @param onRendererStarted an instance of the interface {@link OnRendererStarted}. + */ + public void setOnRendererStarted(OnRendererStarted onRendererStarted) { + this.onRendererStarted = onRendererStarted; + } + + /** + * Sets the listener that will dispatch an event when the layout is drawn by {@link JmeSurfaceView#addGlSurfaceView()}. + * + * @param onLayoutDrawn the event to be dispatched. + * @see JmeSurfaceView#addGlSurfaceView() + */ + public void setOnLayoutDrawn(OnLayoutDrawn onLayoutDrawn) { + this.onLayoutDrawn = onLayoutDrawn; + } + + /** + * Gets the current device GL_ES version. + * + * @return the current gl_es version in a string format. + */ + public String getGlEsVersion() { + return configurationInfo.getGlEsVersion(); + } + + /** + * Checks whether the error dialog is enabled upon encountering exceptions/errors. + * Default is : true. + * + * @return true if the error dialog is activated, false otherwise. + */ + public boolean isShowErrorDialog() { + return showErrorDialog; + } + + /** + * Determines whether the error dialog would be shown on encountering exceptions. + * Default is : true. + * + * @param showErrorDialog true to activate the error dialog, false otherwise. + */ + public void setShowErrorDialog(boolean showErrorDialog) { + this.showErrorDialog = showErrorDialog; + } + + /** + * Determines whether the app context would be destructed as a result of dispatching {@link Activity#finish()} + * with the holder activity context in case of {@link DestructionPolicy#DESTROY_WHEN_FINISH} or be + * spared for a second use in case of {@link DestructionPolicy#KEEP_WHEN_FINISH}. + * Default value is : {@link DestructionPolicy#DESTROY_WHEN_FINISH}. + * + * @see JmeSurfaceView#setDestructionPolicy(DestructionPolicy) + */ + public enum DestructionPolicy { + /** + * Finishes the game context with the activity context (ignores the static memory {@link GameState#legacyApplication}) + * as a result of dispatching {@link Activity#finish()}. + */ + DESTROY_WHEN_FINISH, + /** + * Spares the game context inside a static memory {@link GameState#legacyApplication} + * when the activity context is destroyed dispatching {@link Activity#finish()}, but the {@link android.app.Application} + * stills in the background. + */ + KEEP_WHEN_FINISH + } + + /** + * Used as a static memory to protect the game context from destruction by Activity#onDestroy(). + * + * @see DestructionPolicy + * @see JmeSurfaceView#setDestructionPolicy(DestructionPolicy) + */ + protected static final class GameState { + + private static LegacyApplication legacyApplication; + private static boolean firstUpdatePassed = false; + + /** + * Private constructor to inhibit instantiation of this class. + */ + private GameState() { + } + + /** + * Returns the current application state. + * + * @return game state instance, holding jME3 states (JmeContext, AssetManager, StateManager, Graphics, Sound, Input, Spatial/Nodes in place, etcetera). + */ + protected static LegacyApplication getLegacyApplication() { + return legacyApplication; + } + + /** + * Replaces the current application state. + * + * @param legacyApplication the new app instance holding the game state (including {@link AssetLoader}s, {@link AudioNode}s, {@link Spatial}s, etcetera). + */ + protected static void setLegacyApplication(LegacyApplication legacyApplication) { + GameState.legacyApplication = legacyApplication; + } + + /** + * Tests the first update flag. + * + * @return true if the firstUpdate has passed, false otherwise. + */ + protected static boolean isFirstUpdatePassed() { + return firstUpdatePassed; + } + + /** + * Adjusts the first update flag. + * + * @param firstUpdatePassed set to true to determine whether the firstUpdate has passed, false otherwise. + */ + protected static void setFirstUpdatePassed(boolean firstUpdatePassed) { + GameState.firstUpdatePassed = firstUpdatePassed; + } + } + + /** + * Delays the attachment surface view on the UI for the sake of initial frame pacing and splash screens, + * delaying the display of the game (GlSurfaceView) would lead to a substantial delay in the + * {@link android.opengl.GLSurfaceView.Renderer#onDrawFrame(javax.microedition.khronos.opengles.GL10)} which would + * delay invoking both {@link LegacyApplication#initialize()} and {@link LegacyApplication#update()}. + * + * @see JmeSurfaceView#startRenderer(int) + * @see com.jme3.system.android.OGLESContext#onDrawFrame(javax.microedition.khronos.opengles.GL10) + */ + private class RendererThread implements Runnable { + /** + * Delays the {@link GLSurfaceView} attachment on the UI thread. + * + * @see JmeSurfaceView#startRenderer(int) + */ + @Override + public void run() { + addGlSurfaceView(); + jmeSurfaceViewLogger.log(Level.INFO, "JmeSurfaceView's joined the UI thread"); + } + } +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java new file mode 100644 index 0000000000..a0174b074a --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnExceptionThrown.java @@ -0,0 +1,47 @@ +/* + * 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.view.surfaceview; + +/** + * An interface designed to listen for exceptions and fire an event when an exception is thrown. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnExceptionThrown(OnExceptionThrown) + */ +public interface OnExceptionThrown { + /** + * Listens for a thrown exception or a thrown error. + * + * @param e the exception or the error that is throwable. + */ + void onExceptionThrown(Throwable e); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java new file mode 100644 index 0000000000..14a7d9ca58 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnLayoutDrawn.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2022 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.view.surfaceview; + +import android.view.View; +import com.jme3.app.LegacyApplication; + +/** + * An interface used for dispatching an event when the layout holding the {@link android.opengl.GLSurfaceView} is drawn, + * the event is dispatched on the user activity context thread. + * + * @author pavl_g. + */ +public interface OnLayoutDrawn { + /** + * Dispatched when the layout is drawn on the screen. + * + * @param legacyApplication the application instance. + * @param layout the current layout. + */ + void onLayoutDrawn(LegacyApplication legacyApplication, View layout); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.java new file mode 100644 index 0000000000..284b8e97e5 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererCompleted.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.view.surfaceview; + +import com.jme3.app.LegacyApplication; +import com.jme3.system.AppSettings; + +/** + * An interface used for invoking an event when the user delay finishes, on the first update of the game. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnRendererCompleted(OnRendererCompleted) + */ +public interface OnRendererCompleted { + /** + * Invoked when the user delay finishes, on the first update of the game, the event is dispatched on the + * enclosing Activity context thread. + * + * @param application the current jme game instance. + * @param appSettings the current window settings of the running jme game. + * @see JmeSurfaceView#update() + */ + void onRenderCompletion(LegacyApplication application, AppSettings appSettings); +} diff --git a/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java new file mode 100644 index 0000000000..d92fd19479 --- /dev/null +++ b/jme3-android/src/main/java/com/jme3/view/surfaceview/OnRendererStarted.java @@ -0,0 +1,55 @@ +/* + * 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.view.surfaceview; + +import android.view.View; +import com.jme3.app.LegacyApplication; + +/** + * An interface used for invoking an event when the application is started explicitly from {@link JmeSurfaceView#startRenderer(int)}. + * NB : This listener must be utilized before using {@link JmeSurfaceView#startRenderer(int)}, ie : it would be ignored if you try to use {@link JmeSurfaceView#setOnRendererStarted(OnRendererStarted)} after + * {@link JmeSurfaceView#startRenderer(int)}. + * + * @author pavl_g. + * @see JmeSurfaceView#setOnRendererStarted(OnRendererStarted) + */ +public interface OnRendererStarted { + /** + * Invoked when the game application is started by the {@link LegacyApplication#start()}, the event is dispatched on the + * holder Activity context thread. + * + * @param application the game instance. + * @param layout the enclosing layout. + * @see JmeSurfaceView#startRenderer(int) + */ + void onRenderStart(LegacyApplication application, View layout); +} 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 b793e9f0f5..7234abfaa7 100644 --- a/jme3-android/src/main/resources/com/jme3/asset/Android.cfg +++ b/jme3-android/src/main/resources/com/jme3/asset/Android.cfg @@ -5,4 +5,5 @@ LOCATOR / com.jme3.asset.plugins.AndroidLocator # Android specific loaders LOADER com.jme3.texture.plugins.AndroidNativeImageLoader : jpg, bmp, gif, png, jpeg -LOADER com.jme3.audio.plugins.NativeVorbisLoader : ogg +LOADER com.jme3.texture.plugins.AndroidBufferImageLoader : webp, heic, heif +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-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java new file mode 100644 index 0000000000..efbb721054 --- /dev/null +++ b/jme3-awt-dialogs/src/main/java/com/jme3/awt/AWTSettingsDialog.java @@ -0,0 +1,1030 @@ +/* + * Copyright (c) 2009-2025 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 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; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.MessageFormat; +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; +import javax.swing.*; + +/** + * `AWTSettingsDialog` displays a Swing dialog box to interactively + * configure the `AppSettings` of a desktop application before + * `start()` is invoked. + *

+ * The `AppSettings` instance to be configured is passed to the constructor. + * + * @see AppSettings + * @author Mark Powell + * @author Eric Woroshow + * @author Joshua Slack - reworked for proper use of GL commands. + */ +public final class AWTSettingsDialog extends JFrame { + + /** + * Listener interface for handling selection events from the settings dialog. + */ + public interface SelectionListener { + /** + * Called when a selection is made in the settings dialog (OK or Cancel). + * + * @param selection The type of selection made: `NO_SELECTION`, `APPROVE_SELECTION`, or `CANCEL_SELECTION`. + */ + void onSelection(int selection); + } + + private static final Logger logger = Logger.getLogger(AWTSettingsDialog.class.getName()); + private static final long serialVersionUID = 1L; + + /** + * Indicates that no selection has been made yet. + */ + public static final int NO_SELECTION = 0; + /** + * Indicates that the user approved the settings. + */ + public static final int APPROVE_SELECTION = 1; + /** + * Indicates that the user canceled the settings dialog. + */ + public static final int CANCEL_SELECTION = 2; + + // Resource bundle for i18n. + ResourceBundle resourceBundle = ResourceBundle.getBundle("com.jme3.app/SettingsDialog"); + + // the instance being configured + private final AppSettings source; + + /** + * The URL of the image file to be displayed as a title icon in the dialog. + * Can be `null` if no image is desired. + */ + private URL imageFile = null; + + // Array of supported display modes + private DisplayMode[] modes = null; + private static final DisplayMode[] windowDefaults = new DisplayMode[] { + new DisplayMode(1024, 768, 24, 60), + new DisplayMode(1280, 720, 24, 60), + new DisplayMode(1280, 1024, 24, 60), + new DisplayMode(1440, 900, 24, 60), + new DisplayMode(1680, 1050, 24, 60), + }; + private DisplayMode[] windowModes = null; + + // UI components + private JCheckBox vsyncBox = null; + private JCheckBox gammaBox = null; + private JCheckBox fullscreenBox = null; + private JComboBox displayResCombo = null; + private JComboBox colorDepthCombo = null; + private JComboBox displayFreqCombo = null; + private JComboBox antialiasCombo = null; + private JLabel icon = null; + private int selection = 0; + private SelectionListener selectionListener = null; + + private int minWidth = 0; + private int minHeight = 0; + + /** + * Displays a settings dialog using the provided `AppSettings` source. + * Settings will be loaded from preferences. + * + * @param sourceSettings The `AppSettings` instance to configure. + * @return `true` if the user approved the settings, `false` otherwise. + */ + public static boolean showDialog(AppSettings sourceSettings) { + return showDialog(sourceSettings, true); + } + + /** + * Displays a settings dialog using the provided `AppSettings` source. + * + * @param sourceSettings The `AppSettings` instance to configure. + * @param loadSettings If `true`, settings will be loaded from preferences; otherwise, they will be merged. + * @return `true` if the user approved the settings, `false` otherwise. + */ + 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); + } + + /** + * Displays a settings dialog using the provided `AppSettings` source and an image file path. + * + * @param sourceSettings The `AppSettings` instance to configure. + * @param imageFile The path to the image file to use as the title of the dialog; + * `null` will result in no image being displayed. + * @param loadSettings If `true`, settings will be loaded from preferences; otherwise, they will be merged. + * @return `true` if the user approved the settings, `false` otherwise. + */ + public static boolean showDialog(AppSettings sourceSettings, String imageFile, boolean loadSettings) { + return showDialog(sourceSettings, getURL(imageFile), loadSettings); + } + + /** + * Displays a settings dialog using the provided `AppSettings` source and an image URL. + * This method blocks until the dialog is closed. + * + * @param sourceSettings The `AppSettings` instance to configure (not null). + * @param imageFile The `URL` pointing to the image file to use as the title of the dialog; + * `null` will result in no image being displayed. + * @param loadSettings If `true`, the dialog will copy settings from preferences. If `false` + * and preferences exist, they will be merged with the current settings. + * @return `true` if the user approved the settings, `false` otherwise (`CANCEL_SELECTION` or dialog close). + */ + 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 { + // Wait until notified by the selection listener + lock.wait(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + logger.log(Level.WARNING, "Settings dialog thread interrupted while waiting.", ex); + return false; // Treat as cancel if interrupted + } + } + } + + // If approved, copy the modified settings back to the original source + if (result.get() == APPROVE_SELECTION) { + sourceSettings.copyFrom(settings); + } + + return result.get() == APPROVE_SELECTION; + } + + /** + * Constructs a `SettingsDialog` for the primary display. + * + * @param source The `AppSettings` instance to configure (not null). + * @param imageFile The path to the image file to use as the title of the dialog; + * `null` will result in no image being displayed. + * @param loadSettings If `true`, the dialog will copy settings from preferences. If `false` + * and preferences exist, they will be merged with the current settings. + * @throws IllegalArgumentException if `source` is `null`. + */ + protected AWTSettingsDialog(AppSettings source, String imageFile, boolean loadSettings) { + this(source, getURL(imageFile), loadSettings); + } + + /** + * Constructs a `SettingsDialog` for the primary display. + * + * @param source The `AppSettings` instance to configure (not null). + * @param imageFile The `URL` pointing to the image file to use as the title of the dialog; + * `null` will result in no image being displayed. + * @param loadSettings If `true`, the dialog will copy settings from preferences. If `false` + * and preferences exist, they will be merged with the current settings. + * @throws IllegalArgumentException if `source` is `null`. + */ + protected AWTSettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { + if (source == null) { + throw new IllegalArgumentException("Settings source cannot be null"); + } + + this.source = source; + this.imageFile = imageFile; + + // setModal(true); + setAlwaysOnTop(true); + setResizable(false); + + AppSettings registrySettings = new AppSettings(true); + + String appTitle; + if (source.getTitle() != null) { + appTitle = source.getTitle(); + } else { + appTitle = registrySettings.getTitle(); + } + + minWidth = source.getMinWidth(); + minHeight = source.getMinHeight(); + + try { + logger.log(Level.INFO, "Loading AppSettings from PreferenceKey: {0}", appTitle); + registrySettings.load(appTitle); + AppSettings.printPreferences(appTitle); + + } catch (BackingStoreException ex) { + logger.log(Level.WARNING, "Failed to load settings", ex); + } + + if (loadSettings) { + source.copyFrom(registrySettings); + } else if (!registrySettings.isEmpty()) { + source.mergeFrom(registrySettings); + } + + GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + + modes = device.getDisplayModes(); + 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 < merged.length && (wdIndex < windowDefaults.length || dmIndex < modes.length); mergedIndex++) { + + if (dmIndex >= modes.length) { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } else if (wdIndex >= windowDefaults.length) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getWidth() < windowDefaults[wdIndex].getWidth()) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getWidth() == windowDefaults[wdIndex].getWidth()) { + if (modes[dmIndex].getHeight() < windowDefaults[wdIndex].getHeight()) { + merged[mergedIndex] = modes[dmIndex++]; + } else if (modes[dmIndex].getHeight() == windowDefaults[wdIndex].getHeight()) { + merged[mergedIndex] = modes[dmIndex++]; + wdIndex++; + } else { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } + } else { + merged[mergedIndex] = windowDefaults[wdIndex++]; + } + } + + if (merged.length == mergedIndex) { + windowModes = merged; + } else { + windowModes = Arrays.copyOfRange(merged, 0, mergedIndex); + } + + createUI(); + } + + public void setSelectionListener(SelectionListener sl) { + this.selectionListener = sl; + } + + public int getUserSelection() { + return selection; + } + + private void setUserSelection(int selection) { + this.selection = selection; + selectionListener.onSelection(selection); + } + + public int getMinWidth() { + return minWidth; + } + + public void setMinWidth(int minWidth) { + this.minWidth = minWidth; + } + + public int getMinHeight() { + return minHeight; + } + + public void setMinHeight(int minHeight) { + this.minHeight = minHeight; + } + + /** + * setImage sets the background image of the dialog. + * + * @param image + * String representing the image file. + */ + public void setImage(String image) { + try { + URL file = new URL("file:" + image); + setImage(file); + } catch (MalformedURLException e) { + logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e); + } + } + + /** + * setImage sets the background image of this dialog. + * + * @param image + * URL pointing to the image file. + */ + public void setImage(URL image) { + icon.setIcon(new ImageIcon(image)); + pack(); // Resize to accommodate the new image + setLocationRelativeTo(null); // put in center + } + + /** + * showDialog sets this dialog as visible, and brings it to the + * front. + */ + public void showDialog() { + setLocationRelativeTo(null); + setVisible(true); + toFront(); + } + + /** + * init creates the components to use the dialog. + */ + private void createUI() { + JPanel mainPanel = new JPanel(new GridBagLayout()); + + addWindowListener(new WindowAdapter() { + + @Override + public void windowClosing(WindowEvent e) { + setUserSelection(CANCEL_SELECTION); + dispose(); + } + }); + + Object[] sourceIcons = source.getIcons(); + if (sourceIcons != null && sourceIcons.length > 0) { + safeSetIconImages(Arrays.asList((BufferedImage[]) sourceIcons)); + } + + setTitle(MessageFormat.format(resourceBundle.getString("frame.title"), source.getTitle())); + + // The buttons... + 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); + + KeyListener aListener = new KeyAdapter() { + + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + if (verifyAndSaveCurrentSelection()) { + setUserSelection(APPROVE_SELECTION); + dispose(); + } + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + setUserSelection(CANCEL_SELECTION); + dispose(); + } + } + }; + + displayResCombo = setUpResolutionChooser(); + displayResCombo.addKeyListener(aListener); + colorDepthCombo = new JComboBox<>(); + colorDepthCombo.addKeyListener(aListener); + displayFreqCombo = new JComboBox<>(); + displayFreqCombo.addKeyListener(aListener); + antialiasCombo = new JComboBox<>(); + antialiasCombo.addKeyListener(aListener); + fullscreenBox = new JCheckBox(resourceBundle.getString("checkbox.fullscreen")); + fullscreenBox.setSelected(source.isFullscreen()); + fullscreenBox.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + updateResolutionChoices(); + } + }); + vsyncBox = new JCheckBox(resourceBundle.getString("checkbox.vsync")); + vsyncBox.setSelected(source.isVSync()); + + gammaBox = new JCheckBox(resourceBundle.getString("checkbox.gamma")); + gammaBox.setSelected(source.isGammaCorrection()); + + GridBagConstraints gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 0; + gbc.gridwidth = 2; + gbc.gridy = 1; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(fullscreenBox, gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + // gbc.insets = new Insets(4, 16, 0, 4); + gbc.gridx = 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.anchor = GridBagConstraints.WEST; + mainPanel.add(gammaBox, gbc); + + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 4, 4, 4); + gbc.gridx = 0; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.EAST; + gbc.weightx = 0.5; + mainPanel.add(new JLabel(resourceBundle.getString("label.resolutions")), gbc); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(displayResCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.colordepth")), gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 3; + gbc.gridy = 2; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(colorDepthCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 4, 4, 4); + gbc.weightx = 0.5; + gbc.gridx = 0; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.refresh")), gbc); + gbc = new GridBagConstraints(); + gbc.gridx = 1; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(displayFreqCombo, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridy = 3; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(new JLabel(resourceBundle.getString("label.antialias")), gbc); + gbc = new GridBagConstraints(); + gbc.weightx = 0.5; + gbc.gridx = 3; + 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() { + @Override + 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, + + // intentional double call. see this discussion: + // https://hub.jmonkeyengine.org/t/experimenting-lwjgl3/37275/12 + System.gc(); + System.gc(); + } + } + }); + + cancel.addActionListener(new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + setUserSelection(CANCEL_SELECTION); + dispose(); + } + }); + + gbc = new GridBagConstraints(); + gbc.gridx = 0; + gbc.gridwidth = 2; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.EAST; + mainPanel.add(ok, gbc); + gbc = new GridBagConstraints(); + gbc.insets = new Insets(4, 16, 4, 4); + gbc.gridx = 2; + gbc.gridwidth = 2; + gbc.gridy = 4; + gbc.anchor = GridBagConstraints.WEST; + mainPanel.add(cancel, gbc); + + if (icon != null) { + gbc = new GridBagConstraints(); + gbc.gridwidth = 4; + mainPanel.add(icon, gbc); + } + + 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. + updateResolutionChoices(); + if (source.getWidth() != 0 && source.getHeight() != 0) { + displayResCombo.setSelectedItem(source.getWidth() + " x " + source.getHeight()); + } else { + 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 + */ + 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. + Window owner = getOwner(); + if (owner != null) { + Method setIconImages = owner.getClass().getMethod("setIconImages", List.class); + setIconImages.invoke(owner, icons); + return; + } + + Method setIconImages = getClass().getMethod("setIconImages", List.class); + setIconImages.invoke(this, icons); + } catch (Exception e) { + logger.log(Level.WARNING, "Error setting icon images", e); + } + } + + /** + * verifyAndSaveCurrentSelection first verifies that the + * display mode is valid for this system, and then saves the current + * selection to the backing store. + * + * @return if the selection is valid + */ + private boolean verifyAndSaveCurrentSelection() { + String display = (String) displayResCombo.getSelectedItem(); + boolean fullscreen = fullscreenBox.isSelected(); + boolean vsync = vsyncBox.isSelected(); + boolean gamma = gammaBox.isSelected(); + + String[] parts = display.split(" x "); + int width = Integer.parseInt(parts[0]); + int height = Integer.parseInt(parts[1]); + + String depthString = (String) colorDepthCombo.getSelectedItem(); + int depth = -1; + if (depthString.equals("???")) { + depth = 0; + } else { + depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' '))); + } + + String freqString = (String) displayFreqCombo.getSelectedItem(); + int freq = -1; + if (fullscreen) { + if (freqString.equals("???")) { + freq = 0; + } else { + freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' '))); + } + } + + String aaString = (String) antialiasCombo.getSelectedItem(); + int multisample = -1; + if (aaString.equals(resourceBundle.getString("antialias.disabled"))) { + multisample = 0; + } else { + multisample = Integer.parseInt(aaString.substring(0, aaString.indexOf('x'))); + } + + // FIXME: Does not work in Linux +// if (!fullscreen) { //query the current bit depth of the desktop int +// curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment() +// .getDefaultScreenDevice().getDisplayMode().getBitDepth(); +// if (depth > curDepth) { +// showError(this, "Cannot choose a higher bit depth in +// windowed" + "mode than your current desktop bit depth"); +// return false; +// } +// } + + boolean valid = true; + + // test valid display mode when going full screen + if (fullscreen) { + GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); + valid = device.isFullScreenSupported(); + } + + if (valid) { + // use the AppSettings class to save it to backing store + source.setWidth(width); + source.setHeight(height); + source.setBitsPerPixel(depth); + source.setFrequency(freq); + source.setFullscreen(fullscreen); + source.setVSync(vsync); + source.setGammaCorrection(gamma); + // source.setRenderer(renderer); + source.setSamples(multisample); + + String appTitle = source.getTitle(); + + try { + logger.log(Level.INFO, "Saving AppSettings to PreferencesKey: {0}", appTitle); + source.save(appTitle); + AppSettings.printPreferences(appTitle); + + } catch (BackingStoreException ex) { + logger.log(Level.WARNING, "Failed to save setting changes", ex); + } + } else { + showError(this, resourceBundle.getString("error.unsupportedmode")); + } + + return valid; + } + + /** + * 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() { + JComboBox resolutionBox = new JComboBox<>(); + + resolutionBox.addActionListener(new ActionListener() { + + @Override + public void actionPerformed(ActionEvent e) { + updateDisplayChoices(); + } + }); + + return resolutionBox; + } + + /** + * updateDisplayChoices updates the available color depth and + * display frequency options to match the currently selected resolution. + */ + private void updateDisplayChoices() { + if (!fullscreenBox.isSelected()) { + // don't run this function when changing windowed settings + return; + } + String resolution = (String) displayResCombo.getSelectedItem(); + String colorDepth = (String) colorDepthCombo.getSelectedItem(); + if (colorDepth == null) { + colorDepth = source.getBitsPerPixel() + " bpp"; + } + String displayFreq = (String) displayFreqCombo.getSelectedItem(); + if (displayFreq == null) { + displayFreq = source.getFrequency() + " Hz"; + } + + // grab available depths + String[] depths = getDepths(resolution, modes); + colorDepthCombo.setModel(new DefaultComboBoxModel<>(depths)); + colorDepthCombo.setSelectedItem(colorDepth); + // grab available frequencies + String[] freqs = getFrequencies(resolution, modes); + 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. + displayFreqCombo.setSelectedItem(getBestFrequency(resolution, modes)); + } + } + + /** + * updateResolutionChoices updates the available resolutions + * list to match the currently selected window mode (fullscreen or + * windowed). It then sets up a list of standard options (if windowed) or + * calls updateDisplayChoices (if fullscreen). + */ + private void updateResolutionChoices() { + if (!fullscreenBox.isSelected()) { + displayResCombo.setModel(new DefaultComboBoxModel<>(getWindowedResolutions(windowModes))); + if (displayResCombo.getItemCount() > 0) { + 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") })); + displayFreqCombo.setEnabled(false); + } else { + displayResCombo.setModel(new DefaultComboBoxModel<>(getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE))); + if (displayResCombo.getItemCount() > 0) { + displayResCombo.setSelectedIndex(displayResCombo.getItemCount() - 1); + } + displayFreqCombo.setEnabled(true); + updateDisplayChoices(); + } + } + + 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" + }; + antialiasCombo.setModel(new DefaultComboBoxModel<>(choices)); + antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples() / 2, 5)]); + } + + // + // Utility methods + // + /** + * Utility method for converting a String denoting a file into a URL. + * + * @return a URL pointing to the file or null + */ + private static URL getURL(String file) { + URL url = null; + try { + url = new URL("file:" + file); + } catch (MalformedURLException e) { + logger.log(Level.WARNING, "Invalid file name '" + file + "'", e); + } + return url; + } + + /** + * Displays an error message dialog to the user. + * + * @param parent The parent `Component` for the dialog. + * @param message The message `String` to display. + */ + private static void showError(java.awt.Component parent, String 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. + */ + private String[] getResolutions(DisplayMode[] modes, int heightLimit, int widthLimit) { + Insets insets = getInsets(); + heightLimit -= insets.top + insets.bottom; + widthLimit -= insets.left + insets.right; + + Set resolutions = new LinkedHashSet<>(modes.length); + for (DisplayMode mode : modes) { + int height = mode.getHeight(); + int width = mode.getWidth(); + if (width >= minWidth && height >= minHeight) { + if (height >= heightLimit) { + height = heightLimit; + } + if (width >= widthLimit) { + width = widthLimit; + } + + String res = width + " x " + height; + resolutions.add(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. + */ + private String[] getWindowedResolutions(DisplayMode[] modes) { + int maxHeight = 0; + int maxWidth = 0; + + for (DisplayMode mode : modes) { + if (maxHeight < mode.getHeight()) { + maxHeight = mode.getHeight(); + } + if (maxWidth < mode.getWidth()) { + maxWidth = mode.getWidth(); + } + } + + return getResolutions(modes, maxHeight, maxWidth); + } + + /** + * Returns every possible bit depth for the given resolution. + */ + private static String[] getDepths(String resolution, DisplayMode[] modes) { + Set depths = new LinkedHashSet<>(4); // Use LinkedHashSet for uniqueness and order + 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 (bitDepth < 16 && bitDepth > 0) { + continue; + } + String res = mode.getWidth() + " x " + mode.getHeight(); + if (res.equals(resolution)) { + depths.add(bitDepth + " bpp"); + } + } + + if (depths.isEmpty()) { + // add some default depths, possible system is multi-depth + // supporting + depths.add("24 bpp"); + } + + return depths.toArray(new String[0]); + } + + /** + * Returns every possible unique refresh rate string ("XX Hz" or "???") + * for the given resolution from an array of `DisplayMode`s. + * + * @param resolution The resolution string (e.g., "1280 x 720") to filter by. + * @param modes The array of `DisplayMode`s to process. + * @return An array of unique refresh rate strings. + */ + private static String[] getFrequencies(String resolution, DisplayMode[] modes) { + Set freqs = new LinkedHashSet<>(4); // Use LinkedHashSet for uniqueness and order + for (DisplayMode mode : modes) { + String res = mode.getWidth() + " x " + mode.getHeight(); + String freq; + if (mode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { + freq = "???"; + } else { + freq = mode.getRefreshRate() + " Hz"; + } + freqs.add(freq); + } + + return freqs.toArray(new String[0]); + } + + /** + * Chooses the closest known refresh rate to 60 Hz for a given resolution. + * If no known refresh rates are found for the resolution, returns `null`. + * + * @param resolution The resolution string (e.g., "1280 x 720") to find the best frequency for. + * @param modes The array of `DisplayMode`s to search within. + * @return The best frequency string (e.g., "60 Hz") or `null` if no suitable frequency is found. + */ + private static String getBestFrequency(String resolution, DisplayMode[] modes) { + int closest = Integer.MAX_VALUE; + int desired = 60; + 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 = mode.getRefreshRate(); + } + } + } + + if (closest != Integer.MAX_VALUE) { + return closest + " Hz"; + } else { + return null; + } + } + + /** + * Utility class for sorting DisplayModes. Sorts by resolution, + * then bit depth, and then finally refresh rate. + */ + private class DisplayModeSorter implements Comparator { + + /** + * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) + */ + @Override + public int compare(DisplayMode a, DisplayMode b) { + // Width + if (a.getWidth() != b.getWidth()) { + return (a.getWidth() > b.getWidth()) ? 1 : -1; + } + // Height + if (a.getHeight() != b.getHeight()) { + return (a.getHeight() > b.getHeight()) ? 1 : -1; + } + // Bit depth + if (a.getBitDepth() != b.getBitDepth()) { + return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1; + } + // Refresh rate + if (a.getRefreshRate() != b.getRefreshRate()) { + return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1; + } + // All fields are equal + return 0; + } + } +} 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-blender/build.gradle b/jme3-blender/build.gradle deleted file mode 100644 index a556d7a651..0000000000 --- a/jme3-blender/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -dependencies { - compile project(':jme3-core') - compile project(':jme3-desktop') - compile project(':jme3-effects') - compile ('org.ejml:core:0.27') - compile ('org.ejml:dense64:0.27') - compile ('org.ejml:simple:0.27') -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java b/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java deleted file mode 100644 index 6dbb205b54..0000000000 --- a/jme3-blender/src/main/java/com/jme3/asset/BlenderKey.java +++ /dev/null @@ -1,733 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.asset; - -import java.io.IOException; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.material.Material; -import com.jme3.material.RenderState.FaceCullMode; - -/** - * Blender key. Contains path of the blender file and its loading properties. - * @author Marcin Roguski (Kaelthas) - */ -public class BlenderKey extends ModelKey { - protected static final int DEFAULT_FPS = 25; - /** - * FramesPerSecond parameter describe how many frames there are in each second. It allows to calculate the time - * between the frames. - */ - protected int fps = DEFAULT_FPS; - /** - * This variable is a bitwise flag of FeatureToLoad interface values; By default everything is being loaded. - */ - protected int featuresToLoad = FeaturesToLoad.ALL; - /** The variable that tells if content of the file (along with data unlinked to any feature on the scene) should be stored as 'user data' in the result spatial. */ - protected boolean loadUnlinkedAssets; - /** The root path for all the assets. */ - protected String assetRootPath; - /** This variable indicate if Y axis is UP axis. If not then Z is up. By default set to true. */ - protected boolean fixUpAxis = true; - /** Generated textures resolution (PPU - Pixels Per Unit). */ - protected int generatedTexturePPU = 128; - /** - * The name of world settings that the importer will use. If not set or specified name does not occur in the file - * then the first world settings in the file will be used. - */ - protected String usedWorld; - /** - * User's default material that is set for objects that have no material definition in blender. The default value is - * null. If the value is null the importer will use its own default material (gray color - like in blender). - */ - protected Material defaultMaterial; - /** Face cull mode. By default it is disabled. */ - protected FaceCullMode faceCullMode = FaceCullMode.Back; - /** - * Variable describes which layers will be loaded. N-th bit set means N-th layer will be loaded. - * If set to -1 then the current layer will be loaded. - */ - protected int layersToLoad = -1; - /** A variable that toggles the object custom properties loading. */ - protected boolean loadObjectProperties = true; - /** - * Maximum texture size. Might be dependant on the graphic card. - * This value is taken from org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE. - */ - protected int maxTextureSize = 8192; - /** Allows to toggle generated textures loading. Disabled by default because it very often takes too much memory and needs to be used wisely. */ - protected boolean loadGeneratedTextures; - /** Tells if the mipmaps will be generated by jme or not. By default generation is dependant on the blender settings. */ - protected MipmapGenerationMethod mipmapGenerationMethod = MipmapGenerationMethod.GENERATE_WHEN_NEEDED; - /** - * If the sky has only generated textures applied then they will have the following size (both width and height). If 2d textures are used then the generated - * textures will get their proper size. - */ - protected int skyGeneratedTextureSize = 1000; - /** The radius of a shape that will be used while creating the generated texture for the sky. The higher it is the larger part of the texture will be seen. */ - protected float skyGeneratedTextureRadius = 1; - /** The shape against which the generated texture for the sky will be created. */ - protected SkyGeneratedTextureShape skyGeneratedTextureShape = SkyGeneratedTextureShape.SPHERE; - /** - * This field tells if the importer should optimise the use of textures or not. If set to true, then textures of the same mapping type will be merged together - * and textures that in the final result will never be visible - will be discarded. - */ - protected boolean optimiseTextures; - /** The method of matching animations to skeletons. The default value is: AT_LEAST_ONE_NAME_MATCH. */ - protected AnimationMatchMethod animationMatchMethod = AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH; - /** The size of points that are loaded and do not belong to any edge of the mesh. */ - protected float pointsSize = 1; - /** The width of edges that are loaded from the mesh and do not belong to any face. */ - protected float linesWidth = 1; - - /** - * Constructor used by serialization mechanisms. - */ - public BlenderKey() { - } - - /** - * Constructor. Creates a key for the given file name. - * @param name - * the name (path) of a file - */ - public BlenderKey(String name) { - super(name); - } - - /** - * This method returns frames per second amount. The default value is BlenderKey.DEFAULT_FPS = 25. - * @return the frames per second amount - */ - public int getFps() { - return fps; - } - - /** - * This method sets frames per second amount. - * @param fps - * the frames per second amount - */ - public void setFps(int fps) { - this.fps = fps; - } - - /** - * This method returns the face cull mode. - * @return the face cull mode - */ - public FaceCullMode getFaceCullMode() { - return faceCullMode; - } - - /** - * This method sets the face cull mode. - * @param faceCullMode - * the face cull mode - */ - public void setFaceCullMode(FaceCullMode faceCullMode) { - this.faceCullMode = faceCullMode; - } - - /** - * This method sets layers to be loaded. - * @param layersToLoad - * layers to be loaded - */ - public void setLayersToLoad(int layersToLoad) { - this.layersToLoad = layersToLoad; - } - - /** - * This method returns layers to be loaded. - * @return layers to be loaded - */ - public int getLayersToLoad() { - return layersToLoad; - } - - /** - * This method sets the properies loading policy. - * By default the value is true. - * @param loadObjectProperties - * true to load properties and false to suspend their loading - */ - public void setLoadObjectProperties(boolean loadObjectProperties) { - this.loadObjectProperties = loadObjectProperties; - } - - /** - * @return the current properties loading properties - */ - public boolean isLoadObjectProperties() { - return loadObjectProperties; - } - - /** - * The default value for this parameter is the same as defined by: org.lwjgl.opengl.GL11.GL_MAX_TEXTURE_SIZE. - * If by any means this is too large for user's hardware configuration use the 'setMaxTextureSize' method to change that. - * @return maximum texture size (width/height) - */ - public int getMaxTextureSize() { - return maxTextureSize; - } - - /** - * This method sets the maximum texture size. - * @param maxTextureSize - * the maximum texture size - */ - public void setMaxTextureSize(int maxTextureSize) { - this.maxTextureSize = maxTextureSize; - } - - /** - * This method sets the flag that toggles the generated textures loading. - * @param loadGeneratedTextures - * true if generated textures should be loaded and false otherwise - */ - public void setLoadGeneratedTextures(boolean loadGeneratedTextures) { - this.loadGeneratedTextures = loadGeneratedTextures; - } - - /** - * @return tells if the generated textures should be loaded (false is the default value) - */ - public boolean isLoadGeneratedTextures() { - return loadGeneratedTextures; - } - - /** - * Not used any more. - * This method sets the asset root path. - * @param assetRootPath - * the assets root path - */ - @Deprecated - public void setAssetRootPath(String assetRootPath) { - this.assetRootPath = assetRootPath; - } - - /** - * Not used any more. - * This method returns the asset root path. - * @return the asset root path - */ - @Deprecated - public String getAssetRootPath() { - return assetRootPath; - } - - /** - * This method adds features to be loaded. - * @param featuresToLoad - * bitwise flag of FeaturesToLoad interface values - */ - @Deprecated - public void includeInLoading(int featuresToLoad) { - this.featuresToLoad |= featuresToLoad; - } - - /** - * This method removes features from being loaded. - * @param featuresNotToLoad - * bitwise flag of FeaturesToLoad interface values - */ - @Deprecated - public void excludeFromLoading(int featuresNotToLoad) { - featuresToLoad &= ~featuresNotToLoad; - } - - @Deprecated - public boolean shouldLoad(int featureToLoad) { - return (featuresToLoad & featureToLoad) != 0; - } - - /** - * This method returns bitwise value of FeaturesToLoad interface value. It describes features that will be loaded by - * the blender file loader. - * @return features that will be loaded by the blender file loader - */ - @Deprecated - public int getFeaturesToLoad() { - return featuresToLoad; - } - - /** - * This method determines if unlinked assets should be loaded. - * If not then only objects on selected layers will be loaded and their assets if required. - * If yes then all assets will be loaded even if they are on inactive layers or are not linked - * to anything. - * @return true if unlinked assets should be loaded and false otherwise - */ - public boolean isLoadUnlinkedAssets() { - return loadUnlinkedAssets; - } - - /** - * This method sets if unlinked assets should be loaded. - * If not then only objects on selected layers will be loaded and their assets if required. - * If yes then all assets will be loaded even if they are on inactive layers or are not linked - * to anything. - * @param loadUnlinkedAssets - * true if unlinked assets should be loaded and false otherwise - */ - public void setLoadUnlinkedAssets(boolean loadUnlinkedAssets) { - this.loadUnlinkedAssets = loadUnlinkedAssets; - } - - /** - * This method sets the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By default Y - * is up axis. - * @param fixUpAxis - * the up axis state variable - */ - public void setFixUpAxis(boolean fixUpAxis) { - this.fixUpAxis = fixUpAxis; - } - - /** - * This method returns the fix up axis state. If set to true then Y is up axis. Otherwise the up i Z axis. By - * default Y is up axis. - * @return the up axis state variable - */ - public boolean isFixUpAxis() { - return fixUpAxis; - } - - /** - * This method sets the generated textures resolution. - * @param generatedTexturePPU - * the generated textures resolution - */ - public void setGeneratedTexturePPU(int generatedTexturePPU) { - this.generatedTexturePPU = generatedTexturePPU; - } - - /** - * @return the generated textures resolution - */ - public int getGeneratedTexturePPU() { - return generatedTexturePPU; - } - - /** - * @return mipmaps generation method - */ - public MipmapGenerationMethod getMipmapGenerationMethod() { - return mipmapGenerationMethod; - } - - /** - * @param mipmapGenerationMethod - * mipmaps generation method - */ - public void setMipmapGenerationMethod(MipmapGenerationMethod mipmapGenerationMethod) { - this.mipmapGenerationMethod = mipmapGenerationMethod; - } - - /** - * @return the size of the generated textures for the sky (used if no flat textures are applied) - */ - public int getSkyGeneratedTextureSize() { - return skyGeneratedTextureSize; - } - - /** - * @param skyGeneratedTextureSize - * the size of the generated textures for the sky (used if no flat textures are applied) - */ - public void setSkyGeneratedTextureSize(int skyGeneratedTextureSize) { - if (skyGeneratedTextureSize <= 0) { - throw new IllegalArgumentException("The texture size must be a positive value (the value given as a parameter: " + skyGeneratedTextureSize + ")!"); - } - this.skyGeneratedTextureSize = skyGeneratedTextureSize; - } - - /** - * @return the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen - */ - public float getSkyGeneratedTextureRadius() { - return skyGeneratedTextureRadius; - } - - /** - * @param skyGeneratedTextureRadius - * the radius of a shape that will be used while creating the generated texture for the sky, the higher it is the larger part of the texture will be seen - */ - public void setSkyGeneratedTextureRadius(float skyGeneratedTextureRadius) { - this.skyGeneratedTextureRadius = skyGeneratedTextureRadius; - } - - /** - * @return the shape against which the generated texture for the sky will be created (by default it is a sphere). - */ - public SkyGeneratedTextureShape getSkyGeneratedTextureShape() { - return skyGeneratedTextureShape; - } - - /** - * @param skyGeneratedTextureShape - * the shape against which the generated texture for the sky will be created - */ - public void setSkyGeneratedTextureShape(SkyGeneratedTextureShape skyGeneratedTextureShape) { - if (skyGeneratedTextureShape == null) { - throw new IllegalArgumentException("The sky generated shape type cannot be null!"); - } - this.skyGeneratedTextureShape = skyGeneratedTextureShape; - } - - /** - * If set to true, then textures of the same mapping type will be merged together - * and textures that in the final result will never be visible - will be discarded. - * @param optimiseTextures - * the variable that tells if the textures should be optimised or not - */ - public void setOptimiseTextures(boolean optimiseTextures) { - this.optimiseTextures = optimiseTextures; - } - - /** - * @return the variable that tells if the textures should be optimised or not (by default the optimisation is disabled) - */ - public boolean isOptimiseTextures() { - return optimiseTextures; - } - - /** - * Sets the way the animations will be matched with skeletons. - * - * @param animationMatchMethod - * the way the animations will be matched with skeletons - */ - public void setAnimationMatchMethod(AnimationMatchMethod animationMatchMethod) { - this.animationMatchMethod = animationMatchMethod; - } - - /** - * @return the way the animations will be matched with skeletons - */ - public AnimationMatchMethod getAnimationMatchMethod() { - return animationMatchMethod; - } - - /** - * @return the size of points that are loaded and do not belong to any edge of the mesh - */ - public float getPointsSize() { - return pointsSize; - } - - /** - * Sets the size of points that are loaded and do not belong to any edge of the mesh. - * @param pointsSize - * The size of points that are loaded and do not belong to any edge of the mesh - */ - public void setPointsSize(float pointsSize) { - this.pointsSize = pointsSize; - } - - /** - * @return the width of edges that are loaded from the mesh and do not belong to any face - */ - public float getLinesWidth() { - return linesWidth; - } - - /** - * Sets the width of edges that are loaded from the mesh and do not belong to any face. - * @param linesWidth - * the width of edges that are loaded from the mesh and do not belong to any face - */ - public void setLinesWidth(float linesWidth) { - this.linesWidth = linesWidth; - } - - /** - * This method sets the name of the WORLD data block that should be used during file loading. By default the name is - * not set. If no name is set or the given name does not occur in the file - the first WORLD data block will be used - * during loading (assuming any exists in the file). - * @param usedWorld - * the name of the WORLD block used during loading - */ - public void setUsedWorld(String usedWorld) { - this.usedWorld = usedWorld; - } - - /** - * This method returns the name of the WORLD data block that should be used during file loading. - * @return the name of the WORLD block used during loading - */ - public String getUsedWorld() { - return usedWorld; - } - - /** - * This method sets the default material for objects. - * @param defaultMaterial - * the default material - */ - public void setDefaultMaterial(Material defaultMaterial) { - this.defaultMaterial = defaultMaterial; - } - - /** - * This method returns the default material. - * @return the default material - */ - public Material getDefaultMaterial() { - return defaultMaterial; - } - - @Override - public void write(JmeExporter e) throws IOException { - super.write(e); - OutputCapsule oc = e.getCapsule(this); - oc.write(fps, "fps", DEFAULT_FPS); - oc.write(featuresToLoad, "features-to-load", FeaturesToLoad.ALL); - oc.write(loadUnlinkedAssets, "load-unlinked-assets", false); - oc.write(assetRootPath, "asset-root-path", null); - oc.write(fixUpAxis, "fix-up-axis", true); - oc.write(generatedTexturePPU, "generated-texture-ppu", 128); - oc.write(usedWorld, "used-world", null); - oc.write(defaultMaterial, "default-material", null); - oc.write(faceCullMode, "face-cull-mode", FaceCullMode.Off); - oc.write(layersToLoad, "layers-to-load", -1); - oc.write(mipmapGenerationMethod, "mipmap-generation-method", MipmapGenerationMethod.GENERATE_WHEN_NEEDED); - oc.write(skyGeneratedTextureSize, "sky-generated-texture-size", 1000); - oc.write(skyGeneratedTextureRadius, "sky-generated-texture-radius", 1f); - oc.write(skyGeneratedTextureShape, "sky-generated-texture-shape", SkyGeneratedTextureShape.SPHERE); - oc.write(optimiseTextures, "optimise-textures", false); - oc.write(animationMatchMethod, "animation-match-method", AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH); - oc.write(pointsSize, "points-size", 1); - oc.write(linesWidth, "lines-width", 1); - } - - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule ic = e.getCapsule(this); - fps = ic.readInt("fps", DEFAULT_FPS); - featuresToLoad = ic.readInt("features-to-load", FeaturesToLoad.ALL); - loadUnlinkedAssets = ic.readBoolean("load-unlinked-assets", false); - assetRootPath = ic.readString("asset-root-path", null); - fixUpAxis = ic.readBoolean("fix-up-axis", true); - generatedTexturePPU = ic.readInt("generated-texture-ppu", 128); - usedWorld = ic.readString("used-world", null); - defaultMaterial = (Material) ic.readSavable("default-material", null); - faceCullMode = ic.readEnum("face-cull-mode", FaceCullMode.class, FaceCullMode.Off); - layersToLoad = ic.readInt("layers-to=load", -1); - mipmapGenerationMethod = ic.readEnum("mipmap-generation-method", MipmapGenerationMethod.class, MipmapGenerationMethod.GENERATE_WHEN_NEEDED); - skyGeneratedTextureSize = ic.readInt("sky-generated-texture-size", 1000); - skyGeneratedTextureRadius = ic.readFloat("sky-generated-texture-radius", 1f); - skyGeneratedTextureShape = ic.readEnum("sky-generated-texture-shape", SkyGeneratedTextureShape.class, SkyGeneratedTextureShape.SPHERE); - optimiseTextures = ic.readBoolean("optimise-textures", false); - animationMatchMethod = ic.readEnum("animation-match-method", AnimationMatchMethod.class, AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH); - pointsSize = ic.readFloat("points-size", 1); - linesWidth = ic.readFloat("lines-width", 1); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = super.hashCode(); - result = prime * result + (animationMatchMethod == null ? 0 : animationMatchMethod.hashCode()); - result = prime * result + (assetRootPath == null ? 0 : assetRootPath.hashCode()); - result = prime * result + (defaultMaterial == null ? 0 : defaultMaterial.hashCode()); - result = prime * result + (faceCullMode == null ? 0 : faceCullMode.hashCode()); - result = prime * result + featuresToLoad; - result = prime * result + (fixUpAxis ? 1231 : 1237); - result = prime * result + fps; - result = prime * result + generatedTexturePPU; - result = prime * result + layersToLoad; - result = prime * result + (loadGeneratedTextures ? 1231 : 1237); - result = prime * result + (loadObjectProperties ? 1231 : 1237); - result = prime * result + (loadUnlinkedAssets ? 1231 : 1237); - result = prime * result + maxTextureSize; - result = prime * result + (mipmapGenerationMethod == null ? 0 : mipmapGenerationMethod.hashCode()); - result = prime * result + (optimiseTextures ? 1231 : 1237); - result = prime * result + Float.floatToIntBits(skyGeneratedTextureRadius); - result = prime * result + (skyGeneratedTextureShape == null ? 0 : skyGeneratedTextureShape.hashCode()); - result = prime * result + skyGeneratedTextureSize; - result = prime * result + (usedWorld == null ? 0 : usedWorld.hashCode()); - result = prime * result + (int) pointsSize; - result = prime * result + (int) linesWidth; - return result; - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof BlenderKey) { - return false; - } - BlenderKey other = (BlenderKey) obj; - if (animationMatchMethod != other.animationMatchMethod) { - return false; - } - if (assetRootPath == null) { - if (other.assetRootPath != null) { - return false; - } - } else if (!assetRootPath.equals(other.assetRootPath)) { - return false; - } - if (defaultMaterial == null) { - if (other.defaultMaterial != null) { - return false; - } - } else if (!defaultMaterial.equals(other.defaultMaterial)) { - return false; - } - if (faceCullMode != other.faceCullMode) { - return false; - } - if (featuresToLoad != other.featuresToLoad) { - return false; - } - if (fixUpAxis != other.fixUpAxis) { - return false; - } - if (fps != other.fps) { - return false; - } - if (generatedTexturePPU != other.generatedTexturePPU) { - return false; - } - if (layersToLoad != other.layersToLoad) { - return false; - } - if (loadGeneratedTextures != other.loadGeneratedTextures) { - return false; - } - if (loadObjectProperties != other.loadObjectProperties) { - return false; - } - if (loadUnlinkedAssets != other.loadUnlinkedAssets) { - return false; - } - if (maxTextureSize != other.maxTextureSize) { - return false; - } - if (mipmapGenerationMethod != other.mipmapGenerationMethod) { - return false; - } - if (optimiseTextures != other.optimiseTextures) { - return false; - } - if (Float.floatToIntBits(skyGeneratedTextureRadius) != Float.floatToIntBits(other.skyGeneratedTextureRadius)) { - return false; - } - if (skyGeneratedTextureShape != other.skyGeneratedTextureShape) { - return false; - } - if (skyGeneratedTextureSize != other.skyGeneratedTextureSize) { - return false; - } - if (usedWorld == null) { - if (other.usedWorld != null) { - return false; - } - } else if (!usedWorld.equals(other.usedWorld)) { - return false; - } - if (pointsSize != other.pointsSize) { - return false; - } - if (linesWidth != other.linesWidth) { - return false; - } - return true; - } - - /** - * This enum tells the importer if the mipmaps for textures will be generated by jme.

  • NEVER_GENERATE and ALWAYS_GENERATE are quite understandable
  • GENERATE_WHEN_NEEDED is an option that checks if the texture had 'Generate mipmaps' option set in blender, mipmaps are generated only when the option is set - * @author Marcin Roguski (Kaelthas) - */ - public static enum MipmapGenerationMethod { - NEVER_GENERATE, ALWAYS_GENERATE, GENERATE_WHEN_NEEDED; - } - - /** - * This interface describes the features of the scene that are to be loaded. - * @deprecated this interface is deprecated and is not used anymore; to ensure the loading models consistency - * everything must be loaded because in blender one feature might depend on another - * @author Marcin Roguski (Kaelthas) - */ - @Deprecated - public static interface FeaturesToLoad { - - int SCENES = 0x0000FFFF; - int OBJECTS = 0x0000000B; - int ANIMATIONS = 0x00000004; - int MATERIALS = 0x00000003; - int TEXTURES = 0x00000001; - int CAMERAS = 0x00000020; - int LIGHTS = 0x00000010; - int WORLD = 0x00000040; - int ALL = 0xFFFFFFFF; - } - - /** - * The shape againts which the sky generated texture will be created. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum SkyGeneratedTextureShape { - CUBE, SPHERE; - } - - /** - * This enum describes which animations should be attached to which armature. - * Blender does not store the mapping between action and armature. That is why the importer - * will try to match those by comparing bone name of the armature with the channel names - * int the actions. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum AnimationMatchMethod { - /** - * Animation is matched with skeleton when at leas one bone name matches the name of the action channel. - * All the bones that do not have their corresponding channel in the animation will not get the proper tracks for - * this particulat animation. - * Also the channel will not be used for the animation if it does not find the proper bone name. - */ - AT_LEAST_ONE_NAME_MATCH, - /** - * Animation is matched when all action names are covered by the target names (bone names or the name of the - * animated spatial. - */ - ALL_NAMES_MATCH; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java b/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java deleted file mode 100644 index 6bd9017aee..0000000000 --- a/jme3-blender/src/main/java/com/jme3/asset/GeneratedTextureKey.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.asset; - -/** - * This key is mostly used to distinguish between textures that are loaded from - * the given assets and those being generated automatically. Every generated - * texture will have this kind of key attached. - * - * @author Marcin Roguski (Kaelthas) - */ -public class GeneratedTextureKey extends TextureKey { - - /** - * Constructor. Stores the name. Extension and folder name are empty - * strings. - * - * @param name - * the name of the texture - */ - public GeneratedTextureKey(String name) { - super(name); - } - - @Override - public String getExtension() { - return ""; - } - - @Override - public String getFolder() { - return ""; - } - - @Override - public String toString() { - return "Generated texture [" + name + "]"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java deleted file mode 100644 index 142f757e21..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/AbstractBlenderHelper.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.asset.AssetNotFoundException; -import com.jme3.asset.BlenderKey; -import com.jme3.export.Savable; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.Properties; - -/** - * A purpose of the helper class is to split calculation code into several classes. Each helper after use should be cleared because it can - * hold the state of the calculations. - * @author Marcin Roguski - */ -public abstract class AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(AbstractBlenderHelper.class.getName()); - - /** The blender context. */ - protected BlenderContext blenderContext; - /** The version of the blend file. */ - protected final int blenderVersion; - /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; - /** Quaternion used to rotate data when Y is up axis. */ - protected Quaternion upAxisRotationQuaternion; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public AbstractBlenderHelper(String blenderVersion, BlenderContext blenderContext) { - this.blenderVersion = Integer.parseInt(blenderVersion); - this.blenderContext = blenderContext; - fixUpAxis = blenderContext.getBlenderKey().isFixUpAxis(); - if (fixUpAxis) { - upAxisRotationQuaternion = new Quaternion().fromAngles(-FastMath.HALF_PI, 0, 0); - } - } - - /** - * This method loads the properties if they are available and defined for the structure. - * @param structure - * the structure we read the properties from - * @param blenderContext - * the blender context - * @return loaded properties or null if they are not available - * @throws BlenderFileException - * an exception is thrown when the blend file is somehow corrupted - */ - protected Properties loadProperties(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - Properties properties = null; - Structure id = (Structure) structure.getFieldValue("ID"); - if (id != null) { - Pointer pProperties = (Pointer) id.getFieldValue("properties"); - if (pProperties.isNotNull()) { - Structure propertiesStructure = pProperties.fetchData().get(0); - properties = new Properties(); - properties.load(propertiesStructure, blenderContext); - } - } - return properties; - } - - /** - * The method applies properties to the given spatial. The Properties - * instance cannot be directly applied because the end-user might not have - * the blender plugin jar file and thus receive ClassNotFoundException. The - * values are set by name instead. - * - * @param spatial - * the spatial that is to have properties applied - * @param properties - * the properties to be applied - */ - public void applyProperties(Spatial spatial, Properties properties) { - List propertyNames = properties.getSubPropertiesNames(); - if (propertyNames != null && propertyNames.size() > 0) { - for (String propertyName : propertyNames) { - Object value = properties.findValue(propertyName); - if (value instanceof Savable || value instanceof Boolean || value instanceof String || value instanceof Float || value instanceof Integer || value instanceof Long) { - spatial.setUserData(propertyName, value); - } else if (value instanceof Double) { - spatial.setUserData(propertyName, ((Double) value).floatValue()); - } else if (value instanceof int[]) { - spatial.setUserData(propertyName, Arrays.toString((int[]) value)); - } else if (value instanceof float[]) { - spatial.setUserData(propertyName, Arrays.toString((float[]) value)); - } else if (value instanceof double[]) { - spatial.setUserData(propertyName, Arrays.toString((double[]) value)); - } - } - } - } - - /** - * The method loads library of a given ID from linked blender file. - * @param id - * the ID of the linked feature (it contains its name and blender path) - * @return loaded feature or null if none was found - * @throws BlenderFileException - * and exception is throw when problems with reading a blend file occur - */ - protected Object loadLibrary(Structure id) throws BlenderFileException { - Pointer pLib = (Pointer) id.getFieldValue("lib"); - if (pLib.isNotNull()) { - String fullName = id.getFieldValue("name").toString();// we need full name with the prefix - String nameOfFeatureToLoad = id.getName(); - Structure library = pLib.fetchData().get(0); - String path = library.getFieldValue("filepath").toString(); - - if (!blenderContext.getLinkedFeatures().keySet().contains(path)) { - Spatial loadedAsset = null; - BlenderKey blenderKey = new BlenderKey(path); - blenderKey.setLoadUnlinkedAssets(true); - try { - loadedAsset = blenderContext.getAssetManager().loadAsset(blenderKey); - } catch (AssetNotFoundException e) { - LOGGER.log(Level.FINEST, "Cannot locate linked resource at path: {0}.", path); - } - - if (loadedAsset != null) { - Map> linkedData = loadedAsset.getUserData("linkedData"); - - for (Entry> entry : linkedData.entrySet()) { - String linkedDataFilePath = "this".equals(entry.getKey()) ? path : entry.getKey(); - blenderContext.getLinkedFeatures().put(linkedDataFilePath, entry.getValue()); - } - } else { - LOGGER.log(Level.WARNING, "No features loaded from path: {0}.", path); - } - } - - Object result = blenderContext.getLinkedFeature(path, fullName); - if (result == null) { - LOGGER.log(Level.WARNING, "Could NOT find asset named {0} in the library of path: {1}.", new Object[] { nameOfFeatureToLoad, path }); - } else { - blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.STRUCTURE, id); - blenderContext.addLoadedFeatures(id.getOldMemoryAddress(), LoadedDataType.FEATURE, result); - } - return result; - } else { - LOGGER.warning("Library link points to nothing!"); - } - return null; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java deleted file mode 100644 index fea54f13b7..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderContext.java +++ /dev/null @@ -1,767 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender; - -import java.util.ArrayList; -import java.util.EmptyStackException; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Stack; - -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.asset.AssetManager; -import com.jme3.asset.BlenderKey; -import com.jme3.light.Light; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.post.Filter; -import com.jme3.renderer.Camera; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.animations.BlenderAction; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.Constraint; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.DnaBlockData; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.texture.Texture; - -/** - * The class that stores temporary data and manages it during loading the belnd - * file. This class is intended to be used in a single loading thread. It holds - * the state of loading operations. - * - * @author Marcin Roguski (Kaelthas) - */ -public class BlenderContext { - /** The blender file version. */ - private int blenderVersion; - /** The blender key. */ - private BlenderKey blenderKey; - /** The header of the file block. */ - private DnaBlockData dnaBlockData; - /** The scene structure. */ - private Structure sceneStructure; - /** The input stream of the blend file. */ - private BlenderInputStream inputStream; - /** The asset manager. */ - private AssetManager assetManager; - /** The blocks read from the file. */ - protected List blocks = new ArrayList(); - /** - * A map containing the file block headers. The key is the old memory address. - */ - private Map fileBlockHeadersByOma = new HashMap(); - /** A map containing the file block headers. The key is the block code. */ - private Map> fileBlockHeadersByCode = new HashMap>(); - /** - * This map stores the loaded features by their old memory address. The - * first object in the value table is the loaded structure and the second - - * the structure already converted into proper data. - */ - private Map> loadedFeatures = new HashMap>(); - /** Features loaded from external blender files. The key is the file path and the value is a map between feature name and loaded feature. */ - private Map> linkedFeatures = new HashMap>(); - /** A stack that hold the parent structure of currently loaded feature. */ - private Stack parentStack = new Stack(); - /** A list of constraints for the specified object. */ - protected Map> constraints = new HashMap>(); - /** Animations loaded for features. */ - private Map> animations = new HashMap>(); - /** Loaded skeletons. */ - private Map skeletons = new HashMap(); - /** A map between skeleton and node it modifies. */ - private Map nodesWithSkeletons = new HashMap(); - /** A map of bone contexts. */ - protected Map boneContexts = new HashMap(); - /** A map og helpers that perform loading. */ - private Map helpers = new HashMap(); - /** Markers used by loading classes to store some custom data. This is made to avoid putting this data into user properties. */ - private Map> markers = new HashMap>(); - /** A map of blender actions. The key is the action name and the value is the action itself. */ - private Map actions = new HashMap(); - - /** - * This method sets the blender file version. - * - * @param blenderVersion - * the blender file version - */ - public void setBlenderVersion(String blenderVersion) { - this.blenderVersion = Integer.parseInt(blenderVersion); - } - - /** - * @return the blender file version - */ - public int getBlenderVersion() { - return blenderVersion; - } - - /** - * This method sets the blender key. - * - * @param blenderKey - * the blender key - */ - public void setBlenderKey(BlenderKey blenderKey) { - this.blenderKey = blenderKey; - } - - /** - * This method returns the blender key. - * - * @return the blender key - */ - public BlenderKey getBlenderKey() { - return blenderKey; - } - - /** - * This method sets the dna block data. - * - * @param dnaBlockData - * the dna block data - */ - public void setBlockData(DnaBlockData dnaBlockData) { - this.dnaBlockData = dnaBlockData; - } - - /** - * This method returns the dna block data. - * - * @return the dna block data - */ - public DnaBlockData getDnaBlockData() { - return dnaBlockData; - } - - /** - * This method sets the scene structure data. - * - * @param sceneStructure - * the scene structure data - */ - public void setSceneStructure(Structure sceneStructure) { - this.sceneStructure = sceneStructure; - } - - /** - * This method returns the scene structure data. - * - * @return the scene structure data - */ - public Structure getSceneStructure() { - return sceneStructure; - } - - /** - * This method returns the asset manager. - * - * @return the asset manager - */ - public AssetManager getAssetManager() { - return assetManager; - } - - /** - * This method sets the asset manager. - * - * @param assetManager - * the asset manager - */ - public void setAssetManager(AssetManager assetManager) { - this.assetManager = assetManager; - } - - /** - * This method returns the input stream of the blend file. - * - * @return the input stream of the blend file - */ - public BlenderInputStream getInputStream() { - return inputStream; - } - - /** - * This method sets the input stream of the blend file. - * - * @param inputStream - * the input stream of the blend file - */ - public void setInputStream(BlenderInputStream inputStream) { - this.inputStream = inputStream; - } - - /** - * This method adds a file block header to the map. Its old memory address - * is the key. - * - * @param oldMemoryAddress - * the address of the block header - * @param fileBlockHeader - * the block header to store - */ - public void addFileBlockHeader(Long oldMemoryAddress, FileBlockHeader fileBlockHeader) { - blocks.add(fileBlockHeader); - fileBlockHeadersByOma.put(oldMemoryAddress, fileBlockHeader); - List headers = fileBlockHeadersByCode.get(fileBlockHeader.getCode()); - if (headers == null) { - headers = new ArrayList(); - fileBlockHeadersByCode.put(fileBlockHeader.getCode(), headers); - } - headers.add(fileBlockHeader); - } - - /** - * @return the block headers - */ - public List getBlocks() { - return blocks; - } - - /** - * This method returns the block header of a given memory address. If the - * header is not present then null is returned. - * - * @param oldMemoryAddress - * the address of the block header - * @return loaded header or null if it was not yet loaded - */ - public FileBlockHeader getFileBlock(Long oldMemoryAddress) { - return fileBlockHeadersByOma.get(oldMemoryAddress); - } - - /** - * This method returns a list of file blocks' headers of a specified code. - * - * @param code - * the code of file blocks - * @return a list of file blocks' headers of a specified code - */ - public List getFileBlocks(BlockCode code) { - return fileBlockHeadersByCode.get(code); - } - - /** - * This method adds a helper instance to the helpers' map. - * - * @param - * the type of the helper - * @param clazz - * helper's class definition - * @param helper - * the helper instance - */ - public void putHelper(Class clazz, AbstractBlenderHelper helper) { - helpers.put(clazz.getSimpleName(), helper); - } - - @SuppressWarnings("unchecked") - public T getHelper(Class clazz) { - return (T) helpers.get(clazz.getSimpleName()); - } - - /** - * This method adds a loaded feature to the map. The key is its unique old - * memory address. - * - * @param oldMemoryAddress - * the address of the feature - * @param featureDataType - * @param feature - * the feature we want to store - */ - public void addLoadedFeatures(Long oldMemoryAddress, LoadedDataType featureDataType, Object feature) { - if (oldMemoryAddress == null || featureDataType == null || feature == null) { - throw new IllegalArgumentException("One of the given arguments is null!"); - } - Map map = loadedFeatures.get(oldMemoryAddress); - if (map == null) { - map = new HashMap(); - loadedFeatures.put(oldMemoryAddress, map); - } - map.put(featureDataType, feature); - } - - /** - * This method returns the feature of a given memory address. If the feature - * is not yet loaded then null is returned. - * - * @param oldMemoryAddress - * the address of the feature - * @param loadedFeatureDataType - * the type of data we want to retrieve it can be either filled - * structure or already converted feature - * @return loaded feature or null if it was not yet loaded - */ - public Object getLoadedFeature(Long oldMemoryAddress, LoadedDataType loadedFeatureDataType) { - Map result = loadedFeatures.get(oldMemoryAddress); - if (result != null) { - return result.get(loadedFeatureDataType); - } - return null; - } - - /** - * The method adds linked content to the blender context. - * @param blenderFilePath - * the path of linked blender file - * @param featureGroup - * the linked feature group (ie. scenes, materials, meshes, etc.) - * @param feature - * the linked feature - */ - @Deprecated - public void addLinkedFeature(String blenderFilePath, String featureGroup, Object feature) { - // the method is deprecated and empty at the moment - } - - /** - * The method returns linked feature of a given name from the specified blender path. - * @param blenderFilePath - * the blender file path - * @param featureName - * the feature name we want to get - * @return linked feature or null if none was found - */ - @SuppressWarnings("unchecked") - public Object getLinkedFeature(String blenderFilePath, String featureName) { - Map linkedFeatures = this.linkedFeatures.get(blenderFilePath); - if(linkedFeatures != null) { - String namePrefix = (featureName.charAt(0) + "" + featureName.charAt(1)).toUpperCase(); - featureName = featureName.substring(2); - - if("SC".equals(namePrefix)) { - List scenes = (List) linkedFeatures.get("scenes"); - if(scenes != null) { - for(Node scene : scenes) { - if(featureName.equals(scene.getName())) { - return scene; - } - } - } - } else if("OB".equals(namePrefix)) { - List features = (List) linkedFeatures.get("objects"); - if(features != null) { - for(Node feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("ME".equals(namePrefix)) { - List temporalMeshes = (List) linkedFeatures.get("meshes"); - if(temporalMeshes != null) { - for(TemporalMesh temporalMesh : temporalMeshes) { - if(featureName.equals(temporalMesh.getName())) { - return temporalMesh; - } - } - } - } else if("MA".equals(namePrefix)) { - List features = (List) linkedFeatures.get("materials"); - if(features != null) { - for(MaterialContext feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("TX".equals(namePrefix)) { - List features = (List) linkedFeatures.get("textures"); - if(features != null) { - for(Texture feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("IM".equals(namePrefix)) { - List features = (List) linkedFeatures.get("images"); - if(features != null) { - for(Texture feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("AC".equals(namePrefix)) { - List features = (List) linkedFeatures.get("animations"); - if(features != null) { - for(Animation feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("CA".equals(namePrefix)) { - List features = (List) linkedFeatures.get("cameras"); - if(features != null) { - for(Camera feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("LA".equals(namePrefix)) { - List features = (List) linkedFeatures.get("lights"); - if(features != null) { - for(Light feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } else if("FI".equals(featureName)) { - List features = (List) linkedFeatures.get("lights"); - if(features != null) { - for(Filter feature : features) { - if(featureName.equals(feature.getName())) { - return feature; - } - } - } - } - } - return null; - } - - /** - * @return all linked features for the current blend file - */ - public Map> getLinkedFeatures() { - return linkedFeatures; - } - - /** - * This method adds the structure to the parent stack. - * - * @param parent - * the structure to be added to the stack - */ - public void pushParent(Structure parent) { - parentStack.push(parent); - } - - /** - * This method removes the structure from the top of the parent's stack. - * - * @return the structure that was removed from the stack - */ - public Structure popParent() { - try { - return parentStack.pop(); - } catch (EmptyStackException e) { - return null; - } - } - - /** - * This method retrieves the structure at the top of the parent's stack but - * does not remove it. - * - * @return the structure from the top of the stack - */ - public Structure peekParent() { - try { - return parentStack.peek(); - } catch (EmptyStackException e) { - return null; - } - } - - /** - * This method adds a new modifier to the list. - * - * @param ownerOMA - * the owner's old memory address - * @param constraints - * the object's constraints - */ - public void addConstraints(Long ownerOMA, List constraints) { - List objectConstraints = this.constraints.get(ownerOMA); - if (objectConstraints == null) { - objectConstraints = new ArrayList(); - this.constraints.put(ownerOMA, objectConstraints); - } - objectConstraints.addAll(constraints); - } - - /** - * Returns constraints applied to the feature of the given OMA. - * @param ownerOMA - * the constraints' owner OMA - * @return a list of constraints or null if no constraints are applied to the feature - */ - public List getConstraints(Long ownerOMA) { - return constraints.get(ownerOMA); - } - - /** - * @return all available constraints - */ - public List getAllConstraints() { - List result = new ArrayList(); - for (Entry> entry : constraints.entrySet()) { - result.addAll(entry.getValue()); - } - return result; - } - - /** - * This method adds the animation for the specified OMA of its owner. - * - * @param ownerOMA - * the owner's old memory address - * @param animation - * the animation for the feature specified by ownerOMA - */ - public void addAnimation(Long ownerOMA, Animation animation) { - List animList = animations.get(ownerOMA); - if (animList == null) { - animList = new ArrayList(); - animations.put(ownerOMA, animList); - } - animList.add(animation); - } - - /** - * This method returns the animation data for the specified owner. - * - * @param ownerOMA - * the old memory address of the animation data owner - * @return the animation or null if none exists - */ - public List getAnimations(Long ownerOMA) { - return animations.get(ownerOMA); - } - - /** - * This method sets the skeleton for the specified OMA of its owner. - * - * @param skeletonOMA - * the skeleton's old memory address - * @param skeleton - * the skeleton specified by the given OMA - */ - public void setSkeleton(Long skeletonOMA, Skeleton skeleton) { - skeletons.put(skeletonOMA, skeleton); - } - - /** - * The method stores a binding between the skeleton and the proper armature - * node. - * - * @param skeleton - * the skeleton - * @param node - * the armature node - */ - public void setNodeForSkeleton(Skeleton skeleton, Node node) { - nodesWithSkeletons.put(skeleton, node); - } - - /** - * This method returns the armature node that is defined for the skeleton. - * - * @param skeleton - * the skeleton - * @return the armature node that defines the skeleton in blender - */ - public Node getControlledNode(Skeleton skeleton) { - return nodesWithSkeletons.get(skeleton); - } - - /** - * This method returns the skeleton for the specified OMA of its owner. - * - * @param skeletonOMA - * the skeleton's old memory address - * @return the skeleton specified by the given OMA - */ - public Skeleton getSkeleton(Long skeletonOMA) { - return skeletons.get(skeletonOMA); - } - - /** - * This method sets the bone context for the given bone old memory address. - * If the context is already set it will be replaced. - * - * @param boneOMA - * the bone's old memory address - * @param boneContext - * the bones's context - */ - public void setBoneContext(Long boneOMA, BoneContext boneContext) { - boneContexts.put(boneOMA, boneContext); - } - - /** - * This method returns the bone context for the given bone old memory - * address. If no context exists then null is returned. - * - * @param boneOMA - * the bone's old memory address - * @return bone's context - */ - public BoneContext getBoneContext(Long boneOMA) { - return boneContexts.get(boneOMA); - } - - /** - * Returns bone by given name. - * - * @param skeletonOMA - * the OMA of the skeleton where the bone will be searched - * @param name - * the name of the bone - * @return found bone or null if none bone of a given name exists - */ - public BoneContext getBoneByName(Long skeletonOMA, String name) { - for (Entry entry : boneContexts.entrySet()) { - if (entry.getValue().getArmatureObjectOMA().equals(skeletonOMA)) { - Bone bone = entry.getValue().getBone(); - if (bone != null && name.equals(bone.getName())) { - return entry.getValue(); - } - } - } - return null; - } - - /** - * Returns bone context for the given bone. - * - * @param bone - * the bone - * @return the bone's bone context - */ - public BoneContext getBoneContext(Bone bone) { - for (Entry entry : boneContexts.entrySet()) { - if (entry.getValue().getBone().getName().equals(bone.getName())) { - return entry.getValue(); - } - } - throw new IllegalStateException("Cannot find context for bone: " + bone); - } - - /** - * This metod returns the default material. - * - * @return the default material - */ - public synchronized Material getDefaultMaterial() { - if (blenderKey.getDefaultMaterial() == null) { - Material defaultMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - defaultMaterial.setColor("Color", ColorRGBA.DarkGray); - blenderKey.setDefaultMaterial(defaultMaterial); - } - return blenderKey.getDefaultMaterial(); - } - - /** - * Adds a custom marker for scene's feature. - * - * @param marker - * the marker name - * @param feature - * te scene's feature (can be node, material or texture or - * anything else) - * @param markerValue - * the marker value - */ - public void addMarker(String marker, Object feature, Object markerValue) { - if (markerValue == null) { - throw new IllegalArgumentException("The marker's value cannot be null."); - } - Map markersMap = markers.get(marker); - if (markersMap == null) { - markersMap = new HashMap(); - markers.put(marker, markersMap); - } - markersMap.put(feature, markerValue); - } - - /** - * Returns the marker value. The returned value is null if no marker was - * defined for the given feature. - * - * @param marker - * the marker name - * @param feature - * the scene's feature - * @return marker value or null if it was not defined - */ - public Object getMarkerValue(String marker, Object feature) { - Map markersMap = markers.get(marker); - return markersMap == null ? null : markersMap.get(feature); - } - - /** - * Adds blender action to the context. - * @param action - * the action loaded from the blend file - */ - public void addAction(BlenderAction action) { - actions.put(action.getName(), action); - } - - /** - * @return a map of blender actions; the key is the action name and the value is action itself - */ - public Map getActions() { - return actions; - } - - /** - * This enum defines what loaded data type user wants to retrieve. It can be - * either filled structure or already converted data. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum LoadedDataType { - STRUCTURE, FEATURE, TEMPORAL_MESH; - } - - @Override - public String toString() { - return blenderKey == null ? "BlenderContext [key = null]" : "BlenderContext [ key = " + blenderKey.toString() + " ]"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java deleted file mode 100644 index b7d9df8412..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderLoader.java +++ /dev/null @@ -1,419 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.Animation; -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetKey; -import com.jme3.asset.AssetLoader; -import com.jme3.asset.AssetLocator; -import com.jme3.asset.AssetManager; -import com.jme3.asset.BlenderKey; -import com.jme3.asset.ModelKey; -import com.jme3.asset.StreamAssetInfo; -import com.jme3.light.Light; -import com.jme3.math.ColorRGBA; -import com.jme3.post.Filter; -import com.jme3.renderer.Camera; -import com.jme3.scene.CameraNode; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.cameras.CameraHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.curves.CurvesHelper; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.landscape.LandscapeHelper; -import com.jme3.scene.plugins.blender.lights.LightHelper; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.scene.plugins.blender.meshes.MeshHelper; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; -import com.jme3.scene.plugins.blender.particles.ParticlesHelper; -import com.jme3.scene.plugins.blender.textures.TextureHelper; -import com.jme3.texture.Texture; - -/** - * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. - * @author Marcin Roguski (Kaelthas) - */ -public class BlenderLoader implements AssetLoader { - private static final Logger LOGGER = Logger.getLogger(BlenderLoader.class.getName()); - - @Override - public Spatial load(AssetInfo assetInfo) throws IOException { - try { - BlenderContext blenderContext = this.setup(assetInfo); - - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.loadAnimations(); - - BlenderKey blenderKey = blenderContext.getBlenderKey(); - LoadedFeatures loadedFeatures = new LoadedFeatures(); - for (FileBlockHeader block : blenderContext.getBlocks()) { - switch (block.getCode()) { - case BLOCK_OB00: - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node object = (Node) objectHelper.toObject(block.getStructure(blenderContext), blenderContext); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { object.getName(), object.getLocalTranslation().toString(), object.getParent() == null ? "null" : object.getParent().getName() }); - } - if (object.getParent() == null) { - loadedFeatures.objects.add(object); - } - if (object instanceof LightNode && ((LightNode) object).getLight() != null) { - loadedFeatures.lights.add(((LightNode) object).getLight()); - } else if (object instanceof CameraNode && ((CameraNode) object).getCamera() != null) { - loadedFeatures.cameras.add(((CameraNode) object).getCamera()); - } - break; - case BLOCK_SC00:// Scene - loadedFeatures.sceneBlocks.add(block); - break; - case BLOCK_MA00:// Material - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - MaterialContext materialContext = materialHelper.toMaterialContext(block.getStructure(blenderContext), blenderContext); - loadedFeatures.materials.add(materialContext); - break; - case BLOCK_ME00:// Mesh - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - TemporalMesh temporalMesh = meshHelper.toTemporalMesh(block.getStructure(blenderContext), blenderContext); - loadedFeatures.meshes.add(temporalMesh); - break; - case BLOCK_IM00:// Image - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - Texture image = textureHelper.loadImageAsTexture(block.getStructure(blenderContext), 0, blenderContext); - if (image != null && image.getImage() != null) {// render results are stored as images but are not being loaded - loadedFeatures.images.add(image); - } - break; - case BLOCK_TE00: - Structure textureStructure = block.getStructure(blenderContext); - int type = ((Number) textureStructure.getFieldValue("type")).intValue(); - if (type == TextureHelper.TEX_IMAGE) { - TextureHelper texHelper = blenderContext.getHelper(TextureHelper.class); - Texture texture = texHelper.getTexture(textureStructure, null, blenderContext); - if (texture != null) {// null is returned when texture has no image - loadedFeatures.textures.add(texture); - } - } else { - LOGGER.fine("Only image textures can be loaded as unlinked assets. Generated textures will be applied to an existing object."); - } - break; - case BLOCK_WO00:// World - LandscapeHelper landscapeHelper = blenderContext.getHelper(LandscapeHelper.class); - Structure worldStructure = block.getStructure(blenderContext); - - String worldName = worldStructure.getName(); - if (blenderKey.getUsedWorld() == null || blenderKey.getUsedWorld().equals(worldName)) { - - Light ambientLight = landscapeHelper.toAmbientLight(worldStructure); - if (ambientLight != null) { - loadedFeatures.objects.add(new LightNode(null, ambientLight)); - loadedFeatures.lights.add(ambientLight); - } - loadedFeatures.sky = landscapeHelper.toSky(worldStructure); - loadedFeatures.backgroundColor = landscapeHelper.toBackgroundColor(worldStructure); - - Filter fogFilter = landscapeHelper.toFog(worldStructure); - if (fogFilter != null) { - loadedFeatures.filters.add(landscapeHelper.toFog(worldStructure)); - } - } - break; - case BLOCK_AC00: - LOGGER.fine("Loading unlinked animations is not yet supported!"); - break; - default: - LOGGER.log(Level.FINEST, "Ommiting the block: {0}.", block.getCode()); - } - } - - LOGGER.fine("Baking constraints after every feature is loaded."); - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.bakeConstraints(blenderContext); - - LOGGER.fine("Loading scenes and attaching them to the root object."); - for (FileBlockHeader sceneBlock : loadedFeatures.sceneBlocks) { - loadedFeatures.scenes.add(this.toScene(sceneBlock.getStructure(blenderContext), blenderContext)); - } - - LOGGER.fine("Creating the root node of the model and applying loaded nodes of the scene and loaded features to it."); - Node modelRoot = new Node(blenderKey.getName()); - for (Node scene : loadedFeatures.scenes) { - modelRoot.attachChild(scene); - } - - if (blenderKey.isLoadUnlinkedAssets()) { - LOGGER.fine("Setting loaded content as user data in resulting sptaial."); - Map> linkedData = new HashMap>(); - - Map thisFileData = new HashMap(); - thisFileData.put("scenes", loadedFeatures.scenes == null ? new ArrayList() : loadedFeatures.scenes); - thisFileData.put("objects", loadedFeatures.objects == null ? new ArrayList() : loadedFeatures.objects); - thisFileData.put("meshes", loadedFeatures.meshes == null ? new ArrayList() : loadedFeatures.meshes); - thisFileData.put("materials", loadedFeatures.materials == null ? new ArrayList() : loadedFeatures.materials); - thisFileData.put("textures", loadedFeatures.textures == null ? new ArrayList() : loadedFeatures.textures); - thisFileData.put("images", loadedFeatures.images == null ? new ArrayList() : loadedFeatures.images); - thisFileData.put("animations", loadedFeatures.animations == null ? new ArrayList() : loadedFeatures.animations); - thisFileData.put("cameras", loadedFeatures.cameras == null ? new ArrayList() : loadedFeatures.cameras); - thisFileData.put("lights", loadedFeatures.lights == null ? new ArrayList() : loadedFeatures.lights); - thisFileData.put("filters", loadedFeatures.filters == null ? new ArrayList() : loadedFeatures.filters); - thisFileData.put("backgroundColor", loadedFeatures.backgroundColor); - thisFileData.put("sky", loadedFeatures.sky); - - linkedData.put("this", thisFileData); - linkedData.putAll(blenderContext.getLinkedFeatures()); - - modelRoot.setUserData("linkedData", linkedData); - } - - return modelRoot; - } catch (BlenderFileException e) { - throw new IOException(e.getLocalizedMessage(), e); - } catch (Exception e) { - throw new IOException("Unexpected importer exception occurred: " + e.getLocalizedMessage(), e); - } finally { - this.clear(assetInfo); - } - } - - /** - * This method converts the given structure to a scene node. - * @param structure - * structure of a scene - * @param blenderContext the blender context - * @return scene's node - * @throws BlenderFileException - * an exception throw when problems with blender file occur - */ - private Node toScene(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node result = new Node(structure.getName()); - List base = ((Structure) structure.getFieldValue("base")).evaluateListBase(); - for (Structure b : base) { - Pointer pObject = (Pointer) b.getFieldValue("object"); - if (pObject.isNotNull()) { - Structure objectStructure = pObject.fetchData().get(0); - - Object object = objectHelper.toObject(objectStructure, blenderContext); - if (object instanceof Node) { - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "{0}: {1}--> {2}", new Object[] { ((Node) object).getName(), ((Node) object).getLocalTranslation().toString(), ((Node) object).getParent() == null ? "null" : ((Node) object).getParent().getName() }); - } - - if (((Node) object).getParent() == null) { - result.attachChild((Spatial) object); - } - - if(object instanceof LightNode) { - result.addLight(((LightNode) object).getLight()); - } - } - } - } - return result; - } - - /** - * This method sets up the loader. - * @param assetInfo - * the asset info - * @throws BlenderFileException - * an exception is throw when something wrong happens with blender file - */ - protected BlenderContext setup(AssetInfo assetInfo) throws BlenderFileException { - // registering loaders - ModelKey modelKey = (ModelKey) assetInfo.getKey(); - BlenderKey blenderKey; - if (modelKey instanceof BlenderKey) { - blenderKey = (BlenderKey) modelKey; - } else { - blenderKey = new BlenderKey(modelKey.getName()); - } - - // opening stream - BlenderInputStream inputStream = new BlenderInputStream(assetInfo.openStream()); - - // reading blocks - List blocks = new ArrayList(); - FileBlockHeader fileBlock; - BlenderContext blenderContext = new BlenderContext(); - blenderContext.setBlenderVersion(inputStream.getVersionNumber()); - blenderContext.setAssetManager(assetInfo.getManager()); - blenderContext.setInputStream(inputStream); - blenderContext.setBlenderKey(blenderKey); - - // creating helpers - blenderContext.putHelper(AnimationHelper.class, new AnimationHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(TextureHelper.class, new TextureHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(MeshHelper.class, new MeshHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(ObjectHelper.class, new ObjectHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(CurvesHelper.class, new CurvesHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(LightHelper.class, new LightHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(CameraHelper.class, new CameraHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(ModifierHelper.class, new ModifierHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(MaterialHelper.class, new MaterialHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(ConstraintHelper.class, new ConstraintHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(ParticlesHelper.class, new ParticlesHelper(inputStream.getVersionNumber(), blenderContext)); - blenderContext.putHelper(LandscapeHelper.class, new LandscapeHelper(inputStream.getVersionNumber(), blenderContext)); - - // reading the blocks (dna block is automatically saved in the blender context when found) - FileBlockHeader sceneFileBlock = null; - do { - fileBlock = new FileBlockHeader(inputStream, blenderContext); - if (!fileBlock.isDnaBlock()) { - blocks.add(fileBlock); - // save the scene's file block - if (fileBlock.getCode() == BlockCode.BLOCK_SC00) { - sceneFileBlock = fileBlock; - } - } - } while (!fileBlock.isLastBlock()); - if (sceneFileBlock != null) { - blenderContext.setSceneStructure(sceneFileBlock.getStructure(blenderContext)); - } - - // adding locator for linked content - assetInfo.getManager().registerLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); - - return blenderContext; - } - - /** - * The internal data is only needed during loading so make it unreachable so that the GC can release - * that memory (which can be quite large amount). - */ - protected void clear(AssetInfo assetInfo) { - assetInfo.getManager().unregisterLocator(assetInfo.getKey().getName(), LinkedContentLocator.class); - } - - /** - * This class holds the loading results according to the given loading flag. - * @author Marcin Roguski (Kaelthas) - */ - private static class LoadedFeatures { - private List sceneBlocks = new ArrayList(); - /** The scenes from the file. */ - private List scenes = new ArrayList(); - /** Objects from all scenes. */ - private List objects = new ArrayList(); - /** All meshes. */ - private List meshes = new ArrayList(); - /** Materials from all objects. */ - private List materials = new ArrayList(); - /** Textures from all objects. */ - private List textures = new ArrayList(); - /** The images stored in the blender file. */ - private List images = new ArrayList(); - /** Animations of all objects. */ - private List animations = new ArrayList(); - /** All cameras from the file. */ - private List cameras = new ArrayList(); - /** All lights from the file. */ - private List lights = new ArrayList(); - /** Loaded sky. */ - private Spatial sky; - /** Scene filters (ie. FOG). */ - private List filters = new ArrayList(); - /** - * The background color of the render loaded from the horizon color of the world. If no world is used than the gray color - * is set to default (as in blender editor. - */ - private ColorRGBA backgroundColor = ColorRGBA.Gray; - } - - public static class LinkedContentLocator implements AssetLocator { - private File rootFolder; - - @Override - public void setRootPath(String rootPath) { - rootFolder = new File(rootPath); - if(rootFolder.isFile()) { - rootFolder = rootFolder.getParentFile(); - } - } - - @SuppressWarnings("rawtypes") - @Override - public AssetInfo locate(AssetManager manager, AssetKey key) { - if(key instanceof BlenderKey) { - File linkedAbsoluteFile = new File(key.getName()); - if(linkedAbsoluteFile.exists() && linkedAbsoluteFile.isFile()) { - try { - return new StreamAssetInfo(manager, key, new FileInputStream(linkedAbsoluteFile)); - } catch (FileNotFoundException e) { - return null; - } - } - - File linkedFileInCurrentAssetFolder = new File(rootFolder, linkedAbsoluteFile.getName()); - if(linkedFileInCurrentAssetFolder.exists() && linkedFileInCurrentAssetFolder.isFile()) { - try { - return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentAssetFolder)); - } catch (FileNotFoundException e) { - return null; - } - } - - File linkedFileInCurrentFolder = new File(".", linkedAbsoluteFile.getName()); - if(linkedFileInCurrentFolder.exists() && linkedFileInCurrentFolder.isFile()) { - try { - return new StreamAssetInfo(manager, key, new FileInputStream(linkedFileInCurrentFolder)); - } catch (FileNotFoundException e) { - return null; - } - } - } - return null; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java deleted file mode 100644 index eae0474217..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/BlenderModelLoader.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender; - -/** - * This is the main loading class. Have in notice that asset manager needs to have loaders for resources like textures. - * @deprecated this class is deprecated; use BlenderLoader instead - * @author Marcin Roguski (Kaelthas) - */ -public class BlenderModelLoader extends BlenderLoader { -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java deleted file mode 100644 index 1d889f9926..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/AnimationHelper.java +++ /dev/null @@ -1,391 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map.Entry; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; -import com.jme3.animation.SpatialTrack; -import com.jme3.asset.BlenderKey.AnimationMatchMethod; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.Ipo.ConstIpo; -import com.jme3.scene.plugins.blender.curves.BezierCurve; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.FileBlockHeader.BlockCode; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - -/** - * The helper class that helps in animations loading. - * @author Marcin Roguski (Kaelthas) - */ -public class AnimationHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(AnimationHelper.class.getName()); - - public AnimationHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * Loads all animations that are stored in the blender file. The animations are not yet applied to the scene features. - * This should be called before objects are loaded. - * @throws BlenderFileException - * an exception is thrown when problems with blender file reading occur - */ - public void loadAnimations() throws BlenderFileException { - LOGGER.info("Loading animations that will be later applied to scene features."); - List actionHeaders = blenderContext.getFileBlocks(BlockCode.BLOCK_AC00); - if (actionHeaders != null) { - for (FileBlockHeader header : actionHeaders) { - Structure actionStructure = header.getStructure(blenderContext); - LOGGER.log(Level.INFO, "Found animation: {0}.", actionStructure.getName()); - blenderContext.addAction(this.getTracks(actionStructure, blenderContext)); - } - } - } - - /** - * The method applies animations to the given node. The names of the animations should be the same as actions names in the blender file. - * @param node - * the node to whom the animations will be applied - * @param animationMatchMethod - * the way animation should be matched with node - */ - public void applyAnimations(Node node, AnimationMatchMethod animationMatchMethod) { - List actions = this.getActions(node, animationMatchMethod); - if (actions.size() > 0) { - List animations = new ArrayList(); - for (BlenderAction action : actions) { - SpatialTrack[] tracks = action.toTracks(node, blenderContext); - if (tracks != null && tracks.length > 0) { - Animation spatialAnimation = new Animation(action.getName(), action.getAnimationTime()); - spatialAnimation.setTracks(tracks); - animations.add(spatialAnimation); - blenderContext.addAnimation((Long) node.getUserData(ObjectHelper.OMA_MARKER), spatialAnimation); - } - } - - if (animations.size() > 0) { - AnimControl control = new AnimControl(); - HashMap anims = new HashMap(animations.size()); - for (int i = 0; i < animations.size(); ++i) { - Animation animation = animations.get(i); - anims.put(animation.getName(), animation); - } - control.setAnimations(anims); - node.addControl(control); - } - } - } - - /** - * The method applies skeleton animations to the given node. - * @param node - * the node where the animations will be applied - * @param skeleton - * the skeleton of the node - * @param animationMatchMethod - * the way animation should be matched with skeleton - */ - public void applyAnimations(Node node, Skeleton skeleton, AnimationMatchMethod animationMatchMethod) { - node.addControl(new SkeletonControl(skeleton)); - blenderContext.setNodeForSkeleton(skeleton, node); - List actions = this.getActions(skeleton, animationMatchMethod); - - if (actions.size() > 0) { - List animations = new ArrayList(); - for (BlenderAction action : actions) { - BoneTrack[] tracks = action.toTracks(skeleton, blenderContext); - if (tracks != null && tracks.length > 0) { - Animation boneAnimation = new Animation(action.getName(), action.getAnimationTime()); - boneAnimation.setTracks(tracks); - animations.add(boneAnimation); - Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue(); - blenderContext.addAnimation(animatedNodeOMA, boneAnimation); - } - } - if (animations.size() > 0) { - AnimControl control = new AnimControl(skeleton); - HashMap anims = new HashMap(animations.size()); - for (int i = 0; i < animations.size(); ++i) { - Animation animation = animations.get(i); - anims.put(animation.getName(), animation); - } - control.setAnimations(anims); - node.addControl(control); - - // make sure that SkeletonControl is added AFTER the AnimControl - SkeletonControl skeletonControl = node.getControl(SkeletonControl.class); - if (skeletonControl != null) { - node.removeControl(SkeletonControl.class); - node.addControl(skeletonControl); - } - } - } - } - - /** - * This method creates an ipo object used for interpolation calculations. - * - * @param ipoStructure - * the structure with ipo definition - * @param blenderContext - * the blender context - * @return the ipo object - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Ipo fromIpoStructure(Structure ipoStructure, BlenderContext blenderContext) throws BlenderFileException { - Structure curvebase = (Structure) ipoStructure.getFieldValue("curve"); - - // preparing bezier curves - Ipo result = null; - List curves = curvebase.evaluateListBase();// IpoCurve - if (curves.size() > 0) { - BezierCurve[] bezierCurves = new BezierCurve[curves.size()]; - int frame = 0; - for (Structure curve : curves) { - Pointer pBezTriple = (Pointer) curve.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(); - int type = ((Number) curve.getFieldValue("adrcode")).intValue(); - bezierCurves[frame++] = new BezierCurve(type, bezTriples, 2); - } - curves.clear(); - result = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - Long ipoOma = ipoStructure.getOldMemoryAddress(); - blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.STRUCTURE, ipoStructure); - blenderContext.addLoadedFeatures(ipoOma, LoadedDataType.FEATURE, result); - } - return result; - } - - /** - * This method creates an ipo with only a single value. No track type is - * specified so do not use it for calculating tracks. - * - * @param constValue - * the value of this ipo - * @return constant ipo - */ - public Ipo fromValue(float constValue) { - return new ConstIpo(constValue); - } - - /** - * This method retuns the bone tracks for animation. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BlenderAction getTracks(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { - if (blenderVersion < 250) { - return this.getTracks249(actionStructure, blenderContext); - } else { - return this.getTracks250(actionStructure, blenderContext); - } - } - - /** - * This method retuns the bone tracks for animation for blender version 2.50 - * and higher. - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BlenderAction getTracks250(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - Structure groups = (Structure) actionStructure.getFieldValue("groups"); - List actionGroups = groups.evaluateListBase();// bActionGroup - BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps()); - int lastFrame = 1; - for (Structure actionGroup : actionGroups) { - String name = actionGroup.getFieldValue("name").toString(); - List channels = ((Structure) actionGroup.getFieldValue("channels")).evaluateListBase(); - BezierCurve[] bezierCurves = new BezierCurve[channels.size()]; - int channelCounter = 0; - for (Structure c : channels) { - int type = this.getCurveType(c, blenderContext); - Pointer pBezTriple = (Pointer) c.getFieldValue("bezt"); - List bezTriples = pBezTriple.fetchData(); - bezierCurves[channelCounter++] = new BezierCurve(type, bezTriples, 2); - } - - Ipo ipo = new Ipo(bezierCurves, fixUpAxis, blenderContext.getBlenderVersion()); - lastFrame = Math.max(lastFrame, ipo.getLastFrame()); - blenderAction.featuresTracks.put(name, ipo); - } - blenderAction.stopFrame = lastFrame; - return blenderAction; - } - - /** - * This method retuns the bone tracks for animation for blender version 2.49 - * (and probably several lower versions too). - * - * @param actionStructure - * the structure containing the tracks - * @param blenderContext - * the blender context - * @return a list of tracks for the specified animation - * @throws BlenderFileException - * an exception is thrown when there are problems with the blend - * file - */ - private BlenderAction getTracks249(Structure actionStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Getting tracks!"); - Structure chanbase = (Structure) actionStructure.getFieldValue("chanbase"); - List actionChannels = chanbase.evaluateListBase();// bActionChannel - BlenderAction blenderAction = new BlenderAction(actionStructure.getName(), blenderContext.getBlenderKey().getFps()); - int lastFrame = 1; - for (Structure bActionChannel : actionChannels) { - String animatedFeatureName = bActionChannel.getFieldValue("name").toString(); - Pointer p = (Pointer) bActionChannel.getFieldValue("ipo"); - if (!p.isNull()) { - Structure ipoStructure = p.fetchData().get(0); - Ipo ipo = this.fromIpoStructure(ipoStructure, blenderContext); - if (ipo != null) {// this can happen when ipo with no curves appear in blender file - lastFrame = Math.max(lastFrame, ipo.getLastFrame()); - blenderAction.featuresTracks.put(animatedFeatureName, ipo); - } - } - } - blenderAction.stopFrame = lastFrame; - return blenderAction; - } - - /** - * This method returns the type of the ipo curve. - * - * @param structure - * the structure must contain the 'rna_path' field and - * 'array_index' field (the type is not important here) - * @param blenderContext - * the blender context - * @return the type of the curve - */ - public int getCurveType(Structure structure, BlenderContext blenderContext) { - // reading rna path first - BlenderInputStream bis = blenderContext.getInputStream(); - int currentPosition = bis.getPosition(); - Pointer pRnaPath = (Pointer) structure.getFieldValue("rna_path"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pRnaPath.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - String rnaPath = bis.readString(); - bis.setPosition(currentPosition); - int arrayIndex = ((Number) structure.getFieldValue("array_index")).intValue(); - - // determining the curve type - if (rnaPath.endsWith("location")) { - return Ipo.AC_LOC_X + arrayIndex; - } - if (rnaPath.endsWith("rotation_quaternion")) { - return Ipo.AC_QUAT_W + arrayIndex; - } - if (rnaPath.endsWith("scale")) { - return Ipo.AC_SIZE_X + arrayIndex; - } - if (rnaPath.endsWith("rotation") || rnaPath.endsWith("rotation_euler")) { - return Ipo.OB_ROT_X + arrayIndex; - } - LOGGER.log(Level.WARNING, "Unknown curve rna path: {0}", rnaPath); - return -1; - } - - /** - * The method returns the actions for the given skeleton. The actions represent armature animation in blender. - * @param skeleton - * the skeleton we fetch the actions for - * @param animationMatchMethod - * the method of animation matching - * @return a list of animations for the specified skeleton - */ - private List getActions(Skeleton skeleton, AnimationMatchMethod animationMatchMethod) { - List result = new ArrayList(); - - // first get a set of bone names - Set boneNames = new HashSet(); - for (int i = 0; i < skeleton.getBoneCount(); ++i) { - String boneName = skeleton.getBone(i).getName(); - if (boneName != null && boneName.length() > 0) { - boneNames.add(skeleton.getBone(i).getName()); - } - } - - // finding matches - Set matchingNames = new HashSet(); - for (Entry actionEntry : blenderContext.getActions().entrySet()) { - // compute how many action tracks match the skeleton bones' names - for (String boneName : boneNames) { - if (actionEntry.getValue().hasTrackName(boneName)) { - matchingNames.add(boneName); - } - } - - BlenderAction action = null; - if (animationMatchMethod == AnimationMatchMethod.AT_LEAST_ONE_NAME_MATCH && matchingNames.size() > 0) { - action = actionEntry.getValue(); - } else if (matchingNames.size() == actionEntry.getValue().getTracksCount()) { - action = actionEntry.getValue(); - } - - if (action != null) { - // remove the tracks that do not match the bone names if the matching method is different from ALL_NAMES_MATCH - if (animationMatchMethod != AnimationMatchMethod.ALL_NAMES_MATCH) { - action = action.clone(); - action.removeTracksThatAreNotInTheCollection(matchingNames); - } - result.add(action); - } - - matchingNames.clear(); - } - return result; - } - - /** - * The method returns the actions for the given node. The actions represent object animation in blender. - * @param node - * the node we fetch the actions for - * @param animationMatchMethod - * the method of animation matching - * @return a list of animations for the specified node - */ - private List getActions(Node node, AnimationMatchMethod animationMatchMethod) { - List result = new ArrayList(); - - for (Entry actionEntry : blenderContext.getActions().entrySet()) { - if (actionEntry.getValue().hasTrackName(node.getName())) { - result.add(actionEntry.getValue()); - } - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java deleted file mode 100644 index 38a437ba00..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BlenderAction.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SpatialTrack; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; - -/** - * An abstract representation of animation. The data stored here is mainly a - * raw action data loaded from blender. It can later be transformed into - * bone or spatial animation and applied to the specified node. - * - * @author Marcin Roguski (Kaelthas) - */ -public class BlenderAction implements Cloneable { - /** The action name. */ - /* package */final String name; - /** Animation speed - frames per second. */ - /* package */int fps; - /** - * The last frame of the animation (the last ipo curve node position is - * used as a last frame). - */ - /* package */int stopFrame; - /** - * Tracks of the features. In case of bone animation the keys are the - * names of the bones. In case of spatial animation - the node's name is - * used. A single ipo contains all tracks for location, rotation and - * scales. - */ - /* package */Map featuresTracks = new HashMap(); - - public BlenderAction(String name, int fps) { - this.name = name; - this.fps = fps; - } - - public void removeTracksThatAreNotInTheCollection(Collection trackNames) { - Map newTracks = new HashMap(); - for (String trackName : trackNames) { - if (featuresTracks.containsKey(trackName)) { - newTracks.put(trackName, featuresTracks.get(trackName)); - } - } - featuresTracks = newTracks; - } - - @Override - public BlenderAction clone() { - BlenderAction result = new BlenderAction(name, fps); - result.stopFrame = stopFrame; - result.featuresTracks = new HashMap(featuresTracks); - return result; - } - - /** - * Converts the action into JME spatial animation tracks. - * - * @param node - * the node that will be animated - * @return the spatial tracks for the node - */ - public SpatialTrack[] toTracks(Node node, BlenderContext blenderContext) { - List tracks = new ArrayList(featuresTracks.size()); - for (Entry entry : featuresTracks.entrySet()) { - tracks.add((SpatialTrack) entry.getValue().calculateTrack(0, null, node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale(), 1, stopFrame, fps, true)); - } - return tracks.toArray(new SpatialTrack[tracks.size()]); - } - - /** - * Converts the action into JME bone animation tracks. - * - * @param skeleton - * the skeleton that will be animated - * @return the bone tracks for the node - */ - public BoneTrack[] toTracks(Skeleton skeleton, BlenderContext blenderContext) { - List tracks = new ArrayList(featuresTracks.size()); - for (Entry entry : featuresTracks.entrySet()) { - int boneIndex = skeleton.getBoneIndex(entry.getKey()); - BoneContext boneContext = blenderContext.getBoneContext(skeleton.getBone(boneIndex)); - tracks.add((BoneTrack) entry.getValue().calculateTrack(boneIndex, boneContext, boneContext.getBone().getBindPosition(), boneContext.getBone().getBindRotation(), boneContext.getBone().getBindScale(), 1, stopFrame, fps, false)); - } - return tracks.toArray(new BoneTrack[tracks.size()]); - } - - /** - * @return the name of the action - */ - public String getName() { - return name; - } - - /** - * @return the time of animations (in seconds) - */ - public float getAnimationTime() { - return (stopFrame - 1) / (float) fps; - } - - /** - * Determines if the current action has a track of a given name. - * CAUTION! The names are case sensitive. - * - * @param name - * the name of the track - * @return true if the track of a given name exists for the - * action and false otherwise - */ - public boolean hasTrackName(String name) { - return featuresTracks.containsKey(name); - } - - /** - * @return the amount of tracks in current action - */ - public int getTracksCount() { - return featuresTracks.size(); - } - - @Override - public String toString() { - return "BlenderTrack [name = " + name + "; tracks = [" + featuresTracks.keySet() + "]]"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java deleted file mode 100644 index adc0a5c4cc..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneContext.java +++ /dev/null @@ -1,400 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.ArrayList; -import java.util.List; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - -/** - * This class holds the basic data that describes a bone. - * - * @author Marcin Roguski (Kaelthas) - */ -public class BoneContext { - // the flags of the bone - public static final int SELECTED = 0x000001; - public static final int CONNECTED_TO_PARENT = 0x000010; - public static final int DEFORM = 0x001000; - public static final int NO_LOCAL_LOCATION = 0x400000; - public static final int NO_INHERIT_SCALE = 0x008000; - public static final int NO_INHERIT_ROTATION = 0x000200; - - /** - * The bones' matrices have, unlike objects', the coordinate system identical to JME's (Y axis is UP, X to the right and Z toward us). - * So in order to have them loaded properly we need to transform their armature matrix (which blender sees as rotated) to make sure we get identical results. - */ - public static final Matrix4f BONE_ARMATURE_TRANSFORMATION_MATRIX = new Matrix4f(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1); - - private static final int IKFLAG_LOCK_X = 0x01; - private static final int IKFLAG_LOCK_Y = 0x02; - private static final int IKFLAG_LOCK_Z = 0x04; - private static final int IKFLAG_LIMIT_X = 0x08; - private static final int IKFLAG_LIMIT_Y = 0x10; - private static final int IKFLAG_LIMIT_Z = 0x20; - - private BlenderContext blenderContext; - /** The OMA of the bone's armature object. */ - private Long armatureObjectOMA; - /** The OMA of the model that owns the bone's skeleton. */ - private Long skeletonOwnerOma; - /** The structure of the bone. */ - private Structure boneStructure; - /** Bone's name. */ - private String boneName; - /** The bone's flag. */ - private int flag; - /** The bone's matrix in world space. */ - private Matrix4f globalBoneMatrix; - /** The bone's matrix in the model space. */ - private Matrix4f boneMatrixInModelSpace; - /** The parent context. */ - private BoneContext parent; - /** The children of this context. */ - private List children = new ArrayList(); - /** Created bone (available after calling 'buildBone' method). */ - private Bone bone; - /** The length of the bone. */ - private float length; - /** The bone's deform envelope. */ - private BoneEnvelope boneEnvelope; - - // The below data is used only for IK constraint computations. - - /** The bone's stretch value. */ - private float ikStretch; - /** Bone's rotation minimum values. */ - private Vector3f limitMin; - /** Bone's rotation maximum values. */ - private Vector3f limitMax; - /** The bone's stiffness values (how much it rotates during IK computations. */ - private Vector3f stiffness; - /** Values that indicate if any axis' rotation should be limited by some angle. */ - private boolean[] limits; - /** Values that indicate if any axis' rotation should be disabled during IK computations. */ - private boolean[] locks; - - /** - * Constructor. Creates the basic set of bone's data. - * - * @param armatureObjectOMA - * the OMA of the bone's armature object - * @param boneStructure - * the bone's structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problem with blender data reading - * occurs - */ - public BoneContext(Long armatureObjectOMA, Structure boneStructure, BlenderContext blenderContext) throws BlenderFileException { - this(boneStructure, armatureObjectOMA, null, blenderContext); - } - - /** - * Constructor. Creates the basic set of bone's data. - * - * @param boneStructure - * the bone's structure - * @param armatureObjectOMA - * the OMA of the bone's armature object - * @param parent - * bone's parent (null if the bone is the root bone) - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problem with blender data reading - * occurs - */ - @SuppressWarnings("unchecked") - private BoneContext(Structure boneStructure, Long armatureObjectOMA, BoneContext parent, BlenderContext blenderContext) throws BlenderFileException { - this.parent = parent; - this.blenderContext = blenderContext; - this.boneStructure = boneStructure; - this.armatureObjectOMA = armatureObjectOMA; - boneName = boneStructure.getFieldValue("name").toString(); - flag = ((Number) boneStructure.getFieldValue("flag")).intValue(); - length = ((Number) boneStructure.getFieldValue("length")).floatValue(); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - - // first get the bone matrix in its armature space - globalBoneMatrix = objectHelper.getMatrix(boneStructure, "arm_mat", blenderContext.getBlenderKey().isFixUpAxis()); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - // then make sure it is rotated in a proper way to fit the jme bone transformation conventions - globalBoneMatrix.multLocal(BONE_ARMATURE_TRANSFORMATION_MATRIX); - } - - Structure armatureStructure = blenderContext.getFileBlock(armatureObjectOMA).getStructure(blenderContext); - Spatial armature = (Spatial) objectHelper.toObject(armatureStructure, blenderContext); - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - Matrix4f armatureWorldMatrix = constraintHelper.toMatrix(armature.getWorldTransform(), new Matrix4f()); - - // and now compute the final bone matrix in world space - globalBoneMatrix = armatureWorldMatrix.mult(globalBoneMatrix); - - // load the bone deformation envelope if necessary - if ((flag & DEFORM) == 0) {// if the flag is NOT set then the DEFORM is in use - boneEnvelope = new BoneEnvelope(boneStructure, armatureWorldMatrix, blenderContext.getBlenderKey().isFixUpAxis()); - } - - // load bone's pose channel data - Pointer pPose = (Pointer) armatureStructure.getFieldValue("pose"); - if (pPose != null && pPose.isNotNull()) { - List poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase(); - for (Structure poseChannel : poseChannels) { - Long boneOMA = ((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress(); - if (boneOMA.equals(this.boneStructure.getOldMemoryAddress())) { - ikStretch = ((Number) poseChannel.getFieldValue("ikstretch")).floatValue(); - DynamicArray limitMin = (DynamicArray) poseChannel.getFieldValue("limitmin"); - this.limitMin = new Vector3f(limitMin.get(0).floatValue(), limitMin.get(1).floatValue(), limitMin.get(2).floatValue()); - - DynamicArray limitMax = (DynamicArray) poseChannel.getFieldValue("limitmax"); - this.limitMax = new Vector3f(limitMax.get(0).floatValue(), limitMax.get(1).floatValue(), limitMax.get(2).floatValue()); - - DynamicArray stiffness = (DynamicArray) poseChannel.getFieldValue("stiffness"); - this.stiffness = new Vector3f(stiffness.get(0).floatValue(), stiffness.get(1).floatValue(), stiffness.get(2).floatValue()); - - int ikFlag = ((Number) poseChannel.getFieldValue("ikflag")).intValue(); - locks = new boolean[] { (ikFlag & IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LOCK_Z) != 0 }; - // limits are enabled when locks are disabled, so we ween to take that into account here - limits = new boolean[] { (ikFlag & IKFLAG_LIMIT_X & ~IKFLAG_LOCK_X) != 0, (ikFlag & IKFLAG_LIMIT_Y & ~IKFLAG_LOCK_Y) != 0, (ikFlag & IKFLAG_LIMIT_Z & ~IKFLAG_LOCK_Z) != 0 }; - break;// we have found what we need, no need to search further - } - } - } - - // create the children - List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(); - for (Structure child : childbase) { - children.add(new BoneContext(child, armatureObjectOMA, this, blenderContext)); - } - - blenderContext.setBoneContext(boneStructure.getOldMemoryAddress(), this); - } - - /** - * This method builds the bone. It recursively builds the bone's children. - * - * @param bones - * a list of bones where the newly created bone will be added - * @param skeletonOwnerOma - * the spatial of the object that will own the skeleton - * @param blenderContext - * the blender context - * @return newly created bone - */ - public Bone buildBone(List bones, Long skeletonOwnerOma, BlenderContext blenderContext) { - this.skeletonOwnerOma = skeletonOwnerOma; - Long boneOMA = boneStructure.getOldMemoryAddress(); - bone = new Bone(boneName); - bones.add(bone); - blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.STRUCTURE, boneStructure); - blenderContext.addLoadedFeatures(boneOMA, LoadedDataType.FEATURE, bone); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - - Structure skeletonOwnerObjectStructure = (Structure) blenderContext.getLoadedFeature(skeletonOwnerOma, LoadedDataType.STRUCTURE); - // I could load 'imat' here, but apparently in some older blenders there were bugs or unfinished functionalities that stored ZERO matrix in imat field - // loading 'obmat' and inverting it makes us avoid errors in such cases - Matrix4f invertedObjectOwnerGlobalMatrix = objectHelper.getMatrix(skeletonOwnerObjectStructure, "obmat", blenderContext.getBlenderKey().isFixUpAxis()).invertLocal(); - if (objectHelper.isParent(skeletonOwnerOma, armatureObjectOMA)) { - boneMatrixInModelSpace = globalBoneMatrix.mult(invertedObjectOwnerGlobalMatrix); - } else { - boneMatrixInModelSpace = invertedObjectOwnerGlobalMatrix.mult(globalBoneMatrix); - } - - Matrix4f boneLocalMatrix = parent == null ? boneMatrixInModelSpace : parent.boneMatrixInModelSpace.invert().multLocal(boneMatrixInModelSpace); - - Vector3f poseLocation = parent == null || !this.is(CONNECTED_TO_PARENT) ? boneLocalMatrix.toTranslationVector() : new Vector3f(0, parent.length, 0); - Quaternion rotation = boneLocalMatrix.toRotationQuat().normalizeLocal(); - Vector3f scale = boneLocalMatrix.toScaleVector(); - - bone.setBindTransforms(poseLocation, rotation, scale); - for (BoneContext child : children) { - bone.addChild(child.buildBone(bones, skeletonOwnerOma, blenderContext)); - } - - return bone; - } - - /** - * @return built bone (available after calling 'buildBone' method) - */ - public Bone getBone() { - return bone; - } - - /** - * @return the old memory address of the bone - */ - public Long getBoneOma() { - return boneStructure.getOldMemoryAddress(); - } - - /** - * The method returns the length of the bone. - * If you want to use it for bone debugger take model space scale into account and do - * something like this: - * boneContext.getLength() * boneContext.getBone().getModelSpaceScale().y. - * Otherwise the bones might not look as they should in the bone debugger. - * @return the length of the bone - */ - public float getLength() { - return length; - } - - /** - * @return OMA of the bone's armature object - */ - public Long getArmatureObjectOMA() { - return armatureObjectOMA; - } - - /** - * @return the OMA of the model that owns the bone's skeleton - */ - public Long getSkeletonOwnerOma() { - return skeletonOwnerOma; - } - - /** - * @return the skeleton the bone of this context belongs to - */ - public Skeleton getSkeleton() { - return blenderContext.getSkeleton(armatureObjectOMA); - } - - /** - * @return the initial bone's matrix in model space - */ - public Matrix4f getBoneMatrixInModelSpace() { - return boneMatrixInModelSpace; - } - - /** - * @return the vertex assigning envelope of the bone - */ - public BoneEnvelope getBoneEnvelope() { - return boneEnvelope; - } - - /** - * @return bone's stretch factor - */ - public float getIkStretch() { - return ikStretch; - } - - /** - * @return indicates if the X rotation should be limited - */ - public boolean isLimitX() { - return limits != null ? limits[0] : false; - } - - /** - * @return indicates if the Y rotation should be limited - */ - public boolean isLimitY() { - return limits != null ? limits[1] : false; - } - - /** - * @return indicates if the Z rotation should be limited - */ - public boolean isLimitZ() { - return limits != null ? limits[2] : false; - } - - /** - * @return indicates if the X rotation should be disabled - */ - public boolean isLockX() { - return locks != null ? locks[0] : false; - } - - /** - * @return indicates if the Y rotation should be disabled - */ - public boolean isLockY() { - return locks != null ? locks[1] : false; - } - - /** - * @return indicates if the Z rotation should be disabled - */ - public boolean isLockZ() { - return locks != null ? locks[2] : false; - } - - /** - * @return the minimum values in rotation limitation (if limitation is enabled for specific axis). - */ - public Vector3f getLimitMin() { - return limitMin; - } - - /** - * @return the maximum values in rotation limitation (if limitation is enabled for specific axis). - */ - public Vector3f getLimitMax() { - return limitMax; - } - - /** - * @return the stiffness of the bone - */ - public Vector3f getStiffness() { - return stiffness; - } - - /** - * Tells if the bone is of specified property defined by its flag. - * @param flagMask - * the mask of the flag (constants defined in this class) - * @return true if the bone IS of specified proeprty and false otherwise - */ - public boolean is(int flagMask) { - return (flag & flagMask) != 0; - } - - /** - * @return the root bone context of this bone context - */ - public BoneContext getRoot() { - BoneContext result = this; - while (result.parent != null) { - result = result.parent; - } - return result; - } - - /** - * @return a number of bones from this bone to its root - */ - public int getDistanceFromRoot() { - int result = 0; - BoneContext boneContext = this; - while (boneContext.parent != null) { - boneContext = boneContext.parent; - ++result; - } - return result; - } - - @Override - public String toString() { - return "BoneContext: " + boneName; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java deleted file mode 100644 index 83e708dc10..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/BoneEnvelope.java +++ /dev/null @@ -1,133 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * An implementation of bone envelope. Used when assigning bones to the mesh by envelopes. - * - * @author Marcin Roguski - */ -public class BoneEnvelope { - /** A defined distance that will be included in the envelope space. */ - private float distance; - /** The bone's weight. */ - private float weight; - /** The radius of the bone's head. */ - private float boneHeadRadius; - /** The radius of the bone's tail. */ - private float boneTailRadius; - /** Head position in rest pose in world space. */ - private Vector3f head; - /** Tail position in rest pose in world space. */ - private Vector3f tail; - - /** - * The constructor of bone envelope. It reads all the needed data. Take notice that the positions of head and tail - * are computed in the world space and that the points' positions given for computations should be in world space as well. - * - * @param boneStructure - * the blender bone structure - * @param armatureWorldMatrix - * the world matrix of the armature object - * @param fixUpAxis - * a variable that tells if we use the Y-is up axis orientation - */ - @SuppressWarnings("unchecked") - public BoneEnvelope(Structure boneStructure, Matrix4f armatureWorldMatrix, boolean fixUpAxis) { - distance = ((Number) boneStructure.getFieldValue("dist")).floatValue(); - weight = ((Number) boneStructure.getFieldValue("weight")).floatValue(); - boneHeadRadius = ((Number) boneStructure.getFieldValue("rad_head")).floatValue(); - boneTailRadius = ((Number) boneStructure.getFieldValue("rad_tail")).floatValue(); - - DynamicArray headArray = (DynamicArray) boneStructure.getFieldValue("arm_head"); - head = new Vector3f(headArray.get(0).floatValue(), headArray.get(1).floatValue(), headArray.get(2).floatValue()); - if (fixUpAxis) { - float z = head.z; - head.z = -head.y; - head.y = z; - } - armatureWorldMatrix.mult(head, head);// move the head point to global space - - DynamicArray tailArray = (DynamicArray) boneStructure.getFieldValue("arm_tail"); - tail = new Vector3f(tailArray.get(0).floatValue(), tailArray.get(1).floatValue(), tailArray.get(2).floatValue()); - if (fixUpAxis) { - float z = tail.z; - tail.z = -tail.y; - tail.y = z; - } - armatureWorldMatrix.mult(tail, tail);// move the tail point to global space - } - - /** - * The method verifies if the given point is inside the envelope. - * @param point - * the point in 3D space (MUST be in a world coordinate space) - * @return true if the point is inside the envelope and false otherwise - */ - public boolean isInEnvelope(Vector3f point) { - Vector3f v = tail.subtract(head); - float boneLength = v.length(); - v.normalizeLocal(); - - // computing a plane that contains 'point' and v is its normal vector - // the plane's equation is: Ax + By + Cz + D = 0, where v = [A, B, C] - float D = -v.dot(point); - - // computing a point where a line that contains head and tail crosses the plane - float temp = -(v.dot(head) + D) / v.dot(v); - Vector3f p = head.add(v.x * temp, v.y * temp, v.z * temp); - - // determining if the point p is on the same or other side of head than the tail point - Vector3f headToPointOnLineVector = p.subtract(head); - float headToPointLength = headToPointOnLineVector.length(); - float cosinus = headToPointOnLineVector.dot(v) / headToPointLength;// the length of v is already = 1; cosinus should be either 1, 0 or -1 - if (cosinus < 0 && headToPointLength > boneHeadRadius || headToPointLength > boneLength + boneTailRadius) { - return false;// the point is outside the anvelope - } - - // now check if the point is inside and envelope - float pointDistanceFromLine = point.subtract(p).length(), maximumDistance = 0; - if (cosinus < 0) { - // checking if the distance from p to point is inside the half sphere defined by head envelope - // compute the distance from the line to the half sphere border - maximumDistance = boneHeadRadius; - } else if (headToPointLength < boneLength) { - // compute the maximum available distance - if (boneTailRadius > boneHeadRadius) { - // compute the distance from head to p - float headToPDistance = p.subtract(head).length(); - // from tangens function we have - float x = headToPDistance * ((boneTailRadius - boneHeadRadius) / boneLength); - maximumDistance = x + boneHeadRadius; - } else if (boneTailRadius < boneHeadRadius) { - // compute the distance from head to p - float tailToPDistance = p.subtract(tail).length(); - // from tangens function we have - float x = tailToPDistance * ((boneHeadRadius - boneTailRadius) / boneLength); - maximumDistance = x + boneTailRadius; - } else { - maximumDistance = boneTailRadius; - } - } else { - // checking if the distance from p to point is inside the half sphere defined by tail envelope - maximumDistance = boneTailRadius; - } - - return pointDistanceFromLine <= maximumDistance + distance; - } - - /** - * @return the weight of the bone - */ - public float getWeight() { - return weight; - } - - @Override - public String toString() { - return "BoneEnvelope [d=" + distance + ", w=" + weight + ", hr=" + boneHeadRadius + ", tr=" + boneTailRadius + ", (" + head + ") -> (" + tail + ")]"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java deleted file mode 100644 index 6665752b7e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/animations/Ipo.java +++ /dev/null @@ -1,317 +0,0 @@ -package com.jme3.scene.plugins.blender.animations; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.BoneTrack; -import com.jme3.animation.SpatialTrack; -import com.jme3.animation.Track; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.curves.BezierCurve; - -/** - * This class is used to calculate bezier curves value for the given frames. The - * Ipo (interpolation object) consists of several b-spline curves (connected 3rd - * degree bezier curves) of a different type. - * - * @author Marcin Roguski - */ -public class Ipo { - private static final Logger LOGGER = Logger.getLogger(Ipo.class.getName()); - - public static final int AC_LOC_X = 1; - public static final int AC_LOC_Y = 2; - public static final int AC_LOC_Z = 3; - public static final int OB_ROT_X = 7; - public static final int OB_ROT_Y = 8; - public static final int OB_ROT_Z = 9; - public static final int AC_SIZE_X = 13; - public static final int AC_SIZE_Y = 14; - public static final int AC_SIZE_Z = 15; - public static final int AC_QUAT_W = 25; - public static final int AC_QUAT_X = 26; - public static final int AC_QUAT_Y = 27; - public static final int AC_QUAT_Z = 28; - - /** A list of bezier curves for this interpolation object. */ - private BezierCurve[] bezierCurves; - /** Each ipo contains one bone track. */ - private Track calculatedTrack; - /** This variable indicates if the Y asxis is the UP axis or not. */ - protected boolean fixUpAxis; - /** - * Depending on the blender version rotations are stored in degrees or - * radians so we need to know the version that is used. - */ - protected final int blenderVersion; - - /** - * Constructor. Stores the bezier curves. - * - * @param bezierCurves - * a table of bezier curves - * @param fixUpAxis - * indicates if the Y is the up axis or not - * @param blenderVersion - * the blender version that is currently used - */ - public Ipo(BezierCurve[] bezierCurves, boolean fixUpAxis, int blenderVersion) { - this.bezierCurves = bezierCurves; - this.fixUpAxis = fixUpAxis; - this.blenderVersion = blenderVersion; - } - - /** - * This method calculates the ipo value for the first curve. - * - * @param frame - * the frame for which the value is calculated - * @return calculated ipo value - */ - public double calculateValue(int frame) { - return this.calculateValue(frame, 0); - } - - /** - * This method calculates the ipo value for the curve of the specified - * index. Make sure you do not exceed the curves amount. Alway chech the - * amount of curves before calling this method. - * - * @param frame - * the frame for which the value is calculated - * @param curveIndex - * the index of the curve - * @return calculated ipo value - */ - public double calculateValue(int frame, int curveIndex) { - return bezierCurves[curveIndex].evaluate(frame, BezierCurve.Y_VALUE); - } - - /** - * This method returns the frame where last bezier triple center point of - * the specified bezier curve is located. - * - * @return the frame number of the last defined bezier triple point for the - * specified ipo - */ - public int getLastFrame() { - int result = 1; - for (int i = 0; i < bezierCurves.length; ++i) { - int tempResult = bezierCurves[i].getLastFrame(); - if (tempResult > result) { - result = tempResult; - } - } - return result; - } - - /** - * This method calculates the value of the curves as a bone track between - * the specified frames. - * - * @param targetIndex - * the index of the target for which the method calculates the - * tracks IMPORTANT! Aet to -1 (or any negative number) if you - * want to load spatial animation. - * @param localTranslation - * the local translation of the object/bone that will be animated by - * the track - * @param localRotation - * the local rotation of the object/bone that will be animated by - * the track - * @param localScale - * the local scale of the object/bone that will be animated by - * the track - * @param startFrame - * the first frame of tracks (inclusive) - * @param stopFrame - * the last frame of the tracks (inclusive) - * @param fps - * frame rate (frames per second) - * @param spatialTrack - * this flag indicates if the track belongs to a spatial or to a - * bone; the difference is important because it appears that bones - * in blender have the same type of coordinate system (Y as UP) - * as jme while other features have different one (Z is UP) - * @return bone track for the specified bone - */ - public Track calculateTrack(int targetIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean spatialTrack) { - if (calculatedTrack == null) { - // preparing data for track - int framesAmount = stopFrame - startFrame; - float timeBetweenFrames = 1.0f / fps; - - float[] times = new float[framesAmount + 1]; - Vector3f[] translations = new Vector3f[framesAmount + 1]; - float[] translation = new float[3]; - Quaternion[] rotations = new Quaternion[framesAmount + 1]; - float[] quaternionRotation = new float[] { localRotation.getX(), localRotation.getY(), localRotation.getZ(), localRotation.getW(), }; - float[] eulerRotation = localRotation.toAngles(null); - Vector3f[] scales = new Vector3f[framesAmount + 1]; - float[] scale = new float[] { localScale.x, localScale.y, localScale.z }; - float degreeToRadiansFactor = 1; - if (blenderVersion < 250) {// in blender earlier than 2.50 the values are stored in degrees - degreeToRadiansFactor *= FastMath.DEG_TO_RAD * 10;// the values in blender are divided by 10, so we need to mult it here - } - int yIndex = 1, zIndex = 2; - boolean swapAxes = spatialTrack && fixUpAxis; - if (swapAxes) { - yIndex = 2; - zIndex = 1; - } - boolean eulerRotationUsed = false, queternionRotationUsed = false; - - // calculating track data - for (int frame = startFrame; frame <= stopFrame; ++frame) { - boolean translationSet = false; - translation[0] = translation[1] = translation[2] = 0; - int index = frame - startFrame; - times[index] = index * timeBetweenFrames;// start + (frame - 1) * timeBetweenFrames; - for (int j = 0; j < bezierCurves.length; ++j) { - double value = bezierCurves[j].evaluate(frame, BezierCurve.Y_VALUE); - switch (bezierCurves[j].getType()) { - // LOCATION - case AC_LOC_X: - translation[0] = (float) value; - translationSet = true; - break; - case AC_LOC_Y: - if (swapAxes && value != 0) { - value = -value; - } - translation[yIndex] = (float) value; - translationSet = true; - break; - case AC_LOC_Z: - translation[zIndex] = (float) value; - translationSet = true; - break; - - // EULER ROTATION - case OB_ROT_X: - eulerRotationUsed = true; - eulerRotation[0] = (float) value * degreeToRadiansFactor; - break; - case OB_ROT_Y: - eulerRotationUsed = true; - if (swapAxes && value != 0) { - value = -value; - } - eulerRotation[yIndex] = (float) value * degreeToRadiansFactor; - break; - case OB_ROT_Z: - eulerRotationUsed = true; - eulerRotation[zIndex] = (float) value * degreeToRadiansFactor; - break; - - // SIZE - case AC_SIZE_X: - scale[0] = (float) value; - break; - case AC_SIZE_Y: - scale[yIndex] = (float) value; - break; - case AC_SIZE_Z: - scale[zIndex] = (float) value; - break; - - // QUATERNION ROTATION (used with bone animation) - case AC_QUAT_W: - queternionRotationUsed = true; - quaternionRotation[3] = (float) value; - break; - case AC_QUAT_X: - queternionRotationUsed = true; - quaternionRotation[0] = (float) value; - break; - case AC_QUAT_Y: - queternionRotationUsed = true; - if (swapAxes && value != 0) { - value = -value; - } - quaternionRotation[yIndex] = (float) value; - break; - case AC_QUAT_Z: - quaternionRotation[zIndex] = (float) value; - break; - default: - LOGGER.log(Level.WARNING, "Unknown ipo curve type: {0}.", bezierCurves[j].getType()); - } - } - if(translationSet) { - translations[index] = localRotation.multLocal(new Vector3f(translation[0], translation[1], translation[2])); - } else { - translations[index] = new Vector3f(); - } - - if(boneContext != null) { - if(boneContext.getBone().getParent() == null && boneContext.is(BoneContext.NO_LOCAL_LOCATION)) { - float temp = translations[index].z; - translations[index].z = -translations[index].y; - translations[index].y = temp; - } - } - - if (queternionRotationUsed) { - rotations[index] = new Quaternion(quaternionRotation[0], quaternionRotation[1], quaternionRotation[2], quaternionRotation[3]); - } else { - rotations[index] = new Quaternion().fromAngles(eulerRotation); - } - - scales[index] = new Vector3f(scale[0], scale[1], scale[2]); - } - if (spatialTrack) { - calculatedTrack = new SpatialTrack(times, translations, rotations, scales); - } else { - calculatedTrack = new BoneTrack(targetIndex, times, translations, rotations, scales); - } - - if (queternionRotationUsed && eulerRotationUsed) { - LOGGER.warning("Animation uses both euler and quaternion tracks for rotations. Quaternion rotation is applied. Make sure that this is what you wanted!"); - } - } - - return calculatedTrack; - } - - /** - * Ipo constant curve. This is a curve with only one value and no specified - * type. This type of ipo cannot be used to calculate tracks. It should only - * be used to calculate single value for a given frame. - * - * @author Marcin Roguski (Kaelthas) - */ - /* package */static class ConstIpo extends Ipo { - - /** The constant value of this ipo. */ - private float constValue; - - /** - * Constructor. Stores the constant value of this ipo. - * - * @param constValue - * the constant value of this ipo - */ - public ConstIpo(float constValue) { - super(null, false, 0);// the version is not important here - this.constValue = constValue; - } - - @Override - public double calculateValue(int frame) { - return constValue; - } - - @Override - public double calculateValue(int frame, int curveIndex) { - return constValue; - } - - @Override - public BoneTrack calculateTrack(int boneIndex, BoneContext boneContext, Vector3f localTranslation, Quaternion localRotation, Vector3f localScale, int startFrame, int stopFrame, int fps, boolean boneTrack) { - throw new IllegalStateException("Constatnt ipo object cannot be used for calculating bone tracks!"); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java deleted file mode 100644 index 70cb09b1f5..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/cameras/CameraHelper.java +++ /dev/null @@ -1,148 +0,0 @@ -package com.jme3.scene.plugins.blender.cameras; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.renderer.Camera; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that is used to load cameras into the scene. - * @author Marcin Roguski - */ -public class CameraHelper extends AbstractBlenderHelper { - - private static final Logger LOGGER = Logger.getLogger(CameraHelper.class.getName()); - protected static final int DEFAULT_CAM_WIDTH = 640; - protected static final int DEFAULT_CAM_HEIGHT = 480; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public CameraHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This method converts the given structure to jme camera. - * - * @param structure - * camera structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - public Camera toCamera(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - if (blenderVersion >= 250) { - return this.toCamera250(structure, blenderContext.getSceneStructure()); - } else { - return this.toCamera249(structure); - } - } - - /** - * This method converts the given structure to jme camera. Should be used form blender 2.5+. - * - * @param structure - * camera structure - * @param sceneStructure - * scene structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - private Camera toCamera250(Structure structure, Structure sceneStructure) throws BlenderFileException { - int width = DEFAULT_CAM_WIDTH; - int height = DEFAULT_CAM_HEIGHT; - if (sceneStructure != null) { - Structure renderData = (Structure) sceneStructure.getFieldValue("r"); - width = ((Number) renderData.getFieldValue("xsch")).shortValue(); - height = ((Number) renderData.getFieldValue("ysch")).shortValue(); - } - Camera camera = new Camera(width, height); - int type = ((Number) structure.getFieldValue("type")).intValue(); - if (type != 0 && type != 1) { - LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); - type = 0; - } - // type==0 - perspective; type==1 - orthographic; perspective is used as default - camera.setParallelProjection(type == 1); - float aspect = width / (float) height; - float fovY; // Vertical field of view in degrees - float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); - float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); - if (type == 0) { - // Convert lens MM to vertical degrees in fovY, see Blender rna_Camera_angle_get() - // Default sensor size prior to 2.60 was 32. - float sensor = 32.0f; - boolean sensorVertical = false; - Number sensorFit = (Number) structure.getFieldValue("sensor_fit"); - if (sensorFit != null) { - // If sensor_fit is vert (2), then sensor_y is used - sensorVertical = sensorFit.byteValue() == 2; - String sensorName = "sensor_x"; - if (sensorVertical) { - sensorName = "sensor_y"; - } - sensor = ((Number) structure.getFieldValue(sensorName)).floatValue(); - } - float focalLength = ((Number) structure.getFieldValue("lens")).floatValue(); - float fov = 2.0f * FastMath.atan(sensor / 2.0f / focalLength); - if (sensorVertical) { - fovY = fov * FastMath.RAD_TO_DEG; - } else { - // Convert fov from horizontal to vertical - fovY = 2.0f * FastMath.atan(FastMath.tan(fov / 2.0f) / aspect) * FastMath.RAD_TO_DEG; - } - } else { - // This probably is not correct. - fovY = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); - } - camera.setFrustumPerspective(fovY, aspect, clipsta, clipend); - camera.setName(structure.getName()); - return camera; - } - - /** - * This method converts the given structure to jme camera. Should be used form blender 2.49. - * - * @param structure - * camera structure - * @return jme camera object - * @throws BlenderFileException - * an exception is thrown when there are problems with the - * blender file - */ - private Camera toCamera249(Structure structure) throws BlenderFileException { - Camera camera = new Camera(DEFAULT_CAM_WIDTH, DEFAULT_CAM_HEIGHT); - int type = ((Number) structure.getFieldValue("type")).intValue(); - if (type != 0 && type != 1) { - LOGGER.log(Level.WARNING, "Unknown camera type: {0}. Perspective camera is being used!", type); - type = 0; - } - // type==0 - perspective; type==1 - orthographic; perspective is used as default - camera.setParallelProjection(type == 1); - float aspect = 0; - float clipsta = ((Number) structure.getFieldValue("clipsta")).floatValue(); - float clipend = ((Number) structure.getFieldValue("clipend")).floatValue(); - if (type == 0) { - aspect = ((Number) structure.getFieldValue("lens")).floatValue(); - } else { - aspect = ((Number) structure.getFieldValue("ortho_scale")).floatValue(); - } - camera.setFrustumPerspective(aspect, camera.getWidth() / camera.getHeight(), clipsta, clipend); - camera.setName(structure.getName()); - return camera; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java deleted file mode 100644 index fdba8af3f8..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/BoneConstraint.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - -/** - * Constraint applied on the bone. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class BoneConstraint extends Constraint { - private static final Logger LOGGER = Logger.getLogger(BoneConstraint.class.getName()); - - /** - * The bone constraint constructor. - * - * @param constraintStructure - * the constraint's structure - * @param ownerOMA - * the OMA of the bone that owns the constraint - * @param influenceIpo - * the influence interpolation curve - * @param blenderContext - * the blender context - * @throws BlenderFileException - * exception thrown when problems with blender file occur - */ - public BoneConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } - - @Override - public boolean validate() { - if (targetOMA != null) { - Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE); - if (nodeTarget == null) { - LOGGER.log(Level.WARNING, "Cannot find target for constraint: {0}.", name); - return false; - } - // the second part of the if expression verifies if the found node - // (if any) is an armature node - if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { - if (subtargetName.trim().isEmpty()) { - LOGGER.log(Level.WARNING, "No bone target specified for constraint: {0}.", name); - return false; - } - // if the target is not an object node then it is an Armature, - // so make sure the bone is in the current skeleton - BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); - if (targetOMA.longValue() != boneContext.getArmatureObjectOMA().longValue()) { - LOGGER.log(Level.WARNING, "Bone constraint {0} must target bone in the its own skeleton! Targeting bone in another skeleton is not supported!", name); - return false; - } - } - } - return constraintDefinition == null ? true : constraintDefinition.isTargetRequired(); - } - - @Override - public void apply(int frame) { - super.apply(frame); - blenderContext.getBoneContext(ownerOMA).getBone().updateModelTransforms(); - } - - @Override - public Long getTargetOMA() { - if(targetOMA != null && subtargetName != null && !subtargetName.trim().isEmpty()) { - Spatial nodeTarget = (Spatial) blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE); - if(nodeTarget != null) { - if(blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, nodeTarget) != null) { - BoneContext boneContext = blenderContext.getBoneByName(targetOMA, subtargetName); - return boneContext != null ? boneContext.getBoneOma() : 0L; - } - return targetOMA; - } - } - return 0L; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java deleted file mode 100644 index d96a1f2ce9..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/Constraint.java +++ /dev/null @@ -1,186 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinition; -import com.jme3.scene.plugins.blender.constraints.definitions.ConstraintDefinitionFactory; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * The implementation of a constraint. - * - * @author Marcin Roguski (Kaelthas) - */ -public abstract class Constraint { - private static final Logger LOGGER = Logger.getLogger(Constraint.class.getName()); - - /** The name of this constraint. */ - protected final String name; - /** Indicates if the constraint is already baked or not. */ - protected boolean baked; - - protected Space ownerSpace; - protected final ConstraintDefinition constraintDefinition; - protected Long ownerOMA; - - protected Long targetOMA; - protected Space targetSpace; - protected String subtargetName; - - /** The ipo object defining influence. */ - protected final Ipo ipo; - /** The blender context. */ - protected final BlenderContext blenderContext; - protected final ConstraintHelper constraintHelper; - - /** - * This constructor creates the constraint instance. - * - * @param constraintStructure - * the constraint's structure (bConstraint clss in blender 2.49). - * @param ownerOMA - * the old memory address of the constraint owner - * @param influenceIpo - * the ipo curve of the influence factor - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Constraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - this.blenderContext = blenderContext; - name = constraintStructure.getFieldValue("name").toString(); - Pointer pData = (Pointer) constraintStructure.getFieldValue("data"); - if (pData.isNotNull()) { - Structure data = pData.fetchData().get(0); - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(data, name, ownerOMA, blenderContext); - Pointer pTar = (Pointer) data.getFieldValue("tar"); - if (pTar != null && pTar.isNotNull()) { - targetOMA = pTar.getOldMemoryAddress(); - targetSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("tarspace")).byteValue()); - Object subtargetValue = data.getFieldValue("subtarget"); - if (subtargetValue != null) {// not all constraint data have the - // subtarget field - subtargetName = subtargetValue.toString(); - } - } - } else { - // Null constraint has no data, so create it here - constraintDefinition = ConstraintDefinitionFactory.createConstraintDefinition(null, name, null, blenderContext); - } - ownerSpace = Space.valueOf(((Number) constraintStructure.getFieldValue("ownspace")).byteValue()); - ipo = influenceIpo; - this.ownerOMA = ownerOMA; - constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - LOGGER.log(Level.INFO, "Created constraint: {0} with definition: {1}", new Object[] { name, constraintDefinition }); - } - - /** - * @return true if the constraint is implemented and false - * otherwise - */ - public boolean isImplemented() { - return constraintDefinition == null ? true : constraintDefinition.isImplemented(); - } - - /** - * @return the name of the constraint type, similar to the constraint name - * used in Blender - */ - public String getConstraintTypeName() { - return constraintDefinition.getConstraintTypeName(); - } - - /** - * @return the OMAs of the features whose transform had been altered beside the constraint owner - */ - public Set getAlteredOmas() { - return constraintDefinition.getAlteredOmas(); - } - - /** - * Performs validation before baking. Checks factors that can prevent - * constraint from baking that could not be checked during constraint - * loading. - */ - public abstract boolean validate(); - - /** - * @return the OMA of the target or 0 if no target is specified for the constraint - */ - public abstract Long getTargetOMA(); - - /** - * Applies the constraint to owner (and in some cases can alter other bones of the skeleton). - * @param frame - * the frame of the animation - */ - public void apply(int frame) { - if (LOGGER.isLoggable(Level.FINEST)) { - LOGGER.log(Level.FINEST, "Applying constraint: {0} for frame {1}", new Object[] { name, frame }); - } - Transform targetTransform = targetOMA != null ? constraintHelper.getTransform(targetOMA, subtargetName, targetSpace) : null; - constraintDefinition.bake(ownerSpace, targetSpace, targetTransform, (float) ipo.calculateValue(frame)); - } - - /** - * @return determines if the definition of the constraint will change the bone in any way; in most cases - * it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint - * computing to improve the computation speed and lower the computations complexity - */ - public boolean isTrackToBeChanged() { - return constraintDefinition == null ? false : constraintDefinition.isTrackToBeChanged(); - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (name == null ? 0 : name.hashCode()); - result = prime * result + (ownerOMA == null ? 0 : ownerOMA.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (this.getClass() != obj.getClass()) { - return false; - } - Constraint other = (Constraint) obj; - if (name == null) { - if (other.name != null) { - return false; - } - } else if (!name.equals(other.name)) { - return false; - } - if (ownerOMA == null) { - if (other.ownerOMA != null) { - return false; - } - } else if (!ownerOMA.equals(other.ownerOMA)) { - return false; - } - return true; - } - - @Override - public String toString() { - return "Constraint(name = " + name + ", def = " + constraintDefinition + ")"; - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java deleted file mode 100644 index eb253396a4..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/ConstraintHelper.java +++ /dev/null @@ -1,476 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Logger; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; -import com.jme3.util.TempVars; - -/** - * This class should be used for constraint calculations. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ConstraintHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ConstraintHelper.class.getName()); - - /** - * Helper constructor. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public ConstraintHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This method reads constraints for for the given structure. The - * constraints are loaded only once for object/bone. - * - * @param objectStructure - * the structure we read constraint's for - * @param blenderContext - * the blender context - * @throws BlenderFileException - */ - public void loadConstraints(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.fine("Loading constraints."); - // reading influence ipos for the constraints - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - Map> constraintsIpos = new HashMap>(); - Pointer pActions = (Pointer) objectStructure.getFieldValue("action"); - if (pActions.isNotNull()) { - List actions = pActions.fetchData(); - for (Structure action : actions) { - Structure chanbase = (Structure) action.getFieldValue("chanbase"); - List actionChannels = chanbase.evaluateListBase(); - for (Structure actionChannel : actionChannels) { - Map ipos = new HashMap(); - Structure constChannels = (Structure) actionChannel.getFieldValue("constraintChannels"); - List constraintChannels = constChannels.evaluateListBase(); - for (Structure constraintChannel : constraintChannels) { - Pointer pIpo = (Pointer) constraintChannel.getFieldValue("ipo"); - if (pIpo.isNotNull()) { - String constraintName = constraintChannel.getFieldValue("name").toString(); - Ipo ipo = animationHelper.fromIpoStructure(pIpo.fetchData().get(0), blenderContext); - ipos.put(constraintName, ipo); - } - } - String actionName = actionChannel.getFieldValue("name").toString(); - constraintsIpos.put(actionName, ipos); - } - } - } - - // loading constraints connected with the object's bones - Pointer pPose = (Pointer) objectStructure.getFieldValue("pose"); - if (pPose.isNotNull()) { - List poseChannels = ((Structure) pPose.fetchData().get(0).getFieldValue("chanbase")).evaluateListBase(); - for (Structure poseChannel : poseChannels) { - List constraintsList = new ArrayList(); - Long boneOMA = Long.valueOf(((Pointer) poseChannel.getFieldValue("bone")).getOldMemoryAddress()); - - // the name is read directly from structure because bone might - // not yet be loaded - String name = blenderContext.getFileBlock(boneOMA).getStructure(blenderContext).getFieldValue("name").toString(); - List constraints = ((Structure) poseChannel.getFieldValue("constraints")).evaluateListBase(); - for (Structure constraint : constraints) { - String constraintName = constraint.getFieldValue("name").toString(); - Map ipoMap = constraintsIpos.get(name); - Ipo ipo = ipoMap == null ? null : ipoMap.get(constraintName); - if (ipo == null) { - float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = animationHelper.fromValue(enforce); - } - constraintsList.add(new BoneConstraint(constraint, boneOMA, ipo, blenderContext)); - } - blenderContext.addConstraints(boneOMA, constraintsList); - } - } - - // loading constraints connected with the object itself - List constraints = ((Structure) objectStructure.getFieldValue("constraints")).evaluateListBase(); - if (constraints != null && constraints.size() > 0) { - Pointer pData = (Pointer) objectStructure.getFieldValue("data"); - String dataType = pData.isNotNull() ? pData.fetchData().get(0).getType() : null; - List constraintsList = new ArrayList(constraints.size()); - - for (Structure constraint : constraints) { - String constraintName = constraint.getFieldValue("name").toString(); - String objectName = objectStructure.getName(); - - Map objectConstraintsIpos = constraintsIpos.get(objectName); - Ipo ipo = objectConstraintsIpos != null ? objectConstraintsIpos.get(constraintName) : null; - if (ipo == null) { - float enforce = ((Number) constraint.getFieldValue("enforce")).floatValue(); - ipo = animationHelper.fromValue(enforce); - } - - constraintsList.add(this.createConstraint(dataType, constraint, objectStructure.getOldMemoryAddress(), ipo, blenderContext)); - } - blenderContext.addConstraints(objectStructure.getOldMemoryAddress(), constraintsList); - } - } - - /** - * This method creates a proper constraint object depending on the object's - * data type. Supported data types:
  • Mesh
  • Armature
  • Camera
  • - * Lamp Bone constraints are created in a different place. - * - * @param dataType - * the type of the object's data - * @param constraintStructure - * the constraint structure - * @param ownerOMA - * the owner OMA - * @param influenceIpo - * the influence interpolation curve - * @param blenderContext - * the blender context - * @return constraint object for the required type - * @throws BlenderFileException - * thrown when problems with blender file occurred - */ - private Constraint createConstraint(String dataType, Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - if (dataType == null || "Mesh".equalsIgnoreCase(dataType) || "Camera".equalsIgnoreCase(dataType) || "Lamp".equalsIgnoreCase(dataType)) { - return new SpatialConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } else if ("Armature".equalsIgnoreCase(dataType)) { - return new SkeletonConstraint(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } else { - throw new IllegalArgumentException("Unsupported data type for applying constraints: " + dataType); - } - } - - /** - * The method bakes all available and valid constraints. - * - * @param blenderContext - * the blender context - */ - public void bakeConstraints(BlenderContext blenderContext) { - Set owners = new HashSet(); - for (Constraint constraint : blenderContext.getAllConstraints()) { - if(constraint instanceof BoneConstraint) { - BoneContext boneContext = blenderContext.getBoneContext(constraint.ownerOMA); - owners.add(boneContext.getArmatureObjectOMA()); - } else { - Spatial spatial = (Spatial) blenderContext.getLoadedFeature(constraint.ownerOMA, LoadedDataType.FEATURE); - while (spatial.getParent() != null) { - spatial = spatial.getParent(); - } - owners.add((Long)blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, spatial)); - } - } - - List simulationRootNodes = new ArrayList(owners.size()); - for(Long ownerOMA : owners) { - simulationRootNodes.add(new SimulationNode(ownerOMA, blenderContext)); - } - - for (SimulationNode node : simulationRootNodes) { - node.simulate(); - } - } - - /** - * The method retrieves the transform from a feature in a given space. - * - * @param oma - * the OMA of the feature (spatial or armature node) - * @param subtargetName - * the feature's subtarget (bone in a case of armature's node) - * @param space - * the space the transform is evaluated to - * @return the transform of a feature in a given space - */ - public Transform getTransform(Long oma, String subtargetName, Space space) { - Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE); - boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null; - if (isArmature) { - blenderContext.getSkeleton(oma).updateWorldVectors(); - BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); - Bone bone = targetBoneContext.getBone(); - - if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) { - space = Space.CONSTRAINT_SPACE_POSE; - } - - TempVars tempVars = TempVars.get();// use readable names of the matrices so that the code is more clear - Transform result; - switch (space) { - case CONSTRAINT_SPACE_WORLD: - Spatial model = (Spatial) blenderContext.getLoadedFeature(targetBoneContext.getSkeletonOwnerOma(), LoadedDataType.FEATURE); - Matrix4f boneModelMatrix = this.toMatrix(bone.getModelSpacePosition(), bone.getModelSpaceRotation(), bone.getModelSpaceScale(), tempVars.tempMat4); - Matrix4f modelWorldMatrix = this.toMatrix(model.getWorldTransform(), tempVars.tempMat42); - Matrix4f boneMatrixInWorldSpace = modelWorldMatrix.multLocal(boneModelMatrix); - result = new Transform(boneMatrixInWorldSpace.toTranslationVector(), boneMatrixInWorldSpace.toRotationQuat(), boneMatrixInWorldSpace.toScaleVector()); - break; - case CONSTRAINT_SPACE_LOCAL: - assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!"; - result = new Transform(bone.getLocalPosition(), bone.getLocalRotation(), bone.getLocalScale()); - break; - case CONSTRAINT_SPACE_POSE: { - Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4); - Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal(); - Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix); - result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector()); - break; - } - case CONSTRAINT_SPACE_PARLOCAL: { - Matrix4f boneWorldMatrix = this.toMatrix(this.getTransform(oma, subtargetName, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat4); - Matrix4f armatureInvertedWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat42).invertLocal(); - Matrix4f bonePoseMatrix = armatureInvertedWorldMatrix.multLocal(boneWorldMatrix); - result = new Transform(bonePoseMatrix.toTranslationVector(), bonePoseMatrix.toRotationQuat(), bonePoseMatrix.toScaleVector()); - Bone parent = bone.getParent(); - if(parent != null) { - BoneContext parentContext = blenderContext.getBoneContext(parent); - Vector3f head = parent.getModelSpacePosition(); - Vector3f tail = head.add(bone.getModelSpaceRotation().mult(Vector3f.UNIT_Y.mult(parentContext.getLength()))); - result.getTranslation().subtractLocal(tail); - - } - break; - } - default: - throw new IllegalStateException("Unknown space type: " + space); - } - tempVars.release(); - return result; - } else { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - return feature.getLocalTransform(); - case CONSTRAINT_SPACE_WORLD: - return feature.getWorldTransform(); - case CONSTRAINT_SPACE_PARLOCAL: - case CONSTRAINT_SPACE_POSE: - throw new IllegalStateException("Nodes can have only Local and World spaces applied!"); - default: - throw new IllegalStateException("Unknown space type: " + space); - } - } - } - - /** - * Applies transform to a feature (bone or spatial). Computations transform - * the given transformation from the given space to the feature's local - * space. - * - * @param oma - * the OMA of the feature we apply transformation to - * @param subtargetName - * the name of the feature's subtarget (bone in case of armature) - * @param space - * the space in which the given transform is to be applied - * @param transform - * the transform we apply - */ - public void applyTransform(Long oma, String subtargetName, Space space, Transform transform) { - Spatial feature = (Spatial) blenderContext.getLoadedFeature(oma, LoadedDataType.FEATURE); - boolean isArmature = blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, feature) != null; - if (isArmature) { - Skeleton skeleton = blenderContext.getSkeleton(oma); - BoneContext targetBoneContext = blenderContext.getBoneByName(oma, subtargetName); - Bone bone = targetBoneContext.getBone(); - - if (bone.getParent() == null && (space == Space.CONSTRAINT_SPACE_LOCAL || space == Space.CONSTRAINT_SPACE_PARLOCAL)) { - space = Space.CONSTRAINT_SPACE_POSE; - } - - TempVars tempVars = TempVars.get(); - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - assert bone.getParent() != null : "CONSTRAINT_SPACE_LOCAL should be evaluated as CONSTRAINT_SPACE_POSE if the bone has no parent!"; - bone.setBindTransforms(transform.getTranslation(), transform.getRotation(), transform.getScale()); - break; - case CONSTRAINT_SPACE_WORLD: { - Matrix4f boneMatrixInWorldSpace = this.toMatrix(transform, tempVars.tempMat4); - Matrix4f modelWorldMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42); - Matrix4f boneMatrixInModelSpace = modelWorldMatrix.invertLocal().multLocal(boneMatrixInWorldSpace); - Bone parent = bone.getParent(); - if (parent != null) { - Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4); - boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace); - } - bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); - break; - } - case CONSTRAINT_SPACE_POSE: { - Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4); - Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42)); - Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal(); - Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace); - Bone parent = bone.getParent(); - if (parent != null) { - Matrix4f parentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4); - boneMatrixInModelSpace = parentMatrixInModelSpace.invertLocal().multLocal(boneMatrixInModelSpace); - } - bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); - break; - } - case CONSTRAINT_SPACE_PARLOCAL: - Matrix4f armatureWorldMatrix = this.toMatrix(feature.getWorldTransform(), tempVars.tempMat4); - Matrix4f boneMatrixInWorldSpace = armatureWorldMatrix.multLocal(this.toMatrix(transform, tempVars.tempMat42)); - Matrix4f invertedModelMatrix = this.toMatrix(this.getTransform(targetBoneContext.getSkeletonOwnerOma(), null, Space.CONSTRAINT_SPACE_WORLD), tempVars.tempMat42).invertLocal(); - Matrix4f boneMatrixInModelSpace = invertedModelMatrix.multLocal(boneMatrixInWorldSpace); - Bone parent = bone.getParent(); - if (parent != null) { - //first add the initial parent matrix to the bone's model matrix - BoneContext parentContext = blenderContext.getBoneContext(parent); - - Matrix4f initialParentMatrixInModelSpace = parentContext.getBoneMatrixInModelSpace(); - Matrix4f currentParentMatrixInModelSpace = this.toMatrix(parent.getModelSpacePosition(), parent.getModelSpaceRotation(), parent.getModelSpaceScale(), tempVars.tempMat4); - //the bone will now move with its parent in model space - - //now we need to subtract the difference between current parent's model matrix and its initial model matrix - boneMatrixInModelSpace = initialParentMatrixInModelSpace.mult(boneMatrixInModelSpace); - - Matrix4f diffMatrix = initialParentMatrixInModelSpace.mult(currentParentMatrixInModelSpace.invert()); - boneMatrixInModelSpace.multLocal(diffMatrix); - //now the bone will have its position in model space with initial parent's model matrix added - } - bone.setBindTransforms(boneMatrixInModelSpace.toTranslationVector(), boneMatrixInModelSpace.toRotationQuat(), boneMatrixInModelSpace.toScaleVector()); - break; - default: - tempVars.release(); - throw new IllegalStateException("Invalid space type for target object: " + space.toString()); - } - tempVars.release(); - skeleton.updateWorldVectors(); - } else { - switch (space) { - case CONSTRAINT_SPACE_LOCAL: - feature.getLocalTransform().set(transform); - break; - case CONSTRAINT_SPACE_WORLD: - if (feature.getParent() == null) { - feature.setLocalTransform(transform); - } else { - Transform parentWorldTransform = feature.getParent().getWorldTransform(); - - TempVars tempVars = TempVars.get(); - Matrix4f parentInverseMatrix = this.toMatrix(parentWorldTransform, tempVars.tempMat4).invertLocal(); - Matrix4f m = this.toMatrix(transform, tempVars.tempMat42); - m = m.multLocal(parentInverseMatrix); - tempVars.release(); - - transform.setTranslation(m.toTranslationVector()); - transform.setRotation(m.toRotationQuat()); - transform.setScale(m.toScaleVector()); - - feature.setLocalTransform(transform); - } - break; - default: - throw new IllegalStateException("Invalid space type for spatial object: " + space.toString()); - } - } - } - - /** - * Converts given transform to the matrix. - * - * @param transform - * the transform to be converted - * @param store - * the matrix where the result will be stored - * @return the store matrix - */ - public Matrix4f toMatrix(Transform transform, Matrix4f store) { - if (transform != null) { - return this.toMatrix(transform.getTranslation(), transform.getRotation(), transform.getScale(), store); - } - store.loadIdentity(); - return store; - } - - /** - * Converts given transformation parameters into the matrix. - * - * @param position - * the position of the feature - * @param rotation - * the rotation of the feature - * @param scale - * the scale of the feature - * @param store - * the matrix where the result will be stored - * @return the store matrix - */ - private Matrix4f toMatrix(Vector3f position, Quaternion rotation, Vector3f scale, Matrix4f store) { - store.loadIdentity(); - store.setTranslation(position); - store.setRotationQuaternion(rotation); - store.setScale(scale); - return store; - } - - /** - * The space of target or owner transformation. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum Space { - /** A transformation of the bone or spatial in the world space. */ - CONSTRAINT_SPACE_WORLD, - /** - * For spatial it is the transformation in its parent space or in WORLD space if it has no parent. - * For bone it is a transformation in its bone parent space or in armature space if it has no parent. - */ - CONSTRAINT_SPACE_LOCAL, - /** - * This space IS NOT applicable for spatials. - * For bone it is a transformation in the blender's armature object space. - */ - CONSTRAINT_SPACE_POSE, - - CONSTRAINT_SPACE_PARLOCAL; - - /** - * This method returns the enum instance when given the appropriate - * value from the blend file. - * - * @param c - * the blender's value of the space modifier - * @return the scape enum instance - */ - public static Space valueOf(byte c) { - switch (c) { - case 0: - return CONSTRAINT_SPACE_WORLD; - case 1: - return CONSTRAINT_SPACE_LOCAL; - case 2: - return CONSTRAINT_SPACE_POSE; - case 3: - return CONSTRAINT_SPACE_PARLOCAL; - default: - throw new IllegalArgumentException("Value: " + c + " cannot be converted to Space enum instance!"); - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java deleted file mode 100644 index 279b5311d3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SimulationNode.java +++ /dev/null @@ -1,397 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.Stack; -import java.util.logging.Logger; - -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.Bone; -import com.jme3.animation.BoneTrack; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SpatialTrack; -import com.jme3.animation.Track; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; -import com.jme3.util.TempVars; - -/** - * A node that represents either spatial or bone in constraint simulation. The - * node is applied its translation, rotation and scale for each frame of its - * animation. Then the constraints are applied that will eventually alter it. - * After that the feature's transformation is stored in VirtualTrack which is - * converted to new bone or spatial track at the very end. - * - * @author Marcin Roguski (Kaelthas) - */ -public class SimulationNode { - private static final Logger LOGGER = Logger.getLogger(SimulationNode.class.getName()); - - private Long featureOMA; - /** The blender context. */ - private BlenderContext blenderContext; - /** The name of the node (for debugging purposes). */ - private String name; - /** A list of children for the node (either bones or child spatials). */ - private List children = new ArrayList(); - /** A list of node's animations. */ - private List animations; - - /** The nodes spatial (if null then the boneContext should be set). */ - private Spatial spatial; - /** The skeleton of the bone (not null if the node simulated the bone). */ - private Skeleton skeleton; - /** Animation controller for the node's feature. */ - private AnimControl animControl; - - /** - * The star transform of a spatial. Needed to properly reset the spatial to - * its start position. - */ - private Transform spatialStartTransform; - /** Star transformations for bones. Needed to properly reset the bones. */ - private Map boneStartTransforms; - - /** - * Builds the nodes tree for the given feature. The feature (bone or - * spatial) is found by its OMA. The feature must be a root bone or a root - * spatial. - * - * @param featureOMA - * the OMA of either bone or spatial - * @param blenderContext - * the blender context - */ - public SimulationNode(Long featureOMA, BlenderContext blenderContext) { - this(featureOMA, blenderContext, true); - } - - /** - * Creates the node for the feature. - * - * @param featureOMA - * the OMA of either bone or spatial - * @param blenderContext - * the blender context - * @param rootNode - * indicates if the feature is a root bone or root spatial or not - */ - private SimulationNode(Long featureOMA, BlenderContext blenderContext, boolean rootNode) { - this.featureOMA = featureOMA; - this.blenderContext = blenderContext; - Node spatial = (Node) blenderContext.getLoadedFeature(featureOMA, LoadedDataType.FEATURE); - if (blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, spatial) != null) { - skeleton = blenderContext.getSkeleton(featureOMA); - - Node nodeWithAnimationControl = blenderContext.getControlledNode(skeleton); - animControl = nodeWithAnimationControl.getControl(AnimControl.class); - - boneStartTransforms = new HashMap(); - for (int i = 0; i < skeleton.getBoneCount(); ++i) { - Bone bone = skeleton.getBone(i); - boneStartTransforms.put(bone, new Transform(bone.getBindPosition(), bone.getBindRotation(), bone.getBindScale())); - } - } else { - if (rootNode && spatial.getParent() != null) { - throw new IllegalStateException("Given spatial must be a root node!"); - } - this.spatial = spatial; - spatialStartTransform = spatial.getLocalTransform().clone(); - } - - name = '>' + spatial.getName() + '<'; - - // add children nodes - if (skeleton != null) { - Node node = blenderContext.getControlledNode(skeleton); - Long animatedNodeOMA = ((Number) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, node)).longValue(); - animations = blenderContext.getAnimations(animatedNodeOMA); - } else { - animations = blenderContext.getAnimations(featureOMA); - for (Spatial child : spatial.getChildren()) { - if (child instanceof Node) { - children.add(new SimulationNode((Long) blenderContext.getMarkerValue(ObjectHelper.OMA_MARKER, child), blenderContext, false)); - } - } - } - } - - /** - * Resets the node's feature to its starting transformation. - */ - private void reset() { - if (spatial != null) { - spatial.setLocalTransform(spatialStartTransform); - for (SimulationNode child : children) { - child.reset(); - } - } else if (skeleton != null) { - for (Entry entry : boneStartTransforms.entrySet()) { - Transform t = entry.getValue(); - entry.getKey().setBindTransforms(t.getTranslation(), t.getRotation(), t.getScale()); - entry.getKey().updateModelTransforms(); - } - skeleton.reset(); - } - } - - /** - * Simulates the spatial node. - */ - private void simulateSpatial() { - List constraints = blenderContext.getConstraints(featureOMA); - if (constraints != null && constraints.size() > 0) { - LOGGER.fine("Simulating spatial."); - boolean applyStaticConstraints = true; - if (animations != null) { - for (Animation animation : animations) { - float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); - int maxFrame = (int) animationTimeBoundaries[0]; - float maxTime = animationTimeBoundaries[1]; - - VirtualTrack vTrack = new VirtualTrack(spatial.getName(), maxFrame, maxTime); - for (Track track : animation.getTracks()) { - for (int frame = 0; frame < maxFrame; ++frame) { - spatial.setLocalTranslation(((SpatialTrack) track).getTranslations()[frame]); - spatial.setLocalRotation(((SpatialTrack) track).getRotations()[frame]); - spatial.setLocalScale(((SpatialTrack) track).getScales()[frame]); - - for (Constraint constraint : constraints) { - constraint.apply(frame); - vTrack.setTransform(frame, spatial.getLocalTransform()); - } - } - Track newTrack = vTrack.getAsSpatialTrack(); - if (newTrack != null) { - animation.removeTrack(track); - animation.addTrack(newTrack); - } - applyStaticConstraints = false; - } - } - } - - // if there are no animations then just constraint the static - // object's transformation - if (applyStaticConstraints) { - for (Constraint constraint : constraints) { - constraint.apply(0); - } - } - } - - for (SimulationNode child : children) { - child.simulate(); - } - } - - /** - * Simulates the bone node. - */ - private void simulateSkeleton() { - LOGGER.fine("Simulating skeleton."); - Set alteredOmas = new HashSet(); - - if (animations != null) { - TempVars vars = TempVars.get(); - AnimChannel animChannel = animControl.createChannel(); - - for (Animation animation : animations) { - float[] animationTimeBoundaries = this.computeAnimationTimeBoundaries(animation); - int maxFrame = (int) animationTimeBoundaries[0]; - float maxTime = animationTimeBoundaries[1]; - - Map tracks = new HashMap(); - for (int frame = 0; frame < maxFrame; ++frame) { - // this MUST be done here, otherwise setting next frame of animation will - // lead to possible errors - this.reset(); - - // first set proper time for all bones in all the tracks ... - for (Track track : animation.getTracks()) { - float time = ((BoneTrack) track).getTimes()[frame]; - track.setTime(time, 1, animControl, animChannel, vars); - skeleton.updateWorldVectors(); - } - - // ... and then apply constraints from the root bone to the last child ... - Set applied = new HashSet(); - for (Bone rootBone : skeleton.getRoots()) { - // ignore the 0-indexed bone - if (skeleton.getBoneIndex(rootBone) > 0) { - this.applyConstraints(rootBone, alteredOmas, applied, frame, new Stack()); - } - } - - // ... add virtual tracks if necessary, for bones that were altered but had no tracks before ... - for (Long boneOMA : alteredOmas) { - BoneContext boneContext = blenderContext.getBoneContext(boneOMA); - int boneIndex = skeleton.getBoneIndex(boneContext.getBone()); - if (!tracks.containsKey(boneIndex)) { - tracks.put(boneIndex, new VirtualTrack(boneContext.getBone().getName(), maxFrame, maxTime)); - } - } - alteredOmas.clear(); - - // ... and fill in another frame in the result track - for (Entry trackEntry : tracks.entrySet()) { - Bone bone = skeleton.getBone(trackEntry.getKey()); - Transform startTransform = boneStartTransforms.get(bone); - - // track contains differences between the frame position and bind positions of bones/spatials - Vector3f bonePositionDifference = bone.getLocalPosition().subtract(startTransform.getTranslation()); - Quaternion boneRotationDifference = startTransform.getRotation().inverse().mult(bone.getLocalRotation()).normalizeLocal(); - Vector3f boneScaleDifference = bone.getLocalScale().divide(startTransform.getScale()); - - trackEntry.getValue().setTransform(frame, new Transform(bonePositionDifference, boneRotationDifference, boneScaleDifference)); - } - } - - for (Entry trackEntry : tracks.entrySet()) { - Track newTrack = trackEntry.getValue().getAsBoneTrack(trackEntry.getKey()); - if (newTrack != null) { - boolean trackReplaced = false; - for (Track track : animation.getTracks()) { - if (((BoneTrack) track).getTargetBoneIndex() == trackEntry.getKey().intValue()) { - animation.removeTrack(track); - animation.addTrack(newTrack); - trackReplaced = true; - break; - } - } - if (!trackReplaced) { - animation.addTrack(newTrack); - } - } - } - } - vars.release(); - animControl.clearChannels(); - this.reset(); - } - } - - /** - * Applies constraints to the given bone and its children. - * The goal is to apply constraint from root bone to the last child. - * @param bone - * the bone whose constraints will be applied - * @param alteredOmas - * the set of OMAS of the altered bones (is populated if necessary) - * @param frame - * the current frame of the animation - * @param bonesStack - * the stack of bones used to avoid infinite loops while applying constraints - */ - private void applyConstraints(Bone bone, Set alteredOmas, Set applied, int frame, Stack bonesStack) { - if (!bonesStack.contains(bone)) { - bonesStack.push(bone); - BoneContext boneContext = blenderContext.getBoneContext(bone); - if (!applied.contains(boneContext.getBoneOma())) { - List constraints = this.findConstraints(boneContext.getBoneOma(), blenderContext); - if (constraints != null && constraints.size() > 0) { - for (Constraint constraint : constraints) { - if (constraint.getTargetOMA() != null && constraint.getTargetOMA() > 0L) { - // first apply constraints of the target bone - BoneContext targetBone = blenderContext.getBoneContext(constraint.getTargetOMA()); - this.applyConstraints(targetBone.getBone(), alteredOmas, applied, frame, bonesStack); - } - constraint.apply(frame); - if (constraint.getAlteredOmas() != null) { - alteredOmas.addAll(constraint.getAlteredOmas()); - } - alteredOmas.add(boneContext.getBoneOma()); - } - } - applied.add(boneContext.getBoneOma()); - } - - List children = bone.getChildren(); - if (children != null && children.size() > 0) { - for (Bone child : bone.getChildren()) { - this.applyConstraints(child, alteredOmas, applied, frame, bonesStack); - } - } - bonesStack.pop(); - } - } - - /** - * Simulates the node. - */ - public void simulate() { - this.reset(); - if (spatial != null) { - this.simulateSpatial(); - } else { - this.simulateSkeleton(); - } - } - - /** - * Computes the maximum frame and time for the animation. Different tracks - * can have different lengths so here the maximum one is being found. - * - * @param animation - * the animation - * @return maximum frame and time of the animation - */ - private float[] computeAnimationTimeBoundaries(Animation animation) { - int maxFrame = Integer.MIN_VALUE; - float maxTime = -Float.MAX_VALUE; - for (Track track : animation.getTracks()) { - if (track instanceof BoneTrack) { - maxFrame = Math.max(maxFrame, ((BoneTrack) track).getTranslations().length); - maxTime = Math.max(maxTime, ((BoneTrack) track).getTimes()[((BoneTrack) track).getTimes().length - 1]); - } else if (track instanceof SpatialTrack) { - maxFrame = Math.max(maxFrame, ((SpatialTrack) track).getTranslations().length); - maxTime = Math.max(maxTime, ((SpatialTrack) track).getTimes()[((SpatialTrack) track).getTimes().length - 1]); - } else { - throw new IllegalStateException("Unsupported track type for simuation: " + track); - } - } - return new float[] { maxFrame, maxTime }; - } - - /** - * Finds constraints for the node's features. - * - * @param ownerOMA - * the feature's OMA - * @param blenderContext - * the blender context - * @return a list of feature's constraints or empty list if none were found - */ - private List findConstraints(Long ownerOMA, BlenderContext blenderContext) { - List result = new ArrayList(); - List constraints = blenderContext.getConstraints(ownerOMA); - if (constraints != null) { - for (Constraint constraint : constraints) { - if (constraint.isImplemented() && constraint.validate() && constraint.isTrackToBeChanged()) { - result.add(constraint); - } - // TODO: add proper warnings to some map or set so that they are not logged on every frame - } - } - return result.size() > 0 ? result : null; - } - - @Override - public String toString() { - return name; - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java deleted file mode 100644 index 5560bbd436..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SkeletonConstraint.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.logging.Logger; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * Constraint applied on the skeleton. This constraint is here only to make the - * application not crash when loads constraints applied to armature. But - * skeleton movement is not supported by jme so the constraint will never be - * applied. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class SkeletonConstraint extends Constraint { - private static final Logger LOGGER = Logger.getLogger(SkeletonConstraint.class.getName()); - - public SkeletonConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } - - @Override - public boolean validate() { - LOGGER.warning("Constraints for skeleton are not supported."); - return false; - } - - @Override - public void apply(int frame) { - LOGGER.warning("Applying constraints to skeleton is not supported."); - } - - @Override - public Long getTargetOMA() { - LOGGER.warning("Constraints for skeleton are not supported."); - return null; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java deleted file mode 100644 index 91eb5c5c76..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/SpatialConstraint.java +++ /dev/null @@ -1,32 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.Ipo; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * Constraint applied on the spatial objects. This includes: nodes, cameras - * nodes and light nodes. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class SpatialConstraint extends Constraint { - public SpatialConstraint(Structure constraintStructure, Long ownerOMA, Ipo influenceIpo, BlenderContext blenderContext) throws BlenderFileException { - super(constraintStructure, ownerOMA, influenceIpo, blenderContext); - } - - @Override - public boolean validate() { - if (targetOMA != null) { - return blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE) != null; - } - return constraintDefinition == null ? true : constraintDefinition.isTargetRequired(); - } - - @Override - public Long getTargetOMA() { - return targetOMA; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java deleted file mode 100644 index f182227955..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/VirtualTrack.java +++ /dev/null @@ -1,165 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints; - -import java.util.ArrayList; - -import com.jme3.animation.BoneTrack; -import com.jme3.animation.SpatialTrack; -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; - -/** - * A virtual track that stores computed frames after constraints are applied. - * Not all the frames need to be inserted. If there are lacks then the class - * will fill the gaps. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class VirtualTrack { - /** The name of the track (for debugging purposes). */ - private String name; - /** The last frame for the track. */ - public int maxFrame; - /** The max time for the track. */ - public float maxTime; - /** Translations of the track. */ - public ArrayList translations; - /** Rotations of the track. */ - public ArrayList rotations; - /** Scales of the track. */ - public ArrayList scales; - - /** - * Constructs the object storing the maximum frame and time. - * - * @param maxFrame - * the last frame for the track - * @param maxTime - * the max time for the track - */ - public VirtualTrack(String name, int maxFrame, float maxTime) { - this.name = name; - this.maxFrame = maxFrame; - this.maxTime = maxTime; - } - - /** - * Sets the transform for the given frame. - * - * @param frameIndex - * the frame for which the transform will be set - * @param transform - * the transformation to be set - */ - public void setTransform(int frameIndex, Transform transform) { - if (translations == null) { - translations = this.createList(Vector3f.ZERO, frameIndex); - } - this.append(translations, Vector3f.ZERO, frameIndex - translations.size()); - translations.add(transform.getTranslation().clone()); - - if (rotations == null) { - rotations = this.createList(Quaternion.IDENTITY, frameIndex); - } - this.append(rotations, Quaternion.IDENTITY, frameIndex - rotations.size()); - rotations.add(transform.getRotation().clone()); - - if (scales == null) { - scales = this.createList(Vector3f.UNIT_XYZ, frameIndex); - } - this.append(scales, Vector3f.UNIT_XYZ, frameIndex - scales.size()); - scales.add(transform.getScale().clone()); - } - - /** - * Returns the track as a bone track. - * - * @param targetBoneIndex - * the bone index - * @return the bone track - */ - public BoneTrack getAsBoneTrack(int targetBoneIndex) { - if (translations == null && rotations == null && scales == null) { - return null; - } - return new BoneTrack(targetBoneIndex, this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); - } - - /** - * Returns the track as a spatial track. - * - * @return the spatial track - */ - public SpatialTrack getAsSpatialTrack() { - if (translations == null && rotations == null && scales == null) { - return null; - } - return new SpatialTrack(this.createTimes(), translations.toArray(new Vector3f[maxFrame]), rotations.toArray(new Quaternion[maxFrame]), scales.toArray(new Vector3f[maxFrame])); - } - - /** - * The method creates times for the track based on the given maximum values. - * - * @return the times for the track - */ - private float[] createTimes() { - float[] times = new float[maxFrame]; - float dT = maxTime / maxFrame; - float t = 0; - for (int i = 0; i < maxFrame; ++i) { - times[i] = t; - t += dT; - } - return times; - } - - /** - * Helper method that creates a list of a given size filled with given - * elements. - * - * @param element - * the element to be put into the list - * @param count - * the list size - * @return the list - */ - private ArrayList createList(T element, int count) { - ArrayList result = new ArrayList(count); - for (int i = 0; i < count; ++i) { - result.add(element); - } - return result; - } - - /** - * Appends the element to the given list. - * - * @param list - * the list where the element will be appended - * @param element - * the element to be appended - * @param count - * how many times the element will be appended - */ - private void append(ArrayList list, T element, int count) { - for (int i = 0; i < count; ++i) { - list.add(element); - } - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(2048); - result.append("TRACK: ").append(name).append('\n'); - if (translations != null && translations.size() > 0) { - result.append("TRANSLATIONS: ").append(translations.toString()).append('\n'); - } - if (rotations != null && rotations.size() > 0) { - result.append("ROTATIONS: ").append(rotations.toString()).append('\n'); - } - if (scales != null && scales.size() > 0) { - result.append("SCALES: ").append(scales.toString()).append('\n'); - } - return result.toString(); - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java deleted file mode 100644 index 166b28c614..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinition.java +++ /dev/null @@ -1,162 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import java.util.Set; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A base class for all constraint definitions. - * - * @author Marcin Roguski (Kaelthas) - */ -public abstract class ConstraintDefinition { - protected ConstraintHelper constraintHelper; - /** Constraints flag. Used to load user's options applied to the constraint. */ - protected int flag; - /** The constraint's owner. Loaded during runtime. */ - private Object owner; - /** The blender context. */ - protected BlenderContext blenderContext; - /** The constraint's owner OMA. */ - protected Long ownerOMA; - /** Stores the OMA addresses of all features whose transform had been altered beside the constraint owner. */ - protected Set alteredOmas; - /** The variable that determines if the constraint will alter the track in any way. */ - protected boolean trackToBeChanged = true; - /** The name of the constraint. */ - protected String constraintName; - - /** - * Loads a constraint definition based on the constraint definition - * structure. - * - * @param constraintData - * the constraint definition structure - * @param ownerOMA - * the constraint's owner OMA - * @param blenderContext - * the blender context - */ - public ConstraintDefinition(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - if (constraintData != null) {// Null constraint has no data - Number flag = (Number) constraintData.getFieldValue("flag"); - if (flag != null) { - this.flag = flag.intValue(); - } - } - this.blenderContext = blenderContext; - constraintHelper = (ConstraintHelper) (blenderContext == null ? null : blenderContext.getHelper(ConstraintHelper.class)); - this.ownerOMA = ownerOMA; - } - - public void setConstraintName(String constraintName) { - this.constraintName = constraintName; - } - - /** - * @return determines if the definition of the constraint will change the bone in any way; in most cases - * it is possible to tell that even before the constraint baking simulation is started, so we can discard such bones from constraint - * computing to improve the computation speed and lower the computations complexity - */ - public boolean isTrackToBeChanged() { - return trackToBeChanged; - } - - /** - * @return determines if this constraint definition requires a defined target or not - */ - public abstract boolean isTargetRequired(); - - /** - * This method is here because we have no guarantee that the owner is loaded - * when constraint is being created. So use it to get the owner when it is - * needed for computations. - * - * @return the owner of the constraint or null if none is set - */ - protected Object getOwner() { - if (ownerOMA != null && owner == null) { - owner = blenderContext.getLoadedFeature(ownerOMA, LoadedDataType.FEATURE); - if (owner == null) { - throw new IllegalStateException("Cannot load constraint's owner for constraint type: " + this.getClass().getName()); - } - } - return owner; - } - - /** - * The method gets the owner's transformation. The owner can be either bone or spatial. - * @param ownerSpace - * the space in which the computed transformation is given - * @return the constraint owner's transformation - */ - protected Transform getOwnerTransform(Space ownerSpace) { - if (this.getOwner() instanceof Bone) { - BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); - return constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace); - } - return constraintHelper.getTransform(ownerOMA, null, ownerSpace); - } - - /** - * The method applies the given transformation to the owner. - * @param ownerTransform - * the transformation to apply to the owner - * @param ownerSpace - * the space that defines which owner's transformation (ie. global, local, etc. will be set) - */ - protected void applyOwnerTransform(Transform ownerTransform, Space ownerSpace) { - if (this.getOwner() instanceof Bone) { - BoneContext boneContext = blenderContext.getBoneContext(ownerOMA); - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), ownerSpace, ownerTransform); - } else { - constraintHelper.applyTransform(ownerOMA, null, ownerSpace, ownerTransform); - } - } - - /** - * @return true if the definition is implemented and false - * otherwise - */ - public boolean isImplemented() { - return true; - } - - /** - * @return a list of all OMAs of the features that the constraint had altered beside its owner - */ - public Set getAlteredOmas() { - return alteredOmas; - } - - /** - * @return the type name of the constraint - */ - public abstract String getConstraintTypeName(); - - /** - * Bakes the constraint for the current feature (bone or spatial) position. - * - * @param ownerSpace - * the space where owner transform will be evaluated in - * @param targetSpace - * the space where target transform will be evaluated in - * @param targetTransform - * the target transform used by some of the constraints - * @param influence - * the influence of the constraint from range [0; 1] - */ - public abstract void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence); - - @Override - public String toString() { - return this.getConstraintTypeName(); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java deleted file mode 100644 index aaa7e2d979..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionDistLimit.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Dist limit' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionDistLimit extends ConstraintDefinition { - private static final int LIMITDIST_INSIDE = 0; - private static final int LIMITDIST_OUTSIDE = 1; - private static final int LIMITDIST_ONSURFACE = 2; - - protected int mode; - protected float dist; - - public ConstraintDefinitionDistLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - mode = ((Number) constraintData.getFieldValue("mode")).intValue(); - dist = ((Number) constraintData.getFieldValue("dist")).floatValue(); - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)) { - // distance limit does not work on bones who are connected to their parent - return; - } - if (influence == 0 || targetTransform == null) { - return;// no need to do anything - } - - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Vector3f v = ownerTransform.getTranslation().subtract(targetTransform.getTranslation()); - float currentDistance = v.length(); - switch (mode) { - case LIMITDIST_INSIDE: - if (currentDistance >= dist) { - v.normalizeLocal(); - v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); - ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); - } - break; - case LIMITDIST_ONSURFACE: - if (currentDistance > dist) { - v.normalizeLocal(); - v.multLocal(dist + (currentDistance - dist) * (1.0f - influence)); - ownerTransform.getTranslation().set(v.addLocal(targetTransform.getTranslation())); - } else if (currentDistance < dist) { - v.normalizeLocal().multLocal(dist * influence); - ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); - } - break; - case LIMITDIST_OUTSIDE: - if (currentDistance <= dist) { - v = targetTransform.getTranslation().subtract(ownerTransform.getTranslation()).normalizeLocal().multLocal(dist * influence); - ownerTransform.getTranslation().set(targetTransform.getTranslation().add(v)); - } - break; - default: - throw new IllegalStateException("Unknown distance limit constraint mode: " + mode); - } - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public boolean isTargetRequired() { - return true; - } - - @Override - public String getConstraintTypeName() { - return "Limit distance"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java deleted file mode 100644 index c1d69fe06f..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionFactory.java +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import java.lang.reflect.InvocationTargetException; -import java.util.HashMap; -import java.util.Map; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -public class ConstraintDefinitionFactory { - private static final Map> CONSTRAINT_CLASSES = new HashMap>(); - static { - CONSTRAINT_CLASSES.put("bDistLimitConstraint", ConstraintDefinitionDistLimit.class); - CONSTRAINT_CLASSES.put("bLocateLikeConstraint", ConstraintDefinitionLocLike.class); - CONSTRAINT_CLASSES.put("bLocLimitConstraint", ConstraintDefinitionLocLimit.class); - CONSTRAINT_CLASSES.put("bNullConstraint", ConstraintDefinitionNull.class); - CONSTRAINT_CLASSES.put("bRotateLikeConstraint", ConstraintDefinitionRotLike.class); - CONSTRAINT_CLASSES.put("bRotLimitConstraint", ConstraintDefinitionRotLimit.class); - CONSTRAINT_CLASSES.put("bSizeLikeConstraint", ConstraintDefinitionSizeLike.class); - CONSTRAINT_CLASSES.put("bSizeLimitConstraint", ConstraintDefinitionSizeLimit.class); - CONSTRAINT_CLASSES.put("bKinematicConstraint", ConstraintDefinitionIK.class); - CONSTRAINT_CLASSES.put("bTransLikeConstraint", ConstraintDefinitionTransLike.class);// since blender 2.51 - CONSTRAINT_CLASSES.put("bSameVolumeConstraint", ConstraintDefinitionMaintainVolume.class);// since blender 2.53 - } - - private static final Map UNSUPPORTED_CONSTRAINTS = new HashMap(); - static { - UNSUPPORTED_CONSTRAINTS.put("bActionConstraint", "Action"); - UNSUPPORTED_CONSTRAINTS.put("bChildOfConstraint", "Child of"); - UNSUPPORTED_CONSTRAINTS.put("bClampToConstraint", "Clamp to"); - UNSUPPORTED_CONSTRAINTS.put("bFollowPathConstraint", "Follow path"); - UNSUPPORTED_CONSTRAINTS.put("bLockTrackConstraint", "Lock track"); - UNSUPPORTED_CONSTRAINTS.put("bMinMaxConstraint", "Min max"); - UNSUPPORTED_CONSTRAINTS.put("bPythonConstraint", "Python/Script"); - UNSUPPORTED_CONSTRAINTS.put("bRigidBodyJointConstraint", "Rigid body joint"); - UNSUPPORTED_CONSTRAINTS.put("bShrinkWrapConstraint", "Shrinkwrap"); - UNSUPPORTED_CONSTRAINTS.put("bStretchToConstraint", "Stretch to"); - UNSUPPORTED_CONSTRAINTS.put("bTransformConstraint", "Transform"); - // Blender 2.50+ - UNSUPPORTED_CONSTRAINTS.put("bSplineIKConstraint", "Spline inverse kinematics"); - UNSUPPORTED_CONSTRAINTS.put("bDampTrackConstraint", "Damp track"); - UNSUPPORTED_CONSTRAINTS.put("bPivotConstraint", "Pivot"); - // Blender 2.56+ - UNSUPPORTED_CONSTRAINTS.put("bTrackToConstraint", "Track to"); - // Blender 2.62+ - UNSUPPORTED_CONSTRAINTS.put("bCameraSolverConstraint", "Camera solver"); - UNSUPPORTED_CONSTRAINTS.put("bObjectSolverConstraint", "Object solver"); - UNSUPPORTED_CONSTRAINTS.put("bFollowTrackConstraint", "Follow track"); - } - - /** - * This method creates the constraint instance. - * - * @param constraintStructure - * the constraint's structure (bConstraint clss in blender 2.49). - * If the value is null the NullConstraint is created. - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public static ConstraintDefinition createConstraintDefinition(Structure constraintStructure, String constraintName, Long ownerOMA, BlenderContext blenderContext) throws BlenderFileException { - if (constraintStructure == null) { - return new ConstraintDefinitionNull(null, ownerOMA, blenderContext); - } - String constraintClassName = constraintStructure.getType(); - Class constraintDefinitionClass = CONSTRAINT_CLASSES.get(constraintClassName); - if (constraintDefinitionClass != null) { - try { - ConstraintDefinition def = (ConstraintDefinition) constraintDefinitionClass.getDeclaredConstructors()[0].newInstance(constraintStructure, ownerOMA, blenderContext); - def.setConstraintName(constraintName); - return def; - } catch (IllegalArgumentException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (SecurityException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (InstantiationException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (IllegalAccessException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } catch (InvocationTargetException e) { - throw new BlenderFileException(e.getLocalizedMessage(), e); - } - } else { - String unsupportedConstraintClassName = UNSUPPORTED_CONSTRAINTS.get(constraintClassName); - if (unsupportedConstraintClassName != null) { - return new UnsupportedConstraintDefinition(unsupportedConstraintClassName); - } else { - throw new BlenderFileException("Unknown constraint type: " + constraintClassName); - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java deleted file mode 100644 index 69c0a6f500..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionIK.java +++ /dev/null @@ -1,236 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; - -import org.ejml.simple.SimpleMatrix; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.math.DQuaternion; -import com.jme3.scene.plugins.blender.math.DTransform; -import com.jme3.scene.plugins.blender.math.Matrix; -import com.jme3.scene.plugins.blender.math.Vector3d; - -/** - * A definiotion of a Inverse Kinematics constraint. This implementation uses Jacobian pseudoinverse algorithm. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ConstraintDefinitionIK extends ConstraintDefinition { - private static final float MIN_DISTANCE = 0.001f; - private static final float MIN_ANGLE_CHANGE = 0.001f; - private static final int FLAG_USE_TAIL = 0x01; - private static final int FLAG_POSITION = 0x20; - - private BonesChain bones; - /** The number of affected bones. Zero means that all parent bones of the current bone should take part in baking. */ - private int bonesAffected; - /** Indicates if the tail of the bone should be used or not. */ - private boolean useTail; - /** The amount of iterations of the algorithm. */ - private int iterations; - /** The count of bones' chain. */ - private int bonesCount = -1; - - public ConstraintDefinitionIK(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - bonesAffected = ((Number) constraintData.getFieldValue("rootbone")).intValue(); - iterations = ((Number) constraintData.getFieldValue("iterations")).intValue(); - useTail = (flag & FLAG_USE_TAIL) != 0; - - if ((flag & FLAG_POSITION) == 0) { - trackToBeChanged = false; - } - - if (trackToBeChanged) { - alteredOmas = new HashSet(); - } - } - - /** - * Below are the variables that only need to be allocated once for IK constraint instance. - */ - /** Temporal quaternion. */ - private DQuaternion tempDQuaternion = new DQuaternion(); - /** Temporal matrix column. */ - private Vector3d col = new Vector3d(); - /** Effector's position change. */ - private Matrix deltaP = new Matrix(3, 1); - /** The current target position. */ - private Vector3d target = new Vector3d(); - /** Rotation vectors for each joint (allocated when we know the size of a bones' chain. */ - private Vector3d[] rotationVectors; - /** The Jacobian matrix. Allocated when the bones' chain size is known. */ - private Matrix J; - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || !trackToBeChanged || targetTransform == null || bonesCount == 0) { - return;// no need to do anything - } - - if (bones == null) { - bones = new BonesChain((Bone) this.getOwner(), useTail, bonesAffected, alteredOmas, blenderContext); - } - if (bones.size() == 0) { - bonesCount = 0; - return;// no need to do anything - } - double distanceFromTarget = Double.MAX_VALUE; - target.set(targetTransform.getTranslation().x, targetTransform.getTranslation().y, targetTransform.getTranslation().z); - - if (bonesCount < 0) { - bonesCount = bones.size(); - rotationVectors = new Vector3d[bonesCount]; - for (int i = 0; i < bonesCount; ++i) { - rotationVectors[i] = new Vector3d(); - } - J = new Matrix(3, bonesCount); - } - - BoneContext topBone = bones.get(0); - for (int i = 0; i < iterations; ++i) { - DTransform topBoneTransform = bones.getWorldTransform(topBone); - Vector3d e = topBoneTransform.getTranslation().add(topBoneTransform.getRotation().mult(Vector3d.UNIT_Y).multLocal(topBone.getLength()));// effector - distanceFromTarget = e.distance(target); - if (distanceFromTarget <= MIN_DISTANCE) { - break; - } - - deltaP.setColumn(0, 0, target.x - e.x, target.y - e.y, target.z - e.z); - int column = 0; - for (BoneContext boneContext : bones) { - DTransform boneWorldTransform = bones.getWorldTransform(boneContext); - Vector3d j = boneWorldTransform.getTranslation(); // current join position - Vector3d vectorFromJointToEffector = e.subtract(j); - vectorFromJointToEffector.cross(target.subtract(j), rotationVectors[column]).normalizeLocal(); - rotationVectors[column].cross(vectorFromJointToEffector, col); - J.setColumn(col, column++); - } - Matrix J_1 = J.pseudoinverse(); - - SimpleMatrix deltaThetas = J_1.mult(deltaP); - if (deltaThetas.elementMaxAbs() < MIN_ANGLE_CHANGE) { - break; - } - for (int j = 0; j < deltaThetas.numRows(); ++j) { - double angle = deltaThetas.get(j, 0); - Vector3d rotationVector = rotationVectors[j]; - - tempDQuaternion.fromAngleAxis(angle, rotationVector); - BoneContext boneContext = bones.get(j); - Bone bone = boneContext.getBone(); - if (bone.equals(this.getOwner())) { - if (boneContext.isLockX()) { - tempDQuaternion.set(0, tempDQuaternion.getY(), tempDQuaternion.getZ(), tempDQuaternion.getW()); - } - if (boneContext.isLockY()) { - tempDQuaternion.set(tempDQuaternion.getX(), 0, tempDQuaternion.getZ(), tempDQuaternion.getW()); - } - if (boneContext.isLockZ()) { - tempDQuaternion.set(tempDQuaternion.getX(), tempDQuaternion.getY(), 0, tempDQuaternion.getW()); - } - } - - DTransform boneTransform = bones.getWorldTransform(boneContext); - boneTransform.getRotation().set(tempDQuaternion.mult(boneTransform.getRotation())); - bones.setWorldTransform(boneContext, boneTransform); - } - } - - // applying the results - for (int i = bonesCount - 1; i >= 0; --i) { - BoneContext boneContext = bones.get(i); - DTransform transform = bones.getWorldTransform(boneContext); - constraintHelper.applyTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD, transform.toTransform()); - } - bones = null;// need to reload them again - } - - @Override - public String getConstraintTypeName() { - return "Inverse kinematics"; - } - - @Override - public boolean isTargetRequired() { - return true; - } - - /** - * Loaded bones' chain. This class allows to operate on transform matrices that use double precision in computations. - * Only the final result is being transformed to single precision numbers. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class BonesChain extends ArrayList { - private static final long serialVersionUID = -1850524345643600718L; - - private List localBonesMatrices = new ArrayList(); - - public BonesChain(Bone bone, boolean useTail, int bonesAffected, Collection alteredOmas, BlenderContext blenderContext) { - if (bone != null) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - if (!useTail) { - bone = bone.getParent(); - } - while (bone != null && (bonesAffected <= 0 || this.size() < bonesAffected)) { - BoneContext boneContext = blenderContext.getBoneContext(bone); - this.add(boneContext); - alteredOmas.add(boneContext.getBoneOma()); - - Transform transform = constraintHelper.getTransform(boneContext.getArmatureObjectOMA(), boneContext.getBone().getName(), Space.CONSTRAINT_SPACE_WORLD); - localBonesMatrices.add(new DTransform(transform).toMatrix()); - - bone = bone.getParent(); - } - - if(localBonesMatrices.size() > 0) { - // making the matrices describe the local transformation - Matrix parentWorldMatrix = localBonesMatrices.get(localBonesMatrices.size() - 1); - for(int i=localBonesMatrices.size() - 2;i>=0;--i) { - SimpleMatrix m = parentWorldMatrix.invert().mult(localBonesMatrices.get(i)); - parentWorldMatrix = localBonesMatrices.get(i); - localBonesMatrices.set(i, new Matrix(m)); - } - } - } - } - - public DTransform getWorldTransform(BoneContext bone) { - int index = this.indexOf(bone); - return this.getWorldMatrix(index).toTransform(); - } - - public void setWorldTransform(BoneContext bone, DTransform transform) { - int index = this.indexOf(bone); - Matrix boneMatrix = transform.toMatrix(); - - if (index < this.size() - 1) { - // computing the current bone local transform - Matrix parentWorldMatrix = this.getWorldMatrix(index + 1); - SimpleMatrix m = parentWorldMatrix.invert().mult(boneMatrix); - boneMatrix = new Matrix(m); - } - localBonesMatrices.set(index, boneMatrix); - } - - public Matrix getWorldMatrix(int index) { - if (index == this.size() - 1) { - return new Matrix(localBonesMatrices.get(this.size() - 1)); - } - - SimpleMatrix result = this.getWorldMatrix(index + 1); - result = result.mult(localBonesMatrices.get(index)); - return new Matrix(result); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java deleted file mode 100644 index bf6fb39b14..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLike.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Loc like' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionLocLike extends ConstraintDefinition { - private static final int LOCLIKE_X = 0x01; - private static final int LOCLIKE_Y = 0x02; - private static final int LOCLIKE_Z = 0x04; - // protected static final int LOCLIKE_TIP = 0x08;//this is deprecated in - // blender - private static final int LOCLIKE_X_INVERT = 0x10; - private static final int LOCLIKE_Y_INVERT = 0x20; - private static final int LOCLIKE_Z_INVERT = 0x40; - private static final int LOCLIKE_OFFSET = 0x80; - - public ConstraintDefinitionLocLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - // swapping Y and X limits flag in the bitwise flag - int y = flag & LOCLIKE_Y; - int invY = flag & LOCLIKE_Y_INVERT; - int z = flag & LOCLIKE_Z; - int invZ = flag & LOCLIKE_Z_INVERT; - // clear the other flags to swap them - flag &= LOCLIKE_X | LOCLIKE_X_INVERT | LOCLIKE_OFFSET; - - flag |= y << 1; - flag |= invY << 1; - flag |= z >> 1; - flag |= invZ >> 1; - - trackToBeChanged = (flag & LOCLIKE_X) != 0 || (flag & LOCLIKE_Y) != 0 || (flag & LOCLIKE_Z) != 0; - } - } - - @Override - public boolean isTrackToBeChanged() { - // location copy does not work on bones who are connected to their parent - return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)); - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || targetTransform == null || !this.isTrackToBeChanged()) { - return; - } - - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Vector3f ownerLocation = ownerTransform.getTranslation(); - Vector3f targetLocation = targetTransform.getTranslation(); - - Vector3f startLocation = ownerTransform.getTranslation().clone(); - Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original location to the copied location - offset = startLocation; - } - - if ((flag & LOCLIKE_X) != 0) { - ownerLocation.x = targetLocation.x; - if ((flag & LOCLIKE_X_INVERT) != 0) { - ownerLocation.x = -ownerLocation.x; - } - } - if ((flag & LOCLIKE_Y) != 0) { - ownerLocation.y = targetLocation.y; - if ((flag & LOCLIKE_Y_INVERT) != 0) { - ownerLocation.y = -ownerLocation.y; - } - } - if ((flag & LOCLIKE_Z) != 0) { - ownerLocation.z = targetLocation.z; - if ((flag & LOCLIKE_Z_INVERT) != 0) { - ownerLocation.z = -ownerLocation.z; - } - } - ownerLocation.addLocal(offset); - - if (influence < 1.0f) { - startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); - ownerLocation.addLocal(startLocation); - } - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Copy location"; - } - - @Override - public boolean isTargetRequired() { - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java deleted file mode 100644 index 62e92e73b2..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionLocLimit.java +++ /dev/null @@ -1,106 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Loc limit' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionLocLimit extends ConstraintDefinition { - private static final int LIMIT_XMIN = 0x01; - private static final int LIMIT_XMAX = 0x02; - private static final int LIMIT_YMIN = 0x04; - private static final int LIMIT_YMAX = 0x08; - private static final int LIMIT_ZMIN = 0x10; - private static final int LIMIT_ZMAX = 0x20; - - protected float[][] limits = new float[3][2]; - - public ConstraintDefinitionLocLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - - // swapping Y and X limits flag in the bitwise flag - int ymin = flag & LIMIT_YMIN; - int ymax = flag & LIMIT_YMAX; - int zmin = flag & LIMIT_ZMIN; - int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap - // them - flag |= ymin << 2; - flag |= ymax << 2; - flag |= zmin >> 2; - flag |= zmax >> 2; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } - - trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0; - } - - @Override - public boolean isTrackToBeChanged() { - // location limit does not work on bones who are connected to their parent - return trackToBeChanged && !(this.getOwner() instanceof Bone && ((Bone) this.getOwner()).getParent() != null && blenderContext.getBoneContext(ownerOMA).is(BoneContext.CONNECTED_TO_PARENT)); - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || !this.isTrackToBeChanged()) { - return;// no need to do anything - } - - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Vector3f translation = ownerTransform.getTranslation(); - - if ((flag & LIMIT_XMIN) != 0 && translation.x < limits[0][0]) { - translation.x -= (translation.x - limits[0][0]) * influence; - } - if ((flag & LIMIT_XMAX) != 0 && translation.x > limits[0][1]) { - translation.x -= (translation.x - limits[0][1]) * influence; - } - if ((flag & LIMIT_YMIN) != 0 && translation.y < limits[1][0]) { - translation.y -= (translation.y - limits[1][0]) * influence; - } - if ((flag & LIMIT_YMAX) != 0 && translation.y > limits[1][1]) { - translation.y -= (translation.y - limits[1][1]) * influence; - } - if ((flag & LIMIT_ZMIN) != 0 && translation.z < limits[2][0]) { - translation.z -= (translation.z - limits[2][0]) * influence; - } - if ((flag & LIMIT_ZMAX) != 0 && translation.z > limits[2][1]) { - translation.z -= (translation.z - limits[2][1]) * influence; - } - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Limit location"; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMaintainVolume.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMaintainVolume.java deleted file mode 100644 index c8756129ee..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionMaintainVolume.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.animation.Bone; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Maintain volume' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ConstraintDefinitionMaintainVolume extends ConstraintDefinition { - private static final int FLAG_MASK_X = 0; - private static final int FLAG_MASK_Y = 1; - private static final int FLAG_MASK_Z = 2; - - private float volume; - - public ConstraintDefinitionMaintainVolume(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - volume = (float) Math.sqrt(((Number) constraintData.getFieldValue("volume")).floatValue()); - trackToBeChanged = volume != 1 && (flag & (FLAG_MASK_X | FLAG_MASK_Y | FLAG_MASK_Z)) != 0; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (trackToBeChanged && influence > 0) { - // the maintain volume constraint is applied directly to object's scale, so no need to do it again - // but in case of bones we need to make computations - if (this.getOwner() instanceof Bone) { - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - switch (flag) { - case FLAG_MASK_X: - ownerTransform.getScale().multLocal(1, volume, volume); - break; - case FLAG_MASK_Y: - ownerTransform.getScale().multLocal(volume, 1, volume); - break; - case FLAG_MASK_Z: - ownerTransform.getScale().multLocal(volume, volume, 1); - break; - default: - throw new IllegalStateException("Unknown flag value: " + flag); - } - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - } - } - - @Override - public String getConstraintTypeName() { - return "Maintain volume"; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java deleted file mode 100644 index 032909c653..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionNull.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Null' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionNull extends ConstraintDefinition { - - public ConstraintDefinitionNull(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - trackToBeChanged = false; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - // null constraint does nothing so no need to implement this one - } - - @Override - public String getConstraintTypeName() { - return "Null"; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java deleted file mode 100644 index 280e4e77ea..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLike.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.Quaternion; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Rot like' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionRotLike extends ConstraintDefinition { - private static final int ROTLIKE_X = 0x01; - private static final int ROTLIKE_Y = 0x02; - private static final int ROTLIKE_Z = 0x04; - private static final int ROTLIKE_X_INVERT = 0x10; - private static final int ROTLIKE_Y_INVERT = 0x20; - private static final int ROTLIKE_Z_INVERT = 0x40; - private static final int ROTLIKE_OFFSET = 0x80; - - private transient float[] ownerAngles = new float[3]; - private transient float[] targetAngles = new float[3]; - - public ConstraintDefinitionRotLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - trackToBeChanged = (flag & (ROTLIKE_X | ROTLIKE_Y | ROTLIKE_Z)) != 0; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || targetTransform == null || !trackToBeChanged) { - return;// no need to do anything - } - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Quaternion ownerRotation = ownerTransform.getRotation(); - ownerAngles = ownerRotation.toAngles(ownerAngles); - targetAngles = targetTransform.getRotation().toAngles(targetAngles); - - Quaternion startRotation = ownerRotation.clone(); - Quaternion offset = Quaternion.IDENTITY; - if ((flag & ROTLIKE_OFFSET) != 0) {// we add the original rotation to - // the copied rotation - offset = startRotation; - } - - if ((flag & ROTLIKE_X) != 0) { - ownerAngles[0] = targetAngles[0]; - if ((flag & ROTLIKE_X_INVERT) != 0) { - ownerAngles[0] = -ownerAngles[0]; - } - } - if ((flag & ROTLIKE_Y) != 0) { - ownerAngles[1] = targetAngles[1]; - if ((flag & ROTLIKE_Y_INVERT) != 0) { - ownerAngles[1] = -ownerAngles[1]; - } - } - if ((flag & ROTLIKE_Z) != 0) { - ownerAngles[2] = targetAngles[2]; - if ((flag & ROTLIKE_Z_INVERT) != 0) { - ownerAngles[2] = -ownerAngles[2]; - } - } - ownerRotation.fromAngles(ownerAngles).multLocal(offset); - - if (influence < 1.0f) { - // startLocation.subtractLocal(ownerLocation).normalizeLocal().mult(influence); - // ownerLocation.addLocal(startLocation); - // TODO - } - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Copy rotation"; - } - - @Override - public boolean isTargetRequired() { - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java deleted file mode 100644 index 3d569447fa..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionRotLimit.java +++ /dev/null @@ -1,129 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.FastMath; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Rot limit' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionRotLimit extends ConstraintDefinition { - private static final int LIMIT_XROT = 0x01; - private static final int LIMIT_YROT = 0x02; - private static final int LIMIT_ZROT = 0x04; - - private transient float[][] limits = new float[3][2]; - private transient float[] angles = new float[3]; - - public ConstraintDefinitionRotLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - - // swapping Y and X limits flag in the bitwise flag - int limitY = flag & LIMIT_YROT; - int limitZ = flag & LIMIT_ZROT; - flag &= LIMIT_XROT;// clear the other flags to swap them - flag |= limitY << 1; - flag |= limitZ >> 1; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } - - // until blender 2.49 the rotations values were stored in degrees - if (blenderContext.getBlenderVersion() <= 249) { - for (int i = 0; i < 3; ++i) { - limits[i][0] *= FastMath.DEG_TO_RAD; - limits[i][1] *= FastMath.DEG_TO_RAD; - } - } - - // make sure that the limits are always in range [0, 2PI) - // TODO: left it here because it is essential to make sure all cases - // work poperly - // but will do it a little bit later ;) - /* - * for (int i = 0; i < 3; ++i) { for (int j = 0; j < 2; ++j) { int - * multFactor = (int)Math.abs(limits[i][j] / FastMath.TWO_PI) ; if - * (limits[i][j] < 0) { limits[i][j] += FastMath.TWO_PI * (multFactor + - * 1); } else { limits[i][j] -= FastMath.TWO_PI * multFactor; } } //make - * sure the lower limit is not greater than the upper one - * if(limits[i][0] > limits[i][1]) { float temp = limits[i][0]; - * limits[i][0] = limits[i][1]; limits[i][1] = temp; } } - */ - - trackToBeChanged = (flag & (LIMIT_XROT | LIMIT_YROT | LIMIT_ZROT)) != 0; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || !trackToBeChanged) { - return; - } - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - ownerTransform.getRotation().toAngles(angles); - // make sure that the rotations are always in range [0, 2PI) - // TODO: same comment as in constructor - /* - * for (int i = 0; i < 3; ++i) { int multFactor = - * (int)Math.abs(angles[i] / FastMath.TWO_PI) ; if(angles[i] < 0) { - * angles[i] += FastMath.TWO_PI * (multFactor + 1); } else { angles[i] - * -= FastMath.TWO_PI * multFactor; } } - */ - if ((flag & LIMIT_XROT) != 0) { - float difference = 0.0f; - if (angles[0] < limits[0][0]) { - difference = (angles[0] - limits[0][0]) * influence; - } else if (angles[0] > limits[0][1]) { - difference = (angles[0] - limits[0][1]) * influence; - } - angles[0] -= difference; - } - if ((flag & LIMIT_YROT) != 0) { - float difference = 0.0f; - if (angles[1] < limits[1][0]) { - difference = (angles[1] - limits[1][0]) * influence; - } else if (angles[1] > limits[1][1]) { - difference = (angles[1] - limits[1][1]) * influence; - } - angles[1] -= difference; - } - if ((flag & LIMIT_ZROT) != 0) { - float difference = 0.0f; - if (angles[2] < limits[2][0]) { - difference = (angles[2] - limits[2][0]) * influence; - } else if (angles[2] > limits[2][1]) { - difference = (angles[2] - limits[2][1]) * influence; - } - angles[2] -= difference; - } - ownerTransform.getRotation().fromAngles(angles); - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Limit rotation"; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java deleted file mode 100644 index b05048af61..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLike.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Size like' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionSizeLike extends ConstraintDefinition { - private static final int SIZELIKE_X = 0x01; - private static final int SIZELIKE_Y = 0x02; - private static final int SIZELIKE_Z = 0x04; - private static final int LOCLIKE_OFFSET = 0x80; - - public ConstraintDefinitionSizeLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - // swapping Y and X limits flag in the bitwise flag - int y = flag & SIZELIKE_Y; - int z = flag & SIZELIKE_Z; - flag &= SIZELIKE_X | LOCLIKE_OFFSET;// clear the other flags to swap - // them - flag |= y << 1; - flag |= z >> 1; - - trackToBeChanged = (flag & (SIZELIKE_X | SIZELIKE_Y | SIZELIKE_Z)) != 0; - } - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || targetTransform == null || !trackToBeChanged) { - return;// no need to do anything - } - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Vector3f ownerScale = ownerTransform.getScale(); - Vector3f targetScale = targetTransform.getScale(); - - Vector3f offset = Vector3f.ZERO; - if ((flag & LOCLIKE_OFFSET) != 0) {// we add the original scale to the - // copied scale - offset = ownerScale.clone(); - } - - if ((flag & SIZELIKE_X) != 0) { - ownerScale.x = targetScale.x * influence + (1.0f - influence) * ownerScale.x; - } - if ((flag & SIZELIKE_Y) != 0) { - ownerScale.y = targetScale.y * influence + (1.0f - influence) * ownerScale.y; - } - if ((flag & SIZELIKE_Z) != 0) { - ownerScale.z = targetScale.z * influence + (1.0f - influence) * ownerScale.z; - } - ownerScale.addLocal(offset); - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Copy scale"; - } - - @Override - public boolean isTargetRequired() { - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java deleted file mode 100644 index 6c133bf187..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionSizeLimit.java +++ /dev/null @@ -1,96 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * This class represents 'Size limit' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ConstraintDefinitionSizeLimit extends ConstraintDefinition { - private static final int LIMIT_XMIN = 0x01; - private static final int LIMIT_XMAX = 0x02; - private static final int LIMIT_YMIN = 0x04; - private static final int LIMIT_YMAX = 0x08; - private static final int LIMIT_ZMIN = 0x10; - private static final int LIMIT_ZMAX = 0x20; - - protected transient float[][] limits = new float[3][2]; - - public ConstraintDefinitionSizeLimit(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[2][0] = -((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[2][1] = -((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - - // swapping Y and X limits flag in the bitwise flag - int ymin = flag & LIMIT_YMIN; - int ymax = flag & LIMIT_YMAX; - int zmin = flag & LIMIT_ZMIN; - int zmax = flag & LIMIT_ZMAX; - flag &= LIMIT_XMIN | LIMIT_XMAX;// clear the other flags to swap - // them - flag |= ymin << 2; - flag |= ymax << 2; - flag |= zmin >> 2; - flag |= zmax >> 2; - } else { - limits[0][0] = ((Number) constraintData.getFieldValue("xmin")).floatValue(); - limits[0][1] = ((Number) constraintData.getFieldValue("xmax")).floatValue(); - limits[1][0] = ((Number) constraintData.getFieldValue("ymin")).floatValue(); - limits[1][1] = ((Number) constraintData.getFieldValue("ymax")).floatValue(); - limits[2][0] = ((Number) constraintData.getFieldValue("zmin")).floatValue(); - limits[2][1] = ((Number) constraintData.getFieldValue("zmax")).floatValue(); - } - - trackToBeChanged = (flag & (LIMIT_XMIN | LIMIT_XMAX | LIMIT_YMIN | LIMIT_YMAX | LIMIT_ZMIN | LIMIT_ZMAX)) != 0; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || !trackToBeChanged) { - return; - } - Transform ownerTransform = this.getOwnerTransform(ownerSpace); - - Vector3f scale = ownerTransform.getScale(); - if ((flag & LIMIT_XMIN) != 0 && scale.x < limits[0][0]) { - scale.x -= (scale.x - limits[0][0]) * influence; - } - if ((flag & LIMIT_XMAX) != 0 && scale.x > limits[0][1]) { - scale.x -= (scale.x - limits[0][1]) * influence; - } - if ((flag & LIMIT_YMIN) != 0 && scale.y < limits[1][0]) { - scale.y -= (scale.y - limits[1][0]) * influence; - } - if ((flag & LIMIT_YMAX) != 0 && scale.y > limits[1][1]) { - scale.y -= (scale.y - limits[1][1]) * influence; - } - if ((flag & LIMIT_ZMIN) != 0 && scale.z < limits[2][0]) { - scale.z -= (scale.z - limits[2][0]) * influence; - } - if ((flag & LIMIT_ZMAX) != 0 && scale.z > limits[2][1]) { - scale.z -= (scale.z - limits[2][1]) * influence; - } - - this.applyOwnerTransform(ownerTransform, ownerSpace); - } - - @Override - public String getConstraintTypeName() { - return "Limit scale"; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java deleted file mode 100644 index b388572794..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/ConstraintDefinitionTransLike.java +++ /dev/null @@ -1,81 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.math.Matrix4f; -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; -import com.jme3.util.TempVars; - -/** - * This class represents 'Trans like' constraint type in blender. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ConstraintDefinitionTransLike extends ConstraintDefinition { - private Long targetOMA; - private String subtargetName; - - public ConstraintDefinitionTransLike(Structure constraintData, Long ownerOMA, BlenderContext blenderContext) { - super(constraintData, ownerOMA, blenderContext); - Pointer pTarget = (Pointer) constraintData.getFieldValue("tar"); - targetOMA = pTarget.getOldMemoryAddress(); - Object subtarget = constraintData.getFieldValue("subtarget"); - if (subtarget != null) { - subtargetName = subtarget.toString(); - } - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - if (influence == 0 || targetTransform == null) { - return;// no need to do anything - } - Object target = this.getTarget();// Bone or Node - Object owner = this.getOwner();// Bone or Node - if (!target.getClass().equals(owner.getClass())) { - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - - TempVars tempVars = TempVars.get(); - Matrix4f m = constraintHelper.toMatrix(targetTransform, tempVars.tempMat4); - tempVars.tempMat42.set(BoneContext.BONE_ARMATURE_TRANSFORMATION_MATRIX); - if (target instanceof Bone) { - tempVars.tempMat42.invertLocal(); - } - m = m.multLocal(tempVars.tempMat42); - tempVars.release(); - - targetTransform = new Transform(m.toTranslationVector(), m.toRotationQuat(), m.toScaleVector()); - } - this.applyOwnerTransform(targetTransform, ownerSpace); - } - - /** - * @return the target feature; it is either Node or Bone (vertex group subtarger is not yet supported) - */ - private Object getTarget() { - Object target = blenderContext.getLoadedFeature(targetOMA, LoadedDataType.FEATURE); - if (subtargetName != null && blenderContext.getMarkerValue(ObjectHelper.ARMATURE_NODE_MARKER, target) != null) { - Skeleton skeleton = blenderContext.getSkeleton(targetOMA); - target = skeleton.getBone(subtargetName); - } - return target; - } - - @Override - public String getConstraintTypeName() { - return "Copy transforms"; - } - - @Override - public boolean isTargetRequired() { - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java deleted file mode 100644 index e3fa82e9ab..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/constraints/definitions/UnsupportedConstraintDefinition.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.scene.plugins.blender.constraints.definitions; - -import com.jme3.math.Transform; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper.Space; - -/** - * This class represents a constraint that is defined by blender but not - * supported by either importer ot jme. It only wirtes down a warning when - * baking is called. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class UnsupportedConstraintDefinition extends ConstraintDefinition { - private String typeName; - - public UnsupportedConstraintDefinition(String typeName) { - super(null, null, null); - this.typeName = typeName; - trackToBeChanged = false; - } - - @Override - public void bake(Space ownerSpace, Space targetSpace, Transform targetTransform, float influence) { - } - - @Override - public boolean isImplemented() { - return false; - } - - @Override - public String getConstraintTypeName() { - return typeName; - } - - @Override - public boolean isTargetRequired() { - return false; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java deleted file mode 100644 index 96a91335dc..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/BezierCurve.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.jme3.scene.plugins.blender.curves; - -import java.util.ArrayList; -import java.util.List; - -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that helps to calculate the bezier curves calues. It uses doubles for performing calculations to minimize - * floating point operations errors. - * @author Marcin Roguski (Kaelthas) - */ -public class BezierCurve { - private static final int IPO_CONSTANT = 0; - private static final int IPO_LINEAR = 1; - private static final int IPO_BEZIER = 2; - - public static final int X_VALUE = 0; - public static final int Y_VALUE = 1; - public static final int Z_VALUE = 2; - /** - * The type of the curve. Describes the data it modifies. - * Used in ipos calculations. - */ - private int type; - /** The dimension of the curve. */ - private int dimension; - /** A table of the bezier points. */ - private double[][][] bezierPoints; - /** Array that stores a radius for each bezier triple. */ - private double[] radiuses; - /** Interpolation types of the bezier triples. */ - private int[] interpolations; - - public BezierCurve(final int type, final List bezTriples, final int dimension) { - this(type, bezTriples, dimension, false); - } - - @SuppressWarnings("unchecked") - public BezierCurve(final int type, final List bezTriples, final int dimension, boolean fixUpAxis) { - if (dimension != 2 && dimension != 3) { - throw new IllegalArgumentException("The dimension of the curve should be 2 or 3!"); - } - this.type = type; - this.dimension = dimension; - // first index of the bezierPoints table has the length of triples amount - // the second index points to a table od three points of a bezier triple (handle, point, handle) - // the third index specifies the coordinates of the specific point in a bezier triple - bezierPoints = new double[bezTriples.size()][3][dimension]; - radiuses = new double[bezTriples.size()]; - interpolations = new int[bezTriples.size()]; - int i = 0, j, k; - for (Structure bezTriple : bezTriples) { - DynamicArray vec = (DynamicArray) bezTriple.getFieldValue("vec"); - for (j = 0; j < 3; ++j) { - for (k = 0; k < dimension; ++k) { - bezierPoints[i][j][k] = vec.get(j, k).doubleValue(); - } - if (fixUpAxis && dimension == 3) { - double temp = bezierPoints[i][j][2]; - bezierPoints[i][j][2] = -bezierPoints[i][j][1]; - bezierPoints[i][j][1] = temp; - } - } - radiuses[i] = ((Number) bezTriple.getFieldValue("radius")).floatValue(); - interpolations[i++] = ((Number) bezTriple.getFieldValue("ipo", IPO_BEZIER)).intValue(); - } - } - - /** - * This method evaluates the data for the specified frame. The Y value is returned. - * @param frame - * the frame for which the value is being calculated - * @param valuePart - * this param specifies wheather we should return the X, Y or Z part of the result value; it should have - * one of the following values: X_VALUE - the X factor of the result Y_VALUE - the Y factor of the result - * Z_VALUE - the Z factor of the result - * @return the value of the curve - */ - public double evaluate(int frame, int valuePart) { - for (int i = 0; i < bezierPoints.length - 1; ++i) { - if (frame >= bezierPoints[i][1][0] && frame <= bezierPoints[i + 1][1][0]) { - double t = (frame - bezierPoints[i][1][0]) / (bezierPoints[i + 1][1][0] - bezierPoints[i][1][0]); - switch (interpolations[i]) { - case IPO_BEZIER: - double oneMinusT = 1.0f - t; - double oneMinusT2 = oneMinusT * oneMinusT; - double t2 = t * t; - return bezierPoints[i][1][valuePart] * oneMinusT2 * oneMinusT + 3.0f * bezierPoints[i][2][valuePart] * t * oneMinusT2 + 3.0f * bezierPoints[i + 1][0][valuePart] * t2 * oneMinusT + bezierPoints[i + 1][1][valuePart] * t2 * t; - case IPO_LINEAR: - return (1f - t) * bezierPoints[i][1][valuePart] + t * bezierPoints[i + 1][1][valuePart]; - case IPO_CONSTANT: - return bezierPoints[i][1][valuePart]; - default: - throw new IllegalStateException("Unknown interpolation type for curve: " + interpolations[i]); - } - } - } - if (frame < bezierPoints[0][1][0]) { - return bezierPoints[0][1][1]; - } else { // frame>bezierPoints[bezierPoints.length-1][1][0] - return bezierPoints[bezierPoints.length - 1][1][1]; - } - } - - /** - * This method returns the frame where last bezier triple center point of the bezier curve is located. - * @return the frame number of the last defined bezier triple point for the curve - */ - public int getLastFrame() { - return (int) bezierPoints[bezierPoints.length - 1][1][0]; - } - - /** - * This method returns the type of the bezier curve. The type describes the parameter that this curve modifies - * (ie. LocationX or rotationW of the feature). - * @return the type of the bezier curve - */ - public int getType() { - return type; - } - - /** - * The method returns the radius for the required bezier triple. - * - * @param bezierTripleIndex - * index of the bezier triple - * @return radius of the required bezier triple - */ - public double getRadius(int bezierTripleIndex) { - return radiuses[bezierTripleIndex]; - } - - /** - * This method returns a list of control points for this curve. - * @return a list of control points for this curve. - */ - public List getControlPoints() { - List controlPoints = new ArrayList(bezierPoints.length * 3); - for (int i = 0; i < bezierPoints.length; ++i) { - controlPoints.add(new Vector3f((float) bezierPoints[i][0][0], (float) bezierPoints[i][0][1], (float) bezierPoints[i][0][2])); - controlPoints.add(new Vector3f((float) bezierPoints[i][1][0], (float) bezierPoints[i][1][1], (float) bezierPoints[i][1][2])); - controlPoints.add(new Vector3f((float) bezierPoints[i][2][0], (float) bezierPoints[i][2][1], (float) bezierPoints[i][2][2])); - } - return controlPoints; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder("Bezier curve: ").append(type).append('\n'); - for (int i = 0; i < bezierPoints.length; ++i) { - sb.append(this.toStringBezTriple(i)).append('\n'); - } - return sb.toString(); - } - - /** - * This method converts the bezier triple of a specified index into text. - * @param tripleIndex - * index of the triple - * @return text representation of the triple - */ - private String toStringBezTriple(int tripleIndex) { - if (dimension == 2) { - return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ")]"; - } else { - return "[(" + bezierPoints[tripleIndex][0][0] + ", " + bezierPoints[tripleIndex][0][1] + ", " + bezierPoints[tripleIndex][0][2] + ") (" + bezierPoints[tripleIndex][1][0] + ", " + bezierPoints[tripleIndex][1][1] + ", " + bezierPoints[tripleIndex][1][2] + ") (" + bezierPoints[tripleIndex][2][0] + ", " + bezierPoints[tripleIndex][2][1] + ", " + bezierPoints[tripleIndex][2][2] + ")]"; - } - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java deleted file mode 100644 index dd033d3dca..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesHelper.java +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.curves; - -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that is used in mesh calculations. - * - * @author Marcin Roguski (Kaelthas) - */ -public class CurvesHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(CurvesHelper.class.getName()); - - /** Minimum basis U function degree for NURBS curves and surfaces. */ - protected int minimumBasisUFunctionDegree = 4; - /** Minimum basis V function degree for NURBS curves and surfaces. */ - protected int minimumBasisVFunctionDegree = 4; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public CurvesHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - public CurvesTemporalMesh toCurve(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { - CurvesTemporalMesh result = new CurvesTemporalMesh(curveStructure, blenderContext); - - if (blenderContext.getBlenderKey().isLoadObjectProperties()) { - LOGGER.fine("Reading custom properties."); - result.setProperties(this.loadProperties(curveStructure, blenderContext)); - } - - return result; - } - - /** - * The method transforms the bevel along the curve. - * - * @param bevel - * the bevel to be transformed - * @param prevPos - * previous curve point - * @param currPos - * current curve point (here the center of the new bevel will be - * set) - * @param nextPos - * next curve point - * @return points of transformed bevel - */ - protected Vector3f[] transformBevel(Vector3f[] bevel, Vector3f prevPos, Vector3f currPos, Vector3f nextPos) { - bevel = bevel.clone(); - - // currPos and directionVector define the line in 3D space - Vector3f directionVector = prevPos != null ? currPos.subtract(prevPos) : nextPos.subtract(currPos); - directionVector.normalizeLocal(); - - // plane is described by equation: Ax + By + Cz + D = 0 where planeNormal = [A, B, C] and D = -(Ax + By + Cz) - Vector3f planeNormal = null; - if (prevPos != null) { - planeNormal = currPos.subtract(prevPos).normalizeLocal(); - if (nextPos != null) { - planeNormal.addLocal(nextPos.subtract(currPos).normalizeLocal()).normalizeLocal(); - } - } else { - planeNormal = nextPos.subtract(currPos).normalizeLocal(); - } - float D = -planeNormal.dot(currPos);// D = -(Ax + By + Cz) - - // now we need to compute paralell cast of each bevel point on the plane, the leading line is already known - // parametric equation of a line: x = px + vx * t; y = py + vy * t; z = pz + vz * t - // where p = currPos and v = directionVector - // using x, y and z in plane equation we get value of 't' that will allow us to compute the point where plane and line cross - float temp = planeNormal.dot(directionVector); - for (int i = 0; i < bevel.length; ++i) { - float t = -(planeNormal.dot(bevel[i]) + D) / temp; - if (fixUpAxis) { - bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, bevel[i].y + directionVector.y * t, bevel[i].z + directionVector.z * t); - } else { - bevel[i] = new Vector3f(bevel[i].x + directionVector.x * t, -bevel[i].z + directionVector.z * t, bevel[i].y + directionVector.y * t); - } - } - return bevel; - } - - /** - * This method transforms the first line of the bevel points positioning it - * on the first point of the curve. - * - * @param startingLinePoints - * the vbevel shape points - * @param firstCurvePoint - * the first curve's point - * @param secondCurvePoint - * the second curve's point - * @return points of transformed bevel - */ - protected Vector3f[] transformToFirstLineOfBevelPoints(Vector3f[] startingLinePoints, Vector3f firstCurvePoint, Vector3f secondCurvePoint) { - Vector3f planeNormal = secondCurvePoint.subtract(firstCurvePoint).normalizeLocal(); - - float angle = FastMath.acos(planeNormal.dot(Vector3f.UNIT_X)); - Vector3f rotationVector = Vector3f.UNIT_X.cross(planeNormal).normalizeLocal(); - - Matrix4f m = new Matrix4f(); - m.setRotationQuaternion(new Quaternion().fromAngleAxis(angle, rotationVector)); - m.setTranslation(firstCurvePoint); - - Vector3f temp = new Vector3f(); - Vector3f[] verts = new Vector3f[startingLinePoints.length]; - for (int i = 0; i < verts.length; ++i) { - verts[i] = m.mult(startingLinePoints[i], temp).clone(); - } - return verts; - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java deleted file mode 100644 index 2ad8d3ff2e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/curves/CurvesTemporalMesh.java +++ /dev/null @@ -1,890 +0,0 @@ -package com.jme3.scene.plugins.blender.curves; - -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.TreeMap; -import java.util.logging.Logger; - -import com.jme3.material.RenderState.FaceCullMode; -import com.jme3.math.FastMath; -import com.jme3.math.Spline; -import com.jme3.math.Spline.SplineType; -import com.jme3.math.Vector3f; -import com.jme3.math.Vector4f; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.scene.plugins.blender.meshes.Edge; -import com.jme3.scene.plugins.blender.meshes.Face; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.shape.Curve; -import com.jme3.scene.shape.Surface; -import com.jme3.util.BufferUtils; - -/** - * A temporal mesh for curves and surfaces. It works in similar way as TemporalMesh for meshes. - * It prepares all necessary lines and faces and allows to apply modifiers just like in regular temporal mesh. - * - * @author Marcin Roguski (Kaelthas) - */ -public class CurvesTemporalMesh extends TemporalMesh { - private static final Logger LOGGER = Logger.getLogger(CurvesTemporalMesh.class.getName()); - - private static final int TYPE_BEZIER = 0x0001; - private static final int TYPE_NURBS = 0x0004; - - private static final int FLAG_3D = 0x0001; - private static final int FLAG_FRONT = 0x0002; - private static final int FLAG_BACK = 0x0004; - private static final int FLAG_FILL_CAPS = 0x4000; - - private static final int FLAG_SMOOTH = 0x0001; - - protected CurvesHelper curvesHelper; - protected boolean is2D; - protected boolean isFront; - protected boolean isBack; - protected boolean fillCaps; - protected float bevelStart; - protected float bevelEnd; - protected List beziers = new ArrayList(); - protected CurvesTemporalMesh bevelObject; - protected CurvesTemporalMesh taperObject; - /** The scale that is used if the curve is a bevel or taper curve. */ - protected Vector3f scale = new Vector3f(1, 1, 1); - - /** - * The constructor creates an empty temporal mesh. - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this will never be thrown here - */ - protected CurvesTemporalMesh(BlenderContext blenderContext) throws BlenderFileException { - super(null, blenderContext, false); - } - - /** - * Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface. - * @param curveStructure - * the structure that contains the curve/surface data - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - public CurvesTemporalMesh(Structure curveStructure, BlenderContext blenderContext) throws BlenderFileException { - this(curveStructure, new Vector3f(1, 1, 1), true, blenderContext); - } - - /** - * Loads the temporal mesh from the given curve structure. The mesh can be either curve or surface. - * @param curveStructure - * the structure that contains the curve/surface data - * @param scale - * the scale used if the current curve is used as a bevel curve - * @param loadBevelAndTaper indicates if bevel and taper should be loaded (this is not needed for curves that are loaded to be used as bevel and taper) - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - @SuppressWarnings("unchecked") - private CurvesTemporalMesh(Structure curveStructure, Vector3f scale, boolean loadBevelAndTaper, BlenderContext blenderContext) throws BlenderFileException { - super(curveStructure, blenderContext, false); - name = curveStructure.getName(); - curvesHelper = blenderContext.getHelper(CurvesHelper.class); - this.scale = scale; - - int flag = ((Number) curveStructure.getFieldValue("flag")).intValue(); - is2D = (flag & FLAG_3D) == 0; - if (is2D) { - // TODO: add support for 3D flag - LOGGER.warning("2D flag not yet supported for curves!"); - } - isFront = (flag & FLAG_FRONT) != 0; - isBack = (flag & FLAG_BACK) != 0; - fillCaps = (flag & FLAG_FILL_CAPS) != 0; - bevelStart = ((Number) curveStructure.getFieldValue("bevfac1", 0)).floatValue(); - bevelEnd = ((Number) curveStructure.getFieldValue("bevfac2", 1)).floatValue(); - if (bevelStart > bevelEnd) { - float temp = bevelStart; - bevelStart = bevelEnd; - bevelEnd = temp; - } - - LOGGER.fine("Reading nurbs (and sorting them by material)."); - Map> nurbs = new HashMap>(); - List nurbStructures = ((Structure) curveStructure.getFieldValue("nurb")).evaluateListBase(); - for (Structure nurb : nurbStructures) { - Number matNumber = (Number) nurb.getFieldValue("mat_nr"); - List nurbList = nurbs.get(matNumber); - if (nurbList == null) { - nurbList = new ArrayList(); - nurbs.put(matNumber, nurbList); - } - nurbList.add(nurb); - } - - LOGGER.fine("Getting materials."); - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - materials = materialHelper.getMaterials(curveStructure, blenderContext); - if (materials != null) { - for (MaterialContext materialContext : materials) { - materialContext.setFaceCullMode(FaceCullMode.Off); - } - } - - LOGGER.fine("Getting or creating bevel object."); - bevelObject = loadBevelAndTaper ? this.loadBevelObject(curveStructure) : null; - - LOGGER.fine("Getting taper object."); - Pointer pTaperObject = (Pointer) curveStructure.getFieldValue("taperobj"); - if (bevelObject != null && pTaperObject.isNotNull()) { - Structure taperObjectStructure = pTaperObject.fetchData().get(0); - DynamicArray scaleArray = (DynamicArray) taperObjectStructure.getFieldValue("size"); - scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue()); - Pointer pTaperStructure = (Pointer) taperObjectStructure.getFieldValue("data"); - Structure taperStructure = pTaperStructure.fetchData().get(0); - taperObject = new CurvesTemporalMesh(taperStructure, blenderContext); - } - - LOGGER.fine("Creating the result curves."); - for (Entry> nurbEntry : nurbs.entrySet()) { - for (Structure nurb : nurbEntry.getValue()) { - int type = ((Number) nurb.getFieldValue("type")).intValue(); - if ((type & TYPE_BEZIER) != 0) { - this.loadBezierCurve(nurb, nurbEntry.getKey().intValue()); - } else if ((type & TYPE_NURBS) != 0) { - this.loadNurbSurface(nurb, nurbEntry.getKey().intValue()); - } else { - throw new BlenderFileException("Unknown curve type: " + type); - } - } - } - - if (bevelObject != null && beziers.size() > 0) { - this.append(this.applyBevelAndTaper(this, bevelObject, taperObject, blenderContext)); - } else { - for (BezierLine bezierLine : beziers) { - int originalVerticesAmount = vertices.size(); - vertices.add(bezierLine.vertices[0]); - Vector3f v = bezierLine.vertices[1].subtract(bezierLine.vertices[0]).normalizeLocal(); - float temp = v.x; - v.x = -v.y; - v.y = temp; - v.z = 0; - normals.add(v);// this will be smoothed in the next iteration - - for (int i = 1; i < bezierLine.vertices.length; ++i) { - vertices.add(bezierLine.vertices[i]); - edges.add(new Edge(originalVerticesAmount + i - 1, originalVerticesAmount + i, 0, false, this)); - - // generating normal for vertex at 'i' - v = bezierLine.vertices[i].subtract(bezierLine.vertices[i - 1]).normalizeLocal(); - temp = v.x; - v.x = -v.y; - v.y = temp; - v.z = 0; - - // make the previous normal smooth - normals.get(i - 1).addLocal(v).multLocal(0.5f).normalizeLocal(); - normals.add(v);// this will be smoothed in the next iteration - } - } - } - } - - /** - * The method computes the value of a point at the certain relational distance from its beginning. - * @param alongRatio - * the relative distance along the curve; should be a value between 0 and 1 inclusive; - * if the value exceeds the boundaries it is truncated to them - * @return computed value along the curve - */ - private Vector3f getValueAlongCurve(float alongRatio) { - alongRatio = FastMath.clamp(alongRatio, 0, 1); - Vector3f result = new Vector3f(); - float probeLength = this.getLength() * alongRatio, length = 0; - for (BezierLine bezier : beziers) { - float edgeLength = bezier.getLength(); - if (length + edgeLength >= probeLength) { - float ratioAlongEdge = (probeLength - length) / edgeLength; - return bezier.getValueAlongCurve(ratioAlongEdge); - } - length += edgeLength; - } - return result; - } - - /** - * @return the length of the curve - */ - private float getLength() { - float result = 0; - for (BezierLine bezier : beziers) { - result += bezier.getLength(); - } - return result; - } - - /** - * The methods loads the bezier curve from the given structure. - * @param nurbStructure - * the structure containing a single curve definition - * @param materialIndex - * the index of this segment's material - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - private void loadBezierCurve(Structure nurbStructure, int materialIndex) throws BlenderFileException { - Pointer pBezierTriple = (Pointer) nurbStructure.getFieldValue("bezt"); - if (pBezierTriple.isNotNull()) { - int resolution = ((Number) nurbStructure.getFieldValue("resolu")).intValue(); - boolean cyclic = (((Number) nurbStructure.getFieldValue("flagu")).intValue() & 0x01) != 0; - boolean smooth = (((Number) nurbStructure.getFieldValue("flag")).intValue() & FLAG_SMOOTH) != 0; - - // creating the curve object - BezierCurve bezierCurve = new BezierCurve(0, pBezierTriple.fetchData(), 3, blenderContext.getBlenderKey().isFixUpAxis()); - List controlPoints = bezierCurve.getControlPoints(); - - if (cyclic) { - // copy the first three points at the end - for (int i = 0; i < 3; ++i) { - controlPoints.add(controlPoints.get(i)); - } - } - // removing the first and last handles - controlPoints.remove(0); - controlPoints.remove(controlPoints.size() - 1); - - // creating curve - Curve curve = new Curve(new Spline(SplineType.Bezier, controlPoints, 0, false), resolution); - - FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData(); - beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, cyclic)); - } - } - - /** - * This method loads the NURBS curve or surface. - * @param nurb - * the NURBS data structure - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - @SuppressWarnings("unchecked") - private void loadNurbSurface(Structure nurb, int materialIndex) throws BlenderFileException { - // loading the knots - List[] knots = new List[2]; - Pointer[] pKnots = new Pointer[] { (Pointer) nurb.getFieldValue("knotsu"), (Pointer) nurb.getFieldValue("knotsv") }; - for (int i = 0; i < knots.length; ++i) { - if (pKnots[i].isNotNull()) { - FileBlockHeader fileBlockHeader = blenderContext.getFileBlock(pKnots[i].getOldMemoryAddress()); - BlenderInputStream blenderInputStream = blenderContext.getInputStream(); - blenderInputStream.setPosition(fileBlockHeader.getBlockPosition()); - int knotsAmount = fileBlockHeader.getCount() * fileBlockHeader.getSize() / 4; - knots[i] = new ArrayList(knotsAmount); - for (int j = 0; j < knotsAmount; ++j) { - knots[i].add(Float.valueOf(blenderInputStream.readFloat())); - } - } - } - - // loading the flags and orders (basis functions degrees) - int flag = ((Number) nurb.getFieldValue("flag")).intValue(); - boolean smooth = (flag & FLAG_SMOOTH) != 0; - int flagU = ((Number) nurb.getFieldValue("flagu")).intValue(); - int flagV = ((Number) nurb.getFieldValue("flagv")).intValue(); - int orderU = ((Number) nurb.getFieldValue("orderu")).intValue(); - int orderV = ((Number) nurb.getFieldValue("orderv")).intValue(); - - // loading control points and their weights - int pntsU = ((Number) nurb.getFieldValue("pntsu")).intValue(); - int pntsV = ((Number) nurb.getFieldValue("pntsv")).intValue(); - List bPoints = ((Pointer) nurb.getFieldValue("bp")).fetchData(); - List> controlPoints = new ArrayList>(pntsV); - for (int i = 0; i < pntsV; ++i) { - List uControlPoints = new ArrayList(pntsU); - for (int j = 0; j < pntsU; ++j) { - DynamicArray vec = (DynamicArray) bPoints.get(j + i * pntsU).getFieldValue("vec"); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(2).floatValue(), -vec.get(1).floatValue(), vec.get(3).floatValue())); - } else { - uControlPoints.add(new Vector4f(vec.get(0).floatValue(), vec.get(1).floatValue(), vec.get(2).floatValue(), vec.get(3).floatValue())); - } - } - if ((flagU & 0x01) != 0) { - for (int k = 0; k < orderU - 1; ++k) { - uControlPoints.add(uControlPoints.get(k)); - } - } - controlPoints.add(uControlPoints); - } - if ((flagV & 0x01) != 0) { - for (int k = 0; k < orderV - 1; ++k) { - controlPoints.add(controlPoints.get(k)); - } - } - - int originalVerticesAmount = vertices.size(); - int resolu = ((Number) nurb.getFieldValue("resolu")).intValue(); - if (knots[1] == null) {// creating the NURB curve - Curve curve = new Curve(new Spline(controlPoints.get(0), knots[0]), resolu); - FloatBuffer vertsBuffer = (FloatBuffer) curve.getBuffer(Type.Position).getData(); - beziers.add(new BezierLine(BufferUtils.getVector3Array(vertsBuffer), materialIndex, smooth, false)); - } else {// creating the NURB surface - int resolv = ((Number) nurb.getFieldValue("resolv")).intValue(); - int uSegments = resolu * controlPoints.get(0).size() - 1; - int vSegments = resolv * controlPoints.size() - 1; - Surface nurbSurface = Surface.createNurbsSurface(controlPoints, knots, uSegments, vSegments, orderU, orderV, smooth); - - FloatBuffer vertsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Position).getData(); - vertices.addAll(Arrays.asList(BufferUtils.getVector3Array(vertsBuffer))); - FloatBuffer normalsBuffer = (FloatBuffer) nurbSurface.getBuffer(Type.Normal).getData(); - normals.addAll(Arrays.asList(BufferUtils.getVector3Array(normalsBuffer))); - - IndexBuffer indexBuffer = nurbSurface.getIndexBuffer(); - for (int i = 0; i < indexBuffer.size(); i += 3) { - int index1 = indexBuffer.get(i) + originalVerticesAmount; - int index2 = indexBuffer.get(i + 1) + originalVerticesAmount; - int index3 = indexBuffer.get(i + 2) + originalVerticesAmount; - faces.add(new Face(new Integer[] { index1, index2, index3 }, smooth, materialIndex, null, null, this)); - } - } - } - - /** - * The method loads the bevel object that should be applied to curve. It can either be another curve or a generated one - * based on the bevel generating parameters in blender. - * @param curveStructure - * the structure with the curve's data (the curve being loaded, NOT the bevel curve) - * @return the curve's bevel object - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - @SuppressWarnings("unchecked") - private CurvesTemporalMesh loadBevelObject(Structure curveStructure) throws BlenderFileException { - CurvesTemporalMesh bevelObject = null; - Pointer pBevelObject = (Pointer) curveStructure.getFieldValue("bevobj"); - boolean cyclic = false; - if (pBevelObject.isNotNull()) { - Structure bevelObjectStructure = pBevelObject.fetchData().get(0); - DynamicArray scaleArray = (DynamicArray) bevelObjectStructure.getFieldValue("size"); - Vector3f scale = blenderContext.getBlenderKey().isFixUpAxis() ? new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue()) : new Vector3f(scaleArray.get(0).floatValue(), scaleArray.get(2).floatValue(), scaleArray.get(1).floatValue()); - Pointer pBevelStructure = (Pointer) bevelObjectStructure.getFieldValue("data"); - Structure bevelStructure = pBevelStructure.fetchData().get(0); - bevelObject = new CurvesTemporalMesh(bevelStructure, scale, false, blenderContext); - - // transforming the bezier lines from plane XZ to plane YZ - for (BezierLine bl : bevelObject.beziers) { - for (Vector3f v : bl.vertices) { - // casting the bezier curve orthogonally on the plane XZ (making Y = 0) and then moving the plane XZ to ZY in a way that: - // -Z => +Y and +X => +Z and +Y => +X (but because casting would make Y = 0, then we simply set X = 0) - v.y = -v.z; - v.z = v.x; - v.x = 0; - } - - // bevel curves should not have repeated the first vertex at the end when they are cyclic (this is handled differently) - if (bl.isCyclic()) { - bl.removeLastVertex(); - } - } - } else { - fillCaps = false;// this option is inactive in blender when there is no bevel object applied - int bevResol = ((Number) curveStructure.getFieldValue("bevresol")).intValue(); - float extrude = ((Number) curveStructure.getFieldValue("ext1")).floatValue(); - float bevelDepth = ((Number) curveStructure.getFieldValue("ext2")).floatValue(); - float offset = ((Number) curveStructure.getFieldValue("offset", 0)).floatValue(); - if (offset != 0) { - // TODO: add support for offset parameter - LOGGER.warning("Offset parameter not yet supported."); - } - Curve bevelCurve = null; - if (bevelDepth > 0.0f) { - float handlerLength = bevelDepth / 2.0f; - cyclic = !isFront && !isBack; - List conrtolPoints = new ArrayList(); - - // blenders from 2.49 to 2.52 did not pay attention to fron and back faces - // so in order to draw the scene exactly as it is in different blender versions the blender version is checked here - // when neither fron and back face is selected all version behave the same and draw full bevel around the curve - if (cyclic || blenderContext.getBlenderVersion() < 253) { - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength)); - - conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth)); - - if (extrude > 0) { - conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth)); - } - - conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength)); - conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0)); - - if (cyclic) { - conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, handlerLength)); - - conrtolPoints.add(new Vector3f(0, extrude + handlerLength, bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude, bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude - handlerLength, bevelDepth)); - - if (extrude > 0) { - conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, bevelDepth)); - conrtolPoints.add(new Vector3f(0, -extrude, bevelDepth)); - conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, bevelDepth)); - } - - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, handlerLength)); - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); - } - } else { - if (extrude > 0) { - if (isBack) { - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, 0)); - conrtolPoints.add(new Vector3f(0, -extrude - bevelDepth, -handlerLength)); - - conrtolPoints.add(new Vector3f(0, -extrude - handlerLength, -bevelDepth)); - } - - conrtolPoints.add(new Vector3f(0, -extrude, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, -extrude + handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude - handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, extrude, -bevelDepth)); - - if (isFront) { - conrtolPoints.add(new Vector3f(0, extrude + handlerLength, -bevelDepth)); - - conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, -handlerLength)); - conrtolPoints.add(new Vector3f(0, extrude + bevelDepth, 0)); - } - } else { - if (isFront && isBack) { - conrtolPoints.add(new Vector3f(0, -bevelDepth, 0)); - conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength)); - - conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth)); - - conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength)); - conrtolPoints.add(new Vector3f(0, bevelDepth, 0)); - } else { - if (isBack) { - conrtolPoints.add(new Vector3f(0, -bevelDepth, 0)); - conrtolPoints.add(new Vector3f(0, -bevelDepth, -handlerLength)); - - conrtolPoints.add(new Vector3f(0, -handlerLength, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); - } else { - conrtolPoints.add(new Vector3f(0, 0, -bevelDepth)); - conrtolPoints.add(new Vector3f(0, handlerLength, -bevelDepth)); - - conrtolPoints.add(new Vector3f(0, bevelDepth, -handlerLength)); - conrtolPoints.add(new Vector3f(0, bevelDepth, 0)); - } - } - } - } - - bevelCurve = new Curve(new Spline(SplineType.Bezier, conrtolPoints, 0, false), bevResol); - } else if (extrude > 0.0f) { - Spline bevelSpline = new Spline(SplineType.Linear, new Vector3f[] { new Vector3f(0, extrude, 0), new Vector3f(0, -extrude, 0) }, 1, false); - bevelCurve = new Curve(bevelSpline, bevResol); - } - if (bevelCurve != null) { - bevelObject = new CurvesTemporalMesh(blenderContext); - FloatBuffer vertsBuffer = (FloatBuffer) bevelCurve.getBuffer(Type.Position).getData(); - Vector3f[] verts = BufferUtils.getVector3Array(vertsBuffer); - if (cyclic) {// get rid of the last vertex which is identical to the first one - verts = Arrays.copyOf(verts, verts.length - 1); - } - bevelObject.beziers.add(new BezierLine(verts, 0, false, cyclic)); - } - } - return bevelObject; - } - - private List getScaledBeziers() { - if (scale.equals(Vector3f.UNIT_XYZ)) { - return beziers; - } - List result = new ArrayList(); - for (BezierLine bezierLine : beziers) { - result.add(bezierLine.scale(scale)); - } - return result; - } - - /** - * This method applies bevel and taper objects to the curve. - * @param curve - * the curve we apply the objects to - * @param bevelObject - * the bevel object - * @param taperObject - * the taper object - * @param blenderContext - * the blender context - * @return a list of geometries representing the beveled and/or tapered curve - * @throws BlenderFileException - * an exception is thrown when problems with reading occur - */ - private CurvesTemporalMesh applyBevelAndTaper(CurvesTemporalMesh curve, CurvesTemporalMesh bevelObject, CurvesTemporalMesh taperObject, BlenderContext blenderContext) throws BlenderFileException { - List bevelBezierLines = bevelObject.getScaledBeziers(); - List curveLines = curve.beziers; - if (bevelBezierLines.size() == 0 || curveLines.size() == 0) { - return null; - } - - CurvesTemporalMesh result = new CurvesTemporalMesh(blenderContext); - for (BezierLine curveLine : curveLines) { - Vector3f[] curveLineVertices = curveLine.getVertices(bevelStart, bevelEnd); - - for (BezierLine bevelBezierLine : bevelBezierLines) { - CurvesTemporalMesh partResult = new CurvesTemporalMesh(blenderContext); - - Vector3f[] bevelLineVertices = bevelBezierLine.getVertices(); - List bevels = new ArrayList(); - - Vector3f[] bevelPoints = curvesHelper.transformToFirstLineOfBevelPoints(bevelLineVertices, curveLineVertices[0], curveLineVertices[1]); - bevels.add(bevelPoints); - for (int i = 1; i < curveLineVertices.length - 1; ++i) { - bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[i - 1], curveLineVertices[i], curveLineVertices[i + 1]); - bevels.add(bevelPoints); - } - bevelPoints = curvesHelper.transformBevel(bevelPoints, curveLineVertices[curveLineVertices.length - 2], curveLineVertices[curveLineVertices.length - 1], null); - bevels.add(bevelPoints); - - Vector3f subtractResult = new Vector3f(); - if (bevels.size() > 2) { - // changing the first and last bevel so that they are parallel to their neighbours (blender works this way) - // notice this implicates that the distances of every corresponding point in the two bevels must be identical and - // equal to the distance between the points on curve that define the bevel position - // so instead doing complicated rotations on each point we will simply properly translate each of them - int[][] pointIndexes = new int[][] { { 0, 1 }, { curveLineVertices.length - 1, curveLineVertices.length - 2 } }; - for (int[] indexes : pointIndexes) { - float distance = curveLineVertices[indexes[1]].subtract(curveLineVertices[indexes[0]], subtractResult).length(); - Vector3f[] bevel = bevels.get(indexes[0]); - Vector3f[] nextBevel = bevels.get(indexes[1]); - for (int i = 0; i < bevel.length; ++i) { - float d = bevel[i].subtract(nextBevel[i], subtractResult).length(); - subtractResult.normalizeLocal().multLocal(distance - d); - bevel[i].addLocal(subtractResult); - } - } - } - - if (taperObject != null) { - float curveLength = curveLine.getLength(), lengthAlongCurve = bevelStart; - for (int i = 0; i < curveLineVertices.length; ++i) { - if (i > 0) { - lengthAlongCurve += curveLineVertices[i].subtract(curveLineVertices[i - 1], subtractResult).length(); - } - float taperScale = -taperObject.getValueAlongCurve(lengthAlongCurve / curveLength).z * taperObject.scale.z; - if (taperScale != 1) { - this.applyScale(bevels.get(i), curveLineVertices[i], taperScale); - } - } - } - - // adding vertices to the part result - for (Vector3f[] bevel : bevels) { - for (Vector3f d : bevel) { - partResult.getVertices().add(d); - } - } - - // preparing faces for the part result (each face is a quad) - int bevelVertCount = bevelPoints.length; - for (int i = 0; i < bevels.size() - 1; ++i) { - for (int j = 0; j < bevelVertCount - 1; ++j) { - Integer[] indexes = new Integer[] { i * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j + 1, (i + 1) * bevelVertCount + j, i * bevelVertCount + j }; - partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult)); - partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult)); - } - if (bevelBezierLine.isCyclic()) { - int j = bevelVertCount - 1; - Integer[] indexes = new Integer[] { i * bevelVertCount, (i + 1) * bevelVertCount, (i + 1) * bevelVertCount + j, i * bevelVertCount + j }; - partResult.getFaces().add(new Face(indexes, curveLine.isSmooth(), curveLine.getMaterialNumber(), null, null, partResult)); - partResult.getEdges().add(new Edge(indexes[0], indexes[1], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[1], indexes[2], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[2], indexes[3], 0, true, partResult)); - partResult.getEdges().add(new Edge(indexes[3], indexes[0], 0, true, partResult)); - } - } - - partResult.generateNormals(); - - if (fillCaps) {// caps in blender behave as if they weren't affected by the smooth factor - // START CAP - Vector3f[] cap = bevels.get(0); - List capIndexes = new ArrayList(cap.length); - Vector3f capNormal = curveLineVertices[0].subtract(curveLineVertices[1]).normalizeLocal(); - for (int i = 0; i < cap.length; ++i) { - capIndexes.add(partResult.getVertices().size()); - partResult.getVertices().add(cap[i]); - partResult.getNormals().add(capNormal); - } - Collections.reverse(capIndexes);// the indexes ned to be reversed for the face to have fron face outside the beveled line - partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult)); - for (int i = 1; i < capIndexes.size(); ++i) { - partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult)); - } - - // END CAP - cap = bevels.get(bevels.size() - 1); - capIndexes.clear(); - capNormal = curveLineVertices[curveLineVertices.length - 1].subtract(curveLineVertices[curveLineVertices.length - 2]).normalizeLocal(); - for (int i = 0; i < cap.length; ++i) { - capIndexes.add(partResult.getVertices().size()); - partResult.getVertices().add(cap[i]); - partResult.getNormals().add(capNormal); - } - partResult.getFaces().add(new Face(capIndexes.toArray(new Integer[capIndexes.size()]), false, curveLine.getMaterialNumber(), null, null, partResult)); - for (int i = 1; i < capIndexes.size(); ++i) { - partResult.getEdges().add(new Edge(capIndexes.get(i - 1), capIndexes.get(i), 0, true, partResult)); - } - } - - result.append(partResult); - } - } - - return result; - } - - /** - * The method generates normals for the curve. If any normals were already stored they are discarded. - */ - private void generateNormals() { - Map normalMap = new TreeMap(); - for (Face face : faces) { - // the first 3 verts are enough here (all faces are triangles except for the caps, but those are fully flat anyway) - int index1 = face.getIndexes().get(0); - int index2 = face.getIndexes().get(1); - int index3 = face.getIndexes().get(2); - - Vector3f n = FastMath.computeNormal(vertices.get(index1), vertices.get(index2), vertices.get(index3)); - for (int index : face.getIndexes()) { - Vector3f normal = normalMap.get(index); - if (normal == null) { - normalMap.put(index, n.clone()); - } else { - normal.addLocal(n).normalizeLocal(); - } - } - } - - normals.clear(); - Collections.addAll(normals, new Vector3f[normalMap.size()]); - for (Entry entry : normalMap.entrySet()) { - normals.set(entry.getKey(), entry.getValue()); - } - } - - /** - * the method applies scale for the given bevel points. The points table is - * being modified so expect your result there. - * - * @param points - * the bevel points - * @param centerPoint - * the center point of the bevel - * @param scale - * the scale to be applied - */ - private void applyScale(Vector3f[] points, Vector3f centerPoint, float scale) { - Vector3f taperScaleVector = new Vector3f(); - for (Vector3f p : points) { - taperScaleVector.set(centerPoint).subtractLocal(p).multLocal(1 - scale); - p.addLocal(taperScaleVector); - } - } - - /** - * A helper class that represents a single bezier line. It consists of Edge's and allows to - * get a subline of a length of the line. - * - * @author Marcin Roguski (Kaelthas) - */ - public static class BezierLine { - /** The edges of the bezier line. */ - private Vector3f[] vertices; - /** The material number of the line. */ - private int materialNumber; - /** Indicates if the line is smooth of flat. */ - private boolean smooth; - /** The length of the line. */ - private float length; - /** Indicates if the current line is cyclic or not. */ - private boolean cyclic; - - public BezierLine(Vector3f[] vertices, int materialNumber, boolean smooth, boolean cyclik) { - this.vertices = vertices; - this.materialNumber = materialNumber; - this.smooth = smooth; - cyclic = cyclik; - this.recomputeLength(); - } - - public BezierLine scale(Vector3f scale) { - BezierLine result = new BezierLine(vertices, materialNumber, smooth, cyclic); - result.vertices = new Vector3f[vertices.length]; - for (int i = 0; i < vertices.length; ++i) { - result.vertices[i] = vertices[i].mult(scale); - } - result.recomputeLength(); - return result; - } - - public void removeLastVertex() { - Vector3f[] newVertices = new Vector3f[vertices.length - 1]; - for (int i = 0; i < vertices.length - 1; ++i) { - newVertices[i] = vertices[i]; - } - vertices = newVertices; - this.recomputeLength(); - } - - private void recomputeLength() { - length = 0; - for (int i = 1; i < vertices.length; ++i) { - length += vertices[i - 1].distance(vertices[i]); - } - if (cyclic) { - // if the first vertex is repeated at the end the distance will be = 0 so it won't affect the result, and if it is not repeated - // then it is necessary to add the length between the last and the first vertex - length += vertices[vertices.length - 1].distance(vertices[0]); - } - } - - public Vector3f[] getVertices() { - return this.getVertices(0, 1); - } - - public Vector3f[] getVertices(float startSlice, float endSlice) { - if (startSlice == 0 && endSlice == 1) { - return vertices; - } - List result = new ArrayList(); - float length = this.getLength(), temp = 0; - float startSliceLength = length * startSlice; - float endSliceLength = length * endSlice; - int index = 1; - - if (startSlice > 0) { - while (temp < startSliceLength) { - Vector3f v1 = vertices[index - 1]; - Vector3f v2 = vertices[index++]; - float edgeLength = v1.distance(v2); - temp += edgeLength; - if (temp == startSliceLength) { - result.add(v2); - } else if (temp > startSliceLength) { - result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2)); - } - } - } - - if (endSlice < 1) { - if (index == vertices.length) { - Vector3f v1 = vertices[vertices.length - 2]; - Vector3f v2 = vertices[vertices.length - 1]; - result.add(v1.subtract(v2).normalizeLocal().multLocal(length - endSliceLength).addLocal(v2)); - } else { - for (int i = index; i < vertices.length && temp < endSliceLength; ++i) { - Vector3f v1 = vertices[index - 1]; - Vector3f v2 = vertices[index++]; - temp += v1.distance(v2); - if (temp == endSliceLength) { - result.add(v2); - } else if (temp > endSliceLength) { - result.add(v1.subtract(v2).normalizeLocal().multLocal(temp - startSliceLength).addLocal(v2)); - } - } - } - } else { - result.addAll(Arrays.asList(Arrays.copyOfRange(vertices, index, vertices.length))); - } - - return result.toArray(new Vector3f[result.size()]); - } - - /** - * The method computes the value of a point at the certain relational distance from its beginning. - * @param alongRatio - * the relative distance along the curve; should be a value between 0 and 1 inclusive; - * if the value exceeds the boundaries it is truncated to them - * @return computed value along the curve - */ - public Vector3f getValueAlongCurve(float alongRatio) { - alongRatio = FastMath.clamp(alongRatio, 0, 1); - Vector3f result = new Vector3f(); - float probeLength = this.getLength() * alongRatio; - float length = 0; - for (int i = 1; i < vertices.length; ++i) { - float edgeLength = vertices[i].distance(vertices[i - 1]); - if (length + edgeLength > probeLength) { - float ratioAlongEdge = (probeLength - length) / edgeLength; - return FastMath.interpolateLinear(ratioAlongEdge, vertices[i - 1], vertices[i]); - } else if (length + edgeLength == probeLength) { - return vertices[i]; - } - length += edgeLength; - } - - return result; - } - - /** - * @return the material number of this bezier line - */ - public int getMaterialNumber() { - return materialNumber; - } - - /** - * @return indicates if the line is smooth of flat - */ - public boolean isSmooth() { - return smooth; - } - - /** - * @return the length of this bezier line - */ - public float getLength() { - return length; - } - - /** - * @return indicates if the current line is cyclic or not - */ - public boolean isCyclic() { - return cyclic; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java deleted file mode 100644 index 9deeb8fa77..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderFileException.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender.file; - -/** - * This exception is thrown when blend file data is somehow invalid. - * @author Marcin Roguski - */ -public class BlenderFileException extends Exception { - - private static final long serialVersionUID = 7573482836437866767L; - - /** - * Constructor. Creates an exception with no description. - */ - public BlenderFileException() { - // this constructor has no message - } - - /** - * Constructor. Creates an exception containing the given message. - * @param message - * the message describing the problem that occurred - */ - public BlenderFileException(String message) { - super(message); - } - - /** - * Constructor. Creates an exception that is based upon other thrown object. It contains the whole stacktrace then. - * @param throwable - * an exception/error that occurred - */ - public BlenderFileException(Throwable throwable) { - super(throwable); - } - - /** - * Constructor. Creates an exception with both a message and stacktrace. - * @param message - * the message describing the problem that occurred - * @param throwable - * an exception/error that occurred - */ - public BlenderFileException(String message, Throwable throwable) { - super(message, throwable); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java deleted file mode 100644 index 01d1037532..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/BlenderInputStream.java +++ /dev/null @@ -1,371 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender.file; - -import java.io.BufferedInputStream; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.logging.Logger; -import java.util.zip.GZIPInputStream; - -/** - * An input stream with random access to data. - * @author Marcin Roguski - */ -public class BlenderInputStream extends InputStream { - - private static final Logger LOGGER = Logger.getLogger(BlenderInputStream.class.getName()); - /** The default size of the blender buffer. */ - private static final int DEFAULT_BUFFER_SIZE = 1048576; // 1MB - /** - * Size of a pointer; all pointers in the file are stored in this format. '_' means 4 bytes and '-' means 8 bytes. - */ - private int pointerSize; - /** - * Type of byte ordering used; 'v' means little endian and 'V' means big endian. - */ - private char endianess; - /** Version of Blender the file was created in; '248' means version 2.48. */ - private String versionNumber; - /** The buffer we store the read data to. */ - protected byte[] cachedBuffer; - /** The total size of the stored data. */ - protected int size; - /** The current position of the read cursor. */ - protected int position; - - /** - * Constructor. The input stream is stored and used to read data. - * @param inputStream - * the stream we read data from - * @throws BlenderFileException - * this exception is thrown if the file header has some invalid data - */ - public BlenderInputStream(InputStream inputStream) throws BlenderFileException { - // the size value will canche while reading the file; the available() method cannot be counted on - try { - size = inputStream.available(); - } catch (IOException e) { - size = 0; - } - if (size <= 0) { - size = BlenderInputStream.DEFAULT_BUFFER_SIZE; - } - - // buffered input stream is used here for much faster file reading - BufferedInputStream bufferedInputStream; - if (inputStream instanceof BufferedInputStream) { - bufferedInputStream = (BufferedInputStream) inputStream; - } else { - bufferedInputStream = new BufferedInputStream(inputStream); - } - - try { - this.readStreamToCache(bufferedInputStream); - } catch (IOException e) { - throw new BlenderFileException("Problems occurred while caching the file!", e); - } finally { - try { - inputStream.close(); - } catch (IOException e) { - LOGGER.warning("Unable to close stream with blender file."); - } - } - - try { - this.readFileHeader(); - } catch (BlenderFileException e) {// the file might be packed, don't panic, try one more time ;) - this.decompressFile(); - position = 0; - this.readFileHeader(); - } - } - - /** - * This method reads the whole stream into a buffer. - * @param inputStream - * the stream to read the file data from - * @throws IOException - * an exception is thrown when data read from the stream is invalid or there are problems with i/o - * operations - */ - private void readStreamToCache(InputStream inputStream) throws IOException { - int data = inputStream.read(); - cachedBuffer = new byte[size]; - size = 0;// this will count the actual size - while (data != -1) { - if (size >= cachedBuffer.length) {// widen the cached array - byte[] newBuffer = new byte[cachedBuffer.length + (cachedBuffer.length >> 1)]; - System.arraycopy(cachedBuffer, 0, newBuffer, 0, cachedBuffer.length); - cachedBuffer = newBuffer; - } - cachedBuffer[size++] = (byte) data; - data = inputStream.read(); - } - } - - /** - * This method is used when the blender file is gzipped. It decompresses the data and stores it back into the - * cachedBuffer field. - */ - private void decompressFile() { - GZIPInputStream gis = null; - try { - gis = new GZIPInputStream(new ByteArrayInputStream(cachedBuffer)); - this.readStreamToCache(gis); - } catch (IOException e) { - throw new IllegalStateException("IO errors occurred where they should NOT! " + "The data is already buffered at this point!", e); - } finally { - try { - if (gis != null) { - gis.close(); - } - } catch (IOException e) { - LOGGER.warning(e.getMessage()); - } - } - } - - /** - * This method loads the header from the given stream during instance creation. - * @param inputStream - * the stream we read the header from - * @throws BlenderFileException - * this exception is thrown if the file header has some invalid data - */ - private void readFileHeader() throws BlenderFileException { - byte[] identifier = new byte[7]; - int bytesRead = this.readBytes(identifier); - if (bytesRead != 7) { - throw new BlenderFileException("Error reading header identifier. Only " + bytesRead + " bytes read and there should be 7!"); - } - String strIdentifier = new String(identifier); - if (!"BLENDER".equals(strIdentifier)) { - throw new BlenderFileException("Wrong file identifier: " + strIdentifier + "! Should be 'BLENDER'!"); - } - char pointerSizeSign = (char) this.readByte(); - if (pointerSizeSign == '-') { - pointerSize = 8; - } else if (pointerSizeSign == '_') { - pointerSize = 4; - } else { - throw new BlenderFileException("Invalid pointer size character! Should be '_' or '-' and there is: " + pointerSizeSign); - } - endianess = (char) this.readByte(); - if (endianess != 'v' && endianess != 'V') { - throw new BlenderFileException("Unknown endianess value! 'v' or 'V' expected and found: " + endianess); - } - byte[] versionNumber = new byte[3]; - bytesRead = this.readBytes(versionNumber); - if (bytesRead != 3) { - throw new BlenderFileException("Error reading version numberr. Only " + bytesRead + " bytes read and there should be 3!"); - } - this.versionNumber = new String(versionNumber); - } - - @Override - public int read() throws IOException { - return this.readByte(); - } - - /** - * This method reads 1 byte from the stream. - * It works just in the way the read method does. - * It just not throw an exception because at this moment the whole file - * is loaded into buffer, so no need for IOException to be thrown. - * @return a byte from the stream (1 bytes read) - */ - public int readByte() { - return cachedBuffer[position++] & 0xFF; - } - - /** - * This method reads a bytes number big enough to fill the table. - * It does not throw exceptions so it is for internal use only. - * @param bytes - * an array to be filled with data - * @return number of read bytes (a length of array actually) - */ - private int readBytes(byte[] bytes) { - for (int i = 0; i < bytes.length; ++i) { - bytes[i] = (byte) this.readByte(); - } - return bytes.length; - } - - /** - * This method reads 2-byte number from the stream. - * @return a number from the stream (2 bytes read) - */ - public int readShort() { - int part1 = this.readByte(); - int part2 = this.readByte(); - if (endianess == 'v') { - return (part2 << 8) + part1; - } else { - return (part1 << 8) + part2; - } - } - - /** - * This method reads 4-byte number from the stream. - * @return a number from the stream (4 bytes read) - */ - public int readInt() { - int part1 = this.readByte(); - int part2 = this.readByte(); - int part3 = this.readByte(); - int part4 = this.readByte(); - if (endianess == 'v') { - return (part4 << 24) + (part3 << 16) + (part2 << 8) + part1; - } else { - return (part1 << 24) + (part2 << 16) + (part3 << 8) + part4; - } - } - - /** - * This method reads 4-byte floating point number (float) from the stream. - * @return a number from the stream (4 bytes read) - */ - public float readFloat() { - int intValue = this.readInt(); - return Float.intBitsToFloat(intValue); - } - - /** - * This method reads 8-byte number from the stream. - * @return a number from the stream (8 bytes read) - */ - public long readLong() { - long part1 = this.readInt(); - long part2 = this.readInt(); - long result = -1; - if (endianess == 'v') { - result = part2 << 32 | part1; - } else { - result = part1 << 32 | part2; - } - return result; - } - - /** - * This method reads 8-byte floating point number (double) from the stream. - * @return a number from the stream (8 bytes read) - */ - public double readDouble() { - long longValue = this.readLong(); - return Double.longBitsToDouble(longValue); - } - - /** - * This method reads the pointer value. Depending on the pointer size defined in the header, the stream reads either - * 4 or 8 bytes of data. - * @return the pointer value - */ - public long readPointer() { - if (pointerSize == 4) { - return this.readInt(); - } - return this.readLong(); - } - - /** - * This method reads the string. It assumes the string is terminated with zero in the stream. - * @return the string read from the stream - */ - public String readString() { - StringBuilder stringBuilder = new StringBuilder(); - int data = this.readByte(); - while (data != 0) { - stringBuilder.append((char) data); - data = this.readByte(); - } - return stringBuilder.toString(); - } - - /** - * This method sets the current position of the read cursor. - * @param position - * the position of the read cursor - */ - public void setPosition(int position) { - this.position = position; - } - - /** - * This method returns the position of the read cursor. - * @return the position of the read cursor - */ - public int getPosition() { - return position; - } - - /** - * This method returns the blender version number where the file was created. - * @return blender version number - */ - public String getVersionNumber() { - return versionNumber; - } - - /** - * This method returns the size of the pointer. - * @return the size of the pointer - */ - public int getPointerSize() { - return pointerSize; - } - - /** - * This method aligns cursor position forward to a given amount of bytes. - * @param bytesAmount - * the byte amount to which we aligh the cursor - */ - public void alignPosition(int bytesAmount) { - if (bytesAmount <= 0) { - throw new IllegalArgumentException("Alignment byte number shoulf be positivbe!"); - } - long move = position % bytesAmount; - if (move > 0) { - position += bytesAmount - move; - } - } - - @Override - public void close() throws IOException { - // this method is unimplemented because some loaders (ie. TGALoader) tend close the stream given from the outside - // because the images can be stored directly in the blender file then this stream is properly positioned and given to the loader - // to read the image file, that is why we do not want it to be closed before the reading is done - // and anyway this stream is only a cached buffer, so it does not hold any open connection to anything - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java deleted file mode 100644 index 9fae86160f..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DnaBlockData.java +++ /dev/null @@ -1,203 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.blender.file; - -import com.jme3.scene.plugins.blender.BlenderContext; - -import java.util.HashMap; -import java.util.Map; - -/** - * The data block containing the description of the file. - * @author Marcin Roguski (Kaelthas) - */ -public class DnaBlockData { - - private static final int SDNA_ID = 'S' << 24 | 'D' << 16 | 'N' << 8 | 'A'; // SDNA - private static final int NAME_ID = 'N' << 24 | 'A' << 16 | 'M' << 8 | 'E'; // NAME - private static final int TYPE_ID = 'T' << 24 | 'Y' << 16 | 'P' << 8 | 'E'; // TYPE - private static final int TLEN_ID = 'T' << 24 | 'L' << 16 | 'E' << 8 | 'N'; // TLEN - private static final int STRC_ID = 'S' << 24 | 'T' << 16 | 'R' << 8 | 'C'; // STRC - /** Structures available inside the file. */ - private final Structure[] structures; - /** A map that helps finding a structure by type. */ - private final Map structuresMap; - - /** - * Constructor. Loads the block from the given stream during instance creation. - * @param inputStream - * the stream we read the block from - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is throw if the blend file is invalid or somehow corrupted - */ - public DnaBlockData(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { - int identifier; - - // reading 'SDNA' identifier - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); - - if (identifier != SDNA_ID) { - throw new BlenderFileException("Invalid identifier! '" + this.toString(SDNA_ID) + "' expected and found: " + this.toString(identifier)); - } - - // reading names - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); - if (identifier != NAME_ID) { - throw new BlenderFileException("Invalid identifier! '" + this.toString(NAME_ID) + "' expected and found: " + this.toString(identifier)); - } - int amount = inputStream.readInt(); - if (amount <= 0) { - throw new BlenderFileException("The names amount number should be positive!"); - } - String[] names = new String[amount]; - for (int i = 0; i < amount; ++i) { - names[i] = inputStream.readString(); - } - - // reading types - inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); - if (identifier != TYPE_ID) { - throw new BlenderFileException("Invalid identifier! '" + this.toString(TYPE_ID) + "' expected and found: " + this.toString(identifier)); - } - amount = inputStream.readInt(); - if (amount <= 0) { - throw new BlenderFileException("The types amount number should be positive!"); - } - String[] types = new String[amount]; - for (int i = 0; i < amount; ++i) { - types[i] = inputStream.readString(); - } - - // reading lengths - inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); - if (identifier != TLEN_ID) { - throw new BlenderFileException("Invalid identifier! '" + this.toString(TLEN_ID) + "' expected and found: " + this.toString(identifier)); - } - int[] lengths = new int[amount];// theamount is the same as int types - for (int i = 0; i < amount; ++i) { - lengths[i] = inputStream.readShort(); - } - - // reading structures - inputStream.alignPosition(4); - identifier = inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte(); - if (identifier != STRC_ID) { - throw new BlenderFileException("Invalid identifier! '" + this.toString(STRC_ID) + "' expected and found: " + this.toString(identifier)); - } - amount = inputStream.readInt(); - if (amount <= 0) { - throw new BlenderFileException("The structures amount number should be positive!"); - } - structures = new Structure[amount]; - structuresMap = new HashMap(amount); - for (int i = 0; i < amount; ++i) { - structures[i] = new Structure(inputStream, names, types, blenderContext); - if (structuresMap.containsKey(structures[i].getType())) { - throw new BlenderFileException("Blend file seems to be corrupted! The type " + structures[i].getType() + " is defined twice!"); - } - structuresMap.put(structures[i].getType(), structures[i]); - } - } - - /** - * This method returns the amount of the structures. - * @return the amount of the structures - */ - public int getStructuresCount() { - return structures.length; - } - - /** - * This method returns the structure of the given index. - * @param index - * the index of the structure - * @return the structure of the given index - */ - public Structure getStructure(int index) { - try { - return (Structure) structures[index].clone(); - } catch (CloneNotSupportedException e) { - throw new IllegalStateException("Structure should be clonable!!!", e); - } - } - - /** - * This method returns a structure of the given name. If the name does not exists then null is returned. - * @param name - * the name of the structure - * @return the required structure or null if the given name is inapropriate - */ - public Structure getStructure(String name) { - try { - return (Structure) structuresMap.get(name).clone(); - } catch (CloneNotSupportedException e) { - throw new IllegalStateException(e.getMessage(), e); - } - } - - /** - * This method indicates if the structure of the given name exists. - * @param name - * the name of the structure - * @return true if the structure exists and false otherwise - */ - public boolean hasStructure(String name) { - return structuresMap.containsKey(name); - } - - /** - * This method converts the given identifier code to string. - * @param code - * the code that is to be converted - * @return the string value of the identifier - */ - private String toString(int code) { - char c1 = (char) ((code & 0xFF000000) >> 24); - char c2 = (char) ((code & 0xFF0000) >> 16); - char c3 = (char) ((code & 0xFF00) >> 8); - char c4 = (char) (code & 0xFF); - return String.valueOf(c1) + c2 + c3 + c4; - } - - @Override - public String toString() { - StringBuilder stringBuilder = new StringBuilder("=============== ").append(SDNA_ID).append('\n'); - for (Structure structure : structures) { - stringBuilder.append(structure.toString()).append('\n'); - } - return stringBuilder.append("===============").toString(); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java deleted file mode 100644 index 748757ab05..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/DynamicArray.java +++ /dev/null @@ -1,135 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.blender.file; - -/** - * An array that can be dynamically modified - * @author Marcin Roguski - * @param - * the type of stored data in the array - */ -public class DynamicArray implements Cloneable { - - /** An array object that holds the required data. */ - private T[] array; - /** - * This table holds the sizes of dimensions of the dynamic table. Its length specifies the table dimension or a - * pointer level. For example: if tableSizes.length == 3 then it either specifies a dynamic table of fixed lengths: - * dynTable[a][b][c], where a,b,c are stored in the tableSizes table. - */ - private int[] tableSizes; - - /** - * Constructor. Builds an empty array of the specified sizes. - * @param tableSizes - * the sizes of the table - * @throws IllegalArgumentException - * an exception is thrown if one of the sizes is not a positive number - */ - public DynamicArray(int[] tableSizes, T[] data) { - this.tableSizes = tableSizes; - int totalSize = 1; - for (int size : tableSizes) { - if (size <= 0) { - throw new IllegalArgumentException("The size of the table must be positive!"); - } - totalSize *= size; - } - if (totalSize != data.length) { - throw new IllegalArgumentException("The size of the table does not match the size of the given data!"); - } - this.array = data; - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - /** - * This method returns a value on the specified position. The dimension of the table is not taken into - * consideration. - * @param position - * the position of the data - * @return required data - */ - public T get(int position) { - return array[position]; - } - - /** - * This method returns a value on the specified position in multidimensional array. Be careful not to exceed the - * table boundaries. Check the table's dimension first. - * @param position - * the position of the data indices of data position - * @return required data required data - */ - public T get(int... position) { - if (position.length != tableSizes.length) { - throw new ArrayIndexOutOfBoundsException("The table accepts " + tableSizes.length + " indexing number(s)!"); - } - int index = 0; - for (int i = 0; i < position.length - 1; ++i) { - index += position[i] * tableSizes[i + 1]; - } - index += position[position.length - 1]; - return array[index]; - } - - /** - * This method returns the total amount of data stored in the array. - * @return the total amount of data stored in the array - */ - public int getTotalSize() { - return array.length; - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - if (array instanceof Character[]) {// in case of character array we convert it to String - for (int i = 0; i < array.length && (Character) array[i] != '\0'; ++i) {// strings are terminater with '0' - result.append(array[i]); - } - } else { - result.append('['); - for (int i = 0; i < array.length; ++i) { - result.append(array[i].toString()); - if (i + 1 < array.length) { - result.append(','); - } - } - result.append(']'); - } - return result.toString(); - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java deleted file mode 100644 index 88c33467fd..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Field.java +++ /dev/null @@ -1,327 +0,0 @@ -package com.jme3.scene.plugins.blender.file; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure.DataType; - -import java.util.ArrayList; -import java.util.List; - -/** - * This class represents a single field in the structure. It can be either a primitive type or a table or a reference to - * another structure. - * @author Marcin Roguski - */ -/* package */ -class Field implements Cloneable { - - private static final int NAME_LENGTH = 24; - private static final int TYPE_LENGTH = 16; - /** The blender context. */ - public BlenderContext blenderContext; - /** The type of the field. */ - public String type; - /** The name of the field. */ - public String name; - /** The value of the field. Filled during data reading. */ - public Object value; - /** This variable indicates the level of the pointer. */ - public int pointerLevel; - /** - * This variable determines the sizes of the array. If the value is null then the field is not an array. - */ - public int[] tableSizes; - /** This variable indicates if the field is a function pointer. */ - public boolean function; - - /** - * Constructor. Saves the field data and parses its name. - * @param name - * the name of the field - * @param type - * the type of the field - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown if the names contain errors - */ - public Field(String name, String type, BlenderContext blenderContext) throws BlenderFileException { - this.type = type; - this.blenderContext = blenderContext; - this.parseField(new StringBuilder(name)); - } - - /** - * Copy constructor. Used in clone method. Copying is not full. The value in the new object is not set so that we - * have a clean empty copy of the field to fill with data. - * @param field - * the object that we copy - */ - private Field(Field field) { - type = field.type; - name = field.name; - blenderContext = field.blenderContext; - pointerLevel = field.pointerLevel; - if (field.tableSizes != null) { - tableSizes = field.tableSizes.clone(); - } - function = field.function; - } - - @Override - public Object clone() throws CloneNotSupportedException { - return new Field(this); - } - - /** - * This method fills the field with data read from the input stream. - * @param blenderInputStream - * the stream we read data from - * @throws BlenderFileException - * an exception is thrown when the blend file is somehow invalid or corrupted - */ - public void fill(BlenderInputStream blenderInputStream) throws BlenderFileException { - int dataToRead = 1; - if (tableSizes != null && tableSizes.length > 0) { - for (int size : tableSizes) { - if (size <= 0) { - throw new BlenderFileException("The field " + name + " has invalid table size: " + size); - } - dataToRead *= size; - } - } - DataType dataType = pointerLevel == 0 ? DataType.getDataType(type, blenderContext) : DataType.POINTER; - switch (dataType) { - case POINTER: - if (dataToRead == 1) { - Pointer pointer = new Pointer(pointerLevel, function, blenderContext); - pointer.fill(blenderInputStream); - value = pointer; - } else { - Pointer[] data = new Pointer[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - Pointer pointer = new Pointer(pointerLevel, function, blenderContext); - pointer.fill(blenderInputStream); - data[i] = pointer; - } - value = new DynamicArray(tableSizes, data); - } - break; - case CHARACTER: - // character is also stored as a number, because sometimes the new blender version uses - // other number type instead of character as a field type - // and characters are very often used as byte number stores instead of real chars - if (dataToRead == 1) { - value = Byte.valueOf((byte) blenderInputStream.readByte()); - } else { - Character[] data = new Character[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Character.valueOf((char) blenderInputStream.readByte()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case SHORT: - if (dataToRead == 1) { - value = Integer.valueOf(blenderInputStream.readShort()); - } else { - Number[] data = new Number[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Integer.valueOf(blenderInputStream.readShort()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case INTEGER: - if (dataToRead == 1) { - value = Integer.valueOf(blenderInputStream.readInt()); - } else { - Number[] data = new Number[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Integer.valueOf(blenderInputStream.readInt()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case LONG: - if (dataToRead == 1) { - value = Long.valueOf(blenderInputStream.readLong()); - } else { - Number[] data = new Number[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Long.valueOf(blenderInputStream.readLong()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case FLOAT: - if (dataToRead == 1) { - value = Float.valueOf(blenderInputStream.readFloat()); - } else { - Number[] data = new Number[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Float.valueOf(blenderInputStream.readFloat()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case DOUBLE: - if (dataToRead == 1) { - value = Double.valueOf(blenderInputStream.readDouble()); - } else { - Number[] data = new Number[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - data[i] = Double.valueOf(blenderInputStream.readDouble()); - } - value = new DynamicArray(tableSizes, data); - } - break; - case VOID: - break; - case STRUCTURE: - if (dataToRead == 1) { - Structure structure = blenderContext.getDnaBlockData().getStructure(type); - structure.fill(blenderContext.getInputStream()); - value = structure; - } else { - Structure[] data = new Structure[dataToRead]; - for (int i = 0; i < dataToRead; ++i) { - Structure structure = blenderContext.getDnaBlockData().getStructure(type); - structure.fill(blenderContext.getInputStream()); - data[i] = structure; - } - value = new DynamicArray(tableSizes, data); - } - break; - default: - throw new IllegalStateException("Unimplemented filling of type: " + type); - } - } - - /** - * This method parses the field name to determine how the field should be used. - * @param nameBuilder - * the name of the field (given as StringBuilder) - * @throws BlenderFileException - * this exception is thrown if the names contain errors - */ - private void parseField(StringBuilder nameBuilder) throws BlenderFileException { - this.removeWhitespaces(nameBuilder); - // veryfying if the name is a pointer - int pointerIndex = nameBuilder.indexOf("*"); - while (pointerIndex >= 0) { - ++pointerLevel; - nameBuilder.deleteCharAt(pointerIndex); - pointerIndex = nameBuilder.indexOf("*"); - } - // veryfying if the name is a function pointer - if (nameBuilder.indexOf("(") >= 0) { - function = true; - this.removeCharacter(nameBuilder, '('); - this.removeCharacter(nameBuilder, ')'); - } else { - // veryfying if the name is a table - int tableStartIndex = 0; - List lengths = new ArrayList(3);// 3 dimensions will be enough in most cases - do { - tableStartIndex = nameBuilder.indexOf("["); - if (tableStartIndex > 0) { - int tableStopIndex = nameBuilder.indexOf("]"); - if (tableStopIndex < 0) { - throw new BlenderFileException("Invalid structure name: " + name); - } - try { - lengths.add(Integer.valueOf(nameBuilder.substring(tableStartIndex + 1, tableStopIndex))); - } catch (NumberFormatException e) { - throw new BlenderFileException("Invalid structure name caused by invalid table length: " + name, e); - } - nameBuilder.delete(tableStartIndex, tableStopIndex + 1); - } - } while (tableStartIndex > 0); - if (!lengths.isEmpty()) { - tableSizes = new int[lengths.size()]; - for (int i = 0; i < tableSizes.length; ++i) { - tableSizes[i] = lengths.get(i).intValue(); - } - } - } - name = nameBuilder.toString(); - } - - /** - * This method removes the required character from the text. - * @param text - * the text we remove characters from - * @param toRemove - * the character to be removed - */ - private void removeCharacter(StringBuilder text, char toRemove) { - for (int i = 0; i < text.length(); ++i) { - if (text.charAt(i) == toRemove) { - text.deleteCharAt(i); - --i; - } - } - } - - /** - * This method removes all whitespace from the text. - * @param text - * the text we remove whitespace from - */ - private void removeWhitespaces(StringBuilder text) { - for (int i = 0; i < text.length(); ++i) { - if (Character.isWhitespace(text.charAt(i))) { - text.deleteCharAt(i); - --i; - } - } - } - - /** - * This method builds the full name of the field (with function, pointer and table indications). - * @return the full name of the field - */ - /*package*/ String getFullName() { - StringBuilder result = new StringBuilder(); - if (function) { - result.append('('); - } - for (int i = 0; i < pointerLevel; ++i) { - result.append('*'); - } - result.append(name); - if (tableSizes != null) { - for (int i = 0; i < tableSizes.length; ++i) { - result.append('[').append(tableSizes[i]).append(']'); - } - } - if (function) { - result.append(")()"); - } - return result.toString(); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(this.getFullName()); - - // insert appropriate number of spaces to format the output corrently - int nameLength = result.length(); - result.append(' ');// at least one space is a must - for (int i = 1; i < NAME_LENGTH - nameLength; ++i) {// we start from i=1 because one space is already added - result.append(' '); - } - result.append(type); - nameLength = result.length(); - for (int i = 0; i < NAME_LENGTH + TYPE_LENGTH - nameLength; ++i) { - result.append(' '); - } - if (value instanceof Character) { - result.append(" = ").append((int) ((Character) value).charValue()); - } else { - result.append(" = ").append(value != null ? value.toString() : "null"); - } - return result.toString(); - } -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java deleted file mode 100644 index 6a5222a9cf..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/FileBlockHeader.java +++ /dev/null @@ -1,213 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.file; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.plugins.blender.BlenderContext; - -/** - * A class that holds the header data of a file block. The file block itself is not implemented. This class holds its - * start position in the stream and using this the structure can fill itself with the proper data. - * @author Marcin Roguski - */ -public class FileBlockHeader { - private static final Logger LOGGER = Logger.getLogger(FileBlockHeader.class.getName()); - - /** Identifier of the file-block [4 bytes]. */ - private BlockCode code; - /** Total length of the data after the file-block-header [4 bytes]. */ - private int size; - /** - * Memory address the structure was located when written to disk [4 or 8 bytes (defined in file header as a pointer - * size)]. - */ - private long oldMemoryAddress; - /** Index of the SDNA structure [4 bytes]. */ - private int sdnaIndex; - /** Number of structure located in this file-block [4 bytes]. */ - private int count; - /** Start position of the block's data in the stream. */ - private int blockPosition; - - /** - * Constructor. Loads the block header from the given stream during instance creation. - * @param inputStream - * the stream we read the block header from - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the pointer size is neither 4 nor 8 - */ - public FileBlockHeader(BlenderInputStream inputStream, BlenderContext blenderContext) throws BlenderFileException { - inputStream.alignPosition(4); - code = BlockCode.valueOf(inputStream.readByte() << 24 | inputStream.readByte() << 16 | inputStream.readByte() << 8 | inputStream.readByte()); - size = inputStream.readInt(); - oldMemoryAddress = inputStream.readPointer(); - sdnaIndex = inputStream.readInt(); - count = inputStream.readInt(); - blockPosition = inputStream.getPosition(); - if (BlockCode.BLOCK_DNA1 == code) { - blenderContext.setBlockData(new DnaBlockData(inputStream, blenderContext)); - } else { - inputStream.setPosition(blockPosition + size); - blenderContext.addFileBlockHeader(Long.valueOf(oldMemoryAddress), this); - } - } - - /** - * This method returns the structure described by the header filled with appropriate data. - * @param blenderContext - * the blender context - * @return structure filled with data - * @throws BlenderFileException - */ - public Structure getStructure(BlenderContext blenderContext) throws BlenderFileException { - blenderContext.getInputStream().setPosition(blockPosition); - Structure structure = blenderContext.getDnaBlockData().getStructure(sdnaIndex); - structure.fill(blenderContext.getInputStream()); - return structure; - } - - /** - * This method returns the code of this data block. - * @return the code of this data block - */ - public BlockCode getCode() { - return code; - } - - /** - * This method returns the size of the data stored in this block. - * @return the size of the data stored in this block - */ - public int getSize() { - return size; - } - - /** - * This method returns the sdna index. - * @return the sdna index - */ - public int getSdnaIndex() { - return sdnaIndex; - } - - /** - * This data returns the number of structure stored in the data block after this header. - * @return the number of structure stored in the data block after this header - */ - public int getCount() { - return count; - } - - /** - * This method returns the start position of the data block in the blend file stream. - * @return the start position of the data block - */ - public int getBlockPosition() { - return blockPosition; - } - - /** - * This method indicates if the block is the last block in the file. - * @return true if this block is the last one in the file nad false otherwise - */ - public boolean isLastBlock() { - return BlockCode.BLOCK_ENDB == code; - } - - /** - * This method indicates if the block is the SDNA block. - * @return true if this block is the SDNA block and false otherwise - */ - public boolean isDnaBlock() { - return BlockCode.BLOCK_DNA1 == code; - } - - @Override - public String toString() { - return "FILE BLOCK HEADER [" + code.toString() + " : " + size + " : " + oldMemoryAddress + " : " + sdnaIndex + " : " + count + "]"; - } - - public static enum BlockCode { - BLOCK_ME00('M' << 24 | 'E' << 16), // mesh - BLOCK_CA00('C' << 24 | 'A' << 16), // camera - BLOCK_LA00('L' << 24 | 'A' << 16), // lamp - BLOCK_OB00('O' << 24 | 'B' << 16), // object - BLOCK_MA00('M' << 24 | 'A' << 16), // material - BLOCK_SC00('S' << 24 | 'C' << 16), // scene - BLOCK_WO00('W' << 24 | 'O' << 16), // world - BLOCK_TX00('T' << 24 | 'X' << 16), // texture - BLOCK_IP00('I' << 24 | 'P' << 16), // ipo - BLOCK_AC00('A' << 24 | 'C' << 16), // action - BLOCK_IM00('I' << 24 | 'M' << 16), // image - BLOCK_TE00('T' << 24 | 'E' << 16), - BLOCK_WM00('W' << 24 | 'M' << 16), - BLOCK_SR00('S' << 24 | 'R' << 16), - BLOCK_SN00('S' << 24 | 'N' << 16), - BLOCK_BR00('B' << 24 | 'R' << 16), - BLOCK_LS00('L' << 24 | 'S' << 16), - BLOCK_GR00('G' << 24 | 'R' << 16), - BLOCK_AR00('A' << 24 | 'R' << 16), - BLOCK_GLOB('G' << 24 | 'L' << 16 | 'O' << 8 | 'B'), - BLOCK_REND('R' << 24 | 'E' << 16 | 'N' << 8 | 'D'), - BLOCK_DATA('D' << 24 | 'A' << 16 | 'T' << 8 | 'A'), - BLOCK_DNA1('D' << 24 | 'N' << 16 | 'A' << 8 | '1'), - BLOCK_ENDB('E' << 24 | 'N' << 16 | 'D' << 8 | 'B'), - BLOCK_TEST('T' << 24 | 'E' << 16 | 'S' << 8 | 'T'), - BLOCK_UNKN(0); - - private int code; - - private BlockCode(int code) { - this.code = code; - } - - public static BlockCode valueOf(int code) { - for (BlockCode blockCode : BlockCode.values()) { - if (blockCode.code == code) { - return blockCode; - } - } - byte[] codeBytes = new byte[] { (byte) (code >> 24 & 0xFF), (byte) (code >> 16 & 0xFF), (byte) (code >> 8 & 0xFF), (byte) (code & 0xFF) }; - for (int i = 0; i < codeBytes.length; ++i) { - if (codeBytes[i] == 0) { - codeBytes[i] = '0'; - } - } - LOGGER.log(Level.WARNING, "Unknown block header: {0}", new String(codeBytes)); - return BLOCK_UNKN; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java deleted file mode 100644 index c5a662325f..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Pointer.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.file; - -import java.util.ArrayList; -import java.util.List; - -import com.jme3.scene.plugins.blender.BlenderContext; - -/** - * A class that represents a pointer of any level that can be stored in the file. - * @author Marcin Roguski - */ -public class Pointer { - - /** The blender context. */ - private BlenderContext blenderContext; - /** The level of the pointer. */ - private int pointerLevel; - /** The address in file it points to. */ - private long oldMemoryAddress; - /** This variable indicates if the field is a function pointer. */ - public boolean function; - - /** - * Constructr. Stores the basic data about the pointer. - * @param pointerLevel - * the level of the pointer - * @param function - * this variable indicates if the field is a function pointer - * @param blenderContext - * the repository f data; used in fetching the value that the pointer points - */ - public Pointer(int pointerLevel, boolean function, BlenderContext blenderContext) { - this.pointerLevel = pointerLevel; - this.function = function; - this.blenderContext = blenderContext; - } - - /** - * This method fills the pointer with its address value (it doesn't get the actual data yet. Use the 'fetch' method - * for this. - * @param inputStream - * the stream we read the pointer value from - */ - public void fill(BlenderInputStream inputStream) { - oldMemoryAddress = inputStream.readPointer(); - } - - /** - * This method fetches the data stored under the given address. - * @return the data read from the file - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - public List fetchData() throws BlenderFileException { - if (oldMemoryAddress == 0) { - throw new NullPointerException("The pointer points to nothing!"); - } - List structures = null; - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(oldMemoryAddress); - if (dataFileBlock == null) { - throw new BlenderFileException("No data stored for address: " + oldMemoryAddress + ". Make sure you did not open the newer blender file with older blender version."); - } - BlenderInputStream inputStream = blenderContext.getInputStream(); - if (pointerLevel > 1) { - int pointersAmount = dataFileBlock.getSize() / inputStream.getPointerSize() * dataFileBlock.getCount(); - for (int i = 0; i < pointersAmount; ++i) { - inputStream.setPosition(dataFileBlock.getBlockPosition() + inputStream.getPointerSize() * i); - long oldMemoryAddress = inputStream.readPointer(); - if (oldMemoryAddress != 0L) { - Pointer p = new Pointer(pointerLevel - 1, function, blenderContext); - p.oldMemoryAddress = oldMemoryAddress; - if (structures == null) { - structures = p.fetchData(); - } else { - structures.addAll(p.fetchData()); - } - } else { - // it is necessary to put null's if the pointer is null, ie. in materials array that is attached to the mesh, the index - // of the material is important, that is why we need null's to indicate that some materials' slots are empty - if (structures == null) { - structures = new ArrayList(); - } - structures.add(null); - } - } - } else { - inputStream.setPosition(dataFileBlock.getBlockPosition()); - structures = new ArrayList(dataFileBlock.getCount()); - for (int i = 0; i < dataFileBlock.getCount(); ++i) { - Structure structure = blenderContext.getDnaBlockData().getStructure(dataFileBlock.getSdnaIndex()); - structure.fill(blenderContext.getInputStream()); - structures.add(structure); - } - return structures; - } - return structures; - } - - /** - * This method indicates if this pointer points to a function. - * @return true if this is a function pointer and false otherwise - */ - public boolean isFunction() { - return function; - } - - /** - * This method indicates if this is a null-pointer or not. - * @return true if the pointer is null and false otherwise - */ - public boolean isNull() { - return oldMemoryAddress == 0; - } - - /** - * This method indicates if this is a null-pointer or not. - * @return true if the pointer is not null and false otherwise - */ - public boolean isNotNull() { - return oldMemoryAddress != 0; - } - - /** - * This method returns the old memory address of the structure pointed by the pointer. - * @return the old memory address of the structure pointed by the pointer - */ - public long getOldMemoryAddress() { - return oldMemoryAddress; - } - - @Override - public String toString() { - return oldMemoryAddress == 0 ? "{$null$}" : "{$" + oldMemoryAddress + "$}"; - } - - @Override - public int hashCode() { - return 31 + (int) (oldMemoryAddress ^ oldMemoryAddress >>> 32); - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (this.getClass() != obj.getClass()) { - return false; - } - Pointer other = (Pointer) obj; - if (oldMemoryAddress != other.oldMemoryAddress) { - return false; - } - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java deleted file mode 100644 index fac6597c5e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/file/Structure.java +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.blender.file; - -import java.util.Collection; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import com.jme3.scene.plugins.blender.BlenderContext; - -/** - * A class representing a single structure in the file. - * @author Marcin Roguski - */ -public class Structure implements Cloneable { - - /** The address of the block that fills the structure. */ - private transient Long oldMemoryAddress; - /** The type of the structure. */ - private String type; - /** - * The fields of the structure. Each field consists of a pair: name-type. - */ - private Field[] fields; - - /** - * Constructor that copies the data of the structure. - * @param structure - * the structure to copy. - * @throws CloneNotSupportedException - * this exception should never be thrown - */ - private Structure(Structure structure) throws CloneNotSupportedException { - type = structure.type; - fields = new Field[structure.fields.length]; - for (int i = 0; i < fields.length; ++i) { - fields[i] = (Field) structure.fields[i].clone(); - } - oldMemoryAddress = structure.oldMemoryAddress; - } - - /** - * Constructor. Loads the structure from the given stream during instance creation. - * @param inputStream - * the stream we read the structure from - * @param names - * the names from which the name of structure and its fields will be taken - * @param types - * the names of types for the structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception occurs if the amount of fields, defined in the file, is negative - */ - public Structure(BlenderInputStream inputStream, String[] names, String[] types, BlenderContext blenderContext) throws BlenderFileException { - int nameIndex = inputStream.readShort(); - type = types[nameIndex]; - int fieldsAmount = inputStream.readShort(); - if (fieldsAmount < 0) { - throw new BlenderFileException("The amount of fields of " + type + " structure cannot be negative!"); - } - if (fieldsAmount > 0) { - fields = new Field[fieldsAmount]; - for (int i = 0; i < fieldsAmount; ++i) { - int typeIndex = inputStream.readShort(); - nameIndex = inputStream.readShort(); - fields[i] = new Field(names[nameIndex], types[typeIndex], blenderContext); - } - } - oldMemoryAddress = Long.valueOf(-1L); - } - - /** - * This method fills the structure with data. - * @param inputStream - * the stream we read data from, its read cursor should be placed at the start position of the data for the - * structure - * @throws BlenderFileException - * an exception is thrown when the blend file is somehow invalid or corrupted - */ - public void fill(BlenderInputStream inputStream) throws BlenderFileException { - int position = inputStream.getPosition(); - inputStream.setPosition(position - 8 - inputStream.getPointerSize()); - oldMemoryAddress = Long.valueOf(inputStream.readPointer()); - inputStream.setPosition(position); - for (Field field : fields) { - field.fill(inputStream); - } - } - - /** - * This method returns the value of the filed with a given name. - * @param fieldName - * the name of the field - * @return the value of the field or null if no field with a given name is found - */ - public Object getFieldValue(String fieldName) { - return this.getFieldValue(fieldName, null); - } - - /** - * This method returns the value of the filed with a given name. - * @param fieldName - * the name of the field - * @param defaultValue - * the value that is being returned when no field of a given name is found - * @return the value of the field or the given default value if no field with a given name is found - */ - public Object getFieldValue(String fieldName, Object defaultValue) { - for (Field field : fields) { - if (field.name.equalsIgnoreCase(fieldName)) { - return field.value; - } - } - return defaultValue; - } - - /** - * This method returns the value of the filed with a given name. The structure is considered to have flat fields - * only (no substructures). - * @param fieldName - * the name of the field - * @return the value of the field or null if no field with a given name is found - */ - public Object getFlatFieldValue(String fieldName) { - for (Field field : fields) { - Object value = field.value; - if (field.name.equalsIgnoreCase(fieldName)) { - return value; - } else if (value instanceof Structure) { - value = ((Structure) value).getFlatFieldValue(fieldName); - if (value != null) {// we can compare references here, since we use one static object as a NULL field value - return value; - } - } - } - return null; - } - - /** - * This method should be used on structures that are of a 'ListBase' type. It creates a List of structures that are - * held by this structure within the blend file. - * @return a list of filled structures - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - * @throws IllegalArgumentException - * this exception is thrown if the type of the structure is not 'ListBase' - */ - public List evaluateListBase() throws BlenderFileException { - if (!"ListBase".equals(type)) { - throw new IllegalStateException("This structure is not of type: 'ListBase'"); - } - Pointer first = (Pointer) this.getFieldValue("first"); - Pointer last = (Pointer) this.getFieldValue("last"); - long currentAddress = 0; - long lastAddress = last.getOldMemoryAddress(); - List result = new LinkedList(); - while (currentAddress != lastAddress) { - currentAddress = first.getOldMemoryAddress(); - Structure structure = first.fetchData().get(0); - result.add(structure); - first = (Pointer) structure.getFlatFieldValue("next"); - } - return result; - } - - /** - * This method returns the type of the structure. - * @return the type of the structure - */ - public String getType() { - return type; - } - - /** - * This method returns the amount of fields for the current structure. - * @return the amount of fields for the current structure - */ - public int getFieldsAmount() { - return fields.length; - } - - /** - * This method returns the full field name of the given index. - * @param fieldIndex - * the index of the field - * @return the full field name of the given index - */ - public String getFieldFullName(int fieldIndex) { - return fields[fieldIndex].getFullName(); - } - - /** - * This method returns the field type of the given index. - * @param fieldIndex - * the index of the field - * @return the field type of the given index - */ - public String getFieldType(int fieldIndex) { - return fields[fieldIndex].type; - } - - /** - * This method returns the address of the structure. The structure should be filled with data otherwise an exception - * is thrown. - * @return the address of the feature stored in this structure - */ - public Long getOldMemoryAddress() { - if (oldMemoryAddress.longValue() == -1L) { - throw new IllegalStateException("Call the 'fill' method and fill the structure with data first!"); - } - return oldMemoryAddress; - } - - /** - * This method returns the name of the structure. If the structure has an ID field then the name is returned. - * Otherwise the name does not exists and the method returns null. - * @return the name of the structure read from the ID field or null - */ - public String getName() { - Object fieldValue = this.getFieldValue("ID"); - if (fieldValue instanceof Structure) { - Structure id = (Structure) fieldValue; - return id == null ? null : id.getFieldValue("name").toString().substring(2);// blender adds 2-charactes as a name prefix - } - Object name = this.getFieldValue("name", null); - return name == null ? null : name.toString().substring(2); - } - - @Override - public String toString() { - StringBuilder result = new StringBuilder("struct ").append(type).append(" {\n"); - for (int i = 0; i < fields.length; ++i) { - result.append(fields[i].toString()).append('\n'); - } - return result.append('}').toString(); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return new Structure(this); - } - - /** - * This enum enumerates all known data types that can be found in the blend file. - * @author Marcin Roguski (Kaelthas) - */ - /* package */static enum DataType { - - CHARACTER, SHORT, INTEGER, LONG, FLOAT, DOUBLE, VOID, STRUCTURE, POINTER; - /** The map containing the known primary types. */ - private static final Map PRIMARY_TYPES = new HashMap(10); - - static { - PRIMARY_TYPES.put("char", CHARACTER); - PRIMARY_TYPES.put("uchar", CHARACTER); - PRIMARY_TYPES.put("short", SHORT); - PRIMARY_TYPES.put("ushort", SHORT); - PRIMARY_TYPES.put("int", INTEGER); - PRIMARY_TYPES.put("long", LONG); - PRIMARY_TYPES.put("ulong", LONG); - PRIMARY_TYPES.put("uint64_t", LONG); - PRIMARY_TYPES.put("float", FLOAT); - PRIMARY_TYPES.put("double", DOUBLE); - PRIMARY_TYPES.put("void", VOID); - } - - /** - * This method returns the data type that is appropriate to the given type name. WARNING! The type recognition - * is case sensitive! - * @param type - * the type name of the data - * @param blenderContext - * the blender context - * @return appropriate enum value to the given type name - * @throws BlenderFileException - * this exception is thrown if the given type name does not exist in the blend file - */ - public static DataType getDataType(String type, BlenderContext blenderContext) throws BlenderFileException { - DataType result = PRIMARY_TYPES.get(type); - if (result != null) { - return result; - } - if (blenderContext.getDnaBlockData().hasStructure(type)) { - return STRUCTURE; - } - throw new BlenderFileException("Unknown data type: " + type); - } - - /** - * @return a collection of known primary types names - */ - /* package */static Collection getKnownPrimaryTypesNames() { - return PRIMARY_TYPES.keySet(); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java deleted file mode 100644 index 32160c13a3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/landscape/LandscapeHelper.java +++ /dev/null @@ -1,214 +0,0 @@ -package com.jme3.scene.plugins.blender.landscape; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.light.AmbientLight; -import com.jme3.light.Light; -import com.jme3.math.ColorRGBA; -import com.jme3.post.filters.FogFilter; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.ColorBand; -import com.jme3.scene.plugins.blender.textures.CombinedTexture; -import com.jme3.scene.plugins.blender.textures.ImageUtils; -import com.jme3.scene.plugins.blender.textures.TextureHelper; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.TextureCubeMap; -import com.jme3.util.SkyFactory; - -/** - * The class that allows to load the following:
  • the ambient light of the scene
  • the sky of the scene (with or without texture) - * - * @author Marcin Roguski (Kaelthas) - */ -public class LandscapeHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(LandscapeHelper.class.getName()); - - private static final int SKYTYPE_BLEND = 1; - private static final int SKYTYPE_REAL = 2; - private static final int SKYTYPE_PAPER = 4; - - private static final int MODE_MIST = 0x01; - - public LandscapeHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * Loads scene ambient light. - * @param worldStructure - * the world's blender structure - * @return the scene's ambient light - */ - public Light toAmbientLight(Structure worldStructure) { - LOGGER.fine("Loading ambient light."); - AmbientLight ambientLight = null; - float ambr = ((Number) worldStructure.getFieldValue("ambr")).floatValue(); - float ambg = ((Number) worldStructure.getFieldValue("ambg")).floatValue(); - float ambb = ((Number) worldStructure.getFieldValue("ambb")).floatValue(); - if (ambr > 0 || ambg > 0 || ambb > 0) { - ambientLight = new AmbientLight(); - ColorRGBA ambientLightColor = new ColorRGBA(ambr, ambg, ambb, 0.0f); - ambientLight.setColor(ambientLightColor); - LOGGER.log(Level.FINE, "Loaded ambient light: {0}.", ambientLightColor); - } else { - LOGGER.finer("Ambient light is set to BLACK which means: no ambient light! The ambient light node will not be included in the result."); - } - return ambientLight; - } - - /** - * The method loads fog for the scene. - * NOTICE! Remember to manually set the distance and density of the fog. - * Unfortunately blender's fog parameters in no way fit to the JME. - * @param worldStructure - * the world's structure - * @return fog filter or null if scene does not define it - */ - public FogFilter toFog(Structure worldStructure) { - FogFilter result = null; - int mode = ((Number) worldStructure.getFieldValue("mode")).intValue(); - if ((mode & MODE_MIST) != 0) { - LOGGER.fine("Loading fog."); - result = new FogFilter(); - result.setName("FIfog"); - result.setFogColor(this.toBackgroundColor(worldStructure)); - } - return result; - } - - /** - * Loads the background color. - * @param worldStructure - * the world's structure - * @return the horizon color of the world which is used as a background color. - */ - public ColorRGBA toBackgroundColor(Structure worldStructure) { - float horr = ((Number) worldStructure.getFieldValue("horr")).floatValue(); - float horg = ((Number) worldStructure.getFieldValue("horg")).floatValue(); - float horb = ((Number) worldStructure.getFieldValue("horb")).floatValue(); - return new ColorRGBA(horr, horg, horb, 1); - } - - /** - * Loads scene's sky. Sky can be plain or textured. - * If no sky type is selected in blender then no sky is loaded. - * @param worldStructure - * the world's structure - * @return the scene's sky - * @throws BlenderFileException - * blender exception is thrown when problems with blender file occur - */ - public Spatial toSky(Structure worldStructure) throws BlenderFileException { - int skytype = ((Number) worldStructure.getFieldValue("skytype")).intValue(); - if (skytype == 0) { - return null; - } - - LOGGER.fine("Loading sky."); - ColorRGBA horizontalColor = this.toBackgroundColor(worldStructure); - - float zenr = ((Number) worldStructure.getFieldValue("zenr")).floatValue(); - float zeng = ((Number) worldStructure.getFieldValue("zeng")).floatValue(); - float zenb = ((Number) worldStructure.getFieldValue("zenb")).floatValue(); - ColorRGBA zenithColor = new ColorRGBA(zenr, zeng, zenb, 1); - - // jutr for this case load generated textures wheather user had set it or not because those might be needed to properly load the sky - boolean loadGeneratedTextures = blenderContext.getBlenderKey().isLoadGeneratedTextures(); - blenderContext.getBlenderKey().setLoadGeneratedTextures(true); - - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - List loadedTextures = null; - try { - loadedTextures = textureHelper.readTextureData(worldStructure, new float[] { horizontalColor.r, horizontalColor.g, horizontalColor.b, horizontalColor.a }, true); - } finally { - blenderContext.getBlenderKey().setLoadGeneratedTextures(loadGeneratedTextures); - } - - TextureCubeMap texture = null; - if (loadedTextures != null && loadedTextures.size() > 0) { - if (loadedTextures.size() > 1) { - throw new IllegalStateException("There should be only one combined texture for sky!"); - } - CombinedTexture combinedTexture = loadedTextures.get(0); - texture = combinedTexture.generateSkyTexture(horizontalColor, zenithColor, blenderContext); - } else { - LOGGER.fine("Preparing colors for colorband."); - int colorbandType = ColorBand.IPO_CARDINAL; - List colorbandColors = new ArrayList(3); - colorbandColors.add(horizontalColor); - if ((skytype & SKYTYPE_BLEND) != 0) { - if ((skytype & SKYTYPE_PAPER) != 0) { - colorbandType = ColorBand.IPO_LINEAR; - } - if ((skytype & SKYTYPE_REAL) != 0) { - colorbandColors.add(0, zenithColor); - } - colorbandColors.add(zenithColor); - } - - int size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); - - List positions = new ArrayList(colorbandColors.size()); - positions.add(0); - if (colorbandColors.size() == 2) { - positions.add(size - 1); - } else if (colorbandColors.size() == 3) { - positions.add(size / 2); - positions.add(size - 1); - } - - LOGGER.fine("Generating sky texture."); - float[][] values = new ColorBand(colorbandType, colorbandColors, positions, size).computeValues(); - - Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); - PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - - LOGGER.fine("Creating side textures."); - int[] sideImagesIndexes = new int[] { 0, 1, 4, 5 }; - for (int i : sideImagesIndexes) { - for (int y = 0; y < size; ++y) { - pixel.red = values[y][0]; - pixel.green = values[y][1]; - pixel.blue = values[y][2]; - - for (int x = 0; x < size; ++x) { - pixelIO.write(image, i, pixel, x, y); - } - } - } - - LOGGER.fine("Creating top texture."); - pixelIO.read(image, 0, pixel, 0, image.getHeight() - 1); - for (int y = 0; y < size; ++y) { - for (int x = 0; x < size; ++x) { - pixelIO.write(image, 3, pixel, x, y); - } - } - - LOGGER.fine("Creating bottom texture."); - pixelIO.read(image, 0, pixel, 0, 0); - for (int y = 0; y < size; ++y) { - for (int x = 0; x < size; ++x) { - pixelIO.write(image, 2, pixel, x, y); - } - } - - texture = new TextureCubeMap(image); - } - - LOGGER.fine("Sky texture created. Creating sky."); - return SkyFactory.createSky(blenderContext.getAssetManager(), texture, SkyFactory.EnvMapType.CubeMap); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java deleted file mode 100644 index 3ae866b7c8..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/lights/LightHelper.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.lights; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.light.DirectionalLight; -import com.jme3.light.Light; -import com.jme3.light.PointLight; -import com.jme3.light.SpotLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that is used in light calculations. - * @author Marcin Roguski - */ -public class LightHelper extends AbstractBlenderHelper { - - private static final Logger LOGGER = Logger.getLogger(LightHelper.class.getName()); - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public LightHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - public Light toLight(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - Light result = (Light) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (result != null) { - return result; - } - Light light = null; - int type = ((Number) structure.getFieldValue("type")).intValue(); - switch (type) { - case 0:// Lamp - light = new PointLight(); - float distance = ((Number) structure.getFieldValue("dist")).floatValue(); - ((PointLight) light).setRadius(distance); - break; - case 1:// Sun - LOGGER.log(Level.WARNING, "'Sun' lamp is not supported in jMonkeyEngine. Using PointLight with radius = Float.MAX_VALUE."); - light = new PointLight(); - ((PointLight) light).setRadius(Float.MAX_VALUE); - break; - case 2:// Spot - light = new SpotLight(); - // range - ((SpotLight) light).setSpotRange(((Number) structure.getFieldValue("dist")).floatValue()); - // outer angle - float outerAngle = ((Number) structure.getFieldValue("spotsize")).floatValue() * FastMath.DEG_TO_RAD * 0.5f; - ((SpotLight) light).setSpotOuterAngle(outerAngle); - - // inner angle - float spotblend = ((Number) structure.getFieldValue("spotblend")).floatValue(); - spotblend = FastMath.clamp(spotblend, 0, 1); - float innerAngle = outerAngle * (1 - spotblend); - ((SpotLight) light).setSpotInnerAngle(innerAngle); - break; - case 3:// Hemi - LOGGER.log(Level.WARNING, "'Hemi' lamp is not supported in jMonkeyEngine. Using DirectionalLight instead."); - case 4:// Area - light = new DirectionalLight(); - break; - default: - throw new BlenderFileException("Unknown light source type: " + type); - } - float r = ((Number) structure.getFieldValue("r")).floatValue(); - float g = ((Number) structure.getFieldValue("g")).floatValue(); - float b = ((Number) structure.getFieldValue("b")).floatValue(); - light.setColor(new ColorRGBA(r, g, b, 1.0f)); - light.setName(structure.getName()); - return light; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java deleted file mode 100644 index 7fddd3d41b..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/IAlphaMask.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.jme3.scene.plugins.blender.materials; - -/** - * An interface used in calculating alpha mask during particles' texture calculations. - * @author Marcin Roguski (Kaelthas) - */ -/* package */interface IAlphaMask { - /** - * This method sets the size of the texture's image. - * @param width - * the width of the image - * @param height - * the height of the image - */ - void setImageSize(int width, int height); - - /** - * This method returns the alpha value for the specified texture position. - * @param x - * the X coordinate of the texture position - * @param y - * the Y coordinate of the texture position - * @return the alpha value for the specified texture position - */ - byte getAlpha(float x, float y); -} \ No newline at end of file diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java deleted file mode 100644 index 42da1c9d28..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialContext.java +++ /dev/null @@ -1,366 +0,0 @@ -package com.jme3.scene.plugins.blender.materials; - -import java.io.IOException; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.Savable; -import com.jme3.material.Material; -import com.jme3.material.RenderState.BlendMode; -import com.jme3.material.RenderState.FaceCullMode; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Geometry; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialHelper.DiffuseShader; -import com.jme3.scene.plugins.blender.materials.MaterialHelper.SpecularShader; -import com.jme3.scene.plugins.blender.textures.CombinedTexture; -import com.jme3.scene.plugins.blender.textures.TextureHelper; -import com.jme3.texture.Texture; -import com.jme3.util.BufferUtils; - -/** - * This class holds the data about the material. - * @author Marcin Roguski (Kaelthas) - */ -public final class MaterialContext implements Savable { - private static final Logger LOGGER = Logger.getLogger(MaterialContext.class.getName()); - - // texture mapping types - public static final int MTEX_COL = 0x01; - public static final int MTEX_NOR = 0x02; - public static final int MTEX_SPEC = 0x04; - public static final int MTEX_EMIT = 0x40; - public static final int MTEX_ALPHA = 0x80; - public static final int MTEX_AMB = 0x800; - - public static final int FLAG_TRANSPARENT = 0x10000; - - /* package */final String name; - /* package */final List loadedTextures; - - /* package */final ColorRGBA diffuseColor; - /* package */final DiffuseShader diffuseShader; - /* package */final SpecularShader specularShader; - /* package */final ColorRGBA specularColor; - /* package */final float ambientFactor; - /* package */final float shininess; - /* package */final boolean shadeless; - /* package */final boolean vertexColor; - /* package */final boolean transparent; - /* package */final boolean vTangent; - /* package */FaceCullMode faceCullMode; - - /* package */MaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - name = structure.getName(); - - int mode = ((Number) structure.getFieldValue("mode")).intValue(); - shadeless = (mode & 0x4) != 0; - vertexColor = (mode & 0x80) != 0; - vTangent = (mode & 0x4000000) != 0; // NOTE: Requires tangents - - int diff_shader = ((Number) structure.getFieldValue("diff_shader")).intValue(); - diffuseShader = DiffuseShader.values()[diff_shader]; - ambientFactor = ((Number) structure.getFieldValue("amb")).floatValue(); - - if (shadeless) { - float r = ((Number) structure.getFieldValue("r")).floatValue(); - float g = ((Number) structure.getFieldValue("g")).floatValue(); - float b = ((Number) structure.getFieldValue("b")).floatValue(); - float alpha = ((Number) structure.getFieldValue("alpha")).floatValue(); - - diffuseColor = new ColorRGBA(r, g, b, alpha); - specularShader = null; - specularColor = null; - shininess = 0.0f; - } else { - diffuseColor = this.readDiffuseColor(structure, diffuseShader); - - int spec_shader = ((Number) structure.getFieldValue("spec_shader")).intValue(); - specularShader = SpecularShader.values()[spec_shader]; - specularColor = this.readSpecularColor(structure); - float shininess = ((Number) structure.getFieldValue("har")).floatValue();// this is (probably) the specular hardness in blender - this.shininess = shininess > 0.0f ? shininess : MaterialHelper.DEFAULT_SHININESS; - } - - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - loadedTextures = textureHelper.readTextureData(structure, new float[] { diffuseColor.r, diffuseColor.g, diffuseColor.b, diffuseColor.a }, false); - - long flag = ((Number)structure.getFieldValue("flag")).longValue(); - if((flag & FLAG_TRANSPARENT) != 0) { - // veryfying if the transparency is present - // (in blender transparent mask is 0x10000 but it's better to verify it because blender can indicate transparency when - // it is not required - boolean transparent = false; - if (diffuseColor != null) { - transparent = diffuseColor.a < 1.0f; - if (loadedTextures.size() > 0) {// texture covers the material color - diffuseColor.set(1, 1, 1, 1); - } - } - if (specularColor != null) { - transparent = transparent || specularColor.a < 1.0f; - } - this.transparent = transparent; - } else { - transparent = false; - } - } - - /** - * @return the name of the material - */ - public String getName() { - return name; - } - - /** - * Applies material to a given geometry. - * - * @param geometry - * the geometry - * @param geometriesOMA - * the geometries OMA - * @param userDefinedUVCoordinates - * UV coords defined by user - * @param blenderContext - * the blender context - */ - public void applyMaterial(Geometry geometry, Long geometriesOMA, Map> userDefinedUVCoordinates, BlenderContext blenderContext) { - Material material = null; - if (shadeless) { - material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - - if (!transparent) { - diffuseColor.a = 1; - } - - material.setColor("Color", diffuseColor); - } else { - material = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Light/Lighting.j3md"); - material.setBoolean("UseMaterialColors", Boolean.TRUE); - - // setting the colors - if (!transparent) { - diffuseColor.a = 1; - } - material.setColor("Diffuse", diffuseColor); - - material.setColor("Specular", specularColor); - material.setFloat("Shininess", shininess); - - material.setColor("Ambient", new ColorRGBA(ambientFactor, ambientFactor, ambientFactor, 1f)); - } - - // initializing unused "user-defined UV coords" to all available - Map> unusedUserDefinedUVCoords = Collections.emptyMap(); - if(userDefinedUVCoordinates != null && !userDefinedUVCoordinates.isEmpty()) { - unusedUserDefinedUVCoords = new HashMap<>(userDefinedUVCoordinates); - } - - // applying textures - int textureIndex = 0; - if (loadedTextures != null && loadedTextures.size() > 0) { - if (loadedTextures.size() > TextureHelper.TEXCOORD_TYPES.length) { - LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different textures. JME supports only {0} UV mappings.", TextureHelper.TEXCOORD_TYPES.length); - } - for (CombinedTexture combinedTexture : loadedTextures) { - if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { - String usedUserUVSet = combinedTexture.flatten(geometry, geometriesOMA, userDefinedUVCoordinates, blenderContext); - - this.setTexture(material, combinedTexture.getMappingType(), combinedTexture.getResultTexture()); - - if(usedUserUVSet == null || unusedUserDefinedUVCoords.containsKey(usedUserUVSet)) { - List uvs = combinedTexture.getResultUVS(); - if(uvs != null && uvs.size() > 0) { - VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); - uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); - geometry.getMesh().setBuffer(uvCoordsBuffer); - }//uvs might be null if the user assigned non existing UV coordinates group name to the mesh (this should be fixed in blender file) - - // Remove used "user-defined UV coords" from the unused collection - if(usedUserUVSet != null) { - unusedUserDefinedUVCoords.remove(usedUserUVSet); - } - } - } else { - LOGGER.log(Level.WARNING, "The texture could not be applied because JME only supports up to {0} different UV's.", TextureHelper.TEXCOORD_TYPES.length); - } - } - } - - if (unusedUserDefinedUVCoords != null && unusedUserDefinedUVCoords.size() > 0) { - LOGGER.fine("Storing unused, user defined UV coordinates sets."); - if (unusedUserDefinedUVCoords.size() > TextureHelper.TEXCOORD_TYPES.length) { - LOGGER.log(Level.WARNING, "The blender file has defined more than {0} different UV coordinates for the mesh. JME supports only {0} UV coordinates buffers.", TextureHelper.TEXCOORD_TYPES.length); - } - for (Entry> entry : unusedUserDefinedUVCoords.entrySet()) { - if (textureIndex < TextureHelper.TEXCOORD_TYPES.length) { - List uvs = entry.getValue(); - VertexBuffer uvCoordsBuffer = new VertexBuffer(TextureHelper.TEXCOORD_TYPES[textureIndex++]); - uvCoordsBuffer.setupData(Usage.Static, 2, Format.Float, BufferUtils.createFloatBuffer(uvs.toArray(new Vector2f[uvs.size()]))); - geometry.getMesh().setBuffer(uvCoordsBuffer); - } else { - LOGGER.log(Level.WARNING, "The user's UV set named: '{0}' could not be stored because JME only supports up to {1} different UV's.", new Object[] { - entry.getKey(), TextureHelper.TEXCOORD_TYPES.length - }); - } - } - } - - // applying additional data - material.setName(name); - if (vertexColor) { - material.setBoolean(shadeless ? "VertexColor" : "UseVertexColor", true); - } - material.getAdditionalRenderState().setFaceCullMode(faceCullMode != null ? faceCullMode : blenderContext.getBlenderKey().getFaceCullMode()); - if (transparent) { - material.setTransparent(true); - material.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - geometry.setQueueBucket(Bucket.Transparent); - } - - geometry.setMaterial(material); - } - - /** - * Sets the texture to the given material. - * - * @param material - * the material that we add texture to - * @param mapTo - * the texture mapping type - * @param texture - * the added texture - */ - private void setTexture(Material material, int mapTo, Texture texture) { - switch (mapTo) { - case MTEX_COL: - material.setTexture(shadeless ? MaterialHelper.TEXTURE_TYPE_COLOR : MaterialHelper.TEXTURE_TYPE_DIFFUSE, texture); - break; - case MTEX_NOR: - material.setTexture(MaterialHelper.TEXTURE_TYPE_NORMAL, texture); - break; - case MTEX_SPEC: - material.setTexture(MaterialHelper.TEXTURE_TYPE_SPECULAR, texture); - break; - case MTEX_EMIT: - material.setTexture(MaterialHelper.TEXTURE_TYPE_GLOW, texture); - break; - case MTEX_ALPHA: - if (!shadeless) { - material.setTexture(MaterialHelper.TEXTURE_TYPE_ALPHA, texture); - } else { - LOGGER.warning("JME does not support alpha map on unshaded material. Material name is " + name); - } - break; - case MTEX_AMB: - material.setTexture(MaterialHelper.TEXTURE_TYPE_LIGHTMAP, texture); - break; - default: - LOGGER.severe("Unknown mapping type: " + mapTo); - } - } - - /** - * @return true if the material has at least one generated texture and false otherwise - */ - public boolean hasGeneratedTextures() { - if (loadedTextures != null) { - for (CombinedTexture generatedTextures : loadedTextures) { - if (generatedTextures.hasGeneratedTextures()) { - return true; - } - } - } - return false; - } - - /** - * This method sets the face cull mode. - * @param faceCullMode - * the face cull mode - */ - public void setFaceCullMode(FaceCullMode faceCullMode) { - this.faceCullMode = faceCullMode; - } - - /** - * This method returns the diffuse color. - * - * @param materialStructure - * the material structure - * @param diffuseShader - * the diffuse shader - * @return the diffuse color - */ - private ColorRGBA readDiffuseColor(Structure materialStructure, DiffuseShader diffuseShader) { - // bitwise 'or' of all textures mappings - int commonMapto = ((Number) materialStructure.getFieldValue("mapto")).intValue(); - - // diffuse color - float r = ((Number) materialStructure.getFieldValue("r")).floatValue(); - float g = ((Number) materialStructure.getFieldValue("g")).floatValue(); - float b = ((Number) materialStructure.getFieldValue("b")).floatValue(); - float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); - if ((commonMapto & 0x01) == 0x01) {// Col - return new ColorRGBA(r, g, b, alpha); - } else { - switch (diffuseShader) { - case FRESNEL: - case ORENNAYAR: - case TOON: - break;// TODO: find what is the proper modification - case MINNAERT: - case LAMBERT:// TODO: check if that is correct - float ref = ((Number) materialStructure.getFieldValue("ref")).floatValue(); - r *= ref; - g *= ref; - b *= ref; - break; - default: - throw new IllegalStateException("Unknown diffuse shader type: " + diffuseShader.toString()); - } - return new ColorRGBA(r, g, b, alpha); - } - } - - /** - * This method returns a specular color used by the material. - * - * @param materialStructure - * the material structure filled with data - * @return a specular color used by the material - */ - private ColorRGBA readSpecularColor(Structure materialStructure) { - float specularIntensity = ((Number) materialStructure.getFieldValue("spec")).floatValue(); - float r = ((Number) materialStructure.getFieldValue("specr")).floatValue() * specularIntensity; - float g = ((Number) materialStructure.getFieldValue("specg")).floatValue() * specularIntensity; - float b = ((Number) materialStructure.getFieldValue("specb")).floatValue() * specularIntensity; - float alpha = ((Number) materialStructure.getFieldValue("alpha")).floatValue(); - return new ColorRGBA(r, g, b, alpha); - } - - @Override - public void write(JmeExporter e) throws IOException { - throw new IOException("Material context is not for saving! It implements savable only to be passed to another blend file as a Savable in user data!"); - } - - @Override - public void read(JmeImporter e) throws IOException { - throw new IOException("Material context is not for loading! It implements savable only to be passed to another blend file as a Savable in user data!"); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java deleted file mode 100644 index 885c5850e8..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/materials/MaterialHelper.java +++ /dev/null @@ -1,379 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.materials; - -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.material.MatParam; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.shader.VarType; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; -import com.jme3.texture.Texture; -import com.jme3.util.BufferUtils; - -public class MaterialHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(MaterialHelper.class.getName()); - protected static final float DEFAULT_SHININESS = 20.0f; - - public static final String TEXTURE_TYPE_COLOR = "ColorMap"; - public static final String TEXTURE_TYPE_DIFFUSE = "DiffuseMap"; - public static final String TEXTURE_TYPE_NORMAL = "NormalMap"; - public static final String TEXTURE_TYPE_SPECULAR = "SpecularMap"; - public static final String TEXTURE_TYPE_GLOW = "GlowMap"; - public static final String TEXTURE_TYPE_ALPHA = "AlphaMap"; - public static final String TEXTURE_TYPE_LIGHTMAP = "LightMap"; - - public static final Integer ALPHA_MASK_NONE = Integer.valueOf(0); - public static final Integer ALPHA_MASK_CIRCLE = Integer.valueOf(1); - public static final Integer ALPHA_MASK_CONE = Integer.valueOf(2); - public static final Integer ALPHA_MASK_HYPERBOLE = Integer.valueOf(3); - protected final Map alphaMasks = new HashMap(); - - /** - * The type of the material's diffuse shader. - */ - public static enum DiffuseShader { - LAMBERT, ORENNAYAR, TOON, MINNAERT, FRESNEL - } - - /** - * The type of the material's specular shader. - */ - public static enum SpecularShader { - COOKTORRENCE, PHONG, BLINN, TOON, WARDISO - } - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public MaterialHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - // setting alpha masks - alphaMasks.put(ALPHA_MASK_NONE, new IAlphaMask() { - public void setImageSize(int width, int height) { - } - - public byte getAlpha(float x, float y) { - return (byte) 255; - } - }); - alphaMasks.put(ALPHA_MASK_CIRCLE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); - return (byte) (d >= r ? 0 : 255); - } - }); - alphaMasks.put(ALPHA_MASK_CONE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))); - return (byte) (d >= r ? 0 : -255.0f * d / r + 255.0f); - } - }); - alphaMasks.put(ALPHA_MASK_HYPERBOLE, new IAlphaMask() { - private float r; - private float[] center; - - public void setImageSize(int width, int height) { - r = Math.min(width, height) * 0.5f; - center = new float[] { width * 0.5f, height * 0.5f }; - } - - public byte getAlpha(float x, float y) { - float d = FastMath.abs(FastMath.sqrt((x - center[0]) * (x - center[0]) + (y - center[1]) * (y - center[1]))) / r; - return d >= 1.0f ? 0 : (byte) ((-FastMath.sqrt((2.0f - d) * d) + 1.0f) * 255.0f); - } - }); - } - - /** - * This method converts the material structure to jme Material. - * @param structure - * structure with material data - * @param blenderContext - * the blender context - * @return jme material - * @throws BlenderFileException - * an exception is throw when problems with blend file occur - */ - public MaterialContext toMaterialContext(Structure structure, BlenderContext blenderContext) throws BlenderFileException { - MaterialContext result = (MaterialContext) blenderContext.getLoadedFeature(structure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (result != null) { - return result; - } - - if ("ID".equals(structure.getType())) { - LOGGER.fine("Loading material from external blend file."); - return (MaterialContext) this.loadLibrary(structure); - } - - LOGGER.fine("Loading material."); - result = new MaterialContext(structure, blenderContext); - LOGGER.log(Level.FINE, "Material''s name: {0}", result.name); - Long oma = structure.getOldMemoryAddress(); - blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, structure); - blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result); - return result; - } - - /** - * This method converts the given material into particles-usable material. - * The texture and glow color are being copied. - * The method assumes it receives the Lighting type of material. - * @param material - * the source material - * @param blenderContext - * the blender context - * @return material converted into particles-usable material - */ - public Material getParticlesMaterial(Material material, Integer alphaMaskIndex, BlenderContext blenderContext) { - Material result = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Particle.j3md"); - - // copying texture - MatParam diffuseMap = material.getParam("DiffuseMap"); - if (diffuseMap != null) { - Texture texture = ((Texture) diffuseMap.getValue()).clone(); - - // applying alpha mask to the texture - Image image = texture.getImage(); - ByteBuffer sourceBB = image.getData(0); - sourceBB.rewind(); - int w = image.getWidth(); - int h = image.getHeight(); - ByteBuffer bb = BufferUtils.createByteBuffer(w * h * 4); - IAlphaMask iAlphaMask = alphaMasks.get(alphaMaskIndex); - iAlphaMask.setImageSize(w, h); - - for (int x = 0; x < w; ++x) { - for (int y = 0; y < h; ++y) { - bb.put(sourceBB.get()); - bb.put(sourceBB.get()); - bb.put(sourceBB.get()); - bb.put(iAlphaMask.getAlpha(x, y)); - } - } - - image = new Image(Format.RGBA8, w, h, bb, ColorSpace.Linear); - texture.setImage(image); - - result.setTextureParam("Texture", VarType.Texture2D, texture); - } - - // copying glow color - MatParam glowColor = material.getParam("GlowColor"); - if (glowColor != null) { - ColorRGBA color = (ColorRGBA) glowColor.getValue(); - result.setParam("GlowColor", VarType.Vector3, color); - } - return result; - } - - /** - * This method returns the table of materials connected to the specified structure. The given structure can be of any type (ie. mesh or - * curve) but needs to have 'mat' field/ - * - * @param structureWithMaterials - * the structure containing the mesh data - * @param blenderContext - * the blender context - * @return a list of vertices colors, each color belongs to a single vertex - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - public MaterialContext[] getMaterials(Structure structureWithMaterials, BlenderContext blenderContext) throws BlenderFileException { - Pointer ppMaterials = (Pointer) structureWithMaterials.getFieldValue("mat"); - MaterialContext[] materials = null; - if (ppMaterials.isNotNull()) { - List materialStructures = ppMaterials.fetchData(); - if (materialStructures != null && materialStructures.size() > 0) { - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - materials = new MaterialContext[materialStructures.size()]; - int i = 0; - for (Structure s : materialStructures) { - materials[i++] = s == null ? null : materialHelper.toMaterialContext(s, blenderContext); - } - } - } - return materials; - } - - /** - * This method converts rgb values to hsv values. - * - * @param r - * red value of the color - * @param g - * green value of the color - * @param b - * blue value of the color - * @param hsv - * hsv values of a color (this table contains the result of the transformation) - */ - public void rgbToHsv(float r, float g, float b, float[] hsv) { - float cmax = r; - float cmin = r; - cmax = g > cmax ? g : cmax; - cmin = g < cmin ? g : cmin; - cmax = b > cmax ? b : cmax; - cmin = b < cmin ? b : cmin; - - hsv[2] = cmax; /* value */ - if (cmax != 0.0) { - hsv[1] = (cmax - cmin) / cmax; - } else { - hsv[1] = 0.0f; - hsv[0] = 0.0f; - } - if (hsv[1] == 0.0) { - hsv[0] = -1.0f; - } else { - float cdelta = cmax - cmin; - float rc = (cmax - r) / cdelta; - float gc = (cmax - g) / cdelta; - float bc = (cmax - b) / cdelta; - if (r == cmax) { - hsv[0] = bc - gc; - } else if (g == cmax) { - hsv[0] = 2.0f + rc - bc; - } else { - hsv[0] = 4.0f + gc - rc; - } - hsv[0] *= 60.0f; - if (hsv[0] < 0.0f) { - hsv[0] += 360.0f; - } - } - - hsv[0] /= 360.0f; - if (hsv[0] < 0.0f) { - hsv[0] = 0.0f; - } - } - - /** - * This method converts rgb values to hsv values. - * - * @param h - * hue - * @param s - * saturation - * @param v - * value - * @param rgb - * rgb result vector (should have 3 elements) - */ - public void hsvToRgb(float h, float s, float v, float[] rgb) { - h *= 360.0f; - if (s == 0.0) { - rgb[0] = rgb[1] = rgb[2] = v; - } else { - if (h == 360) { - h = 0; - } else { - h /= 60; - } - int i = (int) Math.floor(h); - float f = h - i; - float p = v * (1.0f - s); - float q = v * (1.0f - s * f); - float t = v * (1.0f - s * (1.0f - f)); - switch (i) { - case 0: - rgb[0] = v; - rgb[1] = t; - rgb[2] = p; - break; - case 1: - rgb[0] = q; - rgb[1] = v; - rgb[2] = p; - break; - case 2: - rgb[0] = p; - rgb[1] = v; - rgb[2] = t; - break; - case 3: - rgb[0] = p; - rgb[1] = q; - rgb[2] = v; - break; - case 4: - rgb[0] = t; - rgb[1] = p; - rgb[2] = v; - break; - case 5: - rgb[0] = v; - rgb[1] = p; - rgb[2] = q; - break; - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java deleted file mode 100644 index 9739ccd4be..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DQuaternion.java +++ /dev/null @@ -1,581 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.math; - -import java.io.IOException; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.Quaternion; - -/** - * DQuaternion defines a single example of a more general class of - * hypercomplex numbers. DQuaternions extends a rotation in three dimensions to a - * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth - * continuous rotation. - * - * DQuaternion is defined by four double point numbers: {x y z w}. - * - * This class's only purpose is to give better accuracy in floating point operations during computations. - * This is made by copying the original Quaternion class from jme3 core and leaving only required methods and basic computation methods, so that - * the class is smaller and easier to maintain. - * Should any other methods be needed, they will be added. - * - * @author Mark Powell - * @author Joshua Slack - * @author Marcin Roguski (Kaelthas) - */ -public final class DQuaternion implements Savable, Cloneable, java.io.Serializable { - private static final long serialVersionUID = 5009180713885017539L; - - /** - * Represents the identity quaternion rotation (0, 0, 0, 1). - */ - public static final DQuaternion IDENTITY = new DQuaternion(); - public static final DQuaternion DIRECTION_Z = new DQuaternion(); - public static final DQuaternion ZERO = new DQuaternion(0, 0, 0, 0); - protected double x, y, z, w = 1; - - /** - * Constructor instantiates a new DQuaternion object - * initializing all values to zero, except w which is initialized to 1. - * - */ - public DQuaternion() { - } - - /** - * Constructor instantiates a new DQuaternion object from the - * given list of parameters. - * - * @param x - * the x value of the quaternion. - * @param y - * the y value of the quaternion. - * @param z - * the z value of the quaternion. - * @param w - * the w value of the quaternion. - */ - public DQuaternion(double x, double y, double z, double w) { - this.set(x, y, z, w); - } - - public DQuaternion(Quaternion q) { - this(q.getX(), q.getY(), q.getZ(), q.getW()); - } - - public Quaternion toQuaternion() { - return new Quaternion((float) x, (float) y, (float) z, (float) w); - } - - public double getX() { - return x; - } - - public double getY() { - return y; - } - - public double getZ() { - return z; - } - - public double getW() { - return w; - } - - /** - * sets the data in a DQuaternion object from the given list - * of parameters. - * - * @param x - * the x value of the quaternion. - * @param y - * the y value of the quaternion. - * @param z - * the z value of the quaternion. - * @param w - * the w value of the quaternion. - * @return this - */ - public DQuaternion set(double x, double y, double z, double w) { - this.x = x; - this.y = y; - this.z = z; - this.w = w; - return this; - } - - /** - * Sets the data in this DQuaternion object to be equal to the - * passed DQuaternion object. The values are copied producing - * a new object. - * - * @param q - * The DQuaternion to copy values from. - * @return this - */ - public DQuaternion set(DQuaternion q) { - x = q.x; - y = q.y; - z = q.z; - w = q.w; - return this; - } - - /** - * Sets this DQuaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1). - */ - public void loadIdentity() { - x = y = z = 0; - w = 1; - } - - /** - * norm returns the norm of this quaternion. This is the dot - * product of this quaternion with itself. - * - * @return the norm of the quaternion. - */ - public double norm() { - return w * w + x * x + y * y + z * z; - } - - public DQuaternion fromRotationMatrix(double m00, double m01, double m02, - double m10, double m11, double m12, double m20, double m21, double m22) { - // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix - // so that the scale does not affect the rotation - double lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; - if (lengthSquared != 1f && lengthSquared != 0f) { - lengthSquared = 1.0 / Math.sqrt(lengthSquared); - m00 *= lengthSquared; - m10 *= lengthSquared; - m20 *= lengthSquared; - } - lengthSquared = m01 * m01 + m11 * m11 + m21 * m21; - if (lengthSquared != 1 && lengthSquared != 0f) { - lengthSquared = 1.0 / Math.sqrt(lengthSquared); - m01 *= lengthSquared; - m11 *= lengthSquared; - m21 *= lengthSquared; - } - lengthSquared = m02 * m02 + m12 * m12 + m22 * m22; - if (lengthSquared != 1f && lengthSquared != 0f) { - lengthSquared = 1.0 / Math.sqrt(lengthSquared); - m02 *= lengthSquared; - m12 *= lengthSquared; - m22 *= lengthSquared; - } - - // Use the Graphics Gems code, from - // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z - // *NOT* the "Matrix and Quaternions FAQ", which has errors! - - // the trace is the sum of the diagonal elements; see - // http://mathworld.wolfram.com/MatrixTrace.html - double t = m00 + m11 + m22; - - // we protect the division by s by ensuring that s>=1 - if (t >= 0) { // |w| >= .5 - double s = Math.sqrt(t + 1); // |s|>=1 ... - w = 0.5f * s; - s = 0.5f / s; // so this division isn't bad - x = (m21 - m12) * s; - y = (m02 - m20) * s; - z = (m10 - m01) * s; - } else if (m00 > m11 && m00 > m22) { - double s = Math.sqrt(1.0 + m00 - m11 - m22); // |s|>=1 - x = s * 0.5f; // |x| >= .5 - s = 0.5f / s; - y = (m10 + m01) * s; - z = (m02 + m20) * s; - w = (m21 - m12) * s; - } else if (m11 > m22) { - double s = Math.sqrt(1.0 + m11 - m00 - m22); // |s|>=1 - y = s * 0.5f; // |y| >= .5 - s = 0.5f / s; - x = (m10 + m01) * s; - z = (m21 + m12) * s; - w = (m02 - m20) * s; - } else { - double s = Math.sqrt(1.0 + m22 - m00 - m11); // |s|>=1 - z = s * 0.5f; // |z| >= .5 - s = 0.5f / s; - x = (m02 + m20) * s; - y = (m21 + m12) * s; - w = (m10 - m01) * s; - } - - return this; - } - - /** - * toRotationMatrix converts this quaternion to a rotational - * matrix. The result is stored in result. 4th row and 4th column values are - * untouched. Note: the result is created from a normalized version of this quat. - * - * @param result - * The Matrix4f to store the result in. - * @return the rotation matrix representation of this quaternion. - */ - public Matrix toRotationMatrix(Matrix result) { - Vector3d originalScale = new Vector3d(); - - result.toScaleVector(originalScale); - result.setScale(1, 1, 1); - double norm = this.norm(); - // we explicitly test norm against one here, saving a division - // at the cost of a test and branch. Is it worth it? - double s = norm == 1f ? 2f : norm > 0f ? 2f / norm : 0; - - // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs - // will be used 2-4 times each. - double xs = x * s; - double ys = y * s; - double zs = z * s; - double xx = x * xs; - double xy = x * ys; - double xz = x * zs; - double xw = w * xs; - double yy = y * ys; - double yz = y * zs; - double yw = w * ys; - double zz = z * zs; - double zw = w * zs; - - // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here - result.set(0, 0, 1 - (yy + zz)); - result.set(0, 1, xy - zw); - result.set(0, 2, xz + yw); - result.set(1, 0, xy + zw); - result.set(1, 1, 1 - (xx + zz)); - result.set(1, 2, yz - xw); - result.set(2, 0, xz - yw); - result.set(2, 1, yz + xw); - result.set(2, 2, 1 - (xx + yy)); - - result.setScale(originalScale); - - return result; - } - - /** - * fromAngleAxis sets this quaternion to the values specified - * by an angle and an axis of rotation. This method creates an object, so - * use fromAngleNormalAxis if your axis is already normalized. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation. - * @return this quaternion - */ - public DQuaternion fromAngleAxis(double angle, Vector3d axis) { - Vector3d normAxis = axis.normalize(); - this.fromAngleNormalAxis(angle, normAxis); - return this; - } - - /** - * fromAngleNormalAxis sets this quaternion to the values - * specified by an angle and a normalized axis of rotation. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation (already normalized). - */ - public DQuaternion fromAngleNormalAxis(double angle, Vector3d axis) { - if (axis.x == 0 && axis.y == 0 && axis.z == 0) { - this.loadIdentity(); - } else { - double halfAngle = 0.5f * angle; - double sin = Math.sin(halfAngle); - w = Math.cos(halfAngle); - x = sin * axis.x; - y = sin * axis.y; - z = sin * axis.z; - } - return this; - } - - /** - * add adds the values of this quaternion to those of the - * parameter quaternion. The result is returned as a new quaternion. - * - * @param q - * the quaternion to add to this. - * @return the new quaternion. - */ - public DQuaternion add(DQuaternion q) { - return new DQuaternion(x + q.x, y + q.y, z + q.z, w + q.w); - } - - /** - * add adds the values of this quaternion to those of the - * parameter quaternion. The result is stored in this DQuaternion. - * - * @param q - * the quaternion to add to this. - * @return This DQuaternion after addition. - */ - public DQuaternion addLocal(DQuaternion q) { - x += q.x; - y += q.y; - z += q.z; - w += q.w; - return this; - } - - /** - * subtract subtracts the values of the parameter quaternion - * from those of this quaternion. The result is returned as a new - * quaternion. - * - * @param q - * the quaternion to subtract from this. - * @return the new quaternion. - */ - public DQuaternion subtract(DQuaternion q) { - return new DQuaternion(x - q.x, y - q.y, z - q.z, w - q.w); - } - - /** - * subtract subtracts the values of the parameter quaternion - * from those of this quaternion. The result is stored in this DQuaternion. - * - * @param q - * the quaternion to subtract from this. - * @return This DQuaternion after subtraction. - */ - public DQuaternion subtractLocal(DQuaternion q) { - x -= q.x; - y -= q.y; - z -= q.z; - w -= q.w; - return this; - } - - /** - * mult multiplies this quaternion by a parameter quaternion. - * The result is returned as a new quaternion. It should be noted that - * quaternion multiplication is not commutative so q * p != p * q. - * - * @param q - * the quaternion to multiply this quaternion by. - * @return the new quaternion. - */ - public DQuaternion mult(DQuaternion q) { - return this.mult(q, null); - } - - /** - * mult multiplies this quaternion by a parameter quaternion. - * The result is returned as a new quaternion. It should be noted that - * quaternion multiplication is not commutative so q * p != p * q. - * - * It IS safe for q and res to be the same object. - * It IS NOT safe for this and res to be the same object. - * - * @param q - * the quaternion to multiply this quaternion by. - * @param res - * the quaternion to store the result in. - * @return the new quaternion. - */ - public DQuaternion mult(DQuaternion q, DQuaternion res) { - if (res == null) { - res = new DQuaternion(); - } - double qw = q.w, qx = q.x, qy = q.y, qz = q.z; - res.x = x * qw + y * qz - z * qy + w * qx; - res.y = -x * qz + y * qw + z * qx + w * qy; - res.z = x * qy - y * qx + z * qw + w * qz; - res.w = -x * qx - y * qy - z * qz + w * qw; - return res; - } - - /** - * mult multiplies this quaternion by a parameter vector. The - * result is returned as a new vector. - * - * @param v - * the vector to multiply this quaternion by. - * @return the new vector. - */ - public Vector3d mult(Vector3d v) { - return this.mult(v, null); - } - - /** - * Multiplies this DQuaternion by the supplied quaternion. The result is - * stored in this DQuaternion, which is also returned for chaining. Similar - * to this *= q. - * - * @param q - * The DQuaternion to multiply this one by. - * @return This DQuaternion, after multiplication. - */ - public DQuaternion multLocal(DQuaternion q) { - double x1 = x * q.w + y * q.z - z * q.y + w * q.x; - double y1 = -x * q.z + y * q.w + z * q.x + w * q.y; - double z1 = x * q.y - y * q.x + z * q.w + w * q.z; - w = -x * q.x - y * q.y - z * q.z + w * q.w; - x = x1; - y = y1; - z = z1; - return this; - } - - /** - * mult multiplies this quaternion by a parameter vector. The - * result is returned as a new vector. - * - * @param v - * the vector to multiply this quaternion by. - * @param store - * the vector to store the result in. It IS safe for v and store - * to be the same object. - * @return the result vector. - */ - public Vector3d mult(Vector3d v, Vector3d store) { - if (store == null) { - store = new Vector3d(); - } - if (v.x == 0 && v.y == 0 && v.z == 0) { - store.set(0, 0, 0); - } else { - double vx = v.x, vy = v.y, vz = v.z; - store.x = w * w * vx + 2 * y * w * vz - 2 * z * w * vy + x * x * vx + 2 * y * x * vy + 2 * z * x * vz - z * z * vx - y * y * vx; - store.y = 2 * x * y * vx + y * y * vy + 2 * z * y * vz + 2 * w * z * vx - z * z * vy + w * w * vy - 2 * x * w * vz - x * x * vy; - store.z = 2 * x * z * vx + 2 * y * z * vy + z * z * vz - 2 * w * y * vx - y * y * vz + 2 * w * x * vy - x * x * vz + w * w * vz; - } - return store; - } - - /** - * - * toString creates the string representation of this DQuaternion. The values of the quaternion are displaced (x, - * y, z, w), in the following manner:
    - * (x, y, z, w) - * - * @return the string representation of this object. - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return "(" + x + ", " + y + ", " + z + ", " + w + ")"; - } - - /** - * equals determines if two quaternions are logically equal, - * that is, if the values of (x, y, z, w) are the same for both quaternions. - * - * @param o - * the object to compare for equality - * @return true if they are equal, false otherwise. - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof DQuaternion)) { - return false; - } - - if (this == o) { - return true; - } - - DQuaternion comp = (DQuaternion) o; - if (Double.compare(x, comp.x) != 0) { - return false; - } - if (Double.compare(y, comp.y) != 0) { - return false; - } - if (Double.compare(z, comp.z) != 0) { - return false; - } - if (Double.compare(w, comp.w) != 0) { - return false; - } - return true; - } - - /** - * - * 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. - * - * @return the hashcode for this instance of DQuaternion. - * @see java.lang.Object#hashCode() - */ - @Override - public int hashCode() { - long hash = 37; - hash = 37 * hash + Double.doubleToLongBits(x); - hash = 37 * hash + Double.doubleToLongBits(y); - hash = 37 * hash + Double.doubleToLongBits(z); - hash = 37 * hash + Double.doubleToLongBits(w); - return (int) hash; - - } - - public void write(JmeExporter e) throws IOException { - OutputCapsule cap = e.getCapsule(this); - cap.write(x, "x", 0); - cap.write(y, "y", 0); - cap.write(z, "z", 0); - cap.write(w, "w", 1); - } - - public void read(JmeImporter e) throws IOException { - InputCapsule cap = e.getCapsule(this); - x = cap.readFloat("x", 0); - y = cap.readFloat("y", 0); - z = cap.readFloat("z", 0); - w = cap.readFloat("w", 1); - } - - @Override - public DQuaternion clone() { - try { - return (DQuaternion) super.clone(); - } catch (CloneNotSupportedException e) { - throw new AssertionError(); // can not happen - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java deleted file mode 100644 index 28fcda0c73..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/DTransform.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.math; - -import java.io.IOException; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.Transform; - -/** - * Started Date: Jul 16, 2004
    - *
    - * Represents a translation, rotation and scale in one object. - * - * This class's only purpose is to give better accuracy in floating point operations during computations. - * This is made by copying the original Transfrom class from jme3 core and removing unnecessary methods so that - * the class is smaller and easier to maintain. - * Should any other methods be needed, they will be added. - * - * @author Jack Lindamood - * @author Joshua Slack - * @author Marcin Roguski (Kaelthas) - */ -public final class DTransform implements Savable, Cloneable, java.io.Serializable { - private static final long serialVersionUID = 7812915425940606722L; - - private DQuaternion rotation; - private Vector3d translation; - private Vector3d scale; - - public DTransform() { - translation = new Vector3d(); - rotation = new DQuaternion(); - scale = new Vector3d(); - } - - public DTransform(Transform transform) { - translation = new Vector3d(transform.getTranslation()); - rotation = new DQuaternion(transform.getRotation()); - scale = new Vector3d(transform.getScale()); - } - - public Transform toTransform() { - return new Transform(translation.toVector3f(), rotation.toQuaternion(), scale.toVector3f()); - } - - public Matrix toMatrix() { - Matrix m = Matrix.identity(4); - m.setTranslation(translation); - m.setRotationQuaternion(rotation); - m.setScale(scale); - return m; - } - - /** - * Sets this translation to the given value. - * @param trans - * The new translation for this matrix. - * @return this - */ - public DTransform setTranslation(Vector3d trans) { - translation.set(trans); - return this; - } - - /** - * Sets this rotation to the given DQuaternion value. - * @param rot - * The new rotation for this matrix. - * @return this - */ - public DTransform setRotation(DQuaternion rot) { - rotation.set(rot); - return this; - } - - /** - * Sets this scale to the given value. - * @param scale - * The new scale for this matrix. - * @return this - */ - public DTransform setScale(Vector3d scale) { - this.scale.set(scale); - return this; - } - - /** - * Sets this scale to the given value. - * @param scale - * The new scale for this matrix. - * @return this - */ - public DTransform setScale(float scale) { - this.scale.set(scale, scale, scale); - return this; - } - - /** - * Return the translation vector in this matrix. - * @return translation vector. - */ - public Vector3d getTranslation() { - return translation; - } - - /** - * Return the rotation quaternion in this matrix. - * @return rotation quaternion. - */ - public DQuaternion getRotation() { - return rotation; - } - - /** - * Return the scale vector in this matrix. - * @return scale vector. - */ - public Vector3d getScale() { - return scale; - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" + "[ " + rotation.x + ", " + rotation.y + ", " + rotation.z + ", " + rotation.w + "]\n" + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]"; - } - - public void write(JmeExporter e) throws IOException { - OutputCapsule capsule = e.getCapsule(this); - capsule.write(rotation, "rot", new DQuaternion()); - capsule.write(translation, "translation", Vector3d.ZERO); - capsule.write(scale, "scale", Vector3d.UNIT_XYZ); - } - - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - - rotation = (DQuaternion) capsule.readSavable("rot", new DQuaternion()); - translation = (Vector3d) capsule.readSavable("translation", Vector3d.ZERO); - scale = (Vector3d) capsule.readSavable("scale", Vector3d.UNIT_XYZ); - } - - @Override - public DTransform clone() { - try { - DTransform tq = (DTransform) super.clone(); - tq.rotation = rotation.clone(); - tq.scale = scale.clone(); - tq.translation = translation.clone(); - return tq; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java deleted file mode 100644 index 0b05f2f1f5..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Matrix.java +++ /dev/null @@ -1,210 +0,0 @@ -package com.jme3.scene.plugins.blender.math; - -import java.text.DecimalFormat; - -import org.ejml.ops.CommonOps; -import org.ejml.simple.SimpleMatrix; -import org.ejml.simple.SimpleSVD; - -import com.jme3.math.FastMath; - -/** - * Encapsulates a 4x4 matrix - * - * - */ -public class Matrix extends SimpleMatrix { - private static final long serialVersionUID = 2396600537315902559L; - - public Matrix(int rows, int cols) { - super(rows, cols); - } - - /** - * Copy constructor - */ - public Matrix(SimpleMatrix m) { - super(m); - } - - public Matrix(double[][] data) { - super(data); - } - - public static Matrix identity(int size) { - Matrix result = new Matrix(size, size); - CommonOps.setIdentity(result.mat); - return result; - } - - public Matrix pseudoinverse() { - return this.pseudoinverse(1); - } - - @SuppressWarnings("unchecked") - public Matrix pseudoinverse(double lambda) { - SimpleSVD simpleSVD = this.svd(); - - SimpleMatrix U = simpleSVD.getU(); - SimpleMatrix S = simpleSVD.getW(); - SimpleMatrix V = simpleSVD.getV(); - - int N = Math.min(this.numRows(),this.numCols()); - double maxSingular = 0; - for( int i = 0; i < N; ++i ) { - if( S.get(i, i) > maxSingular ) { - maxSingular = S.get(i, i); - } - } - - double tolerance = FastMath.DBL_EPSILON * Math.max(this.numRows(),this.numCols()) * maxSingular; - for(int i=0;isetRotationQuaternion builds a rotation from a - * Quaternion. - * - * @param quat - * the quaternion to build the rotation from. - * @throws NullPointerException - * if quat is null. - */ - public void setRotationQuaternion(DQuaternion quat) { - quat.toRotationMatrix(this); - } - - public DTransform toTransform() { - DTransform result = new DTransform(); - result.setTranslation(this.toTranslationVector()); - result.setRotation(this.toRotationQuat()); - result.setScale(this.toScaleVector()); - return result; - } - - public Vector3d toTranslationVector() { - return new Vector3d(this.get(0, 3), this.get(1, 3), this.get(2, 3)); - } - - public DQuaternion toRotationQuat() { - DQuaternion quat = new DQuaternion(); - quat.fromRotationMatrix(this.get(0, 0), this.get(0, 1), this.get(0, 2), this.get(1, 0), this.get(1, 1), this.get(1, 2), this.get(2, 0), this.get(2, 1), this.get(2, 2)); - return quat; - } - - /** - * Retrieves the scale vector from the matrix and stores it into a given - * vector. - */ - public Vector3d toScaleVector() { - Vector3d result = new Vector3d(); - this.toScaleVector(result); - return result; - } - - /** - * Retrieves the scale vector from the matrix and stores it into a given - * vector. - * - * @param vector the vector where the scale will be stored - */ - public void toScaleVector(Vector3d vector) { - double scaleX = Math.sqrt(this.get(0, 0) * this.get(0, 0) + this.get(1, 0) * this.get(1, 0) + this.get(2, 0) * this.get(2, 0)); - double scaleY = Math.sqrt(this.get(0, 1) * this.get(0, 1) + this.get(1, 1) * this.get(1, 1) + this.get(2, 1) * this.get(2, 1)); - double scaleZ = Math.sqrt(this.get(0, 2) * this.get(0, 2) + this.get(1, 2) * this.get(1, 2) + this.get(2, 2) * this.get(2, 2)); - vector.set(scaleX, scaleY, scaleZ); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java deleted file mode 100644 index 92453cfa37..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/math/Vector3d.java +++ /dev/null @@ -1,867 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender.math; - -import java.io.IOException; -import java.io.Serializable; -import java.util.logging.Logger; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.export.Savable; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; - -/* - * -- Added *Local methods to cut down on object creation - JS - */ - -/** - * Vector3d defines a Vector for a three float value tuple. Vector3d can represent any three dimensional value, such as a - * vertex, a normal, etc. Utility methods are also included to aid in - * mathematical calculations. - * - * This class's only purpose is to give better accuracy in floating point operations during computations. - * This is made by copying the original Vector3f class from jme3 core and leaving only required methods and basic computation methods, so that - * the class is smaller and easier to maintain. - * Should any other methods be needed, they will be added. - * - * @author Mark Powell - * @author Joshua Slack - * @author Marcin Roguski (Kaelthas) - */ -public final class Vector3d implements Savable, Cloneable, Serializable { - private static final long serialVersionUID = 3090477054277293078L; - - private static final Logger LOGGER = Logger.getLogger(Vector3d.class.getName()); - - public final static Vector3d ZERO = new Vector3d(); - public final static Vector3d UNIT_XYZ = new Vector3d(1, 1, 1); - public final static Vector3d UNIT_X = new Vector3d(1, 0, 0); - public final static Vector3d UNIT_Y = new Vector3d(0, 1, 0); - public final static Vector3d UNIT_Z = new Vector3d(0, 0, 1); - - /** - * the x value of the vector. - */ - public double x; - - /** - * the y value of the vector. - */ - public double y; - - /** - * the z value of the vector. - */ - public double z; - - /** - * Constructor instantiates a new Vector3d with default - * values of (0,0,0). - * - */ - public Vector3d() { - } - - /** - * Constructor instantiates a new Vector3d with provides - * values. - * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. - */ - public Vector3d(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Constructor instantiates a new Vector3d that is a copy - * of the provided vector - * @param vector3f - * The Vector3f to copy - */ - public Vector3d(Vector3f vector3f) { - this(vector3f.x, vector3f.y, vector3f.z); - } - - public Vector3f toVector3f() { - return new Vector3f((float) x, (float) y, (float) z); - } - - /** - * set sets the x,y,z values of the vector based on passed - * parameters. - * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. - * @return this vector - */ - public Vector3d set(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - return this; - } - - /** - * set sets the x,y,z values of the vector by copying the - * supplied vector. - * - * @param vect - * the vector to copy. - * @return this vector - */ - public Vector3d set(Vector3d vect) { - return this.set(vect.x, vect.y, vect.z); - } - - /** - * - * add adds a provided vector to this vector creating a - * resultant vector which is returned. If the provided vector is null, null - * is returned. - * - * @param vec - * the vector to add to this. - * @return the resultant vector. - */ - public Vector3d add(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - return new Vector3d(x + vec.x, y + vec.y, z + vec.z); - } - - /** - * - * add adds the values of a provided vector storing the - * values in the supplied vector. - * - * @param vec - * the vector to add to this - * @param result - * the vector to store the result in - * @return result returns the supplied result vector. - */ - public Vector3d add(Vector3d vec, Vector3d result) { - result.x = x + vec.x; - result.y = y + vec.y; - result.z = z + vec.z; - return result; - } - - /** - * addLocal adds a provided vector to this vector internally, - * and returns a handle to this vector for easy chaining of calls. If the - * provided vector is null, null is returned. - * - * @param vec - * the vector to add to this vector. - * @return this - */ - public Vector3d addLocal(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - x += vec.x; - y += vec.y; - z += vec.z; - return this; - } - - /** - * - * add adds the provided values to this vector, creating a - * new vector that is then returned. - * - * @param addX - * the x value to add. - * @param addY - * the y value to add. - * @param addZ - * the z value to add. - * @return the result vector. - */ - public Vector3d add(double addX, double addY, double addZ) { - return new Vector3d(x + addX, y + addY, z + addZ); - } - - /** - * addLocal adds the provided values to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. - * - * @param addX - * value to add to x - * @param addY - * value to add to y - * @param addZ - * value to add to z - * @return this - */ - public Vector3d addLocal(double addX, double addY, double addZ) { - x += addX; - y += addY; - z += addZ; - return this; - } - - /** - * - * scaleAdd multiplies this vector by a scalar then adds the - * given Vector3d. - * - * @param scalar - * the value to multiply this vector by. - * @param add - * the value to add - */ - public Vector3d scaleAdd(double scalar, Vector3d add) { - x = x * scalar + add.x; - y = y * scalar + add.y; - z = z * scalar + add.z; - return this; - } - - /** - * - * scaleAdd multiplies the given vector by a scalar then adds - * the given vector. - * - * @param scalar - * the value to multiply this vector by. - * @param mult - * the value to multiply the scalar by - * @param add - * the value to add - */ - public Vector3d scaleAdd(double scalar, Vector3d mult, Vector3d add) { - x = mult.x * scalar + add.x; - y = mult.y * scalar + add.y; - z = mult.z * scalar + add.z; - return this; - } - - /** - * - * dot calculates the dot product of this vector with a - * provided vector. If the provided vector is null, 0 is returned. - * - * @param vec - * the vector to dot with this vector. - * @return the resultant dot product of this vector and a given vector. - */ - public double dot(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, 0 returned."); - return 0; - } - return x * vec.x + y * vec.y + z * vec.z; - } - - /** - * cross calculates the cross product of this vector with a - * parameter vector v. - * - * @param v - * the vector to take the cross product of with this. - * @return the cross product vector. - */ - public Vector3d cross(Vector3d v) { - return this.cross(v, null); - } - - /** - * cross calculates the cross product of this vector with a - * parameter vector v. The result is stored in result - * - * @param v - * the vector to take the cross product of with this. - * @param result - * the vector to store the cross product result. - * @return result, after receiving the cross product vector. - */ - public Vector3d cross(Vector3d v, Vector3d result) { - return this.cross(v.x, v.y, v.z, result); - } - - /** - * cross calculates the cross product of this vector with a - * parameter vector v. The result is stored in result - * - * @param otherX - * x component of the vector to take the cross product of with this. - * @param otherY - * y component of the vector to take the cross product of with this. - * @param otherZ - * z component of the vector to take the cross product of with this. - * @param result - * the vector to store the cross product result. - * @return result, after receiving the cross product vector. - */ - public Vector3d cross(double otherX, double otherY, double otherZ, Vector3d result) { - if (result == null) { - result = new Vector3d(); - } - double resX = y * otherZ - z * otherY; - double resY = z * otherX - x * otherZ; - double resZ = x * otherY - y * otherX; - result.set(resX, resY, resZ); - return result; - } - - /** - * crossLocal calculates the cross product of this vector - * with a parameter vector v. - * - * @param v - * the vector to take the cross product of with this. - * @return this. - */ - public Vector3d crossLocal(Vector3d v) { - return this.crossLocal(v.x, v.y, v.z); - } - - /** - * crossLocal calculates the cross product of this vector - * with a parameter vector v. - * - * @param otherX - * x component of the vector to take the cross product of with this. - * @param otherY - * y component of the vector to take the cross product of with this. - * @param otherZ - * z component of the vector to take the cross product of with this. - * @return this. - */ - public Vector3d crossLocal(double otherX, double otherY, double otherZ) { - double tempx = y * otherZ - z * otherY; - double tempy = z * otherX - x * otherZ; - z = x * otherY - y * otherX; - x = tempx; - y = tempy; - return this; - } - - /** - * length calculates the magnitude of this vector. - * - * @return the length or magnitude of the vector. - */ - public double length() { - return Math.sqrt(this.lengthSquared()); - } - - /** - * lengthSquared calculates the squared value of the - * magnitude of the vector. - * - * @return the magnitude squared of the vector. - */ - public double lengthSquared() { - return x * x + y * y + z * z; - } - - /** - * distanceSquared calculates the distance squared between - * this vector and vector v. - * - * @param v - * the second vector to determine the distance squared. - * @return the distance squared between the two vectors. - */ - public double distanceSquared(Vector3d v) { - double dx = x - v.x; - double dy = y - v.y; - double dz = z - v.z; - return dx * dx + dy * dy + dz * dz; - } - - /** - * distance calculates the distance between this vector and - * vector v. - * - * @param v - * the second vector to determine the distance. - * @return the distance between the two vectors. - */ - public double distance(Vector3d v) { - return Math.sqrt(this.distanceSquared(v)); - } - - /** - * - * mult multiplies this vector by a scalar. The resultant - * vector is returned. - * - * @param scalar - * the value to multiply this vector by. - * @return the new vector. - */ - public Vector3d mult(double scalar) { - return new Vector3d(x * scalar, y * scalar, z * scalar); - } - - /** - * - * mult multiplies this vector by a scalar. The resultant - * vector is supplied as the second parameter and returned. - * - * @param scalar - * the scalar to multiply this vector by. - * @param product - * the product to store the result in. - * @return product - */ - public Vector3d mult(double scalar, Vector3d product) { - if (null == product) { - product = new Vector3d(); - } - - product.x = x * scalar; - product.y = y * scalar; - product.z = z * scalar; - return product; - } - - /** - * multLocal multiplies this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. - * - * @param scalar - * the value to multiply this vector by. - * @return this - */ - public Vector3d multLocal(double scalar) { - x *= scalar; - y *= scalar; - z *= scalar; - return this; - } - - /** - * multLocal multiplies a provided vector by this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to multiply by this vector. - * @return this - */ - public Vector3d multLocal(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - x *= vec.x; - y *= vec.y; - z *= vec.z; - return this; - } - - /** - * multLocal multiplies this vector by 3 scalars - * internally, and returns a handle to this vector for easy chaining of - * calls. - * - * @param x - * @param y - * @param z - * @return this - */ - public Vector3d multLocal(double x, double y, double z) { - this.x *= x; - this.y *= y; - this.z *= z; - return this; - } - - /** - * multLocal multiplies a provided vector by this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to mult to this vector. - * @return this - */ - public Vector3d mult(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - return this.mult(vec, null); - } - - /** - * multLocal multiplies a provided vector by this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to mult to this vector. - * @param store - * result vector (null to create a new vector) - * @return this - */ - public Vector3d mult(Vector3d vec, Vector3d store) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - if (store == null) { - store = new Vector3d(); - } - return store.set(x * vec.x, y * vec.y, z * vec.z); - } - - /** - * divide divides the values of this vector by a scalar and - * returns the result. The values of this vector remain untouched. - * - * @param scalar - * the value to divide this vectors attributes by. - * @return the result Vector. - */ - public Vector3d divide(double scalar) { - scalar = 1f / scalar; - return new Vector3d(x * scalar, y * scalar, z * scalar); - } - - /** - * divideLocal divides this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. Dividing - * by zero will result in an exception. - * - * @param scalar - * the value to divides this vector by. - * @return this - */ - public Vector3d divideLocal(double scalar) { - scalar = 1f / scalar; - x *= scalar; - y *= scalar; - z *= scalar; - return this; - } - - /** - * divide divides the values of this vector by a scalar and - * returns the result. The values of this vector remain untouched. - * - * @param scalar - * the value to divide this vectors attributes by. - * @return the result Vector. - */ - public Vector3d divide(Vector3d scalar) { - return new Vector3d(x / scalar.x, y / scalar.y, z / scalar.z); - } - - /** - * divideLocal divides this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. Dividing - * by zero will result in an exception. - * - * @param scalar - * the value to divides this vector by. - * @return this - */ - public Vector3d divideLocal(Vector3d scalar) { - x /= scalar.x; - y /= scalar.y; - z /= scalar.z; - return this; - } - - /** - * - * negate returns the negative of this vector. All values are - * negated and set to a new vector. - * - * @return the negated vector. - */ - public Vector3d negate() { - return new Vector3d(-x, -y, -z); - } - - /** - * - * negateLocal negates the internal values of this vector. - * - * @return this. - */ - public Vector3d negateLocal() { - x = -x; - y = -y; - z = -z; - return this; - } - - /** - * - * subtract subtracts the values of a given vector from those - * of this vector creating a new vector object. If the provided vector is - * null, null is returned. - * - * @param vec - * the vector to subtract from this vector. - * @return the result vector. - */ - public Vector3d subtract(Vector3d vec) { - return new Vector3d(x - vec.x, y - vec.y, z - vec.z); - } - - /** - * subtractLocal subtracts a provided vector from this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to subtract - * @return this - */ - public Vector3d subtractLocal(Vector3d vec) { - if (null == vec) { - LOGGER.warning("Provided vector is null, null returned."); - return null; - } - x -= vec.x; - y -= vec.y; - z -= vec.z; - return this; - } - - /** - * - * subtract - * - * @param vec - * the vector to subtract from this - * @param result - * the vector to store the result in - * @return result - */ - public Vector3d subtract(Vector3d vec, Vector3d result) { - if (result == null) { - result = new Vector3d(); - } - result.x = x - vec.x; - result.y = y - vec.y; - result.z = z - vec.z; - return result; - } - - /** - * - * subtract subtracts the provided values from this vector, - * creating a new vector that is then returned. - * - * @param subtractX - * the x value to subtract. - * @param subtractY - * the y value to subtract. - * @param subtractZ - * the z value to subtract. - * @return the result vector. - */ - public Vector3d subtract(double subtractX, double subtractY, double subtractZ) { - return new Vector3d(x - subtractX, y - subtractY, z - subtractZ); - } - - /** - * subtractLocal subtracts the provided values from this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. - * - * @param subtractX - * the x value to subtract. - * @param subtractY - * the y value to subtract. - * @param subtractZ - * the z value to subtract. - * @return this - */ - public Vector3d subtractLocal(double subtractX, double subtractY, double subtractZ) { - x -= subtractX; - y -= subtractY; - z -= subtractZ; - return this; - } - - /** - * normalize returns the unit vector of this vector. - * - * @return unit vector of this vector. - */ - public Vector3d normalize() { - double length = x * x + y * y + z * z; - if (length != 1f && length != 0f) { - length = 1.0f / Math.sqrt(length); - return new Vector3d(x * length, y * length, z * length); - } - return this.clone(); - } - - /** - * normalizeLocal makes this vector into a unit vector of - * itself. - * - * @return this. - */ - public Vector3d normalizeLocal() { - // NOTE: this implementation is more optimized - // than the old jme normalize as this method - // is commonly used. - double length = x * x + y * y + z * z; - if (length != 1f && length != 0f) { - length = 1.0f / Math.sqrt(length); - x *= length; - y *= length; - z *= length; - } - return this; - } - - /** - * angleBetween returns (in radians) the angle between two vectors. - * It is assumed that both this vector and the given vector are unit vectors (iow, normalized). - * - * @param otherVector - * a unit vector to find the angle against - * @return the angle in radians. - */ - public double angleBetween(Vector3d otherVector) { - double dot = this.dot(otherVector); - // the vectors are normalized, but if they are parallel then the dot product migh get a value like: 1.000000000000000002 - // which is caused by floating point operations; in such case, the acos function will return NaN so we need to clamp this value - dot = FastMath.clamp((float) dot, -1, 1); - return Math.acos(dot); - } - - @Override - public Vector3d clone() { - try { - return (Vector3d) super.clone(); - } catch (CloneNotSupportedException e) { - throw new AssertionError(); // can not happen - } - } - - /** - * are these two vectors the same? they are is they both have the same x,y, - * and z values. - * - * @param o - * the object to compare for equality - * @return true if they are equal - */ - @Override - public boolean equals(Object o) { - if (!(o instanceof Vector3d)) { - return false; - } - - if (this == o) { - return true; - } - - Vector3d comp = (Vector3d) o; - if (Double.compare(x, comp.x) != 0) { - return false; - } - if (Double.compare(y, comp.y) != 0) { - return false; - } - if (Double.compare(z, comp.z) != 0) { - return false; - } - return true; - } - - /** - * hashCode returns a unique code for this vector object based - * on its values. If two vectors are logically equivalent, they will return - * the same hash code value. - * @return the hash code value of this vector. - */ - @Override - public int hashCode() { - long hash = 37; - hash += 37 * hash + Double.doubleToLongBits(x); - hash += 37 * hash + Double.doubleToLongBits(y); - hash += 37 * hash + Double.doubleToLongBits(z); - return (int) hash; - } - - /** - * toString returns the string representation of this vector. - * The format is: - * - * org.jme.math.Vector3d [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ] - * - * @return the string representation of this vector. - */ - @Override - public String toString() { - return "(" + x + ", " + y + ", " + z + ")"; - } - - public void write(JmeExporter e) throws IOException { - OutputCapsule capsule = e.getCapsule(this); - capsule.write(x, "x", 0); - capsule.write(y, "y", 0); - capsule.write(z, "z", 0); - } - - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - x = capsule.readDouble("x", 0); - y = capsule.readDouble("y", 0); - z = capsule.readDouble("z", 0); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java deleted file mode 100644 index 12aff2f4f3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Edge.java +++ /dev/null @@ -1,349 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.math.Vector3d; -import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate; - -/** - * A class that represents a single edge between two vertices. - * - * @author Marcin Roguski (Kaelthas) - */ -public class Edge { - private static final Logger LOGGER = Logger.getLogger(Edge.class.getName()); - - private static final int FLAG_EDGE_NOT_IN_FACE = 0x80; - - /** The vertices indexes. */ - private int index1, index2; - /** The vertices that can be set if we need and abstract edge outside the mesh (for computations). */ - private Vector3f v1, v2; - /** The weight of the edge. */ - private float crease; - /** A variable that indicates if this edge belongs to any face or not. */ - private boolean inFace; - /** The mesh that owns the edge. */ - private TemporalMesh temporalMesh; - - public Edge(Vector3f v1, Vector3f v2) { - this.v1 = v1 == null ? new Vector3f() : v1; - this.v2 = v2 == null ? new Vector3f() : v2; - index1 = 0; - index2 = 1; - } - - /** - * This constructor only stores the indexes of the vertices. The position vertices should be stored - * outside this class. - * @param index1 - * the first index of the edge - * @param index2 - * the second index of the edge - * @param crease - * the weight of the face - * @param inFace - * a variable that indicates if this edge belongs to any face or not - */ - public Edge(int index1, int index2, float crease, boolean inFace, TemporalMesh temporalMesh) { - this.index1 = index1; - this.index2 = index2; - this.crease = crease; - this.inFace = inFace; - this.temporalMesh = temporalMesh; - } - - @Override - public Edge clone() { - return new Edge(index1, index2, crease, inFace, temporalMesh); - } - - /** - * @return the first index of the edge - */ - public int getFirstIndex() { - return index1; - } - - /** - * @return the second index of the edge - */ - public int getSecondIndex() { - return index2; - } - - /** - * @return the first vertex of the edge - */ - public Vector3f getFirstVertex() { - return temporalMesh == null ? v1 : temporalMesh.getVertices().get(index1); - } - - /** - * @return the second vertex of the edge - */ - public Vector3f getSecondVertex() { - return temporalMesh == null ? v2 : temporalMesh.getVertices().get(index2); - } - - /** - * Returns the index other than the given. - * @param index - * index of the edge - * @return the remaining index number - */ - public int getOtherIndex(int index) { - if (index == index1) { - return index2; - } - if (index == index2) { - return index1; - } - throw new IllegalArgumentException("Cannot give the other index for [" + index + "] because this index does not exist in edge: " + this); - } - - /** - * @return the crease value of the edge (its weight) - */ - public float getCrease() { - return crease; - } - - /** - * @return true if the edge is used by at least one face and false otherwise - */ - public boolean isInFace() { - return inFace; - } - - /** - * @return the length of the edge - */ - public float getLength() { - return this.getFirstVertex().distance(this.getSecondVertex()); - } - - /** - * @return the mesh this edge belongs to - */ - public TemporalMesh getTemporalMesh() { - return temporalMesh; - } - - /** - * @return the centroid of the edge - */ - public Vector3f computeCentroid() { - return this.getFirstVertex().add(this.getSecondVertex()).divideLocal(2); - } - - /** - * Shifts indexes by a given amount. - * @param shift - * how much the indexes should be shifted - * @param predicate - * the predicate that verifies which indexes should be shifted; if null then all will be shifted - */ - public void shiftIndexes(int shift, IndexPredicate predicate) { - if (predicate == null) { - index1 += shift; - index2 += shift; - } else { - index1 += predicate.execute(index1) ? shift : 0; - index2 += predicate.execute(index2) ? shift : 0; - } - } - - /** - * Flips the order of the indexes. - */ - public void flipIndexes() { - int temp = index1; - index1 = index2; - index2 = temp; - } - - /** - * The crossing method first computes the points on both lines (that contain the edges) - * who are closest in distance. If the distance between points is smaller than FastMath.FLT_EPSILON - * the we consider them to be the same point (the lines cross). - * The second step is to check if both points are contained within the edges. - * - * The method of computing the crossing point is as follows: - * Let's assume that: - * (P0, P1) are the points of the first edge - * (Q0, Q1) are the points of the second edge - * - * u = P1 - P0 - * v = Q1 - Q0 - * - * This gives us the equations of two lines: - * L1: (x = P1x + ux*t1; y = P1y + uy*t1; z = P1z + uz*t1) - * L2: (x = P2x + vx*t2; y = P2y + vy*t2; z = P2z + vz*t2) - * - * Comparing the x and y of the first two equations for each line will allow us to compute t1 and t2 - * (which is implemented below). - * Using t1 and t2 we can compute (x, y, z) of each line and that will give us two points that we need to compare. - * - * @param edge - * the edge we check against crossing - * @return true if the edges cross and false otherwise - */ - public boolean cross(Edge edge) { - return this.getCrossPoint(edge) != null; - } - - /** - * The method computes the crossing pint of this edge and another edge. If - * there is no crossing then null is returned. - * - * @param edge - * the edge to compute corss point with - * @return cross point on null if none exist - */ - public Vector3f getCrossPoint(Edge edge) { - return this.getCrossPoint(edge, false, false); - } - - /** - * The method computes the crossing pint of this edge and another edge. If - * there is no crossing then null is returned. Also null is returned if the edges are parallel. - * This method also allows to get the crossing point of the straight lines that contain these edges if - * you set the 'extend' parameter to true. - * - * @param edge - * the edge to compute corss point with - * @param extendThisEdge - * set to true to find a crossing point along the whole - * straight that contains the current edge - * @param extendSecondEdge - * set to true to find a crossing point along the whole - * straight that contains the given edge - * @return cross point on null if none exist or the edges are parallel - */ - public Vector3f getCrossPoint(Edge edge, boolean extendThisEdge, boolean extendSecondEdge) { - Vector3d P1 = new Vector3d(this.getFirstVertex()); - Vector3d P2 = new Vector3d(edge.getFirstVertex()); - Vector3d u = new Vector3d(this.getSecondVertex()).subtract(P1).normalizeLocal(); - Vector3d v = new Vector3d(edge.getSecondVertex()).subtract(P2).normalizeLocal(); - - if(Math.abs(u.dot(v)) >= 1 - FastMath.DBL_EPSILON) { - // the edges are parallel; do not care about the crossing point - return null; - } - - double t1 = 0, t2 = 0; - if(u.x == 0 && v.x == 0) { - t2 = (u.z * (P2.y - P1.y) - u.y * (P2.z - P1.z)) / (u.y * v.z - u.z * v.y); - t1 = (P2.z - P1.z + v.z * t2) / u.z; - } else if(u.y == 0 && v.y == 0) { - t2 = (u.x * (P2.z - P1.z) - u.z * (P2.x - P1.x)) / (u.z * v.x - u.x * v.z); - t1 = (P2.x - P1.x + v.x * t2) / u.x; - } else if(u.z == 0 && v.z == 0) { - t2 = (u.x * (P2.y - P1.y) - u.y * (P2.x - P1.x)) / (u.y * v.x - u.x * v.y); - t1 = (P2.x - P1.x + v.x * t2) / u.x; - } else { - t2 = (P1.y * u.x - P1.x * u.y + P2.x * u.y - P2.y * u.x) / (v.y * u.x - u.y * v.x); - t1 = (P2.x - P1.x + v.x * t2) / u.x; - if(Math.abs(P1.z - P2.z + u.z * t1 - v.z * t2) > FastMath.FLT_EPSILON) { - return null; - } - } - Vector3d p1 = P1.add(u.mult(t1)); - Vector3d p2 = P2.add(v.mult(t2)); - - if (p1.distance(p2) <= FastMath.FLT_EPSILON) { - if(extendThisEdge && extendSecondEdge) { - return p1.toVector3f(); - } - // the lines cross, check if p1 and p2 are within the edges - Vector3d p = p1.subtract(P1); - double cos = p.dot(u) / p.length(); - if (extendThisEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - this.getLength() <= FastMath.FLT_EPSILON) { - // p1 is inside the first edge, lets check the other edge now - p = p2.subtract(P2); - cos = p.dot(v) / p.length(); - if(extendSecondEdge || p.length()<= FastMath.FLT_EPSILON || cos >= 1 - FastMath.FLT_EPSILON && p.length() - edge.getLength() <= FastMath.FLT_EPSILON) { - return p1.toVector3f(); - } - } - } - - return null; - } - - @Override - public String toString() { - String result = "Edge [" + index1 + ", " + index2 + "] {" + crease + "}"; - result += " (" + this.getFirstVertex() + " -> " + this.getSecondVertex() + ")"; - if (inFace) { - result += "[F]"; - } - return result; - } - - @Override - public int hashCode() { - // The hash code must be identical for the same two indexes, no matter their order. - final int prime = 31; - int result = 1; - int lowerIndex = Math.min(index1, index2); - int higherIndex = Math.max(index1, index2); - result = prime * result + lowerIndex; - result = prime * result + higherIndex; - return result; - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof Edge)) { - return false; - } - if (this == obj) { - return true; - } - Edge other = (Edge) obj; - return Math.min(index1, index2) == Math.min(other.index1, other.index2) && Math.max(index1, index2) == Math.max(other.index1, other.index2); - } - - /** - * The method loads all edges from the given mesh structure that does not belong to any face. - * @param meshStructure - * the mesh structure - * @param temporalMesh - * the owner of the edges - * @return all edges without faces - * @throws BlenderFileException - * an exception is thrown when problems with file reading occur - */ - public static List loadAll(Structure meshStructure, TemporalMesh temporalMesh) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading all edges that do not belong to any face from mesh: {0}", meshStructure.getName()); - List result = new ArrayList(); - - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - - if (pMEdge.isNotNull()) { - List edges = pMEdge.fetchData(); - for (Structure edge : edges) { - int flag = ((Number) edge.getFieldValue("flag")).intValue(); - - int v1 = ((Number) edge.getFieldValue("v1")).intValue(); - int v2 = ((Number) edge.getFieldValue("v2")).intValue(); - // I do not know why, but blender stores (possibly only sometimes) crease as negative values and shows positive in the editor - float crease = Math.abs(((Number) edge.getFieldValue("crease")).floatValue()); - boolean edgeInFace = (flag & Edge.FLAG_EDGE_NOT_IN_FACE) == 0; - result.add(new Edge(v1, v2, crease, edgeInFace, temporalMesh)); - } - } - LOGGER.log(Level.FINE, "Loaded {0} edges.", result.size()); - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java deleted file mode 100644 index 009e3c9085..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Face.java +++ /dev/null @@ -1,613 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that represents a single face in the mesh. The face is a polygon. Its minimum count of - * vertices is = 3. - * - * @author Marcin Roguski (Kaelthas) - */ -public class Face implements Comparator { - private static final Logger LOGGER = Logger.getLogger(Face.class.getName()); - - /** The indexes loop of the face. */ - private IndexesLoop indexes; - - private List triangulatedFaces; - /** Indicates if the face is smooth or solid. */ - private boolean smooth; - /** The material index of the face. */ - private int materialNumber; - /** UV coordinate sets attached to the face. The key is the set name and value are the UV coords. */ - private Map> faceUVCoords; - /** The vertex colors of the face. */ - private List vertexColors; - /** The temporal mesh the face belongs to. */ - private TemporalMesh temporalMesh; - - /** - * Creates a complete face with all available data. - * @param indexes - * the indexes of the face (required) - * @param smooth - * indicates if the face is smooth or solid - * @param materialNumber - * the material index of the face - * @param faceUVCoords - * UV coordinate sets of the face (optional) - * @param vertexColors - * the vertex colors of the face (optional) - * @param temporalMesh - * the temporal mesh the face belongs to (required) - */ - public Face(Integer[] indexes, boolean smooth, int materialNumber, Map> faceUVCoords, List vertexColors, TemporalMesh temporalMesh) { - this.setTemporalMesh(temporalMesh); - this.indexes = new IndexesLoop(indexes); - this.smooth = smooth; - this.materialNumber = materialNumber; - this.faceUVCoords = faceUVCoords; - this.temporalMesh = temporalMesh; - this.vertexColors = vertexColors; - } - - /** - * Default constructor. Used by the clone method. - */ - private Face() { - } - - @Override - public Face clone() { - Face result = new Face(); - result.indexes = indexes.clone(); - result.smooth = smooth; - result.materialNumber = materialNumber; - if (faceUVCoords != null) { - result.faceUVCoords = new HashMap>(faceUVCoords.size()); - for (Entry> entry : faceUVCoords.entrySet()) { - List uvs = new ArrayList(entry.getValue().size()); - for (Vector2f v : entry.getValue()) { - uvs.add(v.clone()); - } - result.faceUVCoords.put(entry.getKey(), uvs); - } - } - if (vertexColors != null) { - result.vertexColors = new ArrayList(vertexColors.size()); - for (byte[] colors : vertexColors) { - result.vertexColors.add(colors.clone()); - } - } - result.temporalMesh = temporalMesh; - return result; - } - - /** - * Returns the index at the given position in the index loop. If the given position is negative or exceeds - * the amount of vertices - it is being looped properly so that it always hits an index. - * For example getIndex(-1) will return the index before the 0 - in this case it will be the last one. - * @param indexPosition - * the index position - * @return index value at the given position - */ - private Integer getIndex(int indexPosition) { - if (indexPosition >= indexes.size()) { - indexPosition = indexPosition % indexes.size(); - } else if (indexPosition < 0) { - indexPosition = indexes.size() - -indexPosition % indexes.size(); - } - return indexes.get(indexPosition); - } - - /** - * @return the mesh this face belongs to - */ - public TemporalMesh getTemporalMesh() { - return temporalMesh; - } - - /** - * @return the original indexes of the face - */ - public IndexesLoop getIndexes() { - return indexes; - } - - /** - * @return the centroid of the face - */ - public Vector3f computeCentroid() { - Vector3f result = new Vector3f(); - List vertices = temporalMesh.getVertices(); - for (Integer index : indexes) { - result.addLocal(vertices.get(index)); - } - return result.divideLocal(indexes.size()); - } - - /** - * @return current indexes of the face (if it is already triangulated then more than one index group will be in the result list) - */ - public List> getCurrentIndexes() { - if (triangulatedFaces == null) { - return Arrays.asList(indexes.getAll()); - } - List> result = new ArrayList>(triangulatedFaces.size()); - for (IndexesLoop loop : triangulatedFaces) { - result.add(loop.getAll()); - } - return result; - } - - /** - * The method detaches the triangle from the face. This method keeps the indexes loop normalized - every index - * has only two neighbours. So if detaching the triangle causes a vertex to have more than two neighbours - it is - * also detached and returned as a result. - * The result is an empty list if no such situation happens. - * @param triangleIndexes - * the indexes of a triangle to be detached - * @return a list of faces that need to be detached as well in order to keep them normalized - * @throws BlenderFileException - * an exception is thrown when vertices of a face create more than one loop; this is found during path finding - */ - private List detachTriangle(Integer[] triangleIndexes) throws BlenderFileException { - LOGGER.fine("Detaching triangle."); - if (triangleIndexes.length != 3) { - throw new IllegalArgumentException("Cannot detach triangle with that does not have 3 indexes!"); - } - MeshHelper meshHelper = temporalMesh.getBlenderContext().getHelper(MeshHelper.class); - List detachedFaces = new ArrayList(); - List path = new ArrayList(indexes.size()); - - boolean[] edgeRemoved = new boolean[] { indexes.removeEdge(triangleIndexes[0], triangleIndexes[1]), indexes.removeEdge(triangleIndexes[0], triangleIndexes[2]), indexes.removeEdge(triangleIndexes[1], triangleIndexes[2]) }; - Integer[][] indexesPairs = new Integer[][] { new Integer[] { triangleIndexes[0], triangleIndexes[1] }, new Integer[] { triangleIndexes[0], triangleIndexes[2] }, new Integer[] { triangleIndexes[1], triangleIndexes[2] } }; - - for (int i = 0; i < 3; ++i) { - if (!edgeRemoved[i]) { - indexes.findPath(indexesPairs[i][0], indexesPairs[i][1], path); - if (path.size() == 0) { - indexes.findPath(indexesPairs[i][1], indexesPairs[i][0], path); - } - if (path.size() == 0) { - throw new IllegalStateException("Triangulation failed. Cannot find path between two indexes. Please apply triangulation in Blender as a workaround."); - } - if (detachedFaces.size() == 0 && path.size() < indexes.size()) { - Integer[] indexesSublist = path.toArray(new Integer[path.size()]); - detachedFaces.add(new Face(indexesSublist, smooth, materialNumber, meshHelper.selectUVSubset(this, indexesSublist), meshHelper.selectVertexColorSubset(this, indexesSublist), temporalMesh)); - for (int j = 0; j < path.size() - 1; ++j) { - indexes.removeEdge(path.get(j), path.get(j + 1)); - } - indexes.removeEdge(path.get(path.size() - 1), path.get(0)); - } else { - indexes.addEdge(path.get(path.size() - 1), path.get(0)); - } - } - } - - return detachedFaces; - } - - /** - * Sets the temporal mesh for the face. The given mesh cannot be null. - * @param temporalMesh - * the temporal mesh of the face - * @throws IllegalArgumentException - * thrown if given temporal mesh is null - */ - public void setTemporalMesh(TemporalMesh temporalMesh) { - if (temporalMesh == null) { - throw new IllegalArgumentException("No temporal mesh for the face given!"); - } - this.temporalMesh = temporalMesh; - } - - /** - * Flips the order of the indexes. - */ - public void flipIndexes() { - indexes.reverse(); - if (faceUVCoords != null) { - for (Entry> entry : faceUVCoords.entrySet()) { - Collections.reverse(entry.getValue()); - } - } - } - - /** - * Flips UV coordinates. - * @param u - * indicates if U coords should be flipped - * @param v - * indicates if V coords should be flipped - */ - public void flipUV(boolean u, boolean v) { - if (faceUVCoords != null) { - for (Entry> entry : faceUVCoords.entrySet()) { - for (Vector2f uv : entry.getValue()) { - uv.set(u ? 1 - uv.x : uv.x, v ? 1 - uv.y : uv.y); - } - } - } - } - - /** - * @return the UV sets of the face - */ - public Map> getUvSets() { - return faceUVCoords; - } - - /** - * @return current vertex count of the face - */ - public int vertexCount() { - return indexes.size(); - } - - /** - * The method triangulates the face. - */ - public TriangulationWarning triangulate() { - LOGGER.fine("Triangulating face."); - assert indexes.size() >= 3 : "Invalid indexes amount for face. 3 is the required minimum!"; - triangulatedFaces = new ArrayList(indexes.size() - 2); - Integer[] indexes = new Integer[3]; - TriangulationWarning warning = TriangulationWarning.NONE; - - try { - List facesToTriangulate = new ArrayList(Arrays.asList(this.clone())); - while (facesToTriangulate.size() > 0 && warning == TriangulationWarning.NONE) { - Face face = facesToTriangulate.remove(0); - // two special cases will improve the computations speed - if(face.getIndexes().size() == 3) { - triangulatedFaces.add(face.getIndexes().clone()); - } else { - int previousIndex1 = -1, previousIndex2 = -1, previousIndex3 = -1; - while (face.vertexCount() > 0) { - indexes[0] = face.getIndex(0); - indexes[1] = face.findClosestVertex(indexes[0], -1); - indexes[2] = face.findClosestVertex(indexes[0], indexes[1]); - - LOGGER.finer("Veryfying improper triangulation of the temporal mesh."); - if (indexes[0] < 0 || indexes[1] < 0 || indexes[2] < 0) { - warning = TriangulationWarning.CLOSEST_VERTS; - break; - } - if (previousIndex1 == indexes[0] && previousIndex2 == indexes[1] && previousIndex3 == indexes[2]) { - warning = TriangulationWarning.INFINITE_LOOP; - break; - } - previousIndex1 = indexes[0]; - previousIndex2 = indexes[1]; - previousIndex3 = indexes[2]; - - Arrays.sort(indexes, this); - facesToTriangulate.addAll(face.detachTriangle(indexes)); - triangulatedFaces.add(new IndexesLoop(indexes)); - } - } - } - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Errors occurred during face triangulation: {0}. The face will be triangulated with the most direct algorithm, but the results might not be identical to blender.", e.getLocalizedMessage()); - warning = TriangulationWarning.UNKNOWN; - } - if(warning != TriangulationWarning.NONE) { - LOGGER.finest("Triangulation the face using the most direct algorithm."); - indexes[0] = this.getIndex(0); - for (int i = 1; i < this.vertexCount() - 1; ++i) { - indexes[1] = this.getIndex(i); - indexes[2] = this.getIndex(i + 1); - triangulatedFaces.add(new IndexesLoop(indexes)); - } - } - return warning; - } - - /** - * A warning that indicates a problem with face triangulation. The warnings are collected and displayed once for each type for a mesh to - * avoid multiple warning loggings during triangulation. The amount of iterations can be really huge and logging every single failure would - * really slow down the importing process and make logs unreadable. - * - * @author Marcin Roguski (Kaelthas) - */ - public static enum TriangulationWarning { - NONE(null), - CLOSEST_VERTS("Unable to find two closest vertices while triangulating face."), - INFINITE_LOOP("Infinite loop detected during triangulation."), - UNKNOWN("There was an unknown problem with face triangulation. Please see log for details."); - - private String description; - - private TriangulationWarning(String description) { - this.description = description; - } - - @Override - public String toString() { - return description; - } - } - - /** - * @return true if the face is smooth and false otherwise - */ - public boolean isSmooth() { - return smooth; - } - - /** - * @return the material index of the face - */ - public int getMaterialNumber() { - return materialNumber; - } - - /** - * @return the vertices colord of the face - */ - public List getVertexColors() { - return vertexColors; - } - - @Override - public String toString() { - return "Face " + indexes; - } - - /** - * The method finds the closest vertex to the one specified by index. - * If the vertexToIgnore is positive than it will be ignored in the result. - * The closest vertex must be able to create an edge that is fully contained - * within the face and does not cross any other edges. Also if the - * vertexToIgnore is not negative then the condition that the edge between - * the found index and the one to ignore is inside the face must also be - * met. - * - * @param index - * the index of the vertex that needs to have found the nearest - * neighbour - * @param indexToIgnore - * the index to ignore in the result (pass -1 if none is to be - * ignored) - * @return the index of the closest vertex to the given one - */ - private int findClosestVertex(int index, int indexToIgnore) { - int result = -1; - List vertices = temporalMesh.getVertices(); - Vector3f v1 = vertices.get(index); - float distance = Float.MAX_VALUE; - for (int i : indexes) { - if (i != index && i != indexToIgnore) { - Vector3f v2 = vertices.get(i); - float d = v2.distance(v1); - if (d < distance && this.contains(new Edge(index, i, 0, true, temporalMesh)) && (indexToIgnore < 0 || this.contains(new Edge(indexToIgnore, i, 0, true, temporalMesh)))) { - result = i; - distance = d; - } - } - } - return result; - } - - /** - * The method verifies if the edge is contained within the face. - * It means it cannot cross any other edge and it must be inside the face and not outside of it. - * @param edge - * the edge to be checked - * @return true if the given edge is contained within the face and false otherwise - */ - private boolean contains(Edge edge) { - int index1 = edge.getFirstIndex(); - int index2 = edge.getSecondIndex(); - // check if the line between the vertices is not a border edge of the face - if (!indexes.areNeighbours(index1, index2)) { - for (int i = 0; i < indexes.size(); ++i) { - int i1 = this.getIndex(i - 1); - int i2 = this.getIndex(i); - // check if the edges have no common verts (because if they do, they cannot cross) - if (i1 != index1 && i1 != index2 && i2 != index1 && i2 != index2) { - if (edge.cross(new Edge(i1, i2, 0, false, temporalMesh))) { - return false; - } - } - } - - // computing the edge's middle point - Vector3f edgeMiddlePoint = edge.computeCentroid(); - // computing the edge that is perpendicular to the given edge and has a length of 1 (length actually does not matter) - Vector3f edgeVector = edge.getSecondVertex().subtract(edge.getFirstVertex()); - Vector3f edgeNormal = temporalMesh.getNormals().get(index1).cross(edgeVector).normalizeLocal(); - Edge e = new Edge(edgeMiddlePoint, edgeNormal.add(edgeMiddlePoint)); - // compute the vectors from the middle point to the crossing between the extended edge 'e' and other edges of the face - List crossingVectors = new ArrayList(); - for (int i = 0; i < indexes.size(); ++i) { - int i1 = this.getIndex(i); - int i2 = this.getIndex(i + 1); - Vector3f crossPoint = e.getCrossPoint(new Edge(i1, i2, 0, false, temporalMesh), true, false); - if(crossPoint != null) { - crossingVectors.add(crossPoint.subtractLocal(edgeMiddlePoint)); - } - } - if(crossingVectors.size() == 0) { - return false;// edges do not cross - } - - // use only distinct vertices (doubles may appear if the crossing point is a vertex) - List distinctCrossingVectors = new ArrayList(); - for(Vector3f cv : crossingVectors) { - double minDistance = Double.MAX_VALUE; - for(Vector3f dcv : distinctCrossingVectors) { - minDistance = Math.min(minDistance, dcv.distance(cv)); - } - if(minDistance > FastMath.FLT_EPSILON) { - distinctCrossingVectors.add(cv); - } - } - - if(distinctCrossingVectors.size() == 0) { - throw new IllegalStateException("There MUST be at least 2 crossing vertices!"); - } - // checking if all crossing vectors point to the same direction (if yes then the edge is outside the face) - float direction = Math.signum(distinctCrossingVectors.get(0).dot(edgeNormal));// if at least one vector has different direction that this - it means that the edge is inside the face - for(int i=1;i loadAll(Structure meshStructure, Map> userUVGroups, List verticesColors, TemporalMesh temporalMesh, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading all faces from mesh: {0}", meshStructure.getName()); - List result = new ArrayList(); - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - if (meshHelper.isBMeshCompatible(meshStructure)) { - LOGGER.fine("Reading BMesh."); - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - - if (pMPoly.isNotNull() && pMLoop.isNotNull()) { - List polys = pMPoly.fetchData(); - List loops = pMLoop.fetchData(); - for (Structure poly : polys) { - int materialNumber = ((Number) poly.getFieldValue("mat_nr")).intValue(); - int loopStart = ((Number) poly.getFieldValue("loopstart")).intValue(); - int totLoop = ((Number) poly.getFieldValue("totloop")).intValue(); - boolean smooth = (((Number) poly.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - Integer[] vertexIndexes = new Integer[totLoop]; - - for (int i = loopStart; i < loopStart + totLoop; ++i) { - vertexIndexes[i - loopStart] = ((Number) loops.get(i).getFieldValue("v")).intValue(); - } - - // uvs always must be added wheater we have texture or not - Map> uvCoords = new HashMap>(); - for (Entry> entry : userUVGroups.entrySet()) { - List uvs = entry.getValue().subList(loopStart, loopStart + totLoop); - uvCoords.put(entry.getKey(), new ArrayList(uvs)); - } - - List vertexColors = null; - if (verticesColors != null && verticesColors.size() > 0) { - vertexColors = new ArrayList(totLoop); - for (int i = loopStart; i < loopStart + totLoop; ++i) { - vertexColors.add(verticesColors.get(i)); - } - } - - result.add(new Face(vertexIndexes, smooth, materialNumber, uvCoords, vertexColors, temporalMesh)); - } - } - } else { - LOGGER.fine("Reading traditional faces."); - Pointer pMFace = (Pointer) meshStructure.getFieldValue("mface"); - List mFaces = pMFace.isNotNull() ? pMFace.fetchData() : null; - if (mFaces != null && mFaces.size() > 0) { - // indicates if the material with the specified number should have a texture attached - for (int i = 0; i < mFaces.size(); ++i) { - Structure mFace = mFaces.get(i); - int materialNumber = ((Number) mFace.getFieldValue("mat_nr")).intValue(); - boolean smooth = (((Number) mFace.getFieldValue("flag")).byteValue() & 0x01) != 0x00; - - int v1 = ((Number) mFace.getFieldValue("v1")).intValue(); - int v2 = ((Number) mFace.getFieldValue("v2")).intValue(); - int v3 = ((Number) mFace.getFieldValue("v3")).intValue(); - int v4 = ((Number) mFace.getFieldValue("v4")).intValue(); - - int vertCount = v4 == 0 ? 3 : 4; - - // uvs always must be added wheater we have texture or not - Map> faceUVCoords = new HashMap>(); - for (Entry> entry : userUVGroups.entrySet()) { - List uvCoordsForASingleFace = new ArrayList(vertCount); - for (int j = 0; j < vertCount; ++j) { - uvCoordsForASingleFace.add(entry.getValue().get(i * 4 + j)); - } - faceUVCoords.put(entry.getKey(), uvCoordsForASingleFace); - } - - List vertexColors = null; - if (verticesColors != null && verticesColors.size() > 0) { - vertexColors = new ArrayList(vertCount); - - vertexColors.add(verticesColors.get(v1)); - vertexColors.add(verticesColors.get(v2)); - vertexColors.add(verticesColors.get(v3)); - if (vertCount == 4) { - vertexColors.add(verticesColors.get(v4)); - } - } - - result.add(new Face(vertCount == 4 ? new Integer[] { v1, v2, v3, v4 } : new Integer[] { v1, v2, v3 }, smooth, materialNumber, faceUVCoords, vertexColors, temporalMesh)); - } - } - } - LOGGER.log(Level.FINE, "Loaded {0} faces.", result.size()); - return result; - } - - @Override - public int compare(Integer index1, Integer index2) { - return indexes.indexOf(index1) - indexes.indexOf(index2); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java deleted file mode 100644 index edec9b489b..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/IndexesLoop.java +++ /dev/null @@ -1,305 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; - -import com.jme3.scene.plugins.blender.file.BlenderFileException; - -/** - * This class represents the Face's indexes loop. It is a simplified implementation of directed graph. - * - * @author Marcin Roguski (Kaelthas) - */ -public class IndexesLoop implements Comparator, Iterable { - public static final IndexPredicate INDEX_PREDICATE_USE_ALL = new IndexPredicate() { - @Override - public boolean execute(Integer index) { - return true; - } - }; - - /** The indexes. */ - private List nodes; - /** The edges of the indexes graph. The key is the 'from' index and 'value' is - 'to' index. */ - private Map> edges = new HashMap>(); - - /** - * The constructor uses the given nodes in their give order. Each neighbour indexes will form an edge. - * @param nodes - * the nodes for the loop - */ - public IndexesLoop(Integer[] nodes) { - this.nodes = new ArrayList(Arrays.asList(nodes)); - this.prepareEdges(this.nodes); - } - - @Override - public IndexesLoop clone() { - return new IndexesLoop(nodes.toArray(new Integer[nodes.size()])); - } - - /** - * The method prepares edges for the given indexes. - * @param nodes - * the indexes - */ - private void prepareEdges(List nodes) { - for (int i = 0; i < nodes.size() - 1; ++i) { - if (edges.containsKey(nodes.get(i))) { - edges.get(nodes.get(i)).add(nodes.get(i + 1)); - } else { - edges.put(nodes.get(i), new ArrayList(Arrays.asList(nodes.get(i + 1)))); - } - } - edges.put(nodes.get(nodes.size() - 1), new ArrayList(Arrays.asList(nodes.get(0)))); - } - - /** - * @return the count of indexes - */ - public int size() { - return nodes.size(); - } - - /** - * Adds edge to the loop. - * @param from - * the start index - * @param to - * the end index - */ - public void addEdge(Integer from, Integer to) { - if (nodes.contains(from) && nodes.contains(to)) { - if (edges.containsKey(from) && !edges.get(from).contains(to)) { - edges.get(from).add(to); - } - } - } - - /** - * Removes edge from the face. The edge is removed if it already exists in the face. - * @param node1 - * the first index of the edge to be removed - * @param node2 - * the second index of the edge to be removed - * @return true if the edge was removed and false otherwise - */ - public boolean removeEdge(Integer node1, Integer node2) { - boolean edgeRemoved = false; - if (nodes.contains(node1) && nodes.contains(node2)) { - if (edges.containsKey(node1)) { - edgeRemoved |= edges.get(node1).remove(node2); - } - if (edges.containsKey(node2)) { - edgeRemoved |= edges.get(node2).remove(node1); - } - if (edgeRemoved) { - if (this.getNeighbourCount(node1) == 0) { - this.removeIndexes(node1); - } - if (this.getNeighbourCount(node2) == 0) { - this.removeIndexes(node2); - } - } - } - return edgeRemoved; - } - - /** - * Tells if the given indexes are neighbours. - * @param index1 - * the first index - * @param index2 - * the second index - * @return true if the given indexes are neighbours and false otherwise - */ - public boolean areNeighbours(Integer index1, Integer index2) { - if (index1.equals(index2) || !edges.containsKey(index1) || !edges.containsKey(index2)) { - return false; - } - return edges.get(index1).contains(index2) || edges.get(index2).contains(index1); - } - - /** - * Returns the value of the index located after the given one. Pointint the last index will return the first one. - * @param index - * the index value - * @return the value of 'next' index - */ - public Integer getNextIndex(Integer index) { - int i = nodes.indexOf(index); - return i == nodes.size() - 1 ? nodes.get(0) : nodes.get(i + 1); - } - - /** - * Returns the value of the index located before the given one. Pointint the first index will return the last one. - * @param index - * the index value - * @return the value of 'previous' index - */ - public Integer getPreviousIndex(Integer index) { - int i = nodes.indexOf(index); - return i == 0 ? nodes.get(nodes.size() - 1) : nodes.get(i - 1); - } - - /** - * The method shifts all indexes by a given value. - * @param shift - * the value to shift all indexes - * @param predicate - * the predicate that verifies which indexes should be shifted; if null then all will be shifted - */ - public void shiftIndexes(int shift, IndexPredicate predicate) { - if (predicate == null) { - predicate = INDEX_PREDICATE_USE_ALL; - } - List nodes = new ArrayList(this.nodes.size()); - for (Integer node : this.nodes) { - nodes.add(node + (predicate.execute(node) ? shift : 0)); - } - - Map> edges = new HashMap>(); - for (Entry> entry : this.edges.entrySet()) { - List neighbours = new ArrayList(entry.getValue().size()); - for (Integer neighbour : entry.getValue()) { - neighbours.add(neighbour + (predicate.execute(neighbour) ? shift : 0)); - } - edges.put(entry.getKey() + shift, neighbours); - } - - this.nodes = nodes; - this.edges = edges; - } - - /** - * Reverses the order of the indexes. - */ - public void reverse() { - Collections.reverse(nodes); - edges.clear(); - this.prepareEdges(nodes); - } - - /** - * Returns the neighbour count of the given index. - * @param index - * the index whose neighbour count will be checked - * @return the count of neighbours of the given index - */ - private int getNeighbourCount(Integer index) { - int result = 0; - if (edges.containsKey(index)) { - result = edges.get(index).size(); - for (List neighbours : edges.values()) { - if (neighbours.contains(index)) { - ++result; - } - } - } - return result; - } - - /** - * Returns the position of the given index in the loop. - * @param index - * the index of the face - * @return the indexe's position in the loop - */ - public int indexOf(Integer index) { - return nodes.indexOf(index); - } - - /** - * Returns the index at the given position. - * @param indexPosition - * the position of the index - * @return the index at a given position - */ - public Integer get(int indexPosition) { - return nodes.get(indexPosition); - } - - /** - * @return all indexes of the face - */ - public List getAll() { - return new ArrayList(nodes); - } - - /** - * The method removes all given indexes. - * @param indexes - * the indexes to be removed - */ - public void removeIndexes(Integer... indexes) { - for (Integer index : indexes) { - nodes.remove(index); - edges.remove(index); - for (List neighbours : edges.values()) { - neighbours.remove(index); - } - } - } - - /** - * The method finds the path between the given indexes. - * @param start - * the start index - * @param end - * the end index - * @param result - * a list containing indexes on the path from start to end (inclusive) - * @throws IllegalStateException - * an exception is thrown when the loop is not normalized (at least one - * index has more than 2 neighbours) - * @throws BlenderFileException - * an exception is thrown if the vertices of a face create more than one loop; this is thrown - * to prevent lack of memory errors during triangulation - */ - public void findPath(Integer start, Integer end, List result) throws BlenderFileException { - result.clear(); - Integer node = start; - while (!node.equals(end)) { - if (result.contains(node)) { - throw new BlenderFileException("Indexes of face have infinite loops!"); - } - result.add(node); - List nextSteps = edges.get(node); - if (nextSteps == null || nextSteps.size() == 0) { - result.clear();// no directed path from start to end - return; - } else if (nextSteps.size() == 1) { - node = nextSteps.get(0); - } else { - throw new BlenderFileException("Triangulation failed. Face has ambiguous indexes loop. Please triangulate your model in Blender as a workaround."); - } - } - result.add(end); - } - - @Override - public String toString() { - return "IndexesLoop " + nodes.toString(); - } - - @Override - public int compare(Integer i1, Integer i2) { - return nodes.indexOf(i1) - nodes.indexOf(i2); - } - - @Override - public Iterator iterator() { - return nodes.iterator(); - } - - public static interface IndexPredicate { - boolean execute(Integer index); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java deleted file mode 100644 index 0ec120bad3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshBuffers.java +++ /dev/null @@ -1,364 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.nio.Buffer; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.NavigableMap; -import java.util.TreeMap; - -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Format; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.util.BufferUtils; - -/** - * A class that aggregates the mesh data to prepare proper buffers. The buffers refer only to ONE material. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class MeshBuffers { - private static final int MAXIMUM_WEIGHTS_PER_VERTEX = 4; - - /** The material index. */ - private final int materialIndex; - /** The vertices. */ - private List verts = new ArrayList(); - /** The normals. */ - private List normals = new ArrayList(); - /** The UV coordinate sets. */ - private Map> uvCoords = new HashMap>(); - /** The vertex colors. */ - private List vertColors = new ArrayList(); - /** The indexes. */ - private List indexes = new ArrayList(); - /** The maximum weights count assigned to a single vertex. Used during weights normalization. */ - private int maximumWeightsPerVertex; - /** A list of mapping between weights and indexes. Each entry for the proper vertex. */ - private List> boneWeightAndIndexes = new ArrayList>(); - - /** - * Constructor stores only the material index value. - * @param materialIndex - * the material index - */ - public MeshBuffers(int materialIndex) { - this.materialIndex = materialIndex; - } - - /** - * @return the material index - */ - public int getMaterialIndex() { - return materialIndex; - } - - /** - * @return indexes buffer - */ - public Buffer getIndexBuffer() { - if (indexes.size() <= Short.MAX_VALUE) { - short[] indices = new short[indexes.size()]; - for (int i = 0; i < indexes.size(); ++i) { - indices[i] = indexes.get(i).shortValue(); - } - return BufferUtils.createShortBuffer(indices); - } else { - int[] indices = new int[indexes.size()]; - for (int i = 0; i < indexes.size(); ++i) { - indices[i] = indexes.get(i).intValue(); - } - return BufferUtils.createIntBuffer(indices); - } - } - - /** - * @return positions buffer - */ - public VertexBuffer getPositionsBuffer() { - VertexBuffer positionBuffer = new VertexBuffer(Type.Position); - Vector3f[] data = verts.toArray(new Vector3f[verts.size()]); - positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data)); - return positionBuffer; - } - - /** - * @return normals buffer - */ - public VertexBuffer getNormalsBuffer() { - VertexBuffer positionBuffer = new VertexBuffer(Type.Normal); - Vector3f[] data = normals.toArray(new Vector3f[normals.size()]); - positionBuffer.setupData(Usage.Static, 3, Format.Float, BufferUtils.createFloatBuffer(data)); - return positionBuffer; - } - - /** - * @return bone buffers - */ - public BoneBuffersData getBoneBuffers() { - BoneBuffersData result = null; - if (maximumWeightsPerVertex > 0) { - this.normalizeBoneBuffers(MAXIMUM_WEIGHTS_PER_VERTEX); - maximumWeightsPerVertex = MAXIMUM_WEIGHTS_PER_VERTEX; - - FloatBuffer weightsFloatData = BufferUtils.createFloatBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX); - ByteBuffer indicesData = BufferUtils.createByteBuffer(boneWeightAndIndexes.size() * MAXIMUM_WEIGHTS_PER_VERTEX); - int index = 0; - for (Map boneBuffersData : boneWeightAndIndexes) { - if (boneBuffersData.size() > 0) { - int count = 0; - for (Entry entry : boneBuffersData.entrySet()) { - weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getKey()); - indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX + count, entry.getValue().byteValue()); - ++count; - } - } else { - // if no bone is assigned to this vertex then attach it to the 0-indexed root bone - weightsFloatData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, 1.0f); - indicesData.put(index * MAXIMUM_WEIGHTS_PER_VERTEX, (byte) 0); - } - ++index; - } - VertexBuffer verticesWeights = new VertexBuffer(Type.BoneWeight); - verticesWeights.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.Float, weightsFloatData); - - VertexBuffer verticesWeightsIndices = new VertexBuffer(Type.BoneIndex); - verticesWeightsIndices.setupData(Usage.CpuOnly, maximumWeightsPerVertex, Format.UnsignedByte, indicesData); - - result = new BoneBuffersData(maximumWeightsPerVertex, verticesWeights, verticesWeightsIndices); - } - - return result; - } - - /** - * @return UV coordinates sets - */ - public Map> getUvCoords() { - return uvCoords; - } - - /** - * @return true if vertex colors are used and false otherwise - */ - public boolean areVertexColorsUsed() { - return vertColors.size() > 0; - } - - /** - * @return vertex colors buffer - */ - public ByteBuffer getVertexColorsBuffer() { - ByteBuffer result = null; - if (vertColors.size() > 0) { - result = BufferUtils.createByteBuffer(4 * vertColors.size()); - for (byte[] v : vertColors) { - if (v != null) { - result.put(v[0]).put(v[1]).put(v[2]).put(v[3]); - } else { - result.put((byte) 0).put((byte) 0).put((byte) 0).put((byte) 0); - } - } - result.flip(); - } - return result; - } - - /** - * @return true if indexes can be shorts' and false if they need to be ints' - */ - public boolean isShortIndexBuffer() { - return indexes.size() <= Short.MAX_VALUE; - } - - /** - * Appends a vertex and normal to the buffers. - * @param vert - * vertex - * @param normal - * normal vector - */ - public void append(Vector3f vert, Vector3f normal) { - int index = this.indexOf(vert, normal, null); - if (index >= 0) { - indexes.add(index); - } else { - indexes.add(verts.size()); - verts.add(vert); - normals.add(normal); - } - } - - /** - * Appends the face data to the buffers. - * @param smooth - * tells if the face is smooth or flat - * @param verts - * the vertices - * @param normals - * the normals - * @param uvCoords - * the UV coordinates - * @param vertColors - * the vertex colors - * @param vertexGroups - * the vertex groups - */ - public void append(boolean smooth, Vector3f[] verts, Vector3f[] normals, Map> uvCoords, byte[][] vertColors, List> vertexGroups) { - if (verts.length != normals.length) { - throw new IllegalArgumentException("The amount of verts and normals MUST be equal!"); - } - if (vertColors != null && vertColors.length != verts.length) { - throw new IllegalArgumentException("The amount of vertex colors and vertices MUST be equal!"); - } - if (vertexGroups.size() != 0 && vertexGroups.size() != verts.length) { - throw new IllegalArgumentException("The amount of (if given) vertex groups and vertices MUST be equal!"); - } - - if (!smooth) { - // make the normals perpendicular to the face - normals[0] = normals[1] = normals[2] = FastMath.computeNormal(verts[0], verts[1], verts[2]); - } - - for (int i = 0; i < verts.length; ++i) { - int index = -1; - Map uvCoordsForVertex = this.getUVsForVertex(i, uvCoords); - if (smooth && (index = this.indexOf(verts[i], normals[i], uvCoordsForVertex)) >= 0) { - indexes.add(index); - } else { - indexes.add(this.verts.size()); - this.verts.add(verts[i]); - this.normals.add(normals[i]); - this.vertColors.add(vertColors[i]); - - if (uvCoords != null && uvCoords.size() > 0) { - for (Entry> entry : uvCoords.entrySet()) { - if (this.uvCoords.containsKey(entry.getKey())) { - this.uvCoords.get(entry.getKey()).add(entry.getValue().get(i)); - } else { - List uvs = new ArrayList(); - uvs.add(entry.getValue().get(i)); - this.uvCoords.put(entry.getKey(), uvs); - } - } - } - - if (vertexGroups != null && vertexGroups.size() > 0) { - Map group = vertexGroups.get(i); - maximumWeightsPerVertex = Math.max(maximumWeightsPerVertex, group.size()); - boneWeightAndIndexes.add(new TreeMap(group)); - } - } - } - } - - /** - * Returns UV coordinates assigned for the vertex with the proper index. - * @param vertexIndex - * the index of the vertex - * @param uvs - * all UV coordinates we search in - * @return a set of UV coordinates assigned to the given vertex - */ - private Map getUVsForVertex(int vertexIndex, Map> uvs) { - if (uvs == null || uvs.size() == 0) { - return null; - } - Map result = new HashMap(uvs.size()); - for (Entry> entry : uvs.entrySet()) { - result.put(entry.getKey(), entry.getValue().get(vertexIndex)); - } - return result; - } - - /** - * The method returns an index of a vertex described by the given data. - * The method tries to find a vertex that mathes the given data. If it does it means - * that such vertex is already used. - * @param vert - * the vertex position coordinates - * @param normal - * the vertex's normal vector - * @param uvCoords - * the UV coords of the vertex - * @return index of the found vertex of -1 - */ - private int indexOf(Vector3f vert, Vector3f normal, Map uvCoords) { - for (int i = 0; i < verts.size(); ++i) { - if (verts.get(i).equals(vert) && normals.get(i).equals(normal)) { - if (uvCoords != null && uvCoords.size() > 0) { - for (Entry entry : uvCoords.entrySet()) { - List uvs = this.uvCoords.get(entry.getKey()); - if (uvs == null) { - return -1; - } - if (!uvs.get(i).equals(entry.getValue())) { - return -1; - } - } - } - return i; - } - } - return -1; - } - - /** - * The method normalizes the weights and bone indexes data. - * First it truncates the amount to MAXIMUM_WEIGHTS_PER_VERTEX because this is how many weights JME can handle. - * Next it normalizes the weights so that the sum of all verts is 1. - * @param maximumSize - * the maximum size that the data will be truncated to (usually: MAXIMUM_WEIGHTS_PER_VERTEX) - */ - private void normalizeBoneBuffers(int maximumSize) { - for (TreeMap group : boneWeightAndIndexes) { - if (group.size() > maximumSize) { - NavigableMap descendingWeights = group.descendingMap(); - while (descendingWeights.size() > maximumSize) { - descendingWeights.pollLastEntry(); - } - } - - // normalizing the weights so that the sum of the values is equal to '1' - TreeMap normalizedGroup = new TreeMap(); - float sum = 0; - for (Entry entry : group.entrySet()) { - sum += entry.getKey(); - } - - if (sum != 0 && sum != 1) { - for (Entry entry : group.entrySet()) { - normalizedGroup.put(entry.getKey() / sum, entry.getValue()); - } - group.clear(); - group.putAll(normalizedGroup); - } - } - } - - /** - * A class that gathers the data for mesh bone buffers. - * Added to increase code readability. - * - * @author Marcin Roguski (Kaelthas) - */ - public static class BoneBuffersData { - public final int maximumWeightsPerVertex; - public final VertexBuffer verticesWeights; - public final VertexBuffer verticesWeightsIndices; - - public BoneBuffersData(int maximumWeightsPerVertex, VertexBuffer verticesWeights, VertexBuffer verticesWeightsIndices) { - this.maximumWeightsPerVertex = maximumWeightsPerVertex; - this.verticesWeights = verticesWeights; - this.verticesWeightsIndices = verticesWeightsIndices; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java deleted file mode 100644 index 6a86ffecfa..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/MeshHelper.java +++ /dev/null @@ -1,381 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.blender.meshes; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.scene.plugins.blender.objects.Properties; - -/** - * A class that is used in mesh calculations. - * - * @author Marcin Roguski (Kaelthas) - */ -public class MeshHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(MeshHelper.class.getName()); - - /** A type of UV data layer in traditional faced mesh (triangles or quads). */ - public static final int UV_DATA_LAYER_TYPE_FMESH = 5; - /** A type of UV data layer in bmesh type. */ - public static final int UV_DATA_LAYER_TYPE_BMESH = 16; - - /** A material used for single lines and points. */ - private Material blackUnshadedMaterial; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in different blender - * versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public MeshHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * Converts the mesh structure into temporal mesh. - * The temporal mesh is stored in blender context and here always a clone is being returned because the mesh might - * be modified by modifiers. - * - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @return temporal mesh read from the given structure - * @throws BlenderFileException - * an exception is thrown when problems with reading blend file occur - */ - public TemporalMesh toTemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading temporal mesh named: {0}.", meshStructure.getName()); - TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH); - if (temporalMesh != null) { - LOGGER.fine("The mesh is already loaded. Returning its clone."); - return temporalMesh.clone(); - } - - if ("ID".equals(meshStructure.getType())) { - LOGGER.fine("Loading mesh from external blend file."); - return (TemporalMesh) this.loadLibrary(meshStructure); - } - - String name = meshStructure.getName(); - LOGGER.log(Level.FINE, "Reading mesh: {0}.", name); - temporalMesh = new TemporalMesh(meshStructure, blenderContext); - - LOGGER.fine("Loading materials."); - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - temporalMesh.setMaterials(materialHelper.getMaterials(meshStructure, blenderContext)); - - LOGGER.fine("Reading custom properties."); - Properties properties = this.loadProperties(meshStructure, blenderContext); - temporalMesh.setProperties(properties); - - blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, meshStructure); - blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.TEMPORAL_MESH, temporalMesh); - return temporalMesh.clone(); - } - - /** - * Tells if the given mesh structure supports BMesh. - * - * @param meshStructure - * the mesh structure - * @return true if BMesh is supported and false otherwise - */ - public boolean isBMeshCompatible(Structure meshStructure) { - Pointer pMLoop = (Pointer) meshStructure.getFieldValue("mloop"); - Pointer pMPoly = (Pointer) meshStructure.getFieldValue("mpoly"); - return pMLoop != null && pMPoly != null && pMLoop.isNotNull() && pMPoly.isNotNull(); - } - - /** - * This method returns the vertices: a list of vertex positions and a list - * of vertex normals. - * - * @param meshStructure - * the structure containing the mesh data - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - @SuppressWarnings("unchecked") - public void loadVerticesAndNormals(Structure meshStructure, List vertices, List normals) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading vertices and normals from mesh: {0}.", meshStructure.getName()); - int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); - if (count > 0) { - Pointer pMVert = (Pointer) meshStructure.getFieldValue("mvert"); - List mVerts = pMVert.fetchData(); - Vector3f co = null, no = null; - if (fixUpAxis) { - for (int i = 0; i < count; ++i) { - DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(2).floatValue(), -coordinates.get(1).floatValue()); - vertices.add(co); - - DynamicArray norm = (DynamicArray) mVerts.get(i).getFieldValue("no"); - no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f, -norm.get(1).shortValue() / 32767.0f); - normals.add(no); - } - } else { - for (int i = 0; i < count; ++i) { - DynamicArray coordinates = (DynamicArray) mVerts.get(i).getFieldValue("co"); - co = new Vector3f(coordinates.get(0).floatValue(), coordinates.get(1).floatValue(), coordinates.get(2).floatValue()); - vertices.add(co); - - DynamicArray norm = (DynamicArray) mVerts.get(i).getFieldValue("no"); - no = new Vector3f(norm.get(0).shortValue() / 32767.0f, norm.get(1).shortValue() / 32767.0f, norm.get(2).shortValue() / 32767.0f); - normals.add(no); - } - } - } - LOGGER.log(Level.FINE, "Loaded {0} vertices and normals.", vertices.size()); - } - - /** - * This method returns the vertices colors. Each vertex is stored in byte[4] array. - * - * @param meshStructure - * the structure containing the mesh data - * @param blenderContext - * the blender context - * @return a list of vertices colors, each color belongs to a single vertex or empty list of colors are not specified - * @throws BlenderFileException - * this exception is thrown when the blend file structure is somehow invalid or corrupted - */ - public List loadVerticesColors(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading vertices colors from mesh: {0}.", meshStructure.getName()); - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - Pointer pMCol = (Pointer) meshStructure.getFieldValue(meshHelper.isBMeshCompatible(meshStructure) ? "mloopcol" : "mcol"); - List verticesColors = new ArrayList(); - // it was likely a bug in blender untill version 2.63 (the blue and red factors were misplaced in their structure) - // so we need to put them right - boolean useBGRA = blenderContext.getBlenderVersion() < 263; - if (pMCol.isNotNull()) { - List mCol = pMCol.fetchData(); - for (Structure color : mCol) { - byte r = ((Number) color.getFieldValue("r")).byteValue(); - byte g = ((Number) color.getFieldValue("g")).byteValue(); - byte b = ((Number) color.getFieldValue("b")).byteValue(); - byte a = ((Number) color.getFieldValue("a")).byteValue(); - verticesColors.add(useBGRA ? new byte[] { b, g, r, a } : new byte[] { r, g, b, a }); - } - } - return verticesColors; - } - - /** - * The method loads the UV coordinates. The result is a map where the key is the user's UV set name and the values are UV coordinates. - * But depending on the mesh type (triangle/quads or bmesh) the lists in the map have different meaning. - * For bmesh they are enlisted just like they are stored in the blend file (in loops). - * For traditional faces every 4 UV's should be assigned for a single face. - * @param meshStructure - * the mesh structure - * @return a map that sorts UV coordinates between different UV sets - * @throws BlenderFileException - * an exception is thrown when problems with blend file occur - */ - @SuppressWarnings("unchecked") - public LinkedHashMap> loadUVCoordinates(Structure meshStructure) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading UV coordinates from mesh: {0}.", meshStructure.getName()); - LinkedHashMap> result = new LinkedHashMap>(); - if (this.isBMeshCompatible(meshStructure)) { - // in this case the UV's are assigned to vertices (an array is the same length as the vertex array) - Structure loopData = (Structure) meshStructure.getFieldValue("ldata"); - Pointer pLoopDataLayers = (Pointer) loopData.getFieldValue("layers"); - List loopDataLayers = pLoopDataLayers.fetchData(); - for (Structure structure : loopDataLayers) { - Pointer p = (Pointer) structure.getFieldValue("data"); - if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_BMESH) { - String uvSetName = structure.getFieldValue("name").toString(); - List uvsStructures = p.fetchData(); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray loopUVS = (DynamicArray) uvStructure.getFieldValue("uv"); - uvs.add(new Vector2f(loopUVS.get(0).floatValue(), loopUVS.get(1).floatValue())); - } - result.put(uvSetName, uvs); - } - } - } else { - // in this case UV's are assigned to faces (the array has the same length as the faces count) - Structure facesData = (Structure) meshStructure.getFieldValue("fdata"); - Pointer pFacesDataLayers = (Pointer) facesData.getFieldValue("layers"); - if (pFacesDataLayers.isNotNull()) { - List facesDataLayers = pFacesDataLayers.fetchData(); - for (Structure structure : facesDataLayers) { - Pointer p = (Pointer) structure.getFieldValue("data"); - if (p.isNotNull() && ((Number) structure.getFieldValue("type")).intValue() == MeshHelper.UV_DATA_LAYER_TYPE_FMESH) { - String uvSetName = structure.getFieldValue("name").toString(); - List uvsStructures = p.fetchData(); - List uvs = new ArrayList(uvsStructures.size()); - for (Structure uvStructure : uvsStructures) { - DynamicArray mFaceUVs = (DynamicArray) uvStructure.getFieldValue("uv"); - uvs.add(new Vector2f(mFaceUVs.get(0).floatValue(), mFaceUVs.get(1).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(2).floatValue(), mFaceUVs.get(3).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(4).floatValue(), mFaceUVs.get(5).floatValue())); - uvs.add(new Vector2f(mFaceUVs.get(6).floatValue(), mFaceUVs.get(7).floatValue())); - } - result.put(uvSetName, uvs); - } - } - } - } - return result; - } - - /** - * Loads all vertices groups. - * @param meshStructure - * the mesh structure - * @return a list of vertex groups for every vertex in the mesh - * @throws BlenderFileException - * an exception is thrown when problems with blend file occur - */ - public List> loadVerticesGroups(Structure meshStructure) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading vertices groups from mesh: {0}.", meshStructure.getName()); - List> result = new ArrayList>(); - - Structure parent = blenderContext.peekParent(); - if(parent != null) { - // the mesh might be saved without its parent (it is then unused) - Structure defbase = (Structure) parent.getFieldValue("defbase"); - List groupNames = new ArrayList(); - List defs = defbase.evaluateListBase(); - - if(!defs.isEmpty()) { - for (Structure def : defs) { - groupNames.add(def.getFieldValue("name").toString()); - } - - Pointer pDvert = (Pointer) meshStructure.getFieldValue("dvert");// dvert = DeformVERTices - if (pDvert.isNotNull()) {// assigning weights and bone indices - List dverts = pDvert.fetchData(); - for (Structure dvert : dverts) { - Map weightsForVertex = new HashMap(); - Pointer pDW = (Pointer) dvert.getFieldValue("dw"); - if (pDW.isNotNull()) { - List dw = pDW.fetchData(); - for (Structure deformWeight : dw) { - int groupIndex = ((Number) deformWeight.getFieldValue("def_nr")).intValue(); - float weight = ((Number) deformWeight.getFieldValue("weight")).floatValue(); - String groupName = groupNames.get(groupIndex); - - weightsForVertex.put(groupName, weight); - } - } - result.add(weightsForVertex); - } - } - } - } - return result; - } - - /** - * Selects the proper subsets of UV coordinates for the given sublist of indexes. - * @param face - * the face with the original UV sets - * @param indexesSublist - * the sub list of indexes - * @return a map of UV coordinates subsets - */ - public Map> selectUVSubset(Face face, Integer... indexesSublist) { - Map> result = null; - if (face.getUvSets() != null) { - result = new HashMap>(); - for (Entry> entry : face.getUvSets().entrySet()) { - List uvs = new ArrayList(indexesSublist.length); - for (Integer index : indexesSublist) { - uvs.add(entry.getValue().get(face.getIndexes().indexOf(index))); - } - result.put(entry.getKey(), uvs); - } - } - return result; - } - - /** - * Selects the proper subsets of vertex colors for the given sublist of indexes. - * @param face - * the face with the original vertex colors - * @param indexesSublist - * the sub list of indexes - * @return a sublist of vertex colors - */ - public List selectVertexColorSubset(Face face, Integer... indexesSublist) { - List result = null; - List vertexColors = face.getVertexColors(); - if (vertexColors != null) { - result = new ArrayList(indexesSublist.length); - for (Integer index : indexesSublist) { - result.add(vertexColors.get(face.getIndexes().indexOf(index))); - } - } - return result; - } - - /** - * Returns the black unshaded material. It is used for lines and points because that is how blender - * renders it. - * @param blenderContext - * the blender context - * @return black unshaded material - */ - public synchronized Material getBlackUnshadedMaterial(BlenderContext blenderContext) { - if (blackUnshadedMaterial == null) { - blackUnshadedMaterial = new Material(blenderContext.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - blackUnshadedMaterial.setColor("Color", ColorRGBA.Black); - } - return blackUnshadedMaterial; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java deleted file mode 100644 index 04f705e5b3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/Point.java +++ /dev/null @@ -1,93 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.util.ArrayList; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate; - -/** - * A class that represents a single point on the scene that is not a part of an edge. - * - * @author Marcin Roguski (Kaelthas) - */ -public class Point { - private static final Logger LOGGER = Logger.getLogger(Point.class.getName()); - - /** The point's index. */ - private int index; - - /** - * Constructs a point for a given index. - * @param index - * the index of the point - */ - public Point(int index) { - this.index = index; - } - - @Override - public Point clone() { - return new Point(index); - } - - /** - * @return the index of the point - */ - public int getIndex() { - return index; - } - - /** - * The method shifts the index by a given value. - * @param shift - * the value to shift the index - * @param predicate - * the predicate that verifies which indexes should be shifted; if null then all will be shifted - */ - public void shiftIndexes(int shift, IndexPredicate predicate) { - if (predicate == null || predicate.execute(index)) { - index += shift; - } - } - - /** - * Loads all points of the mesh that do not belong to any edge. - * @param meshStructure - * the mesh structure - * @return a list of points - * @throws BlenderFileException - * an exception is thrown when problems with file reading occur - */ - public static List loadAll(Structure meshStructure) throws BlenderFileException { - LOGGER.log(Level.FINE, "Loading all points that do not belong to any edge from mesh: {0}", meshStructure.getName()); - List result = new ArrayList(); - - Pointer pMEdge = (Pointer) meshStructure.getFieldValue("medge"); - if (pMEdge.isNotNull()) { - int count = ((Number) meshStructure.getFieldValue("totvert")).intValue(); - Set unusedVertices = new HashSet(count); - for (int i = 0; i < count; ++i) { - unusedVertices.add(i); - } - - List edges = pMEdge.fetchData(); - for (Structure edge : edges) { - unusedVertices.remove(((Number) edge.getFieldValue("v1")).intValue()); - unusedVertices.remove(((Number) edge.getFieldValue("v2")).intValue()); - } - - for (Integer unusedIndex : unusedVertices) { - result.add(new Point(unusedIndex)); - } - } - LOGGER.log(Level.FINE, "Loaded {0} points.", result.size()); - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java deleted file mode 100644 index 1b42b0c39e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/meshes/TemporalMesh.java +++ /dev/null @@ -1,767 +0,0 @@ -package com.jme3.scene.plugins.blender.meshes; - -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingVolume; -import com.jme3.material.Material; -import com.jme3.material.RenderState.FaceCullMode; -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Mesh.Mode; -import com.jme3.scene.Node; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.VertexBuffer.Usage; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.meshes.Face.TriangulationWarning; -import com.jme3.scene.plugins.blender.meshes.MeshBuffers.BoneBuffersData; -import com.jme3.scene.plugins.blender.modifiers.Modifier; -import com.jme3.scene.plugins.blender.objects.Properties; - -/** - * The class extends Geometry so that it can be temporalily added to the object's node. - * Later each such node's child will be transformed into a list of geometries. - * - * @author Marcin Roguski (Kaelthas) - */ -public class TemporalMesh extends Geometry { - private static final Logger LOGGER = Logger.getLogger(TemporalMesh.class.getName()); - /** A minimum weight value. */ - private static final double MINIMUM_BONE_WEIGHT = FastMath.DBL_EPSILON; - - /** The blender context. */ - protected final BlenderContext blenderContext; - - /** The mesh's structure. */ - protected final Structure meshStructure; - - /** Loaded vertices. */ - protected List vertices = new ArrayList(); - /** Loaded normals. */ - protected List normals = new ArrayList(); - /** Loaded vertex groups. */ - protected List> vertexGroups = new ArrayList>(); - /** Loaded vertex colors. */ - protected List verticesColors = new ArrayList(); - - /** Materials used by the mesh. */ - protected MaterialContext[] materials; - /** The properties of the mesh. */ - protected Properties properties; - /** The bone indexes. */ - protected Map boneIndexes = new HashMap(); - /** The modifiers that should be applied after the mesh has been created. */ - protected List postMeshCreationModifiers = new ArrayList(); - - /** The faces of the mesh. */ - protected List faces = new ArrayList(); - /** The edges of the mesh. */ - protected List edges = new ArrayList(); - /** The points of the mesh. */ - protected List points = new ArrayList(); - /** A map between index and faces that contain it (for faster index - face queries). */ - protected Map> indexToFaceMapping = new HashMap>(); - /** A map between index and edges that contain it (for faster index - edge queries). */ - protected Map> indexToEdgeMapping = new HashMap>(); - - /** The bounding box of the temporal mesh. */ - protected BoundingBox boundingBox; - - /** - * Creates a temporal mesh based on the given mesh structure. - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when problems with file reading occur - */ - public TemporalMesh(Structure meshStructure, BlenderContext blenderContext) throws BlenderFileException { - this(meshStructure, blenderContext, true); - } - - /** - * Creates a temporal mesh based on the given mesh structure. - * @param meshStructure - * the mesh structure - * @param blenderContext - * the blender context - * @param loadData - * tells if the data should be loaded from the mesh structure - * @throws BlenderFileException - * an exception is thrown when problems with file reading occur - */ - protected TemporalMesh(Structure meshStructure, BlenderContext blenderContext, boolean loadData) throws BlenderFileException { - this.blenderContext = blenderContext; - this.meshStructure = meshStructure; - - if (loadData) { - name = meshStructure.getName(); - - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - - meshHelper.loadVerticesAndNormals(meshStructure, vertices, normals); - verticesColors = meshHelper.loadVerticesColors(meshStructure, blenderContext); - LinkedHashMap> userUVGroups = meshHelper.loadUVCoordinates(meshStructure); - vertexGroups = meshHelper.loadVerticesGroups(meshStructure); - - faces = Face.loadAll(meshStructure, userUVGroups, verticesColors, this, blenderContext); - edges = Edge.loadAll(meshStructure, this); - points = Point.loadAll(meshStructure); - - this.rebuildIndexesMappings(); - } - } - - /** - * @return the blender context - */ - public BlenderContext getBlenderContext() { - return blenderContext; - } - - /** - * @return the vertices of the mesh - */ - public List getVertices() { - return vertices; - } - - /** - * @return the normals of the mesh - */ - public List getNormals() { - return normals; - } - - /** - * @return all faces - */ - public List getFaces() { - return faces; - } - - /** - * @return all edges - */ - public List getEdges() { - return edges; - } - - /** - * @return all points (do not mistake it with vertices) - */ - public List getPoints() { - return points; - } - - /** - * @return all vertices colors - */ - public List getVerticesColors() { - return verticesColors; - } - - /** - * @return all vertex groups for the vertices (each map has groups for the proper vertex) - */ - public List> getVertexGroups() { - return vertexGroups; - } - - /** - * @return the faces that contain the given index or null if none contain it - */ - public Collection getAdjacentFaces(Integer index) { - return indexToFaceMapping.get(index); - } - - /** - * @param edge the edge of the mesh - * @return a list of faces that contain the given edge or an empty list - */ - public Collection getAdjacentFaces(Edge edge) { - List result = new ArrayList(indexToFaceMapping.get(edge.getFirstIndex())); - Set secondIndexAdjacentFaces = indexToFaceMapping.get(edge.getSecondIndex()); - if (secondIndexAdjacentFaces != null) { - result.retainAll(indexToFaceMapping.get(edge.getSecondIndex())); - } - return result; - } - - /** - * @param index the index of the mesh - * @return a list of edges that contain the index - */ - public Collection getAdjacentEdges(Integer index) { - return indexToEdgeMapping.get(index); - } - - /** - * Tells if the given edge is a boundary edge. The boundary edge means that it belongs to a single - * face or to none. - * @param edge the edge of the mesh - * @return true if the edge is a boundary one and false otherwise - */ - public boolean isBoundary(Edge edge) { - return this.getAdjacentFaces(edge).size() <= 1; - } - - /** - * The method tells if the given index is a boundary index. A boundary index belongs to at least - * one boundary edge. - * @param index - * the index of the mesh - * @return true if the index is a boundary one and false otherwise - */ - public boolean isBoundary(Integer index) { - Collection adjacentEdges = this.getAdjacentEdges(index); - for (Edge edge : adjacentEdges) { - if (this.isBoundary(edge)) { - return true; - } - } - return false; - } - - @Override - public TemporalMesh clone() { - try { - TemporalMesh result = new TemporalMesh(meshStructure, blenderContext, false); - result.name = name; - for (Vector3f v : vertices) { - result.vertices.add(v.clone()); - } - for (Vector3f n : normals) { - result.normals.add(n.clone()); - } - for (Map group : vertexGroups) { - result.vertexGroups.add(new HashMap(group)); - } - for (byte[] vertColor : verticesColors) { - result.verticesColors.add(vertColor.clone()); - } - result.materials = materials; - result.properties = properties; - result.boneIndexes.putAll(boneIndexes); - result.postMeshCreationModifiers.addAll(postMeshCreationModifiers); - for (Face face : faces) { - result.faces.add(face.clone()); - } - for (Edge edge : edges) { - result.edges.add(edge.clone()); - } - for (Point point : points) { - result.points.add(point.clone()); - } - result.rebuildIndexesMappings(); - return result; - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, "Error while cloning the temporal mesh: {0}. Returning null.", e.getLocalizedMessage()); - } - return null; - } - - /** - * The method rebuilds the mappings between faces and edges. Should be called after - * every major change of the temporal mesh done outside it. - *

    - * Note: I will remove this method soon and cause the mappings to be done - * automatically when the mesh is modified. - */ - public void rebuildIndexesMappings() { - indexToEdgeMapping.clear(); - indexToFaceMapping.clear(); - for (Face face : faces) { - for (Integer index : face.getIndexes()) { - Set faces = indexToFaceMapping.get(index); - if (faces == null) { - faces = new HashSet(); - indexToFaceMapping.put(index, faces); - } - faces.add(face); - } - } - for (Edge edge : edges) { - Set edges = indexToEdgeMapping.get(edge.getFirstIndex()); - if (edges == null) { - edges = new HashSet(); - indexToEdgeMapping.put(edge.getFirstIndex(), edges); - } - edges.add(edge); - edges = indexToEdgeMapping.get(edge.getSecondIndex()); - if (edges == null) { - edges = new HashSet(); - indexToEdgeMapping.put(edge.getSecondIndex(), edges); - } - edges.add(edge); - } - } - - @Override - public void updateModelBound() { - if (boundingBox == null) { - boundingBox = new BoundingBox(); - } - Vector3f min = new Vector3f(Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); - Vector3f max = new Vector3f(Float.MIN_VALUE, Float.MIN_VALUE, Float.MIN_VALUE); - for (Vector3f v : vertices) { - min.set(Math.min(min.x, v.x), Math.min(min.y, v.y), Math.min(min.z, v.z)); - max.set(Math.max(max.x, v.x), Math.max(max.y, v.y), Math.max(max.z, v.z)); - } - boundingBox.setMinMax(min, max); - } - - @Override - public BoundingVolume getModelBound() { - this.updateModelBound(); - return boundingBox; - } - - @Override - public BoundingVolume getWorldBound() { - this.updateModelBound(); - Node parent = this.getParent(); - if (parent != null) { - BoundingVolume bv = boundingBox.clone(); - bv.setCenter(parent.getWorldTranslation()); - return bv; - } else { - return boundingBox; - } - } - - /** - * Triangulates the mesh. - */ - public void triangulate() { - Set warnings = new HashSet<>(TriangulationWarning.values().length - 1); - LOGGER.fine("Triangulating temporal mesh."); - for (Face face : faces) { - TriangulationWarning warning = face.triangulate(); - if(warning != TriangulationWarning.NONE) { - warnings.add(warning); - } - } - - if(warnings.size() > 0 && LOGGER.isLoggable(Level.WARNING)) { - StringBuilder sb = new StringBuilder(512); - sb.append("There were problems with triangulating the faces of a mesh: ").append(name); - for(TriangulationWarning w : warnings) { - sb.append("\n\t").append(w); - } - LOGGER.warning(sb.toString()); - } - } - - /** - * The method appends the given mesh to the current one. New faces and vertices and indexes are added. - * @param mesh - * the mesh to be appended - */ - public void append(TemporalMesh mesh) { - if (mesh != null) { - // we need to shift the indexes in faces, lines and points - int shift = vertices.size(); - if (shift > 0) { - for (Face face : mesh.faces) { - face.getIndexes().shiftIndexes(shift, null); - face.setTemporalMesh(this); - } - for (Edge edge : mesh.edges) { - edge.shiftIndexes(shift, null); - } - for (Point point : mesh.points) { - point.shiftIndexes(shift, null); - } - } - - faces.addAll(mesh.faces); - edges.addAll(mesh.edges); - points.addAll(mesh.points); - - vertices.addAll(mesh.vertices); - normals.addAll(mesh.normals); - vertexGroups.addAll(mesh.vertexGroups); - verticesColors.addAll(mesh.verticesColors); - boneIndexes.putAll(mesh.boneIndexes); - - this.rebuildIndexesMappings(); - } - } - - /** - * Sets the properties of the mesh. - * @param properties - * the properties of the mesh - */ - public void setProperties(Properties properties) { - this.properties = properties; - } - - /** - * Sets the materials of the mesh. - * @param materials - * the materials of the mesh - */ - public void setMaterials(MaterialContext[] materials) { - this.materials = materials; - } - - /** - * Adds bone index to the mesh. - * @param boneName - * the name of the bone - * @param boneIndex - * the index of the bone - */ - public void addBoneIndex(String boneName, Integer boneIndex) { - boneIndexes.put(boneName, boneIndex); - } - - /** - * The modifier to be applied after the geometries are created. - * @param modifier - * the modifier to be applied - */ - public void applyAfterMeshCreate(Modifier modifier) { - postMeshCreationModifiers.add(modifier); - } - - @Override - public int getVertexCount() { - return vertices.size(); - } - - /** - * Removes all vertices from the mesh. - */ - public void clear() { - vertices.clear(); - normals.clear(); - vertexGroups.clear(); - verticesColors.clear(); - faces.clear(); - edges.clear(); - points.clear(); - indexToEdgeMapping.clear(); - indexToFaceMapping.clear(); - } - - /** - * The mesh builds geometries from the mesh. The result is stored in the blender context - * under the mesh's OMA. - */ - public void toGeometries() { - LOGGER.log(Level.FINE, "Converting temporal mesh {0} to jme geometries.", name); - List result = new ArrayList(); - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - Node parent = this.getParent(); - parent.detachChild(this); - - this.prepareFacesGeometry(result, meshHelper); - this.prepareLinesGeometry(result, meshHelper); - this.preparePointsGeometry(result, meshHelper); - - blenderContext.addLoadedFeatures(meshStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); - - for (Geometry geometry : result) { - parent.attachChild(geometry); - } - - for (Modifier modifier : postMeshCreationModifiers) { - modifier.postMeshCreationApply(parent, blenderContext); - } - } - - /** - * The method creates geometries from faces. - * @param result - * the list where new geometries will be appended - * @param meshHelper - * the mesh helper - */ - protected void prepareFacesGeometry(List result, MeshHelper meshHelper) { - LOGGER.fine("Preparing faces geometries."); - this.triangulate(); - - Vector3f[] tempVerts = new Vector3f[3]; - Vector3f[] tempNormals = new Vector3f[3]; - byte[][] tempVertColors = new byte[3][]; - List> boneBuffers = new ArrayList>(3); - - LOGGER.log(Level.FINE, "Appending {0} faces to mesh buffers.", faces.size()); - Map faceMeshes = new HashMap(); - for (Face face : faces) { - MeshBuffers meshBuffers = faceMeshes.get(face.getMaterialNumber()); - if (meshBuffers == null) { - meshBuffers = new MeshBuffers(face.getMaterialNumber()); - faceMeshes.put(face.getMaterialNumber(), meshBuffers); - } - - List> triangulatedIndexes = face.getCurrentIndexes(); - List vertexColors = face.getVertexColors(); - - for (List indexes : triangulatedIndexes) { - assert indexes.size() == 3 : "The mesh has not been properly triangulated!"; - - Vector3f normal = null; - if(!face.isSmooth()) { - normal = FastMath.computeNormal(vertices.get(indexes.get(0)), vertices.get(indexes.get(1)), vertices.get(indexes.get(2))); - } - - boneBuffers.clear(); - for (int i = 0; i < 3; ++i) { - int vertIndex = indexes.get(i); - tempVerts[i] = vertices.get(vertIndex); - tempNormals[i] = normal != null ? normal : normals.get(vertIndex); - tempVertColors[i] = vertexColors != null ? vertexColors.get(face.getIndexes().indexOf(vertIndex)) : null; - - if (boneIndexes.size() > 0 && vertexGroups.size() > 0) { - Map boneBuffersForVertex = new HashMap(); - Map vertexGroupsForVertex = vertexGroups.get(vertIndex); - for (Entry entry : boneIndexes.entrySet()) { - if (vertexGroupsForVertex.containsKey(entry.getKey())) { - float weight = vertexGroupsForVertex.get(entry.getKey()); - if (weight > MINIMUM_BONE_WEIGHT) { - // only values of weight greater than MINIMUM_BONE_WEIGHT are used - // if all non zero weights were used, and they were samm enough, problems with normalisation would occur - // because adding a very small value to 1.0 will give 1.0 - // so in order to avoid such errors, which can cause severe animation artifacts we need to use some minimum weight value - boneBuffersForVertex.put(weight, entry.getValue()); - } - } - } - if (boneBuffersForVertex.size() == 0) {// attach the vertex to zero-indexed bone so that it does not collapse to (0, 0, 0) - boneBuffersForVertex.put(1.0f, 0); - } - boneBuffers.add(boneBuffersForVertex); - } - } - - Map> uvs = meshHelper.selectUVSubset(face, indexes.toArray(new Integer[indexes.size()])); - meshBuffers.append(face.isSmooth(), tempVerts, tempNormals, uvs, tempVertColors, boneBuffers); - } - } - - LOGGER.fine("Converting mesh buffers to geometries."); - Map geometryToBuffersMap = new HashMap(); - for (Entry entry : faceMeshes.entrySet()) { - MeshBuffers meshBuffers = entry.getValue(); - - Mesh mesh = new Mesh(); - - if (meshBuffers.isShortIndexBuffer()) { - mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer()); - } else { - mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer()); - } - mesh.setBuffer(meshBuffers.getPositionsBuffer()); - mesh.setBuffer(meshBuffers.getNormalsBuffer()); - if (meshBuffers.areVertexColorsUsed()) { - mesh.setBuffer(Type.Color, 4, meshBuffers.getVertexColorsBuffer()); - mesh.getBuffer(Type.Color).setNormalized(true); - } - - BoneBuffersData boneBuffersData = meshBuffers.getBoneBuffers(); - if (boneBuffersData != null) { - mesh.setMaxNumWeights(boneBuffersData.maximumWeightsPerVertex); - mesh.setBuffer(boneBuffersData.verticesWeights); - mesh.setBuffer(boneBuffersData.verticesWeightsIndices); - - LOGGER.fine("Generating bind pose and normal buffers."); - mesh.generateBindPose(true); - - // change the usage type of vertex and normal buffers from Static to Stream - mesh.getBuffer(Type.Position).setUsage(Usage.Stream); - mesh.getBuffer(Type.Normal).setUsage(Usage.Stream); - - // creating empty buffers for HW skinning; the buffers will be setup if ever used - VertexBuffer verticesWeightsHW = new VertexBuffer(Type.HWBoneWeight); - VertexBuffer verticesWeightsIndicesHW = new VertexBuffer(Type.HWBoneIndex); - mesh.setBuffer(verticesWeightsHW); - mesh.setBuffer(verticesWeightsIndicesHW); - } - - Geometry geometry = new Geometry(name + (result.size() + 1), mesh); - if (properties != null && properties.getValue() != null) { - meshHelper.applyProperties(geometry, properties); - } - result.add(geometry); - - geometryToBuffersMap.put(geometry, meshBuffers); - } - - LOGGER.fine("Applying materials to geometries."); - for (Entry entry : geometryToBuffersMap.entrySet()) { - int materialIndex = entry.getValue().getMaterialIndex(); - Geometry geometry = entry.getKey(); - if (materialIndex >= 0 && materials != null && materials.length > materialIndex && materials[materialIndex] != null) { - materials[materialIndex].applyMaterial(geometry, meshStructure.getOldMemoryAddress(), entry.getValue().getUvCoords(), blenderContext); - } else { - Material defaultMaterial = blenderContext.getDefaultMaterial().clone(); - defaultMaterial.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Off); - geometry.setMaterial(defaultMaterial); - } - } - } - - /** - * The method creates geometries from lines. - * @param result - * the list where new geometries will be appended - * @param meshHelper - * the mesh helper - */ - protected void prepareLinesGeometry(List result, MeshHelper meshHelper) { - if (edges.size() > 0) { - LOGGER.fine("Preparing lines geometries."); - - List> separateEdges = new ArrayList>(); - List edges = new ArrayList(this.edges.size()); - for (Edge edge : this.edges) { - if (!edge.isInFace()) { - edges.add(edge); - } - } - while (edges.size() > 0) { - boolean edgeAppended = false; - int edgeIndex = 0; - for (List list : separateEdges) { - for (edgeIndex = 0; edgeIndex < edges.size() && !edgeAppended; ++edgeIndex) { - Edge edge = edges.get(edgeIndex); - if (list.get(0).equals(edge.getFirstIndex())) { - list.add(0, edge.getSecondIndex()); - --edgeIndex; - edgeAppended = true; - } else if (list.get(0).equals(edge.getSecondIndex())) { - list.add(0, edge.getFirstIndex()); - --edgeIndex; - edgeAppended = true; - } else if (list.get(list.size() - 1).equals(edge.getFirstIndex())) { - list.add(edge.getSecondIndex()); - --edgeIndex; - edgeAppended = true; - } else if (list.get(list.size() - 1).equals(edge.getSecondIndex())) { - list.add(edge.getFirstIndex()); - --edgeIndex; - edgeAppended = true; - } - } - if (edgeAppended) { - break; - } - } - Edge edge = edges.remove(edgeAppended ? edgeIndex : 0); - if (!edgeAppended) { - separateEdges.add(new ArrayList(Arrays.asList(edge.getFirstIndex(), edge.getSecondIndex()))); - } - } - - for (List list : separateEdges) { - MeshBuffers meshBuffers = new MeshBuffers(0); - for (int index : list) { - meshBuffers.append(vertices.get(index), normals.get(index)); - } - Mesh mesh = new Mesh(); - mesh.setLineWidth(blenderContext.getBlenderKey().getLinesWidth()); - mesh.setMode(Mode.LineStrip); - if (meshBuffers.isShortIndexBuffer()) { - mesh.setBuffer(Type.Index, 1, (ShortBuffer) meshBuffers.getIndexBuffer()); - } else { - mesh.setBuffer(Type.Index, 1, (IntBuffer) meshBuffers.getIndexBuffer()); - } - mesh.setBuffer(meshBuffers.getPositionsBuffer()); - mesh.setBuffer(meshBuffers.getNormalsBuffer()); - - Geometry geometry = new Geometry(meshStructure.getName() + (result.size() + 1), mesh); - geometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext)); - if (properties != null && properties.getValue() != null) { - meshHelper.applyProperties(geometry, properties); - } - result.add(geometry); - } - } - } - - /** - * The method creates geometries from points. - * @param result - * the list where new geometries will be appended - * @param meshHelper - * the mesh helper - */ - protected void preparePointsGeometry(List result, MeshHelper meshHelper) { - if (points.size() > 0) { - LOGGER.fine("Preparing point geometries."); - - MeshBuffers pointBuffers = new MeshBuffers(0); - for (Point point : points) { - pointBuffers.append(vertices.get(point.getIndex()), normals.get(point.getIndex())); - } - Mesh pointsMesh = new Mesh(); - pointsMesh.setMode(Mode.Points); - pointsMesh.setPointSize(blenderContext.getBlenderKey().getPointsSize()); - if (pointBuffers.isShortIndexBuffer()) { - pointsMesh.setBuffer(Type.Index, 1, (ShortBuffer) pointBuffers.getIndexBuffer()); - } else { - pointsMesh.setBuffer(Type.Index, 1, (IntBuffer) pointBuffers.getIndexBuffer()); - } - pointsMesh.setBuffer(pointBuffers.getPositionsBuffer()); - pointsMesh.setBuffer(pointBuffers.getNormalsBuffer()); - - Geometry pointsGeometry = new Geometry(meshStructure.getName() + (result.size() + 1), pointsMesh); - pointsGeometry.setMaterial(meshHelper.getBlackUnshadedMaterial(blenderContext)); - if (properties != null && properties.getValue() != null) { - meshHelper.applyProperties(pointsGeometry, properties); - } - result.add(pointsGeometry); - } - } - - @Override - public String toString() { - return "TemporalMesh [name=" + name + ", vertices.size()=" + vertices.size() + "]"; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (meshStructure == null ? 0 : meshStructure.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (!(obj instanceof TemporalMesh)) { - return false; - } - TemporalMesh other = (TemporalMesh) obj; - return meshStructure.getOldMemoryAddress().equals(other.meshStructure.getOldMemoryAddress()); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java deleted file mode 100644 index b59f3d05c3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArmatureModifier.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; - -/** - * This modifier allows to add bone animation to the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ArmatureModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ArmatureModifier.class.getName()); - - private static final int FLAG_VERTEX_GROUPS = 0x01; - private static final int FLAG_BONE_ENVELOPES = 0x02; - - private Skeleton skeleton; - - /** - * This constructor reads animation data from the object structore. The - * stored data is the AnimData and additional data is armature's OMA. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public ArmatureModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if (this.validate(modifierStructure, blenderContext)) { - Pointer pArmatureObject = (Pointer) modifierStructure.getFieldValue("object"); - if (pArmatureObject.isNotNull()) { - int deformflag = ((Number) modifierStructure.getFieldValue("deformflag")).intValue(); - boolean useVertexGroups = (deformflag & FLAG_VERTEX_GROUPS) != 0; - boolean useBoneEnvelopes = (deformflag & FLAG_BONE_ENVELOPES) != 0; - modifying = useBoneEnvelopes || useVertexGroups; - if (modifying) {// if neither option is used the modifier will not modify anything anyway - Structure armatureObject = pArmatureObject.fetchData().get(0); - if(blenderContext.getSkeleton(armatureObject.getOldMemoryAddress()) == null) { - LOGGER.fine("Creating new skeleton for armature modifier."); - Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0); - List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); - List bonesList = new ArrayList(); - for (int i = 0; i < bonebase.size(); ++i) { - this.buildBones(armatureObject.getOldMemoryAddress(), bonebase.get(i), null, bonesList, objectStructure.getOldMemoryAddress(), blenderContext); - } - bonesList.add(0, new Bone("")); - Bone[] bones = bonesList.toArray(new Bone[bonesList.size()]); - skeleton = new Skeleton(bones); - blenderContext.setSkeleton(armatureObject.getOldMemoryAddress(), skeleton); - } else { - skeleton = blenderContext.getSkeleton(armatureObject.getOldMemoryAddress()); - } - } - } else { - modifying = false; - } - } - } - - private void buildBones(Long armatureObjectOMA, Structure boneStructure, Bone parent, List result, Long spatialOMA, BlenderContext blenderContext) throws BlenderFileException { - BoneContext bc = new BoneContext(armatureObjectOMA, boneStructure, blenderContext); - bc.buildBone(result, spatialOMA, blenderContext); - } - - @Override - public void postMeshCreationApply(Node node, BlenderContext blenderContext) { - LOGGER.fine("Applying armature modifier after mesh has been created."); - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.applyAnimations(node, skeleton, blenderContext.getBlenderKey().getAnimationMatchMethod()); - node.updateModelBound(); - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Armature modifier is invalid! Cannot be applied to: {0}", node.getName()); - } - - if (modifying) { - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - LOGGER.log(Level.FINE, "Applying armature modifier to: {0}", temporalMesh); - - LOGGER.fine("Creating map between bone name and its index."); - for (int i = 0; i < skeleton.getBoneCount(); ++i) { - Bone bone = skeleton.getBone(i); - temporalMesh.addBoneIndex(bone.getName(), i); - } - temporalMesh.applyAfterMeshCreate(this); - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java deleted file mode 100644 index e00c226401..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ArrayModifier.java +++ /dev/null @@ -1,241 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; -import com.jme3.bounding.BoundingVolume; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.MeshHelper; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; -import com.jme3.scene.shape.Curve; - -/** - * This modifier allows to array modifier to the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ArrayModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(ArrayModifier.class.getName()); - - private int fittype; - private int count; - private float length; - private float[] offset; - private float[] scale; - private Pointer pOffsetObject; - private Pointer pStartCap; - private Pointer pEndCap; - - /** - * This constructor reads array data from the modifier structure. The - * stored data is a map of parameters for array modifier. No additional data - * is loaded. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - @SuppressWarnings("unchecked") - public ArrayModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if (this.validate(modifierStructure, blenderContext)) { - fittype = ((Number) modifierStructure.getFieldValue("fit_type")).intValue(); - switch (fittype) { - case 0:// FIXED COUNT - count = ((Number) modifierStructure.getFieldValue("count")).intValue(); - break; - case 1:// FIXED LENGTH - length = ((Number) modifierStructure.getFieldValue("length")).floatValue(); - break; - case 2:// FITCURVE - Pointer pCurveOb = (Pointer) modifierStructure.getFieldValue("curve_ob"); - if (pCurveOb.isNotNull()) { - Structure curveStructure = pCurveOb.fetchData().get(0); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node curveObject = (Node) objectHelper.toObject(curveStructure, blenderContext); - Set referencesToCurveLengths = new HashSet(curveObject.getChildren().size()); - for (Spatial spatial : curveObject.getChildren()) { - if (spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - if (mesh instanceof Curve) { - length += ((Curve) mesh).getLength(); - } else { - // if bevel object has several parts then each mesh will have the same reference - // to length value (and we should use only one) - Number curveLength = spatial.getUserData("curveLength"); - if (curveLength != null && !referencesToCurveLengths.contains(curveLength)) { - length += curveLength.floatValue(); - referencesToCurveLengths.add(curveLength); - } - } - } - } - } - fittype = 1;// treat it like FIXED LENGTH - break; - default: - assert false : "Unknown array modifier fit type: " + fittype; - } - - // offset parameters - int offsettype = ((Number) modifierStructure.getFieldValue("offset_type")).intValue(); - if ((offsettype & 0x01) != 0) {// Constant offset - DynamicArray offsetArray = (DynamicArray) modifierStructure.getFieldValue("offset"); - offset = new float[] { offsetArray.get(0).floatValue(), offsetArray.get(1).floatValue(), offsetArray.get(2).floatValue() }; - } - if ((offsettype & 0x02) != 0) {// Relative offset - DynamicArray scaleArray = (DynamicArray) modifierStructure.getFieldValue("scale"); - scale = new float[] { scaleArray.get(0).floatValue(), scaleArray.get(1).floatValue(), scaleArray.get(2).floatValue() }; - } - if ((offsettype & 0x04) != 0) {// Object offset - pOffsetObject = (Pointer) modifierStructure.getFieldValue("offset_ob"); - } - - // start cap and end cap - pStartCap = (Pointer) modifierStructure.getFieldValue("start_cap"); - pEndCap = (Pointer) modifierStructure.getFieldValue("end_cap"); - } - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Array modifier is invalid! Cannot be applied to: {0}", node.getName()); - } else { - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - LOGGER.log(Level.FINE, "Applying array modifier to: {0}", temporalMesh); - if (offset == null) {// the node will be repeated several times in the same place - offset = new float[] { 0.0f, 0.0f, 0.0f }; - } - if (scale == null) {// the node will be repeated several times in the same place - scale = new float[] { 0.0f, 0.0f, 0.0f }; - } else { - // getting bounding box - temporalMesh.updateModelBound(); - BoundingVolume boundingVolume = temporalMesh.getWorldBound(); - if (boundingVolume instanceof BoundingBox) { - scale[0] *= ((BoundingBox) boundingVolume).getXExtent() * 2.0f; - scale[1] *= ((BoundingBox) boundingVolume).getYExtent() * 2.0f; - scale[2] *= ((BoundingBox) boundingVolume).getZExtent() * 2.0f; - } else if (boundingVolume instanceof BoundingSphere) { - float radius = ((BoundingSphere) boundingVolume).getRadius(); - scale[0] *= radius * 2.0f; - scale[1] *= radius * 2.0f; - scale[2] *= radius * 2.0f; - } else { - throw new IllegalStateException("Unknown bounding volume type: " + boundingVolume.getClass().getName()); - } - } - - // adding object's offset - float[] objectOffset = new float[] { 0.0f, 0.0f, 0.0f }; - if (pOffsetObject != null && pOffsetObject.isNotNull()) { - FileBlockHeader offsetObjectBlock = blenderContext.getFileBlock(pOffsetObject.getOldMemoryAddress()); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - try {// we take the structure in case the object was not yet loaded - Structure offsetStructure = offsetObjectBlock.getStructure(blenderContext); - Vector3f translation = objectHelper.getTransformation(offsetStructure, blenderContext).getTranslation(); - objectOffset[0] = translation.x; - objectOffset[1] = translation.y; - objectOffset[2] = translation.z; - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Problems in blender file structure! Object offset cannot be applied! The problem: {0}", e.getMessage()); - } - } - - // getting start and end caps - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - TemporalMesh[] caps = new TemporalMesh[] { null, null }; - Pointer[] pCaps = new Pointer[] { pStartCap, pEndCap }; - for (int i = 0; i < pCaps.length; ++i) { - if (pCaps[i].isNotNull()) { - FileBlockHeader capBlock = blenderContext.getFileBlock(pCaps[i].getOldMemoryAddress()); - try {// we take the structure in case the object was not yet loaded - Structure capStructure = capBlock.getStructure(blenderContext); - Pointer pMesh = (Pointer) capStructure.getFieldValue("data"); - List meshesArray = pMesh.fetchData(); - caps[i] = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Problems in blender file structure! Cap object cannot be applied! The problem: {0}", e.getMessage()); - } - } - } - - Vector3f translationVector = new Vector3f(offset[0] + scale[0] + objectOffset[0], offset[1] + scale[1] + objectOffset[1], offset[2] + scale[2] + objectOffset[2]); - if (blenderContext.getBlenderKey().isFixUpAxis()) { - float y = translationVector.y; - translationVector.y = translationVector.z; - translationVector.z = y == 0 ? 0 : -y; - } - - // getting/calculating repeats amount - int count = 0; - if (fittype == 0) {// Fixed count - count = this.count - 1; - } else if (fittype == 1) {// Fixed length - float length = this.length; - if (translationVector.length() > 0.0f) { - count = (int) (length / translationVector.length()) - 1; - } - } else if (fittype == 2) {// Fit curve - throw new IllegalStateException("Fit curve should be transformed to Fixed Length array type!"); - } else { - throw new IllegalStateException("Unknown fit type: " + fittype); - } - - // adding translated nodes and caps - Vector3f totalTranslation = new Vector3f(translationVector); - if (count > 0) { - TemporalMesh originalMesh = temporalMesh.clone(); - for (int i = 0; i < count; ++i) { - TemporalMesh clone = originalMesh.clone(); - for (Vector3f v : clone.getVertices()) { - v.addLocal(totalTranslation); - } - temporalMesh.append(clone); - totalTranslation.addLocal(translationVector); - } - } - if (caps[0] != null) { - translationVector.multLocal(-1); - TemporalMesh capsClone = caps[0].clone(); - for (Vector3f v : capsClone.getVertices()) { - v.addLocal(translationVector); - } - temporalMesh.append(capsClone); - } - if (caps[1] != null) { - TemporalMesh capsClone = caps[1].clone(); - for (Vector3f v : capsClone.getVertices()) { - v.addLocal(totalTranslation); - } - temporalMesh.append(capsClone); - } - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MaskModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MaskModifier.java deleted file mode 100644 index 9201800e66..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MaskModifier.java +++ /dev/null @@ -1,200 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.animations.BoneContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.Edge; -import com.jme3.scene.plugins.blender.meshes.Face; -import com.jme3.scene.plugins.blender.meshes.IndexesLoop.IndexPredicate; -import com.jme3.scene.plugins.blender.meshes.Point; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; - -/** - * This modifier allows to use mask modifier on the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class MaskModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MaskModifier.class.getName()); - - private static final int FLAG_INVERT_MASK = 0x01; - - private static final int MODE_VERTEX_GROUP = 0; - private static final int MODE_ARMATURE = 1; - - private Pointer pArmatureObject; - private String vertexGroupName; - private boolean invertMask; - - public MaskModifier(Structure modifierStructure, BlenderContext blenderContext) { - if (this.validate(modifierStructure, blenderContext)) { - int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue(); - invertMask = (flag & FLAG_INVERT_MASK) != 0; - - int mode = ((Number) modifierStructure.getFieldValue("mode")).intValue(); - if (mode == MODE_VERTEX_GROUP) { - vertexGroupName = modifierStructure.getFieldValue("vgroup").toString(); - if (vertexGroupName != null && vertexGroupName.length() == 0) { - vertexGroupName = null; - } - } else if (mode == MODE_ARMATURE) { - pArmatureObject = (Pointer) modifierStructure.getFieldValue("ob_arm"); - } else { - LOGGER.log(Level.SEVERE, "Unknown mode type: {0}. Cannot apply modifier: {1}.", new Object[] { mode, modifierStructure.getName() }); - invalid = true; - } - } - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); - } else { - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - List vertexGroupsToRemove = new ArrayList(); - if (vertexGroupName != null) { - vertexGroupsToRemove.add(vertexGroupName); - } else if (pArmatureObject != null && pArmatureObject.isNotNull()) { - try { - Structure armatureObject = pArmatureObject.fetchData().get(0); - - Structure armatureStructure = ((Pointer) armatureObject.getFieldValue("data")).fetchData().get(0); - List bonebase = ((Structure) armatureStructure.getFieldValue("bonebase")).evaluateListBase(); - vertexGroupsToRemove.addAll(this.readBoneNames(bonebase)); - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, "Cannot load armature object for the mask modifier. Cause: {0}", e.getLocalizedMessage()); - LOGGER.log(Level.SEVERE, "Mask modifier will NOT be applied to node named: {0}", node.getName()); - } - } else { - // if the mesh has no vertex groups then remove all verts - // if the mesh has at least one vertex group - then do nothing - // I have no idea why we should do that, but blender works this way - Set vertexGroupNames = new HashSet(); - for (Map groups : temporalMesh.getVertexGroups()) { - vertexGroupNames.addAll(groups.keySet()); - } - if (vertexGroupNames.size() == 0 && !invertMask || vertexGroupNames.size() > 0 && invertMask) { - temporalMesh.clear(); - } - } - - if (vertexGroupsToRemove.size() > 0) { - List vertsToBeRemoved = new ArrayList(); - for (int i = 0; i < temporalMesh.getVertexCount(); ++i) { - Map vertexGroups = temporalMesh.getVertexGroups().get(i); - boolean hasVertexGroup = false; - if (vertexGroups != null) { - for (String groupName : vertexGroupsToRemove) { - Float weight = vertexGroups.get(groupName); - if (weight != null && weight > 0) { - hasVertexGroup = true; - break; - } - } - } - - if (!hasVertexGroup && !invertMask || hasVertexGroup && invertMask) { - vertsToBeRemoved.add(i); - } - } - - Collections.reverse(vertsToBeRemoved); - for (Integer vertexIndex : vertsToBeRemoved) { - this.removeVertexAt(vertexIndex, temporalMesh); - } - } - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } - - /** - * Every face, edge and point that contains - * the vertex will be removed. - * @param index - * the index of a vertex to be removed - * @throws IndexOutOfBoundsException - * thrown when given index is negative or beyond the count of vertices - */ - private void removeVertexAt(final int index, TemporalMesh temporalMesh) { - if (index < 0 || index >= temporalMesh.getVertexCount()) { - throw new IndexOutOfBoundsException("The given index is out of bounds: " + index); - } - - temporalMesh.getVertices().remove(index); - temporalMesh.getNormals().remove(index); - if (temporalMesh.getVertexGroups().size() > 0) { - temporalMesh.getVertexGroups().remove(index); - } - if (temporalMesh.getVerticesColors().size() > 0) { - temporalMesh.getVerticesColors().remove(index); - } - - IndexPredicate shiftPredicate = new IndexPredicate() { - @Override - public boolean execute(Integer i) { - return i > index; - } - }; - for (int i = temporalMesh.getFaces().size() - 1; i >= 0; --i) { - Face face = temporalMesh.getFaces().get(i); - if (face.getIndexes().indexOf(index) >= 0) { - temporalMesh.getFaces().remove(i); - } else { - face.getIndexes().shiftIndexes(-1, shiftPredicate); - } - } - for (int i = temporalMesh.getEdges().size() - 1; i >= 0; --i) { - Edge edge = temporalMesh.getEdges().get(i); - if (edge.getFirstIndex() == index || edge.getSecondIndex() == index) { - temporalMesh.getEdges().remove(i); - } else { - edge.shiftIndexes(-1, shiftPredicate); - } - } - for (int i = temporalMesh.getPoints().size() - 1; i >= 0; --i) { - Point point = temporalMesh.getPoints().get(i); - if (point.getIndex() == index) { - temporalMesh.getPoints().remove(i); - } else { - point.shiftIndexes(-1, shiftPredicate); - } - } - } - - /** - * Reads the names of the bones from the given bone base. - * @param boneBase - * the list of bone base structures - * @return a list of bones' names - * @throws BlenderFileException - * is thrown if problems with reading the child bones' bases occur - */ - private List readBoneNames(List boneBase) throws BlenderFileException { - List result = new ArrayList(); - for (Structure boneStructure : boneBase) { - int flag = ((Number) boneStructure.getFieldValue("flag")).intValue(); - if ((flag & BoneContext.SELECTED) != 0) { - result.add(boneStructure.getFieldValue("name").toString()); - } - List childbase = ((Structure) boneStructure.getFieldValue("childbase")).evaluateListBase(); - result.addAll(this.readBoneNames(childbase)); - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java deleted file mode 100644 index 36538afc35..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/MirrorModifier.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.Collections; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.Edge; -import com.jme3.scene.plugins.blender.meshes.Face; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.objects.ObjectHelper; - -/** - * This modifier allows to array modifier to the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class MirrorModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - - private static final int FLAG_MIRROR_X = 0x08; - private static final int FLAG_MIRROR_Y = 0x10; - private static final int FLAG_MIRROR_Z = 0x20; - private static final int FLAG_MIRROR_U = 0x02; - private static final int FLAG_MIRROR_V = 0x04; - private static final int FLAG_MIRROR_VERTEX_GROUP = 0x40; - private static final int FLAG_MIRROR_MERGE = 0x80; - - private boolean[] isMirrored; - private boolean mirrorU, mirrorV; - private boolean merge; - private float tolerance; - private Pointer pMirrorObject; - private boolean mirrorVGroup; - - /** - * This constructor reads mirror data from the modifier structure. The - * stored data is a map of parameters for mirror modifier. No additional data - * is loaded. - * When the modifier is applied it is necessary to get the newly created node. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public MirrorModifier(Structure modifierStructure, BlenderContext blenderContext) { - if (this.validate(modifierStructure, blenderContext)) { - int flag = ((Number) modifierStructure.getFieldValue("flag")).intValue(); - - isMirrored = new boolean[] { (flag & FLAG_MIRROR_X) != 0, (flag & FLAG_MIRROR_Y) != 0, (flag & FLAG_MIRROR_Z) != 0 }; - if (blenderContext.getBlenderKey().isFixUpAxis()) { - boolean temp = isMirrored[1]; - isMirrored[1] = isMirrored[2]; - isMirrored[2] = temp; - } - mirrorU = (flag & FLAG_MIRROR_U) != 0; - mirrorV = (flag & FLAG_MIRROR_V) != 0; - mirrorVGroup = (flag & FLAG_MIRROR_VERTEX_GROUP) != 0; - merge = (flag & FLAG_MIRROR_MERGE) == 0;// in this case we use == instead of != (this is not a mistake) - - tolerance = ((Number) modifierStructure.getFieldValue("tolerance")).floatValue(); - pMirrorObject = (Pointer) modifierStructure.getFieldValue("mirror_ob"); - - if (mirrorVGroup) { - LOGGER.warning("Mirroring vertex groups is currently not supported."); - } - } - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Mirror modifier is invalid! Cannot be applied to: {0}", node.getName()); - } else { - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - LOGGER.log(Level.FINE, "Applying mirror modifier to: {0}", temporalMesh); - Vector3f mirrorPlaneCenter = new Vector3f(); - if (pMirrorObject.isNotNull()) { - Structure objectStructure; - try { - objectStructure = pMirrorObject.fetchData().get(0); - ObjectHelper objectHelper = blenderContext.getHelper(ObjectHelper.class); - Node object = (Node) objectHelper.toObject(objectStructure, blenderContext); - if (object != null) { - // compute the mirror object coordinates in node's local space - mirrorPlaneCenter = this.getWorldMatrix(node).invertLocal().mult(object.getWorldTranslation()); - } - } catch (BlenderFileException e) { - LOGGER.log(Level.SEVERE, "Cannot load mirror''s reference object. Cause: {0}", e.getLocalizedMessage()); - LOGGER.log(Level.SEVERE, "Mirror modifier will not be applied to node named: {0}", node.getName()); - return; - } - } - - LOGGER.finest("Allocating temporal variables."); - float d; - Vector3f mirrorPlaneNormal = new Vector3f(); - Vector3f shiftVector = new Vector3f(); - - LOGGER.fine("Mirroring mesh."); - for (int mirrorIndex = 0; mirrorIndex < 3; ++mirrorIndex) { - if (isMirrored[mirrorIndex]) { - boolean mirrorAtPoint0 = mirrorPlaneCenter.get(mirrorIndex) == 0; - if (!mirrorAtPoint0) {// compute mirror's plane normal vector in node's space - mirrorPlaneNormal.set(0, 0, 0).set(mirrorIndex, Math.signum(mirrorPlaneCenter.get(mirrorIndex))); - } - - TemporalMesh mirror = temporalMesh.clone(); - for (int i = 0; i < mirror.getVertexCount(); ++i) { - Vector3f vertex = mirror.getVertices().get(i); - Vector3f normal = mirror.getNormals().get(i); - - if (mirrorAtPoint0) { - d = Math.abs(vertex.get(mirrorIndex)); - shiftVector.set(0, 0, 0).set(mirrorIndex, -vertex.get(mirrorIndex)); - } else { - d = this.computeDistanceFromPlane(vertex, mirrorPlaneCenter, mirrorPlaneNormal); - mirrorPlaneNormal.mult(d, shiftVector); - } - - if (merge && d <= tolerance) { - vertex.addLocal(shiftVector); - normal.set(mirrorIndex, 0); - temporalMesh.getVertices().get(i).addLocal(shiftVector); - temporalMesh.getNormals().get(i).set(mirrorIndex, 0); - } else { - vertex.addLocal(shiftVector.multLocal(2)); - normal.set(mirrorIndex, -normal.get(mirrorIndex)); - } - } - - // flipping the indexes - for (Face face : mirror.getFaces()) { - face.flipIndexes(); - } - for (Edge edge : mirror.getEdges()) { - edge.flipIndexes(); - } - Collections.reverse(mirror.getPoints()); - - if (mirrorU || mirrorV) { - for (Face face : mirror.getFaces()) { - face.flipUV(mirrorU, mirrorV); - } - } - - temporalMesh.append(mirror); - } - } - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } - - /** - * Fetches the world matrix transformation of the given node. - * @param node - * the node - * @return the node's world transformation matrix - */ - private Matrix4f getWorldMatrix(Node node) { - Matrix4f result = new Matrix4f(); - result.setTranslation(node.getWorldTranslation()); - result.setRotationQuaternion(node.getWorldRotation()); - result.setScale(node.getWorldScale()); - return result; - } - - /** - * The method computes the distance between a point and a plane (described by point in space and normal vector). - * @param p - * the point in the space - * @param c - * mirror's plane center - * @param n - * mirror's plane normal (should be normalized) - * @return the minimum distance from point to plane - */ - private float computeDistanceFromPlane(Vector3f p, Vector3f c, Vector3f n) { - return Math.abs(n.dot(p) - c.dot(n)); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java deleted file mode 100644 index 8890000560..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/Modifier.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.List; - -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; - -/** - * This class represents an object's modifier. The modifier object can be varied - * and the user needs to know what is the type of it for the specified type - * name. For example "ArmatureModifierData" type specified in blender is - * represented by AnimData object from jMonkeyEngine. - * - * @author Marcin Roguski (Kaelthas) - */ -public abstract class Modifier { - public static final String ARRAY_MODIFIER_DATA = "ArrayModifierData"; - public static final String ARMATURE_MODIFIER_DATA = "ArmatureModifierData"; - public static final String PARTICLE_MODIFIER_DATA = "ParticleSystemModifierData"; - public static final String MIRROR_MODIFIER_DATA = "MirrorModifierData"; - public static final String SUBSURF_MODIFIER_DATA = "SubsurfModifierData"; - public static final String OBJECT_ANIMATION_MODIFIER_DATA = "ObjectAnimationModifierData"; - - /** This variable indicates if the modifier is invalid (true) or not (false). */ - protected boolean invalid; - /** - * A variable that tells if the modifier causes modification. Some modifiers like ArmatureModifier might have no - * Armature object attached and thus not really modifying the feature. In such cases it is good to know if it is - * sense to add the modifier to the list of object's modifiers. - */ - protected boolean modifying = true; - - /** - * This method applies the modifier to the given node. - * - * @param node - * the node that will have modifier applied - * @param blenderContext - * the blender context - */ - public abstract void apply(Node node, BlenderContext blenderContext); - - /** - * The method that is called when geometries are already created. - * @param node - * the node that will have the modifier applied - * @param blenderContext - * the blender context - */ - public void postMeshCreationApply(Node node, BlenderContext blenderContext) { - } - - /** - * Determines if the modifier can be applied multiple times over one mesh. - * At this moment only armature and object animation modifiers cannot be - * applied multiple times. - * - * @param modifierType - * the type name of the modifier - * @return true if the modifier can be applied many times and - * false otherwise - */ - public static boolean canBeAppliedMultipleTimes(String modifierType) { - return !(ARMATURE_MODIFIER_DATA.equals(modifierType) || OBJECT_ANIMATION_MODIFIER_DATA.equals(modifierType)); - } - - protected boolean validate(Structure modifierStructure, BlenderContext blenderContext) { - Structure modifierData = (Structure) modifierStructure.getFieldValue("modifier"); - Pointer pError = (Pointer) modifierData.getFieldValue("error"); - invalid = pError.isNotNull(); - return !invalid; - } - - /** - * @return true if the modifier causes feature's modification or false if not - */ - public boolean isModifying() { - return modifying; - } - - protected TemporalMesh getTemporalMesh(Node node) { - List children = node.getChildren(); - if (children != null && children.size() == 1 && children.get(0) instanceof TemporalMesh) { - return (TemporalMesh) children.get(0); - } - return null; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java deleted file mode 100644 index 4f1fae3bde..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ModifierHelper.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; - -/** - * A class that is used in modifiers calculations. - * - * @author Marcin Roguski - */ -public class ModifierHelper extends AbstractBlenderHelper { - - private static final Logger LOGGER = Logger.getLogger(ModifierHelper.class.getName()); - - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public ModifierHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This method reads the given object's modifiers. - * - * @param objectStructure - * the object structure - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public Collection readModifiers(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Set alreadyReadModifiers = new HashSet(); - Collection result = new ArrayList(); - Structure modifiersListBase = (Structure) objectStructure.getFieldValue("modifiers"); - List modifiers = modifiersListBase.evaluateListBase(); - for (Structure modifierStructure : modifiers) { - String modifierType = modifierStructure.getType(); - if (!Modifier.canBeAppliedMultipleTimes(modifierType) && alreadyReadModifiers.contains(modifierType)) { - LOGGER.log(Level.WARNING, "Modifier {0} can only be applied once to object: {1}", new Object[] { modifierType, objectStructure.getName() }); - } else { - Modifier modifier = null; - if (Modifier.ARRAY_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ArrayModifier(modifierStructure, blenderContext); - } else if (Modifier.MIRROR_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new MirrorModifier(modifierStructure, blenderContext); - } else if (Modifier.ARMATURE_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ArmatureModifier(objectStructure, modifierStructure, blenderContext); - } else if (Modifier.PARTICLE_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new ParticlesModifier(modifierStructure, blenderContext); - } else if(Modifier.SUBSURF_MODIFIER_DATA.equals(modifierStructure.getType())) { - modifier = new SubdivisionSurfaceModifier(modifierStructure, blenderContext); - } - - if (modifier != null) { - if (modifier.isModifying()) { - result.add(modifier); - alreadyReadModifiers.add(modifierType); - } else { - LOGGER.log(Level.WARNING, "The modifier {0} will cause no changes in the model. It will be ignored!", modifierStructure.getName()); - } - } else { - LOGGER.log(Level.WARNING, "Unsupported modifier type: {0}", modifierStructure.getType()); - } - } - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java deleted file mode 100644 index c01d269bbe..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/ParticlesModifier.java +++ /dev/null @@ -1,107 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.effect.ParticleEmitter; -import com.jme3.effect.shapes.EmitterMeshVertexShape; -import com.jme3.effect.shapes.EmitterShape; -import com.jme3.material.Material; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.particles.ParticlesHelper; - -/** - * This modifier allows to add particles to the object. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ParticlesModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(MirrorModifier.class.getName()); - - /** Loaded particles emitter. */ - private ParticleEmitter particleEmitter; - - /** - * This constructor reads the particles system structure and stores it in - * order to apply it later to the node. - * - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is throw wneh there are problems with the - * blender file - */ - public ParticlesModifier(Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if (this.validate(modifierStructure, blenderContext)) { - Pointer pParticleSystem = (Pointer) modifierStructure.getFieldValue("psys"); - if (pParticleSystem.isNotNull()) { - ParticlesHelper particlesHelper = blenderContext.getHelper(ParticlesHelper.class); - Structure particleSystem = pParticleSystem.fetchData().get(0); - particleEmitter = particlesHelper.toParticleEmitter(particleSystem); - } - } - } - - @Override - public void postMeshCreationApply(Node node, BlenderContext blenderContext) { - LOGGER.log(Level.FINE, "Applying particles modifier to: {0}", node); - - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - ParticleEmitter emitter = particleEmitter.clone(); - - // veryfying the alpha function for particles' texture - Integer alphaFunction = MaterialHelper.ALPHA_MASK_HYPERBOLE; - char nameSuffix = emitter.getName().charAt(emitter.getName().length() - 1); - if (nameSuffix == 'B' || nameSuffix == 'N') { - alphaFunction = MaterialHelper.ALPHA_MASK_NONE; - } - // removing the type suffix from the name - emitter.setName(emitter.getName().substring(0, emitter.getName().length() - 1)); - - // applying emitter shape - EmitterShape emitterShape = emitter.getShape(); - List meshes = new ArrayList(); - for (Spatial spatial : node.getChildren()) { - if (spatial instanceof Geometry) { - Mesh mesh = ((Geometry) spatial).getMesh(); - if (mesh != null) { - meshes.add(mesh); - Material material = materialHelper.getParticlesMaterial(((Geometry) spatial).getMaterial(), alphaFunction, blenderContext); - emitter.setMaterial(material);// TODO: divide into several pieces - } - } - } - if (meshes.size() > 0 && emitterShape instanceof EmitterMeshVertexShape) { - ((EmitterMeshVertexShape) emitterShape).setMeshes(meshes); - } - - node.attachChild(emitter); - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Particles modifier is invalid! Cannot be applied to: {0}", node.getName()); - } else { - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if(temporalMesh != null) { - temporalMesh.applyAfterMeshCreate(this); - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java deleted file mode 100644 index 402efc3d3f..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/SubdivisionSurfaceModifier.java +++ /dev/null @@ -1,642 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.Edge; -import com.jme3.scene.plugins.blender.meshes.Face; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.textures.TexturePixel; - -/** - * A modifier that subdivides the mesh using either simple or catmull-clark subdivision. - * - * @author Marcin Roguski (Kaelthas) - */ -public class SubdivisionSurfaceModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(SubdivisionSurfaceModifier.class.getName()); - - private static final int TYPE_CATMULLCLARK = 0; - private static final int TYPE_SIMPLE = 1; - - private static final int FLAG_SUBDIVIDE_UVS = 0x8; - - /** The subdivision type. */ - private int subdivType; - /** The amount of subdivision levels. */ - private int levels; - /** Indicates if the UV's should also be subdivided. */ - private boolean subdivideUVS; - /** Stores the vertices that are located on original edges of the mesh. */ - private Set verticesOnOriginalEdges = new HashSet(); - - /** - * Constructor loads all necessary modifier data. - * @param modifierStructure - * the modifier structure - * @param blenderContext - * the blender context - */ - public SubdivisionSurfaceModifier(Structure modifierStructure, BlenderContext blenderContext) { - if (this.validate(modifierStructure, blenderContext)) { - subdivType = ((Number) modifierStructure.getFieldValue("subdivType")).intValue(); - levels = ((Number) modifierStructure.getFieldValue("levels")).intValue(); - int flag = ((Number) modifierStructure.getFieldValue("flags")).intValue(); - subdivideUVS = (flag & FLAG_SUBDIVIDE_UVS) != 0 && subdivType == TYPE_CATMULLCLARK; - - if (subdivType != TYPE_CATMULLCLARK && subdivType != TYPE_SIMPLE) { - LOGGER.log(Level.SEVERE, "Unknown subdivision type: {0}.", subdivType); - invalid = true; - } - if (levels < 0) { - LOGGER.severe("The amount of subdivision levels cannot be negative."); - invalid = true; - } - } - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Subdivision surface modifier is invalid! Cannot be applied to: {0}", node.getName()); - } else if (levels > 0) {// no need to do anything if the levels is set to zero - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - LOGGER.log(Level.FINE, "Applying subdivision surface modifier to: {0}", temporalMesh); - verticesOnOriginalEdges.clear();//in case the instance of this class was used more than once - - for (Edge edge : temporalMesh.getEdges()) { - verticesOnOriginalEdges.add(edge.getFirstIndex()); - verticesOnOriginalEdges.add(edge.getSecondIndex()); - } - - if (subdivType == TYPE_CATMULLCLARK) { - for (int i = 0; i < levels; ++i) { - this.subdivideSimple(temporalMesh);// first do simple subdivision ... - this.subdivideCatmullClark(temporalMesh);// ... and then apply Catmull-Clark algorithm - if (subdivideUVS) {// UV's can be subdivided only for Catmull-Clark subdivision algorithm - this.subdivideUVs(temporalMesh); - } - } - } else { - for (int i = 0; i < levels; ++i) { - this.subdivideSimple(temporalMesh); - } - } - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } - } - - /** - * Catmull-Clark subdivision. It assumes that the mesh was already simple-subdivided. - * @param temporalMesh - * the mesh whose vertices will be transformed to form Catmull-Clark subdivision - */ - private void subdivideCatmullClark(TemporalMesh temporalMesh) { - Set boundaryVertices = new HashSet(); - for (Edge edge : temporalMesh.getEdges()) { - if (!edge.isInFace()) { - boundaryVertices.add(edge.getFirstIndex()); - boundaryVertices.add(edge.getSecondIndex()); - } else { - if (temporalMesh.isBoundary(edge.getFirstIndex())) { - boundaryVertices.add(edge.getFirstIndex()); - } - if (temporalMesh.isBoundary(edge.getSecondIndex())) { - boundaryVertices.add(edge.getSecondIndex()); - } - } - } - - List creasePoints = new ArrayList(temporalMesh.getVertexCount()); - for (int i = 0; i < temporalMesh.getVertexCount(); ++i) { - // finding adjacent edges that were created by dividing original edges - List adjacentOriginalEdges = new ArrayList(); - Collection adjacentEdges = temporalMesh.getAdjacentEdges(i); - if(adjacentEdges != null) {// this can be null if a vertex with index 'i' is not connected to any face nor edge - for (Edge edge : temporalMesh.getAdjacentEdges(i)) { - if (verticesOnOriginalEdges.contains(edge.getFirstIndex()) || verticesOnOriginalEdges.contains(edge.getSecondIndex())) { - adjacentOriginalEdges.add(edge); - } - } - - creasePoints.add(new CreasePoint(i, boundaryVertices.contains(i), adjacentOriginalEdges, temporalMesh)); - } else { - creasePoints.add(null);//the count of crease points must be equal to vertex count; otherwise we'll get IndexOutofBoundsException later - } - } - - Vector3f[] averageVert = new Vector3f[temporalMesh.getVertexCount()]; - int[] averageCount = new int[temporalMesh.getVertexCount()]; - - for (Face face : temporalMesh.getFaces()) { - Vector3f centroid = face.computeCentroid(); - - for (Integer index : face.getIndexes()) { - if (boundaryVertices.contains(index)) { - Edge edge = this.findEdge(temporalMesh, index, face.getIndexes().getNextIndex(index)); - if (temporalMesh.isBoundary(edge)) { - averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid()); - averageCount[index] += 1; - } - edge = this.findEdge(temporalMesh, face.getIndexes().getPreviousIndex(index), index); - if (temporalMesh.isBoundary(edge)) { - averageVert[index] = averageVert[index] == null ? edge.computeCentroid() : averageVert[index].addLocal(edge.computeCentroid()); - averageCount[index] += 1; - } - } else { - averageVert[index] = averageVert[index] == null ? centroid.clone() : averageVert[index].addLocal(centroid); - averageCount[index] += 1; - } - } - } - for (Edge edge : temporalMesh.getEdges()) { - if (!edge.isInFace()) { - Vector3f centroid = temporalMesh.getVertices().get(edge.getFirstIndex()).add(temporalMesh.getVertices().get(edge.getSecondIndex())).divideLocal(2); - - averageVert[edge.getFirstIndex()] = averageVert[edge.getFirstIndex()] == null ? centroid.clone() : averageVert[edge.getFirstIndex()].addLocal(centroid); - averageVert[edge.getSecondIndex()] = averageVert[edge.getSecondIndex()] == null ? centroid.clone() : averageVert[edge.getSecondIndex()].addLocal(centroid); - averageCount[edge.getFirstIndex()] += 1; - averageCount[edge.getSecondIndex()] += 1; - } - } - - for (int i = 0; i < averageVert.length; ++i) { - if(averageVert[i] != null && averageCount[i] > 0) { - Vector3f v = temporalMesh.getVertices().get(i); - averageVert[i].divideLocal(averageCount[i]); - - // computing translation vector - Vector3f t = averageVert[i].subtract(v); - if (!boundaryVertices.contains(i)) { - t.multLocal(4 / (float) averageCount[i]); - } - - // moving the vertex - v.addLocal(t); - - // applying crease weight if necessary - CreasePoint creasePoint = creasePoints.get(i); - if (creasePoint.getTarget() != null && creasePoint.getWeight() != 0) { - t = creasePoint.getTarget().subtractLocal(v).multLocal(creasePoint.getWeight()); - v.addLocal(t); - } - } - } - } - - /** - * The method performs a simple subdivision of the mesh. - * - * @param temporalMesh - * the mesh to be subdivided - */ - private void subdivideSimple(TemporalMesh temporalMesh) { - Map edgePoints = new HashMap(); - Map facePoints = new HashMap(); - Set newFaces = new LinkedHashSet(); - Set newEdges = new LinkedHashSet(temporalMesh.getEdges().size() * 4); - - int originalFacesCount = temporalMesh.getFaces().size(); - - List> vertexGroups = temporalMesh.getVertexGroups(); - // the result vertex array will have verts in the following order [[original_verts], [face_verts], [edge_verts]] - List vertices = temporalMesh.getVertices(); - List edgeVertices = new ArrayList(); - List faceVertices = new ArrayList(); - // the same goes for normals - List normals = temporalMesh.getNormals(); - List edgeNormals = new ArrayList(); - List faceNormals = new ArrayList(); - - List faces = temporalMesh.getFaces(); - for (Face face : faces) { - Map> uvSets = face.getUvSets(); - - Vector3f facePoint = face.computeCentroid(); - Integer facePointIndex = vertices.size() + faceVertices.size(); - facePoints.put(face, facePointIndex); - faceVertices.add(facePoint); - faceNormals.add(this.computeFaceNormal(face)); - Map faceUV = this.computeFaceUVs(face); - byte[] faceVertexColor = this.computeFaceVertexColor(face); - Map faceVertexGroups = this.computeFaceVertexGroups(face); - if (vertexGroups.size() > 0) { - vertexGroups.add(faceVertexGroups); - } - - for (int i = 0; i < face.getIndexes().size(); ++i) { - int vIndex = face.getIndexes().get(i); - int vPrevIndex = i == 0 ? face.getIndexes().get(face.getIndexes().size() - 1) : face.getIndexes().get(i - 1); - int vNextIndex = i == face.getIndexes().size() - 1 ? face.getIndexes().get(0) : face.getIndexes().get(i + 1); - - Edge prevEdge = this.findEdge(temporalMesh, vPrevIndex, vIndex); - Edge nextEdge = this.findEdge(temporalMesh, vIndex, vNextIndex); - int vPrevEdgeVertIndex = edgePoints.containsKey(prevEdge) ? edgePoints.get(prevEdge) : -1; - int vNextEdgeVertIndex = edgePoints.containsKey(nextEdge) ? edgePoints.get(nextEdge) : -1; - - Vector3f v = temporalMesh.getVertices().get(vIndex); - if (vPrevEdgeVertIndex < 0) { - vPrevEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size(); - verticesOnOriginalEdges.add(vPrevEdgeVertIndex); - edgeVertices.add(vertices.get(vPrevIndex).add(v).divideLocal(2)); - edgeNormals.add(normals.get(vPrevIndex).add(normals.get(vIndex)).normalizeLocal()); - edgePoints.put(prevEdge, vPrevEdgeVertIndex); - if (vertexGroups.size() > 0) { - vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vPrevIndex), vertexGroups.get(vIndex)))); - } - } - if (vNextEdgeVertIndex < 0) { - vNextEdgeVertIndex = vertices.size() + originalFacesCount + edgeVertices.size(); - verticesOnOriginalEdges.add(vNextEdgeVertIndex); - edgeVertices.add(vertices.get(vNextIndex).add(v).divideLocal(2)); - edgeNormals.add(normals.get(vNextIndex).add(normals.get(vIndex)).normalizeLocal()); - edgePoints.put(nextEdge, vNextEdgeVertIndex); - if (vertexGroups.size() > 0) { - vertexGroups.add(this.interpolateVertexGroups(Arrays.asList(vertexGroups.get(vNextIndex), vertexGroups.get(vIndex)))); - } - } - - Integer[] indexes = new Integer[] { vIndex, vNextEdgeVertIndex, facePointIndex, vPrevEdgeVertIndex }; - - Map> newUVSets = null; - if (uvSets != null) { - newUVSets = new HashMap>(uvSets.size()); - for (Entry> uvset : uvSets.entrySet()) { - int indexOfvIndex = i; - int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex); - int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex); - - Vector2f uv1 = uvset.getValue().get(indexOfvIndex); - Vector2f uv2 = uvset.getValue().get(indexOfvNextIndex).add(uv1).divideLocal(2); - Vector2f uv3 = faceUV.get(uvset.getKey()); - Vector2f uv4 = uvset.getValue().get(indexOfvPrevIndex).add(uv1).divideLocal(2); - List uvList = Arrays.asList(uv1, uv2, uv3, uv4); - newUVSets.put(uvset.getKey(), new ArrayList(uvList)); - } - } - - List vertexColors = null; - if (face.getVertexColors() != null) { - - int indexOfvIndex = i; - int indexOfvPrevIndex = face.getIndexes().indexOf(vPrevIndex); - int indexOfvNextIndex = face.getIndexes().indexOf(vNextIndex); - - byte[] vCol1 = face.getVertexColors().get(indexOfvIndex); - byte[] vCol2 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvNextIndex), vCol1); - byte[] vCol3 = faceVertexColor; - byte[] vCol4 = this.interpolateVertexColors(face.getVertexColors().get(indexOfvPrevIndex), vCol1); - vertexColors = new ArrayList(Arrays.asList(vCol1, vCol2, vCol3, vCol4)); - } - - newFaces.add(new Face(indexes, face.isSmooth(), face.getMaterialNumber(), newUVSets, vertexColors, temporalMesh)); - - newEdges.add(new Edge(vIndex, vNextEdgeVertIndex, nextEdge.getCrease(), true, temporalMesh)); - newEdges.add(new Edge(vNextEdgeVertIndex, facePointIndex, 0, true, temporalMesh)); - newEdges.add(new Edge(facePointIndex, vPrevEdgeVertIndex, 0, true, temporalMesh)); - newEdges.add(new Edge(vPrevEdgeVertIndex, vIndex, prevEdge.getCrease(), true, temporalMesh)); - } - } - - vertices.addAll(faceVertices); - vertices.addAll(edgeVertices); - normals.addAll(faceNormals); - normals.addAll(edgeNormals); - - for (Edge edge : temporalMesh.getEdges()) { - if (!edge.isInFace()) { - int newVertexIndex = vertices.size(); - vertices.add(vertices.get(edge.getFirstIndex()).add(vertices.get(edge.getSecondIndex())).divideLocal(2)); - normals.add(normals.get(edge.getFirstIndex()).add(normals.get(edge.getSecondIndex())).normalizeLocal()); - - newEdges.add(new Edge(edge.getFirstIndex(), newVertexIndex, edge.getCrease(), false, temporalMesh)); - newEdges.add(new Edge(newVertexIndex, edge.getSecondIndex(), edge.getCrease(), false, temporalMesh)); - verticesOnOriginalEdges.add(newVertexIndex); - } - } - - temporalMesh.getFaces().clear(); - temporalMesh.getFaces().addAll(newFaces); - temporalMesh.getEdges().clear(); - temporalMesh.getEdges().addAll(newEdges); - - temporalMesh.rebuildIndexesMappings(); - } - - /** - * The method subdivides mesh's UV coordinates. It actually performs only Catmull-Clark modifications because if any UV's are present then they are - * automatically subdivided by the simple algorithm. - * @param temporalMesh - * the mesh whose UV coordinates will be applied Catmull-Clark algorithm - */ - private void subdivideUVs(TemporalMesh temporalMesh) { - List faces = temporalMesh.getFaces(); - Map subdividedUVS = new HashMap(); - for (Face face : faces) { - if (face.getUvSets() != null) { - for (Entry> uvset : face.getUvSets().entrySet()) { - UvCoordsSubdivideTemporalMesh uvCoordsSubdivideTemporalMesh = subdividedUVS.get(uvset.getKey()); - if (uvCoordsSubdivideTemporalMesh == null) { - try { - uvCoordsSubdivideTemporalMesh = new UvCoordsSubdivideTemporalMesh(temporalMesh.getBlenderContext()); - } catch (BlenderFileException e) { - assert false : "Something went really wrong! The UvCoordsSubdivideTemporalMesh class should NOT throw exceptions here!"; - } - subdividedUVS.put(uvset.getKey(), uvCoordsSubdivideTemporalMesh); - } - uvCoordsSubdivideTemporalMesh.addFace(uvset.getValue()); - } - } - } - - for (Entry entry : subdividedUVS.entrySet()) { - entry.getValue().rebuildIndexesMappings(); - this.subdivideCatmullClark(entry.getValue()); - - for (int i = 0; i < faces.size(); ++i) { - List uvs = faces.get(i).getUvSets().get(entry.getKey()); - if (uvs != null) { - uvs.clear(); - uvs.addAll(entry.getValue().faceToUVs(i)); - } - } - } - } - - /** - * The method computes the face's normal vector. - * @param face - * the face of the mesh - * @return face's normal vector - */ - private Vector3f computeFaceNormal(Face face) { - Vector3f result = new Vector3f(); - for (Integer index : face.getIndexes()) { - result.addLocal(face.getTemporalMesh().getNormals().get(index)); - } - result.divideLocal(face.getIndexes().size()); - return result; - } - - /** - * The method computes the UV coordinates of the face middle point. - * @param face - * the face of the mesh - * @return a map whose key is the name of the UV set and value is the UV coordinate of the face's middle point - */ - private Map computeFaceUVs(Face face) { - Map result = null; - - Map> uvSets = face.getUvSets(); - if (uvSets != null && uvSets.size() > 0) { - result = new HashMap(uvSets.size()); - - for (Entry> entry : uvSets.entrySet()) { - Vector2f faceUV = new Vector2f(); - for (Vector2f uv : entry.getValue()) { - faceUV.addLocal(uv); - } - faceUV.divideLocal(entry.getValue().size()); - result.put(entry.getKey(), faceUV); - } - } - - return result; - } - - /** - * The mesh interpolates the values of vertex groups weights for new vertices. - * @param vertexGroups - * the vertex groups - * @return interpolated weights of given vertex groups' weights - */ - private Map interpolateVertexGroups(List> vertexGroups) { - Map weightSums = new HashMap(); - if (vertexGroups.size() > 0) { - for (Map vGroup : vertexGroups) { - for (Entry entry : vGroup.entrySet()) { - if (weightSums.containsKey(entry.getKey())) { - weightSums.put(entry.getKey(), weightSums.get(entry.getKey()) + entry.getValue()); - } else { - weightSums.put(entry.getKey(), entry.getValue()); - } - } - } - } - - Map result = new HashMap(weightSums.size()); - for (Entry entry : weightSums.entrySet()) { - result.put(entry.getKey(), entry.getValue() / vertexGroups.size()); - } - - return result; - } - - /** - * The method computes the vertex groups values for face's middle point. - * @param face - * the face of the mesh - * @return face's middle point interpolated vertex groups' weights - */ - private Map computeFaceVertexGroups(Face face) { - if (face.getTemporalMesh().getVertexGroups().size() > 0) { - List> vertexGroups = new ArrayList>(face.getIndexes().size()); - for (Integer index : face.getIndexes()) { - vertexGroups.add(face.getTemporalMesh().getVertexGroups().get(index)); - } - return this.interpolateVertexGroups(vertexGroups); - } - return new HashMap(); - } - - /** - * The method computes face's middle point vertex color. - * @param face - * the face of the mesh - * @return face's middle point vertex color - */ - private byte[] computeFaceVertexColor(Face face) { - if (face.getVertexColors() != null) { - return this.interpolateVertexColors(face.getVertexColors().toArray(new byte[face.getVertexColors().size()][])); - } - return null; - } - - /** - * The method computes the average value for the given vertex colors. - * @param colors - * the vertex colors - * @return vertex colors' average value - */ - private byte[] interpolateVertexColors(byte[]... colors) { - TexturePixel pixel = new TexturePixel(); - TexturePixel temp = new TexturePixel(); - for (int i = 0; i < colors.length; ++i) { - temp.fromARGB8(colors[i][3], colors[i][0], colors[i][1], colors[i][2]); - pixel.add(temp); - } - pixel.divide(colors.length); - byte[] result = new byte[4]; - pixel.toRGBA8(result); - return result; - } - - /** - * The method finds an edge between the given vertices in the mesh. - * @param temporalMesh - * the mesh - * @param index1 - * first index of the edge - * @param index2 - * second index of the edge - * @return found edge or null - */ - private Edge findEdge(TemporalMesh temporalMesh, int index1, int index2) { - for (Edge edge : temporalMesh.getEdges()) { - if (edge.getFirstIndex() == index1 && edge.getSecondIndex() == index2 || edge.getFirstIndex() == index2 && edge.getSecondIndex() == index1) { - return edge; - } - } - return null; - } - - /** - * This is a helper class for UV coordinates subdivision. UV's form a mesh that is being applied the same algorithms as a regular mesh. - * This way one code handles two issues. After applying Catmull-Clark algorithm the UV-mesh is transformed back into UV coordinates. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class UvCoordsSubdivideTemporalMesh extends TemporalMesh { - private static final Vector3f NORMAL = new Vector3f(0, 0, 1); - - public UvCoordsSubdivideTemporalMesh(BlenderContext blenderContext) throws BlenderFileException { - super(null, blenderContext, false); - } - - /** - * Adds a UV-face to the mesh. - * @param uvs - * the UV coordinates - */ - public void addFace(List uvs) { - Integer[] indexes = new Integer[uvs.size()]; - int i = 0; - - for (Vector2f uv : uvs) { - Vector3f v = new Vector3f(uv.x, uv.y, 0); - int index = vertices.indexOf(v); - if (index >= 0) { - indexes[i++] = index; - } else { - indexes[i++] = vertices.size(); - vertices.add(v); - normals.add(NORMAL); - } - } - faces.add(new Face(indexes, false, 0, null, null, this)); - for (i = 1; i < indexes.length; ++i) { - edges.add(new Edge(indexes[i - 1], indexes[i], 0, true, this)); - } - edges.add(new Edge(indexes[indexes.length - 1], indexes[0], 0, true, this)); - } - - /** - * Converts the mesh back into UV coordinates for the given face. - * @param faceIndex - * the index of the face - * @return UV coordinates - */ - public List faceToUVs(int faceIndex) { - Face face = faces.get(faceIndex); - List result = new ArrayList(face.getIndexes().size()); - for (Integer index : face.getIndexes()) { - Vector3f v = vertices.get(index); - result.add(new Vector2f(v.x, v.y)); - } - return result; - } - } - - /** - * A point computed for each vertex before applying CC subdivision and after simple subdivision. - * This class has a target where the vertices will be drawn to with a proper strength (value from 0 to 1). - * - * The algorithm of computing the target point was made by observing how blender behaves. - * If a vertex has one or less creased edges (means edges that have non zero crease value) the target will not exist. - * If a vertex is a border vertex and has two creased edges - the target will be the original simple subdivided vertex. - * If a vertex is not a border vertex and have two creased edges - then it will be drawned to the plane defined by those - * two edges. - * If a vertex has 3 or more creased edges it will be drawn to its original vertex before CC subdivision with average strength - * computed from edges' crease values. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class CreasePoint { - private Vector3f target = new Vector3f(); - private float weight; - private int index; - - public CreasePoint(int index, boolean borderIndex, List creaseEdges, TemporalMesh temporalMesh) { - this.index = index; - if (creaseEdges == null || creaseEdges.size() <= 1) { - target = null;// crease is used when vertex belongs to at least 2 creased edges - } else { - int creasedEdgesCount = 0; - for (Edge edge : creaseEdges) { - if (edge.getCrease() > 0) { - ++creasedEdgesCount; - weight += edge.getCrease(); - target.addLocal(temporalMesh.getVertices().get(edge.getOtherIndex(index))); - } - } - - if (creasedEdgesCount <= 1) { - target = null;// crease is used when vertex belongs to at least 2 creased edges - } else if (creasedEdgesCount == 2) { - if (borderIndex) { - target.set(temporalMesh.getVertices().get(index)); - } else { - target.addLocal(temporalMesh.getVertices().get(index)).divideLocal(creasedEdgesCount + 1); - } - } else { - target.set(temporalMesh.getVertices().get(index)); - } - if (creasedEdgesCount > 0) { - weight /= creasedEdgesCount; - } - } - } - - public Vector3f getTarget() { - return target; - } - - public float getWeight() { - return weight; - } - - @Override - public String toString() { - return "CreasePoint [index = " + index + ", target=" + target + ", weight=" + weight + "]"; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java deleted file mode 100644 index 375565975e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/modifiers/TriangulateModifier.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.jme3.scene.plugins.blender.modifiers; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.Node; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; - -/** - * The triangulation modifier. It does not take any settings into account so if the result is different than - * in blender then please apply the modifier before importing. - * - * @author Marcin Roguski - */ -public class TriangulateModifier extends Modifier { - private static final Logger LOGGER = Logger.getLogger(TriangulateModifier.class.getName()); - - /** - * This constructor reads animation data from the object structore. The - * stored data is the AnimData and additional data is armature's OMA. - * - * @param objectStructure - * the structure of the object - * @param modifierStructure - * the structure of the modifier - * @param blenderContext - * the blender context - * @throws BlenderFileException - * this exception is thrown when the blender file is somehow - * corrupted - */ - public TriangulateModifier(Structure objectStructure, Structure modifierStructure, BlenderContext blenderContext) throws BlenderFileException { - if (this.validate(modifierStructure, blenderContext)) { - LOGGER.warning("Triangulation modifier does not take modifier options into account. If triangulation result is different" + " than the model in blender please apply the modifier before importing!"); - } - } - - @Override - public void apply(Node node, BlenderContext blenderContext) { - if (invalid) { - LOGGER.log(Level.WARNING, "Triangulate modifier is invalid! Cannot be applied to: {0}", node.getName()); - } - TemporalMesh temporalMesh = this.getTemporalMesh(node); - if (temporalMesh != null) { - LOGGER.log(Level.FINE, "Applying triangulation modifier to: {0}", temporalMesh); - temporalMesh.triangulate(); - } else { - LOGGER.log(Level.WARNING, "Cannot find temporal mesh for node: {0}. The modifier will NOT be applied!", node); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java deleted file mode 100644 index 17c0d421e9..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/ObjectHelper.java +++ /dev/null @@ -1,520 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.objects; - -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; -import java.util.Collection; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.light.Light; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix4f; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.scene.CameraNode; -import com.jme3.scene.Geometry; -import com.jme3.scene.LightNode; -import com.jme3.scene.Mesh.Mode; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.Spatial.CullHint; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.animations.AnimationHelper; -import com.jme3.scene.plugins.blender.cameras.CameraHelper; -import com.jme3.scene.plugins.blender.constraints.ConstraintHelper; -import com.jme3.scene.plugins.blender.curves.CurvesHelper; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.lights.LightHelper; -import com.jme3.scene.plugins.blender.meshes.MeshHelper; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.modifiers.Modifier; -import com.jme3.scene.plugins.blender.modifiers.ModifierHelper; -import com.jme3.util.TempVars; - -/** - * A class that is used in object calculations. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ObjectHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ObjectHelper.class.getName()); - - public static final String OMA_MARKER = "oma"; - public static final String ARMATURE_NODE_MARKER = "armature-node"; - - /** - * This constructor parses the given blender version and stores the result. - * Some functionalities may differ in different blender versions. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public ObjectHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This method reads the given structure and createn an object that - * represents the data. - * - * @param objectStructure - * the object's structure - * @param blenderContext - * the blender context - * @return blener's object representation or null if its type is excluded from loading - * @throws BlenderFileException - * an exception is thrown when the given data is inapropriate - */ - public Object toObject(Structure objectStructure, BlenderContext blenderContext) throws BlenderFileException { - Object loadedResult = blenderContext.getLoadedFeature(objectStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (loadedResult != null) { - return loadedResult; - } - - LOGGER.fine("Loading blender object."); - if ("ID".equals(objectStructure.getType())) { - Node object = (Node) this.loadLibrary(objectStructure); - if (object.getParent() != null) { - LOGGER.log(Level.FINEST, "Detaching object {0}, loaded from external file, from its parent.", object); - object.getParent().detachChild(object); - } - return object; - } - int type = ((Number) objectStructure.getFieldValue("type")).intValue(); - ObjectType objectType = ObjectType.valueOf(type); - LOGGER.log(Level.FINE, "Type of the object: {0}.", objectType); - - int lay = ((Number) objectStructure.getFieldValue("lay")).intValue(); - if ((lay & blenderContext.getBlenderKey().getLayersToLoad()) == 0) { - LOGGER.fine("The layer this object is located in is not included in loading."); - return null; - } - - blenderContext.pushParent(objectStructure); - String name = objectStructure.getName(); - LOGGER.log(Level.FINE, "Loading obejct: {0}", name); - - int restrictflag = ((Number) objectStructure.getFieldValue("restrictflag")).intValue(); - boolean visible = (restrictflag & 0x01) != 0; - - Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); - Object parent = blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (parent == null && pParent.isNotNull()) { - Structure parentStructure = pParent.fetchData().get(0); - parent = this.toObject(parentStructure, blenderContext); - } - - Transform t = this.getTransformation(objectStructure, blenderContext); - LOGGER.log(Level.FINE, "Importing object of type: {0}", objectType); - Node result = null; - try { - switch (objectType) { - case LATTICE: - case METABALL: - case TEXT: - case WAVE: - LOGGER.log(Level.WARNING, "{0} type is not supported but the node will be returned in order to keep parent - child relationship.", objectType); - case EMPTY: - case ARMATURE: - // need to use an empty node to properly create - // parent-children relationships between nodes - result = new Node(name); - break; - case MESH: - result = new Node(name); - MeshHelper meshHelper = blenderContext.getHelper(MeshHelper.class); - Pointer pMesh = (Pointer) objectStructure.getFieldValue("data"); - List meshesArray = pMesh.fetchData(); - TemporalMesh temporalMesh = meshHelper.toTemporalMesh(meshesArray.get(0), blenderContext); - if (temporalMesh != null) { - result.attachChild(temporalMesh); - } - break; - case SURF: - case CURVE: - result = new Node(name); - Pointer pCurve = (Pointer) objectStructure.getFieldValue("data"); - if (pCurve.isNotNull()) { - CurvesHelper curvesHelper = blenderContext.getHelper(CurvesHelper.class); - Structure curveData = pCurve.fetchData().get(0); - TemporalMesh curvesTemporalMesh = curvesHelper.toCurve(curveData, blenderContext); - if (curvesTemporalMesh != null) { - result.attachChild(curvesTemporalMesh); - } - } - break; - case LAMP: - Pointer pLamp = (Pointer) objectStructure.getFieldValue("data"); - if (pLamp.isNotNull()) { - LightHelper lightHelper = blenderContext.getHelper(LightHelper.class); - List lampsArray = pLamp.fetchData(); - Light light = lightHelper.toLight(lampsArray.get(0), blenderContext); - if (light == null) { - // probably some light type is not supported, just create a node so that we can maintain child-parent relationship for nodes - result = new Node(name); - } else { - result = new LightNode(name, light); - } - } - break; - case CAMERA: - Pointer pCamera = (Pointer) objectStructure.getFieldValue("data"); - if (pCamera.isNotNull()) { - CameraHelper cameraHelper = blenderContext.getHelper(CameraHelper.class); - List camerasArray = pCamera.fetchData(); - Camera camera = cameraHelper.toCamera(camerasArray.get(0), blenderContext); - if (camera == null) { - // just create a node so that we can maintain child-parent relationship for nodes - result = new Node(name); - } else { - result = new CameraNode(name, camera); - } - } - break; - default: - LOGGER.log(Level.WARNING, "Unsupported object type: {0}", type); - } - - if (result != null) { - LOGGER.fine("Storing loaded feature in blender context and applying markers (those will be removed before the final result is released)."); - Long oma = objectStructure.getOldMemoryAddress(); - blenderContext.addLoadedFeatures(oma, LoadedDataType.STRUCTURE, objectStructure); - blenderContext.addLoadedFeatures(oma, LoadedDataType.FEATURE, result); - - blenderContext.addMarker(OMA_MARKER, result, objectStructure.getOldMemoryAddress()); - if (objectType == ObjectType.ARMATURE) { - blenderContext.addMarker(ARMATURE_NODE_MARKER, result, Boolean.TRUE); - } - - result.setLocalTransform(t); - result.setCullHint(visible ? CullHint.Always : CullHint.Inherit); - if (parent instanceof Node) { - ((Node) parent).attachChild(result); - } - - LOGGER.fine("Reading and applying object's modifiers."); - ModifierHelper modifierHelper = blenderContext.getHelper(ModifierHelper.class); - Collection modifiers = modifierHelper.readModifiers(objectStructure, blenderContext); - for (Modifier modifier : modifiers) { - modifier.apply(result, blenderContext); - } - - if (result.getChildren() != null && result.getChildren().size() > 0) { - if (result.getChildren().size() == 1 && result.getChild(0) instanceof TemporalMesh) { - LOGGER.fine("Converting temporal mesh into jme geometries."); - ((TemporalMesh) result.getChild(0)).toGeometries(); - } - - LOGGER.fine("Applying proper scale to the geometries."); - for (Spatial child : result.getChildren()) { - if (child instanceof Geometry) { - this.flipMeshIfRequired((Geometry) child, child.getWorldScale()); - } - } - } - - // I prefer do compute bounding box here than read it from the file - result.updateModelBound(); - - LOGGER.fine("Applying animations to the object if such are defined."); - AnimationHelper animationHelper = blenderContext.getHelper(AnimationHelper.class); - animationHelper.applyAnimations(result, blenderContext.getBlenderKey().getAnimationMatchMethod()); - - LOGGER.fine("Loading constraints connected with this object."); - ConstraintHelper constraintHelper = blenderContext.getHelper(ConstraintHelper.class); - constraintHelper.loadConstraints(objectStructure, blenderContext); - - LOGGER.fine("Loading custom properties."); - if (blenderContext.getBlenderKey().isLoadObjectProperties()) { - Properties properties = this.loadProperties(objectStructure, blenderContext); - // the loaded property is a group property, so we need to get - // each value and set it to Spatial - if (properties != null && properties.getValue() != null) { - this.applyProperties(result, properties); - } - } - } - } finally { - blenderContext.popParent(); - } - return result; - } - - /** - * The method flips the mesh if the scale is mirroring it. Mirroring scale has either 1 or all 3 factors negative. - * If two factors are negative then there is no mirroring because a rotation and translation can be found that will - * lead to the same transform when all scales are positive. - * - * @param geometry - * the geometry that is being flipped if necessary - * @param scale - * the scale vector of the given geometry - */ - private void flipMeshIfRequired(Geometry geometry, Vector3f scale) { - float s = scale.x * scale.y * scale.z; - - if (s < 0 && geometry.getMesh() != null) {// negative s means that the scale is mirroring the object - FloatBuffer normals = geometry.getMesh().getFloatBuffer(Type.Normal); - if (normals != null) { - for (int i = 0; i < normals.limit(); i += 3) { - if (scale.x < 0) { - normals.put(i, -normals.get(i)); - } - if (scale.y < 0) { - normals.put(i + 1, -normals.get(i + 1)); - } - if (scale.z < 0) { - normals.put(i + 2, -normals.get(i + 2)); - } - } - } - - if (geometry.getMesh().getMode() == Mode.Triangles) {// there is no need to flip the indexes for lines and points - LOGGER.finer("Flipping index order in triangle mesh."); - Buffer indexBuffer = geometry.getMesh().getBuffer(Type.Index).getData(); - for (int i = 0; i < indexBuffer.limit(); i += 3) { - if (indexBuffer instanceof ShortBuffer) { - short index = ((ShortBuffer) indexBuffer).get(i + 1); - ((ShortBuffer) indexBuffer).put(i + 1, ((ShortBuffer) indexBuffer).get(i + 2)); - ((ShortBuffer) indexBuffer).put(i + 2, index); - } else { - int index = ((IntBuffer) indexBuffer).get(i + 1); - ((IntBuffer) indexBuffer).put(i + 1, ((IntBuffer) indexBuffer).get(i + 2)); - ((IntBuffer) indexBuffer).put(i + 2, index); - } - } - } - } - } - - /** - * Checks if the first given OMA points to a parent of the second one. - * The parent need not to be the direct one. This method should be called when we are sure - * that both of the features are alred loaded because it does not check it. - * The OMA's should point to a spatials, otherwise the function will throw ClassCastException. - * @param supposedParentOMA - * the OMA of the node that we suppose might be a parent of the second one - * @param spatialOMA - * the OMA of the scene's node - * @return true if the first given OMA points to a parent of the second one and false otherwise - */ - public boolean isParent(Long supposedParentOMA, Long spatialOMA) { - Spatial supposedParent = (Spatial) blenderContext.getLoadedFeature(supposedParentOMA, LoadedDataType.FEATURE); - Spatial spatial = (Spatial) blenderContext.getLoadedFeature(spatialOMA, LoadedDataType.FEATURE); - - Spatial parent = spatial.getParent(); - while (parent != null) { - if (parent.equals(supposedParent)) { - return true; - } - parent = parent.getParent(); - } - return false; - } - - /** - * This method calculates local transformation for the object. Parentage is - * taken under consideration. - * - * @param objectStructure - * the object's structure - * @return objects transformation relative to its parent - */ - public Transform getTransformation(Structure objectStructure, BlenderContext blenderContext) { - TempVars tempVars = TempVars.get(); - - Matrix4f parentInv = tempVars.tempMat4; - Pointer pParent = (Pointer) objectStructure.getFieldValue("parent"); - if (pParent.isNotNull()) { - Structure parentObjectStructure = (Structure) blenderContext.getLoadedFeature(pParent.getOldMemoryAddress(), LoadedDataType.STRUCTURE); - this.getMatrix(parentObjectStructure, "obmat", fixUpAxis, parentInv).invertLocal(); - } else { - parentInv.loadIdentity(); - } - - Matrix4f globalMatrix = this.getMatrix(objectStructure, "obmat", fixUpAxis, tempVars.tempMat42); - Matrix4f localMatrix = parentInv.multLocal(globalMatrix); - - this.getSizeSignums(objectStructure, tempVars.vect1); - - localMatrix.toTranslationVector(tempVars.vect2); - localMatrix.toRotationQuat(tempVars.quat1); - localMatrix.toScaleVector(tempVars.vect3); - - Transform t = new Transform(tempVars.vect2, tempVars.quat1.normalizeLocal(), tempVars.vect3.multLocal(tempVars.vect1)); - tempVars.release(); - return t; - } - - /** - * The method gets the signs of the scale factors and stores them properly in the given vector. - * @param objectStructure - * the object's structure - * @param store - * the vector where the result will be stored - */ - @SuppressWarnings("unchecked") - private void getSizeSignums(Structure objectStructure, Vector3f store) { - DynamicArray size = (DynamicArray) objectStructure.getFieldValue("size"); - if (fixUpAxis) { - store.x = Math.signum(size.get(0).floatValue()); - store.y = Math.signum(size.get(2).floatValue()); - store.z = Math.signum(size.get(1).floatValue()); - } else { - store.x = Math.signum(size.get(0).floatValue()); - store.y = Math.signum(size.get(1).floatValue()); - store.z = Math.signum(size.get(2).floatValue()); - } - } - - /** - * This method returns the matrix of a given name for the given structure. - * It takes up axis into consideration. - * - * The method that moves the matrix from Z-up axis to Y-up axis space is as follows: - * - load the matrix directly from blender (it has the Z-up axis orientation) - * - switch the second and third rows in the matrix - * - switch the second and third column in the matrix - * - multiply the values in the third row by -1 - * - multiply the values in the third column by -1 - * - * The result matrix is now in Y-up axis orientation. - * The procedure was discovered by experimenting but it looks like it's working :) - * The previous procedure transformet the loaded matrix into component (loc, rot, scale), - * switched several values and pu the back into the matrix. - * It worked fine until models with negative scale are used. - * The current method is not touched by that flaw. - * - * @param structure - * the structure with matrix data - * @param matrixName - * the name of the matrix - * @param fixUpAxis - * tells if the Y axis is a UP axis - * @param store - * the matrix where the result will pe placed - * @return the required matrix - */ - @SuppressWarnings("unchecked") - private Matrix4f getMatrix(Structure structure, String matrixName, boolean fixUpAxis, Matrix4f store) { - DynamicArray obmat = (DynamicArray) structure.getFieldValue(matrixName); - // the matrix must be square - int rowAndColumnSize = Math.abs((int) Math.sqrt(obmat.getTotalSize())); - for (int i = 0; i < rowAndColumnSize; ++i) { - for (int j = 0; j < rowAndColumnSize; ++j) { - float value = obmat.get(j, i).floatValue(); - if (Math.abs(value) <= FastMath.FLT_EPSILON) { - value = 0; - } - store.set(i, j, value); - } - } - if (fixUpAxis) { - // first switch the second and third row - for (int i = 0; i < 4; ++i) { - float temp = store.get(1, i); - store.set(1, i, store.get(2, i)); - store.set(2, i, temp); - } - - // then switch the second and third column - for (int i = 0; i < 4; ++i) { - float temp = store.get(i, 1); - store.set(i, 1, store.get(i, 2)); - store.set(i, 2, temp); - } - - // multiply the values in the third row by -1 - store.m20 *= -1; - store.m21 *= -1; - store.m22 *= -1; - store.m23 *= -1; - - // multiply the values in the third column by -1 - store.m02 *= -1; - store.m12 *= -1; - store.m22 *= -1; - store.m32 *= -1; - } - - return store; - } - - /** - * This method returns the matrix of a given name for the given structure. - * It takes up axis into consideration. - * - * @param structure - * the structure with matrix data - * @param matrixName - * the name of the matrix - * @param fixUpAxis - * tells if the Y axis is a UP axis - * @return the required matrix - */ - public Matrix4f getMatrix(Structure structure, String matrixName, boolean fixUpAxis) { - return this.getMatrix(structure, matrixName, fixUpAxis, new Matrix4f()); - } - - private static enum ObjectType { - EMPTY(0), MESH(1), CURVE(2), SURF(3), TEXT(4), METABALL(5), LAMP(10), CAMERA(11), WAVE(21), LATTICE(22), ARMATURE(25); - - private int blenderTypeValue; - - private ObjectType(int blenderTypeValue) { - this.blenderTypeValue = blenderTypeValue; - } - - public static ObjectType valueOf(int blenderTypeValue) throws BlenderFileException { - for (ObjectType type : ObjectType.values()) { - if (type.blenderTypeValue == blenderTypeValue) { - return type; - } - } - throw new BlenderFileException("Unknown type value: " + blenderTypeValue); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java deleted file mode 100644 index c1f8675438..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/objects/Properties.java +++ /dev/null @@ -1,365 +0,0 @@ -package com.jme3.scene.plugins.blender.objects; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The blender object's custom properties. - * This class is valid for all versions of blender. - * @author Marcin Roguski (Kaelthas) - */ -public class Properties implements Cloneable { - // property type - public static final int IDP_STRING = 0; - public static final int IDP_INT = 1; - public static final int IDP_FLOAT = 2; - public static final int IDP_ARRAY = 5; - public static final int IDP_GROUP = 6; - // public static final int IDP_ID = 7;//this is not implemented in blender (yet) - public static final int IDP_DOUBLE = 8; - // the following are valid for blender 2.5x+ - public static final int IDP_IDPARRAY = 9; - public static final int IDP_NUMTYPES = 10; - - protected static final String RNA_PROPERTY_NAME = "_RNA_UI"; - /** Default name of the property (used if the name is not specified in blender file). */ - protected static final String DEFAULT_NAME = "Unnamed property"; - - /** The name of the property. */ - private String name; - /** The type of the property. */ - private int type; - /** The subtype of the property. Defines the type of array's elements. */ - private int subType; - /** The value of the property. */ - private Object value; - /** The description of the property. */ - private String description; - - /** - * This method loads the property from the belnder file. - * @param idPropertyStructure - * the ID structure constining the property - * @param blenderContext - * the blender context - * @throws BlenderFileException - * an exception is thrown when the belnder file is somehow invalid - */ - public void load(Structure idPropertyStructure, BlenderContext blenderContext) throws BlenderFileException { - name = idPropertyStructure.getFieldValue("name").toString(); - if (name == null || name.length() == 0) { - name = DEFAULT_NAME; - } - subType = ((Number) idPropertyStructure.getFieldValue("subtype")).intValue(); - type = ((Number) idPropertyStructure.getFieldValue("type")).intValue(); - - // reading the data - Structure data = (Structure) idPropertyStructure.getFieldValue("data"); - int len = ((Number) idPropertyStructure.getFieldValue("len")).intValue(); - switch (type) { - case IDP_STRING: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - BlenderInputStream bis = blenderContext.getInputStream(); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - value = bis.readString(); - break; - } - case IDP_INT: - int intValue = ((Number) data.getFieldValue("val")).intValue(); - value = Integer.valueOf(intValue); - break; - case IDP_FLOAT: - int floatValue = ((Number) data.getFieldValue("val")).intValue(); - value = Float.valueOf(Float.intBitsToFloat(floatValue)); - break; - case IDP_ARRAY: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - BlenderInputStream bis = blenderContext.getInputStream(); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pointer.getOldMemoryAddress()); - bis.setPosition(dataFileBlock.getBlockPosition()); - int elementAmount = dataFileBlock.getSize(); - switch (subType) { - case IDP_INT: - elementAmount /= 4; - int[] intList = new int[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - intList[i] = bis.readInt(); - } - value = intList; - break; - case IDP_FLOAT: - elementAmount /= 4; - float[] floatList = new float[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - floatList[i] = bis.readFloat(); - } - value = floatList; - break; - case IDP_DOUBLE: - elementAmount /= 8; - double[] doubleList = new double[elementAmount]; - for (int i = 0; i < elementAmount; ++i) { - doubleList[i] = bis.readDouble(); - } - value = doubleList; - break; - default: - throw new IllegalStateException("Invalid array subtype: " + subType); - } - } - case IDP_GROUP: - Structure group = (Structure) data.getFieldValue("group"); - List dataList = group.evaluateListBase(); - List subProperties = new ArrayList(len); - for (Structure d : dataList) { - Properties properties = new Properties(); - properties.load(d, blenderContext); - subProperties.add(properties); - } - value = subProperties; - break; - case IDP_DOUBLE: - int doublePart1 = ((Number) data.getFieldValue("val")).intValue(); - int doublePart2 = ((Number) data.getFieldValue("val2")).intValue(); - long doubleVal = (long) doublePart2 << 32 | doublePart1; - value = Double.valueOf(Double.longBitsToDouble(doubleVal)); - break; - case IDP_IDPARRAY: { - Pointer pointer = (Pointer) data.getFieldValue("pointer"); - List arrays = pointer.fetchData(); - List result = new ArrayList(arrays.size()); - Properties temp = new Properties(); - for (Structure array : arrays) { - temp.load(array, blenderContext); - result.add(temp.value); - } - value = result; - break; - } - case IDP_NUMTYPES: - throw new UnsupportedOperationException(); - // case IDP_ID://not yet implemented in blender - // return null; - default: - throw new IllegalStateException("Unknown custom property type: " + type); - } - this.completeLoading(); - } - - /** - * This method returns the name of the property. - * @return the name of the property - */ - public String getName() { - return name; - } - - /** - * This method returns the value of the property. - * The type of the value depends on the type of the property. - * @return the value of the property - */ - public Object getValue() { - return value; - } - - /** - * @return the names of properties that are stored withing this property - * (assuming this property is of IDP_GROUP type) - */ - @SuppressWarnings("unchecked") - public List getSubPropertiesNames() { - List result = null; - if (type == IDP_GROUP) { - List properties = (List) value; - if (properties != null && properties.size() > 0) { - result = new ArrayList(properties.size()); - for (Properties property : properties) { - result.add(property.getName()); - } - } - } - return result; - } - - /** - * This method returns the same as getValue if the current property is of - * other type than IDP_GROUP and its name matches 'propertyName' param. If - * this property is a group property the method tries to find subproperty - * value of the given name. The first found value is returnes os use this - * method wisely. If no property of a given name is foung - null - * is returned. - * - * @param propertyName - * the name of the property - * @return found property value or null - */ - @SuppressWarnings("unchecked") - public Object findValue(String propertyName) { - if (name.equals(propertyName)) { - return value; - } else { - if (type == IDP_GROUP) { - List props = (List) value; - for (Properties p : props) { - Object v = p.findValue(propertyName); - if (v != null) { - return v; - } - } - } - } - return null; - } - - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - this.append(sb, new StringBuilder()); - return sb.toString(); - } - - /** - * This method appends the data of the property to the given string buffer. - * @param sb - * string buffer - * @param indent - * indent buffer - */ - @SuppressWarnings("unchecked") - private void append(StringBuilder sb, StringBuilder indent) { - sb.append(indent).append("name: ").append(name).append("\n\r"); - sb.append(indent).append("type: ").append(type).append("\n\r"); - sb.append(indent).append("subType: ").append(subType).append("\n\r"); - sb.append(indent).append("description: ").append(description).append("\n\r"); - indent.append('\t'); - sb.append(indent).append("value: "); - if (value instanceof Properties) { - ((Properties) value).append(sb, indent); - } else if (value instanceof List) { - for (Object v : (List) value) { - if (v instanceof Properties) { - sb.append(indent).append("{\n\r"); - indent.append('\t'); - ((Properties) v).append(sb, indent); - indent.deleteCharAt(indent.length() - 1); - sb.append(indent).append("}\n\r"); - } else { - sb.append(v); - } - } - } else { - sb.append(value); - } - sb.append("\n\r"); - indent.deleteCharAt(indent.length() - 1); - } - - /** - * This method should be called after the properties loading. - * It loads the properties from the _RNA_UI property and removes this property from the - * result list. - */ - @SuppressWarnings("unchecked") - protected void completeLoading() { - if (type == IDP_GROUP) { - List groupProperties = (List) value; - Properties rnaUI = null; - for (Properties properties : groupProperties) { - if (properties.name.equals(RNA_PROPERTY_NAME) && properties.type == IDP_GROUP) { - rnaUI = properties; - break; - } - } - if (rnaUI != null) { - // removing the RNA from the result list - groupProperties.remove(rnaUI); - - // loading the descriptions - Map descriptions = new HashMap(groupProperties.size()); - List propertiesRNA = (List) rnaUI.value; - for (Properties properties : propertiesRNA) { - String name = properties.name; - String description = null; - List rnaData = (List) properties.value; - for (Properties rna : rnaData) { - if ("description".equalsIgnoreCase(rna.name)) { - description = (String) rna.value; - break; - } - } - descriptions.put(name, description); - } - - // applying the descriptions - for (Properties properties : groupProperties) { - properties.description = descriptions.get(properties.name); - } - } - } - } - - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + (description == null ? 0 : description.hashCode()); - result = prime * result + (name == null ? 0 : name.hashCode()); - result = prime * result + subType; - result = prime * result + type; - result = prime * result + (value == null ? 0 : value.hashCode()); - return result; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (this.getClass() != obj.getClass()) { - return false; - } - Properties other = (Properties) obj; - if (description == null) { - if (other.description != null) { - return false; - } - } else if (!description.equals(other.description)) { - return false; - } - if (name == null) { - if (other.name != null) { - return false; - } - } else if (!name.equals(other.name)) { - return false; - } - if (subType != other.subType) { - return false; - } - if (type != other.type) { - return false; - } - if (value == null) { - if (other.value != null) { - return false; - } - } else if (!value.equals(other.value)) { - return false; - } - return true; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java deleted file mode 100644 index 85c093f664..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/particles/ParticlesHelper.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.jme3.scene.plugins.blender.particles; - -import com.jme3.effect.ParticleEmitter; -import com.jme3.effect.ParticleMesh.Type; -import com.jme3.effect.influencers.EmptyParticleInfluencer; -import com.jme3.effect.influencers.NewtonianParticleInfluencer; -import com.jme3.effect.influencers.ParticleInfluencer; -import com.jme3.effect.shapes.EmitterMeshConvexHullShape; -import com.jme3.effect.shapes.EmitterMeshFaceShape; -import com.jme3.effect.shapes.EmitterMeshVertexShape; -import com.jme3.math.ColorRGBA; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -import java.util.logging.Logger; - -public class ParticlesHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(ParticlesHelper.class.getName()); - - // part->type - public static final int PART_EMITTER = 0; - public static final int PART_REACTOR = 1; - public static final int PART_HAIR = 2; - public static final int PART_FLUID = 3; - - // part->flag - public static final int PART_REACT_STA_END = 1; - public static final int PART_REACT_MULTIPLE = 2; - public static final int PART_LOOP = 4; - // public static final int PART_LOOP_INSTANT =8; - public static final int PART_HAIR_GEOMETRY = 16; - public static final int PART_UNBORN = 32; // show unborn particles - public static final int PART_DIED = 64; // show died particles - public static final int PART_TRAND = 128; - public static final int PART_EDISTR = 256; // particle/face from face areas - public static final int PART_STICKY = 512; // collided particles can stick to collider - public static final int PART_DIE_ON_COL = 1 << 12; - public static final int PART_SIZE_DEFL = 1 << 13; // swept sphere deflections - public static final int PART_ROT_DYN = 1 << 14; // dynamic rotation - public static final int PART_SIZEMASS = 1 << 16; - public static final int PART_ABS_LENGTH = 1 << 15; - public static final int PART_ABS_TIME = 1 << 17; - public static final int PART_GLOB_TIME = 1 << 18; - public static final int PART_BOIDS_2D = 1 << 19; - public static final int PART_BRANCHING = 1 << 20; - public static final int PART_ANIM_BRANCHING = 1 << 21; - public static final int PART_SELF_EFFECT = 1 << 22; - public static final int PART_SYMM_BRANCHING = 1 << 24; - public static final int PART_HAIR_BSPLINE = 1024; - public static final int PART_GRID_INVERT = 1 << 26; - public static final int PART_CHILD_EFFECT = 1 << 27; - public static final int PART_CHILD_SEAMS = 1 << 28; - public static final int PART_CHILD_RENDER = 1 << 29; - public static final int PART_CHILD_GUIDE = 1 << 30; - - // part->from - public static final int PART_FROM_VERT = 0; - public static final int PART_FROM_FACE = 1; - public static final int PART_FROM_VOLUME = 2; - public static final int PART_FROM_PARTICLE = 3; - public static final int PART_FROM_CHILD = 4; - - // part->phystype - public static final int PART_PHYS_NO = 0; - public static final int PART_PHYS_NEWTON = 1; - public static final int PART_PHYS_KEYED = 2; - public static final int PART_PHYS_BOIDS = 3; - - // part->draw_as - public static final int PART_DRAW_NOT = 0; - public static final int PART_DRAW_DOT = 1; - public static final int PART_DRAW_CIRC = 2; - public static final int PART_DRAW_CROSS = 3; - public static final int PART_DRAW_AXIS = 4; - public static final int PART_DRAW_LINE = 5; - public static final int PART_DRAW_PATH = 6; - public static final int PART_DRAW_OB = 7; - public static final int PART_DRAW_GR = 8; - public static final int PART_DRAW_BB = 9; - - /** - * This constructor parses the given blender version and stores the result. Some functionalities may differ in - * different blender versions. - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public ParticlesHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - @SuppressWarnings("unchecked") - public ParticleEmitter toParticleEmitter(Structure particleSystem) throws BlenderFileException { - ParticleEmitter result = null; - Pointer pParticleSettings = (Pointer) particleSystem.getFieldValue("part"); - if (pParticleSettings.isNotNull()) { - Structure particleSettings = pParticleSettings.fetchData().get(0); - - int totPart = ((Number) particleSettings.getFieldValue("totpart")).intValue(); - - // draw type will be stored temporarily in the name (it is used during modifier applying operation) - int drawAs = ((Number) particleSettings.getFieldValue("draw_as")).intValue(); - char nameSuffix;// P - point, L - line, N - None, B - Bilboard - switch (drawAs) { - case PART_DRAW_NOT: - nameSuffix = 'N'; - totPart = 0;// no need to generate particles in this case - break; - case PART_DRAW_BB: - nameSuffix = 'B'; - break; - case PART_DRAW_OB: - case PART_DRAW_GR: - nameSuffix = 'P'; - LOGGER.warning("Neither object nor group particles supported yet! Using point representation instead!");// TODO: support groups and aobjects - break; - case PART_DRAW_LINE: - nameSuffix = 'L'; - LOGGER.warning("Lines not yet supported! Using point representation instead!");// TODO: support lines - default:// all others are rendered as points in blender - nameSuffix = 'P'; - } - result = new ParticleEmitter(particleSettings.getName() + nameSuffix, Type.Triangle, totPart); - if (nameSuffix == 'N') { - return result;// no need to set anything else - } - - // setting the emitters shape (the shapes meshes will be set later during modifier applying operation) - int from = ((Number) particleSettings.getFieldValue("from")).intValue(); - switch (from) { - case PART_FROM_VERT: - result.setShape(new EmitterMeshVertexShape()); - break; - case PART_FROM_FACE: - result.setShape(new EmitterMeshFaceShape()); - break; - case PART_FROM_VOLUME: - result.setShape(new EmitterMeshConvexHullShape()); - break; - default: - LOGGER.warning("Default shape used! Unknown emitter shape value ('from' parameter: " + from + ')'); - } - - // reading acceleration - DynamicArray acc = (DynamicArray) particleSettings.getFieldValue("acc"); - result.setGravity(-acc.get(0).floatValue(), -acc.get(1).floatValue(), -acc.get(2).floatValue()); - - // setting the colors - result.setEndColor(new ColorRGBA(1f, 1f, 1f, 1f)); - result.setStartColor(new ColorRGBA(1f, 1f, 1f, 1f)); - - // reading size - float sizeFactor = nameSuffix == 'B' ? 1.0f : 0.3f; - float size = ((Number) particleSettings.getFieldValue("size")).floatValue() * sizeFactor; - result.setStartSize(size); - result.setEndSize(size); - - // reading lifetime - int fps = blenderContext.getBlenderKey().getFps(); - float lifetime = ((Number) particleSettings.getFieldValue("lifetime")).floatValue() / fps; - float randlife = ((Number) particleSettings.getFieldValue("randlife")).floatValue() / fps; - result.setLowLife(lifetime * (1.0f - randlife)); - result.setHighLife(lifetime); - - // preparing influencer - ParticleInfluencer influencer; - int phystype = ((Number) particleSettings.getFieldValue("phystype")).intValue(); - switch (phystype) { - case PART_PHYS_NEWTON: - influencer = new NewtonianParticleInfluencer(); - ((NewtonianParticleInfluencer) influencer).setNormalVelocity(((Number) particleSettings.getFieldValue("normfac")).floatValue()); - ((NewtonianParticleInfluencer) influencer).setVelocityVariation(((Number) particleSettings.getFieldValue("randfac")).floatValue()); - ((NewtonianParticleInfluencer) influencer).setSurfaceTangentFactor(((Number) particleSettings.getFieldValue("tanfac")).floatValue()); - ((NewtonianParticleInfluencer) influencer).setSurfaceTangentRotation(((Number) particleSettings.getFieldValue("tanphase")).floatValue()); - break; - case PART_PHYS_BOIDS: - case PART_PHYS_KEYED:// TODO: support other influencers - LOGGER.warning("Boids and Keyed particles physic not yet supported! Empty influencer used!"); - case PART_PHYS_NO: - default: - influencer = new EmptyParticleInfluencer(); - } - result.setParticleInfluencer(influencer); - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java deleted file mode 100644 index d8298e867b..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ColorBand.java +++ /dev/null @@ -1,401 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures; - -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; - -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A class constaining the colorband data. - * - * @author Marcin Roguski (Kaelthas) - */ -public class ColorBand { - private static final Logger LOGGER = Logger.getLogger(ColorBand.class.getName()); - - // interpolation types - public static final int IPO_LINEAR = 0; - public static final int IPO_EASE = 1; - public static final int IPO_BSPLINE = 2; - public static final int IPO_CARDINAL = 3; - public static final int IPO_CONSTANT = 4; - - private int cursorsAmount, ipoType; - /** The default amount of possible cursor positions. */ - private int resultSize = 1001; - private ColorBandData[] data; - - /** - * A constructor used to instantiate color band by hand instead of reading it from the blend file. - * @param ipoType - * the interpolation type - * @param colors - * the colorband colors - * @param positions - * the positions for colors' cursors - * @param resultSize - * the size of the result table - */ - public ColorBand(int ipoType, List colors, List positions, int resultSize) { - if (colors == null || colors.size() < 1) { - throw new IllegalArgumentException("The amount of colorband's colors must be at least 1."); - } - if (ipoType < IPO_LINEAR || ipoType > IPO_CONSTANT) { - throw new IllegalArgumentException("Unknown colorband interpolation type: " + ipoType); - } - if (positions == null || positions.size() != colors.size()) { - throw new IllegalArgumentException("The size of positions and colors list should be equal!"); - } - for (Integer position : positions) { - if (position.intValue() < 0 || position.intValue() >= resultSize) { - throw new IllegalArgumentException("Invalid position value: " + position + "! Should be from range: [0, " + resultSize + "]!"); - } - } - - cursorsAmount = colors.size(); - this.ipoType = ipoType; - this.resultSize = resultSize; - data = new ColorBandData[cursorsAmount]; - for (int i = 0; i < cursorsAmount; ++i) { - data[i] = new ColorBandData(colors.get(i), positions.get(i)); - } - } - - /** - * Constructor. Loads the data from the given structure. - * @param tex - * @param blenderContext - */ - @SuppressWarnings("unchecked") - public ColorBand(Structure tex, BlenderContext blenderContext) { - int flag = ((Number) tex.getFieldValue("flag")).intValue(); - if ((flag & GeneratedTexture.TEX_COLORBAND) != 0) { - Pointer pColorband = (Pointer) tex.getFieldValue("coba"); - try { - Structure colorbandStructure = pColorband.fetchData().get(0); - cursorsAmount = ((Number) colorbandStructure.getFieldValue("tot")).intValue(); - ipoType = ((Number) colorbandStructure.getFieldValue("ipotype")).intValue(); - data = new ColorBandData[cursorsAmount]; - DynamicArray data = (DynamicArray) colorbandStructure.getFieldValue("data"); - for (int i = 0; i < cursorsAmount; ++i) { - this.data[i] = new ColorBandData(data.get(i)); - } - } catch (BlenderFileException e) { - LOGGER.log(Level.WARNING, "Cannot fetch the colorband structure. The reason: {0}", e.getLocalizedMessage()); - } - } - } - - /** - * This method determines if the colorband has any transparencies or is not - * transparent at all. - * - * @return true if the colorband has transparencies and false - * otherwise - */ - public boolean hasTransparencies() { - if (data != null) { - for (ColorBandData colorBandData : data) { - if (colorBandData.a < 1.0f) { - return true; - } - } - } - return false; - } - - /** - * This method computes the values of the colorband. - * - * @return an array of 1001 elements and each element is float[4] object - * containing rgba values - */ - public float[][] computeValues() { - float[][] result = null; - if (data != null) { - result = new float[resultSize][4];// resultSize - amount of possible cursor positions; 4 = [r, g, b, a] - if (data.length == 1) {// special case; use only one color for all types of colorband interpolation - for (int i = 0; i < result.length; ++i) { - result[i][0] = data[0].r; - result[i][1] = data[0].g; - result[i][2] = data[0].b; - result[i][3] = data[0].a; - } - } else { - int currentCursor = 0; - ColorBandData currentData = data[0]; - ColorBandData nextData = data[0]; - switch (ipoType) { - case ColorBand.IPO_LINEAR: - float rDiff = 0, - gDiff = 0, - bDiff = 0, - aDiff = 0, - posDiff; - for (int i = 0; i < result.length; ++i) { - posDiff = i - currentData.pos; - result[i][0] = currentData.r + rDiff * posDiff; - result[i][1] = currentData.g + gDiff * posDiff; - result[i][2] = currentData.b + bDiff * posDiff; - result[i][3] = currentData.a + aDiff * posDiff; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - // calculate differences - int d = nextData.pos - currentData.pos; - rDiff = (nextData.r - currentData.r) / d; - gDiff = (nextData.g - currentData.g) / d; - bDiff = (nextData.b - currentData.b) / d; - aDiff = (nextData.a - currentData.a) / d; - } else { - rDiff = gDiff = bDiff = aDiff = 0; - } - } - } - break; - case ColorBand.IPO_BSPLINE: - case ColorBand.IPO_CARDINAL: - Map cbDataMap = new TreeMap(); - for (int i = 0; i < data.length; ++i) { - cbDataMap.put(Integer.valueOf(i), data[i]); - } - - if (data[0].pos == 0) { - cbDataMap.put(Integer.valueOf(-1), data[0]); - } else { - ColorBandData cbData = new ColorBandData(data[0]); - cbData.pos = 0; - cbDataMap.put(Integer.valueOf(-1), cbData); - cbDataMap.put(Integer.valueOf(-2), cbData); - } - - if (data[data.length - 1].pos == 1000) { - cbDataMap.put(Integer.valueOf(data.length), data[data.length - 1]); - } else { - ColorBandData cbData = new ColorBandData(data[data.length - 1]); - cbData.pos = 1000; - cbDataMap.put(Integer.valueOf(data.length), cbData); - cbDataMap.put(Integer.valueOf(data.length + 1), cbData); - } - - float[] ipoFactors = new float[4]; - float f; - - ColorBandData data0 = this.getColorbandData(currentCursor - 2, cbDataMap); - ColorBandData data1 = this.getColorbandData(currentCursor - 1, cbDataMap); - ColorBandData data2 = this.getColorbandData(currentCursor, cbDataMap); - ColorBandData data3 = this.getColorbandData(currentCursor + 1, cbDataMap); - - for (int i = 0; i < result.length; ++i) { - if (data2.pos != data1.pos) { - f = (i - data2.pos) / (float) (data1.pos - data2.pos); - f = FastMath.clamp(f, 0.0f, 1.0f); - } else { - f = 0.0f; - } - this.getIpoData(f, ipoFactors); - result[i][0] = ipoFactors[3] * data0.r + ipoFactors[2] * data1.r + ipoFactors[1] * data2.r + ipoFactors[0] * data3.r; - result[i][1] = ipoFactors[3] * data0.g + ipoFactors[2] * data1.g + ipoFactors[1] * data2.g + ipoFactors[0] * data3.g; - result[i][2] = ipoFactors[3] * data0.b + ipoFactors[2] * data1.b + ipoFactors[1] * data2.b + ipoFactors[0] * data3.b; - result[i][3] = ipoFactors[3] * data0.a + ipoFactors[2] * data1.a + ipoFactors[1] * data2.a + ipoFactors[0] * data3.a; - result[i][0] = FastMath.clamp(result[i][0], 0.0f, 1.0f); - result[i][1] = FastMath.clamp(result[i][1], 0.0f, 1.0f); - result[i][2] = FastMath.clamp(result[i][2], 0.0f, 1.0f); - result[i][3] = FastMath.clamp(result[i][3], 0.0f, 1.0f); - - if (nextData.pos == i) { - ++currentCursor; - data0 = cbDataMap.get(currentCursor - 2); - data1 = cbDataMap.get(currentCursor - 1); - data2 = cbDataMap.get(currentCursor); - data3 = cbDataMap.get(currentCursor + 1); - } - } - break; - case ColorBand.IPO_EASE: - float d, - a, - b, - d2; - for (int i = 0; i < result.length; ++i) { - if (nextData.pos != currentData.pos) { - d = (i - currentData.pos) / (float) (nextData.pos - currentData.pos); - d2 = d * d; - a = 3.0f * d2 - 2.0f * d * d2; - b = 1.0f - a; - } else { - d = a = 0.0f; - b = 1.0f; - } - - result[i][0] = b * currentData.r + a * nextData.r; - result[i][1] = b * currentData.g + a * nextData.g; - result[i][2] = b * currentData.b + a * nextData.b; - result[i][3] = b * currentData.a + a * nextData.a; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - } - } - } - break; - case ColorBand.IPO_CONSTANT: - for (int i = 0; i < result.length; ++i) { - result[i][0] = currentData.r; - result[i][1] = currentData.g; - result[i][2] = currentData.b; - result[i][3] = currentData.a; - if (nextData.pos == i) { - currentData = data[currentCursor++]; - if (currentCursor < data.length) { - nextData = data[currentCursor]; - } - } - } - break; - default: - throw new IllegalStateException("Unknown interpolation type: " + ipoType); - } - } - } - return result; - } - - private ColorBandData getColorbandData(int index, Map cbDataMap) { - ColorBandData result = cbDataMap.get(index); - if (result == null) { - result = new ColorBandData(); - } - return result; - } - - /** - * This method returns the data for either B-spline of Cardinal - * interpolation. - * - * @param d - * distance factor for the current intensity - * @param ipoFactors - * table to store the results (size of the table must be at least - * 4) - */ - private void getIpoData(float d, float[] ipoFactors) { - float d2 = d * d; - float d3 = d2 * d; - if (ipoType == ColorBand.IPO_BSPLINE) { - ipoFactors[0] = -0.71f * d3 + 1.42f * d2 - 0.71f * d; - ipoFactors[1] = 1.29f * d3 - 2.29f * d2 + 1.0f; - ipoFactors[2] = -1.29f * d3 + 1.58f * d2 + 0.71f * d; - ipoFactors[3] = 0.71f * d3 - 0.71f * d2; - } else if (ipoType == ColorBand.IPO_CARDINAL) { - ipoFactors[0] = -0.16666666f * d3 + 0.5f * d2 - 0.5f * d + 0.16666666f; - ipoFactors[1] = 0.5f * d3 - d2 + 0.6666666f; - ipoFactors[2] = -0.5f * d3 + 0.5f * d2 + 0.5f * d + 0.16666666f; - ipoFactors[3] = 0.16666666f * d3; - } else { - throw new IllegalStateException("Cannot get interpolation data for other colorband types than B-spline and Cardinal!"); - } - } - - /** - * Class to store the single colorband cursor data. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class ColorBandData { - public final float r, g, b, a; - public int pos; - - public ColorBandData() { - r = g = b = 0; - a = 1; - } - - /** - * Constructor that stores the color and position of the cursor. - * @param color - * the cursor's color - * @param pos - * the cursor's position - */ - public ColorBandData(ColorRGBA color, int pos) { - r = color.r; - g = color.g; - b = color.b; - a = color.a; - this.pos = pos; - } - - /** - * Copy constructor. - */ - private ColorBandData(ColorBandData data) { - r = data.r; - g = data.g; - b = data.b; - a = data.a; - pos = data.pos; - } - - /** - * Constructor. Loads the data from the given structure. - * - * @param cbdataStructure - * the structure containing the CBData object - */ - public ColorBandData(Structure cbdataStructure) { - r = ((Number) cbdataStructure.getFieldValue("r")).floatValue(); - g = ((Number) cbdataStructure.getFieldValue("g")).floatValue(); - b = ((Number) cbdataStructure.getFieldValue("b")).floatValue(); - a = ((Number) cbdataStructure.getFieldValue("a")).floatValue(); - pos = (int) (((Number) cbdataStructure.getFieldValue("pos")).floatValue() * 1000.0f); - } - - @Override - public String toString() { - return "P: " + pos + " [" + r + ", " + g + ", " + b + ", " + a + "]"; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java deleted file mode 100644 index 6c40542e2c..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/CombinedTexture.java +++ /dev/null @@ -1,543 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.image.BufferedImage; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import jme3tools.converters.ImageToAwt; - -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement; -import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; -import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.MagFilter; -import com.jme3.texture.Texture.MinFilter; -import com.jme3.texture.Texture.WrapMode; -import com.jme3.texture.Texture2D; -import com.jme3.texture.TextureCubeMap; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; - -/** - * This class represents a texture that is defined for the material. It can be - * made of several textures (both 2D and 3D) that are merged together and - * returned as a single texture. - * - * @author Marcin Roguski (Kaelthas) - */ -public class CombinedTexture { - private static final Logger LOGGER = Logger.getLogger(CombinedTexture.class.getName()); - - /** The mapping type of the texture. Defined bu MaterialContext.MTEX_COL, MTEX_NOR etc. */ - private final int mappingType; - /** - * If set to true then if a texture without alpha is added then all textures below are discarded because - * the new one will cover them anyway. If set to false then all textures are stored. - */ - private boolean discardCoveredTextures; - /** The data for each of the textures. */ - private List textureDatas = new ArrayList(); - /** The result texture. */ - private Texture resultTexture; - /** The UV values for the result texture. */ - private List resultUVS; - - /** - * Constructor. Stores the texture mapping type (ie. color map, normal map). - * - * @param mappingType - * texture mapping type - * @param discardCoveredTextures - * if set to true then if a texture without alpha is added then all textures below are discarded because - * the new one will cover them anyway, if set to false then all textures are stored - */ - public CombinedTexture(int mappingType, boolean discardCoveredTextures) { - this.mappingType = mappingType; - this.discardCoveredTextures = discardCoveredTextures; - } - - /** - * This method adds a texture data to the resulting texture. - * - * @param texture - * the source texture - * @param textureBlender - * the texture blender (to mix the texture with its material - * color) - * @param uvCoordinatesType - * the type of UV coordinates - * @param projectionType - * the type of UV coordinates projection (for flat textures) - * @param textureStructure - * the texture sructure - * @param uvCoordinatesName - * the name of the used user's UV coordinates for this texture - * @param blenderContext - * the blender context - */ - public void add(Texture texture, TextureBlender textureBlender, int uvCoordinatesType, int projectionType, Structure textureStructure, String uvCoordinatesName, BlenderContext blenderContext) { - if (!(texture instanceof GeneratedTexture) && !(texture instanceof Texture2D)) { - throw new IllegalArgumentException("Unsupported texture type: " + (texture == null ? "null" : texture.getClass())); - } - if (!(texture instanceof GeneratedTexture) || blenderContext.getBlenderKey().isLoadGeneratedTextures()) { - if (UVCoordinatesGenerator.isTextureCoordinateTypeSupported(UVCoordinatesType.valueOf(uvCoordinatesType))) { - TextureData textureData = new TextureData(); - textureData.texture = texture; - textureData.textureBlender = textureBlender; - textureData.uvCoordinatesType = UVCoordinatesType.valueOf(uvCoordinatesType); - textureData.projectionType = UVProjectionType.valueOf(projectionType); - textureData.textureStructure = textureStructure; - textureData.uvCoordinatesName = uvCoordinatesName; - - if (discardCoveredTextures && textureDatas.size() > 0 && this.isWithoutAlpha(textureData, blenderContext)) { - textureDatas.clear();// clear previous textures, they will be covered anyway - } - textureDatas.add(textureData); - } else { - LOGGER.warning("The texture coordinates type is not supported: " + UVCoordinatesType.valueOf(uvCoordinatesType) + ". The texture '" + textureStructure.getName() + "'."); - } - } - } - - /** - * This method flattens the texture and creates a single result of Texture2D - * type. - * - * @param geometry - * the geometry the texture is created for - * @param geometriesOMA - * the old memory address of the geometries list that the given - * geometry belongs to (needed for bounding box creation) - * @param userDefinedUVCoordinates - * the UV's defined by user (null or zero length table if none - * were defined) - * @param blenderContext - * the blender context - * @return the name of the user UV coordinates used (null if the UV's were - * generated) - */ - public String flatten(Geometry geometry, Long geometriesOMA, Map> userDefinedUVCoordinates, BlenderContext blenderContext) { - Mesh mesh = geometry.getMesh(); - Texture previousTexture = null; - UVCoordinatesType masterUVCoordinatesType = null; - String masterUserUVSetName = null; - for (TextureData textureData : textureDatas) { - // decompress compressed textures (all will be merged into one texture anyway) - if (textureDatas.size() > 1 && textureData.texture.getImage().getFormat().isCompressed()) { - textureData.texture.setImage(ImageUtils.decompress(textureData.texture.getImage())); - textureData.textureBlender = TextureBlenderFactory.alterTextureType(textureData.texture.getImage().getFormat(), textureData.textureBlender); - } - - if (previousTexture == null) {// the first texture will lead the others to its shape - if (textureData.texture instanceof GeneratedTexture) { - resultTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); - } else if (textureData.texture instanceof Texture2D) { - resultTexture = textureData.texture; - - if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - if (textureData.uvCoordinatesName == null) { - resultUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available - } else { - resultUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); - } - if(resultUVS == null && LOGGER.isLoggable(Level.WARNING)) { - LOGGER.warning("The texture " + textureData.texture.getName() + " has assigned non existing UV coordinates group: " + textureData.uvCoordinatesName + "."); - } - masterUserUVSetName = textureData.uvCoordinatesName; - } else { - TemporalMesh temporalMesh = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH); - resultUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, temporalMesh); - } - } - this.blend(resultTexture, textureData.textureBlender, blenderContext); - - previousTexture = resultTexture; - masterUVCoordinatesType = textureData.uvCoordinatesType; - } else { - if (textureData.texture instanceof GeneratedTexture) { - if (!(resultTexture instanceof TriangulatedTexture)) { - resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); - resultUVS = null; - previousTexture = resultTexture; - } - - TriangulatedTexture triangulatedTexture = ((GeneratedTexture) textureData.texture).triangulate(mesh, geometriesOMA, textureData.uvCoordinatesType, blenderContext); - triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); - triangulatedTexture.blend(textureData.textureBlender, (TriangulatedTexture) resultTexture, blenderContext); - resultTexture = previousTexture = triangulatedTexture; - } else if (textureData.texture instanceof Texture2D) { - if (this.isUVTypesMatch(masterUVCoordinatesType, masterUserUVSetName, textureData.uvCoordinatesType, textureData.uvCoordinatesName) && resultTexture instanceof Texture2D) { - this.scale((Texture2D) textureData.texture, resultTexture.getImage().getWidth(), resultTexture.getImage().getHeight()); - ImageUtils.merge(resultTexture.getImage(), textureData.texture.getImage()); - previousTexture = resultTexture; - } else { - if (!(resultTexture instanceof TriangulatedTexture)) { - resultTexture = new TriangulatedTexture((Texture2D) resultTexture, resultUVS, blenderContext); - resultUVS = null; - } - // first triangulate the current texture - List textureUVS = null; - if (textureData.uvCoordinatesType == UVCoordinatesType.TEXCO_UV && userDefinedUVCoordinates != null && userDefinedUVCoordinates.size() > 0) { - if (textureData.uvCoordinatesName == null) { - textureUVS = userDefinedUVCoordinates.values().iterator().next();// get the first UV available - } else { - textureUVS = userDefinedUVCoordinates.get(textureData.uvCoordinatesName); - } - } else { - TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH); - textureUVS = UVCoordinatesGenerator.generateUVCoordinatesFor2DTexture(mesh, textureData.uvCoordinatesType, textureData.projectionType, geometries); - } - TriangulatedTexture triangulatedTexture = new TriangulatedTexture((Texture2D) textureData.texture, textureUVS, blenderContext); - // then move the texture to different UV's - triangulatedTexture.castToUVS((TriangulatedTexture) resultTexture, blenderContext); - // merge triangulated textures - for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { - ImageUtils.merge(((TriangulatedTexture) resultTexture).getFaceTextureElement(i).image, triangulatedTexture.getFaceTextureElement(i).image); - } - } - } - } - } - - if (resultTexture instanceof TriangulatedTexture) { - if (mappingType == MaterialContext.MTEX_NOR) { - for (int i = 0; i < ((TriangulatedTexture) resultTexture).getFaceTextureCount(); ++i) { - TriangleTextureElement triangleTextureElement = ((TriangulatedTexture) resultTexture).getFaceTextureElement(i); - triangleTextureElement.image = ImageUtils.convertToNormalMapTexture(triangleTextureElement.image, 1);// TODO: get proper strength factor - } - } - resultUVS = ((TriangulatedTexture) resultTexture).getResultUVS(); - resultTexture = ((TriangulatedTexture) resultTexture).getResultTexture(); - masterUserUVSetName = null; - } - - // setting additional data - resultTexture.setWrap(WrapMode.Repeat); - // the filters are required if generated textures are used because - // otherwise ugly lines appear between the mesh faces - resultTexture.setMagFilter(MagFilter.Nearest); - resultTexture.setMinFilter(MinFilter.NearestNoMipMaps); - - return masterUserUVSetName; - } - - /** - * Generates a texture that will be used by the sky spatial. - * The result texture has 6 layers. Every image in each layer has equal size and its shape is a square. - * The size of each image is the maximum size (width or height) of the textures given. - * The default sky generated texture size is used (this value is set in the BlenderKey) if no picture textures - * are present or their sizes is lower than the generated texture size. - * The textures of lower sizes are properly scaled. - * All the textures are mixed into one and put as layers in the result texture. - * - * @param horizontalColor - * the horizon color - * @param zenithColor - * the zenith color - * @param blenderContext - * the blender context - * @return texture for the sky - */ - public TextureCubeMap generateSkyTexture(ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { - LOGGER.log(Level.FINE, "Preparing sky texture from {0} applied textures.", textureDatas.size()); - - LOGGER.fine("Computing the texture size."); - int size = -1; - for (TextureData textureData : textureDatas) { - if (textureData.texture instanceof Texture2D) { - size = Math.max(textureData.texture.getImage().getWidth(), size); - size = Math.max(textureData.texture.getImage().getHeight(), size); - } - } - if (size < 0) { - size = blenderContext.getBlenderKey().getSkyGeneratedTextureSize(); - } - LOGGER.log(Level.FINE, "The sky texture size will be: {0}x{0}.", size); - - TextureCubeMap result = null; - for (TextureData textureData : textureDatas) { - TextureCubeMap texture = null; - if (textureData.texture instanceof GeneratedTexture) { - texture = ((GeneratedTexture) textureData.texture).generateSkyTexture(size, horizontalColor, zenithColor, blenderContext); - } else { - // first create a grayscale version of the image - Image image = textureData.texture.getImage(); - if (image.getWidth() != image.getHeight() || image.getWidth() != size) { - image = ImageUtils.resizeTo(image, size, size); - } - Image grayscaleImage = ImageUtils.convertToGrayscaleTexture(image); - - // add the sky colors to the image - PixelInputOutput sourcePixelIO = PixelIOFactory.getPixelIO(grayscaleImage.getFormat()); - PixelInputOutput targetPixelIO = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel texturePixel = new TexturePixel(); - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - sourcePixelIO.read(grayscaleImage, 0, texturePixel, x, y); - texturePixel.intensity = texturePixel.red;// no matter which factor we use here, in grayscale they are all equal - ImageUtils.color(texturePixel, horizontalColor, zenithColor); - targetPixelIO.write(image, 0, texturePixel, x, y); - } - } - - // create the cubemap texture from the coloured image - ByteBuffer sourceData = image.getData(0); - ArrayList data = new ArrayList(6); - for (int i = 0; i < 6; ++i) { - data.add(BufferUtils.clone(sourceData)); - } - texture = new TextureCubeMap(new Image(image.getFormat(), image.getWidth(), image.getHeight(), 6, data, ColorSpace.Linear)); - } - - if (result == null) { - result = texture; - } else { - ImageUtils.mix(result.getImage(), texture.getImage()); - } - } - return result; - } - - /** - * The method checks if the texture UV coordinates match. - * It the types are equal and different then UVCoordinatesType.TEXCO_UV then we consider them a match. - * If they are both UVCoordinatesType.TEXCO_UV then they match only when their UV sets names are equal. - * In other cases they are considered NOT a match. - * @param type1 - * the UV coord type - * @param uvSetName1 - * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) - * @param type2 - * the UV coord type - * @param uvSetName2 - * the user's UV coords set name (considered only for UVCoordinatesType.TEXCO_UV) - * @return true if the types match and false otherwise - */ - private boolean isUVTypesMatch(UVCoordinatesType type1, String uvSetName1, UVCoordinatesType type2, String uvSetName2) { - if (type1 == type2) { - if (type1 == UVCoordinatesType.TEXCO_UV) { - if (uvSetName1 != null && uvSetName2 != null && uvSetName1.equals(uvSetName2)) { - return true; - } - } else { - return true; - } - } - return false; - } - - /** - * This method blends the texture. - * - * @param texture - * the texture to be blended - * @param textureBlender - * blending definition for the texture - * @param blenderContext - * the blender context - */ - private void blend(Texture texture, TextureBlender textureBlender, BlenderContext blenderContext) { - if (texture instanceof TriangulatedTexture) { - ((TriangulatedTexture) texture).blend(textureBlender, null, blenderContext); - } else if (texture instanceof Texture2D) { - Image blendedImage = textureBlender.blend(texture.getImage(), null, blenderContext); - texture.setImage(blendedImage); - } else { - throw new IllegalArgumentException("Invalid type for texture to blend!"); - } - } - - /** - * @return the result texture - */ - public Texture getResultTexture() { - return resultTexture; - } - - /** - * @return the result UV coordinates - */ - public List getResultUVS() { - return resultUVS; - } - - /** - * @return the amount of added textures - */ - public int getTexturesCount() { - return textureDatas.size(); - } - - /** - * @return the texture's mapping type - */ - public int getMappingType() { - return mappingType; - } - - /** - * @return true if the texture has at least one generated texture component and false otherwise - */ - public boolean hasGeneratedTextures() { - if (textureDatas != null) { - for (TextureData textureData : textureDatas) { - if (textureData.texture instanceof GeneratedTexture) { - return true; - } - } - } - return false; - } - - /** - * This method determines if the given texture has no alpha channel. - * - * @param texture - * the texture to check for alpha channel - * @return true if the texture has no alpha channel and false - * otherwise - */ - private boolean isWithoutAlpha(TextureData textureData, BlenderContext blenderContext) { - ColorBand colorBand = new ColorBand(textureData.textureStructure, blenderContext); - if (!colorBand.hasTransparencies()) { - int type = ((Number) textureData.textureStructure.getFieldValue("type")).intValue(); - if (type == TextureHelper.TEX_MAGIC) { - return true; - } - if (type == TextureHelper.TEX_VORONOI) { - int voronoiColorType = ((Number) textureData.textureStructure.getFieldValue("vn_coltype")).intValue(); - return voronoiColorType != 0;// voronoiColorType == 0: - // intensity, voronoiColorType - // != 0: col1, col2 or col3 - } - if (type == TextureHelper.TEX_CLOUDS) { - int sType = ((Number) textureData.textureStructure.getFieldValue("stype")).intValue(); - return sType == 1;// sType==0: without colors, sType==1: with - // colors - } - - // checking the flat textures for alpha values presence - if (type == TextureHelper.TEX_IMAGE) { - Image image = textureData.texture.getImage(); - switch (image.getFormat()) { - case BGR8: - case DXT1: - case Luminance16F: - case Luminance32F: - case Luminance8: - case RGB111110F: - case RGB16F: - case RGB32F: - case RGB565: - case RGB8: - return true;// these types have no alpha by definition - case ABGR8: - case DXT1A: - case DXT3: - case DXT5: - case Luminance16FAlpha16F: - case Luminance8Alpha8: - case RGBA16F: - case RGBA32F: - case RGBA8: - case ARGB8: - case BGRA8: - case RGB5A1:// with these types it is better to make sure if the texture is or is not transparent - PixelInputOutput pixelInputOutput = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - int depth = image.getDepth() == 0 ? 1 : image.getDepth(); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - pixelInputOutput.read(image, layerIndex, pixel, x, y); - if (pixel.alpha < 1.0f) { - return false; - } - } - } - } - return true; - default: - throw new IllegalStateException("Unknown image format: " + image.getFormat()); - } - } - } - return false; - } - - /** - * This method scales the given texture to the given size. - * - * @param texture - * the texture to be scaled - * @param width - * new width of the texture - * @param height - * new height of the texture - */ - private void scale(Texture2D texture, int width, int height) { - // first determine if scaling is required - boolean scaleRequired = texture.getImage().getWidth() != width || texture.getImage().getHeight() != height; - - if (scaleRequired) { - Image image = texture.getImage(); - BufferedImage sourceImage = ImageToAwt.convert(image, false, true, 0); - - int sourceWidth = sourceImage.getWidth(); - int sourceHeight = sourceImage.getHeight(); - - BufferedImage targetImage = new BufferedImage(width, height, sourceImage.getType()); - - Graphics2D g = targetImage.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(sourceImage, 0, 0, width, height, 0, 0, sourceWidth, sourceHeight, null); - g.dispose(); - - Image output = new ImageLoader().load(targetImage, false); - image.setWidth(width); - image.setHeight(height); - image.setData(output.getData(0)); - image.setFormat(output.getFormat()); - } - } - - /** - * A simple class to aggregate the texture data (improves code quality). - * - * @author Marcin Roguski (Kaelthas) - */ - private static class TextureData { - /** The texture. */ - public Texture texture; - /** The texture blender (to mix the texture with its material color). */ - public TextureBlender textureBlender; - /** The type of UV coordinates. */ - public UVCoordinatesType uvCoordinatesType; - /** The type of UV coordinates projection (for flat textures). */ - public UVProjectionType projectionType; - /** The texture sructure. */ - public Structure textureStructure; - /** The name of the user's UV coordinates that are used for this texture. */ - public String uvCoordinatesName; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java deleted file mode 100644 index 5537f1e0b5..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/DDSTexelData.java +++ /dev/null @@ -1,157 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import com.jme3.math.FastMath; -import com.jme3.texture.Image.Format; - -/** - * The data that helps in bytes calculations for the result image. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class DDSTexelData { - /** The colors of the texes. */ - private TexturePixel[][] colors; - /** The indexes of the texels. */ - private long[] indexes; - /** The alphas of the texels (might be null). */ - private float[][] alphas; - /** The indexels of texels alpha values (might be null). */ - private long[] alphaIndexes; - /** The counter of texel x column. */ - private int xCounter; - /** The counter of texel y row. */ - private int yCounter; - /** The width of the image in pixels. */ - private int widthInPixels; - /** The height of the image in pixels. */ - private int heightInPixels; - /** The total texel count. */ - private int xTexelCount; - - /** - * Constructor. Allocates memory for data structures. - * - * @param compressedSize - * the size of compressed image (or its mipmap) - * @param widthToHeightRatio - * width/height ratio for the image - * @param format - * the format of the image - */ - public DDSTexelData(int compressedSize, float widthToHeightRatio, Format format) { - int texelsCount = compressedSize * 8 / format.getBitsPerPixel() / 16; - this.colors = new TexturePixel[texelsCount][]; - this.indexes = new long[texelsCount]; - this.widthInPixels = (int) (0.5f * (float) Math.sqrt(this.getSizeInBytes() / widthToHeightRatio)); - this.heightInPixels = (int) (this.widthInPixels / widthToHeightRatio); - this.xTexelCount = widthInPixels >> 2; - this.yCounter = (heightInPixels >> 2) - 1;// xCounter is 0 for now - if (format == Format.DXT3 || format == Format.DXT5) { - this.alphas = new float[texelsCount][]; - this.alphaIndexes = new long[texelsCount]; - } - } - - /** - * This method adds a color and indexes for a texel. - * - * @param colors - * the colors of the texel - * @param indexes - * the indexes of the texel - */ - public void add(TexturePixel[] colors, int indexes) { - this.add(colors, indexes, null, 0); - } - - /** - * This method adds a color, color indexes and alha values (with their - * indexes) for a texel. - * - * @param colors - * the colors of the texel - * @param indexes - * the indexes of the texel - * @param alphas - * the alpha values - * @param alphaIndexes - * the indexes of the given alpha values - */ - public void add(TexturePixel[] colors, int indexes, float[] alphas, long alphaIndexes) { - int index = yCounter * xTexelCount + xCounter; - this.colors[index] = colors; - this.indexes[index] = indexes; - if (alphas != null) { - this.alphas[index] = alphas; - this.alphaIndexes[index] = alphaIndexes; - } - ++this.xCounter; - if (this.xCounter >= this.xTexelCount) { - this.xCounter = 0; - --this.yCounter; - } - } - - /** - * This method returns the values of the pixel located on the given - * coordinates on the result image. - * - * @param x - * the x coordinate of the pixel - * @param y - * the y coordinate of the pixel - * @param result - * the table where the result is stored - * @return true if the pixel was correctly read and false if - * the position was outside the image sizes - */ - public boolean getRGBA8(int x, int y, byte[] result) { - int xTexetlIndex = x % widthInPixels / 4; - int yTexelIndex = y % heightInPixels / 4; - - int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; - if (texelIndex < colors.length) { - TexturePixel[] colors = this.colors[texelIndex]; - - // coordinates of the pixel in the selected texel - x = x - 4 * xTexetlIndex;// pixels are arranged from left to right - y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) - - int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); - int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; - - // getting the pixel - int indexMask = colors.length - 1; - int colorIndex = (int) (this.indexes[texelIndex] >> pixelIndexInTexel & indexMask); - float alpha = this.alphas != null ? this.alphas[texelIndex][(int) (this.alphaIndexes[texelIndex] >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; - result[0] = (byte) (colors[colorIndex].red * 255.0f); - result[1] = (byte) (colors[colorIndex].green * 255.0f); - result[2] = (byte) (colors[colorIndex].blue * 255.0f); - result[3] = (byte) (alpha * 255.0f); - return true; - } - return false; - } - - /** - * @return the size of the decompressed texel (in bytes) - */ - public int getSizeInBytes() { - // indexes.length == count of texels - return indexes.length * 16 * 4; - } - - /** - * @return image (mipmap) width - */ - public int getPixelWidth() { - return widthInPixels; - } - - /** - * @return image (mipmap) height - */ - public int getPixelHeight() { - return heightInPixels; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java deleted file mode 100644 index 772db0c3ce..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/GeneratedTexture.java +++ /dev/null @@ -1,281 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import java.util.Comparator; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; - -import com.jme3.bounding.BoundingBox; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.meshes.TemporalMesh; -import com.jme3.scene.plugins.blender.textures.TriangulatedTexture.TriangleTextureElement; -import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; -import com.jme3.scene.plugins.blender.textures.generating.TextureGenerator; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture; -import com.jme3.texture.TextureCubeMap; -import com.jme3.util.TempVars; - -/** - * The generated texture loaded from blender file. The texture is not generated - * after being read. This class rather stores all required data and can compute - * a pixel in the required 3D space position. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class GeneratedTexture extends Texture { - private static final int POSITIVE_X = 0; - private static final int NEGATIVE_X = 1; - private static final int POSITIVE_Y = 2; - private static final int NEGATIVE_Y = 3; - private static final int POSITIVE_Z = 4; - private static final int NEGATIVE_Z = 5; - - // flag values - public static final int TEX_COLORBAND = 1; - public static final int TEX_FLIPBLEND = 2; - public static final int TEX_NEGALPHA = 4; - public static final int TEX_CHECKER_ODD = 8; - public static final int TEX_CHECKER_EVEN = 16; - public static final int TEX_PRV_ALPHA = 32; - public static final int TEX_PRV_NOR = 64; - public static final int TEX_REPEAT_XMIR = 128; - public static final int TEX_REPEAT_YMIR = 256; - public static final int TEX_FLAG_MASK = TEX_COLORBAND | TEX_FLIPBLEND | TEX_NEGALPHA | TEX_CHECKER_ODD | TEX_CHECKER_EVEN | TEX_PRV_ALPHA | TEX_PRV_NOR | TEX_REPEAT_XMIR | TEX_REPEAT_YMIR; - - /** Material-texture link structure. */ - private final Structure mTex; - /** Texture generateo for the specified texture type. */ - private final TextureGenerator textureGenerator; - /** - * The generated texture cast functions. They are used to cas a given point on a plane to a specified shape in 3D space. - * The functions should be ordered as the ordinal of a BlenderKey.CastFunction enums. - */ - private final static CastFunction[] CAST_FUNCTIONS = new CastFunction[] { - /** - * The cube casting function (does nothing except scaling if needed because the given points are already on a cube). - */ - new CastFunction() { - @Override - public void cast(Vector3f pointToCast, float radius) { - //computed using the Thales' theorem - float length = 2 * pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).length() * radius; - pointToCast.normalizeLocal().addLocal(0.5f, 0.5f, 0.5f).multLocal(length); - } - }, - - /** - * The sphere casting function. - */ - new CastFunction() { - /** - * The method casts a point on a plane to a sphere. - * The plane is one of the faces of a cube that has a edge of length 1 and center in (0.5 0.5, 0.5). This cube is a basic 3d area where generated texture - * is created. - * To cast a point on a cube face to a sphere that is inside the cube we perform several easy vector operations. - * 1. create a vector from the cube's center to the point - * 2. setting its length to 0.5 (the radius of the sphere) - * 3. adding the value of the cube's center to get a point on the sphere - * - * The result is stored in the given vector. - * - * @param pointToCast - * the point on a plane that will be cast to a sphere - * @param radius - * the radius of the sphere - */ - @Override - public void cast(Vector3f pointToCast, float radius) { - pointToCast.subtractLocal(0.5f, 0.5f, 0.5f).normalizeLocal().multLocal(radius).addLocal(0.5f, 0.5f, 0.5f); - } - } - }; - - /** - * Constructor. Reads the required data from the 'tex' structure. - * - * @param tex - * the texture structure - * @param mTex - * the material-texture link data structure - * @param textureGenerator - * the generator for the required texture type - * @param blenderContext - * the blender context - */ - public GeneratedTexture(Structure tex, Structure mTex, TextureGenerator textureGenerator, BlenderContext blenderContext) { - this.mTex = mTex; - this.textureGenerator = textureGenerator; - this.textureGenerator.readData(tex, blenderContext); - super.setImage(new GeneratedTextureImage(textureGenerator.getImageFormat())); - } - - /** - * This method computes the textyre color/intensity at the specified (u, v, - * s) position in 3D space. - * - * @param pixel - * the pixel where the result is stored - * @param u - * the U factor - * @param v - * the V factor - * @param s - * the S factor - */ - public void getPixel(TexturePixel pixel, float u, float v, float s) { - textureGenerator.getPixel(pixel, u, v, s); - } - - /** - * This method triangulates the texture. In the result we get a set of small - * flat textures for each face of the given mesh. This can be later merged - * into one flat texture. - * - * @param mesh - * the mesh we create the texture for - * @param geometriesOMA - * the old memory address of the geometries group that the given - * mesh belongs to (required for bounding box calculations) - * @param coordinatesType - * the types of UV coordinates - * @param blenderContext - * the blender context - * @return triangulated texture - */ - public TriangulatedTexture triangulate(Mesh mesh, Long geometriesOMA, UVCoordinatesType coordinatesType, BlenderContext blenderContext) { - TemporalMesh geometries = (TemporalMesh) blenderContext.getLoadedFeature(geometriesOMA, LoadedDataType.TEMPORAL_MESH); - - int[] coordinatesSwappingIndexes = new int[] { ((Number) mTex.getFieldValue("projx")).intValue(), ((Number) mTex.getFieldValue("projy")).intValue(), ((Number) mTex.getFieldValue("projz")).intValue() }; - List uvs = UVCoordinatesGenerator.generateUVCoordinatesFor3DTexture(mesh, coordinatesType, coordinatesSwappingIndexes, geometries); - Vector3f[] uvsArray = uvs.toArray(new Vector3f[uvs.size()]); - BoundingBox boundingBox = UVCoordinatesGenerator.getBoundingBox(geometries); - Set triangleTextureElements = new TreeSet(new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o1.faceIndex - o2.faceIndex; - } - }); - int[] indices = new int[3]; - for (int i = 0; i < mesh.getTriangleCount(); ++i) { - mesh.getTriangle(i, indices); - triangleTextureElements.add(new TriangleTextureElement(i, boundingBox, this, uvsArray, indices, blenderContext)); - } - return new TriangulatedTexture(triangleTextureElements, blenderContext); - } - - /** - * Creates a texture for the sky. The result texture has 6 layers. - * @param size - * the size of the texture (width and height are equal) - * @param horizontalColor - * the horizon color - * @param zenithColor - * the zenith color - * @param blenderContext - * the blender context - * @return the sky texture - */ - public TextureCubeMap generateSkyTexture(int size, ColorRGBA horizontalColor, ColorRGBA zenithColor, BlenderContext blenderContext) { - Image image = ImageUtils.createEmptyImage(Format.RGB8, size, size, 6); - PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - - float delta = 1 / (float) (size - 1); - float sideV, sideS = 1, forwardU = 1, forwardV, upS; - TempVars tempVars = TempVars.get(); - CastFunction castFunction = CAST_FUNCTIONS[blenderContext.getBlenderKey().getSkyGeneratedTextureShape().ordinal()]; - float castRadius = blenderContext.getBlenderKey().getSkyGeneratedTextureRadius(); - - for (int x = 0; x < size; ++x) { - sideV = 1; - forwardV = 1; - upS = 0; - for (int y = 0; y < size; ++y) { - castFunction.cast(tempVars.vect1.set(1, sideV, sideS), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, NEGATIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// right - - castFunction.cast(tempVars.vect1.set(0, sideV, 1 - sideS), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, POSITIVE_X, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// left - - castFunction.cast(tempVars.vect1.set(forwardU, forwardV, 0), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, POSITIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// front - - castFunction.cast(tempVars.vect1.set(1 - forwardU, forwardV, 1), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, NEGATIVE_Z, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// back - - castFunction.cast(tempVars.vect1.set(forwardU, 0, upS), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, NEGATIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// top - - castFunction.cast(tempVars.vect1.set(forwardU, 1, 1 - upS), castRadius); - textureGenerator.getPixel(pixel, tempVars.vect1.x, tempVars.vect1.y, tempVars.vect1.z); - pixelIO.write(image, POSITIVE_Y, ImageUtils.color(pixel, horizontalColor, zenithColor), x, y);// bottom - - sideV = FastMath.clamp(sideV - delta, 0, 1); - forwardV = FastMath.clamp(forwardV - delta, 0, 1); - upS = FastMath.clamp(upS + delta, 0, 1); - } - sideS = FastMath.clamp(sideS - delta, 0, 1); - forwardU = FastMath.clamp(forwardU - delta, 0, 1); - } - tempVars.release(); - - return new TextureCubeMap(image); - } - - @Override - public void setWrap(WrapAxis axis, WrapMode mode) { - } - - @Override - public void setWrap(WrapMode mode) { - } - - @Override - public WrapMode getWrap(WrapAxis axis) { - return null; - } - - @Override - public Type getType() { - return Type.ThreeDimensional; - } - - @Override - public Texture createSimpleClone() { - return null; - } - - /** - * Private class to give the format of the 'virtual' 3D texture image. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class GeneratedTextureImage extends Image { - public GeneratedTextureImage(Format imageFormat) { - super.format = imageFormat; - } - } - - /** - * The casting functions to create a sky generated texture against selected shape of a selected size. - * - * @author Marcin Roguski (Kaelthas) - */ - private static interface CastFunction { - void cast(Vector3f pointToCast, float radius); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java deleted file mode 100644 index 806de78f51..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageLoader.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.TextureKey; -import com.jme3.scene.plugins.blender.file.BlenderInputStream; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.plugins.AWTLoader; -import com.jme3.texture.plugins.HDRLoader; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * An image loader class. It uses three loaders (AWTLoader, TGALoader and DDSLoader) in an attempt to load the image from the given - * input stream. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class ImageLoader extends AWTLoader { - private static final Logger LOGGER = Logger.getLogger(ImageLoader.class.getName()); - private static final Logger hdrLogger = Logger.getLogger(HDRLoader.class.getName()); // Used to silence HDR Errors - - /** - * List of Blender-Supported Texture Extensions (we have to guess them, so - * the AssetLoader can find them. Not good, but better than nothing. - * Source: https://docs.blender.org/manual/en/dev/data_system/files/media/image_formats.html - */ - private static final String[] extensions = new String[] - { /* Windows Bitmap */".bmp", - /* Iris */ ".sgi", ".rgb", ".bw", - /* PNG */ ".png", - /* JPEG */ ".jpg", ".jpeg", - /* JPEG 2000 */ ".jp2", ".j2c", - /* Targa */".tga", - /* Cineon & DPX */".cin", ".dpx", - /* OpenEXR */ ".exr", - /* Radiance HDR */ ".hdr", - /* TIFF */ ".tif", ".tiff", - /* DDS (Direct X) */ ".dds" }; - - /** - * This method loads a image which is packed into the blender file. - * It makes use of all the registered AssetLoaders - * - * @param inputStream - * blender input stream - * @param startPosition - * position in the stream where the image data starts - * @param flipY - * if the image should be flipped (does not work with DirectX image) - * @return loaded image or null if it could not be loaded - * @deprecated This method has only been left in for API compability. - * Use loadTexture instead - */ - public Image loadImage(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) { - Texture tex = loadTexture(assetManager, inputStream, startPosition, flipY); - - if (tex == null) { - return null; - } else { - return tex.getImage(); - } - } - - /** - * This method loads a texture which is packed into the blender file. - * It makes use of all the registered AssetLoaders - * - * @param inputStream - * blender input stream - * @param startPosition - * position in the stream where the image data starts - * @param flipY - * if the image should be flipped (does not work with DirectX image) - * @return loaded texture or null if it could not be loaded - */ - public Texture loadTexture(AssetManager assetManager, BlenderInputStream inputStream, int startPosition, boolean flipY) { - inputStream.setPosition(startPosition); - TextureKey tKey; - Texture result = null; - - hdrLogger.setLevel(Level.SEVERE); // When we bruteforce try HDR on a non hdr file, it prints unreadable chars - - for (String ext: extensions) { - tKey = new TextureKey("dummy" + ext, flipY); - try { - result = assetManager.loadAssetFromStream(tKey, inputStream); - } catch (Exception e) { - continue; - } - - if (result != null) { - break; // Could locate a possible asset - } - } - - if (result == null) { - LOGGER.warning("Texture could not be loaded by any of the available loaders!\n" - + "Since the file has been packed into the blender file, there is no" - + "way for us to tell you which texture it was."); - } - - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java deleted file mode 100644 index 8ca4f6330c..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/ImageUtils.java +++ /dev/null @@ -1,473 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import java.awt.color.ColorSpace; -import java.awt.geom.AffineTransform; -import java.awt.image.AffineTransformOp; -import java.awt.image.BufferedImage; -import java.awt.image.ColorConvertOp; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; - -import jme3tools.converters.ImageToAwt; -import jme3tools.converters.RGB565; - -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.util.BufferUtils; - -/** - * This utility class has the methods that deal with images. - * - * @author Marcin Roguski (Kaelthas) - */ -public final class ImageUtils { - /** - * Creates an image of the given size and depth. - * @param format - * the image format - * @param width - * the image width - * @param height - * the image height - * @param depth - * the image depth - * @return the new image instance - */ - public static Image createEmptyImage(Format format, int width, int height, int depth) { - int bufferSize = width * height * (format.getBitsPerPixel() >> 3); - if (depth < 2) { - return new Image(format, width, height, BufferUtils.createByteBuffer(bufferSize), com.jme3.texture.image.ColorSpace.Linear); - } - ArrayList data = new ArrayList(depth); - for (int i = 0; i < depth; ++i) { - data.add(BufferUtils.createByteBuffer(bufferSize)); - } - return new Image(Format.RGB8, width, height, depth, data, com.jme3.texture.image.ColorSpace.Linear); - } - - /** - * The method sets a color for the given pixel by merging the two given colors. - * The lowIntensityColor will be most visible when the pixel has low intensity. - * The highIntensityColor will be most visible when the pixel has high intensity. - * - * @param pixel - * the pixel that will have the colors altered - * @param lowIntensityColor - * the low intensity color - * @param highIntensityColor - * the high intensity color - * @return the altered pixel (the same instance) - */ - public static TexturePixel color(TexturePixel pixel, ColorRGBA lowIntensityColor, ColorRGBA highIntensityColor) { - float intensity = pixel.intensity; - pixel.fromColor(lowIntensityColor); - pixel.mult(1 - pixel.intensity); - pixel.add(highIntensityColor.mult(intensity)); - return pixel; - } - - /** - * This method merges two given images. The result is stored in the - * 'target' image. - * - * @param targetImage - * the target image - * @param sourceImage - * the source image - */ - public static void merge(Image targetImage, Image sourceImage) { - if (sourceImage.getDepth() != targetImage.getDepth()) { - throw new IllegalArgumentException("The given images should have the same depth to merge them!"); - } - if (sourceImage.getWidth() != targetImage.getWidth()) { - throw new IllegalArgumentException("The given images should have the same width to merge them!"); - } - if (sourceImage.getHeight() != targetImage.getHeight()) { - throw new IllegalArgumentException("The given images should have the same height to merge them!"); - } - - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); - - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); - targetIO.read(targetImage, layerIndex, targetPixel, x, y); - targetPixel.merge(sourcePixel); - targetIO.write(targetImage, layerIndex, targetPixel, x, y); - } - } - } - } - - /** - * This method merges two given images. The result is stored in the - * 'target' image. - * - * @param targetImage - * the target image - * @param sourceImage - * the source image - */ - public static void mix(Image targetImage, Image sourceImage) { - if (sourceImage.getDepth() != targetImage.getDepth()) { - throw new IllegalArgumentException("The given images should have the same depth to merge them!"); - } - if (sourceImage.getWidth() != targetImage.getWidth()) { - throw new IllegalArgumentException("The given images should have the same width to merge them!"); - } - if (sourceImage.getHeight() != targetImage.getHeight()) { - throw new IllegalArgumentException("The given images should have the same height to merge them!"); - } - - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(targetImage.getFormat()); - TexturePixel sourcePixel = new TexturePixel(); - TexturePixel targetPixel = new TexturePixel(); - int depth = targetImage.getDepth() == 0 ? 1 : targetImage.getDepth(); - - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < sourceImage.getWidth(); ++x) { - for (int y = 0; y < sourceImage.getHeight(); ++y) { - sourceIO.read(sourceImage, layerIndex, sourcePixel, x, y); - targetIO.read(targetImage, layerIndex, targetPixel, x, y); - targetPixel.mix(sourcePixel); - targetIO.write(targetImage, layerIndex, targetPixel, x, y); - } - } - } - } - - /** - * Resizes the image to the given width and height. - * @param source - * the source image (this remains untouched, the new image instance is created) - * @param width - * the target image width - * @param height - * the target image height - * @return the resized image - */ - public static Image resizeTo(Image source, int width, int height) { - BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); - - double scaleX = width / (double) sourceImage.getWidth(); - double scaleY = height / (double) sourceImage.getHeight(); - AffineTransform scaleTransform = AffineTransform.getScaleInstance(scaleX, scaleY); - AffineTransformOp bilinearScaleOp = new AffineTransformOp(scaleTransform, AffineTransformOp.TYPE_BILINEAR); - - BufferedImage scaledImage = bilinearScaleOp.filter(sourceImage, new BufferedImage(width, height, sourceImage.getType())); - return ImageUtils.toJmeImage(scaledImage, source.getFormat()); - } - - /** - * This method converts the given texture into normal-map texture. - * - * @param source - * the source texture - * @param strengthFactor - * the normal strength factor - * @return normal-map texture - */ - public static Image convertToNormalMapTexture(Image source, float strengthFactor) { - BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); - - BufferedImage heightMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - BufferedImage bumpMap = new BufferedImage(sourceImage.getWidth(), sourceImage.getHeight(), BufferedImage.TYPE_INT_ARGB); - ColorConvertOp gscale = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); - gscale.filter(sourceImage, heightMap); - - Vector3f S = new Vector3f(); - Vector3f T = new Vector3f(); - Vector3f N = new Vector3f(); - - for (int x = 0; x < bumpMap.getWidth(); ++x) { - for (int y = 0; y < bumpMap.getHeight(); ++y) { - // generating bump pixel - S.x = 1; - S.y = 0; - S.z = strengthFactor * ImageUtils.getHeight(heightMap, x + 1, y) - strengthFactor * ImageUtils.getHeight(heightMap, x - 1, y); - T.x = 0; - T.y = 1; - T.z = strengthFactor * ImageUtils.getHeight(heightMap, x, y + 1) - strengthFactor * ImageUtils.getHeight(heightMap, x, y - 1); - - float den = (float) Math.sqrt(S.z * S.z + T.z * T.z + 1); - N.x = -S.z; - N.y = -T.z; - N.z = 1; - N.divideLocal(den); - - // setting the pixel in the result image - bumpMap.setRGB(x, y, ImageUtils.vectorToColor(N.x, N.y, N.z)); - } - } - return ImageUtils.toJmeImage(bumpMap, source.getFormat()); - } - - /** - * This method converts the given texture into black and whit (grayscale) texture. - * - * @param source - * the source texture - * @return grayscale texture - */ - public static Image convertToGrayscaleTexture(Image source) { - BufferedImage sourceImage = ImageToAwt.convert(source, false, false, 0); - ColorConvertOp op = new ColorConvertOp(ColorSpace.getInstance(ColorSpace.CS_GRAY), null); - op.filter(sourceImage, sourceImage); - return ImageUtils.toJmeImage(sourceImage, source.getFormat()); - } - - /** - * This method decompresses the given image. If the given image is already - * decompressed nothing happens and it is simply returned. - * - * @param image - * the image to decompress - * @return the decompressed image - */ - public static Image decompress(Image image) { - Format format = image.getFormat(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - int[] sizes = image.getMipMapSizes() != null ? image.getMipMapSizes() : new int[1]; - int[] newMipmapSizes = image.getMipMapSizes() != null ? new int[image.getMipMapSizes().length] : null; - - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - if (sizes.length == 1) { - sizes[0] = data.remaining(); - } - float widthToHeightRatio = image.getWidth() / image.getHeight();// this should always be constant for each mipmap - List texelDataList = new ArrayList(sizes.length); - int maxPosition = 0, resultSize = 0; - - for (int sizeIndex = 0; sizeIndex < sizes.length; ++sizeIndex) { - maxPosition += sizes[sizeIndex]; - DDSTexelData texelData = new DDSTexelData(sizes[sizeIndex], widthToHeightRatio, format); - texelDataList.add(texelData); - switch (format) { - case DXT1:// BC1 - case DXT1A: - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - if (col0 > col1) { - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - } else { - // creating color2 = 1/2color0 + 1/2color1 - colors[2].fromPixel(colors[0]); - colors[2].add(colors[1]); - colors[2].mult(0.5f); - - colors[3].fromARGB8(0); - } - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes); - } - break; - case DXT3:// BC2 - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - long alpha = data.getLong(); - float[] alphas = new float[16]; - long alphasIndex = 0; - for (int i = 0; i < 16; ++i) { - alphasIndex |= i << i * 4; - byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); - alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphasIndex); - } - break; - case DXT5:// BC3 - float[] alphas = new float[8]; - while (data.position() < maxPosition) { - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - alphas[0] = data.get() * 255.0f; - alphas[1] = data.get() * 255.0f; - //the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values - long alphaIndices = data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; - if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. - alphas[2] = (6 * alphas[0] + alphas[1]) / 7; - alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; - alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; - alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; - alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; - alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; - } else { - alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; - alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; - alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; - alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; - alphas[6] = 0; - alphas[7] = 1; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - int indexes = data.getInt();// 4-byte table with color indexes in decompressed table - texelData.add(colors, indexes, alphas, alphaIndices); - } - break; - default: - throw new IllegalStateException("Unknown compressed format: " + format); - } - newMipmapSizes[sizeIndex] = texelData.getSizeInBytes(); - resultSize += texelData.getSizeInBytes(); - } - byte[] bytes = new byte[resultSize]; - int offset = 0; - byte[] pixelBytes = new byte[4]; - for (DDSTexelData texelData : texelDataList) { - for (int i = 0; i < texelData.getPixelWidth(); ++i) { - for (int j = 0; j < texelData.getPixelHeight(); ++j) { - if (texelData.getRGBA8(i, j, pixelBytes)) { - bytes[offset + (j * texelData.getPixelWidth() + i) * 4] = pixelBytes[0]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 1] = pixelBytes[1]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 2] = pixelBytes[2]; - bytes[offset + (j * texelData.getPixelWidth() + i) * 4 + 3] = pixelBytes[3]; - } else { - break; - } - } - } - offset += texelData.getSizeInBytes(); - } - dataArray.add(BufferUtils.createByteBuffer(bytes)); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, image.getWidth(), image.getHeight(), depth, dataArray, com.jme3.texture.image.ColorSpace.Linear) : - new Image(Format.RGBA8, image.getWidth(), image.getHeight(), dataArray.get(0), com.jme3.texture.image.ColorSpace.Linear); - if (newMipmapSizes != null) { - result.setMipMapSizes(newMipmapSizes); - } - return result; - } - - /** - * This method returns the height represented by the specified pixel in the - * given texture. The given texture should be a height-map. - * - * @param image - * the height-map texture - * @param x - * pixel's X coordinate - * @param y - * pixel's Y coordinate - * @return height represented by the given texture in the specified location - */ - private static int getHeight(BufferedImage image, int x, int y) { - if (x < 0) { - x = 0; - } else if (x >= image.getWidth()) { - x = image.getWidth() - 1; - } - if (y < 0) { - y = 0; - } else if (y >= image.getHeight()) { - y = image.getHeight() - 1; - } - return image.getRGB(x, y) & 0xff; - } - - /** - * This method transforms given vector's coordinates into ARGB color (A is - * always = 255). - * - * @param x - * X factor of the vector - * @param y - * Y factor of the vector - * @param z - * Z factor of the vector - * @return color representation of the given vector - */ - private static int vectorToColor(float x, float y, float z) { - int r = Math.round(255 * (x + 1f) / 2f); - int g = Math.round(255 * (y + 1f) / 2f); - int b = Math.round(255 * (z + 1f) / 2f); - return (255 << 24) + (r << 16) + (g << 8) + b; - } - - /** - * Converts java awt image to jme image. - * @param bufferedImage - * the java awt image - * @param format - * the result image format - * @return the jme image - */ - private static Image toJmeImage(BufferedImage bufferedImage, Format format) { - ByteBuffer byteBuffer = BufferUtils.createByteBuffer(bufferedImage.getWidth() * bufferedImage.getHeight() * 3); - ImageToAwt.convert(bufferedImage, format, byteBuffer); - return new Image(format, bufferedImage.getWidth(), bufferedImage.getHeight(), byteBuffer, com.jme3.texture.image.ColorSpace.Linear); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java deleted file mode 100644 index 5b7ccfc9bc..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TextureHelper.java +++ /dev/null @@ -1,691 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.blender.textures; - -import java.awt.geom.AffineTransform; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetLoadException; -import com.jme3.asset.AssetManager; -import com.jme3.asset.AssetNotFoundException; -import com.jme3.asset.BlenderKey; -import com.jme3.asset.GeneratedTextureKey; -import com.jme3.asset.TextureKey; -import com.jme3.math.Vector2f; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.plugins.blender.AbstractBlenderHelper; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.BlenderContext.LoadedDataType; -import com.jme3.scene.plugins.blender.file.BlenderFileException; -import com.jme3.scene.plugins.blender.file.DynamicArray; -import com.jme3.scene.plugins.blender.file.FileBlockHeader; -import com.jme3.scene.plugins.blender.file.Pointer; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.materials.MaterialContext; -import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.UVCoordinatesType; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlenderFactory; -import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture.MinFilter; -import com.jme3.texture.Texture.WrapMode; -import com.jme3.texture.Texture2D; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; -import com.jme3.util.PlaceholderAssets; - -/** - * A class that is used in texture calculations. - * - * @author Marcin Roguski - */ -public class TextureHelper extends AbstractBlenderHelper { - private static final Logger LOGGER = Logger.getLogger(TextureHelper.class.getName()); - - // texture types - public static final int TEX_NONE = 0; - public static final int TEX_CLOUDS = 1; - public static final int TEX_WOOD = 2; - public static final int TEX_MARBLE = 3; - public static final int TEX_MAGIC = 4; - public static final int TEX_BLEND = 5; - public static final int TEX_STUCCI = 6; - public static final int TEX_NOISE = 7; - public static final int TEX_IMAGE = 8; - public static final int TEX_PLUGIN = 9; - public static final int TEX_ENVMAP = 10; - public static final int TEX_MUSGRAVE = 11; - public static final int TEX_VORONOI = 12; - public static final int TEX_DISTNOISE = 13; - public static final int TEX_POINTDENSITY = 14; // v. 25+ - public static final int TEX_VOXELDATA = 15; // v. 25+ - public static final int TEX_OCEAN = 16; // v. 26+ - - public static final Type[] TEXCOORD_TYPES = new Type[] { Type.TexCoord, Type.TexCoord2, Type.TexCoord3, Type.TexCoord4, Type.TexCoord5, Type.TexCoord6, Type.TexCoord7, Type.TexCoord8 }; - - private TextureGeneratorFactory textureGeneratorFactory = new TextureGeneratorFactory(); - - /** - * This constructor parses the given blender version and stores the result. - * It creates noise generator and texture generators. - * - * @param blenderVersion - * the version read from the blend file - * @param blenderContext - * the blender context - */ - public TextureHelper(String blenderVersion, BlenderContext blenderContext) { - super(blenderVersion, blenderContext); - } - - /** - * This class returns a texture read from the file or from packed blender - * data. The returned texture has the name set to the value of its blender - * type. - * - * @param textureStructure - * texture structure filled with data - * @param blenderContext - * the blender context - * @return the texture that can be used by JME engine - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - public Texture getTexture(Structure textureStructure, Structure mTex, BlenderContext blenderContext) throws BlenderFileException { - Texture result = (Texture) blenderContext.getLoadedFeature(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - if (result != null) { - return result; - } - - if ("ID".equals(textureStructure.getType())) { - LOGGER.fine("Loading texture from external blend file."); - return (Texture) this.loadLibrary(textureStructure); - } - - int type = ((Number) textureStructure.getFieldValue("type")).intValue(); - int imaflag = ((Number) textureStructure.getFieldValue("imaflag")).intValue(); - - switch (type) { - case TEX_IMAGE:// (it is first because probably this will be most commonly used) - Pointer pImage = (Pointer) textureStructure.getFieldValue("ima"); - if (pImage.isNotNull()) { - Structure image = pImage.fetchData().get(0); - Texture loadedTexture = this.loadImageAsTexture(image, imaflag, blenderContext); - if (loadedTexture != null) { - result = loadedTexture; - this.applyColorbandAndColorFactors(textureStructure, result.getImage(), blenderContext); - } - } - break; - case TEX_CLOUDS: - case TEX_WOOD: - case TEX_MARBLE: - case TEX_MAGIC: - case TEX_BLEND: - case TEX_STUCCI: - case TEX_NOISE: - case TEX_MUSGRAVE: - case TEX_VORONOI: - case TEX_DISTNOISE: - result = new GeneratedTexture(textureStructure, mTex, textureGeneratorFactory.createTextureGenerator(type), blenderContext); - break; - case TEX_NONE:// No texture, do nothing - break; - case TEX_POINTDENSITY: - case TEX_VOXELDATA: - case TEX_PLUGIN: - case TEX_ENVMAP: - case TEX_OCEAN: - LOGGER.log(Level.WARNING, "Unsupported texture type: {0} for texture: {1}", new Object[] { type, textureStructure.getName() }); - break; - default: - throw new BlenderFileException("Unknown texture type: " + type + " for texture: " + textureStructure.getName()); - } - if (result != null) { - result.setName(textureStructure.getName()); - result.setWrap(WrapMode.Repeat); - - // decide if the mipmaps will be generated - switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { - case ALWAYS_GENERATE: - result.setMinFilter(MinFilter.Trilinear); - break; - case NEVER_GENERATE: - break; - case GENERATE_WHEN_NEEDED: - if ((imaflag & 0x04) != 0) { - result.setMinFilter(MinFilter.Trilinear); - } - break; - default: - throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); - } - - if (type != TEX_IMAGE) {// only generated textures should have this key - result.setKey(new GeneratedTextureKey(textureStructure.getName())); - } - - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.log(Level.FINE, "Adding texture {0} to the loaded features with OMA = {1}", new Object[] { result.getName(), textureStructure.getOldMemoryAddress() }); - } - blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, textureStructure); - blenderContext.addLoadedFeatures(textureStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result); - } - return result; - } - - /** - * This class returns a texture read from the file or from packed blender - * data. - * - * @param imageStructure - * image structure filled with data - * @param imaflag - * the image flag - * @param blenderContext - * the blender context - * @return the texture that can be used by JME engine - * @throws BlenderFileException - * this exception is thrown when the blend file structure is - * somehow invalid or corrupted - */ - public Texture loadImageAsTexture(Structure imageStructure, int imaflag, BlenderContext blenderContext) throws BlenderFileException { - LOGGER.log(Level.FINE, "Fetching texture with OMA = {0}", imageStructure.getOldMemoryAddress()); - Texture result = null; - Image im = (Image) blenderContext.getLoadedFeature(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE); - // if (im == null) { HACK force reaload always, as constructor in else case is destroying the TextureKeys! - if ("ID".equals(imageStructure.getType())) { - LOGGER.fine("Loading texture from external blend file."); - result = (Texture) this.loadLibrary(imageStructure); - } else { - String texturePath = imageStructure.getFieldValue("name").toString(); - Pointer pPackedFile = (Pointer) imageStructure.getFieldValue("packedfile"); - if (pPackedFile.isNull()) { - LOGGER.log(Level.FINE, "Reading texture from file: {0}", texturePath); - result = this.loadImageFromFile(texturePath, imaflag, blenderContext); - } else { - LOGGER.fine("Packed texture. Reading directly from the blend file!"); - Structure packedFile = pPackedFile.fetchData().get(0); - Pointer pData = (Pointer) packedFile.getFieldValue("data"); - FileBlockHeader dataFileBlock = blenderContext.getFileBlock(pData.getOldMemoryAddress()); - blenderContext.getInputStream().setPosition(dataFileBlock.getBlockPosition()); - - // Should the texture be flipped? It works for sinbad .. - result = new ImageLoader().loadTexture(blenderContext.getAssetManager(), blenderContext.getInputStream(), dataFileBlock.getBlockPosition(), true); - if (result == null) { - result = new Texture2D(PlaceholderAssets.getPlaceholderImage(blenderContext.getAssetManager())); - LOGGER.fine("ImageLoader returned null. It probably failed to load the packed texture, using placeholder asset"); - } - } - } - //} else { - // result = new Texture2D(im); - // } - - if (result != null) {// render result is not being loaded - blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.STRUCTURE, imageStructure); - blenderContext.addLoadedFeatures(imageStructure.getOldMemoryAddress(), LoadedDataType.FEATURE, result.getImage()); - result.setName(imageStructure.getName()); - } - return result; - } - - /** - * This method creates the affine transform that is used to transform a - * triangle defined by one UV coordinates into a triangle defined by - * different UV's. - * - * @param source - * source UV coordinates - * @param dest - * target UV coordinates - * @param sourceSize - * the width and height of the source image - * @param targetSize - * the width and height of the target image - * @return affine transform to transform one triangle to another - */ - public AffineTransform createAffineTransform(Vector2f[] source, Vector2f[] dest, int[] sourceSize, int[] targetSize) { - float x11 = source[0].getX() * sourceSize[0]; - float x12 = source[0].getY() * sourceSize[1]; - float x21 = source[1].getX() * sourceSize[0]; - float x22 = source[1].getY() * sourceSize[1]; - float x31 = source[2].getX() * sourceSize[0]; - float x32 = source[2].getY() * sourceSize[1]; - float y11 = dest[0].getX() * targetSize[0]; - float y12 = dest[0].getY() * targetSize[1]; - float y21 = dest[1].getX() * targetSize[0]; - float y22 = dest[1].getY() * targetSize[1]; - float y31 = dest[2].getX() * targetSize[0]; - float y32 = dest[2].getY() * targetSize[1]; - - float a1 = ((y11 - y21) * (x12 - x32) - (y11 - y31) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); - float a2 = ((y11 - y21) * (x11 - x31) - (y11 - y31) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); - float a3 = y11 - a1 * x11 - a2 * x12; - float a4 = ((y12 - y22) * (x12 - x32) - (y12 - y32) * (x12 - x22)) / ((x11 - x21) * (x12 - x32) - (x11 - x31) * (x12 - x22)); - float a5 = ((y12 - y22) * (x11 - x31) - (y12 - y32) * (x11 - x21)) / ((x12 - x22) * (x11 - x31) - (x12 - x32) * (x11 - x21)); - float a6 = y12 - a4 * x11 - a5 * x12; - return new AffineTransform(a1, a4, a2, a5, a3, a6); - } - - /** - * This method returns the proper pixel position on the image. - * - * @param pos - * the relative position (value of range [0, 1] (both inclusive)) - * @param size - * the size of the line the pixel lies on (width, height or - * depth) - * @return the integer index of the pixel on the line of the specified width - */ - public int getPixelPosition(float pos, int size) { - float pixelWidth = 1 / (float) size; - pos *= size; - int result = (int) pos; - // here is where we repair floating point operations errors :) - if (Math.abs(result - pos) > pixelWidth) { - ++result; - } - return result; - } - - /** - * This method returns subimage of the give image. The subimage is - * constrained by the rectangle coordinates. The source image is unchanged. - * - * @param image - * the image to be subimaged - * @param minX - * minimum X position - * @param minY - * minimum Y position - * @param maxX - * maximum X position - * @param maxY - * maximum Y position - * @return a part of the given image - */ - public Image getSubimage(Image image, int minX, int minY, int maxX, int maxY) { - if (minY > maxY) { - throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); - } - if (minX > maxX) { - throw new IllegalArgumentException("Minimum Y value is higher than maximum Y value!"); - } - if (image.getData().size() > 1) { - throw new IllegalArgumentException("Only flat images are allowed for subimage operation!"); - } - if (image.getMipMapSizes() != null) { - LOGGER.warning("Subimaging image with mipmaps is not yet supported!"); - } - - int width = maxX - minX; - int height = maxY - minY; - ByteBuffer data = BufferUtils.createByteBuffer(width * height * (image.getFormat().getBitsPerPixel() >> 3)); - - Image result = new Image(image.getFormat(), width, height, data, ColorSpace.sRGB); - PixelInputOutput pixelIO = PixelIOFactory.getPixelIO(image.getFormat()); - TexturePixel pixel = new TexturePixel(); - - for (int x = minX; x < maxX; ++x) { - for (int y = minY; y < maxY; ++y) { - pixelIO.read(image, 0, pixel, x, y); - pixelIO.write(result, 0, pixel, x - minX, y - minY); - } - } - return result; - } - - /** - * This method applies the colorband and color factors to image type - * textures. If there is no colorband defined for the texture or the color - * factors are all equal to 1.0f then no changes are made. - * - * @param tex - * the texture structure - * @param image - * the image that will be altered if necessary - * @param blenderContext - * the blender context - */ - private void applyColorbandAndColorFactors(Structure tex, Image image, BlenderContext blenderContext) { - float rfac = ((Number) tex.getFieldValue("rfac")).floatValue(); - float gfac = ((Number) tex.getFieldValue("gfac")).floatValue(); - float bfac = ((Number) tex.getFieldValue("bfac")).floatValue(); - float[][] colorBand = new ColorBand(tex, blenderContext).computeValues(); - int depth = image.getDepth() == 0 ? 1 : image.getDepth(); - - if (colorBand != null) { - TexturePixel pixel = new TexturePixel(); - PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - imageIO.read(image, layerIndex, pixel, x, y); - - int colorbandIndex = (int) (pixel.alpha * 1000.0f); - pixel.red = colorBand[colorbandIndex][0] * rfac; - pixel.green = colorBand[colorbandIndex][1] * gfac; - pixel.blue = colorBand[colorbandIndex][2] * bfac; - pixel.alpha = colorBand[colorbandIndex][3]; - - imageIO.write(image, layerIndex, pixel, x, y); - } - } - } - } else if (rfac != 1.0f || gfac != 1.0f || bfac != 1.0f) { - TexturePixel pixel = new TexturePixel(); - PixelInputOutput imageIO = PixelIOFactory.getPixelIO(image.getFormat()); - for (int layerIndex = 0; layerIndex < depth; ++layerIndex) { - for (int x = 0; x < image.getWidth(); ++x) { - for (int y = 0; y < image.getHeight(); ++y) { - imageIO.read(image, layerIndex, pixel, x, y); - - pixel.red *= rfac; - pixel.green *= gfac; - pixel.blue *= bfac; - - imageIO.write(image, layerIndex, pixel, x, y); - } - } - } - } - } - - /** - * This method loads the texture from outside the blend file using the - * AssetManager that the blend file was loaded with. It returns a texture - * with a full assetKey that references the original texture so it later - * doesn't need to be packed when the model data is serialized. It searches - * the AssetManager for the full path if the model file is a relative path - * and will attempt to truncate the path if it is an absolute file path - * until the path can be found in the AssetManager. If the texture can not - * be found, it will issue a load attempt for the initial path anyway so the - * failed load can be reported by the AssetManagers callback methods for - * failed assets. - * - * @param name - * the path to the image - * @param imaflag - * the image flag - * @param blenderContext - * the blender context - * @return the loaded image or null if the image cannot be found - */ - protected Texture loadImageFromFile(String name, int imaflag, BlenderContext blenderContext) { - if (!name.contains(".")) { - return null; // no extension means not a valid image - } - - // decide if the mipmaps will be generated - boolean generateMipmaps = false; - switch (blenderContext.getBlenderKey().getMipmapGenerationMethod()) { - case ALWAYS_GENERATE: - generateMipmaps = true; - break; - case NEVER_GENERATE: - break; - case GENERATE_WHEN_NEEDED: - generateMipmaps = (imaflag & 0x04) != 0; - break; - default: - throw new IllegalStateException("Unknown mipmap generation method: " + blenderContext.getBlenderKey().getMipmapGenerationMethod()); - } - - AssetManager assetManager = blenderContext.getAssetManager(); - name = name.replace('\\', '/'); - Texture result = null; - - if (name.startsWith("//")) { - // This is a relative path, so try to find it relative to the .blend file - String relativePath = name.substring(2); - // Augument the path with blender key path - BlenderKey blenderKey = blenderContext.getBlenderKey(); - int idx = blenderKey.getName().lastIndexOf('/'); - String blenderAssetFolder = blenderKey.getName().substring(0, idx != -1 ? idx : 0); - String absoluteName = blenderAssetFolder + '/' + relativePath; - // Directly try to load texture so AssetManager can report missing textures - try { - TextureKey key = new TextureKey(absoluteName); - key.setFlipY(true); - key.setGenerateMips(generateMipmaps); - result = assetManager.loadTexture(key); - result.setKey(key); - } catch (AssetNotFoundException | AssetLoadException e) { - LOGGER.fine(e.getLocalizedMessage()); - } - } else { - // This is a full path, try to truncate it until the file can be found - // this works as the assetManager root is most probably a part of the - // image path. E.g. AssetManager has a locator at c:/Files/ and the - // texture path is c:/Files/Textures/Models/Image.jpg. - // For this we create a list with every possible full path name from - // the asset name to the root. Image.jpg, Models/Image.jpg, - // Textures/Models/Image.jpg (bingo) etc. - List assetNames = new ArrayList(); - String[] paths = name.split("\\/"); - StringBuilder sb = new StringBuilder(paths[paths.length - 1]);// the asset name - assetNames.add(paths[paths.length - 1]); - - for (int i = paths.length - 2; i >= 0; --i) { - sb.insert(0, '/'); - sb.insert(0, paths[i]); - assetNames.add(0, sb.toString()); - } - // Now try to locate the asset - for (String assetName : assetNames) { - try { - TextureKey key = new TextureKey(assetName); - key.setFlipY(true); - key.setGenerateMips(generateMipmaps); - AssetInfo info = assetManager.locateAsset(key); - if (info != null) { - Texture texture = assetManager.loadTexture(key); - result = texture; - // Set key explicitly here if other ways fail - texture.setKey(key); - // If texture is found return it; - return result; - } - } catch (AssetNotFoundException | AssetLoadException e) { - LOGGER.fine(e.getLocalizedMessage()); - } - } - // The asset was not found in the loop above, call loadTexture with - // the original path once anyway so that the AssetManager can report - // the missing asset to subsystems. - try { - TextureKey key = new TextureKey(name); - assetManager.loadTexture(key); - } catch (AssetNotFoundException | AssetLoadException e) { - LOGGER.fine(e.getLocalizedMessage()); - } - } - - return result; - } - - /** - * Reads the texture data from the given material or sky structure. - * @param structure - * the structure of material or sky - * @param diffuseColorArray - * array of diffuse colors - * @param skyTexture - * indicates it we're going to read sky texture or not - * @return a list of combined textures - * @throws BlenderFileException - * an exception is thrown when problems with reading the blend file occur - */ - @SuppressWarnings("unchecked") - public List readTextureData(Structure structure, float[] diffuseColorArray, boolean skyTexture) throws BlenderFileException { - DynamicArray mtexsArray = (DynamicArray) structure.getFieldValue("mtex"); - int separatedTextures = skyTexture ? 0 : ((Number) structure.getFieldValue("septex")).intValue(); - List texturesList = new ArrayList(); - for (int i = 0; i < mtexsArray.getTotalSize(); ++i) { - Pointer p = mtexsArray.get(i); - if (p.isNotNull() && (separatedTextures & 1 << i) == 0) { - TextureData textureData = new TextureData(); - textureData.mtex = p.fetchData().get(0); - textureData.uvCoordinatesType = skyTexture ? UVCoordinatesType.TEXCO_ORCO.blenderValue : ((Number) textureData.mtex.getFieldValue("texco")).intValue(); - textureData.projectionType = ((Number) textureData.mtex.getFieldValue("mapping")).intValue(); - textureData.uvCoordinatesName = textureData.mtex.getFieldValue("uvName").toString(); - if (textureData.uvCoordinatesName != null && textureData.uvCoordinatesName.trim().length() == 0) { - textureData.uvCoordinatesName = null; - } - - Pointer pTex = (Pointer) textureData.mtex.getFieldValue("tex"); - if (pTex.isNotNull()) { - Structure tex = pTex.fetchData().get(0); - textureData.textureStructure = tex; - texturesList.add(textureData); - } - } - } - - LOGGER.info("Loading model's textures."); - List loadedTextures = new ArrayList(); - if (blenderContext.getBlenderKey().isOptimiseTextures()) { - LOGGER.fine("Optimising the useage of model's textures."); - Map> textureDataMap = this.sortTextures(texturesList); - for (Entry> entry : textureDataMap.entrySet()) { - if (entry.getValue().size() > 0) { - CombinedTexture combinedTexture = new CombinedTexture(entry.getKey().intValue(), !skyTexture); - for (TextureData textureData : entry.getValue()) { - int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); - boolean negateTexture = (texflag & 0x04) != 0; - Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); - if (texture != null) { - int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); - float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; - float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); - TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); - combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); - } - } - if (combinedTexture.getTexturesCount() > 0) { - loadedTextures.add(combinedTexture); - } - } - } - } else { - LOGGER.fine("No textures optimisation applied."); - int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; - for (TextureData textureData : texturesList) { - Texture texture = this.getTexture(textureData.textureStructure, textureData.mtex, blenderContext); - if (texture != null) { - Number mapto = (Number) textureData.mtex.getFieldValue("mapto"); - int texflag = ((Number) textureData.mtex.getFieldValue("texflag")).intValue(); - boolean negateTexture = (texflag & 0x04) != 0; - - boolean colorSet = false; - for (int i = 0; i < mappings.length; ++i) { - if ((mappings[i] & mapto.intValue()) != 0) { - if(mappings[i] == MaterialContext.MTEX_COL) { - colorSet = true; - } else if(colorSet && mappings[i] == MaterialContext.MTEX_ALPHA) { - continue; - } - - CombinedTexture combinedTexture = new CombinedTexture(mappings[i], !skyTexture); - int blendType = ((Number) textureData.mtex.getFieldValue("blendtype")).intValue(); - float[] color = new float[] { ((Number) textureData.mtex.getFieldValue("r")).floatValue(), ((Number) textureData.mtex.getFieldValue("g")).floatValue(), ((Number) textureData.mtex.getFieldValue("b")).floatValue() }; - float colfac = ((Number) textureData.mtex.getFieldValue("colfac")).floatValue(); - TextureBlender textureBlender = TextureBlenderFactory.createTextureBlender(texture.getImage().getFormat(), texflag, negateTexture, blendType, diffuseColorArray, color, colfac); - combinedTexture.add(texture, textureBlender, textureData.uvCoordinatesType, textureData.projectionType, textureData.textureStructure, textureData.uvCoordinatesName, blenderContext); - if (combinedTexture.getTexturesCount() > 0) {// the added texture might not have been accepted (if for example loading generated textures is disabled) - loadedTextures.add(combinedTexture); - } - } - } - } - - } - } - - return loadedTextures; - } - - /** - * This method sorts the textures by their mapping type. In each group only - * textures of one type are put (either two- or three-dimensional). - * - * @return a map with sorted textures - */ - private Map> sortTextures(List textures) { - int[] mappings = new int[] { MaterialContext.MTEX_COL, MaterialContext.MTEX_NOR, MaterialContext.MTEX_EMIT, MaterialContext.MTEX_SPEC, MaterialContext.MTEX_ALPHA, MaterialContext.MTEX_AMB }; - Map> result = new HashMap>(); - for (TextureData data : textures) { - Number mapto = (Number) data.mtex.getFieldValue("mapto"); - - boolean colorSet = false; - for (int i = 0; i < mappings.length; ++i) { - if ((mappings[i] & mapto.intValue()) != 0) { - if(mappings[i] == MaterialContext.MTEX_COL) { - colorSet = true; - } else if(colorSet && mappings[i] == MaterialContext.MTEX_ALPHA) { - continue; - } - - List datas = result.get(mappings[i]); - if (datas == null) { - datas = new ArrayList(); - result.put(mappings[i], datas); - } - datas.add(data); - } - } - } - return result; - } - - private static class TextureData { - public Structure mtex; - public Structure textureStructure; - public int uvCoordinatesType; - public int projectionType; - /** The name of the user's UV coordinates that are used for this texture. */ - public String uvCoordinatesName; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java deleted file mode 100644 index def3462ed8..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TexturePixel.java +++ /dev/null @@ -1,392 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; - -/** - * The class that stores the pixel values of a texture. - * - * @author Marcin Roguski (Kaelthas) - */ -public class TexturePixel implements Cloneable { - /** The pixel data. */ - public float intensity, red, green, blue, alpha; - - /** - * Copies the values from the given pixel. - * - * @param pixel - * the pixel that we read from - */ - public void fromPixel(TexturePixel pixel) { - this.intensity = pixel.intensity; - this.red = pixel.red; - this.green = pixel.green; - this.blue = pixel.blue; - this.alpha = pixel.alpha; - } - - /** - * Copies the values from the given color. - * - * @param colorRGBA - * the color that we read from - */ - public void fromColor(ColorRGBA colorRGBA) { - this.red = colorRGBA.r; - this.green = colorRGBA.g; - this.blue = colorRGBA.b; - this.alpha = colorRGBA.a; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB(float a, float r, float g, float b) { - this.alpha = a; - this.red = r; - this.green = g; - this.blue = b; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB8(byte a, byte r, byte g, byte b) { - this.alpha = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - this.red = r >= 0 ? r / 255.0f : 1.0f - ~r / 255.0f; - this.green = g >= 0 ? g / 255.0f : 1.0f - ~g / 255.0f; - this.blue = b >= 0 ? b / 255.0f : 1.0f - ~b / 255.0f; - } - - /** - * Copies the values from the given values. - * - * @param a - * the alpha value - * @param r - * the red value - * @param g - * the green value - * @param b - * the blue value - */ - public void fromARGB16(short a, short r, short g, short b) { - this.alpha = a >= 0 ? a / 65535.0f : 1.0f - ~a / 65535.0f; - this.red = r >= 0 ? r / 65535.0f : 1.0f - ~r / 65535.0f; - this.green = g >= 0 ? g / 65535.0f : 1.0f - ~g / 65535.0f; - this.blue = b >= 0 ? b / 65535.0f : 1.0f - ~b / 65535.0f; - } - - /** - * Copies the intensity from the given value. - * - * @param intensity - * the intensity value - */ - public void fromIntensity(byte intensity) { - this.intensity = intensity >= 0 ? intensity / 255.0f : 1.0f - ~intensity / 255.0f; - } - - /** - * Copies the intensity from the given value. - * - * @param intensity - * the intensity value - */ - public void fromIntensity(short intensity) { - this.intensity = intensity >= 0 ? intensity / 65535.0f : 1.0f - ~intensity / 65535.0f; - } - - /** - * This method sets the alpha value (converts it to float number from range - * [0, 1]). - * - * @param alpha - * the alpha value - */ - public void setAlpha(byte alpha) { - this.alpha = alpha >= 0 ? alpha / 255.0f : 1.0f - ~alpha / 255.0f; - } - - /** - * This method sets the alpha value (converts it to float number from range - * [0, 1]). - * - * @param alpha - * the alpha value - */ - public void setAlpha(short alpha) { - this.alpha = alpha >= 0 ? alpha / 65535.0f : 1.0f - ~alpha / 65535.0f; - } - - /** - * Copies the values from the given integer that stores the ARGB8 data. - * - * @param argb8 - * the data stored in an integer - */ - public void fromARGB8(int argb8) { - byte pixelValue = (byte) ((argb8 & 0xFF000000) >> 24); - this.alpha = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) ((argb8 & 0xFF0000) >> 16); - this.red = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) ((argb8 & 0xFF00) >> 8); - this.green = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - pixelValue = (byte) (argb8 & 0xFF); - this.blue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - } - - /** - * Stores RGBA values in the given array. - * - * @param result - * the array to store values - */ - public void toRGBA(float[] result) { - result[0] = this.red; - result[1] = this.green; - result[2] = this.blue; - result[3] = this.alpha; - } - - /** - * Stores the data in the given table. - * - * @param result - * the result table - */ - public void toRGBA8(byte[] result) { - result[0] = (byte) (this.red * 255.0f); - result[1] = (byte) (this.green * 255.0f); - result[2] = (byte) (this.blue * 255.0f); - result[3] = (byte) (this.alpha * 255.0f); - } - - /** - * Stores the pixel values in the integer. - * - * @return the integer that stores the pixel values - */ - public int toARGB8() { - int result = 0; - int b = (int) (this.alpha * 255.0f); - result |= b << 24; - b = (int) (this.red * 255.0f); - result |= b << 16; - b = (int) (this.green * 255.0f); - result |= b << 8; - b = (int) (this.blue * 255.0f); - result |= b; - return result; - } - - /** - * @return the intensity of the pixel - */ - public byte getInt() { - return (byte) (this.intensity * 255.0f); - } - - /** - * @return the alpha value of the pixel - */ - public byte getA8() { - return (byte) (this.alpha * 255.0f); - } - - /** - * @return the alpha red of the pixel - */ - public byte getR8() { - return (byte) (this.red * 255.0f); - } - - /** - * @return the green value of the pixel - */ - public byte getG8() { - return (byte) (this.green * 255.0f); - } - - /** - * @return the blue value of the pixel - */ - public byte getB8() { - return (byte) (this.blue * 255.0f); - } - - /** - * @return the alpha value of the pixel - */ - public short getA16() { - return (byte) (this.alpha * 65535.0f); - } - - /** - * @return the alpha red of the pixel - */ - public short getR16() { - return (byte) (this.red * 65535.0f); - } - - /** - * @return the green value of the pixel - */ - public short getG16() { - return (byte) (this.green * 65535.0f); - } - - /** - * @return the blue value of the pixel - */ - public short getB16() { - return (byte) (this.blue * 65535.0f); - } - - /** - * Merges two pixels (adds the values of each color). - * - * @param pixel - * the pixel we merge with - */ - public void merge(TexturePixel pixel) { - float oneMinusAlpha = 1 - pixel.alpha; - this.red = oneMinusAlpha * this.red + pixel.alpha * pixel.red; - this.green = oneMinusAlpha * this.green + pixel.alpha * pixel.green; - this.blue = oneMinusAlpha * this.blue + pixel.alpha * pixel.blue; - this.alpha = (this.alpha + pixel.alpha) * 0.5f; - } - - /** - * Mixes two pixels. - * - * @param pixel - * the pixel we mix with - */ - public void mix(TexturePixel pixel) { - this.red = 0.5f * (this.red + pixel.red); - this.green = 0.5f * (this.green + pixel.green); - this.blue = 0.5f * (this.blue + pixel.blue); - this.alpha = 0.5f * (this.alpha + pixel.alpha); - this.intensity = 0.5f * (this.intensity + pixel.intensity); - } - - /** - * This method negates the colors. - */ - public void negate() { - this.red = 1.0f - this.red; - this.green = 1.0f - this.green; - this.blue = 1.0f - this.blue; - this.alpha = 1.0f - this.alpha; - } - - /** - * This method clears the pixel values. - */ - public void clear() { - this.intensity = this.blue = this.red = this.green = this.alpha = 0.0f; - } - - /** - * This method adds the calues of the given pixel to the current pixel. - * - * @param pixel - * the pixel we add - */ - public void add(TexturePixel pixel) { - this.red += pixel.red; - this.green += pixel.green; - this.blue += pixel.blue; - this.alpha += pixel.alpha; - this.intensity += pixel.intensity; - } - - /** - * This method adds the calues of the given pixel to the current pixel. - * - * @param pixel - * the pixel we add - */ - public void add(ColorRGBA pixel) { - this.red += pixel.r; - this.green += pixel.g; - this.blue += pixel.b; - this.alpha += pixel.a; - } - - /** - * This method multiplies the values of the given pixel by the given value. - * - * @param value - * multiplication factor - */ - public void mult(float value) { - this.red *= value; - this.green *= value; - this.blue *= value; - this.alpha *= value; - this.intensity *= value; - } - - /** - * This method divides the values of the given pixel by the given value. - * ATTENTION! Beware of the zero value. This will cause you NaN's in the - * pixel values. - * - * @param value - * division factor - */ - public void divide(float value) { - this.red /= value; - this.green /= value; - this.blue /= value; - this.alpha /= value; - this.intensity /= value; - } - - /** - * This method clamps the pixel values to the given borders. - * - * @param min - * the minimum value - * @param max - * the maximum value - */ - public void clamp(float min, float max) { - this.red = FastMath.clamp(this.red, min, max); - this.green = FastMath.clamp(this.green, min, max); - this.blue = FastMath.clamp(this.blue, min, max); - this.alpha = FastMath.clamp(this.alpha, min, max); - this.intensity = FastMath.clamp(this.intensity, min, max); - } - - @Override - public Object clone() throws CloneNotSupportedException { - return super.clone(); - } - - @Override - public String toString() { - return "[" + red + ", " + green + ", " + blue + ", " + alpha + " {" + intensity + "}]"; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java deleted file mode 100644 index 9a48891098..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/TriangulatedTexture.java +++ /dev/null @@ -1,660 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import java.awt.Graphics2D; -import java.awt.RenderingHints; -import java.awt.geom.AffineTransform; -import java.awt.image.BufferedImage; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.TreeSet; - -import jme3tools.converters.ImageToAwt; - -import com.jme3.bounding.BoundingBox; -import com.jme3.math.FastMath; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.textures.blending.TextureBlender; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; - -/** - * This texture holds a set of images for each face in the specified mesh. It - * helps to flatten 3D texture, merge 3D and 2D textures and merge 2D textures - * with different UV coordinates. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class TriangulatedTexture extends Texture2D { - /** The result image format. */ - private Format format; - /** The collection of images for each face. */ - private Collection faceTextures; - /** - * The maximum texture size (width/height). This is taken from the blender - * key. - */ - private int maxTextureSize; - /** A variable that can prevent removing identical textures. */ - private boolean keepIdenticalTextures = false; - /** The result texture. */ - private Texture2D resultTexture; - /** The result texture's UV coordinates. */ - private List resultUVS; - - /** - * This method triangulates the given flat texture. The given texture is not - * changed. - * - * @param texture2d - * the texture to be triangulated - * @param uvs - * the UV coordinates for each face - */ - public TriangulatedTexture(Texture2D texture2d, List uvs, BlenderContext blenderContext) { - maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); - faceTextures = new TreeSet(new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o1.faceIndex - o2.faceIndex; - } - }); - int facesCount = uvs.size() / 3; - for (int i = 0; i < facesCount; ++i) { - faceTextures.add(new TriangleTextureElement(i, texture2d.getImage(), uvs, true, blenderContext)); - } - format = texture2d.getImage().getFormat(); - } - - /** - * Constructor that simply stores precalculated images. - * - * @param faceTextures - * a collection of images for the mesh's faces - * @param blenderContext - * the blender context - */ - public TriangulatedTexture(Collection faceTextures, BlenderContext blenderContext) { - maxTextureSize = blenderContext.getBlenderKey().getMaxTextureSize(); - this.faceTextures = faceTextures; - for (TriangleTextureElement faceTextureElement : faceTextures) { - if (format == null) { - format = faceTextureElement.image.getFormat(); - } else if (format != faceTextureElement.image.getFormat()) { - throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); - } - } - } - - /** - * This method blends the each image using the given blender and taking base - * texture into consideration. - * - * @param textureBlender - * the texture blender that holds the blending definition - * @param baseTexture - * the texture that is 'below' the current texture (can be null) - * @param blenderContext - * the blender context - */ - public void blend(TextureBlender textureBlender, TriangulatedTexture baseTexture, BlenderContext blenderContext) { - Format newFormat = null; - for (TriangleTextureElement triangleTextureElement : faceTextures) { - Image baseImage = baseTexture == null ? null : baseTexture.getFaceTextureElement(triangleTextureElement.faceIndex).image; - triangleTextureElement.image = textureBlender.blend(triangleTextureElement.image, baseImage, blenderContext); - if (newFormat == null) { - newFormat = triangleTextureElement.image.getFormat(); - } else if (newFormat != triangleTextureElement.image.getFormat()) { - throw new IllegalArgumentException("Face texture element images MUST have the same image format!"); - } - } - format = newFormat; - } - - /** - * This method alters the images to fit them into UV coordinates of the - * given target texture. - * - * @param targetTexture - * the texture to whose UV coordinates we fit current images - * @param blenderContext - * the blender context - */ - public void castToUVS(TriangulatedTexture targetTexture, BlenderContext blenderContext) { - int[] sourceSize = new int[2], targetSize = new int[2]; - ImageLoader imageLoader = new ImageLoader(); - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - for (TriangleTextureElement entry : faceTextures) { - TriangleTextureElement targetFaceTextureElement = targetTexture.getFaceTextureElement(entry.faceIndex); - Vector2f[] dest = targetFaceTextureElement.uv; - - // get the sizes of the source and target images - sourceSize[0] = entry.image.getWidth(); - sourceSize[1] = entry.image.getHeight(); - targetSize[0] = targetFaceTextureElement.image.getWidth(); - targetSize[1] = targetFaceTextureElement.image.getHeight(); - - // create triangle transformation - AffineTransform affineTransform = textureHelper.createAffineTransform(entry.uv, dest, sourceSize, targetSize); - - // compute the result texture - BufferedImage sourceImage = ImageToAwt.convert(entry.image, false, true, 0); - - BufferedImage targetImage = new BufferedImage(targetSize[0], targetSize[1], sourceImage.getType()); - Graphics2D g = targetImage.createGraphics(); - g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); - g.drawImage(sourceImage, affineTransform, null); - g.dispose(); - - Image output = imageLoader.load(targetImage, false); - entry.image = output; - entry.uv[0].set(dest[0]); - entry.uv[1].set(dest[1]); - entry.uv[2].set(dest[2]); - } - } - - /** - * This method returns the flat texture. It is calculated if required or if - * it was not created before. Images that are identical are discarded to - * reduce the texture size. - * - * @param rebuild - * a variable that forces texture recomputation (even if it was - * computed vefore) - * @return flat result texture (all images merged into one) - */ - public Texture2D getResultTexture(boolean rebuild) { - if (resultTexture == null || rebuild) { - // sorting the parts by their height (from highest to the lowest) - List list = new ArrayList(faceTextures); - Collections.sort(list, new Comparator() { - public int compare(TriangleTextureElement o1, TriangleTextureElement o2) { - return o2.image.getHeight() - o1.image.getHeight(); - } - }); - - // arraging the images on the resulting image (calculating the result image width and height) - Set duplicatedFaceIndexes = new HashSet(); - int resultImageHeight = list.get(0).image.getHeight(); - int resultImageWidth = 0; - int currentXPos = 0, currentYPos = 0; - Map imageLayoutData = new HashMap(list.size()); - while (list.size() > 0) { - TriangleTextureElement currentElement = list.remove(0); - if (currentXPos + currentElement.image.getWidth() > maxTextureSize) { - currentXPos = 0; - currentYPos = resultImageHeight; - resultImageHeight += currentElement.image.getHeight(); - } - Integer[] currentPositions = new Integer[] { currentXPos, currentYPos }; - imageLayoutData.put(currentElement, currentPositions); - - if (keepIdenticalTextures) {// removing identical images - for (int i = 0; i < list.size(); ++i) { - if (currentElement.image.equals(list.get(i).image)) { - duplicatedFaceIndexes.add(list.get(i).faceIndex); - imageLayoutData.put(list.remove(i--), currentPositions); - } - } - } - - currentXPos += currentElement.image.getWidth(); - resultImageWidth = Math.max(resultImageWidth, currentXPos); - // currentYPos += currentElement.image.getHeight(); - - // TODO: implement that to compact the result image - // try to add smaller images below the current one - // int remainingHeight = resultImageHeight - - // currentElement.image.getHeight(); - // while(remainingHeight > 0) { - // for(int i=list.size() - 1;i>=0;--i) { - // - // } - // } - } - - // computing the result UV coordinates - resultUVS = new ArrayList(imageLayoutData.size() * 3); - for (int i = 0; i < imageLayoutData.size() * 3; ++i) { - resultUVS.add(null); - } - Vector2f[] uvs = new Vector2f[3]; - for (Entry entry : imageLayoutData.entrySet()) { - Integer[] position = entry.getValue(); - entry.getKey().computeFinalUVCoordinates(resultImageWidth, resultImageHeight, position[0], position[1], uvs); - resultUVS.set(entry.getKey().faceIndex * 3, uvs[0]); - resultUVS.set(entry.getKey().faceIndex * 3 + 1, uvs[1]); - resultUVS.set(entry.getKey().faceIndex * 3 + 2, uvs[2]); - } - - Image resultImage = new Image(format, resultImageWidth, resultImageHeight, BufferUtils.createByteBuffer(resultImageWidth * resultImageHeight * (format.getBitsPerPixel() >> 3)), ColorSpace.Linear); - resultTexture = new Texture2D(resultImage); - for (Entry entry : imageLayoutData.entrySet()) { - if (!duplicatedFaceIndexes.contains(entry.getKey().faceIndex)) { - this.draw(resultImage, entry.getKey().image, entry.getValue()[0], entry.getValue()[1]); - } - } - - // setting additional data - resultTexture.setWrap(WrapAxis.S, this.getWrap(WrapAxis.S)); - resultTexture.setWrap(WrapAxis.T, this.getWrap(WrapAxis.T)); - resultTexture.setMagFilter(this.getMagFilter()); - resultTexture.setMinFilter(this.getMinFilter()); - } - return resultTexture; - } - - /** - * @return the result flat texture - */ - public Texture2D getResultTexture() { - return this.getResultTexture(false); - } - - /** - * @return the result texture's UV coordinates - */ - public List getResultUVS() { - this.getResultTexture();// this is called here to make sure that the result UVS are computed - return resultUVS; - } - - /** - * This method returns a single image element for the given face index. - * - * @param faceIndex - * the face index - * @return image element for the required face index - * @throws IllegalStateException - * this exception is thrown if the current image set does not - * contain an image for the given face index - */ - public TriangleTextureElement getFaceTextureElement(int faceIndex) { - for (TriangleTextureElement textureElement : faceTextures) { - if (textureElement.faceIndex == faceIndex) { - return textureElement; - } - } - throw new IllegalStateException("No face texture element found for index: " + faceIndex); - } - - /** - * @return the amount of texture faces - */ - public int getFaceTextureCount() { - return faceTextures.size(); - } - - /** - * Tells the object wheather to keep or reduce identical face textures. - * - * @param keepIdenticalTextures - * keeps or discards identical textures - */ - public void setKeepIdenticalTextures(boolean keepIdenticalTextures) { - this.keepIdenticalTextures = keepIdenticalTextures; - } - - /** - * This method draws the source image on the target image starting with the - * specified positions. - * - * @param target - * the target image - * @param source - * the source image - * @param targetXPos - * start X position on the target image - * @param targetYPos - * start Y position on the target image - */ - private void draw(Image target, Image source, int targetXPos, int targetYPos) { - PixelInputOutput sourceIO = PixelIOFactory.getPixelIO(source.getFormat()); - PixelInputOutput targetIO = PixelIOFactory.getPixelIO(target.getFormat()); - TexturePixel pixel = new TexturePixel(); - - for (int x = 0; x < source.getWidth(); ++x) { - for (int y = 0; y < source.getHeight(); ++y) { - sourceIO.read(source, 0, pixel, x, y); - targetIO.write(target, 0, pixel, targetXPos + x, targetYPos + y); - } - } - } - - /** - * A class that represents an image for a single face of the mesh. - * - * @author Marcin Roguski (Kaelthas) - */ - /* package */static class TriangleTextureElement { - /** The image for the face. */ - public Image image; - /** The UV coordinates for the image. */ - public final Vector2f[] uv; - /** The index of the face this image refers to. */ - public final int faceIndex; - - /** - * Constructor that creates the image element from the given texture and - * UV coordinates (it cuts out the smallest rectasngle possible from the - * given image that will hold the triangle defined by the given UV - * coordinates). After the image is cut out the UV coordinates are - * recalculated to be fit for the new image. - * - * @param faceIndex - * the index of mesh's face this image refers to - * @param sourceImage - * the source image - * @param uvCoordinates - * the UV coordinates that define the image - */ - public TriangleTextureElement(int faceIndex, Image sourceImage, List uvCoordinates, boolean wholeUVList, BlenderContext blenderContext) { - TextureHelper textureHelper = blenderContext.getHelper(TextureHelper.class); - this.faceIndex = faceIndex; - - uv = wholeUVList ? new Vector2f[] { uvCoordinates.get(faceIndex * 3).clone(), uvCoordinates.get(faceIndex * 3 + 1).clone(), uvCoordinates.get(faceIndex * 3 + 2).clone() } : new Vector2f[] { uvCoordinates.get(0).clone(), uvCoordinates.get(1).clone(), uvCoordinates.get(2).clone() }; - - // be careful here, floating point operations might cause the - // texture positions to be inapropriate - int[][] texturePosition = new int[3][2]; - for (int i = 0; i < texturePosition.length; ++i) { - texturePosition[i][0] = textureHelper.getPixelPosition(uv[i].x, sourceImage.getWidth()); - texturePosition[i][1] = textureHelper.getPixelPosition(uv[i].y, sourceImage.getHeight()); - } - - // calculating the extent of the texture - int minX = Integer.MAX_VALUE, minY = Integer.MAX_VALUE; - int maxX = Integer.MIN_VALUE, maxY = Integer.MIN_VALUE; - float minUVX = Float.MAX_VALUE, minUVY = Float.MAX_VALUE; - float maxUVX = Float.MIN_VALUE, maxUVY = Float.MIN_VALUE; - - for (int i = 0; i < texturePosition.length; ++i) { - minX = Math.min(texturePosition[i][0], minX); - minY = Math.min(texturePosition[i][1], minY); - - maxX = Math.max(texturePosition[i][0], maxX); - maxY = Math.max(texturePosition[i][1], maxY); - - minUVX = Math.min(uv[i].x, minUVX); - minUVY = Math.min(uv[i].y, minUVY); - maxUVX = Math.max(uv[i].x, maxUVX); - maxUVY = Math.max(uv[i].y, maxUVY); - } - int width = maxX - minX; - int height = maxY - minY; - - if (width == 0) { - width = 1; - } - if (height == 0) { - height = 1; - } - - // copy the pixel from the texture to the result image - PixelInputOutput pixelReader = PixelIOFactory.getPixelIO(sourceImage.getFormat()); - TexturePixel pixel = new TexturePixel(); - ByteBuffer data = BufferUtils.createByteBuffer(width * height * 4); - for (int y = minY; y < maxY; ++y) { - for (int x = minX; x < maxX; ++x) { - int xPos = x >= sourceImage.getWidth() ? x - sourceImage.getWidth() : x; - int yPos = y >= sourceImage.getHeight() ? y - sourceImage.getHeight() : y; - pixelReader.read(sourceImage, 0, pixel, xPos, yPos); - data.put(pixel.getR8()); - data.put(pixel.getG8()); - data.put(pixel.getB8()); - data.put(pixel.getA8()); - } - } - image = new Image(Format.RGBA8, width, height, data, ColorSpace.Linear); - - // modify the UV values so that they fit the new image - float heightUV = maxUVY - minUVY; - float widthUV = maxUVX - minUVX; - for (int i = 0; i < uv.length; ++i) { - // first translate it to the image borders - uv[i].x -= minUVX; - uv[i].y -= minUVY; - // then scale so that it fills the whole area - uv[i].x /= widthUV; - uv[i].y /= heightUV; - } - } - - /** - * Constructor that creates an image element from the 3D texture - * (generated texture). It computes a flat smallest rectangle that can - * hold a (3D) triangle defined by the given UV coordinates. Then it - * defines the image pixels for points in 3D space that define the - * calculated rectangle. - * - * @param faceIndex - * the face index this image refers to - * @param boundingBox - * the bounding box of the mesh - * @param texture - * the texture that allows to compute a pixel value in 3D - * space - * @param uv - * the UV coordinates of the mesh - * @param blenderContext - * the blender context - */ - public TriangleTextureElement(int faceIndex, BoundingBox boundingBox, GeneratedTexture texture, Vector3f[] uv, int[] uvIndices, BlenderContext blenderContext) { - this.faceIndex = faceIndex; - - // compute the face vertices from the UV coordinates - float width = boundingBox.getXExtent() * 2; - float height = boundingBox.getYExtent() * 2; - float depth = boundingBox.getZExtent() * 2; - - Vector3f min = boundingBox.getMin(null); - Vector3f v1 = min.add(uv[uvIndices[0]].x * width, uv[uvIndices[0]].y * height, uv[uvIndices[0]].z * depth); - Vector3f v2 = min.add(uv[uvIndices[1]].x * width, uv[uvIndices[1]].y * height, uv[uvIndices[1]].z * depth); - Vector3f v3 = min.add(uv[uvIndices[2]].x * width, uv[uvIndices[2]].y * height, uv[uvIndices[2]].z * depth); - - // get the rectangle envelope for the triangle - RectangleEnvelope envelope = this.getTriangleEnvelope(v1, v2, v3); - - // create the result image - Format imageFormat = texture.getImage().getFormat(); - int imageWidth = (int) (envelope.width * blenderContext.getBlenderKey().getGeneratedTexturePPU()); - if (imageWidth == 0) { - imageWidth = 1; - } - int imageHeight = (int) (envelope.height * blenderContext.getBlenderKey().getGeneratedTexturePPU()); - if (imageHeight == 0) { - imageHeight = 1; - } - ByteBuffer data = BufferUtils.createByteBuffer(imageWidth * imageHeight * (imageFormat.getBitsPerPixel() >> 3)); - image = new Image(texture.getImage().getFormat(), imageWidth, imageHeight, data, ColorSpace.Linear); - - // computing the pixels - PixelInputOutput pixelWriter = PixelIOFactory.getPixelIO(imageFormat); - TexturePixel pixel = new TexturePixel(); - float[] uvs = new float[3]; - Vector3f point = new Vector3f(envelope.min); - Vector3f vecY = new Vector3f(); - Vector3f wDelta = new Vector3f(envelope.w).multLocal(1.0f / imageWidth); - Vector3f hDelta = new Vector3f(envelope.h).multLocal(1.0f / imageHeight); - for (int x = 0; x < imageWidth; ++x) { - for (int y = 0; y < imageHeight; ++y) { - this.toTextureUV(boundingBox, point, uvs); - texture.getPixel(pixel, uvs[0], uvs[1], uvs[2]); - pixelWriter.write(image, 0, pixel, x, y); - point.addLocal(hDelta); - } - - vecY.addLocal(wDelta); - point.set(envelope.min).addLocal(vecY); - } - - // preparing UV coordinates for the flatted texture - this.uv = new Vector2f[3]; - this.uv[0] = new Vector2f(FastMath.clamp(v1.subtract(envelope.min).length(), 0, Float.MAX_VALUE) / envelope.height, 0); - Vector3f heightDropPoint = v2.subtract(envelope.w);// w is directed from the base to v2 - this.uv[1] = new Vector2f(1, heightDropPoint.subtractLocal(envelope.min).length() / envelope.height); - this.uv[2] = new Vector2f(0, 1); - } - - /** - * This method computes the final UV coordinates for the image (after it - * is combined with other images and drawed on the result image). - * - * @param totalImageWidth - * the result image width - * @param totalImageHeight - * the result image height - * @param xPos - * the most left x coordinate of the image - * @param yPos - * the most top y coordinate of the image - * @param result - * a vector where the result is stored - */ - public void computeFinalUVCoordinates(int totalImageWidth, int totalImageHeight, int xPos, int yPos, Vector2f[] result) { - for (int i = 0; i < 3; ++i) { - result[i] = new Vector2f(); - result[i].x = xPos / (float) totalImageWidth + uv[i].x * (image.getWidth() / (float) totalImageWidth); - result[i].y = yPos / (float) totalImageHeight + uv[i].y * (image.getHeight() / (float) totalImageHeight); - } - } - - /** - * This method converts the given point into 3D UV coordinates. - * - * @param boundingBox - * the bounding box of the mesh - * @param point - * the point to be transformed - * @param uvs - * the result UV coordinates - */ - private void toTextureUV(BoundingBox boundingBox, Vector3f point, float[] uvs) { - uvs[0] = (point.x - boundingBox.getCenter().x) / (boundingBox.getXExtent() == 0 ? 1 : boundingBox.getXExtent()); - uvs[1] = (point.y - boundingBox.getCenter().y) / (boundingBox.getYExtent() == 0 ? 1 : boundingBox.getYExtent()); - uvs[2] = (point.z - boundingBox.getCenter().z) / (boundingBox.getZExtent() == 0 ? 1 : boundingBox.getZExtent()); - // UVS cannot go outside <0, 1> range, but since we are generating texture for triangle envelope it might happen that - // some points of the envelope will exceet the bounding box of the mesh thus generating uvs outside the range - for (int i = 0; i < 3; ++i) { - uvs[i] = FastMath.clamp(uvs[i], 0, 1); - } - } - - /** - * This method returns an envelope of a minimal rectangle, that is set - * in 3D space, and contains the given triangle. - * - * @param triangle - * the triangle - * @return a rectangle minimum and maximum point and height and width - */ - private RectangleEnvelope getTriangleEnvelope(Vector3f v1, Vector3f v2, Vector3f v3) { - Vector3f h = v3.subtract(v1);// the height of the resulting rectangle - Vector3f temp = v2.subtract(v1); - - float field = 0.5f * h.cross(temp).length();// the field of the rectangle: Field = 0.5 * ||h x temp|| - if (field <= 0.0f) { - return new RectangleEnvelope(v1);// return single point envelope - } - - float cosAlpha = h.dot(temp) / (h.length() * temp.length());// the cosinus of angle betweenh and temp - - float triangleHeight = 2 * field / h.length();// the base of the height is the h vector - // now calculate the distance between v1 vertex and the point where - // the above calculated height 'touches' the base line (it can be - // settled outside the h vector) - float x = Math.abs((float) Math.sqrt(FastMath.clamp(temp.lengthSquared() - triangleHeight * triangleHeight, 0, Float.MAX_VALUE))) * Math.signum(cosAlpha); - // now get the height base point - Vector3f xPoint = v1.add(h.normalize().multLocal(x)); - - // get the minimum point of the envelope - Vector3f min = x < 0 ? xPoint : v1; - if (x < 0) { - h = v3.subtract(min); - } else if (x > h.length()) { - h = xPoint.subtract(min); - } - - Vector3f envelopeWidth = v2.subtract(xPoint); - return new RectangleEnvelope(min, envelopeWidth, h); - } - } - - /** - * A class that represents a flat rectangle in 3D space that is built on a - * triangle in 3D space. - * - * @author Marcin Roguski (Kaelthas) - */ - private static class RectangleEnvelope { - /** The minimum point of the rectangle. */ - public final Vector3f min; - /** The width vector. */ - public final Vector3f w; - /** The height vector. */ - public final Vector3f h; - /** The width of the rectangle. */ - public final float width; - /** The height of the rectangle. */ - public final float height; - - /** - * Constructs a rectangle that actually holds a point, not a triangle. - * This is a special case that is sometimes used when generating a - * texture where UV coordinates are defined by normals instead of - * vertices. - * - * @param pointPosition - * a position in 3D space - */ - public RectangleEnvelope(Vector3f pointPosition) { - min = pointPosition; - h = w = Vector3f.ZERO; - width = height = 1; - } - - /** - * Constructs a rectangle envelope. - * - * @param min - * the minimum rectangle point - * @param w - * the width vector - * @param h - * the height vector - */ - public RectangleEnvelope(Vector3f min, Vector3f w, Vector3f h) { - this.min = min; - this.h = h; - this.w = w; - width = w.length(); - height = h.length(); - } - - @Override - public String toString() { - return "Envelope[min = " + min + ", w = " + w + ", h = " + h + "]"; - } - } - - @Override - public Texture createSimpleClone() { - return null; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java deleted file mode 100644 index a922b2588f..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVCoordinatesGenerator.java +++ /dev/null @@ -1,420 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; -import com.jme3.bounding.BoundingVolume; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.plugins.blender.textures.UVProjectionGenerator.UVProjectionType; -import com.jme3.util.BufferUtils; - -/** - * This class is used for UV coordinates generation. - * - * @author Marcin Roguski (Kaelthas) - */ -public class UVCoordinatesGenerator { - private static final Logger LOGGER = Logger.getLogger(UVCoordinatesGenerator.class.getName()); - - public static enum UVCoordinatesType { - TEXCO_ORCO(1), TEXCO_REFL(2), TEXCO_NORM(4), TEXCO_GLOB(8), TEXCO_UV(16), TEXCO_OBJECT(32), TEXCO_LAVECTOR(64), TEXCO_VIEW(128), - TEXCO_STICKY(256), TEXCO_OSA(512), TEXCO_WINDOW(1024), NEED_UV(2048), TEXCO_TANGENT(4096), - TEXCO_PARTICLE_OR_STRAND(8192), //TEXCO_PARTICLE (since blender 2.6x) has also the value of: 8192 but is used for halo materials instead of normal materials - TEXCO_STRESS(16384), TEXCO_SPEED(32768); - - public final int blenderValue; - - private UVCoordinatesType(int blenderValue) { - this.blenderValue = blenderValue; - } - - public static UVCoordinatesType valueOf(int blenderValue) { - for (UVCoordinatesType coordinatesType : UVCoordinatesType.values()) { - if (coordinatesType.blenderValue == blenderValue) { - return coordinatesType; - } - } - return null; - } - } - - /** - * Generates a UV coordinates for 2D texture. - * - * @param mesh - * the mesh we generate UV's for - * @param texco - * UV coordinates type - * @param projection - * projection type - * @param geometries - * the geometris the given mesh belongs to (required to compute - * bounding box) - * @return UV coordinates for the given mesh - */ - public static List generateUVCoordinatesFor2DTexture(Mesh mesh, UVCoordinatesType texco, UVProjectionType projection, Geometry geometries) { - List result = new ArrayList(); - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); - float[] inputData = null;// positions, normals, reflection vectors, etc. - - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV:// this should be used if not defined by user explicitly - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - result.add(data[i % 3]); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } - - if (inputData != null) {// make projection calculations - switch (projection) { - case PROJECTION_FLAT: - inputData = UVProjectionGenerator.flatProjection(inputData, bb); - break; - case PROJECTION_CUBE: - inputData = UVProjectionGenerator.cubeProjection(inputData, bb); - break; - case PROJECTION_TUBE: - BoundingTube bt = UVCoordinatesGenerator.getBoundingTube(geometries); - inputData = UVProjectionGenerator.tubeProjection(inputData, bt); - break; - case PROJECTION_SPHERE: - BoundingSphere bs = UVCoordinatesGenerator.getBoundingSphere(geometries); - inputData = UVProjectionGenerator.sphereProjection(inputData, bs); - break; - default: - throw new IllegalStateException("Unknown projection type: " + projection); - } - for (int i = 0; i < inputData.length; i += 2) { - result.add(new Vector2f(inputData[i], inputData[i + 1])); - } - } - return result; - } - - /** - * Generates a UV coordinates for 3D texture. - * - * @param mesh - * the mesh we generate UV's for - * @param texco - * UV coordinates type - * @param coordinatesSwappingIndexes - * coordinates swapping indexes - * @param geometries - * the geometris the given mesh belongs to (required to compute - * bounding box) - * @return UV coordinates for the given mesh - */ - public static List generateUVCoordinatesFor3DTexture(Mesh mesh, UVCoordinatesType texco, int[] coordinatesSwappingIndexes, Geometry... geometries) { - List result = new ArrayList(); - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometries); - float[] inputData = null;// positions, normals, reflection vectors, etc. - - switch (texco) { - case TEXCO_ORCO: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Position)); - break; - case TEXCO_UV: - Vector2f[] data = new Vector2f[] { new Vector2f(0, 1), new Vector2f(0, 0), new Vector2f(1, 0) }; - for (int i = 0; i < mesh.getVertexCount(); ++i) { - Vector2f uv = data[i % 3]; - result.add(new Vector3f(uv.x, uv.y, 0)); - } - break; - case TEXCO_NORM: - inputData = BufferUtils.getFloatArray(mesh.getFloatBuffer(VertexBuffer.Type.Normal)); - break; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - LOGGER.warning("Texture coordinates type not currently supported: " + texco); - break; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } - - if (inputData != null) {// make calculations - Vector3f min = bb.getMin(null); - float[] uvCoordsResults = new float[4];// used for coordinates swapping - float[] ext = new float[] { bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2 }; - for (int i = 0; i < ext.length; ++i) { - if (ext[i] == 0) { - ext[i] = 1; - } - } - // now transform the coordinates so that they are in the range of - // <0; 1> - for (int i = 0; i < inputData.length; i += 3) { - uvCoordsResults[1] = (inputData[i] - min.x) / ext[0]; - uvCoordsResults[2] = (inputData[i + 1] - min.y) / ext[1]; - uvCoordsResults[3] = (inputData[i + 2] - min.z) / ext[2]; - result.add(new Vector3f(uvCoordsResults[coordinatesSwappingIndexes[0]], uvCoordsResults[coordinatesSwappingIndexes[1]], uvCoordsResults[coordinatesSwappingIndexes[2]])); - } - } - return result; - } - - /** - * This method should be used to determine if the texture will ever be - * computed. If the texture coordinates are not supported then the try of - * flattening the texture might result in runtime exceptions occurence. - * - * @param texco - * the texture coordinates type - * @return true if the type is supported and false otherwise - */ - public static boolean isTextureCoordinateTypeSupported(UVCoordinatesType texco) { - switch (texco) { - case TEXCO_ORCO: - case TEXCO_UV: - case TEXCO_NORM: - return true; - case TEXCO_REFL: - case TEXCO_GLOB: - case TEXCO_TANGENT: - case TEXCO_STRESS: - case TEXCO_LAVECTOR: - case TEXCO_OBJECT: - case TEXCO_OSA: - case TEXCO_PARTICLE_OR_STRAND: - case TEXCO_SPEED: - case TEXCO_STICKY: - case TEXCO_VIEW: - case TEXCO_WINDOW: - return false; - default: - throw new IllegalStateException("Unknown texture coordinates value: " + texco); - } - } - - /** - * This method returns the bounding box of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding box of the given geometries - */ - public static BoundingBox getBoundingBox(Geometry... geometries) { - BoundingBox result = null; - for (Geometry geometry : geometries) { - geometry.updateModelBound(); - BoundingVolume bv = geometry.getModelBound(); - if (bv instanceof BoundingBox) { - return (BoundingBox) bv; - } else if (bv instanceof BoundingSphere) { - BoundingSphere bs = (BoundingSphere) bv; - float r = bs.getRadius(); - return new BoundingBox(bs.getCenter(), r, r, r); - } else { - throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); - } - } - return result; - } - - /** - * This method returns the bounding sphere of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding sphere of the given geometries - */ - /* package */static BoundingSphere getBoundingSphere(Geometry... geometries) { - BoundingSphere result = null; - for (Geometry geometry : geometries) { - geometry.updateModelBound(); - BoundingVolume bv = geometry.getModelBound(); - if (bv instanceof BoundingBox) { - BoundingBox bb = (BoundingBox) bv; - float r = Math.max(bb.getXExtent(), bb.getYExtent()); - r = Math.max(r, bb.getZExtent()); - return new BoundingSphere(r, bb.getCenter()); - } else if (bv instanceof BoundingSphere) { - return (BoundingSphere) bv; - } else { - throw new IllegalStateException("Unknown bounding volume type: " + bv.getClass().getName()); - } - } - return result; - } - - /** - * This method returns the bounding tube of the given geometries. - * - * @param geometries - * the list of geometries - * @return bounding tube of the given geometries - */ - /* package */static BoundingTube getBoundingTube(Geometry... geometries) { - BoundingTube result = null; - for (Geometry geometry : geometries) { - BoundingBox bb = UVCoordinatesGenerator.getBoundingBox(geometry); - Vector3f max = bb.getMax(null); - Vector3f min = bb.getMin(null); - float radius = Math.max(max.x - min.x, max.y - min.y) * 0.5f; - - BoundingTube bt = new BoundingTube(radius, max.z - min.z, bb.getCenter()); - if (result == null) { - result = bt; - } else { - result.merge(bt); - } - } - return result; - } - - /** - * A very simple bounding tube. It holds only the basic data bout the - * bounding tube and does not provide full functionality of a - * BoundingVolume. Should be replaced with a bounding tube that extends the - * BoundingVolume if it is ever created. - * - * @author Marcin Roguski (Kaelthas) - */ - /* package */static class BoundingTube { - private float radius; - private float height; - private Vector3f center; - - /** - * Constructor creates the tube with the given params. - * - * @param radius - * the radius of the tube - * @param height - * the height of the tube - * @param center - * the center of the tube - */ - public BoundingTube(float radius, float height, Vector3f center) { - this.radius = radius; - this.height = height; - this.center = center; - } - - /** - * This method merges two bounding tubes. - * - * @param boundingTube - * bounding tube to be merged woth the current one - * @return new instance of bounding tube representing the tubes' merge - */ - public BoundingTube merge(BoundingTube boundingTube) { - // get tubes (tube1.radius >= tube2.radius) - BoundingTube tube1, tube2; - if (radius >= boundingTube.radius) { - tube1 = this; - tube2 = boundingTube; - } else { - tube1 = boundingTube; - tube2 = this; - } - float r1 = tube1.radius; - float r2 = tube2.radius; - - float minZ = Math.min(tube1.center.z - tube1.height * 0.5f, tube2.center.z - tube2.height * 0.5f); - float maxZ = Math.max(tube1.center.z + tube1.height * 0.5f, tube2.center.z + tube2.height * 0.5f); - float height = maxZ - minZ; - Vector3f distance = tube2.center.subtract(tube1.center); - Vector3f center = tube1.center.add(distance.mult(0.5f)); - distance.z = 0;// projecting this vector on XY plane - float d = distance.length(); - // d <= r1 - r2: tube2 is inside tube1 or touches tube1 from the - // inside - // d > r1 - r2: tube2 is outside or touches tube1 or crosses tube1 - float radius = d <= r1 - r2 ? tube1.radius : (d + r1 + r2) * 0.5f; - return new BoundingTube(radius, height, center); - } - - /** - * @return the radius of the tube - */ - public float getRadius() { - return radius; - } - - /** - * @return the height of the tube - */ - public float getHeight() { - return height; - } - - /** - * @return the center of the tube - */ - public Vector3f getCenter() { - return center; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java deleted file mode 100644 index a213bc6266..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UVProjectionGenerator.java +++ /dev/null @@ -1,240 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; -import com.jme3.math.FastMath; -import com.jme3.math.Triangle; -import com.jme3.math.Vector3f; -import com.jme3.scene.plugins.blender.textures.UVCoordinatesGenerator.BoundingTube; - -/** - * This class helps with projection calculations. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class UVProjectionGenerator { - /** - * 2D texture mapping (projection) - * @author Marcin Roguski (Kaelthas) - */ - public static enum UVProjectionType { - PROJECTION_FLAT(0), PROJECTION_CUBE(1), PROJECTION_TUBE(2), PROJECTION_SPHERE(3); - - public final int blenderValue; - - private UVProjectionType(int blenderValue) { - this.blenderValue = blenderValue; - } - - public static UVProjectionType valueOf(int blenderValue) { - for (UVProjectionType projectionType : UVProjectionType.values()) { - if (projectionType.blenderValue == blenderValue) { - return projectionType; - } - } - return null; - } - } - - /** - * Flat projection for 2D textures. - * - * @param mesh - * mesh that is to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] flatProjection(float[] positions, BoundingBox bb) { - Vector3f min = bb.getMin(null); - float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getZExtent() * 2.0f }; - float[] uvCoordinates = new float[positions.length / 3 * 2]; - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - uvCoordinates[j] = (positions[i] - min.x) / ext[0]; - // skip the Y-coordinate - uvCoordinates[j + 1] = (positions[i + 2] - min.z) / ext[1]; - } - return uvCoordinates; - } - - /** - * Cube projection for 2D textures. - * - * @param positions - * points to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] cubeProjection(float[] positions, BoundingBox bb) { - Triangle triangle = new Triangle(); - Vector3f x = new Vector3f(1, 0, 0); - Vector3f y = new Vector3f(0, 1, 0); - Vector3f z = new Vector3f(0, 0, 1); - Vector3f min = bb.getMin(null); - float[] ext = new float[] { bb.getXExtent() * 2.0f, bb.getYExtent() * 2.0f, bb.getZExtent() * 2.0f }; - - float[] uvCoordinates = new float[positions.length / 3 * 2]; - float borderAngle = (float) Math.sqrt(2.0f) / 2.0f; - for (int i = 0, pointIndex = 0; i < positions.length; i += 9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - Vector3f n = triangle.getNormal(); - float dotNX = Math.abs(n.dot(x)); - float dorNY = Math.abs(n.dot(y)); - float dotNZ = Math.abs(n.dot(z)); - if (dotNX > borderAngle) { - if (dotNZ < borderAngle) {// discard X-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; - } else {// discard Z-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - } - } else { - if (dorNY > borderAngle) {// discard Y-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().z - min.z) / ext[2]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().z - min.z) / ext[2]; - } else {// discard Z-coordinate - uvCoordinates[pointIndex++] = (triangle.get1().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get1().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get2().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get2().y - min.y) / ext[1]; - uvCoordinates[pointIndex++] = (triangle.get3().x - min.x) / ext[0]; - uvCoordinates[pointIndex++] = (triangle.get3().y - min.y) / ext[1]; - } - } - triangle.setNormal(null);// clear the previous normal vector - } - return uvCoordinates; - } - - /** - * Tube projection for 2D textures. - * - * @param positions - * points to be projected - * @param bt - * the bounding tube for projecting - * @return UV coordinates after the projection - */ - public static float[] tubeProjection(float[] positions, BoundingTube bt) { - float[] uvCoordinates = new float[positions.length / 3 * 2]; - Vector3f v = new Vector3f(); - float cx = bt.getCenter().x, cz = bt.getCenter().z; - Vector3f uBase = new Vector3f(0, 0, -1); - - float vBase = bt.getCenter().y - bt.getHeight() * 0.5f; - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - // calculating U - v.set(positions[i] - cx, 0, positions[i + 2] - cz); - v.normalizeLocal(); - float angle = v.angleBetween(uBase);// result between [0; PI] - if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then - angle = FastMath.TWO_PI - angle; - } - uvCoordinates[j] = angle / FastMath.TWO_PI; - - // calculating V - float y = positions[i + 1]; - uvCoordinates[j + 1] = (y - vBase) / bt.getHeight(); - } - - // looking for splitted triangles - Triangle triangle = new Triangle(); - for (int i = 0; i < positions.length; i += 9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - float sgn1 = Math.signum(triangle.get1().x - cx); - float sgn2 = Math.signum(triangle.get2().x - cx); - float sgn3 = Math.signum(triangle.get3().x - cx); - float xSideFactor = sgn1 + sgn2 + sgn3; - float ySideFactor = Math.signum(triangle.get1().z - cz) + Math.signum(triangle.get2().z - cz) + Math.signum(triangle.get3().z - cz); - if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane - if (sgn1 == 1.0f) { - uvCoordinates[i / 3 * 2] += 1.0f; - } - if (sgn2 == 1.0f) { - uvCoordinates[(i / 3 + 1) * 2] += 1.0f; - } - if (sgn3 == 1.0f) { - uvCoordinates[(i / 3 + 2) * 2] += 1.0f; - } - } - } - return uvCoordinates; - } - - /** - * Sphere projection for 2D textures. - * - * @param positions - * points to be projected - * @param bb - * the bounding box for projecting - * @return UV coordinates after the projection - */ - public static float[] sphereProjection(float[] positions, BoundingSphere bs) {// TODO: rotate it to be vertical - float[] uvCoordinates = new float[positions.length / 3 * 2]; - Vector3f v = new Vector3f(); - float cx = bs.getCenter().x, cy = bs.getCenter().y, cz = bs.getCenter().z; - Vector3f uBase = new Vector3f(0, -1, 0); - Vector3f vBase = new Vector3f(0, 0, -1); - - for (int i = 0, j = 0; i < positions.length; i += 3, j += 2) { - // calculating U - v.set(positions[i] - cx, positions[i + 1] - cy, 0); - v.normalizeLocal(); - float angle = v.angleBetween(uBase);// result between [0; PI] - if (v.x < 0) {// the angle should be greater than PI, we're on the other part of the image then - angle = FastMath.TWO_PI - angle; - } - uvCoordinates[j] = angle / FastMath.TWO_PI; - - // calculating V - v.set(positions[i] - cx, positions[i + 1] - cy, positions[i + 2] - cz); - v.normalizeLocal(); - angle = v.angleBetween(vBase);// result between [0; PI] - uvCoordinates[j + 1] = angle / FastMath.PI; - } - - // looking for splitted triangles - Triangle triangle = new Triangle(); - for (int i = 0; i < positions.length; i += 9) { - triangle.set(0, positions[i], positions[i + 1], positions[i + 2]); - triangle.set(1, positions[i + 3], positions[i + 4], positions[i + 5]); - triangle.set(2, positions[i + 6], positions[i + 7], positions[i + 8]); - float sgn1 = Math.signum(triangle.get1().x - cx); - float sgn2 = Math.signum(triangle.get2().x - cx); - float sgn3 = Math.signum(triangle.get3().x - cx); - float xSideFactor = sgn1 + sgn2 + sgn3; - float ySideFactor = Math.signum(triangle.get1().y - cy) + Math.signum(triangle.get2().y - cy) + Math.signum(triangle.get3().y - cy); - if ((xSideFactor > -3 || xSideFactor < 3) && ySideFactor < 0) {// the triangle is on the splitting plane - if (sgn1 == 1.0f) { - uvCoordinates[i / 3 * 2] += 1.0f; - } - if (sgn2 == 1.0f) { - uvCoordinates[(i / 3 + 1) * 2] += 1.0f; - } - if (sgn3 == 1.0f) { - uvCoordinates[(i / 3 + 2) * 2] += 1.0f; - } - } - } - return uvCoordinates; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java deleted file mode 100644 index ea276af56c..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/UserUVCollection.java +++ /dev/null @@ -1,86 +0,0 @@ -package com.jme3.scene.plugins.blender.textures; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import com.jme3.math.Vector2f; - -/** - * A collection of UV coordinates. The coords are stored in groups defined by the material index and their UV set name. - * - * @author Kaelthas (Marcin Roguski) - */ -public class UserUVCollection { - /** A map between material number and UV coordinates of mesh that has this material applied. */ - private Map>> uvCoordinates = new HashMap>>(); - /** A map between vertex index and its UV coordinates. */ - private Map> uvsMap = new HashMap>(); - - /** - * Adds a single UV coordinates for a specified vertex index. - * @param materialIndex - * the material index - * @param uvSetName - * the UV set name - * @param uv - * the added UV coordinates - * @param jmeVertexIndex - * the index of the vertex in result jme mesh - */ - public void addUV(int materialIndex, String uvSetName, Vector2f uv, int jmeVertexIndex) { - // first get all UV sets for the specified material ... - LinkedHashMap> uvsForMaterial = uvCoordinates.get(materialIndex); - if (uvsForMaterial == null) { - uvsForMaterial = new LinkedHashMap>(); - uvCoordinates.put(materialIndex, uvsForMaterial); - } - - // ... then fetch the UVS for the specified UV set name ... - List uvsForName = uvsForMaterial.get(uvSetName); - if (uvsForName == null) { - uvsForName = new ArrayList(); - uvsForMaterial.put(uvSetName, uvsForName); - } - - // ... add the UV coordinates to the proper list ... - uvsForName.add(uv); - - // ... and add the mapping of the UV coordinates to a vertex index for the specified UV set - Map uvToVertexIndexMapping = uvsMap.get(uvSetName); - if (uvToVertexIndexMapping == null) { - uvToVertexIndexMapping = new HashMap(); - uvsMap.put(uvSetName, uvToVertexIndexMapping); - } - uvToVertexIndexMapping.put(jmeVertexIndex, uv); - } - - /** - * @param uvSetName - * the name of the UV set - * @param vertexIndex - * the vertex index corresponds to the index in jme mesh and not the original one in blender - * @return a pre-existing coordinate vector - */ - public Vector2f getUVForVertex(String uvSetName, int vertexIndex) { - return uvsMap.get(uvSetName).get(vertexIndex); - } - - /** - * @param materialNumber - * the material number that is appied to the mesh - * @return UV coordinates of vertices that belong to the required mesh part - */ - public LinkedHashMap> getUVCoordinates(int materialNumber) { - return uvCoordinates.get(materialNumber); - } - - /** - * @return indicates if the mesh has UV coordinates - */ - public boolean hasUVCoordinates() { - return uvCoordinates.size() > 0; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java deleted file mode 100644 index 8ff55baac5..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/AbstractTextureBlender.java +++ /dev/null @@ -1,138 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.blending; - -import java.util.logging.Logger; - -import jme3tools.converters.MipMapGenerator; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.materials.MaterialHelper; -import com.jme3.texture.Image; - -/** - * An abstract class that contains the basic methods used by the classes that - * will derive from it. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */abstract class AbstractTextureBlender implements TextureBlender { - private static final Logger LOGGER = Logger.getLogger(AbstractTextureBlender.class.getName()); - - protected int flag; - protected boolean negateTexture; - protected int blendType; - protected float[] materialColor; - protected float[] color; - protected float blendFactor; - - public AbstractTextureBlender(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - this.flag = flag; - this.negateTexture = negateTexture; - this.blendType = blendType; - this.materialColor = materialColor; - this.color = color; - this.blendFactor = blendFactor; - } - - /** - * The method that performs the ramp blending. - * - * @param type - * the blend type - * @param materialRGB - * the rgb value of the material, here the result is stored too - * @param fac - * color affection factor - * @param pixelColor - * the texture color - * @param blenderContext - * the blender context - */ - protected void blendHSV(int type, float[] materialRGB, float fac, float[] pixelColor, BlenderContext blenderContext) { - float oneMinusFactor = 1.0f - fac; - MaterialHelper materialHelper = blenderContext.getHelper(MaterialHelper.class); - - switch (type) { - case MTEX_BLEND_HUE: {// FIXME: not working well for image textures (works fine for generated textures) - float[] colorTransformResult = new float[3]; - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); - if (colorTransformResult[0] != 0.0f) { - float colH = colorTransformResult[0]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); - materialHelper.hsvToRgb(colH, colorTransformResult[1], colorTransformResult[2], colorTransformResult); - materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * colorTransformResult[0]; - materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * colorTransformResult[1]; - materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * colorTransformResult[2]; - } - break; - } - case MTEX_BLEND_SAT: { - float[] colorTransformResult = new float[3]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], colorTransformResult); - float h = colorTransformResult[0]; - float s = colorTransformResult[1]; - float v = colorTransformResult[2]; - if (s != 0.0f) { - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colorTransformResult); - materialHelper.hsvToRgb(h, oneMinusFactor * s + fac * colorTransformResult[1], v, materialRGB); - } - break; - } - case MTEX_BLEND_VAL: { - float[] rgbToHsv = new float[3]; - float[] colToHsv = new float[3]; - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); - materialHelper.hsvToRgb(rgbToHsv[0], rgbToHsv[1], oneMinusFactor * rgbToHsv[2] + fac * colToHsv[2], materialRGB); - break; - } - case MTEX_BLEND_COLOR: {// FIXME: not working well for image textures (works fine for generated textures) - float[] rgbToHsv = new float[3]; - float[] colToHsv = new float[3]; - materialHelper.rgbToHsv(pixelColor[0], pixelColor[1], pixelColor[2], colToHsv); - if (colToHsv[2] != 0) { - materialHelper.rgbToHsv(materialRGB[0], materialRGB[1], materialRGB[2], rgbToHsv); - materialHelper.hsvToRgb(colToHsv[0], colToHsv[1], rgbToHsv[2], rgbToHsv); - materialRGB[0] = oneMinusFactor * materialRGB[0] + fac * rgbToHsv[0]; - materialRGB[1] = oneMinusFactor * materialRGB[1] + fac * rgbToHsv[1]; - materialRGB[2] = oneMinusFactor * materialRGB[2] + fac * rgbToHsv[2]; - } - break; - } - default: - throw new IllegalStateException("Unknown ramp type: " + type); - } - } - - public void copyBlendingData(TextureBlender textureBlender) { - if (textureBlender instanceof AbstractTextureBlender) { - flag = ((AbstractTextureBlender) textureBlender).flag; - negateTexture = ((AbstractTextureBlender) textureBlender).negateTexture; - blendType = ((AbstractTextureBlender) textureBlender).blendType; - materialColor = ((AbstractTextureBlender) textureBlender).materialColor.clone(); - color = ((AbstractTextureBlender) textureBlender).color.clone(); - blendFactor = ((AbstractTextureBlender) textureBlender).blendFactor; - } else { - LOGGER.warning("Cannot copy blending data from other types than " + this.getClass()); - } - } - - /** - * The method prepares images for blending. It generates mipmaps if one of - * the images has them defined and the other one has not. - * - * @param target - * the image where the blending result is stored - * @param source - * the image that is being read only - */ - protected void prepareImagesForBlending(Image target, Image source) { - LOGGER.fine("Generating mipmaps if needed!"); - boolean targetHasMipmaps = target == null ? false : target.getMipMapSizes() != null && target.getMipMapSizes().length > 0; - boolean sourceHasMipmaps = source == null ? false : source.getMipMapSizes() != null && source.getMipMapSizes().length > 0; - if (target != null && !targetHasMipmaps && sourceHasMipmaps) { - MipMapGenerator.generateMipMaps(target); - } else if (source != null && !sourceHasMipmaps && targetHasMipmaps) { - MipMapGenerator.generateMipMaps(source); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java deleted file mode 100644 index 7ba2b98f24..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlender.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.blending; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.texture.Image; - -/** - * An interface for texture blending classes (the classes that mix the texture - * pixels with the material colors). - * - * @author Marcin Roguski (Kaelthas) - */ -public interface TextureBlender { - // types of blending - int MTEX_BLEND = 0; - int MTEX_MUL = 1; - int MTEX_ADD = 2; - int MTEX_SUB = 3; - int MTEX_DIV = 4; - int MTEX_DARK = 5; - int MTEX_DIFF = 6; - int MTEX_LIGHT = 7; - int MTEX_SCREEN = 8; - int MTEX_OVERLAY = 9; - int MTEX_BLEND_HUE = 10; - int MTEX_BLEND_SAT = 11; - int MTEX_BLEND_VAL = 12; - int MTEX_BLEND_COLOR = 13; - int MTEX_NUM_BLENDTYPES = 14; - - /** - * This method blends the given texture with material color and the defined - * color in 'map to' panel. As a result of this method a new texture is - * created. The input texture is NOT. - * - * @param image - * the image we use in blending - * @param baseImage - * the texture that is underneath the current texture (its pixels - * will be used instead of material color) - * @param blenderContext - * the blender context - * @return new image that was created after the blending - */ - Image blend(Image image, Image baseImage, BlenderContext blenderContext); - - /** - * Copies blending data. Used for blending type format changing. - * - * @param textureBlender - * the blend data that should be copied - */ - void copyBlendingData(TextureBlender textureBlender); -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java deleted file mode 100644 index 41e3e9475e..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderAWT.java +++ /dev/null @@ -1,275 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.blending; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; - -import java.nio.ByteBuffer; -import java.util.ArrayList; - -/** - *

    - * The class that is responsible for blending the following texture types: - *

    - *
      - *
    • RGBA8
    • - *
    • ABGR8
    • - *
    • BGR8
    • - *
    • RGB8
    • - *
    - * - *

    - * Not yet supported (but will be): - *

    - *
      - *
    • ARGB4444
    • - *
    • RGB10
    • - *
    • RGB111110F
    • - *
    • RGB16
    • - *
    • RGB16F
    • - *
    • RGB16F_to_RGB111110F
    • - *
    • RGB16F_to_RGB9E5
    • - *
    • RGB32F
    • - *
    • RGB565
    • - *
    • RGB5A1
    • - *
    • RGB9E5
    • - *
    • RGBA16
    • - *
    • RGBA16F
    • - *
    - * - * @author Marcin Roguski (Kaelthas) - */ -public class TextureBlenderAWT extends AbstractTextureBlender { - public TextureBlenderAWT(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - @Override - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - float[] pixelColor = new float[] { color[0], color[1], color[2], 1.0f }; - Format format = image.getFormat(); - - PixelInputOutput basePixelIO = null, pixelReader = PixelIOFactory.getPixelIO(format); - TexturePixel basePixel = null, pixel = new TexturePixel(); - float[] materialColor = this.materialColor; - if (baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - materialColor = new float[this.materialColor.length]; - basePixel = new TexturePixel(); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - int bytesPerPixel = image.getFormat().getBitsPerPixel() >> 3; - ArrayList dataArray = new ArrayList(depth); - - float[] resultPixel = new float[4]; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - int imagePixelCount = data.limit() / bytesPerPixel; - ByteBuffer newData = BufferUtils.createByteBuffer(imagePixelCount * 4); - - int dataIndex = 0, x = 0, y = 0, index = 0; - while (index < data.limit()) { - // getting the proper material color if the base texture is applied - if (basePixelIO != null) { - basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); - basePixel.toRGBA(materialColor); - ++x; - if (x >= width) { - x = 0; - ++y; - } - } - - // reading the current texture's pixel - pixelReader.read(image, dataLayerIndex, pixel, index); - index += bytesPerPixel; - pixel.toRGBA(pixelColor); - if (negateTexture) { - pixel.negate(); - } - - this.blendPixel(resultPixel, materialColor, pixelColor, blenderContext); - newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); - newData.put(dataIndex++, (byte) (pixelColor[3] * 255.0f)); - } - dataArray.add(newData); - } - - ColorSpace colorSpace; - if (baseImage != null) { - colorSpace = baseImage.getColorSpace() != null ? baseImage.getColorSpace() : ColorSpace.Linear; - } else { - colorSpace = image.getColorSpace(); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, colorSpace) : new Image(Format.RGBA8, width, height, dataArray.get(0), colorSpace); - if (image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } - - /** - * This method blends the single pixel depending on the blending type. - * - * @param result - * the result pixel - * @param materialColor - * the material color - * @param pixelColor - * the pixel color - * @param blenderContext - * the blender context - */ - protected void blendPixel(float[] result, float[] materialColor, float[] pixelColor, BlenderContext blenderContext) { - // We calculate first the importance of the texture (colFactor * texAlphaValue) - float blendFactor = this.blendFactor * pixelColor[3]; - // Then, we get the object material factor ((1 - texture importance) * matAlphaValue) - float oneMinusFactor = (1f - blendFactor) * materialColor[3]; - // Finally, we can get the final blendFactor, which is 1 - the material factor. - blendFactor = 1f - oneMinusFactor; - - // --- Compact method --- - // float blendFactor = this.blendFactor * (1f - ((1f - pixelColor[3]) * materialColor[3])); - // float oneMinusFactor = 1f - blendFactor; - - float col; - - switch (blendType) { - case MTEX_BLEND: - result[0] = blendFactor * pixelColor[0] + oneMinusFactor * materialColor[0]; - result[1] = blendFactor * pixelColor[1] + oneMinusFactor * materialColor[1]; - result[2] = blendFactor * pixelColor[2] + oneMinusFactor * materialColor[2]; - break; - case MTEX_MUL: - result[0] = (oneMinusFactor + blendFactor * materialColor[0]) * pixelColor[0]; - result[1] = (oneMinusFactor + blendFactor * materialColor[1]) * pixelColor[1]; - result[2] = (oneMinusFactor + blendFactor * materialColor[2]) * pixelColor[2]; - break; - case MTEX_DIV: - if (pixelColor[0] != 0.0) { - result[0] = (oneMinusFactor * materialColor[0] + blendFactor * materialColor[0] / pixelColor[0]) * 0.5f; - } - if (pixelColor[1] != 0.0) { - result[1] = (oneMinusFactor * materialColor[1] + blendFactor * materialColor[1] / pixelColor[1]) * 0.5f; - } - if (pixelColor[2] != 0.0) { - result[2] = (oneMinusFactor * materialColor[2] + blendFactor * materialColor[2] / pixelColor[2]) * 0.5f; - } - break; - case MTEX_SCREEN: - result[0] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); - result[1] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); - result[2] = 1.0f - (oneMinusFactor + blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); - break; - case MTEX_OVERLAY: - if (materialColor[0] < 0.5f) { - result[0] = pixelColor[0] * (oneMinusFactor + 2.0f * blendFactor * materialColor[0]); - } else { - result[0] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[0])) * (1.0f - pixelColor[0]); - } - if (materialColor[1] < 0.5f) { - result[1] = pixelColor[1] * (oneMinusFactor + 2.0f * blendFactor * materialColor[1]); - } else { - result[1] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[1])) * (1.0f - pixelColor[1]); - } - if (materialColor[2] < 0.5f) { - result[2] = pixelColor[2] * (oneMinusFactor + 2.0f * blendFactor * materialColor[2]); - } else { - result[2] = 1.0f - (oneMinusFactor + 2.0f * blendFactor * (1.0f - materialColor[2])) * (1.0f - pixelColor[2]); - } - break; - case MTEX_SUB: - result[0] = materialColor[0] - blendFactor * pixelColor[0]; - result[1] = materialColor[1] - blendFactor * pixelColor[1]; - result[2] = materialColor[2] - blendFactor * pixelColor[2]; - result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); - result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); - result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); - break; - case MTEX_ADD: - result[0] = (blendFactor * pixelColor[0] + materialColor[0]) * 0.5f; - result[1] = (blendFactor * pixelColor[1] + materialColor[1]) * 0.5f; - result[2] = (blendFactor * pixelColor[2] + materialColor[2]) * 0.5f; - break; - case MTEX_DIFF: - result[0] = oneMinusFactor * materialColor[0] + blendFactor * Math.abs(materialColor[0] - pixelColor[0]); - result[1] = oneMinusFactor * materialColor[1] + blendFactor * Math.abs(materialColor[1] - pixelColor[1]); - result[2] = oneMinusFactor * materialColor[2] + blendFactor * Math.abs(materialColor[2] - pixelColor[2]); - break; - case MTEX_DARK: - col = blendFactor * pixelColor[0]; - result[0] = col < materialColor[0] ? col : materialColor[0]; - col = blendFactor * pixelColor[1]; - result[1] = col < materialColor[1] ? col : materialColor[1]; - col = blendFactor * pixelColor[2]; - result[2] = col < materialColor[2] ? col : materialColor[2]; - break; - case MTEX_LIGHT: - col = blendFactor * pixelColor[0]; - result[0] = col > materialColor[0] ? col : materialColor[0]; - col = blendFactor * pixelColor[1]; - result[1] = col > materialColor[1] ? col : materialColor[1]; - col = blendFactor * pixelColor[2]; - result[2] = col > materialColor[2] ? col : materialColor[2]; - break; - case MTEX_BLEND_HUE: - case MTEX_BLEND_SAT: - case MTEX_BLEND_VAL: - case MTEX_BLEND_COLOR: - System.arraycopy(materialColor, 0, result, 0, 3); - this.blendHSV(blendType, result, blendFactor, pixelColor, blenderContext); - break; - default: - throw new IllegalStateException("Unknown blend type: " + blendType); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java deleted file mode 100644 index b40e2cc8fd..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderDDS.java +++ /dev/null @@ -1,131 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.blending; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; - -import java.nio.ByteBuffer; -import java.util.ArrayList; - -import jme3tools.converters.RGB565; - -/** - *

    - * The class that is responsible for blending the following texture types: - *

    - *
      - *
    • DXT1
    • - *
    • DXT1A
    • - *
    • DXT3
    • - *
    • DXT5
    • - *
    - * @author Marcin Roguski (Kaelthas) - */ -public class TextureBlenderDDS extends TextureBlenderAWT { - public TextureBlenderDDS(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - @Override - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - Format format = image.getFormat(); - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - - PixelInputOutput basePixelIO = null; - float[][] compressedMaterialColor = null; - TexturePixel[] baseTextureColors = null; - if (baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - compressedMaterialColor = new float[2][4]; - baseTextureColors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; - } - - float[] resultPixel = new float[4]; - float[] pixelColor = new float[4]; - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel() }; - int baseXTexelIndex = 0, baseYTexelIndex = 0; - float[] alphas = new float[] { 1, 1 }; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - ByteBuffer newData = BufferUtils.createByteBuffer(data.remaining()); - while (data.hasRemaining()) { - if (format == Format.DXT3) { - long alpha = data.getLong(); - // get alpha for first and last pixel that is compressed in the texel - byte alpha0 = (byte) (alpha << 4 & 0xFF); - byte alpha1 = (byte) (alpha >> 60 & 0xFF); - alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - alphas[1] = alpha1 >= 0 ? alpha1 / 255.0f : 1.0f - ~alpha1 / 255.0f; - newData.putLong(alpha); - } else if (format == Format.DXT5) { - byte alpha0 = data.get(); - byte alpha1 = data.get(); - alphas[0] = alpha0 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - alphas[1] = alpha1 >= 0 ? alpha0 / 255.0f : 1.0f - ~alpha0 / 255.0f; - newData.put(alpha0); - newData.put(alpha1); - // only read the next 6 bytes (these are alpha indexes) - newData.putInt(data.getInt()); - newData.putShort(data.getShort()); - } - int col0 = RGB565.RGB565_to_ARGB8(data.getShort()); - int col1 = RGB565.RGB565_to_ARGB8(data.getShort()); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // compressing 16 pixels from the base texture as if they belonged to a texel - if (baseImage != null) { - // reading pixels (first and last of the 16 colors array) - basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[0], baseXTexelIndex << 2, baseYTexelIndex << 2);// first pixel - basePixelIO.read(baseImage, dataLayerIndex, baseTextureColors[1], baseXTexelIndex << 2 + 4, baseYTexelIndex << 2 + 4);// last pixel - baseTextureColors[0].toRGBA(compressedMaterialColor[0]); - baseTextureColors[1].toRGBA(compressedMaterialColor[1]); - } - - // blending colors - for (int i = 0; i < colors.length; ++i) { - if (negateTexture) { - colors[i].negate(); - } - colors[i].toRGBA(pixelColor); - pixelColor[3] = alphas[i]; - this.blendPixel(resultPixel, compressedMaterialColor != null ? compressedMaterialColor[i] : materialColor, pixelColor, blenderContext); - colors[i].fromARGB(1, resultPixel[0], resultPixel[1], resultPixel[2]); - int argb8 = colors[i].toARGB8(); - short rgb565 = RGB565.ARGB8_to_RGB565(argb8); - newData.putShort(rgb565); - } - - // just copy the remaining 4 bytes of the current texel - newData.putInt(data.getInt()); - - ++baseXTexelIndex; - if (baseXTexelIndex > image.getWidth() >> 2) { - baseXTexelIndex = 0; - ++baseYTexelIndex; - } - } - dataArray.add(newData); - } - - Image result = dataArray.size() > 1 ? new Image(format, width, height, depth, dataArray, ColorSpace.Linear) : new Image(format, width, height, dataArray.get(0), ColorSpace.Linear); - if (image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java deleted file mode 100644 index f487e240d3..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderFactory.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.blending; - -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; - -/** - * This class creates the texture blending class depending on the texture type. - * - * @author Marcin Roguski (Kaelthas) - */ -public class TextureBlenderFactory { - private static final Logger LOGGER = Logger.getLogger(TextureBlenderFactory.class.getName()); - - /** - * A blender that does not change the image. Used for none supported image types. - */ - private static final TextureBlender NON_CHANGING_BLENDER = new TextureBlender() { - @Override - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - return image; - } - - @Override - public void copyBlendingData(TextureBlender textureBlender) { } - }; - - /** - * This method creates the blending class. - * - * @param format - * the texture format - * @return texture blending class - */ - public static TextureBlender createTextureBlender(Format format, int flag, boolean negate, int blendType, float[] materialColor, float[] color, float colfac) { - switch (format) { - case Luminance8: - case Luminance8Alpha8: - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - return new TextureBlenderLuminance(flag, negate, blendType, materialColor, color, colfac); - case RGBA8: - case ABGR8: - case BGR8: - case RGB8: - case RGB111110F: - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - case RGB32F: - case RGB565: - case RGB5A1: - case RGB9E5: - case RGBA16F: - case RGBA32F: - return new TextureBlenderAWT(flag, negate, blendType, materialColor, color, colfac); - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - return new TextureBlenderDDS(flag, negate, blendType, materialColor, color, colfac); - default: - LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}. Returning a blender that does not change the texture.", format); - return NON_CHANGING_BLENDER; - } - } - - /** - * This method changes the image format in the texture blender. - * - * @param format - * the new image format - * @param textureBlender - * the texture blender that will be altered - * @return altered texture blender - */ - public static TextureBlender alterTextureType(Format format, TextureBlender textureBlender) { - TextureBlender result = TextureBlenderFactory.createTextureBlender(format, 0, false, 0, null, null, 0); - result.copyBlendingData(textureBlender); - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java deleted file mode 100644 index 1f2ae01e18..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/blending/TextureBlenderLuminance.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.blending; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.io.PixelIOFactory; -import com.jme3.scene.plugins.blender.textures.io.PixelInputOutput; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; -import com.jme3.util.BufferUtils; - -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - *

    - * The class that is responsible for blending the following texture types: - *

    - *
      - *
    • Luminance8
    • - *
    • Luminance8Alpha8
    • - *
    - *

    - * Not yet supported (but will be): - *

    - *
      - *
    • Luminance16
    • - *
    • Luminance16Alpha16
    • - *
    • Luminance16F
    • - *
    • Luminance16FAlpha16F
    • - *
    • Luminance32F
    • - *
    - * - * @author Marcin Roguski (Kaelthas) - */ -public class TextureBlenderLuminance extends AbstractTextureBlender { - private static final Logger LOGGER = Logger.getLogger(TextureBlenderLuminance.class.getName()); - - public TextureBlenderLuminance(int flag, boolean negateTexture, int blendType, float[] materialColor, float[] color, float blendFactor) { - super(flag, negateTexture, blendType, materialColor, color, blendFactor); - } - - public Image blend(Image image, Image baseImage, BlenderContext blenderContext) { - this.prepareImagesForBlending(image, baseImage); - - Format format = image.getFormat(); - PixelInputOutput basePixelIO = null; - TexturePixel basePixel = null; - float[] materialColor = this.materialColor; - if (baseImage != null) { - basePixelIO = PixelIOFactory.getPixelIO(baseImage.getFormat()); - materialColor = new float[this.materialColor.length]; - basePixel = new TexturePixel(); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - if (depth == 0) { - depth = 1; - } - ArrayList dataArray = new ArrayList(depth); - - float[] resultPixel = new float[4]; - float[] tinAndAlpha = new float[2]; - for (int dataLayerIndex = 0; dataLayerIndex < depth; ++dataLayerIndex) { - ByteBuffer data = image.getData(dataLayerIndex); - data.rewind(); - ByteBuffer newData = BufferUtils.createByteBuffer(data.limit() * 4); - - int dataIndex = 0, x = 0, y = 0; - while (data.hasRemaining()) { - // getting the proper material color if the base texture is applied - if (basePixelIO != null) { - basePixelIO.read(baseImage, dataLayerIndex, basePixel, x, y); - basePixel.toRGBA(materialColor); - - ++x; - if (x >= width) { - x = 0; - ++y; - } - } - - this.getTinAndAlpha(data, format, negateTexture, tinAndAlpha); - this.blendPixel(resultPixel, materialColor, color, tinAndAlpha[0], blendFactor, blendType, blenderContext); - newData.put(dataIndex++, (byte) (resultPixel[0] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[1] * 255.0f)); - newData.put(dataIndex++, (byte) (resultPixel[2] * 255.0f)); - newData.put(dataIndex++, (byte) (tinAndAlpha[1] * 255.0f)); - } - dataArray.add(newData); - } - - Image result = depth > 1 ? new Image(Format.RGBA8, width, height, depth, dataArray, ColorSpace.Linear) : new Image(Format.RGBA8, width, height, dataArray.get(0), ColorSpace.Linear); - if (image.getMipMapSizes() != null) { - result.setMipMapSizes(image.getMipMapSizes().clone()); - } - return result; - } - - /** - * This method return texture intensity and alpha value. - * - * @param data - * the texture data - * @param imageFormat - * the image format - * @param neg - * indicates if the texture is negated - * @param result - * the table (2 elements) where the result is being stored - */ - protected void getTinAndAlpha(ByteBuffer data, Format imageFormat, boolean neg, float[] result) { - byte pixelValue = data.get();// at least one byte is always taken - float firstPixelValue = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - switch (imageFormat) { - case Luminance8: - result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; - result[1] = 1.0f; - break; - case Luminance8Alpha8: - result[0] = neg ? 1.0f - firstPixelValue : firstPixelValue; - pixelValue = data.get(); - result[1] = pixelValue >= 0 ? pixelValue / 255.0f : 1.0f - ~pixelValue / 255.0f; - break; - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - LOGGER.log(Level.WARNING, "Image type not yet supported for blending: {0}", imageFormat); - break; - default: - throw new IllegalStateException("Invalid image format type for Luminance texture blender: " + imageFormat); - } - } - - /** - * This method blends the texture with an appropriate color. - * - * @param result - * the result color (variable 'in' in blender source code) - * @param materialColor - * the texture color (variable 'out' in blender source coude) - * @param color - * the previous color (variable 'tex' in blender source code) - * @param textureIntensity - * texture intensity (variable 'fact' in blender source code) - * @param textureFactor - * texture affection factor (variable 'facg' in blender source - * code) - * @param blendtype - * the blend type - * @param blenderContext - * the blender context - */ - protected void blendPixel(float[] result, float[] materialColor, float[] color, float textureIntensity, float textureFactor, int blendtype, BlenderContext blenderContext) { - float oneMinusFactor, col; - textureIntensity *= textureFactor; - - switch (blendtype) { - case MTEX_BLEND: - oneMinusFactor = 1.0f - textureIntensity; - result[0] = textureIntensity * color[0] + oneMinusFactor * materialColor[0]; - result[1] = textureIntensity * color[1] + oneMinusFactor * materialColor[1]; - result[2] = textureIntensity * color[2] + oneMinusFactor * materialColor[2]; - break; - case MTEX_MUL: - oneMinusFactor = 1.0f - textureFactor; - result[0] = (oneMinusFactor + textureIntensity * materialColor[0]) * color[0]; - result[1] = (oneMinusFactor + textureIntensity * materialColor[1]) * color[1]; - result[2] = (oneMinusFactor + textureIntensity * materialColor[2]) * color[2]; - break; - case MTEX_DIV: - oneMinusFactor = 1.0f - textureIntensity; - if (color[0] != 0.0) { - result[0] = (oneMinusFactor * materialColor[0] + textureIntensity * materialColor[0] / color[0]) * 0.5f; - } - if (color[1] != 0.0) { - result[1] = (oneMinusFactor * materialColor[1] + textureIntensity * materialColor[1] / color[1]) * 0.5f; - } - if (color[2] != 0.0) { - result[2] = (oneMinusFactor * materialColor[2] + textureIntensity * materialColor[2] / color[2]) * 0.5f; - } - break; - case MTEX_SCREEN: - oneMinusFactor = 1.0f - textureFactor; - result[0] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); - result[1] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); - result[2] = 1.0f - (oneMinusFactor + textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); - break; - case MTEX_OVERLAY: - oneMinusFactor = 1.0f - textureFactor; - if (materialColor[0] < 0.5f) { - result[0] = color[0] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[0]); - } else { - result[0] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[0])) * (1.0f - color[0]); - } - if (materialColor[1] < 0.5f) { - result[1] = color[1] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[1]); - } else { - result[1] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[1])) * (1.0f - color[1]); - } - if (materialColor[2] < 0.5f) { - result[2] = color[2] * (oneMinusFactor + 2.0f * textureIntensity * materialColor[2]); - } else { - result[2] = 1.0f - (oneMinusFactor + 2.0f * textureIntensity * (1.0f - materialColor[2])) * (1.0f - color[2]); - } - break; - case MTEX_SUB: - result[0] = materialColor[0] - textureIntensity * color[0]; - result[1] = materialColor[1] - textureIntensity * color[1]; - result[2] = materialColor[2] - textureIntensity * color[2]; - result[0] = FastMath.clamp(result[0], 0.0f, 1.0f); - result[1] = FastMath.clamp(result[1], 0.0f, 1.0f); - result[2] = FastMath.clamp(result[2], 0.0f, 1.0f); - break; - case MTEX_ADD: - result[0] = (textureIntensity * color[0] + materialColor[0]) * 0.5f; - result[1] = (textureIntensity * color[1] + materialColor[1]) * 0.5f; - result[2] = (textureIntensity * color[2] + materialColor[2]) * 0.5f; - break; - case MTEX_DIFF: - oneMinusFactor = 1.0f - textureIntensity; - result[0] = oneMinusFactor * materialColor[0] + textureIntensity * Math.abs(materialColor[0] - color[0]); - result[1] = oneMinusFactor * materialColor[1] + textureIntensity * Math.abs(materialColor[1] - color[1]); - result[2] = oneMinusFactor * materialColor[2] + textureIntensity * Math.abs(materialColor[2] - color[2]); - break; - case MTEX_DARK: - col = textureIntensity * color[0]; - result[0] = col < materialColor[0] ? col : materialColor[0]; - col = textureIntensity * color[1]; - result[1] = col < materialColor[1] ? col : materialColor[1]; - col = textureIntensity * color[2]; - result[2] = col < materialColor[2] ? col : materialColor[2]; - break; - case MTEX_LIGHT: - col = textureIntensity * color[0]; - result[0] = col > materialColor[0] ? col : materialColor[0]; - col = textureIntensity * color[1]; - result[1] = col > materialColor[1] ? col : materialColor[1]; - col = textureIntensity * color[2]; - result[2] = col > materialColor[2] ? col : materialColor[2]; - break; - case MTEX_BLEND_HUE: - case MTEX_BLEND_SAT: - case MTEX_BLEND_VAL: - case MTEX_BLEND_COLOR: - System.arraycopy(materialColor, 0, result, 0, 3); - this.blendHSV(blendtype, result, textureIntensity, color, blenderContext); - break; - default: - throw new IllegalStateException("Unknown blend type: " + blendtype); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java deleted file mode 100644 index f3b1c30b92..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/NoiseGenerator.java +++ /dev/null @@ -1,782 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import java.io.IOException; -import java.io.InputStream; -import java.io.ObjectInputStream; -import java.util.HashMap; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.textures.generating.TextureGeneratorMusgrave.MusgraveData; - -/** - * This generator is responsible for creating various noises used to create - * generated textures loaded from blender. - * It is only used by TextureHelper. - * Take note that these functions are not thread safe. - * @author Marcin Roguski (Kaelthas) - */ -/* package */class NoiseGenerator { - private static final Logger LOGGER = Logger.getLogger(NoiseGenerator.class.getName()); - - // tex->stype - protected static final int TEX_PLASTIC = 0; - protected static final int TEX_WALLIN = 1; - protected static final int TEX_WALLOUT = 2; - - // musgrave stype - protected static final int TEX_MFRACTAL = 0; - protected static final int TEX_RIDGEDMF = 1; - protected static final int TEX_HYBRIDMF = 2; - protected static final int TEX_FBM = 3; - protected static final int TEX_HTERRAIN = 4; - - // keyblock->type - protected static final int KEY_LINEAR = 0; - protected static final int KEY_CARDINAL = 1; - protected static final int KEY_BSPLINE = 2; - - // CONSTANTS (read from file) - protected static float[] hashpntf; - protected static short[] hash; - protected static float[] hashvectf; - protected static short[] p; - protected static float[][] g; - - /** - * Constructor. Loads the constants needed for computations. They are exactly like the ones the blender uses. Each - * deriving class should override this method and load its own constraints. - */ - public NoiseGenerator() { - LOGGER.fine("Loading noise constants."); - InputStream is = NoiseGenerator.class.getResourceAsStream("noiseconstants.dat"); - try { - ObjectInputStream ois = new ObjectInputStream(is); - hashpntf = (float[]) ois.readObject(); - hash = (short[]) ois.readObject(); - hashvectf = (float[]) ois.readObject(); - p = (short[]) ois.readObject(); - g = (float[][]) ois.readObject(); - } catch (IOException e) { - LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e); - } catch (ClassNotFoundException e) { - assert false : "Constants' classes should be arrays of primitive types, so they are ALWAYS known!"; - } finally { - if (is != null) { - try { - is.close(); - } catch (IOException e) { - LOGGER.log(Level.WARNING, e.getLocalizedMessage()); - } - } - } - } - - protected static Map noiseFunctions = new HashMap(); - static { - noiseFunctions.put(Integer.valueOf(0), new NoiseFunction() { - // originalBlenderNoise - public float execute(float x, float y, float z) { - return NoiseFunctions.originalBlenderNoise(x, y, z); - } - - public float executeSigned(float x, float y, float z) { - return 2.0f * NoiseFunctions.originalBlenderNoise(x, y, z) - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(1), new NoiseFunction() { - // orgPerlinNoise - public float execute(float x, float y, float z) { - return 0.5f + 0.5f * NoiseFunctions.noise3Perlin(x, y, z); - } - - public float executeSigned(float x, float y, float z) { - return NoiseFunctions.noise3Perlin(x, y, z); - } - }); - noiseFunctions.put(Integer.valueOf(2), new NoiseFunction() { - // newPerlin - public float execute(float x, float y, float z) { - return 0.5f + 0.5f * NoiseFunctions.newPerlin(x, y, z); - } - - public float executeSigned(float x, float y, float z) { - return this.execute(x, y, z); - } - }); - noiseFunctions.put(Integer.valueOf(3), new NoiseFunction() { - private final float[] da = new float[4]; - private final float[] pa = new float[12]; - - // voronoi_F1 - public float execute(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return da[0]; - } - - public float executeSigned(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return 2.0f * da[0] - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(4), new NoiseFunction() { - private final float[] da = new float[4]; - private final float[] pa = new float[12]; - - // voronoi_F2 - public float execute(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return da[1]; - } - - public float executeSigned(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return 2.0f * da[1] - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(5), new NoiseFunction() { - private final float[] da = new float[4]; - private final float[] pa = new float[12]; - - // voronoi_F3 - public float execute(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return da[2]; - } - - public float executeSigned(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return 2.0f * da[2] - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(6), new NoiseFunction() { - private final float[] da = new float[4]; - private final float[] pa = new float[12]; - - // voronoi_F4 - public float execute(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return da[3]; - } - - public float executeSigned(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return 2.0f * da[3] - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(7), new NoiseFunction() { - private final float[] da = new float[4]; - private final float[] pa = new float[12]; - - // voronoi_F1F2 - public float execute(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return da[1] - da[0]; - } - - public float executeSigned(float x, float y, float z) { - NoiseFunctions.voronoi(x, y, z, da, pa, 1, NATURAL_DISTANCE_FUNCTION); - return 2.0f * (da[1] - da[0]) - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(8), new NoiseFunction() { - private final NoiseFunction voronoiF1F2NoiseFunction = noiseFunctions.get(Integer.valueOf(7)); - - // voronoi_Cr - public float execute(float x, float y, float z) { - float t = 10 * voronoiF1F2NoiseFunction.execute(x, y, z); - return t > 1.0f ? 1.0f : t; - } - - public float executeSigned(float x, float y, float z) { - float t = 10.0f * voronoiF1F2NoiseFunction.execute(x, y, z); - return t > 1.0f ? 1.0f : 2.0f * t - 1.0f; - } - }); - noiseFunctions.put(Integer.valueOf(14), new NoiseFunction() { - // cellNoise - public float execute(float x, float y, float z) { - int xi = (int) FastMath.floor(x); - int yi = (int) FastMath.floor(y); - int zi = (int) FastMath.floor(z); - long n = xi + yi * 1301 + zi * 314159; - n ^= n << 13; - return (n * (n * n * 15731 + 789221) + 1376312589) / 4294967296.0f; - } - - public float executeSigned(float x, float y, float z) { - return 2.0f * this.execute(x, y, z) - 1.0f; - } - }); - } - /** Distance metrics for voronoi. e parameter only used in Minkovsky. */ - protected static Map distanceFunctions = new HashMap(); - private static DistanceFunction NATURAL_DISTANCE_FUNCTION; // often used in noise functions, se I store it here to avoid fetching it every time - static { - distanceFunctions.put(Integer.valueOf(0), new DistanceFunction() { - // real distance - public float execute(float x, float y, float z, float e) { - return (float) Math.sqrt(x * x + y * y + z * z); - } - }); - distanceFunctions.put(Integer.valueOf(1), new DistanceFunction() { - // distance squared - public float execute(float x, float y, float z, float e) { - return x * x + y * y + z * z; - } - }); - distanceFunctions.put(Integer.valueOf(2), new DistanceFunction() { - // manhattan/taxicab/cityblock distance - public float execute(float x, float y, float z, float e) { - return FastMath.abs(x) + FastMath.abs(y) + FastMath.abs(z); - } - }); - distanceFunctions.put(Integer.valueOf(3), new DistanceFunction() { - // Chebychev - public float execute(float x, float y, float z, float e) { - x = FastMath.abs(x); - y = FastMath.abs(y); - z = FastMath.abs(z); - float t = x > y ? x : y; - return z > t ? z : t; - } - }); - distanceFunctions.put(Integer.valueOf(4), new DistanceFunction() { - // Minkovsky, preset exponent 0.5 (MinkovskyH) - public float execute(float x, float y, float z, float e) { - float d = (float) (Math.sqrt(FastMath.abs(x)) + Math.sqrt(FastMath.abs(y)) + Math.sqrt(FastMath.abs(z))); - return d * d; - } - }); - distanceFunctions.put(Integer.valueOf(5), new DistanceFunction() { - // Minkovsky, preset exponent 0.25 (Minkovsky4) - public float execute(float x, float y, float z, float e) { - x *= x; - y *= y; - z *= z; - return (float) Math.sqrt(Math.sqrt(x * x + y * y + z * z)); - } - }); - distanceFunctions.put(Integer.valueOf(6), new DistanceFunction() { - // Minkovsky, general case - public float execute(float x, float y, float z, float e) { - return (float) Math.pow(Math.pow(FastMath.abs(x), e) + Math.pow(FastMath.abs(y), e) + Math.pow(FastMath.abs(z), e), 1.0f / e); - } - }); - NATURAL_DISTANCE_FUNCTION = distanceFunctions.get(Integer.valueOf(0)); - } - - protected static Map musgraveFunctions = new HashMap(); - static { - musgraveFunctions.put(Integer.valueOf(TEX_MFRACTAL), new MusgraveFunction() { - - public float execute(MusgraveData musgraveData, float x, float y, float z) { - float rmd, value = 1.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); - - for (int i = 0; i < (int) musgraveData.octaves; ++i) { - value *= pwr * musgraveData.noiseFunction.executeSigned(x, y, z) + 1.0f; - pwr *= pwHL; - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - } - rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); - if (rmd != 0.0f) { - value *= rmd * musgraveData.noiseFunction.executeSigned(x, y, z) * pwr + 1.0f; - } - return value; - } - }); - musgraveFunctions.put(Integer.valueOf(TEX_RIDGEDMF), new MusgraveFunction() { - - public float execute(MusgraveData musgraveData, float x, float y, float z) { - float result, signal, weight; - float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); - float pwr = pwHL; - - signal = musgraveData.offset - FastMath.abs(musgraveData.noiseFunction.executeSigned(x, y, z)); - signal *= signal; - result = signal; - weight = 1.0f; - - for (int i = 1; i < (int) musgraveData.octaves; ++i) { - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - weight = signal * musgraveData.gain; - if (weight > 1.0f) { - weight = 1.0f; - } else if (weight < 0.0) { - weight = 0.0f; - } - signal = musgraveData.offset - FastMath.abs(musgraveData.noiseFunction.executeSigned(x, y, z)); - signal *= signal; - signal *= weight; - result += signal * pwr; - pwr *= pwHL; - } - return result; - } - }); - musgraveFunctions.put(Integer.valueOf(TEX_HYBRIDMF), new MusgraveFunction() { - - public float execute(MusgraveData musgraveData, float x, float y, float z) { - float result, signal, weight, rmd; - float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); - float pwr = pwHL; - - result = musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset; - weight = musgraveData.gain * result; - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - - for (int i = 1; weight > 0.001f && i < (int) musgraveData.octaves; ++i) { - if (weight > 1.0f) { - weight = 1.0f; - } - signal = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr; - pwr *= pwHL; - result += weight * signal; - weight *= musgraveData.gain * signal; - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - } - - rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); - if (rmd != 0.0f) { - result += rmd * (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr; - } - return result; - } - }); - musgraveFunctions.put(Integer.valueOf(TEX_FBM), new MusgraveFunction() { - - public float execute(MusgraveData musgraveData, float x, float y, float z) { - float rmd, value = 0.0f, pwr = 1.0f, pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); - - for (int i = 0; i < (int) musgraveData.octaves; ++i) { - value += musgraveData.noiseFunction.executeSigned(x, y, z) * pwr; - pwr *= pwHL; - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - } - - rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); - if (rmd != 0.f) { - value += rmd * musgraveData.noiseFunction.executeSigned(x, y, z) * pwr; - } - return value; - } - }); - musgraveFunctions.put(Integer.valueOf(TEX_HTERRAIN), new MusgraveFunction() { - - public float execute(MusgraveData musgraveData, float x, float y, float z) { - float value, increment, rmd; - float pwHL = (float) Math.pow(musgraveData.lacunarity, -musgraveData.h); - float pwr = pwHL; - - value = musgraveData.offset + musgraveData.noiseFunction.executeSigned(x, y, z); - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - - for (int i = 1; i < (int) musgraveData.octaves; ++i) { - increment = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr * value; - value += increment; - pwr *= pwHL; - x *= musgraveData.lacunarity; - y *= musgraveData.lacunarity; - z *= musgraveData.lacunarity; - } - - rmd = musgraveData.octaves - FastMath.floor(musgraveData.octaves); - if (rmd != 0.0) { - increment = (musgraveData.noiseFunction.executeSigned(x, y, z) + musgraveData.offset) * pwr * value; - value += rmd * increment; - } - return value; - } - }); - } - - public static class NoiseFunctions { - public static float noise(float x, float y, float z, float noiseSize, int noiseDepth, NoiseFunction noiseFunction, boolean isHard) { - if (noiseSize != 0.0) { - noiseSize = 1.0f / noiseSize; - x *= noiseSize; - y *= noiseSize; - z *= noiseSize; - } - float result = noiseFunction.execute(x, y, z); - return isHard ? FastMath.abs(2.0f * result - 1.0f) : result; - } - - public static float turbulence(float x, float y, float z, float noiseSize, int noiseDepth, NoiseFunction noiseFunction, boolean isHard) { - if (noiseSize != 0.0) { - noiseSize = 1.0f / noiseSize; - x *= noiseSize; - y *= noiseSize; - z *= noiseSize; - } - - float sum = 0, t, amp = 1, fscale = 1; - for (int i = 0; i <= noiseDepth; ++i, amp *= 0.5f, fscale *= 2f) { - t = noiseFunction.execute(fscale * x, fscale * y, fscale * z); - if (isHard) { - t = FastMath.abs(2.0f * t - 1.0f); - } - sum += t * amp; - } - - sum *= (float) (1 << noiseDepth) / (float) ((1 << noiseDepth + 1) - 1); - return sum; - } - - private static final float[] voronoiP = new float[3]; - - public static void voronoi(float x, float y, float z, float[] da, float[] pa, float distanceExponent, DistanceFunction distanceFunction) { - float xd, yd, zd, d; - - int xi = (int) FastMath.floor(x); - int yi = (int) FastMath.floor(y); - int zi = (int) FastMath.floor(z); - da[0] = da[1] = da[2] = da[3] = Float.MAX_VALUE;// 1e10f; - for (int i = xi - 1; i <= xi + 1; ++i) { - for (int j = yi - 1; j <= yi + 1; ++j) { - for (int k = zi - 1; k <= zi + 1; ++k) { - NoiseMath.hash(i, j, k, voronoiP); - xd = x - (voronoiP[0] + i); - yd = y - (voronoiP[1] + j); - zd = z - (voronoiP[2] + k); - d = distanceFunction.execute(xd, yd, zd, distanceExponent); - if (d < da[0]) { - da[3] = da[2]; - da[2] = da[1]; - da[1] = da[0]; - da[0] = d; - pa[9] = pa[6]; - pa[10] = pa[7]; - pa[11] = pa[8]; - pa[6] = pa[3]; - pa[7] = pa[4]; - pa[8] = pa[5]; - pa[3] = pa[0]; - pa[4] = pa[1]; - pa[5] = pa[2]; - pa[0] = voronoiP[0] + i; - pa[1] = voronoiP[1] + j; - pa[2] = voronoiP[2] + k; - } else if (d < da[1]) { - da[3] = da[2]; - da[2] = da[1]; - da[1] = d; - pa[9] = pa[6]; - pa[10] = pa[7]; - pa[11] = pa[8]; - pa[6] = pa[3]; - pa[7] = pa[4]; - pa[8] = pa[5]; - pa[3] = voronoiP[0] + i; - pa[4] = voronoiP[1] + j; - pa[5] = voronoiP[2] + k; - } else if (d < da[2]) { - da[3] = da[2]; - da[2] = d; - pa[9] = pa[6]; - pa[10] = pa[7]; - pa[11] = pa[8]; - pa[6] = voronoiP[0] + i; - pa[7] = voronoiP[1] + j; - pa[8] = voronoiP[2] + k; - } else if (d < da[3]) { - da[3] = d; - pa[9] = voronoiP[0] + i; - pa[10] = voronoiP[1] + j; - pa[11] = voronoiP[2] + k; - } - } - } - } - } - - // instead of adding another permutation array, just use hash table defined above - public static float newPerlin(float x, float y, float z) { - int A, AA, AB, B, BA, BB; - float floorX = FastMath.floor(x), floorY = FastMath.floor(y), floorZ = FastMath.floor(z); - int intX = (int) floorX & 0xFF, intY = (int) floorY & 0xFF, intZ = (int) floorZ & 0xFF; - x -= floorX; - y -= floorY; - z -= floorZ; - // computing fading curves - floorX = NoiseMath.npfade(x); - floorY = NoiseMath.npfade(y); - floorZ = NoiseMath.npfade(z); - A = hash[intX] + intY; - AA = hash[A] + intZ; - AB = hash[A + 1] + intZ; - B = hash[intX + 1] + intY; - BA = hash[B] + intZ; - BB = hash[B + 1] + intZ; - return NoiseMath.lerp(floorZ, NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA], x, y, z), NoiseMath.grad(hash[BA], x - 1, y, z)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB], x, y - 1, z), NoiseMath.grad(hash[BB], x - 1, y - 1, z))), - NoiseMath.lerp(floorY, NoiseMath.lerp(floorX, NoiseMath.grad(hash[AA + 1], x, y, z - 1), NoiseMath.grad(hash[BA + 1], x - 1, y, z - 1)), NoiseMath.lerp(floorX, NoiseMath.grad(hash[AB + 1], x, y - 1, z - 1), NoiseMath.grad(hash[BB + 1], x - 1, y - 1, z - 1)))); - } - - public static float noise3Perlin(float x, float y, float z) { - float t = x + 10000.0f; - int bx0 = (int) t & 0xFF; - int bx1 = bx0 + 1 & 0xFF; - float rx0 = t - (int) t; - float rx1 = rx0 - 1.0f; - - t = y + 10000.0f; - int by0 = (int) t & 0xFF; - int by1 = by0 + 1 & 0xFF; - float ry0 = t - (int) t; - float ry1 = ry0 - 1.0f; - - t = z + 10000.0f; - int bz0 = (int) t & 0xFF; - int bz1 = bz0 + 1 & 0xFF; - float rz0 = t - (int) t; - float rz1 = rz0 - 1.0f; - - int i = p[bx0]; - int j = p[bx1]; - - int b00 = p[i + by0]; - int b10 = p[j + by0]; - int b01 = p[i + by1]; - int b11 = p[j + by1]; - - float sx = NoiseMath.surve(rx0); - float sy = NoiseMath.surve(ry0); - float sz = NoiseMath.surve(rz0); - - float[] q = g[b00 + bz0]; - float u = NoiseMath.at(rx0, ry0, rz0, q); - q = g[b10 + bz0]; - float v = NoiseMath.at(rx1, ry0, rz0, q); - float a = NoiseMath.lerp(sx, u, v); - - q = g[b01 + bz0]; - u = NoiseMath.at(rx0, ry1, rz0, q); - q = g[b11 + bz0]; - v = NoiseMath.at(rx1, ry1, rz0, q); - float b = NoiseMath.lerp(sx, u, v); - - float c = NoiseMath.lerp(sy, a, b); - - q = g[b00 + bz1]; - u = NoiseMath.at(rx0, ry0, rz1, q); - q = g[b10 + bz1]; - v = NoiseMath.at(rx1, ry0, rz1, q); - a = NoiseMath.lerp(sx, u, v); - - q = g[b01 + bz1]; - u = NoiseMath.at(rx0, ry1, rz1, q); - q = g[b11 + bz1]; - v = NoiseMath.at(rx1, ry1, rz1, q); - b = NoiseMath.lerp(sx, u, v); - - float d = NoiseMath.lerp(sy, a, b); - return 1.5f * NoiseMath.lerp(sz, c, d); - } - - private static final float[] cn = new float[8]; - private static final int[] b1 = new int[8]; - private static final int[] b2 = new int[2]; - private static final float[] xFactor = new float[8]; - private static final float[] yFactor = new float[8]; - private static final float[] zFactor = new float[8]; - - public static float originalBlenderNoise(float x, float y, float z) { - float n = 0.5f; - - int ix = (int) FastMath.floor(x); - int iy = (int) FastMath.floor(y); - int iz = (int) FastMath.floor(z); - - float ox = x - ix; - float oy = y - iy; - float oz = z - iz; - - float jx = ox - 1; - float jy = oy - 1; - float jz = oz - 1; - - float cn1 = ox * ox; - float cn2 = oy * oy; - float cn3 = oz * oz; - float cn4 = jx * jx; - float cn5 = jy * jy; - float cn6 = jz * jz; - - cn1 = 1.0f - 3.0f * cn1 + 2.0f * cn1 * ox; - cn2 = 1.0f - 3.0f * cn2 + 2.0f * cn2 * oy; - cn3 = 1.0f - 3.0f * cn3 + 2.0f * cn3 * oz; - cn4 = 1.0f - 3.0f * cn4 - 2.0f * cn4 * jx; - cn5 = 1.0f - 3.0f * cn5 - 2.0f * cn5 * jy; - cn6 = 1.0f - 3.0f * cn6 - 2.0f * cn6 * jz; - - cn[0] = cn1 * cn2 * cn3; - cn[1] = cn1 * cn2 * cn6; - cn[2] = cn1 * cn5 * cn3; - cn[3] = cn1 * cn5 * cn6; - cn[4] = cn4 * cn2 * cn3; - cn[5] = cn4 * cn2 * cn6; - cn[6] = cn4 * cn5 * cn3; - cn[7] = cn4 * cn5 * cn6; - - b1[0] = b1[1] = hash[hash[ix & 0xFF] + (iy & 0xFF)]; - b1[2] = b1[3] = hash[hash[ix & 0xFF] + (iy + 1 & 0xFF)]; - b1[4] = b1[5] = hash[hash[ix + 1 & 0xFF] + (iy & 0xFF)]; - b1[6] = b1[7] = hash[hash[ix + 1 & 0xFF] + (iy + 1 & 0xFF)]; - - b2[0] = iz & 0xFF; - b2[1] = iz + 1 & 0xFF; - - xFactor[0] = xFactor[1] = xFactor[2] = xFactor[3] = ox; - xFactor[4] = xFactor[5] = xFactor[6] = xFactor[7] = jx; - yFactor[0] = yFactor[1] = yFactor[4] = yFactor[5] = oy; - yFactor[2] = yFactor[3] = yFactor[6] = yFactor[7] = jy; - zFactor[0] = zFactor[2] = zFactor[4] = zFactor[6] = oz; - zFactor[1] = zFactor[3] = zFactor[5] = zFactor[7] = jz; - - for (int i = 0; i < cn.length; ++i) { - int hIndex = 3 * hash[b1[i] + b2[i % 2]]; - n += cn[i] * (hashvectf[hIndex] * xFactor[i] + hashvectf[hIndex + 1] * yFactor[i] + hashvectf[hIndex + 2] * zFactor[i]); - } - - if (n < 0.0f) { - n = 0.0f; - } else if (n > 1.0f) { - n = 1.0f; - } - return n; - } - } - - /** - * This class is abstract to the noise functions computations. It has two methods. One calculates the Signed (with - * 'S' at the end) and the other Unsigned value. - * @author Marcin Roguski (Kaelthas) - */ - interface NoiseFunction { - - /** - * This method calculates the unsigned value of the noise. - * @param x - * the x texture coordinate - * @param y - * the y texture coordinate - * @param z - * the z texture coordinate - * @return value of the noise - */ - float execute(float x, float y, float z); - - /** - * This method calculates the signed value of the noise. - * @param x - * the x texture coordinate - * @param y - * the y texture coordinate - * @param z - * the z texture coordinate - * @return value of the noise - */ - float executeSigned(float x, float y, float z); - } - - public static class NoiseMath { - public static float lerp(float t, float a, float b) { - return a + t * (b - a); - } - - public static float npfade(float t) { - return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); - } - - public static float grad(int hash, float x, float y, float z) { - int h = hash & 0x0F; - float u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z; - return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); - } - - public static float surve(float t) { - return t * t * (3.0f - 2.0f * t); - } - - public static float at(float x, float y, float z, float[] q) { - return x * q[0] + y * q[1] + z * q[2]; - } - - public static void hash(int x, int y, int z, float[] result) { - result[0] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF]]; - result[1] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 1]; - result[2] = hashpntf[3 * hash[hash[hash[z & 0xFF] + y & 0xFF] + x & 0xFF] + 2]; - } - } - - /** - * This interface is used for distance calculation classes. Distance metrics for voronoi. e parameter only used in - * Minkovsky. - */ - interface DistanceFunction { - - /** - * This method calculates the distance for voronoi algorithms. - * @param x - * the x coordinate - * @param y - * the y coordinate - * @param z - * the z coordinate - * @param e - * this parameter used in Monkovsky (no idea what it really is ;) - * @return - */ - float execute(float x, float y, float z, float e); - } - - interface MusgraveFunction { - - float execute(MusgraveData musgraveData, float x, float y, float z); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java deleted file mode 100644 index e459a0dfb0..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGenerator.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.ColorBand; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image.Format; - -/** - * This class is a base class for texture generators. - * @author Marcin Roguski (Kaelthas) - */ -public abstract class TextureGenerator { - protected NoiseGenerator noiseGenerator; - protected int flag; - protected float[][] colorBand; - protected BrightnessAndContrastData bacd; - protected Format imageFormat; - - public TextureGenerator(NoiseGenerator noiseGenerator, Format imageFormat) { - this.noiseGenerator = noiseGenerator; - this.imageFormat = imageFormat; - } - - public Format getImageFormat() { - return imageFormat; - } - - public void readData(Structure tex, BlenderContext blenderContext) { - flag = ((Number) tex.getFieldValue("flag")).intValue(); - colorBand = new ColorBand(tex, blenderContext).computeValues(); - bacd = new BrightnessAndContrastData(tex); - if (colorBand != null) { - imageFormat = Format.RGBA8; - } - } - - public abstract void getPixel(TexturePixel pixel, float x, float y, float z); - - /** - * This method applies brightness and contrast for RGB textures. - */ - protected void applyBrightnessAndContrast(BrightnessAndContrastData bacd, TexturePixel texres) { - texres.red = (texres.red - 0.5f) * bacd.contrast + bacd.brightness; - if (texres.red < 0.0f) { - texres.red = 0.0f; - } - texres.green = (texres.green - 0.5f) * bacd.contrast + bacd.brightness; - if (texres.green < 0.0f) { - texres.green = 0.0f; - } - texres.blue = (texres.blue - 0.5f) * bacd.contrast + bacd.brightness; - if (texres.blue < 0.0f) { - texres.blue = 0.0f; - } - } - - /** - * This method applies brightness and contrast for Luminance textures. - * @param texres - * @param contrast - * @param brightness - */ - protected void applyBrightnessAndContrast(TexturePixel texres, float contrast, float brightness) { - texres.intensity = (texres.intensity - 0.5f) * contrast + brightness; - if (texres.intensity < 0.0f) { - texres.intensity = 0.0f; - } else if (texres.intensity > 1.0f) { - texres.intensity = 1.0f; - } - } - - /** - * This class contains brightness and contrast data. - * @author Marcin Roguski (Kaelthas) - */ - protected static class BrightnessAndContrastData { - public final float contrast; - public final float brightness; - public final float rFactor; - public final float gFactor; - public final float bFactor; - - /** - * Constructor reads the required data from the given structure. - * @param tex - * texture structure - */ - public BrightnessAndContrastData(Structure tex) { - contrast = ((Number) tex.getFieldValue("contrast")).floatValue(); - brightness = ((Number) tex.getFieldValue("bright")).floatValue() - 0.5f; - rFactor = ((Number) tex.getFieldValue("rfac")).floatValue(); - gFactor = ((Number) tex.getFieldValue("gfac")).floatValue(); - bFactor = ((Number) tex.getFieldValue("bfac")).floatValue(); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java deleted file mode 100644 index eabe6fa745..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorBlend.java +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'blend' texture. - * @author Marcin Roguski (Kaelthas) - */ -public final class TextureGeneratorBlend extends TextureGenerator { - - private static final IntensityFunction INTENSITY_FUNCTION[] = new IntensityFunction[7]; - static { - INTENSITY_FUNCTION[0] = new IntensityFunction() {// Linear: stype = 0 (TEX_LIN) - public float getIntensity(float x, float y, float z) { - return (1.0f + x) * 0.5f; - } - }; - INTENSITY_FUNCTION[1] = new IntensityFunction() {// Quad: stype = 1 (TEX_QUAD) - public float getIntensity(float x, float y, float z) { - float result = (1.0f + x) * 0.5f; - return result * result; - } - }; - INTENSITY_FUNCTION[2] = new IntensityFunction() {// Ease: stype = 2 (TEX_EASE) - public float getIntensity(float x, float y, float z) { - float result = (1.0f + x) * 0.5f; - if (result <= 0.0f) { - return 0.0f; - } else if (result >= 1.0f) { - return 1.0f; - } else { - return result * result * (3.0f - 2.0f * result); - } - } - }; - INTENSITY_FUNCTION[3] = new IntensityFunction() {// Diagonal: stype = 3 (TEX_DIAG) - public float getIntensity(float x, float y, float z) { - return (2.0f + x + y) * 0.25f; - } - }; - INTENSITY_FUNCTION[4] = new IntensityFunction() {// Sphere: stype = 4 (TEX_SPHERE) - public float getIntensity(float x, float y, float z) { - float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); - return result < 0.0f ? 0.0f : result; - } - }; - INTENSITY_FUNCTION[5] = new IntensityFunction() {// Halo: stype = 5 (TEX_HALO) - public float getIntensity(float x, float y, float z) { - float result = 1.0f - (float) Math.sqrt(x * x + y * y + z * z); - return result <= 0.0f ? 0.0f : result * result; - } - }; - INTENSITY_FUNCTION[6] = new IntensityFunction() {// Radial: stype = 6 (TEX_RAD) - public float getIntensity(float x, float y, float z) { - return (float) Math.atan2(y, x) * FastMath.INV_TWO_PI + 0.5f; - } - }; - } - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorBlend(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - protected int stype; - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - stype = ((Number) tex.getFieldValue("stype")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = INTENSITY_FUNCTION[stype].getIntensity(x, y, z); - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - - private static interface IntensityFunction { - float getIntensity(float x, float y, float z); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java deleted file mode 100644 index 0d1cf2c54a..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorClouds.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'clouds' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorClouds extends TextureGenerator { - // noiseType - protected static final int TEX_NOISESOFT = 0; - protected static final int TEX_NOISEPERL = 1; - - // sType - protected static final int TEX_DEFAULT = 0; - protected static final int TEX_COLOR = 1; - - protected float noisesize; - protected int noiseDepth; - protected int noiseBasis; - protected NoiseFunction noiseFunction; - protected int noiseType; - protected boolean isHard; - protected int sType; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorClouds(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - noiseDepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - noiseBasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); - isHard = noiseType != TEX_NOISESOFT; - sType = ((Number) tex.getFieldValue("stype")).intValue(); - if (sType == TEX_COLOR) { - imageFormat = Format.RGBA8; - } - - noiseFunction = NoiseGenerator.noiseFunctions.get(noiseBasis); - if (noiseFunction == null) { - noiseFunction = NoiseGenerator.noiseFunctions.get(0); - noiseBasis = 0; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - if (noiseBasis == 0) { - ++x; - ++y; - ++z; - } - pixel.intensity = NoiseGenerator.NoiseFunctions.turbulence(x, y, z, noisesize, noiseDepth, noiseFunction, isHard); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - - this.applyBrightnessAndContrast(bacd, pixel); - } else if (sType == TEX_COLOR) { - pixel.red = pixel.intensity; - pixel.green = NoiseGenerator.NoiseFunctions.turbulence(y, x, z, noisesize, noiseDepth, noiseFunction, isHard); - pixel.blue = NoiseGenerator.NoiseFunctions.turbulence(y, z, x, noisesize, noiseDepth, noiseFunction, isHard); - - pixel.green = FastMath.clamp(pixel.green, 0.0f, 1.0f); - pixel.blue = FastMath.clamp(pixel.blue, 0.0f, 1.0f); - pixel.alpha = 1.0f; - - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java deleted file mode 100644 index 232ccbeb3d..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorDistnoise.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'distorted noise' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorDistnoise extends TextureGenerator { - protected float noisesize; - protected float distAmount; - protected int noisebasis; - protected int noisebasis2; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorDistnoise(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - distAmount = ((Number) tex.getFieldValue("dist_amount")).floatValue(); - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noisebasis2 = ((Number) tex.getFieldValue("noisebasis2")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.musgraveVariableLunacrityNoise(x * 4, y * 4, z * 4, distAmount, noisebasis, noisebasis2); - pixel.intensity = FastMath.clamp(pixel.intensity, 0.0f, 1.0f); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - - /** - * "Variable Lacunarity Noise" A distorted variety of Perlin noise. This method is used to calculate distorted noise - * texture. - * @param x - * @param y - * @param z - * @param distortion - * @param nbas1 - * @param nbas2 - * @return - */ - private float musgraveVariableLunacrityNoise(float x, float y, float z, float distortion, int nbas1, int nbas2) { - NoiseFunction abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas1)); - if (abstractNoiseFunc1 == null) { - abstractNoiseFunc1 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0)); - } - NoiseFunction abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(nbas2)); - if (abstractNoiseFunc2 == null) { - abstractNoiseFunc2 = NoiseGenerator.noiseFunctions.get(Integer.valueOf(0)); - } - // get a random vector and scale the randomization - float rx = abstractNoiseFunc1.execute(x + 13.5f, y + 13.5f, z + 13.5f) * distortion; - float ry = abstractNoiseFunc1.execute(x, y, z) * distortion; - float rz = abstractNoiseFunc1.execute(x - 13.5f, y - 13.5f, z - 13.5f) * distortion; - return abstractNoiseFunc2.executeSigned(x + rx, y + ry, z + rz); // distorted-domain noise - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java deleted file mode 100644 index a0e29f1b3a..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorFactory.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.scene.plugins.blender.textures.TextureHelper; - -public class TextureGeneratorFactory { - - private NoiseGenerator noiseGenerator = new NoiseGenerator(); - - public TextureGenerator createTextureGenerator(int generatedTexture) { - switch (generatedTexture) { - case TextureHelper.TEX_BLEND: - return new TextureGeneratorBlend(noiseGenerator); - case TextureHelper.TEX_CLOUDS: - return new TextureGeneratorClouds(noiseGenerator); - case TextureHelper.TEX_DISTNOISE: - return new TextureGeneratorDistnoise(noiseGenerator); - case TextureHelper.TEX_MAGIC: - return new TextureGeneratorMagic(noiseGenerator); - case TextureHelper.TEX_MARBLE: - return new TextureGeneratorMarble(noiseGenerator); - case TextureHelper.TEX_MUSGRAVE: - return new TextureGeneratorMusgrave(noiseGenerator); - case TextureHelper.TEX_NOISE: - return new TextureGeneratorNoise(noiseGenerator); - case TextureHelper.TEX_STUCCI: - return new TextureGeneratorStucci(noiseGenerator); - case TextureHelper.TEX_VORONOI: - return new TextureGeneratorVoronoi(noiseGenerator); - case TextureHelper.TEX_WOOD: - return new TextureGeneratorWood(noiseGenerator); - default: - throw new IllegalStateException("Unknown generated texture type: " + generatedTexture); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java deleted file mode 100644 index 6f3d7b599b..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMagic.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'magic' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorMagic extends TextureGenerator { - private static NoiseDepthFunction[] noiseDepthFunctions = new NoiseDepthFunction[10]; - static { - noiseDepthFunctions[0] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.cos(xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[1] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = (float) Math.cos(xyz[0] - xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[2] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[2] = (float) Math.sin(-xyz[0] - xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[3] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = -(float) Math.cos(-xyz[0] + xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[4] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.sin(-xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[5] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.cos(-xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[6] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = (float) Math.cos(xyz[0] + xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[7] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[2] = (float) Math.sin(xyz[0] + xyz[1] - xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[8] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[0] = -(float) Math.cos(-xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - noiseDepthFunctions[9] = new NoiseDepthFunction() { - public void compute(float[] xyz, float turbulence) { - xyz[1] = -(float) Math.sin(xyz[0] - xyz[1] + xyz[2]) * turbulence; - } - }; - } - - protected int noisedepth; - protected float turbul; - protected float[] xyz = new float[3]; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMagic(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.RGBA8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue() / 5.0f; - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - float turb = turbul; - xyz[0] = (float) Math.sin((x + y + z) * 5.0f); - xyz[1] = (float) Math.cos((-x + y - z) * 5.0f); - xyz[2] = -(float) Math.cos((-x - y + z) * 5.0f); - - if (colorBand != null) { - pixel.intensity = FastMath.clamp(0.3333f * (xyz[0] + xyz[1] + xyz[2]), 0.0f, 1.0f); - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - if (noisedepth > 0) { - xyz[0] *= turb; - xyz[1] *= turb; - xyz[2] *= turb; - for (int m = 0; m < noisedepth; ++m) { - noiseDepthFunctions[m].compute(xyz, turb); - } - } - - if (turb != 0.0f) { - turb *= 2.0f; - xyz[0] /= turb; - xyz[1] /= turb; - xyz[2] /= turb; - } - pixel.red = 0.5f - xyz[0]; - pixel.green = 0.5f - xyz[1]; - pixel.blue = 0.5f - xyz[2]; - pixel.alpha = 1.0f; - } - this.applyBrightnessAndContrast(bacd, pixel); - } - - private static interface NoiseDepthFunction { - void compute(float[] xyz, float turbulence); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java deleted file mode 100644 index 9dc7403b12..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMarble.java +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; - -/** - * This class generates the 'marble' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorMarble extends TextureGeneratorWood { - // tex->stype - protected static final int TEX_SOFT = 0; - protected static final int TEX_SHARP = 1; - protected static final int TEX_SHARPER = 2; - - protected MarbleData marbleData; - protected int noisebasis; - protected NoiseFunction noiseFunction; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMarble(NoiseGenerator noiseGenerator) { - super(noiseGenerator); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - marbleData = new MarbleData(tex); - noisebasis = marbleData.noisebasis; - noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); - if (noiseFunction == null) { - noiseFunction = NoiseGenerator.noiseFunctions.get(0); - noisebasis = 0; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.marbleInt(marbleData, x, y, z); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - - public float marbleInt(MarbleData marbleData, float x, float y, float z) { - int waveform; - if (marbleData.waveform > TEX_TRI || marbleData.waveform < TEX_SIN) { - waveform = 0; - } else { - waveform = marbleData.waveform; - } - - float n = 5.0f * (x + y + z); - if (noisebasis == 0) { - ++x; - ++y; - ++z; - } - float mi = n + marbleData.turbul * NoiseGenerator.NoiseFunctions.turbulence(x, y, z, marbleData.noisesize, marbleData.noisedepth, noiseFunction, marbleData.isHard); - - if (marbleData.stype >= TEX_SOFT) { - mi = waveformFunctions[waveform].execute(mi); - if (marbleData.stype == TEX_SHARP) { - mi = (float) Math.sqrt(mi); - } else if (marbleData.stype == TEX_SHARPER) { - mi = (float) Math.sqrt(Math.sqrt(mi)); - } - } - return mi; - } - - private static class MarbleData { - public final float noisesize; - public final int noisebasis; - public final int noisedepth; - public final int stype; - public final float turbul; - public final int waveform; - public final boolean isHard; - - public MarbleData(Structure tex) { - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - stype = ((Number) tex.getFieldValue("stype")).intValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); - int noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); - waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue(); - isHard = noisetype != TEX_NOISESOFT; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java deleted file mode 100644 index 707d4dfe09..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorMusgrave.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.MusgraveFunction; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'musgrave' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorMusgrave extends TextureGenerator { - protected MusgraveData musgraveData; - protected MusgraveFunction musgraveFunction; - protected int stype; - protected float noisesize; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorMusgrave(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - musgraveData = new MusgraveData(tex); - stype = ((Number) tex.getFieldValue("stype")).intValue(); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - musgraveFunction = NoiseGenerator.musgraveFunctions.get(Integer.valueOf(musgraveData.stype)); - if (musgraveFunction == null) { - throw new IllegalStateException("Unknown type of musgrave texture: " + stype); - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = musgraveData.outscale * musgraveFunction.execute(musgraveData, x, y, z); - if (pixel.intensity > 1) { - pixel.intensity = 1.0f; - } else if (pixel.intensity < 0) { - pixel.intensity = 0.0f; - } - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(bacd, pixel); - } - } - - protected static class MusgraveData { - public final int stype; - public final float outscale; - public final float h; - public final float lacunarity; - public final float octaves; - public final int noisebasis; - public final NoiseFunction noiseFunction; - public final float offset; - public final float gain; - - public MusgraveData(Structure tex) { - stype = ((Number) tex.getFieldValue("stype")).intValue(); - outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); - h = ((Number) tex.getFieldValue("mg_H")).floatValue(); - lacunarity = ((Number) tex.getFieldValue("mg_lacunarity")).floatValue(); - octaves = ((Number) tex.getFieldValue("mg_octaves")).floatValue(); - offset = ((Number) tex.getFieldValue("mg_offset")).floatValue(); - gain = ((Number) tex.getFieldValue("mg_gain")).floatValue(); - - int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - NoiseFunction noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); - if (noiseFunction == null) { - noiseFunction = NoiseGenerator.noiseFunctions.get(0); - noisebasis = 0; - } - this.noisebasis = noisebasis; - this.noiseFunction = noiseFunction; - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java deleted file mode 100644 index aa8b7b18db..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorNoise.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'noise' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorNoise extends TextureGenerator { - protected int noisedepth; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorNoise(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisedepth = ((Number) tex.getFieldValue("noisedepth")).intValue(); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - int random = FastMath.rand.nextInt(); - int val = random & 3; - - int loop = noisedepth; - while (loop-- != 0) { - random >>= 2; - val *= random & 3; - } - pixel.intensity = FastMath.clamp(val, 0.0f, 1.0f); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java deleted file mode 100644 index dd07ef1c17..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorStucci.java +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'stucci' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorStucci extends TextureGenerator { - protected static final int TEX_NOISESOFT = 0; - - protected float noisesize; - protected int noisebasis; - protected NoiseFunction noiseFunction; - protected int noisetype; - protected float turbul; - protected boolean isHard; - protected int stype; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorStucci(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - - noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - noiseFunction = NoiseGenerator.noiseFunctions.get(noisebasis); - if (noiseFunction == null) { - noiseFunction = NoiseGenerator.noiseFunctions.get(0); - noisebasis = 0; - } - - noisetype = ((Number) tex.getFieldValue("noisetype")).intValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); - isHard = noisetype != TEX_NOISESOFT; - stype = ((Number) tex.getFieldValue("stype")).intValue(); - if (noisesize <= 0.001f) {// the texture goes black if this value is lower than 0.001f - noisesize = 0.001f; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - if (noisebasis == 0) { - ++x; - ++y; - ++z; - } - float noiseValue = NoiseGenerator.NoiseFunctions.noise(x, y, z, noisesize, 0, noiseFunction, isHard); - float ofs = turbul / 200.0f; - if (stype != 0) { - ofs *= noiseValue * noiseValue; - } - - pixel.intensity = NoiseGenerator.NoiseFunctions.noise(x, y, z + ofs, noisesize, 0, noiseFunction, isHard); - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } - - if (stype == NoiseGenerator.TEX_WALLOUT) { - pixel.intensity = 1.0f - pixel.intensity; - } - if (pixel.intensity < 0.0f) { - pixel.intensity = 0.0f; - } - // no brightness and contrast needed for stucci (it doesn't affect the texture) - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java deleted file mode 100644 index e53a6e73bd..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorVoronoi.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.DistanceFunction; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseMath; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'voronoi' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorVoronoi extends TextureGenerator { - protected float noisesize; - protected float outscale; - protected float mexp; - protected DistanceFunction distanceFunction; - protected int voronoiColorType; - protected float[] da = new float[4], pa = new float[12]; - protected float[] hashPoint; - protected float[] voronoiWeights; - protected float weightSum; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorVoronoi(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - voronoiWeights = new float[4]; - voronoiWeights[0] = ((Number) tex.getFieldValue("vn_w1")).floatValue(); - voronoiWeights[1] = ((Number) tex.getFieldValue("vn_w2")).floatValue(); - voronoiWeights[2] = ((Number) tex.getFieldValue("vn_w3")).floatValue(); - voronoiWeights[3] = ((Number) tex.getFieldValue("vn_w4")).floatValue(); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - outscale = ((Number) tex.getFieldValue("ns_outscale")).floatValue(); - mexp = ((Number) tex.getFieldValue("vn_mexp")).floatValue(); - int distanceType = ((Number) tex.getFieldValue("vn_distm")).intValue(); - distanceFunction = NoiseGenerator.distanceFunctions.get(distanceType); - voronoiColorType = ((Number) tex.getFieldValue("vn_coltype")).intValue(); - hashPoint = voronoiColorType != 0 ? new float[3] : null; - weightSum = voronoiWeights[0] + voronoiWeights[1] + voronoiWeights[2] + voronoiWeights[3]; - if (weightSum != 0.0f) { - weightSum = outscale / weightSum; - } - if (voronoiColorType != 0 || colorBand != null) { - imageFormat = Format.RGBA8; - } - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - // for voronoi we need to widen the range a little - NoiseGenerator.NoiseFunctions.voronoi(x * 4, y * 4, z * 4, da, pa, mexp, distanceFunction); - pixel.intensity = weightSum * FastMath.abs(voronoiWeights[0] * da[0] + voronoiWeights[1] * da[1] + voronoiWeights[2] * da[2] + voronoiWeights[3] * da[3]); - if (pixel.intensity > 1.0f) { - pixel.intensity = 1.0f; - } else if (pixel.intensity < 0.0f) { - pixel.intensity = 0.0f; - } - - if (colorBand != null) {// colorband ALWAYS goes first and covers the color (if set) - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - pixel.alpha = colorBand[colorbandIndex][3]; - } else if (voronoiColorType != 0) { - pixel.red = pixel.green = pixel.blue = 0.0f; - pixel.alpha = 1.0f; - for (int m = 0; m < 12; m += 3) { - float weight = voronoiWeights[m / 3]; - NoiseMath.hash((int) pa[m], (int) pa[m + 1], (int) pa[m + 2], hashPoint); - pixel.red += weight * hashPoint[0]; - pixel.green += weight * hashPoint[1]; - pixel.blue += weight * hashPoint[2]; - } - if (voronoiColorType >= 2) { - float t1 = (da[1] - da[0]) * 10.0f; - if (t1 > 1.0f) { - t1 = 1.0f; - } - if (voronoiColorType == 3) { - t1 *= pixel.intensity; - } else { - t1 *= weightSum; - } - pixel.red *= t1; - pixel.green *= t1; - pixel.blue *= t1; - } else { - pixel.red *= weightSum; - pixel.green *= weightSum; - pixel.blue *= weightSum; - } - } - - if (voronoiColorType != 0 || colorBand != null) { - this.applyBrightnessAndContrast(bacd, pixel); - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java deleted file mode 100644 index 94cf2af4ce..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/generating/TextureGeneratorWood.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * - * $Id: noise.c 14611 2008-04-29 08:24:33Z campbellbarton $ - * - * ***** BEGIN GPL LICENSE BLOCK ***** - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - * - * The Original Code is Copyright (C) 2001-2002 by NaN Holding BV. - * All rights reserved. - * - * The Original Code is: all of this file. - * - * Contributor(s): none yet. - * - * ***** END GPL LICENSE BLOCK ***** - * - */ -package com.jme3.scene.plugins.blender.textures.generating; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.BlenderContext; -import com.jme3.scene.plugins.blender.file.Structure; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.scene.plugins.blender.textures.generating.NoiseGenerator.NoiseFunction; -import com.jme3.texture.Image.Format; - -/** - * This class generates the 'wood' texture. - * @author Marcin Roguski (Kaelthas) - */ -public class TextureGeneratorWood extends TextureGenerator { - // tex->noisebasis2 - protected static final int TEX_SIN = 0; - protected static final int TEX_SAW = 1; - protected static final int TEX_TRI = 2; - - // tex->stype - protected static final int TEX_BAND = 0; - protected static final int TEX_RING = 1; - protected static final int TEX_BANDNOISE = 2; - protected static final int TEX_RINGNOISE = 3; - - // tex->noisetype - protected static final int TEX_NOISESOFT = 0; - protected static final int TEX_NOISEPERL = 1; - - protected WoodIntensityData woodIntensityData; - - /** - * Constructor stores the given noise generator. - * @param noiseGenerator - * the noise generator - */ - public TextureGeneratorWood(NoiseGenerator noiseGenerator) { - super(noiseGenerator, Format.Luminance8); - } - - @Override - public void readData(Structure tex, BlenderContext blenderContext) { - super.readData(tex, blenderContext); - woodIntensityData = new WoodIntensityData(tex); - } - - @Override - public void getPixel(TexturePixel pixel, float x, float y, float z) { - pixel.intensity = this.woodIntensity(woodIntensityData, x, y, z); - - if (colorBand != null) { - int colorbandIndex = (int) (pixel.intensity * 1000.0f); - pixel.red = colorBand[colorbandIndex][0]; - pixel.green = colorBand[colorbandIndex][1]; - pixel.blue = colorBand[colorbandIndex][2]; - - this.applyBrightnessAndContrast(bacd, pixel); - pixel.alpha = colorBand[colorbandIndex][3]; - } else { - this.applyBrightnessAndContrast(pixel, bacd.contrast, bacd.brightness); - } - } - - protected static WaveForm[] waveformFunctions = new WaveForm[3]; - static { - waveformFunctions[0] = new WaveForm() {// sinus (TEX_SIN) - - public float execute(float x) { - return 0.5f + 0.5f * (float) Math.sin(x); - } - }; - waveformFunctions[1] = new WaveForm() {// saw (TEX_SAW) - - public float execute(float x) { - int n = (int) (x * FastMath.INV_TWO_PI); - x -= n * FastMath.TWO_PI; - if (x < 0.0f) { - x += FastMath.TWO_PI; - } - return x * FastMath.INV_TWO_PI; - } - }; - waveformFunctions[2] = new WaveForm() {// triangle (TEX_TRI) - - public float execute(float x) { - return 1.0f - 2.0f * FastMath.abs((float) Math.floor(x * FastMath.INV_TWO_PI + 0.5f) - x * FastMath.INV_TWO_PI); - } - }; - } - - /** - * Computes basic wood intensity value at x,y,z. - * @param woodIntData - * @param x - * X coordinate of the texture pixel - * @param y - * Y coordinate of the texture pixel - * @param z - * Z coordinate of the texture pixel - * @return wood intensity at position [x, y, z] - */ - public float woodIntensity(WoodIntensityData woodIntData, float x, float y, float z) { - float result; - - switch (woodIntData.woodType) { - case TEX_BAND: - result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f); - break; - case TEX_RING: - result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f); - break; - case TEX_BANDNOISE: - if (woodIntData.noisebasis == 0) { - ++x; - ++y; - ++z; - } - result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noiseFunction, woodIntData.isHard); - result = woodIntData.waveformFunction.execute((x + y + z) * 10.0f + result); - break; - case TEX_RINGNOISE: - if (woodIntData.noisebasis == 0) { - ++x; - ++y; - ++z; - } - result = woodIntData.turbul * NoiseGenerator.NoiseFunctions.noise(x, y, z, woodIntData.noisesize, 0, woodIntData.noiseFunction, woodIntData.isHard); - result = woodIntData.waveformFunction.execute((float) Math.sqrt(x * x + y * y + z * z) * 20.0f + result); - break; - default: - result = 0; - } - return result; - } - - /** - * A class that collects the data for wood intensity calculations. - * @author Marcin Roguski (Kaelthas) - */ - private static class WoodIntensityData { - public final WaveForm waveformFunction; - public final int noisebasis; - public NoiseFunction noiseFunction; - - public final float noisesize; - public final float turbul; - public final int noiseType; - public final int woodType; - public final boolean isHard; - - public WoodIntensityData(Structure tex) { - int waveform = ((Number) tex.getFieldValue("noisebasis2")).intValue();// wave form: TEX_SIN=0, TEX_SAW=1, TEX_TRI=2 - if (waveform > TEX_TRI || waveform < TEX_SIN) { - waveform = 0; // check to be sure noisebasis2 is initialized ahead of time - } - waveformFunction = waveformFunctions[waveform]; - int noisebasis = ((Number) tex.getFieldValue("noisebasis")).intValue(); - if (noiseFunction == null) { - noiseFunction = NoiseGenerator.noiseFunctions.get(0); - noisebasis = 0; - } - this.noisebasis = noisebasis; - - woodType = ((Number) tex.getFieldValue("stype")).intValue(); - noisesize = ((Number) tex.getFieldValue("noisesize")).floatValue(); - turbul = ((Number) tex.getFieldValue("turbul")).floatValue(); - noiseType = ((Number) tex.getFieldValue("noisetype")).intValue(); - isHard = noiseType != TEX_NOISESOFT; - } - } - - protected static interface WaveForm { - - float execute(float x); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java deleted file mode 100644 index 569c328be8..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/AWTPixelInputOutput.java +++ /dev/null @@ -1,156 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.io; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image; -import java.nio.ByteBuffer; -import jme3tools.converters.RGB565; - -/** - * Implemens read/write operations for AWT images. - * @author Marcin Roguski (Kaelthas) - */ -/* package */class AWTPixelInputOutput implements PixelInputOutput { - public void read(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch (image.getFormat()) { - case RGBA8: - pixel.fromARGB8(data.get(index + 3), data.get(index), data.get(index + 1), data.get(index + 2)); - break; - case ARGB8: - pixel.fromARGB8(data.get(index), data.get(index + 1), data.get(index + 2), data.get(index + 3)); - break; - case ABGR8: - pixel.fromARGB8(data.get(index), data.get(index + 3), data.get(index + 2), data.get(index + 1)); - break; - case BGR8: - pixel.fromARGB8((byte) 0xFF, data.get(index + 2), data.get(index + 1), data.get(index)); - break; - case BGRA8: - pixel.fromARGB8(data.get(index + 3), data.get(index + 2), data.get(index + 1), data.get(index)); - break; - case RGB8: - pixel.fromARGB8((byte) 0xFF, data.get(index), data.get(index + 1), data.get(index + 2)); - break; - case RGB565: - pixel.fromARGB8(RGB565.RGB565_to_ARGB8(data.getShort(index))); - break; - case RGB5A1: - short rgb5a1 = data.getShort(index); - byte a = (byte) (rgb5a1 & 0x01); - int r = (rgb5a1 & 0xf800) >> 11 << 3; - int g = (rgb5a1 & 0x07c0) >> 6 << 3; - int b = (rgb5a1 & 0x001f) >> 1 << 3; - pixel.fromARGB8(a == 1 ? (byte) 255 : 0, (byte) r, (byte) g, (byte) b); - break; - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - pixel.fromARGB(1, FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); - break; - case RGBA16F: - pixel.fromARGB(FastMath.convertHalfToFloat(data.getShort(index + 6)), FastMath.convertHalfToFloat(data.getShort(index)), FastMath.convertHalfToFloat(data.getShort(index + 2)), FastMath.convertHalfToFloat(data.getShort(index + 4))); - break; - case RGBA32F: - pixel.fromARGB(Float.intBitsToFloat(data.getInt(index + 12)), Float.intBitsToFloat(data.getInt(index)), Float.intBitsToFloat(data.getInt(index + 4)), Float.intBitsToFloat(data.getInt(index + 8))); - break; - case RGB111110F:// the data is stored as 32-bit unsigned int, that is why we cast the read data to long and remove MSB-bytes to get the positive value - pixel.fromARGB(1, (float) Double.longBitsToDouble((long) data.getInt(index) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 4) & 0x00000000FFFFFFFF), (float) Double.longBitsToDouble((long) data.getInt(index + 8) & 0x00000000FFFFFFFF)); - break; - case RGB9E5:// TODO: support these - throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); - default: - throw new IllegalStateException("Unknown image format: " + image.getFormat()); - } - } - - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); - this.read(image, layer, pixel, index); - } - - public void write(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch (image.getFormat()) { - case RGBA8: - data.put(index, pixel.getR8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getB8()); - data.put(index + 3, pixel.getA8()); - break; - case ARGB8: - data.put(index, pixel.getA8()); - data.put(index + 1, pixel.getR8()); - data.put(index + 2, pixel.getG8()); - data.put(index + 3, pixel.getB8()); - break; - case ABGR8: - data.put(index, pixel.getA8()); - data.put(index + 1, pixel.getB8()); - data.put(index + 2, pixel.getG8()); - data.put(index + 3, pixel.getR8()); - break; - case BGR8: - data.put(index, pixel.getB8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getR8()); - break; - case BGRA8: - data.put(index, pixel.getB8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getR8()); - data.put(index + 3, pixel.getA8()); - break; - case RGB8: - data.put(index, pixel.getR8()); - data.put(index + 1, pixel.getG8()); - data.put(index + 2, pixel.getB8()); - break; - case RGB565: - data.putShort(RGB565.ARGB8_to_RGB565(pixel.toARGB8())); - break; - case RGB5A1: - int argb8 = pixel.toARGB8(); - short r = (short) ((argb8 & 0x00F80000) >> 8); - short g = (short) ((argb8 & 0x0000F800) >> 5); - short b = (short) ((argb8 & 0x000000F8) >> 2); - short a = (short) ((short) ((argb8 & 0xFF000000) >> 24) > 0 ? 1 : 0); - data.putShort(index, (short) (r | g | b | a)); - break; - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); - data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); - break; - case RGBA16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.red)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.green)); - data.putShort(index + 4, FastMath.convertFloatToHalf(pixel.blue)); - data.putShort(index + 6, FastMath.convertFloatToHalf(pixel.blue)); - break; - case RGB32F: - case RGB111110F:// this data is stored as 32-bit unsigned int - data.putInt(index, Float.floatToIntBits(pixel.red)); - data.putInt(index + 2, Float.floatToIntBits(pixel.green)); - data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); - break; - case RGBA32F: - data.putInt(index, Float.floatToIntBits(pixel.red)); - data.putInt(index + 2, Float.floatToIntBits(pixel.green)); - data.putInt(index + 4, Float.floatToIntBits(pixel.blue)); - data.putInt(index + 6, Float.floatToIntBits(pixel.alpha)); - break; - case RGB9E5:// TODO: support these - throw new IllegalStateException("Not supported image type for IO operations: " + image.getFormat()); - default: - throw new IllegalStateException("Unknown image format: " + image.getFormat()); - } - } - - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = (y * image.getWidth() + x) * (image.getFormat().getBitsPerPixel() >> 3); - this.write(image, layer, pixel, index); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java deleted file mode 100644 index d82f164a14..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/DDSPixelInputOutput.java +++ /dev/null @@ -1,171 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.io; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image; -import java.nio.ByteBuffer; -import jme3tools.converters.RGB565; - -/** - * Implemens read/write operations for DDS images. - * This class currently implements only read operation. - * @author Marcin Roguski (Kaelthas) - */ -/* package */class DDSPixelInputOutput implements PixelInputOutput { - /** - * For this class the index should be considered as a pixel index in AWT image format. - */ - public void read(Image image, int layer, TexturePixel pixel, int index) { - this.read(image, layer, pixel, index % image.getWidth(), index / image.getWidth()); - } - - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int xTexetlIndex = x % image.getWidth() >> 2; - int yTexelIndex = y % image.getHeight() >> 2; - int xTexelCount = image.getWidth() >> 2; - int texelIndex = yTexelIndex * xTexelCount + xTexetlIndex; - - TexturePixel[] colors = new TexturePixel[] { new TexturePixel(), new TexturePixel(), new TexturePixel(), new TexturePixel() }; - int indexes = 0; - long alphaIndexes = 0; - float[] alphas = null; - ByteBuffer data = image.getData().get(layer); - - switch (image.getFormat()) { - case DXT1: // BC1 - case DXT1A: { - data.position(texelIndex * 8); - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - if (col0 > col1) { - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - } else { - // creating color2 = 1/2color0 + 1/2color1 - colors[2].fromPixel(colors[0]); - colors[2].add(colors[1]); - colors[2].mult(0.5f); - - colors[3].fromARGB8(0); - } - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - case DXT3: {// BC2 - data.position(texelIndex * 16); - long alpha = data.getLong(); - alphas = new float[16]; - for (int i = 0; i < 16; ++i) { - alphaIndexes |= i << i * 4; - byte a = (byte) ((alpha >> i * 4 & 0x0F) << 4); - alphas[i] = a >= 0 ? a / 255.0f : 1.0f - ~a / 255.0f; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - case DXT5: {// BC3 - data.position(texelIndex * 16); - alphas = new float[8]; - alphas[0] = data.get() * 255.0f; - alphas[1] = data.get() * 255.0f; - // the casts to long must be done here because otherwise 32-bit integers would be shifetd by 32 and 40 bits which would result in improper values - alphaIndexes = (long)data.get() | (long)data.get() << 8 | (long)data.get() << 16 | (long)data.get() << 24 | (long)data.get() << 32 | (long)data.get() << 40; - if (alphas[0] > alphas[1]) {// 6 interpolated alpha values. - alphas[2] = (6 * alphas[0] + alphas[1]) / 7; - alphas[3] = (5 * alphas[0] + 2 * alphas[1]) / 7; - alphas[4] = (4 * alphas[0] + 3 * alphas[1]) / 7; - alphas[5] = (3 * alphas[0] + 4 * alphas[1]) / 7; - alphas[6] = (2 * alphas[0] + 5 * alphas[1]) / 7; - alphas[7] = (alphas[0] + 6 * alphas[1]) / 7; - } else { - alphas[2] = (4 * alphas[0] + alphas[1]) * 0.2f; - alphas[3] = (3 * alphas[0] + 2 * alphas[1]) * 0.2f; - alphas[4] = (2 * alphas[0] + 3 * alphas[1]) * 0.2f; - alphas[5] = (alphas[0] + 4 * alphas[1]) * 0.2f; - alphas[6] = 0; - alphas[7] = 1; - } - - short c0 = data.getShort(); - short c1 = data.getShort(); - int col0 = RGB565.RGB565_to_ARGB8(c0); - int col1 = RGB565.RGB565_to_ARGB8(c1); - colors[0].fromARGB8(col0); - colors[1].fromARGB8(col1); - - // creating color2 = 2/3color0 + 1/3color1 - colors[2].fromPixel(colors[0]); - colors[2].mult(2); - colors[2].add(colors[1]); - colors[2].divide(3); - - // creating color3 = 1/3color0 + 2/3color1; - colors[3].fromPixel(colors[1]); - colors[3].mult(2); - colors[3].add(colors[0]); - colors[3].divide(3); - - indexes = data.getInt();// 4-byte table with color indexes in decompressed table - break; - } - default: - throw new IllegalStateException("Unsupported decompression format."); - } - - // coordinates of the pixel in the selected texel - x = x - 4 * xTexetlIndex;// pixels are arranged from left to right - y = 3 - y - 4 * yTexelIndex;// pixels are arranged from bottom to top (that is why '3 - ...' is at the start) - - int pixelIndexInTexel = (y * 4 + x) * (int) FastMath.log(colors.length, 2); - int alphaIndexInTexel = alphas != null ? (y * 4 + x) * (int) FastMath.log(alphas.length, 2) : 0; - - // getting the pixel - int indexMask = colors.length - 1; - int colorIndex = indexes >> pixelIndexInTexel & indexMask; - float alpha = alphas != null ? alphas[(int) (alphaIndexes >> alphaIndexInTexel & 0x07)] : colors[colorIndex].alpha; - pixel.fromPixel(colors[colorIndex]); - pixel.alpha = alpha; - } - - public void write(Image image, int layer, TexturePixel pixel, int index) { - throw new UnsupportedOperationException("Cannot put the DXT pixel by index because not every index contains the pixel color!"); - } - - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - throw new UnsupportedOperationException("Writing to DDS texture pixel by pixel is not yet supported!"); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java deleted file mode 100644 index fae236edcb..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/LuminancePixelInputOutput.java +++ /dev/null @@ -1,74 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.io; - -import com.jme3.math.FastMath; -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image; -import java.nio.ByteBuffer; - -/** - * Implemens read/write operations for luminance images. - * - * @author Marcin Roguski (Kaelthas) - */ -/* package */class LuminancePixelInputOutput implements PixelInputOutput { - public void read(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - switch (image.getFormat()) { - case Luminance8: - pixel.fromIntensity(data.get(index)); - break; - case Luminance8Alpha8: - pixel.fromIntensity(data.get(index)); - pixel.setAlpha(data.get(index + 1)); - break; - case Luminance16F: - pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); - break; - case Luminance16FAlpha16F: - pixel.intensity = FastMath.convertHalfToFloat(data.getShort(index)); - pixel.alpha = FastMath.convertHalfToFloat(data.getShort(index + 2)); - break; - case Luminance32F: - pixel.intensity = Float.intBitsToFloat(data.getInt(index)); - break; - default: - throw new IllegalStateException("Unknown luminance format type."); - } - } - - public void read(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = y * image.getWidth() + x; - this.read(image, layer, pixel, index); - } - - public void write(Image image, int layer, TexturePixel pixel, int index) { - ByteBuffer data = image.getData(layer); - data.put(index, pixel.getInt()); - switch (image.getFormat()) { - case Luminance8: - data.put(index, pixel.getInt()); - break; - case Luminance8Alpha8: - data.put(index, pixel.getInt()); - data.put(index + 1, pixel.getA8()); - break; - case Luminance16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); - break; - case Luminance16FAlpha16F: - data.putShort(index, FastMath.convertFloatToHalf(pixel.intensity)); - data.putShort(index + 2, FastMath.convertFloatToHalf(pixel.alpha)); - break; - case Luminance32F: - data.putInt(index, Float.floatToIntBits(pixel.intensity)); - break; - default: - throw new IllegalStateException("Unknown luminance format type."); - } - } - - public void write(Image image, int layer, TexturePixel pixel, int x, int y) { - int index = y * image.getWidth() + x; - this.write(image, layer, pixel, index); - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java deleted file mode 100644 index 47cf7090b9..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelIOFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.io; - -import com.jme3.texture.Image.Format; -import java.util.HashMap; -import java.util.Map; - -/** - * This class creates a pixel IO object for the specified image format. - * - * @author Marcin Roguski (Kaelthas) - */ -public class PixelIOFactory { - private static final Map PIXEL_INPUT_OUTPUT = new HashMap(); - - /** - * This method returns pixel IO object for the specified format. - * - * @param format - * the format of the image - * @return pixel IO object - */ - public static PixelInputOutput getPixelIO(Format format) { - PixelInputOutput result = PIXEL_INPUT_OUTPUT.get(format); - if (result == null) { - switch (format) { - case ABGR8: - case RGBA8: - case BGR8: - case BGRA8: - case RGB8: - case ARGB8: - case RGB111110F: - case RGB16F: - case RGB16F_to_RGB111110F: - case RGB16F_to_RGB9E5: - case RGB32F: - case RGB565: - case RGB5A1: - case RGB9E5: - case RGBA16F: - case RGBA32F: - result = new AWTPixelInputOutput(); - break; - case Luminance8: - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - case Luminance8Alpha8: - result = new LuminancePixelInputOutput(); - break; - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - result = new DDSPixelInputOutput(); - break; - default: - throw new IllegalStateException("Unsupported image format for IO operations: " + format); - } - synchronized (PIXEL_INPUT_OUTPUT) { - PIXEL_INPUT_OUTPUT.put(format, result); - } - } - return result; - } -} diff --git a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java b/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java deleted file mode 100644 index 3ff5920474..0000000000 --- a/jme3-blender/src/main/java/com/jme3/scene/plugins/blender/textures/io/PixelInputOutput.java +++ /dev/null @@ -1,65 +0,0 @@ -package com.jme3.scene.plugins.blender.textures.io; - -import com.jme3.scene.plugins.blender.textures.TexturePixel; -import com.jme3.texture.Image; - -/** - * Implemens read/write operations for images. - * - * @author Marcin Roguski (Kaelthas) - */ -public interface PixelInputOutput { - /** - * This method reads a pixel that starts at the given index. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param index - * the index where the pixel begins in the image data - */ - void read(Image image, int layer, TexturePixel pixel, int index); - - /** - * This method reads a pixel that is located at the given position on the - * image. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param x - * the X coordinate of the pixel - * @param y - * the Y coordinate of the pixel - */ - void read(Image image, int layer, TexturePixel pixel, int x, int y); - - /** - * This method writes a pixel that starts at the given index. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param index - * the index where the pixel begins in the image data - */ - void write(Image image, int layer, TexturePixel pixel, int index); - - /** - * This method writes a pixel that is located at the given position on the - * image. - * - * @param image - * the image we read pixel from - * @param pixel - * the pixel where the result is stored - * @param x - * the X coordinate of the pixel - * @param y - * the Y coordinate of the pixel - */ - void write(Image image, int layer, TexturePixel pixel, int x, int y); -} diff --git a/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat b/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat deleted file mode 100644 index 81fea0b612..0000000000 Binary files a/jme3-blender/src/main/resources/com/jme3/scene/plugins/blender/textures/generating/noiseconstants.dat and /dev/null differ diff --git a/jme3-bullet-native-android/build.gradle b/jme3-bullet-native-android/build.gradle deleted file mode 100644 index fecb39b39f..0000000000 --- a/jme3-bullet-native-android/build.gradle +++ /dev/null @@ -1,131 +0,0 @@ -String jmeBulletNativeProjectPath = project(":jme3-bullet-native").projectDir - -String localUnzipPath = jmeBulletNativeProjectPath -String localZipFile = jmeBulletNativeProjectPath + File.separator + bulletZipFile -String localZipFolder = jmeBulletNativeProjectPath + File.separator + bulletFolder -String bulletSrcPath = localZipFolder + File.separator + 'src' - -String jmeAndroidPath = 'src/native/android' -String jmeCppPath = jmeBulletNativeProjectPath + '/src/native/cpp' - -//Working directories for the ndk build. -String ndkWorkingPath = "${buildDir}" + '/bullet' -String jniPath = ndkWorkingPath + '/jni' -String ndkOutputPath = ndkWorkingPath + '/libs' - -//Pre-compiled libs directory -def rootPath = rootProject.projectDir.absolutePath -String bulletPreCompiledLibsDir = rootPath + File.separator + 'build' + File.separator + 'native' + File.separator + 'android' + File.separator + 'bullet' - -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -dependencies { - compile project(':jme3-bullet') -} - -// Java source sets for IDE access and source jar bundling / mavenization -sourceSets { - main { - java { - srcDir jmeCppPath - srcDir jmeAndroidPath - } - } -} - -// Download bullet if not available -task downloadBullet(type: MyDownload) { - sourceUrl = bulletUrl - target = file(localZipFile) -} - -// Unzip bullet if not available -task unzipBullet(type: Copy) { - from zipTree(localZipFile) - into file(localUnzipPath) -} - -unzipBullet.dependsOn { - if (!file(localZipFile).exists()) { - downloadBullet - } -} - -// Copy Bullet files to jni directory -task copyBullet(type: Copy) { - from file(bulletSrcPath) - into file(jniPath) -} - -copyBullet.dependsOn { - if (!file(localZipFolder).isDirectory()) { - unzipBullet - } -} - -// Copy jME cpp native files to jni directory -task copyJmeCpp(type: Copy) { - from file(jmeCppPath) - into file(jniPath) -} - -// Copy jME android native files to jni directory -task copyJmeAndroid(type: Copy) { - from file(jmeAndroidPath) - into file(jniPath) -} - -task buildBulletNativeLib(type: Exec, dependsOn: [copyJmeAndroid, ':jme3-bullet:compileJava', copyJmeCpp, copyBullet]) { -// args 'TARGET_PLATFORM=android-9' -// println "buildBulletNativeLib ndkWorkingPath: " + ndkWorkingPath -// println "buildBulletNativeLib rootProject.ndkCommandPath: " + rootProject.ndkCommandPath - workingDir ndkWorkingPath - executable rootProject.ndkCommandPath - args "-j" + Runtime.runtime.availableProcessors() -} - -/* The following two tasks: We store a prebuilt version in the repository, so nobody has to build - * natives in order to build the engine. When building these natives however, the prebuilt libraries - * can be updated (which is what the CI does). That's what the following two tasks do - */ - -task updatePreCompiledBulletLibs(type: Copy, dependsOn: buildBulletNativeLib) { - from file(ndkOutputPath) - into file(bulletPreCompiledLibsDir) -} - -// Copy pre-compiled libs to build directory (when not building new libs) -task copyPreCompiledBulletLibs(type: Copy) { - from file(bulletPreCompiledLibsDir) - into file(ndkOutputPath) -} - -// 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 { updatePreCompiledBulletLibs } -} else { - // use pre-compiled native libs (not building new ones) - compileJava.dependsOn { copyPreCompiledBulletLibs } -} - -jar.into("lib") { from ndkOutputPath } - - -// Helper class to wrap ant download task -class MyDownload extends DefaultTask { - @Input - String sourceUrl - - @OutputFile - File target - - @TaskAction - void download() { - ant.get(src: sourceUrl, dest: target) - } -} - diff --git a/jme3-bullet-native-android/src/native/android/Android.mk b/jme3-bullet-native-android/src/native/android/Android.mk deleted file mode 100644 index d04478e285..0000000000 --- a/jme3-bullet-native-android/src/native/android/Android.mk +++ /dev/null @@ -1,77 +0,0 @@ -# /* -# Bullet Continuous Collision Detection and Physics Library for Android NDK -# Copyright (c) 2006-2009 Noritsuna Imamura http://www.siprop.org/ -# -# This software is provided 'as-is', without any express or implied warranty. -# In no event will the authors be held liable for any damages arising from the use of this software. -# Permission is granted to anyone to use this software for any purpose, -# including commercial applications, and to alter it and redistribute it freely, -# subject to the following restrictions: -# -# 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required. -# 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software. -# 3. This notice may not be removed or altered from any source distribution. -# */ -LOCAL_PATH:= $(call my-dir) -BULLET_PATH:= ${LOCAL_PATH}/../ - -include $(CLEAR_VARS) - -LOCAL_MODULE := bulletjme -LOCAL_C_INCLUDES := $(BULLET_PATH)/\ - $(BULLET_PATH)/BulletCollision\ - $(BULLET_PATH)/BulletCollision/BroadphaseCollision\ - $(BULLET_PATH)/BulletCollision/CollisionDispatch\ - $(BULLET_PATH)/BulletCollision/CollisionShapes\ - $(BULLET_PATH)/BulletCollision/NarrowPhaseCollision\ - $(BULLET_PATH)/BulletCollision/Gimpact\ - $(BULLET_PATH)/BulletDynamics\ - $(BULLET_PATH)/BulletDynamics/ConstraintSolver\ - $(BULLET_PATH)/BulletDynamics/Dynamics\ - $(BULLET_PATH)/BulletDynamics/Vehicle\ - $(BULLET_PATH)/BulletDynamics/Character\ - $(BULLET_PATH)/BulletMultiThreaded\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers\ - $(BULLET_PATH)/BulletMultiThreaded/SpuNarrowPhaseCollisionTask\ - $(BULLET_PATH)/BulletMultiThreaded/SpuSampleTask\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/CPU\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/DX11/HLSL\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/AMD\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/Apple\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/MiniCL\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/NVidia\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC\ - $(BULLET_PATH)/BulletMultiThreaded/GpuSoftBodySolvers/OpenCL/OpenCLC10\ - $(BULLET_PATH)/LinearMath\ - $(BULLET_PATH)/BulletSoftBody\ - $(BULLET_PATH)/LinearMath\ - $(BULLET_PATH)/MiniCL\ - $(BULLET_PATH)/MiniCL/MiniCLTask\ - $(BULLET_PATH)/vectormath\ - $(BULLET_PATH)/vectormath/scalar\ - $(BULLET_PATH)/vectormath/sse\ - $(BULLET_PATH)/vectormath/neon - -#ARM mode more performant than thumb for old armeabi -ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI), armeabi)) -LOCAL_ARM_MODE := arm -endif - -#Enable neon for armv7 -ifeq ($(TARGET_ARCH_ABI),$(filter $(TARGET_ARCH_ABI), armeabi-v7a)) -LOCAL_ARM_NEON := true -endif - -LOCAL_CFLAGS := $(LOCAL_C_INCLUDES:%=-I%) -LOCAL_LDLIBS := -L$(SYSROOT)/usr/lib -ldl -lm -llog - -FILE_LIST := $(wildcard $(LOCAL_PATH)/*.cpp) -FILE_LIST += $(wildcard $(LOCAL_PATH)/**/*.cpp) -FILE_LIST += $(wildcard $(LOCAL_PATH)/**/**/*.cpp) -FILE_LIST := $(filter-out $(wildcard $(LOCAL_PATH)/Bullet3OpenCL/**/*.cpp), $(FILE_LIST)) -FILE_LIST := $(filter-out $(wildcard $(LOCAL_PATH)/*All.cpp), $(FILE_LIST)) -LOCAL_SRC_FILES := $(FILE_LIST:$(LOCAL_PATH)/%=%) - -include $(BUILD_SHARED_LIBRARY) diff --git a/jme3-bullet-native-android/src/native/android/Application.mk b/jme3-bullet-native-android/src/native/android/Application.mk deleted file mode 100644 index 6467a5a7f9..0000000000 --- a/jme3-bullet-native-android/src/native/android/Application.mk +++ /dev/null @@ -1,7 +0,0 @@ -APP_OPTIM := release -APP_ABI := all -# Used to be stlport_static, but that has been removed. -APP_STL := c++_static -APP_MODULES := bulletjme -APP_CFLAGS += -funroll-loops -Ofast - diff --git a/jme3-bullet-native/build.gradle b/jme3-bullet-native/build.gradle deleted file mode 100644 index 745630a3b8..0000000000 --- a/jme3-bullet-native/build.gradle +++ /dev/null @@ -1,276 +0,0 @@ -apply plugin: 'cpp' - -import java.nio.file.Paths - -def rootPath = rootProject.projectDir.absolutePath - -String bulletSrcPath = bulletFolder + '/src' - -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -dependencies { - compile project(':jme3-bullet') -} -clean { dependsOn 'cleanHeaders', 'cleanUnzipped' } - -// clean up auto-generated C++ headers -task cleanHeaders(type: Delete) { - delete fileTree(dir: 'src/native/cpp', include: 'com_jme3_bullet_*.h') -} -// clean up unzipped files -task cleanUnzipped(type: Delete) { - delete bulletFolder -} -// clean up the downloaded archive -task cleanZipFile(type: Delete) { - delete bulletZipFile -} - -model { - components { - bulletjme(NativeLibrarySpec) { - - - String[] targets=[ - "Windows64", - "Windows32", - "Mac64", - "Linux64", - "Linux32", - "LinuxArm", - "LinuxArmHF", - "LinuxArm64" - ]; - - String[] filter=gradle.rootProject.ext.usePrebuildNatives==true?null:buildForPlatforms.split(","); - if(filter==null)println("No filter set. build for all"); - for(String target:targets){ - if(filter==null){ - targetPlatform(target); - }else{ - for(String f:filter){ - if(f.equals(target)){ - targetPlatform(target); - break; - } - } - } - } - - sources { - cpp { - source { - srcDir 'src/native/cpp' - srcDir bulletSrcPath - exclude 'Bullet3Collision/**' - exclude 'Bullet3Dynamics/**' - exclude 'Bullet3Geometry/**' - exclude 'Bullet3OpenCL/**' - exclude 'Bullet3Serialize/**' - include '**/*.cpp' - exclude '**/*All.cpp' - } - exportedHeaders { - srcDir 'src/native/cpp' - srcDir bulletSrcPath - exclude 'Bullet3Collision/**' - exclude 'Bullet3Dynamics/**' - exclude 'Bullet3Geometry/**' - exclude 'Bullet3OpenCL/**' - exclude 'Bullet3Serialize/**' - include '**/*.h' - } - } - } - } - } - - - toolChains { - visualCpp(VisualCpp) - gcc(Gcc) - clang(Clang) - gccArm(Gcc) { - // Fun Fact: Gradle uses gcc as linker frontend, so we don't specify ld directly here - target("LinuxArm"){ - path "/usr/bin" - cCompiler.executable = "arm-linux-gnueabi-gcc-8" - cppCompiler.executable = "arm-linux-gnueabi-g++-8" - linker.executable = "arm-linux-gnueabi-gcc-8" - assembler.executable = "arm-linux-gnueabi-as" - } - - target("LinuxArmHF"){ - path "/usr/bin" - cCompiler.executable = "arm-linux-gnueabihf-gcc-8" - cppCompiler.executable = "arm-linux-gnueabihf-g++-8" - linker.executable = "arm-linux-gnueabihf-gcc-8" - assembler.executable = "arm-linux-gnueabihf-as" - } - - target("LinuxArm64"){ - path "/usr/bin" - cCompiler.executable = "aarch64-linux-gnu-gcc-8" - cppCompiler.executable = "aarch64-linux-gnu-g++-8" - linker.executable = "aarch64-linux-gnu-gcc-8" - assembler.executable = "aarch64-linux-gnu-as" - } - } - } - - binaries { - withType(SharedLibraryBinarySpec) { - def javaHome = org.gradle.internal.jvm.Jvm.current().javaHome - def os = targetPlatform.operatingSystem.name - def arch = targetPlatform.architecture.name - def fileName = sharedLibraryFile.name - - // Gradle decided to change underscores to dashes - fix that. - arch = arch.replaceAll('-', '_') - - // For all binaries that can't be built on the current system - if (buildNativeProjects != "true") buildable = false - - if (buildable) { - cppCompiler.define('BT_NO_PROFILE') - if (toolChain in VisualCpp) { - cppCompiler.args "/I$javaHome\\include" - } else{ - cppCompiler.args '-I', "$javaHome/include" - } - - if (os == "osx") { - cppCompiler.args '-I', "$javaHome/include/darwin" - cppCompiler.args "-O3" - cppCompiler.args "-U_FORTIFY_SOURCE" - } else if (os == "linux") { - cppCompiler.args "-fvisibility=hidden" - cppCompiler.args '-I', "$javaHome/include/linux" - cppCompiler.args "-fPIC" - cppCompiler.args "-O3" - cppCompiler.args "-U_FORTIFY_SOURCE" - cppCompiler.args "-fpermissive" - linker.args "-fvisibility=hidden" - } else if (os == "windows") { - if (toolChain in Gcc) { - if (toolChain.name.startsWith('mingw')) cppCompiler.args '-I', "$projectDir/src/native/cpp/fake_win32" - else cppCompiler.args '-I', "$javaHome/include/win32" - cppCompiler.args "-fpermissive" - cppCompiler.args "-static" - cppCompiler.args "-O3" - cppCompiler.args "-U_FORTIFY_SOURCE" - linker.args "-static" - linker.args "-Wl,--exclude-all-symbols" - } else if (toolChain in VisualCpp) { - cppCompiler.args "/I$javaHome\\include\\win32" - } - cppCompiler.define('WIN32') - } - tasks.all { - dependsOn unzipBulletIfNeeded - dependsOn ':jme3-bullet:compileJava' - } - - task "copyBinaryToLibs${targetPlatform.name}"(type: Copy, dependsOn: tasks) { - from sharedLibraryFile - into "${rootPath}/build/native/bullet/native/${os}/${arch}" - } - - // Add depend on copy - jar.dependsOn("copyBinaryToLibs${targetPlatform.name}") - - } - } - withType(StaticLibraryBinarySpec) { - buildable = false - } - } - - platforms { - Windows32 { - architecture "x86" - operatingSystem "windows" - } - Windows64 { - architecture "x86_64" - operatingSystem "windows" - } - Mac64 { - architecture "x86_64" - operatingSystem "osx" - } - Linux32 { - architecture "x86" - operatingSystem "linux" - } - Linux64 { - architecture "x86_64" - operatingSystem "linux" - } - LinuxArm { - architecture "arm" - operatingSystem "linux" - } - LinuxArmHF { - architecture "armhf" - operatingSystem "linux" - } - LinuxArm64 { - architecture "aarch64" - operatingSystem "linux" - } - } -} - -// Java source sets for IDE access and source jar bundling / mavenization -sourceSets { - main { - java { - srcDir 'src/native/cpp' - } - resources { - srcDir file(Paths.get(rootPath, 'build', 'native', 'bullet')) - } - } -} - -task downloadBullet(type: MyDownload) { - sourceUrl = bulletUrl - target = file(bulletZipFile) -} - -task unzipBullet(type: Copy) { - from zipTree(bulletZipFile) - into file('.') -} - -unzipBullet.dependsOn { - if (!file(bulletZipFile).exists()) { - downloadBullet - } -} - -task unzipBulletIfNeeded { -} - -unzipBulletIfNeeded.dependsOn { - if (buildNativeProjects == "true") { - unzipBullet - } -} - -// Helper class to wrap ant download task -class MyDownload extends DefaultTask { - @Input - String sourceUrl - - @OutputFile - File target - - @TaskAction - void download() { - ant.get(src: sourceUrl, dest: target) - } -} diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp deleted file mode 100644 index 4e143cb4a6..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_PhysicsSpace.cpp +++ /dev/null @@ -1,551 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "com_jme3_bullet_PhysicsSpace.h" -#include "jmePhysicsSpace.h" -#include "jmeBulletUtil.h" - -/** - * Author: Normen Hansen - */ -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: createPhysicsSpace - * Signature: (FFFFFFI)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_PhysicsSpace_createPhysicsSpace - (JNIEnv * env, jobject object, jfloat minX, jfloat minY, jfloat minZ, - jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphase, - jboolean threading) { - jmeClasses::initJavaClasses(env); - - jmePhysicsSpace* space = new jmePhysicsSpace(env, object); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space has not been created."); - return 0; - } - - space->createPhysicsSpace(minX, minY, minZ, maxX, maxY, maxZ, - broadphase, threading); - return reinterpret_cast (space); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: stepSimulation - * Signature: (JFIF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_stepSimulation - (JNIEnv * env, jobject object, jlong spaceId, jfloat tpf, jint maxSteps, - jfloat accuracy) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - space->stepSimulation(tpf, maxSteps, accuracy); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addCollisionObject - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCollisionObject - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = space; - - space->getDynamicsWorld()->addCollisionObject(collisionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeCollisionObject - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCollisionObject - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - space->getDynamicsWorld()->removeCollisionObject(collisionObject); - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = NULL; - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addRigidBody - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addRigidBody - (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btRigidBody* collisionObject = reinterpret_cast (rigidBodyId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = space; - space->getDynamicsWorld()->addRigidBody(collisionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeRigidBody - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeRigidBody - (JNIEnv * env, jobject object, jlong spaceId, jlong rigidBodyId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btRigidBody* collisionObject = reinterpret_cast (rigidBodyId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = NULL; - space->getDynamicsWorld()->removeRigidBody(collisionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addCharacterObject - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addCharacterObject - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = space; - space->getDynamicsWorld()->addCollisionObject(collisionObject, - btBroadphaseProxy::CharacterFilter, - btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter - ); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeCharacterObject - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeCharacterObject - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*) collisionObject->getUserPointer(); - userPointer -> space = NULL; - space->getDynamicsWorld()->removeCollisionObject(collisionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addAction - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addAction - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btActionInterface* actionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (actionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The action object does not exist."); - return; - } - space->getDynamicsWorld()->addAction(actionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeAction - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeAction - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btActionInterface* actionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (actionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The action object does not exist."); - return; - } - space->getDynamicsWorld()->removeAction(actionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addVehicle - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addVehicle - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btActionInterface* actionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (actionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The vehicle object does not exist."); - return; - } - space->getDynamicsWorld()->addVehicle(actionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeVehicle - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeVehicle - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btActionInterface* actionObject = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (actionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The action object does not exist."); - return; - } - space->getDynamicsWorld()->removeVehicle(actionObject); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addConstraint - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraint - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btTypedConstraint* constraint = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (constraint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The constraint object does not exist."); - return; - } - space->getDynamicsWorld()->addConstraint(constraint); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: addConstraint - * Signature: (JJZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_addConstraintC - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId, jboolean collision) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btTypedConstraint* constraint = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (constraint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The constraint object does not exist."); - return; - } - space->getDynamicsWorld()->addConstraint(constraint, collision); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: removeConstraint - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_removeConstraint - (JNIEnv * env, jobject object, jlong spaceId, jlong objectId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - btTypedConstraint* constraint = reinterpret_cast (objectId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - if (constraint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The constraint object does not exist."); - return; - } - space->getDynamicsWorld()->removeConstraint(constraint); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: setGravity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setGravity - (JNIEnv * env, jobject object, jlong spaceId, jobject vector) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - btVector3 gravity = btVector3(); - jmeBulletUtil::convert(env, vector, &gravity); - - space->getDynamicsWorld()->setGravity(gravity); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: initNativePhysics - * Signature: ()V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_initNativePhysics - (JNIEnv * env, jclass clazz) { - jmeClasses::initJavaClasses(env); - } - - /* - * Class: com_jme3_bullet_PhysicsSpace - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_finalizeNative - (JNIEnv * env, jobject object, jlong spaceId) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - return; - } - delete(space); - } - - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_rayTest_1native - (JNIEnv * env, jobject object, jobject from, jobject to, jlong spaceId, jobject resultlist, jint flags) { - - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - - struct AllRayResultCallback : public btCollisionWorld::RayResultCallback { - - AllRayResultCallback(const btVector3& rayFromWorld, const btVector3 & rayToWorld) : m_rayFromWorld(rayFromWorld), m_rayToWorld(rayToWorld) { - } - jobject resultlist; - JNIEnv* env; - btVector3 m_rayFromWorld; //used to calculate hitPointWorld from hitFraction - btVector3 m_rayToWorld; - - btVector3 m_hitNormalWorld; - btVector3 m_hitPointWorld; - - virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) { - if (normalInWorldSpace) { - m_hitNormalWorld = rayResult.m_hitNormalLocal; - } else { - m_hitNormalWorld = m_collisionObject->getWorldTransform().getBasis() * rayResult.m_hitNormalLocal; - } - m_hitPointWorld.setInterpolate3(m_rayFromWorld, m_rayToWorld, rayResult.m_hitFraction); - - jmeBulletUtil::addResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, rayResult.m_hitFraction, rayResult.m_collisionObject); - - return 1.f; - } - }; - - btVector3 native_to = btVector3(); - jmeBulletUtil::convert(env, to, &native_to); - - btVector3 native_from = btVector3(); - jmeBulletUtil::convert(env, from, &native_from); - - AllRayResultCallback resultCallback(native_from, native_to); - resultCallback.env = env; - resultCallback.resultlist = resultlist; - resultCallback.m_flags = flags; - space->getDynamicsWorld()->rayTest(native_from, native_to, resultCallback); - - return; - } - - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_sweepTest_1native - (JNIEnv * env, jobject object, jlong shapeId, jobject from, jobject to, jlong spaceId, jobject resultlist, jfloat allowedCcdPenetration) { - - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - - btCollisionShape* shape = reinterpret_cast (shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The shape does not exist."); - return; - } - - struct AllConvexResultCallback : public btCollisionWorld::ConvexResultCallback { - - AllConvexResultCallback(const btTransform& convexFromWorld, const btTransform & convexToWorld) : m_convexFromWorld(convexFromWorld), m_convexToWorld(convexToWorld) { - } - jobject resultlist; - JNIEnv* env; - btTransform m_convexFromWorld; //used to calculate hitPointWorld from hitFraction - btTransform m_convexToWorld; - - btVector3 m_hitNormalWorld; - btVector3 m_hitPointWorld; - - virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool normalInWorldSpace) { - if (normalInWorldSpace) { - m_hitNormalWorld = convexResult.m_hitNormalLocal; - } else { - m_hitNormalWorld = convexResult.m_hitCollisionObject->getWorldTransform().getBasis() * convexResult.m_hitNormalLocal; - } - m_hitPointWorld.setInterpolate3(m_convexFromWorld.getBasis() * m_convexFromWorld.getOrigin(), m_convexToWorld.getBasis() * m_convexToWorld.getOrigin(), convexResult.m_hitFraction); - - jmeBulletUtil::addSweepResult(env, resultlist, &m_hitNormalWorld, &m_hitPointWorld, convexResult.m_hitFraction, convexResult.m_hitCollisionObject); - - return 1.f; - } - }; - - btTransform native_to = btTransform(); - jmeBulletUtil::convert(env, to, &native_to); - - btTransform native_from = btTransform(); - jmeBulletUtil::convert(env, from, &native_from); - - btScalar native_allowed_ccd_penetration = btScalar(allowedCcdPenetration); - - AllConvexResultCallback resultCallback(native_from, native_to); - resultCallback.env = env; - resultCallback.resultlist = resultlist; - space->getDynamicsWorld()->convexSweepTest((btConvexShape *) shape, native_from, native_to, resultCallback, native_allowed_ccd_penetration); - return; - } - - JNIEXPORT void JNICALL Java_com_jme3_bullet_PhysicsSpace_setSolverNumIterations - (JNIEnv *env, jobject object, jlong spaceId, jint value) { - jmePhysicsSpace* space = reinterpret_cast (spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The physics space does not exist."); - return; - } - - space->getDynamicsWorld()->getSolverInfo().m_numIterations = value; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp deleted file mode 100644 index 071f0d59c8..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionEvent.cpp +++ /dev/null @@ -1,342 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#include "jmeBulletUtil.h" -#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h" -#include "com_jme3_bullet_collision_PhysicsCollisionEvent.h" - -// Change to trigger build - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getAppliedImpulse - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulse - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_appliedImpulse; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getAppliedImpulseLateral1 - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral1 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_appliedImpulseLateral1; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getAppliedImpulseLateral2 - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getAppliedImpulseLateral2 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_appliedImpulseLateral2; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getCombinedFriction - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedFriction - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_combinedFriction; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getCombinedRestitution - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getCombinedRestitution - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_combinedRestitution; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getDistance1 - * Signature: (J)F - */ -JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getDistance1 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_distance1; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getIndex0 - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex0 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_index0; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getIndex1 - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getIndex1 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_index1; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getLateralFrictionDir1 - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir1 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir1) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir1, lateralFrictionDir1); -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getLateralFrictionDir2 - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLateralFrictionDir2 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject lateralFrictionDir2) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_lateralFrictionDir2, lateralFrictionDir2); -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: isLateralFrictionInitialized - * Signature: (J)Z - */ -JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_isLateralFrictionInitialized - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return (mp -> m_contactPointFlags) & BT_CONTACT_FLAG_LATERAL_FRICTION_INITIALIZED; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getLifeTime - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLifeTime - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_lifeTime; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getLocalPointA - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointA - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointA) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_localPointA, localPointA); -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getLocalPointB - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getLocalPointB - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject localPointB) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_localPointB, localPointB); -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getNormalWorldOnB - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getNormalWorldOnB - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject normalWorldOnB) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_normalWorldOnB, normalWorldOnB); -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getPartId0 - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId0 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_partId0; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getPartId1 - * Signature: (J)I - */ -JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPartId1 - (JNIEnv * env, jobject object, jlong manifoldPointObjectId) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return 0; - } - return mp -> m_partId1; -} - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getPositionWorldOnA - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnA - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnA) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_positionWorldOnA, positionWorldOnA); -} - - -/* - * Class: com_jme3_bullet_collision_PhysicsCollisionEvent - * Method: getPositionWorldOnB - * Signature: (JLcom/jme3/math/Vector3f;)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionEvent_getPositionWorldOnB - (JNIEnv * env, jobject object, jlong manifoldPointObjectId, jobject positionWorldOnB) { - btManifoldPoint* mp = reinterpret_cast(manifoldPointObjectId); - if (mp == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The manifoldPoint does not exist."); - return; - } - jmeBulletUtil::convert(env, &mp -> m_positionWorldOnB, positionWorldOnB); -} diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp deleted file mode 100644 index 15d27849ce..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_PhysicsCollisionObject.cpp +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (c) 2009-2018 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. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_PhysicsCollisionObject.h" -#include "jmeBulletUtil.h" -#include "jmePhysicsSpace.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: attachCollisionShape - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_attachCollisionShape - (JNIEnv * env, jobject object, jlong objectId, jlong shapeId) { - btCollisionObject* collisionObject = reinterpret_cast(objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/IllegalStateException"); - env->ThrowNew(newExc, "The collision object does not exist."); - return; - } - btCollisionShape* collisionShape = reinterpret_cast(shapeId); - if (collisionShape == NULL) { - jclass newExc = env->FindClass("java/lang/IllegalStateException"); - env->ThrowNew(newExc, "The collision shape does not exist."); - return; - } - collisionObject->setCollisionShape(collisionShape); - } - - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_finalizeNative - (JNIEnv * env, jobject object, jlong objectId) { - btCollisionObject* collisionObject = reinterpret_cast(objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - if (collisionObject -> getUserPointer() != NULL){ - jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); - delete(userPointer); - } - delete(collisionObject); - } - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: initUserPointer - * Signature: (JII)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_initUserPointer - (JNIEnv *env, jobject object, jlong objectId, jint group, jint groups) { - btCollisionObject* collisionObject = reinterpret_cast(objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); - if (userPointer != NULL) { -// delete(userPointer); - } - userPointer = new jmeUserPointer(); - userPointer -> javaCollisionObject = env->NewWeakGlobalRef(object); - userPointer -> group = group; - userPointer -> groups = groups; - userPointer -> space = NULL; - collisionObject -> setUserPointer(userPointer); - } - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: setCollisionGroup - * Signature: (JI)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionGroup - (JNIEnv *env, jobject object, jlong objectId, jint group) { - btCollisionObject* collisionObject = reinterpret_cast(objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); - if (userPointer != NULL){ - userPointer -> group = group; - } - } - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: setCollideWithGroups - * Signature: (JI)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollideWithGroups - (JNIEnv *env, jobject object, jlong objectId, jint groups) { - btCollisionObject* collisionObject = reinterpret_cast(objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeUserPointer *userPointer = (jmeUserPointer*)collisionObject->getUserPointer(); - if (userPointer != NULL){ - userPointer -> groups = groups; - } - } - - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: getCollisionFlags - * Signature: (J)I - */ - JNIEXPORT jint JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_getCollisionFlags - (JNIEnv *env, jobject object, jlong objectId) { - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - - jint result = collisionObject->getCollisionFlags(); - return result; - } - - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: setCollisionFlags - * Signature: (JI)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_setCollisionFlags - (JNIEnv *env, jobject object, jlong objectId, jint desiredFlags) { - btCollisionObject* collisionObject = reinterpret_cast (objectId); - if (collisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - - collisionObject->setCollisionFlags(desiredFlags); - } - - /* - * Class: com_jme3_bullet_collision_PhysicsCollisionObject - * Method: getDeactivationTime - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_PhysicsCollisionObject_getDeactivationTime - (JNIEnv *env, jobject object, jlong pcoId) { - btCollisionObject *pCollisionObject - = reinterpret_cast (pcoId); - if (pCollisionObject == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - - jfloat result = pCollisionObject->getDeactivationTime(); - return result; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp deleted file mode 100644 index d5af179307..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_BoxCollisionShape.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_BoxCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_BoxCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_BoxCollisionShape_createShape - (JNIEnv *env, jobject object, jobject halfExtents) { - jmeClasses::initJavaClasses(env); - btVector3 extents = btVector3(); - jmeBulletUtil::convert(env, halfExtents, &extents); - btBoxShape* shape = new btBoxShape(extents); - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp deleted file mode 100644 index fd3957ba13..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CapsuleCollisionShape.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_CapsuleCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_CapsuleCollisionShape - * Method: createShape - * Signature: (IFF)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CapsuleCollisionShape_createShape - (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) { - jmeClasses::initJavaClasses(env); - btCollisionShape* shape; - switch(axis){ - case 0: - shape = new btCapsuleShapeX(radius, height); - break; - case 1: - shape = new btCapsuleShape(radius, height); - break; - case 2: - shape = new btCapsuleShapeZ(radius, height); - break; - } - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp deleted file mode 100644 index bfdaa20de6..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CollisionShape.cpp +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_CollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_CollisionShape - * Method: getMargin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_getMargin - (JNIEnv * env, jobject object, jlong shapeId) { - btCollisionShape* shape = reinterpret_cast(shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return shape->getMargin(); - } - - /* - * Class: com_jme3_bullet_collision_shapes_CollisionShape - * Method: setLocalScaling - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setLocalScaling - (JNIEnv * env, jobject object, jlong shapeId, jobject scale) { - btCollisionShape* shape = reinterpret_cast(shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 scl = btVector3(); - jmeBulletUtil::convert(env, scale, &scl); - shape->setLocalScaling(scl); - } - - /* - * Class: com_jme3_bullet_collision_shapes_CollisionShape - * Method: setMargin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_setMargin - (JNIEnv * env, jobject object, jlong shapeId, jfloat newMargin) { - btCollisionShape* shape = reinterpret_cast(shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - shape->setMargin(newMargin); - } - - /* - * Class: com_jme3_bullet_collision_shapes_CollisionShape - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_finalizeNative - (JNIEnv * env, jobject object, jlong shapeId) { - btCollisionShape* shape = reinterpret_cast(shapeId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(shape); - } - - /* - * Class: com_jme3_bullet_collision_shapes_CollisionShape - * Method: isNonMoving - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_collision_shapes_CollisionShape_isNonMoving - (JNIEnv *env, jobject object, jlong shapeId) { - btCollisionShape *pShape - = reinterpret_cast (shapeId); - if (pShape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - - return pShape->isNonMoving(); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp deleted file mode 100644 index c3b07f45d4..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CompoundCollisionShape.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_CompoundCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape - * Method: createShape - * Signature: ()J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_createShape - (JNIEnv *env, jobject object) { - jmeClasses::initJavaClasses(env); - btCompoundShape* shape = new btCompoundShape(); - return reinterpret_cast(shape); - } - - /* - * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape - * Method: addChildShape - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_addChildShape - (JNIEnv *env, jobject object, jlong compoundId, jlong childId, jobject childLocation, jobject childRotation) { - btCompoundShape* shape = reinterpret_cast(compoundId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btCollisionShape* child = reinterpret_cast(childId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btMatrix3x3 mtx = btMatrix3x3(); - btTransform trans = btTransform(mtx); - jmeBulletUtil::convert(env, childLocation, &trans.getOrigin()); - jmeBulletUtil::convert(env, childRotation, &trans.getBasis()); - shape->addChildShape(trans, child); - return 0; - } - - /* - * Class: com_jme3_bullet_collision_shapes_CompoundCollisionShape - * Method: removeChildShape - * Signature: (JJ)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CompoundCollisionShape_removeChildShape - (JNIEnv * env, jobject object, jlong compoundId, jlong childId) { - btCompoundShape* shape = reinterpret_cast(compoundId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btCollisionShape* child = reinterpret_cast(childId); - if (shape == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - shape->removeChildShape(child); - return 0; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp deleted file mode 100644 index d218975b08..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_ConeCollisionShape.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_ConeCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_ConeCollisionShape - * Method: createShape - * Signature: (IFF)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_ConeCollisionShape_createShape - (JNIEnv * env, jobject object, jint axis, jfloat radius, jfloat height) { - jmeClasses::initJavaClasses(env); - btCollisionShape* shape; - switch (axis) { - case 0: - shape = new btConeShapeX(radius, height); - break; - case 1: - shape = new btConeShape(radius, height); - break; - case 2: - shape = new btConeShapeZ(radius, height); - break; - } - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp deleted file mode 100644 index fbab5baf7c..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_CylinderCollisionShape.cpp +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_CylinderCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_CylinderCollisionShape - * Method: createShape - * Signature: (ILcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_CylinderCollisionShape_createShape - (JNIEnv * env, jobject object, jint axis, jobject halfExtents) { - jmeClasses::initJavaClasses(env); - btVector3 extents = btVector3(); - jmeBulletUtil::convert(env, halfExtents, &extents); - btCollisionShape* shape; - switch (axis) { - case 0: - shape = new btCylinderShapeX(extents); - break; - case 1: - shape = new btCylinderShape(extents); - break; - case 2: - shape = new btCylinderShapeZ(extents); - break; - } - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp deleted file mode 100644 index 71a3d3742b..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_GImpactCollisionShape.cpp +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_GImpactCollisionShape.h" -#include "jmeBulletUtil.h" -#include - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape - * Method: createShape - * Signature: (J)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_createShape - (JNIEnv * env, jobject object, jlong meshId) { - jmeClasses::initJavaClasses(env); - btTriangleIndexVertexArray* array = reinterpret_cast(meshId); - btGImpactMeshShape* shape = new btGImpactMeshShape(array); - shape->updateBound(); - return reinterpret_cast(shape); - } - - /* - * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape - * Method: recalcAabb - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_recalcAabb - (JNIEnv *env, jobject object, jlong shapeId) { - btGImpactMeshShape *pShape - = reinterpret_cast (shapeId); - pShape->updateBound(); - } - - /* - * Class: com_jme3_bullet_collision_shapes_GImpactCollisionShape - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_GImpactCollisionShape_finalizeNative - (JNIEnv * env, jobject object, jlong meshId) { - btTriangleIndexVertexArray* array = reinterpret_cast (meshId); - delete(array); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp deleted file mode 100644 index c217e3a2d6..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_HeightfieldCollisionShape.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionShapes/btHeightfieldTerrainShape.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_HeightfieldCollisionShape - * Method: createShape - * Signature: (II[FFFFIZ)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HeightfieldCollisionShape_createShape - (JNIEnv * env, jobject object, jint heightStickWidth, jint heightStickLength, jobject heightfieldData, jfloat heightScale, jfloat minHeight, jfloat maxHeight, jint upAxis, jboolean flipQuadEdges) { - jmeClasses::initJavaClasses(env); - void* data = env->GetDirectBufferAddress(heightfieldData); - btHeightfieldTerrainShape* shape=new btHeightfieldTerrainShape(heightStickWidth, heightStickLength, data, heightScale, minHeight, maxHeight, upAxis, PHY_FLOAT, flipQuadEdges); - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp deleted file mode 100644 index 604f5a0e9b..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_HullCollisionShape.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2009-2019 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. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_HullCollisionShape.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionShapes/btConvexHullShape.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_HullCollisionShape - * Method: createShape - * Signature: ([F)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_HullCollisionShape_createShape - (JNIEnv *env, jobject object, jobject array) { - jmeClasses::initJavaClasses(env); - float* data = (float*) env->GetDirectBufferAddress(array); - //TODO: capacity will not always be length! - int length = env->GetDirectBufferCapacity(array)/4; - btConvexHullShape* shape = new btConvexHullShape(); - for (int i = 0; i < length; i+=3) { - btVector3 vect = btVector3(data[i], - data[i + 1], - data[i + 2]); - - shape->addPoint(vect); - } - - shape->optimizeConvexHull(); - - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp deleted file mode 100644 index a5eabcb3cd..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_MeshCollisionShape.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** -* Author: Normen Hansen -*/ -#include "com_jme3_bullet_collision_shapes_MeshCollisionShape.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionShapes/btBvhTriangleMeshShape.h" -#include "btBulletDynamicsCommon.h" -#include "BulletCollision/Gimpact/btGImpactShape.h" - - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape - * Method: createShape - * Signature: (J)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_createShape - (JNIEnv* env, jobject object,jboolean isMemoryEfficient,jboolean buildBVH, jlong arrayId) { - jmeClasses::initJavaClasses(env); - btTriangleIndexVertexArray* array = reinterpret_cast(arrayId); - btBvhTriangleMeshShape* shape = new btBvhTriangleMeshShape(array, isMemoryEfficient, buildBVH); - return reinterpret_cast(shape); - } - - JNIEXPORT jbyteArray JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_saveBVH(JNIEnv* env, jobject, jlong meshobj){ - btBvhTriangleMeshShape* mesh = reinterpret_cast(meshobj); - btOptimizedBvh* bvh = mesh->getOptimizedBvh(); - unsigned int ssize = bvh->calculateSerializeBufferSize(); - char* buffer = (char*)btAlignedAlloc(ssize, 16); - bool success = bvh->serialize(buffer, ssize, true); - if(!success){ - jclass newExc = env->FindClass("java/lang/RuntimeException"); - env->ThrowNew(newExc, "Unableto Serialize, native error reported"); - } - - jbyteArray byteArray = env->NewByteArray(ssize); - env->SetByteArrayRegion(byteArray, 0, ssize , (jbyte*) buffer); - btAlignedFree(buffer); - return byteArray; - }; - - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_setBVH(JNIEnv* env, jobject,jbyteArray bytearray,jlong meshobj){ - int len = env->GetArrayLength (bytearray); - void* buffer = btAlignedAlloc(len, 16); - env->GetByteArrayRegion (bytearray, 0, len, reinterpret_cast(buffer)); - - btOptimizedBvh* bhv = btOptimizedBvh::deSerializeInPlace(buffer, len, true); - btBvhTriangleMeshShape* mesh = reinterpret_cast(meshobj); - mesh->setOptimizedBvh(bhv); - return reinterpret_cast(buffer); - }; - - /* - * Class: com_jme3_bullet_collision_shapes_MeshCollisionShape - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_collision_shapes_MeshCollisionShape_finalizeNative - (JNIEnv* env, jobject object, jlong arrayId,jlong nativeBVHBuffer){ - btTriangleIndexVertexArray* array = reinterpret_cast(arrayId); - delete(array); - if (nativeBVHBuffer > 0) { - void* buffer = reinterpret_cast(nativeBVHBuffer); - btAlignedFree(buffer); - } - } - - -#ifdef __cplusplus -} -#endif - diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp deleted file mode 100644 index 6362516ad1..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_PlaneCollisionShape.cpp +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_PlaneCollisionShape.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionShapes/btStaticPlaneShape.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_PlaneCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;F)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_PlaneCollisionShape_createShape - (JNIEnv * env, jobject object, jobject normal, jfloat constant) { - jmeClasses::initJavaClasses(env); - btVector3 norm = btVector3(); - jmeBulletUtil::convert(env, normal, &norm); - btStaticPlaneShape* shape = new btStaticPlaneShape(norm, constant); - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp deleted file mode 100644 index 43a4ddf8a7..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SimplexCollisionShape.cpp +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_SimplexCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2 - (JNIEnv *env, jobject object, jobject vector1) { - jmeClasses::initJavaClasses(env); - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, vector1, &vec1); - btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1); - return reinterpret_cast(simplexShape); - } - - /* - * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 - (JNIEnv *env, jobject object, jobject vector1, jobject vector2) { - jmeClasses::initJavaClasses(env); - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, vector1, &vec1); - btVector3 vec2 = btVector3(); - jmeBulletUtil::convert(env, vector2, &vec2); - btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2); - return reinterpret_cast(simplexShape); - } - /* - * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 - (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3) { - jmeClasses::initJavaClasses(env); - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, vector1, &vec1); - btVector3 vec2 = btVector3(); - jmeBulletUtil::convert(env, vector2, &vec2); - btVector3 vec3 = btVector3(); - jmeBulletUtil::convert(env, vector3, &vec3); - btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3); - return reinterpret_cast(simplexShape); - } - /* - * Class: com_jme3_bullet_collision_shapes_SimplexCollisionShape - * Method: createShape - * Signature: (Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SimplexCollisionShape_createShape__Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2Lcom_jme3_math_Vector3f_2 - (JNIEnv * env, jobject object, jobject vector1, jobject vector2, jobject vector3, jobject vector4) { - jmeClasses::initJavaClasses(env); - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, vector1, &vec1); - btVector3 vec2 = btVector3(); - jmeBulletUtil::convert(env, vector2, &vec2); - btVector3 vec3 = btVector3(); - jmeBulletUtil::convert(env, vector3, &vec3); - btVector3 vec4 = btVector3(); - jmeBulletUtil::convert(env, vector4, &vec4); - btBU_Simplex1to4* simplexShape = new btBU_Simplex1to4(vec1, vec2, vec3, vec4); - return reinterpret_cast(simplexShape); - } -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp deleted file mode 100644 index e161ed9bff..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_collision_shapes_SphereCollisionShape.cpp +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_collision_shapes_SphereCollisionShape.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_collision_shapes_SphereCollisionShape - * Method: createShape - * Signature: (F)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_collision_shapes_SphereCollisionShape_createShape - (JNIEnv *env, jobject object, jfloat radius) { - jmeClasses::initJavaClasses(env); - btSphereShape* shape=new btSphereShape(radius); - return reinterpret_cast(shape); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp deleted file mode 100644 index 27c90e1d9d..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_ConeJoint.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_ConeJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_ConeJoint - * Method: setLimit - * Signature: (JFFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setLimit - (JNIEnv * env, jobject object, jlong jointId, jfloat swingSpan1, jfloat swingSpan2, jfloat twistSpan) { - btConeTwistConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - //TODO: extended setLimit! - joint->setLimit(swingSpan1, swingSpan2, twistSpan); - } - - /* - * Class: com_jme3_bullet_joints_ConeJoint - * Method: setAngularOnly - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_ConeJoint_setAngularOnly - (JNIEnv * env, jobject object, jlong jointId, jboolean angularOnly) { - btConeTwistConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setAngularOnly(angularOnly); - } - - /* - * Class: com_jme3_bullet_joints_ConeJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_ConeJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btMatrix3x3 mtx1 = btMatrix3x3(); - btMatrix3x3 mtx2 = btMatrix3x3(); - btTransform transA = btTransform(mtx1); - jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); - jmeBulletUtil::convert(env, rotA, &transA.getBasis()); - btTransform transB = btTransform(mtx2); - jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); - jmeBulletUtil::convert(env, rotB, &transB.getBasis()); - btConeTwistConstraint* joint = new btConeTwistConstraint(*bodyA, *bodyB, transA, transB); - return reinterpret_cast(joint); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp deleted file mode 100644 index 351be3ffa3..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_HingeJoint.cpp +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_HingeJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: enableMotor - * Signature: (JZFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_enableMotor - (JNIEnv * env, jobject object, jlong jointId, jboolean enable, jfloat targetVelocity, jfloat maxMotorImpulse) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->enableAngularMotor(enable, targetVelocity, maxMotorImpulse); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getEnableAngularMotor - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_HingeJoint_getEnableAngularMotor - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return joint->getEnableAngularMotor(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getMotorTargetVelocity - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMotorTargetVelocity - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getMotorTargetVelocity(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getMaxMotorImpulse - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getMaxMotorImpulse - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getMaxMotorImpulse(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: setLimit - * Signature: (JFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFF - (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - return joint->setLimit(low, high); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: setLimit - * Signature: (JFFFFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setLimit__JFFFFF - (JNIEnv * env, jobject object, jlong jointId, jfloat low, jfloat high, jfloat softness, jfloat biasFactor, jfloat relaxationFactor) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - return joint->setLimit(low, high, softness, biasFactor, relaxationFactor); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getUpperLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getUpperLimit - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getUpperLimit(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getLowerLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getLowerLimit - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getLowerLimit(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: setAngularOnly - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_HingeJoint_setAngularOnly - (JNIEnv * env, jobject object, jlong jointId, jboolean angular) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setAngularOnly(angular); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: getHingeAngle - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_HingeJoint_getHingeAngle - (JNIEnv * env, jobject object, jlong jointId) { - btHingeConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getHingeAngle(); - } - - /* - * Class: com_jme3_bullet_joints_HingeJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_HingeJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject axisA, jobject pivotB, jobject axisB) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btVector3 vec1 = btVector3(); - btVector3 vec2 = btVector3(); - btVector3 vec3 = btVector3(); - btVector3 vec4 = btVector3(); - jmeBulletUtil::convert(env, pivotA, &vec1); - jmeBulletUtil::convert(env, pivotB, &vec2); - jmeBulletUtil::convert(env, axisA, &vec3); - jmeBulletUtil::convert(env, axisB, &vec4); - btHingeConstraint* joint = new btHingeConstraint(*bodyA, *bodyB, vec1, vec2, vec3, vec4); - return reinterpret_cast(joint); - } -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp deleted file mode 100644 index b00425e88c..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_PhysicsJoint.cpp +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (c) 2009-2018 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. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_PhysicsJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_PhysicsJoint - * Method: getAppliedImpulse - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_getAppliedImpulse - (JNIEnv * env, jobject object, jlong jointId) { - btTypedConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getAppliedImpulse(); - } - - /* - * Class: com_jme3_bullet_joints_PhysicsJoint - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_PhysicsJoint_finalizeNative - (JNIEnv * env, jobject object, jlong jointId) { - btTypedConstraint* joint = reinterpret_cast (jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(joint); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp deleted file mode 100644 index 5284518788..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_Point2PointJoint.cpp +++ /dev/null @@ -1,159 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_Point2PointJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: setDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setDamping - (JNIEnv * env, jobject object, jlong jointId, jfloat damping) { - btPoint2PointConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->m_setting.m_damping = damping; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: setImpulseClamp - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setImpulseClamp - (JNIEnv * env, jobject object, jlong jointId, jfloat clamp) { - btPoint2PointConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->m_setting.m_impulseClamp = clamp; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: setTau - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_setTau - (JNIEnv * env, jobject object, jlong jointId, jfloat tau) { - btPoint2PointConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->m_setting.m_tau = tau; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: getDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getDamping - (JNIEnv * env, jobject object, jlong jointId) { - btPoint2PointConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->m_setting.m_damping; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: getImpulseClamp - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getImpulseClamp - (JNIEnv * env, jobject object, jlong jointId) { - btPoint2PointConstraint* joint = reinterpret_cast (jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->m_setting.m_impulseClamp; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: getTau - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_getTau - (JNIEnv * env, jobject object, jlong jointId) { - btPoint2PointConstraint* joint = reinterpret_cast (jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->m_setting.m_tau; - } - - /* - * Class: com_jme3_bullet_joints_Point2PointJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_Point2PointJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject pivotB) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btVector3 pivotAIn; - btVector3 pivotBIn; - jmeBulletUtil::convert(env, pivotA, &pivotAIn); - jmeBulletUtil::convert(env, pivotB, &pivotBIn); - btPoint2PointConstraint * joint = new btPoint2PointConstraint(*bodyA, *bodyB, pivotAIn, pivotBIn); - return reinterpret_cast(joint); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp deleted file mode 100644 index 24a26c0cfa..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofJoint.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_SixDofJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: getRotationalLimitMotor - * Signature: (JI)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getRotationalLimitMotor - (JNIEnv * env, jobject object, jlong jointId, jint index) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return reinterpret_cast(joint->getRotationalLimitMotor(index)); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: getTranslationalLimitMotor - * Signature: (J)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getTranslationalLimitMotor - (JNIEnv * env, jobject object, jlong jointId) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return reinterpret_cast(joint->getTranslationalLimitMotor()); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: setLinearUpperLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearUpperLimit - (JNIEnv * env, jobject object, jlong jointId, jobject vector) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - joint->setLinearUpperLimit(vec); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: setLinearLowerLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setLinearLowerLimit - (JNIEnv * env, jobject object, jlong jointId, jobject vector) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - joint->setLinearLowerLimit(vec); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: setAngularUpperLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularUpperLimit - (JNIEnv * env, jobject object, jlong jointId, jobject vector) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - joint->setAngularUpperLimit(vec); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: setAngularLowerLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_setAngularLowerLimit - (JNIEnv * env, jobject object, jlong jointId, jobject vector) { - btGeneric6DofConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - joint->setAngularLowerLimit(vec); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btMatrix3x3 mtx1 = btMatrix3x3(); - btMatrix3x3 mtx2 = btMatrix3x3(); - btTransform transA = btTransform(mtx1); - jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); - jmeBulletUtil::convert(env, rotA, &transA.getBasis()); - btTransform transB = btTransform(mtx2); - jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); - jmeBulletUtil::convert(env, rotB, &transB.getBasis()); - btGeneric6DofConstraint* joint = new btGeneric6DofConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); - return reinterpret_cast(joint); - } - - /* - * Class: com_jme3_bullet_joints_SixDofJoint - * Method: getAngles - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofJoint_getAngles - (JNIEnv *env, jobject object, jlong jointId, jobject storeVector) { - btGeneric6DofConstraint *pJoint - = reinterpret_cast (jointId); - if (pJoint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - - pJoint->calculateTransforms(); - btScalar x = pJoint->getAngle(0); - btScalar y = pJoint->getAngle(1); - btScalar z = pJoint->getAngle(2); - const btVector3& angles = btVector3(x, y, z); - jmeBulletUtil::convert(env, &angles, storeVector); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp deleted file mode 100644 index da225ee2fa..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SixDofSpringJoint.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_SixDofSpringJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: enableString - * Signature: (JIZ)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_enableSpring - (JNIEnv *env, jobject object, jlong jointId, jint index, jboolean onOff) { - btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); - joint -> enableSpring(index, onOff); -} - - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: setStiffness - * Signature: (JIF)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setStiffness - (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat stiffness) { - btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); - joint -> setStiffness(index, stiffness); -} - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: setDamping - * Signature: (JIF)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setDamping - (JNIEnv *env, jobject object, jlong jointId, jint index, jfloat damping) { - btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); - joint -> setDamping(index, damping); -} - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: setEquilibriumPoint - * Signature: (JIF)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__J - (JNIEnv *env, jobject object, jlong jointId) { - btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); - joint -> setEquilibriumPoint(); -} - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: setEquilibriumPoint - * Signature: (JI)V - */ -JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_setEquilibriumPoint__JI - (JNIEnv *env, jobject object, jlong jointId, jint index) { - btGeneric6DofSpringConstraint* joint = reinterpret_cast(jointId); - joint -> setEquilibriumPoint(index); -} - - - - -/* - * Class: com_jme3_bullet_joints_SixDofSpringJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J - */ -JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SixDofSpringJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btTransform transA; - jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); - jmeBulletUtil::convert(env, rotA, &transA.getBasis()); - btTransform transB; - jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); - jmeBulletUtil::convert(env, rotB, &transB.getBasis()); - - btGeneric6DofSpringConstraint* joint = new btGeneric6DofSpringConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); - return reinterpret_cast(joint); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp deleted file mode 100644 index 0817d79752..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_SliderJoint.cpp +++ /dev/null @@ -1,963 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_SliderJoint.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getLowerLinLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerLinLimit - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getLowerLinLimit(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setLowerLinLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerLinLimit - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setLowerLinLimit(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getUpperLinLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperLinLimit - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getUpperLinLimit(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setUpperLinLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperLinLimit - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setUpperLinLimit(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getLowerAngLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getLowerAngLimit - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getLowerAngLimit(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setLowerAngLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setLowerAngLimit - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setLowerAngLimit(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getUpperAngLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getUpperAngLimit - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getUpperAngLimit(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setUpperAngLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setUpperAngLimit - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setUpperAngLimit(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessDirLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessDirLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessDirLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessDirLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionDirLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionDirLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionDirLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionDirLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingDirLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingDirLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingDirLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingDirLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessDirAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessDirAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessDirAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessDirAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessDirAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessDirAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionDirAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionDirAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionDirAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionDirAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionDirAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionDirAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingDirAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingDirAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingDirAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingDirAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingDirAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingDirAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessLimLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessLimLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessLimLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessLimLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionLimLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionLimLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionLimLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionLimLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingLimLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingLimLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingLimLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingLimLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessLimAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessLimAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessLimAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessLimAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessLimAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessLimAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionLimAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionLimAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionLimAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionLimAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionLimAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionLimAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingLimAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingLimAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingLimAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingLimAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingLimAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingLimAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessOrthoLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessOrthoLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessOrthoLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessOrthoLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionOrthoLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionOrthoLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionOrthoLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionOrthoLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingOrthoLin - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoLin - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingOrthoLin(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingOrthoLin - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoLin - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingOrthoLin(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getSoftnessOrthoAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getSoftnessOrthoAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getSoftnessOrthoAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setSoftnessOrthoAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setSoftnessOrthoAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setSoftnessOrthoAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getRestitutionOrthoAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getRestitutionOrthoAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getRestitutionOrthoAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setRestitutionOrthoAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setRestitutionOrthoAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setRestitutionOrthoAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getDampingOrthoAng - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getDampingOrthoAng - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getDampingOrthoAng(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setDampingOrthoAng - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setDampingOrthoAng - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setDampingOrthoAng(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: isPoweredLinMotor - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredLinMotor - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return joint->getPoweredLinMotor(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setPoweredLinMotor - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredLinMotor - (JNIEnv * env, jobject object, jlong jointId, jboolean value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setPoweredLinMotor(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getTargetLinMotorVelocity - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetLinMotorVelocity - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getTargetLinMotorVelocity(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setTargetLinMotorVelocity - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetLinMotorVelocity - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setTargetLinMotorVelocity(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getMaxLinMotorForce - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxLinMotorForce - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getMaxLinMotorForce(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setMaxLinMotorForce - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxLinMotorForce - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setMaxLinMotorForce(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: isPoweredAngMotor - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_SliderJoint_isPoweredAngMotor - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return joint->getPoweredAngMotor(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setPoweredAngMotor - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setPoweredAngMotor - (JNIEnv * env, jobject object, jlong jointId, jboolean value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setPoweredAngMotor(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getTargetAngMotorVelocity - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getTargetAngMotorVelocity - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getTargetAngMotorVelocity(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setTargetAngMotorVelocity - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setTargetAngMotorVelocity - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setTargetAngMotorVelocity(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: getMaxAngMotorForce - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_SliderJoint_getMaxAngMotorForce - (JNIEnv * env, jobject object, jlong jointId) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return joint->getMaxAngMotorForce(); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: setMaxAngMotorForce - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_SliderJoint_setMaxAngMotorForce - (JNIEnv * env, jobject object, jlong jointId, jfloat value) { - btSliderConstraint* joint = reinterpret_cast(jointId); - if (joint == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - joint->setMaxAngMotorForce(value); - } - - /* - * Class: com_jme3_bullet_joints_SliderJoint - * Method: createJoint - * Signature: (JJLcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Matrix3f;Z)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_joints_SliderJoint_createJoint - (JNIEnv * env, jobject object, jlong bodyIdA, jlong bodyIdB, jobject pivotA, jobject rotA, jobject pivotB, jobject rotB, jboolean useLinearReferenceFrameA) { - jmeClasses::initJavaClasses(env); - btRigidBody* bodyA = reinterpret_cast(bodyIdA); - btRigidBody* bodyB = reinterpret_cast(bodyIdB); - btMatrix3x3 mtx1 = btMatrix3x3(); - btMatrix3x3 mtx2 = btMatrix3x3(); - btTransform transA = btTransform(mtx1); - jmeBulletUtil::convert(env, pivotA, &transA.getOrigin()); - jmeBulletUtil::convert(env, rotA, &transA.getBasis()); - btTransform transB = btTransform(mtx2); - jmeBulletUtil::convert(env, pivotB, &transB.getOrigin()); - jmeBulletUtil::convert(env, rotB, &transB.getBasis()); - btSliderConstraint* joint = new btSliderConstraint(*bodyA, *bodyB, transA, transB, useLinearReferenceFrameA); - return reinterpret_cast(joint); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp deleted file mode 100644 index 396d47ae3a..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_RotationalLimitMotor.cpp +++ /dev/null @@ -1,365 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_motors_RotationalLimitMotor.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getLoLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLoLimit - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_loLimit; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setLoLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLoLimit - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_loLimit = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getHiLimit - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getHiLimit - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_hiLimit; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setHiLimit - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setHiLimit - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_hiLimit = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getTargetVelocity - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getTargetVelocity - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_targetVelocity; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setTargetVelocity - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setTargetVelocity - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_targetVelocity = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getMaxMotorForce - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxMotorForce - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_maxMotorForce; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setMaxMotorForce - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxMotorForce - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_maxMotorForce = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getMaxLimitForce - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getMaxLimitForce - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_maxLimitForce; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setMaxLimitForce - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setMaxLimitForce - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_maxLimitForce = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getDamping - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_damping; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setDamping - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_damping = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getLimitSoftness - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getLimitSoftness - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_limitSoftness; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setLimitSoftness - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setLimitSoftness - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_limitSoftness = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getERP - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getERP - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_stopERP; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setERP - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setERP - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_stopERP = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: getBounce - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_getBounce - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_bounce; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setBounce - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setBounce - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_bounce = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: isEnableMotor - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_isEnableMotor - (JNIEnv *env, jobject object, jlong motorId) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return motor->m_enableMotor; - } - - /* - * Class: com_jme3_bullet_joints_motors_RotationalLimitMotor - * Method: setEnableMotor - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_RotationalLimitMotor_setEnableMotor - (JNIEnv *env, jobject object, jlong motorId, jboolean value) { - btRotationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_enableMotor = value; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp deleted file mode 100644 index 14e66933f6..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_joints_motors_TranslationalLimitMotor.cpp +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (c) 2009-2019 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. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_joints_motors_TranslationalLimitMotor.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getLowerLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLowerLimit - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &motor->m_lowerLimit, vector); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setLowerLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLowerLimit - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, vector, &motor->m_lowerLimit); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getUpperLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getUpperLimit - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &motor->m_upperLimit, vector); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setUpperLimit - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setUpperLimit - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, vector, &motor->m_upperLimit); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getAccumulatedImpulse - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getAccumulatedImpulse - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &motor->m_accumulatedImpulse, vector); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setAccumulatedImpulse - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setAccumulatedImpulse - (JNIEnv *env, jobject object, jlong motorId, jobject vector) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, vector, &motor->m_accumulatedImpulse); - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getLimitSoftness - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getLimitSoftness - (JNIEnv *env, jobject object, jlong motorId) { - btTranslationalLimitMotor* motor = reinterpret_cast (motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_limitSoftness; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setLimitSoftness - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setLimitSoftness - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_limitSoftness = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getDamping - (JNIEnv *env, jobject object, jlong motorId) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_damping; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setDamping - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_damping = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: getRestitution - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_getRestitution - (JNIEnv *env, jobject object, jlong motorId) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return motor->m_restitution; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setRestitution - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setRestitution - (JNIEnv *env, jobject object, jlong motorId, jfloat value) { - btTranslationalLimitMotor* motor = reinterpret_cast(motorId); - if (motor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - motor->m_restitution = value; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: setEnabled - * Signature: (JIZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_setEnabled - (JNIEnv *env, jobject object, jlong motorId, jint axisIndex, jboolean newSetting) { - btTranslationalLimitMotor *pMotor - = reinterpret_cast (motorId); - if (pMotor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - - pMotor->m_enableMotor[axisIndex] = (bool)newSetting; - } - - /* - * Class: com_jme3_bullet_joints_motors_TranslationalLimitMotor - * Method: isEnabled - * Signature: (JI)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_joints_motors_TranslationalLimitMotor_isEnabled - (JNIEnv *env, jobject object, jlong motorId, jint axisIndex) { - btTranslationalLimitMotor *pMotor - = reinterpret_cast (motorId); - if (pMotor == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - - bool result = pMotor->m_enableMotor[axisIndex]; - return (jboolean) result; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp deleted file mode 100644 index e807d43f89..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsCharacter.cpp +++ /dev/null @@ -1,627 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ - -#include "com_jme3_bullet_objects_PhysicsCharacter.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionDispatch/btGhostObject.h" -#include "BulletDynamics/Character/btKinematicCharacterController.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: createGhostObject - * Signature: ()J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createGhostObject - (JNIEnv * env, jobject object) { - jmeClasses::initJavaClasses(env); - btPairCachingGhostObject* ghost = new btPairCachingGhostObject(); - return reinterpret_cast(ghost); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setCharacterFlags - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCharacterFlags - (JNIEnv *env, jobject object, jlong ghostId) { - btPairCachingGhostObject* ghost = reinterpret_cast(ghostId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCollisionFlags(/*ghost->getCollisionFlags() |*/ btCollisionObject::CF_CHARACTER_OBJECT); - ghost->setCollisionFlags(ghost->getCollisionFlags() & ~btCollisionObject::CF_NO_CONTACT_RESPONSE); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: createCharacterObject - * Signature: (JJF)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_createCharacterObject - (JNIEnv *env, jobject object, jlong objectId, jlong shapeId, jfloat stepHeight) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - //TODO: check convexshape! - btConvexShape* shape = reinterpret_cast(shapeId); - btKinematicCharacterController* character = new btKinematicCharacterController(ghost, shape, stepHeight); - return reinterpret_cast(character); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: warp - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_warp - (JNIEnv *env, jobject object, jlong objectId, jobject vector) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - character->warp(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setWalkDirection - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setWalkDirection - (JNIEnv *env, jobject object, jlong objectId, jobject vector) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, vector, &vec); - character->setWalkDirection(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setUp - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setUp - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->setUp(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setAngularVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularVelocity - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->setAngularVelocity(vec); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getAngularVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularVelocity - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 a_vel = character->getAngularVelocity(); - jmeBulletUtil::convert(env, &a_vel, value); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setLinearVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearVelocity - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->setLinearVelocity(vec); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getLinearVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearVelocity - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 l_vel = character->getLinearVelocity(); - jmeBulletUtil::convert(env, &l_vel, value); - } - - - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setFallSpeed - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setFallSpeed - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setFallSpeed(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setJumpSpeed - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setJumpSpeed - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setJumpSpeed(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setGravity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setGravity - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->setGravity(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getGravity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getGravity - (JNIEnv *env, jobject object, jlong objectId,jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 g = character->getGravity(); - jmeBulletUtil::convert(env, &g, value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setLinearDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setLinearDamping - (JNIEnv *env, jobject object, jlong objectId,jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return ; - } - character->setLinearDamping(value); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getLinearDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getLinearDamping - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return character->getLinearDamping(); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setAngularDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setAngularDamping - (JNIEnv *env, jobject object, jlong objectId,jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setAngularDamping(value); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getAngularDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getAngularDamping - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return character->getAngularDamping(); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setStepHeight - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setStepHeight - (JNIEnv *env, jobject object, jlong objectId,jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setStepHeight(value); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getStepHeight - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getStepHeight - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return character->getStepHeight(); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setMaxSlope - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxSlope - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setMaxSlope(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getMaxSlope - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxSlope - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return character->getMaxSlope(); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setMaxPenetrationDepth - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setMaxPenetrationDepth - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - character->setMaxPenetrationDepth(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getMaxPenetrationDepth - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getMaxPenetrationDepth - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return character->getMaxPenetrationDepth(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: onGround - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_onGround - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return character->onGround(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: jump - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_jump - (JNIEnv *env, jobject object, jlong objectId,jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->jump(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: applyImpulse - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_applyImpulse - (JNIEnv *env, jobject object, jlong objectId,jobject value) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - character->applyImpulse(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getPhysicsLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getPhysicsLocation - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setCcdSweptSphereRadius - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCcdSweptSphereRadius(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: setCcdMotionThreshold - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_setCcdMotionThreshold - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCcdMotionThreshold(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getCcdSweptSphereRadius - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong objectId) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdSweptSphereRadius(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getCcdMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdMotionThreshold - (JNIEnv *env, jobject object, jlong objectId) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdMotionThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: getCcdSquareMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_getCcdSquareMotionThreshold - (JNIEnv *env, jobject object, jlong objectId) { - btGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdSquareMotionThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsCharacter - * Method: finalizeNativeCharacter - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsCharacter_finalizeNativeCharacter - (JNIEnv *env, jobject object, jlong objectId) { - btKinematicCharacterController* character = reinterpret_cast(objectId); - if (character == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(character); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp deleted file mode 100644 index fa38c1b639..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsGhostObject.cpp +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ - -#include - -#include "com_jme3_bullet_objects_PhysicsGhostObject.h" -#include "BulletCollision/BroadphaseCollision/btOverlappingPairCache.h" -#include "jmeBulletUtil.h" -#include "jmePhysicsSpace.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: createGhostObject - * Signature: ()J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_createGhostObject - (JNIEnv * env, jobject object) { - jmeClasses::initJavaClasses(env); - btPairCachingGhostObject* ghost = new btPairCachingGhostObject(); - return reinterpret_cast(ghost); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setGhostFlags - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setGhostFlags - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCollisionFlags(ghost->getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setPhysicsLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsLocation - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getOrigin()); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setPhysicsRotation - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, value, &ghost->getWorldTransform().getBasis()); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setPhysicsRotation - * Signature: (JLcom/jme3/math/Quaternion;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convertQuat(env, value, &ghost->getWorldTransform().getBasis()); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getPhysicsLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsLocation - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &ghost->getWorldTransform().getOrigin(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getPhysicsRotation - * Signature: (JLcom/jme3/math/Quaternion;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotation - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convertQuat(env, &ghost->getWorldTransform().getBasis(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getPhysicsRotationMatrix - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getPhysicsRotationMatrix - (JNIEnv *env, jobject object, jlong objectId, jobject value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &ghost->getWorldTransform().getBasis(), value); - } - - class jmeGhostOverlapCallback : public btOverlapCallback { - JNIEnv* m_env; - jobject m_object; - btCollisionObject *m_ghost; - public: - jmeGhostOverlapCallback(JNIEnv *env, jobject object, btCollisionObject *ghost) - :m_env(env), - m_object(object), - m_ghost(ghost) - { - } - virtual ~jmeGhostOverlapCallback() {} - virtual bool processOverlap(btBroadphasePair& pair) - { - btCollisionObject *other; - if(pair.m_pProxy1->m_clientObject == m_ghost){ - other = (btCollisionObject *)pair.m_pProxy0->m_clientObject; - }else{ - other = (btCollisionObject *)pair.m_pProxy1->m_clientObject; - } - jmeUserPointer *up1 = (jmeUserPointer*)other -> getUserPointer(); - jobject javaCollisionObject1 = m_env->NewLocalRef(up1->javaCollisionObject); - m_env->CallVoidMethod(m_object, jmeClasses::PhysicsGhostObject_addOverlappingObject, javaCollisionObject1); - m_env->DeleteLocalRef(javaCollisionObject1); - if (m_env->ExceptionCheck()) { - m_env->Throw(m_env->ExceptionOccurred()); - return false; - } - - return false; - } - }; - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getOverlappingObjects - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingObjects - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btHashedOverlappingPairCache * pc = ghost->getOverlappingPairCache(); - jmeGhostOverlapCallback cb(env, object, ghost); - pc -> processAllOverlappingPairs(&cb, NULL); - } - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getOverlappingCount - * Signature: (J)I - */ - JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getOverlappingCount - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getNumOverlappingObjects(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setCcdSweptSphereRadius - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCcdSweptSphereRadius(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: setCcdMotionThreshold - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_setCcdMotionThreshold - (JNIEnv *env, jobject object, jlong objectId, jfloat value) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - ghost->setCcdMotionThreshold(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getCcdSweptSphereRadius - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdSweptSphereRadius(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getCcdMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdMotionThreshold - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdMotionThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsGhostObject - * Method: getCcdSquareMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsGhostObject_getCcdSquareMotionThreshold - (JNIEnv *env, jobject object, jlong objectId) { - btPairCachingGhostObject* ghost = reinterpret_cast(objectId); - if (ghost == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return ghost->getCcdSquareMotionThreshold(); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp deleted file mode 100644 index 3c05e124fb..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsRigidBody.cpp +++ /dev/null @@ -1,918 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_objects_PhysicsRigidBody.h" -#include "jmeBulletUtil.h" -#include "jmeMotionState.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: createRigidBody - * Signature: (FJJ)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_createRigidBody - (JNIEnv *env, jobject object, jfloat mass, jlong motionstatId, jlong shapeId) { - jmeClasses::initJavaClasses(env); - btMotionState* motionState = reinterpret_cast(motionstatId); - btCollisionShape* shape = reinterpret_cast(shapeId); - btVector3 localInertia = btVector3(); - shape->calculateLocalInertia(mass, localInertia); - btRigidBody* body = new btRigidBody(mass, motionState, shape, localInertia); - body->setUserPointer(NULL); - return reinterpret_cast(body); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: isInWorld - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isInWorld - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return body->isInWorld(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setPhysicsLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsLocation - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - // if (body->isStaticOrKinematicObject() || !body->isInWorld()) - ((jmeMotionState*) body->getMotionState())->setKinematicLocation(env, value); - body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); - // else{ - // btMatrix3x3* mtx = &btMatrix3x3(); - // btTransform* trans = &btTransform(*mtx); - // trans->setBasis(body->getCenterOfMassTransform().getBasis()); - // jmeBulletUtil::convert(env, value, &trans->getOrigin()); - // body->setCenterOfMassTransform(*trans); - // } - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setPhysicsRotation - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Matrix3f_2 - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - // if (body->isStaticOrKinematicObject() || !body->isInWorld()) - ((jmeMotionState*) body->getMotionState())->setKinematicRotation(env, value); - body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); - // else{ - // btMatrix3x3* mtx = &btMatrix3x3(); - // btTransform* trans = &btTransform(*mtx); - // trans->setOrigin(body->getCenterOfMassTransform().getOrigin()); - // jmeBulletUtil::convert(env, value, &trans->getBasis()); - // body->setCenterOfMassTransform(*trans); - // } - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setPhysicsRotation - * Signature: (JLcom/jme3/math/Quaternion;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setPhysicsRotation__JLcom_jme3_math_Quaternion_2 - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - // if (body->isStaticOrKinematicObject() || !body->isInWorld()) - ((jmeMotionState*) body->getMotionState())->setKinematicRotationQuat(env, value); - body->setCenterOfMassTransform(((jmeMotionState*) body->getMotionState())->worldTransform); - // else{ - // btMatrix3x3* mtx = &btMatrix3x3(); - // btTransform* trans = &btTransform(*mtx); - // trans->setOrigin(body->getCenterOfMassTransform().getOrigin()); - // jmeBulletUtil::convertQuat(env, value, &trans->getBasis()); - // body->setCenterOfMassTransform(*trans); - // } - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setInverseInertiaLocal - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setInverseInertiaLocal - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - body->setInvInertiaDiagLocal(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getInverseInertiaLocal - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getInverseInertiaLocal - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getInvInertiaDiagLocal(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getPhysicsLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsLocation - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getWorldTransform().getOrigin(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getPhysicsRotation - * Signature: (JLcom/jme3/math/Quaternion;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotation - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convertQuat(env, &body->getWorldTransform().getBasis(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getPhysicsRotationMatrix - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getPhysicsRotationMatrix - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getWorldTransform().getBasis(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setKinematic - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setKinematic - (JNIEnv *env, jobject object, jlong bodyId, jboolean value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - if (value) { - body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_KINEMATIC_OBJECT); - body->setActivationState(DISABLE_DEACTIVATION); - } else { - body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_KINEMATIC_OBJECT); - body->activate(true); - body->forceActivationState(ACTIVE_TAG); - } - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setCcdSweptSphereRadius - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setCcdSweptSphereRadius(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setCcdMotionThreshold - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCcdMotionThreshold - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setCcdMotionThreshold(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getCcdSweptSphereRadius - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSweptSphereRadius - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getCcdSweptSphereRadius(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getCcdMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdMotionThreshold - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getCcdMotionThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getCcdSquareMotionThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getCcdSquareMotionThreshold - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getCcdSquareMotionThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setStatic - * Signature: (JZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setStatic - (JNIEnv *env, jobject object, jlong bodyId, jboolean value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - if (value) { - body->setCollisionFlags(body->getCollisionFlags() | btCollisionObject::CF_STATIC_OBJECT); - } else { - body->setCollisionFlags(body->getCollisionFlags() & ~btCollisionObject::CF_STATIC_OBJECT); - } - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: updateMassProps - * Signature: (JJF)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_updateMassProps - (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId, jfloat mass) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btCollisionShape* shape = reinterpret_cast(shapeId); - btVector3 localInertia = btVector3(); - shape->calculateLocalInertia(mass, localInertia); - body->setMassProps(mass, localInertia); - return reinterpret_cast(body); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getGravity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getGravity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getGravity(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setGravity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setGravity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - body->setGravity(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getFriction - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getFriction - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getFriction(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setFriction - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setFriction - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setFriction(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setDamping - * Signature: (JFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setDamping - (JNIEnv *env, jobject object, jlong bodyId, jfloat value1, jfloat value2) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setDamping(value1, value2); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setAngularDamping - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularDamping - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setDamping(body->getLinearDamping(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getLinearDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearDamping - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getLinearDamping(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getAngularDamping - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularDamping - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getAngularDamping(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getRestitution - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getRestitution - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getRestitution(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setRestitution - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setRestitution - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setRestitution(value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getAngularVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularVelocity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getAngularVelocity(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setAngularVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularVelocity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - body->setAngularVelocity(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getLinearVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearVelocity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getLinearVelocity(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setLinearVelocity - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearVelocity - (JNIEnv *env, jobject object, jlong bodyId, jobject value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, value, &vec); - body->setLinearVelocity(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: applyForce - * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyForce - (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec1 = btVector3(); - btVector3 vec2 = btVector3(); - jmeBulletUtil::convert(env, force, &vec1); - jmeBulletUtil::convert(env, location, &vec2); - body->applyForce(vec1, vec2); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: applyCentralForce - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyCentralForce - (JNIEnv *env, jobject object, jlong bodyId, jobject force) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, force, &vec1); - body->applyCentralForce(vec1); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: applyTorque - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorque - (JNIEnv *env, jobject object, jlong bodyId, jobject force) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, force, &vec1); - body->applyTorque(vec1); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: applyImpulse - * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyImpulse - (JNIEnv *env, jobject object, jlong bodyId, jobject force, jobject location) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec1 = btVector3(); - btVector3 vec2 = btVector3(); - jmeBulletUtil::convert(env, force, &vec1); - jmeBulletUtil::convert(env, location, &vec2); - body->applyImpulse(vec1, vec2); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: applyTorqueImpulse - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_applyTorqueImpulse - (JNIEnv *env, jobject object, jlong bodyId, jobject force) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec1 = btVector3(); - jmeBulletUtil::convert(env, force, &vec1); - body->applyTorqueImpulse(vec1); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: clearForces - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_clearForces - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->clearForces(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setCollisionShape - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setCollisionShape - (JNIEnv *env, jobject object, jlong bodyId, jlong shapeId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btCollisionShape* shape = reinterpret_cast(shapeId); - body->setCollisionShape(shape); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: activate - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_activate - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->activate(false); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: isActive - * Signature: (J)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_isActive - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return body->isActive(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setSleepingThresholds - * Signature: (JFF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setSleepingThresholds - (JNIEnv *env, jobject object, jlong bodyId, jfloat linear, jfloat angular) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setSleepingThresholds(linear, angular); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setLinearSleepingThreshold - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearSleepingThreshold - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setSleepingThresholds(value, body->getAngularSleepingThreshold()); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setAngularSleepingThreshold - * Signature: (JF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularSleepingThreshold - (JNIEnv *env, jobject object, jlong bodyId, jfloat value) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - body->setSleepingThresholds(body->getLinearSleepingThreshold(), value); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getLinearSleepingThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearSleepingThreshold - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getLinearSleepingThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getAngularSleepingThreshold - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularSleepingThreshold - (JNIEnv *env, jobject object, jlong bodyId) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return body->getAngularSleepingThreshold(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getAngularFactor - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getAngularFactor - (JNIEnv *env, jobject object, jlong bodyId, jobject factor) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getAngularFactor(), factor); - } - - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setAngularFactor - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setAngularFactor - (JNIEnv *env, jobject object, jlong bodyId, jobject factor) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, factor, &vec); - body->setAngularFactor(vec); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: getLinearFactor - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_getLinearFactor - (JNIEnv *env, jobject object, jlong bodyId, jobject factor) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &body->getLinearFactor(), factor); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsRigidBody - * Method: setLinearFactor - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsRigidBody_setLinearFactor - (JNIEnv *env, jobject object, jlong bodyId, jobject factor) { - btRigidBody* body = reinterpret_cast(bodyId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 vec = btVector3(); - jmeBulletUtil::convert(env, factor, &vec); - body->setLinearFactor(vec); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp deleted file mode 100644 index 58ceaf9daf..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_PhysicsVehicle.cpp +++ /dev/null @@ -1,272 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ - -#include "com_jme3_bullet_objects_PhysicsVehicle.h" -#include "jmeBulletUtil.h" -#include "jmePhysicsSpace.h" -#include "BulletDynamics/Vehicle/btRaycastVehicle.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: updateWheelTransform - * Signature: (JIZ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_updateWheelTransform - (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jboolean interpolated) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->updateWheelTransform(wheel, interpolated); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: createVehicleRaycaster - * Signature: (JJ)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createVehicleRaycaster - (JNIEnv *env, jobject object, jlong bodyId, jlong spaceId) { - //btRigidBody* body = reinterpret_cast bodyId; - jmeClasses::initJavaClasses(env); - jmePhysicsSpace *space = reinterpret_cast(spaceId); - if (space == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btDefaultVehicleRaycaster* caster = new btDefaultVehicleRaycaster(space->getDynamicsWorld()); - return reinterpret_cast(caster); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: createRaycastVehicle - * Signature: (JJ)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_createRaycastVehicle - (JNIEnv *env, jobject object, jlong objectId, jlong casterId) { - jmeClasses::initJavaClasses(env); - btRigidBody* body = reinterpret_cast(objectId); - if (body == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - body->setActivationState(DISABLE_DEACTIVATION); - btVehicleRaycaster* caster = reinterpret_cast(casterId); - if (caster == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btRaycastVehicle::btVehicleTuning tuning; - btRaycastVehicle* vehicle = new btRaycastVehicle(tuning, body, caster); - return reinterpret_cast(vehicle); - - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: setCoordinateSystem - * Signature: (JIII)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_setCoordinateSystem - (JNIEnv *env, jobject object, jlong vehicleId, jint right, jint up, jint forward) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->setCoordinateSystem(right, up, forward); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: addWheel - * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;Lcom/jme3/math/Vector3f;FFLcom/jme3/bullet/objects/infos/VehicleTuning;Z)J - */ - JNIEXPORT jint JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_addWheel - (JNIEnv *env, jobject object, jlong vehicleId, jobject location, jobject direction, jobject axle, jfloat restLength, jfloat radius, jobject tuning, jboolean frontWheel) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - btVector3 vec1 = btVector3(); - btVector3 vec2 = btVector3(); - btVector3 vec3 = btVector3(); - jmeBulletUtil::convert(env, location, &vec1); - jmeBulletUtil::convert(env, direction, &vec2); - jmeBulletUtil::convert(env, axle, &vec3); - btRaycastVehicle::btVehicleTuning tune; - btWheelInfo* info = &vehicle->addWheel(vec1, vec2, vec3, restLength, radius, tune, frontWheel); - int idx = vehicle->getNumWheels(); - return idx-1; - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: resetSuspension - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_resetSuspension - (JNIEnv *env, jobject object, jlong vehicleId) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->resetSuspension(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: applyEngineForce - * Signature: (JIF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_applyEngineForce - (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat force) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->applyEngineForce(force, wheel); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: steer - * Signature: (JIF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_steer - (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->setSteeringValue(value, wheel); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: brake - * Signature: (JIF)F - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_brake - (JNIEnv *env, jobject object, jlong vehicleId, jint wheel, jfloat value) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - vehicle->setBrake(value, wheel); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: getCurrentVehicleSpeedKmHour - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getCurrentVehicleSpeedKmHour - (JNIEnv *env, jobject object, jlong vehicleId) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return vehicle->getCurrentSpeedKmHour(); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: getForwardVector - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_getForwardVector - (JNIEnv *env, jobject object, jlong vehicleId, jobject out) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - btVector3 forwardVector = vehicle->getForwardVector(); - jmeBulletUtil::convert(env, &forwardVector, out); - } - - /* - * Class: com_jme3_bullet_objects_PhysicsVehicle - * Method: finalizeNative - * Signature: (JJ)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_PhysicsVehicle_finalizeNative - (JNIEnv *env, jobject object, jlong casterId, jlong vehicleId) { - btVehicleRaycaster* rayCaster = reinterpret_cast(casterId); - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(vehicle); - if (rayCaster == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(rayCaster); - } - -#ifdef __cplusplus -} -#endif - diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp deleted file mode 100644 index b3d38a6733..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_VehicleWheel.cpp +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ - -#include "com_jme3_bullet_objects_VehicleWheel.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getWheelLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelLocation - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getOrigin(), out); - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getWheelRotation - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getWheelRotation - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_worldTransform.getBasis(), out); - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: applyInfo - * Signature: (JFFFFFFFFZF)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_applyInfo - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jfloat suspensionStiffness, jfloat wheelsDampingRelaxation, jfloat wheelsDampingCompression, jfloat frictionSlip, jfloat rollInfluence, jfloat maxSuspensionTravelCm, jfloat maxSuspensionForce, jfloat radius, jboolean frontWheel, jfloat restLength) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - vehicle->getWheelInfo(wheelIndex).m_suspensionStiffness = suspensionStiffness; - vehicle->getWheelInfo(wheelIndex).m_wheelsDampingRelaxation = wheelsDampingRelaxation; - vehicle->getWheelInfo(wheelIndex).m_wheelsDampingCompression = wheelsDampingCompression; - vehicle->getWheelInfo(wheelIndex).m_frictionSlip = frictionSlip; - vehicle->getWheelInfo(wheelIndex).m_rollInfluence = rollInfluence; - vehicle->getWheelInfo(wheelIndex).m_maxSuspensionTravelCm = maxSuspensionTravelCm; - vehicle->getWheelInfo(wheelIndex).m_maxSuspensionForce = maxSuspensionForce; - vehicle->getWheelInfo(wheelIndex).m_wheelsRadius = radius; - vehicle->getWheelInfo(wheelIndex).m_bIsFrontWheel = frontWheel; - vehicle->getWheelInfo(wheelIndex).m_suspensionRestLength1 = restLength; - - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getCollisionLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionLocation - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactPointWS, out); - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getCollisionNormal - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getCollisionNormal - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex, jobject out) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &vehicle->getWheelInfo(wheelIndex).m_raycastInfo.m_contactNormalWS, out); - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getSkidInfo - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getSkidInfo - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return vehicle->getWheelInfo(wheelIndex).m_skidInfo; - } - - /* - * Class: com_jme3_bullet_objects_VehicleWheel - * Method: getDeltaRotation - * Signature: (J)F - */ - JNIEXPORT jfloat JNICALL Java_com_jme3_bullet_objects_VehicleWheel_getDeltaRotation - (JNIEnv *env, jobject object, jlong vehicleId, jint wheelIndex) { - btRaycastVehicle* vehicle = reinterpret_cast(vehicleId); - if (vehicle == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return 0; - } - return vehicle->getWheelInfo(wheelIndex).m_deltaRotation; - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp deleted file mode 100644 index 5f614f052d..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_objects_infos_RigidBodyMotionState.cpp +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_objects_infos_RigidBodyMotionState.h" -#include "jmeBulletUtil.h" -#include "jmeMotionState.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: createMotionState - * Signature: ()J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_createMotionState - (JNIEnv *env, jobject object) { - jmeClasses::initJavaClasses(env); - jmeMotionState* motionState = new jmeMotionState(); - return reinterpret_cast(motionState); - } - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: applyTransform - * Signature: (JLcom/jme3/math/Vector3f;Lcom/jme3/math/Quaternion;)Z - */ - JNIEXPORT jboolean JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_applyTransform - (JNIEnv *env, jobject object, jlong stateId, jobject location, jobject rotation) { - jmeMotionState* motionState = reinterpret_cast(stateId); - if (motionState == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return false; - } - return motionState->applyTransform(env, location, rotation); - } - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: getWorldLocation - * Signature: (JLcom/jme3/math/Vector3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldLocation - (JNIEnv *env, jobject object, jlong stateId, jobject value) { - jmeMotionState* motionState = reinterpret_cast(stateId); - if (motionState == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &motionState->worldTransform.getOrigin(), value); - } - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: getWorldRotation - * Signature: (JLcom/jme3/math/Matrix3f;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotation - (JNIEnv *env, jobject object, jlong stateId, jobject value) { - jmeMotionState* motionState = reinterpret_cast(stateId); - if (motionState == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convert(env, &motionState->worldTransform.getBasis(), value); - } - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: getWorldRotationQuat - * Signature: (JLcom/jme3/math/Quaternion;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_getWorldRotationQuat - (JNIEnv *env, jobject object, jlong stateId, jobject value) { - jmeMotionState* motionState = reinterpret_cast(stateId); - if (motionState == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - jmeBulletUtil::convertQuat(env, &motionState->worldTransform.getBasis(), value); - } - - /* - * Class: com_jme3_bullet_objects_infos_RigidBodyMotionState - * Method: finalizeNative - * Signature: (J)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_objects_infos_RigidBodyMotionState_finalizeNative - (JNIEnv *env, jobject object, jlong stateId) { - jmeMotionState* motionState = reinterpret_cast(stateId); - if (motionState == NULL) { - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, "The native object does not exist."); - return; - } - delete(motionState); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp deleted file mode 100644 index 0496cb07de..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_DebugShapeFactory.cpp +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen, CJ Hare - */ -#include "com_jme3_bullet_util_DebugShapeFactory.h" -#include "jmeBulletUtil.h" -#include "BulletCollision/CollisionShapes/btShapeHull.h" - -class DebugCallback : public btTriangleCallback, public btInternalTriangleIndexCallback { -public: - JNIEnv* env; - jobject callback; - - DebugCallback(JNIEnv* env, jobject object) { - this->env = env; - this->callback = object; - } - - virtual void internalProcessTriangleIndex(btVector3* triangle, int partId, int triangleIndex) { - processTriangle(triangle, partId, triangleIndex); - } - - virtual void processTriangle(btVector3* triangle, int partId, int triangleIndex) { - btVector3 vertexA, vertexB, vertexC; - vertexA = triangle[0]; - vertexB = triangle[1]; - vertexC = triangle[2]; - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ(), partId, triangleIndex); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -// triangle = - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ(), partId, triangleIndex); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ(), partId, triangleIndex); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - } -}; - -#ifdef __cplusplus -extern "C" { -#endif - - /* Inaccessible static: _00024assertionsDisabled */ - - /* - * Class: com_jme3_bullet_util_DebugShapeFactory - * Method: getVertices - * Signature: (JLcom/jme3/bullet/util/DebugMeshCallback;)V - */ - JNIEXPORT void JNICALL Java_com_jme3_bullet_util_DebugShapeFactory_getVertices - (JNIEnv *env, jclass clazz, jlong shapeId, jobject callback) { - btCollisionShape* shape = reinterpret_cast(shapeId); - if (shape->isConcave()) { - btConcaveShape* concave = reinterpret_cast(shape); - DebugCallback* clb = new DebugCallback(env, callback); - btVector3 min = btVector3(-1e30, -1e30, -1e30); - btVector3 max = btVector3(1e30, 1e30, 1e30); - concave->processAllTriangles(clb, min, max); - delete(clb); - } else if (shape->isConvex()) { - btConvexShape* convexShape = reinterpret_cast(shape); - // Check there is a hull shape to render - if (convexShape->getUserPointer() == NULL) { - // create a hull approximation - btShapeHull* hull = new btShapeHull(convexShape); - float margin = convexShape->getMargin(); - hull->buildHull(margin); - convexShape->setUserPointer(hull); - } - - btShapeHull* hull = (btShapeHull*) convexShape->getUserPointer(); - - int numberOfTriangles = hull->numTriangles(); - int numberOfFloats = 3 * 3 * numberOfTriangles; - int byteBufferSize = numberOfFloats * 4; - - // Loop variables - const unsigned int* hullIndices = hull->getIndexPointer(); - const btVector3* hullVertices = hull->getVertexPointer(); - btVector3 vertexA, vertexB, vertexC; - int index = 0; - - for (int i = 0; i < numberOfTriangles; i++) { - // Grab the data for this triangle from the hull - vertexA = hullVertices[hullIndices[index++]]; - vertexB = hullVertices[hullIndices[index++]]; - vertexC = hullVertices[hullIndices[index++]]; - - // Put the verticies into the vertex buffer - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexA.getX(), vertexA.getY(), vertexA.getZ()); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexB.getX(), vertexB.getY(), vertexB.getZ()); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->CallVoidMethod(callback, jmeClasses::DebugMeshCallback_addVector, vertexC.getX(), vertexC.getY(), vertexC.getZ()); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - } - delete hull; - convexShape->setUserPointer(NULL); - } - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp b/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp deleted file mode 100644 index a85d2d84df..0000000000 --- a/jme3-bullet-native/src/native/cpp/com_jme3_bullet_util_NativeMeshUtil.cpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/** - * Author: Normen Hansen - */ -#include "com_jme3_bullet_util_NativeMeshUtil.h" -#include "jmeBulletUtil.h" - -#ifdef __cplusplus -extern "C" { -#endif - - /* - * Class: com_jme3_bullet_util_NativeMeshUtil - * Method: createTriangleIndexVertexArray - * Signature: (Ljava/nio/ByteBuffer;Ljava/nio/ByteBuffer;IIII)J - */ - JNIEXPORT jlong JNICALL Java_com_jme3_bullet_util_NativeMeshUtil_createTriangleIndexVertexArray - (JNIEnv * env, jclass cls, jobject triangleIndexBase, jobject vertexIndexBase, jint numTriangles, jint numVertices, jint vertexStride, jint triangleIndexStride) { - jmeClasses::initJavaClasses(env); - int* triangles = (int*) env->GetDirectBufferAddress(triangleIndexBase); - float* vertices = (float*) env->GetDirectBufferAddress(vertexIndexBase); - btTriangleIndexVertexArray* array = new btTriangleIndexVertexArray(numTriangles, triangles, triangleIndexStride, numVertices, vertices, vertexStride); - return reinterpret_cast(array); - } - -#ifdef __cplusplus -} -#endif diff --git a/jme3-bullet-native/src/native/cpp/fake_win32/jni_md.h b/jme3-bullet-native/src/native/cpp/fake_win32/jni_md.h deleted file mode 100644 index 2cab367e98..0000000000 --- a/jme3-bullet-native/src/native/cpp/fake_win32/jni_md.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -#ifndef JNI_MD_H -#define JNI_MD_H - -#ifndef __has_attribute -#define __has_attribute(x) 0 -#endif - -#define JNIEXPORT __declspec(dllexport) -#define JNIIMPORT __declspec(dllimport) -#define JNICALL - -typedef int jint; -typedef long long jlong; -typedef signed char jbyte; - -#endif diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp deleted file mode 100644 index a51bcf8339..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.cpp +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include "jmeBulletUtil.h" - -/** - * Author: Normen Hansen,Empire Phoenix, Lutherion - */ -void jmeBulletUtil::convert(JNIEnv* env, jobject in, btVector3* out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float x = env->GetFloatField(in, jmeClasses::Vector3f_x); //env->CallFloatMethod(in, jmeClasses::Vector3f_getX); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float y = env->GetFloatField(in, jmeClasses::Vector3f_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float z = env->GetFloatField(in, jmeClasses::Vector3f_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - out->setX(x); - out->setY(y); - out->setZ(z); -} - -void jmeBulletUtil::convert(JNIEnv* env, jobject in, btQuaternion* out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float x = env->GetFloatField(in, jmeClasses::Quaternion_x); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float y = env->GetFloatField(in, jmeClasses::Quaternion_y); //env->CallFloatMethod(in, jmeClasses::Vector3f_getY); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float z = env->GetFloatField(in, jmeClasses::Quaternion_z); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - float w = env->GetFloatField(in, jmeClasses::Quaternion_w); //env->CallFloatMethod(in, jmeClasses::Vector3f_getZ); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - out->setX(x); - out->setY(y); - out->setZ(z); - out->setW(w); -} - - -void jmeBulletUtil::convert(JNIEnv* env, const btVector3* in, jobject out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float x = in->getX(); - float y = in->getY(); - float z = in->getZ(); - env->SetFloatField(out, jmeClasses::Vector3f_x, x); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Vector3f_y, y); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Vector3f_z, z); - // env->CallObjectMethod(out, jmeClasses::Vector3f_set, x, y, z); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - -void jmeBulletUtil::convert(JNIEnv* env, jobject in, btMatrix3x3* out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float m00 = env->GetFloatField(in, jmeClasses::Matrix3f_m00); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m01 = env->GetFloatField(in, jmeClasses::Matrix3f_m01); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m02 = env->GetFloatField(in, jmeClasses::Matrix3f_m02); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m10 = env->GetFloatField(in, jmeClasses::Matrix3f_m10); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m11 = env->GetFloatField(in, jmeClasses::Matrix3f_m11); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m12 = env->GetFloatField(in, jmeClasses::Matrix3f_m12); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m20 = env->GetFloatField(in, jmeClasses::Matrix3f_m20); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m21 = env->GetFloatField(in, jmeClasses::Matrix3f_m21); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float m22 = env->GetFloatField(in, jmeClasses::Matrix3f_m22); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - out->setValue(m00, m01, m02, m10, m11, m12, m20, m21, m22); -} - -void jmeBulletUtil::convert(JNIEnv* env, const btMatrix3x3* in, jobject out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float m00 = in->getRow(0).m_floats[0]; - float m01 = in->getRow(0).m_floats[1]; - float m02 = in->getRow(0).m_floats[2]; - float m10 = in->getRow(1).m_floats[0]; - float m11 = in->getRow(1).m_floats[1]; - float m12 = in->getRow(1).m_floats[2]; - float m20 = in->getRow(2).m_floats[0]; - float m21 = in->getRow(2).m_floats[1]; - float m22 = in->getRow(2).m_floats[2]; - env->SetFloatField(out, jmeClasses::Matrix3f_m00, m00); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m01, m01); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m02, m02); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m10, m10); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m11, m11); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m12, m12); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m20, m20); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m21, m21); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Matrix3f_m22, m22); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - -void jmeBulletUtil::convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - float x = env->GetFloatField(in, jmeClasses::Quaternion_x); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float y = env->GetFloatField(in, jmeClasses::Quaternion_y); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float z = env->GetFloatField(in, jmeClasses::Quaternion_z); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - float w = env->GetFloatField(in, jmeClasses::Quaternion_w); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - float norm = w * w + x * x + y * y + z * z; - float s = (norm == 1.0) ? 2.0 : (norm > 0.1) ? 2.0 / norm : 0.0; - - // compute xs/ys/zs first to save 6 multiplications, since xs/ys/zs - // will be used 2-4 times each. - float xs = x * s; - float ys = y * s; - float zs = z * s; - float xx = x * xs; - float xy = x * ys; - float xz = x * zs; - float xw = w * xs; - float yy = y * ys; - float yz = y * zs; - float yw = w * ys; - float zz = z * zs; - float zw = w * zs; - - // using s=2/norm (instead of 1/norm) saves 9 multiplications by 2 here - out->setValue(1.0 - (yy + zz), (xy - zw), (xz + yw), - (xy + zw), 1 - (xx + zz), (yz - xw), - (xz - yw), (yz + xw), 1.0 - (xx + yy)); -} - -void jmeBulletUtil::convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - // the trace is the sum of the diagonal elements; see - // http://mathworld.wolfram.com/MatrixTrace.html - float t = in->getRow(0).m_floats[0] + in->getRow(1).m_floats[1] + in->getRow(2).m_floats[2]; - float w, x, y, z; - // we protect the division by s by ensuring that s>=1 - if (t >= 0) { // |w| >= .5 - float s = sqrt(t + 1); // |s|>=1 ... - w = 0.5f * s; - s = 0.5f / s; // so this division isn't bad - x = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s; - y = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s; - z = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s; - } else if ((in->getRow(0).m_floats[0] > in->getRow(1).m_floats[1]) && (in->getRow(0).m_floats[0] > in->getRow(2).m_floats[2])) { - float s = sqrt(1.0f + in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1] - in->getRow(2).m_floats[2]); // |s|>=1 - x = s * 0.5f; // |x| >= .5 - s = 0.5f / s; - y = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s; - z = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s; - w = (in->getRow(2).m_floats[1] - in->getRow(1).m_floats[2]) * s; - } else if (in->getRow(1).m_floats[1] > in->getRow(2).m_floats[2]) { - float s = sqrt(1.0f + in->getRow(1).m_floats[1] - in->getRow(0).m_floats[0] - in->getRow(2).m_floats[2]); // |s|>=1 - y = s * 0.5f; // |y| >= .5 - s = 0.5f / s; - x = (in->getRow(1).m_floats[0] + in->getRow(0).m_floats[1]) * s; - z = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s; - w = (in->getRow(0).m_floats[2] - in->getRow(2).m_floats[0]) * s; - } else { - float s = sqrt(1.0f + in->getRow(2).m_floats[2] - in->getRow(0).m_floats[0] - in->getRow(1).m_floats[1]); // |s|>=1 - z = s * 0.5f; // |z| >= .5 - s = 0.5f / s; - x = (in->getRow(0).m_floats[2] + in->getRow(2).m_floats[0]) * s; - y = (in->getRow(2).m_floats[1] + in->getRow(1).m_floats[2]) * s; - w = (in->getRow(1).m_floats[0] - in->getRow(0).m_floats[1]) * s; - } - - env->SetFloatField(out, jmeClasses::Quaternion_x, x); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Quaternion_y, y); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Quaternion_z, z); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - env->SetFloatField(out, jmeClasses::Quaternion_w, w); - // env->CallObjectMethod(out, jmeClasses::Quaternion_set, x, y, z, w); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - -void jmeBulletUtil::addResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, btScalar m_hitFraction, const btCollisionObject* hitobject) { - - jobject singleresult = env->AllocObject(jmeClasses::PhysicsRay_Class); - jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f); - - convert(env, hitnormal, hitnormalvec); - jmeUserPointer *up1 = (jmeUserPointer*) hitobject -> getUserPointer(); - - env->SetObjectField(singleresult, jmeClasses::PhysicsRay_normalInWorldSpace, hitnormalvec); - env->SetFloatField(singleresult, jmeClasses::PhysicsRay_hitfraction, m_hitFraction); - - env->SetObjectField(singleresult, jmeClasses::PhysicsRay_collisionObject, up1->javaCollisionObject); - env->CallBooleanMethod(resultlist, jmeClasses::PhysicsRay_addmethod, singleresult); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - - -void jmeBulletUtil::addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, btScalar m_hitFraction, const btCollisionObject* hitobject) { - - jobject singleresult = env->AllocObject(jmeClasses::PhysicsSweep_Class); - jobject hitnormalvec = env->AllocObject(jmeClasses::Vector3f); - - convert(env, hitnormal, hitnormalvec); - jmeUserPointer *up1 = (jmeUserPointer*)hitobject->getUserPointer(); - - env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_normalInWorldSpace, hitnormalvec); - env->SetFloatField(singleresult, jmeClasses::PhysicsSweep_hitfraction, m_hitFraction); - - env->SetObjectField(singleresult, jmeClasses::PhysicsSweep_collisionObject, up1->javaCollisionObject); - env->CallBooleanMethod(resultlist, jmeClasses::PhysicsSweep_addmethod, singleresult); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - -void jmeBulletUtil::convert(JNIEnv* env, jobject in, btTransform* out) { - if (in == NULL || out == NULL) { - jmeClasses::throwNPE(env); - } - - jobject translation_vec = env->CallObjectMethod(in, jmeClasses::Transform_translation); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - jobject rot_quat = env->CallObjectMethod(in, jmeClasses::Transform_rotation); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - /* - //Scale currently not supported by bullet - //@TBD: Create an assertion somewhere to avoid scale values - jobject scale_vec = env->GetObjectField(in, jmeClasses::Transform_scale); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - */ - btVector3 native_translation_vec = btVector3(); - //btVector3 native_scale_vec = btVector3(); - btQuaternion native_rot_quat = btQuaternion(); - - convert(env, translation_vec, &native_translation_vec); - //convert(env, scale_vec, native_scale_vec); - convert(env, rot_quat, &native_rot_quat); - - out->setRotation(native_rot_quat); - out->setOrigin(native_translation_vec); -} diff --git a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h b/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h deleted file mode 100644 index 7d982c80ef..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeBulletUtil.h +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "jmeClasses.h" -#include "btBulletDynamicsCommon.h" -#include "btBulletCollisionCommon.h" -#include "LinearMath/btVector3.h" - -/** - * Author: Normen Hansen - */ -class jmeBulletUtil{ -public: - static void convert(JNIEnv* env, jobject in, btVector3* out); - static void convert(JNIEnv* env, const btVector3* in, jobject out); - static void convert(JNIEnv* env, jobject in, btMatrix3x3* out); - static void convert(JNIEnv* env, jobject in, btQuaternion* out); - static void convert(JNIEnv* env, const btMatrix3x3* in, jobject out); - static void convertQuat(JNIEnv* env, jobject in, btMatrix3x3* out); - static void convertQuat(JNIEnv* env, const btMatrix3x3* in, jobject out); - static void convert(JNIEnv* env, jobject in, btTransform* out); - static void addResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld,const btScalar m_hitFraction,const btCollisionObject* hitobject); - static void addSweepResult(JNIEnv* env, jobject resultlist, btVector3* hitnormal, btVector3* m_hitPointWorld, const btScalar m_hitFraction, const btCollisionObject* hitobject); -private: - jmeBulletUtil(){}; - ~jmeBulletUtil(){}; - -}; - -class jmeUserPointer { -public: - jobject javaCollisionObject; - jint group; - jint groups; - void *space; -}; \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp b/jme3-bullet-native/src/native/cpp/jmeClasses.cpp deleted file mode 100644 index ae7784c826..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.cpp +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "jmeClasses.h" -#include - -/** - * Author: Normen Hansen,Empire Phoenix, Lutherion - */ -//public fields -jclass jmeClasses::PhysicsSpace; -jmethodID jmeClasses::PhysicsSpace_preTick; -jmethodID jmeClasses::PhysicsSpace_postTick; -jmethodID jmeClasses::PhysicsSpace_addCollisionEvent; -jmethodID jmeClasses::PhysicsSpace_notifyCollisionGroupListeners; - -jclass jmeClasses::PhysicsGhostObject; -jmethodID jmeClasses::PhysicsGhostObject_addOverlappingObject; - -jclass jmeClasses::Vector3f; -jmethodID jmeClasses::Vector3f_set; -jmethodID jmeClasses::Vector3f_toArray; -jmethodID jmeClasses::Vector3f_getX; -jmethodID jmeClasses::Vector3f_getY; -jmethodID jmeClasses::Vector3f_getZ; -jfieldID jmeClasses::Vector3f_x; -jfieldID jmeClasses::Vector3f_y; -jfieldID jmeClasses::Vector3f_z; - -jclass jmeClasses::Quaternion; -jmethodID jmeClasses::Quaternion_set; -jmethodID jmeClasses::Quaternion_getX; -jmethodID jmeClasses::Quaternion_getY; -jmethodID jmeClasses::Quaternion_getZ; -jmethodID jmeClasses::Quaternion_getW; -jfieldID jmeClasses::Quaternion_x; -jfieldID jmeClasses::Quaternion_y; -jfieldID jmeClasses::Quaternion_z; -jfieldID jmeClasses::Quaternion_w; - -jclass jmeClasses::Matrix3f; -jmethodID jmeClasses::Matrix3f_set; -jmethodID jmeClasses::Matrix3f_get; -jfieldID jmeClasses::Matrix3f_m00; -jfieldID jmeClasses::Matrix3f_m01; -jfieldID jmeClasses::Matrix3f_m02; -jfieldID jmeClasses::Matrix3f_m10; -jfieldID jmeClasses::Matrix3f_m11; -jfieldID jmeClasses::Matrix3f_m12; -jfieldID jmeClasses::Matrix3f_m20; -jfieldID jmeClasses::Matrix3f_m21; -jfieldID jmeClasses::Matrix3f_m22; - -jclass jmeClasses::DebugMeshCallback; -jmethodID jmeClasses::DebugMeshCallback_addVector; - -jclass jmeClasses::PhysicsRay_Class; -jmethodID jmeClasses::PhysicsRay_newSingleResult; - -jfieldID jmeClasses::PhysicsRay_normalInWorldSpace; -jfieldID jmeClasses::PhysicsRay_hitfraction; -jfieldID jmeClasses::PhysicsRay_collisionObject; - -jclass jmeClasses::PhysicsRay_listresult; -jmethodID jmeClasses::PhysicsRay_addmethod; - -jclass jmeClasses::PhysicsSweep_Class; -jmethodID jmeClasses::PhysicsSweep_newSingleResult; - -jfieldID jmeClasses::PhysicsSweep_normalInWorldSpace; -jfieldID jmeClasses::PhysicsSweep_hitfraction; -jfieldID jmeClasses::PhysicsSweep_collisionObject; - -jclass jmeClasses::PhysicsSweep_listresult; -jmethodID jmeClasses::PhysicsSweep_addmethod; - - -jclass jmeClasses::Transform; -jmethodID jmeClasses::Transform_rotation; -jmethodID jmeClasses::Transform_translation; - -//private fields -//JNIEnv* jmeClasses::env; -JavaVM* jmeClasses::vm; - -void jmeClasses::initJavaClasses(JNIEnv* env) { -// if (env != NULL) { -// fprintf(stdout, "Check Java VM state\n"); -// fflush(stdout); -// int res = vm->AttachCurrentThread((void**) &jmeClasses::env, NULL); -// if (res < 0) { -// fprintf(stdout, "** ERROR: getting Java env!\n"); -// if (res == JNI_EVERSION) fprintf(stdout, "GetEnv Error because of different JNI Version!\n"); -// fflush(stdout); -// } -// return; -// } - if(PhysicsSpace!=NULL) return; - fprintf(stdout, "Bullet-Native: Initializing java classes\n"); - fflush(stdout); -// jmeClasses::env = env; - env->GetJavaVM(&vm); - - PhysicsSpace = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/PhysicsSpace")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsSpace_preTick = env->GetMethodID(PhysicsSpace, "preTick_native", "(F)V"); - PhysicsSpace_postTick = env->GetMethodID(PhysicsSpace, "postTick_native", "(F)V"); - PhysicsSpace_addCollisionEvent = env->GetMethodID(PhysicsSpace, "addCollisionEvent_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;J)V"); - PhysicsSpace_notifyCollisionGroupListeners = env->GetMethodID(PhysicsSpace, "notifyCollisionGroupListeners_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;Lcom/jme3/bullet/collision/PhysicsCollisionObject;)Z"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsGhostObject = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/objects/PhysicsGhostObject")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - PhysicsGhostObject_addOverlappingObject = env->GetMethodID(PhysicsGhostObject, "addOverlappingObject_native","(Lcom/jme3/bullet/collision/PhysicsCollisionObject;)V"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - Vector3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Vector3f")); - Vector3f_set = env->GetMethodID(Vector3f, "set", "(FFF)Lcom/jme3/math/Vector3f;"); - Vector3f_toArray = env->GetMethodID(Vector3f, "toArray", "([F)[F"); - Vector3f_getX = env->GetMethodID(Vector3f, "getX", "()F"); - Vector3f_getY = env->GetMethodID(Vector3f, "getY", "()F"); - Vector3f_getZ = env->GetMethodID(Vector3f, "getZ", "()F"); - Vector3f_x = env->GetFieldID(Vector3f, "x", "F"); - Vector3f_y = env->GetFieldID(Vector3f, "y", "F"); - Vector3f_z = env->GetFieldID(Vector3f, "z", "F"); - - Quaternion = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Quaternion")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - Quaternion_set = env->GetMethodID(Quaternion, "set", "(FFFF)Lcom/jme3/math/Quaternion;"); - Quaternion_getW = env->GetMethodID(Quaternion, "getW", "()F"); - Quaternion_getX = env->GetMethodID(Quaternion, "getX", "()F"); - Quaternion_getY = env->GetMethodID(Quaternion, "getY", "()F"); - Quaternion_getZ = env->GetMethodID(Quaternion, "getZ", "()F"); - Quaternion_x = env->GetFieldID(Quaternion, "x", "F"); - Quaternion_y = env->GetFieldID(Quaternion, "y", "F"); - Quaternion_z = env->GetFieldID(Quaternion, "z", "F"); - Quaternion_w = env->GetFieldID(Quaternion, "w", "F"); - - Matrix3f = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Matrix3f")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - Matrix3f_set = env->GetMethodID(Matrix3f, "set", "(IIF)Lcom/jme3/math/Matrix3f;"); - Matrix3f_get = env->GetMethodID(Matrix3f, "get", "(II)F"); - Matrix3f_m00 = env->GetFieldID(Matrix3f, "m00", "F"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - Matrix3f_m01 = env->GetFieldID(Matrix3f, "m01", "F"); - Matrix3f_m02 = env->GetFieldID(Matrix3f, "m02", "F"); - Matrix3f_m10 = env->GetFieldID(Matrix3f, "m10", "F"); - Matrix3f_m11 = env->GetFieldID(Matrix3f, "m11", "F"); - Matrix3f_m12 = env->GetFieldID(Matrix3f, "m12", "F"); - Matrix3f_m20 = env->GetFieldID(Matrix3f, "m20", "F"); - Matrix3f_m21 = env->GetFieldID(Matrix3f, "m21", "F"); - Matrix3f_m22 = env->GetFieldID(Matrix3f, "m22", "F"); - - DebugMeshCallback = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/util/DebugMeshCallback")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - DebugMeshCallback_addVector = env->GetMethodID(DebugMeshCallback, "addVector", "(FFFII)V"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsRay_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsRayTestResult")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsRay_newSingleResult = env->GetMethodID(PhysicsRay_Class,"","()V"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsRay_normalInWorldSpace = env->GetFieldID(PhysicsRay_Class,"hitNormalLocal","Lcom/jme3/math/Vector3f;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - - PhysicsRay_hitfraction = env->GetFieldID(PhysicsRay_Class,"hitFraction","F"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - - PhysicsRay_collisionObject = env->GetFieldID(PhysicsRay_Class,"collisionObject","Lcom/jme3/bullet/collision/PhysicsCollisionObject;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsRay_listresult = env->FindClass("java/util/List"); - PhysicsRay_listresult = (jclass)env->NewGlobalRef(PhysicsRay_listresult); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsRay_addmethod = env->GetMethodID(PhysicsRay_listresult,"add","(Ljava/lang/Object;)Z"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsSweep_Class = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/bullet/collision/PhysicsSweepTestResult")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; -} - - PhysicsSweep_newSingleResult = env->GetMethodID(PhysicsSweep_Class, "", "()V"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsSweep_normalInWorldSpace = env->GetFieldID(PhysicsSweep_Class, "hitNormalLocal", "Lcom/jme3/math/Vector3f;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - - PhysicsSweep_hitfraction = env->GetFieldID(PhysicsSweep_Class, "hitFraction", "F"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - - PhysicsSweep_collisionObject = env->GetFieldID(PhysicsSweep_Class, "collisionObject", "Lcom/jme3/bullet/collision/PhysicsCollisionObject;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsSweep_listresult = env->FindClass("java/util/List"); - PhysicsSweep_listresult = (jclass)env->NewGlobalRef(PhysicsSweep_listresult); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - PhysicsSweep_addmethod = env->GetMethodID(PhysicsSweep_listresult, "add", "(Ljava/lang/Object;)Z"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - Transform = (jclass)env->NewGlobalRef(env->FindClass("com/jme3/math/Transform")); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - Transform_rotation = env->GetMethodID(Transform, "getRotation", "()Lcom/jme3/math/Quaternion;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - - Transform_translation = env->GetMethodID(Transform, "getTranslation", "()Lcom/jme3/math/Vector3f;"); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - -} - -void jmeClasses::throwNPE(JNIEnv* env) { - if (env == NULL) return; - jclass newExc = env->FindClass("java/lang/NullPointerException"); - env->ThrowNew(newExc, ""); - return; -} diff --git a/jme3-bullet-native/src/native/cpp/jmeClasses.h b/jme3-bullet-native/src/native/cpp/jmeClasses.h deleted file mode 100644 index bdead6b70d..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeClasses.h +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include - -/** - * Author: Normen Hansen - */ - -class jmeClasses { -public: - static void initJavaClasses(JNIEnv* env); -// static JNIEnv* env; - static JavaVM* vm; - static jclass PhysicsSpace; - static jmethodID PhysicsSpace_preTick; - static jmethodID PhysicsSpace_postTick; - static jmethodID PhysicsSpace_addCollisionEvent; - static jclass PhysicsGhostObject; - static jmethodID PhysicsGhostObject_addOverlappingObject; - static jmethodID PhysicsSpace_notifyCollisionGroupListeners; - - static jclass Vector3f; - static jmethodID Vector3f_set; - static jmethodID Vector3f_getX; - static jmethodID Vector3f_getY; - static jmethodID Vector3f_getZ; - static jmethodID Vector3f_toArray; - static jfieldID Vector3f_x; - static jfieldID Vector3f_y; - static jfieldID Vector3f_z; - - static jclass Quaternion; - static jmethodID Quaternion_set; - static jmethodID Quaternion_getX; - static jmethodID Quaternion_getY; - static jmethodID Quaternion_getZ; - static jmethodID Quaternion_getW; - static jfieldID Quaternion_x; - static jfieldID Quaternion_y; - static jfieldID Quaternion_z; - static jfieldID Quaternion_w; - - static jclass Matrix3f; - static jmethodID Matrix3f_get; - static jmethodID Matrix3f_set; - static jfieldID Matrix3f_m00; - static jfieldID Matrix3f_m01; - static jfieldID Matrix3f_m02; - static jfieldID Matrix3f_m10; - static jfieldID Matrix3f_m11; - static jfieldID Matrix3f_m12; - static jfieldID Matrix3f_m20; - static jfieldID Matrix3f_m21; - static jfieldID Matrix3f_m22; - - static jclass PhysicsRay_Class; - static jmethodID PhysicsRay_newSingleResult; - static jfieldID PhysicsRay_normalInWorldSpace; - static jfieldID PhysicsRay_hitfraction; - static jfieldID PhysicsRay_collisionObject; - static jclass PhysicsRay_listresult; - static jmethodID PhysicsRay_addmethod; - - static jclass PhysicsSweep_Class; - static jmethodID PhysicsSweep_newSingleResult; - static jfieldID PhysicsSweep_normalInWorldSpace; - static jfieldID PhysicsSweep_hitfraction; - static jfieldID PhysicsSweep_collisionObject; - static jclass PhysicsSweep_listresult; - static jmethodID PhysicsSweep_addmethod; - - static jclass Transform; - static jmethodID Transform_rotation; - static jmethodID Transform_translation; - - static jclass DebugMeshCallback; - static jmethodID DebugMeshCallback_addVector; - - static void throwNPE(JNIEnv* env); -private: - jmeClasses(){}; - ~jmeClasses(){}; -}; \ No newline at end of file diff --git a/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp b/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp deleted file mode 100644 index 7c14207fe9..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeMotionState.cpp +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include "jmeMotionState.h" -#include "jmeBulletUtil.h" - -/** - * Author: Normen Hansen - */ - -jmeMotionState::jmeMotionState() { - trans = new btTransform(); - trans -> setIdentity(); - worldTransform = *trans; - dirty = true; -} - -void jmeMotionState::getWorldTransform(btTransform& worldTrans) const { - worldTrans = worldTransform; -} - -void jmeMotionState::setWorldTransform(const btTransform& worldTrans) { - worldTransform = worldTrans; - dirty = true; -} - -void jmeMotionState::setKinematicTransform(const btTransform& worldTrans) { - worldTransform = worldTrans; - dirty = true; -} - -void jmeMotionState::setKinematicLocation(JNIEnv* env, jobject location) { - jmeBulletUtil::convert(env, location, &worldTransform.getOrigin()); - dirty = true; -} - -void jmeMotionState::setKinematicRotation(JNIEnv* env, jobject rotation) { - jmeBulletUtil::convert(env, rotation, &worldTransform.getBasis()); - dirty = true; -} - -void jmeMotionState::setKinematicRotationQuat(JNIEnv* env, jobject rotation) { - jmeBulletUtil::convertQuat(env, rotation, &worldTransform.getBasis()); - dirty = true; -} - -bool jmeMotionState::applyTransform(JNIEnv* env, jobject location, jobject rotation) { - if (dirty) { - // fprintf(stdout, "Apply world translation\n"); - // fflush(stdout); - jmeBulletUtil::convert(env, &worldTransform.getOrigin(), location); - jmeBulletUtil::convertQuat(env, &worldTransform.getBasis(), rotation); - dirty = false; - return true; - } - return false; -} - -jmeMotionState::~jmeMotionState() { - free(trans); -} diff --git a/jme3-bullet-native/src/native/cpp/jmeMotionState.h b/jme3-bullet-native/src/native/cpp/jmeMotionState.h deleted file mode 100644 index d4e4d195f6..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmeMotionState.h +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include - -/** - * Author: Normen Hansen - */ - -#include "btBulletDynamicsCommon.h" -//#include "btBulletCollisionCommon.h" - -class jmeMotionState : public btMotionState { -private: - bool dirty; - btTransform* trans; -public: - jmeMotionState(); - virtual ~jmeMotionState(); - - btTransform worldTransform; - virtual void getWorldTransform(btTransform& worldTrans) const; - virtual void setWorldTransform(const btTransform& worldTrans); - void setKinematicTransform(const btTransform& worldTrans); - void setKinematicLocation(JNIEnv*, jobject); - void setKinematicRotation(JNIEnv*, jobject); - void setKinematicRotationQuat(JNIEnv*, jobject); - bool applyTransform(JNIEnv* env, jobject location, jobject rotation); -}; diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp deleted file mode 100644 index 953511a713..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.cpp +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright (c) 2009-2019 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. - */ -#include "jmePhysicsSpace.h" -#include "jmeBulletUtil.h" - -/** - * Author: Normen Hansen - */ -jmePhysicsSpace::jmePhysicsSpace(JNIEnv* env, jobject javaSpace) { - //TODO: global ref? maybe not -> cleaning, rather callback class? - this->javaPhysicsSpace = env->NewWeakGlobalRef(javaSpace); - this->env = env; - env->GetJavaVM(&vm); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } -} - -void jmePhysicsSpace::attachThread() { -#ifdef ANDROID - vm->AttachCurrentThread((JNIEnv**) &env, NULL); -#elif defined (JNI_VERSION_1_2) - vm->AttachCurrentThread((void**) &env, NULL); -#else - vm->AttachCurrentThread(&env, NULL); -#endif -} - -JNIEnv* jmePhysicsSpace::getEnv() { - attachThread(); - return this->env; -} - -void jmePhysicsSpace::stepSimulation(jfloat tpf, jint maxSteps, jfloat accuracy) { - dynamicsWorld->stepSimulation(tpf, maxSteps, accuracy); -} - -void jmePhysicsSpace::createPhysicsSpace(jfloat minX, jfloat minY, jfloat minZ, jfloat maxX, jfloat maxY, jfloat maxZ, jint broadphaseId, jboolean threading /*unused*/) { - btCollisionConfiguration* collisionConfiguration = new btDefaultCollisionConfiguration(); - - btVector3 min = btVector3(minX, minY, minZ); - btVector3 max = btVector3(maxX, maxY, maxZ); - - btBroadphaseInterface* broadphase; - - switch (broadphaseId) { - case 0: // SIMPLE - broadphase = new btSimpleBroadphase(); - break; - case 1: // AXIS_SWEEP_3 - broadphase = new btAxisSweep3(min, max); - break; - case 2: // AXIS_SWEEP_3_32 - broadphase = new bt32BitAxisSweep3(min, max); - break; - case 3: // DBVT - broadphase = new btDbvtBroadphase(); - break; - } - - btCollisionDispatcher* dispatcher = new btCollisionDispatcher(collisionConfiguration); - btGImpactCollisionAlgorithm::registerAlgorithm(dispatcher); - - btConstraintSolver* solver = new btSequentialImpulseConstraintSolver(); - - //create dynamics world - btDiscreteDynamicsWorld* world = new btDiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); - dynamicsWorld = world; - dynamicsWorld->setWorldUserInfo(this); - - broadphase->getOverlappingPairCache()->setInternalGhostPairCallback(new btGhostPairCallback()); - dynamicsWorld->setGravity(btVector3(0, -9.81f, 0)); - - struct jmeFilterCallback : public btOverlapFilterCallback { - // return true when pairs need collision - - virtual bool needBroadphaseCollision(btBroadphaseProxy* proxy0, btBroadphaseProxy * proxy1) const { - // bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0; - // collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask); - bool collides = (proxy0->m_collisionFilterGroup & proxy1->m_collisionFilterMask) != 0; - collides = collides && (proxy1->m_collisionFilterGroup & proxy0->m_collisionFilterMask); - if (collides) { - btCollisionObject* co0 = (btCollisionObject*) proxy0->m_clientObject; - btCollisionObject* co1 = (btCollisionObject*) proxy1->m_clientObject; - jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer(); - jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer(); - if (up0 != NULL && up1 != NULL) { - collides = (up0->group & up1->groups) != 0 || (up1->group & up0->groups) != 0; - - if(collides){ - jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space; - JNIEnv* env = dynamicsWorld->getEnv(); - jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); - jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject); - jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject); - - jboolean notifyResult = env->CallBooleanMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_notifyCollisionGroupListeners, javaCollisionObject0, javaCollisionObject1); - - env->DeleteLocalRef(javaPhysicsSpace); - env->DeleteLocalRef(javaCollisionObject0); - env->DeleteLocalRef(javaCollisionObject1); - - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return collides; - } - - collides = (bool) notifyResult; - } - - //add some additional logic here that modified 'collides' - return collides; - } - return false; - } - return collides; - } - }; - dynamicsWorld->getPairCache()->setOverlapFilterCallback(new jmeFilterCallback()); - dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::preTickCallback, static_cast (this), true); - dynamicsWorld->setInternalTickCallback(&jmePhysicsSpace::postTickCallback, static_cast (this)); - if (gContactStartedCallback == NULL) { - gContactStartedCallback = &jmePhysicsSpace::contactStartedCallback; - } -} - -void jmePhysicsSpace::preTickCallback(btDynamicsWorld *world, btScalar timeStep) { - jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo(); - JNIEnv* env = dynamicsWorld->getEnv(); - jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); - if (javaPhysicsSpace != NULL) { - env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_preTick, timeStep); - env->DeleteLocalRef(javaPhysicsSpace); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - } -} - -void jmePhysicsSpace::postTickCallback(btDynamicsWorld *world, btScalar timeStep) { - jmePhysicsSpace* dynamicsWorld = (jmePhysicsSpace*) world->getWorldUserInfo(); - JNIEnv* env = dynamicsWorld->getEnv(); - jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); - if (javaPhysicsSpace != NULL) { - env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_postTick, timeStep); - env->DeleteLocalRef(javaPhysicsSpace); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - return; - } - } -} - -void jmePhysicsSpace::contactStartedCallback(btPersistentManifold* const &pm) { - const btCollisionObject* co0 = pm->getBody0(); - const btCollisionObject* co1 = pm->getBody1(); - jmeUserPointer *up0 = (jmeUserPointer*) co0 -> getUserPointer(); - jmeUserPointer *up1 = (jmeUserPointer*) co1 -> getUserPointer(); - if (up0 != NULL) { - jmePhysicsSpace *dynamicsWorld = (jmePhysicsSpace *)up0->space; - if (dynamicsWorld != NULL) { - JNIEnv* env = dynamicsWorld->getEnv(); - jobject javaPhysicsSpace = env->NewLocalRef(dynamicsWorld->getJavaPhysicsSpace()); - if (javaPhysicsSpace != NULL) { - jobject javaCollisionObject0 = env->NewLocalRef(up0->javaCollisionObject); - jobject javaCollisionObject1 = env->NewLocalRef(up1->javaCollisionObject); - for(int i=0;igetNumContacts();i++){ - env->CallVoidMethod(javaPhysicsSpace, jmeClasses::PhysicsSpace_addCollisionEvent, javaCollisionObject0, javaCollisionObject1, (jlong) & pm->getContactPoint(i)); - if (env->ExceptionCheck()) { - env->Throw(env->ExceptionOccurred()); - } - } - env->DeleteLocalRef(javaPhysicsSpace); - env->DeleteLocalRef(javaCollisionObject0); - env->DeleteLocalRef(javaCollisionObject1); - } - } - } -} - -btDynamicsWorld* jmePhysicsSpace::getDynamicsWorld() { - return dynamicsWorld; -} - -jobject jmePhysicsSpace::getJavaPhysicsSpace() { - return javaPhysicsSpace; -} - -jmePhysicsSpace::~jmePhysicsSpace() { - delete(dynamicsWorld); -} diff --git a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h b/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h deleted file mode 100644 index 72c49b23cf..0000000000 --- a/jme3-bullet-native/src/native/cpp/jmePhysicsSpace.h +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -#include -#include "btBulletDynamicsCommon.h" -#include "btBulletCollisionCommon.h" -#include "BulletCollision/CollisionDispatch/btCollisionDispatcher.h" -#include "BulletCollision/CollisionDispatch/btCollisionObject.h" -#include "BulletCollision/CollisionDispatch/btGhostObject.h" -#include "BulletDynamics/Character/btKinematicCharacterController.h" -#include "BulletCollision/CollisionDispatch/btSimulationIslandManager.h" -#include "BulletCollision/NarrowPhaseCollision/btManifoldPoint.h" -#include "BulletCollision/NarrowPhaseCollision/btPersistentManifold.h" -#include "BulletCollision/Gimpact/btGImpactCollisionAlgorithm.h" - -/** - * Author: Normen Hansen - */ -class jmePhysicsSpace { -private: - JNIEnv* env; - JavaVM* vm; - btDynamicsWorld* dynamicsWorld; - jobject javaPhysicsSpace; - void attachThread(); -public: - jmePhysicsSpace(){}; - ~jmePhysicsSpace(); - jmePhysicsSpace(JNIEnv*, jobject); - void stepSimulation(jfloat, jint, jfloat); - void createPhysicsSpace(jfloat, jfloat, jfloat, jfloat, jfloat, jfloat, jint, jboolean); - btDynamicsWorld* getDynamicsWorld(); - jobject getJavaPhysicsSpace(); - JNIEnv* getEnv(); - static void preTickCallback(btDynamicsWorld*, btScalar); - static void postTickCallback(btDynamicsWorld*, btScalar); - static void contactStartedCallback(btPersistentManifold* const &); -}; \ No newline at end of file diff --git a/jme3-bullet/build.gradle b/jme3-bullet/build.gradle deleted file mode 100644 index 64185b3442..0000000000 --- a/jme3-bullet/build.gradle +++ /dev/null @@ -1,27 +0,0 @@ -apply plugin: 'java' - -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -String classBuildDir = "${buildDir}" + File.separator + 'classes' -def nativeIncludes = new File(project(":jme3-bullet-native").projectDir, "src/native/cpp") - -sourceSets { - main { - java { - srcDir 'src/main/java' - srcDir 'src/common/java' - } - } -} - -dependencies { - compile project(':jme3-core') - compile project(':jme3-terrain') -} - -compileJava { - // The Android-Native Project requires the jni headers to be generated, so we do that here - options.compilerArgs += ["-h", nativeIncludes] -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java deleted file mode 100644 index f2d709131b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ /dev/null @@ -1,1377 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet; - -import com.jme3.app.AppTask; -import com.jme3.bullet.collision.*; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.control.PhysicsControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.PhysicsCharacter; -import com.jme3.bullet.objects.PhysicsGhostObject; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.bullet.objects.PhysicsVehicle; -import com.jme3.math.Transform; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.util.SafeArrayList; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Comparator; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A jbullet-jme physics space with its own btDynamicsWorld. - * - * @author normenhansen - */ -public class PhysicsSpace { - - /** - * message logger for this class - */ - private static final Logger logger = Logger.getLogger(PhysicsSpace.class.getName()); - /** - * index of the X axis - */ - public static final int AXIS_X = 0; - /** - * index of the Y axis - */ - public static final int AXIS_Y = 1; - /** - * index of the Z axis - */ - public static final int AXIS_Z = 2; - /** - * Bullet identifier of the physics space. The constructor sets this to a - * non-zero value. - */ - private long physicsSpaceId = 0; - /** - * first-in/first-out (FIFO) queue of physics tasks for each thread - */ - private static ThreadLocal>> pQueueTL = - new ThreadLocal>>() { - @Override - protected ConcurrentLinkedQueue> initialValue() { - return new ConcurrentLinkedQueue>(); - } - }; - /** - * first-in/first-out (FIFO) queue of physics tasks - */ - private ConcurrentLinkedQueue> pQueue = new ConcurrentLinkedQueue>(); - /** - * physics space for each thread - */ - private static ThreadLocal physicsSpaceTL = new ThreadLocal(); - /** - * copy of type of acceleration structure used - */ - private BroadphaseType broadphaseType = BroadphaseType.DBVT; -// private DiscreteDynamicsWorld dynamicsWorld = null; -// private BroadphaseInterface broadphase; -// private CollisionDispatcher dispatcher; -// private ConstraintSolver solver; -// private DefaultCollisionConfiguration collisionConfiguration; -// private Map physicsGhostNodes = new ConcurrentHashMap(); - private Map physicsGhostObjects = new ConcurrentHashMap(); - private Map physicsCharacters = new ConcurrentHashMap(); - private Map physicsBodies = new ConcurrentHashMap(); - private Map physicsJoints = new ConcurrentHashMap(); - private Map physicsVehicles = new ConcurrentHashMap(); - /** - * list of registered collision listeners - */ - final private List collisionListeners - = new SafeArrayList<>(PhysicsCollisionListener.class); - /** - * queue of collision events not yet distributed to listeners - */ - private ArrayDeque collisionEvents = new ArrayDeque(); - /** - * map from collision groups to registered group listeners - */ - private Map collisionGroupListeners = new ConcurrentHashMap(); - /** - * queue of registered tick listeners - */ - private ConcurrentLinkedQueue tickListeners = new ConcurrentLinkedQueue(); - private PhysicsCollisionEventFactory eventFactory = new PhysicsCollisionEventFactory(); - /** - * copy of minimum coordinate values when using AXIS_SWEEP broadphase - * algorithms - */ - private Vector3f worldMin = new Vector3f(-10000f, -10000f, -10000f); - /** - * copy of maximum coordinate values when using AXIS_SWEEP broadphase - * algorithms - */ - private Vector3f worldMax = new Vector3f(10000f, 10000f, 10000f); - /** - * physics time step (in seconds, >0) - */ - private float accuracy = 1f / 60f; - /** - * maximum number of physics steps per frame (≥0, default=4) - */ - private int maxSubSteps = 4; - /** - * flags used in ray tests - */ - private int rayTestFlags = 1 << 2; - /** - * copy of number of iterations used by the contact-and-constraint solver - * (default=10) - */ - private int solverNumIterations = 10; - - static { -// System.loadLibrary("bulletjme"); -// initNativePhysics(); - } - - /** - * Access the PhysicsSpace running on this thread. For parallel - * physics, this can be invoked from the OpenGL thread. - * - * @return the PhysicsSpace running on this thread - */ - public static PhysicsSpace getPhysicsSpace() { - return physicsSpaceTL.get(); - } - - /** - * Used internally - * - * @param space which physics space to simulate on this thread - */ - public static void setLocalThreadPhysicsSpace(PhysicsSpace space) { - physicsSpaceTL.set(space); - } - - /** - * Instantiate a PhysicsSpace. Must be invoked on the designated physics - * thread. - */ - public PhysicsSpace() { - this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), BroadphaseType.DBVT); - } - - /** - * Instantiate a PhysicsSpace. Must be invoked on the designated physics - * thread. - */ - public PhysicsSpace(BroadphaseType broadphaseType) { - this(new Vector3f(-10000f, -10000f, -10000f), new Vector3f(10000f, 10000f, 10000f), broadphaseType); - } - - /** - * Instantiate a PhysicsSpace. Must be invoked on the designated physics - * thread. - */ - public PhysicsSpace(Vector3f worldMin, Vector3f worldMax) { - this(worldMin, worldMax, BroadphaseType.AXIS_SWEEP_3); - } - - /** - * Instantiate a PhysicsSpace. Must be invoked on the designated physics - * thread. - * - * @param worldMin the desired minimum coordinates values (not null, - * unaffected, default=-10k,-10k,-10k) - * @param worldMax the desired minimum coordinates values (not null, - * unaffected, default=10k,10k,10k) - * @param broadphaseType which broadphase collision-detection algorithm to - * use (not null) - */ - public PhysicsSpace(Vector3f worldMin, Vector3f worldMax, BroadphaseType broadphaseType) { - this.worldMin.set(worldMin); - this.worldMax.set(worldMax); - this.broadphaseType = broadphaseType; - create(); - } - - /** - * Must be invoked on the designated physics thread. - */ - public void create() { - physicsSpaceId = createPhysicsSpace(worldMin.x, worldMin.y, worldMin.z, worldMax.x, worldMax.y, worldMax.z, broadphaseType.ordinal(), false); - pQueueTL.set(pQueue); - physicsSpaceTL.set(this); - -// collisionConfiguration = new DefaultCollisionConfiguration(); -// dispatcher = new CollisionDispatcher(collisionConfiguration); -// switch (broadphaseType) { -// case SIMPLE: -// broadphase = new SimpleBroadphase(); -// break; -// case AXIS_SWEEP_3: -// broadphase = new AxisSweep3(Converter.convert(worldMin), Converter.convert(worldMax)); -// break; -// case AXIS_SWEEP_3_32: -// broadphase = new AxisSweep3_32(Converter.convert(worldMin), Converter.convert(worldMax)); -// break; -// case DBVT: -// broadphase = new DbvtBroadphase(); -// break; -// } -// -// solver = new SequentialImpulseConstraintSolver(); -// -// dynamicsWorld = new DiscreteDynamicsWorld(dispatcher, broadphase, solver, collisionConfiguration); -// dynamicsWorld.setGravity(new javax.vecmath.Vector3f(0, -9.81f, 0)); -// -// broadphase.getOverlappingPairCache().setInternalGhostPairCallback(new GhostPairCallback()); -// GImpactCollisionAlgorithm.registerAlgorithm(dispatcher); -// -// //register filter callback for tick / collision -// setTickCallback(); -// setContactCallbacks(); -// //register filter callback for collision groups -// setOverlapFilterCallback(); - } - - private native long createPhysicsSpace(float minX, float minY, float minZ, float maxX, float maxY, float maxZ, int broadphaseType, boolean threading); - - /** - * Callback invoked just before the physics is stepped. - *

    - * This method is invoked from native code. - * - * @param timeStep the time per physics step (in seconds, ≥0) - */ - private void preTick_native(float f) { - AppTask task; - while((task=pQueue.poll())!=null){ - if(task.isCancelled())continue; - try{ - task.invoke(); - } catch (Exception ex) { - logger.log(Level.SEVERE, null, ex); - } - } - - for (Iterator it = tickListeners.iterator(); it.hasNext();) { - PhysicsTickListener physicsTickCallback = it.next(); - physicsTickCallback.prePhysicsTick(this, f); - } - } - - /** - * Callback invoked just after the physics is stepped. - *

    - * This method is invoked from native code. - * - * @param timeStep the time per physics step (in seconds, ≥0) - */ - private void postTick_native(float f) { - for (Iterator it = tickListeners.iterator(); it.hasNext();) { - PhysicsTickListener physicsTickCallback = it.next(); - physicsTickCallback.physicsTick(this, f); - } - } - - /** - * This method is invoked from native code. - */ - private void addCollision_native() { - } - - private boolean needCollision_native(PhysicsCollisionObject objectA, PhysicsCollisionObject objectB) { - return false; - } - -// private void setOverlapFilterCallback() { -// OverlapFilterCallback callback = new OverlapFilterCallback() { -// -// public boolean needBroadphaseCollision(BroadphaseProxy bp, BroadphaseProxy bp1) { -// boolean collides = (bp.collisionFilterGroup & bp1.collisionFilterMask) != 0; -// if (collides) { -// collides = (bp1.collisionFilterGroup & bp.collisionFilterMask) != 0; -// } -// if (collides) { -// assert (bp.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject && bp1.clientObject instanceof com.bulletphysics.collision.dispatch.CollisionObject); -// com.bulletphysics.collision.dispatch.CollisionObject colOb = (com.bulletphysics.collision.dispatch.CollisionObject) bp.clientObject; -// com.bulletphysics.collision.dispatch.CollisionObject colOb1 = (com.bulletphysics.collision.dispatch.CollisionObject) bp1.clientObject; -// assert (colOb.getUserPointer() != null && colOb1.getUserPointer() != null); -// PhysicsCollisionObject collisionObject = (PhysicsCollisionObject) colOb.getUserPointer(); -// PhysicsCollisionObject collisionObject1 = (PhysicsCollisionObject) colOb1.getUserPointer(); -// if ((collisionObject.getCollideWithGroups() & collisionObject1.getCollisionGroup()) > 0 -// || (collisionObject1.getCollideWithGroups() & collisionObject.getCollisionGroup()) > 0) { -// PhysicsCollisionGroupListener listener = collisionGroupListeners.get(collisionObject.getCollisionGroup()); -// PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(collisionObject1.getCollisionGroup()); -// if (listener != null) { -// return listener.collide(collisionObject, collisionObject1); -// } else if (listener1 != null) { -// return listener1.collide(collisionObject, collisionObject1); -// } -// return true; -// } else { -// return false; -// } -// } -// return collides; -// } -// }; -// dynamicsWorld.getPairCache().setOverlapFilterCallback(callback); -// } -// private void setTickCallback() { -// final PhysicsSpace space = this; -// InternalTickCallback callback2 = new InternalTickCallback() { -// -// @Override -// public void internalTick(DynamicsWorld dw, float f) { -// //execute task list -// AppTask task = pQueue.poll(); -// task = pQueue.poll(); -// while (task != null) { -// while (task.isCancelled()) { -// task = pQueue.poll(); -// } -// try { -// task.invoke(); -// } catch (Exception ex) { -// logger.log(Level.SEVERE, null, ex); -// } -// task = pQueue.poll(); -// } -// for (Iterator it = tickListeners.iterator(); it.hasNext();) { -// PhysicsTickListener physicsTickCallback = it.next(); -// physicsTickCallback.prePhysicsTick(space, f); -// } -// } -// }; -// dynamicsWorld.setPreTickCallback(callback2); -// InternalTickCallback callback = new InternalTickCallback() { -// -// @Override -// public void internalTick(DynamicsWorld dw, float f) { -// for (Iterator it = tickListeners.iterator(); it.hasNext();) { -// PhysicsTickListener physicsTickCallback = it.next(); -// physicsTickCallback.physicsTick(space, f); -// } -// } -// }; -// dynamicsWorld.setInternalTickCallback(callback, this); -// } -// private void setContactCallbacks() { -// BulletGlobals.setContactAddedCallback(new ContactAddedCallback() { -// -// public boolean contactAdded(ManifoldPoint cp, com.bulletphysics.collision.dispatch.CollisionObject colObj0, -// int partId0, int index0, com.bulletphysics.collision.dispatch.CollisionObject colObj1, int partId1, -// int index1) { -// System.out.println("contact added"); -// return true; -// } -// }); -// -// BulletGlobals.setContactProcessedCallback(new ContactProcessedCallback() { -// -// public boolean contactProcessed(ManifoldPoint cp, Object body0, Object body1) { -// if (body0 instanceof CollisionObject && body1 instanceof CollisionObject) { -// PhysicsCollisionObject node = null, node1 = null; -// CollisionObject rBody0 = (CollisionObject) body0; -// CollisionObject rBody1 = (CollisionObject) body1; -// node = (PhysicsCollisionObject) rBody0.getUserPointer(); -// node1 = (PhysicsCollisionObject) rBody1.getUserPointer(); -// collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, cp)); -// } -// return true; -// } -// }); -// -// BulletGlobals.setContactDestroyedCallback(new ContactDestroyedCallback() { -// -// public boolean contactDestroyed(Object userPersistentData) { -// System.out.println("contact destroyed"); -// return true; -// } -// }); -// } - private void addCollisionEvent_native(PhysicsCollisionObject node, PhysicsCollisionObject node1, long manifoldPointObjectId) { -// System.out.println("addCollisionEvent:"+node.getObjectId()+" "+ node1.getObjectId()); - collisionEvents.add(eventFactory.getEvent(PhysicsCollisionEvent.TYPE_PROCESSED, node, node1, manifoldPointObjectId)); - } - - /** - * This method is invoked from native code. - */ - private boolean notifyCollisionGroupListeners_native(PhysicsCollisionObject node, PhysicsCollisionObject node1){ - PhysicsCollisionGroupListener listener = collisionGroupListeners.get(node.getCollisionGroup()); - PhysicsCollisionGroupListener listener1 = collisionGroupListeners.get(node1.getCollisionGroup()); - boolean result = true; - - if(listener != null){ - result = listener.collide(node, node1); - } - if(listener1 != null && node.getCollisionGroup() != node1.getCollisionGroup()){ - result = listener1.collide(node, node1) && result; - } - - return result; - } - - /** - * Update this space. Invoked (by the Bullet app state) once per frame while - * the app state is attached and enabled. - * - * @param time time-per-frame multiplied by speed (in seconds, ≥0) - */ - public void update(float time) { - update(time, maxSubSteps); - } - - /** - * Simulate for the specified time interval, using no more than the - * specified number of steps. - * - * @param time the time interval (in seconds, ≥0) - * @param maxSteps the maximum number of steps (≥1) - */ - public void update(float time, int maxSteps) { -// if (getDynamicsWorld() == null) { -// return; -// } - //step simulation - stepSimulation(physicsSpaceId, time, maxSteps, accuracy); - } - - private native void stepSimulation(long space, float time, int maxSteps, float accuracy); - - /** - * Distribute each collision event to all listeners. - */ - public void distributeEvents() { - //add collision callbacks - int clistsize = collisionListeners.size(); - while( collisionEvents.isEmpty() == false ) { - PhysicsCollisionEvent physicsCollisionEvent = collisionEvents.pop(); - for(int i=0;i the task's result type - * @param callable the task to be executed - * @return a new task (not null) - */ - public static Future enqueueOnThisThread(Callable callable) { - AppTask task = new AppTask(callable); - System.out.println("created apptask"); - pQueueTL.get().add(task); - return task; - } - - /** - * Invoke the specified callable during the next physics tick. This is - * useful for applying forces. - * - * @param the return type of the callable - * @param callable which callable to invoke - * @return Future object - */ - public Future enqueue(Callable callable) { - AppTask task = new AppTask(callable); - pQueue.add(task); - return task; - } - - /** - * Add the specified object to this space. - * - * @param obj the PhysicsControl, Spatial-with-PhysicsControl, - * PhysicsCollisionObject, or PhysicsJoint to add (not null, modified) - */ - public void add(Object obj) { - if (obj instanceof PhysicsControl) { - ((PhysicsControl) obj).setPhysicsSpace(this); - } else if (obj instanceof Spatial) { - Spatial node = (Spatial) obj; - for (int i = 0; i < node.getNumControls(); i++) { - if (node.getControl(i) instanceof PhysicsControl) { - add(((PhysicsControl) node.getControl(i))); - } - } - } else if (obj instanceof PhysicsCollisionObject) { - addCollisionObject((PhysicsCollisionObject) obj); - } else if (obj instanceof PhysicsJoint) { - addJoint((PhysicsJoint) obj); - } else { - throw (new UnsupportedOperationException("Cannot add this kind of object to the physics space.")); - } - } - - /** - * Add the specified collision object to this space. - * - * @param obj the PhysicsCollisionObject to add (not null, modified) - */ - public void addCollisionObject(PhysicsCollisionObject obj) { - if (obj instanceof PhysicsGhostObject) { - addGhostObject((PhysicsGhostObject) obj); - } else if (obj instanceof PhysicsRigidBody) { - addRigidBody((PhysicsRigidBody) obj); - } else if (obj instanceof PhysicsVehicle) { - addRigidBody((PhysicsVehicle) obj); - } else if (obj instanceof PhysicsCharacter) { - addCharacter((PhysicsCharacter) obj); - } - } - - /** - * Remove the specified object from this space. - * - * @param obj the PhysicsCollisionObject to add, or null (modified) - */ - public void remove(Object obj) { - if (obj == null) return; - if (obj instanceof PhysicsControl) { - ((PhysicsControl) obj).setPhysicsSpace(null); - } else if (obj instanceof Spatial) { - Spatial node = (Spatial) obj; - for (int i = 0; i < node.getNumControls(); i++) { - if (node.getControl(i) instanceof PhysicsControl) { - remove(((PhysicsControl) node.getControl(i))); - } - } - } else if (obj instanceof PhysicsCollisionObject) { - removeCollisionObject((PhysicsCollisionObject) obj); - } else if (obj instanceof PhysicsJoint) { - removeJoint((PhysicsJoint) obj); - } else { - throw (new UnsupportedOperationException("Cannot remove this kind of object from the physics space.")); - } - } - - /** - * Remove the specified collision object from this space. - * - * @param obj the PhysicsControl or Spatial with PhysicsControl to remove - */ - public void removeCollisionObject(PhysicsCollisionObject obj) { - if (obj instanceof PhysicsGhostObject) { - removeGhostObject((PhysicsGhostObject) obj); - } else if (obj instanceof PhysicsRigidBody) { - removeRigidBody((PhysicsRigidBody) obj); - } else if (obj instanceof PhysicsCharacter) { - removeCharacter((PhysicsCharacter) obj); - } - } - - /** - * Add all collision objects and joints in the specified subtree of the - * scene graph to this space (e.g. after loading from disk). Note: - * recursive! - * - * @param spatial the root of the subtree (not null) - */ - public void addAll(Spatial spatial) { - add(spatial); - - if (spatial.getControl(RigidBodyControl.class) != null) { - RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class); - //add joints with physicsNode as BodyA - List joints = physicsNode.getJoints(); - for (Iterator it1 = joints.iterator(); it1.hasNext();) { - PhysicsJoint physicsJoint = it1.next(); - if (physicsNode.equals(physicsJoint.getBodyA())) { - //add(physicsJoint.getBodyB()); - add(physicsJoint); - } - } - } - //recursion - if (spatial instanceof Node) { - List children = ((Node) spatial).getChildren(); - for (Iterator it = children.iterator(); it.hasNext();) { - Spatial spat = it.next(); - addAll(spat); - } - } - } - - /** - * Remove all physics controls and joints in the specified subtree of the - * scene graph from the physics space (e.g. before saving to disk) Note: - * recursive! - * - * @param spatial the root of the subtree (not null) - */ - public void removeAll(Spatial spatial) { - if (spatial.getControl(RigidBodyControl.class) != null) { - RigidBodyControl physicsNode = spatial.getControl(RigidBodyControl.class); - //remove joints with physicsNode as BodyA - List joints = physicsNode.getJoints(); - for (Iterator it1 = joints.iterator(); it1.hasNext();) { - PhysicsJoint physicsJoint = it1.next(); - if (physicsNode.equals(physicsJoint.getBodyA())) { - removeJoint(physicsJoint); - //remove(physicsJoint.getBodyB()); - } - } - } - - remove(spatial); - //recursion - if (spatial instanceof Node) { - List children = ((Node) spatial).getChildren(); - for (Iterator it = children.iterator(); it.hasNext();) { - Spatial spat = it.next(); - removeAll(spat); - } - } - } - - private native void addCollisionObject(long space, long id); - - private native void removeCollisionObject(long space, long id); - - private native void addRigidBody(long space, long id); - - private native void removeRigidBody(long space, long id); - - private native void addCharacterObject(long space, long id); - - private native void removeCharacterObject(long space, long id); - - private native void addAction(long space, long id); - - private native void removeAction(long space, long id); - - private native void addVehicle(long space, long id); - - private native void removeVehicle(long space, long id); - - private native void addConstraint(long space, long id); - - private native void addConstraintC(long space, long id, boolean collision); - - private native void removeConstraint(long space, long id); - - private void addGhostObject(PhysicsGhostObject node) { - if (physicsGhostObjects.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "GhostObject {0} already exists in PhysicsSpace, cannot add.", node); - return; - } - physicsGhostObjects.put(node.getObjectId(), node); - logger.log(Level.FINE, "Adding ghost object {0} to physics space.", Long.toHexString(node.getObjectId())); - addCollisionObject(physicsSpaceId, node.getObjectId()); - } - - private void removeGhostObject(PhysicsGhostObject node) { - if (!physicsGhostObjects.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "GhostObject {0} does not exist in PhysicsSpace, cannot remove.", node); - return; - } - physicsGhostObjects.remove(node.getObjectId()); - logger.log(Level.FINE, "Removing ghost object {0} from physics space.", Long.toHexString(node.getObjectId())); - removeCollisionObject(physicsSpaceId, node.getObjectId()); - } - - private void addCharacter(PhysicsCharacter node) { - if (physicsCharacters.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "Character {0} already exists in PhysicsSpace, cannot add.", node); - return; - } - physicsCharacters.put(node.getObjectId(), node); - logger.log(Level.FINE, "Adding character {0} to physics space.", Long.toHexString(node.getObjectId())); - addCharacterObject(physicsSpaceId, node.getObjectId()); - addAction(physicsSpaceId, node.getControllerId()); -// dynamicsWorld.addCollisionObject(node.getObjectId(), CollisionFilterGroups.CHARACTER_FILTER, (short) (CollisionFilterGroups.STATIC_FILTER | CollisionFilterGroups.DEFAULT_FILTER)); -// dynamicsWorld.addAction(node.getControllerId()); - } - - private void removeCharacter(PhysicsCharacter node) { - if (!physicsCharacters.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "Character {0} does not exist in PhysicsSpace, cannot remove.", node); - return; - } - physicsCharacters.remove(node.getObjectId()); - logger.log(Level.FINE, "Removing character {0} from physics space.", Long.toHexString(node.getObjectId())); - removeAction(physicsSpaceId, node.getControllerId()); - removeCharacterObject(physicsSpaceId, node.getObjectId()); -// dynamicsWorld.removeAction(node.getControllerId()); -// dynamicsWorld.removeCollisionObject(node.getObjectId()); - } - - /** - * NOTE: When a rigid body is added, its gravity gets set to that of the - * physics space. - * - * @param node the body to add (not null, not already in the space) - */ - private void addRigidBody(PhysicsRigidBody node) { - if (physicsBodies.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "RigidBody {0} already exists in PhysicsSpace, cannot add.", node); - return; - } - physicsBodies.put(node.getObjectId(), node); - - //Workaround - //It seems that adding a Kinematic RigidBody to the dynamicWorld prevents it from being non-kinematic again afterward. - //so we add it non kinematic, then set it kinematic again. - boolean kinematic = false; - if (node.isKinematic()) { - kinematic = true; - node.setKinematic(false); - } - addRigidBody(physicsSpaceId, node.getObjectId()); - if (kinematic) { - node.setKinematic(true); - } - - logger.log(Level.FINE, "Adding RigidBody {0} to physics space.", node.getObjectId()); - if (node instanceof PhysicsVehicle) { - PhysicsVehicle vehicle = (PhysicsVehicle) node; - vehicle.createVehicle(this); - long vehicleId = vehicle.getVehicleId(); - assert vehicleId != 0L; - logger.log(Level.FINE, - "Adding vehicle constraint {0} to physics space.", - Long.toHexString(vehicleId)); - physicsVehicles.put(vehicleId, vehicle); - addVehicle(physicsSpaceId, vehicleId); - } - } - - private void removeRigidBody(PhysicsRigidBody node) { - if (!physicsBodies.containsKey(node.getObjectId())) { - logger.log(Level.WARNING, "RigidBody {0} does not exist in PhysicsSpace, cannot remove.", node); - return; - } - if (node instanceof PhysicsVehicle) { - logger.log(Level.FINE, "Removing vehicle constraint {0} from physics space.", Long.toHexString(((PhysicsVehicle) node).getVehicleId())); - physicsVehicles.remove(((PhysicsVehicle) node).getVehicleId()); - removeVehicle(physicsSpaceId, ((PhysicsVehicle) node).getVehicleId()); - } - logger.log(Level.FINE, "Removing RigidBody {0} from physics space.", Long.toHexString(node.getObjectId())); - physicsBodies.remove(node.getObjectId()); - removeRigidBody(physicsSpaceId, node.getObjectId()); - } - - private void addJoint(PhysicsJoint joint) { - if (physicsJoints.containsKey(joint.getObjectId())) { - logger.log(Level.WARNING, "Joint {0} already exists in PhysicsSpace, cannot add.", joint); - return; - } - logger.log(Level.FINE, "Adding Joint {0} to physics space.", Long.toHexString(joint.getObjectId())); - physicsJoints.put(joint.getObjectId(), joint); - addConstraintC(physicsSpaceId, joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys()); -// dynamicsWorld.addConstraint(joint.getObjectId(), !joint.isCollisionBetweenLinkedBodys()); - } - - private void removeJoint(PhysicsJoint joint) { - if (!physicsJoints.containsKey(joint.getObjectId())) { - logger.log(Level.WARNING, "Joint {0} does not exist in PhysicsSpace, cannot remove.", joint); - return; - } - logger.log(Level.FINE, "Removing Joint {0} from physics space.", Long.toHexString(joint.getObjectId())); - physicsJoints.remove(joint.getObjectId()); - removeConstraint(physicsSpaceId, joint.getObjectId()); -// dynamicsWorld.removeConstraint(joint.getObjectId()); - } - - /** - * Copy the list of rigid bodies that have been added to this space and not - * yet removed. - * - * @return a new list (not null) - */ - public Collection getRigidBodyList() { - return new LinkedList(physicsBodies.values()); - } - - /** - * Copy the list of ghost objects that have been added to this space and not - * yet removed. - * - * @return a new list (not null) - */ - public Collection getGhostObjectList() { - return new LinkedList(physicsGhostObjects.values()); - } - - /** - * Copy the list of physics characters that have been added to this space - * and not yet removed. - * - * @return a new list (not null) - */ - public Collection getCharacterList() { - return new LinkedList(physicsCharacters.values()); - } - - /** - * Copy the list of physics joints that have been added to this space and - * not yet removed. - * - * @return a new list (not null) - */ - public Collection getJointList() { - return new LinkedList(physicsJoints.values()); - } - - /** - * Copy the list of physics vehicles that have been added to this space and - * not yet removed. - * - * @return a new list (not null) - */ - public Collection getVehicleList() { - return new LinkedList(physicsVehicles.values()); - } - - /** - * Alter the gravitational acceleration acting on newly-added bodies. - *

    - * Whenever a rigid body is added to a space, the body's gravity gets set to - * that of the space. Thus it makes sense to set the space's vector before - * adding any bodies to the space. - * - * @param gravity the desired acceleration vector (not null, unaffected) - */ - public void setGravity(Vector3f gravity) { - this.gravity.set(gravity); - setGravity(physicsSpaceId, gravity); - } - - private native void setGravity(long spaceId, Vector3f gravity); - - /** - * copy of gravity-acceleration vector (default is 9.81 in the -Y direction, - * corresponding to Earth-normal in MKS units) - */ - private final Vector3f gravity = new Vector3f(0,-9.81f,0); - /** - * Copy the gravitational acceleration acting on newly-added bodies. - * - * @param gravity storage for the result (not null, modified) - * @return acceleration (in the vector provided) - */ - public Vector3f getGravity(Vector3f gravity) { - return gravity.set(this.gravity); - } - -// /** -// * applies gravity value to all objects -// */ -// public void applyGravity() { -//// dynamicsWorld.applyGravity(); -// } -// -// /** -// * clears forces of all objects -// */ -// public void clearForces() { -//// dynamicsWorld.clearForces(); -// } -// - /** - * Register the specified tick listener with this space. - *

    - * Tick listeners are notified before and after each physics step. A physics - * step is not necessarily the same as a frame; it is more influenced by the - * accuracy of the physics space. - * - * @see #setAccuracy(float) - * - * @param listener the listener to register (not null) - */ - public void addTickListener(PhysicsTickListener listener) { - tickListeners.add(listener); - } - - /** - * De-register the specified tick listener. - * - * @see #addTickListener(com.jme3.bullet.PhysicsTickListener) - * @param listener the listener to de-register (not null) - */ - public void removeTickListener(PhysicsTickListener listener) { - tickListeners.remove(listener); - } - - /** - * Register the specified collision listener with this space. - *

    - * Collision listeners are notified when collisions occur in the space. - * - * @param listener the listener to register (not null, alias created) - */ - public void addCollisionListener(PhysicsCollisionListener listener) { - collisionListeners.add(listener); - } - - /** - * De-register the specified collision listener. - * - * @see - * #addCollisionListener(com.jme3.bullet.collision.PhysicsCollisionListener) - * @param listener the listener to de-register (not null) - */ - public void removeCollisionListener(PhysicsCollisionListener listener) { - collisionListeners.remove(listener); - } - - /** - * Register the specified collision-group listener with the specified - * collision group of this space. - *

    - * Such a listener can disable collisions when they occur. There can be only - * one listener per collision group per space. - * - * @param listener the listener to register (not null) - * @param collisionGroup which group it should listen for (bit mask with - * exactly one bit set) - */ - public void addCollisionGroupListener(PhysicsCollisionGroupListener listener, int collisionGroup) { - collisionGroupListeners.put(collisionGroup, listener); - } - - /** - * De-register the specified collision-group listener. - * - * @see - * #addCollisionGroupListener(com.jme3.bullet.collision.PhysicsCollisionGroupListener, - * int) - * @param collisionGroup the group of the listener to de-register (bit mask - * with exactly one bit set) - */ - public void removeCollisionGroupListener(int collisionGroup) { - collisionGroupListeners.remove(collisionGroup); - } - - /** - * Perform a ray-collision test and return the results as a list of - * PhysicsRayTestResults sorted by ascending hitFraction. - * - * @param from the starting location (physics-space coordinates, not null, - * unaffected) - * @param to the ending location (in physics-space coordinates, not null, - * unaffected) - * @return a new list of results (not null) - */ - public List rayTest(Vector3f from, Vector3f to) { - List results = new ArrayList(); - rayTest(from, to, results); - - return results; - } - - /** - * Perform a ray-collision test and return the results as a list of - * PhysicsRayTestResults in arbitrary order. - * - * @param from the starting location (in physics-space coordinates, not - * null, unaffected) - * @param to the ending location (in physics-space coordinates, not null, - * unaffected) - * @return a new list of results (not null) - */ - public List rayTestRaw(Vector3f from, Vector3f to) { - List results = new ArrayList(); - rayTestRaw(from, to, results); - - return results; - } - - /** - * Alters the m_flags used in ray tests. see - * https://code.google.com/p/bullet/source/browse/trunk/src/BulletCollision/NarrowPhaseCollision/btRaycastCallback.h - * for possible options. Defaults to using the faster, approximate raytest. - * - * @param flags the desired flags, ORed together (default=0x4) - */ - public void SetRayTestFlags(int flags) { - rayTestFlags = flags; - } - - /** - * Reads m_flags used in ray tests. see - * https://code.google.com/p/bullet/source/browse/trunk/src/BulletCollision/NarrowPhaseCollision/btRaycastCallback.h - * for possible options. - * - * @return which flags are used - */ - public int GetRayTestFlags() { - return rayTestFlags; - } - - private static Comparator hitFractionComparator = new Comparator() { - @Override - public int compare(PhysicsRayTestResult r1, PhysicsRayTestResult r2) { - float comp = r1.getHitFraction() - r2.getHitFraction(); - return comp > 0 ? 1 : -1; - } - }; - - /** - * Perform a ray-collision test and return the results as a list of - * PhysicsRayTestResults sorted by ascending hitFraction. - * - * @param from coordinates of the starting location (in physics space, not - * null, unaffected) - * @param to coordinates of the ending location (in physics space, not null, - * unaffected) - * @param results the list to hold results (not null, modified) - * @return results - */ - public List rayTest(Vector3f from, Vector3f to, List results) { - results.clear(); - rayTest_native(from, to, physicsSpaceId, results, rayTestFlags); - - Collections.sort(results, hitFractionComparator); - return results; - } - - /** - * Perform a ray-collision test and return the results as a list of - * PhysicsRayTestResults in arbitrary order. - * - * @param from coordinates of the starting location (in physics space, not - * null, unaffected) - * @param to coordinates of the ending location (in physics space, not null, - * unaffected) - * @param results the list to hold results (not null, modified) - * @return results - */ - public List rayTestRaw(Vector3f from, Vector3f to, List results) { - results.clear(); - rayTest_native(from, to, physicsSpaceId, results, rayTestFlags); - return results; - } - - public native void rayTest_native(Vector3f from, Vector3f to, long physicsSpaceId, List results, int flags); - -// private class InternalRayListener extends CollisionWorld.RayResultCallback { -// -// private List results; -// -// public InternalRayListener(List results) { -// this.results = results; -// } -// -// @Override -// public float addSingleResult(LocalRayResult lrr, boolean bln) { -// PhysicsCollisionObject obj = (PhysicsCollisionObject) lrr.collisionObject.getUserPointer(); -// results.add(new PhysicsRayTestResult(obj, Converter.convert(lrr.hitNormalLocal), lrr.hitFraction, bln)); -// return lrr.hitFraction; -// } -// } -// -// - - - /** - * Perform a sweep-collision test and return the results as a new list. - *

    - * The starting and ending locations must be at least 0.4f physics-space - * units apart. - *

    - * A sweep test will miss a collision if it starts inside an object and - * sweeps away from the object's center. - * - * @param shape the shape to sweep (not null) - * @param start the starting physics-space transform (not null) - * @param end the ending physics-space transform (not null) - * @return a new list of results - */ - public List sweepTest(CollisionShape shape, Transform start, Transform end) { - List results = new LinkedList(); - sweepTest(shape, start, end , results); - return (List) results; - } - - /** - * Perform a sweep-collision test and store the results in an existing list. - *

    - * The starting and ending locations must be at least 0.4f physics-space - * units apart. - *

    - * A sweep test will miss a collision if it starts inside an object and - * sweeps away from the object's center. - * - * @param shape the shape to sweep (not null) - * @param start the starting physics-space transform (not null) - * @param end the ending physics-space transform (not null) - * @param results the list to hold results (not null, modified) - * @return results - */ - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { - return sweepTest(shape, start, end, results, 0.0f); - } - - public native void sweepTest_native(long shape, Transform from, Transform to, long physicsSpaceId, List results, float allowedCcdPenetration); - /** - * Perform a sweep-collision test and store the results in an existing list. - *

    - * The starting and ending locations must be at least 0.4f physics-space - * units apart. - *

    - * A sweep test will miss a collision if it starts inside an object and - * sweeps away from the object's center. - * - * @param shape the shape to sweep (not null) - * @param start the starting physics-space transform (not null) - * @param end the ending physics-space transform (not null) - * @param results the list to hold results (not null, modified) - * @param allowedCcdPenetration true→allow, false→disallow - * @return results - */ - public List sweepTest(CollisionShape shape, Transform start, Transform end, List results, float allowedCcdPenetration ) { - results.clear(); - sweepTest_native(shape.getObjectId(), start, end, physicsSpaceId, results, allowedCcdPenetration); - return results; - } - -/* private class InternalSweepListener extends CollisionWorld.ConvexResultCallback { - - private List results; - - public InternalSweepListener(List results) { - this.results = results; - } - - @Override - public float addSingleResult(LocalConvexResult lcr, boolean bln) { - PhysicsCollisionObject obj = (PhysicsCollisionObject) lcr.hitCollisionObject.getUserPointer(); - results.add(new PhysicsSweepTestResult(obj, Converter.convert(lcr.hitNormalLocal), lcr.hitFraction, bln)); - return lcr.hitFraction; - } - } - - */ - - /** - * Destroy this space so that a new one can be instantiated. - */ - public void destroy() { - physicsBodies.clear(); - physicsJoints.clear(); - -// dynamicsWorld.destroy(); -// dynamicsWorld = null; - } - - /** - * // * used internally // - * - * @return the dynamicsWorld // - */ - public long getSpaceId() { - return physicsSpaceId; - } - - /** - * Read the type of acceleration structure used. - * - * @return an enum value (not null) - */ - public BroadphaseType getBroadphaseType() { - return broadphaseType; - } - - /** - * Alter the type of acceleration structure used. - * - * @param broadphaseType the desired algorithm (not null) - */ - public void setBroadphaseType(BroadphaseType broadphaseType) { - this.broadphaseType = broadphaseType; - } - - /** - * Alter the maximum number of physics steps per frame. - *

    - * Extra physics steps help maintain determinism when the render fps drops - * below 1/accuracy. For example a value of 2 can compensate for frame rates - * as low as 30fps, assuming the physics has an accuracy of 1/60 sec. - *

    - * Setting this value too high can depress the frame rate. - * - * @param steps the desired maximum number of steps per frame (≥1, - * default=4) - */ - public void setMaxSubSteps(int steps) { - maxSubSteps = steps; - } - - /** - * Read the accuracy (time step) of the physics simulation. - * - * @return the timestep (in seconds, >0) - */ - public float getAccuracy() { - return accuracy; - } - - /** - * Alter the accuracy (time step) of the physics simulation. - *

    - * In general, the smaller the time step, the more accurate (and - * compute-intensive) the simulation will be. Bullet works best with a - * time step of no more than 1/60 second. - * - * @param accuracy the desired time step (in seconds, >0, default=1/60) - */ - public void setAccuracy(float accuracy) { - this.accuracy = accuracy; - } - - /** - * Access the minimum coordinate values for this space. - * - * @return the pre-existing vector - */ - public Vector3f getWorldMin() { - return worldMin; - } - - /** - * Alter the minimum coordinate values for this space. (only affects - * AXIS_SWEEP broadphase algorithms) - * - * @param worldMin the desired minimum coordinate values (not null, - * unaffected) - */ - public void setWorldMin(Vector3f worldMin) { - this.worldMin.set(worldMin); - } - - /** - * Access the maximum coordinate values for this space. - * - * @return the pre-existing vector (not null) - */ - public Vector3f getWorldMax() { - return worldMax; - } - - /** - * only applies for AXIS_SWEEP broadphase - * - * @param worldMax - */ - public void setWorldMax(Vector3f worldMax) { - this.worldMax.set(worldMax); - } - - /** - * Alter the number of iterations used by the contact-and-constraint solver. - *

    - * Use 4 for low quality, 20 for high quality. - * - * @param numIterations the desired number of iterations (≥1, default=10) - */ - public void setSolverNumIterations(int numIterations) { - this.solverNumIterations = numIterations; - setSolverNumIterations(physicsSpaceId, numIterations); - } - - /** - * Read the number of iterations used by the contact-and-constraint solver. - * - * @return the number of iterations used - */ - public int getSolverNumIterations() { - return solverNumIterations; - } - - private native void setSolverNumIterations(long physicsSpaceId, int numIterations); - - public static native void initNativePhysics(); - - /** - * Enumerate the available acceleration structures for broadphase collision - * detection. - */ - public enum BroadphaseType { - - /** - * btSimpleBroadphase: a brute-force reference implementation for - * debugging purposes - */ - SIMPLE, - /** - * btAxisSweep3: uses incremental 3-D sweep and prune, requires world - * bounds, limited to 16_384 objects - */ - AXIS_SWEEP_3, - /** - * bt32BitAxisSweep3: uses incremental 3-D sweep and prune, requires - * world bounds, limited to 65_536 objects - */ - AXIS_SWEEP_3_32, - /** - * btDbvtBroadphase: uses a fast, dynamic bounding-volume hierarchy - * based on AABB tree to allow quicker addition/removal of physics - * objects - */ - DBVT; - } - - /** - * Finalize this physics space just before it is destroyed. Should be - * invoked only by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing PhysicsSpace {0}", Long.toHexString(physicsSpaceId)); - finalizeNative(physicsSpaceId); - } - - private native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java deleted file mode 100644 index 6445af53f3..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/CollisionFlag.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2018 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.bullet.collision; - -/** - * Named collision flags for a {@link PhysicsCollisionObject}. Values must agree - * with those in BulletCollision/CollisionDispatch/btCollisionObject.h - * - * @author Stephen Gold sgold@sonic.net - * @see PhysicsCollisionObject#getCollisionFlags(long) - */ -public class CollisionFlag { - /** - * flag for a static object - */ - final public static int STATIC_OBJECT = 0x1; - /** - * flag for a kinematic object - */ - final public static int KINEMATIC_OBJECT = 0x2; - /** - * flag for an object with no contact response, such as a PhysicsGhostObject - */ - final public static int NO_CONTACT_RESPONSE = 0x4; - /** - * flag to enable a custom material callback for per-triangle - * friction/restitution (not used by JME) - */ - final public static int CUSTOM_MATERIAL_CALLBACK = 0x8; - /** - * flag for a character object, such as a PhysicsCharacter - */ - final public static int CHARACTER_OBJECT = 0x10; - /** - * flag to disable debug visualization (not used by JME) - */ - final public static int DISABLE_VISUALIZE_OBJECT = 0x20; - /** - * flag to disable parallel/SPU processing (not used by JME) - */ - final public static int DISABLE_SPU_COLLISION_PROCESSING = 0x40; - /** - * flag not used by JME - */ - final public static int HAS_CONTACT_STIFFNESS_DAMPING = 0x80; - /** - * flag not used by JME - */ - final public static int HAS_CUSTOM_DEBUG_RENDERING_COLOR = 0x100; - /** - * flag not used by JME - */ - final public static int HAS_FRICTION_ANCHOR = 0x200; - /** - * flag not used by JME - */ - final public static int HAS_COLLISION_SOUND_TRIGGER = 0x400; -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java deleted file mode 100644 index bb62760020..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java +++ /dev/null @@ -1,443 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.collision; - -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.util.EventObject; - -/** - * Describe a collision in the physics world. - *

    - * Do not retain this object, as it will be reused after the collision() method - * returns. Copy any data you need during the collide() method. - * - * @author normenhansen - */ -public class PhysicsCollisionEvent extends EventObject { - - /** - * type value to indicate a new event - */ - public static final int TYPE_ADDED = 0; - /** - * type value to indicate an event that has been added to a PhysicsSpace - * queue - */ - public static final int TYPE_PROCESSED = 1; - /** - * type value to indicate a cleaned/destroyed event - */ - public static final int TYPE_DESTROYED = 2; - /** - * type value that indicates the event's status - */ - private int type; - /** - * 1st involved object - */ - private PhysicsCollisionObject nodeA; - /** - * 2nd involved object - */ - private PhysicsCollisionObject nodeB; - /** - * Bullet identifier of the btManifoldPoint - */ - private long manifoldPointObjectId = 0; - - /** - * Instantiate a collision event. - * - * @param type event type (0=added/1=processed/2=destroyed) - * @param nodeA 1st involved object (alias created) - * @param nodeB 2nd involved object (alias created) - * @param manifoldPointObjectId Bullet identifier of the btManifoldPoint - */ - public PhysicsCollisionEvent(int type, PhysicsCollisionObject nodeA, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { - super(nodeA); - this.type = type; - this.nodeA = nodeA; - this.nodeB = nodeB; - this.manifoldPointObjectId = manifoldPointObjectId; - } - - /** - * Destroy this event. - */ - public void clean() { - source = null; - this.type = 0; - this.nodeA = null; - this.nodeB = null; - this.manifoldPointObjectId = 0; - } - - /** - * Reuse this event. - * - * @param type event type (added/processed/destroyed) - * @param source 1st involved object (alias created) - * @param nodeB 2nd involved object (alias created) - * @param manifoldPointObjectId Bullet identifier - */ - public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { - this.source = source; - this.type = type; - this.nodeA = source; - this.nodeB = nodeB; - this.manifoldPointObjectId = manifoldPointObjectId; - } - - /** - * Read the type of event. - * - * @return added/processed/destroyed - */ - public int getType() { - return type; - } - - /** - * Access the user object of collision object A, provided it's a Spatial. - * - * @return the pre-existing Spatial, or null if none - */ - public Spatial getNodeA() { - if (nodeA.getUserObject() instanceof Spatial) { - return (Spatial) nodeA.getUserObject(); - } - return null; - } - - /** - * Access the user object of collision object B, provided it's a Spatial. - * - * @return the pre-existing Spatial, or null if none - */ - public Spatial getNodeB() { - if (nodeB.getUserObject() instanceof Spatial) { - return (Spatial) nodeB.getUserObject(); - } - return null; - } - - /** - * Access collision object A. - * - * @return the pre-existing object (not null) - */ - public PhysicsCollisionObject getObjectA() { - return nodeA; - } - - /** - * Access collision object B. - * - * @return the pre-existing object (not null) - */ - public PhysicsCollisionObject getObjectB() { - return nodeB; - } - - /** - * Read the collision's applied impulse. - * - * @return impulse - */ - public float getAppliedImpulse() { - return getAppliedImpulse(manifoldPointObjectId); - } - private native float getAppliedImpulse(long manifoldPointObjectId); - - /** - * Read the collision's applied lateral impulse #1. - * - * @return impulse - */ - public float getAppliedImpulseLateral1() { - return getAppliedImpulseLateral1(manifoldPointObjectId); - } - private native float getAppliedImpulseLateral1(long manifoldPointObjectId); - - /** - * Read the collision's applied lateral impulse #2. - * - * @return impulse - */ - public float getAppliedImpulseLateral2() { - return getAppliedImpulseLateral2(manifoldPointObjectId); - } - private native float getAppliedImpulseLateral2(long manifoldPointObjectId); - - /** - * Read the collision's combined friction. - * - * @return friction - */ - public float getCombinedFriction() { - return getCombinedFriction(manifoldPointObjectId); - } - private native float getCombinedFriction(long manifoldPointObjectId); - - /** - * Read the collision's combined restitution. - * - * @return restitution - */ - public float getCombinedRestitution() { - return getCombinedRestitution(manifoldPointObjectId); - } - private native float getCombinedRestitution(long manifoldPointObjectId); - - /** - * Read the collision's distance #1. - * - * @return distance - */ - public float getDistance1() { - return getDistance1(manifoldPointObjectId); - } - private native float getDistance1(long manifoldPointObjectId); - - /** - * Read the collision's index 0. - * - * @return index - */ - public int getIndex0() { - return getIndex0(manifoldPointObjectId); - } - private native int getIndex0(long manifoldPointObjectId); - - /** - * Read the collision's index 1. - * - * @return index - */ - public int getIndex1() { - return getIndex1(manifoldPointObjectId); - } - private native int getIndex1(long manifoldPointObjectId); - - /** - * Copy the collision's lateral friction direction #1. - * - * @return a new vector (not null) - */ - public Vector3f getLateralFrictionDir1() { - return getLateralFrictionDir1(new Vector3f()); - } - - /** - * Copy the collision's lateral friction direction #1. - * - * @param lateralFrictionDir1 storage for the result (not null, modified) - * @return direction vector (not null) - */ - public Vector3f getLateralFrictionDir1(Vector3f lateralFrictionDir1) { - getLateralFrictionDir1(manifoldPointObjectId, lateralFrictionDir1); - return lateralFrictionDir1; - } - private native void getLateralFrictionDir1(long manifoldPointObjectId, Vector3f lateralFrictionDir1); - - /** - * Copy the collision's lateral friction direction #2. - * - * @return a new vector - */ - public Vector3f getLateralFrictionDir2() { - return getLateralFrictionDir2(new Vector3f()); - } - - /** - * Copy the collision's lateral friction direction #2. - * - * @param lateralFrictionDir2 storage for the result (not null, modified) - * @return direction vector (not null) - */ - public Vector3f getLateralFrictionDir2(Vector3f lateralFrictionDir2) { - getLateralFrictionDir2(manifoldPointObjectId, lateralFrictionDir2); - return lateralFrictionDir2; - } - private native void getLateralFrictionDir2(long manifoldPointObjectId, Vector3f lateralFrictionDir2); - - /** - * Test whether the collision's lateral friction is initialized. - * - * @return true if initialized, otherwise false - */ - public boolean isLateralFrictionInitialized() { - return isLateralFrictionInitialized(manifoldPointObjectId); - } - private native boolean isLateralFrictionInitialized(long manifoldPointObjectId); - - /** - * Read the collision's lifetime. - * - * @return lifetime - */ - public int getLifeTime() { - return getLifeTime(manifoldPointObjectId); - } - private native int getLifeTime(long manifoldPointObjectId); - - /** - * Copy the collision's location in the local coordinates of object A. - * - * @return a new location vector (in local coordinates, not null) - */ - public Vector3f getLocalPointA() { - return getLocalPointA(new Vector3f()); - } - - /** - * Copy the collision's location in the local coordinates of object A. - * - * @param localPointA storage for the result (not null, modified) - * @return a location vector (in local coordinates, not null) - */ - public Vector3f getLocalPointA(Vector3f localPointA) { - getLocalPointA(manifoldPointObjectId, localPointA); - return localPointA; - } - private native void getLocalPointA(long manifoldPointObjectId, Vector3f localPointA); - - /** - * Copy the collision's location in the local coordinates of object B. - * - * @return a new location vector (in local coordinates, not null) - */ - public Vector3f getLocalPointB() { - return getLocalPointB(new Vector3f()); - } - - /** - * Copy the collision's location in the local coordinates of object B. - * - * @param localPointB storage for the result (not null, modified) - * @return a location vector (in local coordinates, not null) - */ - public Vector3f getLocalPointB(Vector3f localPointB) { - getLocalPointB(manifoldPointObjectId, localPointB); - return localPointB; - } - private native void getLocalPointB(long manifoldPointObjectId, Vector3f localPointB); - - /** - * Copy the collision's normal on object B. - * - * @return a new normal vector (in physics-space coordinates, not null) - */ - public Vector3f getNormalWorldOnB() { - return getNormalWorldOnB(new Vector3f()); - } - - /** - * Copy the collision's normal on object B. - * - * @param normalWorldOnB storage for the result (not null, modified) - * @return a normal vector (in physics-space coordinates, not null) - */ - public Vector3f getNormalWorldOnB(Vector3f normalWorldOnB) { - getNormalWorldOnB(manifoldPointObjectId, normalWorldOnB); - return normalWorldOnB; - } - private native void getNormalWorldOnB(long manifoldPointObjectId, Vector3f normalWorldOnB); - - /** - * Read part identifier 0. - * - * @return identifier - */ - public int getPartId0() { - return getPartId0(manifoldPointObjectId); - } - private native int getPartId0(long manifoldPointObjectId); - - /** - * Read part identifier 1. - * - * @return identifier - */ - public int getPartId1() { - return getPartId1(manifoldPointObjectId); - } - - private native int getPartId1(long manifoldPointObjectId); - - /** - * Copy the collision's location. - * - * @return a new vector (in physics-space coordinates, not null) - */ - public Vector3f getPositionWorldOnA() { - return getPositionWorldOnA(new Vector3f()); - } - - /** - * Copy the collision's location. - * - * @param positionWorldOnA storage for the result (not null, modified) - * @return a location vector (in physics-space coordinates, not null) - */ - public Vector3f getPositionWorldOnA(Vector3f positionWorldOnA) { - getPositionWorldOnA(manifoldPointObjectId, positionWorldOnA); - return positionWorldOnA; - } - private native void getPositionWorldOnA(long manifoldPointObjectId, Vector3f positionWorldOnA); - - /** - * Copy the collision's location. - * - * @return a new location vector (in physics-space coordinates, not null) - */ - public Vector3f getPositionWorldOnB() { - return getPositionWorldOnB(new Vector3f()); - } - - /** - * Copy the collision's location. - * - * @param positionWorldOnB storage for the result (not null, modified) - * @return a location vector (in physics-space coordinates, not null) - */ - public Vector3f getPositionWorldOnB(Vector3f positionWorldOnB) { - getPositionWorldOnB(manifoldPointObjectId, positionWorldOnB); - return positionWorldOnB; - } - private native void getPositionWorldOnB(long manifoldPointObjectId, Vector3f positionWorldOnB); - -// public Object getUserPersistentData() { -// return userPersistentData; -// } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java deleted file mode 100644 index d8eb118797..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.bullet.collision; - -import java.util.concurrent.ConcurrentLinkedQueue; - -/** - * - * @author normenhansen - */ -public class PhysicsCollisionEventFactory { - - private ConcurrentLinkedQueue eventBuffer = new ConcurrentLinkedQueue(); - - /** - * Obtain an unused event. - * - * @return an event (not null) - */ - public PhysicsCollisionEvent getEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, long manifoldPointObjectId) { - PhysicsCollisionEvent event = eventBuffer.poll(); - if (event == null) { - event = new PhysicsCollisionEvent(type, source, nodeB, manifoldPointObjectId); - }else{ - event.refactor(type, source, nodeB, manifoldPointObjectId); - } - return event; - } - - /** - * Recycle the specified event. - * - * @param event - */ - public void recycle(PhysicsCollisionEvent event) { - event.clean(); - eventBuffer.add(event); - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java deleted file mode 100644 index df4265be5c..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java +++ /dev/null @@ -1,377 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.collision; - -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.export.*; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The abstract base class for collision objects based on Bullet's - * btCollisionObject. - *

    - * Collision objects include PhysicsCharacter, PhysicsRigidBody, and - * PhysicsGhostObject. - * - * @author normenhansen - */ -public abstract class PhysicsCollisionObject implements Savable { - - /** - * Unique identifier of the btCollisionObject. Constructors are responsible - * for setting this to a non-zero value. The id might change if the object - * gets rebuilt. - */ - protected long objectId = 0; - /** - * shape associated with this object (not null) - */ - protected CollisionShape collisionShape; - /** - * collideWithGroups bitmask that represents "no groups" - */ - public static final int COLLISION_GROUP_NONE = 0x00000000; - /** - * collisionGroup/collideWithGroups bitmask that represents group #1 - */ - public static final int COLLISION_GROUP_01 = 0x00000001; - /** - * collisionGroup/collideWithGroups bitmask that represents group #2 - */ - public static final int COLLISION_GROUP_02 = 0x00000002; - /** - * collisionGroup/collideWithGroups bitmask that represents group #3 - */ - public static final int COLLISION_GROUP_03 = 0x00000004; - /** - * collisionGroup/collideWithGroups bitmask that represents group #4 - */ - public static final int COLLISION_GROUP_04 = 0x00000008; - /** - * collisionGroup/collideWithGroups bitmask that represents group #5 - */ - public static final int COLLISION_GROUP_05 = 0x00000010; - /** - * collisionGroup/collideWithGroups bitmask that represents group #6 - */ - public static final int COLLISION_GROUP_06 = 0x00000020; - /** - * collisionGroup/collideWithGroups bitmask that represents group #7 - */ - public static final int COLLISION_GROUP_07 = 0x00000040; - /** - * collisionGroup/collideWithGroups bitmask that represents group #8 - */ - public static final int COLLISION_GROUP_08 = 0x00000080; - /** - * collisionGroup/collideWithGroups bitmask that represents group #9 - */ - public static final int COLLISION_GROUP_09 = 0x00000100; - /** - * collisionGroup/collideWithGroups bitmask that represents group #10 - */ - public static final int COLLISION_GROUP_10 = 0x00000200; - /** - * collisionGroup/collideWithGroups bitmask that represents group #11 - */ - public static final int COLLISION_GROUP_11 = 0x00000400; - /** - * collisionGroup/collideWithGroups bitmask that represents group #12 - */ - public static final int COLLISION_GROUP_12 = 0x00000800; - /** - * collisionGroup/collideWithGroups bitmask that represents group #13 - */ - public static final int COLLISION_GROUP_13 = 0x00001000; - /** - * collisionGroup/collideWithGroups bitmask that represents group #14 - */ - public static final int COLLISION_GROUP_14 = 0x00002000; - /** - * collisionGroup/collideWithGroups bitmask that represents group #15 - */ - public static final int COLLISION_GROUP_15 = 0x00004000; - /** - * collisionGroup/collideWithGroups bitmask that represents group #16 - */ - public static final int COLLISION_GROUP_16 = 0x00008000; - /** - * collision group to which this physics object belongs (default=group #1) - */ - protected int collisionGroup = 0x00000001; - /** - * collision groups with which this object can collide (default=only group - * #1) - */ - protected int collisionGroupsMask = 0x00000001; - private Object userObject; - - /** - * Apply the specified CollisionShape to this object. Note that the object - * should not be in any physics space while changing shape; the object gets - * rebuilt on the physics side. - * - * @param collisionShape the shape to apply (not null, alias created) - */ - public void setCollisionShape(CollisionShape collisionShape) { - this.collisionShape = collisionShape; - } - - /** - * Access the shape of this physics object. - * - * @return the pre-existing instance, which can then be applied to other - * physics objects (increases performance) - */ - public CollisionShape getCollisionShape() { - return collisionShape; - } - - /** - * Read the deactivation time. - * - * @return the time (in seconds) - */ - public float getDeactivationTime() { - float time = getDeactivationTime(objectId); - return time; - } - - native private float getDeactivationTime(long objectId); - - /** - * Read the collision group for this physics object. - * - * @return the collision group (bit mask with exactly one bit set) - */ - public int getCollisionGroup() { - return collisionGroup; - } - - /** - * Alter the collision group for this physics object. - *

    - * Groups are represented by integer bit masks with exactly 1 bit set. - * Pre-made variables are available in PhysicsCollisionObject. By default, - * physics objects are in COLLISION_GROUP_01. - *

    - * Two objects can collide only if one of them has the collisionGroup of the - * other in its collideWithGroups set. - * - * @param collisionGroup the collisionGroup to apply (bit mask with exactly - * 1 bit set) - */ - public void setCollisionGroup(int collisionGroup) { - this.collisionGroup = collisionGroup; - if (objectId != 0) { - setCollisionGroup(objectId, collisionGroup); - } - } - - /** - * Add collision groups to the set with which this object can collide. - * - * Two objects can collide only if one of them has the collisionGroup of the - * other in its collideWithGroups set. - * - * @param collisionGroup groups to add (bit mask) - */ - public void addCollideWithGroup(int collisionGroup) { - this.collisionGroupsMask = this.collisionGroupsMask | collisionGroup; - if (objectId != 0) { - setCollideWithGroups(objectId, this.collisionGroupsMask); - } - } - - /** - * Remove collision groups from the set with which this object can collide. - * - * @param collisionGroup groups to remove, ORed together (bit mask) - */ - public void removeCollideWithGroup(int collisionGroup) { - this.collisionGroupsMask = this.collisionGroupsMask & ~collisionGroup; - if (objectId != 0) { - setCollideWithGroups(this.collisionGroupsMask); - } - } - - /** - * Directly alter the collision groups with which this object can collide. - * - * @param collisionGroups desired groups, ORed together (bit mask) - */ - public void setCollideWithGroups(int collisionGroups) { - this.collisionGroupsMask = collisionGroups; - if (objectId != 0) { - setCollideWithGroups(objectId, this.collisionGroupsMask); - } - } - - /** - * Read the set of collision groups with which this object can collide. - * - * @return bit mask - */ - public int getCollideWithGroups() { - return collisionGroupsMask; - } - - /** - * Initialize the user pointer and collision-group information of this - * object. - */ - protected void initUserPointer() { - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "initUserPointer() objectId = {0}", Long.toHexString(objectId)); - initUserPointer(objectId, collisionGroup, collisionGroupsMask); - } - native void initUserPointer(long objectId, int group, int groups); - - /** - * Access the user object associated with this collision object. - * - * @return the pre-existing instance, or null if none - */ - public Object getUserObject() { - return userObject; - } - - /** - * Test whether this object responds to contact with other objects. - * - * @return true if responsive, otherwise false - */ - public boolean isContactResponse() { - int flags = getCollisionFlags(objectId); - boolean result = (flags & CollisionFlag.NO_CONTACT_RESPONSE) == 0x0; - return result; - } - - /** - * Associate a user object (such as a Spatial) with this collision object. - * - * @param userObject the object to associate with this collision object - * (alias created, may be null) - */ - public void setUserObject(Object userObject) { - this.userObject = userObject; - } - - /** - * Read the id of the btCollisionObject. - * - * @return the unique identifier (not zero) - */ - public long getObjectId(){ - return objectId; - } - - /** - * Attach the identified btCollisionShape to the identified - * btCollisionObject. Native method. - * - * @param objectId the unique identifier of the btCollisionObject (not zero) - * @param collisionShapeId the unique identifier of the btCollisionShape - * (not zero) - */ - protected native void attachCollisionShape(long objectId, long collisionShapeId); - native void setCollisionGroup(long objectId, int collisionGroup); - native void setCollideWithGroups(long objectId, int collisionGroups); - - /** - * Serialize this object, for example when saving to a J3O file. - * - * @param e exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter e) throws IOException { - OutputCapsule capsule = e.getCapsule(this); - capsule.write(collisionGroup, "collisionGroup", 0x00000001); - capsule.write(collisionGroupsMask, "collisionGroupsMask", 0x00000001); - capsule.write(collisionShape, "collisionShape", null); - } - - /** - * De-serialize this object, for example when loading from a J3O file. - * - * @param e importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - collisionGroup = capsule.readInt("collisionGroup", 0x00000001); - collisionGroupsMask = capsule.readInt("collisionGroupsMask", 0x00000001); - CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null); - collisionShape = shape; - } - - /** - * Read the collision flags of this object. Subclasses are responsible for - * cloning/reading/writing these flags. - * - * @param objectId the ID of the btCollisionObject (not zero) - * @return the collision flags (bit mask) - */ - native protected int getCollisionFlags(long objectId); - - /** - * Alter the collision flags of this object. Subclasses are responsible for - * cloning/reading/writing these flags. - * - * @param objectId the ID of the btCollisionObject (not zero) - * @param desiredFlags the desired collision flags (bit mask) - */ - native protected void setCollisionFlags(long objectId, int desiredFlags); - - /** - * Finalize this collision object just before it is destroyed. Should be - * invoked only by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing CollisionObject {0}", Long.toHexString(objectId)); - finalizeNative(objectId); - } - - /** - * Finalize the identified btCollisionObject. Native method. - * - * @param objectId the unique identifier of the btCollisionObject (not zero) - */ - protected native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java deleted file mode 100644 index 02bf0162e6..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.collision; - -import com.jme3.math.Vector3f; - -/** - * Represent the results of a Bullet ray test. - * - * @author Empire-Phoenix,normenhansen - */ -public class PhysicsRayTestResult { - - /** - * collision object that was hit - */ - private PhysicsCollisionObject collisionObject; - /** - * normal vector at the point of contact - */ - private Vector3f hitNormalLocal; - /** - * fraction of the ray's total length (from=0, to=1, ≥0, ≤1) - */ - private float hitFraction; - /** - * true→need to transform normal into world space - */ - private boolean normalInWorldSpace = true; - - /** - * A private constructor to inhibit instantiation of this class by Java. - * These results are instantiated exclusively by native code. - */ - private PhysicsRayTestResult() { - } - - /** - * Access the collision object that was hit. - * - * @return the pre-existing instance - */ - public PhysicsCollisionObject getCollisionObject() { - return collisionObject; - } - - /** - * Access the normal vector at the point of contact. - * - * @return a pre-existing unit vector (not null) - */ - public Vector3f getHitNormalLocal() { - return hitNormalLocal; - } - - /** - * Read the fraction of the ray's total length. - * - * @return fraction (from=0, to=1, ≥0, ≤1) - */ - public float getHitFraction() { - return hitFraction; - } - - /** - * Test whether the normal is in world space. - * - * @return true if in world space, otherwise false - */ - public boolean isNormalInWorldSpace() { - return normalInWorldSpace; - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java deleted file mode 100644 index 9becbb1c76..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/PhysicsSweepTestResult.java +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.collision; - -import com.jme3.math.Vector3f; - -/** - * Represent the results of a Bullet sweep test. - * - * @author normenhansen - */ -public class PhysicsSweepTestResult { - - /** - * collision object that was hit - */ - private PhysicsCollisionObject collisionObject; - /** - * normal vector at the point of contact - */ - private Vector3f hitNormalLocal; - /** - * fraction of the way between the transforms (from=0, to=1, ≥0, ≤1) - */ - private float hitFraction; - /** - * true→need to transform normal into world space - */ - private boolean normalInWorldSpace; - - /** - * A private constructor to inhibit instantiation of this class by Java. - * These results are instantiated exclusively by native code. - */ - public PhysicsSweepTestResult() { - } - - public PhysicsSweepTestResult(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) { - this.collisionObject = collisionObject; - this.hitNormalLocal = hitNormalLocal; - this.hitFraction = hitFraction; - this.normalInWorldSpace = normalInWorldSpace; - } - - /** - * Access the collision object that was hit. - * - * @return the pre-existing instance - */ - public PhysicsCollisionObject getCollisionObject() { - return collisionObject; - } - - /** - * Access the normal vector at the point of contact. - * - * @return the pre-existing vector (not null) - */ - public Vector3f getHitNormalLocal() { - return hitNormalLocal; - } - - /** - * Read the fraction of fraction of the way between the transforms (from=0, - * to=1, ≥0, ≤1) - * - * @return fraction (from=0, to=1, ≥0, ≤1) - */ - public float getHitFraction() { - return hitFraction; - } - - /** - * Test whether the normal is in world space. - * - * @return true if in world space, otherwise false - */ - public boolean isNormalInWorldSpace() { - return normalInWorldSpace; - } - - /** - * Fill in the fields of this result. - * - * @param collisionObject - * @param hitNormalLocal - * @param hitFraction - * @param normalInWorldSpace - */ - public void fill(PhysicsCollisionObject collisionObject, Vector3f hitNormalLocal, float hitFraction, boolean normalInWorldSpace) { - this.collisionObject = collisionObject; - this.hitNormalLocal = hitNormalLocal; - this.hitFraction = hitFraction; - this.normalInWorldSpace = normalInWorldSpace; - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java deleted file mode 100644 index be8b89df58..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A rectangular-solid collision shape based on Bullet's btBoxShape. - * - * @author normenhansen - */ -public class BoxCollisionShape extends CollisionShape { - - /** - * copy of half-extents of the box on each local axis (not null, no negative - * component) - */ - private Vector3f halfExtents; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected BoxCollisionShape() { - } - - /** - * Instantiate a box shape with the specified half extents. - * - * @param halfExtents the desired unscaled half extents (not null, no - * negative component, alias created) - */ - public BoxCollisionShape(Vector3f halfExtents) { - this.halfExtents = halfExtents; - createShape(); - } - - /** - * Access the half extents of the box. - * - * @return the pre-existing instance (not null, no negative component) - */ - public final Vector3f getHalfExtents() { - return halfExtents; - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1)); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - Vector3f halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(1, 1, 1)); - this.halfExtents = halfExtents; - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - objectId = createShape(halfExtents); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// cShape = new BoxShape(Converter.convert(halfExtents)); - setScale(scale); - setMargin(margin); - } - - private native long createShape(Vector3f halfExtents); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java deleted file mode 100644 index 31d642028c..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A capsule collision shape based on Bullet's btCapsuleShapeX, btCapsuleShape, - * or btCapsuleShapeZ. These shapes have no margin and cannot be scaled. - * - * @author normenhansen - */ -public class CapsuleCollisionShape extends CollisionShape{ - /** - * copy of height of the cylindrical portion (≥0) - */ - private float height; - /** - * copy of radius (≥0) - */ - private float radius; - /** - * copy of main (height) axis (0→X, 1→Y, 2→Z) - */ - private int axis; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected CapsuleCollisionShape() { - } - - /** - * Instantiate a Y-axis capsule shape with the specified radius and height. - * - * @param radius the desired radius (≥0) - * @param height the desired height (of the cylindrical portion) (≥0) - */ - public CapsuleCollisionShape(float radius, float height) { - this.radius=radius; - this.height=height; - this.axis=1; - createShape(); - } - - /** - * Instantiate a capsule shape around the specified main (height) axis. - * - * @param radius the desired radius (≥0) - * @param height the desired height (of the cylindrical portion) (≥0) - * @param axis which local axis: 0→X, 1→Y, 2→Z - */ - public CapsuleCollisionShape(float radius, float height, int axis) { - this.radius=radius; - this.height=height; - this.axis=axis; - createShape(); - } - - /** - * Read the radius of the capsule. - * - * @return the radius (≥0) - */ - public float getRadius() { - return radius; - } - - /** - * Read the height (of the cylindrical portion) of the capsule. - * - * @return height (≥0) - */ - public float getHeight() { - return height; - } - - /** - * Determine the main (height) axis of the capsule. - * - * @return 0 for local X, 1 for local Y, or 2 for local Z - */ - public int getAxis() { - return axis; - } - - /** - * Alter the scaling factors of this shape. Scaling is disabled - * for capsule shapes. - * - * @param scale the desired scaling factor for each local axis (not null, no - * negative component, unaffected, default=1,1,1) - */ - @Override - public void setScale(Vector3f scale) { - if (!scale.equals(Vector3f.UNIT_XYZ)) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CapsuleCollisionShape cannot be scaled"); - } - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(radius, "radius", 0.5f); - capsule.write(height, "height", 1); - capsule.write(axis, "axis", 1); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - radius = capsule.readFloat("radius", 0.5f); - height = capsule.readFloat("height", 0.5f); - axis = capsule.readInt("axis", 1); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape(){ - objectId = createShape(axis, radius, height); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); - setScale(scale); - setMargin(margin); -// switch(axis){ -// case 0: -// objectId=new CapsuleShapeX(radius,height); -// break; -// case 1: -// objectId=new CapsuleShape(radius,height); -// break; -// case 2: -// objectId=new CapsuleShapeZ(radius,height); -// break; -// } -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - } - - private native long createShape(int axis, float radius, float height); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java deleted file mode 100644 index 94d39973d9..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.*; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The abstract base class for collision shapes based on Bullet's - * btCollisionShape. - *

    - * Collision shapes include BoxCollisionShape and CapsuleCollisionShape. As - * suggested in the Bullet manual, a single collision shape can be shared among - * multiple collision objects. - * - * @author normenhansen - */ -public abstract class CollisionShape implements Savable { - - /** - * default margin for new non-sphere/non-capsule shapes (in physics-space - * units, >0, default=0.04) - */ - private static float defaultMargin = 0.04f; - /** - * unique identifier of the btCollisionShape - *

    - * Constructors are responsible for setting this to a non-zero value. After - * that, the id never changes. - */ - protected long objectId = 0; - /** - * copy of scaling factors: one for each local axis (default=1,1,1) - */ - protected Vector3f scale = new Vector3f(1, 1, 1); - /** - * copy of collision margin (in physics-space units, >0, default=0) - */ - protected float margin = defaultMargin; - - public CollisionShape() { - } - -// /** -// * used internally, not safe -// */ -// public void calculateLocalInertia(long objectId, float mass) { -// if (this.objectId == 0) { -// return; -// } -//// if (this instanceof MeshCollisionShape) { -//// vector.set(0, 0, 0); -//// } else { -// calculateLocalInertia(objectId, this.objectId, mass); -//// objectId.calculateLocalInertia(mass, vector); -//// } -// } -// -// private native void calculateLocalInertia(long objectId, long shapeId, float mass); - - /** - * Read the id of the btCollisionShape. - * - * @return the unique identifier (not zero) - */ - public long getObjectId() { - return objectId; - } - - /** - * used internally - */ - public void setObjectId(long id) { - this.objectId = id; - } - - /** - * Alter the scaling factors of this shape. CAUTION: Not all shapes can be - * scaled. - *

    - * Note that if the shape is shared (between collision objects and/or - * compound shapes) changes can have unintended consequences. - * - * @param scale the desired scaling factor for each local axis (not null, no - * negative component, unaffected, default=1,1,1) - */ - public void setScale(Vector3f scale) { - this.scale.set(scale); - setLocalScaling(objectId, scale); - } - /** - * Access the scaling factors. - * - * @return the pre-existing vector (not null) - */ - public Vector3f getScale() { - return scale; - } - - /** - * Test whether this shape can be applied to a dynamic rigid body. The only - * non-moving shapes are the heightfield, mesh, and plane shapes. - * - * @return true if non-moving, false otherwise - */ - public boolean isNonMoving() { - boolean result = isNonMoving(objectId); - return result; - } - - native private boolean isNonMoving(long objectId); - - /** - * Read the collision margin for this shape. - * - * @return the margin distance (in physics-space units, ≥0) - */ - public float getMargin() { - return getMargin(objectId); - } - - private native float getMargin(long objectId); - - /** - * Alter the default margin for new shapes that are neither capsules nor - * spheres. - * - * @param margin the desired margin distance (in physics-space units, >0, - * default=0.04) - */ - public static void setDefaultMargin(float margin) { - defaultMargin = margin; - } - - /** - * Read the default margin for new shapes. - * - * @return margin the default margin distance (in physics-space units, - * >0) - */ - public static float getDefaultMargin() { - return defaultMargin; - } - - /** - * Alter the collision margin of this shape. CAUTION: Margin is applied - * differently, depending on the type of shape. Generally the collision - * margin expands the object, creating a gap. Don't set the collision margin - * to zero. - *

    - * Note that if the shape is shared (between collision objects and/or - * compound shapes) changes can have unintended consequences. - * - * @param margin the desired margin distance (in physics-space units, >0, - * default=0.04) - */ - public void setMargin(float margin) { - setMargin(objectId, margin); - this.margin = margin; - } - - private native void setLocalScaling(long obectId, Vector3f scale); - - private native void setMargin(long objectId, float margin); - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(scale, "scale", new Vector3f(1, 1, 1)); - capsule.write(getMargin(), "margin", 0.0f); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule capsule = im.getCapsule(this); - this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1)); - this.margin = capsule.readFloat("margin", 0.0f); - } - - /** - * Finalize this shape just before it is destroyed. Should be invoked only - * by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing CollisionShape {0}", Long.toHexString(objectId)); - finalizeNative(objectId); - } - - private native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java deleted file mode 100644 index 254006676e..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.collision.shapes; - -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A collision shape formed by combining convex child shapes, based on Bullet's - * btCompoundShape. - * - * @author normenhansen - */ -public class CompoundCollisionShape extends CollisionShape { - - /** - * children of this shape - */ - protected ArrayList children = new ArrayList(); - - /** - * Instantiate an empty compound shape (with no children). - */ - public CompoundCollisionShape() { - objectId = createShape();//new CompoundShape(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); - } - - /** - * Add a child shape with the specified local translation. - * - * @param shape the child shape to add (not null, not a compound shape, - * alias created) - * @param location the local coordinates of the child shape's center (not - * null, unaffected) - */ - public void addChildShape(CollisionShape shape, Vector3f location) { -// Transform transA = new Transform(Converter.convert(new Matrix3f())); -// Converter.convert(location, transA.origin); -// children.add(new ChildCollisionShape(location.clone(), new Matrix3f(), shape)); -// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); - addChildShape(shape, location, new Matrix3f()); - } - - /** - * Add a child shape with the specified local translation and orientation. - * - * @param shape the child shape to add (not null, not a compound shape, - * alias created) - * @param location the local coordinates of the child shape's center (not - * null, unaffected) - * @param rotation the local orientation of the child shape (not null, - * unaffected) - */ - public void addChildShape(CollisionShape shape, Vector3f location, Matrix3f rotation) { - if(shape instanceof CompoundCollisionShape){ - throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!"); - } -// Transform transA = new Transform(Converter.convert(rotation)); -// Converter.convert(location, transA.origin); -// Converter.convert(rotation, transA.basis); - children.add(new ChildCollisionShape(location.clone(), rotation.clone(), shape)); - addChildShape(objectId, shape.getObjectId(), location, rotation); -// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); - } - - private void addChildShapeDirect(CollisionShape shape, Vector3f location, Matrix3f rotation) { - if(shape instanceof CompoundCollisionShape){ - throw new IllegalStateException("CompoundCollisionShapes cannot have CompoundCollisionShapes as children!"); - } -// Transform transA = new Transform(Converter.convert(rotation)); -// Converter.convert(location, transA.origin); -// Converter.convert(rotation, transA.basis); - addChildShape(objectId, shape.getObjectId(), location, rotation); -// ((CompoundShape) objectId).addChildShape(transA, shape.getObjectId()); - } - - /** - * Remove a child from this shape. - * - * @param shape the child shape to remove (not null) - */ - public void removeChildShape(CollisionShape shape) { - removeChildShape(objectId, shape.getObjectId()); -// ((CompoundShape) objectId).removeChildShape(shape.getObjectId()); - for (Iterator it = children.iterator(); it.hasNext();) { - ChildCollisionShape childCollisionShape = it.next(); - if (childCollisionShape.shape == shape) { - it.remove(); - } - } - } - - /** - * Access the list of children. - * - * @return the pre-existing list (not null) - */ - public List getChildren() { - return children; - } - - private native long createShape(); - - private native long addChildShape(long objectId, long childId, Vector3f location, Matrix3f rotation); - - private native long removeChildShape(long objectId, long childId); - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.writeSavableArrayList(children, "children", new ArrayList()); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - children = capsule.readSavableArrayList("children", new ArrayList()); - setScale(scale); - setMargin(margin); - loadChildren(); - } - - private void loadChildren() { - for (Iterator it = children.iterator(); it.hasNext();) { - ChildCollisionShape child = it.next(); - addChildShapeDirect(child.shape, child.location, child.rotation); - } - } - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java deleted file mode 100644 index a8782d6a83..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java +++ /dev/null @@ -1,165 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A conical collision shape based on Bullet's btConeShapeX, btConeShape, or - * btConeShapeZ. - * - * @author normenhansen - */ -public class ConeCollisionShape extends CollisionShape { - - /** - * copy of radius (≥0) - */ - protected float radius; - /** - * copy of height (≥0) - */ - protected float height; - /** - * copy of main (height) axis (0→X, 1→Y, 2→Z) - */ - protected int axis; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected ConeCollisionShape() { - } - - /** - * Instantiate a cone shape around the specified main (height) axis. - * - * @param radius the desired radius (≥0) - * @param height the desired height (≥0) - * @param axis which local axis: 0→X, 1→Y, 2→Z - */ - public ConeCollisionShape(float radius, float height, int axis) { - this.radius = radius; - this.height = height; - this.axis = axis; - createShape(); - } - - /** - * Instantiate a cone shape oriented along the Y axis. - * - * @param radius the desired radius (≥0) - * @param height the desired height (≥0) - */ - public ConeCollisionShape(float radius, float height) { - this.radius = radius; - this.height = height; - this.axis = PhysicsSpace.AXIS_Y; - createShape(); - } - - /** - * Read the radius of the cone. - * - * @return radius (≥0) - */ - public float getRadius() { - return radius; - } - - /** - * Read the height of the cone. - * - * @return height (≥0) - */ - public float getHeight() { - return height; - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(radius, "radius", 0.5f); - capsule.write(height, "height", 0.5f); - capsule.write(axis, "axis", PhysicsSpace.AXIS_Y); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - radius = capsule.readFloat("radius", 0.5f); - height = capsule.readFloat("height", 0.5f); - axis = capsule.readInt("axis", PhysicsSpace.AXIS_Y); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - objectId = createShape(axis, radius, height); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// if (axis == PhysicsSpace.AXIS_X) { -// objectId = new ConeShapeX(radius, height); -// } else if (axis == PhysicsSpace.AXIS_Y) { -// objectId = new ConeShape(radius, height); -// } else if (axis == PhysicsSpace.AXIS_Z) { -// objectId = new ConeShapeZ(radius, height); -// } -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - setScale(scale); - setMargin(margin); - } - - private native long createShape(int axis, float radius, float height); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java deleted file mode 100644 index d85b41b98d..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A cylindrical collision shape based on Bullet's btCylinderShapeX, new - * btCylinderShape, or btCylinderShapeZ. - * - * @author normenhansen - */ -public class CylinderCollisionShape extends CollisionShape { - - /** - * copy of half-extents of the cylinder on each local axis (not null, no - * negative component) - */ - protected Vector3f halfExtents; - /** - * copy of main (height) axis (0→X, 1→Y, 2→Z) - */ - protected int axis; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected CylinderCollisionShape() { - } - - /** - * Instantiate a Z-axis cylinder shape with the specified half extents. - * - * @param halfExtents the desired unscaled half extents (not null, no - * negative component, alias created) - */ - public CylinderCollisionShape(Vector3f halfExtents) { - this.halfExtents = halfExtents; - this.axis = 2; - createShape(); - } - - /** - * Instantiate a cylinder shape around the specified axis. - * - * @param halfExtents the desired unscaled half extents (not null, no - * negative component, alias created) - * @param axis which local axis: 0→X, 1→Y, 2→Z - */ - public CylinderCollisionShape(Vector3f halfExtents, int axis) { - this.halfExtents = halfExtents; - this.axis = axis; - createShape(); - } - - /** - * Access the half extents of the cylinder. - * - * @return the pre-existing vector (not null, no negative component) - */ - public final Vector3f getHalfExtents() { - return halfExtents; - } - - /** - * Determine the main axis of the cylinder. - * - * @return 0→X, 1→Y, 2→Z - */ - public int getAxis() { - return axis; - } - - /** - * Alter the scaling factors of this shape. Scaling is disabled - * for cylinder shapes. - * - * @param scale the desired scaling factor for each local axis (not null, no - * negative component, unaffected, default=1,1,1) - */ - @Override - public void setScale(Vector3f scale) { - if (!scale.equals(Vector3f.UNIT_XYZ)) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CylinderCollisionShape cannot be scaled"); - } - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(halfExtents, "halfExtents", new Vector3f(0.5f, 0.5f, 0.5f)); - capsule.write(axis, "axis", 1); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - halfExtents = (Vector3f) capsule.readSavable("halfExtents", new Vector3f(0.5f, 0.5f, 0.5f)); - axis = capsule.readInt("axis", 1); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - objectId = createShape(axis, halfExtents); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// switch (axis) { -// case 0: -// objectId = new CylinderShapeX(Converter.convert(halfExtents)); -// break; -// case 1: -// objectId = new CylinderShape(Converter.convert(halfExtents)); -// break; -// case 2: -// objectId = new CylinderShapeZ(Converter.convert(halfExtents)); -// break; -// } -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - setScale(scale); - setMargin(margin); - } - - private native long createShape(int axis, Vector3f halfExtents); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java deleted file mode 100644 index 6878b7806b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java +++ /dev/null @@ -1,217 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.bullet.util.NativeMeshUtil; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A mesh collision shape based on Bullet's btGImpactMeshShape. - * - * @author normenhansen - */ -public class GImpactCollisionShape extends CollisionShape { - -// protected Vector3f worldScale; - protected int numVertices, numTriangles, vertexStride, triangleIndexStride; - protected ByteBuffer triangleIndexBase, vertexBase; - /** - * Unique identifier of the Bullet mesh. The constructor sets this to a - * non-zero value. - */ - protected long meshId = 0; -// protected IndexedMesh bulletMesh; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected GImpactCollisionShape() { - } - - /** - * Instantiate a shape based on the specified JME mesh. - * - * @param mesh the Mesh to use - */ - public GImpactCollisionShape(Mesh mesh) { - createCollisionMesh(mesh); - } - - private void createCollisionMesh(Mesh mesh) { - triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); - vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); -// triangleIndexBase = ByteBuffer.allocate(mesh.getTriangleCount() * 3 * 4); -// vertexBase = ByteBuffer.allocate(mesh.getVertexCount() * 3 * 4); - numVertices = mesh.getVertexCount(); - vertexStride = 12; //3 verts * 4 bytes per. - numTriangles = mesh.getTriangleCount(); - triangleIndexStride = 12; //3 index entries * 4 bytes each. - - IndexBuffer indices = mesh.getIndicesAsList(); - FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); - vertices.rewind(); - - int verticesLength = mesh.getVertexCount() * 3; - for (int i = 0; i < verticesLength; i++) { - float tempFloat = vertices.get(); - vertexBase.putFloat(tempFloat); - } - - int indicesLength = mesh.getTriangleCount() * 3; - for (int i = 0; i < indicesLength; i++) { - triangleIndexBase.putInt(indices.get(i)); - } - vertices.rewind(); - vertices.clear(); - - createShape(); - } - -// /** -// * creates a jme mesh from the collision shape, only needed for debugging -// */ -// public Mesh createJmeMesh() { -// return Converter.convert(bulletMesh); -// } - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); -// capsule.write(worldScale, "worldScale", new Vector3f(1, 1, 1)); - capsule.write(numVertices, "numVertices", 0); - capsule.write(numTriangles, "numTriangles", 0); - capsule.write(vertexStride, "vertexStride", 0); - capsule.write(triangleIndexStride, "triangleIndexStride", 0); - - capsule.write(triangleIndexBase.array(), "triangleIndexBase", new byte[0]); - capsule.write(vertexBase.array(), "vertexBase", new byte[0]); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); -// worldScale = (Vector3f) capsule.readSavable("worldScale", new Vector3f(1, 1, 1)); - numVertices = capsule.readInt("numVertices", 0); - numTriangles = capsule.readInt("numTriangles", 0); - vertexStride = capsule.readInt("vertexStride", 0); - triangleIndexStride = capsule.readInt("triangleIndexStride", 0); - - triangleIndexBase = ByteBuffer.wrap(capsule.readByteArray("triangleIndexBase", new byte[0])); - vertexBase = ByteBuffer.wrap(capsule.readByteArray("vertexBase", new byte[0])); - createShape(); - } - - /** - * Alter the scaling factors of this shape. - *

    - * Note that if the shape is shared (between collision objects and/or - * compound shapes) changes can have unintended consequences. - * - * @param scale the desired scaling factor for each local axis (not null, no - * negative component, unaffected, default=(1,1,1)) - */ - @Override - public void setScale(Vector3f scale) { - super.setScale(scale); - recalcAabb(objectId); - } - - native private void recalcAabb(long shapeId); - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { -// bulletMesh = new IndexedMesh(); -// bulletMesh.numVertices = numVertices; -// bulletMesh.numTriangles = numTriangles; -// bulletMesh.vertexStride = vertexStride; -// bulletMesh.triangleIndexStride = triangleIndexStride; -// bulletMesh.triangleIndexBase = triangleIndexBase; -// bulletMesh.vertexBase = vertexBase; -// bulletMesh.triangleIndexBase = triangleIndexBase; -// TriangleIndexVertexArray tiv = new TriangleIndexVertexArray(numTriangles, triangleIndexBase, triangleIndexStride, numVertices, vertexBase, vertexStride); -// objectId = new GImpactMeshShape(tiv); -// objectId.setLocalScaling(Converter.convert(worldScale)); -// ((GImpactMeshShape)objectId).updateBound(); -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - meshId = NativeMeshUtil.createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(meshId)); - objectId = createShape(meshId); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); - setScale(scale); - setMargin(margin); - } - - private native long createShape(long meshId); - - /** - * Finalize this shape just before it is destroyed. Should be invoked only - * by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(meshId)); - finalizeNative(meshId); - } - - private native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java deleted file mode 100644 index ae57bcf109..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Mesh; -import com.jme3.util.BufferUtils; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A terrain collision shape based on Bullet's btHeightfieldTerrainShape. - *

    - * This is much more efficient than a regular mesh, but it has a couple - * limitations: - *

      - *
    • No rotation or translation.
    • - *
    • The collision bounding box must be centered on (0,0,0) with the height - * above and below the X-Z plane being equal on either side. If not, the whole - * collision box is shifted vertically and objects won't collide properly.
    • - *
    - * - * @author Brent Owens - */ -public class HeightfieldCollisionShape extends CollisionShape { - - /** - * number of rows in the heightfield (>1) - */ - protected int heightStickWidth; - /** - * number of columns in the heightfield (>1) - */ - protected int heightStickLength; - /** - * array of heightfield samples - */ - protected float[] heightfieldData; - protected float heightScale; - protected float minHeight; - protected float maxHeight; - /** - * index of the height axis (0→X, 1→Y, 2→Z) - */ - protected int upAxis; - protected boolean flipQuadEdges; - /** - * buffer for passing height data to Bullet - *

    - * A Java reference must persist after createShape() completes, or else the - * buffer might get garbage collected. - */ - protected ByteBuffer bbuf; -// protected FloatBuffer fbuf; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected HeightfieldCollisionShape() { - } - - /** - * Instantiate a new shape for the specified height map. - * - * @param heightmap (not null, length≥4, length a perfect square) - */ - public HeightfieldCollisionShape(float[] heightmap) { - createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ); - } - - /** - * Instantiate a new shape for the specified height map and scale vector. - * - * @param heightmap (not null, length≥4, length a perfect square) - * @param scale (not null, no negative component, unaffected, default=1,1,1) - */ - public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { - createCollisionHeightfield(heightmap, scale); - } - - protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { - this.scale = worldScale; - this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale - - this.heightfieldData = heightmap; - - float min = heightfieldData[0]; - float max = heightfieldData[0]; - // calculate min and max height - for (int i = 0; i < heightfieldData.length; i++) { - if (heightfieldData[i] < min) { - min = heightfieldData[i]; - } - if (heightfieldData[i] > max) { - max = heightfieldData[i]; - } - } - // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the - // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect. - if (max < 0) { - max = -min; - } else { - if (Math.abs(max) > Math.abs(min)) { - min = -max; - } else { - max = -min; - } - } - this.minHeight = min; - this.maxHeight = max; - - this.upAxis = 1; - flipQuadEdges = true; - - heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); - heightStickLength = heightStickWidth; - - - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - bbuf = BufferUtils.createByteBuffer(heightfieldData.length * 4); -// fbuf = bbuf.asFloatBuffer();//FloatBuffer.wrap(heightfieldData); -// fbuf.rewind(); -// fbuf.put(heightfieldData); - for (int i = 0; i < heightfieldData.length; i++) { - float f = heightfieldData[i]; - bbuf.putFloat(f); - } -// fbuf.rewind(); - objectId = createShape(heightStickWidth, heightStickLength, bbuf, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); - setScale(scale); - setMargin(margin); - } - - private native long createShape(int heightStickWidth, int heightStickLength, ByteBuffer heightfieldData, float heightScale, float minHeight, float maxHeight, int upAxis, boolean flipQuadEdges); - - /** - * Does nothing. - * - * @return null - */ - public Mesh createJmeMesh() { - //TODO return Converter.convert(bulletMesh); - return null; - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(heightStickWidth, "heightStickWidth", 0); - capsule.write(heightStickLength, "heightStickLength", 0); - capsule.write(heightScale, "heightScale", 0); - capsule.write(minHeight, "minHeight", 0); - capsule.write(maxHeight, "maxHeight", 0); - capsule.write(upAxis, "upAxis", 1); - capsule.write(heightfieldData, "heightfieldData", new float[0]); - capsule.write(flipQuadEdges, "flipQuadEdges", false); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - heightStickWidth = capsule.readInt("heightStickWidth", 0); - heightStickLength = capsule.readInt("heightStickLength", 0); - heightScale = capsule.readFloat("heightScale", 0); - minHeight = capsule.readFloat("minHeight", 0); - maxHeight = capsule.readFloat("maxHeight", 0); - upAxis = capsule.readInt("upAxis", 1); - heightfieldData = capsule.readFloatArray("heightfieldData", new float[0]); - flipQuadEdges = capsule.readBoolean("flipQuadEdges", false); - createShape(); - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java deleted file mode 100644 index 7e584844ba..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.util.BufferUtils; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A convex hull collision shape based on Bullet's btConvexHullShape. - */ -public class HullCollisionShape extends CollisionShape { - - private float[] points; -// protected FloatBuffer fbuf; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected HullCollisionShape() { - } - - /** - * Instantiate a collision shape based on the specified JME mesh. For best - * performance and stability, use the mesh should have no more than 100 - * vertices. - * - * @param mesh a mesh on which to base the shape (not null, at least one - * vertex) - */ - public HullCollisionShape(Mesh mesh) { - this.points = getPoints(mesh); - createShape(); - } - - /** - * Instantiate a collision shape based on the specified array of - * coordinates. - * - * @param points an array of coordinates on which to base the shape (not - * null, not empty, length a multiple of 3) - */ - public HullCollisionShape(float[] points) { - this.points = points; - createShape(); - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(points, "points", null); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - - // for backwards compatability - Mesh mesh = (Mesh) capsule.readSavable("hullMesh", null); - if (mesh != null) { - this.points = getPoints(mesh); - } else { - this.points = capsule.readFloatArray("points", null); - - } -// fbuf = ByteBuffer.allocateDirect(points.length * 4).asFloatBuffer(); -// fbuf.put(points); -// fbuf = FloatBuffer.wrap(points).order(ByteOrder.nativeOrder()).asFloatBuffer(); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { -// ObjectArrayList pointList = new ObjectArrayList(); -// for (int i = 0; i < points.length; i += 3) { -// pointList.add(new Vector3f(points[i], points[i + 1], points[i + 2])); -// } -// objectId = new ConvexHullShape(pointList); -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - ByteBuffer bbuf=BufferUtils.createByteBuffer(points.length * 4); -// fbuf = bbuf.asFloatBuffer(); -// fbuf.rewind(); -// fbuf.put(points); - for (int i = 0; i < points.length; i++) { - float f = points[i]; - bbuf.putFloat(f); - } - bbuf.rewind(); - objectId = createShape(bbuf); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); - setScale(scale); - setMargin(margin); - } - - private native long createShape(ByteBuffer points); - - /** - * Copy the vertex positions from a JME mesh. - * - * @param mesh the mesh to read (not null) - * @return a new array (not null, length a multiple of 3) - */ - protected float[] getPoints(Mesh mesh) { - FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); - vertices.rewind(); - int components = mesh.getVertexCount() * 3; - float[] pointsArray = new float[components]; - for (int i = 0; i < components; i += 3) { - pointsArray[i] = vertices.get(); - pointsArray[i + 1] = vertices.get(); - pointsArray[i + 2] = vertices.get(); - } - return pointsArray; - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java deleted file mode 100644 index db95b8120e..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java +++ /dev/null @@ -1,255 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.bullet.util.NativeMeshUtil; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; - -/** - * A mesh collision shape based on Bullet's btBvhTriangleMeshShape. - * - * @author normenhansen - */ -public class MeshCollisionShape extends CollisionShape { - - private static final String VERTEX_BASE = "vertexBase"; - private static final String TRIANGLE_INDEX_BASE = "triangleIndexBase"; - private static final String TRIANGLE_INDEX_STRIDE = "triangleIndexStride"; - private static final String VERTEX_STRIDE = "vertexStride"; - private static final String NUM_TRIANGLES = "numTriangles"; - private static final String NUM_VERTICES = "numVertices"; - private static final String NATIVE_BVH = "nativeBvh"; - protected int numVertices, numTriangles, vertexStride, triangleIndexStride; - protected ByteBuffer triangleIndexBase, vertexBase; - /** - * Unique identifier of the Bullet mesh. The constructor sets this to a - * non-zero value. - */ - protected long meshId = 0; - protected long nativeBVHBuffer = 0; - private boolean memoryOptimized; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected MeshCollisionShape() { - } - - /** - * Instantiate a collision shape based on the specified JME mesh, optimized - * for memory usage. - * - * @param mesh the mesh on which to base the shape (not null) - */ - public MeshCollisionShape(Mesh mesh) { - this(mesh, true); - } - - /** - * Instantiate a collision shape based on the specified JME mesh. - *

    - * memoryOptimized determines if optimized instead of quantized - * BVH will be used. Internally, memoryOptimized BVH is slower - * to calculate (~4x) but also smaller (~0.5x). It is preferable to use the - * memory optimized version and then serialize the resulting - * MeshCollisionshape as this will also save the generated BVH. An exception - * can be procedurally / generated collision shapes, where the generation - * time is more of a concern - * - * @param mesh the mesh on which to base the shape (not null) - * @param memoryOptimized true to generate a memory-optimized BVH, false to - * generate quantized BVH - */ - public MeshCollisionShape(final Mesh mesh, final boolean memoryOptimized) { - this.memoryOptimized = memoryOptimized; - this.createCollisionMesh(mesh); - } - - /** - * An advanced constructor. Passing false values can lead to a crash. - * Usually you don’t want to use this. Use at own risk. - *

    - * This constructor bypasses all copy logic normally used, this allows for - * faster Bullet shape generation when using procedurally generated Meshes. - * - * @param indices the raw index buffer - * @param vertices the raw vertex buffer - * @param memoryOptimized use quantized BVH, uses less memory, but slower - */ - public MeshCollisionShape(ByteBuffer indices, ByteBuffer vertices, boolean memoryOptimized) { - this.triangleIndexBase = indices; - this.vertexBase = vertices; - this.numVertices = vertices.limit() / 4 / 3; - this.numTriangles = this.triangleIndexBase.limit() / 4 / 3; - this.vertexStride = 12; - this.triangleIndexStride = 12; - this.memoryOptimized = memoryOptimized; - this.createShape(null); - } - - private void createCollisionMesh(Mesh mesh) { - this.triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); - this.vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); - this.numVertices = mesh.getVertexCount(); - this.vertexStride = 12; // 3 verts * 4 bytes per. - this.numTriangles = mesh.getTriangleCount(); - this.triangleIndexStride = 12; // 3 index entries * 4 bytes each. - - IndexBuffer indices = mesh.getIndicesAsList(); - FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); - vertices.rewind(); - - int verticesLength = mesh.getVertexCount() * 3; - for (int i = 0; i < verticesLength; i++) { - float tempFloat = vertices.get(); - vertexBase.putFloat(tempFloat); - } - - int indicesLength = mesh.getTriangleCount() * 3; - for (int i = 0; i < indicesLength; i++) { - triangleIndexBase.putInt(indices.get(i)); - } - vertices.rewind(); - vertices.clear(); - - this.createShape(null); - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(final JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(numVertices, MeshCollisionShape.NUM_VERTICES, 0); - capsule.write(numTriangles, MeshCollisionShape.NUM_TRIANGLES, 0); - capsule.write(vertexStride, MeshCollisionShape.VERTEX_STRIDE, 0); - capsule.write(triangleIndexStride, MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0); - - triangleIndexBase.position(0); - byte[] triangleIndexBasearray = new byte[triangleIndexBase.limit()]; - triangleIndexBase.get(triangleIndexBasearray); - capsule.write(triangleIndexBasearray, MeshCollisionShape.TRIANGLE_INDEX_BASE, null); - - vertexBase.position(0); - byte[] vertexBaseArray = new byte[vertexBase.limit()]; - vertexBase.get(vertexBaseArray); - capsule.write(vertexBaseArray, MeshCollisionShape.VERTEX_BASE, null); - - if (memoryOptimized) { - byte[] data = saveBVH(objectId); - capsule.write(data, MeshCollisionShape.NATIVE_BVH, null); - } - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(final JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - this.numVertices = capsule.readInt(MeshCollisionShape.NUM_VERTICES, 0); - this.numTriangles = capsule.readInt(MeshCollisionShape.NUM_TRIANGLES, 0); - this.vertexStride = capsule.readInt(MeshCollisionShape.VERTEX_STRIDE, 0); - this.triangleIndexStride = capsule.readInt(MeshCollisionShape.TRIANGLE_INDEX_STRIDE, 0); - - this.triangleIndexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.TRIANGLE_INDEX_BASE, null)); - this.vertexBase = BufferUtils.createByteBuffer(capsule.readByteArray(MeshCollisionShape.VERTEX_BASE, null)); - - byte[] nativeBvh = capsule.readByteArray(MeshCollisionShape.NATIVE_BVH, null); - memoryOptimized=nativeBvh != null; - createShape(nativeBvh); - } - - /** - * Instantiate the configured shape in Bullet. - */ - private void createShape(byte bvh[]) { - boolean buildBvh=bvh==null||bvh.length==0; - this.meshId = NativeMeshUtil.createTriangleIndexVertexArray(this.triangleIndexBase, this.vertexBase, this.numTriangles, this.numVertices, this.vertexStride, this.triangleIndexStride); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Mesh {0}", Long.toHexString(this.meshId)); - this.objectId = createShape(memoryOptimized, buildBvh, this.meshId); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(this.objectId)); - if(!buildBvh) nativeBVHBuffer = setBVH(bvh, this.objectId); - this.setScale(this.scale); - this.setMargin(this.margin); - } - - /** - * returns the pointer to the native buffer used by the in place - * de-serialized shape, must be freed when not used anymore! - */ - private native long setBVH(byte[] buffer, long objectid); - - private native byte[] saveBVH(long objectId); - - private native long createShape(boolean memoryOptimized, boolean buildBvt, long meshId); - - /** - * Finalize this shape just before it is destroyed. Should be invoked only - * by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - public void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Mesh {0}", Long.toHexString(this.meshId)); - if (this.meshId > 0) { - this.finalizeNative(this.meshId, this.nativeBVHBuffer); - } - } - - private native void finalizeNative(long objectId, long nativeBVHBuffer); -} \ No newline at end of file diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java deleted file mode 100644 index 691b4871a3..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Plane; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A planar collision shape based on Bullet's btStaticPlaneShape. - * - * @author normenhansen - */ -public class PlaneCollisionShape extends CollisionShape{ - /** - * description of the plane - */ - private Plane plane; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PlaneCollisionShape() { - } - - /** - * Instantiate a plane shape defined by the specified plane. - * - * @param plane the desired plane (not null, alias created) - */ - public PlaneCollisionShape(Plane plane) { - this.plane = plane; - createShape(); - } - - /** - * Access the defining plane. - * - * @return the pre-existing instance (not null) - */ - public final Plane getPlane() { - return plane; - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(plane, "collisionPlane", new Plane()); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - plane = (Plane) capsule.readSavable("collisionPlane", new Plane()); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - objectId = createShape(plane.getNormal(), plane.getConstant()); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// objectId = new StaticPlaneShape(Converter.convert(plane.getNormal()),plane.getConstant()); -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - setScale(scale); - setMargin(margin); - } - - private native long createShape(Vector3f normal, float constant); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java deleted file mode 100644 index b8db8599d1..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A simple point, line-segment, triangle, or tetrahedron collision shape based - * on Bullet's btBU_Simplex1to4. - * - * @author normenhansen - */ -public class SimplexCollisionShape extends CollisionShape { - - /** - * vertex positions - */ - private Vector3f vector1, vector2, vector3, vector4; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected SimplexCollisionShape() { - } - - /** - * Instantiate a tetrahedral collision shape based on the specified points. - * - * @param point1 the coordinates of 1st point (not null, alias created) - * @param point2 the coordinates of 2nd point (not null, alias created) - * @param point3 the coordinates of 3rd point (not null, alias created) - * @param point4 the coordinates of 4th point (not null, alias created) - */ - public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3, Vector3f point4) { - vector1 = point1; - vector2 = point2; - vector3 = point3; - vector4 = point4; - createShape(); - } - - /** - * Instantiate a triangular collision shape based on the specified points. - * - * @param point1 the coordinates of 1st point (not null, alias created) - * @param point2 the coordinates of 2nd point (not null, alias created) - * @param point3 the coordinates of 3rd point (not null, alias created) - */ - public SimplexCollisionShape(Vector3f point1, Vector3f point2, Vector3f point3) { - vector1 = point1; - vector2 = point2; - vector3 = point3; - createShape(); - } - - /** - * Instantiate a line-segment collision shape based on the specified points. - * - * @param point1 the coordinates of 1st point (not null, alias created) - * @param point2 the coordinates of 2nd point (not null, alias created) - */ - public SimplexCollisionShape(Vector3f point1, Vector3f point2) { - vector1 = point1; - vector2 = point2; - createShape(); - } - - /** - * Instantiate a point collision shape based on the specified points. - * - * @param point1 the coordinates of point (not null, alias created) - */ - public SimplexCollisionShape(Vector3f point1) { - vector1 = point1; - createShape(); - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(vector1, "simplexPoint1", null); - capsule.write(vector2, "simplexPoint2", null); - capsule.write(vector3, "simplexPoint3", null); - capsule.write(vector4, "simplexPoint4", null); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - vector1 = (Vector3f) capsule.readSavable("simplexPoint1", null); - vector2 = (Vector3f) capsule.readSavable("simplexPoint2", null); - vector3 = (Vector3f) capsule.readSavable("simplexPoint3", null); - vector4 = (Vector3f) capsule.readSavable("simplexPoint4", null); - createShape(); - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - if (vector4 != null) { - objectId = createShape(vector1, vector2, vector3, vector4); -// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3), Converter.convert(vector4)); - } else if (vector3 != null) { - objectId = createShape(vector1, vector2, vector3); -// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2), Converter.convert(vector3)); - } else if (vector2 != null) { - objectId = createShape(vector1, vector2); -// objectId = new BU_Simplex1to4(Converter.convert(vector1), Converter.convert(vector2)); - } else { - objectId = createShape(vector1); -// objectId = new BU_Simplex1to4(Converter.convert(vector1)); - } - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - setScale(scale); - setMargin(margin); - } - - private native long createShape(Vector3f vector1); - - private native long createShape(Vector3f vector1, Vector3f vector2); - - private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3); - - private native long createShape(Vector3f vector1, Vector3f vector2, Vector3f vector3, Vector3f vector4); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java deleted file mode 100644 index 27549da90b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java +++ /dev/null @@ -1,136 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.collision.shapes; - -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A spherical collision shape based on Bullet's btSphereShape. These shapes - * have no margin and cannot be scaled. - * - * @author normenhansen - */ -public class SphereCollisionShape extends CollisionShape { - - /** - * copy of radius (≥0) - */ - protected float radius; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected SphereCollisionShape() { - } - - /** - * Instantiate a sphere shape with the specified radius. - * - * @param radius the desired radius (≥0) - */ - public SphereCollisionShape(float radius) { - this.radius = radius; - createShape(); - } - - /** - * Read the radius of the sphere. - * - * @return the radius (≥0) - */ - public float getRadius() { - return radius; - } - - /** - * Serialize this shape, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(radius, "radius", 0.5f); - } - - /** - * De-serialize this shape, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - radius = capsule.readFloat("radius", 0.5f); - createShape(); - } - - /** - * Alter the scaling factors of this shape. Scaling is disabled - * for sphere shapes. - * - * @param scale the desired scaling factor for each local axis (not null, no - * negative component, unaffected, default=1,1,1) - */ - @Override - public void setScale(Vector3f scale) { - if (!scale.equals(Vector3f.UNIT_XYZ)) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "SphereCollisionShape cannot be scaled"); - } - } - - /** - * Instantiate the configured shape in Bullet. - */ - protected void createShape() { - objectId = createShape(radius); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Shape {0}", Long.toHexString(objectId)); -// new SphereShape(radius); -// objectId.setLocalScaling(Converter.convert(getScale())); -// objectId.setMargin(margin); - setScale(scale); // Set the scale to 1 - setMargin(margin); - } - - private native long createShape(float radius); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java deleted file mode 100644 index ea05e9f659..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java +++ /dev/null @@ -1,192 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A joint based on Bullet's btConeTwistConstraint. - *

    - * From the Bullet manual:
    - * To create ragdolls, the cone twist constraint is very useful for limbs like - * the upper arm. It is a special point to point constraint that adds cone and - * twist axis limits. The x-axis serves as twist axis. - * - * @author normenhansen - */ -public class ConeJoint extends PhysicsJoint { - - protected Matrix3f rotA, rotB; - protected float swingSpan1 = 1e30f; - protected float swingSpan2 = 1e30f; - protected float twistSpan = 1e30f; - protected boolean angularOnly = false; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected ConeJoint() { - } - - /** - * Instantiate a ConeJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - */ - public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) { - super(nodeA, nodeB, pivotA, pivotB); - this.rotA = new Matrix3f(); - this.rotB = new Matrix3f(); - createJoint(); - } - - /** - * Instantiate a ConeJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA local translation of the joint connection point in node A - * (not null, alias created) - * @param pivotB local translation of the joint connection point in node B - * (not null, alias created) - * @param rotA the local orientation of the connection to node A (not null, - * alias created) - * @param rotB the local orientation of the connection to node B (not null, - * alias created) - */ - public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB) { - super(nodeA, nodeB, pivotA, pivotB); - this.rotA = rotA; - this.rotB = rotB; - createJoint(); - } - - /** - * Alter the angular limits for this joint. - * - * @param swingSpan1 angle (in radians) - * @param swingSpan2 angle (in radians) - * @param twistSpan angle (in radians) - */ - public void setLimit(float swingSpan1, float swingSpan2, float twistSpan) { - this.swingSpan1 = swingSpan1; - this.swingSpan2 = swingSpan2; - this.twistSpan = twistSpan; - setLimit(objectId, swingSpan1, swingSpan2, twistSpan); - } - - private native void setLimit(long objectId, float swingSpan1, float swingSpan2, float twistSpan); - - /** - * Alter whether this joint is angular only. - * - * @param value the desired setting (default=false) - */ - public void setAngularOnly(boolean value) { - angularOnly = value; - setAngularOnly(objectId, value); - } - - private native void setAngularOnly(long objectId, boolean value); - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(rotA, "rotA", new Matrix3f()); - capsule.write(rotB, "rotB", new Matrix3f()); - - capsule.write(angularOnly, "angularOnly", false); - capsule.write(swingSpan1, "swingSpan1", 1e30f); - capsule.write(swingSpan2, "swingSpan2", 1e30f); - capsule.write(twistSpan, "twistSpan", 1e30f); - } - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - this.rotA = (Matrix3f) capsule.readSavable("rotA", new Matrix3f()); - this.rotB = (Matrix3f) capsule.readSavable("rotB", new Matrix3f()); - - this.angularOnly = capsule.readBoolean("angularOnly", false); - this.swingSpan1 = capsule.readFloat("swingSpan1", 1e30f); - this.swingSpan2 = capsule.readFloat("swingSpan2", 1e30f); - this.twistSpan = capsule.readFloat("twistSpan", 1e30f); - createJoint(); - } - - /** - * Create the configured joint in Bullet. - */ - protected void createJoint() { - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - setLimit(objectId, swingSpan1, swingSpan2, twistSpan); - setAngularOnly(objectId, angularOnly); - } - - private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java deleted file mode 100644 index 9b38b2dc3b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A joint based on Bullet's btHingeConstraint. - *

    - * From the Bullet manual:
    - * Hinge constraint, or revolute joint restricts two additional angular degrees - * of freedom, so the body can only rotate around one axis, the hinge axis. This - * can be useful to represent doors or wheels rotating around one axis. The user - * can specify limits and motor for the hinge. - * - * @author normenhansen - */ -public class HingeJoint extends PhysicsJoint { - - protected Vector3f axisA; - protected Vector3f axisB; - /** - * copy of the angular-only flag (default=false) - */ - protected boolean angularOnly = false; - /** - * copy of the limit's bias factor, how strictly position errors (drift) is - * corrected (default=0.3) - */ - protected float biasFactor = 0.3f; - /** - * copy of the limit's relaxation factor, the rate at which velocity errors - * are corrected (default=1) - */ - protected float relaxationFactor = 1.0f; - /** - * copy of the limit's softness, the range fraction at which velocity-error - * correction starts operating (default=0.9) - */ - protected float limitSoftness = 0.9f; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected HingeJoint() { - } - - /** - * Instantiate a HingeJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param axisA the local axis of the connection to node A (not null, alias - * created) - * @param axisB the local axis of the connection to node B (not null, alias - * created) - */ - public HingeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Vector3f axisA, Vector3f axisB) { - super(nodeA, nodeB, pivotA, pivotB); - this.axisA = axisA; - this.axisB = axisB; - createJoint(); - } - - /** - * Enable or disable this joint's motor. - * - * @param enable true to enable, false to disable - * @param targetVelocity the desired target velocity - * @param maxMotorImpulse the desired maximum rotational force - */ - public void enableMotor(boolean enable, float targetVelocity, float maxMotorImpulse) { - enableMotor(objectId, enable, targetVelocity, maxMotorImpulse); - } - - private native void enableMotor(long objectId, boolean enable, float targetVelocity, float maxMotorImpulse); - - /** - * Test whether this joint's motor is enabled. - * - * @return true if enabled, otherwise false - */ - public boolean getEnableMotor() { - return getEnableAngularMotor(objectId); - } - - private native boolean getEnableAngularMotor(long objectId); - - /** - * Read the motor's target velocity. - * - * @return velocity - */ - public float getMotorTargetVelocity() { - return getMotorTargetVelocity(objectId); - } - - private native float getMotorTargetVelocity(long objectId); - - /** - * Read the motor's maximum impulse. - * - * @return impulse - */ - public float getMaxMotorImpulse() { - return getMaxMotorImpulse(objectId); - } - - private native float getMaxMotorImpulse(long objectId); - - /** - * Alter this joint's limits. - * - * @param low the desired lower limit of the hinge angle (in radians) - * @param high the desired upper limit of the joint angle (in radians) - */ - public void setLimit(float low, float high) { - setLimit(objectId, low, high); - } - - private native void setLimit(long objectId, float low, float high); - - /** - * Alter this joint's limits. If you're above the softness, velocities that - * would shoot through the actual limit are slowed down. The bias should be - * in the range of 0.2 - 0.5. - * - * @param low the desired lower limit of the hinge angle (in radians) - * @param high the desired upper limit of the joint angle (in radians) - * @param _softness the desired range fraction at which velocity-error - * correction starts operating. A softness of 0.9 means that the correction - * starts at 90% of the limit range. (default=0.9) - * @param _biasFactor the desired magnitude of the position correction, how - * strictly position errors (drift) is corrected. (default=0.3) - * @param _relaxationFactor the desired rate at which velocity errors are - * corrected. This can be seen as the strength of the limits. A low value - * will make the limits more spongy. (default=1) - */ - public void setLimit(float low, float high, float _softness, float _biasFactor, float _relaxationFactor) { - biasFactor = _biasFactor; - relaxationFactor = _relaxationFactor; - limitSoftness = _softness; - setLimit(objectId, low, high, _softness, _biasFactor, _relaxationFactor); - } - - private native void setLimit(long objectId, float low, float high, float _softness, float _biasFactor, float _relaxationFactor); - - /** - * Read the upper limit of the hinge angle. - * - * @return angle (in radians) - */ - public float getUpperLimit() { - return getUpperLimit(objectId); - } - - private native float getUpperLimit(long objectId); - - /** - * Read the lower limit of the hinge angle. - * - * @return the angle (in radians) - */ - public float getLowerLimit() { - return getLowerLimit(objectId); - } - - private native float getLowerLimit(long objectId); - - /** - * Alter the hinge translation flag. - * - * @param angularOnly true→rotate only, false→rotate and translate - * (default=false) - */ - public void setAngularOnly(boolean angularOnly) { - this.angularOnly = angularOnly; - setAngularOnly(objectId, angularOnly); - } - - private native void setAngularOnly(long objectId, boolean angularOnly); - - /** - * Read the hinge angle. - * - * @return the angle (in radians) - */ - public float getHingeAngle() { - return getHingeAngle(objectId); - } - - private native float getHingeAngle(long objectId); - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(axisA, "axisA", new Vector3f()); - capsule.write(axisB, "axisB", new Vector3f()); - - capsule.write(angularOnly, "angularOnly", false); - - capsule.write(getLowerLimit(), "lowerLimit", 1e30f); - capsule.write(getUpperLimit(), "upperLimit", -1e30f); - - capsule.write(biasFactor, "biasFactor", 0.3f); - capsule.write(relaxationFactor, "relaxationFactor", 1f); - capsule.write(limitSoftness, "limitSoftness", 0.9f); - - capsule.write(getEnableMotor(), "enableAngularMotor", false); - capsule.write(getMotorTargetVelocity(), "targetVelocity", 0.0f); - capsule.write(getMaxMotorImpulse(), "maxMotorImpulse", 0.0f); - } - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - this.axisA = (Vector3f) capsule.readSavable("axisA", new Vector3f()); - this.axisB = (Vector3f) capsule.readSavable("axisB", new Vector3f()); - - this.angularOnly = capsule.readBoolean("angularOnly", false); - float lowerLimit = capsule.readFloat("lowerLimit", 1e30f); - float upperLimit = capsule.readFloat("upperLimit", -1e30f); - - this.biasFactor = capsule.readFloat("biasFactor", 0.3f); - this.relaxationFactor = capsule.readFloat("relaxationFactor", 1f); - this.limitSoftness = capsule.readFloat("limitSoftness", 0.9f); - - boolean enableAngularMotor = capsule.readBoolean("enableAngularMotor", false); - float targetVelocity = capsule.readFloat("targetVelocity", 0.0f); - float maxMotorImpulse = capsule.readFloat("maxMotorImpulse", 0.0f); - - createJoint(); - enableMotor(enableAngularMotor, targetVelocity, maxMotorImpulse); - setLimit(lowerLimit, upperLimit, limitSoftness, biasFactor, relaxationFactor); - } - - /** - * Create the configured joint in Bullet. - */ - protected void createJoint() { - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, axisA, pivotB, axisB); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - setAngularOnly(objectId, angularOnly); - } - - private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f axisA, Vector3f pivotB, Vector3f axisB); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java deleted file mode 100644 index 1a80fae035..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java +++ /dev/null @@ -1,231 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.*; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The abstract base class for physics joints based on Bullet's - * btTypedConstraint, used to connect 2 dynamic rigid bodies in the same - * physics space. - *

    - * Joints include ConeJoint, HingeJoint, Point2PointJoint, and SixDofJoint. - * - * @author normenhansen - */ -public abstract class PhysicsJoint implements Savable { - - /** - * Unique identifier of the Bullet constraint. Constructors are responsible - * for setting this to a non-zero value. After that, the id never changes. - */ - protected long objectId = 0; - /** - * one of the connected rigid bodies - */ - protected PhysicsRigidBody nodeA; - /** - * the other connected rigid body - */ - protected PhysicsRigidBody nodeB; - /** - * local offset of this joint's connection point in node A - */ - protected Vector3f pivotA; - /** - * local offset of this joint's connection point in node B - */ - protected Vector3f pivotB; - protected boolean collisionBetweenLinkedBodys = true; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PhysicsJoint() { - } - - /** - * Instantiate a PhysicsJoint. To be effective, the joint must be added to - * the physics space of the two bodies. Also, the bodies must be dynamic and - * distinct. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA local offset of the joint connection point in node A (not - * null, alias created) - * @param pivotB local offset of the joint connection point in node B (not - * null, alias created) - */ - public PhysicsJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) { - this.nodeA = nodeA; - this.nodeB = nodeB; - this.pivotA = pivotA; - this.pivotB = pivotB; - nodeA.addJoint(this); - nodeB.addJoint(this); - } - - /** - * Read the magnitude of the applied impulse. - * - * @return impulse - */ - public float getAppliedImpulse() { - return getAppliedImpulse(objectId); - } - - private native float getAppliedImpulse(long objectId); - - /** - * Read the id of the Bullet constraint. - * - * @return the unique identifier (not zero) - */ - public long getObjectId() { - return objectId; - } - - /** - * Test whether collisions are allowed between the linked bodies. - * - * @return true if collision are allowed, otherwise false - */ - public boolean isCollisionBetweenLinkedBodys() { - return collisionBetweenLinkedBodys; - } - - /** - * Enable or disable collisions between the linked bodies. The joint must be - * removed from and added to PhysicsSpace for this change to be effective. - * - * @param collisionBetweenLinkedBodys true → allow collisions, false → prevent them - */ - public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodys) { - this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodys; - } - - /** - * Access the 1st body specified in during construction. - * - * @return the pre-existing body - */ - public PhysicsRigidBody getBodyA() { - return nodeA; - } - - /** - * Access the 2nd body specified in during construction. - * - * @return the pre-existing body - */ - public PhysicsRigidBody getBodyB() { - return nodeB; - } - - /** - * Access the local offset of the joint connection point in node A. - * - * @return the pre-existing vector (not null) - */ - public Vector3f getPivotA() { - return pivotA; - } - - /** - * Access the local offset of the joint connection point in node A. - * - * @return the pre-existing vector (not null) - */ - public Vector3f getPivotB() { - return pivotB; - } - - /** - * Destroy this joint and remove it from the joint lists of its connected - * bodies. - */ - public void destroy() { - getBodyA().removeJoint(this); - getBodyB().removeJoint(this); - } - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - public void write(JmeExporter ex) throws IOException { - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(nodeA, "nodeA", null); - capsule.write(nodeB, "nodeB", null); - capsule.write(pivotA, "pivotA", null); - capsule.write(pivotB, "pivotB", null); - } - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - public void read(JmeImporter im) throws IOException { - InputCapsule capsule = im.getCapsule(this); - this.nodeA = ((PhysicsRigidBody) capsule.readSavable("nodeA", null)); - this.nodeB = (PhysicsRigidBody) capsule.readSavable("nodeB", null); - this.pivotA = (Vector3f) capsule.readSavable("pivotA", new Vector3f()); - this.pivotB = (Vector3f) capsule.readSavable("pivotB", new Vector3f()); - } - - /** - * Finalize this physics joint just before it is destroyed. Should be - * invoked only by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Joint {0}", Long.toHexString(objectId)); - finalizeNative(objectId); - } - - private native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java deleted file mode 100644 index 4c2ddbffe9..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A joint based on Bullet's btPoint2PointConstraint. - *

    - * From the Bullet manual:
    - * Point to point constraint limits the translation so that the local pivot - * points of 2 rigidbodies match in worldspace. A chain of rigidbodies can be - * connected using this constraint. - * - * @author normenhansen - */ -public class Point2PointJoint extends PhysicsJoint { - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected Point2PointJoint() { - } - - /** - * Instantiate a Point2PointJoint. To be effective, the joint must be added - * to a physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - */ - public Point2PointJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB) { - super(nodeA, nodeB, pivotA, pivotB); - createJoint(); - } - - /** - * Alter the joint's damping. - * - * @param value the desired viscous damping ratio (0→no damping, - * 1→critically damped, default=1) - */ - public void setDamping(float value) { - setDamping(objectId, value); - } - - private native void setDamping(long objectId, float value); - - /** - * Alter the joint's impulse clamp. - * - * @param value the desired impulse clamp value (default=0) - */ - public void setImpulseClamp(float value) { - setImpulseClamp(objectId, value); - } - - private native void setImpulseClamp(long objectId, float value); - - /** - * Alter the joint's tau value. - * - * @param value the desired tau value (default=0.3) - */ - public void setTau(float value) { - setTau(objectId, value); - } - - private native void setTau(long objectId, float value); - - /** - * Read the joint's damping ratio. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDamping() { - return getDamping(objectId); - } - - private native float getDamping(long objectId); - - /** - * Read the joint's impulse clamp. - * - * @return the clamp value - */ - public float getImpulseClamp() { - return getImpulseClamp(objectId); - } - - private native float getImpulseClamp(long objectId); - - /** - * Read the joint's tau value. - * - * @return the tau value - */ - public float getTau() { - return getTau(objectId); - } - - private native float getTau(long objectId); - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule cap = ex.getCapsule(this); - cap.write(getDamping(), "damping", 1.0f); - cap.write(getTau(), "tau", 0.3f); - cap.write(getImpulseClamp(), "impulseClamp", 0f); - } - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - createJoint(); - InputCapsule cap = im.getCapsule(this); - setDamping(cap.readFloat("damping", 1.0f)); - setDamping(cap.readFloat("tau", 0.3f)); - setDamping(cap.readFloat("impulseClamp", 0f)); - } - - /** - * Create the configured joint in Bullet. - */ - protected void createJoint() { - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, pivotB); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - } - - private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Vector3f pivotB); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java deleted file mode 100644 index 6d9fb7b10b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java +++ /dev/null @@ -1,350 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.joints.motors.RotationalLimitMotor; -import com.jme3.bullet.joints.motors.TranslationalLimitMotor; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A joint based on Bullet's btGeneric6DofConstraint. - *

    - * From the Bullet manual:
    - * This generic constraint can emulate a variety of standard constraints, by - * configuring each of the 6 degrees of freedom (dof). The first 3 dof axis are - * linear axis, which represent translation of rigidbodies, and the latter 3 dof - * axis represent the angular motion. Each axis can be either locked, free or - * limited. On construction of a new btGeneric6DofSpring2Constraint, all axis - * are locked. Afterwards the axis can be reconfigured. Note that several - * combinations that include free and/or limited angular degrees of freedom are - * undefined. - *

    - * For each axis:

      - *
    • Lowerlimit = Upperlimit → axis is locked
    • - *
    • Lowerlimit > Upperlimit → axis is free
    • - *
    • Lowerlimit < Upperlimit → axis it limited in that range
    • - *
    - * - * @author normenhansen - */ -public class SixDofJoint extends PhysicsJoint { - - Matrix3f rotA, rotB; - /** - * true→limits give the allowable range of movement of frameB in frameA - * space, false→limits give the allowable range of movement of frameA - * in frameB space - */ - boolean useLinearReferenceFrameA; - LinkedList rotationalMotors = new LinkedList(); - TranslationalLimitMotor translationalMotor; - /** - * upper limits for rotation of all 3 axes - */ - Vector3f angularUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY); - /** - * lower limits for rotation of all 3 axes - */ - Vector3f angularLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY); - /** - * upper limit for translation of all 3 axes - */ - Vector3f linearUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY); - /** - * lower limits for translation of all 3 axes - */ - Vector3f linearLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY); - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected SixDofJoint() { - } - - /** - * Instantiate a SixDofJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param rotA the local orientation of the connection to node A (not null, - * alias created) - * @param rotB the local orientation of the connection to node B (not null, - * alias created) - * @param useLinearReferenceFrameA true→use node A, false→use node - * B - */ - public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { - super(nodeA, nodeB, pivotA, pivotB); - this.useLinearReferenceFrameA = useLinearReferenceFrameA; - this.rotA = rotA; - this.rotB = rotB; - - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - gatherMotors(); - } - - /** - * Instantiate a SixDofJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param useLinearReferenceFrameA true→use node A, false→use node - * B - */ - public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) { - super(nodeA, nodeB, pivotA, pivotB); - this.useLinearReferenceFrameA = useLinearReferenceFrameA; - rotA = new Matrix3f(); - rotB = new Matrix3f(); - - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - gatherMotors(); - } - - private void gatherMotors() { - for (int i = 0; i < 3; i++) { - RotationalLimitMotor rmot = new RotationalLimitMotor(getRotationalLimitMotor(objectId, i)); - rotationalMotors.add(rmot); - } - translationalMotor = new TranslationalLimitMotor(getTranslationalLimitMotor(objectId)); - } - - native private void getAngles(long jointId, Vector3f storeVector); - - /** - * Copy the joint's rotation angles. - * - * @param storeResult storage for the result (modified if not null) - * @return the rotation angle for each local axis (in radians, either - * storeResult or a new vector, not null) - */ - public Vector3f getAngles(Vector3f storeResult) { - Vector3f result = (storeResult == null) ? new Vector3f() : storeResult; - - long constraintId = getObjectId(); - getAngles(constraintId, result); - - return result; - } - - private native long getRotationalLimitMotor(long objectId, int index); - - private native long getTranslationalLimitMotor(long objectId); - - /** - * Access the TranslationalLimitMotor of this joint, the motor which - * influences translation on all 3 axes. - * - * @return the pre-existing instance - */ - public TranslationalLimitMotor getTranslationalLimitMotor() { - return translationalMotor; - } - - /** - * Access the indexed RotationalLimitMotor of this joint, the motor which - * influences rotation around one axis. - * - * @param index the axis index of the desired motor: 0→X, 1→Y, - * 2→Z - * @return the pre-existing instance - */ - public RotationalLimitMotor getRotationalLimitMotor(int index) { - return rotationalMotors.get(index); - } - - /** - * Alter the joint's upper limits for translation of all 3 axes. - * - * @param vector the desired upper limits (not null, unaffected) - */ - public void setLinearUpperLimit(Vector3f vector) { - linearUpperLimit.set(vector); - setLinearUpperLimit(objectId, vector); - } - - private native void setLinearUpperLimit(long objctId, Vector3f vector); - - /** - * Alter the joint's lower limits for translation of all 3 axes. - * - * @param vector the desired lower limits (not null, unaffected) - */ - public void setLinearLowerLimit(Vector3f vector) { - linearLowerLimit.set(vector); - setLinearLowerLimit(objectId, vector); - } - - private native void setLinearLowerLimit(long objctId, Vector3f vector); - - /** - * Alter the joint's upper limits for rotation of all 3 axes. - * - * @param vector the desired upper limits (in radians, not null, unaffected) - */ - public void setAngularUpperLimit(Vector3f vector) { - angularUpperLimit.set(vector); - setAngularUpperLimit(objectId, vector); - } - - private native void setAngularUpperLimit(long objctId, Vector3f vector); - - /** - * Alter the joint's lower limits for rotation of all 3 axes. - * - * @param vector the desired lower limits (in radians, not null, unaffected) - */ - public void setAngularLowerLimit(Vector3f vector) { - angularLowerLimit.set(vector); - setAngularLowerLimit(objectId, vector); - } - - private native void setAngularLowerLimit(long objctId, Vector3f vector); - - native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - gatherMotors(); - - setAngularUpperLimit((Vector3f) capsule.readSavable("angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY))); - setAngularLowerLimit((Vector3f) capsule.readSavable("angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY))); - setLinearUpperLimit((Vector3f) capsule.readSavable("linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY))); - setLinearLowerLimit((Vector3f) capsule.readSavable("linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY))); - - for (int i = 0; i < 3; i++) { - RotationalLimitMotor rotationalLimitMotor = getRotationalLimitMotor(i); - rotationalLimitMotor.setBounce(capsule.readFloat("rotMotor" + i + "_Bounce", 0.0f)); - rotationalLimitMotor.setDamping(capsule.readFloat("rotMotor" + i + "_Damping", 1.0f)); - rotationalLimitMotor.setERP(capsule.readFloat("rotMotor" + i + "_ERP", 0.5f)); - rotationalLimitMotor.setHiLimit(capsule.readFloat("rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY)); - rotationalLimitMotor.setLimitSoftness(capsule.readFloat("rotMotor" + i + "_LimitSoftness", 0.5f)); - rotationalLimitMotor.setLoLimit(capsule.readFloat("rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY)); - rotationalLimitMotor.setMaxLimitForce(capsule.readFloat("rotMotor" + i + "_MaxLimitForce", 300.0f)); - rotationalLimitMotor.setMaxMotorForce(capsule.readFloat("rotMotor" + i + "_MaxMotorForce", 0.1f)); - rotationalLimitMotor.setTargetVelocity(capsule.readFloat("rotMotor" + i + "_TargetVelocity", 0)); - rotationalLimitMotor.setEnableMotor(capsule.readBoolean("rotMotor" + i + "_EnableMotor", false)); - } - getTranslationalLimitMotor().setAccumulatedImpulse((Vector3f) capsule.readSavable("transMotor_AccumulatedImpulse", Vector3f.ZERO)); - getTranslationalLimitMotor().setDamping(capsule.readFloat("transMotor_Damping", 1.0f)); - getTranslationalLimitMotor().setLimitSoftness(capsule.readFloat("transMotor_LimitSoftness", 0.7f)); - getTranslationalLimitMotor().setLowerLimit((Vector3f) capsule.readSavable("transMotor_LowerLimit", Vector3f.ZERO)); - getTranslationalLimitMotor().setRestitution(capsule.readFloat("transMotor_Restitution", 0.5f)); - getTranslationalLimitMotor().setUpperLimit((Vector3f) capsule.readSavable("transMotor_UpperLimit", Vector3f.ZERO)); - - for (int axisIndex = 0; axisIndex < 3; ++axisIndex) { - translationalMotor.setEnabled(axisIndex, capsule.readBoolean( - "transMotor_Enable" + axisIndex, false)); - } - } - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(angularUpperLimit, "angularUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)); - capsule.write(angularLowerLimit, "angularLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)); - capsule.write(linearUpperLimit, "linearUpperLimit", new Vector3f(Vector3f.POSITIVE_INFINITY)); - capsule.write(linearLowerLimit, "linearLowerLimit", new Vector3f(Vector3f.NEGATIVE_INFINITY)); - int i = 0; - for (Iterator it = rotationalMotors.iterator(); it.hasNext();) { - RotationalLimitMotor rotationalLimitMotor = it.next(); - capsule.write(rotationalLimitMotor.getBounce(), "rotMotor" + i + "_Bounce", 0.0f); - capsule.write(rotationalLimitMotor.getDamping(), "rotMotor" + i + "_Damping", 1.0f); - capsule.write(rotationalLimitMotor.getERP(), "rotMotor" + i + "_ERP", 0.5f); - capsule.write(rotationalLimitMotor.getHiLimit(), "rotMotor" + i + "_HiLimit", Float.POSITIVE_INFINITY); - capsule.write(rotationalLimitMotor.getLimitSoftness(), "rotMotor" + i + "_LimitSoftness", 0.5f); - capsule.write(rotationalLimitMotor.getLoLimit(), "rotMotor" + i + "_LoLimit", Float.NEGATIVE_INFINITY); - capsule.write(rotationalLimitMotor.getMaxLimitForce(), "rotMotor" + i + "_MaxLimitForce", 300.0f); - capsule.write(rotationalLimitMotor.getMaxMotorForce(), "rotMotor" + i + "_MaxMotorForce", 0.1f); - capsule.write(rotationalLimitMotor.getTargetVelocity(), "rotMotor" + i + "_TargetVelocity", 0); - capsule.write(rotationalLimitMotor.isEnableMotor(), "rotMotor" + i + "_EnableMotor", false); - i++; - } - capsule.write(getTranslationalLimitMotor().getAccumulatedImpulse(), "transMotor_AccumulatedImpulse", Vector3f.ZERO); - capsule.write(getTranslationalLimitMotor().getDamping(), "transMotor_Damping", 1.0f); - capsule.write(getTranslationalLimitMotor().getLimitSoftness(), "transMotor_LimitSoftness", 0.7f); - capsule.write(getTranslationalLimitMotor().getLowerLimit(), "transMotor_LowerLimit", Vector3f.ZERO); - capsule.write(getTranslationalLimitMotor().getRestitution(), "transMotor_Restitution", 0.5f); - capsule.write(getTranslationalLimitMotor().getUpperLimit(), "transMotor_UpperLimit", Vector3f.ZERO); - - for (int axisIndex = 0; axisIndex < 3; ++axisIndex) { - capsule.write(translationalMotor.isEnabled(axisIndex), - "transMotor_Enable" + axisIndex, false); - } - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java deleted file mode 100644 index a9479d1e7f..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SixDofSpringJoint.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; - -/** - * A 6 degree-of-freedom joint based on Bullet's btGeneric6DofSpringConstraint. - *

    - * From the Bullet manual:
    - * This generic constraint can emulate a variety of standard constraints, by - * configuring each of the 6 degrees of freedom (dof). The first 3 dof axis are - * linear axis, which represent translation of rigidbodies, and the latter 3 dof - * axis represent the angular motion. Each axis can be either locked, free or - * limited. On construction of a new btGeneric6DofSpring2Constraint, all axis - * are locked. Afterwards the axis can be reconfigured. Note that several - * combinations that include free and/or limited angular degrees of freedom are - * undefined. - *

    - * For each axis:

      - *
    • Lowerlimit = Upperlimit → axis is locked
    • - *
    • Lowerlimit > Upperlimit → axis is free
    • - *
    • Lowerlimit < Upperlimit → axis it limited in that range
    • - *
    - * - * @author normenhansen - */ -public class SixDofSpringJoint extends SixDofJoint { - - final boolean springEnabled[] = new boolean[6]; - final float equilibriumPoint[] = new float[6]; - final float springStiffness[] = new float[6]; - final float springDamping[] = new float[6]; // between 0 and 1 (1 == no damping) - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - public SixDofSpringJoint() { - } - - /** - * Instantiate a SixDofSpringJoint. To be effective, the joint must be added - * to a physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param rotA the local orientation of the connection to node A (not - * null, alias created) - * @param rotB the local orientation of the connection to node B (not - * null, alias created) - * @param useLinearReferenceFrameA true→use node A, false→use node - * B - */ - public SixDofSpringJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { - super(nodeA, nodeB, pivotA, pivotB, rotA, rotB, useLinearReferenceFrameA); - } - - /** - * Enable or disable the spring for the indexed degree of freedom. - * - * @param index which degree of freedom (≥0, <6) - * @param onOff true → enable, false → disable - */ - public void enableSpring(int index, boolean onOff) { - enableSpring(objectId, index, onOff); - } - native void enableSpring(long objctId, int index, boolean onOff); - - /** - * Alter the spring stiffness for the indexed degree of freedom. - * - * @param index which degree of freedom (≥0, <6) - * @param stiffness the desired stiffness - */ - public void setStiffness(int index, float stiffness) { - setStiffness(objectId, index, stiffness); - } - native void setStiffness(long objctId, int index, float stiffness); - - /** - * Alter the damping for the indexed degree of freedom. - * - * @param index which degree of freedom (≥0, <6) - * @param damping the desired viscous damping ratio (0→no damping, - * 1→critically damped, default=1) - */ - public void setDamping(int index, float damping) { - setDamping(objectId, index, damping); - - } - native void setDamping(long objctId, int index, float damping); - /** - * Alter the equilibrium points for all degrees of freedom, based on the - * current constraint position/orientation. - */ - public void setEquilibriumPoint() { // set the current constraint position/orientation as an equilibrium point for all DOF - setEquilibriumPoint(objectId); - } - native void setEquilibriumPoint(long objctId); - /** - * Alter the equilibrium point of the indexed degree of freedom, based on - * the current constraint position/orientation. - * - * @param index which degree of freedom (≥0, <6) - */ - public void setEquilibriumPoint(int index){ // set the current constraint position/orientation as an equilibrium point for given DOF - setEquilibriumPoint(objectId, index); - } - native void setEquilibriumPoint(long objctId, int index); - @Override - native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java deleted file mode 100644 index 8fa93dd131..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java +++ /dev/null @@ -1,888 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints; - -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A slider joint based on Bullet's btSliderConstraint. - *

    - * From the Bullet manual:
    - * The slider constraint allows the body to rotate around one axis and translate - * along this axis. - * - * @author normenhansen - */ -public class SliderJoint extends PhysicsJoint { - - protected Matrix3f rotA, rotB; - protected boolean useLinearReferenceFrameA; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected SliderJoint() { - } - - /** - * Instantiate a SliderJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param rotA the local orientation of the connection to node A (not null, alias created) - * @param rotB the local orientation of the connection to node B (not null, alias created) - * @param useLinearReferenceFrameA true→use node A, false→use node - * B - */ - public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { - super(nodeA, nodeB, pivotA, pivotB); - this.rotA = rotA; - this.rotB = rotB; - this.useLinearReferenceFrameA = useLinearReferenceFrameA; - createJoint(); - } - - /** - * Instantiate a SliderJoint. To be effective, the joint must be added to a - * physics space. - * - * @param nodeA the 1st body connected by the joint (not null, alias - * created) - * @param nodeB the 2nd body connected by the joint (not null, alias - * created) - * @param pivotA the local offset of the connection point in node A (not - * null, alias created) - * @param pivotB the local offset of the connection point in node B (not - * null, alias created) - * @param useLinearReferenceFrameA true→use node A, false→use node - * B - */ - public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) { - super(nodeA, nodeB, pivotA, pivotB); - this.rotA = new Matrix3f(); - this.rotB = new Matrix3f(); - this.useLinearReferenceFrameA = useLinearReferenceFrameA; - createJoint(); - } - - /** - * Read the joint's lower limit for on-axis translation. - * - * @return the lower limit - */ - public float getLowerLinLimit() { - return getLowerLinLimit(objectId); - } - - private native float getLowerLinLimit(long objectId); - - /** - * Alter the joint's lower limit for on-axis translation. - * - * @param lowerLinLimit the desired lower limit (default=-1) - */ - public void setLowerLinLimit(float lowerLinLimit) { - setLowerLinLimit(objectId, lowerLinLimit); - } - - private native void setLowerLinLimit(long objectId, float value); - - /** - * Read the joint's upper limit for on-axis translation. - * - * @return the upper limit - */ - public float getUpperLinLimit() { - return getUpperLinLimit(objectId); - } - - private native float getUpperLinLimit(long objectId); - - /** - * Alter the joint's upper limit for on-axis translation. - * - * @param upperLinLimit the desired upper limit (default=1) - */ - public void setUpperLinLimit(float upperLinLimit) { - setUpperLinLimit(objectId, upperLinLimit); - } - - private native void setUpperLinLimit(long objectId, float value); - - /** - * Read the joint's lower limit for on-axis rotation. - * - * @return the lower limit angle (in radians) - */ - public float getLowerAngLimit() { - return getLowerAngLimit(objectId); - } - - private native float getLowerAngLimit(long objectId); - - /** - * Alter the joint's lower limit for on-axis rotation. - * - * @param lowerAngLimit the desired lower limit angle (in radians, - * default=0) - */ - public void setLowerAngLimit(float lowerAngLimit) { - setLowerAngLimit(objectId, lowerAngLimit); - } - - private native void setLowerAngLimit(long objectId, float value); - - /** - * Read the joint's upper limit for on-axis rotation. - * - * @return the upper limit angle (in radians) - */ - public float getUpperAngLimit() { - return getUpperAngLimit(objectId); - } - - private native float getUpperAngLimit(long objectId); - - /** - * Alter the joint's upper limit for on-axis rotation. - * - * @param upperAngLimit the desired upper limit angle (in radians, - * default=0) - */ - public void setUpperAngLimit(float upperAngLimit) { - setUpperAngLimit(objectId, upperAngLimit); - } - - private native void setUpperAngLimit(long objectId, float value); - - /** - * Read the joint's softness for on-axis translation between the limits. - * - * @return the softness - */ - public float getSoftnessDirLin() { - return getSoftnessDirLin(objectId); - } - - private native float getSoftnessDirLin(long objectId); - - /** - * Alter the joint's softness for on-axis translation between the limits. - * - * @param softnessDirLin the desired softness (default=1) - */ - public void setSoftnessDirLin(float softnessDirLin) { - setSoftnessDirLin(objectId, softnessDirLin); - } - - private native void setSoftnessDirLin(long objectId, float value); - - /** - * Read the joint's restitution for on-axis translation between the limits. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionDirLin() { - return getRestitutionDirLin(objectId); - } - - private native float getRestitutionDirLin(long objectId); - - /** - * Alter the joint's restitution for on-axis translation between the limits. - * - * @param restitutionDirLin the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionDirLin(float restitutionDirLin) { - setRestitutionDirLin(objectId, restitutionDirLin); - } - - private native void setRestitutionDirLin(long objectId, float value); - - /** - * Read the joint's damping for on-axis translation between the limits. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingDirLin() { - return getDampingDirLin(objectId); - } - - private native float getDampingDirLin(long objectId); - - /** - * Alter the joint's damping for on-axis translation between the limits. - * - * @param dampingDirLin the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=0) - */ - public void setDampingDirLin(float dampingDirLin) { - setDampingDirLin(objectId, dampingDirLin); - } - - private native void setDampingDirLin(long objectId, float value); - - /** - * Read the joint's softness for on-axis rotation between the limits. - * - * @return the softness - */ - public float getSoftnessDirAng() { - return getSoftnessDirAng(objectId); - } - - private native float getSoftnessDirAng(long objectId); - - /** - * Alter the joint's softness for on-axis rotation between the limits. - * - * @param softnessDirAng the desired softness (default=1) - */ - public void setSoftnessDirAng(float softnessDirAng) { - setSoftnessDirAng(objectId, softnessDirAng); - } - - private native void setSoftnessDirAng(long objectId, float value); - - /** - * Read the joint's restitution for on-axis rotation between the limits. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionDirAng() { - return getRestitutionDirAng(objectId); - } - - private native float getRestitutionDirAng(long objectId); - - /** - * Alter the joint's restitution for on-axis rotation between the limits. - * - * @param restitutionDirAng the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionDirAng(float restitutionDirAng) { - setRestitutionDirAng(objectId, restitutionDirAng); - } - - private native void setRestitutionDirAng(long objectId, float value); - - /** - * Read the joint's damping for on-axis rotation between the limits. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingDirAng() { - return getDampingDirAng(objectId); - } - - private native float getDampingDirAng(long objectId); - - /** - * Alter the joint's damping for on-axis rotation between the limits. - * - * @param dampingDirAng the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=0) - */ - public void setDampingDirAng(float dampingDirAng) { - setDampingDirAng(objectId, dampingDirAng); - } - - private native void setDampingDirAng(long objectId, float value); - - /** - * Read the joint's softness for on-axis translation hitting the limits. - * - * @return the softness - */ - public float getSoftnessLimLin() { - return getSoftnessLimLin(objectId); - } - - private native float getSoftnessLimLin(long objectId); - - /** - * Alter the joint's softness for on-axis translation hitting the limits. - * - * @param softnessLimLin the desired softness (default=1) - */ - public void setSoftnessLimLin(float softnessLimLin) { - setSoftnessLimLin(objectId, softnessLimLin); - } - - private native void setSoftnessLimLin(long objectId, float value); - - /** - * Read the joint's restitution for on-axis translation hitting the limits. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionLimLin() { - return getRestitutionLimLin(objectId); - } - - private native float getRestitutionLimLin(long objectId); - - /** - * Alter the joint's restitution for on-axis translation hitting the limits. - * - * @param restitutionLimLin the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionLimLin(float restitutionLimLin) { - setRestitutionLimLin(objectId, restitutionLimLin); - } - - private native void setRestitutionLimLin(long objectId, float value); - - /** - * Read the joint's damping for on-axis translation hitting the limits. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingLimLin() { - return getDampingLimLin(objectId); - } - - private native float getDampingLimLin(long objectId); - - /** - * Alter the joint's damping for on-axis translation hitting the limits. - * - * @param dampingLimLin the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=1) - */ - public void setDampingLimLin(float dampingLimLin) { - setDampingLimLin(objectId, dampingLimLin); - } - - private native void setDampingLimLin(long objectId, float value); - - /** - * Read the joint's softness for on-axis rotation hitting the limits. - * - * @return the softness - */ - public float getSoftnessLimAng() { - return getSoftnessLimAng(objectId); - } - - private native float getSoftnessLimAng(long objectId); - - /** - * Alter the joint's softness for on-axis rotation hitting the limits. - * - * @param softnessLimAng the desired softness (default=1) - */ - public void setSoftnessLimAng(float softnessLimAng) { - setSoftnessLimAng(objectId, softnessLimAng); - } - - private native void setSoftnessLimAng(long objectId, float value); - - /** - * Read the joint's restitution for on-axis rotation hitting the limits. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionLimAng() { - return getRestitutionLimAng(objectId); - } - - private native float getRestitutionLimAng(long objectId); - - /** - * Alter the joint's restitution for on-axis rotation hitting the limits. - * - * @param restitutionLimAng the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionLimAng(float restitutionLimAng) { - setRestitutionLimAng(objectId, restitutionLimAng); - } - - private native void setRestitutionLimAng(long objectId, float value); - - /** - * Read the joint's damping for on-axis rotation hitting the limits. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingLimAng() { - return getDampingLimAng(objectId); - } - - private native float getDampingLimAng(long objectId); - - /** - * Alter the joint's damping for on-axis rotation hitting the limits. - * - * @param dampingLimAng the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=1) - */ - public void setDampingLimAng(float dampingLimAng) { - setDampingLimAng(objectId, dampingLimAng); - } - - private native void setDampingLimAng(long objectId, float value); - - /** - * Read the joint's softness for off-axis translation. - * - * @return the softness - */ - public float getSoftnessOrthoLin() { - return getSoftnessOrthoLin(objectId); - } - - private native float getSoftnessOrthoLin(long objectId); - - /** - * Alter the joint's softness for off-axis translation. - * - * @param softnessOrthoLin the desired softness (default=1) - */ - public void setSoftnessOrthoLin(float softnessOrthoLin) { - setSoftnessOrthoLin(objectId, softnessOrthoLin); - } - - private native void setSoftnessOrthoLin(long objectId, float value); - - /** - * Read the joint's restitution for off-axis translation. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionOrthoLin() { - return getRestitutionOrthoLin(objectId); - } - - private native float getRestitutionOrthoLin(long objectId); - - /** - * Alter the joint's restitution for off-axis translation. - * - * @param restitutionOrthoLin the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionOrthoLin(float restitutionOrthoLin) { - setRestitutionOrthoLin(objectId, restitutionOrthoLin); - } - - private native void setRestitutionOrthoLin(long objectId, float value); - - /** - * Read the joint's damping for off-axis translation. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingOrthoLin() { - return getDampingOrthoLin(objectId); - } - - private native float getDampingOrthoLin(long objectId); - - /** - * Alter the joint's damping for off-axis translation. - * - * @param dampingOrthoLin the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=1) - */ - public void setDampingOrthoLin(float dampingOrthoLin) { - setDampingOrthoLin(objectId, dampingOrthoLin); - } - - private native void setDampingOrthoLin(long objectId, float value); - - /** - * Read the joint's softness for off-axis rotation. - * - * @return the softness - */ - public float getSoftnessOrthoAng() { - return getSoftnessOrthoAng(objectId); - } - - private native float getSoftnessOrthoAng(long objectId); - - /** - * Alter the joint's softness for off-axis rotation. - * - * @param softnessOrthoAng the desired softness (default=1) - */ - public void setSoftnessOrthoAng(float softnessOrthoAng) { - setSoftnessOrthoAng(objectId, softnessOrthoAng); - } - - private native void setSoftnessOrthoAng(long objectId, float value); - - /** - * Read the joint's restitution for off-axis rotation. - * - * @return the restitution (bounce) factor - */ - public float getRestitutionOrthoAng() { - return getRestitutionOrthoAng(objectId); - } - - private native float getRestitutionOrthoAng(long objectId); - - /** - * Alter the joint's restitution for off-axis rotation. - * - * @param restitutionOrthoAng the desired restitution (bounce) factor - * (default=0.7) - */ - public void setRestitutionOrthoAng(float restitutionOrthoAng) { - setRestitutionOrthoAng(objectId, restitutionOrthoAng); - } - - private native void setRestitutionOrthoAng(long objectId, float value); - - /** - * Read the joint's damping for off-axis rotation. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDampingOrthoAng() { - return getDampingOrthoAng(objectId); - } - - private native float getDampingOrthoAng(long objectId); - - /** - * Alter the joint's damping for off-axis rotation. - * - * @param dampingOrthoAng the desired viscous damping ratio (0→no - * damping, 1→critically damped, default=1) - */ - public void setDampingOrthoAng(float dampingOrthoAng) { - setDampingOrthoAng(objectId, dampingOrthoAng); - } - - private native void setDampingOrthoAng(long objectId, float value); - - /** - * Test whether the translation motor is powered. - * - * @return true if powered, otherwise false - */ - public boolean isPoweredLinMotor() { - return isPoweredLinMotor(objectId); - } - - private native boolean isPoweredLinMotor(long objectId); - - /** - * Alter whether the translation motor is powered. - * - * @param poweredLinMotor true to power the motor, false to de-power it - * (default=false) - */ - public void setPoweredLinMotor(boolean poweredLinMotor) { - setPoweredLinMotor(objectId, poweredLinMotor); - } - - private native void setPoweredLinMotor(long objectId, boolean value); - - /** - * Read the velocity target of the translation motor. - * - * @return the velocity target - */ - public float getTargetLinMotorVelocity() { - return getTargetLinMotorVelocity(objectId); - } - - private native float getTargetLinMotorVelocity(long objectId); - - /** - * Alter the velocity target of the translation motor. - * - * @param targetLinMotorVelocity the desired velocity target (default=0) - */ - public void setTargetLinMotorVelocity(float targetLinMotorVelocity) { - setTargetLinMotorVelocity(objectId, targetLinMotorVelocity); - } - - private native void setTargetLinMotorVelocity(long objectId, float value); - - /** - * Read the maximum force of the translation motor. - * - * @return the maximum force - */ - public float getMaxLinMotorForce() { - return getMaxLinMotorForce(objectId); - } - - private native float getMaxLinMotorForce(long objectId); - - /** - * Alter the maximum force of the translation motor. - * - * @param maxLinMotorForce the desired maximum force (default=0) - */ - public void setMaxLinMotorForce(float maxLinMotorForce) { - setMaxLinMotorForce(objectId, maxLinMotorForce); - } - - private native void setMaxLinMotorForce(long objectId, float value); - - /** - * Test whether the rotation motor is powered. - * - * @return true if powered, otherwise false - */ - public boolean isPoweredAngMotor() { - return isPoweredAngMotor(objectId); - } - - private native boolean isPoweredAngMotor(long objectId); - - /** - * Alter whether the rotation motor is powered. - * - * @param poweredAngMotor true to power the motor, false to de-power it - * (default=false) - */ - public void setPoweredAngMotor(boolean poweredAngMotor) { - setPoweredAngMotor(objectId, poweredAngMotor); - } - - private native void setPoweredAngMotor(long objectId, boolean value); - - /** - * Read the velocity target of the rotation motor. - * - * @return the velocity target (in radians per second) - */ - public float getTargetAngMotorVelocity() { - return getTargetAngMotorVelocity(objectId); - } - - private native float getTargetAngMotorVelocity(long objectId); - - /** - * Alter the velocity target of the rotation motor. - * - * @param targetAngMotorVelocity the desired velocity target (in radians per - * second, default=0) - */ - public void setTargetAngMotorVelocity(float targetAngMotorVelocity) { - setTargetAngMotorVelocity(objectId, targetAngMotorVelocity); - } - - private native void setTargetAngMotorVelocity(long objectId, float value); - - /** - * Read the maximum force of the rotation motor. - * - * @return the maximum force - */ - public float getMaxAngMotorForce() { - return getMaxAngMotorForce(objectId); - } - - private native float getMaxAngMotorForce(long objectId); - - /** - * Alter the maximum force of the rotation motor. - * - * @param maxAngMotorForce the desired maximum force (default=0) - */ - public void setMaxAngMotorForce(float maxAngMotorForce) { - setMaxAngMotorForce(objectId, maxAngMotorForce); - } - - private native void setMaxAngMotorForce(long objectId, float value); - - /** - * Serialize this joint, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule capsule = ex.getCapsule(this); - //TODO: standard values.. - capsule.write(getDampingDirAng(), "dampingDirAng", 0f); - capsule.write(getDampingDirLin(), "dampingDirLin", 0f); - capsule.write(getDampingLimAng(), "dampingLimAng", 0f); - capsule.write(getDampingLimLin(), "dampingLimLin", 0f); - capsule.write(getDampingOrthoAng(), "dampingOrthoAng", 0f); - capsule.write(getDampingOrthoLin(), "dampingOrthoLin", 0f); - capsule.write(getLowerAngLimit(), "lowerAngLimit", 0f); - capsule.write(getLowerLinLimit(), "lowerLinLimit", 0f); - capsule.write(getMaxAngMotorForce(), "maxAngMotorForce", 0f); - capsule.write(getMaxLinMotorForce(), "maxLinMotorForce", 0f); - capsule.write(isPoweredAngMotor(), "poweredAngMotor", false); - capsule.write(isPoweredLinMotor(), "poweredLinMotor", false); - capsule.write(getRestitutionDirAng(), "restitutionDirAng", 0f); - capsule.write(getRestitutionDirLin(), "restitutionDirLin", 0f); - capsule.write(getRestitutionLimAng(), "restitutionLimAng", 0f); - capsule.write(getRestitutionLimLin(), "restitutionLimLin", 0f); - capsule.write(getRestitutionOrthoAng(), "restitutionOrthoAng", 0f); - capsule.write(getRestitutionOrthoLin(), "restitutionOrthoLin", 0f); - - capsule.write(getSoftnessDirAng(), "softnessDirAng", 0f); - capsule.write(getSoftnessDirLin(), "softnessDirLin", 0f); - capsule.write(getSoftnessLimAng(), "softnessLimAng", 0f); - capsule.write(getSoftnessLimLin(), "softnessLimLin", 0f); - capsule.write(getSoftnessOrthoAng(), "softnessOrthoAng", 0f); - capsule.write(getSoftnessOrthoLin(), "softnessOrthoLin", 0f); - - capsule.write(getTargetAngMotorVelocity(), "targetAngMotorVelicoty", 0f); - capsule.write(getTargetLinMotorVelocity(), "targetLinMotorVelicoty", 0f); - - capsule.write(getUpperAngLimit(), "upperAngLimit", 0f); - capsule.write(getUpperLinLimit(), "upperLinLimit", 0f); - - capsule.write(useLinearReferenceFrameA, "useLinearReferenceFrameA", false); - } - - /** - * De-serialize this joint, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule capsule = im.getCapsule(this); - float dampingDirAng = capsule.readFloat("dampingDirAng", 0f); - float dampingDirLin = capsule.readFloat("dampingDirLin", 0f); - float dampingLimAng = capsule.readFloat("dampingLimAng", 0f); - float dampingLimLin = capsule.readFloat("dampingLimLin", 0f); - float dampingOrthoAng = capsule.readFloat("dampingOrthoAng", 0f); - float dampingOrthoLin = capsule.readFloat("dampingOrthoLin", 0f); - float lowerAngLimit = capsule.readFloat("lowerAngLimit", 0f); - float lowerLinLimit = capsule.readFloat("lowerLinLimit", 0f); - float maxAngMotorForce = capsule.readFloat("maxAngMotorForce", 0f); - float maxLinMotorForce = capsule.readFloat("maxLinMotorForce", 0f); - boolean poweredAngMotor = capsule.readBoolean("poweredAngMotor", false); - boolean poweredLinMotor = capsule.readBoolean("poweredLinMotor", false); - float restitutionDirAng = capsule.readFloat("restitutionDirAng", 0f); - float restitutionDirLin = capsule.readFloat("restitutionDirLin", 0f); - float restitutionLimAng = capsule.readFloat("restitutionLimAng", 0f); - float restitutionLimLin = capsule.readFloat("restitutionLimLin", 0f); - float restitutionOrthoAng = capsule.readFloat("restitutionOrthoAng", 0f); - float restitutionOrthoLin = capsule.readFloat("restitutionOrthoLin", 0f); - - float softnessDirAng = capsule.readFloat("softnessDirAng", 0f); - float softnessDirLin = capsule.readFloat("softnessDirLin", 0f); - float softnessLimAng = capsule.readFloat("softnessLimAng", 0f); - float softnessLimLin = capsule.readFloat("softnessLimLin", 0f); - float softnessOrthoAng = capsule.readFloat("softnessOrthoAng", 0f); - float softnessOrthoLin = capsule.readFloat("softnessOrthoLin", 0f); - - float targetAngMotorVelicoty = capsule.readFloat("targetAngMotorVelicoty", 0f); - float targetLinMotorVelicoty = capsule.readFloat("targetLinMotorVelicoty", 0f); - - float upperAngLimit = capsule.readFloat("upperAngLimit", 0f); - float upperLinLimit = capsule.readFloat("upperLinLimit", 0f); - - useLinearReferenceFrameA = capsule.readBoolean("useLinearReferenceFrameA", false); - - createJoint(); - - setDampingDirAng(dampingDirAng); - setDampingDirLin(dampingDirLin); - setDampingLimAng(dampingLimAng); - setDampingLimLin(dampingLimLin); - setDampingOrthoAng(dampingOrthoAng); - setDampingOrthoLin(dampingOrthoLin); - setLowerAngLimit(lowerAngLimit); - setLowerLinLimit(lowerLinLimit); - setMaxAngMotorForce(maxAngMotorForce); - setMaxLinMotorForce(maxLinMotorForce); - setPoweredAngMotor(poweredAngMotor); - setPoweredLinMotor(poweredLinMotor); - setRestitutionDirAng(restitutionDirAng); - setRestitutionDirLin(restitutionDirLin); - setRestitutionLimAng(restitutionLimAng); - setRestitutionLimLin(restitutionLimLin); - setRestitutionOrthoAng(restitutionOrthoAng); - setRestitutionOrthoLin(restitutionOrthoLin); - - setSoftnessDirAng(softnessDirAng); - setSoftnessDirLin(softnessDirLin); - setSoftnessLimAng(softnessLimAng); - setSoftnessLimLin(softnessLimLin); - setSoftnessOrthoAng(softnessOrthoAng); - setSoftnessOrthoLin(softnessOrthoLin); - - setTargetAngMotorVelocity(targetAngMotorVelicoty); - setTargetLinMotorVelocity(targetLinMotorVelicoty); - - setUpperAngLimit(upperAngLimit); - setUpperLinLimit(upperLinLimit); - } - - /** - * Instantiate the configured constraint in Bullet. - */ - protected void createJoint() { - objectId = createJoint(nodeA.getObjectId(), nodeB.getObjectId(), pivotA, rotA, pivotB, rotB, useLinearReferenceFrameA); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Joint {0}", Long.toHexString(objectId)); - // = new SliderConstraint(nodeA.getObjectId(), nodeB.getObjectId(), transA, transB, useLinearReferenceFrameA); - } - - private native long createJoint(long objectIdA, long objectIdB, Vector3f pivotA, Matrix3f rotA, Vector3f pivotB, Matrix3f rotB, boolean useLinearReferenceFrameA); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java deleted file mode 100644 index 88db32aba6..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/RotationalLimitMotor.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.joints.motors; - -/** - * A motor based on Bullet's btRotationalLimitMotor. Motors are used to drive - * joints. - * - * @author normenhansen - */ -public class RotationalLimitMotor { - - /** - * Unique identifier of the btRotationalLimitMotor. The constructor sets - * this to a non-zero value. - */ - private long motorId = 0; - - /** - * Instantiate a motor for the identified btRotationalLimitMotor. - * - * @param motor the unique identifier (not zero) - */ - public RotationalLimitMotor(long motor) { - this.motorId = motor; - } - - /** - * Read the id of the btRotationalLimitMotor. - * - * @return the identifier of the btRotationalLimitMotor (not zero) - */ - public long getMotor() { - return motorId; - } - - /** - * Read this motor's constraint lower limit. - * - * @return the limit value - */ - public float getLoLimit() { - return getLoLimit(motorId); - } - - private native float getLoLimit(long motorId); - - /** - * Alter this motor's constraint lower limit. - * - * @param loLimit the desired limit value - */ - public void setLoLimit(float loLimit) { - setLoLimit(motorId, loLimit); - } - - private native void setLoLimit(long motorId, float loLimit); - - /** - * Read this motor's constraint upper limit. - * - * @return the limit value - */ - public float getHiLimit() { - return getHiLimit(motorId); - } - - private native float getHiLimit(long motorId); - - /** - * Alter this motor's constraint upper limit. - * - * @param hiLimit the desired limit value - */ - public void setHiLimit(float hiLimit) { - setHiLimit(motorId, hiLimit); - } - - private native void setHiLimit(long motorId, float hiLimit); - - /** - * Read this motor's target velocity. - * - * @return the target velocity (in radians per second) - */ - public float getTargetVelocity() { - return getTargetVelocity(motorId); - } - - private native float getTargetVelocity(long motorId); - - /** - * Alter this motor's target velocity. - * - * @param targetVelocity the desired target velocity (in radians per second) - */ - public void setTargetVelocity(float targetVelocity) { - setTargetVelocity(motorId, targetVelocity); - } - - private native void setTargetVelocity(long motorId, float targetVelocity); - - /** - * Read this motor's maximum force. - * - * @return the maximum force - */ - public float getMaxMotorForce() { - return getMaxMotorForce(motorId); - } - - private native float getMaxMotorForce(long motorId); - - /** - * Alter this motor's maximum force. - * - * @param maxMotorForce the desired maximum force on the motor - */ - public void setMaxMotorForce(float maxMotorForce) { - setMaxMotorForce(motorId, maxMotorForce); - } - - private native void setMaxMotorForce(long motorId, float maxMotorForce); - - /** - * Read the limit's maximum force. - * - * @return the maximum force on the limit - */ - public float getMaxLimitForce() { - return getMaxLimitForce(motorId); - } - - private native float getMaxLimitForce(long motorId); - - /** - * Alter the limit's maximum force. - * - * @param maxLimitForce the desired maximum force on the limit - */ - public void setMaxLimitForce(float maxLimitForce) { - setMaxLimitForce(motorId, maxLimitForce); - } - - private native void setMaxLimitForce(long motorId, float maxLimitForce); - - /** - * Read this motor's damping. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDamping() { - return getDamping(motorId); - } - - private native float getDamping(long motorId); - - /** - * Alter this motor's damping. - * - * @param damping the desired viscous damping ratio (0→no damping, - * 1→critically damped, default=1) - */ - public void setDamping(float damping) { - setDamping(motorId, damping); - } - - private native void setDamping(long motorId, float damping); - - /** - * Read this motor's limit softness. - * - * @return the limit softness - */ - public float getLimitSoftness() { - return getLimitSoftness(motorId); - } - - private native float getLimitSoftness(long motorId); - - /** - * Alter this motor's limit softness. - * - * @param limitSoftness the desired limit softness - */ - public void setLimitSoftness(float limitSoftness) { - setLimitSoftness(motorId, limitSoftness); - } - - private native void setLimitSoftness(long motorId, float limitSoftness); - - /** - * Read this motor's error tolerance at limits. - * - * @return the error tolerance (>0) - */ - public float getERP() { - return getERP(motorId); - } - - private native float getERP(long motorId); - - /** - * Alter this motor's error tolerance at limits. - * - * @param ERP the desired error tolerance (>0) - */ - public void setERP(float ERP) { - setERP(motorId, ERP); - } - - private native void setERP(long motorId, float ERP); - - /** - * Read this motor's bounce. - * - * @return the bounce (restitution factor) - */ - public float getBounce() { - return getBounce(motorId); - } - - private native float getBounce(long motorId); - - /** - * Alter this motor's bounce. - * - * @param bounce the desired bounce (restitution factor) - */ - public void setBounce(float bounce) { - setBounce(motorId, bounce); - } - - private native void setBounce(long motorId, float limitSoftness); - - /** - * Test whether this motor is enabled. - * - * @return true if enabled, otherwise false - */ - public boolean isEnableMotor() { - return isEnableMotor(motorId); - } - - private native boolean isEnableMotor(long motorId); - - /** - * Enable or disable this motor. - * - * @param enableMotor true→enable, false→disable - */ - public void setEnableMotor(boolean enableMotor) { - setEnableMotor(motorId, enableMotor); - } - - private native void setEnableMotor(long motorId, boolean enableMotor); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java b/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java deleted file mode 100644 index 4fbf0cb896..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/joints/motors/TranslationalLimitMotor.java +++ /dev/null @@ -1,233 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.joints.motors; - -import com.jme3.math.Vector3f; - -/** - * A motor based on Bullet's btTranslationalLimitMotor. Motors are used to drive - * joints. - * - * @author normenhansen - */ -public class TranslationalLimitMotor { - - /** - * Unique identifier of the btTranslationalLimitMotor. The constructor sets - * this to a non-zero value. After that, the id never changes. - */ - private long motorId = 0; - - /** - * Instantiate a motor for the identified btTranslationalLimitMotor. - * - * @param motor the unique identifier (not zero) - */ - public TranslationalLimitMotor(long motor) { - this.motorId = motor; - } - - /** - * Read the id of the btTranslationalLimitMotor. - * - * @return the unique identifier (not zero) - */ - public long getMotor() { - return motorId; - } - - /** - * Copy this motor's constraint lower limits. - * - * @return a new vector (not null) - */ - public Vector3f getLowerLimit() { - Vector3f vec = new Vector3f(); - getLowerLimit(motorId, vec); - return vec; - } - - private native void getLowerLimit(long motorId, Vector3f vector); - - /** - * Alter the constraint lower limits. - * - * @param lowerLimit (unaffected, not null) - */ - public void setLowerLimit(Vector3f lowerLimit) { - setLowerLimit(motorId, lowerLimit); - } - - private native void setLowerLimit(long motorId, Vector3f vector); - - /** - * Copy this motor's constraint upper limits. - * - * @return a new vector (not null) - */ - public Vector3f getUpperLimit() { - Vector3f vec = new Vector3f(); - getUpperLimit(motorId, vec); - return vec; - } - - private native void getUpperLimit(long motorId, Vector3f vector); - - /** - * Alter the constraint upper limits. - * - * @param upperLimit (unaffected, not null) - */ - public void setUpperLimit(Vector3f upperLimit) { - setUpperLimit(motorId, upperLimit); - } - - private native void setUpperLimit(long motorId, Vector3f vector); - - /** - * Copy the accumulated impulse. - * - * @return a new vector (not null) - */ - public Vector3f getAccumulatedImpulse() { - Vector3f vec = new Vector3f(); - getAccumulatedImpulse(motorId, vec); - return vec; - } - - private native void getAccumulatedImpulse(long motorId, Vector3f vector); - - /** - * Alter the accumulated impulse. - * - * @param accumulatedImpulse the desired vector (not null, unaffected) - */ - public void setAccumulatedImpulse(Vector3f accumulatedImpulse) { - setAccumulatedImpulse(motorId, accumulatedImpulse); - } - - private native void setAccumulatedImpulse(long motorId, Vector3f vector); - - /** - * Read this motor's limit softness. - * - * @return the softness - */ - public float getLimitSoftness() { - return getLimitSoftness(motorId); - } - - private native float getLimitSoftness(long motorId); - - /** - * Alter the limit softness. - * - * @param limitSoftness the desired limit softness (default=0.5) - */ - public void setLimitSoftness(float limitSoftness) { - setLimitSoftness(motorId, limitSoftness); - } - - private native void setLimitSoftness(long motorId, float limitSoftness); - - /** - * Read this motor's damping. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getDamping() { - return getDamping(motorId); - } - - private native float getDamping(long motorId); - - /** - * Alter this motor's damping. - * - * @param damping the desired viscous damping ratio (0→no damping, - * 1→critically damped, default=1) - */ - public void setDamping(float damping) { - setDamping(motorId, damping); - } - - private native void setDamping(long motorId, float damping); - - /** - * Read this motor's restitution. - * - * @return the restitution (bounce) factor - */ - public float getRestitution() { - return getRestitution(motorId); - } - - private native float getRestitution(long motorId); - - /** - * Alter this motor's restitution. - * - * @param restitution the desired restitution (bounce) factor - */ - public void setRestitution(float restitution) { - setRestitution(motorId, restitution); - } - - private native void setRestitution(long motorId, float restitution); - - /** - * Enable or disable the indexed axis. - * - * @param axisIndex which axis: 0→X, 1→Y, 2→Z - * @param enableMotor true→enable, false→disable (default=false) - */ - public void setEnabled(int axisIndex, boolean enableMotor) { - setEnabled(motorId, axisIndex, enableMotor); - } - - native private void setEnabled(long motorId, int axisIndex, - boolean enableMotor); - - /** - * Test whether the indexed axis is enabled. - * - * @param axisIndex which axis: 0→X, 1→Y, 2→Z - * @return true if enabled, otherwise false - */ - public boolean isEnabled(int axisIndex) { - boolean result = isEnabled(motorId, axisIndex); - return result; - } - - native private boolean isEnabled(long motorId, int axisIndex); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java deleted file mode 100644 index 0341ecbe8a..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java +++ /dev/null @@ -1,710 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.objects; - -import com.jme3.bullet.collision.CollisionFlag; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A collision object for simplified character simulation, based on Bullet's - * btKinematicCharacterController. - * - * @author normenhansen - */ -public class PhysicsCharacter extends PhysicsCollisionObject { - /** - * Unique identifier of btKinematicCharacterController (as opposed to its - * collision object, which is a ghost). Constructors are responsible for - * setting this to a non-zero value. The id might change if the character - * gets rebuilt. - */ - protected long characterId = 0; - protected float stepHeight; - protected Vector3f walkDirection = new Vector3f(); - protected float fallSpeed = 55.0f; - protected float jumpSpeed = 10.0f; - protected int upAxis = 1; - protected boolean locationDirty = false; - //TEMP VARIABLES - protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PhysicsCharacter() { - } - - /** - * Instantiate a character with the specified collision shape and step - * height. - * - * @param shape the desired shape (not null, alias created) - * @param stepHeight the quantization size for vertical movement - */ - public PhysicsCharacter(CollisionShape shape, float stepHeight) { - this.collisionShape = shape; -// if (shape instanceof MeshCollisionShape || shape instanceof CompoundCollisionShape) { -// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh or compound collision shapes")); -// } - this.stepHeight = stepHeight; - buildObject(); - /* - * The default gravity for a Bullet btKinematicCharacterController - * is (0,0,-29.4), which makes no sense for JME. - * So override the default. - */ - setGravity(new Vector3f(0f, -29.4f, 0f)); - } - - /** - * Create the configured character in Bullet. - */ - protected void buildObject() { - if (objectId == 0) { - objectId = createGhostObject(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Creating GhostObject {0}", Long.toHexString(objectId)); - initUserPointer(); - } - setCharacterFlags(objectId); - attachCollisionShape(objectId, collisionShape.getObjectId()); - if (characterId != 0) { - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing Character {0}", Long.toHexString(objectId)); - finalizeNativeCharacter(characterId); - } - characterId = createCharacterObject(objectId, collisionShape.getObjectId(), stepHeight); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Creating Character {0}", Long.toHexString(characterId)); - } - - private native long createGhostObject(); - - private native void setCharacterFlags(long objectId); - - private native long createCharacterObject(long objectId, long shapeId, float stepHeight); - - /** - * Directly alter the location of this character's center of mass. - * - * @param location the desired physics location (not null, unaffected) - */ - public void warp(Vector3f location) { - warp(characterId, location); - } - - private native void warp(long characterId, Vector3f location); - - /** - * Alter the walk offset. The offset will continue to be applied until - * altered again. - * - * @param vec the desired position increment for each physics tick (not - * null, unaffected) - */ - public void setWalkDirection(Vector3f vec) { - walkDirection.set(vec); - setWalkDirection(characterId, vec); - } - - private native void setWalkDirection(long characterId, Vector3f vec); - - /** - * Access the walk offset. - * - * @return the pre-existing instance - */ - public Vector3f getWalkDirection() { - return walkDirection; - } - - /** - * @deprecated Deprecated in bullet 2.86.1 use setUp(Vector3f) instead - * @param axis which axis: 0→X, 1→Y, 2→Z - */ - @Deprecated - public void setUpAxis(int axis) { - if(axis<0) axis=0; - else if(axis>2) axis=2; - switch(axis){ - case 0: - setUp(Vector3f.UNIT_X); - break; - case 1: - setUp(Vector3f.UNIT_Y); - break; - case 2: - setUp(Vector3f.UNIT_Z); - } - } - - /** - * Alter this character's "up" direction. - * - * @param axis the desired direction (not null, not zero, unaffected) - */ - public void setUp(Vector3f axis) { - setUp(characterId, axis); - } - - - private native void setUp(long characterId, Vector3f axis); - - /** - * Alter this character's angular velocity. - * - * @param v the desired angular velocity vector (not null, unaffected) - */ - - public void setAngularVelocity(Vector3f v){ - setAngularVelocity(characterId,v); - } - - private native void setAngularVelocity(long characterId, Vector3f v); - - /** - * Copy this character's angular velocity. - * - * @param out storage for the result (modified if not null) - * @return the velocity vector (either the provided storage or a new vector, - * not null) - */ - public Vector3f getAngularVelocity(Vector3f out){ - if(out==null)out=new Vector3f(); - getAngularVelocity(characterId,out); - return out; - } - - private native void getAngularVelocity(long characterId, Vector3f out); - - - /** - * Alter the linear velocity of this character's center of mass. - * - * @param v the desired velocity vector (not null) - */ - public void setLinearVelocity(Vector3f v){ - setLinearVelocity(characterId,v); - } - - private native void setLinearVelocity(long characterId, Vector3f v); - - /** - * Copy the linear velocity of this character's center of mass. - * - * @param out storage for the result (modified if not null) - * @return a vector (either the provided storage or a new vector, not null) - */ - public Vector3f getLinearVelocity(Vector3f out){ - if(out==null)out=new Vector3f(); - getLinearVelocity(characterId,out); - return out; - } - - private native void getLinearVelocity(long characterId, Vector3f out); - - - /** - * Read the index of the "up" axis. - * - * @return which axis: 0→X, 1→Y, 2→Z - */ - public int getUpAxis() { - return upAxis; - } - - /** - * Alter this character's fall speed. - * - * @param fallSpeed the desired speed (default=55) - */ - public void setFallSpeed(float fallSpeed) { - this.fallSpeed = fallSpeed; - setFallSpeed(characterId, fallSpeed); - } - - private native void setFallSpeed(long characterId, float fallSpeed); - - /** - * Read this character's fall speed. - * - * @return speed - */ - public float getFallSpeed() { - return fallSpeed; - } - - /** - * Alter this character's jump speed. - * - * @param jumpSpeed the desired speed (default=10) - */ - public void setJumpSpeed(float jumpSpeed) { - this.jumpSpeed = jumpSpeed; - setJumpSpeed(characterId, jumpSpeed); - } - - private native void setJumpSpeed(long characterId, float jumpSpeed); - - /** - * Read this character's jump speed. - * - * @return speed - */ - public float getJumpSpeed() { - return jumpSpeed; - } - - /** - * @deprecated Deprecated in bullet 2.86.1. Use setGravity(Vector3f) - * instead. - * @param value the desired downward (-Y) component of the acceleration - * (typically positive) - */ - @Deprecated - public void setGravity(float value) { - setGravity(new Vector3f(0, -value, 0)); - } - - /** - * Alter this character's gravitational acceleration. - * - * @param value the desired acceleration vector (not null, unaffected) - */ - public void setGravity(Vector3f value) { - setGravity(characterId, value); - } - - private native void setGravity(long characterId, Vector3f gravity); - - /** - * @deprecated Deprecated in bullet 2.86.1. Use getGravity(Vector3f) - * instead. - * @return the downward (-Y) component of the acceleration (typically - * positive) - */ - @Deprecated - public float getGravity() { - return -getGravity(null).y; - } - - /** - * Copy this character's gravitational acceleration. - * - * @param out storage for the result (modified if not null) - * @return the acceleration vector (either the provided storage or a new - * vector, not null) - */ - public Vector3f getGravity(Vector3f out) { - if(out==null)out=new Vector3f(); - getGravity(characterId,out); - return out; - } - - private native void getGravity(long characterId,Vector3f out); - - - /** - * Read this character's linear damping. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getLinearDamping(){ - return getLinearDamping(characterId); - } - - private native float getLinearDamping(long characterId); - - /** - * Alter this character's linear damping. - * - * @param v the desired viscous damping ratio (0→no damping, - * 1→critically damped) - */ - - public void setLinearDamping(float v ){ - setLinearDamping(characterId,v ); - } - - private native void setLinearDamping(long characterId,float v); - - - /** - * Read this character's angular damping. - * - * @return the viscous damping ratio (0→no damping, 1→critically - * damped) - */ - public float getAngularDamping(){ - return getAngularDamping(characterId); - } - - private native float getAngularDamping(long characterId); - - /** - * Alter this character's angular damping. - * - * @param v the desired viscous damping ratio (0→no damping, - * 1→critically damped, default=0) - */ - public void setAngularDamping(float v ){ - setAngularDamping(characterId,v ); - } - - private native void setAngularDamping(long characterId,float v); - - - /** - * Read this character's step height. - * - * @return the height (in physics-space units) - */ - public float getStepHeight(){ - return getStepHeight(characterId); - } - - private native float getStepHeight(long characterId); - - /** - * Alter this character's step height. - * - * @param v the desired height (in physics-space units) - */ - public void setStepHeight(float v ){ - setStepHeight(characterId,v ); - } - - private native void setStepHeight(long characterId,float v); - - - /** - * Read this character's maximum penetration depth. - * - * @return the depth (in physics-space units) - */ - public float getMaxPenetrationDepth(){ - return getMaxPenetrationDepth(characterId); - } - - private native float getMaxPenetrationDepth(long characterId); - - /** - * Alter this character's maximum penetration depth. - * - * @param v the desired depth (in physics-space units) - */ - public void setMaxPenetrationDepth(float v ){ - setMaxPenetrationDepth(characterId,v ); - } - - private native void setMaxPenetrationDepth(long characterId,float v); - - - - - - /** - * Alter this character's maximum slope angle. - * - * @param slopeRadians the desired angle (in radians) - */ - public void setMaxSlope(float slopeRadians) { - setMaxSlope(characterId, slopeRadians); - } - - private native void setMaxSlope(long characterId, float slopeRadians); - - /** - * Read this character's maximum slope angle. - * - * @return the angle (in radians) - */ - public float getMaxSlope() { - return getMaxSlope(characterId); - } - - private native float getMaxSlope(long characterId); - - /** - * Enable/disable this character's contact response. - * - * @param responsive true to respond to contacts, false to ignore them - * (default=true) - */ - public void setContactResponse(boolean responsive) { - int flags = getCollisionFlags(objectId); - if (responsive) { - flags &= ~CollisionFlag.NO_CONTACT_RESPONSE; - } else { - flags |= CollisionFlag.NO_CONTACT_RESPONSE; - } - setCollisionFlags(objectId, flags); - } - - /** - * Test whether this character is on the ground. - * - * @return true if on the ground, otherwise false - */ - public boolean onGround() { - return onGround(characterId); - } - - private native boolean onGround(long characterId); - - /** - * @deprecated Deprecated in bullet 2.86.1. Use jump(Vector3f) instead. - */ - @Deprecated - public void jump() { - jump(Vector3f.ZERO); - /* - * The zero vector is treated as a special case - * by Bullet's btKinematicCharacterController::jump(), - * causing the character to jump in its "up" direction - * with the pre-set speed. - */ - } - - /** - * Jump in the specified direction. - * - * @param dir desired jump direction (not null, unaffected) - */ - public void jump(Vector3f dir) { - jump(characterId,dir); - } - - private native void jump(long characterId,Vector3f v); - - /** - * Apply the specified CollisionShape to this character. Note that the - * character should not be in any physics space while changing shape; the - * character gets rebuilt on the physics side. - * - * @param collisionShape the shape to apply (not null, alias created) - */ - @Override - public void setCollisionShape(CollisionShape collisionShape) { -// if (!(collisionShape.getObjectId() instanceof ConvexShape)) { -// throw (new UnsupportedOperationException("Kinematic character nodes cannot have mesh collision shapes")); -// } - super.setCollisionShape(collisionShape); - if (objectId == 0) { - buildObject(); - } else { - attachCollisionShape(objectId, collisionShape.getObjectId()); - } - } - - /** - * Directly alter this character's location. (Same as - * {@link #warp(com.jme3.math.Vector3f)}).) - * - * @param location the desired location (not null, unaffected) - */ - public void setPhysicsLocation(Vector3f location) { - warp(location); - } - - /** - * Copy the location of this character's center of mass. - * - * @param trans storage for the result (modified if not null) - * @return the location vector (either the provided storage or a new vector, - * not null) - */ - public Vector3f getPhysicsLocation(Vector3f trans) { - if (trans == null) { - trans = new Vector3f(); - } - getPhysicsLocation(objectId, trans); - return trans; - } - - private native void getPhysicsLocation(long objectId, Vector3f vec); - - /** - * Copy the location of this character's center of mass. - * - * @return a new location vector (not null) - */ - public Vector3f getPhysicsLocation() { - return getPhysicsLocation(null); - } - - /** - * Alter this character's continuous collision detection (CCD) swept sphere - * radius. - * - * @param radius (≥0, default=0) - */ - public void setCcdSweptSphereRadius(float radius) { - setCcdSweptSphereRadius(objectId, radius); - } - - private native void setCcdSweptSphereRadius(long objectId, float radius); - - /** - * Alter the amount of motion required to activate continuous collision - * detection (CCD). - *

    - * This addresses the issue of fast objects passing through other objects - * with no collision detected. - * - * @param threshold the desired threshold velocity (>0) or zero to - * disable CCD (default=0) - */ - public void setCcdMotionThreshold(float threshold) { - setCcdMotionThreshold(objectId, threshold); - } - - private native void setCcdMotionThreshold(long objectId, float threshold); - - /** - * Read the radius of the sphere used for continuous collision detection - * (CCD). - * - * @return radius (≥0) - */ - public float getCcdSweptSphereRadius() { - return getCcdSweptSphereRadius(objectId); - } - - private native float getCcdSweptSphereRadius(long objectId); - - /** - * Calculate this character's continuous collision detection (CCD) motion - * threshold. - * - * @return the threshold velocity (≥0) - */ - public float getCcdMotionThreshold() { - return getCcdMotionThreshold(objectId); - } - - private native float getCcdMotionThreshold(long objectId); - - /** - * Calculate the square of this character's continuous collision detection - * (CCD) motion threshold. - * - * @return the threshold velocity squared (≥0) - */ - public float getCcdSquareMotionThreshold() { - return getCcdSquareMotionThreshold(objectId); - } - - private native float getCcdSquareMotionThreshold(long objectId); - - /** - * used internally - * - * @return the Bullet id - */ - public long getControllerId() { - return characterId; - } - - /** - * Has no effect. - */ - public void destroy() { - } - - /** - * Serialize this character, for example when saving to a J3O file. - * - * @param e exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter e) throws IOException { - super.write(e); - OutputCapsule capsule = e.getCapsule(this); - capsule.write(stepHeight, "stepHeight", 1.0f); - capsule.write(getGravity(), "gravity", 9.8f * 3); - capsule.write(getMaxSlope(), "maxSlope", 1.0f); - capsule.write(fallSpeed, "fallSpeed", 55.0f); - capsule.write(jumpSpeed, "jumpSpeed", 10.0f); - capsule.write(upAxis, "upAxis", 1); - capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); - capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); - capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); - } - - /** - * De-serialize this character from the specified importer, for example when - * loading from a J3O file. - * - * @param e importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); - stepHeight = capsule.readFloat("stepHeight", 1.0f); - buildObject(); - setGravity(capsule.readFloat("gravity", 9.8f * 3)); - setMaxSlope(capsule.readFloat("maxSlope", 1.0f)); - setFallSpeed(capsule.readFloat("fallSpeed", 55.0f)); - setJumpSpeed(capsule.readFloat("jumpSpeed", 10.0f)); - setUpAxis(capsule.readInt("upAxis", 1)); - setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); - setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); - setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); - } - - /** - * Finalize this physics character just before it is destroyed. Should be - * invoked only by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - finalizeNativeCharacter(characterId); - } - - private native void finalizeNativeCharacter(long characterId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java deleted file mode 100644 index f3efc7366d..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.objects; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A collision object for intangibles, based on Bullet's - * btPairCachingGhostObject. This is useful for creating a character controller, - * collision sensors/triggers, explosions etc. - *

    - * From Bullet manual:
    - * btGhostObject is a special btCollisionObject, useful for fast localized - * collision queries. - * - * @author normenhansen - */ -public class PhysicsGhostObject extends PhysicsCollisionObject { - - protected boolean locationDirty = false; - protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); - private List overlappingObjects = new LinkedList(); - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PhysicsGhostObject() { - } - - /** - * Instantiate an object with the specified collision shape. - * - * @param shape the desired shape (not null, alias created) - */ - public PhysicsGhostObject(CollisionShape shape) { - collisionShape = shape; - buildObject(); - } - - public PhysicsGhostObject(Spatial child, CollisionShape shape) { - collisionShape = shape; - buildObject(); - } - - /** - * Create the configured object in Bullet. - */ - protected void buildObject() { - if (objectId == 0) { -// gObject = new PairCachingGhostObject(); - objectId = createGhostObject(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Ghost Object {0}", Long.toHexString(objectId)); - setGhostFlags(objectId); - initUserPointer(); - } -// if (gObject == null) { -// gObject = new PairCachingGhostObject(); -// gObject.setCollisionFlags(gObject.getCollisionFlags() | CollisionFlags.NO_CONTACT_RESPONSE); -// } - attachCollisionShape(objectId, collisionShape.getObjectId()); - } - - private native long createGhostObject(); - - private native void setGhostFlags(long objectId); - - /** - * Apply the specified CollisionShape to this object. Note that the object - * should not be in any physics space while changing shape; the object gets - * rebuilt on the physics side. - * - * @param collisionShape the shape to apply (not null, alias created) - */ - @Override - public void setCollisionShape(CollisionShape collisionShape) { - super.setCollisionShape(collisionShape); - if (objectId == 0) { - buildObject(); - } else { - attachCollisionShape(objectId, collisionShape.getObjectId()); - } - } - - /** - * Directly alter the location of this object's center. - * - * @param location the desired location (in physics-space coordinates, not - * null, unaffected) - */ - public void setPhysicsLocation(Vector3f location) { - setPhysicsLocation(objectId, location); - } - - private native void setPhysicsLocation(long objectId, Vector3f location); - - /** - * Directly alter this object's orientation. - * - * @param rotation the desired orientation (a rotation matrix in - * physics-space coordinates, not null, unaffected) - */ - public void setPhysicsRotation(Matrix3f rotation) { - setPhysicsRotation(objectId, rotation); - } - - private native void setPhysicsRotation(long objectId, Matrix3f rotation); - - /** - * Directly alter this object's orientation. - * - * @param rotation the desired orientation (quaternion, not null, - * unaffected) - */ - public void setPhysicsRotation(Quaternion rotation) { - setPhysicsRotation(objectId, rotation); - } - - private native void setPhysicsRotation(long objectId, Quaternion rotation); - - /** - * Copy the location of this object's center. - * - * @param trans storage for the result (modified if not null) - * @return a location vector (in physics-space coordinates, either - * the provided storage or a new vector, not null) - */ - public Vector3f getPhysicsLocation(Vector3f trans) { - if (trans == null) { - trans = new Vector3f(); - } - getPhysicsLocation(objectId, trans); - return trans; - } - - private native void getPhysicsLocation(long objectId, Vector3f vector); - - /** - * Copy this object's orientation to a quaternion. - * - * @param rot storage for the result (modified if not null) - * @return an orientation (in physics-space coordinates, either the provided - * storage or a new quaternion, not null) - */ - public Quaternion getPhysicsRotation(Quaternion rot) { - if (rot == null) { - rot = new Quaternion(); - } - getPhysicsRotation(objectId, rot); - return rot; - } - - private native void getPhysicsRotation(long objectId, Quaternion rot); - - /** - * Copy this object's orientation to a matrix. - * - * @param rot storage for the result (modified if not null) - * @return an orientation (in physics-space coordinates, either the provided - * storage or a new matrix, not null) - */ - public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) { - if (rot == null) { - rot = new Matrix3f(); - } - getPhysicsRotationMatrix(objectId, rot); - return rot; - } - - private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot); - - /** - * Copy the location of this object's center. - * - * @return a new location vector (not null) - */ - public Vector3f getPhysicsLocation() { - Vector3f vec = new Vector3f(); - getPhysicsLocation(objectId, vec); - return vec; - } - - /** - * Copy this object's orientation to a quaternion. - * - * @return a new quaternion (not null) - */ - public Quaternion getPhysicsRotation() { - Quaternion quat = new Quaternion(); - getPhysicsRotation(objectId, quat); - return quat; - } - - /** - * Copy this object's orientation to a matrix. - * - * @return a new matrix (not null) - */ - public Matrix3f getPhysicsRotationMatrix() { - Matrix3f mtx = new Matrix3f(); - getPhysicsRotationMatrix(objectId, mtx); - return mtx; - } - - /** - * used internally - */ -// public PairCachingGhostObject getObjectId() { -// return gObject; -// } - /** - * Has no effect. - */ - public void destroy() { - } - - /** - * Access a list of overlapping objects. - * - * @return an internal list which may get reused (not null) - */ - public List getOverlappingObjects() { - overlappingObjects.clear(); - getOverlappingObjects(objectId); -// for (com.bulletphysics.collision.dispatch.CollisionObject collObj : gObject.getOverlappingPairs()) { -// overlappingObjects.add((PhysicsCollisionObject) collObj.getUserPointer()); -// } - return overlappingObjects; - } - - protected native void getOverlappingObjects(long objectId); - - /** - * This method is invoked from native code. - * - * @param co the collision object to add - */ - private void addOverlappingObject_native(PhysicsCollisionObject co) { - overlappingObjects.add(co); - } - - /** - * Count how many collision objects this object overlaps. - * - * @return count (≥0) - */ - public int getOverlappingCount() { - return getOverlappingCount(objectId); - } - - private native int getOverlappingCount(long objectId); - - /** - * Access an overlapping collision object by its position in the list. - * - * @param index which list position (≥0, <count) - * @return the pre-existing object - */ - public PhysicsCollisionObject getOverlapping(int index) { - return overlappingObjects.get(index); - } - - /** - * Alter the continuous collision detection (CCD) swept sphere radius for - * this object. - * - * @param radius (≥0) - */ - public void setCcdSweptSphereRadius(float radius) { - setCcdSweptSphereRadius(objectId, radius); - } - - private native void setCcdSweptSphereRadius(long objectId, float radius); - - /** - * Alter the amount of motion required to trigger continuous collision - * detection (CCD). - *

    - * This addresses the issue of fast objects passing through other objects - * with no collision detected. - * - * @param threshold the desired threshold value (squared velocity, >0) or - * zero to disable CCD (default=0) - */ - public void setCcdMotionThreshold(float threshold) { - setCcdMotionThreshold(objectId, threshold); - } - - private native void setCcdMotionThreshold(long objectId, float threshold); - - /** - * Read the radius of the sphere used for continuous collision detection - * (CCD). - * - * @return radius (≥0) - */ - public float getCcdSweptSphereRadius() { - return getCcdSweptSphereRadius(objectId); - } - - private native float getCcdSweptSphereRadius(long objectId); - - /** - * Read the continuous collision detection (CCD) motion threshold for this - * object. - * - * @return threshold value (squared velocity, ≥0) - */ - public float getCcdMotionThreshold() { - return getCcdMotionThreshold(objectId); - } - - private native float getCcdMotionThreshold(long objectId); - - /** - * Read the CCD square motion threshold for this object. - * - * @return threshold value (≥0) - */ - public float getCcdSquareMotionThreshold() { - return getCcdSquareMotionThreshold(objectId); - } - - private native float getCcdSquareMotionThreshold(long objectId); - - /** - * Serialize this object, for example when saving to a J3O file. - * - * @param e exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter e) throws IOException { - super.write(e); - OutputCapsule capsule = e.getCapsule(this); - capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); - capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f()); - capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); - capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); - } - - /** - * De-serialize this object from the specified importer, for example when - * loading from a J3O file. - * - * @param e importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); - buildObject(); - setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); - setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()))); - setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); - setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java deleted file mode 100644 index a765fdbe05..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java +++ /dev/null @@ -1,1079 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.objects; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.CollisionFlag; -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.MeshCollisionShape; -import com.jme3.bullet.joints.PhysicsJoint; -import com.jme3.bullet.objects.infos.RigidBodyMotionState; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A collision object for a rigid body, based on Bullet's btRigidBody. - * - * @author normenhansen - */ -public class PhysicsRigidBody extends PhysicsCollisionObject { - /** - * motion state - */ - protected RigidBodyMotionState motionState = new RigidBodyMotionState(); - /** - * copy of mass (>0) of a dynamic body, or 0 for a static body - * (default=1) - */ - protected float mass = 1.0f; - /** - * copy of kinematic flag: true→set kinematic mode (spatial controls - * body), false→dynamic/static mode (body controls spatial) - * (default=false) - */ - protected boolean kinematic = false; - /** - * joint list - */ - protected ArrayList joints = new ArrayList(4); - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PhysicsRigidBody() { - } - - /** - * Instantiate a dynamic body with mass=1 and the specified collision shape. - * - * @param shape the desired shape (not null, alias created) - */ - public PhysicsRigidBody(CollisionShape shape) { - collisionShape = shape; - rebuildRigidBody(); - } - - /** - * Instantiate a body with the specified collision shape and mass. - * - * @param shape the desired shape (not null, alias created) - * @param mass if 0, a static body is created; otherwise a dynamic body is - * created (≥0) - */ - public PhysicsRigidBody(CollisionShape shape, float mass) { - collisionShape = shape; - this.mass = mass; - rebuildRigidBody(); - } - - /** - * Build/rebuild this body after parameters have changed. - */ - protected void rebuildRigidBody() { - boolean removed = false; - if (collisionShape instanceof MeshCollisionShape && mass != 0) { - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); - } - if (objectId != 0) { - if (isInWorld(objectId)) { - PhysicsSpace.getPhysicsSpace().remove(this); - removed = true; - } - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing RigidBody {0}", Long.toHexString(objectId)); - finalizeNative(objectId); - } - preRebuild(); - objectId = createRigidBody(mass, motionState.getObjectId(), collisionShape.getObjectId()); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created RigidBody {0}", Long.toHexString(objectId)); - postRebuild(); - if (removed) { - PhysicsSpace.getPhysicsSpace().add(this); - } - } - - /** - * For use by subclasses. - */ - protected void preRebuild() { - } - - private native long createRigidBody(float mass, long motionStateId, long collisionShapeId); - - /** - * For use by subclasses. - */ - protected void postRebuild() { - if (mass == 0.0f) { - setStatic(objectId, true); - } else { - setStatic(objectId, false); - } - initUserPointer(); - } - - /** - * Access this body's motion state. - * - * @return the pre-existing instance - */ - public RigidBodyMotionState getMotionState() { - return motionState; - } - - /** - * Test whether this body is in a physics space. - * - * @return true→in a space, false→not in a space - */ - public boolean isInWorld() { - return isInWorld(objectId); - } - - private native boolean isInWorld(long objectId); - - /** - * Directly alter the location of this body's center of mass. - * - * @param location the desired location (not null, unaffected) - */ - public void setPhysicsLocation(Vector3f location) { - setPhysicsLocation(objectId, location); - } - - private native void setPhysicsLocation(long objectId, Vector3f location); - - /** - * Directly alter this body's orientation. - * - * @param rotation the desired orientation (rotation matrix, not null, - * unaffected) - */ - public void setPhysicsRotation(Matrix3f rotation) { - setPhysicsRotation(objectId, rotation); - } - - private native void setPhysicsRotation(long objectId, Matrix3f rotation); - - /** - * Directly alter this body's orientation. - * - * @param rotation the desired orientation (quaternion, in physics-space - * coordinates, not null, unaffected) - */ - public void setPhysicsRotation(Quaternion rotation) { - setPhysicsRotation(objectId, rotation); - } - - private native void setPhysicsRotation(long objectId, Quaternion rotation); - - /** - * Copy the location of this body's center of mass. - * - * @param trans storage for the result (modified if not null) - * @return the location (in physics-space coordinates, either the provided - * storage or a new vector, not null) - */ - public Vector3f getPhysicsLocation(Vector3f trans) { - if (trans == null) { - trans = new Vector3f(); - } - getPhysicsLocation(objectId, trans); - return trans; - } - - private native void getPhysicsLocation(long objectId, Vector3f vector); - - /** - * Copy this body's orientation to a quaternion. - * - * @param rot storage for the result (modified if not null) - * @return the orientation (either the provided storage or a new quaternion, - * not null) - */ - public Quaternion getPhysicsRotation(Quaternion rot) { - if (rot == null) { - rot = new Quaternion(); - } - getPhysicsRotation(objectId, rot); - return rot; - } - - /** - * Alter the principal components of the local inertia tensor. - * - * @param gravity (not null, unaffected) - */ - public void setInverseInertiaLocal(Vector3f gravity) { - setInverseInertiaLocal(objectId, gravity); - } - private native void setInverseInertiaLocal(long objectId, Vector3f gravity); - - /** - * Copy the principal components of the local inverse inertia tensor. - * - * @param trans storage for the result (modified if not null) - * @return a vector (either the provided storage or a new vector, not null) - */ - public Vector3f getInverseInertiaLocal(Vector3f trans) { - if (trans == null) { - trans = new Vector3f(); - } - getInverseInertiaLocal(objectId, trans); - return trans; - } - - private native void getInverseInertiaLocal(long objectId, Vector3f vector); - - private native void getPhysicsRotation(long objectId, Quaternion rot); - - /** - * Copy this body's orientation to a matrix. - * - * @param rot storage for the result (modified if not null) - * @return the orientation (in physics-space coordinates, either the - * provided storage or a new matrix, not null) - */ - public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) { - if (rot == null) { - rot = new Matrix3f(); - } - getPhysicsRotationMatrix(objectId, rot); - return rot; - } - - private native void getPhysicsRotationMatrix(long objectId, Matrix3f rot); - - /** - * Copy the location of this body's center of mass. - * - * @return a new location vector (not null) - */ - public Vector3f getPhysicsLocation() { - Vector3f vec = new Vector3f(); - getPhysicsLocation(objectId, vec); - return vec; - } - - /** - * Copy this body's orientation to a quaternion. - * - * @return a new quaternion (not null) - */ - public Quaternion getPhysicsRotation() { - Quaternion quat = new Quaternion(); - getPhysicsRotation(objectId, quat); - return quat; - } - - /** - * Copy this body's orientation to a matrix. - * - * @return a new matrix (not null) - */ - public Matrix3f getPhysicsRotationMatrix() { - Matrix3f mtx = new Matrix3f(); - getPhysicsRotationMatrix(objectId, mtx); - return mtx; - } - -// /** -// * Gets the physics object location -// * @param location the location of the actual physics object is stored in this Vector3f -// */ -// public Vector3f getInterpolatedPhysicsLocation(Vector3f location) { -// if (location == null) { -// location = new Vector3f(); -// } -// rBody.getInterpolationWorldTransform(tempTrans); -// return Converter.convert(tempTrans.origin, location); -// } -// -// /** -// * Gets the physics object rotation -// * @param rotation the rotation of the actual physics object is stored in this Matrix3f -// */ -// public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) { -// if (rotation == null) { -// rotation = new Matrix3f(); -// } -// rBody.getInterpolationWorldTransform(tempTrans); -// return Converter.convert(tempTrans.basis, rotation); -// } - /** - * Put this body into kinematic mode or take it out of kinematic mode. - *

    - * In kinematic mode, the body is not influenced by physics but can affect - * other physics objects. Its kinetic force is calculated based on its - * movement and weight. - * - * @param kinematic true→set kinematic mode, false→set - * dynamic/static mode (default=false) - */ - public void setKinematic(boolean kinematic) { - this.kinematic = kinematic; - setKinematic(objectId, kinematic); - } - - private native void setKinematic(long objectId, boolean kinematic); - - /** - * Test whether this body is in kinematic mode. - *

    - * In kinematic mode, the body is not influenced by physics but can affect - * other physics objects. Its kinetic force is calculated based on its - * movement and weight. - * - * @return true if in kinematic mode, otherwise false (dynamic/static mode) - */ - public boolean isKinematic() { - return kinematic; - } - - /** - * Enable/disable this body's contact response. - * - * @param responsive true to respond to contacts, false to ignore them - * (default=true) - */ - public void setContactResponse(boolean responsive) { - int flags = getCollisionFlags(objectId); - if (responsive) { - flags &= ~CollisionFlag.NO_CONTACT_RESPONSE; - } else { - flags |= CollisionFlag.NO_CONTACT_RESPONSE; - } - setCollisionFlags(objectId, flags); - } - - /** - * Alter the radius of the swept sphere used for continuous collision - * detection (CCD). - * - * @param radius the desired radius (≥0, default=0) - */ - public void setCcdSweptSphereRadius(float radius) { - setCcdSweptSphereRadius(objectId, radius); - } - - private native void setCcdSweptSphereRadius(long objectId, float radius); - - /** - * Alter the amount of motion required to activate continuous collision - * detection (CCD). - *

    - * This addresses the issue of fast objects passing through other objects - * with no collision detected. - * - * @param threshold the desired threshold velocity (>0) or zero to - * disable CCD (default=0) - */ - public void setCcdMotionThreshold(float threshold) { - setCcdMotionThreshold(objectId, threshold); - } - - private native void setCcdMotionThreshold(long objectId, float threshold); - - /** - * Read the radius of the swept sphere used for continuous collision - * detection (CCD). - * - * @return radius (≥0) - */ - public float getCcdSweptSphereRadius() { - return getCcdSweptSphereRadius(objectId); - } - - private native float getCcdSweptSphereRadius(long objectId); - - /** - * Calculate this body's continuous collision detection (CCD) motion - * threshold. - * - * @return the threshold velocity (≥0) - */ - public float getCcdMotionThreshold() { - return getCcdMotionThreshold(objectId); - } - - private native float getCcdMotionThreshold(long objectId); - - /** - * Calculate the square of this body's continuous collision detection (CCD) - * motion threshold. - * - * @return the threshold velocity squared (≥0) - */ - public float getCcdSquareMotionThreshold() { - return getCcdSquareMotionThreshold(objectId); - } - - private native float getCcdSquareMotionThreshold(long objectId); - - /** - * Read this body's mass. - * - * @return the mass (>0) or zero for a static body - */ - public float getMass() { - return mass; - } - - /** - * Alter this body's mass. Bodies with mass=0 are static. For dynamic - * bodies, it is best to keep the mass around 1. - * - * @param mass the desired mass (>0) or 0 for a static body (default=1) - */ - public void setMass(float mass) { - this.mass = mass; - if (collisionShape instanceof MeshCollisionShape && mass != 0) { - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); - } - if (objectId != 0) { - if (collisionShape != null) { - updateMassProps(objectId, collisionShape.getObjectId(), mass); - } - if (mass == 0.0f) { - setStatic(objectId, true); - } else { - setStatic(objectId, false); - } - } - } - - private native void setStatic(long objectId, boolean state); - - private native long updateMassProps(long objectId, long collisionShapeId, float mass); - - /** - * Copy this body's gravitational acceleration. - * - * @return a new acceleration vector (in physics-space coordinates, not - * null) - */ - public Vector3f getGravity() { - return getGravity(null); - } - - /** - * Copy this body's gravitational acceleration. - * - * @param gravity storage for the result (modified if not null) - * @return an acceleration vector (in physics-space coordinates, either the - * provided storage or a new vector, not null) - */ - public Vector3f getGravity(Vector3f gravity) { - if (gravity == null) { - gravity = new Vector3f(); - } - getGravity(objectId, gravity); - return gravity; - } - - private native void getGravity(long objectId, Vector3f gravity); - - /** - * Alter this body's gravitational acceleration. - *

    - * Invoke this after adding the body to a PhysicsSpace. Adding a body to a - * PhysicsSpace alters its gravity. - * - * @param gravity the desired acceleration vector (not null, unaffected) - */ - public void setGravity(Vector3f gravity) { - setGravity(objectId, gravity); - } - private native void setGravity(long objectId, Vector3f gravity); - - /** - * Read this body's friction. - * - * @return friction value - */ - public float getFriction() { - return getFriction(objectId); - } - - private native float getFriction(long objectId); - - /** - * Alter this body's friction. - * - * @param friction the desired friction value (default=0.5) - */ - public void setFriction(float friction) { - setFriction(objectId, friction); - } - - private native void setFriction(long objectId, float friction); - - /** - * Alter this body's damping. - * - * @param linearDamping the desired linear damping value (default=0) - * @param angularDamping the desired angular damping value (default=0) - */ - public void setDamping(float linearDamping, float angularDamping) { - setDamping(objectId, linearDamping, angularDamping); - } - - private native void setDamping(long objectId, float linearDamping, float angularDamping); - -// private native void setRestitution(long objectId, float factor); -// -// public void setLinearDamping(float linearDamping) { -// constructionInfo.linearDamping = linearDamping; -// rBody.setDamping(linearDamping, constructionInfo.angularDamping); -// } -// -// private native void setRestitution(long objectId, float factor); -// - /** - * Alter this body's linear damping. - * - * @param linearDamping the desired linear damping value (default=0) - */ - public void setLinearDamping(float linearDamping) { - setDamping(objectId, linearDamping, getAngularDamping()); - } - - /** - * Alter this body's angular damping. - * - * @param angularDamping the desired angular damping value (default=0) - */ - public void setAngularDamping(float angularDamping) { - setAngularDamping(objectId, angularDamping); - } - private native void setAngularDamping(long objectId, float factor); - - /** - * Read this body's linear damping. - * - * @return damping value - */ - public float getLinearDamping() { - return getLinearDamping(objectId); - } - - private native float getLinearDamping(long objectId); - - /** - * Read this body's angular damping. - * - * @return damping value - */ - public float getAngularDamping() { - return getAngularDamping(objectId); - } - - private native float getAngularDamping(long objectId); - - /** - * Read this body's restitution (bounciness). - * - * @return restitution value - */ - public float getRestitution() { - return getRestitution(objectId); - } - - private native float getRestitution(long objectId); - - /** - * Alter this body's restitution (bounciness). For best performance, set - * restitution=0. - * - * @param restitution the desired value (default=0) - */ - public void setRestitution(float restitution) { - setRestitution(objectId, restitution); - } - - private native void setRestitution(long objectId, float factor); - - /** - * Copy this body's angular velocity. - * - * @return a new velocity vector (in physics-space coordinates, not null) - */ - public Vector3f getAngularVelocity() { - Vector3f vec = new Vector3f(); - getAngularVelocity(objectId, vec); - return vec; - } - - private native void getAngularVelocity(long objectId, Vector3f vec); - - /** - * Copy this body's angular velocity. - * - * @param vec storage for the result (in physics-space coordinates, not - * null, modified) - */ - public void getAngularVelocity(Vector3f vec) { - getAngularVelocity(objectId, vec); - } - - /** - * Alter this body's angular velocity. - * - * @param vec the desired angular velocity vector (not null, unaffected) - */ - public void setAngularVelocity(Vector3f vec) { - setAngularVelocity(objectId, vec); - activate(); - } - - private native void setAngularVelocity(long objectId, Vector3f vec); - - /** - * Copy the linear velocity of this body's center of mass. - * - * @return a new velocity vector (in physics-space coordinates, not null) - */ - public Vector3f getLinearVelocity() { - Vector3f vec = new Vector3f(); - getLinearVelocity(objectId, vec); - return vec; - } - - private native void getLinearVelocity(long objectId, Vector3f vec); - - /** - * Copy the linear velocity of this body's center of mass. - * - * @param vec storage for the result (in physics-space coordinates, not - * null, modified) - */ - public void getLinearVelocity(Vector3f vec) { - getLinearVelocity(objectId, vec); - } - - /** - * Alter the linear velocity of this body's center of mass. - * - * @param vec the desired velocity vector (not null) - */ - public void setLinearVelocity(Vector3f vec) { - setLinearVelocity(objectId, vec); - activate(); - } - - private native void setLinearVelocity(long objectId, Vector3f vec); - - /** - * Apply a force to the PhysicsRigidBody. Effective only if the next physics - * update steps the physics space. - *

    - * To apply an impulse, use applyImpulse, use applyContinuousForce to apply - * continuous force. - * - * @param force the force (not null, unaffected) - * @param location the location of the force - */ - public void applyForce(Vector3f force, Vector3f location) { - applyForce(objectId, force, location); - activate(); - } - - private native void applyForce(long objectId, Vector3f force, Vector3f location); - - /** - * Apply a force to the PhysicsRigidBody. Effective only if the next physics - * update steps the physics space. - *

    - * To apply an impulse, use - * {@link #applyImpulse(com.jme3.math.Vector3f, com.jme3.math.Vector3f)}. - * - * @param force the force (not null, unaffected) - */ - public void applyCentralForce(Vector3f force) { - applyCentralForce(objectId, force); - activate(); - } - - private native void applyCentralForce(long objectId, Vector3f force); - - /** - * Apply a force to the PhysicsRigidBody. Effective only if the next physics - * update steps the physics space. - *

    - * To apply an impulse, use - * {@link #applyImpulse(com.jme3.math.Vector3f, com.jme3.math.Vector3f)}. - * - * @param torque the torque (not null, unaffected) - */ - public void applyTorque(Vector3f torque) { - applyTorque(objectId, torque); - activate(); - } - - private native void applyTorque(long objectId, Vector3f vec); - - /** - * Apply an impulse to the body the next physics update. - * - * @param impulse applied impulse (not null, unaffected) - * @param rel_pos location relative to object (not null, unaffected) - */ - public void applyImpulse(Vector3f impulse, Vector3f rel_pos) { - applyImpulse(objectId, impulse, rel_pos); - activate(); - } - - private native void applyImpulse(long objectId, Vector3f impulse, Vector3f rel_pos); - - /** - * Apply a torque impulse to the body in the next physics update. - * - * @param vec the torque to apply - */ - public void applyTorqueImpulse(Vector3f vec) { - applyTorqueImpulse(objectId, vec); - activate(); - } - - private native void applyTorqueImpulse(long objectId, Vector3f vec); - - /** - * Clear all forces acting on this body. - */ - public void clearForces() { - clearForces(objectId); - } - - private native void clearForces(long objectId); - - /** - * Apply the specified CollisionShape to this body. - *

    - * Note that the body should not be in any physics space while changing - * shape; the body gets rebuilt on the physics side. - * - * @param collisionShape the shape to apply (not null, alias created) - */ - public void setCollisionShape(CollisionShape collisionShape) { - super.setCollisionShape(collisionShape); - if (collisionShape instanceof MeshCollisionShape && mass != 0) { - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); - } - if (objectId == 0) { - rebuildRigidBody(); - } else { - setCollisionShape(objectId, collisionShape.getObjectId()); - updateMassProps(objectId, collisionShape.getObjectId(), mass); - } - } - - private native void setCollisionShape(long objectId, long collisionShapeId); - - /** - * Reactivates this body if it has been deactivated due to lack of motion. - */ - public void activate() { - activate(objectId); - } - - private native void activate(long objectId); - - /** - * Test whether this body has been deactivated due to lack of motion. - * - * @return true if still active, false if deactivated - */ - public boolean isActive() { - return isActive(objectId); - } - - private native boolean isActive(long objectId); - - /** - * Alter this body's sleeping thresholds. - *

    - * These thresholds determine when the body can be deactivated to save - * resources. Low values keep the body active when it barely moves. - * - * @param linear the desired linear sleeping threshold (≥0, default=0.8) - * @param angular the desired angular sleeping threshold (≥0, default=1) - */ - public void setSleepingThresholds(float linear, float angular) { - setSleepingThresholds(objectId, linear, angular); - } - - private native void setSleepingThresholds(long objectId, float linear, float angular); - - /** - * Alter this body's linear sleeping threshold. - * - * @param linearSleepingThreshold the desired threshold (≥0, default=0.8) - */ - public void setLinearSleepingThreshold(float linearSleepingThreshold) { - setLinearSleepingThreshold(objectId, linearSleepingThreshold); - } - - private native void setLinearSleepingThreshold(long objectId, float linearSleepingThreshold); - - /** - * Alter this body's angular sleeping threshold. - * - * @param angularSleepingThreshold the desired threshold (≥0, default=1) - */ - public void setAngularSleepingThreshold(float angularSleepingThreshold) { - setAngularSleepingThreshold(objectId, angularSleepingThreshold); - } - - private native void setAngularSleepingThreshold(long objectId, float angularSleepingThreshold); - - /** - * Read this body's linear sleeping threshold. - * - * @return the linear sleeping threshold (≥0) - */ - public float getLinearSleepingThreshold() { - return getLinearSleepingThreshold(objectId); - } - - private native float getLinearSleepingThreshold(long objectId); - - /** - * Read this body's angular sleeping threshold. - * - * @return the angular sleeping threshold (≥0) - */ - public float getAngularSleepingThreshold() { - return getAngularSleepingThreshold(objectId); - } - - private native float getAngularSleepingThreshold(long objectId); - - /** - * Read this body's angular factor for the X axis. - * - * @return the angular factor - */ - public float getAngularFactor() { - return getAngularFactor(null).getX(); - } - - /** - * Copy this body's angular factors. - * - * @param store storage for the result (modified if not null) - * @return the angular factor for each axis (either the provided storage or - * new vector, not null) - */ - public Vector3f getAngularFactor(Vector3f store) { - // Done this way to prevent breaking the API. - if (store == null) { - store = new Vector3f(); - } - getAngularFactor(objectId, store); - return store; - } - - private native void getAngularFactor(long objectId, Vector3f vec); - - /** - * Alter this body's angular factor. - * - * @param factor the desired angular factor for all axes (not null, - * unaffected, default=1) - */ - public void setAngularFactor(float factor) { - setAngularFactor(objectId, new Vector3f(factor, factor, factor)); - } - - /** - * Alter this body's angular factors. - * - * @param factor the desired angular factor for each axis (not null, - * unaffected, default=(1,1,1)) - */ - public void setAngularFactor(Vector3f factor) { - setAngularFactor(objectId, factor); - } - - private native void setAngularFactor(long objectId, Vector3f factor); - - /** - * Copy this body's linear factors. - * - * @return the linear factor for each axis (not null) - */ - public Vector3f getLinearFactor() { - Vector3f vec = new Vector3f(); - getLinearFactor(objectId, vec); - return vec; - } - - private native void getLinearFactor(long objectId, Vector3f vec); - - /** - * Alter this body's linear factors. - * - * @param factor the desired linear factor for each axis (not null, - * unaffected, default=(1,1,1)) - */ - public void setLinearFactor(Vector3f factor) { - setLinearFactor(objectId, factor); - } - - private native void setLinearFactor(long objectId, Vector3f factor); - - - /** - * Do not invoke directly! Joints are added automatically when created. - * - * @param joint the joint to add (not null) - */ - public void addJoint(PhysicsJoint joint) { - if (!joints.contains(joint)) { - joints.add(joint); - } - } - - /** - * Do not invoke directly! Joints are removed automatically when destroyed. - * - * @param joint the joint to remove (not null) - */ - public void removeJoint(PhysicsJoint joint) { - joints.remove(joint); - } - - /** - * Access the list of joints connected with this body. - *

    - * This list is only filled when the PhysicsRigidBody is added to a physics - * space. - * - * @return the pre-existing list (not null) - */ - public List getJoints() { - return joints; - } - - /** - * Serialize this body, for example when saving to a J3O file. - * - * @param e exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter e) throws IOException { - super.write(e); - OutputCapsule capsule = e.getCapsule(this); - - capsule.write(getMass(), "mass", 1.0f); - - capsule.write(getGravity(), "gravity", Vector3f.ZERO); - capsule.write(isContactResponse(), "contactResponse", true); - capsule.write(getFriction(), "friction", 0.5f); - capsule.write(getRestitution(), "restitution", 0); - Vector3f angularFactor = getAngularFactor(null); - if (angularFactor.x == angularFactor.y && angularFactor.y == angularFactor.z) { - capsule.write(getAngularFactor(), "angularFactor", 1); - } else { - capsule.write(getAngularFactor(null), "angularFactor", Vector3f.UNIT_XYZ); - capsule.write(getLinearFactor(), "linearFactor", Vector3f.UNIT_XYZ); - } - capsule.write(kinematic, "kinematic", false); - - capsule.write(getLinearDamping(), "linearDamping", 0); - capsule.write(getAngularDamping(), "angularDamping", 0); - capsule.write(getLinearSleepingThreshold(), "linearSleepingThreshold", 0.8f); - capsule.write(getAngularSleepingThreshold(), "angularSleepingThreshold", 1.0f); - - capsule.write(getCcdMotionThreshold(), "ccdMotionThreshold", 0); - capsule.write(getCcdSweptSphereRadius(), "ccdSweptSphereRadius", 0); - - capsule.write(getPhysicsLocation(new Vector3f()), "physicsLocation", new Vector3f()); - capsule.write(getPhysicsRotationMatrix(new Matrix3f()), "physicsRotation", new Matrix3f()); - capsule.write(getLinearVelocity(), "linearVelocity", null); - capsule.write(getAngularVelocity(), "angularVelocity", null); - - capsule.writeSavableArrayList(joints, "joints", null); - } - - /** - * De-serialize this body, for example when loading from a J3O file. - * - * @param e importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - - InputCapsule capsule = e.getCapsule(this); - float mass = capsule.readFloat("mass", 1.0f); - this.mass = mass; - rebuildRigidBody(); - setGravity((Vector3f) capsule.readSavable("gravity", Vector3f.ZERO.clone())); - setContactResponse(capsule.readBoolean("contactResponse", true)); - setFriction(capsule.readFloat("friction", 0.5f)); - setKinematic(capsule.readBoolean("kinematic", false)); - - setRestitution(capsule.readFloat("restitution", 0)); - Vector3f angularFactor = (Vector3f) capsule.readSavable("angularFactor", null); - if(angularFactor == null) { - setAngularFactor(capsule.readFloat("angularFactor", 1)); - } else { - setAngularFactor(angularFactor); - setLinearFactor((Vector3f) capsule.readSavable("linearFactor", Vector3f.UNIT_XYZ.clone())); - } - setDamping(capsule.readFloat("linearDamping", 0), capsule.readFloat("angularDamping", 0)); - setSleepingThresholds(capsule.readFloat("linearSleepingThreshold", 0.8f), capsule.readFloat("angularSleepingThreshold", 1.0f)); - setCcdMotionThreshold(capsule.readFloat("ccdMotionThreshold", 0)); - setCcdSweptSphereRadius(capsule.readFloat("ccdSweptSphereRadius", 0)); - - setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); - setPhysicsRotation((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f())); - setLinearVelocity((Vector3f) capsule.readSavable("linearVelocity", new Vector3f())); - setAngularVelocity((Vector3f) capsule.readSavable("angularVelocity", new Vector3f())); - - joints = capsule.readSavableArrayList("joints", null); - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java deleted file mode 100644 index 9ce983b6d9..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java +++ /dev/null @@ -1,694 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.objects; - -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.objects.infos.VehicleTuning; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.io.IOException; -import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A collision object for simplified vehicle simulation based on Bullet's - * btRaycastVehicle. - *

    - * From Bullet manual:
    - * For arcade style vehicle simulations, it is recommended to use the simplified - * Bullet vehicle model as provided in btRaycastVehicle. Instead of simulation - * each wheel and chassis as separate rigid bodies, connected by constraints, it - * uses a simplified model. This simplified model has many benefits, and is - * widely used in commercial driving games. - *

    - * The entire vehicle is represented as a single rigidbody, the chassis. The - * collision detection of the wheels is approximated by ray casts, and the tire - * friction is a basic anisotropic friction model. - * - * @author normenhansen - */ -public class PhysicsVehicle extends PhysicsRigidBody { - - /** - * Unique identifier of the btRaycastVehicle. The constructor sets this to a - * non-zero value. - */ - protected long vehicleId = 0; - /** - * Unique identifier of the ray caster. - */ - protected long rayCasterId = 0; - /** - * tuning parameters applied when a wheel is created - */ - protected VehicleTuning tuning = new VehicleTuning(); - /** - * list of wheels - */ - protected ArrayList wheels = new ArrayList(); - /** - * physics space where this vehicle is added, or null if none - */ - protected PhysicsSpace physicsSpace; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected PhysicsVehicle() { - } - - /** - * Instantiate a vehicle with the specified collision shape and mass=1. - * - * @param shape the desired shape (not null, alias created) - */ - public PhysicsVehicle(CollisionShape shape) { - super(shape); - } - - /** - * Instantiate a vehicle with the specified collision shape and mass. - * - * @param shape the desired shape (not null, alias created) - * @param mass (>0) - */ - public PhysicsVehicle(CollisionShape shape, float mass) { - super(shape, mass); - } - - /** - * used internally - */ - public void updateWheels() { - if (vehicleId != 0) { - for (int i = 0; i < wheels.size(); i++) { - updateWheelTransform(vehicleId, i, true); - wheels.get(i).updatePhysicsState(); - } - } - } - - private native void updateWheelTransform(long vehicleId, int wheel, boolean interpolated); - - /** - * used internally - */ - public void applyWheelTransforms() { - if (wheels != null) { - for (int i = 0; i < wheels.size(); i++) { - wheels.get(i).applyWheelTransform(); - } - } - } - - @Override - protected void postRebuild() { - super.postRebuild(); - motionState.setVehicle(this); - createVehicle(physicsSpace); - } - - /** - * Used internally, creates the actual vehicle constraint when vehicle is - * added to physics space. - * - * @param space which physics space - */ - public void createVehicle(PhysicsSpace space) { - physicsSpace = space; - if (space == null) { - return; - } - if (space.getSpaceId() == 0) { - throw new IllegalStateException("Physics space is not initialized!"); - } - if (rayCasterId != 0) { - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing RayCaster {0}", Long.toHexString(rayCasterId)); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Clearing Vehicle {0}", Long.toHexString(vehicleId)); - finalizeNative(rayCasterId, vehicleId); - } - rayCasterId = createVehicleRaycaster(objectId, space.getSpaceId()); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created RayCaster {0}", Long.toHexString(rayCasterId)); - vehicleId = createRaycastVehicle(objectId, rayCasterId); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created Vehicle {0}", Long.toHexString(vehicleId)); - setCoordinateSystem(vehicleId, 0, 1, 2); - for (VehicleWheel wheel : wheels) { - wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel())); - } - } - - private native long createVehicleRaycaster(long objectId, long physicsSpaceId); - - private native long createRaycastVehicle(long objectId, long rayCasterId); - - private native void setCoordinateSystem(long objectId, int a, int b, int c); - - private native int addWheel(long objectId, Vector3f location, Vector3f direction, Vector3f axle, float restLength, float radius, VehicleTuning tuning, boolean frontWheel); - - /** - * Add a wheel to this vehicle. - * - * @param connectionPoint the location where the suspension connects to the - * chassis (in chassis coordinates, not null, unaffected) - * @param direction the suspension direction (in chassis coordinates, not - * null, unaffected, typically down/0,-1,0) - * @param axle the axis direction (in chassis coordinates, not null, - * unaffected, typically -1,0,0) - * @param suspensionRestLength the rest length of the suspension (in - * physics-space units) - * @param wheelRadius the wheel radius (in physics-space units, >0) - * @param isFrontWheel true→front (steering) wheel, - * false→non-front wheel - * @return a new VehicleWheel for access (not null) - */ - public VehicleWheel addWheel(Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) { - return addWheel(null, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); - } - - /** - * Add a wheel to this vehicle - * - * @param spat the associated spatial, or null if none - * @param connectionPoint the location where the suspension connects to the - * chassis (in chassis coordinates, not null, unaffected) - * @param direction the suspension direction (in chassis coordinates, not - * null, unaffected, typically down/0,-1,0) - * @param axle the axis direction (in chassis coordinates, not null, - * unaffected, typically -1,0,0) - * @param suspensionRestLength the rest length of the suspension (in - * physics-space units) - * @param wheelRadius the wheel radius (in physics-space units, >0) - * @param isFrontWheel true→front (steering) wheel, - * false→non-front wheel - * @return a new VehicleWheel for access (not null) - */ - public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f direction, Vector3f axle, float suspensionRestLength, float wheelRadius, boolean isFrontWheel) { - VehicleWheel wheel = null; - if (spat == null) { - wheel = new VehicleWheel(connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); - } else { - wheel = new VehicleWheel(spat, connectionPoint, direction, axle, suspensionRestLength, wheelRadius, isFrontWheel); - } - wheel.setFrictionSlip(tuning.frictionSlip); - wheel.setMaxSuspensionTravelCm(tuning.maxSuspensionTravelCm); - wheel.setSuspensionStiffness(tuning.suspensionStiffness); - wheel.setWheelsDampingCompression(tuning.suspensionCompression); - wheel.setWheelsDampingRelaxation(tuning.suspensionDamping); - wheel.setMaxSuspensionForce(tuning.maxSuspensionForce); - wheels.add(wheel); - if (vehicleId != 0) { - wheel.setVehicleId(vehicleId, addWheel(vehicleId, wheel.getLocation(), wheel.getDirection(), wheel.getAxle(), wheel.getRestLength(), wheel.getRadius(), tuning, wheel.isFrontWheel())); - } - return wheel; - } - - /** - * Remove a wheel. - * - * @param wheel the index of the wheel to remove (≥0) - */ - public void removeWheel(int wheel) { - wheels.remove(wheel); - rebuildRigidBody(); -// updateDebugShape(); - } - - /** - * Access the indexed wheel of this vehicle. - * - * @param wheel the index of the wheel to access (≥0, <count) - * @return the pre-existing instance - */ - public VehicleWheel getWheel(int wheel) { - return wheels.get(wheel); - } - - /** - * Read the number of wheels on this vehicle. - * - * @return count (≥0) - */ - public int getNumWheels() { - return wheels.size(); - } - - /** - * Read the initial friction for new wheels. - * - * @return the coefficient of friction between tyre and ground - * (0.8→realistic car, 10000→kart racer) - */ - public float getFrictionSlip() { - return tuning.frictionSlip; - } - - /** - * Alter the initial friction for new wheels. Effective only before adding - * wheels. After adding a wheel, use {@link #setFrictionSlip(int, float)}. - *

    - * For better handling, increase the friction. - * - * @param frictionSlip the desired coefficient of friction between tyre and - * ground (0.8→realistic car, 10000→kart racer, default=10.5) - */ - public void setFrictionSlip(float frictionSlip) { - tuning.frictionSlip = frictionSlip; - } - - /** - * Alter the friction of the indexed wheel. - *

    - * For better handling, increase the friction. - * - * @param wheel the index of the wheel to modify (≥0) - * @param frictionSlip the desired coefficient of friction between tyre and - * ground (0.8→realistic car, 10000→kart racer) - */ - public void setFrictionSlip(int wheel, float frictionSlip) { - wheels.get(wheel).setFrictionSlip(frictionSlip); - } - - /** - * Alter the roll influence of the indexed wheel. - *

    - * The roll-influence factor reduces (or magnifies) any torque contributed - * by the wheel that would tend to cause the vehicle to roll over. This is a - * bit of a hack, but it's quite effective. - *

    - * If the friction between the tyres and the ground is too high, you may - * reduce this factor to prevent the vehicle from rolling over. You should - * also try lowering the vehicle's center of mass. - * - * @param wheel the index of the wheel to modify (≥0) - * @param rollInfluence the desired roll-influence factor (0→no roll - * torque, 1→realistic behavior, default=1) - */ - public void setRollInfluence(int wheel, float rollInfluence) { - wheels.get(wheel).setRollInfluence(rollInfluence); - } - - /** - * Read the initial maximum suspension travel distance for new wheels. - * - * @return the maximum distance the suspension can be compressed (in - * centimeters) - */ - public float getMaxSuspensionTravelCm() { - return tuning.maxSuspensionTravelCm; - } - - /** - * Alter the initial maximum suspension travel distance for new wheels. - * Effective only before adding wheels. After adding a wheel, use - * {@link #setMaxSuspensionTravelCm(int, float)}. - * - * @param maxSuspensionTravelCm the desired maximum distance the suspension - * can be compressed (in centimeters, default=500) - */ - public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { - tuning.maxSuspensionTravelCm = maxSuspensionTravelCm; - } - - /** - * Alter the maximum suspension travel distance for the indexed wheel. - * - * @param wheel the index of the wheel to modify (≥0) - * @param maxSuspensionTravelCm the desired maximum distance the suspension - * can be compressed (in centimeters) - */ - public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) { - wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm); - } - - /** - * Read the initial maximum suspension force for new wheels. - * - * @return the maximum force per wheel - */ - public float getMaxSuspensionForce() { - return tuning.maxSuspensionForce; - } - - /** - * Alter the initial maximum suspension force for new wheels. Effective only - * before adding wheels. After adding a wheel, use - * {@link #setMaxSuspensionForce(int, float)}. - *

    - * If the suspension cannot handle the vehicle's weight, increase this - * limit. - * - * @param maxSuspensionForce the desired maximum force per wheel - * (default=6000) - */ - public void setMaxSuspensionForce(float maxSuspensionForce) { - tuning.maxSuspensionForce = maxSuspensionForce; - } - - /** - * Alter the maximum suspension force for the specified wheel. - *

    - * If the suspension cannot handle the vehicle's weight, increase this - * limit. - * - * @param wheel the index of the wheel to modify (≥0) - * @param maxSuspensionForce the desired maximum force per wheel - * (default=6000) - */ - public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) { - wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce); - } - - /** - * Read the initial damping (when the suspension is compressed) for new - * wheels. - * - * @return the damping coefficient - */ - public float getSuspensionCompression() { - return tuning.suspensionCompression; - } - - /** - * Alter the initial damping (when the suspension is compressed) for new - * wheels. Effective only before adding wheels. After adding a wheel, use - * {@link #setSuspensionCompression(int, float)}. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param suspensionCompression the desired damping coefficient - * (default=0.83) - */ - public void setSuspensionCompression(float suspensionCompression) { - tuning.suspensionCompression = suspensionCompression; - } - - /** - * Alter the damping (when the suspension is compressed) for the indexed - * wheel. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param wheel the index of the wheel to modify (≥0) - * @param suspensionCompression the desired damping coefficient - */ - public void setSuspensionCompression(int wheel, float suspensionCompression) { - wheels.get(wheel).setWheelsDampingCompression(suspensionCompression); - } - - /** - * Read the initial damping (when the suspension is expanded) for new - * wheels. - * - * @return the damping coefficient - */ - public float getSuspensionDamping() { - return tuning.suspensionDamping; - } - - /** - * Alter the initial damping (when the suspension is expanded) for new - * wheels. Effective only before adding wheels. After adding a wheel, use - * {@link #setSuspensionCompression(int, float)}. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param suspensionDamping the desired damping coefficient (default=0.88) - */ - public void setSuspensionDamping(float suspensionDamping) { - tuning.suspensionDamping = suspensionDamping; - } - - /** - * Alter the damping (when the suspension is expanded) for the indexed - * wheel. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param wheel the index of the wheel to modify (≥0) - * @param suspensionDamping the desired damping coefficient - */ - public void setSuspensionDamping(int wheel, float suspensionDamping) { - wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping); - } - - /** - * Read the initial suspension stiffness for new wheels. - * - * @return the stiffness constant (10→off-road buggy, 50→sports - * car, 200→Formula-1 race car) - */ - public float getSuspensionStiffness() { - return tuning.suspensionStiffness; - } - - /** - * Alter the initial suspension stiffness for new wheels. Effective only - * before adding wheels. After adding a wheel, use - * {@link #setSuspensionStiffness(int, float)}. - * - * @param suspensionStiffness the desired stiffness coefficient - * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, - * default=5.88) - */ - public void setSuspensionStiffness(float suspensionStiffness) { - tuning.suspensionStiffness = suspensionStiffness; - } - - /** - * Alter the suspension stiffness of the indexed wheel. - * - * @param wheel the index of the wheel to modify (≥0) - * @param suspensionStiffness the desired stiffness coefficient - * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, - * default=5.88) - */ - public void setSuspensionStiffness(int wheel, float suspensionStiffness) { - wheels.get(wheel).setSuspensionStiffness(suspensionStiffness); - } - - /** - * Reset this vehicle's suspension. - */ - public void resetSuspension() { - resetSuspension(vehicleId); - } - - private native void resetSuspension(long vehicleId); - - /** - * Apply the specified engine force to all wheels. Works continuously. - * - * @param force the desired amount of force - */ - public void accelerate(float force) { - for (int i = 0; i < wheels.size(); i++) { - accelerate(i, force); - } - } - - /** - * Apply the given engine force to the indexed wheel. Works continuously. - * - * @param wheel the index of the wheel to apply the force to (≥0) - * @param force the desired amount of force - */ - public void accelerate(int wheel, float force) { - applyEngineForce(vehicleId, wheel, force); - - } - - private native void applyEngineForce(long vehicleId, int wheel, float force); - - /** - * Alter the steering angle of all front wheels. - * - * @param value the desired steering angle (in radians, 0=straight) - */ - public void steer(float value) { - for (int i = 0; i < wheels.size(); i++) { - if (getWheel(i).isFrontWheel()) { - steer(i, value); - } - } - } - - /** - * Alter the steering angle of the indexed wheel. - * - * @param wheel the index of the wheel to steer (≥0) - * @param value the desired steering angle (in radians, 0=straight) - */ - public void steer(int wheel, float value) { - steer(vehicleId, wheel, value); - } - - private native void steer(long vehicleId, int wheel, float value); - - /** - * Apply the given brake force to all wheels. Works continuously. - * - * @param force the desired amount of force - */ - public void brake(float force) { - for (int i = 0; i < wheels.size(); i++) { - brake(i, force); - } - } - - /** - * Apply the given brake force to the indexed wheel. Works continuously. - * - * @param wheel the index of the wheel to apply the force to (≥0) - * @param force the desired amount of force - */ - public void brake(int wheel, float force) { - brake(vehicleId, wheel, force); - } - - private native void brake(long vehicleId, int wheel, float force); - - /** - * Read the vehicle's speed in km/h. - * - * @return speed (in kilometers per hour) - */ - public float getCurrentVehicleSpeedKmHour() { - return getCurrentVehicleSpeedKmHour(vehicleId); - } - - private native float getCurrentVehicleSpeedKmHour(long vehicleId); - - /** - * Copy the vehicle's forward direction. - * - * @param vector storage for the result (modified if not null) - * @return a direction vector (in physics-space coordinates, either the - * provided storage or a new vector, not null) - */ - public Vector3f getForwardVector(Vector3f vector) { - if (vector == null) { - vector = new Vector3f(); - } - getForwardVector(vehicleId, vector); - return vector; - } - - private native void getForwardVector(long objectId, Vector3f vector); - - /** - * used internally - * - * @return the unique identifier - */ - public long getVehicleId() { - return vehicleId; - } - - /** - * De-serialize this vehicle, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule capsule = im.getCapsule(this); - tuning = new VehicleTuning(); - tuning.frictionSlip = capsule.readFloat("frictionSlip", 10.5f); - tuning.maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f); - tuning.maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f); - tuning.suspensionCompression = capsule.readFloat("suspensionCompression", 0.83f); - tuning.suspensionDamping = capsule.readFloat("suspensionDamping", 0.88f); - tuning.suspensionStiffness = capsule.readFloat("suspensionStiffness", 5.88f); - wheels = capsule.readSavableArrayList("wheelsList", new ArrayList()); - motionState.setVehicle(this); - super.read(im); - } - - /** - * Serialize this vehicle, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(tuning.frictionSlip, "frictionSlip", 10.5f); - capsule.write(tuning.maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f); - capsule.write(tuning.maxSuspensionForce, "maxSuspensionForce", 6000f); - capsule.write(tuning.suspensionCompression, "suspensionCompression", 0.83f); - capsule.write(tuning.suspensionDamping, "suspensionDamping", 0.88f); - capsule.write(tuning.suspensionStiffness, "suspensionStiffness", 5.88f); - capsule.writeSavableArrayList(wheels, "wheelsList", new ArrayList()); - super.write(ex); - } - - /** - * Finalize this vehicle just before it is destroyed. Should be invoked only - * by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing RayCaster {0}", Long.toHexString(rayCasterId)); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing Vehicle {0}", Long.toHexString(vehicleId)); - finalizeNative(rayCasterId, vehicleId); - } - - private native void finalizeNative(long rayCaster, long vehicle); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java deleted file mode 100644 index 9091559586..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java +++ /dev/null @@ -1,713 +0,0 @@ -/* - * Copyright (c) 2009-2019 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.bullet.objects; - -import com.jme3.bullet.collision.PhysicsCollisionObject; -import com.jme3.export.*; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.io.IOException; - -/** - * Information about one wheel of a PhysicsVehicle. - * - * @author normenhansen - */ -public class VehicleWheel implements Savable { - - /** - * unique identifier of the btRaycastVehicle - */ - protected long wheelId = 0; - /** - * 0-origin index among the vehicle's wheels (≥0) - */ - protected int wheelIndex = 0; - /** - * copy of wheel type: true→front (steering) wheel, - * false→non-front wheel - */ - protected boolean frontWheel; - /** - * location where the suspension connects to the chassis (in chassis - * coordinates) - */ - protected Vector3f location = new Vector3f(); - /** - * suspension direction (in chassis coordinates, typically down/0,-1,0) - */ - protected Vector3f direction = new Vector3f(); - /** - * axis direction (in chassis coordinates, typically to the right/-1,0,0) - */ - protected Vector3f axle = new Vector3f(); - /** - * copy of suspension stiffness constant (10→off-road buggy, - * 50→sports car, 200→Formula-1 race car, default=20) - */ - protected float suspensionStiffness = 20.0f; - /** - * copy of suspension damping when expanded (0→no damping, default=2.3) - */ - protected float wheelsDampingRelaxation = 2.3f; - /** - * copy of suspension damping when compressed (0→no damping, - * default=4.4) - */ - protected float wheelsDampingCompression = 4.4f; - /** - * copy of coefficient of friction between tyre and ground - * (0.8→realistic car, 10000→kart racer, default=10.5) - */ - protected float frictionSlip = 10.5f; - /** - * copy of roll-influence factor (0→no roll torque, 1→realistic - * behavior, default=1) - */ - protected float rollInfluence = 1.0f; - /** - * copy of maximum suspension travel distance (in centimeters, default=500) - */ - protected float maxSuspensionTravelCm = 500f; - /** - * copy of maximum force exerted by the suspension (default=6000) - */ - protected float maxSuspensionForce = 6000f; - /** - * copy of wheel radius (in physics-space units, >0) - */ - protected float radius = 0.5f; - /** - * copy of rest length of the suspension (in physics-space units) - */ - protected float restLength = 1f; - /** - * wheel location in physics-space coordinates - */ - protected Vector3f wheelWorldLocation = new Vector3f(); - /** - * wheel orientation in physics-space coordinates - */ - protected Quaternion wheelWorldRotation = new Quaternion(); - /** - * associated spatial, or null if none - */ - protected Spatial wheelSpatial; - protected Matrix3f tmp_Matrix = new com.jme3.math.Matrix3f(); - protected final Quaternion tmp_inverseWorldRotation = new Quaternion(); - /** - * true → physics coordinates match local transform, false → - * physics coordinates match world transform - */ - private boolean applyLocal = false; - - /** - * No-argument constructor needed by SavableClassUtil. Do not invoke - * directly! - */ - protected VehicleWheel() { - } - - /** - * Instantiate a wheel. - * - * @param spat the associated spatial, or null if none - * @param location the location where the suspension connects to the chassis - * (in chassis coordinates, not null, unaffected) - * @param direction the suspension direction (in chassis coordinates, not - * null, unaffected, typically down/0,-1,0) - * @param axle the axis direction (in chassis coordinates, not null, - * unaffected, typically right/-1,0,0) - * @param restLength the rest length of the suspension (in physics-space - * units) - * @param radius the wheel's radius (in physics-space units, ≥0) - * @param frontWheel true→front (steering) wheel, false→non-front - * wheel - */ - public VehicleWheel(Spatial spat, Vector3f location, Vector3f direction, Vector3f axle, - float restLength, float radius, boolean frontWheel) { - this(location, direction, axle, restLength, radius, frontWheel); - wheelSpatial = spat; - } - - /** - * Instantiate a wheel without an associated spatial. - * - * @param location the location where the suspension connects to the chassis - * (in chassis coordinates, not null, unaffected) - * @param direction the suspension direction (in chassis coordinates, not - * null, unaffected, typically down/0,-1,0) - * @param axle the axis direction (in chassis coordinates, not null, - * unaffected, typically right/-1,0,0) - * @param restLength the rest length of the suspension (in physics-space - * units) - * @param radius the wheel's radius (in physics-space units, ≥0) - * @param frontWheel true→front (steering) wheel, false→non-front - * wheel - */ - public VehicleWheel(Vector3f location, Vector3f direction, Vector3f axle, - float restLength, float radius, boolean frontWheel) { - this.location.set(location); - this.direction.set(direction); - this.axle.set(axle); - this.frontWheel = frontWheel; - this.restLength = restLength; - this.radius = radius; - } - - /** - * Update this wheel's location and orientation in physics space. - */ - public void updatePhysicsState() { - getWheelLocation(wheelId, wheelIndex, wheelWorldLocation); - getWheelRotation(wheelId, wheelIndex, tmp_Matrix); - wheelWorldRotation.fromRotationMatrix(tmp_Matrix); - } - - private native void getWheelLocation(long vehicleId, int wheelId, Vector3f location); - - private native void getWheelRotation(long vehicleId, int wheelId, Matrix3f location); - - /** - * Apply this wheel's physics location and orientation to its associated - * spatial, if any. - */ - public void applyWheelTransform() { - if (wheelSpatial == null) { - return; - } - Quaternion localRotationQuat = wheelSpatial.getLocalRotation(); - Vector3f localLocation = wheelSpatial.getLocalTranslation(); - if (!applyLocal && wheelSpatial.getParent() != null) { - localLocation.set(wheelWorldLocation).subtractLocal(wheelSpatial.getParent().getWorldTranslation()); - localLocation.divideLocal(wheelSpatial.getParent().getWorldScale()); - tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation); - - localRotationQuat.set(wheelWorldRotation); - tmp_inverseWorldRotation.set(wheelSpatial.getParent().getWorldRotation()).inverseLocal().mult(localRotationQuat, localRotationQuat); - - wheelSpatial.setLocalTranslation(localLocation); - wheelSpatial.setLocalRotation(localRotationQuat); - } else { - wheelSpatial.setLocalTranslation(wheelWorldLocation); - wheelSpatial.setLocalRotation(wheelWorldRotation); - } - } - - /** - * Read the id of the btRaycastVehicle. - * - * @return the unique identifier (not zero) - */ - public long getWheelId() { - return wheelId; - } - - /** - * Assign this wheel to a vehicle. - * - * @param vehicleId the id of the btRaycastVehicle (not zero) - * @param wheelIndex index among the vehicle's wheels (≥0) - */ - public void setVehicleId(long vehicleId, int wheelIndex) { - this.wheelId = vehicleId; - this.wheelIndex = wheelIndex; - applyInfo(); - } - - /** - * Test whether this wheel is a front wheel. - * - * @return true if front wheel, otherwise false - */ - public boolean isFrontWheel() { - return frontWheel; - } - - /** - * Alter whether this wheel is a front (steering) wheel. - * - * @param frontWheel true→front wheel, false→non-front wheel - */ - public void setFrontWheel(boolean frontWheel) { - this.frontWheel = frontWheel; - applyInfo(); - } - - /** - * Access the location where the suspension connects to the chassis. - * - * @return the pre-existing location vector (in chassis coordinates, not - * null) - */ - public Vector3f getLocation() { - return location; - } - - /** - * Access this wheel's suspension direction. - * - * @return the pre-existing direction vector (in chassis coordinates, not - * null) - */ - public Vector3f getDirection() { - return direction; - } - - /** - * Access this wheel's axle direction. - * - * @return the pre-existing direction vector (not null) - */ - public Vector3f getAxle() { - return axle; - } - - /** - * Read the stiffness constant for this wheel's suspension. - * - * @return the stiffness constant - */ - public float getSuspensionStiffness() { - return suspensionStiffness; - } - - /** - * Alter the stiffness constant for this wheel's suspension. - * - * @param suspensionStiffness the desired stiffness constant - * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, - * default=20) - */ - public void setSuspensionStiffness(float suspensionStiffness) { - this.suspensionStiffness = suspensionStiffness; - applyInfo(); - } - - /** - * Read this wheel's damping when the suspension is expanded. - * - * @return the damping - */ - public float getWheelsDampingRelaxation() { - return wheelsDampingRelaxation; - } - - /** - * Alter this wheel's damping when the suspension is expanded. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param wheelsDampingRelaxation the desired damping (default=2.3) - */ - public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) { - this.wheelsDampingRelaxation = wheelsDampingRelaxation; - applyInfo(); - } - - /** - * Read this wheel's damping when the suspension is compressed. - * - * @return the damping - */ - public float getWheelsDampingCompression() { - return wheelsDampingCompression; - } - - /** - * Alter this wheel's damping when the suspension is compressed. - *

    - * Set to k * 2 * FastMath.sqrt(m_suspensionStiffness) where k is the - * damping ratio: - *

    - * k = 0.0 undamped and bouncy, k = 1.0 critical damping, k between 0.1 and - * 0.3 are good values - * - * @param wheelsDampingCompression the desired damping (default=4.4) - */ - public void setWheelsDampingCompression(float wheelsDampingCompression) { - this.wheelsDampingCompression = wheelsDampingCompression; - applyInfo(); - } - - /** - * Read the friction between this wheel's tyre and the ground. - * - * @return the coefficient of friction - */ - public float getFrictionSlip() { - return frictionSlip; - } - - /** - * Alter the friction between this wheel's tyre and the ground. - *

    - * Should be about 0.8 for realistic cars, but can increased for better - * handling. Set large (10000.0) for kart racers. - * - * @param frictionSlip the desired coefficient of friction (default=10.5) - */ - public void setFrictionSlip(float frictionSlip) { - this.frictionSlip = frictionSlip; - applyInfo(); - } - - /** - * Read this wheel's roll influence. - * - * @return the roll-influence factor - */ - public float getRollInfluence() { - return rollInfluence; - } - - /** - * Alter this wheel's roll influence. - *

    - * The roll-influence factor reduces (or magnifies) the torque contributed - * by this wheel that tends to cause the vehicle to roll over. This is a bit - * of a hack, but it's quite effective. - *

    - * If the friction between the tyres and the ground is too high, you may - * reduce this factor to prevent the vehicle from rolling over. You should - * also try lowering the vehicle's centre of mass. - * - * @param rollInfluence the desired roll-influence factor (0→no roll - * torque, 1→realistic behaviour, default=1) - */ - public void setRollInfluence(float rollInfluence) { - this.rollInfluence = rollInfluence; - applyInfo(); - } - - /** - * Read the travel distance for this wheel's suspension. - * - * @return the maximum travel distance (in centimeters) - */ - public float getMaxSuspensionTravelCm() { - return maxSuspensionTravelCm; - } - - /** - * Alter the travel distance for this wheel's suspension. - * - * @param maxSuspensionTravelCm the desired maximum travel distance (in - * centimetres, default=500) - */ - public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { - this.maxSuspensionTravelCm = maxSuspensionTravelCm; - applyInfo(); - } - - /** - * Read the maximum force exerted by this wheel's suspension. - * - * @return the maximum force - */ - public float getMaxSuspensionForce() { - return maxSuspensionForce; - } - - /** - * Alter the maximum force exerted by this wheel's suspension. - *

    - * Increase this if your suspension cannot handle the weight of your - * vehicle. - * - * @param maxSuspensionForce the desired maximum force (default=6000) - */ - public void setMaxSuspensionForce(float maxSuspensionForce) { - this.maxSuspensionForce = maxSuspensionForce; - applyInfo(); - } - - private void applyInfo() { - if (wheelId == 0) { - return; - } - applyInfo(wheelId, wheelIndex, suspensionStiffness, wheelsDampingRelaxation, wheelsDampingCompression, frictionSlip, rollInfluence, maxSuspensionTravelCm, maxSuspensionForce, radius, frontWheel, restLength); - } - - private native void applyInfo(long wheelId, int wheelIndex, - float suspensionStiffness, - float wheelsDampingRelaxation, - float wheelsDampingCompression, - float frictionSlip, - float rollInfluence, - float maxSuspensionTravelCm, - float maxSuspensionForce, - float wheelsRadius, - boolean frontWheel, - float suspensionRestLength); - - /** - * Read the radius of this wheel. - * - * @return the radius (in physics-space units, ≥0) - */ - public float getRadius() { - return radius; - } - - /** - * Alter the radius of this wheel. - * - * @param radius the desired radius (in physics-space units, ≥0, - * default=0.5) - */ - public void setRadius(float radius) { - this.radius = radius; - applyInfo(); - } - - /** - * Read the rest length of this wheel. - * - * @return the length - */ - public float getRestLength() { - return restLength; - } - - /** - * Alter the rest length of the suspension of this wheel. - * - * @param restLength the desired length (default=1) - */ - public void setRestLength(float restLength) { - this.restLength = restLength; - applyInfo(); - } - - /** - * returns the object this wheel is in contact with or null if no contact - * @return the PhysicsCollisionObject (PhysicsRigidBody, PhysicsGhostObject) - */ - public PhysicsCollisionObject getGroundObject() { -// if (wheelInfo.raycastInfo.groundObject == null) { -// return null; -// } else if (wheelInfo.raycastInfo.groundObject instanceof RigidBody) { -// System.out.println("RigidBody"); -// return (PhysicsRigidBody) ((RigidBody) wheelInfo.raycastInfo.groundObject).getUserPointer(); -// } else { - return null; -// } - } - - /** - * Copy the location where the wheel touches the ground. - * - * @param vec storage for the result (not null, modified) - * @return a new location vector (in physics-space coordinates, not null) - */ - public Vector3f getCollisionLocation(Vector3f vec) { - getCollisionLocation(wheelId, wheelIndex, vec); - return vec; - } - - private native void getCollisionLocation(long wheelId, int wheelIndex, Vector3f vec); - - /** - * Copy the location where the wheel collides with the ground. - * - * @return a new location vector (in physics-space coordinates) - */ - public Vector3f getCollisionLocation() { - Vector3f vec = new Vector3f(); - getCollisionLocation(wheelId, wheelIndex, vec); - return vec; - } - - /** - * Copy the normal where the wheel touches the ground. - * - * @param vec storage for the result (not null, modified) - * @return a unit vector (in physics-space coordinates, not null) - */ - public Vector3f getCollisionNormal(Vector3f vec) { - getCollisionNormal(wheelId, wheelIndex, vec); - return vec; - } - - private native void getCollisionNormal(long wheelId, int wheelIndex, Vector3f vec); - - /** - * Copy the normal where the wheel touches the ground. - * - * @return a new unit vector (in physics-space coordinates, not null) - */ - public Vector3f getCollisionNormal() { - Vector3f vec = new Vector3f(); - getCollisionNormal(wheelId, wheelIndex, vec); - return vec; - } - - /** - * Calculate to what extent the wheel is skidding (for skid sounds/smoke - * etc.) - * - * @return the relative amount of traction (0→wheel is sliding, - * 1→wheel has full traction) - */ - public float getSkidInfo() { - return getSkidInfo(wheelId, wheelIndex); - } - - public native float getSkidInfo(long wheelId, int wheelIndex); - - /** - * Calculate how much this wheel has turned since the last physics step. - * - * @return the rotation angle (in radians) - */ - public float getDeltaRotation() { - return getDeltaRotation(wheelId, wheelIndex); - } - - public native float getDeltaRotation(long wheelId, int wheelIndex); - - /** - * De-serialize this wheel, for example when loading from a J3O file. - * - * @param im importer (not null) - * @throws IOException from importer - */ - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule capsule = im.getCapsule(this); - wheelSpatial = (Spatial) capsule.readSavable("wheelSpatial", null); - frontWheel = capsule.readBoolean("frontWheel", false); - location = (Vector3f) capsule.readSavable("wheelLocation", new Vector3f()); - direction = (Vector3f) capsule.readSavable("wheelDirection", new Vector3f()); - axle = (Vector3f) capsule.readSavable("wheelAxle", new Vector3f()); - suspensionStiffness = capsule.readFloat("suspensionStiffness", 20.0f); - wheelsDampingRelaxation = capsule.readFloat("wheelsDampingRelaxation", 2.3f); - wheelsDampingCompression = capsule.readFloat("wheelsDampingCompression", 4.4f); - frictionSlip = capsule.readFloat("frictionSlip", 10.5f); - rollInfluence = capsule.readFloat("rollInfluence", 1.0f); - maxSuspensionTravelCm = capsule.readFloat("maxSuspensionTravelCm", 500f); - maxSuspensionForce = capsule.readFloat("maxSuspensionForce", 6000f); - radius = capsule.readFloat("wheelRadius", 0.5f); - restLength = capsule.readFloat("restLength", 1f); - } - - /** - * Serialize this wheel, for example when saving to a J3O file. - * - * @param ex exporter (not null) - * @throws IOException from exporter - */ - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule capsule = ex.getCapsule(this); - capsule.write(wheelSpatial, "wheelSpatial", null); - capsule.write(frontWheel, "frontWheel", false); - capsule.write(location, "wheelLocation", new Vector3f()); - capsule.write(direction, "wheelDirection", new Vector3f()); - capsule.write(axle, "wheelAxle", new Vector3f()); - capsule.write(suspensionStiffness, "suspensionStiffness", 20.0f); - capsule.write(wheelsDampingRelaxation, "wheelsDampingRelaxation", 2.3f); - capsule.write(wheelsDampingCompression, "wheelsDampingCompression", 4.4f); - capsule.write(frictionSlip, "frictionSlip", 10.5f); - capsule.write(rollInfluence, "rollInfluence", 1.0f); - capsule.write(maxSuspensionTravelCm, "maxSuspensionTravelCm", 500f); - capsule.write(maxSuspensionForce, "maxSuspensionForce", 6000f); - capsule.write(radius, "wheelRadius", 0.5f); - capsule.write(restLength, "restLength", 1f); - } - - /** - * Access the spatial associated with this wheel. - * - * @return the pre-existing instance, or null - */ - public Spatial getWheelSpatial() { - return wheelSpatial; - } - - /** - * Alter which spatial is associated with this wheel. - * - * @param wheelSpatial the desired spatial, or null for none - */ - public void setWheelSpatial(Spatial wheelSpatial) { - this.wheelSpatial = wheelSpatial; - } - - /** - * Test whether physics coordinates should match the local transform of the - * Spatial. - * - * @return true if matching local transform, false if matching world - * transform - */ - public boolean isApplyLocal() { - return applyLocal; - } - - /** - * Alter whether physics coordinates should match the local transform of the - * Spatial. - * - * @param applyLocal true→match local transform, false→match world - * transform (default=false) - */ - public void setApplyLocal(boolean applyLocal) { - this.applyLocal = applyLocal; - } - - /** - * Copy this wheel's physics-space orientation to the specified quaternion. - * - * @param store storage for the result (not null, modified) - */ - public void getWheelWorldRotation(final Quaternion store) { - store.set(this.wheelWorldRotation); - } - - /** - * Copy this wheel's physics-space location to the specified vector. - * - * @param store storage for the result (not null, modified) - */ - public void getWheelWorldLocation(final Vector3f store) { - store.set(this.wheelWorldLocation); - } - -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java deleted file mode 100644 index bee3bf78df..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.objects.infos; - -import com.jme3.bullet.objects.PhysicsVehicle; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * The motion state (transform) of a rigid body, with thread-safe access. - * - * @author normenhansen - */ -public class RigidBodyMotionState { - long motionStateId = 0; - private Vector3f worldLocation = new Vector3f(); - private Matrix3f worldRotation = new Matrix3f(); - private Quaternion worldRotationQuat = new Quaternion(); - private Quaternion tmp_inverseWorldRotation = new Quaternion(); - private PhysicsVehicle vehicle; - /** - * true → physics coordinates match local transform, false → - * physics coordinates match world transform - */ - private boolean applyPhysicsLocal = false; -// protected LinkedList listeners = new LinkedList(); - - /** - * Instantiate a motion state. - */ - public RigidBodyMotionState() { - this.motionStateId = createMotionState(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Created MotionState {0}", Long.toHexString(motionStateId)); - } - - private native long createMotionState(); - - /** - * If the motion state has been updated, apply the new transform to the - * specified spatial. - * - * @param spatial where to apply the physics transform (not null, modified) - * @return true if changed - */ - public boolean applyTransform(Spatial spatial) { - Vector3f localLocation = spatial.getLocalTranslation(); - Quaternion localRotationQuat = spatial.getLocalRotation(); - boolean physicsLocationDirty = applyTransform(motionStateId, localLocation, localRotationQuat); - if (!physicsLocationDirty) { - return false; - } - if (!applyPhysicsLocal && spatial.getParent() != null) { - localLocation.subtractLocal(spatial.getParent().getWorldTranslation()); - localLocation.divideLocal(spatial.getParent().getWorldScale()); - tmp_inverseWorldRotation.set(spatial.getParent().getWorldRotation()).inverseLocal().multLocal(localLocation); - -// localRotationQuat.set(worldRotationQuat); - tmp_inverseWorldRotation.mult(localRotationQuat, localRotationQuat); - - spatial.setLocalTranslation(localLocation); - spatial.setLocalRotation(localRotationQuat); - } else { - spatial.setLocalTranslation(localLocation); - spatial.setLocalRotation(localRotationQuat); -// spatial.setLocalTranslation(worldLocation); -// spatial.setLocalRotation(worldRotationQuat); - } - if (vehicle != null) { - vehicle.updateWheels(); - } - return true; - } - - private native boolean applyTransform(long stateId, Vector3f location, Quaternion rotation); - - /** - * Copy the location from this motion state. - * - * @return the pre-existing location vector (in physics-space coordinates, - * not null) - */ - public Vector3f getWorldLocation() { - getWorldLocation(motionStateId, worldLocation); - return worldLocation; - } - - private native void getWorldLocation(long stateId, Vector3f vec); - - /** - * Read the rotation of this motion state (as a matrix). - * - * @return the pre-existing rotation matrix (in physics-space coordinates, - * not null) - */ - public Matrix3f getWorldRotation() { - getWorldRotation(motionStateId, worldRotation); - return worldRotation; - } - - private native void getWorldRotation(long stateId, Matrix3f vec); - - /** - * Read the rotation of this motion state (as a quaternion). - * - * @return the pre-existing instance (in physics-space coordinates, not - * null) - */ - public Quaternion getWorldRotationQuat() { - getWorldRotationQuat(motionStateId, worldRotationQuat); - return worldRotationQuat; - } - - private native void getWorldRotationQuat(long stateId, Quaternion vec); - - /** - * @param vehicle which vehicle will use this motion state - */ - public void setVehicle(PhysicsVehicle vehicle) { - this.vehicle = vehicle; - } - - /** - * Test whether physics-space coordinates should match the spatial's local - * coordinates. - * - * @return true if matching local coordinates, false if matching world - * coordinates - */ - public boolean isApplyPhysicsLocal() { - return applyPhysicsLocal; - } - - /** - * Alter whether physics-space coordinates should match the spatial's local - * coordinates. - * - * @param applyPhysicsLocal true→match local coordinates, - * false→match world coordinates (default=false) - */ - public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { - this.applyPhysicsLocal = applyPhysicsLocal; - } - - /** - * Read the unique id of the native object. - * - * @return id (not zero) - */ - public long getObjectId(){ - return motionStateId; - } -// public void addMotionStateListener(PhysicsMotionStateListener listener){ -// listeners.add(listener); -// } -// -// public void removeMotionStateListener(PhysicsMotionStateListener listener){ -// listeners.remove(listener); -// } - - /** - * Finalize this motion state just before it is destroyed. Should be invoked - * only by a subclass or by the garbage collector. - * - * @throws Throwable ignored by the garbage collector - */ - @Override - protected void finalize() throws Throwable { - super.finalize(); - Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Finalizing MotionState {0}", Long.toHexString(motionStateId)); - finalizeNative(motionStateId); - } - - private native void finalizeNative(long objectId); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java b/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java deleted file mode 100644 index 3a6dabbc7b..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/objects/infos/VehicleTuning.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.objects.infos; - -/** - * Typical tuning parameters for a PhysicsVehicle. - * - * @author normenhansen - */ -public class VehicleTuning { - /** - * suspension stiffness constant (10→off-road buggy, 50→sports - * car, 200→Formula-1 race car, default=5.88) - */ - public float suspensionStiffness = 5.88f; - /** - * suspension damping when compressed (0→no damping, default=0.83) - */ - public float suspensionCompression = 0.83f; - /** - * suspension damping when expanded (0→no damping, default=0.88) - */ - public float suspensionDamping = 0.88f; - /** - * maximum suspension travel distance (in centimeters, default=500) - */ - public float maxSuspensionTravelCm = 500f; - /** - * maximum force exerted by each wheel's suspension (default=6000) - */ - public float maxSuspensionForce = 6000f; - /** - * coefficient of friction between tyres and ground (0.8→realistic car, - * 10000→kart racer, default=10.5) - */ - public float frictionSlip = 10.5f; -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java deleted file mode 100644 index f0bc3c45f3..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugMeshCallback.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.util; - -import com.jme3.math.Vector3f; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; -import java.util.ArrayList; - -/** - * - * @author normenhansen - */ -public class DebugMeshCallback { - - private ArrayList list = new ArrayList(); - - /** - * Add a vertex to the mesh under construction. - *

    - * This method is invoked from native code. - * - * @param x local X coordinate of new vertex - * @param y local Y coordinate of new vertex - * @param z local Z coordinate of new vertex - * @param part ignored - * @param index ignored - */ - public void addVector(float x, float y, float z, int part, int index) { - list.add(new Vector3f(x, y, z)); - } - - public FloatBuffer getVertices() { - FloatBuffer buf = BufferUtils.createFloatBuffer(list.size() * 3); - for (int i = 0; i < list.size(); i++) { - Vector3f vector3f = list.get(i); - buf.put(vector3f.x); - buf.put(vector3f.y); - buf.put(vector3f.z); - } - return buf; - } -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java deleted file mode 100644 index d56440911a..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.util; - -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.CompoundCollisionShape; -import com.jme3.bullet.collision.shapes.infos.ChildCollisionShape; -import com.jme3.math.Matrix3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.util.TempVars; -import java.util.Iterator; -import java.util.List; - -/** - * A utility class to generate debug spatials from Bullet collision shapes. - * - * @author CJ Hare, normenhansen - */ -public class DebugShapeFactory { - - /** The maximum corner for the aabb used for triangles to include in ConcaveShape processing.*/ -// private static final Vector3f aabbMax = new Vector3f(1e30f, 1e30f, 1e30f); - /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/ -// private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f); - - /** - * Create a debug spatial from the specified collision shape. - *

    - * This is mostly used internally. To attach a debug shape to a physics - * object, call attachDebugShape(AssetManager manager); on it. - * - * @param collisionShape the shape to visualize (may be null, unaffected) - * @return a new tree of geometries, or null - */ - public static Spatial getDebugShape(CollisionShape collisionShape) { - if (collisionShape == null) { - return null; - } - Spatial debugShape; - if (collisionShape instanceof CompoundCollisionShape) { - CompoundCollisionShape shape = (CompoundCollisionShape) collisionShape; - List children = shape.getChildren(); - Node node = new Node("DebugShapeNode"); - for (Iterator it = children.iterator(); it.hasNext();) { - ChildCollisionShape childCollisionShape = it.next(); - CollisionShape ccollisionShape = childCollisionShape.shape; - Geometry geometry = createDebugShape(ccollisionShape); - - // apply translation - geometry.setLocalTranslation(childCollisionShape.location); - - // apply rotation - TempVars vars = TempVars.get(); - Matrix3f tempRot = vars.tempMat3; - - tempRot.set(geometry.getLocalRotation()); - childCollisionShape.rotation.mult(tempRot, tempRot); - geometry.setLocalRotation(tempRot); - - vars.release(); - - node.attachChild(geometry); - } - debugShape = node; - } else { - debugShape = createDebugShape(collisionShape); - } - if (debugShape == null) { - return null; - } - debugShape.updateGeometricState(); - return debugShape; - } - - /** - * Create a geometry for visualizing the specified shape. - * - * @param shape (not null, unaffected) - * @return a new geometry (not null) - */ - private static Geometry createDebugShape(CollisionShape shape) { - Geometry geom = new Geometry(); - geom.setMesh(DebugShapeFactory.getDebugMesh(shape)); -// geom.setLocalScale(shape.getScale()); - geom.updateModelBound(); - return geom; - } - - /** - * Create a mesh for visualizing the specified shape. - * - * @param shape (not null, unaffected) - * @return a new mesh (not null) - */ - public static Mesh getDebugMesh(CollisionShape shape) { - Mesh mesh = new Mesh(); - DebugMeshCallback callback = new DebugMeshCallback(); - long id = shape.getObjectId(); - getVertices(id, callback); - - mesh.setBuffer(Type.Position, 3, callback.getVertices()); - mesh.getFloatBuffer(Type.Position).clear(); - return mesh; - } - - private static native void getVertices(long shapeId, DebugMeshCallback buffer); -} diff --git a/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java b/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java deleted file mode 100644 index caf172c821..0000000000 --- a/jme3-bullet/src/main/java/com/jme3/bullet/util/NativeMeshUtil.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.bullet.util; - -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer.Type; -import com.jme3.scene.mesh.IndexBuffer; -import com.jme3.util.BufferUtils; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; - -/** - * A utility class for interfacing with Native Bullet. - * - * @author normenhansen - */ -public class NativeMeshUtil { - - /** - * Pass a mesh to Native Bullet. - * - * @param mesh the JME mesh to pass (not null) - * @return the unique identifier of the resulting btTriangleIndexVertexArray - * (not 0) - */ - public static long getTriangleIndexVertexArray(Mesh mesh){ - ByteBuffer triangleIndexBase = BufferUtils.createByteBuffer(mesh.getTriangleCount() * 3 * 4); - ByteBuffer vertexBase = BufferUtils.createByteBuffer(mesh.getVertexCount() * 3 * 4); - int numVertices = mesh.getVertexCount(); - int vertexStride = 12; //3 verts * 4 bytes each - int numTriangles = mesh.getTriangleCount(); - int triangleIndexStride = 12; //3 index entries * 4 bytes each - - IndexBuffer indices = mesh.getIndicesAsList(); - FloatBuffer vertices = mesh.getFloatBuffer(Type.Position); - vertices.rewind(); - - int verticesLength = mesh.getVertexCount() * 3; - for (int i = 0; i < verticesLength; i++) { - float tempFloat = vertices.get(); - vertexBase.putFloat(tempFloat); - } - - int indicesLength = mesh.getTriangleCount() * 3; - for (int i = 0; i < indicesLength; i++) { - triangleIndexBase.putInt(indices.get(i)); - } - vertices.rewind(); - vertices.clear(); - - return createTriangleIndexVertexArray(triangleIndexBase, vertexBase, numTriangles, numVertices, vertexStride, triangleIndexStride); - } - - /** - * Instantiate a btTriangleIndexVertexArray. Native method. - * - * @param triangleIndexBase index buffer (not null) - * @param vertexBase vertex buffer (not null) - * @param numTraingles the number of triangles in the mesh (≥0) - * @param numVertices the number of vertices in the mesh (≥0) - * @param vertextStride (in bytes, >0) - * @param triangleIndexStride (in bytes, >0) - * @return the unique identifier of the resulting btTriangleIndexVertexArray - * (not 0) - */ - public static native long createTriangleIndexVertexArray(ByteBuffer triangleIndexBase, ByteBuffer vertexBase, int numTraingles, int numVertices, int vertextStride, int triangleIndexStride); - -} diff --git a/jme3-core/build.gradle b/jme3-core/build.gradle index c8b311739c..58ac362921 100644 --- a/jme3-core/build.gradle +++ b/jme3-core/build.gradle @@ -1,7 +1,3 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - sourceSets { main { java { @@ -14,9 +10,17 @@ sourceSets { java { srcDir 'src/test/java' } + + System.setProperty "java.awt.headless", "true" } } +dependencies { + testRuntimeOnly project(':jme3-testdata') + testImplementation project(':jme3-desktop') + testRuntimeOnly project(':jme3-plugins') +} + task updateVersionPropertiesFile { def versionFile = file('src/main/resources/com/jme3/system/version.properties') def versionFileText = "# THIS IS AN AUTO-GENERATED FILE..\n" + @@ -40,4 +44,4 @@ task updateVersionPropertiesFile { } } -processResources.dependsOn updateVersionPropertiesFile \ No newline at end of file +processResources.dependsOn updateVersionPropertiesFile diff --git a/jme3-core/src/main/java/checkers/quals/DefaultLocation.java b/jme3-core/src/main/java/checkers/quals/DefaultLocation.java index 90dc78a037..bae40d7dd2 100644 --- a/jme3-core/src/main/java/checkers/quals/DefaultLocation.java +++ b/jme3-core/src/main/java/checkers/quals/DefaultLocation.java @@ -15,8 +15,8 @@ public enum DefaultLocation { ALL_EXCEPT_LOCALS, /** Apply default annotations to unannotated upper bounds: both - * explicit ones in extends clauses, and implicit upper bounds - * when no explicit extends or super clause is + * explicit ones in extends clauses, and implicit upper bounds + * when no explicit extends or super clause is * present. */ // Especially useful for parameterized classes that provide a lot of // static methods with the same generic parameters as the class. diff --git a/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java b/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java index 5caf5dabbd..b1b821ed06 100644 --- a/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java +++ b/jme3-core/src/main/java/checkers/quals/DefaultQualifier.java @@ -36,6 +36,8 @@ * To prevent affecting other type systems, always specify an annotation * in your own type hierarchy. (For example, do not set * "checkers.quals.Unqualified" as the default.) + * + * @return the name of the default annotation */ String value(); diff --git a/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java b/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java index 42854ee431..c2b9bef9c6 100644 --- a/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java +++ b/jme3-core/src/main/java/checkers/quals/DefaultQualifiers.java @@ -16,13 +16,13 @@ * * Example: * - *

    + * 
      *   @DefaultQualifiers({
      *       @DefaultQualifier("NonNull"),
      *       @DefaultQualifier(value = "Interned", locations = ALL_EXCEPT_LOCALS),
      *       @DefaultQualifier("Tainted")
      *   })
    - * 
    + *
    * * @see DefaultQualifier */ @@ -30,6 +30,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target({CONSTRUCTOR, METHOD, FIELD, LOCAL_VARIABLE, PARAMETER, TYPE}) public @interface DefaultQualifiers { - /** The default qualifier settings */ + /** @return the default qualifier settings */ DefaultQualifier[] value() default { }; } diff --git a/jme3-core/src/main/java/checkers/quals/Dependent.java b/jme3-core/src/main/java/checkers/quals/Dependent.java index 88d3b39d18..1ee853cf6c 100644 --- a/jme3-core/src/main/java/checkers/quals/Dependent.java +++ b/jme3-core/src/main/java/checkers/quals/Dependent.java @@ -26,12 +26,12 @@ public @interface Dependent { /** - * The class of the refined qualifier to be applied. + * @return the class of the refined qualifier to be applied. */ Class result(); /** - * The qualifier class of the receiver that causes the {@code result} + * @return the qualifier class of the receiver that causes the {@code result} * qualifier to be applied. */ Class when(); diff --git a/jme3-core/src/main/java/checkers/quals/SubtypeOf.java b/jme3-core/src/main/java/checkers/quals/SubtypeOf.java index 189969c050..4ac0c4ea88 100644 --- a/jme3-core/src/main/java/checkers/quals/SubtypeOf.java +++ b/jme3-core/src/main/java/checkers/quals/SubtypeOf.java @@ -54,6 +54,6 @@ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.ANNOTATION_TYPE) public @interface SubtypeOf { - /** An array of the supertype qualifiers of the annotated qualifier **/ + /** @return supertype qualifiers of the annotated qualifier **/ Class[] value(); } diff --git a/jme3-core/src/main/java/checkers/quals/Unused.java b/jme3-core/src/main/java/checkers/quals/Unused.java index 17a9c8039e..54cdb5f531 100644 --- a/jme3-core/src/main/java/checkers/quals/Unused.java +++ b/jme3-core/src/main/java/checkers/quals/Unused.java @@ -38,6 +38,8 @@ /** * The field that is annotated with @Unused may not be accessed via a * receiver that is annotated with the "when" annotation. + * + * @return the annotation class */ Class when(); } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java index eb00f8c43f..9758c2d718 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimClip.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimClip.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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; import com.jme3.export.*; @@ -7,6 +38,8 @@ import java.io.IOException; /** + * A named set of animation tracks that can be played in synchrony. + * * Created by Nehon on 20/12/2017. */ public class AnimClip implements JmeCloneable, Savable { @@ -16,13 +49,27 @@ public class AnimClip implements JmeCloneable, Savable { private AnimTrack[] tracks; + /** + * No-argument constructor needed by SavableClassUtil. + */ protected AnimClip() { } + /** + * Instantiate a zero-length clip with the specified name. + * + * @param name desired name for the new clip + */ public AnimClip(String name) { this.name = name; } + /** + * Replace all tracks in this clip. This method may increase the clip's + * length, but it will never reduce it. + * + * @param tracks the tracks to use (alias created) + */ public void setTracks(AnimTrack[] tracks) { this.tracks = tracks; for (AnimTrack track : tracks) { @@ -32,20 +79,38 @@ public void setTracks(AnimTrack[] tracks) { } } + /** + * Determine the name of this clip. + * + * @return the name + */ public String getName() { return name; } - + /** + * Determine the duration of this clip. + * + * @return the duration (in seconds) + */ public double getLength() { return length; } - + /** + * Access all the tracks in this clip. + * + * @return the pre-existing array + */ public AnimTrack[] getTracks() { return tracks; } + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ @Override public Object jmeClone() { try { @@ -55,6 +120,15 @@ public Object jmeClone() { } } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned clip into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this clip (not null) + * @param original the instance from which this clip was shallow-cloned (not + * null, unaffected) + */ @Override public void cloneFields(Cloner cloner, Object original) { AnimTrack[] newTracks = new AnimTrack[tracks.length]; @@ -64,11 +138,23 @@ public void cloneFields(Cloner cloner, Object original) { this.tracks = newTracks; } + /** + * Represent this clip as a String. + * + * @return a descriptive string of text (not null, not empty) + */ @Override public String toString() { return "Clip " + name + ", " + length + 's'; } + /** + * Serialize this clip to the specified exporter, for example when saving to + * a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); @@ -77,6 +163,13 @@ public void write(JmeExporter ex) throws IOException { } + /** + * De-serialize this clip from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to read from (not null) + * @throws IOException from the importer + */ @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); 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 740c4e034e..4a47008542 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimComposer.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2025 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; import com.jme3.anim.tween.Tween; @@ -11,27 +42,40 @@ import com.jme3.renderer.ViewPort; import com.jme3.scene.control.AbstractControl; import com.jme3.util.clone.Cloner; -import com.jme3.util.clone.JmeCloneable; - import java.io.IOException; import java.util.*; /** - * Created by Nehon on 20/12/2017. + * AnimComposer is a Spatial control that allows manipulation of + * {@link Armature armature} (skeletal) animation. + * + * @author Nehon */ public class AnimComposer extends AbstractControl { + /** + * The name of the default layer. + */ public static final String DEFAULT_LAYER = "Default"; private Map animClipMap = new HashMap<>(); private Map actions = new HashMap<>(); private float globalSpeed = 1f; - private Map layers = new LinkedHashMap<>(); + private Map layers = new LinkedHashMap<>(4); + /** + * Instantiate a composer with a single layer, no actions, and no clips. + */ public AnimComposer() { - layers.put(DEFAULT_LAYER, new Layer(this)); + layers.put(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); } + /** + * Tells if an animation is contained in the list of animations. + * + * @param name The name of the animation. + * @return true, if the named animation is in the list of animations. + */ public boolean hasAnimClip(String name) { return animClipMap.containsKey(name); } @@ -71,78 +115,133 @@ public void removeAnimClip(AnimClip anim) { animClipMap.remove(anim.getName()); } + /** + * 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. + */ public Action setCurrentAction(String name) { - return setCurrentAction(name, DEFAULT_LAYER); + return setCurrentAction(name, DEFAULT_LAYER, true); } - + /** - * 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) { - Layer l = layers.get(layerName); - if (l == null) { - throw new IllegalArgumentException("Unknown layer " + 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 layer = getLayer(layerName); Action currentAction = action(actionName); - l.time = 0; - l.currentAction = currentAction; + layer.setCurrentAction(actionName, currentAction, loop); + return currentAction; } - + + /** + * Return the current action on the default layer. + * + * @return The action corresponding to the given name. + */ + public Action getCurrentAction() { + return getCurrentAction(DEFAULT_LAYER); + } + + /** + * Return current action on specified layer. + * + * @param layerName The layer on which action should run. + * @return The action corresponding to the given name. + */ + public Action getCurrentAction(String layerName) { + AnimLayer l = getLayer(layerName); + Action result = l.getCurrentAction(); + + return result; + } + + /** + * Remove current action on default layer. + */ + public void removeCurrentAction() { + removeCurrentAction(DEFAULT_LAYER); + } + /** * Remove current action on specified layer. * * @param layerName The name of the layer we want to remove its action. */ public void removeCurrentAction(String layerName) { - Layer l = layers.get(layerName); - if (l == null) { - throw new IllegalArgumentException("Unknown layer " + layerName); - } - - l.time = 0; - l.currentAction = null; + AnimLayer l = getLayer(layerName); + l.setCurrentAction(null); } - + + /** + * Returns current time of the default layer. + * + * @return The current time. + */ + public double getTime() { + return getTime(DEFAULT_LAYER); + } + /** * Returns current time of the specified layer. + * + * @param layerName The layer from which to get the time. + * @return the time (in seconds) */ public double getTime(String layerName) { - Layer l = layers.get(layerName); - if (l == null) { - throw new IllegalArgumentException("Unknown layer " + layerName); - } - return l.time; + AnimLayer l = getLayer(layerName); + double result = l.getTime(); + + return result; + } + + /** + * Sets current time on the default layer. + * + * @param time the desired time (in seconds) + */ + public void setTime(double time) { + setTime(DEFAULT_LAYER, time); } /** - * Sets current time on the specified layer. + * Sets current time on the specified layer. + * + * @param layerName the name of the Layer to modify + * @param time the desired time (in seconds) */ public void setTime(String layerName, double time) { - Layer l = layers.get(layerName); - if (l == null) { - throw new IllegalArgumentException("Unknown layer " + layerName); - } - if (l.currentAction == null) { + AnimLayer l = getLayer(layerName); + if (l.getCurrentAction() == null) { throw new RuntimeException("There is no action running in layer " + layerName); } - double length = l.currentAction.getLength(); - if (time >= 0) { - l.time = time % length; - } else { - l.time = time % length + length; - } + + l.setTime(time); } /** - * + * * @param name The name of the action to return. - * @return The action registered with specified name. It will make a new action if there isn't any. + * @return The action registered with specified name. It will make a new + * action if there isn't any. * @see #makeAction(java.lang.String) */ public Action action(String name) { @@ -153,29 +252,30 @@ public Action action(String name) { } return action; } - + /** - * + * * @param name The name of the action to return. - * @return The action registered with specified name or null if nothing is registered. + * @return The action registered with specified name or null if nothing is + * registered. */ - public Action getAction(String name){ + public Action getAction(String name) { return actions.get(name); } - + /** * Register given action with specified name. - * + * * @param name The name of the action. * @param action The action to add. */ - public void addAction(String name, Action action){ + public void addAction(String name, Action action) { actions.put(name, action); } /** * Create a new ClipAction with specified clip name. - * + * * @param name The name of the clip. * @return a new action * @throws IllegalArgumentException if clip with specified name not found. @@ -189,11 +289,17 @@ public Action makeAction(String name) { action = new ClipAction(clip); return action; } - + + /** + * Tells if an action is contained in the list of actions. + * + * @param name The name of the action. + * @return true, if the named action is in the list of actions. + */ public boolean hasAction(String name) { return actions.containsKey(name); } - + /** * Remove specified action. * @@ -204,9 +310,14 @@ public Action removeAction(String name) { return actions.remove(name); } + /** + * Add a layer to this composer. + * + * @param name the desired name for the new layer + * @param mask the desired mask for the new layer (alias created) + */ public void makeLayer(String name, AnimationMask mask) { - Layer l = new Layer(this); - l.mask = mask; + AnimLayer l = new AnimLayer(name, mask); layers.put(name, l); } @@ -219,12 +330,29 @@ public void removeLayer(String name) { layers.remove(name); } + /** + * Creates an action that will interpolate over an entire sequence of tweens + * in order. + * + * @param name a name for the new Action + * @param tweens the desired sequence of tweens + * @return a new instance + */ public BaseAction actionSequence(String name, Tween... tweens) { BaseAction action = new BaseAction(Tweens.sequence(tweens)); actions.put(name, action); return action; } + /** + * Creates an action that blends the named clips using the given blend + * space. + * + * @param name a name for the new Action + * @param blendSpace how to blend the clips (not null, alias created) + * @param clips the names of the clips to be used (not null) + * @return a new instance + */ public BlendAction actionBlended(String name, BlendSpace blendSpace, String... clips) { BlendableAction[] acts = new BlendableAction[clips.length]; for (int i = 0; i < acts.length; i++) { @@ -236,16 +364,19 @@ public BlendAction actionBlended(String name, BlendSpace blendSpace, String... c return action; } + /** + * Reset all layers to t=0 with no current action. + */ public void reset() { - for (Layer layer : layers.values()) { - layer.currentAction = null; - layer.time = 0; + for (AnimLayer layer : layers.values()) { + layer.setCurrentAction(null); } } /** - * Returns an unmodifiable collection of all available animations. When an attempt - * is made to modify the collection, an UnsupportedOperationException is thrown. + * Returns an unmodifiable collection of all available animations. When an + * attempt is made to modify the collection, an + * UnsupportedOperationException is thrown. * * @return the unmodifiable collection of animations */ @@ -264,38 +395,101 @@ public Set getAnimClipsNames() { return Collections.unmodifiableSet(animClipMap.keySet()); } + /** + * used internally + * + * @param tpf time per frame (in seconds) + */ @Override protected void controlUpdate(float tpf) { - for (Layer layer : layers.values()) { - Action currentAction = layer.currentAction; - if (currentAction == null) { - continue; - } - layer.advance(tpf); - - currentAction.setMask(layer.mask); - boolean running = currentAction.interpolate(layer.time); - currentAction.setMask(null); - - if (!running) { - layer.time = 0; - } + for (AnimLayer layer : layers.values()) { + layer.update(tpf, globalSpeed); } } + /** + * used internally + * + * @param rm the RenderManager rendering the controlled Spatial (not null) + * @param vp the ViewPort being rendered (not null) + */ @Override protected void controlRender(RenderManager rm, ViewPort vp) { } + /** + * Determine the global speed applied to all layers. + * + * @return the speed factor (1=normal speed) + */ public float getGlobalSpeed() { return globalSpeed; } + /** + * Alter the global speed applied to all layers. + * + * @param globalSpeed the desired speed factor (1=normal speed, default=1) + */ public void setGlobalSpeed(float globalSpeed) { this.globalSpeed = globalSpeed; } + /** + * Provides access to the named layer. + * + * @param layerName the name of the layer to access + * @return the pre-existing instance + */ + public AnimLayer getLayer(String layerName) { + AnimLayer result = layers.get(layerName); + if (result == null) { + throw new IllegalArgumentException("Unknown layer " + layerName); + } + return result; + } + + /** + * Access the manager of the named layer. + * + * @param layerName the name of the layer to access + * @return the current manager (typically an AnimEvent) or null for none + */ + public Object getLayerManager(String layerName) { + AnimLayer layer = getLayer(layerName); + Object result = layer.getManager(); + + return result; + } + + /** + * Enumerates the names of all layers. + * + * @return an unmodifiable set of names + */ + public Set getLayerNames() { + Set result = Collections.unmodifiableSet(layers.keySet()); + return result; + } + + /** + * Assign a manager to the named layer. + * + * @param layerName the name of the layer to modify + * @param manager the desired manager (typically an AnimEvent) or null for + * none + */ + public void setLayerManager(String layerName, Object manager) { + AnimLayer layer = getLayer(layerName); + layer.setManager(manager); + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ @Override public Object jmeClone() { try { @@ -306,6 +500,15 @@ public Object jmeClone() { } } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned composer into a deep-cloned one, using the specified + * Cloner and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this composer (not null) + * @param original the instance from which this composer was shallow-cloned + * (not null, unaffected) + */ @Override public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); @@ -320,66 +523,45 @@ public void cloneFields(Cloner cloner, Object original) { actions = act; animClipMap = clips; - Map newLayers = new LinkedHashMap<>(); + Map newLayers = new LinkedHashMap<>(); for (String key : layers.keySet()) { newLayers.put(key, cloner.clone(layers.get(key))); } - + newLayers.putIfAbsent(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); layers = newLayers; - } + /** + * De-serialize this composer from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to use (not null) + * @throws IOException from the importer + */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); animClipMap = (Map) ic.readStringSavableMap("animClipMap", new HashMap()); globalSpeed = ic.readFloat("globalSpeed", 1f); + layers = (Map) ic.readStringSavableMap("layers", new HashMap()); + layers.putIfAbsent(DEFAULT_LAYER, new AnimLayer(DEFAULT_LAYER, null)); } + /** + * Serialize this composer to the specified exporter, for example when + * saving to a J3O file. + * + * @param ex the exporter to use (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.writeStringSavableMap(animClipMap, "animClipMap", new HashMap()); oc.write(globalSpeed, "globalSpeed", 1f); - } - - private static class Layer implements JmeCloneable { - private AnimComposer ac; - private Action currentAction; - private AnimationMask mask; - private float weight; - private double time; - - public Layer(AnimComposer ac) { - this.ac = ac; - } - - public void advance(float tpf) { - time += tpf * currentAction.getSpeed() * ac.globalSpeed; - // make sure negative time is in [0, length] range - if (time < 0) { - double length = currentAction.getLength(); - time = (time % length + length) % length; - } - - } - - @Override - public Object jmeClone() { - try { - Layer clone = (Layer) super.clone(); - return clone; - } catch (CloneNotSupportedException ex) { - throw new AssertionError(); - } - } - - @Override - public void cloneFields(Cloner cloner, Object original) { - ac = cloner.clone(ac); - currentAction = null; - } + oc.writeStringSavableMap(layers, "layers", new HashMap()); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimFactory.java b/jme3-core/src/main/java/com/jme3/anim/AnimFactory.java new file mode 100644 index 0000000000..38af4cf927 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimFactory.java @@ -0,0 +1,421 @@ +/* + * Copyright (c) 2009-2021 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; + +import com.jme3.anim.util.HasLocalTransform; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Vector3f; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +/** + * A convenience class to smoothly animate a Spatial using translation, + * rotation, and scaling. + * + * Add keyframes for translation, rotation, and scaling. Invoking + * {@link #buildAnimation(com.jme3.anim.util.HasLocalTransform)} will then + * generate an AnimClip that interpolates among the keyframes. + * + * By default, the first keyframe (index=0) has an identity Transform. You can + * override this by replacing the first keyframe with different Transform. + * + * For a loop animation, make sure the final transform matches the starting one. + * Because of a heuristic used by + * {@link com.jme3.math.Quaternion#slerp(com.jme3.math.Quaternion, com.jme3.math.Quaternion, float)}, + * it's possible for + * {@link #buildAnimation(com.jme3.anim.util.HasLocalTransform)} to negate the + * final rotation. To prevent an unwanted rotation at the end of the loop, you + * may need to add intermediate rotation keyframes. + * + * Inspired by Nehon's {@link com.jme3.animation.AnimationFactory}. + */ +public class AnimFactory { + + /** + * clip duration (in seconds) + */ + final private float duration; + /** + * frame/sample rate for the clip (in frames per second) + */ + final private float fps; + /** + * rotations that define the clip + */ + final private Map rotations = new TreeMap<>(); + /** + * scales that define the clip + */ + final private Map scales = new TreeMap<>(); + /** + * translations that define the clip + */ + final private Map translations = new TreeMap<>(); + /** + * name for the resulting clip + */ + final private String name; + + /** + * Instantiate an AnimFactory with an identity transform at t=0. + * + * @param duration the duration for the clip (in seconds, >0) + * @param name the name for the resulting clip + * @param fps the frame rate for the clip (in frames per second, >0) + */ + public AnimFactory(float duration, String name, float fps) { + if (!(duration > 0f)) { + throw new IllegalArgumentException("duration must be positive"); + } + if (!(fps > 0f)) { + throw new IllegalArgumentException("FPS must be positive"); + } + + this.name = name; + this.duration = duration; + this.fps = fps; + /* + * Add the initial Transform. + */ + Transform transform = new Transform(); + translations.put(0f, transform.getTranslation()); + rotations.put(0f, transform.getRotation()); + scales.put(0f, transform.getScale()); + } + + /** + * Add a keyframe for the specified rotation at the specified index. + * + * @param keyFrameIndex the keyframe in which full rotation should be + * achieved (≥0) + * @param rotation the local rotation to apply to the target (not null, + * non-zero norm, unaffected) + */ + public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) { + float animationTime = keyFrameIndex / fps; + addTimeRotation(animationTime, rotation); + } + + /** + * Add a keyframe for the specified scaling at the specified index. + * + * @param keyFrameIndex the keyframe in which full scaling should be + * achieved (≥0) + * @param scale the local scaling to apply to the target (not null, + * unaffected) + */ + public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) { + float animationTime = keyFrameIndex / fps; + addTimeScale(animationTime, scale); + } + + /** + * Add a keyframe for the specified Transform at the specified index. + * + * @param keyFrameIndex the keyframe in which the full Transform should be + * achieved (≥0) + * @param transform the local Transform to apply to the target (not null, + * unaffected) + */ + public void addKeyFrameTransform(int keyFrameIndex, Transform transform) { + float time = keyFrameIndex / fps; + addTimeTransform(time, transform); + } + + /** + * Add a keyframe for the specified translation at the specified index. + * + * @param keyFrameIndex the keyframe in which full translation should be + * achieved (≥0) + * @param offset the local translation to apply to the target (not null, + * unaffected) + */ + public void addKeyFrameTranslation(int keyFrameIndex, Vector3f offset) { + float time = keyFrameIndex / fps; + addTimeTranslation(time, offset); + } + + /** + * Add a keyframe for the specified rotation at the specified time. + * + * @param time the animation time when full rotation should be achieved + * (≥0, ≤duration) + * @param rotation the local rotation to apply to the target (not null, + * non-zero norm, unaffected) + */ + public void addTimeRotation(float time, Quaternion rotation) { + if (!(time >= 0f && time <= duration)) { + throw new IllegalArgumentException("animation time out of range"); + } + float norm = rotation.norm(); + if (norm == 0f) { + throw new IllegalArgumentException("rotation cannot have norm=0"); + } + + float normalizingFactor = 1f / FastMath.sqrt(norm); + Quaternion normalized = rotation.mult(normalizingFactor); + rotations.put(time, normalized); + } + + /** + * Add a keyframe for the specified rotation at the specified time, based on + * Tait-Bryan angles. Note that this is NOT equivalent to + * {@link com.jme3.animation.AnimationFactory#addTimeRotationAngles(float, float, float, float)}. + * + * @param time the animation time when full rotation should be achieved + * (≥0, ≤duration) + * @param xAngle the X angle (in radians) + * @param yAngle the Y angle (in radians) + * @param zAngle the Z angle (in radians) + */ + public void addTimeRotation(float time, float xAngle, float yAngle, + float zAngle) { + if (!(time >= 0f && time <= duration)) { + throw new IllegalArgumentException("animation time out of range"); + } + + Quaternion quat = new Quaternion().fromAngles(xAngle, yAngle, zAngle); + rotations.put(time, quat); + } + + /** + * Add a keyframe for the specified scale at the specified time. + * + * @param time the animation time when full scaling should be achieved + * (≥0, ≤duration) + * @param scale the local scaling to apply to the target (not null, + * unaffected) + */ + public void addTimeScale(float time, Vector3f scale) { + if (!(time >= 0f && time <= duration)) { + throw new IllegalArgumentException("animation time out of range"); + } + + Vector3f clone = scale.clone(); + scales.put(time, clone); + } + + /** + * Add a keyframe for the specified Transform at the specified time. + * + * @param time the animation time when the full Transform should be achieved + * (≥0, ≤duration) + * @param transform the local Transform to apply to the target (not null, + * unaffected) + */ + public void addTimeTransform(float time, Transform transform) { + if (!(time >= 0f && time <= duration)) { + throw new IllegalArgumentException("animation time out of range"); + } + + Vector3f translation = transform.getTranslation(null); + translations.put(time, translation); + rotations.put(time, transform.getRotation(null)); + scales.put(time, transform.getScale(null)); + } + + /** + * Add a keyframe for the specified translation at the specified time. + * + * @param time the animation time when the full translation should be + * achieved (≥0, ≤duration) + * @param offset the local translation to apply to the target (not null, + * unaffected) + */ + public void addTimeTranslation(float time, Vector3f offset) { + if (!(time >= 0f && time <= duration)) { + throw new IllegalArgumentException("animation time out of range"); + } + + Vector3f clone = offset.clone(); + translations.put(time, clone); + } + + /** + * Create an AnimClip based on the keyframes added to this factory. + * + * @param target the target for this clip (which is typically a Spatial) + * @return a new clip + */ + public AnimClip buildAnimation(HasLocalTransform target) { + Set times = new TreeSet<>(); + for (int frameI = 0;; ++frameI) { + float time = frameI / fps; + if (time > duration) { + break; + } + times.add(time); + } + times.addAll(rotations.keySet()); + times.addAll(scales.keySet()); + times.addAll(translations.keySet()); + + int numFrames = times.size(); + float[] timeArray = new float[numFrames]; + Vector3f[] translateArray = new Vector3f[numFrames]; + Quaternion[] rotArray = new Quaternion[numFrames]; + Vector3f[] scaleArray = new Vector3f[numFrames]; + + int iFrame = 0; + for (float time : times) { + timeArray[iFrame] = time; + translateArray[iFrame] = interpolateTranslation(time); + rotArray[iFrame] = interpolateRotation(time); + scaleArray[iFrame] = interpolateScale(time); + + ++iFrame; + } + + AnimTrack[] tracks = new AnimTrack[1]; + tracks[0] = new TransformTrack(target, timeArray, translateArray, + rotArray, scaleArray); + AnimClip result = new AnimClip(name); + result.setTracks(tracks); + + return result; + } + + /** + * Interpolate successive rotation keyframes for the specified time. + * + * @param keyFrameTime the animation time (in seconds, ≥0) + * @return a new instance + */ + private Quaternion interpolateRotation(float keyFrameTime) { + assert keyFrameTime >= 0f && keyFrameTime <= duration; + + float prev = 0f; + float next = duration; + for (float key : rotations.keySet()) { + if (key <= keyFrameTime && key > prev) { + prev = key; + } + if (key >= keyFrameTime && key < next) { + next = key; + } + } + assert prev <= next; + Quaternion prevRotation = rotations.get(prev); + + Quaternion result = new Quaternion(); + if (prev == next || !rotations.containsKey(next)) { + result.set(prevRotation); + + } else { // interpolate + float fraction = (keyFrameTime - prev) / (next - prev); + assert fraction >= 0f && fraction <= 1f; + Quaternion nextRotation = rotations.get(next); + result.slerp(prevRotation, nextRotation, fraction); + /* + * XXX slerp() sometimes negates nextRotation, + * but usually that's okay because nextRotation and its negative + * both represent the same rotation. + */ + } + + return result; + } + + /** + * Interpolate successive scale keyframes for the specified time. + * + * @param keyFrameTime the animation time (in seconds, ≥0) + * @return a new instance + */ + private Vector3f interpolateScale(float keyFrameTime) { + assert keyFrameTime >= 0f && keyFrameTime <= duration; + + float prev = 0f; + float next = duration; + for (float key : scales.keySet()) { + if (key <= keyFrameTime && key > prev) { + prev = key; + } + if (key >= keyFrameTime && key < next) { + next = key; + } + } + assert prev <= next; + Vector3f prevScale = scales.get(prev); + + Vector3f result = new Vector3f(); + if (prev == next || !scales.containsKey(next)) { + result.set(prevScale); + + } else { // interpolate + float fraction = (keyFrameTime - prev) / (next - prev); + assert fraction >= 0f && fraction <= 1f; + Vector3f nextScale = scales.get(next); + result.interpolateLocal(prevScale, nextScale, fraction); + } + + return result; + } + + /** + * Interpolate successive translation keyframes for the specified time. + * + * @param keyFrameTime the animation time (in seconds, ≥0) + * @return a new instance + */ + private Vector3f interpolateTranslation(float keyFrameTime) { + float prev = 0f; + float next = duration; + for (float key : translations.keySet()) { + if (key <= keyFrameTime && key > prev) { + prev = key; + } + if (key >= keyFrameTime && key < next) { + next = key; + } + } + assert prev <= next; + Vector3f prevTranslation = translations.get(prev); + + Vector3f result = new Vector3f(); + if (prev == next || !translations.containsKey(next)) { + result.set(prevTranslation); + + } else { // interpolate + float fraction = (keyFrameTime - prev) / (next - prev); + assert fraction >= 0f && fraction <= 1f; + Vector3f nextTranslation = translations.get(next); + result.interpolateLocal(prevTranslation, nextTranslation, fraction); + } + + return result; + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java new file mode 100644 index 0000000000..6cd79e731d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/AnimLayer.java @@ -0,0 +1,330 @@ +/* + * 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; + +import com.jme3.anim.tween.action.Action; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; +import java.io.IOException; + +/** + * A named portion of an AnimComposer that can run (at most) one Action at a + * time. + * + *

    A composer with multiple layers can run multiple actions simultaneously. + * For instance, one layer could run a "wave" action on the model's upper body + * while another ran a "walk" action on the model's lower body. + * + *

    A layer cannot be shared between multiple composers. + * + *

    Animation time may advance at a different rate from application time, + * based on speedup factors in the composer and the current Action. + */ +public class AnimLayer implements JmeCloneable, Savable { + /** + * 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; + + /** + * Limits the portion of the model animated by this layer. If null, this + * layer can animate the entire model. + */ + private AnimationMask mask; + /** + * The current animation time, in scaled seconds. Always non-negative. + */ + private double time; + /** + * The software object (such as an AnimEvent) that currently controls this + * layer, or null if unknown. + */ + private Object manager; + /** + * The name of this layer. + */ + private String name; + + private boolean loop = true; + + /** + * For serialization only. Do not use. + */ + protected AnimLayer() { + + } + + /** + * Instantiates a layer without a manager or a current Action, owned by the + * specified composer. + * + * @param name the layer name (not null) + * @param mask the AnimationMask (alias created) or null to allow this layer + * to animate the entire model + */ + AnimLayer(String name, AnimationMask mask) { + assert name != null; + this.name = name; + + this.mask = mask; + } + + /** + * Returns the Action that's currently running. + * + * @return the pre-existing instance, or null if none + */ + 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. + * + * @return the pre-existing object (such as an AnimEvent) or null for + * unknown + */ + public Object getManager() { + return manager; + } + + /** + * Returns the animation mask. + * + * @return the pre-existing instance, or null if this layer can animate the + * entire model + */ + public AnimationMask getMask() { + return mask; + } + + /** + * Returns the layer name. + * + * @return the name of this layer + */ + public String getName() { + return name; + } + + /** + * Returns the animation time, in scaled seconds. + * + * @return the current animation time (not negative) + */ + public double getTime() { + return time; + } + + /** + * Runs the specified Action, starting from time = 0. This cancels any + * 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; + } + + /** + * Assigns the specified manager. This cancels any manager previously + * assigned. + * + * @param manager the desired manager (such as an AnimEvent, alias created) + * or null for unknown manager + */ + public void setManager(Object manager) { + this.manager = manager; + } + + /** + * Changes the animation time, wrapping the specified time to fit the + * current Action. An Action must be running. + * + * @param animationTime the desired time (in scaled seconds) + */ + public void setTime(double animationTime) { + double length = currentAction.getLength(); + if (animationTime >= 0.0) { + time = animationTime % length; + } else { + time = (animationTime % length) + length; + } + } + + /** + * @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(). + * + * @param appDeltaTimeInSeconds the amount application time to advance the + * current Action, in seconds + * @param globalSpeed the global speed applied to all layers. + */ + void update(float appDeltaTimeInSeconds, float globalSpeed) { + Action action = currentAction; + if (action == null) { + return; + } + + double speedup = action.getSpeed() * globalSpeed; + double scaledDeltaTime = speedup * appDeltaTimeInSeconds; + time += scaledDeltaTime; + + // wrap negative times to the [0, length] range: + if (time < 0.0) { + double length = action.getLength(); + time = (time % length + length) % length; + } + + // update the current Action, filtered by this layer's mask: + action.setMask(mask); + boolean running = action.interpolate(time); + action.setMask(null); + + if (!running) { // went past the end of the current Action + time = 0.0; + if (!loop) { + // Clear the current action + setCurrentAction(null); + } + } + } + + /** + * Converts this shallow-cloned layer into a deep-cloned one, using the + * specified Cloner and original to resolve copied fields. + * + *

    The clone's current Action gets nulled out. Its manager and mask get + * aliased to the original's manager and mask. + * + * @param cloner the Cloner that's cloning this layer (not null) + * @param original the instance from which this layer was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + currentAction = null; + currentActionName = null; + } + + @Override + public Object jmeClone() { + try { + AnimLayer clone = (AnimLayer) super.clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new AssertionError(); + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(name, "name", null); + if (mask instanceof Savable) { + oc.write((Savable) mask, "mask", null); + } + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + name = ic.readString("name", null); + mask = (AnimationMask) ic.readSavable("mask", null); + } +} diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java b/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java index 45b54cf7f9..4c00902016 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimTrack.java @@ -3,10 +3,25 @@ import com.jme3.export.Savable; import com.jme3.util.clone.JmeCloneable; +/** + * Interface to derive animation data from a track. + * + * @param the type of data that's being animated, such as Transform + */ public interface AnimTrack extends Savable, JmeCloneable { + /** + * Determine the track value for the specified time. + * + * @param time the track time (in seconds) + * @param store storage for the value (not null, modified) + */ public void getDataAtTime(double time, T store); - public double getLength(); - + /** + * Determine the duration of the track. + * + * @return the duration (in seconds, ≥0) + */ + public double getLength(); } diff --git a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java index 2aa8328b62..bcfd77f0a1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/AnimationMask.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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; /** @@ -7,6 +38,12 @@ */ public interface AnimationMask { + /** + * Test whether the animation should be applied to the specified element. + * + * @param target the target element + * @return true if animation should be applied, otherwise false + */ boolean contains(Object target); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/anim/Armature.java b/jme3-core/src/main/java/com/jme3/anim/Armature.java index 2443702547..197242cfa1 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Armature.java +++ b/jme3-core/src/main/java/com/jme3/anim/Armature.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim; import com.jme3.anim.util.JointModelTransform; @@ -6,8 +37,8 @@ import com.jme3.math.Matrix4f; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; - import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.*; /** @@ -47,7 +78,7 @@ public Armature(Joint[] jointList) { for (int i = jointList.length - 1; i >= 0; i--) { Joint joint = jointList[i]; joint.setId(i); - instanciateJointModelTransform(joint); + instantiateJointModelTransform(joint); if (joint.getParent() == null) { rootJointList.add(joint); } @@ -80,9 +111,9 @@ private void createSkinningMatrices() { /** * Sets the JointModelTransform implementation - * Default is {@link MatrixJointModelTransform} + * Default is {@link SeparateJointModelTransform} * - * @param modelTransformClass + * @param modelTransformClass which implementation to use * @see JointModelTransform * @see MatrixJointModelTransform * @see SeparateJointModelTransform @@ -93,14 +124,16 @@ public void setModelTransformClass(Class modelTra return; } for (Joint joint : jointList) { - instanciateJointModelTransform(joint); + instantiateJointModelTransform(joint); } } - private void instanciateJointModelTransform(Joint joint) { + private void instantiateJointModelTransform(Joint joint) { try { - joint.setJointModelTransform(modelTransformClass.newInstance()); - } catch (InstantiationException | IllegalAccessException e) { + joint.setJointModelTransform(modelTransformClass.getDeclaredConstructor().newInstance()); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException(e); } } @@ -114,6 +147,11 @@ public Joint[] getRoots() { return rootJoints; } + /** + * Access all joints in this Armature. + * + * @return a new list of pre-existing joints + */ public List getJointList() { return Arrays.asList(jointList); } @@ -121,7 +159,7 @@ public List getJointList() { /** * return a joint for the given index * - * @param index + * @param index a zero-based joint index (≥0) * @return the pre-existing instance */ public Joint getJoint(int index) { @@ -131,7 +169,7 @@ public Joint getJoint(int index) { /** * returns the joint with the given name * - * @param name + * @param name the name to search for * @return the pre-existing instance or null if not found */ public Joint getJoint(String name) { @@ -146,7 +184,7 @@ public Joint getJoint(String name) { /** * returns the bone index of the given bone * - * @param joint + * @param joint the Joint to search for * @return the index (≥0) or -1 if not found */ public int getJointIndex(Joint joint) { @@ -162,7 +200,7 @@ public int getJointIndex(Joint joint) { /** * returns the joint index of the joint that has the given name * - * @param name + * @param name the name to search for * @return the index (≥0) or -1 if not found */ public int getJointIndex(String name) { @@ -190,7 +228,7 @@ public void saveBindPose() { } /** - * This methods sets this armature in its bind pose (aligned with the mesh to deform) + * This method sets this armature to its bind pose (aligned with the mesh to deform). * Note that this is only useful for debugging purpose. */ public void applyBindPose() { @@ -255,12 +293,19 @@ public void cloneFields(Cloner cloner, Object original) { this.jointList = cloner.clone(jointList); this.skinningMatrixes = cloner.clone(skinningMatrixes); for (Joint joint : jointList) { - instanciateJointModelTransform(joint); + instantiateJointModelTransform(joint); } } - + /** + * De-serialize this Armature from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to read from (not null) + * @throws IOException from the importer + */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule input = im.getCapsule(this); @@ -282,7 +327,7 @@ public void read(JmeImporter im) throws IOException { int i = 0; for (Joint joint : jointList) { joint.setId(i++); - instanciateJointModelTransform(joint); + instantiateJointModelTransform(joint); } createSkinningMatrices(); @@ -292,6 +337,13 @@ public void read(JmeImporter im) throws IOException { applyInitialPose(); } + /** + * Serialize this Armature to the specified exporter, for example when + * saving to a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { OutputCapsule output = ex.getCapsule(this); diff --git a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java index b492eb4ed7..bf8e10155d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java +++ b/jme3-core/src/main/java/com/jme3/anim/ArmatureMask.java @@ -1,33 +1,151 @@ +/* + * 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.anim; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import java.io.IOException; import java.util.BitSet; -public class ArmatureMask implements AnimationMask { +/** + * An AnimationMask to select joints from a single Armature. + */ +public class ArmatureMask implements AnimationMask, Savable { private BitSet affectedJoints = new BitSet(); + /** + * Instantiate a mask that affects no joints. + */ + public ArmatureMask() { + // do nothing + } + + /** + * Instantiate a mask that affects all joints in the specified Armature. + * + * @param armature the Armature containing the joints (not null, unaffected) + */ + public ArmatureMask(Armature armature) { + int numJoints = armature.getJointCount(); + affectedJoints.set(0, numJoints); + } + + /** + * Remove all joints affected by the specified ArmatureMask. + * + * @param removeMask the set of joints to remove (not null, unaffected) + * @return this + */ + public ArmatureMask remove(ArmatureMask removeMask) { + BitSet removeBits = removeMask.getAffectedJoints(); + affectedJoints.andNot(removeBits); + + return this; + } + + private BitSet getAffectedJoints() { + return affectedJoints; + } + + /** + * Remove the named joints. + * + * @param armature the Armature containing the joints (not null, unaffected) + * @param jointNames the names of the joints to be removed + * @return this + * + * @throws IllegalArgumentException if it can not find the joint + * with the specified name on the provided armature + */ + public ArmatureMask removeJoints(Armature armature, String... jointNames) { + for (String jointName : jointNames) { + Joint joint = findJoint(armature, jointName); + int jointId = joint.getId(); + affectedJoints.clear(jointId); + } + + return this; + } + @Override public boolean contains(Object target) { return affectedJoints.get(((Joint) target).getId()); } + /** + * Create an ArmatureMask that selects the named Joint and all its + * descendants. + * + * @param armature the Armature containing the joints (not null) + * @param fromJoint the name of the ancestor joint + * @return a new mask + * + * @throws IllegalArgumentException if it can not find the joint + * with the specified name on the provided armature + */ public static ArmatureMask createMask(Armature armature, String fromJoint) { ArmatureMask mask = new ArmatureMask(); mask.addFromJoint(armature, fromJoint); return mask; } + /** + * Create an ArmatureMask that selects the named joints. + * + * @param armature the Armature containing the joints (not null) + * @param joints the names of the joints to be included + * @return a new mask + * + * @throws IllegalArgumentException if it can not find the joint + * with the specified name on the provided armature + */ public static ArmatureMask createMask(Armature armature, String... joints) { ArmatureMask mask = new ArmatureMask(); mask.addBones(armature, joints); - for (String joint : joints) { - mask.affectedJoints.set(armature.getJoint(joint).getId()); - } return mask; } /** * Add joints to be influenced by this animation mask. + * + * @param armature the Armature containing the joints + * @param jointNames the names of the joints to be influenced + * + * @throws IllegalArgumentException if it can not find the joint + * with the specified name on the provided armature */ public void addBones(Armature armature, String... jointNames) { for (String jointName : jointNames) { @@ -46,6 +164,12 @@ private Joint findJoint(Armature armature, String jointName) { /** * Add a joint and all its sub armature joints to be influenced by this animation mask. + * + * @param armature the Armature containing the ancestor joint + * @param jointName the names of the ancestor joint + * + * @throws IllegalArgumentException if it can not find the joint + * with the specified name on the provided armature */ public void addFromJoint(Armature armature, String jointName) { Joint joint = findJoint(armature, jointName); @@ -59,4 +183,45 @@ private void recurseAddJoint(Joint joint) { } } + /** + * Add the specified Joint and all its ancestors. + * + * @param start the starting point (may be null, unaffected) + * @return this + */ + public ArmatureMask addAncestors(Joint start) { + for (Joint cur = start; cur != null; cur = cur.getParent()) { + int jointId = cur.getId(); + affectedJoints.set(jointId); + } + + return this; + } + + /** + * Remove the specified Joint and all its ancestors. + * + * @param start the starting point (may be null, unaffected) + * @return this + */ + public ArmatureMask removeAncestors(Joint start) { + for (Joint cur = start; cur != null; cur = cur.getParent()) { + int jointId = cur.getId(); + affectedJoints.clear(jointId); + } + + return this; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(affectedJoints, "affectedJoints", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + affectedJoints = ic.readBitSet("affectedJoints", null); + } } diff --git a/jme3-core/src/main/java/com/jme3/anim/Joint.java b/jme3-core/src/main/java/com/jme3/anim/Joint.java index 1c1699f64e..8f2275b202 100644 --- a/jme3-core/src/main/java/com/jme3/anim/Joint.java +++ b/jme3-core/src/main/java/com/jme3/anim/Joint.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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; import com.jme3.anim.util.HasLocalTransform; @@ -57,16 +88,23 @@ public class Joint implements Savable, JmeCloneable, HasLocalTransform { */ private Matrix4f inverseModelBindMatrix = new Matrix4f(); - + /** + * Instantiate a nameless Joint. + */ public Joint() { } + /** + * Instantiate a Joint with the specified name. + * + * @param name the desired name + */ public Joint(String name) { this.name = name; } /** - * Updates world transforms for this bone and it's children. + * Updates world transforms for this bone and its children. */ public final void update() { this.updateModelTransforms(); @@ -77,11 +115,11 @@ public final void update() { } /** - * Updates the model transforms for this bone, and, possibly the attach node + * Updates the model transforms for this bone and for the attachments node * if not null. *

    * The model transform of this bone is computed by combining the parent's - * model transform with this bones' local transform. + * model transform with this bone's local transform. */ public final void updateModelTransforms() { jointModelTransform.updateModelTransform(localTransform, parent); @@ -129,9 +167,9 @@ private void updateAttachNode() { * have already been computed, otherwise this method will return undefined * results. * - * @param outTransform + * @param outTransform storage for the result (modified) */ - void getOffsetTransform(Matrix4f outTransform) { + protected void getOffsetTransform(Matrix4f outTransform) { jointModelTransform.getOffsetTransform(outTransform, inverseModelBindMatrix); } @@ -175,63 +213,139 @@ protected void applyInitialPose() { } } + /** + * Access the accumulated model transform. + * + * @return the pre-existing instance + */ protected JointModelTransform getJointModelTransform() { return jointModelTransform; } + /** + * Replace the accumulated model transform. + * + * @param jointModelTransform the transform to use (alias created) + */ protected void setJointModelTransform(JointModelTransform jointModelTransform) { this.jointModelTransform = jointModelTransform; } + /** + * Access the local translation vector. + * + * @return the pre-existing vector + */ public Vector3f getLocalTranslation() { return localTransform.getTranslation(); } + /** + * Access the local rotation. + * + * @return the pre-existing Quaternion + */ public Quaternion getLocalRotation() { return localTransform.getRotation(); } + /** + * Access the local scale vector. + * + * @return the pre-existing vector + */ public Vector3f getLocalScale() { return localTransform.getScale(); } + /** + * Alter the local translation vector. + * + * @param translation the desired offset vector (not null, unaffected) + */ public void setLocalTranslation(Vector3f translation) { localTransform.setTranslation(translation); } + /** + * Alter the local rotation. + * + * @param rotation the desired rotation (not null, unaffected) + */ public void setLocalRotation(Quaternion rotation) { localTransform.setRotation(rotation); } + /** + * Alter the local scale vector. + * + * @param scale the desired scale factors (not null, unaffected) + */ public void setLocalScale(Vector3f scale) { localTransform.setScale(scale); } + /** + * Add the specified Joint as a child. + * + * @param child the Joint to add (not null, modified) + */ public void addChild(Joint child) { children.add(child); child.parent = this; } + /** + * Alter the name. + * + * @param name the desired name + */ public void setName(String name) { this.name = name; } + /** + * Alter the local transform. + * + * @param localTransform the desired Transform (not null, unaffected) + */ + @Override public void setLocalTransform(Transform localTransform) { this.localTransform.set(localTransform); } + /** + * Replace the inverse model bind matrix. + * + * @param inverseModelBindMatrix the matrix to use (alias created) + */ public void setInverseModelBindMatrix(Matrix4f inverseModelBindMatrix) { this.inverseModelBindMatrix = inverseModelBindMatrix; } + /** + * Determine the name. + * + * @return the name + */ public String getName() { return name; } + /** + * Access the parent joint. + * + * @return the pre-existing instance, or null if this is a root joint + */ public Joint getParent() { return parent; } + /** + * Access the list of child joints. + * + * @return the pre-existing list + */ public List getChildren() { return children; } @@ -244,8 +358,9 @@ public List getChildren() { * @param jointIndex this bone's index in its armature (≥0) * @param targets a list of geometries animated by this bone's skeleton (not * null, unaffected) + * @return the attachments node (not null) */ - Node getAttachmentsNode(int jointIndex, SafeArrayList targets) { + protected Node getAttachmentsNode(int jointIndex, SafeArrayList targets) { targetGeometry = null; /* * Search for a geometry animated by this particular bone. @@ -268,30 +383,66 @@ Node getAttachmentsNode(int jointIndex, SafeArrayList targets) { return attachedNode; } + /** + * Access the initial transform. + * + * @return the pre-existing instance + */ public Transform getInitialTransform() { return initialTransform; } + /** + * Access the local transform. + * + * @return the pre-existing instance + */ + @Override public Transform getLocalTransform() { return localTransform; } + /** + * Determine the model transform. + * + * @return a shared instance + */ public Transform getModelTransform() { return jointModelTransform.getModelTransform(); } + /** + * Determine the inverse model bind matrix. + * + * @return the pre-existing instance + */ public Matrix4f getInverseModelBindMatrix() { return inverseModelBindMatrix; } + /** + * Determine this joint's index in the Armature that contains it. + * + * @return an index (≥0) + */ public int getId() { return id; } + /** + * Alter this joint's index in the Armature that contains it. + * + * @param id the desired index (≥0) + */ public void setId(int id) { this.id = id; } + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ @Override public Object jmeClone() { try { @@ -302,6 +453,15 @@ public Object jmeClone() { } } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned Joint into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this Joint (not null) + * @param original the instance from which this Joint was shallow-cloned + * (not null, unaffected) + */ @Override public void cloneFields(Cloner cloner, Object original) { this.children = cloner.clone(children); @@ -309,10 +469,17 @@ public void cloneFields(Cloner cloner, Object original) { this.attachedNode = cloner.clone(attachedNode); this.targetGeometry = cloner.clone(targetGeometry); this.localTransform = cloner.clone(localTransform); + this.initialTransform = cloner.clone(initialTransform); this.inverseModelBindMatrix = cloner.clone(inverseModelBindMatrix); } - + /** + * De-serialize this Joint from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to use (not null) + * @throws IOException from the importer + */ @Override @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { @@ -330,7 +497,15 @@ public void read(JmeImporter im) throws IOException { } } + /** + * Serialize this Joint to the specified exporter, for example when saving + * to a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override + @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { OutputCapsule output = ex.getCapsule(this); diff --git a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java index 5f1aadad72..2d4fd32809 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/MatrixJointModelTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2025 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; import com.jme3.anim.util.JointModelTransform; @@ -5,23 +36,39 @@ import com.jme3.math.Transform; /** - * This JointModelTransform implementation accumulate joints transforms in a Matrix4f to properly - * support non uniform scaling in an armature hierarchy + * An implementation of {@link JointModelTransform} that accumulates joint transformations + * into a {@link Matrix4f}. This approach is particularly useful for correctly handling + * non-uniform scaling within an armature hierarchy, as {@code Matrix4f} can represent + * non-uniform scaling directly, unlike {@link Transform}, which typically handles + * uniform scaling. + *

    + * This class maintains a single {@link Matrix4f} to represent the accumulated + * model-space transform of the joint it's associated with. */ public class MatrixJointModelTransform implements JointModelTransform { - private Matrix4f modelTransformMatrix = new Matrix4f(); - private Transform modelTransform = new Transform(); + /** + * The model-space transform of the joint represented as a Matrix4f. + * This matrix accumulates the local transform of the joint and the model transform + * of its parent. + */ + private final Matrix4f modelTransformMatrix = new Matrix4f(); + /** + * A temporary Transform instance used for converting the modelTransformMatrix + * to a Transform object when {@link #getModelTransform()} is called. + */ + private final Transform modelTransform = new Transform(); @Override public void updateModelTransform(Transform localTransform, Joint parent) { localTransform.toTransformMatrix(modelTransformMatrix); if (parent != null) { - ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix); + MatrixJointModelTransform transform = (MatrixJointModelTransform) parent.getJointModelTransform(); + transform.getModelTransformMatrix().mult(modelTransformMatrix, modelTransformMatrix); } - } + @Override public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { modelTransformMatrix.mult(inverseModelBindMatrix, outTransform); } @@ -30,11 +77,17 @@ public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindM public void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent) { modelTransformMatrix.set(inverseModelBindMatrix).invertLocal(); // model transform = model bind if (parent != null) { - ((MatrixJointModelTransform) parent.getJointModelTransform()).getModelTransformMatrix().invert().mult(modelTransformMatrix, modelTransformMatrix); + MatrixJointModelTransform transform = (MatrixJointModelTransform) parent.getJointModelTransform(); + transform.getModelTransformMatrix().invert().mult(modelTransformMatrix, modelTransformMatrix); } localTransform.fromTransformMatrix(modelTransformMatrix); } + /** + * Access the model transform. + * + * @return the pre-existing instance + */ public Matrix4f getModelTransformMatrix() { return modelTransformMatrix; } diff --git a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java index 1aa055555b..4c2f0a6c75 100644 --- a/jme3-core/src/main/java/com/jme3/anim/MorphControl.java +++ b/jme3-core/src/main/java/com/jme3/anim/MorphControl.java @@ -1,7 +1,40 @@ +/* + * Copyright (c) 2009-2021 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; -import com.jme3.export.Savable; -import com.jme3.material.*; +import com.jme3.export.*; +import com.jme3.material.MatParam; +import com.jme3.material.MatParamOverride; +import com.jme3.material.Material; import com.jme3.renderer.*; import com.jme3.scene.*; import com.jme3.scene.control.AbstractControl; @@ -9,8 +42,10 @@ import com.jme3.shader.VarType; import com.jme3.util.BufferUtils; import com.jme3.util.SafeArrayList; - +import com.jme3.util.clone.Cloner; +import java.io.IOException; import java.nio.FloatBuffer; +import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; @@ -19,6 +54,9 @@ * All stock shaders only support morphing these 3 buffers, but note that MorphTargets can have any type of buffers. * If you want to use other types of buffers you will need a custom MorphControl and a custom shader. * + * Note that if morphed children are attached to or detached from the sub graph after the MorphControl is added to + * spatial, you must detach and attach the control again for the changes to get reflected. + * * @author Rémy Bouquet */ public class MorphControl extends AbstractControl implements Savable { @@ -28,6 +66,9 @@ public class MorphControl extends AbstractControl implements Savable { private static final int MAX_MORPH_BUFFERS = 14; private final static float MIN_WEIGHT = 0.005f; + private static final String TAG_APPROXIMATE = "approximateTangents"; + private static final String TAG_TARGETS = "targets"; + private SafeArrayList targets = new SafeArrayList<>(Geometry.class); private TargetLocator targetLocator = new TargetLocator(); @@ -41,22 +82,31 @@ public class MorphControl extends AbstractControl implements Savable { private static final VertexBuffer.Type bufferTypes[] = VertexBuffer.Type.values(); @Override - protected void controlUpdate(float tpf) { - if (!enabled) { - return; + public void setSpatial(Spatial spatial) { + super.setSpatial(spatial); + + // Remove matparam override from the old targets + for (Geometry target : targets.getArray()) { + target.removeMatParamOverride(nullNumberOfBones); } + // gathering geometries in the sub graph. - // This must be done in the update phase as the gathering might add a matparam override + // This must not be done in the render phase as the gathering might add a matparam override + // which then will throw an IllegalStateException if done in the render phase. targets.clear(); - this.spatial.depthFirstTraversal(targetLocator); + if (spatial != null) { + spatial.depthFirstTraversal(targetLocator); + } + } + + @Override + protected void controlUpdate(float tpf) { + } @Override protected void controlRender(RenderManager rm, ViewPort vp) { - if (!enabled) { - return; - } - for (Geometry geom : targets) { + for (Geometry geom : targets.getArray()) { Mesh mesh = geom.getMesh(); if (!geom.isDirtyMorph()) { continue; @@ -92,7 +142,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { lastGpuTargetIndex = i; // binding the morph target's buffers to the mesh morph buffers. MorphTarget t = morphTargets[i]; - boundBufferIdx = bindMorphtargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t); + boundBufferIdx = bindMorphTargetBuffer(mesh, targetNumBuffers, boundBufferIdx, t); // setting the weight in the mat param array matWeights[nbGPUTargets] = weights[i]; nbGPUTargets++; @@ -129,7 +179,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { writeCpuBuffer(targetNumBuffers, mt); // binding the merged morph target - bindMorphtargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt); + bindMorphTargetBuffer(mesh, targetNumBuffers, (nbGPUTargets - 1) * targetNumBuffers, mt); // setting the eight of the merged targets matWeights[nbGPUTargets - 1] = cpuWeightSum; @@ -153,7 +203,7 @@ private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int MatParam param = mat.getParam("MorphWeights"); if (param == null) { - // init the mat param if it doesn't exists. + // init the mat param if it doesn't exist. float[] wts = new float[maxGPUTargets]; mat.setParam("MorphWeights", VarType.FloatArray, wts); } @@ -171,18 +221,22 @@ private int getMaxGPUTargets(RenderManager rm, Geometry geom, Material mat, int rm.preloadScene(spatial); compilationOk = true; } catch (RendererException e) { - logger.log(Level.FINE, geom.getName() + ": failed at " + maxGPUTargets); - // the compilation failed let's decrement the number of targets an try again. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "{0}: failed at {1}", new Object[]{geom.getName(), maxGPUTargets}); + } + // the compilation failed let's decrement the number of targets and try again. maxGPUTargets--; } } - logger.log(Level.FINE, geom.getName() + ": " + maxGPUTargets); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "{0}: {1}", new Object[]{geom.getName(), maxGPUTargets}); + } // set the number of GPU morph on the geom to not have to recompute it next frame. geom.setNbSimultaneousGPUMorph(maxGPUTargets); return maxGPUTargets; } - private int bindMorphtargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { + private int bindMorphTargetBuffer(Mesh mesh, int targetNumBuffers, int boundBufferIdx, MorphTarget t) { int start = VertexBuffer.Type.MorphTarget0.ordinal(); if (targetNumBuffers >= 1) { activateBuffer(mesh, boundBufferIdx, start, t.getBuffer(VertexBuffer.Type.Position)); @@ -322,14 +376,94 @@ private int getRemainingBuffers(Mesh mesh, Renderer renderer) { return renderer.getLimits().get(Limits.VertexAttributes) - nbUsedBuffers; } + /** + * Alter whether this Control approximates tangents. + * + * @param approximateTangents true to approximate tangents, false to get + * them from a VertexBuffer + */ public void setApproximateTangents(boolean approximateTangents) { this.approximateTangents = approximateTangents; } + /** + * Test whether this Control approximates tangents. + * + * @return true if approximating tangents, false if getting them from a + * VertexBuffer + */ public boolean isApproximateTangents() { return approximateTangents; } + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned Control into a deep-cloned one, using the specified Cloner + * and original to resolve copied fields. + * + * @param cloner the Cloner that's cloning this Control (not null, modified) + * @param original the instance from which this Control was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + + targets = cloner.clone(targets); + targetLocator = new TargetLocator(); + nullNumberOfBones = cloner.clone(nullNumberOfBones); + tmpPosArray = null; + tmpNormArray = null; + tmpTanArray = null; + } + + /** + * Create a shallow clone for the JME cloner. + * + * @return a new instance + */ + @Override + public MorphControl jmeClone() { + try { + MorphControl clone = (MorphControl) super.clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } + + /** + * De-serialize this Control from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + approximateTangents = capsule.readBoolean(TAG_APPROXIMATE, true); + targets.addAll(capsule.readSavableArrayList(TAG_TARGETS, null)); + } + + /** + * Serialize this Control to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + @SuppressWarnings("unchecked") + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + capsule.write(approximateTangents, TAG_APPROXIMATE, true); + capsule.writeSavableArrayList(new ArrayList(targets), TAG_TARGETS, null); + } + private class TargetLocator extends SceneGraphVisitorAdapter { @Override public void visit(Geometry geom) { 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 4ae14dd55a..92ff43514a 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-2019 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; @@ -65,8 +69,13 @@ protected MorphTrack() { /** * Creates a morph track with the given Geometry as a target * - * @param times a float array with the time of each frame - * @param weights the morphs for each frames + * @param target the desired target (alias created) + * @param times a float array with the time of each frame (alias created + * -- do not modify after passing it to this constructor) + * @param weights the morphs for each frames (alias created -- do not + * modify after passing it to this constructor) + * @param nbMorphTargets + * the desired number of morph targets */ public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTargets) { this.target = target; @@ -77,16 +86,41 @@ public MorphTrack(Geometry target, float[] times, float[] weights, int nbMorphTa /** * return the array of weights of this track * - * @return the pre-existing array + * @return the pre-existing array -- do not modify */ public float[] getWeights() { return weights; } + /** + * Set the weights for this morph track. Note that the number of weights + * must equal the number of frames times the number of morph targets. + * + * @param weights the weights of the morphs for each frame (alias created + * -- do not modify after passing it to this setter) + * + * @throws IllegalStateException if this track does not have times set + * @throws IllegalArgumentException if weights is an empty array or if + * the number of weights violates the frame count constraint + */ + public void setKeyframesWeight(float[] weights) { + if (times == null) { + throw new IllegalStateException("MorphTrack doesn't have any time for key frames, please call setTimes first"); + } + if (weights.length == 0) { + throw new IllegalArgumentException("MorphTrack with no weight keyframes!"); + } + if (times.length * nbMorphTargets != weights.length) { + throw new IllegalArgumentException("weights.length must equal nbMorphTargets * times.length"); + } + + this.weights = weights; + } + /** * returns the arrays of time for this track * - * @return the pre-existing array + * @return the pre-existing array -- do not modify */ public float[] getTimes() { return times; @@ -95,11 +129,13 @@ public float[] getTimes() { /** * Sets the keyframes times for this Joint track * - * @param times the keyframes times + * @param times the keyframes times (alias created -- do not modify after + * passing it to this setter) + * @throws IllegalArgumentException if times is empty */ public void setTimes(float[] times) { if (times.length == 0) { - throw new RuntimeException("TransformTrack with no keyframes!"); + throw new IllegalArgumentException("TransformTrack with no keyframes!"); } this.times = times; length = times[times.length - 1] - times[0]; @@ -107,23 +143,46 @@ public void setTimes(float[] times) { /** - * Set the weight for this morph track + * Sets the times and weights for this morph track. Note that the number of weights + * must equal the number of frames times the number of morph targets. * - * @param times a float array with the time of each frame - * @param weights the weights of the morphs for each frame - + * @param times a float array with the time of each frame (alias created + * -- do not modify after passing it to this setter) + * @param weights the weights of the morphs for each frame (alias created + * -- do not modify after passing it to this setter) */ public void setKeyframes(float[] times, float[] weights) { - setTimes(times); + if (times != null) { + setTimes(times); + } if (weights != null) { - if (times == null) { - throw new RuntimeException("MorphTrack doesn't have any time for key frames, please call setTimes first"); - } + setKeyframesWeight(weights); + } + } - this.weights = weights; + /** + * @return the number of morph targets + */ + public int getNbMorphTargets() { + return nbMorphTargets; + } - assert times != null && times.length == weights.length; + /** + * Sets the number of morph targets and the corresponding weights. + * Note that the number of weights must equal the number of frames times the number of morph targets. + * + * @param weights the weights for each frame (alias created + * -- do not modify after passing it to this setter) + * @param nbMorphTargets the desired number of morph targets + * @throws IllegalArgumentException if the number of weights and the new + * number of morph targets violate the frame count constraint + */ + public void setNbMorphTargets(float[] weights, int nbMorphTargets) { + if (times.length * nbMorphTargets != weights.length) { + throw new IllegalArgumentException("weights.length must equal nbMorphTargets * times.length"); } + this.nbMorphTargets = nbMorphTargets; + setKeyframesWeight(weights); } @Override @@ -164,21 +223,54 @@ 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); + } + + /** + * Access the FrameInterpolator. + * + * @return the pre-existing instance or null + */ + public FrameInterpolator getFrameInterpolator() { + return interpolator; } + /** + * Replace the FrameInterpolator. + * + * @param interpolator the interpolator to use (alias created) + */ public void setFrameInterpolator(FrameInterpolator interpolator) { this.interpolator = interpolator; } + /** + * Access the target geometry. + * + * @return the pre-existing instance + */ public Geometry getTarget() { return target; } + /** + * Replace the target geometry. + * + * @param target the Geometry to use as the target (alias created) + */ public void setTarget(Geometry target) { this.target = target; } + /** + * Serialize this track to the specified exporter, for example when saving + * to a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); @@ -188,6 +280,13 @@ public void write(JmeExporter ex) throws IOException { oc.write(nbMorphTargets, "nbMorphTargets", 0); } + /** + * De-serialize this track from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to use (not null) + * @throws IOException from the importer + */ @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); @@ -211,7 +310,7 @@ public Object jmeClone() { @Override public void cloneFields(Cloner cloner, Object original) { this.target = cloner.clone(target); + // Note: interpolator, times, and weights are not cloned } - } diff --git a/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java index c06b97ba4f..91d940ab42 100644 --- a/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/SeparateJointModelTransform.java @@ -6,14 +6,14 @@ /** * This JointModelTransform implementation accumulates model transform in a Transform class - * This does NOT support proper non uniform scale in the armature hierarchy. + * This does NOT support proper nonuniform scale in the armature hierarchy. * But the effect might be useful in some circumstances. * Note that this is how the old animation system was working, so you might want to use this - * if your model has non uniform scale and was migrated from old j3o model. + * if your model has nonuniform scale and was migrated from an old j3o model. */ public class SeparateJointModelTransform implements JointModelTransform { - private Transform modelTransform = new Transform(); + final private Transform modelTransform = new Transform(); @Override public void updateModelTransform(Transform localTransform, Joint parent) { @@ -23,6 +23,7 @@ public void updateModelTransform(Transform localTransform, Joint parent) { } } + @Override public void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix) { modelTransform.toTransformMatrix(outTransform).mult(inverseModelBindMatrix, outTransform); } diff --git a/jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java b/jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java new file mode 100644 index 0000000000..0050b9ba2c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/SingleLayerInfluenceMask.java @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2025 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; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; + +/** + * Mask that excludes joints from participating in the layer if a higher layer + * is using those joints in an animation. + * + * @author codex + */ +public class SingleLayerInfluenceMask extends ArmatureMask { + + private String targetLayer; + private AnimComposer animComposer; + + /** + * For serialization only. Do not use + */ + protected SingleLayerInfluenceMask() { + } + + /** + * Instantiate a mask that affects all joints in the specified Armature. + * + * @param targetLayer The layer this mask is targeted for. + * @param animComposer The animation composer associated with this mask. + * @param armature The Armature containing the joints. + */ + public SingleLayerInfluenceMask(String targetLayer, AnimComposer animComposer, Armature armature) { + super(armature); + this.targetLayer = targetLayer; + this.animComposer = animComposer; + } + + /** + * Instantiate a mask that affects no joints. + * + * @param targetLayer The layer this mask is targeted for. + * @param animComposer The animation composer associated with this mask. + */ + public SingleLayerInfluenceMask(String targetLayer, AnimComposer animComposer) { + this.targetLayer = targetLayer; + this.animComposer = animComposer; + } + + /** + * Get the layer this mask is targeted for. + * + * @return The target layer + */ + public String getTargetLayer() { + return targetLayer; + } + + /** + * Sets the animation composer for this mask. + * + * @param animComposer The new animation composer. + */ + public void setAnimComposer(AnimComposer animComposer) { + this.animComposer = animComposer; + } + + /** + * Checks if the specified target is contained within this mask. + * + * @param target The target to check. + * @return True if the target is contained within this mask, false otherwise. + */ + @Override + public boolean contains(Object target) { + return simpleContains(target) && (animComposer == null || !isAffectedByUpperLayers(target)); + } + + private boolean simpleContains(Object target) { + return super.contains(target); + } + + private boolean isAffectedByUpperLayers(Object target) { + boolean higher = false; + for (String layerName : animComposer.getLayerNames()) { + if (layerName.equals(targetLayer)) { + higher = true; + continue; + } + if (!higher) { + continue; + } + + AnimLayer animLayer = animComposer.getLayer(layerName); + if (animLayer.getCurrentAction() != null) { + AnimationMask mask = animLayer.getMask(); + + if (mask instanceof SingleLayerInfluenceMask) { + // dodge some needless recursion by calling a simpler method + if (((SingleLayerInfluenceMask) mask).simpleContains(target)) { + return true; + } + } else if (mask != null && mask.contains(target)) { + return true; + } + } + } + return false; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(targetLayer, "targetLayer", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + targetLayer = ic.readString("targetLayer", null); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java index 1542213610..9ceb4b3638 100644 --- a/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java +++ b/jme3-core/src/main/java/com/jme3/anim/SkinningControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,12 +31,21 @@ */ package com.jme3.anim; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.MatParamOverride; import com.jme3.math.FastMath; import com.jme3.math.Matrix4f; -import com.jme3.renderer.*; -import com.jme3.scene.*; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.control.AbstractControl; import com.jme3.scene.mesh.IndexBuffer; @@ -53,64 +62,77 @@ import java.util.logging.Logger; /** - * The Skinning control deforms a model according to an armature, It handles the - * computation of the deformation matrices and performs the transformations on - * the mesh + * The `SkinningControl` deforms a 3D model according to an {@link Armature}. It manages the + * computation of deformation matrices and applies these transformations to the mesh, + * supporting both software and hardware-accelerated skinning. + * + *

    + * **Software Skinning:** Performed on the CPU, offering broader compatibility but + * potentially lower performance for complex models. *

    - * It can perform software skinning or Hardware skinning + * **Hardware Skinning:** Utilizes the GPU for deformation, providing significantly + * better performance but requiring shader support and having a limit on the number + * of bones (typically 255 in common shaders). * - * @author Rémy Bouquet Based on SkeletonControl by Kirill Vainer + * @author Nehon */ -public class SkinningControl extends AbstractControl implements Cloneable, JmeCloneable { +public class SkinningControl extends AbstractControl implements JmeCloneable { private static final Logger logger = Logger.getLogger(SkinningControl.class.getName()); + /** + * The maximum number of bones supported for hardware skinning in common shaders. + */ + private static final int MAX_BONES_HW_SKINNING_SUPPORT = 255; + /** * The armature of the model. */ private Armature armature; /** - * List of geometries affected by this control. + * A list of geometries that this control will deform. */ private SafeArrayList targets = new SafeArrayList<>(Geometry.class); /** - * Used to track when a mesh was updated. Meshes are only updated if they + * Used to track when a mesh needs to be updated. Meshes are only updated if they * are visible in at least one camera. */ - private boolean wasMeshUpdated = false; + private boolean meshUpdateRequired = true; /** - * User wishes to use hardware skinning if available. + * Indicates whether hardware skinning is preferred. If `true` and the GPU + * supports it, hardware skinning will be enabled. */ - private transient boolean hwSkinningDesired = true; + private transient boolean hwSkinningPreferred = true; /** - * Hardware skinning is currently being used. + * Indicates if hardware skinning is currently active and being used. */ private transient boolean hwSkinningEnabled = false; /** - * Hardware skinning was tested on this GPU, results - * are stored in {@link #hwSkinningSupported} variable. + * Flag indicating whether hardware skinning compatibility has been tested + * on the current GPU. Results are stored in {@link #hwSkinningSupported}. */ private transient boolean hwSkinningTested = false; /** - * If hardware skinning was {@link #hwSkinningTested tested}, then - * this variable will be set to true if supported, and false if otherwise. + * Stores the result of the hardware skinning compatibility test. `true` if + * supported, `false` otherwise. This is only valid after + * {@link #hwSkinningTested} is `true`. */ private transient boolean hwSkinningSupported = false; /** - * Bone offset matrices, recreated each frame + * Bone offset matrices, computed each frame to deform the mesh based on + * the armature's current pose. */ - private transient Matrix4f[] offsetMatrices; + private transient Matrix4f[] boneOffsetMatrices; - - private MatParamOverride numberOfJointsParam; - private MatParamOverride jointMatricesParam; + private MatParamOverride numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); + private MatParamOverride jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); /** * Serialization only. Do not use. @@ -119,26 +141,26 @@ protected SkinningControl() { } /** - * Creates a armature control. The list of targets will be acquired - * automatically when the control is attached to a node. + * Creates a new `SkinningControl` for the given armature. * - * @param armature the armature + * @param armature The armature that drives the deformation (not null). */ public SkinningControl(Armature armature) { if (armature == null) { - throw new IllegalArgumentException("armature cannot be null"); + throw new IllegalArgumentException("armature cannot be null."); } this.armature = armature; - this.numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); - this.jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); } - - private void switchToHardware() { + /** + * Configures the material parameters and meshes for hardware skinning. + */ + private void enableHardwareSkinning() { numberOfJointsParam.setEnabled(true); jointMatricesParam.setEnabled(true); - // Next full 10 bones (e.g. 30 on 24 bones) + // Calculate the number of bones rounded up to the nearest multiple of 10. + // This is often required by shaders for array uniform declarations. int numBones = ((armature.getJointCount() / 10) + 1) * 10; numberOfJointsParam.setValue(numBones); @@ -150,7 +172,10 @@ private void switchToHardware() { } } - private void switchToSoftware() { + /** + * Configures the material parameters and meshes for software skinning. + */ + private void enableSoftwareSkinning() { numberOfJointsParam.setEnabled(false); jointMatricesParam.setEnabled(false); @@ -162,34 +187,47 @@ private void switchToSoftware() { } } - private boolean testHardwareSupported(RenderManager rm) { - - //Only 255 bones max supported with hardware skinning - if (armature.getJointCount() > 255) { + /** + * Tests if hardware skinning is supported by the GPU for the current spatial. + * + * @param renderManager the RenderManager instance + * @return true if hardware skinning is supported, false otherwise + */ + private boolean testHardwareSupported(RenderManager renderManager) { + // Only 255 bones max supported with hardware skinning in common shaders. + if (armature.getJointCount() > MAX_BONES_HW_SKINNING_SUPPORT) { + logger.log(Level.INFO, "Hardware skinning not supported for {0}: Too many bones ({1} > 255).", + new Object[]{spatial, armature.getJointCount()}); return false; } - switchToHardware(); + // Temporarily enable hardware skinning to test shader compilation. + enableHardwareSkinning(); + boolean hwSkinningEngaged = false; try { - rm.preloadScene(spatial); - return true; - } catch (RendererException e) { - logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error:", e); - return false; + renderManager.preloadScene(spatial); + logger.log(Level.INFO, "Hardware skinning engaged for {0}", spatial); + hwSkinningEngaged = true; + + } catch (RendererException ex) { + logger.log(Level.WARNING, "Could not enable HW skinning due to shader compile error: ", ex); } + + return hwSkinningEngaged; } /** * Specifies if hardware skinning is preferred. If it is preferred and - * supported by GPU, it shall be enabled, if its not preferred, or not + * supported by GPU, it shall be enabled. If it's not preferred, or not * supported by GPU, then it shall be disabled. * - * @param preferred + * @param preferred true to prefer hardware skinning, false to prefer + * software skinning (default=true) * @see #isHardwareSkinningUsed() */ public void setHardwareSkinningPreferred(boolean preferred) { - hwSkinningDesired = preferred; + hwSkinningPreferred = preferred; } /** @@ -198,7 +236,7 @@ public void setHardwareSkinningPreferred(boolean preferred) { * @see #setHardwareSkinningPreferred(boolean) */ public boolean isHardwareSkinningPreferred() { - return hwSkinningDesired; + return hwSkinningPreferred; } /** @@ -208,25 +246,21 @@ public boolean isHardwareSkinningUsed() { return hwSkinningEnabled; } - /** - * If specified the geometry has an animated mesh, add its mesh and material - * to the lists of animation targets. + * Recursively finds and adds animated geometries to the targets list. + * + * @param sp The spatial to search within. */ - private void findTargets(Geometry geometry) { - Mesh mesh = geometry.getMesh(); - if (mesh != null && mesh.isAnimated()) { - targets.add(geometry); - } - - } - - private void findTargets(Node node) { - for (Spatial child : node.getChildren()) { - if (child instanceof Geometry) { - findTargets((Geometry) child); - } else if (child instanceof Node) { - findTargets((Node) child); + private void collectAnimatedGeometries(Spatial sp) { + if (sp instanceof Geometry) { + Geometry geo = (Geometry) sp; + Mesh mesh = geo.getMesh(); + if (mesh != null && mesh.isAnimated()) { + targets.add(geo); + } + } else if (sp instanceof Node) { + for (Spatial child : ((Node) sp).getChildren()) { + collectAnimatedGeometries(child); } } } @@ -235,65 +269,72 @@ private void findTargets(Node node) { public void setSpatial(Spatial spatial) { Spatial oldSpatial = this.spatial; super.setSpatial(spatial); - updateTargetsAndMaterials(spatial); + updateAnimationTargets(spatial); if (oldSpatial != null) { + // Ensure parameters are removed from the old spatial to prevent memory leaks oldSpatial.removeMatParamOverride(numberOfJointsParam); oldSpatial.removeMatParamOverride(jointMatricesParam); } if (spatial != null) { - spatial.removeMatParamOverride(numberOfJointsParam); - spatial.removeMatParamOverride(jointMatricesParam); + // Add parameters to the new spatial. No need to remove first if they are not already present. spatial.addMatParamOverride(numberOfJointsParam); spatial.addMatParamOverride(jointMatricesParam); } } + /** + * Performs software skinning updates. + */ private void controlRenderSoftware() { resetToBind(); // reset morph meshes to bind pose - offsetMatrices = armature.computeSkinningMatrices(); + boneOffsetMatrices = armature.computeSkinningMatrices(); for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); - // NOTE: This assumes code higher up has - // already ensured this mesh is animated. - // Otherwise a crash will happen in skin update. - softwareSkinUpdate(mesh, offsetMatrices); + // NOTE: This assumes code higher up has already ensured this mesh is animated. + // Otherwise, a crash will happen in skin update. + applySoftwareSkinning(mesh, boneOffsetMatrices); } } + /** + * Prepares parameters for hardware skinning. + */ private void controlRenderHardware() { - offsetMatrices = armature.computeSkinningMatrices(); - jointMatricesParam.setValue(offsetMatrices); + boneOffsetMatrices = armature.computeSkinningMatrices(); + jointMatricesParam.setValue(boneOffsetMatrices); } @Override protected void controlRender(RenderManager rm, ViewPort vp) { - if (!wasMeshUpdated) { - updateTargetsAndMaterials(spatial); + if (meshUpdateRequired) { + updateAnimationTargets(spatial); // Prevent illegal cases. These should never happen. - assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled); - assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported); + assert hwSkinningTested || (!hwSkinningSupported && !hwSkinningEnabled); + assert !hwSkinningEnabled || (hwSkinningTested && hwSkinningSupported); - if (hwSkinningDesired && !hwSkinningTested) { + if (hwSkinningPreferred && !hwSkinningTested) { + // If hardware skinning is preferred and hasn't been tested yet, test it. hwSkinningTested = true; hwSkinningSupported = testHardwareSupported(rm); if (hwSkinningSupported) { hwSkinningEnabled = true; - - Logger.getLogger(SkinningControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial); } else { - switchToSoftware(); + enableSoftwareSkinning(); } - } else if (hwSkinningDesired && hwSkinningSupported && !hwSkinningEnabled) { - switchToHardware(); + } else if (hwSkinningPreferred && hwSkinningSupported && !hwSkinningEnabled) { + // If hardware skinning is preferred, supported, but not yet enabled, enable it. + enableHardwareSkinning(); hwSkinningEnabled = true; - } else if (!hwSkinningDesired && hwSkinningEnabled) { - switchToSoftware(); + + } else if (!hwSkinningPreferred && hwSkinningEnabled) { + // If hardware skinning is no longer preferred but is enabled, switch to software. + enableSoftwareSkinning(); hwSkinningEnabled = false; } @@ -303,17 +344,22 @@ protected void controlRender(RenderManager rm, ViewPort vp) { controlRenderSoftware(); } - wasMeshUpdated = true; + meshUpdateRequired = false; // Reset flag after update } } @Override protected void controlUpdate(float tpf) { - wasMeshUpdated = false; + meshUpdateRequired = true; // Mark for mesh update on next render pass armature.update(); } - //only do this for software updates + /** + * Resets the vertex, normal, and tangent buffers of animated meshes to their + * original bind pose. This is crucial for software skinning to ensure + * transformations are applied from a consistent base. + * This method is only applied when performing software updates. + */ void resetToBind() { for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); @@ -326,17 +372,22 @@ void resetToBind() { VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal); VertexBuffer pos = mesh.getBuffer(Type.Position); - VertexBuffer norm = mesh.getBuffer(Type.Normal); FloatBuffer pb = (FloatBuffer) pos.getData(); - FloatBuffer nb = (FloatBuffer) norm.getData(); FloatBuffer bpb = (FloatBuffer) bindPos.getData(); - FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); pb.clear(); - nb.clear(); bpb.clear(); - bnb.clear(); - //reseting bind tangents if there is a bind tangent buffer + // reset bind normals if there is a BindPoseNormal buffer + if (bindNorm != null) { + VertexBuffer norm = mesh.getBuffer(Type.Normal); + FloatBuffer nb = (FloatBuffer) norm.getData(); + FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); + nb.clear(); + bnb.clear(); + nb.put(bnb).clear(); + } + + //resetting bind tangents if there is a bind tangent buffer VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); if (bindTangents != null) { VertexBuffer tangents = mesh.getBuffer(Type.Tangent); @@ -347,9 +398,7 @@ void resetToBind() { tb.put(btb).clear(); } - pb.put(bpb).clear(); - nb.put(bnb).clear(); } } } @@ -374,51 +423,51 @@ public void cloneFields(Cloner cloner, Object original) { } /** - * Access the attachments node of the named bone. If the bone doesn't - * already have an attachments node, create one and attach it to the scene - * graph. Models and effects attached to the attachments node will follow - * the bone's motions. + * Provides access to the attachment node for a specific joint in the armature. + * If an attachment node does not already exist for the named joint, one will be + * created and attached to the scene graph. Models or effects attached to this + * node will follow the motion of the corresponding bone. * * @param jointName the name of the joint * @return the attachments node of the joint */ public Node getAttachmentsNode(String jointName) { - Joint b = armature.getJoint(jointName); - if (b == null) { - throw new IllegalArgumentException("Given bone name does not exist " - + "in the armature."); + Joint joint = armature.getJoint(jointName); + if (joint == null) { + throw new IllegalArgumentException( + "Given joint name '" + jointName + "' does not exist in the armature."); } - updateTargetsAndMaterials(spatial); - int boneIndex = armature.getJointIndex(b); - Node n = b.getAttachmentsNode(boneIndex, targets); - /* - * Select a node to parent the attachments node. - */ + updateAnimationTargets(spatial); + int jointIndex = armature.getJointIndex(joint); + Node attachNode = joint.getAttachmentsNode(jointIndex, targets); + + // Determine the appropriate parent for the attachment node. Node parent; if (spatial instanceof Node) { parent = (Node) spatial; // the usual case } else { parent = spatial.getParent(); } - parent.attachChild(n); + parent.attachChild(attachNode); - return n; + return attachNode; } /** - * returns the armature of this control + * Returns the armature associated with this skinning control. * - * @return the pre-existing instance + * @return The pre-existing `Armature` instance. */ public Armature getArmature() { return armature; } /** - * Enumerate the target meshes of this control. + * Returns an array containing all the target meshes that this control + * is currently affecting. * - * @return a new array + * @return A new array of `Mesh` objects. */ public Mesh[] getTargets() { Mesh[] result = new Mesh[targets.size()]; @@ -433,30 +482,31 @@ public Mesh[] getTargets() { } /** - * Update the mesh according to the given transformation matrices + * Applies software skinning transformations to the given mesh using the + * provided bone offset matrices. * - * @param mesh then mesh - * @param offsetMatrices the transformation matrices to apply + * @param mesh The mesh to deform. + * @param offsetMatrices The array of transformation matrices for each bone. */ - private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { + private void applySoftwareSkinning(Mesh mesh, Matrix4f[] offsetMatrices) { VertexBuffer tb = mesh.getBuffer(Type.Tangent); if (tb == null) { - //if there are no tangents use the classic skinning + // if there are no tangents use the classic skinning applySkinning(mesh, offsetMatrices); } else { - //if there are tangents use the skinning with tangents + // if there are tangents use the skinning with tangents applySkinningTangents(mesh, offsetMatrices, tb); } - - } /** - * Method to apply skinning transforms to a mesh's buffers + * Applies skinning transformations to a mesh's position and normal buffers. + * This method iterates through each vertex, applies the weighted sum of + * bone transformations, and updates the vertex buffers. * - * @param mesh the mesh - * @param offsetMatrices the offset matices to apply + * @param mesh The mesh to apply skinning to. + * @param offsetMatrices The bone offset matrices to use for transformation. */ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { int maxWeightsPerVert = mesh.getMaxNumWeights(); @@ -551,19 +601,16 @@ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { vb.updateData(fvb); nb.updateData(fnb); - } /** - * Specific method for skinning with tangents to avoid cluttering the - * classic skinning calculation with null checks that would slow down the - * process even if tangents don't have to be computed. Also the iteration - * has additional indexes since tangent has 4 components instead of 3 for - * pos and norm + * Applies skinning transformations to a mesh's position, normal, and tangent buffers. + * This method is specifically designed for meshes that include tangent data, + * ensuring proper deformation of tangents alongside positions and normals. * - * @param mesh the mesh - * @param offsetMatrices the offsetMatrices to apply - * @param tb the tangent vertexBuffer + * @param mesh The mesh to apply skinning to. + * @param offsetMatrices The bone offset matrices to use for transformation. + * @param tb The tangent `VertexBuffer`. */ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { int maxWeightsPerVert = mesh.getMaxNumWeights(); @@ -582,14 +629,14 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB VertexBuffer nb = mesh.getBuffer(Type.Normal); - FloatBuffer fnb = (FloatBuffer) nb.getData(); - fnb.rewind(); - + FloatBuffer fnb = (nb == null) ? null : (FloatBuffer) nb.getData(); + if (fnb != null) { + fnb.rewind(); + } FloatBuffer ftb = (FloatBuffer) tb.getData(); ftb.rewind(); - // get boneIndexes and weights for mesh IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData()); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); @@ -600,8 +647,6 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB int idxWeights = 0; TempVars vars = TempVars.get(); - - float[] posBuf = vars.skinPositions; float[] normBuf = vars.skinNormals; float[] tanBuf = vars.skinTangents; @@ -614,11 +659,13 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB bufLength = Math.min(posBuf.length, fvb.remaining()); tanLength = Math.min(tanBuf.length, ftb.remaining()); fvb.get(posBuf, 0, bufLength); - fnb.get(normBuf, 0, bufLength); + if (fnb != null) { + fnb.get(normBuf, 0, bufLength); + } ftb.get(tanBuf, 0, tanLength); int verts = bufLength / 3; int idxPositions = 0; - //tangents has their own index because of the 4 components + // Tangents have their own index because they have 4 components. int idxTangents = 0; // iterate vertices and apply skinning transform for each effecting bone @@ -687,8 +734,10 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB fvb.position(fvb.position() - bufLength); fvb.put(posBuf, 0, bufLength); - fnb.position(fnb.position() - bufLength); - fnb.put(normBuf, 0, bufLength); + if (fnb != null) { + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + } ftb.position(ftb.position() - tanLength); ftb.put(tanBuf, 0, tanLength); } @@ -696,35 +745,46 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB vars.release(); vb.updateData(fvb); - nb.updateData(fnb); + if (nb != null) { + nb.updateData(fnb); + } tb.updateData(ftb); } + /** + * Serialize this Control to the specified exporter, for example when saving + * to a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(armature, "armature", null); - - oc.write(numberOfJointsParam, "numberOfBonesParam", null); - oc.write(jointMatricesParam, "boneMatricesParam", null); } + /** + * De-serialize this Control from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to read from (not null) + * @throws IOException from the importer + */ @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule in = im.getCapsule(this); armature = (Armature) in.readSavable("armature", null); - numberOfJointsParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null); - jointMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null); - - if (numberOfJointsParam == null) { - numberOfJointsParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); - jointMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); - getSpatial().addMatParamOverride(numberOfJointsParam); - getSpatial().addMatParamOverride(jointMatricesParam); + for (MatParamOverride mpo : spatial.getLocalMatParamOverrides().getArray()) { + if (mpo.getName().equals("NumberOfBones") || mpo.getName().equals("BoneMatrices")) { + spatial.removeMatParamOverride(mpo); + } } + spatial.addMatParamOverride(numberOfJointsParam); + spatial.addMatParamOverride(jointMatricesParam); } /** @@ -732,13 +792,9 @@ public void read(JmeImporter im) throws IOException { * * @param spatial the controlled spatial */ - private void updateTargetsAndMaterials(Spatial spatial) { + private void updateAnimationTargets(Spatial spatial) { targets.clear(); - - if (spatial instanceof Node) { - findTargets((Node) spatial); - } else if (spatial instanceof Geometry) { - findTargets((Geometry) spatial); - } + collectAnimatedGeometries(spatial); } + } 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 63eba06cfc..39b66d5d36 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,37 +42,46 @@ import java.io.IOException; /** - * Contains a list of transforms and times for each keyframe. + * An AnimTrack that transforms a Joint or Spatial + * using a list of transforms and times for keyframes. * * @author Rémy Bouquet */ public class TransformTrack implements AnimTrack { private double length; + /** + * The interpolator to use, or null to always use the default interpolator + * of the current thread. + */ + private FrameInterpolator interpolator = null; private HasLocalTransform target; /** - * Transforms and times for track. + * Transforms and times for keyframes. */ private CompactVector3Array translations; private CompactQuaternionArray rotations; private CompactVector3Array scales; - private FrameInterpolator interpolator = FrameInterpolator.DEFAULT; private float[] times; /** * Serialization-only. Do not use. */ - public TransformTrack() { + protected TransformTrack() { } /** - * Creates a transform track for the given bone index + * Creates a transform track for the given target. * - * @param times a float array with the time of each frame - * @param translations the translation of the bone for each frame - * @param rotations the rotation of the bone for each frame - * @param scales the scale of the bone for each frame + * @param target the target Joint or Spatial of the new track + * @param times the time for each keyframe, or null for none + * @param translations the translation of the target for each keyframe + * (same length as times) or null for no translation + * @param rotations the rotation of the target for each keyframe + * (same length as times) or null for no rotation + * @param scales the scale of the target for each keyframe + * (same length as times) or null for no scaling */ public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { this.target = target; @@ -80,25 +89,25 @@ public TransformTrack(HasLocalTransform target, float[] times, Vector3f[] transl } /** - * return the array of rotations of this track + * Copies the rotations. * - * @return an array + * @return a new array, or null if no rotations */ public Quaternion[] getRotations() { - return rotations.toObjectArray(); + return rotations == null ? null : rotations.toObjectArray(); } /** - * returns the array of scales for this track + * Copies the scales. * - * @return an array or null + * @return a new array, or null if no scales */ public Vector3f[] getScales() { return scales == null ? null : scales.toObjectArray(); } /** - * returns the arrays of time for this track + * Gives access to the keyframe times. * * @return the pre-existing array */ @@ -107,39 +116,44 @@ public float[] getTimes() { } /** - * returns the array of translations of this track + * Copies the translations. * - * @return an array + * @return a new array, or null if no translations */ public Vector3f[] getTranslations() { - return translations.toObjectArray(); + return translations == null ? null : translations.toObjectArray(); } /** - * Sets the keyframes times for this Joint track + * Sets the keyframe times. * - * @param times the keyframes times + * @param times the desired keyframe times (alias created, not null, not empty) */ public void setTimes(float[] times) { - if (times.length == 0) { - throw new RuntimeException("TransformTrack with no keyframes!"); + if (times == null || times.length == 0) { + throw new IllegalArgumentException( + "No keyframe times were provided."); } this.times = times; length = times[times.length - 1] - times[0]; } /** - * Set the translations for this joint track + * Sets the translations. * - * @param translations the translation of the bone for each frame + * @param translations the desired translation of the target for each + * keyframe (not null, same length as "times") */ public void setKeyframesTranslation(Vector3f[] translations) { if (times == null) { - throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + throw new IllegalStateException( + "TransformTrack lacks keyframe times. " + + "Please invoke setTimes() first."); } - if (translations.length == 0) { - throw new RuntimeException("TransformTrack with no translation keyframes!"); + if (translations == null || translations.length == 0) { + throw new IllegalArgumentException( + "No translations were provided."); } this.translations = new CompactVector3Array(); this.translations.add(translations); @@ -149,16 +163,20 @@ public void setKeyframesTranslation(Vector3f[] translations) { } /** - * Set the scales for this joint track + * Sets the scales. * - * @param scales the scales of the bone for each frame + * @param scales the desired scale of the target for each keyframe (not + * null, same length as "times") */ public void setKeyframesScale(Vector3f[] scales) { if (times == null) { - throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + throw new IllegalStateException( + "TransformTrack lacks keyframe times. " + + "Please invoke setTimes() first."); } - if (scales.length == 0) { - throw new RuntimeException("TransformTrack with no scale keyframes!"); + if (scales == null || scales.length == 0) { + throw new IllegalArgumentException( + "No scale vectors were provided."); } this.scales = new CompactVector3Array(); this.scales.add(scales); @@ -168,16 +186,20 @@ public void setKeyframesScale(Vector3f[] scales) { } /** - * Set the rotations for this joint track + * Sets the rotations. * - * @param rotations the rotations of the bone for each frame + * @param rotations the desired rotation of the target for each keyframe + * (not null, same length as "times") */ public void setKeyframesRotation(Quaternion[] rotations) { if (times == null) { - throw new RuntimeException("TransformTrack doesn't have any time for key frames, please call setTimes first"); + throw new IllegalStateException( + "TransformTrack lacks keyframe times. " + + "Please invoke setTimes() first."); } - if (rotations.length == 0) { - throw new RuntimeException("TransformTrack with no rotation keyframes!"); + if (rotations == null || rotations.length == 0) { + throw new IllegalArgumentException( + "No rotations were provided."); } this.rotations = new CompactQuaternionArray(); this.rotations.add(rotations); @@ -188,15 +210,24 @@ public void setKeyframesRotation(Quaternion[] rotations) { /** - * Set the translations, rotations and scales for this bone track + * Sets the translations, rotations, and/or scales. * - * @param times a float array with the time of each frame - * @param translations the translation of the bone for each frame - * @param rotations the rotation of the bone for each frame - * @param scales the scale of the bone for each frame + * @param times the desired time for each keyframe, + * or null to leave the times unchanged + * @param translations the desired translation of the target for each + * keyframe (same length as times) + * or null to leave the translations unchanged + * @param rotations the desired rotation of the target for each keyframe + * (same length as times) + * or null to leave the rotations unchanged + * @param scales the desired scale of the target for each keyframe + * (same length as times) + * or null to leave the scales unchanged */ public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { - setTimes(times); + if (times != null) { + setTimes(times); + } if (translations != null) { setKeyframesTranslation(translations); } @@ -208,10 +239,12 @@ public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] ro } } + @Override public double getLength() { return length; } + @Override public void getDataAtTime(double t, Transform transform) { float time = (float) t; @@ -233,7 +266,7 @@ public void getDataAtTime(double t, Transform transform) { int endFrame = 1; float blend = 0; if (time >= times[lastFrame]) { - // extrapolate beyond the final frame of the animation + // extrapolate beyond the final keyframe of the animation startFrame = lastFrame; float inferredInterval = times[lastFrame] - times[lastFrame - 1]; @@ -252,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()); @@ -265,18 +301,49 @@ public void getDataAtTime(double t, Transform transform) { } } + /** + * Access the FrameInterpolator. + * + * @return the pre-existing instance or null + */ + public FrameInterpolator getFrameInterpolator() { + return interpolator; + } + + /** + * Replaces the frame interpolator. + * + * @param interpolator the interpolator to use (alias created) + */ public void setFrameInterpolator(FrameInterpolator interpolator) { this.interpolator = interpolator; } + /** + * Gives access to the target, which might be a Joint or a Spatial. + * + * @return the pre-existing instance + */ public HasLocalTransform getTarget() { return target; } + /** + * Replaces the target, which might be a Joint or a Spatial. + * + * @param target the target to use (alias created) + */ public void setTarget(HasLocalTransform target) { this.target = target; } + /** + * Serializes this track to the specified exporter, for example when + * saving to a J3O file. + * + * @param ex the exporter to write to (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); @@ -287,6 +354,13 @@ public void write(JmeExporter ex) throws IOException { oc.write(target, "target", null); } + /** + * De-serializes this track from the specified importer, for example when + * loading from a J3O file. + * + * @param im the importer to read from (not null) + * @throws IOException from the importer + */ @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); diff --git a/jme3-core/src/main/java/com/jme3/anim/Weights.java b/jme3-core/src/main/java/com/jme3/anim/Weights.java deleted file mode 100644 index 8ac9b5b4dc..0000000000 --- a/jme3-core/src/main/java/com/jme3/anim/Weights.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.jme3.anim; - -import java.util.ArrayList; - -public class Weights {//} extends Savable, JmeCloneable{ - - - private final static float MIN_WEIGHT = 0.005f; - - private int[] indices; - private float[] data; - private int size; - - public Weights(float[] array, int start, int length) { - ArrayList list = new ArrayList<>(); - ArrayList idx = new ArrayList<>(); - - for (int i = start; i < length; i++) { - float val = array[i]; - if (val > MIN_WEIGHT) { - list.add(val); - idx.add(i); - } - } - size = list.size(); - data = new float[size]; - indices = new int[size]; - for (int i = 0; i < size; i++) { - data[i] = list.get(i); - indices[i] = idx.get(i); - } - } - - public int getSize() { - return size; - } - - // public Weights(float[] array, int start, int length) { -// LinkedList list = new LinkedList<>(); -// LinkedList idx = new LinkedList<>(); -// for (int i = start; i < length; i++) { -// float val = array[i]; -// if (val > MIN_WEIGHT) { -// int index = insert(list, val); -// if (idx.size() < index) { -// idx.add(i); -// } else { -// idx.add(index, i); -// } -// } -// } -// data = new float[list.size()]; -// for (int i = 0; i < data.length; i++) { -// data[i] = list.get(i); -// } -// -// indices = new int[idx.size()]; -// for (int i = 0; i < indices.length; i++) { -// indices[i] = idx.get(i); -// } -// } -// -// private int insert(LinkedList list, float value) { -// for (int i = 0; i < list.size(); i++) { -// float w = list.get(i); -// if (value > w) { -// list.add(i, value); -// return i; -// } -// } -// -// list.add(value); -// return list.size(); -// } - - @Override - public String toString() { - StringBuilder b = new StringBuilder(); - for (int i = 0; i < indices.length; i++) { - b.append(indices[i]).append(","); - } - b.append("\n"); - for (int i = 0; i < data.length; i++) { - b.append(data[i]).append(","); - } - return b.toString(); - } - - public static void main(String... args) { - // 6 7 4 8 - float values[] = {0, 0, 0, 0, 0.5f, 0.001f, 0.7f, 0.6f, 0.2f, 0, 0, 0}; - Weights w = new Weights(values, 0, values.length); - System.err.println(w); - } -} diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java index a924a9d28b..6fd7116f5f 100644 --- a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolator.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2020 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.interpolator; import static com.jme3.anim.interpolator.FrameInterpolator.TrackDataReader; diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java index 5f8b5f3043..2e82a78b4c 100644 --- a/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/AnimInterpolators.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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.interpolator; import com.jme3.math.*; @@ -75,10 +106,10 @@ public Vector3f interpolate(float t, int currentIndex, TrackDataReader public static final CatmullRomInterpolator CatmullRom = new CatmullRomInterpolator(); public static class CatmullRomInterpolator extends AnimInterpolator { - private Vector3f p0 = new Vector3f(); - private Vector3f p1 = new Vector3f(); - private Vector3f p2 = new Vector3f(); - private Vector3f p3 = new Vector3f(); + final private Vector3f p0 = new Vector3f(); + final private Vector3f p1 = new Vector3f(); + final private Vector3f p2 = new Vector3f(); + final private Vector3f p3 = new Vector3f(); private float tension = 0.7f; public CatmullRomInterpolator(float tension) { @@ -103,7 +134,7 @@ public Vector3f interpolate(float t, int currentIndex, TrackDataReader //Time Interpolators public static class TimeInterpolator extends AnimInterpolator { - private EaseFunction ease; + final private EaseFunction ease; public TimeInterpolator(EaseFunction ease) { this.ease = ease; @@ -145,5 +176,9 @@ public Float interpolate(float t, int currentIndex, TrackDataReader data, public static final TimeInterpolator constant = new TimeInterpolator(Easing.constant); - + /** + * A private constructor to inhibit instantiation of this class. + */ + private AnimInterpolators() { + } } 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 9577107a86..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,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.anim.interpolator; import com.jme3.animation.*; @@ -7,36 +38,57 @@ * 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; private AnimInterpolator rotationInterpolator = AnimInterpolators.NLerp; private AnimInterpolator scaleInterpolator = AnimInterpolators.LinearVec3f; - private TrackDataReader translationReader = new TrackDataReader<>(); - private TrackDataReader rotationReader = new TrackDataReader<>(); - private TrackDataReader scaleReader = new TrackDataReader<>(); - private TrackTimeReader timesReader = new TrackTimeReader(); + final private TrackDataReader translationReader = new TrackDataReader<>(); + final private TrackDataReader rotationReader = new TrackDataReader<>(); + final private TrackDataReader scaleReader = new TrackDataReader<>(); + final private TrackTimeReader timesReader = new TrackTimeReader(); + final private Transform transforms = new Transform(); - 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){ + public Transform interpolate(float t, int currentIndex, CompactVector3Array translations, + CompactQuaternionArray rotations, CompactVector3Array scales, float[] times) { timesReader.setData(times); - if( timeInterpolator != null){ - t = timeInterpolator.interpolate(t,currentIndex, null, timesReader, null ); + if (timeInterpolator != null) { + t = timeInterpolator.interpolate(t, currentIndex, null, timesReader, null); } - if(translations != null) { + if (translations != null) { translationReader.setData(translations); translationInterpolator.interpolate(t, currentIndex, translationReader, timesReader, transforms.getTranslation()); } - if(rotations != null) { + if (rotations != null) { rotationReader.setData(rotations); rotationInterpolator.interpolate(t, currentIndex, rotationReader, timesReader, transforms.getRotation()); } - if(scales != null){ + if (scales != null) { scaleReader.setData(scales); scaleInterpolator.interpolate(t, currentIndex, scaleReader, timesReader, transforms.getScale()); } @@ -52,7 +104,7 @@ public void interpolateWeights(float t, int currentIndex, float[] weights, int n next = current; } - float val = FastMath.interpolateLinear(t, weights[current], weights[next]); + float val = FastMath.interpolateLinear(t, weights[current], weights[next]); store[i] = val; } } @@ -73,7 +125,6 @@ public void setScaleInterpolator(AnimInterpolator scaleInterpolator) { this.scaleInterpolator = scaleInterpolator; } - public static class TrackTimeReader { private float[] data; @@ -91,7 +142,6 @@ public int getLength() { } public static class TrackDataReader { - private CompactArray data; protected void setData(CompactArray data) { @@ -117,7 +167,6 @@ public T getEntryModSkip(int index, T store) { index = mod(index, total); - return data.get(index, store); } } @@ -132,5 +181,4 @@ public T getEntryModSkip(int index, T store) { private static int mod(int val, int n) { return ((val % n) + n) % n; } - } diff --git a/jme3-core/src/main/java/com/jme3/anim/interpolator/package-info.java b/jme3-core/src/main/java/com/jme3/anim/interpolator/package-info.java new file mode 100644 index 0000000000..ef7a426023 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/interpolator/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * interpolation support for the "new" animation system + */ +package com.jme3.anim.interpolator; diff --git a/jme3-core/src/main/java/com/jme3/anim/package-info.java b/jme3-core/src/main/java/com/jme3/anim/package-info.java new file mode 100644 index 0000000000..ea711dc780 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * central classes of the "new" animation system (the one using AnimComposer and + * tweens) + */ +package com.jme3.anim; diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java index 34d4fb83e3..5216051f64 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/AbstractTween.java @@ -1,39 +1,34 @@ /* - * $Id$ - * - * Copyright (c) 2015, Simsilica, LLC + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions + * 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. + * + * * 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; import com.jme3.util.clone.Cloner; @@ -41,7 +36,7 @@ /** * Base implementation of the Tween interface that provides - * default implementations of the getLength() and interopolate() + * default implementations of the getLength() and interpolate() * methods that provide common tween clamping and bounds checking. * Subclasses need only override the doInterpolate() method and * the rest is handled for them. @@ -62,6 +57,9 @@ public double getLength() { } public void setLength(double length) { + if (length < 0.0) { + throw new IllegalArgumentException("length must be greater than or equal to 0"); + } this.length = length; } diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java index e79b4d29e6..f23ba1d72e 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/Tween.java @@ -1,39 +1,34 @@ /* - * $Id$ - * - * Copyright (c) 2015, Simsilica, LLC + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions + * 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. + * + * * 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; /** @@ -48,9 +43,11 @@ public interface Tween extends Cloneable { /** * Returns the length of the tween. If 't' represents time in * seconds then this is the notional time in seconds that the tween - * will run. Note: all of the caveats are because tweens may be + * will run. Note: all the caveats are because tweens may be * externally scaled in such a way that 't' no longer represents * actual time. + * + * @return the duration (in de-scaled seconds) */ public double getLength(); @@ -61,6 +58,9 @@ public interface Tween extends Cloneable { * then it is internally clamped and the method returns false. * If 't' is still in the tween's range then this method returns * true. + * + * @param t animation time (in de-scaled seconds) + * @return true if t>length(), otherwise false */ public boolean interpolate(double t); 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 0fedf9b75a..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,39 +1,34 @@ /* - * $Id$ - * - * Copyright (c) 2015, Simsilica, LLC + * Copyright (c) 2015-2022 jMonkeyEngine * All rights reserved. - * + * * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions + * 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. + * + * * 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; import com.jme3.anim.util.Primitives; @@ -52,14 +47,23 @@ */ public class Tweens { - static Logger log = Logger.getLogger(Tweens.class.getName()); + private static final Logger log = Logger.getLogger(Tweens.class.getName()); private static final CurveFunction SMOOTH = new SmoothStep(); private static final CurveFunction SINE = new Sine(); + /** + * A private constructor to inhibit instantiation of this class. + */ + private Tweens() { + } + /** * Creates a tween that will interpolate over an entire sequence * of tweens in order. + * + * @param delegates the desired sequence of tweens + * @return a new instance */ public static Tween sequence(Tween... delegates) { return new Sequence(delegates); @@ -69,6 +73,9 @@ public static Tween sequence(Tween... delegates) { * Creates a tween that will interpolate over an entire list * of tweens in parallel, ie: all tweens will be run at the same * time. + * + * @param delegates the tweens to be interpolated + * @return a new instance */ public static Tween parallel(Tween... delegates) { return new Parallel(delegates); @@ -77,6 +84,9 @@ public static Tween parallel(Tween... delegates) { /** * Creates a tween that will perform a no-op until the length * has expired. + * + * @param length the desired duration (in seconds) + * @return a new instance */ public static Tween delay(double length) { return new Delay(length); @@ -86,6 +96,10 @@ public static Tween delay(double length) { * Creates a tween that scales the specified delegate tween or tweens * to the desired length. If more than one tween is specified then they * are wrapped in a sequence using the sequence() method. + * + * @param desiredLength the desired duration (in seconds) + * @param delegates the desired sequence of tweens + * @return a new instance */ public static Tween stretch(double desiredLength, Tween... delegates) { if (delegates.length == 1) { @@ -98,6 +112,9 @@ public static Tween stretch(double desiredLength, Tween... delegates) { * Creates a tween that uses a sine function to smooth step the time value * for the specified delegate tween or tweens. These 'curved' wrappers * can be used to smooth the interpolation of another tween. + * + * @param delegates the desired sequence of tweens + * @return a new instance */ public static Tween sineStep(Tween... delegates) { if (delegates.length == 1) { @@ -111,6 +128,9 @@ public static Tween sineStep(Tween... delegates) { * for the specified delegate tween or tweens. This is similar to GLSL's * smoothstep(). These 'curved' wrappers can be used to smooth the interpolation * of another tween. + * + * @param delegates the desired sequence of tweens + * @return a new instance */ public static Tween smoothStep(Tween... delegates) { if (delegates.length == 1) { @@ -123,6 +143,11 @@ public static Tween smoothStep(Tween... delegates) { * Creates a Tween that will call the specified method and optional arguments * whenever supplied a time value greater than or equal to 0. This creates * an "instant" tween of length 0. + * + * @param target object on which the method is to be invoked + * @param method name of the method to be invoked + * @param args arguments to be passed to the method + * @return a new instance */ public static Tween callMethod(Object target, String method, Object... args) { return new CallMethod(target, method, args); @@ -133,21 +158,83 @@ public static Tween callMethod(Object target, String method, Object... args) { * including the time value scaled between 0 and 1. The method must take * a float or double value as its first or last argument, in addition to whatever * optional arguments are specified. - *

    + * *

    For example:

    *
    Tweens.callTweenMethod(1, myObject, "foo", "bar")
    *

    Would work for any of the following method signatures:

    *
    -     *    void foo( float t, String arg )
    -     *    void foo( double t, String arg )
    -     *    void foo( String arg, float t )
    -     *    void foo( String arg, double t )
    -     *  
    + * void foo(float t, String arg) + * void foo(double t, String arg) + * void foo(String arg, float t) + * void foo(String arg, double t) + * + * + * @param length the desired duration (in seconds) + * @param target object on which the method is to be invoked + * @param method name of the method to be invoked + * @param args additional arguments to be passed to the method + * @return a new instance */ public static Tween callTweenMethod(double length, Object target, String method, Object... args) { 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); } @@ -177,7 +264,7 @@ public double curve(double t) { } else if (t > 1) { return 1; } - // Sine starting at -90 will go from -1 to 1 through 0 + // Sine starting at -90 will go from -1 to 1 through 0 double result = Math.sin(t * Math.PI - Math.PI * 0.5); return (result + 1) * 0.5; } @@ -378,7 +465,7 @@ public Stretch(Tween delegate, double length) { this.length = length; // Caller desires delegate to be 'length' instead of - // its actual length so we will calculate a time scale + // its actual length, so we will calculate a time scale. // If the desired length is longer than delegate's then // we need to feed time in slower, ie: scale < 1 if (length != 0) { @@ -443,6 +530,7 @@ public CallMethod(Object target, String methodName, Object... args) { this.method.setAccessible(true); } + @SuppressWarnings("unchecked") private static Method findMethod(Class type, String name, Object... args) { for (Method m : type.getDeclaredMethods()) { if (!Objects.equals(m.getName(), name)) { @@ -473,9 +561,7 @@ private static Method findMethod(Class type, String name, Object... args) { protected void doInterpolate(double t) { try { method.invoke(target, args); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error running method:" + method + " for object:" + target, e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Error running method:" + method + " for object:" + target, e); } } @@ -508,7 +594,7 @@ public CallTweenMethod(double length, Object target, String methodName, Object.. } this.method.setAccessible(true); - // So now setup the real args list + // So now set up the real args list. this.args = new Object[args.length + 1]; if (tIndex == 0) { for (int i = 0; i < args.length; i++) { @@ -537,15 +623,15 @@ private Method findMethod(Class type, String name, Object... args) { Class[] paramTypes = m.getParameterTypes(); if (paramTypes.length != args.length + 1) { if (log.isLoggable(Level.FINE)) { - log.log(Level.FINE, "Param lengths of [" + m + "] differ. method arg count:" + paramTypes.length + " lookging for:" + (args.length + 1)); + log.log(Level.FINE, "Param lengths of [" + m + "] differ. method arg count:" + paramTypes.length + " looking for:" + (args.length + 1)); } continue; } - // We accept the 't' parameter as either first or last + // We accept the 't' parameter as either first or last, // so we'll see which one matches. if (isFloatType(paramTypes[0]) || isDoubleType(paramTypes[0])) { - // Try it as the first parameter + // Try it as the first parameter int matches = 0; for (int i = 1; i < paramTypes.length; i++) { @@ -567,7 +653,7 @@ private Method findMethod(Class type, String name, Object... args) { return m; } - // Else try it at the end + // Else try it at the end int last = paramTypes.length - 1; if (isFloatType(paramTypes[last]) || isDoubleType(paramTypes[last])) { int matches = 0; @@ -604,9 +690,7 @@ protected void doInterpolate(double t) { args[tIndex] = t; } method.invoke(target, args); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error running method:" + method + " for object:" + target, e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Error running method:" + method + " for object:" + target, e); } } @@ -616,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/Action.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java index 6846f91460..344a69b216 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/Action.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.anim.tween.action; import com.jme3.anim.AnimationMask; @@ -5,14 +36,43 @@ import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; +/** + * Wraps an array of Tween actions into an action object. + * + *

    + * Notes : + *

  • The sequence of tweens is determined by {@link com.jme3.anim.tween.Tweens} utility class and the {@link BaseAction} interpolates that sequence.
  • + *
  • This implementation mimics the {@link com.jme3.anim.tween.AbstractTween}, but it delegates the interpolation method {@link Tween#interpolate(double)} + * to the {@link BlendableAction} class.
  • + *

    + * + * Created by Nehon. + * + * @see BlendableAction + * @see BaseAction + */ public abstract class Action implements JmeCloneable, Tween { - + + /** + * A sequence of actions which wraps given tween actions. + */ protected Action[] actions; private double length; private double speed = 1; private AnimationMask mask; private boolean forward = true; - + + /** + * Instantiates an action object that wraps a tween actions array by extracting their actions to the collection {@link Action#actions}. + *

    + * Notes : + *

  • If intentions are to wrap some tween actions, then subclasses have to call this constructor, examples : {@link BlendableAction} and {@link BlendAction}.
  • + *
  • If intentions are to make an implementation of {@link Action} that shouldn't wrap tweens of actions, then subclasses shouldn't call this + * constructor, examples : {@link ClipAction} and {@link BaseAction}.
  • + *

    + * + * @param tweens the tween actions to be wrapped (not null). + */ protected Action(Tween... tweens) { this.actions = new Action[tweens.length]; for (int i = 0; i < tweens.length; i++) { @@ -24,54 +84,87 @@ protected Action(Tween... tweens) { } } } - + + /** + * Retrieves the length (the duration) of the current action. + * + * @return the length of the action in seconds. + */ @Override public double getLength() { return length; } - protected void setLength(double length) { + /** + * Alters the length (duration) of this Action. This can be used to extend + * or truncate an Action. + * + * @param length the desired length (in unscaled seconds, default=0) + */ + public void setLength(double length) { this.length = length; } + /** + * Retrieves the speedup factor applied by the layer for this action. + * + * @see Action#setSpeed(double) for detailed documentation + * @return the speed of frames. + */ public double getSpeed() { return speed; } + /** + * Alters the speedup factor applied by the layer running this action. + *

    + * Notes: + *

  • This factor controls the animation direction, if the speed is a positive value then the animation will run forward and vice versa.
  • + *
  • The speed factor gets applied, inside the {@link com.jme3.anim.AnimLayer}, on each interpolation step by this formula : time += tpf * action.getSpeed() * composer.globalSpeed.
  • + *
  • Default speed is 1.0, it plays the animation clips at their normal speed.
  • + *
  • Setting the speed factor to Zero will stop the animation, while setting it to a negative number will play the animation in a backward fashion.
  • + *

    + * + * @param speed the speed of frames. + */ public void setSpeed(double speed) { this.speed = speed; - if( speed < 0){ + if (speed < 0) { setForward(false); } else { setForward(true); } } + /** + * Retrieves the animation mask for this action. + * The animation mask controls which part of the model would be animated. A model part can be + * registered using a {@link com.jme3.anim.Joint}. + * + * @return the animation mask instance, or null if this action will animate the entire model + * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated + */ public AnimationMask getMask() { return mask; } + /** + * Meant for internal use only. + * + *

    + * Note: This method can be invoked from the user code only if this Action is wrapped by a {@link BaseAction} and + * the {@link BaseAction#isMaskPropagationEnabled()} is false. + *

    + * + * @param mask an animation mask to be applied to this action. + * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated + */ public void setMask(AnimationMask mask) { this.mask = mask; } - protected boolean isForward() { - return forward; - } - - protected void setForward(boolean forward) { - if(this.forward == forward){ - return; - } - this.forward = forward; - for (Action action : actions) { - action.setForward(forward); - } - - } - /** - * Create a shallow clone for the JME cloner. + * Creates a shallow clone for the JME cloner. * * @return a new action (not null) */ @@ -99,4 +192,29 @@ public void cloneFields(Cloner cloner, Object original) { actions = cloner.clone(actions); mask = cloner.clone(mask); } + + /** + * Tests whether the Action is running in the "forward" mode. + * + * @return true if the animation action is running forward, false otherwise. + */ + protected boolean isForward() { + return forward; + } + + /** + * Adjusts the forward flag which controls the animation action directionality. + * + * @param forward true to run the animation forward, false otherwise. + * @see Action#setSpeed(double) to change the directionality of the tween actions, negative numbers play the animation in a backward fashion + */ + protected void setForward(boolean forward) { + if (this.forward == forward) { + return; + } + this.forward = forward; + for (Action action : actions) { + action.setForward(forward); + } + } } 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 7de04bc742..edc819f7ff 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,15 +1,74 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.tween.action; +import com.jme3.anim.AnimationMask; import com.jme3.anim.tween.ContainsTweens; import com.jme3.anim.tween.Tween; import com.jme3.util.SafeArrayList; - import java.util.List; +/** + * A simple implementation for the abstract class {@link Action} to provide a wrapper for a {@link Tween}. + * Internally, it is used as a helper class for {@link Action} to extract and gather actions from a tween and interpolate it. + *

    + * An example showing two clip actions running in parallel at 2x of their ordinary speed + * by the help of BaseAction on a new Animation Layer : + *

    + * //create a base action from a tween.
    + * final BaseAction action = new BaseAction(Tweens.parallel(clipAction0, clipAction1));
    + * //set the action properties - utilized within the #{@link Action} class.
    + * baseAction.setSpeed(2f);
    + * //register the action as an observer to the animComposer control.
    + * animComposer.addAction("basicAction", action);
    + * //make a new Layer for a basic armature mask
    + * animComposer.makeLayer(ActionState.class.getSimpleName(), new ArmatureMask());
    + * //run the action within this layer
    + * animComposer.setCurrentAction("basicAction", ActionState.class.getSimpleName());
    + * 
    + *

    + * Created by Nehon. + */ public class BaseAction extends Action { - private Tween tween; + final private Tween tween; + private boolean maskPropagationEnabled = true; + /** + * Instantiates an action from a tween by extracting the actions from a tween + * to a list of sub-actions to be interpolated later. + * + * @param tween a tween to extract the actions from (not null). + */ public BaseAction(Tween tween) { this.tween = tween; setLength(tween.getLength()); @@ -19,6 +78,57 @@ public BaseAction(Tween tween) { subActions.toArray(actions); } + /** + * Tests whether the animation mask is applied to the wrapped actions {@link BaseAction#actions}. + * + * @return true if mask propagation to child actions is enabled else returns false. + */ + public boolean isMaskPropagationEnabled() { + return maskPropagationEnabled; + } + + /** + * Determines whether to apply the animation mask to the wrapped or child actions {@link BaseAction#actions}. + * + * @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; + } + + /** + * Sets the animation mask which determines which part of the model will + * be animated by the animation layer. If the {@link BaseAction#isMaskPropagationEnabled()} is false, setting + * the mask attribute will not affect the actions under this base action. Setting this to 'null' will animate + * the entire model. + * + * @param mask an animation mask to be applied to this action (nullable). + * @see com.jme3.anim.AnimLayer to adjust the animation mask to control which part will be animated + * @see BaseAction#setMaskPropagationEnabled(boolean) + */ + @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); + } + + /** + * Extracts the actions from a tween into a list. + * + * @param tween the tween to extract the actions from (not null). + * @param subActions a collection to gather the extracted actions (not null). + */ private void gatherActions(Tween tween, List subActions) { if (tween instanceof Action) { subActions.add((Action) tween); @@ -29,9 +139,4 @@ private void gatherActions(Tween tween, List subActions) { } } } - - @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 9c5848e4da..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; @@ -11,10 +44,11 @@ public class BlendAction extends BlendableAction { private int firstActiveIndex; private int secondActiveIndex; - private BlendSpace blendSpace; + final private BlendSpace blendSpace; private float blendWeight; - private double[] timeFactor; - private Map targetMap = new HashMap<>(); + final private double[] timeFactor; + private double[] speedFactors; + final private Map targetMap = new HashMap<>(); public BlendAction(BlendSpace blendSpace, BlendableAction... actions) { super(actions); @@ -47,8 +81,13 @@ public BlendAction(BlendSpace blendSpace, BlendableAction... actions) { } } } + + // Calculate default factors that dynamically adjust speed to resolve + // slow motion effect on stretched actions. + applyDefaultSpeedFactors(); } + @Override public void doInterpolate(double t) { blendWeight = blendSpace.getWeight(); BlendableAction firstActiveAction = (BlendableAction) actions[firstActiveIndex]; @@ -56,7 +95,7 @@ public void doInterpolate(double t) { firstActiveAction.setCollectTransformDelegate(this); secondActiveAction.setCollectTransformDelegate(this); - //only interpolate the first action if the weight if below 1. + // Only interpolate the first action if the weight is below 1. if (blendWeight < 1f) { firstActiveAction.setWeight(1f); firstActiveAction.interpolate(t * timeFactor[firstActiveIndex]); @@ -84,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/BlendSpace.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java index a88be7529c..17b66f8b07 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/BlendSpace.java @@ -1,10 +1,95 @@ +/* + * Copyright (c) 2009-2025 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; +/** + * A provider interface which provides a value {@link BlendSpace#getWeight()} to control the blending between 2 successive actions in a {@link BlendAction}. + * The blending weight is a read-only value, and it can be manipulated using the arbitrary value {@link BlendSpace#setValue(float)} during the application runtime. + * + *

    + * Notes about the blending action and its relations with the blending weight: + *

      + *
    • Blending is the action of mixing between 2 successive animation {@link BlendableAction}s by interpolating their transforms and + * then applying the result on the assigned {@link HasLocalTransform} object, the {@link BlendSpace} provides this blending action with a blend weight value.
    • + *
    • The blend weight is the value for the interpolation for the target transforms.
    • + *
    • The blend weight value must be in this interval [0, 1].
    • + *
    + *

    + * + *

    + * Different blending weight case scenarios managed by {@link BlendAction} internally: + *

      + *
    • In case of (0 < Blending weight < 1), the blending is executed each update among 2 actions, the first action will use + * a blend value of 1 and the second action will use the blend space weight as a value for the interpolation.
    • + *
    • In case of (Blending weight = 0), the blending hasn't started yet, only the first action will be interpolated at (weight = 1).
    • + *
    • In case of (Blending weight = 1), the blending is finished and only the second action will continue to run at (weight = 1).
    • + *
    + *

    + * + *

    + * Notes about the blending weight value: + *

      + *
    • Negative values and values greater than 1 aren't allowed (i.e., extrapolations aren't allowed).
    • + *
    • For more details, see {@link BlendAction#doInterpolate(double)} and {@link BlendAction#collectTransform(HasLocalTransform, Transform, float, BlendableAction)}.
    • + *
    + *

    + * + * Created by Nehon. + * @see LinearBlendSpace an example of blendspace implementation + */ public interface BlendSpace { + /** + * Adjusts the target blend action instance that will utilize the blend weight value provided by this blend-space implementation. + * + * @param action the blend action instance that will utilize this blend-space (not null). + */ public void setBlendAction(BlendAction action); + /** + * Provides the blend weight value to the assigned {@link BlendAction} instance, + * this value will be used for interpolating a collection of actions' transformations (i.e., keyframes). + * + * @return the blending weight value in the range from 0 to 1, + * negative values and values above 1 aren't allowed. + * @see LinearBlendSpace#getWeight() + */ public float getWeight(); + /** + * An arbitrary value used for adjusting the blending weight value. + * + * @param value the value in floats. + * @see LinearBlendSpace#setValue(float) + */ public void setValue(float value); } 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..9064fb042e 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); @@ -73,6 +105,9 @@ public double getTransitionLength() { } public void setTransitionLength(double transitionLength) { + if (transitionLength < 0.0) { + throw new IllegalArgumentException("transitionLength must be greater than or equal to 0"); + } this.transitionLength = transitionLength; this.transition.setLength(transitionLength); } @@ -81,6 +116,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 +176,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/anim/tween/action/ClipAction.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java index fdf211962a..7c442bd122 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/ClipAction.java @@ -1,16 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.tween.action; -import com.jme3.anim.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimTrack; +import com.jme3.anim.MorphTrack; +import com.jme3.anim.TransformTrack; +import com.jme3.anim.tween.action.BlendableAction; import com.jme3.anim.util.HasLocalTransform; import com.jme3.math.Transform; import com.jme3.scene.Geometry; import com.jme3.util.clone.Cloner; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; public class ClipAction extends BlendableAction { - + private AnimClip clip; private Transform transform = new Transform(); @@ -25,7 +61,7 @@ public void doInterpolate(double t) { for (AnimTrack track : tracks) { if (track instanceof TransformTrack) { TransformTrack tt = (TransformTrack) track; - if(getMask() != null && !getMask().contains(tt.getTarget())){ + if (getMask() != null && !getMask().contains(tt.getTarget())) { continue; } interpolateTransformTrack(t, tt); @@ -46,6 +82,7 @@ private void interpolateTransformTrack(double t, TransformTrack track) { this.collectTransform(target, transform, getTransitionWeight(), this); } } + private void interpolateMorphTrack(double t, MorphTrack track) { Geometry target = track.getTarget(); float[] weights = target.getMorphState(); @@ -59,10 +96,16 @@ private void interpolateMorphTrack(double t, MorphTrack track) { // } } - public void reset() { - + /** + * Gets the animation clip associated with this action. + * + * @return The animation clip + */ + public AnimClip getAnimClip() { + return clip; } + @Override public String toString() { return clip.toString(); } @@ -99,8 +142,8 @@ public ClipAction jmeClone() { try { ClipAction clone = (ClipAction) super.clone(); return clone; - } catch (CloneNotSupportedException exception) { - throw new RuntimeException(exception); + } catch (CloneNotSupportedException ex) { + throw new RuntimeException(ex); } } diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java index 31d2931feb..482720051e 100644 --- a/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/LinearBlendSpace.java @@ -4,8 +4,8 @@ public class LinearBlendSpace implements BlendSpace { private BlendAction action; private float value; - private float maxValue; - private float minValue; + final private float maxValue; + final private float minValue; private float step; public LinearBlendSpace(float minValue, float maxValue) { @@ -17,7 +17,7 @@ public LinearBlendSpace(float minValue, float maxValue) { public void setBlendAction(BlendAction action) { this.action = action; Action[] actions = action.getActions(); - step = (maxValue - minValue) / (float) (actions.length - 1); + step = (maxValue - minValue) / (actions.length - 1); } @Override diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/action/package-info.java b/jme3-core/src/main/java/com/jme3/anim/tween/action/package-info.java new file mode 100644 index 0000000000..42439d4665 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/action/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * actions and blending support for the "new" animation system + */ +package com.jme3.anim.tween.action; diff --git a/jme3-core/src/main/java/com/jme3/anim/tween/package-info.java b/jme3-core/src/main/java/com/jme3/anim/tween/package-info.java new file mode 100644 index 0000000000..285e349d49 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/tween/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * tweening support for the "new" animation system + */ +package com.jme3.anim.tween; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java index d50bf90b88..6c463f329d 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/AnimMigrationUtils.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.*; @@ -10,9 +41,14 @@ public class AnimMigrationUtils { - private static AnimControlVisitor animControlVisitor = new AnimControlVisitor(); - private static SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); + private static final AnimControlVisitor animControlVisitor = new AnimControlVisitor(); + private static final SkeletonControlVisitor skeletonControlVisitor = new SkeletonControlVisitor(); + /** + * A private constructor to inhibit instantiation of this class. + */ + private AnimMigrationUtils() { + } public static Spatial migrate(Spatial source) { Map skeletonArmatureMap = new HashMap<>(); @@ -59,6 +95,7 @@ public void visit(Spatial spatial) { Armature armature = new Armature(joints); armature.saveBindPose(); + armature.saveInitialPose(); skeletonArmatureMap.put(skeleton, armature); List tracks = new ArrayList<>(); diff --git a/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java index 28f560dfce..f483cce7f0 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/HasLocalTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.export.Savable; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java index c73c033169..708d1410dd 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/JointModelTransform.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.Joint; @@ -10,11 +41,37 @@ */ public interface JointModelTransform { + /** + * Update the joint's transform in model space. + * + * @param localTransform the joint's local transform (not null, unaffected) + * @param parent the joint's parent, or null for a root joint + */ void updateModelTransform(Transform localTransform, Joint parent); + /** + * Determine the joint's skinning transform. + * + * @param outTransform storage for the result (modified if not null) + * @param inverseModelBindMatrix the joint's inverse model bind matrix (not + * null, unaffected) + */ void getOffsetTransform(Matrix4f outTransform, Matrix4f inverseModelBindMatrix); + /** + * Configure joint's local transform for bind pose. + * + * @param localTransform the joint's local transform (not null, unaffected) + * @param inverseModelBindMatrix the joint's inverse model bind matrix (not + * null, unaffected) + * @param parent the joint's parent, or null for a root joint + */ void applyBindPose(Transform localTransform, Matrix4f inverseModelBindMatrix, Joint parent); + /** + * Determine the joint's transform in model space. + * + * @return a new instance or a pre-existing one + */ Transform getModelTransform(); } diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java index f61a996f88..4e04c49cbb 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Primitives.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import java.util.Collections; @@ -33,15 +64,25 @@ public class Primitives { PRIMITIVE_TO_WRAPPER_TYPE = Collections.unmodifiableMap(primToWrap); } + /** + * A private constructor to inhibit instantiation of this class. + */ + private Primitives() { + } + /** * Returns the corresponding wrapper type of {@code type} if it is a primitive type; otherwise * returns {@code type} itself. Idempotent. - *

    + * *

          *     wrap(int.class) == Integer.class
          *     wrap(Integer.class) == Integer.class
          *     wrap(String.class) == String.class
          * 
    + * + * @param type + * @param type the type to be boxed (not null) + * @return the boxed type */ public static Class wrap(Class type) { if (type == null) { diff --git a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java index f771d44edd..1a2a690d1b 100644 --- a/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java +++ b/jme3-core/src/main/java/com/jme3/anim/util/Weighted.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.anim.util; import com.jme3.anim.tween.action.Action; diff --git a/jme3-core/src/main/java/com/jme3/anim/util/package-info.java b/jme3-core/src/main/java/com/jme3/anim/util/package-info.java new file mode 100644 index 0000000000..96d045ec8d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/anim/util/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * utility classes that support the "new" animation system + */ +package com.jme3.anim.util; diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java index 04c8c2f49a..9f5ef50d56 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimChannel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,20 +38,19 @@ /** * AnimChannel provides controls, such as play, pause, - * fast forward, etc, for an animation. The animation + * fast-forward, etcetera, for an animation. The animation * channel may influence the entire model or specific bones of the model's * skeleton. A single model may have multiple animation channels influencing * various parts of its body. For example, a character model may have an * animation channel for its feet, and another for its torso, and * the animations for each channel are controlled independently. - * + * * @author Kirill Vainer */ @Deprecated public final class AnimChannel { - private static final float DEFAULT_BLEND_TIME = 0.15f; - + private AnimControl control; private BitSet affectedBones; @@ -63,36 +62,35 @@ public final class AnimChannel { private float timeBlendFrom; private float blendTime; private float speedBlendFrom; - private boolean notified=false; + private boolean notified = false; private LoopMode loopMode, loopModeBlendFrom; - + private float blendAmount = 1f; - private float blendRate = 0; - - public AnimChannel(){ - + private float blendRate = 0; + + public AnimChannel() { } - - public AnimChannel(AnimControl control){ + + public AnimChannel(AnimControl control) { this.control = control; } /** * Returns the parent control of this AnimChannel. - * + * * @return the parent control of this AnimChannel. * @see AnimControl */ public AnimControl getControl() { return control; } - + /** * @return The name of the currently playing animation, or null if * none is assigned. * - * @see AnimChannel#setAnim(java.lang.String) + * @see AnimChannel#setAnim(java.lang.String) */ public String getAnimationName() { return animation != null ? animation.getName() : null; @@ -102,7 +100,7 @@ public String getAnimationName() { * @return The loop mode currently set for the animation. The loop mode * determines what will happen to the animation once it finishes * playing. - * + * * For more information, see the LoopMode enum class. * @see LoopMode * @see AnimChannel#setLoopMode(com.jme3.animation.LoopMode) @@ -141,10 +139,10 @@ public float getSpeed() { */ public void setSpeed(float speed) { this.speed = speed; - if(blendTime>0){ + if (blendTime > 0) { this.speedBlendFrom = speed; - blendTime = Math.min(blendTime, animation.getLength() / speed); - blendRate = 1/ blendTime; + blendTime = Math.min(blendTime, animation.getLength() / speed); + blendRate = 1 / blendTime; } } @@ -160,7 +158,7 @@ public float getTime() { /** * @param time Set the time of the currently playing animation, the time - * is clamped from 0 to {@link #getAnimMaxTime()}. + * is clamped from 0 to {@link #getAnimMaxTime()}. */ public void setTime(float time) { this.time = FastMath.clamp(time, 0, getAnimMaxTime()); @@ -172,7 +170,7 @@ public void setTime(float time) { * * @see AnimChannel#getTime() */ - public float getAnimMaxTime(){ + public float getAnimMaxTime() { return animation != null ? animation.getLength() : 0f; } @@ -188,7 +186,7 @@ public float getAnimMaxTime(){ * with the old one. If zero, then no blending will occur and the new * animation will be applied instantly. */ - public void setAnim(String name, float blendTime){ + public void setAnim(String name, float blendTime) { if (name == null) throw new IllegalArgumentException("name cannot be null"); @@ -197,21 +195,21 @@ public void setAnim(String name, float blendTime){ Animation anim = control.animationMap.get(name); if (anim == null) - throw new IllegalArgumentException("Cannot find animation named: '"+name+"'"); + throw new IllegalArgumentException("Cannot find animation named: '" + name + "'"); control.notifyAnimChange(this, name); - if (animation != null && blendTime > 0f){ + if (animation != null && blendTime > 0f) { this.blendTime = blendTime; // activate blending - blendTime = Math.min(blendTime, anim.getLength() / speed); + blendTime = Math.min(blendTime, anim.getLength() / speed); blendFrom = animation; timeBlendFrom = time; speedBlendFrom = speed; loopModeBlendFrom = loopMode; blendAmount = 0f; - blendRate = 1f / blendTime; - }else{ + blendRate = 1f / blendTime; + } else { blendFrom = null; } @@ -227,10 +225,10 @@ public void setAnim(String name, float blendTime){ *

    * See {@link #setAnim(java.lang.String, float)}. * The blendTime argument by default is 150 milliseconds. - * + * * @param name The name of the animation to play */ - public void setAnim(String name){ + public void setAnim(String name) { setAnim(name, DEFAULT_BLEND_TIME); } @@ -244,6 +242,8 @@ public void addAllBones() { /** * Add a single bone to be influenced by this animation channel. + * + * @param name the name of the Bone to be influenced */ public void addBone(String name) { addBone(control.getSkeleton().getBone(name)); @@ -251,10 +251,12 @@ public void addBone(String name) { /** * Add a single bone to be influenced by this animation channel. + * + * @param bone the Bone to be influenced */ public void addBone(Bone bone) { int boneIndex = control.getSkeleton().getBoneIndex(bone); - if(affectedBones == null) { + if (affectedBones == null) { affectedBones = new BitSet(control.getSkeleton().getBoneCount()); } affectedBones.set(boneIndex); @@ -263,6 +265,8 @@ public void addBone(Bone bone) { /** * Add bones to be influenced by this animation channel starting from the * given bone name and going toward the root bone. + * + * @param name the name of the Bone to use as a starting point */ public void addToRootBone(String name) { addToRootBone(control.getSkeleton().getBone(name)); @@ -271,6 +275,8 @@ public void addToRootBone(String name) { /** * Add bones to be influenced by this animation channel starting from the * given bone and going toward the root bone. + * + * @param bone the Bone to use as a starting point */ public void addToRootBone(Bone bone) { addBone(bone); @@ -283,6 +289,8 @@ public void addToRootBone(Bone bone) { /** * Add bones to be influenced by this animation channel, starting * from the given named bone and going toward its children. + * + * @param name the name of the Bone to use as a starting point */ public void addFromRootBone(String name) { addFromRootBone(control.getSkeleton().getBone(name)); @@ -291,6 +299,8 @@ public void addFromRootBone(String name) { /** * Add bones to be influenced by this animation channel, starting * from the given bone and going toward its children. + * + * @param bone the Bone to use as a starting point */ public void addFromRootBone(Bone bone) { addBone(bone); @@ -302,19 +312,19 @@ public void addFromRootBone(Bone bone) { } } - BitSet getAffectedBones(){ + BitSet getAffectedBones() { return affectedBones; } - - public void reset(boolean rewind){ - if(rewind){ - setTime(0); - if(control.getSkeleton()!=null){ + + public void reset(boolean rewind) { + if (rewind) { + setTime(0); + if (control.getSkeleton() != null) { control.getSkeleton().resetAndUpdate(); - }else{ + } else { TempVars vars = TempVars.get(); update(0, vars); - vars.release(); + vars.release(); } } animation = null; @@ -325,43 +335,43 @@ void update(float tpf, TempVars vars) { if (animation == null) return; - if (blendFrom != null && blendAmount != 1.0f){ + if (blendFrom != null && blendAmount != 1.0f) { // The blendFrom anim is set, the actual animation - // playing will be set + // playing will be set // blendFrom.setTime(timeBlendFrom, 1f, control, this, vars); blendFrom.setTime(timeBlendFrom, 1f - blendAmount, control, this, vars); - + timeBlendFrom += tpf * speedBlendFrom; timeBlendFrom = AnimationUtils.clampWrapTime(timeBlendFrom, - blendFrom.getLength(), - loopModeBlendFrom); - if (timeBlendFrom < 0){ + blendFrom.getLength(), + loopModeBlendFrom); + if (timeBlendFrom < 0) { timeBlendFrom = -timeBlendFrom; speedBlendFrom = -speedBlendFrom; } blendAmount += tpf * blendRate; - if (blendAmount > 1f){ + if (blendAmount > 1f) { blendAmount = 1f; blendFrom = null; } } - + animation.setTime(time, blendAmount, control, this, vars); - time += tpf * speed; - if (animation.getLength() > 0){ + time += tpf * speed; + if (animation.getLength() > 0) { if (!notified && (time >= animation.getLength() || time < 0)) { if (loopMode == LoopMode.DontLoop) { - // Note that this flag has to be set before calling the notify + // Note that this flag has to be set before calling the notify, // since the notify may start a new animation and then unset // the flag. notified = true; } control.notifyAnimCycleDone(this, animation.getName()); - } + } } time = AnimationUtils.clampWrapTime(time, animation.getLength(), loopMode); - if (time < 0){ + if (time < 0) { // Negative time indicates that speed should be inverted // (for cycle loop mode only) time = -time; diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java index 022170a2e0..a7f9f1a35a 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,15 +79,15 @@ public final class AnimControl extends AbstractControl implements Cloneable, Jme /** * List of animations */ - HashMap animationMap = new HashMap(); + HashMap animationMap = new HashMap<>(); /** * Animation channels */ - private transient ArrayList channels = new ArrayList(); + private transient ArrayList channels = new ArrayList<>(); /** * Animation event listeners */ - private transient ArrayList listeners = new ArrayList(); + private transient ArrayList listeners = new ArrayList<>(); /** * Creates a new animation control for the given skeleton. @@ -109,32 +109,32 @@ public AnimControl(Skeleton skeleton) { public AnimControl() { } - @Override + @Override public Object jmeClone() { AnimControl clone = (AnimControl) super.jmeClone(); clone.channels = new ArrayList(); clone.listeners = new ArrayList(); return clone; - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); - + this.skeleton = cloner.clone(skeleton); - - // Note cloneForSpatial() never actually cloned the animation map... just its reference + + // Note cloneForSpatial() never actually cloned the animation map... just its reference HashMap newMap = new HashMap<>(); - + // animationMap is cloned, but only ClonableTracks will be cloned as they need a reference to a cloned spatial - for( Map.Entry e : animationMap.entrySet() ) { + for (Map.Entry e : animationMap.entrySet()) { newMap.put(e.getKey(), cloner.clone(e.getValue())); } - + this.animationMap = newMap; } - + /** * @param animations Set the animations that this AnimControl * will be capable of playing. The animations should be compatible @@ -182,7 +182,7 @@ public void removeAnim(Animation anim) { /** * Create a new animation channel, by default assigned to all bones * in the skeleton. - * + * * @return A new animation channel for this AnimControl. */ public AnimChannel createChannel() { @@ -242,7 +242,7 @@ public Skeleton getSkeleton() { public void addListener(AnimEventListener listener) { if (listeners.contains(listener)) { throw new IllegalArgumentException("The given listener is already " - + "registed at this AnimControl"); + + "registered at this AnimControl"); } listeners.add(listener); @@ -251,13 +251,13 @@ public void addListener(AnimEventListener listener) { /** * Removes the given listener from listening to events. * - * @param listener + * @param listener the listener to remove * @see AnimControl#addListener(com.jme3.animation.AnimEventListener) */ public void removeListener(AnimEventListener listener) { if (!listeners.remove(listener)) { throw new IllegalArgumentException("The given listener is not " - + "registed at this AnimControl"); + + "registered at this AnimControl"); } } @@ -365,6 +365,7 @@ public void write(JmeExporter ex) throws IOException { } @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule in = im.getCapsule(this); @@ -375,12 +376,12 @@ public void read(JmeImporter im) throws IOException { } if (im.getFormatVersion() == 0) { - // Changed for backward compatibility with j3o files generated + // Changed for backward compatibility with j3o files generated // before the AnimControl/SkeletonControl split. - // If we find a target mesh array the AnimControl creates the - // SkeletonControl for old files and add it to the spatial. - // When backward compatibility won't be needed anymore this can deleted + // If we find a target mesh array the AnimControl creates the + // SkeletonControl for old files and add it to the spatial. + // When backward compatibility isn't needed anymore, this can be deleted. Savable[] sav = in.readSavableArray("targets", null); if (sav != null) { // NOTE: allow the targets to be gathered automatically diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java index c20f150da1..eefa1a9189 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ public interface AnimEventListener { /** * Invoked when an animation "cycle" is done. For non-looping animations, * this event is invoked when the animation is finished playing. For - * looping animations, this even is invoked each time the animation is restarted. + * looping animations, this event is invoked each time the animation is restarted. * * @param control The control to which the listener is assigned. * @param channel The channel being altered @@ -52,7 +52,7 @@ public interface AnimEventListener { public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName); /** - * Invoked when a animation is set to play by the user on the given channel. + * Invoked when an animation is set to play by the user on the given channel. * * @param control The control to which the listener is assigned. * @param channel The channel being altered diff --git a/jme3-core/src/main/java/com/jme3/animation/Animation.java b/jme3-core/src/main/java/com/jme3/animation/Animation.java index 6e899c5fc4..554e6a0cc5 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Animation.java +++ b/jme3-core/src/main/java/com/jme3/animation/Animation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,25 +42,24 @@ /** * The animation class updates the animation target with the tracks of a given type. - * + * * @author Kirill Vainer, Marcin Roguski (Kaelthas) * @deprecated use {@link com.jme3.anim.AnimClip} */ @Deprecated public class Animation implements Savable, Cloneable, JmeCloneable { - - /** - * The name of the animation. + /** + * The name of the animation. */ private String name; - /** - * The length of the animation. + /** + * The length of the animation. */ private float length; - /** - * The tracks of the animation. + /** + * The tracks of the animation. */ - private SafeArrayList tracks = new SafeArrayList(Track.class); + private SafeArrayList tracks = new SafeArrayList<>(Track.class); /** * Serialization-only. Do not use. @@ -70,7 +69,7 @@ protected Animation() { /** * Creates a new Animation with the given name and length. - * + * * @param name The name of the animation. * @param length Length in seconds of the animation. */ @@ -89,7 +88,7 @@ public String getName() { /** * Returns the length in seconds of this animation - * + * * @return the length in seconds of this animation */ public float getLength() { @@ -99,7 +98,7 @@ public float getLength() { /** * Set the length of the animation * - * @param length + * @param length the desired duration (in seconds) */ public void setLength(float length) { this.length = length; @@ -108,7 +107,7 @@ public void setLength(float length) { /** * Sets the name of the animation * - * @param name + * @param name the desired name */ public void setName(String name) { this.name = name; @@ -118,7 +117,7 @@ public void setName(String name) { * This method sets the current time of the animation. * This method behaves differently for every known track type. * Override this method if you have your own type of track. - * + * * @param time the time of the animation * @param blendAmount the blend amount factor * @param control the animation control @@ -136,7 +135,7 @@ void setTime(float time, float blendAmount, AnimControl control, AnimChannel cha /** * Set the {@link Track}s to be used by this animation. - * + * * @param tracksArray The tracks to set. */ public void setTracks(Track[] tracksArray) { @@ -166,7 +165,7 @@ public void removeTrack(Track track) { /** * Returns the tracks set in {@link #setTracks(com.jme3.animation.Track[]) }. - * + * * @return the tracks set previously */ public Track[] getTracks() { @@ -181,7 +180,7 @@ public Track[] getTracks() { public Animation clone() { try { Animation result = (Animation) super.clone(); - result.tracks = new SafeArrayList(Track.class); + result.tracks = new SafeArrayList<>(Track.class); for (Track track : tracks) { result.tracks.add(track.clone()); } @@ -192,14 +191,14 @@ public Animation clone() { } /** - * - * @param spat + * + * @param spat the Spatial to clone for * @return a new instance */ public Animation cloneForSpatial(Spatial spat) { try { Animation result = (Animation) super.clone(); - result.tracks = new SafeArrayList(Track.class); + result.tracks = new SafeArrayList<>(Track.class); for (Track track : tracks) { if (track instanceof ClonableTrack) { result.tracks.add(((ClonableTrack) track).cloneForSpatial(spat)); @@ -213,33 +212,32 @@ public Animation cloneForSpatial(Spatial spat) { } } - @Override + @Override public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning", e); } - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { - - // There is some logic here that I'm copying but I'm not sure if + @Override + public void cloneFields(Cloner cloner, Object original) { + // There is some logic here that I'm copying, but I'm not sure if // it's a mistake or not. If a track is not a CloneableTrack then it // isn't cloned at all... even though they all implement clone() methods. -pspeed SafeArrayList newTracks = new SafeArrayList<>(Track.class); - for( Track track : tracks ) { + for (Track track : tracks) { if (track instanceof JmeCloneable) { newTracks.add(cloner.clone(track)); } else { - // this is the part that seems fishy + // this is the part that seems fishy newTracks.add(track); } } this.tracks = newTracks; } - + @Override public String toString() { return getClass().getSimpleName() + "[name=" + name + ", length=" + length + ']'; @@ -261,11 +259,11 @@ public void read(JmeImporter im) throws IOException { Savable[] arr = in.readSavableArray("tracks", null); if (arr != null) { - // NOTE: Backward compat only .. Some animations have no - // tracks set at all even though it makes no sense. + // NOTE: Backward compatibility only. Some animations have no + // tracks set at all, even though it makes no sense. // Since there's a null check in setTime(), // it's only appropriate that the check is made here as well. - tracks = new SafeArrayList(Track.class); + tracks = new SafeArrayList<>(Track.class); for (Savable savable : arr) { tracks.add((Track) savable); } diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java index 0b0a435c76..74e0daef36 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimationFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,25 +37,27 @@ import com.jme3.math.Vector3f; /** - * A convenience class to easily setup a spatial keyframed animation - * you can add some keyFrames for a given time or a given keyFrameIndex, for translation rotation and scale. - * The animationHelper will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames. + * A convenience class to easily set up a spatial keyframe animation. + * You can add some keyFrames for a given time or a given keyFrameIndex, for translation, rotation, and scale. + * The animationFactory will then generate an appropriate SpatialAnimation by interpolating values between the keyFrames. *

    * Usage is :
    - * - Create the AnimationHelper
    + * - Create the AnimationFactory
    * - add some keyFrames
    * - call the buildAnimation() method that will return a new Animation
    * - add the generated Animation to any existing AnimationControl
    *

    - * Note that the first keyFrame (index 0) is defaulted with the identity transforms. - * If you want to change that you have to replace this keyFrame with any transform you want. + * Note that the first keyFrame (index 0) is defaulted with the identity transform. + * If you want to change that you have to replace this keyFrame with the transform you want. * * @author Nehon + * @deprecated use {@link com.jme3.anim.AnimFactory} */ +@Deprecated public class AnimationFactory { /** - * step for splitting rotation that have a n angle above PI/2 + * step for splitting rotations that have an angle above PI/2 */ private final static float EULER_STEP = FastMath.QUARTER_PI * 3; @@ -68,8 +70,9 @@ private enum Type { } /** - * Inner Rotation type class to kep track on a rotation Euler angle + * inner class to keep track of a rotation using Euler angles */ + @Deprecated protected class Rotation { /** @@ -81,7 +84,7 @@ protected class Rotation { */ Vector3f eulerAngles = new Vector3f(); /** - * the index of the parent key frame is this keyFrame is a splitted rotation + * the index of the parent key frame is this keyFrame is a split rotation */ int masterKeyFrame = -1; @@ -146,7 +149,7 @@ void set(float x, float y, float z) { protected Rotation[] keyFramesRotation; /** - * Creates and AnimationHelper + * Creates an AnimationFactory * @param duration the desired duration for the resulting animation * @param name the name of the resulting animation */ @@ -155,7 +158,7 @@ public AnimationFactory(float duration, String name) { } /** - * Creates and AnimationHelper + * Creates an AnimationFactory * @param duration the desired duration for the resulting animation * @param name the name of the resulting animation * @param fps the number of frames per second for this animation (default is 30) @@ -182,7 +185,7 @@ public AnimationFactory(float duration, String name, int fps) { /** * Adds a key frame for the given Transform at the given time * @param time the time at which the keyFrame must be inserted - * @param transform the transforms to use for this keyFrame + * @param transform the transform to use for this keyFrame */ public void addTimeTransform(float time, Transform transform) { addKeyFrameTransform((int) (time / tpf), transform); @@ -191,7 +194,7 @@ public void addTimeTransform(float time, Transform transform) { /** * Adds a key frame for the given Transform at the given keyFrame index * @param keyFrameIndex the index at which the keyFrame must be inserted - * @param transform the transforms to use for this keyFrame + * @param transform the transform to use for this keyFrame */ public void addKeyFrameTransform(int keyFrameIndex, Transform transform) { addKeyFrameTranslation(keyFrameIndex, transform.getTranslation()); @@ -221,7 +224,7 @@ public void addKeyFrameTranslation(int keyFrameIndex, Vector3f translation) { /** * Adds a key frame for the given rotation at the given time
    * This can't be used if the interpolated angle is higher than PI (180°)
    - * Use {@link #addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angles rotations.
    * + * Use {@link #addTimeRotationAngles(float time, float x, float y, float z)} instead that uses Euler angle rotations.
    * @param time the time at which the keyFrame must be inserted * @param rotation the rotation Quaternion to use for this keyFrame * @see #addTimeRotationAngles(float time, float x, float y, float z) @@ -233,7 +236,7 @@ public void addTimeRotation(float time, Quaternion rotation) { /** * Adds a key frame for the given rotation at the given keyFrame index
    * This can't be used if the interpolated angle is higher than PI (180°)
    - * Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angles rotations. + * Use {@link #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z)} instead that uses Euler angle rotations. * @param keyFrameIndex the index at which the keyFrame must be inserted * @param rotation the rotation Quaternion to use for this keyFrame * @see #addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float z) @@ -245,7 +248,7 @@ public void addKeyFrameRotation(int keyFrameIndex, Quaternion rotation) { /** * Adds a key frame for the given rotation at the given time.
    - * Rotation is expressed by Euler angles values in radians.
    + * Rotation is expressed by Euler angles in radians.
    * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
    * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
    * @@ -260,7 +263,7 @@ public void addTimeRotationAngles(float time, float x, float y, float z) { /** * Adds a key frame for the given rotation at the given key frame index.
    - * Rotation is expressed by Euler angles values in radians.
    + * Rotation is expressed by Euler angles in radians.
    * Note that the generated rotation will be stored as a quaternion and interpolated using a spherical linear interpolation (slerp)
    * Hence, this method may create intermediate keyFrames if the interpolation angle is higher than PI to ensure continuity in animation
    * @@ -273,7 +276,7 @@ public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float Rotation r = getRotationForFrame(keyFrameIndex); r.set(x, y, z); - // if the delta of euler angles is higher than PI, we create intermediate keyframes + // if the delta of Euler angles is higher than PI, we create intermediate keyframes // since we are using quaternions and slerp for rotation interpolation, we cannot interpolate over an angle higher than PI int prev = getPreviousKeyFrame(keyFrameIndex, keyFramesRotation); if (prev != -1) { @@ -287,12 +290,12 @@ public void addKeyFrameRotationAngles(int keyFrameIndex, float x, float y, float //frames delta int dF = keyFrameIndex - prev; //angle per frame for x,y ,z - float dXAngle = (x - prevRot.eulerAngles.x) / (float) dF; - float dYAngle = (y - prevRot.eulerAngles.y) / (float) dF; - float dZAngle = (z - prevRot.eulerAngles.z) / (float) dF; + float dXAngle = (x - prevRot.eulerAngles.x) / dF; + float dYAngle = (y - prevRot.eulerAngles.y) / dF; + float dZAngle = (z - prevRot.eulerAngles.z) / dF; // the keyFrame step - int keyStep = (int) (((float) (dF)) / delta * (float) EULER_STEP); + int keyStep = (int) (dF / delta * EULER_STEP); // the current keyFrame int cursor = prev + keyStep; while (cursor < keyFrameIndex) { @@ -329,7 +332,7 @@ public void addKeyFrameScale(int keyFrameIndex, Vector3f scale) { /** * returns the translation for a given frame index - * creates the translation if it doesn't exists + * creates the translation if it doesn't exist * @param keyFrameIndex index * @return the translation */ @@ -347,7 +350,7 @@ private Vector3f getTranslationForFrame(int keyFrameIndex) { /** * returns the scale for a given frame index - * creates the scale if it doesn't exists + * creates the scale if it doesn't exist * @param keyFrameIndex index * @return the scale */ @@ -365,7 +368,7 @@ private Vector3f getScaleForFrame(int keyFrameIndex) { /** * returns the rotation for a given frame index - * creates the rotation if it doesn't exists + * creates the rotation if it doesn't exist * @param keyFrameIndex index * @return the rotation */ @@ -382,7 +385,7 @@ private Rotation getRotationForFrame(int keyFrameIndex) { } /** - * Creates an Animation based on the keyFrames previously added to the helper. + * Creates an Animation based on the keyFrames previously added to the factory. * @return the generated animation */ public Animation buildAnimation() { @@ -422,11 +425,11 @@ private void interpolate(Object[] keyFrames, Type type) { if (key != -1) { //computing the frame span to interpolate over int span = key - i; - //interating over the frames + //iterating over the frames for (int j = i; j <= key; j++) { // computing interpolation value - float val = (float) (j - i) / (float) span; - //interpolationg depending on the transform type + float val = (j - i) / (float) span; + //interpolating depending on the transform type switch (type) { case Translation: translations[j] = FastMath.interpolateLinear(val, (Vector3f) keyFrames[i], (Vector3f) keyFrames[key]); @@ -451,7 +454,7 @@ private void interpolate(Object[] keyFrames, Type type) { translations[j] = ((Vector3f) keyFrames[i]).clone(); break; case Rotation: - rotations[j] = ((Quaternion) ((Rotation) keyFrames[i]).rotation).clone(); + rotations[j] = ((Rotation) keyFrames[i]).rotation.clone(); break; case Scale: scales[j] = ((Vector3f) keyFrames[i]).clone(); diff --git a/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java b/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java index f7f2c54ddd..3e311a288a 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java +++ b/jme3-core/src/main/java/com/jme3/animation/AnimationUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,21 +36,21 @@ * @author Nehon */ public class AnimationUtils { - - public AnimationUtils(){ - + private AnimationUtils() { } + /** * Clamps the time according to duration and loopMode - * @param time - * @param duration - * @param loopMode + * + * @param time the unclamped time value (in seconds) + * @param duration the animation's duration (in seconds) + * @param loopMode the animation's looping behavior (not null) * @return the clamped time (in seconds) */ - public static float clampWrapTime(float time, float duration, LoopMode loopMode){ - if (time == 0 || duration == 0) { + public static float clampWrapTime(float time, float duration, LoopMode loopMode) { + if (time == 0 || duration == 0) { return 0; // prevent division by 0 errors - } + } switch (loopMode) { case Cycle: boolean sign = ((int) (time / duration) % 2) != 0; @@ -61,5 +61,5 @@ public static float clampWrapTime(float time, float duration, LoopMode loopMode) return time % duration; } return time; - } + } } diff --git a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java index e511cc9d95..cccb883bee 100644 --- a/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/AudioTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,12 +44,12 @@ /** * AudioTrack is a track to add to an existing animation, to play a sound during - * an animations for example : gun shot, foot step, shout, etc... + * an animations for example : gunshot, footstep, shout, etcetera. * - * usage is + * Typical usage is: *

      * AnimControl control model.getControl(AnimControl.class);
    - * AudioTrack track = new AudioTrack(existionAudioNode, control.getAnim("TheAnim").getLength());
    + * AudioTrack track = new AudioTrack(existingAudioNode, control.getAnim("TheAnim").getLength());
      * control.getAnim("TheAnim").addTrack(track);
      * 
    * @@ -73,10 +73,12 @@ public class AudioTrack implements ClonableTrack { //Animation listener to stop the sound when the animation ends or is changed private class OnEndListener implements AnimEventListener { + @Override public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { stop(); } + @Override public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { } } @@ -120,6 +122,7 @@ public AudioTrack(AudioNode audio, float length, float startOffset) { * @see Track#setTime(float, float, com.jme3.animation.AnimControl, * com.jme3.animation.AnimChannel, com.jme3.util.TempVars) */ + @Override public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { if (time >= length) { @@ -146,22 +149,23 @@ private void stop() { * * @return length of the track */ + @Override public float getLength() { return length; } @Override public float[] getKeyFrameTimes() { - return new float[] { startOffset }; + return new float[]{startOffset}; } - + /** * Clone this track * * @return a new track */ @Override - public Track clone() { + public AudioTrack clone() { return new AudioTrack(audio, length, startOffset); } @@ -193,27 +197,25 @@ public Track cloneForSpatial(Spatial spatial) { return audioTrack; } - @Override + @Override public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning", e); } - } - + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { // Duplicating the old cloned state from cloneForSpatial() this.initialized = false; this.started = false; - this.played = false; + this.played = false; this.audio = cloner.clone(audio); } - - - /** + + /** * recursive function responsible for finding the newly cloned AudioNode * * @param spat @@ -255,6 +257,7 @@ private void setUserData(AudioTrack audioTrack) { data.addTrack(audioTrack); } + @Override public void cleanUp() { TrackInfo t = (TrackInfo) audio.getUserData("TrackInfo"); t.getTracks().remove(this); @@ -274,7 +277,7 @@ public AudioNode getAudio() { /** * sets the audio node to be used for this track * - * @param audio + * @param audio the desired AudioNode (alias created) */ public void setAudio(AudioNode audio) { if (this.audio != null) { @@ -296,7 +299,7 @@ public float getStartOffset() { /** * set the start offset of the track * - * @param startOffset + * @param startOffset the desired start offset */ public void setStartOffset(float startOffset) { this.startOffset = startOffset; @@ -308,6 +311,7 @@ public void setStartOffset(float startOffset) { * @param ex exporter * @throws IOException exception */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); out.write(audio, "audio", null); @@ -321,6 +325,7 @@ public void write(JmeExporter ex) throws IOException { * @param im importer * @throws IOException Exception */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); audio = (AudioNode) in.readSavable("audio", null); diff --git a/jme3-core/src/main/java/com/jme3/animation/Bone.java b/jme3-core/src/main/java/com/jme3/animation/Bone.java index a171002fec..2d4211b90a 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Bone.java +++ b/jme3-core/src/main/java/com/jme3/animation/Bone.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,21 +48,21 @@ * Bone describes a bone in the bone-weight skeletal animation * system. A bone contains a name and an index, as well as relevant * transformation data. - * + * * A bone has 3 sets of transforms : * 1. The bind transforms, that are the transforms of the bone when the skeleton - * is in its rest pose (also called bind pose or T pose in the literature). - * The bind transforms are expressed in Local space meaning relatively to the + * is in its rest pose (also called bind pose or T pose in the literature). + * The bind transforms are expressed in Local space meaning relatively to the * parent bone. - * + * * 2. The Local transforms, that are the transforms of the bone once animation * or user transforms has been applied to the bind pose. The local transforms are * expressed in Local space meaning relatively to the parent bone. - * - * 3. The Model transforms, that are the transforms of the bone relatives to the - * rootBone of the skeleton. Those transforms are what is needed to apply skinning + * + * 3. The Model transforms, that are the transforms of the bone relatives to the + * rootBone of the skeleton. Those transforms are what is needed to apply skinning * to the mesh the skeleton controls. - * Note that there can be several rootBones in a skeleton. The one considered for + * Note that there can be several rootBones in a skeleton. The one considered for * these transforms is the one that is an ancestor of this bone. * * @author Kirill Vainer @@ -76,7 +76,7 @@ public final class Bone implements Savable, JmeCloneable { public static final int SAVABLE_VERSION = 2; private String name; private Bone parent; - private ArrayList children = new ArrayList(); + private ArrayList children = new ArrayList<>(); /** * If enabled, user can control bone transform with setUserTransforms. * Animation transforms are not applied to this bone when enabled. @@ -96,14 +96,14 @@ public final class Bone implements Savable, JmeCloneable { private Vector3f bindPos; private Quaternion bindRot; private Vector3f bindScale; - + /** - * The inverse bind transforms of this bone expressed in model space + * The inverse bind transforms of this bone expressed in model space */ private Vector3f modelBindInversePos; private Quaternion modelBindInverseRot; private Vector3f modelBindInverseScale; - + /** * The local animated or user transform combined with the local bind transform */ @@ -111,15 +111,15 @@ public final class Bone implements Savable, JmeCloneable { private Quaternion localRot = new Quaternion(); private Vector3f localScale = new Vector3f(1.0f, 1.0f, 1.0f); /** - * The model transforms of this bone + * The model transforms of this bone */ private Vector3f modelPos = new Vector3f(); private Quaternion modelRot = new Quaternion(); private Vector3f modelScale = new Vector3f(); - + // Used for getCombinedTransform private Transform tmpTransform; - + /** * Used to handle blending from one animation to another. * See {@link #blendAnimTransforms(com.jme3.math.Vector3f, com.jme3.math.Quaternion, com.jme3.math.Vector3f, float)} @@ -129,13 +129,13 @@ public final class Bone implements Savable, JmeCloneable { /** * Creates a new bone with the given name. - * + * * @param name Name to give to this bone */ public Bone(String name) { if (name == null) throw new IllegalArgumentException("Name cannot be null"); - + this.name = name; bindPos = new Vector3f(); @@ -148,13 +148,13 @@ public Bone(String name) { } /** - * Special-purpose copy constructor. + * Special-purpose copy constructor. *

    * Only copies the name, user control state and bind pose transforms from the original. *

    * The rest of the data is NOT copied, as it will be * generated automatically when the bone is animated. - * + * * @param source The bone from which to copy the data. */ Bone(Bone source) { @@ -170,7 +170,7 @@ public Bone(String name) { modelBindInverseRot = source.modelBindInverseRot.clone(); modelBindInverseScale = source.modelBindInverseScale.clone(); - // parent and children will be assigned manually.. + // Parent and children will be assigned manually. } /** @@ -178,48 +178,48 @@ public Bone(String name) { */ protected Bone() { } - - @Override + + @Override public Object jmeClone() { try { - Bone clone = (Bone)super.clone(); + Bone clone = (Bone) super.clone(); return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); } - } + } + + @Override + public void cloneFields(Cloner cloner, Object original) { - @Override - public void cloneFields( Cloner cloner, Object original ) { - this.parent = cloner.clone(parent); - this.children = cloner.clone(children); - + this.children = cloner.clone(children); + this.attachNode = cloner.clone(attachNode); this.targetGeometry = cloner.clone(targetGeometry); this.bindPos = cloner.clone(bindPos); this.bindRot = cloner.clone(bindRot); this.bindScale = cloner.clone(bindScale); - + this.modelBindInversePos = cloner.clone(modelBindInversePos); this.modelBindInverseRot = cloner.clone(modelBindInverseRot); this.modelBindInverseScale = cloner.clone(modelBindInverseScale); - + this.localPos = cloner.clone(localPos); this.localRot = cloner.clone(localRot); this.localScale = cloner.clone(localScale); - + this.modelPos = cloner.clone(modelPos); this.modelRot = cloner.clone(modelRot); this.modelScale = cloner.clone(modelScale); - + this.tmpTransform = cloner.clone(tmpTransform); } /** * Returns the name of the bone, set in the constructor. - * + * * @return The name of the bone, set in the constructor. */ public String getName() { @@ -236,7 +236,7 @@ public Bone getParent() { /** * Returns all the children bones of this bone. - * + * * @return All the children bones of this bone. */ public ArrayList getChildren() { @@ -245,7 +245,7 @@ public ArrayList getChildren() { /** * Returns the local position of the bone, relative to the parent bone. - * + * * @return The local position of the bone, relative to the parent bone. */ public Vector3f getLocalPosition() { @@ -254,7 +254,7 @@ public Vector3f getLocalPosition() { /** * Returns the local rotation of the bone, relative to the parent bone. - * + * * @return The local rotation of the bone, relative to the parent bone. */ public Quaternion getLocalRotation() { @@ -263,7 +263,7 @@ public Quaternion getLocalRotation() { /** * Returns the local scale of the bone, relative to the parent bone. - * + * * @return The local scale of the bone, relative to the parent bone. */ public Vector3f getLocalScale() { @@ -272,7 +272,7 @@ public Vector3f getLocalScale() { /** * Returns the position of the bone in model space. - * + * * @return The position of the bone in model space. */ public Vector3f getModelSpacePosition() { @@ -281,7 +281,7 @@ public Vector3f getModelSpacePosition() { /** * Returns the rotation of the bone in model space. - * + * * @return The rotation of the bone in model space. */ public Quaternion getModelSpaceRotation() { @@ -290,68 +290,70 @@ public Quaternion getModelSpaceRotation() { /** * Returns the scale of the bone in model space. - * + * * @return The scale of the bone in model space. */ public Vector3f getModelSpaceScale() { return modelScale; } - /** + /** + * @return the pre-existing vector * @deprecated use {@link #getModelBindInversePosition()} */ - @Deprecated + @Deprecated public Vector3f getWorldBindInversePosition() { return modelBindInversePos; } - - /** + + /** * Returns the inverse Bind position of this bone expressed in model space. *

    * The inverse bind pose transform of the bone in model space is its "default" * transform with no animation applied. - * + * * @return the inverse bind position of this bone expressed in model space. - */ + */ public Vector3f getModelBindInversePosition() { return modelBindInversePos; } - /** + /** + * @return the pre-existing Quaternion * @deprecated use {@link #getModelBindInverseRotation()} */ @Deprecated public Quaternion getWorldBindInverseRotation() { return modelBindInverseRot; } - - /** + + /** * Returns the inverse bind rotation of this bone expressed in model space. *

    * The inverse bind pose transform of the bone in model space is its "default" * transform with no animation applied. - * + * * @return the inverse bind rotation of this bone expressed in model space. */ public Quaternion getModelBindInverseRotation() { return modelBindInverseRot; } - - /** + /** + * @return the pre-existing vector * @deprecated use {@link #getModelBindInverseScale()} */ @Deprecated public Vector3f getWorldBindInverseScale() { return modelBindInverseScale; } - + /** * Returns the inverse world bind pose scale. *

    * The inverse bind pose transform of the bone in model space is its "default" * transform with no animation applied. - * + * * @return the inverse world bind pose scale. */ public Vector3f getModelBindInverseScale() { @@ -367,7 +369,7 @@ public Transform getModelBindInverseTransform() { } return t; } - + public Transform getBindInverseTransform() { Transform t = new Transform(); t.setTranslation(bindPos); @@ -377,30 +379,32 @@ public Transform getBindInverseTransform() { } return t.invert(); } - - /** + + /** + * @return the pre-existing vector * @deprecated use {@link #getBindPosition()} */ @Deprecated public Vector3f getWorldBindPosition() { return bindPos; } - - /** + + /** * Returns the bind position expressed in local space (relative to the parent bone). *

    * The bind pose transform of the bone in local space is its "default" * transform with no animation applied. - * + * * @return the bind position in local space. */ public Vector3f getBindPosition() { return bindPos; } - /** + /** + * @return the pre-existing Quaternion * @deprecated use {@link #getBindRotation() } - */ + */ @Deprecated public Quaternion getWorldBindRotation() { return bindRot; @@ -411,27 +415,28 @@ public Quaternion getWorldBindRotation() { *

    * The bind pose transform of the bone in local space is its "default" * transform with no animation applied. - * + * * @return the bind rotation in local space. - */ + */ public Quaternion getBindRotation() { return bindRot; - } - + } + /** + * @return the pre-existing vector * @deprecated use {@link #getBindScale() } */ @Deprecated public Vector3f getWorldBindScale() { return bindScale; } - + /** * Returns the bind scale expressed in local space (relative to the parent bone). *

    * The bind pose transform of the bone in local space is its "default" * transform with no animation applied. - * + * * @return the bind scale in local space. */ public Vector3f getBindScale() { @@ -441,6 +446,8 @@ public Vector3f getBindScale() { /** * If enabled, user can control bone transform with setUserTransforms. * Animation transforms are not applied to this bone when enabled. + * + * @param enable true for direct control, false for canned animations */ public void setUserControl(boolean enable) { userControl = enable; @@ -449,7 +456,7 @@ public void setUserControl(boolean enable) { /** * Add a new child to this bone. Shouldn't be used by user code. * Can corrupt skeleton. - * + * * @param bone The bone to add */ public void addChild(Bone bone) { @@ -458,21 +465,20 @@ public void addChild(Bone bone) { } /** - * + * * @deprecated use {@link #updateModelTransforms() } */ @Deprecated - public final void updateWorldVectors(){ + public final void updateWorldVectors() { updateModelTransforms(); } - - + /** - * Updates the model transforms for this bone, and, possibly the attach node + * Updates the model transforms for this bone and, possibly, the attachments node * if not null. *

    * The model transform of this bone is computed by combining the parent's - * model transform with this bones' local transform. + * model transform with this bone's local transform. */ public final void updateModelTransforms() { if (currentWeightSum == 1f) { @@ -489,11 +495,11 @@ public final void updateModelTransforms() { localPos.interpolateLocal(bindPos, invWeightSum); localScale.interpolateLocal(bindScale, invWeightSum); } - + // Future invocations of transform blend will start over. currentWeightSum = -1; } - + if (parent != null) { //rotation parent.modelRot.mult(localRot, modelRot); @@ -504,7 +510,7 @@ public final void updateModelTransforms() { parent.modelScale.mult(localScale, modelScale); //translation - //scale and rotation of parent affect bone position + //scale and rotation of parent affect bone position parent.modelRot.mult(localPos, modelPos); modelPos.multLocal(parent.modelScale); modelPos.addLocal(parent.modelPos); @@ -616,14 +622,14 @@ final void reset() { } } - /** + /** * Stores the skinning transform in the specified Matrix4f. * The skinning transform applies the animation of the bone to a vertex. - * + * * This assumes that the world transforms for the entire bone hierarchy * have already been computed, otherwise this method will return undefined * results. - * + * * @param outTransform */ void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, Vector3f tmp3, Matrix3f tmp4) { @@ -634,7 +640,7 @@ void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, V Quaternion rotate = modelRot.mult(modelBindInverseRot, tmp1); // Computing translation - // Translation depend on rotation and scale + // Translation depends on rotation and scale. Vector3f translate = modelPos.add(rotate.mult(scale.mult(modelBindInversePos, tmp2), tmp2), tmp2); // Populating the matrix @@ -642,7 +648,7 @@ void getOffsetTransform(Matrix4f outTransform, Quaternion tmp1, Vector3f tmp2, V } /** - * + * * Sets the transforms of this bone in local space (relative to the parent bone) * * @param translation the translation in local space @@ -663,19 +669,9 @@ public void setUserTransforms(Vector3f translation, Quaternion rotation, Vector3 localScale.multLocal(scale); } - /** - * - * @param translation - - * @param rotation - - * @deprecated use {@link #setUserTransformsInModelSpace(com.jme3.math.Vector3f, com.jme3.math.Quaternion) } - */ - @Deprecated - public void setUserTransformsWorld(Vector3f translation, Quaternion rotation) { - - } /** * Sets the transforms of this bone in model space (relative to the root bone) - * + * * Must update all bones in skeleton for this to work. * @param translation translation in model space * @param rotation rotation in model space @@ -688,9 +684,9 @@ public void setUserTransformsInModelSpace(Vector3f translation, Quaternion rotat // TODO: add scale here ??? modelPos.set(translation); modelRot.set(rotation); - - //if there is an attached Node we need to set its local transforms too. - if(attachNode != null){ + + // if there is an attached Node we need to set its local transforms too. + if (attachNode != null) { attachNode.setLocalTranslation(translation); attachNode.setLocalRotation(rotation); } @@ -700,9 +696,10 @@ public void setUserTransformsInModelSpace(Vector3f translation, Quaternion rotat * Returns the local transform of this bone combined with the given position and rotation * @param position a position * @param rotation a rotation + * @return the resulting Transform (in reusable temporary storage!) */ public Transform getCombinedTransform(Vector3f position, Quaternion rotation) { - if(tmpTransform == null){ + if (tmpTransform == null) { tmpTransform = new Transform(); } rotation.mult(localPos, tmpTransform.getTranslation()).addLocal(position); @@ -735,7 +732,7 @@ Node getAttachmentsNode(int boneIndex, SafeArrayList targets) { if (attachNode == null) { attachNode = new Node(name + "_attachnode"); attachNode.setUserData("AttachedBone", this); - //We don't want the node to have a numBone set by a parent node so we force it to null + //We don't want the node to have NumberOfBones set by a parent node, so we force it to null. attachNode.addMatParamOverride(new MatParamOverride(VarType.Int, "NumberOfBones", null)); } @@ -782,7 +779,7 @@ void setAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f scale * updateModelTransforms() call will result in final transform = transform * 0.5. * Two transform blends with weight = 0.5 each will result in the two * transforms blended together (nlerp) with blend = 0.5. - * + * * @param translation The translation to blend in * @param rotation The rotation to blend in * @param scale The scale to blend in @@ -793,13 +790,13 @@ void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f sca if (userControl) { return; } - + if (weight == 0) { // Do not apply this transform at all. return; } - if (currentWeightSum == 1){ + if (currentWeightSum == 1) { return; // More than 2 transforms are being blended } else if (currentWeightSum == -1 || currentWeightSum == 0) { // Set the transform fully @@ -811,14 +808,14 @@ void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f sca // Set the weight. It will be applied in updateModelTransforms(). currentWeightSum = weight; } else { - // The weight is already set. + // The weight is already set. // Blend in the new transform. TempVars vars = TempVars.get(); Vector3f tmpV = vars.vect1; Vector3f tmpV2 = vars.vect2; Quaternion tmpQ = vars.quat1; - + tmpV.set(bindPos).addLocal(translation); localPos.interpolateLocal(tmpV, weight); @@ -829,17 +826,21 @@ void blendAnimTransforms(Vector3f translation, Quaternion rotation, Vector3f sca tmpV2.set(bindScale).multLocal(scale); localScale.interpolateLocal(tmpV2, weight); } - + // Ensures no new weights will be blended in the future. currentWeightSum = 1; - + vars.release(); } } /** - * Sets local bind transform for bone. - * Call setBindingPose() after all of the skeleton bones' bind transforms are set to save them. + * Sets the local bind transform of the bone. + * Call setBindingPose() after all of the bones' bind transforms are set to save them. + * + * @param translation the desired bind translation (not null, unaffected) + * @param rotation the desired bind rotation (not null, unaffected) + * @param scale the desired bind scale (unaffected) or null for no scaling */ public void setBindTransforms(Vector3f translation, Quaternion rotation, Vector3f scale) { bindPos.set(translation); @@ -881,16 +882,16 @@ public void read(JmeImporter im) throws IOException { name = input.readString("name", null); int ver = input.getSavableVersion(Bone.class); - if(ver < 2){ + if (ver < 2) { bindPos = (Vector3f) input.readSavable("initialPos", null); bindRot = (Quaternion) input.readSavable("initialRot", null); bindScale = (Vector3f) input.readSavable("initialScale", new Vector3f(1.0f, 1.0f, 1.0f)); - }else{ + } else { bindPos = (Vector3f) input.readSavable("bindPos", null); bindRot = (Quaternion) input.readSavable("bindRot", null); bindScale = (Vector3f) input.readSavable("bindScale", new Vector3f(1.0f, 1.0f, 1.0f)); } - + attachNode = (Node) input.readSavable("attachNode", null); targetGeometry = (Geometry) input.readSavable("targetGeometry", null); @@ -925,9 +926,10 @@ public void write(JmeExporter ex) throws IOException { /** * Sets the rotation of the bone in object space. * Warning: you need to call {@link #setUserControl(boolean)} with true to be able to do that operation - * @param rot + * + * @param rot the desired rotation (not null, unaffected) */ - public void setLocalRotation(Quaternion rot){ + public void setLocalRotation(Quaternion rot) { if (!userControl) { throw new IllegalStateException("User control must be on bone to allow user transforms"); } @@ -937,9 +939,10 @@ public void setLocalRotation(Quaternion rot){ /** * Sets the position of the bone in object space. * Warning: you need to call {@link #setUserControl(boolean)} with true to be able to do that operation - * @param pos + * + * @param pos the desired translation (not null, unaffected) */ - public void setLocalTranslation(Vector3f pos){ + public void setLocalTranslation(Vector3f pos) { if (!userControl) { throw new IllegalStateException("User control must be on bone to allow user transforms"); } @@ -951,7 +954,7 @@ public void setLocalTranslation(Vector3f pos){ * Warning: you need to call {@link #setUserControl(boolean)} with true to be able to do that operation * @param scale the scale to apply */ - public void setLocalScale(Vector3f scale){ + public void setLocalScale(Vector3f scale) { if (!userControl) { throw new IllegalStateException("User control must be on bone to allow user transforms"); } @@ -961,9 +964,9 @@ public void setLocalScale(Vector3f scale){ /** * returns true if this bone can be directly manipulated by the user. * @see #setUserControl(boolean) - * @return true if can be manipulated + * @return true if it can be manipulated */ - public boolean hasUserControl(){ + public boolean hasUserControl() { return userControl; } } diff --git a/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java index d12f004def..88781f8a08 100644 --- a/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/BoneTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ /** * Contains a list of transforms and times for each keyframe. - * + * * @author Kirill Vainer * @deprecated use {@link com.jme3.anim.AnimTrack} */ @@ -53,7 +53,7 @@ public final class BoneTrack implements JmeCloneable, Track { * Bone index in the skeleton which this track affects. */ private int targetBoneIndex; - + /** * Transforms and times for track. */ @@ -61,7 +61,7 @@ public final class BoneTrack implements JmeCloneable, Track { private CompactQuaternionArray rotations; private CompactVector3Array scales; private float[] times; - + /** * Serialization-only. Do not use. */ @@ -89,7 +89,7 @@ public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Qu * @param scales the scale of the bone for each frame */ public BoneTrack(int targetBoneIndex, float[] times, Vector3f[] translations, Quaternion[] rotations, Vector3f[] scales) { - this.targetBoneIndex = targetBoneIndex; + this.targetBoneIndex = targetBoneIndex; this.setKeyframes(times, translations, rotations, scales); } @@ -191,23 +191,24 @@ public void setKeyframes(float[] times, Vector3f[] translations, Quaternion[] ro } /** - * + * * Modify the bone which this track modifies in the skeleton to contain * the correct animation transforms for a given time. * The transforms can be interpolated in some method from the keyframes. * * @param time the current time of the animation * @param weight the weight of the animation - * @param control - * @param channel - * @param vars + * @param control to access the Skeleton + * @param channel which bones can be affected + * @param vars storage for temporary values */ + @Override public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { BitSet affectedBones = channel.getAffectedBones(); if (affectedBones != null && !affectedBones.get(targetBoneIndex)) { return; } - + Bone target = control.getSkeleton().getBone(targetBoneIndex); Vector3f tempV = vars.vect1; @@ -216,7 +217,7 @@ public void setTime(float time, float weight, AnimControl control, AnimChannel c Vector3f tempV2 = vars.vect3; Vector3f tempS2 = vars.vect4; Quaternion tempQ2 = vars.quat2; - + int lastFrame = times.length - 1; if (time < 0 || lastFrame == 0) { rotations.get(0, tempQ); @@ -259,15 +260,16 @@ public void setTime(float time, float weight, AnimControl control, AnimChannel c } // if (weight != 1f) { - target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight); + target.blendAnimTransforms(tempV, tempQ, scales != null ? tempS : null, weight); // } else { // target.setAnimTransforms(tempV, tempQ, scales != null ? tempS : null); // } } - + /** * @return the length of the track */ + @Override public float getLength() { return times == null ? 0 : times[times.length - 1] - times[0]; } @@ -339,7 +341,7 @@ public void read(JmeImporter im) throws IOException { scales = (CompactVector3Array) ic.readSavable("scales", null); //Backward compatibility for old j3o files generated before revision 6807 - if (im.getFormatVersion() == 0){ + if (im.getFormatVersion() == 0) { if (translations == null) { Savable[] sav = ic.readSavableArray("translations", null); if (sav != null) { diff --git a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java index b777203a57..6c7da6636f 100644 --- a/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/ClonableTrack.java @@ -63,7 +63,7 @@ public interface ClonableTrack extends Track, JmeCloneable { public Track cloneForSpatial(Spatial spatial); /** - * Method responsible of cleaning UserData on referenced Spatials when the Track is deleted + * Method responsible for cleaning UserData on referenced Spatials when the Track is deleted */ public void cleanUp(); } diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java index 54516145a4..31bb6653ce 100644 --- a/jme3-core/src/main/java/com/jme3/animation/CompactArray.java +++ b/jme3-core/src/main/java/com/jme3/animation/CompactArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,11 +40,12 @@ /** * Object is indexed and stored in primitive float[] * @author Lim, YongHoon - * @param + * + * @param the type of object (i.e. Vector3f) */ public abstract class CompactArray implements JmeCloneable { - protected Map indexPool = new HashMap(); + protected Map indexPool = new HashMap<>(); protected int[] index; protected float[] array; private boolean invalid; @@ -57,8 +58,9 @@ public CompactArray() { /** * create array using serialized data - * @param compressedArray - * @param index + * + * @param compressedArray storage for float data + * @param index storage for indices */ public CompactArray(float[] compressedArray, int[] index) { this.array = compressedArray; @@ -68,7 +70,8 @@ public CompactArray(float[] compressedArray, int[] index) { /** * Add objects. * They are serialized automatically when get() method is called. - * @param objArray + * + * @param objArray the objects to be added (may be null) */ @SuppressWarnings("unchecked") public void add(T... objArray) { @@ -119,8 +122,8 @@ protected void setInvalid(boolean invalid) { } /** - * @param index - * @param value + * @param index zero-origin index of the element to be altered + * @param value the desired value */ public final void set(int index, T value) { int j = getCompactIndex(index); @@ -194,7 +197,8 @@ protected float[] ensureCapacity(float[] arr, int size) { /** * Return an array of indices for the given objects - * @param objArray + * + * @param objArray the input objects * @return a new array */ @SuppressWarnings("unchecked") @@ -209,7 +213,8 @@ public final int[] getIndex(T... objArray) { /** * returns the corresponding index in the compact array - * @param objIndex + * + * @param objIndex the input index * @return object index in the compacted object array */ public int getCompactIndex(int objIndex) { @@ -241,7 +246,7 @@ public final T[] toObjectArray() { try { T[] compactArr = (T[]) Array.newInstance(getElementClass(), getSerializedSize() / getTupleSize()); for (int i = 0; i < compactArr.length; i++) { - compactArr[i] = getElementClass().newInstance(); + compactArr[i] = getElementClass().getDeclaredConstructor().newInstance(); deserialize(i, compactArr[i]); } @@ -260,10 +265,10 @@ public final T[] toObjectArray() { * Create a deep clone of this array. * * @return a new array - * @throws java.lang.CloneNotSupportedException + * @throws CloneNotSupportedException never */ @Override - public Object clone() throws CloneNotSupportedException { + public CompactArray clone() throws CloneNotSupportedException { return Cloner.deepClone(this); } @@ -300,19 +305,22 @@ public void cloneFields(Cloner cloner, Object original) { /** * serialize object * @param compactIndex compacted object index - * @param store + * @param store the value to be serialized (not null, unaffected) */ protected abstract void serialize(int compactIndex, T store); /** * deserialize object * @param compactIndex compacted object index - * @param store + * @param store storage for the result + * @return the deserialized value */ protected abstract T deserialize(int compactIndex, T store); /** * serialized size of one object element + * + * @return the number of primitive components (floats) per object */ protected abstract int getTupleSize(); diff --git a/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java b/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java index 097bfa4009..27d62c2b63 100644 --- a/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java +++ b/jme3-core/src/main/java/com/jme3/animation/CompactFloatArray.java @@ -81,12 +81,12 @@ public void read(JmeImporter im) throws IOException { index = in.readIntArray("index", null); } - public void fill(int startIndex, float[] store ){ + public void fill(int startIndex, float[] store) { for (int i = 0; i < store.length; i++) { store[i] = get(startIndex + i, null); } } - + @Override protected void serialize(int i, Float data) { array[i] = data; @@ -96,4 +96,4 @@ protected void serialize(int i, Float data) { protected Float deserialize(int i, Float store) { return array[i]; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java index 278568c3f9..60a0f30dae 100644 --- a/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/EffectTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ * usage is *

      * AnimControl control model.getControl(AnimControl.class);
    - * EffectTrack track = new EffectTrack(existingEmmitter, control.getAnim("TheAnim").getLength());
    + * EffectTrack track = new EffectTrack(existingEmitter, control.getAnim("TheAnim").getLength());
      * control.getAnim("TheAnim").addTrack(track);
      * 
    * @@ -75,7 +75,7 @@ public class EffectTrack implements ClonableTrack { private boolean emitted = false; private boolean initialized = false; //control responsible for disable and cull the emitter once all particles are gone - private KillParticleControl killParticles = new KillParticleControl(); + final private KillParticleControl killParticles = new KillParticleControl(); public static class KillParticleControl extends AbstractControl { @@ -96,8 +96,6 @@ public void setSpatial(Spatial spatial) { throw new IllegalArgumentException("KillParticleEmitter can only ba attached to ParticleEmitter"); } } - - } @Override @@ -130,7 +128,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { } } - //Anim listener that stops the Emmitter when the animation is finished or changed. + //Anim listener that stops the emitter when the animation is finished or changed. private class OnEndListener implements AnimEventListener { @Override @@ -160,7 +158,7 @@ public EffectTrack(ParticleEmitter emitter, float length) { this.emitter = emitter; //saving particles per second value this.particlesPerSeconds = emitter.getParticlesPerSec(); - //setting the emmitter to not emmit. + //setting the emitter to not emit. this.emitter.setParticlesPerSec(0); this.length = length; //Marking the emitter with a reference to this track for further use in deserialization. @@ -204,7 +202,7 @@ public void setTime(float time, float weight, AnimControl control, AnimChannel c emitted = true; emitter.setCullHint(CullHint.Dynamic); emitter.setEnabled(true); - //if the emitter has 0 particles per seconds emmit all particles in one shot + //if the emitter has 0 particles per second, emit all particles in one shot if (particlesPerSeconds == 0) { emitter.emitAllParticles(); if (!killParticles.stopRequested) { @@ -212,13 +210,13 @@ public void setTime(float time, float weight, AnimControl control, AnimChannel c killParticles.stopRequested = true; } } else { - //else reset its former particlePerSec value to let it emmit. + //else reset its former particlePerSec value to let it emit. emitter.setParticlesPerSec(particlesPerSeconds); } } } - //stops the emmiter to emit. + // Stop the emitter from emitting. private void stop() { emitter.setParticlesPerSec(0); emitted = false; @@ -241,7 +239,7 @@ public float getLength() { @Override public float[] getKeyFrameTimes() { - return new float[] { startOffset }; + return new float[]{startOffset}; } /** @@ -250,7 +248,7 @@ public float[] getKeyFrameTimes() { * @return a new instance */ @Override - public Track clone() { + public EffectTrack clone() { return new EffectTrack(emitter, length, startOffset); } @@ -278,7 +276,7 @@ public Track cloneForSpatial(Spatial spatial) { } removeUserData(this); - //setting user data on the new emmitter and marking it with a reference to the cloned Track. + //setting user data on the new emitter and marking it with a reference to the cloned Track. setUserData(effectTrack); effectTrack.emitter.setParticlesPerSec(0); return effectTrack; @@ -288,14 +286,13 @@ public Track cloneForSpatial(Spatial spatial) { public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning", e); } } - @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.emitter = cloner.clone(emitter); } @@ -347,7 +344,7 @@ public ParticleEmitter getEmitter() { /** * Sets the Emitter to use in this track * - * @param emitter + * @param emitter the emitter to be controlled (alias created) */ public void setEmitter(ParticleEmitter emitter) { if (this.emitter != null) { @@ -357,7 +354,7 @@ public void setEmitter(ParticleEmitter emitter) { this.emitter = emitter; //saving particles per second value this.particlesPerSeconds = emitter.getParticlesPerSec(); - //setting the emmitter to not emmit. + //setting the emitter to not emit. this.emitter.setParticlesPerSec(0); setUserData(this); } @@ -373,7 +370,7 @@ public float getStartOffset() { /** * set the start offset of the track * - * @param startOffset + * @param startOffset the start offset (in seconds) */ public void setStartOffset(float startOffset) { this.startOffset = startOffset; @@ -391,8 +388,6 @@ private void setUserData(EffectTrack effectTrack) { //adding the given Track to the TrackInfo. data.addTrack(effectTrack); - - } private void removeUserData(EffectTrack effectTrack) { @@ -406,8 +401,6 @@ private void removeUserData(EffectTrack effectTrack) { //removing the given Track to the TrackInfo. data.getTracks().remove(effectTrack); - - } /** diff --git a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java index 16ca1deb02..33a50b96bc 100644 --- a/jme3-core/src/main/java/com/jme3/animation/LoopMode.java +++ b/jme3-core/src/main/java/com/jme3/animation/LoopMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,7 +35,6 @@ * LoopMode determines how animations repeat, or if they * do not repeat. */ -@Deprecated public enum LoopMode { /** * The animation will play repeatedly, when it reaches the end @@ -46,7 +45,7 @@ public enum LoopMode { /** * The animation will not loop. It will play until the last frame, and then * freeze at that frame. It is possible to decide to play a new animation - * when that happens by using a AnimEventListener. + * when that happens by using an AnimEventListener. */ DontLoop, @@ -55,6 +54,6 @@ public enum LoopMode { * animation will play backwards from the last frame until it reaches * the first frame. */ - Cycle, + Cycle } diff --git a/jme3-core/src/main/java/com/jme3/animation/Pose.java b/jme3-core/src/main/java/com/jme3/animation/Pose.java index 4a0f659c84..f2208e1937 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Pose.java +++ b/jme3-core/src/main/java/com/jme3/animation/Pose.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,10 +50,10 @@ public final class Pose implements Savable, Cloneable { private Vector3f[] offsets; private int[] indices; - private transient final Vector3f tempVec = new Vector3f(); + private transient final Vector3f tempVec = new Vector3f(); private transient final Vector3f tempVec2 = new Vector3f(); - public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices){ + public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices) { this.name = name; this.targetMeshIndex = targetMeshIndex; this.offsets = offsets; @@ -63,39 +63,37 @@ public Pose(String name, int targetMeshIndex, Vector3f[] offsets, int[] indices) /** * Serialization-only. Do not use. */ - protected Pose() - { + protected Pose() { } - - public int getTargetMeshIndex(){ + + public int getTargetMeshIndex() { return targetMeshIndex; } - /** * Applies the offsets of this pose to the vertex buffer given by the blend factor. * * @param blend Blend factor, 0 = no change to vertex buffer, 1 = apply full offsets - * @param vertbuf Vertex buffer to apply this pose to + * @param vertexBuffer Vertex buffer to apply this pose to */ - public void apply(float blend, FloatBuffer vertbuf){ - for (int i = 0; i < indices.length; i++){ + public void apply(float blend, FloatBuffer vertexBuffer) { + for (int i = 0; i < indices.length; i++) { Vector3f offset = offsets[i]; - int vertIndex = indices[i]; + int vertIndex = indices[i]; tempVec.set(offset).multLocal(blend); // acquire vertex - BufferUtils.populateFromBuffer(tempVec2, vertbuf, vertIndex); + BufferUtils.populateFromBuffer(tempVec2, vertexBuffer, vertIndex); // add offset multiplied by factor tempVec2.addLocal(tempVec); // write modified vertex - BufferUtils.setInBuffer(tempVec2, vertbuf, vertIndex); + BufferUtils.setInBuffer(tempVec2, vertexBuffer, vertIndex); } } - + /** * This method creates a clone of the current object. * @return a clone of the current object @@ -117,6 +115,7 @@ public Pose clone() { } } + @Override public void write(JmeExporter e) throws IOException { OutputCapsule out = e.getCapsule(this); out.write(name, "name", ""); @@ -125,6 +124,7 @@ public void write(JmeExporter e) throws IOException { out.write(indices, "indices", null); } + @Override public void read(JmeImporter i) throws IOException { InputCapsule in = i.getCapsule(this); name = in.readString("name", ""); diff --git a/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java b/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java index 09817c5eaf..4a7405ef7c 100644 --- a/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/PoseTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,19 +32,14 @@ package com.jme3.animation; import com.jme3.export.*; -import com.jme3.scene.Mesh; -import com.jme3.scene.VertexBuffer; -import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.TempVars; import java.io.IOException; -import java.nio.FloatBuffer; /** * A single track of pose animation associated with a certain mesh. */ @Deprecated public final class PoseTrack implements Track { - private int targetMeshIndex; private PoseFrame[] frames; private float[] times; @@ -58,14 +53,13 @@ public PoseFrame(Pose[] poses, float[] weights) { this.poses = poses; this.weights = weights; } - + /** * Serialization-only. Do not use. */ - protected PoseFrame() - { + protected PoseFrame() { } - + /** * This method creates a clone of the current object. * @return a clone of the current object @@ -87,16 +81,18 @@ public PoseFrame clone() { } } + @Override public void write(JmeExporter e) throws IOException { OutputCapsule out = e.getCapsule(this); out.write(poses, "poses", null); out.write(weights, "weights", null); } + @Override public void read(JmeImporter i) throws IOException { InputCapsule in = i.getCapsule(this); weights = in.readFloatArray("weights", null); - + Savable[] readSavableArray = in.readSavableArray("poses", null); if (readSavableArray != null) { poses = new Pose[readSavableArray.length]; @@ -105,37 +101,23 @@ public void read(JmeImporter i) throws IOException { } } - public PoseTrack(int targetMeshIndex, float[] times, PoseFrame[] frames){ + public PoseTrack(int targetMeshIndex, float[] times, PoseFrame[] frames) { this.targetMeshIndex = targetMeshIndex; this.times = times; this.frames = frames; } - + /** * Serialization-only. Do not use. */ - protected PoseTrack() - { - } - - private void applyFrame(Mesh target, int frameIndex, float weight){ - PoseFrame frame = frames[frameIndex]; - VertexBuffer pb = target.getBuffer(Type.Position); - for (int i = 0; i < frame.poses.length; i++){ - Pose pose = frame.poses[i]; - float poseWeight = frame.weights[i] * weight; - - pose.apply(poseWeight, (FloatBuffer) pb.getData()); - } - - // force to re-upload data to gpu - pb.updateData(pb.getData()); + protected PoseTrack() { } + @Override public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { // TODO: When MeshControl is created, it will gather targets // list automatically which is then retrieved here. - + /* Mesh target = targets[targetMeshIndex]; if (time < times[0]) { @@ -161,15 +143,16 @@ public void setTime(float time, float weight, AnimControl control, AnimChannel c /** * @return the length of the track */ + @Override public float getLength() { return times == null ? 0 : times[times.length - 1] - times[0]; } - + @Override public float[] getKeyFrameTimes() { return times; } - + /** * This method creates a clone of the current object. * @return a clone of the current object @@ -190,7 +173,7 @@ public PoseTrack clone() { throw new AssertionError(); } } - + @Override public void write(JmeExporter e) throws IOException { OutputCapsule out = e.getCapsule(this); @@ -204,7 +187,7 @@ public void read(JmeImporter i) throws IOException { InputCapsule in = i.getCapsule(this); targetMeshIndex = in.readInt("meshIndex", 0); times = in.readFloatArray("times", null); - + Savable[] readSavableArray = in.readSavableArray("frames", null); if (readSavableArray != null) { frames = new PoseFrame[readSavableArray.length]; diff --git a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java index 33c81e8f03..28a8515b2d 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Skeleton.java +++ b/jme3-core/src/main/java/com/jme3/animation/Skeleton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,7 @@ * Skeleton is a convenience class for managing a bone hierarchy. * Skeleton updates the world transforms to reflect the current local * animated matrixes. - * + * * @author Kirill Vainer * @deprecated use {@link Armature} */ @@ -55,7 +55,7 @@ public final class Skeleton implements Savable, JmeCloneable { private Bone[] rootBones; private Bone[] boneList; - + /** * Contains the skinning matrices, multiplying it by a vertex affected by a bone * will cause it to go to the animated position. @@ -63,12 +63,12 @@ public final class Skeleton implements Savable, JmeCloneable { private transient Matrix4f[] skinningMatrixes; /** - * Creates a skeleton from a bone list. + * Creates a skeleton from a bone list. * The root bones are found automatically. *

    * Note that using this constructor will cause the bones in the list * to have their bind pose recomputed based on their local transforms. - * + * * @param boneList The list of bones to manage by this Skeleton */ public Skeleton(Bone[] boneList) { @@ -97,7 +97,7 @@ public Skeleton(Bone[] boneList) { *

    * Shallow copies bind pose data from the source skeleton, does not * copy any other data. - * + * * @param source The source Skeleton to copy from */ public Skeleton(Skeleton source) { @@ -124,21 +124,21 @@ public Skeleton(Skeleton source) { protected Skeleton() { } - @Override + @Override public Object jmeClone() { try { - Skeleton clone = (Skeleton)super.clone(); + Skeleton clone = (Skeleton) super.clone(); return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); } - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { this.rootBones = cloner.clone(rootBones); this.boneList = cloner.clone(boneList); - this.skinningMatrixes = cloner.clone(skinningMatrixes); + this.skinningMatrixes = cloner.clone(skinningMatrixes); } private void createSkinningMatrices() { @@ -211,7 +211,8 @@ public Bone[] getRoots() { /** * return a bone for the given index - * @param index + * + * @param index the (zero-based) bone index (≥0) * @return the pre-existing instance */ public Bone getBone(int index) { @@ -220,7 +221,8 @@ public Bone getBone(int index) { /** * returns the bone with the given name - * @param name + * + * @param name the name to search for * @return the pre-existing instance, or null if not found */ public Bone getBone(String name) { @@ -234,7 +236,8 @@ public Bone getBone(String name) { /** * returns the bone index of the given bone - * @param bone + * + * @param bone the Bone to search for (unaffected) * @return the index (≥0) or -1 if not found */ public int getBoneIndex(Bone bone) { @@ -249,8 +252,9 @@ public int getBoneIndex(Bone bone) { /** * returns the bone index of the bone that has the given name - * @param name - * @return the index (≥0) or -1 if not found + * + * @param name the name to search for + * @return the index (≥0) or -1 if not found */ public int getBoneIndex(String name) { for (int i = 0; i < boneList.length; i++) { diff --git a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java index 31c4372794..34d712f707 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java +++ b/jme3-core/src/main/java/com/jme3/animation/SkeletonControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,45 +71,44 @@ public class SkeletonControl extends AbstractControl implements Cloneable, JmeCl /** * List of geometries affected by this control. */ - private SafeArrayList targets = new SafeArrayList(Geometry.class); + private SafeArrayList targets = new SafeArrayList<>(Geometry.class); /** * Used to track when a mesh was updated. Meshes are only updated if they * are visible in at least one camera. */ private boolean wasMeshUpdated = false; - + /** * User wishes to use hardware skinning if available. */ private transient boolean hwSkinningDesired = true; - + /** * Hardware skinning is currently being used. */ private transient boolean hwSkinningEnabled = false; - + /** * Hardware skinning was tested on this GPU, results * are stored in {@link #hwSkinningSupported} variable. */ private transient boolean hwSkinningTested = false; - + /** * If hardware skinning was {@link #hwSkinningTested tested}, then * this variable will be set to true if supported, and false if otherwise. */ private transient boolean hwSkinningSupported = false; - + /** * Bone offset matrices, recreated each frame */ private transient Matrix4f[] offsetMatrices; - private MatParamOverride numberOfBonesParam; private MatParamOverride boneMatricesParam; - + /** * Serialization only. Do not use. */ @@ -119,11 +118,11 @@ protected SkeletonControl() { private void switchToHardware() { numberOfBonesParam.setEnabled(true); boneMatricesParam.setEnabled(true); - + // Next full 10 bones (e.g. 30 on 24 bones) int numBones = ((skeleton.getBoneCount() / 10) + 1) * 10; numberOfBonesParam.setValue(numBones); - + for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); if (mesh != null && mesh.isAnimated()) { @@ -135,7 +134,7 @@ private void switchToHardware() { private void switchToSoftware() { numberOfBonesParam.setEnabled(false); boneMatricesParam.setEnabled(false); - + for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); if (mesh != null && mesh.isAnimated()) { @@ -152,7 +151,7 @@ private boolean testHardwareSupported(RenderManager rm) { } switchToHardware(); - + try { rm.preloadScene(spatial); return true; @@ -166,31 +165,32 @@ private boolean testHardwareSupported(RenderManager rm) { * Specifies if hardware skinning is preferred. If it is preferred and * supported by GPU, it shall be enabled, if it's not preferred, or not * supported by GPU, then it shall be disabled. - * - * @param preferred - * @see #isHardwareSkinningUsed() + * + * @param preferred true to prefer hardware skinning, false to prefer + * software skinning (default=true) + * @see #isHardwareSkinningUsed() */ public void setHardwareSkinningPreferred(boolean preferred) { hwSkinningDesired = preferred; } - + /** * @return True if hardware skinning is preferable to software skinning. * Set to false by default. - * - * @see #setHardwareSkinningPreferred(boolean) + * + * @see #setHardwareSkinningPreferred(boolean) */ public boolean isHardwareSkinningPreferred() { return hwSkinningDesired; } - + /** * @return True is hardware skinning is activated and is currently used, false otherwise. */ public boolean isHardwareSkinningUsed() { return hwSkinningEnabled; } - + /** * Creates a skeleton control. The list of targets will be acquired * automatically when the control is attached to a node. @@ -215,7 +215,6 @@ private void findTargets(Geometry geometry) { if (mesh != null && mesh.isAnimated()) { targets.add(geometry); } - } private void findTargets(Node node) { @@ -233,12 +232,12 @@ public void setSpatial(Spatial spatial) { Spatial oldSpatial = this.spatial; super.setSpatial(spatial); updateTargetsAndMaterials(spatial); - + if (oldSpatial != null) { oldSpatial.removeMatParamOverride(numberOfBonesParam); oldSpatial.removeMatParamOverride(boneMatricesParam); } - + if (spatial != null) { spatial.removeMatParamOverride(numberOfBonesParam); spatial.removeMatParamOverride(boneMatricesParam); @@ -251,6 +250,8 @@ private void controlRenderSoftware() { resetToBind(); // reset morph meshes to bind pose offsetMatrices = skeleton.computeSkinningMatrices(); + numberOfBonesParam.setEnabled(false); + boneMatricesParam.setEnabled(false); for (Geometry geometry : targets) { Mesh mesh = geometry.getMesh(); @@ -258,19 +259,19 @@ private void controlRenderSoftware() { // already ensured this mesh is animated. // Otherwise a crash will happen in skin update. softwareSkinUpdate(mesh, offsetMatrices); - } + } } - + private void controlRenderHardware() { offsetMatrices = skeleton.computeSkinningMatrices(); boneMatricesParam.setValue(offsetMatrices); } - + @Override protected void controlRender(RenderManager rm, ViewPort vp) { if (!wasMeshUpdated) { updateTargetsAndMaterials(spatial); - + // Prevent illegal cases. These should never happen. assert hwSkinningTested || (!hwSkinningTested && !hwSkinningSupported && !hwSkinningEnabled); assert !hwSkinningEnabled || (hwSkinningEnabled && hwSkinningTested && hwSkinningSupported); @@ -281,7 +282,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { if (hwSkinningSupported) { hwSkinningEnabled = true; - + Logger.getLogger(SkeletonControl.class.getName()).log(Level.INFO, "Hardware skinning engaged for {0}", spatial); } else { switchToSoftware(); @@ -307,7 +308,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { @Override protected void controlUpdate(float tpf) { wasMeshUpdated = false; - } + } //only do this for software updates void resetToBind() { @@ -322,15 +323,20 @@ void resetToBind() { VertexBuffer bindPos = mesh.getBuffer(Type.BindPosePosition); VertexBuffer bindNorm = mesh.getBuffer(Type.BindPoseNormal); VertexBuffer pos = mesh.getBuffer(Type.Position); - VertexBuffer norm = mesh.getBuffer(Type.Normal); FloatBuffer pb = (FloatBuffer) pos.getData(); - FloatBuffer nb = (FloatBuffer) norm.getData(); FloatBuffer bpb = (FloatBuffer) bindPos.getData(); - FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); pb.clear(); - nb.clear(); bpb.clear(); - bnb.clear(); + + // reset bind normals if there is a BindPoseNormal buffer + if (bindNorm != null) { + VertexBuffer norm = mesh.getBuffer(Type.Normal); + FloatBuffer nb = (FloatBuffer) norm.getData(); + FloatBuffer bnb = (FloatBuffer) bindNorm.getData(); + nb.clear(); + bnb.clear(); + nb.put(bnb).clear(); + } //reset bind tangents if there is a bind tangent buffer VertexBuffer bindTangents = mesh.getBuffer(Type.BindPoseTangent); @@ -343,32 +349,30 @@ void resetToBind() { tb.put(btb).clear(); } - pb.put(bpb).clear(); - nb.put(bnb).clear(); } } } - @Override + @Override public Object jmeClone() { return super.jmeClone(); - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); - + this.skeleton = cloner.clone(skeleton); - + // If the targets were cloned then this will clone them. If the targets // were shared then this will share them. this.targets = cloner.clone(targets); - + this.numberOfBonesParam = cloner.clone(numberOfBonesParam); this.boneMatricesParam = cloner.clone(boneMatricesParam); } - + /** * Access the attachments node of the named bone. If the bone doesn't * already have an attachments node, create one and attach it to the scene @@ -444,15 +448,13 @@ private void softwareSkinUpdate(Mesh mesh, Matrix4f[] offsetMatrices) { //if there are tangents use the skinning with tangents applySkinningTangents(mesh, offsetMatrices, tb); } - - } /** * Method to apply skinning transforms to a mesh's buffers * * @param mesh the mesh - * @param offsetMatrices the offset matices to apply + * @param offsetMatrices the offset matrices to apply */ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { int maxWeightsPerVert = mesh.getMaxNumWeights(); @@ -557,9 +559,8 @@ private void applySkinning(Mesh mesh, Matrix4f[] offsetMatrices) { * has additional indexes since tangent has 4 components instead of 3 for * pos and norm * - * @param maxWeightsPerVert maximum number of weights per vertex * @param mesh the mesh - * @param offsetMatrices the offsetMaytrices to apply + * @param offsetMatrices the offset matrices to apply * @param tb the tangent vertexBuffer */ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexBuffer tb) { @@ -579,14 +580,14 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB VertexBuffer nb = mesh.getBuffer(Type.Normal); - FloatBuffer fnb = (FloatBuffer) nb.getData(); - fnb.rewind(); - + FloatBuffer fnb = (nb == null) ? null : (FloatBuffer) nb.getData(); + if (fnb != null) { + fnb.rewind(); + } FloatBuffer ftb = (FloatBuffer) tb.getData(); ftb.rewind(); - // get boneIndexes and weights for mesh IndexBuffer ib = IndexBuffer.wrapIndexBuffer(mesh.getBuffer(Type.BoneIndex).getData()); FloatBuffer wb = (FloatBuffer) mesh.getBuffer(Type.BoneWeight).getData(); @@ -598,7 +599,6 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB TempVars vars = TempVars.get(); - float[] posBuf = vars.skinPositions; float[] normBuf = vars.skinNormals; float[] tanBuf = vars.skinTangents; @@ -611,11 +611,13 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB bufLength = Math.min(posBuf.length, fvb.remaining()); tanLength = Math.min(tanBuf.length, ftb.remaining()); fvb.get(posBuf, 0, bufLength); - fnb.get(normBuf, 0, bufLength); + if (fnb != null) { + fnb.get(normBuf, 0, bufLength); + } ftb.get(tanBuf, 0, tanLength); int verts = bufLength / 3; int idxPositions = 0; - //tangents has their own index because of the 4 components + // Tangents have their own index because they have 4 components. int idxTangents = 0; // iterate vertices and apply skinning transform for each effecting bone @@ -684,8 +686,10 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB fvb.position(fvb.position() - bufLength); fvb.put(posBuf, 0, bufLength); - fnb.position(fnb.position() - bufLength); - fnb.put(normBuf, 0, bufLength); + if (fnb != null) { + fnb.position(fnb.position() - bufLength); + fnb.put(normBuf, 0, bufLength); + } ftb.position(ftb.position() - tanLength); ftb.put(tanBuf, 0, tanLength); } @@ -693,10 +697,10 @@ private void applySkinningTangents(Mesh mesh, Matrix4f[] offsetMatrices, VertexB vars.release(); vb.updateData(fvb); - nb.updateData(fnb); + if (nb != null) { + nb.updateData(fnb); + } tb.updateData(ftb); - - } @Override @@ -704,7 +708,7 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(skeleton, "skeleton", null); - + oc.write(numberOfBonesParam, "numberOfBonesParam", null); oc.write(boneMatricesParam, "boneMatricesParam", null); } @@ -714,10 +718,10 @@ public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule in = im.getCapsule(this); skeleton = (Skeleton) in.readSavable("skeleton", null); - + numberOfBonesParam = (MatParamOverride) in.readSavable("numberOfBonesParam", null); boneMatricesParam = (MatParamOverride) in.readSavable("boneMatricesParam", null); - + if (numberOfBonesParam == null) { numberOfBonesParam = new MatParamOverride(VarType.Int, "NumberOfBones", null); boneMatricesParam = new MatParamOverride(VarType.Matrix4Array, "BoneMatrices", null); diff --git a/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java index fc992d2b00..5c7e4cf0ca 100644 --- a/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java +++ b/jme3-core/src/main/java/com/jme3/animation/SpatialTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -101,6 +101,7 @@ public SpatialTrack(float[] times, Vector3f[] translations, * @param time * the current time of the animation */ + @Override public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars) { Spatial spatial = trackSpatial; if (spatial == null) { @@ -242,6 +243,7 @@ public Vector3f[] getTranslations() { /** * @return the length of the track */ + @Override public float getLength() { return times == null ? 0 : times[times.length - 1] - times[0]; } diff --git a/jme3-core/src/main/java/com/jme3/animation/Track.java b/jme3-core/src/main/java/com/jme3/animation/Track.java index 7777e88948..a9d753cd18 100644 --- a/jme3-core/src/main/java/com/jme3/animation/Track.java +++ b/jme3-core/src/main/java/com/jme3/animation/Track.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,7 @@ public interface Track extends Savable, Cloneable { * @param weight The weight from 0 to 1 on how much to apply the track * @param control The control which the track should affect * @param channel The channel which the track should affect + * @param vars temporary storage */ public void setTime(float time, float weight, AnimControl control, AnimChannel channel, TempVars vars); diff --git a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java index 93fd6a88e2..5d3c1cbfba 100644 --- a/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java +++ b/jme3-core/src/main/java/com/jme3/animation/TrackInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ * This class is intended as a UserData added to a Spatial that is referenced by a Track. * (ParticleEmitter for EffectTrack and AudioNode for AudioTrack) * It holds the list of tracks that are directly referencing the Spatial. - * + * * This is used when loading a Track to find the cloned reference of a Spatial in the cloned model returned by the assetManager. * * @author Nehon @@ -50,16 +50,19 @@ @Deprecated public class TrackInfo implements Savable, JmeCloneable { - ArrayList tracks = new ArrayList(); + ArrayList tracks = new ArrayList<>(); public TrackInfo() { } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule c = ex.getCapsule(this); c.writeSavableArrayList(tracks, "tracks", null); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule c = im.getCapsule(this); tracks = c.readSavableArrayList("tracks", null); @@ -72,18 +75,18 @@ public ArrayList getTracks() { public void addTrack(Track track) { tracks.add(track); } - - @Override + + @Override public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning", e); } - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { - this.tracks = cloner.clone(tracks); - } + @Override + public void cloneFields(Cloner cloner, Object original) { + this.tracks = cloner.clone(tracks); + } } diff --git a/jme3-core/src/main/java/com/jme3/animation/package.html b/jme3-core/src/main/java/com/jme3/animation/package.html index a69904f3b4..672cfeddec 100644 --- a/jme3-core/src/main/java/com/jme3/animation/package.html +++ b/jme3-core/src/main/java/com/jme3/animation/package.html @@ -48,9 +48,9 @@

    Skeletal Animation System

    valid bone should be 0 and the weights following the last valid bone should be 0.0. The buffers are designed in such a way so as to permit hardware skinning.

    -The {@link com.jme3.animation.Skeleton} class describes a bone heirarchy with one +The {@link com.jme3.animation.Skeleton} class describes a bone hierarchy with one or more root bones having children, thus containing all bones of the skeleton. -In addition to accessing the bones in the skeleton via the tree heirarchy, it +In addition to accessing the bones in the skeleton via the tree hierarchy, it is also possible to access bones via index. The index for any given bone is arbitrary and does not depend on the bone's location in the tree hierarchy. It is this index that is specified in the BoneIndex VertexBuffer mentioned above diff --git a/jme3-core/src/main/java/com/jme3/app/AppTask.java b/jme3-core/src/main/java/com/jme3/app/AppTask.java index 1830885815..a7122765a5 100644 --- a/jme3-core/src/main/java/com/jme3/app/AppTask.java +++ b/jme3-core/src/main/java/com/jme3/app/AppTask.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,12 @@ */ package com.jme3.app; -import java.util.concurrent.*; + +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Level; @@ -52,7 +57,8 @@ public class AppTask implements Future { private V result; private ExecutionException exception; - private boolean cancelled, finished; + private boolean cancelled; + private boolean finished; private final ReentrantLock stateLock = new ReentrantLock(); private final Condition finishedCondition = stateLock.newCondition(); @@ -66,6 +72,7 @@ public AppTask(Callable callable) { this.callable = callable; } + @Override public boolean cancel(boolean mayInterruptIfRunning) { stateLock.lock(); try { @@ -82,6 +89,7 @@ public boolean cancel(boolean mayInterruptIfRunning) { } } + @Override public V get() throws InterruptedException, ExecutionException { stateLock.lock(); try { @@ -97,7 +105,9 @@ public V get() throws InterruptedException, ExecutionException { } } - public V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + @Override + public V get(long timeout, TimeUnit unit) + throws InterruptedException, ExecutionException, TimeoutException { stateLock.lock(); try { if (!isDone()) { @@ -115,6 +125,7 @@ public V get(long timeout, TimeUnit unit) throws InterruptedException, Execution } } + @Override public boolean isCancelled() { stateLock.lock(); try { @@ -124,6 +135,7 @@ public boolean isCancelled() { } } + @Override public boolean isDone() { stateLock.lock(); try { 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 82e4959a7f..dba60e140c 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,9 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; -import com.jme3.system.*; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; +import com.jme3.system.Timer; import java.util.concurrent.Callable; import java.util.concurrent.Future; @@ -59,11 +61,11 @@ public interface Application { public LostFocusBehavior getLostFocusBehavior(); /** - * Change the application's behavior when unfocused. + * Changes the application's behavior when unfocused. * * By default, the application will * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop} - * so as to not take 100% CPU usage when it is not in focus, e.g. + * so as not to use 100% of the CPU when it is out of focus, e.g. * alt-tabbed, minimized, or obstructed by another window. * * @param lostFocusBehavior The new lost focus behavior to use. @@ -82,15 +84,15 @@ public interface Application { public boolean isPauseOnLostFocus(); /** - * Enable or disable pause on lost focus. + * Enables or disables pause on lost focus. *

    * By default, pause on lost focus is enabled. * If enabled, the application will stop updating * when it loses focus or becomes inactive (e.g. alt-tab). * For online or real-time applications, this might not be preferable, - * so this feature should be set to disabled. For other applications, - * it is best to keep it on so that CPU usage is not used when - * not necessary. + * so this feature should be disabled. For other applications, + * it is best to keep it enabled so that CPU is not used + * unnecessarily. * * @param pauseOnLostFocus True to enable pause on lost focus, false * otherwise. @@ -115,6 +117,8 @@ public interface Application { * Sets the Timer implementation that will be used for calculating * frame times. By default, Application will use the Timer as returned * by the current JmeContext implementation. + * + * @param timer the desired timer (alias created) */ public void setTimer(Timer timer); @@ -167,11 +171,18 @@ public interface Application { /** * Starts the application. + * 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 on macOS. + * + * @param waitFor true→wait for the context to be initialized, + * false→don't wait */ public void start(boolean waitFor); @@ -179,11 +190,16 @@ public interface Application { * Sets an AppProfiler hook that will be called back for * specific steps within a single update frame. Value defaults * to null. + * + * @param prof the profiler to use (alias created) or null for none + * (default=null) */ public void setAppProfiler(AppProfiler prof); /** * Returns the current AppProfiler hook, or null if none is set. + * + * @return the pre-existing instance, or null if none */ public AppProfiler getAppProfiler(); @@ -210,6 +226,9 @@ public interface Application { * Requests the context to close, shutting down the main loop * and making necessary cleanup operations. * After the application has stopped, it cannot be used anymore. + * + @param waitFor true→wait for the context to be fully destroyed, + * false→don't wait */ public void stop(boolean waitFor); @@ -221,7 +240,9 @@ public interface Application { * They are executed even if the application is currently paused * or out of focus. * + * @param type of result returned by the Callable * @param callable The callable to run in the main jME3 thread + * @return a new instance */ public Future enqueue(Callable callable); diff --git a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java index 6987ebc048..1c95730bbc 100644 --- a/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/BasicProfiler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 jMonkeyEngine + * Copyright (c) 2014-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,10 +29,14 @@ * 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; -import com.jme3.profile.*; + +import com.jme3.profile.AppProfiler; +import com.jme3.profile.AppStep; +import com.jme3.profile.SpStep; +import com.jme3.profile.VpStep; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Mesh; @@ -40,13 +44,12 @@ import com.jme3.util.BufferUtils; import java.nio.FloatBuffer; - /** * An AppProfiler implementation that collects two - * per-frame application-wide timings for update versus - * render and uses it to create a bar chart style Mesh. - * The number of frames displayed and the update interval - * can be specified. The chart Mesh is in 'milliseconds' + * per-frame application-wide timings for update versus + * render and uses it to create a bar chart style Mesh. + * The number of frames displayed and the update interval + * can be specified. The chart Mesh is in 'milliseconds' * and can be scaled up or down as required. * *

    Each column of the chart represents a single frames @@ -56,9 +59,9 @@ * the cyan portion represents the rendering time.

    * *

    When the end of the chart is reached, the current - * frame cycles back around to the beginning.

    + * frame cycles back around to the beginning.

    * - * @author Paul Speed + * @author Paul Speed */ public class BasicProfiler implements AppProfiler { @@ -67,51 +70,54 @@ public class BasicProfiler implements AppProfiler { private long[] frames; private long startTime; private long renderTime; - private long previousFrame; private long updateInterval = 1000000L; // once a millisecond private long lastUpdate = 0; - + private Mesh mesh; - + public BasicProfiler() { this(1280); } - - public BasicProfiler( int size ) { + + public BasicProfiler(int size) { setFrameCount(size); } /** - * Sets the number of frames to display and track. By default + * Sets the number of frames to display and track. By default, * this is 1280. + * + * @param size the desired number of frames (≥0, default=1280) */ - public final void setFrameCount( int size ) { - if( this.size == size ) { + public final void setFrameCount(int size) { + if (this.size == size) { return; } - + this.size = size; - this.frames = new long[size*2]; - + this.frames = new long[size * 2]; + createMesh(); - - if( frameIndex >= size ) { + + if (frameIndex >= size) { frameIndex = 0; - } + } } - + public int getFrameCount() { return size; } /** * Sets the number of nanoseconds to wait before updating the - * mesh. By default this is once a millisecond, ie: 1000000 nanoseconds. + * mesh. By default, this is once a millisecond, i.e. 1000000 nanoseconds. + * + * @param nanos the desired update interval (in nanoseconds, default=1e6) */ - public void setUpdateInterval( long nanos ) { + public void setUpdateInterval(long nanos) { this.updateInterval = nanos; } - + public long getUpdateInterval() { return updateInterval; } @@ -119,39 +125,41 @@ public long getUpdateInterval() { /** * Returns the mesh that contains the bar chart of tracked frame * timings. + * + * @return the pre-existing Mesh */ public Mesh getMesh() { return mesh; } protected final void createMesh() { - if( mesh == null ) { + if (mesh == null) { mesh = new Mesh(); mesh.setMode(Mesh.Mode.Lines); } - + mesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(size * 4 * 3)); - + FloatBuffer cb = BufferUtils.createFloatBuffer(size * 4 * 4); - for( int i = 0; i < size; i++ ) { + for (int i = 0; i < size; i++) { // For each index we add 4 colors, one for each line // endpoint for two layers. cb.put(0.5f).put(0.5f).put(0).put(1); cb.put(1).put(1).put(0).put(1); cb.put(0).put(0.5f).put(0.5f).put(1); cb.put(0).put(1).put(1).put(1); - } + } mesh.setBuffer(Type.Color, 4, cb); } - + protected void updateMesh() { - FloatBuffer pb = (FloatBuffer)mesh.getBuffer(Type.Position).getData(); + FloatBuffer pb = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); pb.rewind(); float scale = 1 / 1000000f; // scaled to ms as pixels - for( int i = 0; i < size; i++ ) { + for (int i = 0; i < size; i++) { float t1 = frames[i * 2] * scale; float t2 = frames[i * 2 + 1] * scale; - + pb.put(i).put(0).put(0); pb.put(i).put(t1).put(0); pb.put(i).put(t1).put(0); @@ -161,9 +169,8 @@ protected void updateMesh() { } @Override - public void appStep( AppStep step ) { - - switch(step) { + public void appStep(AppStep step) { + switch (step) { case BeginFrame: startTime = System.nanoTime(); break; @@ -174,32 +181,27 @@ public void appStep( AppStep step ) { case EndFrame: long time = System.nanoTime(); frames[frameIndex * 2 + 1] = time - renderTime; - previousFrame = startTime; frameIndex++; - if( frameIndex >= size ) { + if (frameIndex >= size) { frameIndex = 0; } - if( startTime - lastUpdate > updateInterval ) { + if (startTime - lastUpdate > updateInterval) { updateMesh(); lastUpdate = startTime; - } + } break; } } - + @Override public void appSubStep(String... additionalInfo) { } - + @Override - public void vpStep( VpStep step, ViewPort vp, Bucket bucket ) { + public void vpStep(VpStep step, ViewPort vp, Bucket bucket) { } @Override public void spStep(SpStep step, String... additionalInfo) { - } - } - - diff --git a/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java b/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java index 50ace1dab6..68ee1483fb 100644 --- a/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java +++ b/jme3-core/src/main/java/com/jme3/app/BasicProfilerState.java @@ -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; import com.jme3.app.state.BaseAppState; @@ -44,13 +44,12 @@ import com.jme3.scene.Node; import com.jme3.scene.VertexBuffer.Type; - /** * Provides a basic profiling visualization that shows * per-frame application-wide timings for update and * rendering. * - * @author Paul Speed + * @author Paul Speed */ public class BasicProfilerState extends BaseAppState { @@ -67,7 +66,7 @@ public BasicProfilerState() { this(false); } - public BasicProfilerState( boolean enabled ) { + public BasicProfilerState(boolean enabled) { setEnabled(enabled); this.profiler = new BasicProfiler(); } @@ -86,103 +85,101 @@ public BasicProfiler getProfiler() { * single millisecond stretches two pixels high. * @param scale the scale */ - public void setGraphScale( float scale ) { - if( this.scale == scale ) { + public void setGraphScale(float scale) { + if (this.scale == scale) { return; } this.scale = scale; - if( graph != null ) { - graph.setLocalScale(1, scale, 1); + if (graph != null) { + graph.setLocalScale(1, scale, 1); } } public float getGraphScale() { return scale; } - + /** * Sets the number frames displayed and tracked. * @param count the number of frames */ - public void setFrameCount( int count ) { - if( profiler.getFrameCount() == count ) { + public void setFrameCount(int count) { + if (profiler.getFrameCount() == count) { return; } profiler.setFrameCount(count); refreshBackground(); } - + public int getFrameCount() { return profiler.getFrameCount(); } - + protected void refreshBackground() { Mesh mesh = background.getMesh(); - + int size = profiler.getFrameCount(); float frameTime = 1000f / 60; mesh.setBuffer(Type.Position, 3, new float[] { - - // first quad - 0, 0, 0, - size, 0, 0, - size, frameTime, 0, - 0, frameTime, 0, - - // second quad - 0, frameTime, 0, - size, frameTime, 0, - size, frameTime * 2, 0, - 0, frameTime * 2, 0, - - // A lower dark border just to frame the - // 'update' stats against bright backgrounds - 0, -2, 0, - size, -2, 0, - size, 0, 0, - 0, 0, 0 - }); - + + // first quad + 0, 0, 0, + size, 0, 0, + size, frameTime, 0, + 0, frameTime, 0, + // second quad + 0, frameTime, 0, + size, frameTime, 0, + size, frameTime * 2, 0, + 0, frameTime * 2, 0, + + // A lower dark border just to frame the + // 'update' stats against bright backgrounds + 0, -2, 0, + size, -2, 0, + size, 0, 0, + 0, 0, 0 + }); + mesh.setBuffer(Type.Color, 4, new float[] { // first quad, within normal frame limits - 0, 1, 0, 0.25f, - 0, 1, 0, 0.25f, - 0, 0.25f, 0, 0.25f, - 0, 0.25f, 0, 0.25f, - - // Second quad, dropped frames - 0.25f, 0, 0, 0.25f, - 0.25f, 0, 0, 0.25f, - 1, 0, 0, 0.25f, - 1, 0, 0, 0.25f, - - 0, 0, 0, 0.5f, - 0, 0, 0, 0.5f, - 0, 0, 0, 0.5f, - 0, 0, 0, 0.5f - }); - + 0, 1, 0, 0.25f, + 0, 1, 0, 0.25f, + 0, 0.25f, 0, 0.25f, + 0, 0.25f, 0, 0.25f, + + // Second quad, dropped frames + 0.25f, 0, 0, 0.25f, + 0.25f, 0, 0, 0.25f, + 1, 0, 0, 0.25f, + 1, 0, 0, 0.25f, + + 0, 0, 0, 0.5f, + 0, 0, 0, 0.5f, + 0, 0, 0, 0.5f, + 0, 0, 0, 0.5f + }); + mesh.setBuffer(Type.Index, 3, new short[] { - 0, 1, 2, - 0, 2, 3, - 4, 5, 6, - 4, 6, 7, - 8, 9, 10, - 8, 10, 11 - }); + 0, 1, 2, + 0, 2, 3, + 4, 5, 6, + 4, 6, 7, + 8, 9, 10, + 8, 10, 11 + }); } @Override - protected void initialize( Application app ) { - + protected void initialize(Application app) { graph = new Geometry("profiler", profiler.getMesh()); - + Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); mat.setBoolean("VertexColor", true); graph.setMaterial(mat); graph.setLocalTranslation(0, 300, 0); graph.setLocalScale(1, scale, 1); - + Mesh mesh = new Mesh(); background = new Geometry("profiler.background", mesh); mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); @@ -190,34 +187,33 @@ protected void initialize( Application app ) { mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); background.setMaterial(mat); background.setLocalTranslation(0, 300, -1); - background.setLocalScale(1, scale, 1); - + background.setLocalScale(1, scale, 1); + refreshBackground(); - - InputManager inputManager = app.getInputManager(); - if( inputManager != null ) { + + InputManager inputManager = app.getInputManager(); + if (inputManager != null) { inputManager.addMapping(INPUT_MAPPING_PROFILER_TOGGLE, new KeyTrigger(KeyInput.KEY_F6)); - inputManager.addListener(keyListener, INPUT_MAPPING_PROFILER_TOGGLE); - } + inputManager.addListener(keyListener, INPUT_MAPPING_PROFILER_TOGGLE); + } } @Override - protected void cleanup( Application app ) { - InputManager inputManager = app.getInputManager(); - if( inputManager.hasMapping(INPUT_MAPPING_PROFILER_TOGGLE) ) { - inputManager.deleteMapping(INPUT_MAPPING_PROFILER_TOGGLE); + protected void cleanup(Application app) { + InputManager inputManager = app.getInputManager(); + if (inputManager.hasMapping(INPUT_MAPPING_PROFILER_TOGGLE)) { + inputManager.deleteMapping(INPUT_MAPPING_PROFILER_TOGGLE); } inputManager.removeListener(keyListener); } @Override protected void onEnable() { - // Set the number of visible frames to the current width of the screen setFrameCount(getApplication().getCamera().getWidth()); - + getApplication().setAppProfiler(profiler); - Node gui = ((SimpleApplication)getApplication()).getGuiNode(); + Node gui = ((SimpleApplication) getApplication()).getGuiNode(); gui.attachChild(graph); gui.attachChild(background); } @@ -228,9 +224,8 @@ protected void onDisable() { graph.removeFromParent(); background.removeFromParent(); } - - private class ProfilerKeyListener implements ActionListener { + private class ProfilerKeyListener implements ActionListener { @Override public void onAction(String name, boolean value, float tpf) { if (!value) { @@ -240,4 +235,3 @@ public void onAction(String name, boolean value, float tpf) { } } } - diff --git a/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java b/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java index 1823e18ac9..9e3740bec9 100644 --- a/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/ChaseCameraAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -82,7 +82,8 @@ public class ChaseCameraAppState extends AbstractAppState implements ActionListe protected Vector3f leftVector = new Vector3f(); protected Trigger[] zoomOutTrigger = {new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)}; protected Trigger[] zoomInTrigger = {new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)}; - protected Trigger[] toggleRotateTrigger = {new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)}; + protected Trigger[] toggleRotateTrigger = {new MouseButtonTrigger(MouseInput.BUTTON_LEFT), + new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)}; // // protected boolean rotating = false; @@ -97,7 +98,7 @@ public void initialize(AppStateManager stateManager, Application app) { super.initialize(stateManager, app); this.inputManager = app.getInputManager(); target = new Node("ChaseCamTarget"); - camNode.setCamera(app.getCamera()); + camNode.setCamera(app.getCamera()); camNode.setControlDir(CameraControl.ControlDirection.SpatialToCamera); target.attachChild(camNode); camNode.setLocalTranslation(0, 0, distance); @@ -123,12 +124,13 @@ public final void registerWithInput() { initVerticalAxisInputs(); initZoomInput(); initHorizontalAxisInput(); - initTogleRotateInput(); + initToggleRotateInput(); inputManager.addListener(this, inputs); inputManager.setCursorVisible(dragToRotate); } + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (isEnabled()) { if (dragToRotate) { @@ -150,6 +152,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { } + @Override public void onAnalog(String name, float value, float tpf) { if (isEnabled()) { if (canRotate) { @@ -192,6 +195,8 @@ protected void rotateCamera() { /** * move the camera toward or away the target + * + * @param value the distance to move */ protected void zoomCamera(float value) { distance = FastMath.clamp(distance + value, minDistance, maxDistance); @@ -205,7 +210,8 @@ public void setTarget(Spatial targetSpatial) { @Override public void update(float tpf) { if (spatial == null) { - throw new IllegalArgumentException("The spatial to follow is null, please use the setTarget method"); + throw new IllegalArgumentException( + "The spatial to follow is null, please use the setTarget method"); } target.setLocalTranslation(spatial.getWorldTranslation()); camNode.lookAt(target.getWorldTranslation(), upVector); @@ -219,13 +225,13 @@ public void update(float tpf) { * new MouseButtonTrigger(MouseInput.BUTTON_LEFT) left mouse button new * MouseButtonTrigger(MouseInput.BUTTON_RIGHT) right mouse button * - * @param triggers + * @param triggers the desired triggers */ public void setToggleRotationTrigger(Trigger... triggers) { toggleRotateTrigger = triggers; if (inputManager != null) { inputManager.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE); - initTogleRotateInput(); + initToggleRotateInput(); inputManager.addListener(this, CameraInput.CHASECAM_TOGGLEROTATE); } } @@ -234,7 +240,7 @@ public void setToggleRotationTrigger(Trigger... triggers) { * Sets custom triggers for zooming in the cam default is new * MouseAxisTrigger(MouseInput.AXIS_WHEEL, true) mouse wheel up * - * @param triggers + * @param triggers the desired triggers */ public void setZoomInTrigger(Trigger... triggers) { zoomInTrigger = triggers; @@ -249,7 +255,7 @@ public void setZoomInTrigger(Trigger... triggers) { * Sets custom triggers for zooming out the cam default is new * MouseAxisTrigger(MouseInput.AXIS_WHEEL, false) mouse wheel down * - * @param triggers + * @param triggers the desired triggers */ public void setZoomOutTrigger(Trigger... triggers) { zoomOutTrigger = triggers; @@ -272,11 +278,12 @@ public float getMaxDistance() { /** * Sets the max zoom distance of the camera (default is 40) * - * @param maxDistance + * @param maxDistance the desired maximum distance (in world units, + * default=40) */ public void setMaxDistance(float maxDistance) { this.maxDistance = maxDistance; - if(initialized){ + if (initialized) { zoomCamera(distance); } } @@ -284,7 +291,7 @@ public void setMaxDistance(float maxDistance) { /** * Returns the min zoom distance of the camera (default is 1) * - * @return minDistance + * @return the minimum distance (in world units) */ public float getMinDistance() { return minDistance; @@ -292,12 +299,13 @@ public float getMinDistance() { /** * Sets the min zoom distance of the camera (default is 1) - * - * @param minDistance + * + * @param minDistance the desired minimum distance (in world units, + * default=1) */ public void setMinDistance(float minDistance) { this.minDistance = minDistance; - if(initialized){ + if (initialized) { zoomCamera(distance); } } @@ -314,11 +322,12 @@ public float getMaxVerticalRotation() { * Sets the maximal vertical rotation angle in radian of the camera around * the target. Default is Pi/2; * - * @param maxVerticalRotation + * @param maxVerticalRotation the desired maximum angle (in radians, + * default=Pi/2) */ public void setMaxVerticalRotation(float maxVerticalRotation) { this.maxVerticalRotation = maxVerticalRotation; - if(initialized){ + if (initialized) { rotateCamera(); } } @@ -336,11 +345,11 @@ public float getMinVerticalRotation() { * Sets the minimal vertical rotation angle in radian of the camera around * the target default is 0; * - * @param minHeight + * @param minHeight the desired minimum angle (in radians, default=0) */ public void setMinVerticalRotation(float minHeight) { this.minVerticalRotation = minHeight; - if(initialized){ + if (initialized) { rotateCamera(); } } @@ -374,8 +383,8 @@ public float getRotationSpeed() { } /** - * Sets the rotate amount when user moves his mouse, the lower the value, - * the slower the camera will rotate. default is 1. + * Sets the rotate amount when user moves his mouse. The lower the value, + * the slower the camera will rotate. Default is 1. * * @param rotationSpeed Rotation speed on mouse movement, default is 1. */ @@ -386,7 +395,7 @@ public void setRotationSpeed(float rotationSpeed) { /** * Sets the default distance at start of application * - * @param defaultDistance + * @param defaultDistance the desired distance (in world units, default=20) */ public void setDefaultDistance(float defaultDistance) { distance = defaultDistance; @@ -396,7 +405,7 @@ public void setDefaultDistance(float defaultDistance) { * sets the default horizontal rotation in radian of the camera at start of * the application * - * @param angleInRad + * @param angleInRad the desired rotation (in radians, default=0) */ public void setDefaultHorizontalRotation(float angleInRad) { horizontalRotation = angleInRad; @@ -406,7 +415,7 @@ public void setDefaultHorizontalRotation(float angleInRad) { * sets the default vertical rotation in radian of the camera at start of * the application * - * @param angleInRad + * @param angleInRad the desired rotation (in radians, default=0) */ public void setDefaultVerticalRotation(float angleInRad) { verticalRotation = angleInRad; @@ -431,7 +440,7 @@ public boolean isDragToRotate() { public void setDragToRotate(boolean dragToRotate) { this.dragToRotate = dragToRotate; this.canRotate = !dragToRotate; - if(inputManager != null){ + if (inputManager != null) { inputManager.setCursorVisible(dragToRotate); } } @@ -439,7 +448,7 @@ public void setDragToRotate(boolean dragToRotate) { /** * invert the vertical axis movement of the mouse * - * @param invertYaxis + * @param invertYaxis true→inverted, false→not inverted */ public void setInvertVerticalAxis(boolean invertYaxis) { this.invertYaxis = invertYaxis; @@ -454,7 +463,7 @@ public void setInvertVerticalAxis(boolean invertYaxis) { /** * invert the Horizontal axis movement of the mouse * - * @param invertXaxis + * @param invertXaxis true→inverted, false→not inverted */ public void setInvertHorizontalAxis(boolean invertXaxis) { this.invertXaxis = invertXaxis; @@ -491,7 +500,7 @@ private void initZoomInput() { inputManager.addMapping(CameraInput.CHASECAM_ZOOMOUT, zoomOutTrigger); } - private void initTogleRotateInput() { + private void initToggleRotateInput() { inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, toggleRotateTrigger); } } diff --git a/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java b/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java index 8e7e3f943d..143e13210a 100644 --- a/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/DebugKeysAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ public class DebugKeysAppState extends AbstractAppState { public static final String INPUT_MAPPING_MEMORY = "SIMPLEAPP_Memory"; private Application app; - private DebugKeyListener keyListener = new DebugKeyListener(); + private final DebugKeyListener keyListener = new DebugKeyListener(); private InputManager inputManager; public DebugKeysAppState() { @@ -83,10 +83,12 @@ public void initialize(AppStateManager stateManager, Application app) { public void cleanup() { super.cleanup(); - if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS)) + if (inputManager.hasMapping(INPUT_MAPPING_CAMERA_POS)) { inputManager.deleteMapping(INPUT_MAPPING_CAMERA_POS); - if (inputManager.hasMapping(INPUT_MAPPING_MEMORY)) + } + if (inputManager.hasMapping(INPUT_MAPPING_MEMORY)) { inputManager.deleteMapping(INPUT_MAPPING_MEMORY); + } inputManager.removeListener(keyListener); } @@ -94,6 +96,7 @@ public void cleanup() { private class DebugKeyListener implements ActionListener { + @Override public void onAction(String name, boolean value, float tpf) { if (!value) { return; @@ -110,7 +113,8 @@ public void onAction(String name, boolean value, float tpf) { System.out.println("Camera Direction: " + cam.getDirection()); System.out.println("cam.setLocation(new Vector3f(" + loc.x + "f, " + loc.y + "f, " + loc.z + "f));"); - System.out.println("cam.setRotation(new Quaternion(" + rot.getX() + "f, " +rot.getY()+ "f, " + rot.getZ() + "f, " + rot.getW() + "f));"); + System.out.println("cam.setRotation(new Quaternion(" + rot.getX() + "f, " + rot.getY() + + "f, " + rot.getZ() + "f, " + rot.getW() + "f));"); } } else if (name.equals(INPUT_MAPPING_MEMORY)) { diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java index e5673df53c..2936a30072 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfiler.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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.app; import com.jme3.profile.*; @@ -12,7 +43,7 @@ */ public class DetailedProfiler implements AppProfiler { - private final static int MAX_FRAMES = 100; + private static final int MAX_FRAMES = 100; private Map data; private Map pool; private long startFrame; @@ -28,16 +59,17 @@ public class DetailedProfiler implements AppProfiler { private String curSpPath = null; private VpStep lastVpStep = null; - private StringBuilder path = new StringBuilder(256); - private StringBuilder vpPath = new StringBuilder(256); + private final StringBuilder path = new StringBuilder(256); + private final StringBuilder vpPath = new StringBuilder(256); - private Deque idsPool = new ArrayDeque<>(100); + private final Deque idsPool = new ArrayDeque<>(100); StatLine frameTime; @Override public void appStep(AppStep step) { + curAppPath = step.name(); if (step == AppStep.BeginFrame) { @@ -102,7 +134,10 @@ public void appSubStep(String... additionalInfo) { private void closeFrame() { //close frame if (data != null) { - + if (ongoingGpuProfiling && renderer != null) { + renderer.stopProfiling(); + ongoingGpuProfiling = false; + } prevPath = null; for (StatLine statLine : data.values()) { @@ -117,14 +152,17 @@ public void vpStep(VpStep step, ViewPort vp, RenderQueue.Bucket bucket) { if (data != null) { vpPath.setLength(0); - vpPath.append(vp.getName()).append("/").append((bucket == null ? step.name() : bucket.name() + " Bucket")); + vpPath.append(vp.getName()).append("/") + .append((bucket == null ? step.name() : bucket.name() + " Bucket")); path.setLength(0); if ((lastVpStep == VpStep.PostQueue || lastVpStep == VpStep.PostFrame) && bucket != null) { - path.append(curAppPath).append("/").append(curVpPath).append(curSpPath).append("/").append(vpPath); + path.append(curAppPath).append("/").append(curVpPath).append(curSpPath).append("/") + .append(vpPath); curVpPath = vpPath.toString(); } else { if (bucket != null) { - path.append(curAppPath).append("/").append(curVpPath).append("/").append(bucket.name() + " Bucket"); + path.append(curAppPath).append("/").append(curVpPath).append("/") + .append(bucket.name() + " Bucket"); } else { path.append(curAppPath).append("/").append(vpPath); curVpPath = vpPath.toString(); @@ -150,7 +188,7 @@ public void spStep(SpStep step, String... additionalInfo) { public Map getStats() { if (data != null) { - return data;//new LinkedHashMap<>(data); + return data; //new LinkedHashMap<>(data); } return null; } @@ -221,8 +259,8 @@ private int getUnusedTaskId() { } public static class StatLine { - private long[] cpuTimes = new long[MAX_FRAMES]; - private long[] gpuTimes = new long[MAX_FRAMES]; + private final long[] cpuTimes = new long[MAX_FRAMES]; + private final long[] gpuTimes = new long[MAX_FRAMES]; private int startCursor = 0; private int cpuCursor = 0; private int gpuCursor = 0; @@ -284,7 +322,7 @@ public double getAverageCpu() { if (nbFramesCpu == 0) { return 0; } - return (double) cpuSum / (double) Math.min(nbFramesCpu, MAX_FRAMES); + return cpuSum / (double) Math.min(nbFramesCpu, MAX_FRAMES); } public double getAverageGpu() { @@ -292,7 +330,7 @@ public double getAverageGpu() { return 0; } - return (double) gpuSum / (double) Math.min(nbFramesGpu, MAX_FRAMES); + return gpuSum / (double) Math.min(nbFramesGpu, MAX_FRAMES); } } diff --git a/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java b/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java index c4d6e74da0..7a3e0393cc 100644 --- a/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java +++ b/jme3-core/src/main/java/com/jme3/app/DetailedProfilerState.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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.app; import com.jme3.app.state.BaseAppState; @@ -29,13 +60,13 @@ public class DetailedProfilerState extends BaseAppState { private static final String TOGGLE_KEY = "Toggle_Detailed_Profiler"; private static final String CLICK_KEY = "Click_Detailed_Profiler"; private static final String INSIGNIFICANT = "Hide insignificant stat"; - private DetailedProfiler prof = new DetailedProfiler(); + private final DetailedProfiler prof = new DetailedProfiler(); private float time = 0; private BitmapFont font; private BitmapFont bigFont; - private Node ui = new Node("Stats ui"); - private Map lines = new HashMap<>(); + private final Node ui = new Node("Stats ui"); + private final Map lines = new HashMap<>(); private double totalTimeCpu; private double totalTimeGpu; private int maxLevel = 0; @@ -52,14 +83,14 @@ public class DetailedProfilerState extends BaseAppState { private StatLineView rootLine; private int height = 0; - private DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US)); + private final DecimalFormat df = new DecimalFormat("##0.00", new DecimalFormatSymbols(Locale.US)); - private ColorRGBA dimmedWhite = ColorRGBA.White.mult(0.7f); - private ColorRGBA dimmedGreen = ColorRGBA.Green.mult(0.7f); - private ColorRGBA dimmedOrange = ColorRGBA.Orange.mult(0.7f); - private ColorRGBA dimmedRed = ColorRGBA.Red.mult(0.7f); + private final ColorRGBA dimmedWhite = ColorRGBA.White.mult(0.7f); + private final ColorRGBA dimmedGreen = ColorRGBA.Green.mult(0.7f); + private final ColorRGBA dimmedOrange = ColorRGBA.Orange.mult(0.7f); + private final ColorRGBA dimmedRed = ColorRGBA.Red.mult(0.7f); - private ProfilerInputListener inputListener = new ProfilerInputListener(); + private final ProfilerInputListener inputListener = new ProfilerInputListener(); public DetailedProfilerState() { @@ -70,7 +101,8 @@ protected void initialize(Application app) { Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f)); mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); - Geometry darkenStats = new Geometry("StatsDarken", new Quad(PANEL_WIDTH, app.getCamera().getHeight())); + Geometry darkenStats = new Geometry("StatsDarken", new Quad(PANEL_WIDTH, + app.getCamera().getHeight())); darkenStats.setMaterial(mat); darkenStats.setLocalTranslation(0, -app.getCamera().getHeight(), -1); @@ -85,17 +117,20 @@ protected void initialize(Application app) { BitmapText frameLabel = new BitmapText(bigFont); frameLabel.setText("Total Frame Time: "); ui.attachChild(frameLabel); - frameLabel.setLocalTranslation(new Vector3f(PANEL_WIDTH / 2 - bigFont.getLineWidth(frameLabel.getText()), -PADDING, 0)); + frameLabel.setLocalTranslation( + new Vector3f(PANEL_WIDTH / 2 - bigFont.getLineWidth(frameLabel.getText()), -PADDING, 0)); BitmapText cpuLabel = new BitmapText(bigFont); cpuLabel.setText("CPU"); ui.attachChild(cpuLabel); - cpuLabel.setLocalTranslation(PANEL_WIDTH / 4 - bigFont.getLineWidth(cpuLabel.getText()) / 2, -PADDING - 30, 0); + cpuLabel.setLocalTranslation(PANEL_WIDTH / 4 - bigFont.getLineWidth(cpuLabel.getText()) / 2, + -PADDING - 30, 0); BitmapText gpuLabel = new BitmapText(bigFont); gpuLabel.setText("GPU"); ui.attachChild(gpuLabel); - gpuLabel.setLocalTranslation(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(gpuLabel.getText()) / 2, -PADDING - 30, 0); + gpuLabel.setLocalTranslation(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(gpuLabel.getText()) / 2, + -PADDING - 30, 0); frameTimeValue = new BitmapText(bigFont); frameCpuTimeValue = new BitmapText(bigFont); @@ -190,16 +225,22 @@ private void layout() { setColor(frameTimeValue, prof.getAverageFrameTime(), totalTimeCpu, false, false); frameCpuTimeValue.setText(df.format(getMsFromNs(totalTimeCpu)) + "ms"); - frameCpuTimeValue.setLocalTranslation(new Vector3f(PANEL_WIDTH / 4 - bigFont.getLineWidth(frameCpuTimeValue.getText()) / 2, -PADDING - 50, 0)); + frameCpuTimeValue.setLocalTranslation( + new Vector3f(PANEL_WIDTH / 4 - bigFont.getLineWidth(frameCpuTimeValue.getText()) / 2, + -PADDING - 50, 0)); setColor(frameCpuTimeValue, totalTimeCpu, totalTimeCpu, false, false); frameGpuTimeValue.setText(df.format(getMsFromNs(totalTimeGpu)) + "ms"); - frameGpuTimeValue.setLocalTranslation(new Vector3f(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(frameGpuTimeValue.getText()) / 2, -PADDING - 50, 0)); + frameGpuTimeValue.setLocalTranslation( + new Vector3f(3 * PANEL_WIDTH / 4 - bigFont.getLineWidth(frameGpuTimeValue.getText()) / 2, + -PADDING - 50, 0)); setColor(frameGpuTimeValue, totalTimeGpu, totalTimeGpu, false, false); - selectedField.setText("Selected: " + df.format(getMsFromNs(selectedValueCpu)) + "ms / " + df.format(getMsFromNs(selectedValueGpu)) + "ms"); + selectedField.setText("Selected: " + df.format(getMsFromNs(selectedValueCpu)) + "ms / " + + df.format(getMsFromNs(selectedValueGpu)) + "ms"); - selectedField.setLocalTranslation(3 * PANEL_WIDTH / 4 - font.getLineWidth(selectedField.getText()) / 2, -PADDING - 75, 0); + selectedField.setLocalTranslation( + 3 * PANEL_WIDTH / 4 - font.getLineWidth(selectedField.getText()) / 2, -PADDING - 75, 0); } private StatLineView getStatLineView(String path) { @@ -254,7 +295,8 @@ protected void onDisable() { ui.removeFromParent(); } - public boolean setColor(BitmapText t, double value, double totalTime, boolean isParent, boolean expended) { + public boolean setColor(BitmapText t, double value, double totalTime, boolean isParent, + boolean expended) { boolean dimmed = isParent && expended; boolean insignificant = false; @@ -382,7 +424,8 @@ public void layout(int indent) { int y = -(height * LINE_HEIGHT + HEADER_HEIGHT); label.setLocalTranslation(PADDING + indent * PADDING, y, 0); - float gpuPos = PANEL_WIDTH - font.getLineWidth(gpuText.getText()) - PADDING * (maxLevel - indent + 1); + float gpuPos = PANEL_WIDTH - font.getLineWidth(gpuText.getText()) + - PADDING * (maxLevel - indent + 1); cpuText.setLocalTranslation(gpuPos - font.getLineWidth(cpuText.getText()), y, 0); gpuText.setLocalTranslation(gpuPos, y, 0); @@ -435,7 +478,8 @@ public void setVisible(boolean visible) { @Override public String toString() { - return label.getText() + " - " + df.format(getMsFromNs(cpuValue)) + "ms / " + df.format(getMsFromNs(gpuValue)) + "ms"; + return label.getText() + " - " + df.format(getMsFromNs(cpuValue)) + "ms / " + + df.format(getMsFromNs(gpuValue)) + "ms"; } } diff --git a/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java b/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java index 11d3e3fa29..d8a834b877 100644 --- a/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/FlyCamAppState.java @@ -35,11 +35,10 @@ import com.jme3.app.state.AppStateManager; import com.jme3.input.FlyByCamera; - /** - * Manages a FlyByCamera. + * Manages a FlyByCamera. * - * @author Paul Speed + * @author Paul Speed */ public class FlyCamAppState extends AbstractAppState { @@ -47,15 +46,15 @@ public class FlyCamAppState extends AbstractAppState { private FlyByCamera flyCam; public FlyCamAppState() { - } + } /** - * This is called by SimpleApplication during initialize(). + * This is called by SimpleApplication during initialize(). */ - void setCamera( FlyByCamera cam ) { + void setCamera(FlyByCamera cam) { this.flyCam = cam; } - + public FlyByCamera getCamera() { return flyCam; } @@ -63,34 +62,31 @@ public FlyByCamera getCamera() { @Override public void initialize(AppStateManager stateManager, Application app) { super.initialize(stateManager, app); - + this.app = app; if (app.getInputManager() != null) { - + if (flyCam == null) { flyCam = new FlyByCamera(app.getCamera()); } - - flyCam.registerWithInput(app.getInputManager()); - } + + flyCam.registerWithInput(app.getInputManager()); + } } - + @Override public void setEnabled(boolean enabled) { super.setEnabled(enabled); - flyCam.setEnabled(enabled); } - + @Override public void cleanup() { super.cleanup(); - if (app.getInputManager() != null) { + if (app.getInputManager() != null) { flyCam.unregisterInput(); - } + } } - - } 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 1cc53b75e3..1e0a75daf7 100644 --- a/jme3-core/src/main/java/com/jme3/app/LegacyApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/LegacyApplication.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 @@ -37,7 +37,11 @@ import com.jme3.audio.AudioContext; import com.jme3.audio.AudioRenderer; import com.jme3.audio.Listener; -import com.jme3.input.*; +import com.jme3.input.InputManager; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; import com.jme3.math.Vector3f; import com.jme3.profile.AppProfiler; import com.jme3.profile.AppStep; @@ -45,8 +49,15 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; -import com.jme3.system.*; +import com.jme3.system.AppSettings; +import com.jme3.system.Displays; +import com.jme3.system.JmeContext; import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import com.jme3.system.NanoTimer; +import com.jme3.system.SystemListener; +import com.jme3.system.Timer; +import com.jme3.util.res.Resources; import java.net.MalformedURLException; import java.net.URL; import java.util.concurrent.Callable; @@ -96,20 +107,22 @@ public class LegacyApplication implements Application, SystemListener { protected AppProfiler prof; - private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); + private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue<>(); /** * Create a new instance of LegacyApplication. */ public LegacyApplication() { - this((AppState[])null); + this((AppState[]) null); } /** * Create a new instance of LegacyApplication, preinitialized * with the specified set of app states. + * + * @param initialStates app states to pre-attach, or null for none */ - public LegacyApplication( AppState... initialStates ) { + public LegacyApplication(AppState... initialStates) { initStateManager(); if (initialStates != null) { @@ -126,22 +139,24 @@ public LegacyApplication( AppState... initialStates ) { * * @return The lost focus behavior of the application. */ + @Override public LostFocusBehavior getLostFocusBehavior() { return lostFocusBehavior; } /** - * Change the application's behavior when unfocused. + * Changes the application's behavior when unfocused. * * By default, the application will * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop} - * so as to not take 100% CPU usage when it is not in focus, e.g. - * alt-tabbed, minimized, or obstructed by another window. + * so as not to use 100% of the CPU when out of focus, e.g. + * alt-tabbed, minimized, or hidden by another window. * * @param lostFocusBehavior The new lost focus behavior to use. * * @see LostFocusBehavior */ + @Override public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) { this.lostFocusBehavior = lostFocusBehavior; } @@ -153,6 +168,7 @@ public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) { * * @see #getLostFocusBehavior() */ + @Override public boolean isPauseOnLostFocus() { return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus; } @@ -163,16 +179,16 @@ public boolean isPauseOnLostFocus() { * By default, pause on lost focus is enabled. * If enabled, the application will stop updating * when it loses focus or becomes inactive (e.g. alt-tab). - * For online or real-time applications, this might not be preferable, - * so this feature should be set to disabled. For other applications, - * it is best to keep it on so that CPU usage is not used when - * not necessary. + * For online or real-time applications, this might be undesirable, + * so this feature should be disabled. For other applications, + * it is best to keep it enabled so the CPU is not used unnecessarily. * * @param pauseOnLostFocus True to enable pause on lost focus, false * otherwise. * * @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior) */ + @Override public void setPauseOnLostFocus(boolean pauseOnLostFocus) { if (pauseOnLostFocus) { setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus); @@ -182,28 +198,33 @@ public void setPauseOnLostFocus(boolean pauseOnLostFocus) { } @Deprecated - public void setAssetManager(AssetManager assetManager){ - if (this.assetManager != null) - throw new IllegalStateException("Can only set asset manager" - + " before initialization."); + public void setAssetManager(AssetManager assetManager) { + if (this.assetManager != null) { + throw new IllegalStateException("Can only set asset manager" + " before initialization."); + } this.assetManager = assetManager; } - private void initAssetManager(){ + private void initAssetManager() { URL assetCfgUrl = null; - if (settings != null){ + if (settings != null) { String assetCfg = settings.getString("AssetConfigURL"); - if (assetCfg != null){ + if (assetCfg != null) { try { assetCfgUrl = new URL(assetCfg); } catch (MalformedURLException ex) { + //do nothing, we check assetCfgUrl } if (assetCfgUrl == null) { - assetCfgUrl = LegacyApplication.class.getClassLoader().getResource(assetCfg); + assetCfgUrl = Resources.getResource(assetCfg); if (assetCfgUrl == null) { - logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg); + logger.log( + Level.SEVERE, + "Unable to access AssetConfigURL in asset config:{0}", + assetCfg + ); return; } } @@ -212,7 +233,7 @@ private void initAssetManager(){ if (assetCfgUrl == null) { assetCfgUrl = JmeSystem.getPlatformAssetConfigURL(); } - if (assetManager == null){ + if (assetManager == null) { assetManager = JmeSystem.newAssetManager(assetCfgUrl); } } @@ -227,18 +248,19 @@ private void initAssetManager(){ * * @param settings The settings to set. */ - public void setSettings(AppSettings settings){ + @Override + public void setSettings(AppSettings settings) { this.settings = settings; - if (context != null && settings.useInput() != inputEnabled){ + if (context != null && settings.useInput() != inputEnabled) { // may need to create or destroy input based // on settings change inputEnabled = !inputEnabled; - if (inputEnabled){ + if (inputEnabled) { initInput(); - }else{ + } else { destroyInput(); } - }else{ + } else { inputEnabled = settings.useInput(); } } @@ -248,7 +270,8 @@ public void setSettings(AppSettings settings){ * frame times. By default, Application will use the Timer as returned * by the current JmeContext implementation. */ - public void setTimer(Timer timer){ + @Override + public void setTimer(Timer timer) { this.timer = timer; if (timer != null) { @@ -260,12 +283,13 @@ public void setTimer(Timer timer){ } } - public Timer getTimer(){ + @Override + public Timer getTimer() { return timer; } - private void initDisplay(){ - // aquire important objects + private void initDisplay() { + // acquire important objects // from the context settings = context.getSettings(); @@ -277,8 +301,8 @@ private void initDisplay(){ renderer = context.getRenderer(); } - private void initAudio(){ - if (settings.getAudioRenderer() != null && context.getType() != Type.Headless){ + private void initAudio() { + if (settings.getAudioRenderer() != null && context.getType() != Type.Headless) { audioRenderer = JmeSystem.newAudioRenderer(settings); audioRenderer.initialize(); AudioContext.setAudioRenderer(audioRenderer); @@ -293,15 +317,15 @@ private void initAudio(){ * projection with 45° field of view, with near and far values 1 and 1000 * units respectively. */ - private void initCamera(){ + private void initCamera() { cam = new Camera(settings.getWidth(), settings.getHeight()); - cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f); + cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 1f, 1000f); cam.setLocation(new Vector3f(0f, 0f, 10f)); cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); renderManager = new RenderManager(renderer); - //Remy - 09/14/2010 setted the timer in the renderManager + //Remy - 09/14/2010 set the timer in the renderManager renderManager.setTimer(timer); if (prof != null) { @@ -322,29 +346,33 @@ private void initCamera(){ * initializes joystick input if joysticks are enabled in the * AppSettings. */ - private void initInput(){ + private void initInput() { mouseInput = context.getMouseInput(); - if (mouseInput != null) + if (mouseInput != null) { mouseInput.initialize(); + } keyInput = context.getKeyInput(); - if (keyInput != null) + if (keyInput != null) { keyInput.initialize(); + } touchInput = context.getTouchInput(); - if (touchInput != null) + if (touchInput != null) { touchInput.initialize(); + } - if (!settings.getBoolean("DisableJoysticks")){ + if (settings.useJoysticks()) { joyInput = context.getJoyInput(); - if (joyInput != null) + if (joyInput != null) { joyInput.initialize(); + } } inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); } - private void initStateManager(){ + private void initStateManager() { stateManager = new AppStateManager(this); // Always register a ResetStatsState to make sure @@ -355,20 +383,23 @@ private void initStateManager(){ /** * @return The {@link AssetManager asset manager} for this application. */ - public AssetManager getAssetManager(){ + @Override + public AssetManager getAssetManager() { return assetManager; } /** * @return the {@link InputManager input manager}. */ - public InputManager getInputManager(){ + @Override + public InputManager getInputManager() { return inputManager; } /** * @return the {@link AppStateManager app state manager} */ + @Override public AppStateManager getStateManager() { return stateManager; } @@ -376,6 +407,7 @@ public AppStateManager getStateManager() { /** * @return the {@link RenderManager render manager} */ + @Override public RenderManager getRenderManager() { return renderManager; } @@ -383,13 +415,15 @@ public RenderManager getRenderManager() { /** * @return The {@link Renderer renderer} for the application */ - public Renderer getRenderer(){ + @Override + public Renderer getRenderer() { return renderer; } /** * @return The {@link AudioRenderer audio renderer} for the application */ + @Override public AudioRenderer getAudioRenderer() { return audioRenderer; } @@ -397,6 +431,7 @@ public AudioRenderer getAudioRenderer() { /** * @return The {@link Listener listener} object for audio */ + @Override public Listener getListener() { return listener; } @@ -404,14 +439,16 @@ public Listener getListener() { /** * @return The {@link JmeContext display context} for the application */ - public JmeContext getContext(){ + @Override + public JmeContext getContext() { return context; } /** * @return The {@link Camera camera} for the application */ - public Camera getCamera(){ + @Override + public Camera getCamera() { return cam; } @@ -420,16 +457,20 @@ public Camera getCamera(){ * * @see #start(com.jme3.system.JmeContext.Type) */ - public void start(){ + @Override + public void start() { start(JmeContext.Type.Display, false); } /** * Starts the application in {@link Type#Display display} mode. * + * @param waitFor true→wait for the context to be initialized, + * false→don't wait * @see #start(com.jme3.system.JmeContext.Type) */ - public void start(boolean waitFor){ + @Override + public void start(boolean waitFor) { start(JmeContext.Type.Display, waitFor); } @@ -437,6 +478,8 @@ public void start(boolean waitFor){ * Starts the application. * Creating a rendering context and executing * the main loop in a separate thread. + * + * @param contextType the type of context to create */ public void start(JmeContext.Type contextType) { start(contextType, false); @@ -446,14 +489,18 @@ public void start(JmeContext.Type contextType) { * Starts the application. * Creating a rendering context and executing * the main loop in a separate thread. + * + * @param contextType the type of context to create + * @param waitFor true→wait for the context to be initialized, + * false→don't wait */ - public void start(JmeContext.Type contextType, boolean waitFor){ - if (context != null && context.isCreated()){ + public void start(JmeContext.Type contextType, boolean waitFor) { + if (context != null && context.isCreated()) { logger.warning("start() called when application already created!"); return; } - if (settings == null){ + if (settings == null) { settings = new AppSettings(true); } @@ -467,7 +514,10 @@ public void start(JmeContext.Type contextType, boolean waitFor){ * Sets an AppProfiler hook that will be called back for * specific steps within a single update frame. Value defaults * to null. + * + * @param prof the profiler to use (alias created) or null for none */ + @Override public void setAppProfiler(AppProfiler prof) { this.prof = prof; if (renderManager != null) { @@ -478,6 +528,7 @@ public void setAppProfiler(AppProfiler prof) { /** * Returns the current AppProfiler hook, or null if none is set. */ + @Override public AppProfiler getAppProfiler() { return prof; } @@ -496,17 +547,19 @@ public AppProfiler getAppProfiler() { * * @see Type#Canvas */ - public void createCanvas(){ - if (context != null && context.isCreated()){ + public void createCanvas() { + if (context != null && context.isCreated()) { logger.warning("createCanvas() called when application already created!"); return; } - if (settings == null){ + if (settings == null) { settings = new AppSettings(true); } - logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); + } context = JmeSystem.newContext(settings, JmeContext.Type.Canvas); context.setSystemListener(this); } @@ -518,7 +571,7 @@ public void createCanvas(){ * * @see #startCanvas(boolean) */ - public void startCanvas(){ + public void startCanvas() { startCanvas(false); } @@ -531,19 +584,27 @@ public void startCanvas(){ * @param waitFor If true, the current thread will block until the * rendering thread is running */ - public void startCanvas(boolean waitFor){ + public void startCanvas(boolean waitFor) { context.create(waitFor); } /** * Internal use only. */ - public void reshape(int w, int h){ + @Override + public void reshape(int w, int h) { if (renderManager != null) { renderManager.notifyReshape(w, h); } } + @Override + public void rescale(float x, float y) { + if (renderManager != null) { + renderManager.notifyRescale(x, y); + } + } + /** * Restarts the context, applying any changed settings. *

    @@ -551,7 +612,8 @@ public void reshape(int w, int h){ * applied immediately; calling this method forces the context * to restart, applying the new settings. */ - public void restart(){ + @Override + public void restart() { context.setSettings(settings); context.restart(); } @@ -564,7 +626,8 @@ public void restart(){ * * @see #stop(boolean) */ - public void stop(){ + @Override + public void stop() { stop(false); } @@ -572,8 +635,12 @@ public void stop(){ * Requests the context to close, shutting down the main loop * and making necessary cleanup operations. * After the application has stopped, it cannot be used anymore. + * + * @param waitFor true→wait for the context to be fully destroyed, + * false→don't wait */ - public void stop(boolean waitFor){ + @Override + public void stop(boolean waitFor) { logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); context.destroy(waitFor); } @@ -588,39 +655,44 @@ public void stop(boolean waitFor){ * perspective projection with 45° field of view, with near * and far values 1 and 1000 units respectively. */ - public void initialize(){ - if (assetManager == null){ + @Override + public void initialize() { + if (assetManager == null) { initAssetManager(); } initDisplay(); initCamera(); - if (inputEnabled){ + if (inputEnabled) { initInput(); } initAudio(); // update timer so that the next delta is not too large -// timer.update(); + // timer.update(); timer.reset(); - - // user code here.. + // user code here } /** * Internal use only. */ - public void handleError(String errMsg, Throwable t){ + @Override + public void handleError(String errMsg, Throwable t) { // Print error to log. logger.log(Level.SEVERE, errMsg, 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() + - (t.getMessage() != null ? ": " + t.getMessage() : "")); + JmeSystem.handleErrorMessage( + errMsg + + "\n" + + t.getClass().getSimpleName() + + (t.getMessage() != null ? ": " + t.getMessage() : "") + ); } else { - JmeSystem.showErrorDialog(errMsg); + JmeSystem.handleErrorMessage(errMsg); } } @@ -630,7 +702,8 @@ public void handleError(String errMsg, Throwable t){ /** * Internal use only. */ - public void gainFocus(){ + @Override + public void gainFocus() { if (lostFocusBehavior != LostFocusBehavior.Disabled) { if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) { paused = false; @@ -645,8 +718,9 @@ public void gainFocus(){ /** * Internal use only. */ - public void loseFocus(){ - if (lostFocusBehavior != LostFocusBehavior.Disabled){ + @Override + public void loseFocus() { + if (lostFocusBehavior != LostFocusBehavior.Disabled) { if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) { paused = true; } @@ -657,7 +731,8 @@ public void loseFocus(){ /** * Internal use only. */ - public void requestClose(boolean esc){ + @Override + public void requestClose(boolean esc) { context.destroy(false); } @@ -669,10 +744,13 @@ public void requestClose(boolean esc){ * They are executed even if the application is currently paused * or out of focus. * + * @param type of result returned by the Callable * @param callable The callable to run in the main jME3 thread + * @return a new instance */ + @Override public Future enqueue(Callable callable) { - AppTask task = new AppTask(callable); + AppTask task = new AppTask<>(callable); taskQueue.add(task); return task; } @@ -687,7 +765,9 @@ public Future enqueue(Callable callable) { * * @param runnable The runnable to run in the main jME3 thread */ - public void enqueue(Runnable runnable){ + @Override + @SuppressWarnings("unchecked") + public void enqueue(Runnable runnable) { enqueue(new RunnableWrapper(runnable)); } @@ -695,8 +775,8 @@ public void enqueue(Runnable runnable){ * Runs tasks enqueued via {@link #enqueue(Callable)} */ protected void runQueuedTasks() { - AppTask task; - while( (task = taskQueue.poll()) != null ) { + AppTask task; + while ((task = taskQueue.poll()) != null) { if (!task.isCancelled()) { task.invoke(); } @@ -707,43 +787,54 @@ protected void runQueuedTasks() { * Do not call manually. * Callback from ContextListener. */ - public void update(){ + @Override + public void update() { // Make sure the audio renderer is available to callables AudioContext.setAudioRenderer(audioRenderer); - if (prof!=null) prof.appStep(AppStep.QueuedTasks); + if (prof != null) { + prof.appStep(AppStep.QueuedTasks); + } runQueuedTasks(); - if (speed == 0 || paused) + if (speed == 0 || paused) { return; + } timer.update(); - if (inputEnabled){ - if (prof!=null) prof.appStep(AppStep.ProcessInput); + if (inputEnabled) { + if (prof != null) { + prof.appStep(AppStep.ProcessInput); + } inputManager.update(timer.getTimePerFrame()); } - if (audioRenderer != null){ - if (prof!=null) prof.appStep(AppStep.ProcessAudio); + if (audioRenderer != null) { + if (prof != null) { + prof.appStep(AppStep.ProcessAudio); + } audioRenderer.update(timer.getTimePerFrame()); } - - // user code here.. + // user code here } - protected void destroyInput(){ - if (mouseInput != null) + protected void destroyInput() { + if (mouseInput != null) { mouseInput.destroy(); + } - if (keyInput != null) + if (keyInput != null) { keyInput.destroy(); + } - if (joyInput != null) + if (joyInput != null) { joyInput.destroy(); + } - if (touchInput != null) + if (touchInput != null) { touchInput.destroy(); + } inputManager = null; } @@ -752,12 +843,14 @@ protected void destroyInput(){ * Do not call manually. * Callback from ContextListener. */ - public void destroy(){ + @Override + public void destroy() { stateManager.cleanup(); destroyInput(); - if (audioRenderer != null) + if (audioRenderer != null) { audioRenderer.cleanup(); + } timer.reset(); } @@ -766,27 +859,50 @@ public void destroy(){ * @return The GUI viewport. Which is used for the on screen * statistics and FPS. */ + @Override public ViewPort getGuiViewPort() { return guiViewPort; } + @Override public ViewPort getViewPort() { return viewPort; } - private class RunnableWrapper implements Callable{ + private class RunnableWrapper implements Callable { + private final Runnable runnable; - public RunnableWrapper(Runnable runnable){ + public RunnableWrapper(Runnable runnable) { this.runnable = runnable; } @Override - public Object call(){ + public Object call() { runnable.run(); return null; } + } + /** + * This call will return a list of Monitors that glfwGetMonitors() + * returns and information about the monitor, like width, height, + * and refresh rate. + * + * @return returns a list of monitors and their information. + */ + public Displays getDisplays() { + return context.getDisplays(); } + /** + * Use this to get the positional number of the primary + * monitor from the glfwGetMonitors() function call. + * + * @return the position of the value in the arraylist of + * the primary monitor. + */ + public int getPrimaryDisplay() { + return context.getPrimaryDisplay(); + } } diff --git a/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java b/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java index 03f301326f..e03ff056bb 100644 --- a/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java +++ b/jme3-core/src/main/java/com/jme3/app/ResetStatsState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ * Resets (clearFrame()) the render's stats object every frame * during AppState.render(). This state is registered once * with Application to ensure that the stats are cleared once - * a frame. Using this makes sure that any Appliction based + * a frame. Using this makes sure that any Application-based * application that properly runs its state manager will have * stats reset no matter how many views it has or if it even * has views. diff --git a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java index 9f7a1f671e..704d0eb77a 100644 --- a/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java +++ b/jme3-core/src/main/java/com/jme3/app/SimpleApplication.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,28 +45,47 @@ import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Node; import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.threadwarden.SceneGraphThreadWarden; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext.Type; import com.jme3.system.JmeSystem; +import java.util.logging.Level; +import java.util.logging.Logger; + /** - * SimpleApplication is the base class for all jME3 Applications. - * SimpleApplication will display a statistics view - * using the {@link com.jme3.app.StatsAppState} AppState. It will display - * the current frames-per-second value on-screen in addition to the statistics. - * Several keys have special functionality in SimpleApplication:
    + * `SimpleApplication` is the foundational base class for all jMonkeyEngine 3 (jME3) applications. + * It provides a streamlined setup for common game development tasks, including scene management, + * camera controls, and performance monitoring. + * + *

    By default, `SimpleApplication` attaches several essential {@link com.jme3.app.state.AppState} instances: + *

      + *
    • {@link com.jme3.app.StatsAppState}: Displays real-time frames-per-second (FPS) and + * detailed performance statistics on-screen.
    • + *
    • {@link com.jme3.app.FlyCamAppState}: Provides a convenient first-person fly-by camera + * controller, allowing easy navigation within the scene.
    • + *
    • {@link com.jme3.audio.AudioListenerState}: Manages the audio listener, essential for 3D sound.
    • + *
    • {@link com.jme3.app.DebugKeysAppState}: Enables debug functionalities like displaying + * camera position and memory usage in the console.
    • + *
    • {@link com.jme3.app.state.ConstantVerifierState}: A utility state for verifying constant + * values, primarily for internal engine debugging.
    • + *
    * - * - * - * - * - *
    Esc- Close the application
    C- Display the camera position and rotation in the console.
    M- Display memory usage in the console.
    + *

    Default Key Bindings:

    + *
      + *
    • Esc: Closes and exits the application.
    • + *
    • F5: Toggles the visibility of the statistics view (FPS and debug stats).
    • + *
    • C: Prints the current camera position and rotation to the console.
    • + *
    • M: Prints memory usage statistics to the console.
    • + *
    * - * A {@link com.jme3.app.FlyCamAppState} is by default attached as well and can - * be removed by calling stateManager.detach( stateManager.getState(FlyCamAppState.class) ); + *

    Applications extending `SimpleApplication` should implement the + * {@link #simpleInitApp()} method to set up their initial scene and game logic. */ public abstract class SimpleApplication extends LegacyApplication { + protected static final Logger logger = Logger.getLogger(SimpleApplication.class.getName()); + public static final String INPUT_MAPPING_EXIT = "SIMPLEAPP_Exit"; public static final String INPUT_MAPPING_CAMERA_POS = DebugKeysAppState.INPUT_MAPPING_CAMERA_POS; public static final String INPUT_MAPPING_MEMORY = DebugKeysAppState.INPUT_MAPPING_MEMORY; @@ -78,31 +97,49 @@ public abstract class SimpleApplication extends LegacyApplication { protected BitmapFont guiFont; protected FlyByCamera flyCam; protected boolean showSettings = true; - private AppActionListener actionListener = new AppActionListener(); + private final AppActionListener actionListener = new AppActionListener(); private class AppActionListener implements ActionListener { - public void onAction(String name, boolean value, float tpf) { - if (!value) { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) { return; } if (name.equals(INPUT_MAPPING_EXIT)) { stop(); - }else if (name.equals(INPUT_MAPPING_HIDE_STATS)){ - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).toggleStats(); + } else if (name.equals(INPUT_MAPPING_HIDE_STATS)) { + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.toggleStats(); } } } } + /** + * Constructs a `SimpleApplication` with a predefined set of default + * {@link com.jme3.app.state.AppState} instances. + * These states provide common functionalities like statistics display, + * fly camera control, audio listener, debug keys, and constant verification. + */ public SimpleApplication() { - this(new StatsAppState(), new FlyCamAppState(), new AudioListenerState(), new DebugKeysAppState(), - new ConstantVerifierState()); + this(new StatsAppState(), + new FlyCamAppState(), + new AudioListenerState(), + new DebugKeysAppState(), + new ConstantVerifierState()); } - public SimpleApplication( AppState... initialStates ) { + /** + * Constructs a `SimpleApplication` with a custom array of initial + * {@link com.jme3.app.state.AppState} instances. + * + * @param initialStates An array of `AppState` instances to be attached + * to the `stateManager` upon initialization. + */ + public SimpleApplication(AppState... initialStates) { super(initialStates); } @@ -112,6 +149,7 @@ public void start() { // settings dialog is not shown boolean loadSettings = false; if (settings == null) { + logger.log(Level.INFO, "AppSettings not set, creating default settings."); setSettings(new AppSettings(true)); loadSettings = true; } @@ -126,59 +164,75 @@ public void start() { setSettings(settings); super.start(); } - + /** - * Returns the applications speed. + * Returns the current speed multiplier of the application. + * This value affects how quickly the game world updates relative to real time. + * A value of 1.0f means normal speed, 0.5f means half speed, 2.0f means double speed. * - * @return The speed of the application. + * @return The current speed of the application. */ public float getSpeed() { return speed; } - + /** - * Changes the application speed. 0.0f prevents the application from updating. - * @param speed The speed to set. + * Changes the application's speed multiplier. + * A `speed` of 0.0f effectively pauses the application's update cycle. + * + * @param speed The desired speed multiplier. A value of 1.0f is normal speed. + * Must be non-negative. */ public void setSpeed(float speed) { this.speed = speed; } /** - * Retrieves flyCam - * @return flyCam Camera object + * Retrieves the `FlyByCamera` instance associated with this application. + * This camera allows free-form navigation within the 3D scene. * + * @return The `FlyByCamera` object, or `null` if `FlyCamAppState` is not attached + * or has not yet initialized the camera. */ public FlyByCamera getFlyByCamera() { return flyCam; } /** - * Retrieves guiNode - * @return guiNode Node object + * Retrieves the `Node` dedicated to 2D graphical user interface (GUI) elements. + * Objects attached to this node are rendered on top of the 3D scene, + * typically without perspective effects, suitable for HUDs and UI. * + * @return The `Node` object representing the GUI root. */ public Node getGuiNode() { return guiNode; } /** - * Retrieves rootNode - * @return rootNode Node object + * Retrieves the root `Node` of the 3D scene graph. + * All main 3D spatial objects and models should be attached to this node + * to be part of the rendered scene. * + * @return The `Node` object representing the 3D scene root. */ public Node getRootNode() { return rootNode; } + /** + * Checks whether the settings dialog is configured to be shown at application startup. + * + * @return `true` if the settings dialog will be displayed, `false` otherwise. + */ public boolean isShowSettings() { return showSettings; } /** - * Toggles settings window to display at start-up - * @param showSettings Sets true/false + * Sets whether the jME3 settings dialog should be displayed before the application starts. * + * @param showSettings `true` to show the settings dialog, `false` to suppress it. */ public void setShowSettings(boolean showSettings) { this.showSettings = showSettings; @@ -187,6 +241,8 @@ public void setShowSettings(boolean showSettings) { /** * Creates the font that will be set to the guiFont field * and subsequently set as the font for the stats text. + * + * @return the loaded BitmapFont */ protected BitmapFont loadGuiFont() { return assetManager.loadFont("Interface/Fonts/Default.fnt"); @@ -196,103 +252,169 @@ protected BitmapFont loadGuiFont() { public void initialize() { super.initialize(); + //noinspection AssertWithSideEffects + assert SceneGraphThreadWarden.setup(rootNode); + //noinspection AssertWithSideEffects + assert SceneGraphThreadWarden.setup(guiNode); + // Several things rely on having this guiFont = loadGuiFont(); guiNode.setQueueBucket(Bucket.Gui); guiNode.setCullHint(CullHint.Never); + viewPort.attachScene(rootNode); guiViewPort.attachScene(guiNode); if (inputManager != null) { - - // We have to special-case the FlyCamAppState because too - // many SimpleApplication subclasses expect it to exist in - // simpleInit(). But at least it only gets initialized if - // the app state is added. - if (stateManager.getState(FlyCamAppState.class) != null) { + // Special handling for FlyCamAppState: + // Although FlyCamAppState manages the FlyByCamera, SimpleApplication + // historically initializes and configures a default FlyByCamera instance + // and sets its initial speed. This allows subclasses to directly access + // 'flyCam' early in simpleInitApp(). + + FlyCamAppState flyCamState = stateManager.getState(FlyCamAppState.class); + if (flyCamState != null) { flyCam = new FlyByCamera(cam); - flyCam.setMoveSpeed(1f); // odd to set this here but it did it before - stateManager.getState(FlyCamAppState.class).setCamera( flyCam ); + flyCam.setMoveSpeed(1f); // Set a default movement speed for the camera + flyCamState.setCamera(flyCam); // Link the FlyCamAppState to this camera instance } + // Register the "Exit" input mapping for the Escape key, but only for Display contexts. if (context.getType() == Type.Display) { inputManager.addMapping(INPUT_MAPPING_EXIT, new KeyTrigger(KeyInput.KEY_ESCAPE)); } - if (stateManager.getState(StatsAppState.class) != null) { + // Register the "Hide Stats" input mapping for the F5 key, if StatsAppState is active. + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { inputManager.addMapping(INPUT_MAPPING_HIDE_STATS, new KeyTrigger(KeyInput.KEY_F5)); inputManager.addListener(actionListener, INPUT_MAPPING_HIDE_STATS); } + // Attach the action listener to the "Exit" mapping. inputManager.addListener(actionListener, INPUT_MAPPING_EXIT); } - if (stateManager.getState(StatsAppState.class) != null) { - // Some of the tests rely on having access to fpsText - // for quick display. Maybe a different way would be better. - stateManager.getState(StatsAppState.class).setFont(guiFont); - fpsText = stateManager.getState(StatsAppState.class).getFpsText(); + // Configure the StatsAppState if it exists. + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setFont(guiFont); + fpsText = statsState.getFpsText(); } - // call user code + // Call the user's application initialization code. simpleInitApp(); } + @Override + public void stop(boolean waitFor) { + //noinspection AssertWithSideEffects + assert SceneGraphThreadWarden.reset(); + super.stop(waitFor); + } + @Override public void update() { - if (prof!=null) prof.appStep(AppStep.BeginFrame); + if (prof != null) { + prof.appStep(AppStep.BeginFrame); + } + + // Executes AppTasks from the main thread + super.update(); - super.update(); // makes sure to execute AppTasks + // Skip updates if paused or speed is zero if (speed == 0 || paused) { return; } float tpf = timer.getTimePerFrame() * speed; - // update states - if (prof!=null) prof.appStep(AppStep.StateManagerUpdate); + // Update AppStates + if (prof != null) { + prof.appStep(AppStep.StateManagerUpdate); + } stateManager.update(tpf); - // simple update and root node + // Call user's per-frame update method simpleUpdate(tpf); - if (prof!=null) prof.appStep(AppStep.SpatialUpdate); + // Update scene graph nodes (logical and geometric states) + if (prof != null) { + prof.appStep(AppStep.SpatialUpdate); + } rootNode.updateLogicalState(tpf); guiNode.updateLogicalState(tpf); rootNode.updateGeometricState(); guiNode.updateGeometricState(); - // render states - if (prof!=null) prof.appStep(AppStep.StateManagerRender); + // Render AppStates and the scene + if (prof != null) { + prof.appStep(AppStep.StateManagerRender); + } stateManager.render(renderManager); - if (prof!=null) prof.appStep(AppStep.RenderFrame); + if (prof != null) { + prof.appStep(AppStep.RenderFrame); + } renderManager.render(tpf, context.isRenderable()); + // Call user's custom render method simpleRender(renderManager); stateManager.postRender(); - if (prof!=null) prof.appStep(AppStep.EndFrame); + if (prof != null) { + prof.appStep(AppStep.EndFrame); + } } + /** + * Controls the visibility of the frames-per-second (FPS) display on the screen. + * + * @param show `true` to display the FPS, `false` to hide it. + */ public void setDisplayFps(boolean show) { - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).setDisplayFps(show); + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setDisplayFps(show); } } + /** + * Controls the visibility of the comprehensive statistics view on the screen. + * This view typically includes details about memory, triangles, and other performance metrics. + * + * @param show `true` to display the statistics view, `false` to hide it. + */ public void setDisplayStatView(boolean show) { - if (stateManager.getState(StatsAppState.class) != null) { - stateManager.getState(StatsAppState.class).setDisplayStatView(show); + StatsAppState statsState = stateManager.getState(StatsAppState.class); + if (statsState != null) { + statsState.setDisplayStatView(show); } } public abstract void simpleInitApp(); + /** + * An optional method that can be overridden by subclasses for per-frame update logic. + * This method is called during the application's update loop, after AppStates are updated + * and before the scene graph's logical state is updated. + * + * @param tpf The time per frame (in seconds), adjusted by the application's speed. + */ public void simpleUpdate(float tpf) { + // Default empty implementation; subclasses can override } + /** + * An optional method that can be overridden by subclasses for custom rendering logic. + * This method is called during the application's render loop, after the main scene + * has been rendered and before post-rendering for states. + * Useful for drawing overlays or specific rendering tasks outside the main scene graph. + * + * @param rm The `RenderManager` instance, which provides access to rendering functionalities. + */ public void simpleRender(RenderManager rm) { + // Default empty implementation; subclasses can override } } diff --git a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java index d5ffde097b..52a2939143 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,12 +43,11 @@ import com.jme3.scene.Spatial.CullHint; import com.jme3.scene.shape.Quad; - /** * Displays stats in SimpleApplication's GUI node or * using the node and font parameters provided. * - * @author Paul Speed + * @author Paul Speed */ public class StatsAppState extends AbstractAppState { @@ -70,7 +69,7 @@ public class StatsAppState extends AbstractAppState { public StatsAppState() { } - public StatsAppState( Node guiNode, BitmapFont guiFont ) { + public StatsAppState(Node guiNode, BitmapFont guiFont) { this.guiNode = guiNode; this.guiFont = guiFont; } @@ -80,10 +79,12 @@ public StatsAppState( Node guiNode, BitmapFont guiFont ) { * so that the fpsText can be created before init. This * is because several applications expect to directly access * fpsText... unfortunately. + * + * @param guiFont the desired font (not null, alias created) */ - public void setFont( BitmapFont guiFont ) { + public void setFont(BitmapFont guiFont) { this.guiFont = guiFont; - this.fpsText = new BitmapText(guiFont, false); + this.fpsText = new BitmapText(guiFont); } public BitmapText getFpsText() { @@ -99,8 +100,8 @@ public float getSecondCounter() { } public void toggleStats() { - setDisplayFps( !showFps ); - setDisplayStatView( !showStats ); + setDisplayFps(!showFps); + setDisplayStatView(!showStats); } public void setDisplayFps(boolean show) { @@ -116,7 +117,7 @@ public void setDisplayFps(boolean show) { public void setDisplayStatView(boolean show) { showStats = show; - if (statsView != null ) { + if (statsView != null) { statsView.setEnabled(show); statsView.setCullHint(show ? CullHint.Never : CullHint.Always); if (darkenStats != null) { @@ -140,17 +141,17 @@ public void initialize(AppStateManager stateManager, Application app) { this.app = app; if (app instanceof SimpleApplication) { - SimpleApplication simpleApp = (SimpleApplication)app; + SimpleApplication simpleApp = (SimpleApplication) app; if (guiNode == null) { guiNode = simpleApp.guiNode; } - if (guiFont == null ) { + if (guiFont == null) { guiFont = simpleApp.guiFont; } } if (guiNode == null) { - throw new RuntimeException( "No guiNode specific and cannot be automatically determined." ); + throw new RuntimeException("No guiNode specific and cannot be automatically determined."); } if (guiFont == null) { @@ -168,7 +169,7 @@ public void initialize(AppStateManager stateManager, Application app) { */ public void loadFpsText() { if (fpsText == null) { - fpsText = new BitmapText(guiFont, false); + fpsText = new BitmapText(guiFont); } fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); @@ -185,8 +186,8 @@ public void loadFpsText() { */ public void loadStatsView() { statsView = new StatsView("Statistics View", - app.getAssetManager(), - app.getRenderer().getStatistics()); + app.getAssetManager(), + app.getRenderer().getStatistics()); // move it up so it appears above fps text statsView.setLocalTranslation(0, fpsText.getLineHeight(), 0); statsView.setEnabled(showStats); @@ -196,7 +197,7 @@ public void loadStatsView() { public void loadDarken() { Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", new ColorRGBA(0,0,0,0.5f)); + mat.setColor("Color", new ColorRGBA(0, 0, 0, 0.5f)); mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); darkenFps = new Geometry("StatsDarken", new Quad(200, fpsText.getLineHeight())); @@ -235,7 +236,7 @@ public void setEnabled(boolean enabled) { public void update(float tpf) { if (showFps) { secondCounter += app.getTimer().getTimePerFrame(); - frameCounter ++; + frameCounter++; if (secondCounter >= 1.0f) { int fps = (int) (frameCounter / secondCounter); fpsText.setText("Frames per second: " + fps); @@ -254,6 +255,4 @@ public void cleanup() { guiNode.detachChild(darkenFps); guiNode.detachChild(darkenStats); } - - } diff --git a/jme3-core/src/main/java/com/jme3/app/StatsView.java b/jme3-core/src/main/java/com/jme3/app/StatsView.java index 4e9411f18f..8e274e97b5 100644 --- a/jme3-core/src/main/java/com/jme3/app/StatsView.java +++ b/jme3-core/src/main/java/com/jme3/app/StatsView.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,32 +47,30 @@ /** * The StatsView provides a heads-up display (HUD) of various * statistics of rendering. The data is retrieved every frame from a - * {@link com.jme3.renderer.Statistics} and then displayed on screen.
    - *
    - * Usage:
    + * {@link com.jme3.renderer.Statistics} and then displayed on screen. + *

    * To use the stats view, you need to retrieve the * {@link com.jme3.renderer.Statistics} from the * {@link com.jme3.renderer.Renderer} used by the application. Then, attach - * the StatsView to the scene graph.
    - *
    - * Statistics stats = renderer.getStatistics();
    - * StatsView statsView = new StatsView("MyStats", assetManager, stats);
    - * rootNode.attachChild(statsView);
    - *
    + * the StatsView to the scene graph. + *

    + * Statistics stats = renderer.getStatistics();
    + * StatsView statsView = new StatsView("MyStats", assetManager, stats);
    + * rootNode.attachChild(statsView);
    + * 
    */ public class StatsView extends Node implements Control, JmeCloneable { + private final BitmapText statText; + private final Statistics statistics; - private BitmapText statText; - private Statistics statistics; - - private String[] statLabels; - private int[] statData; + private final String[] statLabels; + private final int[] statData; private boolean enabled = true; private final StringBuilder stringBuilder = new StringBuilder(); - public StatsView(String name, AssetManager manager, Statistics stats){ + public StatsView(String name, AssetManager manager, Statistics stats) { super(name); setQueueBucket(Bucket.Gui); @@ -96,10 +94,11 @@ public float getHeight() { return statText.getLineHeight() * statLabels.length; } + @Override public void update(float tpf) { - - if (!isEnabled()) + if (!isEnabled()) { return; + } statistics.getData(statData); stringBuilder.setLength(0); @@ -113,7 +112,7 @@ public void update(float tpf) { // Moved to ResetStatsState to make sure it is // done even if there is no StatsView or the StatsView - // is disable. + // is disabled. //statistics.clearFrame(); } @@ -122,17 +121,18 @@ public void update(float tpf) { public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException(); } - + @Override public StatsView jmeClone() { throw new UnsupportedOperationException("Not yet implemented."); } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { throw new UnsupportedOperationException("Not yet implemented."); } + @Override public void setSpatial(Spatial spatial) { } @@ -145,7 +145,7 @@ public boolean isEnabled() { return enabled; } + @Override public void render(RenderManager rm, ViewPort vp) { } - } diff --git a/jme3-core/src/main/java/com/jme3/app/package.html b/jme3-core/src/main/java/com/jme3/app/package.html index 63d18c955c..77e3ed267a 100644 --- a/jme3-core/src/main/java/com/jme3/app/package.html +++ b/jme3-core/src/main/java/com/jme3/app/package.html @@ -27,7 +27,7 @@
  • {@link com.jme3.audio.AudioRenderer} - Allows playing sound effects and music.
  • {@link com.jme3.system.Timer} - The timer keeps track of time and allows - computing the time since the last frame (TPF) that is neccessary + computing the time since the last frame (TPF) that is necessary for framerate-independent updates and motion.
  • {@link com.jme3.system.AppSettings} - A database containing various settings for the application. These settings may be set by the user diff --git a/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java b/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java index 96b7e6b6c0..4d0e8606d2 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/AbstractAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,11 +51,11 @@ public abstract class AbstractAppState implements AppState { protected boolean initialized = false; private boolean enabled = true; private String id; - - protected AbstractAppState() { + + protected AbstractAppState() { } - - protected AbstractAppState( String id ) { + + protected AbstractAppState(String id) { this.id = id; } @@ -73,8 +73,10 @@ public boolean isInitialized() { * Sets the unique ID of this app state. Note: that setting * this while an app state is attached to the state manager will * have no effect on ID-based lookups. + * + * @param id the desired ID */ - protected void setId( String id ) { + protected void setId(String id) { this.id = id; } @@ -87,7 +89,7 @@ public String getId() { public void setEnabled(boolean enabled) { this.enabled = enabled; } - + @Override public boolean isEnabled() { return enabled; @@ -110,12 +112,11 @@ public void render(RenderManager rm) { } @Override - public void postRender(){ + public void postRender() { } @Override public void cleanup() { initialized = false; } - } diff --git a/jme3-core/src/main/java/com/jme3/app/state/AppState.java b/jme3-core/src/main/java/com/jme3/app/state/AppState.java index 33a6d28d5d..04e4d65493 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/AppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/AppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,16 +35,16 @@ import com.jme3.renderer.RenderManager; /** - * AppState represents continously executing code inside the main loop. + * AppState represents continuously executing code inside the main loop. * * An AppState can track when it is attached to the * {@link AppStateManager} or when it is detached. * - *
    AppStates are initialized in the render thread, upon a call to + * AppStates are initialized in the render thread, upon a call to * {@link AppState#initialize(com.jme3.app.state.AppStateManager, com.jme3.app.Application) } * and are de-initialized upon a call to {@link AppState#cleanup()}. * Implementations should return the correct value with a call to - * {@link AppState#isInitialized() } as specified above.
    + * {@link AppState#isInitialized() } as specified above. * *
      *
    • If a detached AppState is attached then initialize() will be called @@ -93,6 +93,8 @@ public interface AppState { /** * Returns the unique ID for this AppState or null if it has no * unique ID. + * + * @return the ID, or null if none */ public String getId(); diff --git a/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java b/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java index 96a7295d35..3a86894765 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java +++ b/jme3-core/src/main/java/com/jme3/app/state/AppStateManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,9 +30,8 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.app.state; - + import com.jme3.app.Application; -import com.jme3.profile.AppProfiler; import com.jme3.renderer.RenderManager; import com.jme3.util.SafeArrayList; import java.util.Arrays; @@ -42,7 +41,7 @@ /** * The AppStateManager holds a list of {@link AppState}s which - * it will update and render.
      + * it will update and render.
      * When an {@link AppState} is attached or detached, the * {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) } and * {@link AppState#stateDetached(com.jme3.app.state.AppStateManager) } methods @@ -59,92 +58,92 @@ * render thread and it is not necessarily safe to modify * the scene graph, etc.. *
    • cleanup() : called ONCE on the render thread at the beginning of the next update - * after the state has been detached or when the application is - * terminating. - *
    + * after the state has been detached or when the application is + * terminating. + * * * @author Kirill Vainer, Paul Speed */ public class AppStateManager { - /** * List holding the attached app states that are pending * initialization. Once initialized they will be added to - * the running app states. + * the running app states. */ - private final SafeArrayList initializing = new SafeArrayList(AppState.class); - + private final SafeArrayList initializing = new SafeArrayList<>(AppState.class); + /** - * Holds the active states once they are initialized. + * Holds the active states once they are initialized. */ - private final SafeArrayList states = new SafeArrayList(AppState.class); - + private final SafeArrayList states = new SafeArrayList<>(AppState.class); + /** * List holding the detached app states that are pending - * cleanup. + * cleanup. */ - private final SafeArrayList terminating = new SafeArrayList(AppState.class); + private final SafeArrayList terminating = new SafeArrayList<>(AppState.class); /** * Thread-safe index of every state that is currently attached and has * an ID. */ private final ConcurrentMap stateIndex = new ConcurrentHashMap<>(); - - // All of the above lists need to be thread safe but access will be + + // All of the above lists need to be thread-safe, but access will be // synchronized separately.... but always on the states list. This - // is to avoid deadlocking that may occur and the most common use case - // is that they are all modified from the same thread anyway. - + // is to avoid deadlocking. Anyway, the most common use case + // is that they are all modified from the same thread. + private final Application app; - private AppState[] stateArray; - public AppStateManager(Application app){ + public AppStateManager(Application app) { this.app = app; } /** - * Returns the Application to which this AppStateManager belongs. + * Returns the Application to which this AppStateManager belongs. + * + * @return the pre-existing instance */ public Application getApplication() { return app; } - protected AppState[] getInitializing() { - synchronized (states){ + protected AppState[] getInitializing() { + synchronized (states) { return initializing.getArray(); } - } + } - protected AppState[] getTerminating() { - synchronized (states){ + protected AppState[] getTerminating() { + synchronized (states) { return terminating.getArray(); } - } + } - protected AppState[] getStates(){ - synchronized (states){ + protected AppState[] getStates() { + synchronized (states) { return states.getArray(); } } /** * Attach a state to the AppStateManager, the same state cannot be attached - * twice. Throws an IllegalArgumentException if the state has an ID and that - * ID has already been associated with another AppState. + * twice. Throws an IllegalArgumentException if the state has an ID and that + * ID has already been associated with another AppState. * * @param state The state to attach * @return True if the state was successfully attached, false if the state * was already attached. */ - public boolean attach(AppState state){ - synchronized (states){ - if( state.getId() != null && stateIndex.putIfAbsent(state.getId(), state) != null ) { - throw new IllegalArgumentException("ID:" + state.getId() - + " is already being used by another state:" + public boolean attach(AppState state) { + synchronized (states) { + if (state.getId() != null && stateIndex.putIfAbsent(state.getId(), state) != null) { + throw new IllegalArgumentException("ID:" + state.getId() + + " is already being used by another state:" + stateIndex.get(state.getId())); } - if (!states.contains(state) && !initializing.contains(state)){ + if (!states.contains(state) && !initializing.contains(state)) { state.stateAttached(this); initializing.add(state); return true; @@ -161,7 +160,7 @@ public boolean attach(AppState state){ * * @param states The states to attach */ - public void attachAll(AppState... states){ + public void attachAll(AppState... states) { attachAll(Arrays.asList(states)); } @@ -172,39 +171,39 @@ public void attachAll(AppState... states){ * * @param states The states to attach */ - public void attachAll(Iterable states){ - synchronized (this.states){ - for( AppState state : states ) { + public void attachAll(Iterable states) { + synchronized (this.states) { + for (AppState state : states) { attach(state); } } } /** - * Detaches the state from the AppStateManager. + * Detaches the state from the AppStateManager. * * @param state The state to detach * @return True if the state was detached successfully, false - * if the state was not attached in the first place. + * if the state was not attached in the first place. */ - public boolean detach(AppState state){ - synchronized (states){ - + public boolean detach(AppState state) { + synchronized (states) { + // Remove it from the index if it exists. // Note: we remove it directly from the values() in case // the state has changed its ID since registered. stateIndex.values().remove(state); - - if (states.contains(state)){ + + if (states.contains(state)) { state.stateDetached(this); states.remove(state); terminating.add(state); return true; - } else if(initializing.contains(state)){ + } else if (initializing.contains(state)) { state.stateDetached(this); initializing.remove(state); return true; - }else{ + } else { return false; } } @@ -215,140 +214,156 @@ public boolean detach(AppState state){ * * @param state The state to check * @return True if the state is currently attached to this AppStateManager. - * + * * @see AppStateManager#attach(com.jme3.app.state.AppState) */ - public boolean hasState(AppState state){ - synchronized (states){ + public boolean hasState(AppState state) { + synchronized (states) { return states.contains(state) || initializing.contains(state); } } /** * Returns the first state that is an instance of subclass of the specified class. - * @param - * @param stateClass + * + * @param the desired type of AppState + * @param stateClass the desired type of AppState * @return First attached state that is an instance of stateClass */ - public T getState(Class stateClass){ + public T getState(Class stateClass) { return getState(stateClass, false); } - + /** * Returns the first state that is an instance of subclass of the specified class. - * @param - * @param stateClass - * @param failOnMiss - * @return First attached state that is an instance of stateClass. If failOnMiss is true - * then an IllegalArgumentException is thrown if the state is not attached. + * + * @param the desired type of AppState + * @param stateClass the desired type of AppState + * @param failOnMiss true to throw an exception, false to return null + * @return First attached state that is an instance of stateClass. If failOnMiss is true + * then an IllegalArgumentException is thrown if the state is not attached. */ - public T getState(Class stateClass, boolean failOnMiss){ - synchronized (states){ + @SuppressWarnings("unchecked") + public T getState(Class stateClass, boolean failOnMiss) { + synchronized (states) { AppState[] array = getStates(); for (AppState state : array) { - if (stateClass.isAssignableFrom(state.getClass())){ + if (stateClass.isAssignableFrom(state.getClass())) { return (T) state; } } - - // This may be more trouble than it's worth but I think + + // This may be more trouble than it's worth, but I think // it's necessary for proper decoupling of states and provides // similar behavior to before where a state could be looked // up even if it wasn't initialized. -pspeed array = getInitializing(); for (AppState state : array) { - if (stateClass.isAssignableFrom(state.getClass())){ + if (stateClass.isAssignableFrom(state.getClass())) { return (T) state; } } } - - if(failOnMiss) { + + if (failOnMiss) { throw new IllegalArgumentException("State not found for:" + stateClass); - } + } return null; } /** - * Returns the state associated with the specified ID at the time it was - * attached or null if not state was attached with that ID. + * Returns the state associated with the specified ID at the time it was + * attached or null if not state was attached with that ID. + * + * @param the desired type of AppState + * @param id the AppState ID + * @param stateClass the desired type of AppState + * @return the pre-existing instance, or null if not found */ - public T getState( String id, Class stateClass ) { + public T getState(String id, Class stateClass) { return stateClass.cast(stateIndex.get(id)); } - + /** - * Returns true if there is currently a state associated with the specified - * ID. + * Returns true if there is currently a state associated with the specified + * ID. + * + * @param id the AppState ID + * @return true if found, otherwise false */ - public boolean hasState( String id ) { + public boolean hasState(String id) { return stateIndex.containsKey(id); } - + /** * Returns the state associated with the specified ID at the time it - * was attached or throws an IllegalArgumentException if the ID was + * was attached or throws an IllegalArgumentException if the ID was * not found. - */ - public T stateForId( String id, Class stateClass ) { + * + * @param the desired type of AppState + * @param id the AppState ID + * @param stateClass the desired type of AppState + * @return the pre-existing instance (not null) + */ + public T stateForId(String id, Class stateClass) { T result = getState(id, stateClass); - if( result == null ) { + if (result == null) { throw new IllegalArgumentException("State not found for:" + id); } return stateClass.cast(result); - } + } - protected void initializePending(){ + protected void initializePending() { AppState[] array = getInitializing(); if (array.length == 0) return; - - synchronized( states ) { + + synchronized (states) { // Move the states that will be initialized // into the active array. In all but one case the // order doesn't matter but if we do this here then // a state can detach itself in initialize(). If we // did it after then it couldn't. - List transfer = Arrays.asList(array); + List transfer = Arrays.asList(array); states.addAll(transfer); initializing.removeAll(transfer); - } + } for (AppState state : array) { state.initialize(this, app); } } - - protected void terminatePending(){ + + protected void terminatePending() { AppState[] array = getTerminating(); if (array.length == 0) return; - + for (AppState state : array) { state.cleanup(); - } - synchronized( states ) { + } + synchronized (states) { // Remove just the states that were terminated... // which might now be a subset of the total terminating // list. - terminating.removeAll(Arrays.asList(array)); + terminating.removeAll(Arrays.asList(array)); } - } + } /** * Calls update for attached states, do not call directly. * @param tpf Time per frame. */ - public void update(float tpf){ - + public void update(float tpf) { + // Cleanup any states pending terminatePending(); // Initialize any states pending initializePending(); - // Update enabled states + // Update enabled states AppState[] array = getStates(); - for (AppState state : array){ + for (AppState state : array) { if (state.isEnabled()) { if (app.getAppProfiler() != null) { app.getAppProfiler().appSubStep(state.getClass().getSimpleName()); @@ -362,10 +377,13 @@ public void update(float tpf){ * Calls render for all attached and initialized states, do not call directly. * @param rm The RenderManager */ - public void render(RenderManager rm){ + public void render(RenderManager rm) { AppState[] array = getStates(); - for (AppState state : array){ + for (AppState state : array) { if (state.isEnabled()) { + if (app.getAppProfiler() != null) { + app.getAppProfiler().appSubStep(state.getClass().getSimpleName()); + } state.render(rm); } } @@ -374,10 +392,13 @@ public void render(RenderManager rm){ /** * Calls render for all attached and initialized states, do not call directly. */ - public void postRender(){ + public void postRender() { AppState[] array = getStates(); - for (AppState state : array){ + for (AppState state : array) { if (state.isEnabled()) { + if (app.getAppProfiler() != null) { + app.getAppProfiler().appSubStep(state.getClass().getSimpleName()); + } state.postRender(); } } @@ -386,10 +407,10 @@ public void postRender(){ /** * Calls cleanup on attached states, do not call directly. */ - public void cleanup(){ + public void cleanup() { AppState[] array = getStates(); - for (AppState state : array){ + for (AppState state : array) { state.cleanup(); } - } + } } diff --git a/jme3-core/src/main/java/com/jme3/app/state/BaseAppState.java b/jme3-core/src/main/java/com/jme3/app/state/BaseAppState.java index a745215613..e172d4cd16 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/BaseAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/BaseAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 jMonkeyEngine + * Copyright (c) 2014-2021 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.state; import com.jme3.app.Application; @@ -37,18 +37,16 @@ import java.util.logging.Level; import java.util.logging.Logger; - - /** * A base app state implementation the provides more built-in * management convenience than AbstractAppState, including methods * for enable/disable/initialize state management. * The abstract onEnable() and onDisable() methods are called - * appropriately during initialize(), terminate(), or setEnabled() + * appropriately during initialize(), cleanup(), or setEnabled() * depending on the mutual state of "initialized" and "enabled". - * - *

    initialize() and terminate() can be used by subclasses to - * manage resources that should exist the entire time that the + * + *

    initialize() and cleanup() can be used by subclasses to + * manage resources that should exist the entire time that the * app state is attached. This is useful for resources that might * be expensive to create or load.

    * @@ -61,7 +59,7 @@ * will always be called after initialize() if the state is enable(). * onEnable()/onDisable() are also called appropriate when setEnabled() * is called that changes the enabled state AND if the state is attached. - * In other words, onEnable()/onDisable() are only ever called on an already + * In other words, onEnable()/onDisable() are only ever called on an already * attached state.

    * *

    It is technically safe to do all initialization and cleanup in @@ -72,8 +70,7 @@ * @author Paul Speed */ public abstract class BaseAppState implements AppState { - - static final Logger log = Logger.getLogger(BaseAppState.class.getName()); + private static final Logger log = Logger.getLogger(BaseAppState.class.getName()); private Application app; private boolean initialized; @@ -82,8 +79,8 @@ public abstract class BaseAppState implements AppState { protected BaseAppState() { } - - protected BaseAppState( String id ) { + + protected BaseAppState(String id) { this.id = id; } @@ -92,8 +89,8 @@ protected BaseAppState( String id ) { * attached and before onEnable() is called. * @param app the application */ - protected abstract void initialize( Application app ); - + protected abstract void initialize(Application app); + /** * Called after the app state is detached or during * application shutdown if the state is still attached. @@ -101,15 +98,15 @@ protected BaseAppState( String id ) { * the state is enabled at the time of cleanup. * @param app the application */ - protected abstract void cleanup( Application app ); - + protected abstract void cleanup(Application app); + /** * Called when the state is fully enabled, ie: is attached * and isEnabled() is true or when the setEnabled() status * changes after the state is attached. */ protected abstract void onEnable(); - + /** * Called when the state was previously enabled but is * now disabled either because setEnabled(false) was called @@ -118,19 +115,19 @@ protected BaseAppState( String id ) { protected abstract void onDisable(); /** - * Do not call directly: Called by the state manager to initialize this + * Do not call directly: Called by the state manager to initialize this * state post-attachment. * This implementation calls initialize(app) and then onEnable() if the * state is enabled. */ @Override - public final void initialize( AppStateManager stateManager, Application app ) { + public final void initialize(AppStateManager stateManager, Application app) { log.log(Level.FINEST, "initialize():{0}", this); this.app = app; initialized = true; initialize(app); - if( isEnabled() ) { + if (isEnabled()) { log.log(Level.FINEST, "onEnable():{0}", this); onEnable(); } @@ -145,8 +142,10 @@ public final boolean isInitialized() { * Sets the unique ID of this app state. Note: that setting * this while an app state is attached to the state manager will * have no effect on ID-based lookups. + * + * @param id the desired ID */ - protected void setId( String id ) { + protected void setId(String id) { this.id = id; } @@ -163,23 +162,33 @@ public final AppStateManager getStateManager() { return app.getStateManager(); } - public final T getState( Class type ) { - return getState( type, false ); + public final T getState(Class type) { + return getState(type, false); + } + + public final T getState(Class type, boolean failOnMiss) { + return getStateManager().getState(type, failOnMiss); } - - public final T getState( Class type, boolean failOnMiss ) { - return getStateManager().getState( type, failOnMiss ); + + public final T getState(String id, Class type) { + return getState(id, type, false); + } + + public final T getState(String id, Class type, boolean failOnMiss) { + if (failOnMiss) { + return getStateManager().stateForId(id, type); + } + return getStateManager().getState(id, type); } @Override - public final void setEnabled( boolean enabled ) - { - if( this.enabled == enabled ) + public final void setEnabled(boolean enabled) { + if (this.enabled == enabled) return; this.enabled = enabled; - if( !isInitialized() ) + if (!isInitialized()) return; - if( enabled ) { + if (enabled) { log.log(Level.FINEST, "onEnable():{0}", this); onEnable(); } else { @@ -194,19 +203,19 @@ public final boolean isEnabled() { } @Override - public void stateAttached( AppStateManager stateManager ) { + public void stateAttached(AppStateManager stateManager) { } @Override - public void stateDetached( AppStateManager stateManager ) { + public void stateDetached(AppStateManager stateManager) { } @Override - public void update( float tpf ) { + public void update(float tpf) { } @Override - public void render( RenderManager rm ) { + public void render(RenderManager rm) { } @Override @@ -214,7 +223,7 @@ public void postRender() { } /** - * Do not call directly: Called by the state manager to terminate this + * Do not call directly: Called by the state manager to terminate this * state post-detachment or during state manager termination. * This implementation calls onDisable() if the state is enabled and * then cleanup(app). @@ -223,7 +232,7 @@ public void postRender() { public final void cleanup() { log.log(Level.FINEST, "cleanup():{0}", this); - if( isEnabled() ) { + if (isEnabled()) { log.log(Level.FINEST, "onDisable():{0}", this); onDisable(); } diff --git a/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java b/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java new file mode 100644 index 0000000000..c8d2d9acb5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/app/state/CompositeAppState.java @@ -0,0 +1,236 @@ +/* + * + * Copyright (c) 2014-2024 jMonkeyEngine + * Copied with Paul Speed's permission from: https://github.com/Simsilica/SiO2 + * All rights reserved. + * + * 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. + */ + +package com.jme3.app.state; + +import com.jme3.app.Application; +import com.jme3.util.SafeArrayList; + +/** + * An AppState that manages a set of child app states, making sure + * they are attached/detached and optional enabled/disabled with the + * parent state. + * + * @author Paul Speed + */ +public class CompositeAppState extends BaseAppState { + + private final SafeArrayList states = new SafeArrayList<>(AppStateEntry.class); + private boolean childrenEnabled; + + /** + * Since we manage attachmend/detachment possibly before + * initialization, we need to keep track of the stateManager we + * were given in stateAttached() in case we have to attach another + * child prior to initialization (but after we're attached). + * It's possible that we should actually be waiting for initialize + * to add these but I feel like there was some reason I did it this + * way originally. Past-me did not leave any clues. + */ + private AppStateManager stateManager; + private boolean attached; + + public CompositeAppState(AppState... states) { + for (AppState a : states) { + this.states.add(new AppStateEntry(a, false)); + } + } + + private int indexOf(AppState state) { + for (int i = 0; i < states.size(); i++) { + AppStateEntry e = states.get(i); + if (e.state == state) { + return i; + } + } + return -1; + } + + private AppStateEntry entry(AppState state) { + for (AppStateEntry e : states.getArray()) { + if (e.state == state) { + return e; + } + } + return null; + } + + protected T addChild(T state) { + return addChild(state, false); + } + + protected T addChild(T state, boolean overrideEnable) { + if (indexOf(state) >= 0) { + return state; + } + states.add(new AppStateEntry(state, overrideEnable)); + if (attached) { + stateManager.attach(state); + } + return state; + } + + protected void removeChild( AppState state ) { + int index = indexOf(state); + if( index < 0 ) { + return; + } + states.remove(index); + if( attached ) { + stateManager.detach(state); + } + } + + protected T getChild( Class stateType ) { + for( AppStateEntry e : states.getArray() ) { + if( stateType.isInstance(e.state) ) { + return stateType.cast(e.state); + } + } + return null; + } + + protected void clearChildren() { + for( AppStateEntry e : states.getArray() ) { + removeChild(e.state); + } + } + + @Override + public void stateAttached(AppStateManager stateManager) { + this.stateManager = stateManager; + for (AppStateEntry e : states.getArray()) { + stateManager.attach(e.state); + } + this.attached = true; + } + + @Override + public void stateDetached(AppStateManager stateManager) { + // Reverse order + for (int i = states.size() - 1; i >= 0; i--) { + stateManager.detach(states.get(i).state); + } + this.attached = false; + this.stateManager = null; + } + + protected void setChildrenEnabled(boolean b) { + if(childrenEnabled == b) { + return; + } + childrenEnabled = b; + for (AppStateEntry e : states.getArray()) { + e.setEnabled(b); + } + } + + /** + * Overrides the automatic synching of a child's enabled state. + * When override is true, a child will remember its old state when + * the parent's enabled state is false so that when the parent is + * re-enabled the child can resume its previous enabled state. This + * is useful for the cases where a child may want to be disabled + * independent of the parent... and then not automatically become + * enabled just because the parent does. + * Currently, the parent's disabled state always disables the children, + * too. Override is about remembering the child's state before that + * happened and restoring it when the 'family' is enabled again as a whole. + */ + public void setOverrideEnabled(AppState state, boolean override) { + AppStateEntry e = entry(state); + if (e == null) { + throw new IllegalArgumentException("State not managed:" + state); + } + if (override) { + e.override = true; + } else { + e.override = false; + e.state.setEnabled(isEnabled()); + } + } + + @Override + protected void initialize(Application app) { + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + setChildrenEnabled(true); + } + + @Override + protected void onDisable() { + setChildrenEnabled(false); + } + + private class AppStateEntry { + AppState state; + boolean enabled; + boolean override; + + public AppStateEntry(AppState state, boolean overrideEnable) { + this.state = state; + this.override = overrideEnable; + this.enabled = state.isEnabled(); + } + + public void setEnabled(boolean b) { + + if (override) { + if (b) { + // Set it to whatever its enabled state + // was before going disabled last time. + state.setEnabled(enabled); + } else { + // We are going to set enabled to false + // but keep track of what it was before we did + // that + this.enabled = state.isEnabled(); + state.setEnabled(false); + } + } else { + // Just synch it always + state.setEnabled(b); + } + } + } +} + diff --git a/jme3-core/src/main/java/com/jme3/app/state/ConstantVerifierState.java b/jme3-core/src/main/java/com/jme3/app/state/ConstantVerifierState.java index 1b4cd2e7a3..8d42fad624 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/ConstantVerifierState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/ConstantVerifierState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 jMonkeyEngine + * Copyright (c) 2014-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,49 +29,67 @@ * 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.state; -import java.util.Arrays; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.jme3.app.Application; -import com.jme3.math.*; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; import com.jme3.util.SafeArrayList; +import java.util.Arrays; +import java.util.logging.Logger; + +import static java.lang.Float.NEGATIVE_INFINITY; import static java.lang.Float.NaN; import static java.lang.Float.POSITIVE_INFINITY; -import static java.lang.Float.NEGATIVE_INFINITY; /** - * Checks the various JME 'constants' for drift using either asserts - * or straight checks. The list of constants can also be configured - * but defaults to the standard JME Vector3f, Quaternion, etc. constants. + * An AppState that periodically checks the values of various JME math constants + * (e.g., `Vector3f.ZERO`, `Quaternion.IDENTITY`) against their known good values. + * This is useful for detecting accidental modifications or "drift" of these + * supposedly immutable constants during application runtime. + *

    + * The state can be configured to report discrepancies using asserts, + * throwing runtime exceptions, or logging severe messages. + * The set of constants to check is configurable. * - * @author Paul Speed + * @author Paul Speed */ public class ConstantVerifierState extends BaseAppState { - static final Logger log = Logger.getLogger(BaseAppState.class.getName()); + private static final Logger log = Logger.getLogger(ConstantVerifierState.class.getName()); // Note: I've used actual constructed objects for the good values // instead of clone just to better catch cases where the values // might have been corrupted even before the app state was touched. -pspeed - public static final Checker[] DEFAULT_CHECKS = new Checker[] { + private static final Checker[] DEFAULT_CHECKS = new Checker[] { new Checker(Vector3f.ZERO, new Vector3f(0, 0, 0)), new Checker(Vector3f.NAN, new Vector3f(NaN, NaN, NaN)), new Checker(Vector3f.UNIT_X, new Vector3f(1, 0, 0)), new Checker(Vector3f.UNIT_Y, new Vector3f(0, 1, 0)), new Checker(Vector3f.UNIT_Z, new Vector3f(0, 0, 1)), new Checker(Vector3f.UNIT_XYZ, new Vector3f(1, 1, 1)), - new Checker(Vector3f.POSITIVE_INFINITY, new Vector3f(POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY)), - new Checker(Vector3f.NEGATIVE_INFINITY, new Vector3f(NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY)), + new Checker(Vector3f.POSITIVE_INFINITY, + new Vector3f(POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY)), + new Checker(Vector3f.NEGATIVE_INFINITY, + new Vector3f(NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY)), new Checker(Quaternion.IDENTITY, new Quaternion()), - new Checker(Quaternion.DIRECTION_Z, new Quaternion().fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z)), + new Checker(Quaternion.DIRECTION_Z, + new Quaternion().fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z)), new Checker(Quaternion.ZERO, new Quaternion(0, 0, 0, 0)), new Checker(Vector2f.ZERO, new Vector2f(0f, 0f)), + new Checker(Vector2f.NAN, new Vector2f(NaN, NaN)), + new Checker(Vector2f.UNIT_X, new Vector2f(1, 0)), + new Checker(Vector2f.UNIT_Y, new Vector2f(0, 1)), new Checker(Vector2f.UNIT_XY, new Vector2f(1f, 1f)), + new Checker(Vector2f.POSITIVE_INFINITY, + new Vector2f(POSITIVE_INFINITY, POSITIVE_INFINITY)), + new Checker(Vector2f.NEGATIVE_INFINITY, + new Vector2f(NEGATIVE_INFINITY, NEGATIVE_INFINITY)), new Checker(Vector4f.ZERO, new Vector4f(0, 0, 0, 0)), new Checker(Vector4f.NAN, new Vector4f(NaN, NaN, NaN, NaN)), new Checker(Vector4f.UNIT_X, new Vector4f(1, 0, 0, 0)), @@ -79,129 +97,167 @@ public class ConstantVerifierState extends BaseAppState { new Checker(Vector4f.UNIT_Z, new Vector4f(0, 0, 1, 0)), new Checker(Vector4f.UNIT_W, new Vector4f(0, 0, 0, 1)), new Checker(Vector4f.UNIT_XYZW, new Vector4f(1, 1, 1, 1)), - new Checker(Vector4f.POSITIVE_INFINITY, new Vector4f(POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY)), - new Checker(Vector4f.NEGATIVE_INFINITY, new Vector4f(NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY)), + new Checker(Vector4f.POSITIVE_INFINITY, + new Vector4f(POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY, POSITIVE_INFINITY)), + new Checker(Vector4f.NEGATIVE_INFINITY, + new Vector4f(NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY, NEGATIVE_INFINITY)), new Checker(Matrix3f.ZERO, new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0)), new Checker(Matrix3f.IDENTITY, new Matrix3f()), new Checker(Matrix4f.ZERO, new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)), new Checker(Matrix4f.IDENTITY, new Matrix4f()) }; - public enum ErrorType { Assert, Exception, Log }; + /** + * Defines how constant value discrepancies should be reported. + */ + public enum ErrorType { + /** Causes an `assert` failure if the constant has changed. Requires assertions to be enabled. */ + Assert, + /** Throws a `RuntimeException` if the constant has changed. */ + Exception, + /** Logs a severe message if the constant has changed. */ + Log + } - private SafeArrayList checkers = new SafeArrayList<>(Checker.class); + private final SafeArrayList checkers = new SafeArrayList<>(Checker.class); private ErrorType errorType; /** - * Creates a verifier app state that will check all of the default - * constant checks using asserts. + * Creates a verifier app state that will check all of the default + * JME math constants using `ErrorType.Assert`. */ public ConstantVerifierState() { - this(ErrorType.Assert); + this(ErrorType.Assert); } - + /** - * Creates a verifier app state that will check all of the default - * constant checks using the specified error reporting mechanism. + * Creates a verifier app state that will check all of the default + * JME math constants using the specified error reporting mechanism. + * + * @param errorType The mechanism to use when a constant's value drifts. */ - public ConstantVerifierState( ErrorType errorType ) { + public ConstantVerifierState(ErrorType errorType) { this(errorType, DEFAULT_CHECKS); } /** * Creates a verifier app state that will check all of the specified * checks and report errors using the specified error type. + * + * @param errorType the mechanism to use + * @param checkers which checks to perform */ - public ConstantVerifierState( ErrorType errorType, Checker... checkers ) { + private ConstantVerifierState(ErrorType errorType, Checker... checkers) { this.errorType = errorType; this.checkers.addAll(Arrays.asList(checkers)); } - - public void addChecker( Object constant, Object goodValue ) { + + /** + * Adds a new constant and its expected good value to the list of items to be checked. + * The `constant` and `goodValue` should be instances of the same class. + * + * @param constant The JME constant object to monitor for drift (e.g., `Vector3f.ZERO`). + * @param goodValue An independent instance representing the expected correct value of the constant. + * This instance should match the initial value of `constant`. + */ + public void addChecker(Object constant, Object goodValue) { checkers.add(new Checker(constant, goodValue)); } - - public void setErrorType( ErrorType errorType ) { + + /** + * Sets the error reporting mechanism to be used when a constant's value drifts. + * + * @param errorType The desired error reporting type. + */ + public void setErrorType(ErrorType errorType) { this.errorType = errorType; } - + + /** + * Returns the currently configured error reporting mechanism. + * + * @return The current `ErrorType`. + */ public ErrorType getErrorType() { return errorType; } - - protected SafeArrayList getCheckers() { - return checkers; - } - + @Override - protected void initialize( Application app ) { + protected void initialize(Application app) { } - + @Override - protected void cleanup( Application app ) { + protected void cleanup(Application app) { } - + @Override protected void onEnable() { } - + @Override protected void onDisable() { } - + @Override public void postRender() { // Check as late in the frame as possible. Subclasses can check earlier // if they like. checkValues(); } - + + /** + * Iterates through all registered checkers and verifies the current values + * of the constants against their known good values. + * Reports any discrepancies based on the configured `ErrorType`. + */ protected void checkValues() { - for( Checker checker : checkers.getArray() ) { - switch( errorType ) { - default: + for (Checker checker : checkers.getArray()) { + switch (errorType) { + default: // Fall through to Assert if somehow null case Assert: assert checker.isValid() : checker.toString(); break; case Exception: - if( !checker.isValid() ) { - throw new RuntimeException("Constant has changed, " + checker.toString()); + if (!checker.isValid()) { + throw new RuntimeException("JME Constant has changed, " + checker.toString()); } break; case Log: - if( !checker.isValid() ) { - log.severe("Constant has changed, " + checker.toString()); + if (!checker.isValid()) { + log.severe("JME Constant has changed, " + checker.toString()); } break; } } } - + /** - * Checks the specified 'constant' value against it's known good + * Checks the specified 'constant' value against its known good * value. These should obviously be different instances for this to * mean anything. - */ + */ private static class Checker { - private Object constant; - private Object goodValue; - - public Checker( Object constant, Object goodValue ) { - if( constant == null ) { + + private final Object constant; + private final Object goodValue; + + public Checker(Object constant, Object goodValue) { + if (constant == null) { throw new IllegalArgumentException("Constant cannot be null"); } - if( !constant.equals(goodValue) ) { - throw new IllegalArgumentException("Constant value:" + constant + " does not match value:" + goodValue); + if (!constant.equals(goodValue)) { + throw new IllegalArgumentException( + "Constant value: " + constant + " does not match value: " + goodValue); } this.constant = constant; this.goodValue = goodValue; } - + public boolean isValid() { return constant.equals(goodValue); } - - @Override + + @Override public String toString() { return "Constant:" + constant + ", correct value:" + goodValue + ", type:" + goodValue.getClass(); } diff --git a/jme3-core/src/main/java/com/jme3/app/state/RootNodeAppState.java b/jme3-core/src/main/java/com/jme3/app/state/RootNodeAppState.java index f7406ba55a..482dc098a6 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/RootNodeAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/RootNodeAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -88,10 +88,12 @@ public RootNodeAppState(ViewPort viewPort, Node rootNode) { /** * Creates the AppState with the given unique ID, ViewPort, and root Node, attaches * the root Node to the ViewPort and updates it. + * + * @param id the desired AppState ID * @param viewPort An existing ViewPort * @param rootNode An existing root Node */ - public RootNodeAppState( String id, ViewPort viewPort, Node rootNode ) { + public RootNodeAppState(String id, ViewPort viewPort, Node rootNode) { super(id); this.viewPort = viewPort; this.rootNode = rootNode; @@ -113,12 +115,12 @@ public void initialize(AppStateManager stateManager, Application app) { public void update(float tpf) { super.update(tpf); rootNode.updateLogicalState(tpf); - + // FIXME: I'm 99% sure that updateGeometricState() should be // called in render() so that it is done as late as possible. - // In complicated app state setups, cross-state chatter could - // cause nodes (or their children) to be updated after this - // app state's update has been called. -pspeed:2019-09-15 + // In complicated app state setups, cross-state chatter could + // cause nodes (or their children) to be updated after this + // app state's update has been called. -pspeed:2019-09-15 rootNode.updateGeometricState(); } diff --git a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java index 4a2b1acc89..462971a91c 100644 --- a/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java +++ b/jme3-core/src/main/java/com/jme3/app/state/ScreenshotAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,7 +67,14 @@ public class ScreenshotAppState extends AbstractAppState implements ActionListen private String shotName; private long shotIndex = 0; private int width, height; - private AppProfiler prof; + /** + * InputManager to which the ActionListener and the mapping are added + */ + private InputManager inputManager; + /** + * ViewPort to which the SceneProcessor is attached + */ + private ViewPort last; /** * Using this constructor, the screenshot files will be written sequentially to the system @@ -108,8 +115,8 @@ public ScreenshotAppState(String filePath, String fileName) { * Use an empty string to use the application folder. Use NULL to use the system * default storage folder. * @param filePath The screenshot file path to use. Include the separator at the end of the path. - * @param shotIndex The base index for screen shots. The first screen shot will have - * shotIndex + 1 appended, the next shotIndex + 2, and so on. + * @param shotIndex The base index for screenshots. The first screenshot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. */ public ScreenshotAppState(String filePath, long shotIndex) { this.filePath = filePath; @@ -124,15 +131,15 @@ public ScreenshotAppState(String filePath, long shotIndex) { * default storage folder. * @param filePath The screenshot file path to use. Include the separator at the end of the path. * @param fileName The name of the file to save the screenshot as. - * @param shotIndex The base index for screen shots. The first screen shot will have - * shotIndex + 1 appended, the next shotIndex + 2, and so on. + * @param shotIndex The base index for screenshots. The first screenshot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. */ public ScreenshotAppState(String filePath, String fileName, long shotIndex) { this.filePath = filePath; this.shotName = fileName; this.shotIndex = shotIndex; } - + /** * Set the file path to store the screenshot. * Include the separator at the end of the path. @@ -153,14 +160,16 @@ public void setFileName(String fileName) { } /** - * Sets the base index that will used for subsequent screen shots. + * Sets the base index that will used for subsequent screenshots. + * + * @param index the desired base index */ public void setShotIndex(long index) { this.shotIndex = index; } /** - * Sets if the filename should be appended with a number representing the + * Sets if the filename should be appended with a number representing the * current sequence. * @param numberedWanted If numbering is wanted. */ @@ -170,13 +179,13 @@ public void setIsNumbered(boolean numberedWanted) { @Override public void initialize(AppStateManager stateManager, Application app) { - if (!super.isInitialized()){ - InputManager inputManager = app.getInputManager(); + if (!super.isInitialized()) { + inputManager = app.getInputManager(); inputManager.addMapping("ScreenShot", new KeyTrigger(KeyInput.KEY_SYSRQ)); inputManager.addListener(this, "ScreenShot"); List vps = app.getRenderManager().getPostViews(); - ViewPort last = vps.get(vps.size()-1); + last = vps.get(vps.size() - 1); last.addProcessor(this); if (shotName == null) { @@ -187,8 +196,45 @@ public void initialize(AppStateManager stateManager, Application app) { super.initialize(stateManager, app); } + /** + * Clean up this AppState during the first update after it gets detached. + *

    + * Because each ScreenshotAppState is also a SceneProcessor (in addition to + * being an AppState) this method is also invoked when the SceneProcessor + * get removed from its ViewPort, leading to an indirect recursion: + *

    1. AppStateManager invokes ScreenshotAppState.cleanup()
    2. + *
    3. cleanup() invokes ViewPort.removeProcessor()
    4. + *
    5. removeProcessor() invokes ScreenshotAppState.cleanup()
    6. + *
    7. ... and so on.
    8. + *
    + *

    + * In order to break this recursion, this method only removes the + * SceneProcessor if it has not previously been removed. + *

    + * A better design would have the AppState and SceneProcessor be 2 distinct + * objects, but doing so now might break applications that rely on them + * being a single object. + */ + @Override + public void cleanup() { + if (inputManager != null) { + inputManager.deleteMapping("ScreenShot"); + inputManager.removeListener(this); + inputManager = null; + } + + ViewPort viewPort = last; + if (viewPort != null) { + last = null; + viewPort.removeProcessor(this); // XXX indirect recursion! + } + + super.cleanup(); + } + + @Override public void onAction(String name, boolean value, float tpf) { - if (value){ + if (value) { capture = true; } } @@ -197,6 +243,7 @@ public void takeScreenshot() { capture = true; } + @Override public void initialize(RenderManager rm, ViewPort vp) { renderer = rm.getRenderer(); this.rm = rm; @@ -208,20 +255,26 @@ public boolean isInitialized() { return super.isInitialized() && renderer != null; } + @Override public void reshape(ViewPort vp, int w, int h) { outBuf = BufferUtils.createByteBuffer(w * h * 4); width = w; height = h; } + @Override public void preFrame(float tpf) { + // do nothing } + @Override public void postQueue(RenderQueue rq) { + // do nothing } + @Override public void postFrame(FrameBuffer out) { - if (capture){ + if (capture) { capture = false; Camera curCamera = rm.getCurrentCamera(); @@ -248,30 +301,36 @@ public void postFrame(FrameBuffer out) { } else { file = new File(filePath + filename + ".png").getAbsoluteFile(); } - logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath()); + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath()); + } try { writeImageFile(file); } catch (IOException ex) { logger.log(Level.SEVERE, "Error while saving screenshot", ex); - } + } } } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } /** - * Called by postFrame() once the screen has been captured to outBuf. + * Called by postFrame() once the screen has been captured to outBuf. + * + * @param file the output file + * @throws IOException if an I/O error occurs */ - protected void writeImageFile( File file ) throws IOException { + protected void writeImageFile(File file) throws IOException { OutputStream outStream = new FileOutputStream(file); try { JmeSystem.writeImageFile(outStream, "png", outBuf, width, height); } finally { outStream.close(); } - } + } } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java index bef9cfecef..3307cca4cb 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,14 +39,16 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.util.res.Resources; + /** * AssetConfig loads a config file to configure the asset manager. - *

    + * * The config file is specified with the following format: * - * "INCLUDE" - * "LOADER" : ( ",")* - * "LOCATOR" + * "INCLUDE" path + * "LOADER" class : (extension ",")* extension + * "LOCATOR" path class * * * @author Kirill Vainer @@ -54,9 +56,9 @@ public final class AssetConfig { private static final Logger logger = Logger.getLogger(AssetConfig.class.getName()); - + private AssetConfig() { } - + private static Class acquireClass(String name) { try { return Class.forName(name); @@ -64,23 +66,24 @@ private static Class acquireClass(String name) { return null; } } - - public static void loadText(AssetManager assetManager, URL configUrl) throws IOException{ + + @SuppressWarnings("unchecked") + public static void loadText(AssetManager assetManager, URL configUrl) throws IOException { InputStream in = configUrl.openStream(); try { Scanner scan = new Scanner(in, "UTF-8"); scan.useLocale(Locale.US); // Fix commas / periods ?? - while (scan.hasNext()){ + while (scan.hasNext()) { String cmd = scan.next(); - if (cmd.equals("LOADER")){ + if (cmd.equals("LOADER")) { String loaderClass = scan.next(); String colon = scan.next(); - if (!colon.equals(":")){ - throw new IOException("Expected ':', got '"+colon+"'"); + if (!colon.equals(":")) { + throw new IOException("Expected ':', got '" + colon + "'"); } String extensionsList = scan.nextLine(); String[] extensions = extensionsList.split(","); - for (int i = 0; i < extensions.length; i++){ + for (int i = 0; i < extensions.length; i++) { extensions[i] = extensions[i].trim(); } Class clazz = acquireClass(loaderClass); @@ -100,7 +103,7 @@ public static void loadText(AssetManager assetManager, URL configUrl) throws IOE } } else if (cmd.equals("INCLUDE")) { String includedCfg = scan.nextLine().trim(); - URL includedCfgUrl = Thread.currentThread().getContextClassLoader().getResource(includedCfg); + URL includedCfgUrl = Resources.getResource(includedCfg); if (includedCfgUrl != null) { loadText(assetManager, includedCfgUrl); } else { @@ -114,7 +117,8 @@ public static void loadText(AssetManager assetManager, URL configUrl) throws IOE } } } finally { - if (in != null) in.close(); + if (in != null) + in.close(); } } } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java index c581335349..5e391ba5ff 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetEventListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,12 +51,12 @@ public interface AssetEventListener { public void assetLoaded(AssetKey key); /** - * Called when an asset has been requested (e.g any of the load*** methods + * Called when an asset has been requested (e.g. any of the load*** methods * in AssetManager are called). * In contrast to the assetLoaded() method, this one will be called even * if the asset has failed to load, or if it was retrieved from the cache. * - * @param key + * @param key the key of the requested asset */ public void assetRequested(AssetKey key); diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java b/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java index 34cd8c5abc..6148e72dae 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetInfo.java @@ -58,7 +58,7 @@ public AssetManager getManager() { } @Override - public String toString(){ + public String toString() { return getClass().getName() + "[" + "key=" + key + "]"; } @@ -68,9 +68,8 @@ public String toString(){ *

    * Each invocation of this method should return a new stream to the * asset data, starting at the beginning of the file. - * + * * @return The asset data. */ public abstract InputStream openStream(); - } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java index 4066d7e14f..ec158690f1 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ /** * AssetKey is a key that is used to - * look up a resource from a cache. + * look up a resource from a cache. * This class should be immutable. */ public class AssetKey implements Savable, Cloneable { @@ -49,16 +49,17 @@ public class AssetKey implements Savable, Cloneable { protected String name; protected transient String folder; protected transient String extension; - - public AssetKey(String name){ + + public AssetKey(String name) { this.name = reducePath(name); this.extension = getExtension(this.name); } - public AssetKey(){ + public AssetKey() { } @Override + @SuppressWarnings("unchecked") public AssetKey clone() { try { return (AssetKey) super.clone(); @@ -66,7 +67,7 @@ public AssetKey clone() { throw new AssertionError(); } } - + protected static String getExtension(String name) { int idx = name.lastIndexOf('.'); //workaround for filenames ending with xml and another dot ending before that (my.mesh.xml) @@ -109,33 +110,33 @@ public String getExtension() { /** * @return The folder in which the asset is located in. - * E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o" + * E.g. if the {@link #getName() name} is "Models/MyModel/MyModel.j3o" * then "Models/MyModel/" is returned. */ - public String getFolder(){ + public String getFolder() { if (folder == null) folder = getFolder(name); - + return folder; } /** * @return The preferred cache class for this asset type. Specify "null" - * if caching is to be disabled. By default the + * if caching is to be disabled. By default, the * {@link SimpleAssetCache} is returned. */ - public Class getCacheType(){ + public Class getCacheType() { return SimpleAssetCache.class; } - + /** * @return The preferred processor type for this asset type. Specify "null" * if no processing is required. */ - public Class getProcessorType(){ + public Class getProcessorType() { return null; } - + /** * Removes all relative elements of a path (A/B/../C.png and A/./C.png). * @param path The path containing relative elements @@ -146,7 +147,7 @@ public static String reducePath(String path) { return path; } String[] parts = path.split("/"); - LinkedList list = new LinkedList(); + LinkedList list = new LinkedList<>(); for (int i = 0; i < parts.length; i++) { String string = parts[i]; if (string.length() == 0 || string.equals(".")) { @@ -156,7 +157,8 @@ public static String reducePath(String path) { list.removeLast(); } else { list.add(".."); - Logger.getLogger(AssetKey.class.getName()).log(Level.SEVERE, "Asset path \"{0}\" is outside assetmanager root", path); + Logger.getLogger(AssetKey.class.getName()) + .log(Level.SEVERE, "Asset path \"{0}\" is outside the asset manager root", path); } } else { list.add(string); @@ -172,34 +174,35 @@ public static String reducePath(String path) { } return builder.toString(); } - + @Override - public boolean equals(Object other){ - if (!(other instanceof AssetKey)){ + public boolean equals(Object other) { + if (!(other instanceof AssetKey)) { return false; } - return name.equals(((AssetKey)other).name); + return name.equals(((AssetKey) other).name); } @Override - public int hashCode(){ + public int hashCode() { return name.hashCode(); } @Override - public String toString(){ + public String toString() { return name; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = reducePath(ic.readString("name", null)); extension = getExtension(name); } - } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java b/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java index 770640e04c..38aa38e3ff 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLoadException.java @@ -39,10 +39,11 @@ * @author Kirill Vainer */ public class AssetLoadException extends RuntimeException { - public AssetLoadException(String message){ + public AssetLoadException(String message) { super(message); } - public AssetLoadException(String message, Throwable cause){ + + public AssetLoadException(String message, Throwable cause) { super(message, cause); } } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java b/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java index dde7476c22..2d29d99eee 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ public interface AssetLoader { * Loads asset from the given input stream, parsing it into * an application-usable object. * + * @param assetInfo the located asset * @return An object representing the resource. * @throws java.io.IOException If an I/O error occurs while loading */ diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java b/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java index caa28524b4..5ae8ffa63a 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ public interface AssetLocator { /** * @param rootPath The root path where to look for assets. - * Typically this method will only be called once per + * Typically, this method will only be called once for each * instance of an asset locator. */ public void setRootPath(String rootPath); @@ -51,8 +51,8 @@ public interface AssetLocator { * The {@link AssetInfo} implementation provided should have a proper * return value for its {@link AssetInfo#openStream() } method. * - * @param manager - * @param key + * @param manager for managing assets + * @param key identifies the asset to be located * @return The {@link AssetInfo} that was located, or null if not found. */ public AssetInfo locate(AssetManager manager, AssetKey key); diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java index 0f6836e6d2..73762a1234 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ import com.jme3.texture.plugins.TGALoader; import java.io.IOException; import java.io.InputStream; +import java.util.ArrayList; import java.util.EnumSet; import java.util.List; @@ -86,7 +87,7 @@ * so that modifications to one instance do not leak onto others. */ public interface AssetManager { - + /** * Adds a {@link ClassLoader} that is used to load {@link Class classes} * that are needed for finding and loading Assets. @@ -94,19 +95,35 @@ public interface AssetManager { * use registerLocator for that. * * @param loader A ClassLoader that Classes in asset files can be loaded from. + * @deprecated use {@link com.jme3.util.res.Resources} */ - public void addClassLoader(ClassLoader loader); + @Deprecated + public default void addClassLoader(ClassLoader loader) { + + } /** * Remove a {@link ClassLoader} from the list of registered ClassLoaders + * + * @param loader the ClassLoader to be removed + * @deprecated use {@link com.jme3.util.res.Resources} */ - public void removeClassLoader(ClassLoader loader); + @Deprecated + public default void removeClassLoader(ClassLoader loader) { + + } /** * Retrieve the list of registered ClassLoaders that are used for loading * {@link Class classes} from asset files. + * + * @return an unmodifiable list + * @deprecated use {@link com.jme3.util.res.Resources} */ - public List getClassLoaders(); + @Deprecated + public default List getClassLoaders() { + return new ArrayList<>(); + } /** * Register an {@link AssetLoader} by using a class object. @@ -355,6 +372,8 @@ public interface AssetManager { /** * Returns the shaderGenerator responsible for generating the shaders + * + * @param caps a set of required capabilities * @return the shaderGenerator */ public ShaderGenerator getShaderGenerator(EnumSet caps); diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java b/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java index acee6db727..6c6968aff8 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetNotFoundException.java @@ -39,10 +39,11 @@ * @author Kirill Vainer */ public class AssetNotFoundException extends RuntimeException { - public AssetNotFoundException(String message){ + public AssetNotFoundException(String message) { super(message); } - public AssetNotFoundException(String message, Exception ex){ + + public AssetNotFoundException(String message, Exception ex) { super(message, ex); } } diff --git a/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java b/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java index e8e3728b3b..a8eb8d7ba2 100644 --- a/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java +++ b/jme3-core/src/main/java/com/jme3/asset/AssetProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ /** * AssetProcessor is used to apply processing to assets * after they have been loaded. They are assigned to a particular - * asset type (which is represented by a {@link Class} and any assets + * asset type (which is represented by a {@link Class}) and any assets * loaded that are of that class will be processed by the assigned * processor. * @@ -45,10 +45,11 @@ */ public interface AssetProcessor { /** - * Applies post processing to an asset. + * Applies post-processing to an asset. * The method may return an object that is not the same * instance as the parameter object, and it could be from a different class. - * + * + * @param key the key used to load the asset * @param obj The asset that was loaded from an {@link AssetLoader}. * @return Either the same object with processing applied, or an instance * of a new object. diff --git a/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java b/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java index bc6529b6b1..6303e1a5eb 100644 --- a/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java +++ b/jme3-core/src/main/java/com/jme3/asset/CloneableAssetProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,10 +39,12 @@ */ public class CloneableAssetProcessor implements AssetProcessor { + @Override public Object postProcess(AssetKey key, Object obj) { return obj; } + @Override public Object createClone(Object obj) { CloneableSmartAsset asset = (CloneableSmartAsset) obj; return asset.clone(); 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 784085613b..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 @@ -62,16 +62,17 @@ public interface CloneableSmartAsset extends Cloneable { * @return A clone of this asset. * The cloned asset cannot reference equal this asset. */ - public Object clone(); + 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/asset/DesktopAssetManager.java b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java index 0c92e19e91..a8ff73f2c4 100644 --- a/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/DesktopAssetManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,27 +67,27 @@ public class DesktopAssetManager implements AssetManager { private static final Logger logger = Logger.getLogger(AssetManager.class.getName()); private ShaderGenerator shaderGenerator; - + private final ImplHandler handler = new ImplHandler(this); - private CopyOnWriteArrayList eventListeners = - new CopyOnWriteArrayList(); - - private List classLoaders = - Collections.synchronizedList(new ArrayList()); + final private CopyOnWriteArrayList eventListeners = + new CopyOnWriteArrayList<>(); + + @Deprecated + final private List classLoaders = Collections.synchronizedList(new ArrayList<>()); - public DesktopAssetManager(){ + public DesktopAssetManager() { this(null); } - - public DesktopAssetManager(boolean usePlatformConfig){ + + public DesktopAssetManager(boolean usePlatformConfig) { this(usePlatformConfig ? JmeSystem.getPlatformAssetConfigURL() : null); } - public DesktopAssetManager(URL configFile){ - if (configFile != null){ + public DesktopAssetManager(URL configFile) { + if (configFile != null) { loadConfigFile(configFile); - } + } logger.fine("DesktopAssetManager created."); } @@ -98,104 +98,117 @@ private void loadConfigFile(URL configFile) { logger.log(Level.SEVERE, "Failed to load asset config", ex); } } - + + @Deprecated + @Override public void addClassLoader(ClassLoader loader) { classLoaders.add(loader); } - + + @Deprecated + @Override public void removeClassLoader(ClassLoader loader) { classLoaders.remove(loader); } - public List getClassLoaders(){ + @Deprecated + @Override + public List getClassLoaders() { return Collections.unmodifiableList(classLoaders); } - + @Override public void addAssetEventListener(AssetEventListener listener) { eventListeners.add(listener); } + @Override public void removeAssetEventListener(AssetEventListener listener) { eventListeners.remove(listener); } + @Override public void clearAssetEventListeners() { eventListeners.clear(); } - - public void setAssetEventListener(AssetEventListener listener){ + + public void setAssetEventListener(AssetEventListener listener) { eventListeners.clear(); eventListeners.add(listener); } - public void registerLoader(Class loader, String ... extensions){ + @Override + public void registerLoader(Class loader, String ... extensions) { handler.addLoader(loader, extensions); - if (logger.isLoggable(Level.FINER)){ + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "Registered loader: {0} for extensions {1}", - new Object[]{loader.getSimpleName(), Arrays.toString(extensions)}); + new Object[]{loader.getSimpleName(), Arrays.toString(extensions)}); } } - public void registerLoader(String clsName, String ... extensions){ + @SuppressWarnings("unchecked") + public void registerLoader(String clsName, String ... extensions) { Class clazz = null; - try{ + try { clazz = (Class) Class.forName(clsName); - }catch (ClassNotFoundException ex){ - logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex); - }catch (NoClassDefFoundError ex){ - logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex); + } catch (ClassNotFoundException | NoClassDefFoundError ex) { + logger.log(Level.WARNING, "Failed to find loader: " + clsName, ex); } - if (clazz != null){ + if (clazz != null) { registerLoader(clazz, extensions); } } - + + @Override public void unregisterLoader(Class loaderClass) { handler.removeLoader(loaderClass); - if (logger.isLoggable(Level.FINER)){ + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "Unregistered loader: {0}", loaderClass.getSimpleName()); } } - public void registerLocator(String rootPath, Class locatorClass){ + @Override + public void registerLocator(String rootPath, Class locatorClass) { handler.addLocator(locatorClass, rootPath); - if (logger.isLoggable(Level.FINER)){ + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "Registered locator: {0}", locatorClass.getSimpleName()); } } - public void registerLocator(String rootPath, String clsName){ + @SuppressWarnings("unchecked") + public void registerLocator(String rootPath, String clsName) { Class clazz = null; - try{ + try { clazz = (Class) Class.forName(clsName); - }catch (ClassNotFoundException ex){ - logger.log(Level.WARNING, "Failed to find locator: "+clsName, ex); - }catch (NoClassDefFoundError ex){ - logger.log(Level.WARNING, "Failed to find loader: "+clsName, ex); + } catch (ClassNotFoundException ex) { + logger.log(Level.WARNING, "Failed to find locator: " + clsName, ex); + } catch (NoClassDefFoundError ex) { + logger.log(Level.WARNING, "Failed to find loader: " + clsName, ex); } - if (clazz != null){ + if (clazz != null) { registerLocator(rootPath, clazz); } } - - public void unregisterLocator(String rootPath, Class clazz){ + + @Override + public void unregisterLocator(String rootPath, Class clazz) { handler.removeLocator(clazz, rootPath); - if (logger.isLoggable(Level.FINER)){ + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "Unregistered locator: {0}", clazz.getSimpleName()); } } - - public AssetInfo locateAsset(AssetKey key){ + + @Override + public AssetInfo locateAsset(AssetKey key) { AssetInfo info = handler.tryLocate(key); - if (info == null){ + if (info == null) { logger.log(Level.WARNING, "Cannot locate resource: {0}", key); } return info; } - + @Override public T getFromCache(AssetKey key) { AssetCache cache = handler.getCache(key.getCacheType()); @@ -210,7 +223,7 @@ public T getFromCache(AssetKey key) { throw new IllegalArgumentException("Key " + key + " specifies no cache."); } } - + @Override public void addToCache(AssetKey key, T asset) { AssetCache cache = handler.getCache(key.getCacheType()); @@ -221,7 +234,7 @@ public void addToCache(AssetKey key, T asset) { throw new IllegalArgumentException("Key " + key + " specifies no cache."); } } - + @Override public boolean deleteFromCache(AssetKey key) { AssetCache cache = handler.getCache(key.getCacheType()); @@ -231,11 +244,11 @@ public boolean deleteFromCache(AssetKey key) { throw new IllegalArgumentException("Key " + key + " specifies no cache."); } } - + @Override - public void clearCache(){ + public void clearCache() { handler.clearCache(); - if (logger.isLoggable(Level.FINER)){ + if (logger.isLoggable(Level.FINER)) { logger.log(Level.FINER, "All asset caches cleared."); } } @@ -248,10 +261,11 @@ public void clearCache(){ * @param proc AssetProcessor to use, or null to disable processing * @param cache The cache to store the asset in, or null to disable caching * @return The loaded asset - * + * * @throws AssetLoadException If failed to load asset due to exception or * other error. */ + @SuppressWarnings("unchecked") protected T loadLocatedAsset(AssetKey key, AssetInfo info, AssetProcessor proc, AssetCache cache) { AssetLoader loader = handler.aquireLoader(key); Object obj; @@ -289,24 +303,25 @@ protected T loadLocatedAsset(AssetKey key, AssetInfo info, AssetProcessor return (T) obj; } } - + /** * Clones the asset using the given processor and registers the clone * with the cache. - * + * * @param The asset type * @param key The asset key - * @param obj The asset to clone / register, must implement - * {@link CloneableSmartAsset}. + * @param obj The asset to clone / register, must implement + * {@link CloneableSmartAsset}. * @param proc The processor which will generate the clone, cannot be null * @param cache The cache to register the clone with, cannot be null. * @return The cloned asset, cannot be the same as the given asset since - * it is a clone. - * - * @throws IllegalStateException If asset does not implement - * {@link CloneableSmartAsset}, if the cache is null, or if the + * it is a clone. + * + * @throws IllegalStateException If asset does not implement + * {@link CloneableSmartAsset}, if the cache is null, or if the * processor did not clone the asset. */ + @SuppressWarnings("unchecked") protected T registerAndCloneSmartAsset(AssetKey key, T obj, AssetProcessor proc, AssetCache cache) { // object obj is the original asset // create an instance for user @@ -326,106 +341,118 @@ protected T registerAndCloneSmartAsset(AssetKey key, T obj, AssetProcesso return clone; } } - + @Override public T loadAssetFromStream(AssetKey key, InputStream inputStream) { if (key == null) { throw new IllegalArgumentException("key cannot be null"); } - - for (AssetEventListener listener : eventListeners){ + + for (AssetEventListener listener : eventListeners) { listener.assetRequested(key); } - + AssetProcessor proc = handler.getProcessor(key.getProcessorType()); StreamAssetInfo info = new StreamAssetInfo(this, key, inputStream); return loadLocatedAsset(key, info, proc, null); } - + @Override - public T loadAsset(AssetKey key){ + @SuppressWarnings("unchecked") + public T loadAsset(AssetKey key) { if (key == null) throw new IllegalArgumentException("key cannot be null"); - - for (AssetEventListener listener : eventListeners){ + + for (AssetEventListener listener : eventListeners) { listener.assetRequested(key); } - + AssetCache cache = handler.getCache(key.getCacheType()); AssetProcessor proc = handler.getProcessor(key.getProcessorType()); - + Object obj = cache != null ? cache.getFromCache(key) : null; - if (obj == null){ + if (obj == null) { // Asset not in cache, load it from file system. AssetInfo info = handler.tryLocate(key); - if (info == null){ - if (handler.getParentKey() != null){ + if (info == null) { + if (handler.getParentKey() != null) { // Inform event listener that an asset has failed to load. // If the parent AssetLoader chooses not to propagate // the exception, this is the only means of finding // that something went wrong. - for (AssetEventListener listener : eventListeners){ + for (AssetEventListener listener : eventListeners) { listener.assetDependencyNotFound(handler.getParentKey(), key); } } throw new AssetNotFoundException(key.toString()); } - + obj = loadLocatedAsset(key, info, proc, cache); } T clone = (T) obj; - + if (obj instanceof CloneableSmartAsset) { clone = registerAndCloneSmartAsset(key, clone, proc, cache); } - + return clone; } - public Object loadAsset(String name){ - return loadAsset(new AssetKey(name)); + @Override + public Object loadAsset(String name) { + return loadAsset(new AssetKey<>(name)); } - public Texture loadTexture(TextureKey key){ - return (Texture) loadAsset(key); + @Override + public Texture loadTexture(TextureKey key) { + return loadAsset(key); } - public Material loadMaterial(String name){ - return (Material) loadAsset(new MaterialKey(name)); + @Override + public Material loadMaterial(String name) { + return loadAsset(new MaterialKey(name)); } - public Texture loadTexture(String name){ + @Override + public Texture loadTexture(String name) { TextureKey key = new TextureKey(name, true); key.setGenerateMips(true); return loadTexture(key); } - public AudioData loadAudio(AudioKey key){ - return (AudioData) loadAsset(key); + @Override + public AudioData loadAudio(AudioKey key) { + return loadAsset(key); } - public AudioData loadAudio(String name){ + @Override + public AudioData loadAudio(String name) { return loadAudio(new AudioKey(name, false)); } - public BitmapFont loadFont(String name){ - return (BitmapFont) loadAsset(new AssetKey(name)); + @Override + public BitmapFont loadFont(String name) { + return loadAsset(new AssetKey(name)); } - public Spatial loadModel(ModelKey key){ - return (Spatial) loadAsset(key); + @Override + public Spatial loadModel(ModelKey key) { + return loadAsset(key); } - public Spatial loadModel(String name){ + @Override + public Spatial loadModel(String name) { return loadModel(new ModelKey(name)); } - public FilterPostProcessor loadFilter(FilterKey key){ - return (FilterPostProcessor) loadAsset(key); + @Override + public FilterPostProcessor loadFilter(FilterKey key) { + return loadAsset(key); } - public FilterPostProcessor loadFilter(String name){ + @Override + public FilterPostProcessor loadFilter(String name) { return loadFilter(new FilterKey(name)); } @@ -435,11 +462,11 @@ public FilterPostProcessor loadFilter(String name){ @Override public ShaderGenerator getShaderGenerator(EnumSet caps) { if (shaderGenerator == null) { - if(caps.contains(Caps.OpenGLES30) && caps.contains(Caps.GLSL300)){ + if (caps.contains(Caps.OpenGLES30) && caps.contains(Caps.GLSL300)) { shaderGenerator = new Glsl300ShaderGenerator(this); - }else if(caps.contains(Caps.GLSL150)) { + } else if (caps.contains(Caps.GLSL150)) { shaderGenerator = new Glsl150ShaderGenerator(this); - }else{ + } else { shaderGenerator = new Glsl100ShaderGenerator(this); } } @@ -453,6 +480,4 @@ public ShaderGenerator getShaderGenerator(EnumSet caps) { public void setShaderGenerator(ShaderGenerator shaderGenerator) { this.shaderGenerator = shaderGenerator; } - - } diff --git a/jme3-core/src/main/java/com/jme3/asset/FilterKey.java b/jme3-core/src/main/java/com/jme3/asset/FilterKey.java index 1bb1f60f03..8edbfe60af 100644 --- a/jme3-core/src/main/java/com/jme3/asset/FilterKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/FilterKey.java @@ -36,7 +36,7 @@ /** * Used to load FilterPostProcessors which are not cached. - * + * * @author Andrew Wason */ public class FilterKey extends AssetKey { @@ -50,7 +50,7 @@ public FilterKey() { } @Override - public Class getCacheType(){ + public Class getCacheType() { // Do not cache filter processors return null; } diff --git a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java index 581c18b4ee..90af79d803 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java +++ b/jme3-core/src/main/java/com/jme3/asset/ImplHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,7 @@ package com.jme3.asset; import com.jme3.asset.cache.AssetCache; - +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; @@ -53,26 +53,26 @@ final class ImplHandler { private static final Logger logger = Logger.getLogger(ImplHandler.class.getName()); private final AssetManager assetManager; - - private final ThreadLocal parentAssetKey - = new ThreadLocal(); - + + private final ThreadLocal parentAssetKey + = new ThreadLocal<>(); + private final CopyOnWriteArrayList> locatorsList = - new CopyOnWriteArrayList>(); - - private final HashMap, ImplThreadLocal> classToLoaderMap = - new HashMap, ImplThreadLocal>(); + new CopyOnWriteArrayList<>(); + + private final HashMap, ImplThreadLocal> classToLoaderMap = + new HashMap<>(); private final ConcurrentHashMap> extensionToLoaderMap = - new ConcurrentHashMap>(); - + new ConcurrentHashMap<>(); + private final ConcurrentHashMap, AssetProcessor> classToProcMap = - new ConcurrentHashMap, AssetProcessor>(); - + new ConcurrentHashMap<>(); + private final ConcurrentHashMap, AssetCache> classToCacheMap = - new ConcurrentHashMap, AssetCache>(); + new ConcurrentHashMap<>(); - public ImplHandler(AssetManager assetManager){ + public ImplHandler(AssetManager assetManager) { this.assetManager = assetManager; } @@ -82,54 +82,49 @@ protected static class ImplThreadLocal extends ThreadLocal { private final String path; private final String[] extensions; - public ImplThreadLocal(Class type, String[] extensions){ + public ImplThreadLocal(Class type, String[] extensions) { this.type = type; this.extensions = extensions.clone(); this.path = null; } - public ImplThreadLocal(Class type, String path){ + public ImplThreadLocal(Class type, String path) { this.type = type; this.path = path; this.extensions = null; } - public ImplThreadLocal(Class type){ + public ImplThreadLocal(Class type) { this.type = type; this.path = null; this.extensions = null; } - + public String getPath() { return path; } - - public String[] getExtensions(){ + + public String[] getExtensions() { return extensions; } - - public Class getTypeClass(){ + + public Class getTypeClass() { return type; } @Override - protected T initialValue(){ + protected T initialValue() { try { - T obj = type.newInstance(); + T obj = type.getDeclaredConstructor().newInstance(); + if (path != null) { - ((AssetLocator)obj).setRootPath(path); + ((AssetLocator) obj).setRootPath(path); } return obj; - } catch (InstantiationException ex) { - logger.log(Level.SEVERE,"Cannot create locator of type {0}, does" - + " the class have an empty and publically accessible"+ - " constructor?", type.getName()); - logger.throwing(type.getName(), "", ex); - } catch (IllegalAccessException ex) { - logger.log(Level.SEVERE,"Cannot create locator of type {0}, " - + "does the class have an empty and publically " - + "accessible constructor?", type.getName()); - logger.throwing(type.getName(), "", ex); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { + logger.log(Level.SEVERE, "An exception occurred while instantiating asset locator: " + type.getName(), ex); } return null; } @@ -140,51 +135,51 @@ protected T initialValue(){ * that have failed to load. When set, the {@link DesktopAssetManager} * gets a hint that it should suppress {@link AssetNotFoundException}s * and instead call the listener callback (if set). - * - * @param parentKey The parent key + * + * @param parentKey The parent key */ - public void establishParentKey(AssetKey parentKey){ - if (parentAssetKey.get() == null){ + public void establishParentKey(AssetKey parentKey) { + if (parentAssetKey.get() == null) { parentAssetKey.set(parentKey); } } - - public void releaseParentKey(AssetKey parentKey){ - if (parentAssetKey.get() == parentKey){ + + public void releaseParentKey(AssetKey parentKey) { + if (parentAssetKey.get() == parentKey) { parentAssetKey.set(null); } } - - public AssetKey getParentKey(){ + + public AssetKey getParentKey() { return parentAssetKey.get(); } - + /** * Attempts to locate the given resource name. * @param key The full name of the resource. * @return The AssetInfo containing resource information required for * access, or null if not found. */ - public AssetInfo tryLocate(AssetKey key){ - if (locatorsList.isEmpty()){ - logger.warning("There are no locators currently"+ - " registered. Use AssetManager."+ - "registerLocator() to register a"+ - " locator."); + public AssetInfo tryLocate(AssetKey key) { + if (locatorsList.isEmpty()) { + logger.warning("There are no locators currently" + + " registered. Use AssetManager." + + "registerLocator() to register a" + + " locator."); return null; } - - for (ImplThreadLocal local : locatorsList){ + + for (ImplThreadLocal local : locatorsList) { AssetInfo info = local.get().locate(assetManager, key); if (info != null) { return info; } } - + return null; } - public int getLocatorCount(){ + public int getLocatorCount() { return locatorsList.size(); } @@ -193,43 +188,44 @@ public int getLocatorCount(){ * of the current thread. * @return AssetLoader registered with addLoader. */ - public AssetLoader aquireLoader(AssetKey key){ + public AssetLoader aquireLoader(AssetKey key) { // No need to synchronize() against map, its concurrent ImplThreadLocal local = extensionToLoaderMap.get(key.getExtension()); - if (local == null){ - throw new AssetLoadException("No loader registered for type \"" + - key.getExtension() + "\""); + if (local == null) { + throw new AssetLoadException("No loader registered for type \"" + + key.getExtension() + "\""); } return (AssetLoader) local.get(); } - - public void clearCache(){ - // The iterator of the values collection is thread safe + + public void clearCache() { + // The iterator over the values collection is thread-safe. synchronized (classToCacheMap) { - for (AssetCache cache : classToCacheMap.values()){ + for (AssetCache cache : classToCacheMap.values()) { cache.clearCache(); } } } - + + @SuppressWarnings("unchecked") public T getCache(Class cacheClass) { if (cacheClass == null) { return null; } - + T cache = (T) classToCacheMap.get(cacheClass); if (cache == null) { synchronized (classToCacheMap) { cache = (T) classToCacheMap.get(cacheClass); if (cache == null) { try { - cache = cacheClass.newInstance(); + cache = cacheClass.getDeclaredConstructor().newInstance(); classToCacheMap.put(cacheClass, cache); - } catch (InstantiationException ex) { + } catch (InstantiationException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException ex) { throw new IllegalArgumentException("The cache class cannot" + " be created, ensure it has empty constructor", ex); - } catch (IllegalAccessException ex) { + } catch (IllegalAccessException | SecurityException ex) { throw new IllegalArgumentException("The cache class cannot " + "be accessed", ex); } @@ -238,23 +234,24 @@ public T getCache(Class cacheClass) { } return cache; } - - public T getProcessor(Class procClass){ + + @SuppressWarnings("unchecked") + public T getProcessor(Class procClass) { if (procClass == null) return null; - + T proc = (T) classToProcMap.get(procClass); - if (proc == null){ - synchronized(classToProcMap){ + if (proc == null) { + synchronized (classToProcMap) { proc = (T) classToProcMap.get(procClass); if (proc == null) { try { - proc = procClass.newInstance(); + proc = procClass.getDeclaredConstructor().newInstance(); classToProcMap.put(procClass, proc); - } catch (InstantiationException ex) { + } catch (InstantiationException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException ex) { throw new IllegalArgumentException("The processor class cannot" + " be created, ensure it has empty constructor", ex); - } catch (IllegalAccessException ex) { + } catch (IllegalAccessException | SecurityException ex) { throw new IllegalArgumentException("The processor class cannot " + "be accessed", ex); } @@ -263,53 +260,55 @@ public T getProcessor(Class procClass){ } return proc; } - - public void addLoader(final Class loaderType, String ... extensions){ + + @SuppressWarnings("unchecked") + public void addLoader(final Class loaderType, String ... extensions) { // Synchronized access must be used for any ops on classToLoaderMap ImplThreadLocal local = new ImplThreadLocal(loaderType, extensions); - for (String extension : extensions){ + for (String extension : extensions) { extension = extension.toLowerCase(); - synchronized (classToLoaderMap){ + synchronized (classToLoaderMap) { classToLoaderMap.put(loaderType, local); extensionToLoaderMap.put(extension, local); } } } - public void removeLoader(final Class loaderType){ + public void removeLoader(final Class loaderType) { // Synchronized access must be used for any ops on classToLoaderMap // Find the loader ImplThreadLocal for this class - synchronized (classToLoaderMap){ + synchronized (classToLoaderMap) { // Remove it from the class->loader map ImplThreadLocal local = classToLoaderMap.remove(loaderType); - if (local == null) return; + if (local == null) + return; // Remove it from the extension->loader map - for (String extension : local.getExtensions()){ + for (String extension : local.getExtensions()) { extensionToLoaderMap.remove(extension); } } } - - public void addLocator(final Class locatorType, String rootPath){ + + @SuppressWarnings("unchecked") + public void addLocator(final Class locatorType, String rootPath) { locatorsList.add(new ImplThreadLocal(locatorType, rootPath)); } - public void removeLocator(final Class locatorType, String rootPath){ - ArrayList> locatorsToRemove = new ArrayList>(); + public void removeLocator(final Class locatorType, String rootPath) { + ArrayList> locatorsToRemove = new ArrayList<>(); Iterator> it = locatorsList.iterator(); - - while (it.hasNext()){ - ImplThreadLocal locator = it.next(); - if (locator.getPath().equals(rootPath) && - locator.getTypeClass().equals(locatorType)){ + + while (it.hasNext()) { + ImplThreadLocal locator = it.next(); + if (locator.getPath().equals(rootPath) + && locator.getTypeClass().equals(locatorType)) { //it.remove(); // copy on write list doesn't support iterator remove, // must use temporary list locatorsToRemove.add(locator); } } - + locatorsList.removeAll(locatorsToRemove); } - } diff --git a/jme3-core/src/main/java/com/jme3/asset/ModelKey.java b/jme3-core/src/main/java/com/jme3/asset/ModelKey.java index fa47df6485..1223c5f24a 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ModelKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/ModelKey.java @@ -39,9 +39,9 @@ * Used to load model files, such as OBJ or Blender models. * This uses cloneable smart asset management, so that when all clones of * this model become unreachable, the original asset is purged from the cache, - * allowing textures, materials, shaders, etc referenced by the model to + * allowing textures, materials, shaders, etc referenced by the model to * become collected. - * + * * @author Kirill Vainer */ public class ModelKey extends AssetKey { @@ -53,14 +53,14 @@ public ModelKey(String name) { public ModelKey() { super(); } - + @Override - public Class getCacheType(){ + public Class getCacheType() { return WeakRefCloneAssetCache.class; } - + @Override - public Class getProcessorType(){ + public Class getProcessorType() { return CloneableAssetProcessor.class; } } diff --git a/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java index 9713d6ab85..ed1495397f 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/ShaderNodeDefinitionKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ /** * Used for loading {@link ShaderNodeDefinition shader nodes definition} * - * Tells if the defintion has to be loaded with or without its documentation + * Determines whether the definition will be loaded with or without its documentation */ public class ShaderNodeDefinitionKey extends AssetKey> { diff --git a/jme3-core/src/main/java/com/jme3/asset/TextureKey.java b/jme3-core/src/main/java/com/jme3/asset/TextureKey.java index cb5450b359..be096de5da 100644 --- a/jme3-core/src/main/java/com/jme3/asset/TextureKey.java +++ b/jme3-core/src/main/java/com/jme3/asset/TextureKey.java @@ -44,14 +44,14 @@ import java.io.IOException; /** - * Used to load textures from image files such as JPG or PNG. + * Used to load textures from image files such as JPG or PNG. * Note that texture loaders actually load the asset as an {@link Image} - * object, which is then converted to a {@link Texture} in the - * {@link TextureProcessor#postProcess(com.jme3.asset.AssetKey, java.lang.Object) } + * object, which is then converted to a {@link Texture} in the + * {@link TextureProcessor#postProcess(com.jme3.asset.AssetKey, java.lang.Object)} * method. Since textures are cloneable smart assets, the texture stored * in the cache will be collected when all clones of the texture become * unreachable. - * + * * @author Kirill Vainer */ public class TextureKey extends AssetKey { @@ -96,17 +96,17 @@ public String toString() { } return name + (flipY ? " (Flipped)" : "") + type + (generateMips ? " (Mipmapped)" : ""); } - + @Override - public Class getCacheType(){ + public Class getCacheType() { return WeakRefCloneAssetCache.class; } @Override - public Class getProcessorType(){ + public Class getProcessorType() { return TextureProcessor.class; } - + public boolean isFlipY() { return flipY; } @@ -114,7 +114,7 @@ public boolean isFlipY() { public void setFlipY(boolean flipY) { this.flipY = flipY; } - + public int getAnisotropy() { return anisotropy; } @@ -133,7 +133,7 @@ public void setGenerateMips(boolean generateMips) { /** * The type of texture expected to be returned. - * + * * @return type of texture expected to be returned. */ public Type getTextureTypeHint() { @@ -142,13 +142,13 @@ public Type getTextureTypeHint() { /** * Hints the loader as to which type of texture is expected. - * + * * @param textureTypeHint The type of texture expected to be loaded. */ public void setTextureTypeHint(Type textureTypeHint) { this.textureTypeHint = textureTypeHint; } - + @Override public boolean equals(Object obj) { if (obj == null) { @@ -186,7 +186,7 @@ public int hashCode() { hash = 17 * hash + (this.textureTypeHint != null ? this.textureTypeHint.hashCode() : 0); return hash; } - + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -195,7 +195,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(generateMips, "generate_mips", false); oc.write(anisotropy, "anisotropy", 0); oc.write(textureTypeHint, "tex_type", Type.TwoDimensional); - + // Backwards compat oc.write(textureTypeHint == Type.CubeMap, "as_cubemap", false); } @@ -208,7 +208,7 @@ public void read(JmeImporter im) throws IOException { generateMips = ic.readBoolean("generate_mips", false); anisotropy = ic.readInt("anisotropy", 0); boolean asCube = ic.readBoolean("as_cubemap", false); - + if (asCube) { // Backwards compat textureTypeHint = Type.CubeMap; diff --git a/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java b/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java index 28f1cdb04d..4d8dfc7d7b 100644 --- a/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java +++ b/jme3-core/src/main/java/com/jme3/asset/ThreadingManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,22 +37,23 @@ * ThreadingManager manages the threads used to load content * within the Content Manager system. A pool of threads and a task queue * is used to load resource data and perform I/O while the application's - * render thread is active. + * render thread is active. */ public class ThreadingManager { protected final ExecutorService executor = - Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), - new LoadingThreadFactory()); + Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), + new LoadingThreadFactory()); protected final AssetManager owner; protected int nextThreadId = 0; - public ThreadingManager(AssetManager owner){ + public ThreadingManager(AssetManager owner) { this.owner = owner; } protected class LoadingThreadFactory implements ThreadFactory { + @Override public Thread newThread(Runnable r) { Thread t = new Thread(r, "jME3-threadpool-" + (nextThreadId++)); t.setDaemon(true); @@ -69,18 +70,17 @@ public LoadingTask(AssetKey assetKey) { this.assetKey = assetKey; } + @Override public T call() throws Exception { return owner.loadAsset(assetKey); } } public Future loadAsset(AssetKey assetKey) { - return executor.submit(new LoadingTask(assetKey)); + return executor.submit(new LoadingTask<>(assetKey)); } public static boolean isLoadingThread() { return Thread.currentThread().getName().startsWith("jME3-threadpool"); } - - } diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java index 929c254a43..7bb6be5adb 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/AssetCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,10 +56,10 @@ public interface AssetCache { * Adds an asset to the cache. * Once added, it should be possible to retrieve the asset * by using the {@link #getFromCache(com.jme3.asset.AssetKey) } method. - * However the caching criteria may at some point choose that the asset - * should be removed from the cache to save memory, in that case, + * However, the caching criteria may at some point decide that the asset + * should be removed from the cache to save memory. In that case, * {@link #getFromCache(com.jme3.asset.AssetKey) } will return null. - *

    Thread-Safe + *

    Thread-Safe

    * * @param The type of the asset to cache. * @param key The asset key that can be used to look up the asset. @@ -71,7 +71,7 @@ public interface AssetCache { * This should be called by the asset manager when it has successfully * acquired a cached asset (with {@link #getFromCache(com.jme3.asset.AssetKey) }) * and cloned it for use. - *

    Thread-Safe + *

    Thread-Safe

    * * @param The type of the asset to register. * @param key The asset key of the loaded asset (used to retrieve from cache) @@ -95,17 +95,17 @@ public interface AssetCache { * {@link #addToCache(com.jme3.asset.AssetKey, java.lang.Object) }. * The asset may be removed from the cache automatically even if * it was added previously, in that case, this method will return null. - *

    Thread-Safe + *

    Thread-Safe

    * * @param The type of the asset to retrieve - * @param key The key used to lookup the asset. + * @param key The key used to look up the asset. * @return The asset that was previously cached, or null if not found. */ public T getFromCache(AssetKey key); /** * Deletes an asset from the cache. - *

    Thread-Safe + *

    Thread-Safe

    * * @param key The asset key to find the asset to delete. * @return True if the asset was successfully found in the cache @@ -115,7 +115,7 @@ public interface AssetCache { /** * Deletes all assets from the cache. - *

    Thread-Safe + *

    Thread-Safe

    */ public void clearCache(); } diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java index 0b3617ac05..a2d07f6ee9 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/SimpleAssetCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,27 +44,34 @@ */ public class SimpleAssetCache implements AssetCache { - private final ConcurrentHashMap keyToAssetMap = new ConcurrentHashMap(); + private final ConcurrentHashMap keyToAssetMap = new ConcurrentHashMap<>(); + @Override public void addToCache(AssetKey key, T obj) { keyToAssetMap.put(key, obj); } + @Override public void registerAssetClone(AssetKey key, T clone) { } + @Override + @SuppressWarnings("unchecked") public T getFromCache(AssetKey key) { return (T) keyToAssetMap.get(key); } + @Override public boolean deleteFromCache(AssetKey key) { return keyToAssetMap.remove(key) != null; } + @Override public void clearCache() { keyToAssetMap.clear(); } + @Override public void notifyNoAssetClone() { } diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java index 42f73f4a5a..08631731d7 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefAssetCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,79 +40,87 @@ import java.util.logging.Logger; /** - * A garbage collector bound asset cache that handles non-clonable objects. + * A garbage collector bound asset cache that handles non-cloneable objects. * This cache assumes that the asset given to the user is the same asset - * that has been stored in the cache, in other words, + * that has been stored in the cache, in other words, * {@link AssetProcessor#createClone(java.lang.Object) } for that asset * returns the same object as the argument. - * This implementation will remove the asset from the cache + * This implementation will remove the asset from the cache * once the asset is no longer referenced in user code and memory is low, * e.g. the VM feels like purging the weak references for that asset. - * + * * @author Kirill Vainer */ public class WeakRefAssetCache implements AssetCache { private static final Logger logger = Logger.getLogger(WeakRefAssetCache.class.getName()); - - private final ReferenceQueue refQueue = new ReferenceQueue(); - - private final ConcurrentHashMap assetCache - = new ConcurrentHashMap(); + + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + + private final ConcurrentHashMap assetCache + = new ConcurrentHashMap<>(); private static class AssetRef extends WeakReference { - + private final AssetKey assetKey; - - public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue refQueue){ + + public AssetRef(AssetKey assetKey, Object originalAsset, ReferenceQueue refQueue) { super(originalAsset, refQueue); this.assetKey = assetKey; } } - - private void removeCollectedAssets(){ + + private void removeCollectedAssets() { int removedAssets = 0; - for (AssetRef ref; (ref = (AssetRef)refQueue.poll()) != null;){ - // Asset was collected, note that at this point the asset cache + for (AssetRef ref; (ref = (AssetRef) refQueue.poll()) != null;) { + // Asset was collected, note that at this point the asset cache // might not even have this asset anymore, it is OK. - if (assetCache.remove(ref.assetKey) != null){ - removedAssets ++; + if (assetCache.remove(ref.assetKey) != null) { + removedAssets++; } } if (removedAssets >= 1) { - logger.log(Level.FINE, "WeakRefAssetCache: {0} assets were purged from the cache.", removedAssets); + logger.log(Level.FINE, + "WeakRefAssetCache: {0} assets were purged from the cache.", removedAssets); } } - + + @Override public void addToCache(AssetKey key, T obj) { removeCollectedAssets(); - - // NOTE: Some thread issues can hapen if another - // thread is loading an asset with the same key .. + + // NOTE: Some thread issues can happen if another + // thread is loading an asset with the same key. AssetRef ref = new AssetRef(key, obj, refQueue); assetCache.put(key, ref); } + @Override + @SuppressWarnings("unchecked") public T getFromCache(AssetKey key) { AssetRef ref = assetCache.get(key); - if (ref != null){ + if (ref != null) { return (T) ref.get(); - }else{ + } else { return null; } } + @Override public boolean deleteFromCache(AssetKey key) { return assetCache.remove(key) != null; } + @Override public void clearCache() { assetCache.clear(); } - + + @Override public void registerAssetClone(AssetKey key, T clone) { } - + + @Override public void notifyNoAssetClone() { } } diff --git a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java index c7446dfc39..722afde411 100644 --- a/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java +++ b/jme3-core/src/main/java/com/jme3/asset/cache/WeakRefCloneAssetCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,44 +45,43 @@ * WeakRefCloneAssetCache caches cloneable assets in a weak-key * cache, allowing them to be collected when memory is low. * The cache stores weak references to the asset keys, so that - * when all clones of the original asset are collected, will cause the + * when all clones of the original asset are collected, will cause the * asset to be automatically removed from the cache. - * -* @author Kirill Vainer + * + * @author Kirill Vainer */ public class WeakRefCloneAssetCache implements AssetCache { private static final Logger logger = Logger.getLogger(WeakRefAssetCache.class.getName()); - - private final ReferenceQueue refQueue = new ReferenceQueue(); - + + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + /** - * Maps cloned key to AssetRef which has a weak ref to the original + * Maps cloned key to AssetRef which has a weak ref to the original * key and a strong ref to the original asset. */ - private final ConcurrentHashMap smartCache - = new ConcurrentHashMap(); - + private final ConcurrentHashMap smartCache = new ConcurrentHashMap<>(); + /** * Stored in the ReferenceQueue to find out when originalKey is collected * by GC. Once collected, the clonedKey is used to remove the asset * from the cache. */ private static final class KeyRef extends PhantomReference { - + AssetKey clonedKey; - + public KeyRef(AssetKey originalKey, ReferenceQueue refQueue) { super(originalKey, refQueue); clonedKey = originalKey.clone(); } } - + /** * Stores the original key and original asset. * The asset info contains a cloneable asset (e.g. the original, from - * which all clones are made). Also a weak reference to the - * original key which is used when the clones are produced. + * which all clones are made) and also a weak reference to the + * original key, which is used when the clones are produced. */ private static final class AssetRef extends WeakReference { @@ -94,65 +93,71 @@ public AssetRef(CloneableSmartAsset originalAsset, AssetKey originalKey) { } } - private final ThreadLocal> assetLoadStack + private final ThreadLocal> assetLoadStack = new ThreadLocal>() { @Override protected ArrayList initialValue() { - return new ArrayList(); + return new ArrayList<>(); } }; - - private void removeCollectedAssets(){ + + private void removeCollectedAssets() { int removedAssets = 0; - for (KeyRef ref; (ref = (KeyRef)refQueue.poll()) != null;){ + for (KeyRef ref; (ref = (KeyRef) refQueue.poll()) != null;) { // (Cannot use ref.get() since it was just collected by GC!) AssetKey key = ref.clonedKey; - - // Asset was collected, note that at this point the asset cache + + // Asset was collected, note that at this point the asset cache // might not even have this asset anymore, it is OK. - if (smartCache.remove(key) != null){ - removedAssets ++; + if (smartCache.remove(key) != null) { + removedAssets++; } } if (removedAssets >= 1) { - logger.log(Level.FINE, "WeakRefCloneAssetCache: {0} assets were purged from the cache.", removedAssets); + logger.log(Level.FINE, + "WeakRefCloneAssetCache: {0} assets were purged from the cache.", removedAssets); } } - + + @Override public void addToCache(AssetKey originalKey, T obj) { // Make room for new asset removeCollectedAssets(); - + CloneableSmartAsset asset = (CloneableSmartAsset) obj; - - // No circular references, since the original asset is + + // No circular references, since the original asset is // strongly referenced, we don't want the key strongly referenced. - asset.setKey(null); - + asset.setKey(null); + // Start tracking the collection of originalKey // (this adds the KeyRef to the ReferenceQueue) KeyRef ref = new KeyRef(originalKey, refQueue); - - // Place the asset in the cache, but use a clone of + + // Place the asset in the cache, but use a clone of // the original key. smartCache.put(ref.clonedKey, new AssetRef(asset, originalKey)); - + // Push the original key used to load the asset // so that it can be set on the clone later ArrayList loadStack = assetLoadStack.get(); loadStack.add(originalKey); } + @Override public void registerAssetClone(AssetKey key, T clone) { ArrayList loadStack = assetLoadStack.get(); - ((CloneableSmartAsset)clone).setKey(loadStack.remove(loadStack.size() - 1)); + ((CloneableSmartAsset) clone).setKey(loadStack.remove(loadStack.size() - 1)); } - + + @Override public void notifyNoAssetClone() { ArrayList loadStack = assetLoadStack.get(); loadStack.remove(loadStack.size() - 1); } + @Override + @SuppressWarnings("unchecked") public T getFromCache(AssetKey key) { AssetRef smartInfo = smartCache.get(key); if (smartInfo == null) { @@ -162,40 +167,42 @@ public T getFromCache(AssetKey key) { // can check this and determine that the asset clone // belongs to the asset retrieved here. AssetKey keyForTheClone = smartInfo.get(); - if (keyForTheClone == null){ + if (keyForTheClone == null) { // The asset was JUST collected by GC // (between here and smartCache.get) return null; } - + // Prevent original key from getting collected // while an asset is loaded for it. ArrayList loadStack = assetLoadStack.get(); loadStack.add(keyForTheClone); - + return (T) smartInfo.asset; } } + @Override public boolean deleteFromCache(AssetKey key) { ArrayList loadStack = assetLoadStack.get(); - - if (!loadStack.isEmpty()){ + + if (!loadStack.isEmpty()) { throw new UnsupportedOperationException("Cache cannot be modified" - + "while assets are being loaded"); + + "while assets are being loaded"); } - + return smartCache.remove(key) != null; } - + + @Override public void clearCache() { ArrayList loadStack = assetLoadStack.get(); - - if (!loadStack.isEmpty()){ + + if (!loadStack.isEmpty()) { throw new UnsupportedOperationException("Cache cannot be modified" - + "while assets are being loaded"); + + "while assets are being loaded"); } - + smartCache.clear(); } } 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 230299f5de..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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,15 @@ */ package com.jme3.audio; -import com.jme3.audio.AudioData.DataType; import com.jme3.util.BufferUtils; import com.jme3.util.NativeObject; + import java.nio.ByteBuffer; /** * An AudioBuffer is an implementation of AudioData * where the audio is buffered (stored in memory). All parts of it - * are accessible at any time.
    + * are accessible at any time.
    * AudioBuffers are useful for short sounds, like effects, etc. * * @author Kirill Vainer @@ -51,14 +51,15 @@ public class AudioBuffer extends AudioData { */ protected ByteBuffer audioData; - public AudioBuffer(){ + public AudioBuffer() { super(); } - - protected AudioBuffer(int id){ + + protected AudioBuffer(int id) { super(id); } + @Override public DataType getDataType() { return DataType.Buffer; } @@ -67,7 +68,8 @@ public DataType getDataType() { * @return The duration of the audio in seconds. It is expected * that audio is uncompressed. */ - public float getDuration(){ + @Override + public float getDuration() { int bytesPerSec = (bitsPerSample / 8) * channels * sampleRate; if (audioData != null) return (float) audioData.limit() / bytesPerSec; @@ -76,17 +78,24 @@ public float getDuration(){ } @Override - public String toString(){ - return getClass().getSimpleName() + - "[id="+id+", ch="+channels+", bits="+bitsPerSample + - ", rate="+sampleRate+", duration="+getDuration()+"]"; + public String toString() { + return getClass().getSimpleName() + + "[id=" + id + ", ch=" + channels + ", bits=" + bitsPerSample + + ", rate=" + sampleRate + ", duration=" + getDuration() + "]"; } /** * Update the data in the buffer with new data. - * @param data + * + * @param data the audio data provided (not null, direct, alias created) + * @throws IllegalArgumentException if the provided buffer is not a direct buffer */ - public void updateData(ByteBuffer data){ + public void updateData(ByteBuffer data) { + if (!data.isDirect()) { + throw new IllegalArgumentException( + "Currently only direct buffers are allowed"); + } + this.audioData = data; updateNeeded = true; } @@ -94,10 +103,11 @@ public void updateData(ByteBuffer data){ /** * @return The buffered audio data. */ - public ByteBuffer getData(){ + public ByteBuffer getData() { return audioData; } + @Override public void resetObject() { id = -1; setUpdateNeeded(); @@ -109,10 +119,10 @@ protected void deleteNativeBuffers() { BufferUtils.destroyDirectBuffer(audioData); } } - + @Override public void deleteObject(Object rendererObject) { - ((AudioRenderer)rendererObject).deleteAudioData(this); + ((AudioRenderer) rendererObject).deleteAudioData(this); } @Override @@ -122,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/AudioContext.java b/jme3-core/src/main/java/com/jme3/audio/AudioContext.java index 5bdad3b4bf..a819b93351 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioContext.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,12 +38,18 @@ */ public class AudioContext { - private static ThreadLocal audioRenderer = new ThreadLocal(); - - public static void setAudioRenderer( AudioRenderer ar ) { - audioRenderer.set(ar); + final private static ThreadLocal audioRenderer = new ThreadLocal(); + + /** + * A private constructor to inhibit instantiation of this class. + */ + private AudioContext() { + } + + public static void setAudioRenderer(AudioRenderer ar) { + audioRenderer.set(ar); } - + public static AudioRenderer getAudioRenderer() { return audioRenderer.get(); } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioData.java b/jme3-core/src/main/java/com/jme3/audio/AudioData.java index 750aa1f842..6a0f1953aa 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioData.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioData.java @@ -51,15 +51,15 @@ public enum DataType { Buffer, Stream } - - public AudioData(){ + + public AudioData() { super(); } - protected AudioData(int id){ + protected AudioData(int id) { super(id); } - + /** * @return The data type, either Buffer or Stream. */ @@ -69,7 +69,7 @@ protected AudioData(int id){ * @return the duration in seconds of the audio clip. */ public abstract float getDuration(); - + /** * @return Bits per single sample from a channel. */ @@ -92,12 +92,12 @@ public int getSampleRate() { } /** - * Setup the format of the audio data. + * Sets the format of the audio data. * @param channels # of channels, 1 = mono, 2 = stereo * @param bitsPerSample Bits per sample, e.g 8 bits, 16 bits. * @param sampleRate Sample rate, 44100, 22050, etc. */ - public void setupFormat(int channels, int bitsPerSample, int sampleRate){ + public void setupFormat(int channels, int bitsPerSample, int sampleRate) { if (id != -1) throw new IllegalStateException("Already set up"); diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioKey.java b/jme3-core/src/main/java/com/jme3/audio/AudioKey.java index de937eb69d..6e4ee356c8 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioKey.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioKey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,47 +53,45 @@ public class AudioKey extends AssetKey { /** * Create a new AudioKey. - * + * * @param name Name of the asset - * @param stream If true, the audio will be streamed from harddrive, - * otherwise it will be buffered entirely and then played. + * @param stream If true, the audio will be streamed. + * Otherwise, it will be buffered entirely and then played. * @param streamCache If stream is true, then this specifies if * the stream cache is used. When enabled, the audio stream will - * be read entirely but not decoded, allowing features such as + * be read entirely but not decoded, allowing features such as * seeking, determining duration and looping. */ - public AudioKey(String name, boolean stream, boolean streamCache){ + public AudioKey(String name, boolean stream, boolean streamCache) { this(name, stream); this.streamCache = streamCache; } - + /** * Create a new AudioKey * * @param name Name of the asset - * @param stream If true, the audio will be streamed from harddrive, - * otherwise it will be buffered entirely and then played. + * @param stream If true, the audio will be streamed. + * Otherwise, it will be buffered entirely and then played. */ - public AudioKey(String name, boolean stream){ + public AudioKey(String name, boolean stream) { super(name); this.stream = stream; } - public AudioKey(String name){ + public AudioKey(String name) { super(name); this.stream = false; } - public AudioKey(){ + public AudioKey() { } @Override - public String toString(){ - return name + (stream ? - (streamCache ? - " (Stream/Cache)" : - " (Stream)") : - " (Buffer)"); + public String toString() { + return name + (stream + ? (streamCache ? " (Stream/Cache)" : " (Stream)") + : " (Buffer)"); } /** @@ -103,15 +101,17 @@ public String toString(){ public boolean isStream() { return stream; } - + /** - * Specifies if the stream cache is used. - * + * Specifies if the stream cache is used. + * * When enabled, the audio stream will - * be read entirely but not decoded, allowing features such as + * be read entirely but not decoded, allowing features such as * seeking, looping and determining duration. + * + * @return true if stream cache is in use, otherwise false */ - public boolean useStreamCache(){ + public boolean useStreamCache() { return streamCache; } @@ -155,14 +155,14 @@ public int hashCode() { hash = 67 * hash + (this.streamCache ? 1 : 0); return hash; } - + @Override public Class getProcessorType() { return null; } - + @Override - public void write(JmeExporter ex) throws IOException{ + public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(stream, "do_stream", false); @@ -170,11 +170,10 @@ public void write(JmeExporter ex) throws IOException{ } @Override - public void read(JmeImporter im) throws IOException{ + public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); stream = ic.readBoolean("do_stream", false); streamCache = ic.readBoolean("use_stream_cache", false); } - } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java index fead019aea..d9b9e8cd2c 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioNode.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2016, 2018-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,7 @@ * An AudioNode is either positional or ambient, with positional being the * default. Once a positional node is attached to the scene, its location and * velocity relative to the {@link Listener} affect how it sounds when played. - * Positional nodes can only play monoaural (single-channel) assets, not stereo + * Positional nodes can only play monaural (single-channel) assets, not stereo * ones. * * An ambient AudioNode plays in "headspace", meaning that the node's location @@ -60,14 +60,14 @@ * play stereo assets. * * The "positional" property of an AudioNode can be set via - * {@link AudioNode#setPositional(boolean) }. + * {@link AudioNode#setPositional(boolean)}. * * @author normenhansen * @author Kirill Vainer */ public class AudioNode extends Node implements AudioSource { - //Version #1 : AudioKey is now stored into "audio_key" instead of "key" + // Version #1 : AudioKey is now stored into "audio_key" instead of "key" public static final int SAVABLE_VERSION = 1; protected boolean loop = false; protected float volume = 1; @@ -137,8 +137,9 @@ public AudioNode(AudioData audioData, AudioKey audioKey) { * Creates a new AudioNode with the given audio file. * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file - * @param type The type. If {@link com.jme3.audio.AudioData.DataType}.Stream, the audio will be streamed gradually from disk, - * otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer) + * @param type The type. If {@link com.jme3.audio.AudioData.DataType}.Stream, + * the audio will be streamed gradually from disk, + * otherwise it will be buffered ({@link com.jme3.audio.AudioData.DataType}.Buffer) */ public AudioNode(AssetManager assetManager, String name, DataType type) { this(assetManager, name, type == DataType.Stream, true); @@ -150,18 +151,19 @@ public AudioNode(AssetManager assetManager, String name, DataType type) { * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file * @param stream If true, the audio will be streamed gradually from disk, - * otherwise, it will be buffered. + * otherwise, it will be buffered. * @param streamCache If stream is also true, then this specifies if - * the stream cache is used. When enabled, the audio stream will - * be read entirely but not decoded, allowing features such as - * seeking, looping and determining duration. + * the stream cache is used. When enabled, the audio stream will + * be read entirely but not decoded, allowing features such as + * seeking, looping and determining duration. * - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, + * com.jme3.audio.AudioData.DataType)} instead */ @Deprecated public AudioNode(AssetManager assetManager, String name, boolean stream, boolean streamCache) { this.audioKey = new AudioKey(name, stream, streamCache); - this.data = (AudioData) assetManager.loadAsset(audioKey); + this.data = assetManager.loadAsset(audioKey); } /** @@ -170,9 +172,10 @@ public AudioNode(AssetManager assetManager, String name, boolean stream, boolean * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file * @param stream If true, the audio will be streamed gradually from disk, - * otherwise, it will be buffered. + * otherwise, it will be buffered. * - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType)} instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, + * com.jme3.audio.AudioData.DataType)} instead */ @Deprecated public AudioNode(AssetManager assetManager, String name, boolean stream) { @@ -198,7 +201,8 @@ public AudioNode(AudioRenderer audioRenderer, AssetManager assetManager, String * * @param assetManager The asset manager to use to load the audio file * @param name The filename of the audio file - * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, com.jme3.audio.AudioData.DataType) } instead + * @deprecated Use {@link AudioNode#AudioNode(com.jme3.asset.AssetManager, java.lang.String, + * com.jme3.audio.AudioData.DataType) } instead */ @Deprecated public AudioNode(AssetManager assetManager, String name) { @@ -207,15 +211,16 @@ public AudioNode(AssetManager assetManager, String name) { protected AudioRenderer getRenderer() { AudioRenderer result = AudioContext.getAudioRenderer(); - if( result == null ) - throw new IllegalStateException( "No audio renderer available, make sure call is being performed on render thread." ); + if (result == null) + throw new IllegalStateException( + "No audio renderer available, make sure call is being performed on render thread."); return result; } /** * Start playing the audio. */ - public void play(){ + public void play() { if (positional && data.getChannels() > 1) { throw new IllegalStateException("Only mono audio is supported for positional audio nodes"); } @@ -228,7 +233,7 @@ public void play(){ * that changes to the parameters of this AudioNode will not affect the * instances already playing. */ - public void playInstance(){ + public void playInstance() { if (positional && data.getChannels() > 1) { throw new IllegalStateException("Only mono audio is supported for positional audio nodes"); } @@ -238,20 +243,21 @@ public void playInstance(){ /** * Stop playing the audio that was started with {@link AudioNode#play() }. */ - public void stop(){ + public void stop() { getRenderer().stopSource(this); } /** * Pause the audio that was started with {@link AudioNode#play() }. */ - public void pause(){ + public void pause() { getRenderer().pauseSource(this); } /** * Do not use. */ + @Override public final void setChannel(int channel) { if (status != AudioSource.Status.Stopped) { throw new IllegalStateException("Can only set source id when stopped"); @@ -263,6 +269,7 @@ public final void setChannel(int channel) { /** * Do not use. */ + @Override public int getChannel() { return channel; } @@ -271,6 +278,7 @@ public int getChannel() { * @return The {#link Filter dry filter} that is set. * @see AudioNode#setDryFilter(com.jme3.audio.Filter) */ + @Override public Filter getDryFilter() { return dryFilter; } @@ -309,12 +317,17 @@ public void setAudioData(AudioData audioData, AudioKey audioKey) { data = audioData; this.audioKey = audioKey; } + + public AudioKey getAudioKey() { + return audioKey; + } /** * @return The {@link AudioData} set previously with * {@link AudioNode#setAudioData(com.jme3.audio.AudioData, com.jme3.audio.AudioKey) } * or any of the constructors that initialize the audio data. */ + @Override public AudioData getAudioData() { return data; } @@ -324,13 +337,17 @@ public AudioData getAudioData() { * The status will be changed when either the {@link AudioNode#play() } * or {@link AudioNode#stop() } methods are called. */ + @Override public AudioSource.Status getStatus() { return status; } /** * Do not use. + * + * @param status the desired status */ + @Override public final void setStatus(AudioSource.Status status) { this.status = status; } @@ -353,6 +370,7 @@ public DataType getType() { * otherwise, false. * @see AudioNode#setLooping(boolean) */ + @Override public boolean isLooping() { return loop; } @@ -373,6 +391,7 @@ public void setLooping(boolean loop) { * * @see AudioNode#setPitch(float) */ + @Override public float getPitch() { return pitch; } @@ -399,6 +418,7 @@ public void setPitch(float pitch) { * * @see AudioNode#setVolume(float) */ + @Override public float getVolume() { return volume; } @@ -424,6 +444,7 @@ public void setVolume(float volume) { /** * @return the time offset in the sound sample when to start playing. */ + @Override public float getTimeOffset() { return timeOffset; } @@ -442,7 +463,7 @@ public void setTimeOffset(float timeOffset) { this.timeOffset = timeOffset; if (data instanceof AudioStream) { ((AudioStream) data).setTime(timeOffset); - }else if(status == AudioSource.Status.Playing){ + } else if (status == AudioSource.Status.Playing) { stop(); play(); } @@ -456,6 +477,7 @@ public float getPlaybackTime() { return 0; } + @Override public Vector3f getPosition() { return getWorldTranslation(); } @@ -465,6 +487,7 @@ public Vector3f getPosition() { * * @see AudioNode#setVelocity(com.jme3.math.Vector3f) */ + @Override public Vector3f getVelocity() { return velocity; } @@ -487,6 +510,7 @@ public void setVelocity(Vector3f velocity) { * * @see AudioNode#setReverbEnabled(boolean) */ + @Override public boolean isReverbEnabled() { return reverbEnabled; } @@ -513,13 +537,14 @@ public void setReverbEnabled(boolean reverbEnabled) { * * @see AudioNode#setReverbFilter(com.jme3.audio.Filter) */ + @Override public Filter getReverbFilter() { return reverbFilter; } /** * Set the reverb filter for this audio node. - *
    + *
    * The reverb filter will influence the reverberations * of the audio node playing. This only has an effect if * reverb is enabled. @@ -534,10 +559,11 @@ public void setReverbFilter(Filter reverbFilter) { } /** - * @return Max distance for this audio node. + * @return Maximum distance for this audio node. * * @see AudioNode#setMaxDistance(float) */ + @Override public float getMaxDistance() { return maxDistance; } @@ -545,7 +571,7 @@ public float getMaxDistance() { /** * Set the maximum distance for the attenuation of the audio node. * Does nothing if the audio node is not positional. - *
    + *
    * The maximum distance is the distance beyond which the audio * node will no longer be attenuated. Normal attenuation is logarithmic * from refDistance (it reduces by half when the distance doubles). @@ -572,6 +598,7 @@ public void setMaxDistance(float maxDistance) { * * @see AudioNode#setRefDistance(float) */ + @Override public float getRefDistance() { return refDistance; } @@ -579,12 +606,12 @@ public float getRefDistance() { /** * Set the reference playing distance for the audio node. * Does nothing if the audio node is not positional. - *
    + *
    * The reference playing distance is the distance at which the * audio node will be exactly half of its volume. * * @param refDistance The reference playing distance. - * @throws IllegalArgumentException If refDistance is negative + * @throws IllegalArgumentException If refDistance is negative */ public void setRefDistance(float refDistance) { if (refDistance < 0) { @@ -601,6 +628,7 @@ public void setRefDistance(float refDistance) { * * @see AudioNode#setDirectional(boolean) */ + @Override public boolean isDirectional() { return directional; } @@ -608,7 +636,7 @@ public boolean isDirectional() { /** * Set the audio node to be directional. * Does nothing if the audio node is not positional. - *
    + *
    * After setting directional, you should call * {@link AudioNode#setDirection(com.jme3.math.Vector3f) } * to set the audio node's direction. @@ -626,6 +654,7 @@ public void setDirectional(boolean directional) { * * @see AudioNode#setDirection(com.jme3.math.Vector3f) */ + @Override public Vector3f getDirection() { return direction; } @@ -634,7 +663,7 @@ public Vector3f getDirection() { * Set the direction of this audio node. * Does nothing if the audio node is not directional. * - * @param direction + * @param direction a direction vector (alias created) * @see AudioNode#setDirectional(boolean) */ public void setDirection(Vector3f direction) { @@ -648,6 +677,7 @@ public void setDirection(Vector3f direction) { * * @see AudioNode#setInnerAngle(float) */ + @Override public float getInnerAngle() { return innerAngle; } @@ -669,6 +699,7 @@ public void setInnerAngle(float innerAngle) { * * @see AudioNode#setOuterAngle(float) */ + @Override public float getOuterAngle() { return outerAngle; } @@ -690,6 +721,7 @@ public void setOuterAngle(float outerAngle) { * * @see AudioNode#setPositional(boolean) */ + @Override public boolean isPositional() { return positional; } @@ -726,12 +758,14 @@ public void updateLogicalState(float tpf) { @Override public void updateGeometricState() { super.updateGeometricState(); - if (channel < 0) return; + if (channel < 0) + return; Vector3f currentWorldTranslation = worldTransform.getTranslation(); if (!previousWorldTranslation.equals(currentWorldTranslation)) { getRenderer().updateSourceParam(this, AudioParam.Position); if (velocityFromTranslation && !Float.isNaN(previousWorldTranslation.x)) { - velocity.set(currentWorldTranslation).subtractLocal(previousWorldTranslation).multLocal(1f / lastTpf); + velocity.set(currentWorldTranslation) + .subtractLocal(previousWorldTranslation).multLocal(1f / lastTpf); getRenderer().updateSourceParam(this, AudioParam.Velocity); } previousWorldTranslation.set(currentWorldTranslation); @@ -739,21 +773,21 @@ public void updateGeometricState() { } @Override - public AudioNode clone(){ + public AudioNode clone() { AudioNode clone = (AudioNode) super.clone(); return clone; } /** - * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { - super.cloneFields(cloner, original); + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); - this.direction=cloner.clone(direction); - this.velocity=velocityFromTranslation?new Vector3f():cloner.clone(velocity); - this.previousWorldTranslation=Vector3f.NAN.clone(); + this.direction = cloner.clone(direction); + this.velocity = velocityFromTranslation ? new Vector3f() : cloner.clone(velocity); + this.previousWorldTranslation = Vector3f.NAN.clone(); // Change in behavior: the filters were not cloned before meaning // that two cloned audio nodes would share the same filter instance. @@ -762,7 +796,7 @@ public void cloneFields( Cloner cloner, Object original ) { // a filter change from one AudioNode when a different AudioNode's // filter attributes are updated. // Plus if they disable and re-enable the thing using the filter then - // the settings get reapplied and it might be surprising to have them + // the settings get reapplied, and it might be surprising to have them // suddenly be strange. // ...so I'll clone them. -pspeed this.dryFilter = cloner.clone(dryFilter); @@ -803,9 +837,9 @@ public void read(JmeImporter im) throws IOException { // NOTE: In previous versions of jME3, audioKey was actually // written with the name "key". This has been changed // to "audio_key" in case Spatial's key will be written as "key". - if (ic.getSavableVersion(AudioNode.class) == 0){ + if (ic.getSavableVersion(AudioNode.class) == 0) { audioKey = (AudioKey) ic.readSavable("key", null); - }else{ + } else { audioKey = (AudioKey) ic.readSavable("audio_key", null); } @@ -832,8 +866,9 @@ public void read(JmeImporter im) throws IOException { if (audioKey != null) { try { data = im.getAssetManager().loadAsset(audioKey); - } catch (AssetNotFoundException ex){ - Logger.getLogger(AudioNode.class.getName()).log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key}); + } catch (AssetNotFoundException ex) { + Logger.getLogger(AudioNode.class.getName()) + .log(Level.FINE, "Cannot locate {0} for audio node {1}", new Object[]{audioKey, key}); data = PlaceholderAssets.getPlaceholderAudio(); } } diff --git a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java index de2540022c..0f10b4ae65 100644 --- a/jme3-core/src/main/java/com/jme3/audio/AudioSource.java +++ b/jme3-core/src/main/java/com/jme3/audio/AudioSource.java @@ -1,6 +1,33 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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; @@ -37,11 +64,15 @@ public enum Status { /** * Do not use. + * + * @param channel the desired channel index, or -1 if stopped */ public void setChannel(int channel); /** * Do not use. + * + * @return the channel index, or -1 if stopped */ public int getChannel(); @@ -60,6 +91,8 @@ public enum Status { /** * Do not use. + * + * @param status the desired Status */ public void setStatus(Status status); @@ -128,7 +161,7 @@ public enum Status { public Filter getReverbFilter(); /** - * @return Max distance for this audio source. + * @return Maximum distance for this audio source. * * @see AudioNode#setMaxDistance(float) */ 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 1047c673bc..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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -115,6 +115,7 @@ public int readSamples(byte[] buf) { return readSamples(buf, 0, buf.length); } + @Override public float getDuration() { return duration; } @@ -178,9 +179,8 @@ public boolean isEOF() { /** * Closes the stream, releasing all data relating to it. * Reading from the stream will return eof. - * - * @throws IOException */ + @Override public void close() { if (in != null && open) { try { @@ -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/BandPassFilter.java b/jme3-core/src/main/java/com/jme3/audio/BandPassFilter.java new file mode 100644 index 0000000000..950371278c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/BandPassFilter.java @@ -0,0 +1,152 @@ +/* + * Copyright (c) 2009-2025 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; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.util.NativeObject; + +import java.io.IOException; + +/** + * Represents an OpenAL EFX Band-Pass Filter. + */ +public class BandPassFilter extends Filter { + + // Default values based on OpenAL EFX specification defaults + protected float volume = 1.0f; + protected float highFreqVolume = 1.0f; + protected float lowFreqVolume = 1.0f; + + /** + * Constructs a band-pass filter with default settings. + * Required for jME deserialization + */ + public BandPassFilter() {} + + protected BandPassFilter(int id) { + super(id); + } + + public BandPassFilter(float volume, float highFreqVolume, float lowFreqVolume) { + super(); + setVolume(volume); + setHighFreqVolume(highFreqVolume); + setLowFreqVolume(lowFreqVolume); + } + + public float getVolume() { + return volume; + } + + /** + * Sets the overall gain of the Band-Pass filter. + * + * @param volume The gain value (0.0 to 1.0). + */ + public void setVolume(float volume) { + if (volume < 0 || volume > 1) + throw new IllegalArgumentException("Volume must be between 0 and 1"); + + this.volume = volume; + this.updateNeeded = true; + } + + public float getHighFreqVolume() { + return highFreqVolume; + } + + /** + * Sets the gain at high frequencies for the Band-Pass filter. + * + * @param highFreqVolume The high-frequency gain value (0.0 to 1.0). + */ + public void setHighFreqVolume(float highFreqVolume) { + if (highFreqVolume < 0 || highFreqVolume > 1) + throw new IllegalArgumentException("High freq volume must be between 0 and 1"); + + this.highFreqVolume = highFreqVolume; + this.updateNeeded = true; + } + + public float getLowFreqVolume() { + return lowFreqVolume; + } + + /** + * Sets the gain at low frequencies for the Band-Pass filter. + * + * @param lowFreqVolume The low-frequency gain value (0.0 to 1.0). + */ + public void setLowFreqVolume(float lowFreqVolume) { + if (lowFreqVolume < 0 || lowFreqVolume > 1) + throw new IllegalArgumentException("Low freq volume must be between 0 and 1"); + + this.lowFreqVolume = lowFreqVolume; + this.updateNeeded = true; + } + + @Override + public NativeObject createDestructableClone() { + return new BandPassFilter(this.id); + } + + /** + * Retrieves a unique identifier for this filter. Used internally for native object management. + * + * @return a unique long identifier. + */ + @Override + public long getUniqueId() { + return ((long) OBJTYPE_FILTER << 32) | (0xffffffffL & (long) id); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(this.volume, "volume", 1f); + oc.write(this.lowFreqVolume, "lf_volume", 1f); + oc.write(this.highFreqVolume, "hf_volume", 1f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + this.volume = ic.readFloat("volume", 1f); + this.lowFreqVolume = ic.readFloat("lf_volume", 1f); + this.highFreqVolume = ic.readFloat("hf_volume", 1f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/Environment.java b/jme3-core/src/main/java/com/jme3/audio/Environment.java index 72327ef00c..05bec8770c 100644 --- a/jme3-core/src/main/java/com/jme3/audio/Environment.java +++ b/jme3-core/src/main/java/com/jme3/audio/Environment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,49 +34,80 @@ import com.jme3.math.FastMath; /** - * Audio environment, for reverb effects. + * Represents an audio environment, primarily used to define reverb effects. + * This class provides parameters that correspond to the properties controllable + * through the OpenAL EFX (Environmental Effects Extension) library. + * By adjusting these parameters, developers can simulate various acoustic spaces + * like rooms, caves, and concert halls, adding depth and realism to the audio experience. + * * @author Kirill */ public class Environment { - private float airAbsorbGainHf = 0.99426f; + /** High-frequency air absorption gain (0.0f to 1.0f). */ + private float airAbsorbGainHf = 0.99426f; + /** Factor controlling room effect rolloff with distance. */ private float roomRolloffFactor = 0; - - private float decayTime = 1.49f; - private float decayHFRatio = 0.54f; - - private float density = 1.0f; - private float diffusion = 0.3f; - - private float gain = 0.316f; - private float gainHf = 0.022f; - - private float lateReverbDelay = 0.088f; - private float lateReverbGain = 0.768f; - - private float reflectDelay = 0.162f; - private float reflectGain = 0.052f; - - private boolean decayHfLimit = true; - - public static final Environment Garage, Dungeon, Cavern, AcousticLab, Closet; - - static { - Garage = new Environment(1, 1, 1, 1, .9f, .5f, .751f, .0039f, .661f, .0137f); - Dungeon = new Environment(.75f, 1, 1, .75f, 1.6f, 1, 0.95f, 0.0026f, 0.93f, 0.0103f); - Cavern = new Environment(.5f, 1, 1, .5f, 2.25f, 1, .908f, .0103f, .93f, .041f); - AcousticLab = new Environment(.5f, 1, 1, 1, .28f, 1, .87f, .002f, .81f, .008f); - Closet = new Environment(1, 1, 1, 1, .15f, 1, .6f, .0025f, .5f, .0006f); - } - - private static float eaxDbToAmp(float eaxDb){ + /** Overall decay time of the reverberation (in seconds). */ + private float decayTime = 1.49f; + /** Ratio of high-frequency decay time to overall decay time (0.0f to 1.0f). */ + private float decayHFRatio = 0.54f; + /** Density of the medium affecting reverb smoothness (0.0f to 1.0f). */ + private float density = 1.0f; + /** Diffusion of reflections affecting echo distinctness (0.0f to 1.0f). */ + private float diffusion = 0.3f; + /** Overall gain of the environment effect (linear scale). */ + private float gain = 0.316f; + /** High-frequency gain of the environment effect (linear scale). */ + private float gainHf = 0.022f; + /** Delay time for late reverberation relative to early reflections (in seconds). */ + private float lateReverbDelay = 0.088f; + /** Gain of the late reverberation (linear scale). */ + private float lateReverbGain = 0.768f; + /** Delay time for the initial reflections (in seconds). */ + private float reflectDelay = 0.162f; + /** Gain of the initial reflections (linear scale). */ + private float reflectGain = 0.052f; + /** Flag limiting high-frequency decay by the overall decay time. */ + private boolean decayHfLimit = true; + + public static final Environment Garage = new Environment( + 1, 1, 1, 1, .9f, .5f, .751f, .0039f, .661f, .0137f); + public static final Environment Dungeon = new Environment( + .75f, 1, 1, .75f, 1.6f, 1, 0.95f, 0.0026f, 0.93f, 0.0103f); + public static final Environment Cavern = new Environment( + .5f, 1, 1, .5f, 2.25f, 1, .908f, .0103f, .93f, .041f); + public static final Environment AcousticLab = new Environment( + .5f, 1, 1, 1, .28f, 1, .87f, .002f, .81f, .008f); + public static final Environment Closet = new Environment( + 1, 1, 1, 1, .15f, 1, .6f, .0025f, .5f, .0006f); + + /** + * Utility method to convert an EAX decibel value to an amplitude factor. + * EAX often expresses gain and attenuation in decibels scaled by 1000. + * This method performs the reverse of that conversion to obtain a linear + * amplitude value suitable for OpenAL. + * + * @param eaxDb The EAX decibel value (scaled by 1000). + * @return The corresponding amplitude factor. + */ + private static float eaxDbToAmp(float eaxDb) { float dB = eaxDb / 2000f; return FastMath.pow(10f, dB); } - public Environment(){ + /** + * Constructs a new, default {@code Environment}. The default values are + * typically chosen to represent a neutral or common acoustic space. + */ + public Environment() { } + /** + * Creates a new {@code Environment} as a copy of the provided {@code Environment}. + * + * @param source The {@code Environment} to copy the settings from. + */ public Environment(Environment source) { this.airAbsorbGainHf = source.airAbsorbGainHf; this.roomRolloffFactor = source.roomRolloffFactor; @@ -93,9 +124,24 @@ public Environment(Environment source) { this.decayHfLimit = source.decayHfLimit; } + /** + * Creates a new {@code Environment} with the specified parameters. These parameters + * directly influence the properties of the reverb effect as managed by OpenAL EFX. + * + * @param density The density of the medium. + * @param diffusion The diffusion of the reflections. + * @param gain Overall gain applied to the environment effect. + * @param gainHf High-frequency gain applied to the environment effect. + * @param decayTime The overall decay time of the reflected sound. + * @param decayHf Ratio of high-frequency decay time to the overall decay time. + * @param reflectGain Gain applied to the initial reflections. + * @param reflectDelay Delay time for the initial reflections. + * @param lateGain Gain applied to the late reverberation. + * @param lateDelay Delay time for the late reverberation. + */ public Environment(float density, float diffusion, float gain, float gainHf, - float decayTime, float decayHf, float reflGain, - float reflDelay, float lateGain, float lateDelay){ + float decayTime, float decayHf, float reflectGain, float reflectDelay, + float lateGain, float lateDelay) { this.decayTime = decayTime; this.decayHFRatio = decayHf; this.density = density; @@ -104,11 +150,21 @@ public Environment(float density, float diffusion, float gain, float gainHf, this.gainHf = gainHf; this.lateReverbDelay = lateDelay; this.lateReverbGain = lateGain; - this.reflectDelay = reflDelay; - this.reflectGain = reflGain; + this.reflectDelay = reflectDelay; + this.reflectGain = reflectGain; } - public Environment(float[] e){ + /** + * Creates a new {@code Environment} by interpreting an array of 28 float values + * as an EAX preset. This constructor attempts to map the EAX preset values to + * the corresponding OpenAL EFX parameters. Note that not all EAX parameters + * have a direct equivalent in standard OpenAL EFX, so some values might be + * approximated or ignored. + * + * @param e An array of 28 float values representing an EAX preset. + * @throws IllegalArgumentException If the provided array does not have a length of 28. + */ + public Environment(float[] e) { if (e.length != 28) throw new IllegalArgumentException("Not an EAX preset"); @@ -252,4 +308,73 @@ public float getRoomRolloffFactor() { public void setRoomRolloffFactor(float roomRolloffFactor) { this.roomRolloffFactor = roomRolloffFactor; } + + @Override + public boolean equals(Object obj) { + + if (!(obj instanceof Environment)) + return false; + + if (obj == this) + return true; + + Environment other = (Environment) obj; + float epsilon = 1e-6f; + + float[] thisFloats = { + this.airAbsorbGainHf, + this.decayHFRatio, + this.decayTime, + this.density, + this.diffusion, + this.gain, + this.gainHf, + this.lateReverbDelay, + this.lateReverbGain, + this.reflectDelay, + this.reflectGain, + this.roomRolloffFactor + }; + + float[] otherFloats = { + other.airAbsorbGainHf, + other.decayHFRatio, + other.decayTime, + other.density, + other.diffusion, + other.gain, + other.gainHf, + other.lateReverbDelay, + other.lateReverbGain, + other.reflectDelay, + other.reflectGain, + other.roomRolloffFactor + }; + + for (int i = 0; i < thisFloats.length; i++) { + if (Math.abs(thisFloats[i] - otherFloats[i]) >= epsilon) { + return false; + } + } + + return this.decayHfLimit == other.decayHfLimit; + } + + @Override + public int hashCode() { + int result = (airAbsorbGainHf != +0.0f ? Float.floatToIntBits(airAbsorbGainHf) : 0); + result = 31 * result + (roomRolloffFactor != +0.0f ? Float.floatToIntBits(roomRolloffFactor) : 0); + result = 31 * result + (decayTime != +0.0f ? Float.floatToIntBits(decayTime) : 0); + result = 31 * result + (decayHFRatio != +0.0f ? Float.floatToIntBits(decayHFRatio) : 0); + result = 31 * result + (density != +0.0f ? Float.floatToIntBits(density) : 0); + result = 31 * result + (diffusion != +0.0f ? Float.floatToIntBits(diffusion) : 0); + result = 31 * result + (gain != +0.0f ? Float.floatToIntBits(gain) : 0); + result = 31 * result + (gainHf != +0.0f ? Float.floatToIntBits(gainHf) : 0); + result = 31 * result + (lateReverbDelay != +0.0f ? Float.floatToIntBits(lateReverbDelay) : 0); + result = 31 * result + (lateReverbGain != +0.0f ? Float.floatToIntBits(lateReverbGain) : 0); + result = 31 * result + (reflectDelay != +0.0f ? Float.floatToIntBits(reflectDelay) : 0); + result = 31 * result + (reflectGain != +0.0f ? Float.floatToIntBits(reflectGain) : 0); + result = 31 * result + (decayHfLimit ? 1 : 0); + return result; + } } diff --git a/jme3-core/src/main/java/com/jme3/audio/Filter.java b/jme3-core/src/main/java/com/jme3/audio/Filter.java index b46270c11e..86bf987cd6 100644 --- a/jme3-core/src/main/java/com/jme3/audio/Filter.java +++ b/jme3-core/src/main/java/com/jme3/audio/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,24 +35,45 @@ import com.jme3.export.JmeImporter; import com.jme3.export.Savable; import com.jme3.util.NativeObject; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; + import java.io.IOException; -public abstract class Filter extends NativeObject implements Savable { +public abstract class Filter extends NativeObject implements Savable, JmeCloneable { - public Filter(){ + public Filter() { super(); } - - protected Filter(int id){ + + protected Filter(int id) { super(id); } - + + @Override public void write(JmeExporter ex) throws IOException { - // nothing to save + // no-op } + @Override public void read(JmeImporter im) throws IOException { - // nothing to read + // no-op + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public Object jmeClone() { + return super.clone(); + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + // no-op } @Override @@ -63,7 +84,7 @@ public void resetObject() { @Override public void deleteObject(Object rendererObject) { - ((AudioRenderer)rendererObject).deleteFilter(this); + ((AudioRenderer) rendererObject).deleteFilter(this); } @Override diff --git a/jme3-core/src/main/java/com/jme3/audio/HighPassFilter.java b/jme3-core/src/main/java/com/jme3/audio/HighPassFilter.java new file mode 100644 index 0000000000..f3838abc0e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/HighPassFilter.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2009-2025 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; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.util.NativeObject; + +import java.io.IOException; + +/** + * Represents an OpenAL EFX High-Pass Filter. + */ +public class HighPassFilter extends Filter { + + // Default values based on OpenAL EFX specification defaults + protected float volume = 1.0f; + protected float lowFreqVolume = 1.0f; + + /** + * Constructs a high-pass filter with default settings. + * Required for jME deserialization + */ + public HighPassFilter(){} + + protected HighPassFilter(int id) { + super(id); + } + + public HighPassFilter(float volume, float lowFreqVolume) { + super(); + setVolume(volume); + setLowFreqVolume(lowFreqVolume); + } + + public float getVolume() { + return volume; + } + + /** + * Sets the gain of the High-Pass filter. + * + * @param volume The gain value (0.0 to 1.0). + */ + public void setVolume(float volume) { + if (volume < 0 || volume > 1) + throw new IllegalArgumentException("Volume must be between 0 and 1"); + + this.volume = volume; + this.updateNeeded = true; + } + + public float getLowFreqVolume() { + return lowFreqVolume; + } + + /** + * Sets the gain at low frequencies for the High-Pass filter. + * + * @param lowFreqVolume The low-frequency gain value (0.0 to 1.0). + */ + public void setLowFreqVolume(float lowFreqVolume) { + if (lowFreqVolume < 0 || lowFreqVolume > 1) + throw new IllegalArgumentException("Low freq volume must be between 0 and 1"); + + this.lowFreqVolume = lowFreqVolume; + this.updateNeeded = true; + } + + @Override + public NativeObject createDestructableClone() { + return new HighPassFilter(this.id); + } + + /** + * Retrieves a unique identifier for this filter. Used internally for native object management. + * + * @return a unique long identifier. + */ + @Override + public long getUniqueId() { + return ((long) OBJTYPE_FILTER << 32) | (0xffffffffL & (long) id); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(this.volume, "volume", 1f); + oc.write(this.lowFreqVolume, "lf_volume", 1f); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + this.volume = ic.readFloat("volume", 1f); + this.lowFreqVolume = ic.readFloat("lf_volume", 1f); + } +} diff --git a/jme3-core/src/main/java/com/jme3/audio/Listener.java b/jme3-core/src/main/java/com/jme3/audio/Listener.java index afd08a7303..d582df33cd 100644 --- a/jme3-core/src/main/java/com/jme3/audio/Listener.java +++ b/jme3-core/src/main/java/com/jme3/audio/Listener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,11 @@ import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; +/** + * Represents the audio listener in the 3D sound scene. + * The listener defines the point of view from which sound is heard, + * influencing spatial audio effects like panning and Doppler shift. + */ public class Listener { private final Vector3f location; @@ -42,72 +47,159 @@ public class Listener { private float volume = 1; private AudioRenderer renderer; - public Listener(){ + /** + * Constructs a new {@code Listener} with default parameters. + */ + public Listener() { location = new Vector3f(); velocity = new Vector3f(); rotation = new Quaternion(); } - - public Listener(Listener source){ - location = source.location.clone(); - velocity = source.velocity.clone(); - rotation = source.rotation.clone(); - volume = source.volume; + + /** + * Constructs a new {@code Listener} by copying the properties of another {@code Listener}. + * + * @param source The {@code Listener} to copy the properties from. + */ + public Listener(Listener source) { + this.location = source.location.clone(); + this.velocity = source.velocity.clone(); + this.rotation = source.rotation.clone(); + this.volume = source.volume; + this.renderer = source.renderer; // Note: Renderer is also copied } - public void setRenderer(AudioRenderer renderer){ + /** + * Sets the {@link AudioRenderer} associated with this listener. + * The renderer is responsible for applying the listener's parameters + * to the audio output. + * + * @param renderer The {@link AudioRenderer} to associate with. + */ + public void setRenderer(AudioRenderer renderer) { this.renderer = renderer; } + /** + * Gets the current volume of the listener. + * + * @return The current volume. + */ public float getVolume() { return volume; } + /** + * Sets the volume of the listener. + * If an {@link AudioRenderer} is set, it will be notified of the volume change. + * + * @param volume The new volume. + */ public void setVolume(float volume) { this.volume = volume; - if (renderer != null) - renderer.updateListenerParam(this, ListenerParam.Volume); + updateListenerParam(ListenerParam.Volume); } - + + /** + * Gets the current location of the listener in world space. + * + * @return The listener's location as a {@link Vector3f}. + */ public Vector3f getLocation() { return location; } + /** + * Gets the current rotation of the listener in world space. + * + * @return The listener's rotation as a {@link Quaternion}. + */ public Quaternion getRotation() { return rotation; } + /** + * Gets the current velocity of the listener. + * This is used for Doppler effect calculations. + * + * @return The listener's velocity as a {@link Vector3f}. + */ public Vector3f getVelocity() { return velocity; } - public Vector3f getLeft(){ + /** + * Gets the left direction vector of the listener. + * This vector is derived from the listener's rotation. + * + * @return The listener's left direction as a {@link Vector3f}. + */ + public Vector3f getLeft() { return rotation.getRotationColumn(0); } - public Vector3f getUp(){ + /** + * Gets the up direction vector of the listener. + * This vector is derived from the listener's rotation. + * + * @return The listener's up direction as a {@link Vector3f}. + */ + public Vector3f getUp() { return rotation.getRotationColumn(1); } - public Vector3f getDirection(){ + /** + * Gets the forward direction vector of the listener. + * This vector is derived from the listener's rotation. + * + * @return The listener's forward direction. + */ + public Vector3f getDirection() { return rotation.getRotationColumn(2); } - + + /** + * Sets the location of the listener in world space. + * If an {@link AudioRenderer} is set, it will be notified of the position change. + * + * @param location The new location of the listener. + */ public void setLocation(Vector3f location) { this.location.set(location); - if (renderer != null) - renderer.updateListenerParam(this, ListenerParam.Position); + updateListenerParam(ListenerParam.Position); } + /** + * Sets the rotation of the listener in world space. + * If an {@link AudioRenderer} is set, it will be notified of the rotation change. + * + * @param rotation The new rotation of the listener. + */ public void setRotation(Quaternion rotation) { this.rotation.set(rotation); - if (renderer != null) - renderer.updateListenerParam(this, ListenerParam.Rotation); + updateListenerParam(ListenerParam.Rotation); } + /** + * Sets the velocity of the listener. + * This is used for Doppler effect calculations. + * If an {@link AudioRenderer} is set, it will be notified of the velocity change. + * + * @param velocity The new velocity of the listener. + */ public void setVelocity(Vector3f velocity) { this.velocity.set(velocity); - if (renderer != null) - renderer.updateListenerParam(this, ListenerParam.Velocity); + updateListenerParam(ListenerParam.Velocity); + } + + /** + * Updates the associated {@link AudioRenderer} with the specified listener parameter. + * + * @param param The {@link ListenerParam} to update on the renderer. + */ + private void updateListenerParam(ListenerParam param) { + if (renderer != null) { + renderer.updateListenerParam(this, param); + } } } 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 4445c2463c..8c70490ba6 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-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,26 +36,71 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import com.jme3.util.NativeObject; + import java.io.IOException; +/** + * A filter that attenuates frequencies above a specified threshold, allowing lower + * frequencies to pass through with less attenuation. Commonly used to simulate effects + * such as muffling or underwater acoustics. + */ public class LowPassFilter extends Filter { - protected float volume, highFreqVolume; + /** + * The overall volume scaling of the filtered sound + */ + protected float volume = 1.0f; + /** + * The volume scaling of the high frequencies allowed to pass through. Valid values range + * from 0.0 to 1.0, where 0.0 completely eliminates high frequencies and 1.0 lets them pass + * through unchanged. + */ + protected float highFreqVolume = 1.0f; + + /** + * Constructs a low-pass filter with default settings. + * Required for jME deserialization. + */ + public LowPassFilter() { + super(); + } + /** + * Constructs a low-pass filter. + * + * @param volume the overall volume scaling of the filtered sound (0.0 - 1.0). + * @param highFreqVolume the volume scaling of high frequencies (0.0 - 1.0). + * @throws IllegalArgumentException if {@code volume} or {@code highFreqVolume} is out of range. + */ public LowPassFilter(float volume, float highFreqVolume) { super(); setVolume(volume); setHighFreqVolume(highFreqVolume); } - - protected LowPassFilter(int id){ + + /** + * For internal cloning + * @param id the native object ID + */ + protected LowPassFilter(int id) { super(id); } + /** + * Retrieves the current volume scaling of high frequencies. + * + * @return the high-frequency volume scaling. + */ public float getHighFreqVolume() { return highFreqVolume; } + /** + * Sets the high-frequency volume. + * + * @param highFreqVolume the new high-frequency volume scaling (0.0 - 1.0). + * @throws IllegalArgumentException if {@code highFreqVolume} is out of range. + */ public void setHighFreqVolume(float highFreqVolume) { if (highFreqVolume < 0 || highFreqVolume > 1) throw new IllegalArgumentException("High freq volume must be between 0 and 1"); @@ -64,40 +109,62 @@ public void setHighFreqVolume(float highFreqVolume) { this.updateNeeded = true; } + /** + * Retrieves the current overall volume scaling of the filtered sound. + * + * @return the overall volume scaling. + */ public float getVolume() { return volume; } + /** + * Sets the overall volume. + * + * @param volume the new overall volume scaling (0.0 - 1.0). + * @throws IllegalArgumentException if {@code volume} is out of range. + */ public void setVolume(float volume) { if (volume < 0 || volume > 1) throw new IllegalArgumentException("Volume must be between 0 and 1"); - + this.volume = volume; this.updateNeeded = true; } - public void write(JmeExporter ex) throws IOException{ + @Override + public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(volume, "volume", 0); - oc.write(highFreqVolume, "hf_volume", 0); + oc.write(volume, "volume", 1f); + oc.write(highFreqVolume, "hf_volume", 1f); } @Override - public void read(JmeImporter im) throws IOException{ + public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - volume = ic.readFloat("volume", 0); - highFreqVolume = ic.readFloat("hf_volume", 0); + volume = ic.readFloat("volume", 1f); + highFreqVolume = ic.readFloat("hf_volume", 1f); } + /** + * Creates a native object clone of this filter for internal usage. + * + * @return a new {@code LowPassFilter} instance with the same native ID. + */ @Override public NativeObject createDestructableClone() { return new LowPassFilter(id); } + /** + * Retrieves a unique identifier for this filter. Used internally for native object management. + * + * @return a unique long identifier. + */ @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/audio/openal/AL.java b/jme3-core/src/main/java/com/jme3/audio/openal/AL.java index 3805502549..1fc0331f4a 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/AL.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/AL.java @@ -45,11 +45,11 @@ public interface AL { public static final int AL_PITCH = 0x1003; /** - * Specify the current location in three dimensional space. OpenAL, like - * OpenGL, uses a right handed coordinate system, where in a frontal default + * Specify the current location in three-dimensional space. OpenAL, like + * OpenGL, uses a right-handed coordinate system, where in a frontal default * view X (thumb) points right, Y points up (index finger), and Z points - * towards the viewer/camera (middle finger). To switch from a left handed - * coordinate system, flip the sign on the Z coordinate. Listener position + * towards the viewer/camera (middle finger). To switch from a left-handed + * coordinate system, flip the sign of the Z coordinate. Listener position * is always in the world coordinate system. */ public static final int AL_POSITION = 0x1004; @@ -60,7 +60,7 @@ public interface AL { public static final int AL_DIRECTION = 0x1005; /** - * Specify the current velocity in three dimensional space. + * Specify the current velocity in three-dimensional space. */ public static final int AL_VELOCITY = 0x1006; @@ -86,7 +86,7 @@ public interface AL { */ public static final int AL_GAIN = 0x100A; - /* + /** * Indicate minimum source attenuation * Type: ALfloat * Range: [0.0 - 1.0] @@ -131,7 +131,7 @@ public interface AL { public static final int AL_SAMPLE_OFFSET = 0x1025; public static final int AL_BYTE_OFFSET = 0x1026; - /* + /** * Source type (Static, Streaming or undetermined) * Source is Static if a Buffer has been attached using AL_BUFFER * Source is Streaming if one or more Buffers have been attached using alSourceQueueBuffers @@ -235,7 +235,7 @@ public interface AL { public static final int AL_RENDERER = 0xB003; public static final int AL_EXTENSIONS = 0xB004; - /** + /* * Global tweakage. */ /** @@ -285,14 +285,16 @@ public interface AL { /** * Obtains error information. - *

    + * *

    Each detectable error is assigned a numeric code. When an error is detected by AL, a flag is set and the error code is recorded. Further errors, if they * occur, do not affect this recorded code. When alGetError is called, the code is returned and the flag is cleared, so that a further error will again * record its code. If a call to alGetError returns AL_NO_ERROR then there has been no detectable error since the last call to alGetError (or since the AL * was initialized).

    - *

    + * *

    Error codes can be mapped to strings. The alGetString function returns a pointer to a constant (literal) string that is identical to the identifier used * for the enumeration value, as defined in the specification.

    + * + * @return the error code, or AL_NO_ERROR if none */ public int alGetError(); @@ -322,7 +324,7 @@ public interface AL { /** * Sets the source state to AL_STOPPED. - *

    + * *

    alSourceStop applied to an AL_INITIAL source is a legal NOP. alSourceStop applied to a AL_PLAYING source will change its state to AL_STOPPED. The source * is exempt from processing, its current state is preserved. alSourceStop applied to a AL_PAUSED source will change its state to AL_STOPPED, with the same * consequences as on a AL_PLAYING source. alSourceStop applied to a AL_STOPPED source is a legal NOP.

    @@ -342,30 +344,35 @@ public interface AL { /** * Sets the sample data of the specified buffer. - *

    + * *

    The data specified is copied to an internal software, or if possible, hardware buffer. The implementation is free to apply decompression, conversion, * resampling, and filtering as needed.

    - *

    + * *

    8-bit data is expressed as an unsigned value over the range 0 to 255, 128 being an audio output level of zero.

    - *

    + * *

    16-bit data is expressed as a signed value over the range -32768 to 32767, 0 being an audio output level of zero. Byte order for 16-bit values is * determined by the native format of the CPU.

    - *

    + * *

    Stereo data is expressed in an interleaved format, left channel sample followed by the right channel sample.

    - *

    + * *

    Buffers containing audio data with more than one channel will be played without 3D spatialization features – these formats are normally used for * background music.

    * * @param buffer the buffer to modify. - * @param format the data format. One of:
    {@link #AL_FORMAT_MONO8 FORMAT_MONO8}{@link #AL_FORMAT_MONO16 FORMAT_MONO16}{@link #AL_FORMAT_STEREO8 FORMAT_STEREO8}{@link #AL_FORMAT_STEREO16 FORMAT_STEREO16}
    + * @param format the data format. One of: + * {@link #AL_FORMAT_MONO8 FORMAT_MONO8} + * {@link #AL_FORMAT_MONO16 FORMAT_MONO16} + * {@link #AL_FORMAT_STEREO8 FORMAT_STEREO8} + * {@link #AL_FORMAT_STEREO16 FORMAT_STEREO16} * @param data the sample data. + * @param size the length of the data (in bytes, ≥0) * @param frequency the data frequency. */ public void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency); /** * Sets the source state to AL_PLAYING. - *

    + * *

    alSourcePlay applied to an AL_INITIAL source will promote the source to AL_PLAYING, thus the data found in the buffer will be fed into the processing, * starting at the beginning. alSourcePlay applied to a AL_PLAYING source will restart the source from the beginning. It will not affect the configuration, * and will leave the source in AL_PLAYING state, but reset the sampling offset to the beginning. alSourcePlay applied to a AL_PAUSED source will resume @@ -378,7 +385,7 @@ public interface AL { /** * Sets the source state to AL_PAUSED. - *

    + * *

    alSourcePause applied to an AL_INITIAL source is a legal NOP. alSourcePause applied to a AL_PLAYING source will change its state to AL_PAUSED. The * source is exempt from processing, its current state is preserved. alSourcePause applied to a AL_PAUSED source is a legal NOP. alSourcePause applied to a * AL_STOPPED source is a legal NOP.

    @@ -391,7 +398,22 @@ public interface AL { * Sets the float value of a source parameter. * * @param source the source to modify. - * @param param the parameter to modify. One of:
    {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE}{@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE}{@link #AL_PITCH PITCH}{@link #AL_DIRECTION DIRECTION}{@link #AL_LOOPING LOOPING}{@link #AL_BUFFER BUFFER}{@link #AL_SOURCE_STATE SOURCE_STATE}
    {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN}{@link #AL_SOURCE_TYPE SOURCE_TYPE}{@link #AL_POSITION POSITION}{@link #AL_VELOCITY VELOCITY}{@link #AL_GAIN GAIN}{@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE}{@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR}
    {@link #AL_MAX_DISTANCE MAX_DISTANCE}
    + * @param param the parameter to modify. One of: + * {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE} + * {@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE} + * {@link #AL_PITCH PITCH} + * {@link #AL_DIRECTION DIRECTION} + * {@link #AL_LOOPING LOOPING} + * {@link #AL_BUFFER BUFFER} + * {@link #AL_SOURCE_STATE SOURCE_STATE} + * {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN} + * {@link #AL_SOURCE_TYPE SOURCE_TYPE} + * {@link #AL_POSITION POSITION} + * {@link #AL_VELOCITY VELOCITY} + * {@link #AL_GAIN GAIN} + * {@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE} + * {@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR} + * {@link #AL_MAX_DISTANCE MAX_DISTANCE} * @param value the parameter value. */ public void alSourcef(int source, int param, float value); @@ -400,7 +422,22 @@ public interface AL { * Sets the 3 dimensional values of a source parameter. * * @param source the source to modify. - * @param param the parameter to modify. One of:
    {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE}{@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE}{@link #AL_PITCH PITCH}{@link #AL_DIRECTION DIRECTION}{@link #AL_LOOPING LOOPING}{@link #AL_BUFFER BUFFER}{@link #AL_SOURCE_STATE SOURCE_STATE}
    {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN}{@link #AL_SOURCE_TYPE SOURCE_TYPE}{@link #AL_POSITION POSITION}{@link #AL_VELOCITY VELOCITY}{@link #AL_GAIN GAIN}{@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE}{@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR}
    {@link #AL_MAX_DISTANCE MAX_DISTANCE}
    + * @param param the parameter to modify. One of: + * {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE} + * {@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE} + * {@link #AL_PITCH PITCH} + * {@link #AL_DIRECTION DIRECTION} + * {@link #AL_LOOPING LOOPING} + * {@link #AL_BUFFER BUFFER} + * {@link #AL_SOURCE_STATE SOURCE_STATE} + * {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN} + * {@link #AL_SOURCE_TYPE SOURCE_TYPE} + * {@link #AL_POSITION POSITION} + * {@link #AL_VELOCITY VELOCITY} + * {@link #AL_GAIN GAIN} + * {@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE} + * {@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR} + * {@link #AL_MAX_DISTANCE MAX_DISTANCE} * @param value1 the first parameter value. * @param value2 the second parameter value. * @param value3 the third parameter value. @@ -411,13 +448,29 @@ public interface AL { * Returns the integer value of the specified source parameter. * * @param source the source to query. - * @param param the parameter to query. One of:
    {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE}{@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE}{@link #AL_PITCH PITCH}{@link #AL_DIRECTION DIRECTION}{@link #AL_LOOPING LOOPING}{@link #AL_BUFFER BUFFER}{@link #AL_SOURCE_STATE SOURCE_STATE}
    {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN}{@link #AL_SOURCE_TYPE SOURCE_TYPE}{@link #AL_POSITION POSITION}{@link #AL_VELOCITY VELOCITY}{@link #AL_GAIN GAIN}{@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE}{@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR}
    {@link #AL_MAX_DISTANCE MAX_DISTANCE}
    + * @param param the parameter to query. One of: + * {@link #AL_CONE_INNER_ANGLE CONE_INNER_ANGLE} + * {@link #AL_CONE_OUTER_ANGLE CONE_OUTER_ANGLE} + * {@link #AL_PITCH PITCH} + * {@link #AL_DIRECTION DIRECTION} + * {@link #AL_LOOPING LOOPING} + * {@link #AL_BUFFER BUFFER} + * {@link #AL_SOURCE_STATE SOURCE_STATE} + * {@link #AL_CONE_OUTER_GAIN CONE_OUTER_GAIN} + * {@link #AL_SOURCE_TYPE SOURCE_TYPE} + * {@link #AL_POSITION POSITION} + * {@link #AL_VELOCITY VELOCITY} + * {@link #AL_GAIN GAIN} + * {@link #AL_REFERENCE_DISTANCE REFERENCE_DISTANCE} + * {@link #AL_ROLLOFF_FACTOR ROLLOFF_FACTOR} + * {@link #AL_MAX_DISTANCE MAX_DISTANCE} + * @return the parameter value */ public int alGetSourcei(int source, int param); /** - * Removes a number of buffer entries that have finished processing, in the order of apperance, from the queue of the specified source. - *

    + * Removes a number of buffer entries that have finished processing, in the order of appearance, from the queue of the specified source. + * *

    Once a queue entry for a buffer has been appended to a queue and is pending processing, it should not be changed. Removal of a given queue entry is not * possible unless either the source is stopped (in which case then entire queue is considered processed), or if the queue entry has already been processed * (AL_PLAYING or AL_PAUSED source). A playing source will enter the AL_STOPPED state if it completes playback of the last buffer in its queue (the same @@ -431,7 +484,7 @@ public interface AL { /** * Queues up one or multiple buffer names to the specified source. - *

    + * *

    The buffers will be queued in the sequence in which they appear in the array. This command is legal on a source in any playback state (to allow for * streaming, queuing has to be possible on a AL_PLAYING source). All buffers in a queue must have the same format and attributes, with the exception of * the {@code NULL} buffer (i.e., 0) which can always be queued.

    @@ -453,7 +506,11 @@ public interface AL { /** * Sets the float value of a listener parameter. * - * @param param the parameter to modify. One of:
    {@link #AL_ORIENTATION ORIENTATION}{@link #AL_POSITION POSITION}{@link #AL_VELOCITY VELOCITY}{@link #AL_GAIN GAIN}
    + * @param param the parameter to modify. One of: + * {@link #AL_ORIENTATION ORIENTATION} + * {@link #AL_POSITION POSITION} + * {@link #AL_VELOCITY VELOCITY} + * {@link #AL_GAIN GAIN} * @param value the parameter value. */ public void alListenerf(int param, float value); @@ -461,7 +518,11 @@ public interface AL { /** * Sets the 3 dimensional float values of a listener parameter. * - * @param param the parameter to modify. One of:
    {@link #AL_ORIENTATION ORIENTATION}{@link #AL_POSITION POSITION}{@link #AL_VELOCITY VELOCITY}{@link #AL_GAIN GAIN}
    + * @param param the parameter to modify. One of: + * {@link #AL_ORIENTATION ORIENTATION} + * {@link #AL_POSITION POSITION} + * {@link #AL_VELOCITY VELOCITY} + * {@link #AL_GAIN GAIN} * @param value1 the first value. * @param value2 the second value. * @param value3 the third value. diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java index a4bcddeead..ec89b9df06 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALAudioRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,105 +31,139 @@ */ package com.jme3.audio.openal; -import com.jme3.audio.*; +import com.jme3.audio.AudioBuffer; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioParam; +import com.jme3.audio.AudioRenderer; +import com.jme3.audio.AudioSource; import com.jme3.audio.AudioSource.Status; +import static com.jme3.audio.openal.AL.*; + +import com.jme3.audio.AudioStream; +import com.jme3.audio.BandPassFilter; +import com.jme3.audio.Environment; +import com.jme3.audio.Filter; +import com.jme3.audio.HighPassFilter; +import com.jme3.audio.Listener; +import com.jme3.audio.ListenerParam; +import com.jme3.audio.LowPassFilter; import com.jme3.math.Vector3f; import com.jme3.util.BufferUtils; import com.jme3.util.NativeObjectManager; - import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.logging.Level; import java.util.logging.Logger; -import static com.jme3.audio.openal.AL.*; - +/** + * ALAudioRenderer is the backend implementation for OpenAL audio rendering. + */ public class ALAudioRenderer implements AudioRenderer, Runnable { private static final Logger logger = Logger.getLogger(ALAudioRenderer.class.getName()); - + private static final String THREAD_NAME = "jME3 Audio Decoder"; - + private final NativeObjectManager objManager = new NativeObjectManager(); // When multiplied by STREAMING_BUFFER_COUNT, will equal 44100 * 2 * 2 // which is exactly 1 second of audio. private static final int BUFFER_SIZE = 35280; private static final int STREAMING_BUFFER_COUNT = 5; - private final static int MAX_NUM_CHANNELS = 64; - private IntBuffer ib = BufferUtils.createIntBuffer(1); - private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); - private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); - private final byte[] arrayBuf = new byte[BUFFER_SIZE]; - private int[] channels; - private AudioSource[] chanSrcs; - private int nextChan = 0; - private final ArrayList freeChans = new ArrayList(); + private static final int MAX_NUM_CHANNELS = 64; + + // Buffers for OpenAL calls + private IntBuffer ib = BufferUtils.createIntBuffer(1); // Reused for single int operations + private final FloatBuffer fb = BufferUtils.createVector3Buffer(2); // For listener orientation + private final ByteBuffer nativeBuf = BufferUtils.createByteBuffer(BUFFER_SIZE); // For streaming data + private final byte[] arrayBuf = new byte[BUFFER_SIZE]; // Intermediate array buffer for streaming + + // Channel management + private int[] channels; // OpenAL source IDs + private AudioSource[] channelSources; // jME source associated with each channel + private int nextChannelIndex = 0; // Next available channel index + private final ArrayDeque freeChannels = new ArrayDeque<>(); // Pool of freed channels + + // Listener and environment private Listener listener; + private Environment environment; + private int reverbFx = -1; // EFX reverb effect ID + private int reverbFxSlot = -1; // EFX effect slot ID + + // State and capabilities private boolean audioDisabled = false; private boolean supportEfx = false; private boolean supportPauseDevice = false; - private int auxSends = 0; - private int reverbFx = -1; - private int reverbFxSlot = -1; - - // Fill streaming sources every 50 ms - private static final float UPDATE_RATE = 0.05f; + private boolean supportDisconnect = false; + + // Update thread + private static final float UPDATE_RATE = 0.05f; // Update streaming sources every 50ms private final Thread decoderThread = new Thread(this, THREAD_NAME); - private final Object threadLock = new Object(); + private final Object threadLock = new Object(); // Lock for thread safety + // OpenAL API interfaces private final AL al; private final ALC alc; private final EFX efx; - + + /** + * Creates a new ALAudioRenderer instance. + * + * @param al The OpenAL interface. + * @param alc The OpenAL Context interface. + * @param efx The OpenAL Effects Extension interface. + */ public ALAudioRenderer(AL al, ALC alc, EFX efx) { this.al = al; this.alc = alc; this.efx = efx; } - + + /** + * Initializes the OpenAL and ALC context. + */ private void initOpenAL() { try { if (!alc.isCreated()) { alc.createALC(); } } catch (UnsatisfiedLinkError ex) { - logger.log(Level.SEVERE, "Failed to load audio library", ex); + logger.log(Level.SEVERE, "Failed to load audio library (OpenAL). Audio will be disabled.", ex); audioDisabled = true; return; } - // Find maximum # of sources supported by this implementation - ArrayList channelList = new ArrayList(); - for (int i = 0; i < MAX_NUM_CHANNELS; i++) { - int chan = al.alGenSources(); - if (al.alGetError() != 0) { - break; - } else { - channelList.add(chan); - } - } + enumerateAvailableChannels(); - channels = new int[channelList.size()]; - for (int i = 0; i < channels.length; i++) { - channels[i] = channelList.get(i); + printAudioRendererInfo(); + + // Check for specific ALC extensions + supportPauseDevice = alc.alcIsExtensionPresent("ALC_SOFT_pause_device"); + if (!supportPauseDevice) { + logger.log(Level.WARNING, "Pausing audio device not supported (ALC_SOFT_pause_device)."); + } + supportDisconnect = alc.alcIsExtensionPresent("ALC_EXT_disconnect"); + if (!supportDisconnect) { + logger.log(Level.INFO, "Device disconnect detection not supported (ALC_EXT_disconnect)."); } - ib = BufferUtils.createIntBuffer(channels.length); - chanSrcs = new AudioSource[channels.length]; + initEfx(); + } + private void printAudioRendererInfo() { final String deviceName = alc.alcGetString(ALC.ALC_DEVICE_SPECIFIER); - logger.log(Level.INFO, "Audio Renderer Information\n" + - " * Device: {0}\n" + - " * Vendor: {1}\n" + - " * Renderer: {2}\n" + - " * Version: {3}\n" + - " * Supported channels: {4}\n" + - " * ALC extensions: {5}\n" + - " * AL extensions: {6}", - new Object[]{ + logger.log(Level.INFO, "Audio Renderer Information\n" + + " * Device: {0}\n" + + " * Vendor: {1}\n" + + " * Renderer: {2}\n" + + " * Version: {3}\n" + + " * Supported channels: {4}\n" + + " * ALC extensions: {5}\n" + + " * AL extensions: {6}", + new Object[] { deviceName, al.alGetString(AL_VENDOR), al.alGetString(AL_RENDERER), @@ -138,56 +172,84 @@ private void initOpenAL() { alc.alcGetString(ALC.ALC_EXTENSIONS), al.alGetString(AL_EXTENSIONS) }); + } - // Pause device is a feature used specifically on Android - // where the application could be closed but still running, - // thus the audio context remains open but no audio should be playing. - supportPauseDevice = alc.alcIsExtensionPresent("ALC_SOFT_pause_device"); - if (!supportPauseDevice) { - logger.log(Level.WARNING, "Pausing audio device not supported."); + /** + * Generates OpenAL sources to determine the maximum number supported. + */ + private void enumerateAvailableChannels() { + // Find maximum # of sources supported by this implementation + ArrayList channelList = new ArrayList<>(); + for (int i = 0; i < MAX_NUM_CHANNELS; i++) { + int sourceId = al.alGenSources(); + if (al.alGetError() != 0) { + break; + } else { + channelList.add(sourceId); + } } - + + channels = new int[channelList.size()]; + for (int i = 0; i < channels.length; i++) { + channels[i] = channelList.get(i); + } + + ib = BufferUtils.createIntBuffer(channels.length); + channelSources = new AudioSource[channels.length]; + } + + /** + * Initializes the EFX extension if supported. + */ + private void initEfx() { supportEfx = alc.alcIsExtensionPresent("ALC_EXT_EFX"); if (supportEfx) { - ib.position(0).limit(1); + ib.clear().limit(1); alc.alcGetInteger(EFX.ALC_EFX_MAJOR_VERSION, ib, 1); - int major = ib.get(0); - ib.position(0).limit(1); + int majorVersion = ib.get(0); + + ib.clear().limit(1); alc.alcGetInteger(EFX.ALC_EFX_MINOR_VERSION, ib, 1); - int minor = ib.get(0); - logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{major, minor}); + int minorVersion = ib.get(0); + logger.log(Level.INFO, "Audio effect extension version: {0}.{1}", new Object[]{majorVersion, minorVersion}); + ib.clear().limit(1); alc.alcGetInteger(EFX.ALC_MAX_AUXILIARY_SENDS, ib, 1); - auxSends = ib.get(0); - logger.log(Level.INFO, "Audio max auxiliary sends: {0}", auxSends); + int maxAuxSends = ib.get(0); + logger.log(Level.INFO, "Audio max auxiliary sends: {0}", maxAuxSends); - // create slot - ib.position(0).limit(1); + // 1. Create reverb effect slot + ib.clear().limit(1); efx.alGenAuxiliaryEffectSlots(1, ib); reverbFxSlot = ib.get(0); - // create effect - ib.position(0).limit(1); + // 2. Create reverb effect + ib.clear().limit(1); efx.alGenEffects(1, ib); reverbFx = ib.get(0); + + // 3. Configure effect type efx.alEffecti(reverbFx, EFX.AL_EFFECT_TYPE, EFX.AL_EFFECT_REVERB); - // attach reverb effect to effect slot + // 4. attach reverb effect to effect slot efx.alAuxiliaryEffectSloti(reverbFxSlot, EFX.AL_EFFECTSLOT_EFFECT, reverbFx); + } else { logger.log(Level.WARNING, "OpenAL EFX not available! Audio effects won't work."); } } - + + /** + * Destroys the OpenAL context, deleting sources, buffers, filters, and effects. + */ private void destroyOpenAL() { if (audioDisabled) { - alc.destroyALC(); - return; + return; // Nothing to destroy if context wasn't created } - // stop any playing channels - for (int i = 0; i < chanSrcs.length; i++) { - if (chanSrcs[i] != null) { + // Stops channels and detaches buffers/filters + for (int i = 0; i < channelSources.length; i++) { + if (channelSources[i] != null) { clearChannel(i); } } @@ -198,32 +260,53 @@ private void destroyOpenAL() { ib.flip(); al.alDeleteSources(channels.length, ib); - // delete audio buffers and filters + // Delete audio buffers and filters managed by NativeObjectManager objManager.deleteAllObjects(this); + // Delete EFX objects if they were created if (supportEfx) { - ib.position(0).limit(1); - ib.put(0, reverbFx); - efx.alDeleteEffects(1, ib); - - // If this is not allocated, why is it deleted? - // Commented out to fix native crash in OpenAL. - ib.position(0).limit(1); - ib.put(0, reverbFxSlot); - efx.alDeleteAuxiliaryEffectSlots(1, ib); + if (reverbFx != -1) { + ib.clear().limit(1); + ib.put(0, reverbFx); + efx.alDeleteEffects(1, ib); + reverbFx = -1; + } + + if (reverbFxSlot != -1) { + ib.clear().limit(1); + ib.put(0, reverbFxSlot); + efx.alDeleteAuxiliaryEffectSlots(1, ib); + reverbFxSlot = -1; + } } + channels = null; // Force re-enumeration + channelSources = null; + freeChannels.clear(); + nextChannelIndex = 0; + alc.destroyALC(); + logger.info("OpenAL context destroyed."); } + /** + * Initializes the OpenAL context, enumerates channels, checks capabilities, + * and starts the audio decoder thread. + */ + @Override public void initialize() { if (decoderThread.isAlive()) { throw new IllegalStateException("Initialize already called"); } - + // Initialize OpenAL context. initOpenAL(); + if (audioDisabled) { + logger.warning("Audio Disabled. Cannot start decoder thread."); + return; + } + // Initialize decoder thread. // Set high priority to avoid buffer starvation. decoderThread.setDaemon(true); @@ -231,62 +314,85 @@ public void initialize() { decoderThread.start(); } + /** + * Checks if the audio thread has terminated unexpectedly. + * @throws IllegalStateException if the decoding thread is terminated. + */ private void checkDead() { if (decoderThread.getState() == Thread.State.TERMINATED) { throw new IllegalStateException("Decoding thread is terminated"); } } + /** + * Main loop for the audio decoder thread. Updates streaming sources. + */ + @Override public void run() { - long updateRateNanos = (long) (UPDATE_RATE * 1000000000); + long updateRateNanos = (long) (UPDATE_RATE * 1_000_000_000); mainloop: while (true) { long startTime = System.nanoTime(); if (Thread.interrupted()) { + logger.fine("Audio decoder thread interrupted, exiting."); break; } synchronized (threadLock) { + checkDevice(); updateInDecoderThread(UPDATE_RATE); } long endTime = System.nanoTime(); long diffTime = endTime - startTime; + // Sleep to maintain the desired update rate if (diffTime < updateRateNanos) { long desiredEndTime = startTime + updateRateNanos; while (System.nanoTime() < desiredEndTime) { try { Thread.sleep(1); } catch (InterruptedException ex) { + logger.fine("Audio decoder thread interrupted during sleep, exiting."); break mainloop; } } } } + logger.fine("Audio decoder thread finished."); } + /** + * Shuts down the audio decoder thread and destroys the OpenAL context. + */ + @Override public void cleanup() { // kill audio thread - if (!decoderThread.isAlive()) { - return; - } - - decoderThread.interrupt(); - try { - decoderThread.join(); - } catch (InterruptedException ex) { + if (decoderThread.isAlive()) { + decoderThread.interrupt(); + try { + decoderThread.join(); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); // Re-interrupt thread + logger.log(Level.WARNING, "Interrupted while waiting for audio thread to finish.", ex); + } } - - // destroy OpenAL context + + // Destroy OpenAL context destroyOpenAL(); } + /** + * Updates an OpenAL filter object based on the jME Filter properties. + * Generates the AL filter ID if necessary. + * @param f The Filter object. + */ private void updateFilter(Filter f) { int id = f.getId(); if (id == -1) { - ib.position(0).limit(1); + // Generate OpenAL filter ID + ib.clear().limit(1); efx.alGenFilters(1, ib); id = ib.get(0); f.setId(id); @@ -295,18 +401,37 @@ private void updateFilter(Filter f) { } if (f instanceof LowPassFilter) { - LowPassFilter lpf = (LowPassFilter) f; + LowPassFilter lowPass = (LowPassFilter) f; efx.alFilteri(id, EFX.AL_FILTER_TYPE, EFX.AL_FILTER_LOWPASS); - efx.alFilterf(id, EFX.AL_LOWPASS_GAIN, lpf.getVolume()); - efx.alFilterf(id, EFX.AL_LOWPASS_GAINHF, lpf.getHighFreqVolume()); + efx.alFilterf(id, EFX.AL_LOWPASS_GAIN, lowPass.getVolume()); + efx.alFilterf(id, EFX.AL_LOWPASS_GAINHF, lowPass.getHighFreqVolume()); + + } else if (f instanceof HighPassFilter) { + HighPassFilter highPass = (HighPassFilter) f; + efx.alFilteri(id, EFX.AL_FILTER_TYPE, EFX.AL_FILTER_HIGHPASS); + efx.alFilterf(id, EFX.AL_HIGHPASS_GAIN, highPass.getVolume()); + efx.alFilterf(id, EFX.AL_HIGHPASS_GAINLF, highPass.getLowFreqVolume()); + + } else if (f instanceof BandPassFilter) { + BandPassFilter bandPass = (BandPassFilter) f; + efx.alFilteri(id, EFX.AL_FILTER_TYPE, EFX.AL_FILTER_BANDPASS); + efx.alFilterf(id, EFX.AL_BANDPASS_GAIN, bandPass.getVolume()); + efx.alFilterf(id, EFX.AL_BANDPASS_GAINHF, bandPass.getHighFreqVolume()); + efx.alFilterf(id, EFX.AL_BANDPASS_GAINLF, bandPass.getLowFreqVolume()); + } else { - throw new UnsupportedOperationException("Filter type unsupported: " - + f.getClass().getName()); + throw new UnsupportedOperationException("Unsupported filter type: " + f.getClass().getName()); } f.clearUpdateNeeded(); } + /** + * Gets the current playback time (in seconds) for a source. + * For streams, includes the time represented by already processed buffers. + * @param src The audio source. + * @return The playback time in seconds, or 0 if not playing or invalid. + */ @Override public float getSourcePlaybackTime(AudioSource src) { checkDead(); @@ -314,51 +439,51 @@ public float getSourcePlaybackTime(AudioSource src) { if (audioDisabled) { return 0; } - - // See comment in updateSourceParam(). + if (src.getChannel() < 0) { - return 0; + return 0; // Not playing or invalid state } - - int id = channels[src.getChannel()]; + + int sourceId = channels[src.getChannel()]; AudioData data = src.getAudioData(); + if (data == null) { + return 0; // No audio data + } int playbackOffsetBytes = 0; - + + // For streams, add the bytes from buffers that have already been fully processed and unqueued. if (data instanceof AudioStream) { - // Because audio streams are processed in buffer chunks, - // we have to compute the amount of time the stream was already - // been playing based on the number of buffers that were processed. AudioStream stream = (AudioStream) data; - - // NOTE: the assumption is that all enqueued buffers are the same size. - // this is currently enforced by fillBuffer(). - - // The number of unenqueued bytes that the decoder thread - // keeps track of. - int unqueuedBytes = stream.getUnqueuedBufferBytes(); - - // Additional processed buffers that the decoder thread - // did not unenqueue yet (it only updates 20 times per second). - int unqueuedBytesExtra = al.alGetSourcei(id, AL_BUFFERS_PROCESSED) * BUFFER_SIZE; - - // Total additional bytes that need to be considered. - playbackOffsetBytes = unqueuedBytes; // + unqueuedBytesExtra; + // This value is updated by the decoder thread when buffers are unqueued. + playbackOffsetBytes = stream.getUnqueuedBufferBytes(); } - + // Add byte offset from source (for both streams and buffers) - playbackOffsetBytes += al.alGetSourcei(id, AL_BYTE_OFFSET); - + int byteOffset = al.alGetSourcei(sourceId, AL_BYTE_OFFSET); + playbackOffsetBytes += byteOffset; + // Compute time value from bytes // E.g. for 44100 source with 2 channels and 16 bits per sample: // (44100 * 2 * 16 / 8) = 176400 - int bytesPerSecond = (data.getSampleRate() * - data.getChannels() * - data.getBitsPerSample() / 8); - - return (float)playbackOffsetBytes / bytesPerSecond; + int bytesPerSecond = (data.getSampleRate() + * data.getChannels() + * data.getBitsPerSample() / 8); + + if (bytesPerSecond <= 0) { + logger.warning("Invalid bytesPerSecond calculated for source. Cannot get playback time."); + return 0; // Avoid division by zero + } + + return (float) playbackOffsetBytes / bytesPerSecond; } } - + + /** + * Updates a specific parameter for an audio source on its assigned channel. + * @param src The audio source. + * @param param The parameter to update. + */ + @Override public void updateSourceParam(AudioSource src, AudioParam param) { checkDead(); synchronized (threadLock) { @@ -366,227 +491,237 @@ public void updateSourceParam(AudioSource src, AudioParam param) { return; } - // There is a race condition in AudioSource that can - // cause this to be called for a node that has been - // detached from its channel. For example, setVolume() - // called from the render thread may see that that AudioSource - // still has a channel value but the audio thread may - // clear that channel before setVolume() gets to call - // updateSourceParam() (because the audio stopped playing - // on its own right as the volume was set). In this case, - // it should be safe to just ignore the update - if (src.getChannel() < 0) { + int channel = src.getChannel(); + // Parameter updates only make sense if the source is associated with a channel + // and hasn't been stopped (which would set channel to -1). + if (channel < 0) { + // This can happen due to race conditions if a source stops playing + // right as a parameter update is requested from another thread. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Ignoring parameter update for source {0} as it's not validly associated with channel {1}.", + new Object[]{src, channel}); + } return; } - assert src.getChannel() >= 0; + int sourceId = channels[channel]; - int id = channels[src.getChannel()]; switch (param) { case Position: - if (!src.isPositional()) { - return; + if (src.isPositional()) { + Vector3f pos = src.getPosition(); + al.alSource3f(sourceId, AL_POSITION, pos.x, pos.y, pos.z); } - - Vector3f pos = src.getPosition(); - al.alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); break; + case Velocity: - if (!src.isPositional()) { - return; + if (src.isPositional()) { + Vector3f vel = src.getVelocity(); + al.alSource3f(sourceId, AL_VELOCITY, vel.x, vel.y, vel.z); } - - Vector3f vel = src.getVelocity(); - al.alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); break; - case MaxDistance: - if (!src.isPositional()) { - return; - } - al.alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); - break; - case RefDistance: - if (!src.isPositional()) { - return; + case MaxDistance: + if (src.isPositional()) { + al.alSourcef(sourceId, AL_MAX_DISTANCE, src.getMaxDistance()); } - - al.alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); break; - case ReverbFilter: - if (!supportEfx || !src.isPositional() || !src.isReverbEnabled()) { - return; - } - int filter = EFX.AL_FILTER_NULL; - if (src.getReverbFilter() != null) { - Filter f = src.getReverbFilter(); - if (f.isUpdateNeeded()) { - updateFilter(f); - } - filter = f.getId(); + case RefDistance: + if (src.isPositional()) { + al.alSourcef(sourceId, AL_REFERENCE_DISTANCE, src.getRefDistance()); } - al.alSource3i(id, EFX.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); break; - case ReverbEnabled: - if (!supportEfx || !src.isPositional()) { - return; - } - if (src.isReverbEnabled()) { - updateSourceParam(src, AudioParam.ReverbFilter); - } else { - al.alSource3i(id, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); - } - break; case IsPositional: - if (!src.isPositional()) { - // Play in headspace - al.alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); - al.alSource3f(id, AL_POSITION, 0, 0, 0); - al.alSource3f(id, AL_VELOCITY, 0, 0, 0); - - // Disable reverb - al.alSource3i(id, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); - } else { - al.alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); - updateSourceParam(src, AudioParam.Position); - updateSourceParam(src, AudioParam.Velocity); - updateSourceParam(src, AudioParam.MaxDistance); - updateSourceParam(src, AudioParam.RefDistance); - updateSourceParam(src, AudioParam.ReverbEnabled); - } + applySourcePositionalState(sourceId, src); break; + case Direction: - if (!src.isDirectional()) { - return; + if (src.isDirectional()) { + Vector3f dir = src.getDirection(); + al.alSource3f(sourceId, AL_DIRECTION, dir.x, dir.y, dir.z); } - - Vector3f dir = src.getDirection(); - al.alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); break; + case InnerAngle: - if (!src.isDirectional()) { - return; + if (src.isDirectional()) { + al.alSourcef(sourceId, AL_CONE_INNER_ANGLE, src.getInnerAngle()); } - - al.alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); break; + case OuterAngle: - if (!src.isDirectional()) { - return; + if (src.isDirectional()) { + al.alSourcef(sourceId, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); } - - al.alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); break; + case IsDirectional: - if (src.isDirectional()) { - updateSourceParam(src, AudioParam.Direction); - updateSourceParam(src, AudioParam.InnerAngle); - updateSourceParam(src, AudioParam.OuterAngle); - al.alSourcef(id, AL_CONE_OUTER_GAIN, 0); - } else { - al.alSourcef(id, AL_CONE_INNER_ANGLE, 360); - al.alSourcef(id, AL_CONE_OUTER_ANGLE, 360); - al.alSourcef(id, AL_CONE_OUTER_GAIN, 1f); - } + applySourceDirectionalState(sourceId, src); break; + case DryFilter: - if (!supportEfx) { - return; - } + applySourceDryFilter(sourceId, src); + break; - if (src.getDryFilter() != null) { - Filter f = src.getDryFilter(); - if (f.isUpdateNeeded()) { - updateFilter(f); + case ReverbFilter: + if (src.isPositional()) { + applySourceReverbFilter(sourceId, src); + } + break; - // NOTE: must re-attach filter for changes to apply. - al.alSourcei(id, EFX.AL_DIRECT_FILTER, f.getId()); + case ReverbEnabled: + if (supportEfx && src.isPositional()) { + if (!src.isReverbEnabled()) { + al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); + } else { + applySourceReverbFilter(sourceId, src); } - } else { - al.alSourcei(id, EFX.AL_DIRECT_FILTER, EFX.AL_FILTER_NULL); } break; + case Looping: - if (src.isLooping() && !(src.getAudioData() instanceof AudioStream)) { - al.alSourcei(id, AL_LOOPING, AL_TRUE); - } else { - al.alSourcei(id, AL_LOOPING, AL_FALSE); - } + applySourceLooping(sourceId, src, false); break; + case Volume: - al.alSourcef(id, AL_GAIN, src.getVolume()); + al.alSourcef(sourceId, AL_GAIN, src.getVolume()); break; + case Pitch: - al.alSourcef(id, AL_PITCH, src.getPitch()); + al.alSourcef(sourceId, AL_PITCH, src.getPitch()); + break; + + default: + logger.log(Level.WARNING, "Unhandled source parameter update: {0}", param); break; } } } - private void setSourceParams(int id, AudioSource src, boolean forceNonLoop) { - if (src.isPositional()) { - Vector3f pos = src.getPosition(); - Vector3f vel = src.getVelocity(); - al.alSource3f(id, AL_POSITION, pos.x, pos.y, pos.z); - al.alSource3f(id, AL_VELOCITY, vel.x, vel.y, vel.z); - al.alSourcef(id, AL_MAX_DISTANCE, src.getMaxDistance()); - al.alSourcef(id, AL_REFERENCE_DISTANCE, src.getRefDistance()); - al.alSourcei(id, AL_SOURCE_RELATIVE, AL_FALSE); - - if (src.isReverbEnabled() && supportEfx) { - int filter = EFX.AL_FILTER_NULL; - if (src.getReverbFilter() != null) { - Filter f = src.getReverbFilter(); - if (f.isUpdateNeeded()) { - updateFilter(f); - } - filter = f.getId(); + /** + * Applies all parameters from the AudioSource to the specified OpenAL source ID. + * Used when initially playing a source or instance. + * + * @param sourceId The OpenAL source ID. + * @param src The jME AudioSource. + * @param forceNonLoop If true, looping will be disabled regardless of source setting (used for instances). + */ + private void setSourceParams(int sourceId, AudioSource src, boolean forceNonLoop) { + + al.alSourcef(sourceId, AL_GAIN, src.getVolume()); + al.alSourcef(sourceId, AL_PITCH, src.getPitch()); + al.alSourcef(sourceId, AL_SEC_OFFSET, src.getTimeOffset()); + + applySourceLooping(sourceId, src, forceNonLoop); + applySourcePositionalState(sourceId, src); + applySourceDirectionalState(sourceId, src); + applySourceDryFilter(sourceId, src); + } + + // --- Source Parameter Helper Methods --- + + private void applySourceDryFilter(int sourceId, AudioSource src) { + if (supportEfx) { + int filterId = EFX.AL_FILTER_NULL; + if (src.getDryFilter() != null) { + Filter f = src.getDryFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); } - al.alSource3i(id, EFX.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filter); + filterId = f.getId(); } - } else { - // play in headspace - al.alSourcei(id, AL_SOURCE_RELATIVE, AL_TRUE); - al.alSource3f(id, AL_POSITION, 0, 0, 0); - al.alSource3f(id, AL_VELOCITY, 0, 0, 0); + // NOTE: must re-attach filter for changes to apply. + al.alSourcei(sourceId, EFX.AL_DIRECT_FILTER, filterId); } + } - if (src.getDryFilter() != null && supportEfx) { - Filter f = src.getDryFilter(); - if (f.isUpdateNeeded()) { - updateFilter(f); - - // NOTE: must re-attach filter for changes to apply. - al.alSourcei(id, EFX.AL_DIRECT_FILTER, f.getId()); + private void applySourceReverbFilter(int sourceId, AudioSource src) { + if (supportEfx) { + int filterId = EFX.AL_FILTER_NULL; + if (src.isReverbEnabled() && src.getReverbFilter() != null) { + Filter f = src.getReverbFilter(); + if (f.isUpdateNeeded()) { + updateFilter(f); + } + filterId = f.getId(); } + al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, reverbFxSlot, 0, filterId); + } + } + + private void applySourceLooping(int sourceId, AudioSource src, boolean forceNonLoop) { + boolean looping = !forceNonLoop && src.isLooping(); + // Streams handle looping internally by rewinding, not via AL_LOOPING. + if (src.getAudioData() instanceof AudioStream) { + looping = false; } + al.alSourcei(sourceId, AL_LOOPING, looping ? AL_TRUE : AL_FALSE); + } - if (forceNonLoop || src.getAudioData() instanceof AudioStream) { - al.alSourcei(id, AL_LOOPING, AL_FALSE); + /** Sets AL_SOURCE_RELATIVE and applies position/velocity/distance accordingly */ + private void applySourcePositionalState(int sourceId, AudioSource src) { + if (src.isPositional()) { + // Play in world space: absolute position/velocity + Vector3f pos = src.getPosition(); + Vector3f vel = src.getVelocity(); + al.alSource3f(sourceId, AL_POSITION, pos.x, pos.y, pos.z); + al.alSource3f(sourceId, AL_VELOCITY, vel.x, vel.y, vel.z); + al.alSourcef(sourceId, AL_REFERENCE_DISTANCE, src.getRefDistance()); + al.alSourcef(sourceId, AL_MAX_DISTANCE, src.getMaxDistance()); + al.alSourcei(sourceId, AL_SOURCE_RELATIVE, AL_FALSE); + + if (supportEfx) { + if (!src.isReverbEnabled()) { + al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); + } else { + applySourceReverbFilter(sourceId, src); + } + } } else { - al.alSourcei(id, AL_LOOPING, src.isLooping() ? AL_TRUE : AL_FALSE); + // Play in headspace: relative to listener, fixed position/velocity + al.alSource3f(sourceId, AL_POSITION, 0, 0, 0); + al.alSource3f(sourceId, AL_VELOCITY, 0, 0, 0); + al.alSourcei(sourceId, AL_SOURCE_RELATIVE, AL_TRUE); + + // Disable reverb send for non-positional sounds + if (supportEfx) { + al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); + } } - al.alSourcef(id, AL_GAIN, src.getVolume()); - al.alSourcef(id, AL_PITCH, src.getPitch()); - al.alSourcef(id, AL_SEC_OFFSET, src.getTimeOffset()); + } + /** Sets cone angles/gain based on whether the source is directional */ + private void applySourceDirectionalState(int sourceId, AudioSource src) { if (src.isDirectional()) { Vector3f dir = src.getDirection(); - al.alSource3f(id, AL_DIRECTION, dir.x, dir.y, dir.z); - al.alSourcef(id, AL_CONE_INNER_ANGLE, src.getInnerAngle()); - al.alSourcef(id, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); - al.alSourcef(id, AL_CONE_OUTER_GAIN, 0); + al.alSource3f(sourceId, AL_DIRECTION, dir.x, dir.y, dir.z); + al.alSourcef(sourceId, AL_CONE_INNER_ANGLE, src.getInnerAngle()); + al.alSourcef(sourceId, AL_CONE_OUTER_ANGLE, src.getOuterAngle()); + al.alSourcef(sourceId, AL_CONE_OUTER_GAIN, 0); } else { - al.alSourcef(id, AL_CONE_INNER_ANGLE, 360); - al.alSourcef(id, AL_CONE_OUTER_ANGLE, 360); - al.alSourcef(id, AL_CONE_OUTER_GAIN, 1f); + // Omnidirectional: 360 degree cone, full gain + al.alSourcef(sourceId, AL_CONE_INNER_ANGLE, 360f); + al.alSourcef(sourceId, AL_CONE_OUTER_ANGLE, 360f); + al.alSourcef(sourceId, AL_CONE_OUTER_GAIN, 1f); } } + /** + * Updates a specific parameter for the listener. + * + * @param listener The listener object. + * @param param The parameter to update. + */ + @Override public void updateListenerParam(Listener listener, ListenerParam param) { checkDead(); + // Check if this listener is the active one + if (this.listener != listener) { + logger.warning("updateListenerParam called on inactive listener."); + return; + } + synchronized (threadLock) { if (audioDisabled) { return; @@ -594,63 +729,85 @@ public void updateListenerParam(Listener listener, ListenerParam param) { switch (param) { case Position: - Vector3f pos = listener.getLocation(); - al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z); + applyListenerPosition(listener); break; case Rotation: - Vector3f dir = listener.getDirection(); - Vector3f up = listener.getUp(); - fb.rewind(); - fb.put(dir.x).put(dir.y).put(dir.z); - fb.put(up.x).put(up.y).put(up.z); - fb.flip(); - al.alListener(AL_ORIENTATION, fb); + applyListenerRotation(listener); break; case Velocity: - Vector3f vel = listener.getVelocity(); - al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + applyListenerVelocity(listener); break; case Volume: - al.alListenerf(AL_GAIN, listener.getVolume()); + applyListenerVolume(listener); + break; + default: + logger.log(Level.WARNING, "Unhandled listener parameter: {0}", param); break; } } } + /** + * Applies all parameters from the listener object to OpenAL. + * @param listener The listener object. + */ private void setListenerParams(Listener listener) { + applyListenerPosition(listener); + applyListenerRotation(listener); + applyListenerVelocity(listener); + applyListenerVolume(listener); + } + + // --- Listener Parameter Helper Methods --- + + private void applyListenerPosition(Listener listener) { Vector3f pos = listener.getLocation(); - Vector3f vel = listener.getVelocity(); + al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z); + } + + private void applyListenerRotation(Listener listener) { Vector3f dir = listener.getDirection(); Vector3f up = listener.getUp(); - - al.alListener3f(AL_POSITION, pos.x, pos.y, pos.z); - al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + // Use the shared FloatBuffer fb fb.rewind(); fb.put(dir.x).put(dir.y).put(dir.z); fb.put(up.x).put(up.y).put(up.z); fb.flip(); al.alListener(AL_ORIENTATION, fb); + } + + private void applyListenerVelocity(Listener listener) { + Vector3f vel = listener.getVelocity(); + al.alListener3f(AL_VELOCITY, vel.x, vel.y, vel.z); + } + + private void applyListenerVolume(Listener listener) { al.alListenerf(AL_GAIN, listener.getVolume()); } private int newChannel() { - if (freeChans.size() > 0) { - return freeChans.remove(0); - } else if (nextChan < channels.length) { - return nextChan++; + if (!freeChannels.isEmpty()) { + return freeChannels.removeFirst(); + } else if (nextChannelIndex < channels.length) { + return nextChannelIndex++; } else { return -1; } } private void freeChannel(int index) { - if (index == nextChan - 1) { - nextChan--; + if (index == nextChannelIndex - 1) { + nextChannelIndex--; } else { - freeChans.add(index); + freeChannels.add(index); } } + /** + * Configures the global reverb effect based on the Environment settings. + * @param env The Environment object. + */ + @Override public void setEnvironment(Environment env) { checkDead(); synchronized (threadLock) { @@ -658,6 +815,7 @@ public void setEnvironment(Environment env) { return; } + // Apply reverb properties from the Environment object efx.alEffectf(reverbFx, EFX.AL_REVERB_DENSITY, env.getDensity()); efx.alEffectf(reverbFx, EFX.AL_REVERB_DIFFUSION, env.getDiffusion()); efx.alEffectf(reverbFx, EFX.AL_REVERB_GAIN, env.getGain()); @@ -671,72 +829,94 @@ public void setEnvironment(Environment env) { efx.alEffectf(reverbFx, EFX.AL_REVERB_AIR_ABSORPTION_GAINHF, env.getAirAbsorbGainHf()); efx.alEffectf(reverbFx, EFX.AL_REVERB_ROOM_ROLLOFF_FACTOR, env.getRoomRolloffFactor()); - // attach effect to slot + // (Re)attach the configured reverb effect to the slot efx.alAuxiliaryEffectSloti(reverbFxSlot, EFX.AL_EFFECTSLOT_EFFECT, reverbFx); + this.environment = env; } } - private boolean fillBuffer(AudioStream stream, int id) { - int size = 0; - int result; - - while (size < arrayBuf.length) { - result = stream.readSamples(arrayBuf, size, arrayBuf.length - size); - - if (result > 0) { - size += result; + /** + * Fills a single OpenAL buffer with data from the audio stream. + * Uses the shared nativeBuf and arrayBuf. + * + * @param stream The AudioStream to read from. + * @param bufferId The OpenAL buffer ID to fill. + * @return True if the buffer was filled with data, false if stream EOF was reached before filling. + */ + private boolean fillBuffer(AudioStream stream, int bufferId) { + int totalBytesRead = 0; + int bytesRead; + + while (totalBytesRead < arrayBuf.length) { + bytesRead = stream.readSamples(arrayBuf, totalBytesRead, arrayBuf.length - totalBytesRead); + + if (bytesRead > 0) { + totalBytesRead += bytesRead; } else { break; } } - if (size == 0) { + if (totalBytesRead == 0) { return false; } + // Copy data from arrayBuf to nativeBuf nativeBuf.clear(); - nativeBuf.put(arrayBuf, 0, size); + nativeBuf.put(arrayBuf, 0, totalBytesRead); nativeBuf.flip(); - al.alBufferData(id, convertFormat(stream), nativeBuf, size, stream.getSampleRate()); + // Upload data to the OpenAL buffer + int format = getOpenALFormat(stream); + int sampleRate = stream.getSampleRate(); + al.alBufferData(bufferId, format, nativeBuf, totalBytesRead, sampleRate); return true; } + /** + * Unqueues processed buffers from a streaming source and refills/requeues them. + * Updates the stream's internal count of processed bytes. + * + * @param sourceId The OpenAL source ID. + * @param stream The AudioStream. + * @param looping Whether the stream should loop internally. + * @return True if at least one buffer was successfully refilled and requeued. + */ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean looping) { boolean success = false; int processed = al.alGetSourcei(sourceId, AL_BUFFERS_PROCESSED); int unqueuedBufferBytes = 0; - + for (int i = 0; i < processed; i++) { int buffer; - ib.position(0).limit(1); + ib.clear().limit(1); al.alSourceUnqueueBuffers(sourceId, 1, ib); buffer = ib.get(0); - - // XXX: assume that reading from AudioStream always + + // XXX: assume that reading from AudioStream always // gives BUFFER_SIZE amount of bytes! This might not always // be the case... unqueuedBufferBytes += BUFFER_SIZE; - - boolean active = fillBuffer(stream, buffer); - - if (!active && !stream.isEOF()) { + + // Try to refill the buffer + boolean filled = fillBuffer(stream, buffer); + if (!filled && !stream.isEOF()) { throw new AssertionError(); } - - if (!active && looping) { + + if (!filled && looping) { stream.setTime(0); - active = fillBuffer(stream, buffer); - if (!active) { - throw new IllegalStateException("Looping streaming source " + - "was rewinded but could not be filled"); + filled = fillBuffer(stream, buffer); // Try filling again + if (!filled) { + throw new IllegalStateException("Looping streaming source " + + "was rewound but could not be filled"); } } - - if (active) { - ib.position(0).limit(1); + + if (filled) { + ib.clear().limit(1); ib.put(0, buffer); al.alSourceQueueBuffers(sourceId, 1, ib); // At least one buffer enqueued = success. @@ -746,7 +926,8 @@ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean lo break; } } - + + // Update the stream's internal counter for processed bytes stream.setUnqueuedBufferBytes(stream.getUnqueuedBufferBytes() + unqueuedBufferBytes); return success; @@ -754,45 +935,45 @@ private boolean fillStreamingSource(int sourceId, AudioStream stream, boolean lo private void attachStreamToSource(int sourceId, AudioStream stream, boolean looping) { boolean success = false; - - // Reset the stream. Typically happens if it finished playing on - // its own and got reclaimed. - // Note that AudioNode.stop() already resets the stream - // since it might not be in EOF when stopped. + + // Reset the stream. Typically, happens if it finished playing on its own and got reclaimed. + // Note that AudioNode.stop() already resets the stream since it might not be at the EOF when stopped. if (stream.isEOF()) { stream.setTime(0); } - + for (int id : stream.getIds()) { - boolean active = fillBuffer(stream, id); - if (!active && !stream.isEOF()) { + // Try to refill the buffer + boolean filled = fillBuffer(stream, id); + if (!filled && !stream.isEOF()) { throw new AssertionError(); } - if (!active && looping) { + + if (!filled && looping) { stream.setTime(0); - active = fillBuffer(stream, id); - if (!active) { - throw new IllegalStateException("Looping streaming source " + - "was rewinded but could not be filled"); + filled = fillBuffer(stream, id); + if (!filled) { + throw new IllegalStateException("Looping streaming source " + + "was rewound but could not be filled"); } } - if (active) { - ib.position(0).limit(1); + + if (filled) { + ib.clear().limit(1); ib.put(id).flip(); al.alSourceQueueBuffers(sourceId, 1, ib); success = true; } } - + if (!success) { // should never happen throw new IllegalStateException("No valid data could be read from stream"); } } - private boolean attachBufferToSource(int sourceId, AudioBuffer buffer) { + private void attachBufferToSource(int sourceId, AudioBuffer buffer) { al.alSourcei(sourceId, AL_BUFFER, buffer.getId()); - return true; } private void attachAudioToSource(int sourceId, AudioData data, boolean looping) { @@ -805,34 +986,41 @@ private void attachAudioToSource(int sourceId, AudioData data, boolean looping) } } + /** + * Stops the AL source on the channel, detaches buffers and filters, + * and clears the jME source association. Does NOT free the channel index itself. + * + * @param index The channel index to clear. + */ private void clearChannel(int index) { // make room at this channel - if (chanSrcs[index] != null) { - AudioSource src = chanSrcs[index]; + if (channelSources[index] != null) { + AudioSource src = channelSources[index]; int sourceId = channels[index]; al.alSourceStop(sourceId); - + // For streaming sources, this will clear all queued buffers. al.alSourcei(sourceId, AL_BUFFER, 0); - if (src.getDryFilter() != null && supportEfx) { - // detach filter - al.alSourcei(sourceId, EFX.AL_DIRECT_FILTER, EFX.AL_FILTER_NULL); - } - if (src.isPositional()) { - AudioSource pas = (AudioSource) src; - if (pas.isReverbEnabled() && supportEfx) { + if (supportEfx) { + if (src.getDryFilter() != null) { + // detach direct filter + al.alSourcei(sourceId, EFX.AL_DIRECT_FILTER, EFX.AL_FILTER_NULL); + } + + if (src.isPositional() && src.isReverbEnabled()) { + // Detach auxiliary send filter (reverb) al.alSource3i(sourceId, EFX.AL_AUXILIARY_SEND_FILTER, 0, 0, EFX.AL_FILTER_NULL); } } - chanSrcs[index] = null; + channelSources[index] = null; } } - - private AudioSource.Status convertStatus(int oalStatus) { - switch (oalStatus) { + + private AudioSource.Status convertStatus(int openALState) { + switch (openALState) { case AL_INITIAL: case AL_STOPPED: return Status.Stopped; @@ -841,107 +1029,198 @@ private AudioSource.Status convertStatus(int oalStatus) { case AL_PLAYING: return Status.Playing; default: - throw new UnsupportedOperationException("Unrecognized OAL state: " + oalStatus); + throw new UnsupportedOperationException("Unrecognized OpenAL state: " + openALState); } } + @Override public void update(float tpf) { synchronized (threadLock) { updateInRenderThread(tpf); } } + /** + * Checks the device connection status and attempts to restart the renderer if disconnected. + * Called periodically from the decoder thread. + */ + private void checkDevice() { + if (isDeviceDisconnected()) { + logger.log(Level.WARNING, "Audio device disconnected! Attempting to restart audio renderer..."); + restartAudioRenderer(); + } + } + + /** + * Checks if the audio device has been disconnected. + * Requires ALC_EXT_disconnect extension. + * @return True if disconnected, false otherwise or if not supported. + */ + private boolean isDeviceDisconnected() { + if (audioDisabled || !supportDisconnect) { + return false; + } + + ib.clear().limit(1); + alc.alcGetInteger(ALC.ALC_CONNECTED, ib, 1); + // Returns 1 if connected, 0 if disconnected. + return ib.get(0) == 0; + } + + private void restartAudioRenderer() { + // Preserve internal state variables + Listener currentListener = this.listener; + Environment currentEnvironment = this.environment; + + // Destroy existing OpenAL resources + destroyOpenAL(); + + // Re-initialize OpenAL + // Creates new context, enumerates channels, checks caps, inits EFX + initOpenAL(); + + // Restore Listener and Environment (if possible and successful init) + if (!audioDisabled) { + if (currentListener != null) { + setListener(currentListener); // Re-apply listener params + } + if (currentEnvironment != null) { + setEnvironment(currentEnvironment); // Re-apply environment + } + // TODO: What about existing AudioSource objects? + // Their state (Playing/Paused/Stopped) is lost. + // Their AudioData (buffers/streams) needs re-uploading/re-preparing. + // This requires iterating through all known AudioNodes, which the renderer doesn't track. + // The application layer would need to handle re-playing sounds after a device reset. + logger.warning("Audio renderer restarted. Application may need to re-play active sounds."); + + } else { + logger.severe("Audio remained disabled after attempting restart."); + } + } + + /** + * Internal update logic called from the render thread within the lock. + * Checks source statuses and reclaims finished channels. + * + * @param tpf Time per frame (currently unused). + */ public void updateInRenderThread(float tpf) { if (audioDisabled) { return; } - + for (int i = 0; i < channels.length; i++) { - AudioSource src = chanSrcs[i]; - + AudioSource src = channelSources[i]; + if (src == null) { - continue; + continue; // No source on this channel } int sourceId = channels[i]; boolean boundSource = i == src.getChannel(); boolean reclaimChannel = false; - - Status oalStatus = convertStatus(al.alGetSourcei(sourceId, AL_SOURCE_STATE)); - + + // Get OpenAL status for the source + int openALState = al.alGetSourcei(sourceId, AL_SOURCE_STATE); + Status openALStatus = convertStatus(openALState); + + // --- Handle Instanced Playback (Not bound to a specific channel) --- if (!boundSource) { - // Rules for instanced playback vary significantly. - // Handle it here. - if (oalStatus == Status.Stopped) { - // Instanced audio stopped playing. Reclaim channel. - clearChannel(i); - freeChannel(i); - } else if (oalStatus == Status.Paused) { - throw new AssertionError("Instanced audio cannot be paused"); + if (openALStatus == Status.Stopped) { + // Instanced audio (non-looping buffer) finished playing. Reclaim channel. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Reclaiming channel {0} from finished instance.", i); + } + clearChannel(i); // Stop source, detach buffer/filter + freeChannel(i); // Add channel back to the free pool + } else if (openALStatus == Status.Paused) { + throw new AssertionError("Instanced audio source on channel " + i + " cannot be paused."); } - + + // If Playing, do nothing, let it finish. continue; } - + + // --- Handle Bound Playback (Normal play/pause/stop) --- Status jmeStatus = src.getStatus(); - - // Check if we need to sync JME status with OAL status. - if (oalStatus != jmeStatus) { - if (oalStatus == Status.Stopped && jmeStatus == Status.Playing) { - // Maybe we need to reclaim the channel. + + // Check if we need to sync JME status with OpenAL status. + if (openALStatus != jmeStatus) { + if (openALStatus == Status.Stopped && jmeStatus == Status.Playing) { + + // Source stopped playing unexpectedly (finished or starved) if (src.getAudioData() instanceof AudioStream) { AudioStream stream = (AudioStream) src.getAudioData(); - + if (stream.isEOF() && !src.isLooping()) { - // Stream finished playing + // Stream reached EOF and is not looping. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Stream source on channel {0} finished.", i); + } reclaimChannel = true; } else { - // Stream still has data. - // Buffer starvation occurred. - // Audio decoder thread will fill the data - // and start the channel again. + // Stream still has data or is looping, but stopped. + // This indicates buffer starvation. The decoder thread will handle restarting it. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Stream source on channel {0} likely starved.", i); + } + // Don't reclaim channel here, let decoder thread refill and restart. } } else { // Buffer finished playing. if (src.isLooping()) { - // When a device is disconnected, all sources - // will enter the "stopped" state. - logger.warning("A looping sound has stopped playing"); + // This is unexpected for looping buffers unless the device was disconnected/reset. + logger.log(Level.WARNING, "Looping buffer source on channel {0} stopped unexpectedly.", i); + } else { + // Non-looping buffer finished normally. + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Buffer source on channel {0} finished.", i); + } } reclaimChannel = true; } - + if (reclaimChannel) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Reclaiming channel {0} from finished source.", i); + } src.setStatus(Status.Stopped); src.setChannel(-1); - clearChannel(i); - freeChannel(i); + clearChannel(i); // Stop AL source, detach buffers/filters + freeChannel(i); // Add channel back to the free pool } } else { - // jME3 state does not match OAL state. + // jME3 state does not match OpenAL state. // This is only relevant for bound sources. throw new AssertionError("Unexpected sound status. " - + "OAL: " + oalStatus - + ", JME: " + jmeStatus); + + "OpenAL: " + openALStatus + ", JME: " + jmeStatus); } } else { // Stopped channel was not cleared correctly. - if (oalStatus == Status.Stopped) { + if (openALStatus == Status.Stopped) { throw new AssertionError("Channel " + i + " was not reclaimed"); } } } } - + + /** + * Internal update logic called from the decoder thread within the lock. + * Fills streaming buffers and restarts starved sources. Deletes unused objects. + * + * @param tpf Time per frame (currently unused). + */ public void updateInDecoderThread(float tpf) { if (audioDisabled) { return; } for (int i = 0; i < channels.length; i++) { - AudioSource src = chanSrcs[i]; - + AudioSource src = channelSources[i]; + + // Only process streaming sources associated with this channel if (src == null || !(src.getAudioData() instanceof AudioStream)) { continue; } @@ -949,34 +1228,30 @@ public void updateInDecoderThread(float tpf) { int sourceId = channels[i]; AudioStream stream = (AudioStream) src.getAudioData(); - Status oalStatus = convertStatus(al.alGetSourcei(sourceId, AL_SOURCE_STATE)); + // Get current AL state, primarily to check if we need to restart playback + int openALState = al.alGetSourcei(sourceId, AL_SOURCE_STATE); + Status openALStatus = convertStatus(openALState); Status jmeStatus = src.getStatus(); // Keep filling data (even if we are stopped / paused) boolean buffersWereFilled = fillStreamingSource(sourceId, stream, src.isLooping()); - if (buffersWereFilled) { - if (oalStatus == Status.Stopped && jmeStatus == Status.Playing) { - // The source got stopped due to buffer starvation. - // Start it again. - logger.log(Level.WARNING, "Buffer starvation " - + "occurred while playing stream"); - al.alSourcePlay(sourceId); - } else { - // Buffers were filled, stream continues to play. - if (oalStatus == Status.Playing && jmeStatus == Status.Playing) { - // Nothing to do. - } else { - throw new AssertionError(); - } - } + // Check if the source stopped due to buffer starvation while it was supposed to be playing + if (buffersWereFilled + && openALStatus == Status.Stopped + && jmeStatus == Status.Playing) { + // The source got stopped due to buffer starvation. + // Start it again. + logger.log(Level.WARNING, "Buffer starvation detected for stream on channel {0}. Restarting playback.", i); + al.alSourcePlay(sourceId); } } - // Delete any unused objects. + // Delete any unused objects (buffers, filters) that are no longer referenced. objManager.deleteUnused(this); } + @Override public void setListener(Listener listener) { checkDead(); synchronized (threadLock) { @@ -985,33 +1260,61 @@ public void setListener(Listener listener) { } if (this.listener != null) { - // previous listener no longer associated with current - // renderer + // previous listener no longer associated with current renderer this.listener.setRenderer(null); } this.listener = listener; - this.listener.setRenderer(this); - setListenerParams(listener); + + if (this.listener != null) { + this.listener.setRenderer(this); + setListenerParams(listener); + } else { + logger.info("Listener set to null."); + } } } - + + /** + * Pauses all audio playback by pausing the OpenAL device context. + * Requires ALC_SOFT_pause_device extension. + * @throws UnsupportedOperationException if the extension is not supported. + */ + @Override public void pauseAll() { if (!supportPauseDevice) { - throw new UnsupportedOperationException("Pause device is NOT supported!"); + throw new UnsupportedOperationException( + "Pausing the audio device is not supported by the current OpenAL driver" + + " (requires ALC_SOFT_pause_device)."); } - + alc.alcDevicePauseSOFT(); + logger.info("Audio device paused."); } + /** + * Resumes all audio playback by resuming the OpenAL device context. + * Requires ALC_SOFT_pause_device extension. + * @throws UnsupportedOperationException if the extension is not supported. + */ + @Override public void resumeAll() { if (!supportPauseDevice) { - throw new UnsupportedOperationException("Pause device is NOT supported!"); + throw new UnsupportedOperationException( + "Resuming the audio device is not supported by the current OpenAL driver" + + " (requires ALC_SOFT_pause_device)."); } - + alc.alcDeviceResumeSOFT(); + logger.info("Audio device resumed."); } + /** + * Plays an audio source as a one-shot instance (non-looping buffer). + * A free channel is allocated temporarily. + * @param src The audio source to play. + */ + @Override public void playSourceInstance(AudioSource src) { checkDead(); synchronized (threadLock) { @@ -1019,36 +1322,47 @@ public void playSourceInstance(AudioSource src) { return; } - if (src.getAudioData() instanceof AudioStream) { + AudioData audioData = src.getAudioData(); + if (audioData == null) { + logger.log(Level.WARNING, "playSourceInstance called on source with null AudioData: {0}", src); + return; + } + if (audioData instanceof AudioStream) { throw new UnsupportedOperationException( - "Cannot play instances " - + "of audio streams. Use play() instead."); + "Cannot play instances of audio streams. Use play() instead."); } - if (src.getAudioData().isUpdateNeeded()) { - updateAudioData(src.getAudioData()); + if (audioData.isUpdateNeeded()) { + updateAudioData(audioData); } - // create a new index for an audio-channel + // Allocate a temporary channel int index = newChannel(); if (index == -1) { + logger.log(Level.WARNING, "No channel available to play instance of {0}", src); return; } + // Ensure channel is clean before use int sourceId = channels[index]; - clearChannel(index); - // set parameters, like position and max distance + // Set parameters for this specific instance (force non-looping) setSourceParams(sourceId, src, true); - attachAudioToSource(sourceId, src.getAudioData(), false); - chanSrcs[index] = src; + attachAudioToSource(sourceId, audioData, false); + channelSources[index] = src; // play the channel al.alSourcePlay(sourceId); } } + /** + * Plays an audio source, allocating a persistent channel for it. + * Handles both buffers and streams. Can be paused and stopped. + * @param src The audio source to play. + */ + @Override public void playSource(AudioSource src) { checkDead(); synchronized (threadLock) { @@ -1057,36 +1371,52 @@ public void playSource(AudioSource src) { } if (src.getStatus() == Status.Playing) { + // Already playing, do nothing. return; - } else if (src.getStatus() == Status.Stopped) { - //Assertion removed as it seems it's not possible to have - //something different than =1 when first playing an AudioNode - // assert src.getChannel() != -1; - - // allocate channel to this source + } + + if (src.getStatus() == Status.Stopped) { + + AudioData audioData = src.getAudioData(); + if (audioData == null) { + logger.log(Level.WARNING, "playSource called on source with null AudioData: {0}", src); + return; + } + + // Allocate a temporary channel int index = newChannel(); if (index == -1) { - logger.log(Level.WARNING, "No channel available to play {0}", src); + logger.log(Level.WARNING, "No channel available to play instance of {0}", src); return; } + + // Ensure channel is clean before use + int sourceId = channels[index]; clearChannel(index); src.setChannel(index); - AudioData data = src.getAudioData(); - if (data.isUpdateNeeded()) { - updateAudioData(data); + if (audioData.isUpdateNeeded()) { + updateAudioData(audioData); } - chanSrcs[index] = src; - setSourceParams(channels[index], src, false); - attachAudioToSource(channels[index], data, src.isLooping()); + // Set all source parameters and attach the audio data + channelSources[index] = src; + setSourceParams(sourceId, src, false); + attachAudioToSource(sourceId, audioData, src.isLooping()); } - al.alSourcePlay(channels[src.getChannel()]); - src.setStatus(Status.Playing); + // play the channel + int sourceId = channels[src.getChannel()]; + al.alSourcePlay(sourceId); + src.setStatus(Status.Playing); // Update JME status } } + /** + * Pauses a playing audio source. + * @param src The audio source to pause. + */ + @Override public void pauseSource(AudioSource src) { checkDead(); synchronized (threadLock) { @@ -1094,34 +1424,53 @@ public void pauseSource(AudioSource src) { return; } + AudioData audioData = src.getAudioData(); + if (audioData == null) { + logger.log(Level.WARNING, "pauseSource called on source with null AudioData: {0}", src); + return; + } + if (src.getStatus() == Status.Playing) { assert src.getChannel() != -1; - al.alSourcePause(channels[src.getChannel()]); - src.setStatus(Status.Paused); + int sourceId = channels[src.getChannel()]; + al.alSourcePause(sourceId); + src.setStatus(Status.Paused); // Update JME status } } } + /** + * Stops a playing or paused audio source, releasing its channel. + * For streams, resets or closes the stream. + * @param src The audio source to stop. + */ + @Override public void stopSource(AudioSource src) { synchronized (threadLock) { if (audioDisabled) { return; } + AudioData audioData = src.getAudioData(); + if (audioData == null) { + logger.log(Level.WARNING, "stopSource called on source with null AudioData: {0}", src); + return; + } + if (src.getStatus() != Status.Stopped) { - int chan = src.getChannel(); - assert chan != -1; // if it's not stopped, must have id + int channel = src.getChannel(); + assert channel != -1; // if it's not stopped, must have id src.setStatus(Status.Stopped); src.setChannel(-1); - clearChannel(chan); - freeChannel(chan); - + clearChannel(channel); + freeChannel(channel); + if (src.getAudioData() instanceof AudioStream) { - // If the stream is seekable, then rewind it. - // Otherwise, close it, as it is no longer valid. - AudioStream stream = (AudioStream)src.getAudioData(); + // If the stream is seekable, rewind it to the beginning. + // Otherwise (non-seekable), close it, as it might be invalid now. + AudioStream stream = (AudioStream) src.getAudioData(); if (stream.isSeekable()) { stream.setTime(0); } else { @@ -1132,105 +1481,140 @@ public void stopSource(AudioSource src) { } } - private int convertFormat(AudioData ad) { - switch (ad.getBitsPerSample()) { - case 8: - if (ad.getChannels() == 1) { - return AL_FORMAT_MONO8; - } else if (ad.getChannels() == 2) { - return AL_FORMAT_STEREO8; - } - - break; - case 16: - if (ad.getChannels() == 1) { - return AL_FORMAT_MONO16; - } else { - return AL_FORMAT_STEREO16; - } + /** + * Gets the corresponding OpenAL format enum for the audio data properties. + * @param audioData The AudioData. + * @return The OpenAL format enum. + * @throws UnsupportedOperationException if the format is not supported. + */ + private int getOpenALFormat(AudioData audioData) { + + int channels = audioData.getChannels(); + int bitsPerSample = audioData.getBitsPerSample(); + + if (channels == 1) { + if (bitsPerSample == 8) { + return AL_FORMAT_MONO8; + } else if (bitsPerSample == 16) { + return AL_FORMAT_MONO16; + } + } else if (channels == 2) { + if (bitsPerSample == 8) { + return AL_FORMAT_STEREO8; + } else if (bitsPerSample == 16) { + return AL_FORMAT_STEREO16; + } } - throw new UnsupportedOperationException("Unsupported channels/bits combination: " - + "bits=" + ad.getBitsPerSample() + ", channels=" + ad.getChannels()); + + throw new UnsupportedOperationException("Unsupported audio format: " + + channels + " channels, " + bitsPerSample + " bits per sample."); } + /** + * Uploads buffer data to OpenAL. Generates buffer ID if needed. + * @param ab The AudioBuffer. + */ private void updateAudioBuffer(AudioBuffer ab) { int id = ab.getId(); if (ab.getId() == -1) { - ib.position(0).limit(1); + ib.clear().limit(1); al.alGenBuffers(1, ib); id = ib.get(0); ab.setId(id); + // Register for automatic cleanup if unused objManager.registerObject(ab); } - ab.getData().clear(); - al.alBufferData(id, convertFormat(ab), ab.getData(), ab.getData().capacity(), ab.getSampleRate()); + ByteBuffer data = ab.getData(); + + data.clear(); // Ensure buffer is ready for reading + int format = getOpenALFormat(ab); + int sampleRate = ab.getSampleRate(); + + al.alBufferData(id, format, data, data.capacity(), sampleRate); ab.clearUpdateNeeded(); } + /** + * Prepares OpenAL buffers for an AudioStream. Generates buffer IDs. + * Does not fill buffers with data yet. + * @param as The AudioStream. + */ private void updateAudioStream(AudioStream as) { + // Delete old buffers if they exist (e.g., re-initializing stream) if (as.getIds() != null) { deleteAudioData(as); } int[] ids = new int[STREAMING_BUFFER_COUNT]; - ib.position(0).limit(STREAMING_BUFFER_COUNT); + ib.clear().limit(STREAMING_BUFFER_COUNT); + al.alGenBuffers(STREAMING_BUFFER_COUNT, ib); - ib.position(0).limit(STREAMING_BUFFER_COUNT); - ib.get(ids); - // Not registered with object manager. - // AudioStreams can be handled without object manager - // since their lifecycle is known to the audio renderer. + ib.rewind(); + ib.get(ids); + // Streams are managed directly, not via NativeObjectManager, + // because their lifecycle is tied to active playback. as.setIds(ids); as.clearUpdateNeeded(); } - private void updateAudioData(AudioData ad) { - if (ad instanceof AudioBuffer) { - updateAudioBuffer((AudioBuffer) ad); - } else if (ad instanceof AudioStream) { - updateAudioStream((AudioStream) ad); + private void updateAudioData(AudioData audioData) { + if (audioData instanceof AudioBuffer) { + updateAudioBuffer((AudioBuffer) audioData); + } else if (audioData instanceof AudioStream) { + updateAudioStream((AudioStream) audioData); } } + /** + * Deletes the OpenAL filter object associated with the Filter. + * @param filter The Filter object. + */ + @Override public void deleteFilter(Filter filter) { int id = filter.getId(); if (id != -1) { - ib.position(0).limit(1); + ib.clear().limit(1); ib.put(id).flip(); efx.alDeleteFilters(1, ib); filter.resetObject(); } } - public void deleteAudioData(AudioData ad) { + /** + * Deletes the OpenAL objects associated with the AudioData. + * @param audioData The AudioData to delete. + */ + @Override + public void deleteAudioData(AudioData audioData) { synchronized (threadLock) { if (audioDisabled) { return; } - if (ad instanceof AudioBuffer) { - AudioBuffer ab = (AudioBuffer) ad; + if (audioData instanceof AudioBuffer) { + AudioBuffer ab = (AudioBuffer) audioData; int id = ab.getId(); if (id != -1) { ib.put(0, id); - ib.position(0).limit(1); + ib.clear().limit(1); al.alDeleteBuffers(1, ib); - ab.resetObject(); + ab.resetObject(); // Mark as deleted on JME side } - } else if (ad instanceof AudioStream) { - AudioStream as = (AudioStream) ad; + } else if (audioData instanceof AudioStream) { + AudioStream as = (AudioStream) audioData; int[] ids = as.getIds(); if (ids != null) { ib.clear(); ib.put(ids).flip(); al.alDeleteBuffers(ids.length, ib); - as.resetObject(); + as.resetObject(); // Mark as deleted on JME side } } } } + } diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALC.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALC.java index 0d3c6301fe..9e23965f92 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALC.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALC.java @@ -61,6 +61,7 @@ public interface ALC { public static final int ALC_ALL_DEVICES_SPECIFIER = 0x1013; //public static ALCCapabilities createCapabilities(long device); + public static final int ALC_CONNECTED = 0x313; /** * Creates an AL context. @@ -82,24 +83,36 @@ public interface ALC { /** * Obtains string value(s) from ALC. * - * @param parameter the information to query. One of:
    {@link #ALC_DEFAULT_DEVICE_SPECIFIER DEFAULT_DEVICE_SPECIFIER}{@link #ALC_DEVICE_SPECIFIER DEVICE_SPECIFIER}{@link #ALC_EXTENSIONS EXTENSIONS}
    {@link #ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER CAPTURE_DEFAULT_DEVICE_SPECIFIER}{@link #ALC_CAPTURE_DEVICE_SPECIFIER CAPTURE_DEVICE_SPECIFIER}
    + * @param parameter the information to query. One of: + * {@link #ALC_DEFAULT_DEVICE_SPECIFIER DEFAULT_DEVICE_SPECIFIER} + * {@link #ALC_DEVICE_SPECIFIER DEVICE_SPECIFIER} + * {@link #ALC_EXTENSIONS EXTENSIONS} + * {@link #ALC_CAPTURE_DEFAULT_DEVICE_SPECIFIER CAPTURE_DEFAULT_DEVICE_SPECIFIER} + * {@link #ALC_CAPTURE_DEVICE_SPECIFIER CAPTURE_DEVICE_SPECIFIER} + * @return the parameter value */ public String alcGetString(int parameter); /** * Verifies that a given extension is available for the current context and the device it is associated with. - *

    + * *

    Invalid and unsupported string tokens return ALC_FALSE. A {@code NULL} deviceHandle is acceptable. {@code extName} is not case sensitive – the implementation * will convert the name to all upper-case internally (and will express extension names in upper-case).

    * * @param extension the extension name. + * @return true if the extension is available, otherwise false */ public boolean alcIsExtensionPresent(String extension); /** * Obtains integer value(s) from ALC. * - * @param param the information to query. One of:
    {@link #ALC_MAJOR_VERSION MAJOR_VERSION}{@link #ALC_MINOR_VERSION MINOR_VERSION}{@link #ALC_ATTRIBUTES_SIZE ATTRIBUTES_SIZE}{@link #ALC_ALL_ATTRIBUTES ALL_ATTRIBUTES}{@link #ALC_CAPTURE_SAMPLES CAPTURE_SAMPLES}
    + * @param param the information to query. One of: + * {@link #ALC_MAJOR_VERSION MAJOR_VERSION} + * {@link #ALC_MINOR_VERSION MINOR_VERSION} + * {@link #ALC_ATTRIBUTES_SIZE ATTRIBUTES_SIZE} + * {@link #ALC_ALL_ATTRIBUTES ALL_ATTRIBUTES} + * {@link #ALC_CAPTURE_SAMPLES CAPTURE_SAMPLES} * @param buffer the destination buffer. * @param size the buffer size. */ @@ -107,7 +120,7 @@ public interface ALC { /** * Pauses a playback device. - *

    + * *

    When paused, no contexts associated with the device will be processed or updated. Playing sources will not produce sound, have their offsets * incremented, or process any more buffers, until the device is resumed. Pausing a device that is already paused is a legal no-op.

    */ @@ -115,10 +128,10 @@ public interface ALC { /** * Resumes playback of a paused device. - *

    + * *

    This will restart processing on the device -- sources will resume playing sound as normal. Resuming playback on a device that is not paused is a legal * no-op.

    - *

    + * *

    These functions are not reference counted. alcDeviceResumeSOFT only needs to be called once to resume playback, regardless of how many times * {@link #alcDevicePauseSOFT DevicePauseSOFT} was called.

    */ diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/ALUtil.java b/jme3-core/src/main/java/com/jme3/audio/openal/ALUtil.java index 9a69b6b37a..1d891b83a0 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/ALUtil.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/ALUtil.java @@ -29,7 +29,8 @@ public static String getALErrorMessage(int errorCode) { errorText = "Out of Memory"; break; default: - errorText = "Unknown Error Code: " + String.valueOf(errorCode); + errorText = "Unknown Error Code: " + errorCode; + break; } return errorText; } diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/EFX.java b/jme3-core/src/main/java/com/jme3/audio/openal/EFX.java index 4ae5462632..32d572da09 100644 --- a/jme3-core/src/main/java/com/jme3/audio/openal/EFX.java +++ b/jme3-core/src/main/java/com/jme3/audio/openal/EFX.java @@ -27,19 +27,19 @@ public interface EFX { /* Effect properties. */ /* Reverb effect parameters */ - public static final int AL_REVERB_DENSITY = 0x0001; - public static final int AL_REVERB_DIFFUSION = 0x0002; - public static final int AL_REVERB_GAIN = 0x0003; - public static final int AL_REVERB_GAINHF = 0x0004; - public static final int AL_REVERB_DECAY_TIME = 0x0005; - public static final int AL_REVERB_DECAY_HFRATIO = 0x0006; - public static final int AL_REVERB_REFLECTIONS_GAIN = 0x0007; - public static final int AL_REVERB_REFLECTIONS_DELAY = 0x0008; - public static final int AL_REVERB_LATE_REVERB_GAIN = 0x0009; - public static final int AL_REVERB_LATE_REVERB_DELAY = 0x000A; + public static final int AL_REVERB_DENSITY = 0x0001; + public static final int AL_REVERB_DIFFUSION = 0x0002; + public static final int AL_REVERB_GAIN = 0x0003; + public static final int AL_REVERB_GAINHF = 0x0004; + public static final int AL_REVERB_DECAY_TIME = 0x0005; + public static final int AL_REVERB_DECAY_HFRATIO = 0x0006; + public static final int AL_REVERB_REFLECTIONS_GAIN = 0x0007; + public static final int AL_REVERB_REFLECTIONS_DELAY = 0x0008; + public static final int AL_REVERB_LATE_REVERB_GAIN = 0x0009; + public static final int AL_REVERB_LATE_REVERB_DELAY = 0x000A; public static final int AL_REVERB_AIR_ABSORPTION_GAINHF = 0x000B; - public static final int AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C; - public static final int AL_REVERB_DECAY_HFLIMIT = 0x000D; + public static final int AL_REVERB_ROOM_ROLLOFF_FACTOR = 0x000C; + public static final int AL_REVERB_DECAY_HFLIMIT = 0x000D; /* EAX Reverb effect parameters */ //#define AL_EAXREVERB_DENSITY 0x0001 @@ -109,11 +109,11 @@ public interface EFX { //#define AL_VOCAL_MORPHER_WAVEFORM 0x0005 //#define AL_VOCAL_MORPHER_RATE 0x0006 // - ///* Pitchshifter effect parameters */ + ///* Pitch-shifter effect parameters */ //#define AL_PITCH_SHIFTER_COARSE_TUNE 0x0001 //#define AL_PITCH_SHIFTER_FINE_TUNE 0x0002 // - ///* Ringmodulator effect parameters */ + ///* Ring-modulator effect parameters */ //#define AL_RING_MODULATOR_FREQUENCY 0x0001 //#define AL_RING_MODULATOR_HIGHPASS_CUTOFF 0x0002 //#define AL_RING_MODULATOR_WAVEFORM 0x0003 @@ -171,28 +171,28 @@ public interface EFX { ///* Filter properties. */ /* Lowpass filter parameters */ - public static final int AL_LOWPASS_GAIN = 0x0001; - public static final int AL_LOWPASS_GAINHF = 0x0002; + public static final int AL_LOWPASS_GAIN = 0x0001; + public static final int AL_LOWPASS_GAINHF = 0x0002; - ///* Highpass filter parameters */ - //#define AL_HIGHPASS_GAIN 0x0001 - //#define AL_HIGHPASS_GAINLF 0x0002 + // * Highpass filter parameters */ + public static final int AL_HIGHPASS_GAIN = 0x0001; + public static final int AL_HIGHPASS_GAINLF = 0x0002; - ///* Bandpass filter parameters */ - //#define AL_BANDPASS_GAIN 0x0001 - //#define AL_BANDPASS_GAINLF 0x0002 - //#define AL_BANDPASS_GAINHF 0x0003 + // * Bandpass filter parameters */ + public static final int AL_BANDPASS_GAIN = 0x0001; + public static final int AL_BANDPASS_GAINLF = 0x0002; + public static final int AL_BANDPASS_GAINHF = 0x0003; /* Filter type */ //#define AL_FILTER_FIRST_PARAMETER 0x0000 //#define AL_FILTER_LAST_PARAMETER 0x8000 - public static final int AL_FILTER_TYPE = 0x8001; + public static final int AL_FILTER_TYPE = 0x8001; /* Filter types, used with the AL_FILTER_TYPE property */ - public static final int AL_FILTER_NULL = 0x0000; - public static final int AL_FILTER_LOWPASS = 0x0001; - public static final int AL_FILTER_HIGHPASS = 0x0002; - //#define AL_FILTER_BANDPASS 0x0003 + public static final int AL_FILTER_NULL = 0x0000; + public static final int AL_FILTER_LOWPASS = 0x0001; + public static final int AL_FILTER_HIGHPASS = 0x0002; + public static final int AL_FILTER_BANDPASS = 0x0003; ///* Filter ranges and defaults. */ // diff --git a/jme3-core/src/main/java/com/jme3/audio/openal/package-info.java b/jme3-core/src/main/java/com/jme3/audio/openal/package-info.java new file mode 100644 index 0000000000..1cecaa2317 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/openal/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio support based on the OpenAL API + */ +package com.jme3.audio.openal; diff --git a/jme3-core/src/main/java/com/jme3/audio/package-info.java b/jme3-core/src/main/java/com/jme3/audio/package-info.java new file mode 100644 index 0000000000..924c74c262 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/audio/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio support, including music and sound effects + */ +package com.jme3.audio; diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java index 6e422dea42..6b0e023b34 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2013 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ import java.io.IOException; import java.nio.FloatBuffer; //import com.jme.scene.TriMesh; +import java.util.Objects; /** * BoundingBox describes a bounding volume as an axis-aligned box. @@ -68,7 +69,7 @@ public class BoundingBox extends BoundingVolume { * the Z-extent of the box (>=0, may be +Infinity) */ float zExtent; - + /** * Instantiate a BoundingBox without initializing it. */ @@ -102,10 +103,19 @@ public BoundingBox(BoundingBox source) { this.zExtent = source.zExtent; } + /** + * Instantiate a BoundingBox with the specified extremes. + * + * @param min the desired minimum coordinate value for each axis (not null, + * not altered) + * @param max the desired maximum coordinate value for each axis (not null, + * not altered) + */ public BoundingBox(Vector3f min, Vector3f max) { setMinMax(min, max); } + @Override public Type getType() { return Type.AABB; } @@ -113,10 +123,11 @@ public Type getType() { /** * computeFromPoints creates a new Bounding Box from a given * set of points. It uses the containAABB method as default. - * + * * @param points * the points to contain. */ + @Override public void computeFromPoints(FloatBuffer points) { containAABB(points); } @@ -124,10 +135,10 @@ public void computeFromPoints(FloatBuffer points) { /** * computeFromTris creates a new Bounding Box from a given * set of triangles. It is used in OBBTree calculations. - * - * @param tris - * @param start - * @param end + * + * @param tris triangle data (unaffected) + * @param start the index of the first triangle to be used + * @param end the index of the triangle after the last one to be used */ public void computeFromTris(Triangle[] tris, int start, int end) { if (end - start <= 0) { @@ -136,8 +147,10 @@ public void computeFromTris(Triangle[] tris, int start, int end) { TempVars vars = TempVars.get(); - Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); - Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); + Vector3f min = vars.vect1.set(new Vector3f(Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY)); + Vector3f max = vars.vect2.set(new Vector3f(Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); Vector3f point; for (int i = start; i < end; i++) { @@ -219,7 +232,7 @@ public static void checkMinMax(Vector3f min, Vector3f max, Vector3f point) { * containAABB creates a minimum-volume axis-aligned bounding * box of the points, then selects the smallest enclosing sphere of the box * with the sphere centered at the boxes center. - * + * * @param points * the list of points. */ @@ -235,12 +248,16 @@ public void containAABB(FloatBuffer points) { } TempVars vars = TempVars.get(); - + float[] tmpArray = vars.skinPositions; - float minX = Float.POSITIVE_INFINITY, minY = Float.POSITIVE_INFINITY, minZ = Float.POSITIVE_INFINITY; - float maxX = Float.NEGATIVE_INFINITY, maxY = Float.NEGATIVE_INFINITY, maxZ = Float.NEGATIVE_INFINITY; - + float minX = Float.POSITIVE_INFINITY, + minY = Float.POSITIVE_INFINITY, + minZ = Float.POSITIVE_INFINITY; + float maxX = Float.NEGATIVE_INFINITY, + maxY = Float.NEGATIVE_INFINITY, + maxZ = Float.NEGATIVE_INFINITY; + int iterations = (int) FastMath.ceil(points.limit() / ((float) tmpArray.length)); for (int i = iterations - 1; i >= 0; i--) { int bufLength = Math.min(tmpArray.length, points.remaining()); @@ -248,9 +265,9 @@ public void containAABB(FloatBuffer points) { for (int j = 0; j < bufLength; j += 3) { vars.vect1.x = tmpArray[j]; - vars.vect1.y = tmpArray[j+1]; - vars.vect1.z = tmpArray[j+2]; - + vars.vect1.y = tmpArray[j + 1]; + vars.vect1.z = tmpArray[j + 2]; + if (vars.vect1.x < minX) { minX = vars.vect1.x; } @@ -287,12 +304,13 @@ public void containAABB(FloatBuffer points) { /** * transform modifies the center of the box to reflect the * change made via a rotation, translation and scale. - * - * @param trans + * + * @param trans * the transform to apply * @param store * box to store result in */ + @Override public BoundingVolume transform(Transform trans, BoundingVolume store) { BoundingBox box; @@ -314,7 +332,9 @@ public BoundingVolume transform(Transform trans, BoundingVolume store) { transMatrix.absoluteLocal(); Vector3f scale = trans.getScale(); - vars.vect1.set(xExtent * FastMath.abs(scale.x), yExtent * FastMath.abs(scale.y), zExtent * FastMath.abs(scale.z)); + vars.vect1.set(xExtent * FastMath.abs(scale.x), + yExtent * FastMath.abs(scale.y), + zExtent * FastMath.abs(scale.z)); transMatrix.mult(vars.vect1, vars.vect2); // Assign the biggest rotations after scales. box.xExtent = FastMath.abs(vars.vect2.getX()); @@ -326,6 +346,7 @@ public BoundingVolume transform(Transform trans, BoundingVolume store) { return box; } + @Override public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { BoundingBox box; if (store == null || store.getType() != Type.AABB) { @@ -335,7 +356,6 @@ public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { } TempVars vars = TempVars.get(); - float w = trans.multProj(center, box.center); box.center.divideLocal(w); @@ -361,10 +381,11 @@ public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { /** * whichSide takes a plane (typically provided by a view * frustum) to determine which side this bound is on. - * + * * @param plane * the plane to check against. */ + @Override public Plane.Side whichSide(Plane plane) { float radius = FastMath.abs(xExtent * plane.getNormal().getX()) + FastMath.abs(yExtent * plane.getNormal().getY()) @@ -392,6 +413,7 @@ public Plane.Side whichSide(Plane plane) { * @return this box (with its components modified) or null if the second * volume is of some type other than AABB or Sphere */ + @Override public BoundingVolume merge(BoundingVolume volume) { return mergeLocal(volume); } @@ -406,6 +428,7 @@ public BoundingVolume merge(BoundingVolume volume) { * @return this box (with its components modified) or null if the second * volume is of some type other than AABB or Sphere */ + @Override public BoundingVolume mergeLocal(BoundingVolume volume) { if (volume == null) { return this; @@ -425,15 +448,14 @@ public BoundingVolume mergeLocal(BoundingVolume volume) { // case OBB: { // return mergeOBB((OrientedBoundingBox) volume); // } - default: return null; } } - /** + /* * Merges this AABB with the given OBB. - * + * * @param volume * the OBB to merge this AABB with. * @return This AABB extended to fit the given OBB. @@ -474,6 +496,7 @@ public BoundingVolume mergeLocal(BoundingVolume volume) { // zExtent = max.z - center.z; // return this; // } + /** * mergeLocal combines this bounding box locally with a second * bounding box described by its center and extents. @@ -542,12 +565,13 @@ private BoundingBox mergeLocal(Vector3f c, float x, float y, float z) { /** * clone creates a new BoundingBox object containing the same * data as this one. - * + * * @param store * where to store the cloned information. if null or wrong class, * a new store is created. * @return the new BoundingBox */ + @Override public BoundingVolume clone(BoundingVolume store) { if (store != null && store.getType() == Type.AABB) { BoundingBox rVal = (BoundingBox) store; @@ -564,9 +588,79 @@ public BoundingVolume clone(BoundingVolume store) { return rVal; } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingBox)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingBox otherBoundingBox = (BoundingBox) other; + if (Float.compare(xExtent, otherBoundingBox.xExtent) != 0) { + return false; + } else if (Float.compare(yExtent, otherBoundingBox.yExtent) != 0) { + return false; + } else if (Float.compare(zExtent, otherBoundingBox.zExtent) != 0) { + return false; + } else { + return super.equals(otherBoundingBox); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(xExtent, yExtent, zExtent); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding box, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param aabb the bounding box to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingBox aabb, float epsilon) { + if (aabb == null) { + return false; + } else if (Float.compare(Math.abs(aabb.xExtent - xExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.yExtent - yExtent), epsilon) > 0) { + return false; + } else if (Float.compare(Math.abs(aabb.zExtent - zExtent), epsilon) > 0) { + return false; + } else if (!center.isSimilar(aabb.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. - * The form is: "[Center: xExtent: X.XX yExtent: Y.YY zExtent: + * The form is: "[Center: vector xExtent: X.XX yExtent: Y.YY zExtent: * Z.ZZ]". * * @return the string representation of this. @@ -581,18 +675,20 @@ public String toString() { /** * intersects determines if this Bounding Box intersects with another given * bounding volume. If so, true is returned, otherwise, false is returned. - * - * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume) + * + * @see BoundingVolume#intersects(com.jme3.bounding.BoundingVolume) */ + @Override public boolean intersects(BoundingVolume bv) { return bv.intersectsBoundingBox(this); } /** * determines if this bounding box intersects a given bounding sphere. - * + * * @see BoundingVolume#intersectsSphere(com.jme3.bounding.BoundingSphere) */ + @Override public boolean intersectsSphere(BoundingSphere bs) { return bs.intersectsBoundingBox(this); } @@ -601,9 +697,10 @@ public boolean intersectsSphere(BoundingSphere bs) { * determines if this bounding box intersects a given bounding box. If the * two boxes intersect in any way, true is returned. Otherwise, false is * returned. - * + * * @see BoundingVolume#intersectsBoundingBox(com.jme3.bounding.BoundingBox) */ + @Override public boolean intersectsBoundingBox(BoundingBox bb) { assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bb.center); @@ -621,10 +718,10 @@ public boolean intersectsBoundingBox(BoundingBox bb) { } } - /** + /* * determines if this bounding box intersects with a given oriented bounding * box. - * + * * @see com.jme.bounding.BoundingVolume#intersectsOrientedBoundingBox(com.jme.bounding.OrientedBoundingBox) */ // public boolean intersectsOrientedBoundingBox(OrientedBoundingBox obb) { @@ -633,9 +730,10 @@ public boolean intersectsBoundingBox(BoundingBox bb) { /** * determines if this bounding box intersects with a given ray object. If an * intersection has occurred, true is returned, otherwise false is returned. - * - * @see BoundingVolume#intersects(com.jme3.math.Ray) + * + * @see BoundingVolume#intersects(com.jme3.math.Ray) */ + @Override public boolean intersects(Ray ray) { assert Vector3f.isValidVector(center); @@ -706,18 +804,18 @@ public boolean intersects(Ray ray) { } /** - * @see com.jme.bounding.BoundingVolume#intersectsWhere(com.jme.math.Ray) + * @see com.jme3.bounding.BoundingVolume#intersects(com.jme3.math.Ray) */ private int collideWithRay(Ray ray, CollisionResults results) { TempVars vars = TempVars.get(); - try { + try { Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center); Vector3f direction = vars.vect2.set(ray.direction); //float[] t = {0f, Float.POSITIVE_INFINITY}; float[] t = vars.fWdU; // use one of the tempvars arrays t[0] = 0; - t[1] = Float.POSITIVE_INFINITY; + t[1] = Float.POSITIVE_INFINITY; float saveT0 = t[0], saveT1 = t[1]; boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t) @@ -732,14 +830,14 @@ && clip(+direction.z, -diff.z - zExtent, t) float[] distances = t; Vector3f point0 = new Vector3f(ray.direction).multLocal(distances[0]).addLocal(ray.origin); Vector3f point1 = new Vector3f(ray.direction).multLocal(distances[1]).addLocal(ray.origin); - + CollisionResult result = new CollisionResult(point0, distances[0]); results.addCollision(result); result = new CollisionResult(point1, distances[1]); results.addCollision(result); return 2; } - + Vector3f point = new Vector3f(ray.direction).multLocal(t[0]).addLocal(ray.origin); CollisionResult result = new CollisionResult(point, t[0]); results.addCollision(result); @@ -753,14 +851,14 @@ && clip(+direction.z, -diff.z - zExtent, t) private int collideWithRay(Ray ray) { TempVars vars = TempVars.get(); - try { + try { Vector3f diff = vars.vect1.set(ray.origin).subtractLocal(center); Vector3f direction = vars.vect2.set(ray.direction); //float[] t = {0f, Float.POSITIVE_INFINITY}; float[] t = vars.fWdU; // use one of the tempvars arrays t[0] = 0; - t[1] = Float.POSITIVE_INFINITY; + t[1] = Float.POSITIVE_INFINITY; float saveT0 = t[0], saveT1 = t[1]; boolean notEntirelyClipped = clip(+direction.x, -diff.x - xExtent, t) @@ -771,15 +869,18 @@ && clip(+direction.z, -diff.z - zExtent, t) && clip(-direction.z, +diff.z - zExtent, t); if (notEntirelyClipped && (t[0] != saveT0 || t[1] != saveT1)) { - if (t[1] > t[0]) return 2; - else return 1; + if (t[1] > t[0]) { + return 2; + } else { + return 1; + } } return 0; } finally { vars.release(); } } - + @Override public int collideWith(Collidable other, CollisionResults results) { if (other instanceof Ray) { @@ -801,12 +902,12 @@ public int collideWith(Collidable other, CollisionResults results) { } return 0; } else if (other instanceof Spatial) { - return ((Spatial)other).collideWith(this, results); + return other.collideWith(this, results); } else { throw new UnsupportedCollisionException("With: " + other.getClass().getSimpleName()); } } - + @Override public int collideWith(Collidable other) { if (other instanceof Ray) { @@ -853,12 +954,13 @@ public boolean intersects(Vector3f point) { && FastMath.abs(center.z - point.z) <= zExtent; } + @Override public float distanceToEdge(Vector3f point) { // compute coordinates of point in box coordinate system - TempVars vars= TempVars.get(); + TempVars vars = TempVars.get(); Vector3f closest = vars.vect1; - - point.subtract(center,closest); + + point.subtract(center, closest); // project test point onto box float sqrDistance = 0.0f; @@ -893,7 +995,7 @@ public float distanceToEdge(Vector3f point) { sqrDistance += delta * delta; closest.z = zExtent; } - + vars.release(); return FastMath.sqrt(sqrDistance); } @@ -901,49 +1003,49 @@ public float distanceToEdge(Vector3f point) { /** * clip determines if a line segment intersects the current * test plane. - * + * * @param denom * the denominator of the line segment. - * @param numer + * @param numerator * the numerator of the line segment. * @param t * test values of the plane. * @return true if the line segment intersects the plane, false otherwise. */ - private boolean clip(float denom, float numer, float[] t) { + private boolean clip(float denom, float numerator, float[] t) { // Return value is 'true' if line segment intersects the current test - // plane. Otherwise 'false' is returned in which case the line segment + // plane. Otherwise, 'false' is returned, in which case the line segment // is entirely clipped. if (denom > 0.0f) { // This is the old if statement... - // if (numer > denom * t[1]) { + // if (numerator > denom * t[1]) { // // The problem is that what is actually stored is - // numer/denom. In non-floating point, this math should + // numerator/denom. In non-floating point, this math should // work out the same but in floating point there can // be subtle math errors. The multiply will exaggerate // errors that may have been introduced when the value - // was originally divided. + // was originally divided. // // This is especially true when the bounding box has zero // extents in some plane because the error rate is critical. // comparing a to b * c is not the same as comparing a/b to c - // in this case. In fact, I tried converting this method to - // double and the and the error was in the last decimal place. + // in this case. In fact, I tried converting this method to + // double and the and the error was in the last decimal place. // // So, instead, we now compare the divided version to the divided // version. We lose some slight performance here as divide // will be more expensive than the divide. Some microbenchmarks // show divide to be 3x slower than multiple on Java 1.6. - // BUT... we also saved a multiply in the non-clipped case because + // BUT... we also saved a multiply in the non-clipped case because // we can reuse the divided version in both if checks. // I think it's better to be right in this case. // // Bug that I'm fixing: rays going right through quads at certain // angles and distances because they fail the bounding box test. - // Many Bothans died bring you this fix. - // -pspeed - float newT = numer / denom; + // Many Bothans died bring you this fix. + // -pspeed + float newT = numerator / denom; if (newT > t[1]) { return false; } @@ -953,13 +1055,13 @@ private boolean clip(float denom, float numer, float[] t) { return true; } else if (denom < 0.0f) { // Old if statement... see above - // if (numer > denom * t[0]) { + // if (numerator > denom * t[0]) { // // Note though that denom is always negative in this block. // When we move it over to the other side we have to flip // the comparison. Algebra for the win. - float newT = numer / denom; - if (newT < t[0]) { + float newT = numerator / denom; + if (newT < t[0]) { return false; } if (newT < t[1]) { @@ -967,13 +1069,13 @@ private boolean clip(float denom, float numer, float[] t) { } return true; } else { - return numer <= 0.0; + return numerator <= 0.0; } } /** * Query extent. - * + * * @param store * where extent gets stored - null to return a new vector * @return store / new vector @@ -986,18 +1088,38 @@ public Vector3f getExtent(Vector3f store) { return store; } + /** + * Determine the X-axis distance between the center and the boundary. + * + * @return the distance + */ public float getXExtent() { return xExtent; } + /** + * Determine the Y-axis distance between the center and the boundary. + * + * @return the distance + */ public float getYExtent() { return yExtent; } + /** + * Determine the Z-axis distance between the center and the boundary. + * + * @return the distance + */ public float getZExtent() { return zExtent; } + /** + * Alter the X-axis distance between the center and the boundary. + * + * @param xExtent the desired distance (≥0) + */ public void setXExtent(float xExtent) { if (xExtent < 0) { throw new IllegalArgumentException(); @@ -1006,6 +1128,11 @@ public void setXExtent(float xExtent) { this.xExtent = xExtent; } + /** + * Alter the Y-axis distance between the center and the boundary. + * + * @param yExtent the desired distance (≥0) + */ public void setYExtent(float yExtent) { if (yExtent < 0) { throw new IllegalArgumentException(); @@ -1014,6 +1141,11 @@ public void setYExtent(float yExtent) { this.yExtent = yExtent; } + /** + * Alter the Z-axis distance between the center and the boundary. + * + * @param zExtent the desired distance (≥0) + */ public void setZExtent(float zExtent) { if (zExtent < 0) { throw new IllegalArgumentException(); @@ -1022,6 +1154,12 @@ public void setZExtent(float zExtent) { this.zExtent = zExtent; } + /** + * Determine the minimum coordinate value for each axis. + * + * @param store storage for the result (modified if not null) + * @return either storeResult or a new vector + */ public Vector3f getMin(Vector3f store) { if (store == null) { store = new Vector3f(); @@ -1030,6 +1168,12 @@ public Vector3f getMin(Vector3f store) { return store; } + /** + * Determine the maximum coordinate value for each axis. + * + * @param store storage for the result (modified if not null) + * @return either storeResult or a new vector + */ public Vector3f getMax(Vector3f store) { if (store == null) { store = new Vector3f(); @@ -1038,6 +1182,14 @@ public Vector3f getMax(Vector3f store) { return store; } + /** + * Reconfigure with the specified extremes. + * + * @param min the desired minimum coordinate value for each axis (not null, + * not altered) + * @param max the desired maximum coordinate value for each axis (not null, + * not altered) + */ public void setMinMax(Vector3f min, Vector3f max) { this.center.set(max).addLocal(min).multLocal(0.5f); xExtent = FastMath.abs(max.x - center.x); @@ -1055,9 +1207,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); xExtent = capsule.readFloat("xExtent", 0); yExtent = capsule.readFloat("yExtent", 0); zExtent = capsule.readFloat("zExtent", 0); @@ -1067,4 +1219,4 @@ public void read(JmeImporter e) throws IOException { public float getVolume() { return (8 * xExtent * yExtent * zExtent); } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java index 7936c72244..67cf7263f8 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; @@ -66,7 +67,7 @@ public class BoundingSphere extends BoundingVolume { private static final float RADIUS_EPSILON = 1f + 0.00001f; /** - * Default contstructor instantiates a new BoundingSphere + * Default constructor instantiates a new BoundingSphere * object. */ public BoundingSphere() { @@ -85,6 +86,7 @@ public BoundingSphere(float r, Vector3f c) { this.radius = r; } + @Override public Type getType() { return Type.Sphere; } @@ -116,6 +118,7 @@ public void setRadius(float radius) { * @param points * the points to contain. */ + @Override public void computeFromPoints(FloatBuffer points) { calcWelzl(points); } @@ -124,9 +127,9 @@ public void computeFromPoints(FloatBuffer points) { * computeFromTris creates a new Bounding Box from a given * set of triangles. It is used in OBBTree calculations. * - * @param tris - * @param start - * @param end + * @param tris triangle data (unaffected) + * @param start the index of the first triangle to be used + * @param end the index of the triangle after the last one to be used */ public void computeFromTris(Triangle[] tris, int start, int end) { if (end - start <= 0) { @@ -148,24 +151,24 @@ public void computeFromTris(Triangle[] tris, int start, int end) { // * computeFromTris creates a new Bounding Box from a given // * set of triangles. It is used in OBBTree calculations. // * -// * @param indices -// * @param mesh +// * @param indices +// * @param mesh // * @param start // * @param end // */ // public void computeFromTris(int[] indices, Mesh mesh, int start, int end) { -// if (end - start <= 0) { +// if (end - start <= 0) { // return; // } // -// Vector3f[] vertList = new Vector3f[(end - start) * 3]; +// Vector3f[] vertList = new Vector3f[(end - start) * 3]; // // int count = 0; // for (int i = start; i < end; i++) { -// mesh.getTriangle(indices[i], verts); -// vertList[count++] = new Vector3f(verts[0]); -// vertList[count++] = new Vector3f(verts[1]); -// vertList[count++] = new Vector3f(verts[2]); +// mesh.getTriangle(indices[i], verts); +// vertList[count++] = new Vector3f(verts[0]); +// vertList[count++] = new Vector3f(verts[1]); +// vertList[count++] = new Vector3f(verts[2]); // } // // averagePoints(vertList); @@ -175,9 +178,9 @@ public void computeFromTris(Triangle[] tris, int start, int end) { * Calculates a minimum bounding sphere for the set of points. The algorithm * was originally found in C++ at
    * - * http://flipcode.com/archives/Smallest_Enclosing_Spheres.shtml
    + * http://flipcode.com/archives/Smallest_Enclosing_Spheres.shtml
    * and translated to java by Cep21 - * + * * @param points * The points to calculate the minimum bounds from. */ @@ -204,7 +207,7 @@ public void calcWelzl(FloatBuffer points) { * The number of points currently considering to include with the * sphere. * @param ap - * A variable simulating pointer arithmatic from C++, and offset + * A variable simulating pointer arithmetic from C++, and offset * in points. */ private void recurseMini(FloatBuffer points, int p, int b, int ap) { @@ -380,9 +383,9 @@ public void averagePoints(Vector3f[] points) { * the transform to apply * @param store * sphere to store result in - * @return BoundingVolume - * @return ref + * @return either store or a new BoundingSphere */ + @Override public BoundingVolume transform(Transform trans, BoundingVolume store) { BoundingSphere sphere; if (store == null || store.getType() != BoundingVolume.Type.Sphere) { @@ -398,6 +401,7 @@ public BoundingVolume transform(Transform trans, BoundingVolume store) { return sphere; } + @Override public BoundingVolume transform(Matrix4f trans, BoundingVolume store) { BoundingSphere sphere; if (store == null || store.getType() != BoundingVolume.Type.Sphere) { @@ -441,6 +445,7 @@ private float getMaxAxis(Vector3f scale) { * the plane to check against. * @return side */ + @Override public Plane.Side whichSide(Plane plane) { float distance = plane.pseudoDistance(center); @@ -461,6 +466,7 @@ public Plane.Side whichSide(Plane plane) { * the sphere to combine with this sphere. * @return a new sphere */ + @Override public BoundingVolume merge(BoundingVolume volume) { if (volume == null) { return this; @@ -486,7 +492,7 @@ public BoundingVolume merge(BoundingVolume volume) { } // case OBB: { -// OrientedBoundingBox box = (OrientedBoundingBox) volume; +// OrientedBoundingBox box = (OrientedBoundingBox) volume; // BoundingSphere rVal = (BoundingSphere) this.clone(null); // return rVal.mergeOBB(box); // } @@ -506,6 +512,7 @@ public BoundingVolume merge(BoundingVolume volume) { * the sphere to combine with this sphere. * @return this */ + @Override public BoundingVolume mergeLocal(BoundingVolume volume) { if (volume == null) { return this; @@ -532,7 +539,7 @@ public BoundingVolume mergeLocal(BoundingVolume volume) { } // case OBB: { -// return mergeOBB((OrientedBoundingBox) volume); +// return mergeOBB((OrientedBoundingBox) volume); // } default: @@ -608,7 +615,7 @@ private BoundingVolume merge(float temp_radius, Vector3f temp_center, if (rCenter == null) { rVal.setCenter(rCenter = new Vector3f()); } - if (length > RADIUS_EPSILON) { + if (length > RADIUS_EPSILON && Float.isFinite(length)) { float coeff = (length + radiusDiff) / (2.0f * length); rCenter.set(center.addLocal(diff.multLocal(coeff))); } else { @@ -629,6 +636,7 @@ private BoundingVolume merge(float temp_radius, Vector3f temp_center, * a new store is created. * @return the new BoundingSphere */ + @Override public BoundingVolume clone(BoundingVolume store) { if (store != null && store.getType() == Type.Sphere) { BoundingSphere rVal = (BoundingSphere) store; @@ -644,9 +652,71 @@ public BoundingVolume clone(BoundingVolume store) { return new BoundingSphere(radius, center.clone()); } + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingSphere)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingSphere otherBoundingSphere = (BoundingSphere) other; + if (Float.compare(radius, otherBoundingSphere.getRadius()) != 0) { + return false; + } else { + return super.equals(otherBoundingSphere); + } + } + + /** + * Returns a hash code. If two bounding boxes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(radius); + hash = 59 * hash + super.hashCode(); + + return hash; + } + + /** + * Tests for approximate equality with the specified bounding sphere, using + * the specified tolerance. If {@code other} is null, false is returned. + * Either way, the current instance is unaffected. + * + * @param sphere the bounding sphere to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all components are within tolerance, otherwise false + */ + public boolean isSimilar(BoundingSphere sphere, float epsilon) { + if (sphere == null) { + return false; + } else if (Float.compare(Math.abs(sphere.getRadius() - radius), epsilon) > 0) { + return false; + } else if (!center.isSimilar(sphere.getCenter(), epsilon)) { + return false; + } + // The checkPlane field is ignored. + return true; + } + /** * toString returns the string representation of this object. - * The form is: "Radius: RRR.SSSS Center: ". + * The form is: "Radius: RRR.SSSS Center: vector". * * @return the string representation of this. */ @@ -661,6 +731,7 @@ public String toString() { * * @see com.jme.bounding.BoundingVolume#intersects(com.jme.bounding.BoundingVolume) */ + @Override public boolean intersects(BoundingVolume bv) { return bv.intersectsSphere(this); } @@ -670,6 +741,7 @@ public boolean intersects(BoundingVolume bv) { * * @see com.jme.bounding.BoundingVolume#intersectsSphere(com.jme.bounding.BoundingSphere) */ + @Override public boolean intersectsSphere(BoundingSphere bs) { return Intersection.intersect(bs, center, radius); } @@ -679,6 +751,7 @@ public boolean intersectsSphere(BoundingSphere bs) { * * @see com.jme.bounding.BoundingVolume#intersectsBoundingBox(com.jme.bounding.BoundingBox) */ + @Override public boolean intersectsBoundingBox(BoundingBox bb) { return Intersection.intersect(bb, center, radius); } @@ -697,6 +770,7 @@ public boolean intersectsBoundingBox(BoundingBox bb) { * * @see com.jme.bounding.BoundingVolume#intersects(com.jme.math.Ray) */ + @Override public boolean intersects(Ray ray) { assert Vector3f.isValidVector(center); @@ -801,39 +875,39 @@ private int collideWithRay(Ray ray) { } return 1; } - + private int collideWithTri(Triangle tri, CollisionResults results) { TempVars tvars = TempVars.get(); try { - + // Much of this is based on adaptation from this algorithm: // http://realtimecollisiondetection.net/blog/?p=103 // ...mostly the stuff about eliminating sqrts wherever // possible. - + // Math is done in center-relative space. Vector3f a = tri.get1().subtract(center, tvars.vect1); Vector3f b = tri.get2().subtract(center, tvars.vect2); Vector3f c = tri.get3().subtract(center, tvars.vect3); - + Vector3f ab = b.subtract(a, tvars.vect4); Vector3f ac = c.subtract(a, tvars.vect5); - + // Check the plane... if it doesn't intersect the plane // then it doesn't intersect the triangle. Vector3f n = ab.cross(ac, tvars.vect6); float d = a.dot(n); - float e = n.dot(n); - if( d * d > radius * radius * e ) { + float e = n.dot(n); + if (d * d > radius * radius * e) { // Can't possibly intersect return 0; } - + // We intersect the verts, or the edges, or the face... - + // First check against the face since it's the most // specific. - + // Calculate the barycentric coordinates of the // sphere center Vector3f v0 = ac; @@ -841,169 +915,170 @@ private int collideWithTri(Triangle tri, CollisionResults results) { // a was P relative, so p.subtract(a) is just -a // instead of wasting a vector we'll just negate the // dot products below... it's all v2 is used for. - Vector3f v2 = a; - + Vector3f v2 = a; + float dot00 = v0.dot(v0); float dot01 = v0.dot(v1); float dot02 = -v0.dot(v2); float dot11 = v1.dot(v1); float dot12 = -v1.dot(v2); - + float invDenom = 1 / (dot00 * dot11 - dot01 * dot01); float u = (dot11 * dot02 - dot01 * dot12) * invDenom; float v = (dot00 * dot12 - dot01 * dot02) * invDenom; - - if( u >= 0 && v >= 0 && (u + v) <= 1 ) { + + if (u >= 0 && v >= 0 && (u + v) <= 1) { // We intersect... and we even know where Vector3f part1 = ac; Vector3f part2 = ab; - Vector3f p = center.add(a.add(part1.mult(u)).addLocal(part2.mult(v))); - + Vector3f p = center.add(a.add(part1.mult(u)).addLocal(part2.mult(v))); + CollisionResult r = new CollisionResult(); - Vector3f normal = n.normalize(); + Vector3f normal = n.normalize(); float dist = -normal.dot(a); // a is center relative, so -a points to center dist = dist - radius; - + r.setDistance(dist); r.setContactNormal(normal); r.setContactPoint(p); results.addCollision(r); return 1; } - + // Check the edges looking for the nearest point // that is also less than the radius. We don't care // about points that are farther away than that. Vector3f nearestPt = null; - float nearestDist = radius * radius; - + float nearestDist = radius * radius; + Vector3f base; Vector3f edge; float t; - + // Edge AB base = a; - edge = ab; - + edge = ab; + t = -edge.dot(base) / edge.dot(edge); - if( t >= 0 && t <= 1 ) { + if (t >= 0 && t <= 1) { Vector3f Q = base.add(edge.mult(t, tvars.vect7), tvars.vect8); float distSq = Q.dot(Q); // distance squared to origin - if( distSq < nearestDist ) { + if (distSq < nearestDist) { nearestPt = Q; nearestDist = distSq; } } - + // Edge AC base = a; edge = ac; - + t = -edge.dot(base) / edge.dot(edge); - if( t >= 0 && t <= 1 ) { + if (t >= 0 && t <= 1) { Vector3f Q = base.add(edge.mult(t, tvars.vect7), tvars.vect9); float distSq = Q.dot(Q); // distance squared to origin - if( distSq < nearestDist ) { + if (distSq < nearestDist) { nearestPt = Q; nearestDist = distSq; } } - + // Edge BC base = b; - Vector3f bc = c.subtract(b); - edge = bc; - + Vector3f bc = c.subtract(b); + edge = bc; + t = -edge.dot(base) / edge.dot(edge); - if( t >= 0 && t <= 1 ) { + if (t >= 0 && t <= 1) { Vector3f Q = base.add(edge.mult(t, tvars.vect7), tvars.vect10); float distSq = Q.dot(Q); // distance squared to origin - if( distSq < nearestDist ) { + if (distSq < nearestDist) { nearestPt = Q; nearestDist = distSq; } } - + // If we have a point at all then it is going to be // closer than any vertex to center distance... so we're - // done. - if( nearestPt != null ) { + // done. + if (nearestPt != null) { // We have a hit float dist = FastMath.sqrt(nearestDist); Vector3f cn = nearestPt.divide(-dist); - - CollisionResult r = new CollisionResult(); + + CollisionResult r = new CollisionResult(); r.setDistance(dist - radius); r.setContactNormal(cn); r.setContactPoint(nearestPt.add(center)); results.addCollision(r); - - return 1; + + return 1; } - - // Finally check each of the triangle corners - + + // Finally, check each of the triangle corners. + // Vert A base = a; t = base.dot(base); // distance squared to origin - if( t < nearestDist ) { + if (t < nearestDist) { nearestDist = t; nearestPt = base; } - + // Vert B base = b; t = base.dot(base); // distance squared to origin - if( t < nearestDist ) { + if (t < nearestDist) { nearestDist = t; nearestPt = base; } - + // Vert C base = c; t = base.dot(base); // distance squared to origin - if( t < nearestDist ) { + if (t < nearestDist) { nearestDist = t; nearestPt = base; } - - if( nearestPt != null ) { + + if (nearestPt != null) { // We have a hit float dist = FastMath.sqrt(nearestDist); Vector3f cn = nearestPt.divide(-dist); - - CollisionResult r = new CollisionResult(); + + CollisionResult r = new CollisionResult(); r.setDistance(dist - radius); r.setContactNormal(cn); r.setContactPoint(nearestPt.add(center)); results.addCollision(r); - - return 1; + + return 1; } - - // Nothing hit... oh, well + + // Nothing hit... oh, well return 0; } finally { tvars.release(); } } - + + @Override public int collideWith(Collidable other, CollisionResults results) { if (other instanceof Ray) { Ray ray = (Ray) other; return collideWithRay(ray, results); - } else if (other instanceof Triangle){ + } else if (other instanceof Triangle) { Triangle t = (Triangle) other; return collideWithTri(t, results); } else if (other instanceof BoundingVolume) { - if (intersects((BoundingVolume)other)) { + if (intersects((BoundingVolume) other)) { CollisionResult result = new CollisionResult(); results.addCollision(result); return 1; } return 0; } else if (other instanceof Spatial) { - return ((Spatial)other).collideWith(this, results); + return other.collideWith(this, results); } else { throw new UnsupportedCollisionException(); } @@ -1014,16 +1089,15 @@ public int collideWith(Collidable other) { if (other instanceof Ray) { Ray ray = (Ray) other; return collideWithRay(ray); - } else if (other instanceof Triangle){ + } else if (other instanceof Triangle) { return super.collideWith(other); } else if (other instanceof BoundingVolume) { - return intersects((BoundingVolume)other) ? 1 : 0; + return intersects((BoundingVolume) other) ? 1 : 0; } else { throw new UnsupportedCollisionException(); } } - @Override public boolean contains(Vector3f point) { return center.distanceSquared(point) < (getRadius() * getRadius()); @@ -1034,6 +1108,7 @@ public boolean intersects(Vector3f point) { return center.distanceSquared(point) <= (getRadius() * getRadius()); } + @Override public float distanceToEdge(Vector3f point) { return center.distance(point) - radius; } @@ -1049,10 +1124,10 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); + public void read(JmeImporter importer) throws IOException { + super.read(importer); try { - radius = e.getCapsule(this).readFloat("radius", 0); + radius = importer.getCapsule(this).readFloat("radius", 0); } catch (IOException ex) { logger.logp(Level.SEVERE, this.getClass().toString(), "read(JMEImporter)", "Exception", ex); } @@ -1062,4 +1137,4 @@ public void read(JmeImporter e) throws IOException { public float getVolume() { return 4 * FastMath.ONE_THIRD * FastMath.PI * radius * radius * radius; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java index 4491fcedf1..3a80764910 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java +++ b/jme3-core/src/main/java/com/jme3/bounding/BoundingVolume.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,16 +40,16 @@ import com.jme3.util.TempVars; import java.io.IOException; import java.nio.FloatBuffer; +import java.util.Objects; /** * BoundingVolume defines an interface for dealing with * containment of a collection of points. - * + * * @author Mark Powell * @version $Id: BoundingVolume.java,v 1.24 2007/09/21 15:45:32 nca Exp $ */ public abstract class BoundingVolume implements Savable, Cloneable, Collidable { - /** * The type of bounding volume being used. */ @@ -57,13 +57,11 @@ public enum Type { /** * {@link BoundingSphere} */ - Sphere, - + Sphere, /** * {@link BoundingBox}. */ - AABB, - + AABB, /** * Currently unsupported by jME3. */ @@ -81,8 +79,9 @@ public BoundingVolume(Vector3f center) { } /** - * Grabs the checkplane we should check first. + * Grabs the plane we should check first. * + * @return the index of the plane to be checked first */ public int getCheckPlane() { return checkPlane; @@ -91,7 +90,7 @@ public int getCheckPlane() { /** * Sets the index of the plane that should be first checked during rendering. * - * @param value + * @param value the index of the plane to be checked first */ public final void setCheckPlane(int value) { checkPlane = value; @@ -99,11 +98,12 @@ public final void setCheckPlane(int value) { /** * getType returns the type of bounding volume this is. + * + * @return an enum value */ public abstract Type getType(); /** - * * transform alters the location of the bounding volume by a * rotation, translation and a scalar. * @@ -116,7 +116,6 @@ public final BoundingVolume transform(Transform trans) { } /** - * * transform alters the location of the bounding volume by a * rotation, translation and a scalar. * @@ -131,7 +130,6 @@ public final BoundingVolume transform(Transform trans) { public abstract BoundingVolume transform(Matrix4f trans, BoundingVolume store); /** - * * whichSide returns the side on which the bounding volume * lies on a plane. Possible values are POSITIVE_SIDE, NEGATIVE_SIDE, and * NO_SIDE. @@ -143,7 +141,6 @@ public final BoundingVolume transform(Transform trans) { public abstract Plane.Side whichSide(Plane plane); /** - * * computeFromPoints generates a bounding volume that * encompasses a collection of points. * @@ -184,6 +181,48 @@ public final BoundingVolume transform(Transform trans) { */ public abstract BoundingVolume clone(BoundingVolume store); + /** + * Tests for exact equality with the argument, distinguishing -0 from 0. If + * {@code other} is null, false is returned. Either way, the current + * instance is unaffected. + * + * @param other the object to compare (may be null, unaffected) + * @return true if {@code this} and {@code other} have identical values, + * otherwise false + */ + @Override + public boolean equals(Object other) { + if (!(other instanceof BoundingVolume)) { + return false; + } + + if (this == other) { + return true; + } + + BoundingVolume otherBoundingVolume = (BoundingVolume) other; + if (!center.equals(otherBoundingVolume.getCenter())) { + return false; + } + // The checkPlane field is ignored. + + return true; + } + + /** + * Returns a hash code. If two bounding volumes have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing + */ + @Override + public int hashCode() { + int hash = Objects.hash(center); + // The checkPlane field is ignored. + + return hash; + } + public final Vector3f getCenter() { return center; } @@ -204,7 +243,7 @@ public final void setCenter(float x, float y, float z) { /** * Find the distance from the center of this Bounding Volume to the given * point. - * + * * @param point * The point to get the distance to * @return distance @@ -216,7 +255,7 @@ public final float distanceTo(Vector3f point) { /** * Find the squared distance from the center of this Bounding Volume to the * given point. - * + * * @param point * The point to get the distance to * @return distance @@ -228,7 +267,7 @@ public final float distanceSquaredTo(Vector3f point) { /** * Find the distance from the nearest edge of this Bounding Volume to the given * point. - * + * * @param point * The point to get the distance to * @return distance @@ -255,7 +294,6 @@ public final float distanceSquaredTo(Vector3f point) { */ public abstract boolean intersects(Ray ray); - /** * determines if this bounding volume and a given bounding sphere are * intersecting. @@ -276,7 +314,7 @@ public final float distanceSquaredTo(Vector3f point) { */ public abstract boolean intersectsBoundingBox(BoundingBox bb); - /** + /* * determines if this bounding volume and a given bounding box are * intersecting. * @@ -284,13 +322,12 @@ public final float distanceSquaredTo(Vector3f point) { * the bounding box to test against. * @return true if this volume intersects the given bounding box. */ -// public abstract boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb); +// public abstract boolean intersectsOrientedBoundingBox(OrientedBoundingBox bb); /** - * * determines if a given point is contained within this bounding volume. * If the point is on the edge of the bounding volume, this method will * return false. Use intersects(Vector3f) to check for edge intersection. - * + * * @param point * the point to check * @return true if the point lies within this bounding volume. @@ -299,6 +336,7 @@ public final float distanceSquaredTo(Vector3f point) { /** * Determines if a given point intersects (touches or is inside) this bounding volume. + * * @param point the point to check * @return true if the point lies within this bounding volume. */ @@ -308,23 +346,25 @@ public final float distanceSquaredTo(Vector3f point) { @Override public BoundingVolume clone() { - try{ + try { BoundingVolume clone = (BoundingVolume) super.clone(); clone.center = center.clone(); return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } + @Override public void write(JmeExporter e) throws IOException { e.getCapsule(this).write(center, "center", Vector3f.ZERO); } - public void read(JmeImporter e) throws IOException { - center = (Vector3f) e.getCapsule(this).readSavable("center", Vector3f.ZERO.clone()); + @Override + public void read(JmeImporter importer) throws IOException { + center = (Vector3f) importer.getCapsule(this).readSavable("center", Vector3f.ZERO.clone()); } - + public int collideWith(Collidable other) { TempVars tempVars = TempVars.get(); try { @@ -336,4 +376,3 @@ public int collideWith(Collidable other) { } } } - diff --git a/jme3-core/src/main/java/com/jme3/bounding/Intersection.java b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java index 37beede012..bf7b056244 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/Intersection.java +++ b/jme3-core/src/main/java/com/jme3/bounding/Intersection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ /** * This class includes some utility methods for computing intersection * between bounding volumes and triangles. - * + * * @author Kirill */ public final class Intersection { @@ -62,36 +62,40 @@ public static boolean intersect(BoundingSphere sphere, Vector3f center, float ra vars.release(); } } - + public static boolean intersect(BoundingBox bbox, Vector3f center, float radius) { assert Vector3f.isValidVector(center) && Vector3f.isValidVector(bbox.center); - // Arvo's algorithm + // Arvo's algorithm float distSqr = radius * radius; - + float minX = bbox.center.x - bbox.xExtent; float maxX = bbox.center.x + bbox.xExtent; - + float minY = bbox.center.y - bbox.yExtent; float maxY = bbox.center.y + bbox.yExtent; - + float minZ = bbox.center.z - bbox.zExtent; float maxZ = bbox.center.z + bbox.zExtent; - - if (center.x < minX) distSqr -= FastMath.sqr(center.x - minX); - else if (center.x > maxX) distSqr -= FastMath.sqr(center.x - maxX); - - - if (center.y < minY) distSqr -= FastMath.sqr(center.y - minY); - else if (center.y > maxY) distSqr -= FastMath.sqr(center.y - maxY); - - - if (center.z < minZ) distSqr -= FastMath.sqr(center.z - minZ); - else if (center.z > maxZ) distSqr -= FastMath.sqr(center.z - maxZ); - + + if (center.x < minX) + distSqr -= FastMath.sqr(center.x - minX); + else if (center.x > maxX) + distSqr -= FastMath.sqr(center.x - maxX); + + if (center.y < minY) + distSqr -= FastMath.sqr(center.y - minY); + else if (center.y > maxY) + distSqr -= FastMath.sqr(center.y - maxY); + + if (center.z < minZ) + distSqr -= FastMath.sqr(center.z - minZ); + else if (center.z > maxZ) + distSqr -= FastMath.sqr(center.z - maxZ); + return distSqr > 0; } - + private static final void findMinMax(float x0, float x1, float x2, Vector3f minMax) { minMax.set(x0, x0, 0); if (x1 < minMax.x) { @@ -108,7 +112,7 @@ private static final void findMinMax(float x0, float x1, float x2, Vector3f minM } } - public static boolean intersect(Camera camera, Vector3f center,float radius){ + public static boolean intersect(Camera camera, Vector3f center, float radius) { for (int i = 5; i >= 0; i--) { if (camera.getWorldPlane(i).pseudoDistance(center) <= -radius) { return false; @@ -121,8 +125,8 @@ public static boolean intersect(Camera camera, Vector3f center,float radius){ // private boolean axisTestX01(float a, float b, float fa, float fb, // Vector3f center, Vector3f ext, // Vector3f v1, Vector3f v2, Vector3f v3){ -// float p0 = a * v0.y - b * v0.z; -// float p2 = a * v2.y - b * v2.z; +// float p0 = a * v0.y - b * v0.z; +// float p2 = a * v2.y - b * v2.z; // if(p0 < p2){ // min = p0; // max = p2; @@ -130,8 +134,8 @@ public static boolean intersect(Camera camera, Vector3f center,float radius){ // min = p2; // max = p0; // } -// float rad = fa * boxhalfsize.y + fb * boxhalfsize.z; -// if(min > rad || max < -rad) +// float rad = fa * boxhalfsize.y + fb * boxhalfsize.z; +// if(min > rad || max < -rad) // return false; // } public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vector3f v3) { @@ -140,12 +144,11 @@ public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vect // 1) the {x,y,z}-directions (actually, since we use the AABB of the triangle // we do not even need to test these) // 2) normal of the triangle - // 3) crossproduct(edge from tri, {x,y,z}-directin) + // 3) cross product (edge from tri, {x,y,z}-direction) // this gives 3x3=9 more tests TempVars vars = TempVars.get(); - Vector3f tmp0 = vars.vect1, tmp1 = vars.vect2, tmp2 = vars.vect3; @@ -179,8 +182,6 @@ public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vect float fey = FastMath.abs(e0.y); float fez = FastMath.abs(e0.z); - - //AXISTEST_X01(e0[Z], e0[Y], fez, fey); p0 = e0.z * tmp0.y - e0.y * tmp0.z; p2 = e0.z * tmp2.y - e0.y * tmp2.z; @@ -294,7 +295,6 @@ public static boolean intersect(BoundingBox bbox, Vector3f v1, Vector3f v2, Vect // that direction -- this is equivalent to testing a minimal AABB around // the triangle against the AABB - Vector3f minMax = vars.vect7; // test in X-direction diff --git a/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java b/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java index 1fe1624cbd..3c9a80f6b7 100644 --- a/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java +++ b/jme3-core/src/main/java/com/jme3/bounding/OrientedBoundingBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -108,7 +108,7 @@ // public final Vector3f[] vectorStore = new Vector3f[8]; // // private final Vector3f tempVk = new Vector3f(); -// private final Vector3f tempForword = new Vector3f(0, 0, 1); +// private final Vector3f tempForward = new Vector3f(0, 0, 1); // private final Vector3f tempLeft = new Vector3f(1, 0, 0); // private final Vector3f tempUp = new Vector3f(0, 1, 0); // @@ -506,7 +506,7 @@ //// Vector3f max = _compVect2.set(new Vector3f(Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY)); //// Vector3f point; //// for (int i = start; i < end; i++) { -//// mesh.getTriangle(indices[i], verts); +//// mesh.getTriangle(indices[i], verts); //// point = verts[0]; //// if (point.x < min.x) //// min.x = point.x; @@ -880,7 +880,7 @@ // // // convenience variables // Vector3f akA[] = new Vector3f[] { xAxis, yAxis, zAxis }; -// Vector3f[] akB = new Vector3f[] { tempForword, tempLeft, tempUp }; +// Vector3f[] akB = new Vector3f[] { tempForward, tempLeft, tempUp }; // Vector3f afEA = extent; // Vector3f afEB = tempVk.set(bb.xExtent, bb.yExtent, bb.zExtent); // @@ -1381,7 +1381,7 @@ // */ // private boolean clip(float denom, float numer, float[] t) { // // Return value is 'true' if line segment intersects the current test -// // plane. Otherwise 'false' is returned in which case the line segment +// // plane. Otherwise, 'false' is returned, in which case the line segment // // is entirely clipped. // if (denom > 0.0f) { // if (numer > denom * t[1]) diff --git a/jme3-core/src/main/java/com/jme3/bounding/package-info.java b/jme3-core/src/main/java/com/jme3/bounding/package-info.java new file mode 100644 index 0000000000..9630b89b68 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/bounding/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * bounding volumes for collision detection and scene-graph culling + */ +package com.jme3.bounding; diff --git a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java index 73db01d478..daca01b843 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/Cinematic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ import java.util.logging.Logger; /** - * An appstate for composing and playing cut scenes in a game. The cinematic + * An appstate for composing and playing cutscenes in a game. The cinematic * schedules CinematicEvents over a timeline. Once the Cinematic created it has * to be attached to the stateManager. * @@ -71,16 +71,16 @@ * Cinematic#enqueueCinematicEvent(com.jme3.cinematic.events.CinematicEvent) * that enqueue events one after the other according to their initialDuration * - * a cinematic has convenient methods to handle the playback : + * A Cinematic has convenient methods to manage playback: * @see Cinematic#play() * @see Cinematic#pause() * @see Cinematic#stop() * - * A cinematic is itself a CinematicEvent, meaning you can embed several - * Cinematics Embed cinematics must not be added to the stateManager though. + * A Cinematic is itself a CinematicEvent, meaning you can embed several + * cinematics. Embedded cinematics must not be added to the stateManager though. * - * Cinematic has a way to handle several point of view by creating CameraNode - * over a cam and activating them on schedule. + * Cinematic can handle several points of view by creating camera nodes + * and activating them on schedule. * @see Cinematic#bindCamera(java.lang.String, com.jme3.renderer.Camera) * @see Cinematic#activateCamera(float, java.lang.String) * @see Cinematic#setActiveCamera(java.lang.String) @@ -93,7 +93,7 @@ public class Cinematic extends AbstractCinematicEvent implements AppState { private Node scene; protected TimeLine timeLine = new TimeLine(); private int lastFetchedKeyFrame = -1; - private List cinematicEvents = new ArrayList<>(); + private final List cinematicEvents = new ArrayList<>(); private Map cameras = new HashMap<>(); private CameraNode currentCam; private boolean initialized = false; @@ -214,8 +214,8 @@ public void onPause() { /** * used internally for serialization * - * @param ex - * @throws IOException + * @param ex the exporter (not null) + * @throws IOException from the exporter */ @Override public void write(JmeExporter ex) throws IOException { @@ -230,10 +230,11 @@ public void write(JmeExporter ex) throws IOException { /** * used internally for serialization * - * @param im - * @throws IOException + * @param im the importer (not null) + * @throws IOException from the importer */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); @@ -270,13 +271,14 @@ public void setSpeed(float speed) { * @param stateManager the state manager * @param app the application */ + @Override public void initialize(AppStateManager stateManager, Application app) { initEvent(app, this); for (CinematicEvent cinematicEvent : cinematicEvents) { cinematicEvent.initEvent(app, this); } - if(!cameras.isEmpty()){ - for(CameraNode n : cameras.values()){ + if (!cameras.isEmpty()) { + for (CameraNode n : cameras.values()) { n.setCamera(app.getCamera()); } } @@ -288,6 +290,7 @@ public void initialize(AppStateManager stateManager, Application app) { * * @return true if initialized, otherwise false */ + @Override public boolean isInitialized() { return initialized; } @@ -296,8 +299,10 @@ public boolean isInitialized() { * Sets the unique ID of this app state. Note: that setting * this while an app state is attached to the state manager will * have no effect on ID-based lookups. + * + * @param id the desired ID */ - protected void setId( String id ) { + protected void setId(String id) { this.id = id; } @@ -312,6 +317,7 @@ public String getId() { * * @param enabled true or false */ + @Override public void setEnabled(boolean enabled) { if (enabled) { play(); @@ -324,6 +330,7 @@ public void setEnabled(boolean enabled) { * * @return true if enabled */ + @Override public boolean isEnabled() { return playState == PlayState.Playing; } @@ -333,6 +340,7 @@ public boolean isEnabled() { * * @param stateManager the state manager */ + @Override public void stateAttached(AppStateManager stateManager) { } @@ -341,6 +349,7 @@ public void stateAttached(AppStateManager stateManager) { * * @param stateManager the state manager */ + @Override public void stateDetached(AppStateManager stateManager) { stop(); } @@ -348,7 +357,7 @@ public void stateDetached(AppStateManager stateManager) { /** * called internally don't call it directly. * - * @param tpf + * @param tpf time per frame (in seconds) */ @Override public void update(float tpf) { @@ -360,7 +369,7 @@ public void update(float tpf) { /** * used internally, don't call this directly. * - * @param tpf + * @param tpf time per frame (in seconds) */ @Override public void onUpdate(float tpf) { @@ -396,7 +405,7 @@ public void setTime(float time) { super.setTime(time); int keyFrameIndex = timeLine.getKeyFrameIndexFromTime(time); - //triggering all the event from start to "time" + //triggering all the event from start to "time" //then computing timeOffset for each event for (int i = 0; i <= keyFrameIndex; i++) { KeyFrame keyFrame = timeLine.get(i); @@ -440,9 +449,8 @@ public KeyFrame addCinematicEvent(float timeStamp, CinematicEvent cinematicEvent } /** - * enqueue a cinematic event to a cinematic. This is a handy method when you - * want to chain event of a given duration without knowing their initial - * duration + * Enqueue a cinematic event to a Cinematic. This is handy when you + * want to chain events without knowing their durations. * * @param cinematicEvent the cinematic event to enqueue * @return the timestamp the event was scheduled. @@ -508,6 +516,7 @@ public boolean removeCinematicEvent(KeyFrame keyFrame, CinematicEvent cinematicE * * @see AppState#render(com.jme3.renderer.RenderManager) */ + @Override public void render(RenderManager rm) { } @@ -516,6 +525,7 @@ public void render(RenderManager rm) { * * @see AppState#postRender() */ + @Override public void postRender() { } @@ -524,6 +534,7 @@ public void postRender() { * * @see AppState#cleanup() */ + @Override public void cleanup() { } @@ -546,11 +557,11 @@ public void fitDuration() { } /** - * Binds a camera to this cinematic, tagged by a unique name. This methods - * creates and returns a CameraNode for the cam and attach it to the scene. + * Binds a camera to this Cinematic, tagged by a unique name. This method + * creates and returns a CameraNode for the cam and attaches it to the scene. * The control direction is set to SpatialToCamera. This camera Node can - * then be used in other events to handle the camera movements during the - * playback + * then be used in other events to handle the camera movements during + * playback. * * @param cameraName the unique tag the camera should have * @param cam the scene camera. @@ -558,7 +569,7 @@ public void fitDuration() { */ public CameraNode bindCamera(String cameraName, Camera cam) { if (cameras.containsKey(cameraName)) { - throw new IllegalArgumentException("Camera " + cameraName + " is already binded to this cinematic"); + throw new IllegalArgumentException("Camera " + cameraName + " is already bound to this cinematic"); } CameraNode node = new CameraNode(cameraName, cam); node.setControlDir(ControlDirection.SpatialToCamera); @@ -686,8 +697,8 @@ public void removeEventData(String type, Object key) { */ public void setScene(Node scene) { this.scene = scene; - if(!cameras.isEmpty()){ - for(CameraNode n : cameras.values()){ + if (!cameras.isEmpty()) { + for (CameraNode n : cameras.values()) { this.scene.attachChild(n); } } @@ -703,7 +714,7 @@ public Node getScene() { } /** - * clear the cinematic of its events. + * Remove all events from the Cinematic. */ public void clear() { dispose(); @@ -715,7 +726,7 @@ public void clear() { } /** - * used internally to cleanup the cinematic. Called when the clear() method + * used internally to clean up the cinematic. Called when the clear() method * is called */ @Override diff --git a/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java b/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java index 458a482a94..55dab48f52 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/KeyFrame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,9 @@ */ public class KeyFrame implements Savable { - public KeyFrame(){ - + public KeyFrame() { } - + List cinematicEvents = new ArrayList<>(); private int index; @@ -64,21 +63,24 @@ public List trigger() { } return cinematicEvents; } - - public boolean isEmpty(){ + + public boolean isEmpty() { return cinematicEvents.isEmpty(); } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.writeSavableArrayList((ArrayList) cinematicEvents, "cinematicEvents", null); oc.write(index, "index", 0); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); cinematicEvents = ic.readSavableArrayList("cinematicEvents", null); - index=ic.readInt("index", 0); + index = ic.readInt("index", 0); } public int getIndex() { @@ -88,6 +90,4 @@ public int getIndex() { public void setIndex(int index) { this.index = index; } - - } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java index a0f978f0a1..20e3232ad7 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/MotionPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,6 +45,8 @@ import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Curve; import com.jme3.util.TempVars; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -54,7 +56,7 @@ * Motion path is used to create a path between way points. * @author Nehon */ -public class MotionPath implements Savable { +public class MotionPath implements JmeCloneable, Savable { private Node debugNode; private AssetManager assetManager; @@ -69,10 +71,12 @@ public MotionPath() { } /** - * interpolate the path giving the time since the beginning and the motionControl + * interpolate the path giving the time since the beginning and the motionControl * this methods sets the new localTranslation to the spatial of the MotionEvent control. * @param time the time since the animation started * @param control the control over the moving spatial + * @param tpf time per frame (in seconds) + * @return the distance travelled (in world units) */ public float interpolatePath(float time, MotionEvent control, float tpf) { @@ -85,7 +89,7 @@ public float interpolatePath(float time, MotionEvent control, float tpf) { traveledDistance = time * (getLength() / control.getInitialDuration()); //getting waypoint index and current value from new traveled distance - v = getWayPointIndexForDistance(traveledDistance,v); + v = getWayPointIndexForDistance(traveledDistance, v); //setting values control.setCurrentWayPoint((int) v.x); @@ -106,7 +110,7 @@ public float interpolatePath(float time, MotionEvent control, float tpf) { public void checkWayPoint(MotionEvent control, float tpf) { //Epsilon varies with the tpf to avoid missing a waypoint on low framerate. - float epsilon = tpf * 4f; + float epsilon = tpf * 4f; if (control.getCurrentWayPoint() != prevWayPoint) { if (control.getCurrentValue() >= 0f && control.getCurrentValue() < epsilon) { triggerWayPointReach(control.getCurrentWayPoint(), control); @@ -129,13 +133,13 @@ private void attachDebugNode(Node root) { } switch (spline.getType()) { case CatmullRom: - debugNode.attachChild(CreateCatmullRomPath()); + debugNode.attachChild(createCatmullRomPath()); break; case Linear: - debugNode.attachChild(CreateLinearPath()); + debugNode.attachChild(createLinearPath()); break; default: - debugNode.attachChild(CreateLinearPath()); + debugNode.attachChild(createLinearPath()); break; } @@ -143,7 +147,7 @@ private void attachDebugNode(Node root) { } } - private Geometry CreateLinearPath() { + private Geometry createLinearPath() { Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); @@ -153,7 +157,7 @@ private Geometry CreateLinearPath() { return lineGeometry; } - private Geometry CreateCatmullRomPath() { + private Geometry createCatmullRomPath() { Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); @@ -173,18 +177,52 @@ public void write(JmeExporter ex) throws IOException { public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); spline = (Spline) in.readSavable("spline", null); + } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned MotionPath into a deep-cloned one, using the specified + * cloner and original to resolve copied fields. + * + * @param cloner the cloner that's cloning this MotionPath (not null) + * @param original the object from which this MotionPath was shallow-cloned + * (not null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.debugNode = cloner.clone(debugNode); + this.spline = cloner.clone(spline); + /* + * The clone will share both the asset manager and the list of listeners + * of the original MotionPath. + */ + } + /** + * Creates a shallow clone for the JME cloner. + * + * @return a new object + */ + @Override + public MotionPath jmeClone() { + try { + MotionPath clone = (MotionPath) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } } /** * compute the index of the waypoint and the interpolation value according to a distance * returns a vector 2 containing the index in the x field and the interpolation value in the y field * @param distance the distance traveled on this path + * @param store storage for the result (not null, modified) * @return the waypoint index and the interpolation value in a vector2 */ public Vector2f getWayPointIndexForDistance(float distance, Vector2f store) { float sum = 0; - if(spline.getTotalLength() == 0){ + if (spline.getTotalLength() == 0) { store.set(0, 0); return store; } @@ -192,7 +230,7 @@ public Vector2f getWayPointIndexForDistance(float distance, Vector2f store) { int i = 0; for (Float len : spline.getSegmentsLength()) { if (sum + len >= distance) { - return new Vector2f((float) i, (distance - sum) / len); + return new Vector2f(i, (distance - sum) / len); } sum += len; i++; @@ -260,7 +298,7 @@ public SplineType getPathSplineType() { /** * sets the type of spline used for the path interpolation for this path - * @param pathSplineType + * @param pathSplineType the desired type */ public void setPathSplineType(SplineType pathSplineType) { spline.setType(pathSplineType); @@ -290,7 +328,7 @@ public void disableDebugShape() { */ public void enableDebugShape(AssetManager manager, Node rootNode) { assetManager = manager; - // computeTotalLentgh(); + // computeTotalLength(); attachDebugNode(rootNode); } @@ -342,7 +380,8 @@ public float getCurveTension() { /** * sets the tension of the curve (only for catmull rom) 0.0 will give a linear curve, 1.0 a round curve - * @param curveTension + * + * @param curveTension the desired value */ public void setCurveTension(float curveTension) { spline.setCurveTension(curveTension); @@ -361,7 +400,8 @@ public void clearWayPoints() { /** * Sets the path to be a cycle - * @param cycle + * + * @param cycle true for a cycle, false for a non-cycle */ public void setCycle(boolean cycle) { diff --git a/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java b/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java index 2b78eca330..ed6d4dc99d 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/PlayState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,11 +37,11 @@ */ public enum PlayState { - /**The CinematicEvent is currently beeing played*/ + /** The CinematicEvent is currently being played. */ Playing, - /**The animatable has been paused*/ + /** The CinematicEvent is paused. */ Paused, - /**the animatable is stoped*/ + /** The CinematicEvent is stopped. */ Stopped } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java b/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java index e51c909ddb..b6faaf27aa 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/TimeLine.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,8 +81,6 @@ public void removeKeyFrame(int keyFrameIndex) { } } } - - public void removeKeyFrame(float time) { removeKeyFrame(getKeyFrameIndexFromTime(time)); @@ -91,9 +89,9 @@ public void removeKeyFrame(float time) { public int getKeyFrameIndexFromTime(float time) { return Math.round(time * keyFramesPerSeconds); } - + public float getKeyFrameTime(KeyFrame keyFrame) { - return (float)keyFrame.getIndex()/(float)keyFramesPerSeconds; + return keyFrame.getIndex() / (float) keyFramesPerSeconds; } public Collection getAllKeyFrames() { @@ -104,6 +102,8 @@ public int getLastKeyFrameIndex() { return lastKeyFrameIndex; } + @Override + @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); ArrayList list = new ArrayList(); @@ -111,6 +111,7 @@ public void write(JmeExporter ex) throws IOException { oc.writeSavableArrayList(list, "keyFrames", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); ArrayList list = ic.readSavableArrayList("keyFrames", null); diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java index 6fb4899c36..85bae7c857 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AbstractCinematicEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,8 +47,8 @@ /** * This call contains the basic behavior of a cinematic event. * Every cinematic event must extend this class. - * - * A cinematic event must be given an initial duration in seconds + * + * A cinematic event must be given an initial duration in seconds * (duration of the event at speed = 1). Default is 10 sec. * @author Nehon */ @@ -60,7 +60,7 @@ public abstract class AbstractCinematicEvent implements CinematicEvent { protected float speed = 1; protected float time = 0; protected boolean resuming = false; - + /** * The list of listeners. */ @@ -74,7 +74,8 @@ public AbstractCinematicEvent() { /** * Construct a cinematic event with the given initial duration. - * @param initialDuration + * + * @param initialDuration the desired duration (in seconds, default=10) */ public AbstractCinematicEvent(float initialDuration) { this.initialDuration = initialDuration; @@ -82,7 +83,8 @@ public AbstractCinematicEvent(float initialDuration) { /** * Construct a cinematic event with the given loopMode. - * @param loopMode + * + * @param loopMode the desired mode (Loop/DontLoop/Cycle) */ public AbstractCinematicEvent(LoopMode loopMode) { this.loopMode = loopMode; @@ -90,6 +92,7 @@ public AbstractCinematicEvent(LoopMode loopMode) { /** * Construct a cinematic event with the given loopMode and the given initialDuration. + * * @param initialDuration the duration of the event at speed = 1. * @param loopMode the loop mode of the event. */ @@ -97,22 +100,24 @@ public AbstractCinematicEvent(float initialDuration, LoopMode loopMode) { this.initialDuration = initialDuration; this.loopMode = loopMode; } - + /** - * Implement this method if the event needs different handling when + * Implement this method if the event needs different handling when * stopped naturally (when the event reach its end), * or when it was force-stopped during playback. * By default, this method just calls regular stop(). */ - public void forceStop(){ + @Override + public void forceStop() { stop(); } /** * Play this event. */ + @Override public void play() { - onPlay(); + onPlay(); playState = PlayState.Playing; if (listeners != null) { for (int i = 0; i < listeners.size(); i++) { @@ -131,19 +136,20 @@ public void play() { * Used internally only. * @param tpf time per frame. */ + @Override public void internalUpdate(float tpf) { if (playState == PlayState.Playing) { - time = time + (tpf * speed); + time = time + (tpf * speed); onUpdate(tpf); if (time >= initialDuration && loopMode == LoopMode.DontLoop) { stop(); - } else if(time >= initialDuration && loopMode == LoopMode.Loop){ + } else if (time >= initialDuration && loopMode == LoopMode.Loop) { setTime(0); - }else{ + } else { time = AnimationUtils.clampWrapTime(time, initialDuration, loopMode); - if(time<0){ - speed = - speed; - time = - time; + if (time < 0) { + speed = -speed; + time = -time; } } } @@ -151,16 +157,17 @@ public void internalUpdate(float tpf) { } /** - * Implement this method with the code that you want to execute on update + * Implement this method with the code that you want to execute on update * (only called when the event is playing). * @param tpf time per frame */ protected abstract void onUpdate(float tpf); /** - * Stops the animation. + * Stops the animation. * Next time when play() is called, the animation starts from the beginning. */ + @Override public void stop() { onStop(); time = 0; @@ -182,6 +189,7 @@ public void stop() { * Pause this event. * Next time when play() is called, the animation restarts from here. */ + @Override public void pause() { onPause(); playState = PlayState.Paused; @@ -202,6 +210,7 @@ public void pause() { * Returns the actual duration of the animation (initialDuration/speed) * @return the duration (in seconds) */ + @Override public float getDuration() { return initialDuration / speed; } @@ -210,8 +219,9 @@ public float getDuration() { * Sets the speed of the animation. * At speed = 1, the animation will last initialDuration seconds, * At speed = 2, the animation will last initialDuration/2... - * @param speed + * @param speed the desired speedup factor (default=1) */ + @Override public void setSpeed(float speed) { this.speed = speed; } @@ -220,14 +230,16 @@ public void setSpeed(float speed) { * Returns the speed of the animation. * @return the speed */ + @Override public float getSpeed() { return speed; } /** - * Returns the current playstate of the animation (playing or paused or stopped). + * Returns the current play state of the animation (playing or paused or stopped). * @return the enum value */ + @Override public PlayState getPlayState() { return playState; } @@ -236,14 +248,16 @@ public PlayState getPlayState() { * Returns the initial duration of the animation at speed = 1 in seconds. * @return the duration in seconds */ + @Override public float getInitialDuration() { return initialDuration; } /** * Sets the duration of the animation at speed = 1 in seconds. - * @param initialDuration + * @param initialDuration the desired duration (in de-scaled seconds) */ + @Override public void setInitialDuration(float initialDuration) { this.initialDuration = initialDuration; } @@ -253,6 +267,7 @@ public void setInitialDuration(float initialDuration) { * @see LoopMode * @return the enum value */ + @Override public LoopMode getLoopMode() { return loopMode; } @@ -260,8 +275,9 @@ public LoopMode getLoopMode() { /** * Sets the loopMode of the animation. * @see LoopMode - * @param loopMode + * @param loopMode the desired mode (default=DontLoop) */ + @Override public void setLoopMode(LoopMode loopMode) { this.loopMode = loopMode; } @@ -269,8 +285,9 @@ public void setLoopMode(LoopMode loopMode) { /** * Used for serialization only. * @param ex exporter - * @throws IOException + * @throws IOException from the exporter */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(playState, "playState", PlayState.Stopped); @@ -282,8 +299,9 @@ public void write(JmeExporter ex) throws IOException { /** * Used for serialization only. * @param im importer - * @throws IOException + * @throws IOException from the importer */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); playState = ic.readEnum("playState", PlayState.class, PlayState.Stopped); @@ -294,15 +312,17 @@ public void read(JmeImporter im) throws IOException { /** * Initialize this event (called internally only). - * @param app - * @param cinematic + * + * @param app ignored + * @param cinematic ignored */ + @Override public void initEvent(Application app, Cinematic cinematic) { } /** * Returns the list of CinematicEventListeners added to this event. - * @return + * @return */ private List getListeners() { if (listeners == null) { @@ -312,7 +332,8 @@ private List getListeners() { } /** - * Add a CinematicEventListener to this event. + * Adds a CinematicEventListener to this event. + * * @param listener CinematicEventListener */ public void addListener(CinematicEventListener listener) { @@ -320,7 +341,8 @@ public void addListener(CinematicEventListener listener) { } /** - * Remove a CinematicEventListener from this event. + * Removes a CinematicEventListener from this event. + * * @param listener CinematicEventListener */ public void removeListener(CinematicEventListener listener) { @@ -328,22 +350,24 @@ public void removeListener(CinematicEventListener listener) { } /** - * Fast-forward the event to the given timestamp. Time=0 is the start of the event. - * @param time the time to fast forward to. + * Fast-forwards the event to the given timestamp. Time=0 is the start of the event. + * + * @param time the time to fast-forward to. */ + @Override public void setTime(float time) { - this.time = time ; + this.time = time; } /** * Return the current timestamp of the event. Time=0 is the start of the event. */ + @Override public float getTime() { return time; } - public void dispose() { + @Override + public void dispose() { } - - } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java new file mode 100644 index 0000000000..7d7721e166 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimEvent.java @@ -0,0 +1,278 @@ +/* + * Copyright (c) 2009-2021 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.cinematic.events; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.tween.action.Action; +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.cinematic.Cinematic; +import com.jme3.cinematic.PlayState; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import java.io.IOException; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A CinematicEvent that plays a canned animation action in an + * {@link com.jme3.anim.AnimComposer}. + * + * Inspired by Nehon's {@link AnimationEvent}. + */ +public class AnimEvent extends AbstractCinematicEvent { + + public static final Logger logger + = Logger.getLogger(AnimEvent.class.getName()); + + /* + * Control that will play the animation + */ + private AnimComposer composer; + /* + * Cinematic that contains this event + */ + private Cinematic cinematic; + /* + * name of the animation action to be played + */ + private String actionName; + /* + * name of the animation layer on which the action will be played + */ + private String layerName; + + /** + * Instantiate a non-looping event to play the named action on the named + * layer of the specified AnimComposer. + * + * @param composer the Control that will play the animation (not null) + * @param actionName the name of the animation action to be played + * @param layerName the name of the animation layer on which the action will + * be played + */ + public AnimEvent(AnimComposer composer, String actionName, + String layerName) { + this.composer = composer; + this.actionName = actionName; + this.layerName = layerName; + /* + * Override initialDuration, which defaults to 10 seconds. + */ + Action eventAction = composer.action(actionName); + initialDuration = (float) eventAction.getLength(); + } + + /** + * No-argument constructor needed by SavableClassUtil. + */ + protected AnimEvent() { + super(); + } + + /** + * Initialize this event. (for internal use) + * + * @param app the Application that contains this event + * @param cinematic the Cinematic that contains this event + */ + @Override + public void initEvent(Application app, Cinematic cinematic) { + super.initEvent(app, cinematic); + this.cinematic = cinematic; + } + + /** + * Callback when the event is paused. + */ + @Override + public void onPause() { + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); + } + + Object layerManager = composer.getLayerManager(layerName); + if (layerManager == this) { + Action eventAction = composer.action(actionName); + eventAction.setSpeed(0f); + } + } + + /** + * Callback when the event is started. + */ + @Override + public void onPlay() { + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); + } + + Action currentAction = composer.getCurrentAction(layerName); + Action eventAction = composer.action(actionName); + if (currentAction != eventAction) { + composer.setCurrentAction(actionName, layerName); + assert composer.getCurrentAction(layerName) == eventAction; + } + + if (playState == PlayState.Stopped) { + composer.setTime(layerName, 0.0); + } + eventAction.setSpeed(speed); + composer.setLayerManager(layerName, this); + } + + /** + * Callback when the event is stopped. + */ + @Override + public void onStop() { + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "layer={0} action={1}", + new Object[]{layerName, actionName}); + } + Object layerManager = composer.getLayerManager(layerName); + if (layerManager == this) { + composer.removeCurrentAction(layerName); + composer.setLayerManager(layerName, null); + } + } + + /** + * Callback on each render pass while the event is playing. + * + * @param tpf time per frame (in seconds) + */ + @Override + public void onUpdate(float tpf) { + // do nothing + } + + /** + * De-serialize this event from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + actionName = capsule.readString("actionName", ""); + cinematic = (Cinematic) capsule.readSavable("cinematic", null); + composer = (AnimComposer) capsule.readSavable("composer", null); + layerName = capsule.readString("layerName", AnimComposer.DEFAULT_LAYER); + } + + /** + * Alter the speed of the animation. + * + * @param speed the relative speed (default=1) + */ + @Override + public void setSpeed(float speed) { + logger.log(Level.INFO, "speed = {0}", speed); + super.setSpeed(speed); + + if (playState != PlayState.Stopped) { + Action eventAction = composer.action(actionName); + eventAction.setSpeed(speed); + } + } + + /** + * Jump to the specified time. + * + * @param time the desired time (in seconds, time=0 is the start of the + * event) + */ + @Override + public void setTime(float time) { + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "layer={0} action={1} time={2}", + new Object[]{layerName, actionName, time}); + } + super.setTime(time); + + Action currentAction = composer.getCurrentAction(layerName); + Action eventAction = composer.action(actionName); + if (currentAction != eventAction) { + composer.setCurrentAction(actionName, layerName); + assert composer.getCurrentAction(layerName) == eventAction; + } + + float t = time; + float duration = (float) eventAction.getLength(); + if (loopMode == LoopMode.Loop) { + t %= duration; + } else if (loopMode == LoopMode.Cycle) { + float direction = (float) Math.ceil(time / duration); + if (direction > 0f && direction % 2f == 0f) { + t = duration - t % duration; + } else { + t %= duration; + } + } + + if (t < 0f) { + composer.setTime(layerName, 0.0); + } else if (t > duration) { + composer.setTime(layerName, t); + stop(); + } else { + composer.setTime(layerName, t); + } + } + + /** + * Serialize this event to the specified exporter, for example when saving + * to a J3O file. + * + * @param exporter (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(actionName, "actionName", ""); + capsule.write(cinematic, "cinematic", null); + capsule.write(composer, "composer", null); + capsule.write(layerName, "layerName", AnimComposer.DEFAULT_LAYER); + } +} diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java index cc409b8ec5..6511da701b 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/AnimationEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,11 +51,12 @@ * * * @author Nehon + * @deprecated use {@link AnimEvent} */ @Deprecated public class AnimationEvent extends AbstractCinematicEvent { - // Version #2: directly keeping track on the model instead of trying to retrieve + // Version #2: directly keeping track on the model instead of trying to retrieve //it from the scene according to its name, because the name is not supposed to be unique //For backward compatibility, if the model is null it's looked up into the scene public static final int SAVABLE_VERSION = 2; @@ -78,7 +79,7 @@ public class AnimationEvent extends AbstractCinematicEvent { protected AnimationEvent() { super(); } - + /** * creates an animation event * @@ -144,7 +145,7 @@ public AnimationEvent(Spatial model, String animationName, float initialDuration * @param model the model on which the animation will be played * @param animationName the name of the animation to play * @param initialDuration the initial duration of the event - * @param blendTime the time during the animation are gonna be blended + * @param blendTime the time interval during which the animation will be blended * @see AnimChannel#setAnim(java.lang.String, float) */ public AnimationEvent(Spatial model, String animationName, float initialDuration, float blendTime) { @@ -162,7 +163,7 @@ public AnimationEvent(Spatial model, String animationName, float initialDuration * @param animationName the name of the animation to play * @param loopMode the loopMode * @see LoopMode - * @param blendTime the time during the animation are gonna be blended + * @param blendTime the time interval during which the animation will be blended * @see AnimChannel#setAnim(java.lang.String, float) */ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, float blendTime) { @@ -182,7 +183,7 @@ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, fl * @param initialDuration the initial duration of the event * @param loopMode the loopMode * @see LoopMode - * @param blendTime the time during the animation are gonna be blended + * @param blendTime the time interval during which the animation will be blended * @see AnimChannel#setAnim(java.lang.String, float) */ public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, float blendTime) { @@ -201,7 +202,7 @@ public AnimationEvent(Spatial model, String animationName, float initialDuration * @param loopMode the loopMode * @see LoopMode * @param channelIndex the index of the channel default is 0. Events on the - * same channelIndex will use the same channel. + * same channelIndex will use the same channel. */ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex) { super(loopMode); @@ -218,7 +219,7 @@ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, in * @param model the model on which the animation will be played * @param animationName the name of the animation to play * @param channelIndex the index of the channel default is 0. Events on the - * same channelIndex will use the same channel. + * same channelIndex will use the same channel. */ public AnimationEvent(Spatial model, String animationName, int channelIndex) { this.model = model; @@ -227,15 +228,16 @@ public AnimationEvent(Spatial model, String animationName, int channelIndex) { initialDuration = model.getControl(AnimControl.class).getAnimationLength(animationName); this.channelIndex = channelIndex; } - + /** * creates an animation event * * @param model the model on which the animation will be played * @param animationName the name of the animation to play + * @param loopMode the desired mode (Loop/DontLoop/Cycle) * @param channelIndex the index of the channel default is 0. Events on the - * @param blendTime the time during the animation are gonna be blended - * same channelIndex will use the same channel. + * @param blendTime the time interval during which the animation will be blended + * same channelIndex will use the same channel. */ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, int channelIndex, float blendTime) { this.model = model; @@ -254,7 +256,7 @@ public AnimationEvent(Spatial model, String animationName, LoopMode loopMode, in * @param animationName the name of the animation to play * @param initialDuration the initial duration of the event * @param channelIndex the index of the channel default is 0. Events on the - * same channelIndex will use the same channel. + * same channelIndex will use the same channel. */ public AnimationEvent(Spatial model, String animationName, float initialDuration, int channelIndex) { super(initialDuration); @@ -273,7 +275,7 @@ public AnimationEvent(Spatial model, String animationName, float initialDuration * @param loopMode the loopMode * @see LoopMode * @param channelIndex the index of the channel default is 0. Events on the - * same channelIndex will use the same channel. + * same channelIndex will use the same channel. */ public AnimationEvent(Spatial model, String animationName, float initialDuration, LoopMode loopMode, int channelIndex) { super(initialDuration, loopMode); @@ -284,6 +286,7 @@ public AnimationEvent(Spatial model, String animationName, float initialDuration } @Override + @SuppressWarnings("unchecked") public void initEvent(Application app, Cinematic cinematic) { super.initEvent(app, cinematic); this.cinematic = cinematic; @@ -292,8 +295,9 @@ public void initEvent(Application app, Cinematic cinematic) { if (s == null) { s = new HashMap(); int numChannels = model.getControl(AnimControl.class).getNumChannels(); - for(int i = 0; i < numChannels; i++){ - ((HashMap)s).put(i, model.getControl(AnimControl.class).getChannel(i)); + for (int i = 0; i < numChannels; i++) { + ((HashMap) s) + .put(i, model.getControl(AnimControl.class).getChannel(i)); } cinematic.putEventData(MODEL_CHANNELS, model, s); } @@ -304,13 +308,13 @@ public void initEvent(Application app, Cinematic cinematic) { if (model == null) { //the model is null we try to find it according to the name //this should occur only when loading an old saved cinematic - //othewise it's an error + //otherwise it's an error model = cinematic.getScene().getChild(modelName); } if (model != null) { - if(cinematic.getScene() != null){ + if (cinematic.getScene() != null) { Spatial sceneModel = cinematic.getScene().getChild(model.getName()); - if(sceneModel != null){ + if (sceneModel != null) { Node parent = sceneModel.getParent(); parent.detachChild(sceneModel); sceneModel = model; @@ -319,15 +323,14 @@ public void initEvent(Application app, Cinematic cinematic) { cinematic.getScene().attachChild(model); } } - + channel = model.getControl(AnimControl.class).createChannel(); map.put(channelIndex, channel); } else { //it's an error throw new UnsupportedOperationException("model should not be null"); } - } - + } } } @@ -338,10 +341,10 @@ public void setTime(float time) { channel.setAnim(animationName, blendTime); } float t = time; - if (loopMode == loopMode.Loop) { + if (loopMode == LoopMode.Loop) { t = t % channel.getAnimMaxTime(); } - if (loopMode == loopMode.Cycle) { + if (loopMode == LoopMode.Cycle) { float parity = (float) Math.ceil(time / channel.getAnimMaxTime()); if (parity > 0 && parity % 2 == 0) { t = channel.getAnimMaxTime() - t % channel.getAnimMaxTime(); @@ -362,7 +365,6 @@ public void setTime(float time) { channel.setTime(t); channel.getControl().update(0); } - } @Override @@ -426,7 +428,6 @@ public void write(JmeExporter ex) throws IOException { oc.write(animationName, "animationName", ""); oc.write(blendTime, "blendTime", 0f); oc.write(channelIndex, "channelIndex", 0); - } @Override @@ -434,14 +435,14 @@ public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); // if (im.getFormatVersion() == 0) { - modelName = ic.readString("modelName", ""); + modelName = ic.readString("modelName", ""); // } - //FIXME always the same issue, because of the clonning of assets, this won't work - //we have to somehow store userdata in the spatial and then recurse the + //FIXME always the same issue, because of the cloning of assets, this won't work + //we have to somehow store userdata in the spatial and then recurse the //scene sub scenegraph to find the correct instance of the model - //This brings a reflaxion about the cinematic being an appstate, + //This brings a reflection about the cinematic being an appstate, //shouldn't it be a control over the scene - // this would allow to use the cloneForSpatial method and automatically + // this would allow to use the cloneForSpatial method and automatically //rebind cloned references of original objects. //for now as nobody probably ever saved a cinematic, this is not a critical issue model = (Spatial) ic.readSavable("model", null); @@ -451,6 +452,7 @@ public void read(JmeImporter im) throws IOException { } @Override + @SuppressWarnings("unchecked") public void dispose() { super.dispose(); if (cinematic != null) { diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java index 8d2b708942..a9dfec3960 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/CameraEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,43 +40,52 @@ import java.io.IOException; /** + * A `CameraEvent` is a cinematic event that instantly sets the active camera + * within a `Cinematic` sequence. * * @author Rickard (neph1 @ github) */ -public class CameraEvent extends AbstractCinematicEvent{ +public class CameraEvent extends AbstractCinematicEvent { + /** + * The name of the camera to activate. + */ private String cameraName; + /** + * The `Cinematic` instance to which this event belongs and on which the + * camera will be set. + */ private Cinematic cinematic; - public String getCameraName() { - return cameraName; + /** + * For serialization only. Do not use. + */ + public CameraEvent() { } - public void setCameraName(String cameraName) { + /** + * Constructs a new `CameraEvent` with the specified cinematic and camera name. + * + * @param cinematic The `Cinematic` instance this event belongs to (cannot be null). + * @param cameraName The name of the camera to be activated by this event (cannot be null or empty). + */ + public CameraEvent(Cinematic cinematic, String cameraName) { + this.cinematic = cinematic; this.cameraName = cameraName; } - public CameraEvent(){ - - } - - public CameraEvent(Cinematic parentEvent, String cameraName){ - this.cinematic = parentEvent; - this.cameraName = cameraName; - } - - @Override + @Override public void initEvent(Application app, Cinematic cinematic) { super.initEvent(app, cinematic); this.cinematic = cinematic; } - + @Override public void play() { super.play(); stop(); } - + @Override public void onPlay() { cinematic.setActiveCamera(cameraName); @@ -103,35 +112,56 @@ public void setTime(float time) { play(); } + /** + * Returns the `Cinematic` instance associated with this event. + * @return The `Cinematic` instance. + */ public Cinematic getCinematic() { return cinematic; } + /** + * Sets the `Cinematic` instance for this event. + * @param cinematic The `Cinematic` instance to set (cannot be null). + */ public void setCinematic(Cinematic cinematic) { this.cinematic = cinematic; } - - - + + /** + * Returns the name of the camera that this event will activate. + * @return The camera name. + */ + public String getCameraName() { + return cameraName; + } + /** - * used internally for serialization + * Sets the name of the camera that this event will activate. + * @param cameraName The new camera name (cannot be null or empty). + */ + public void setCameraName(String cameraName) { + this.cameraName = cameraName; + } + + /** + * Used internally for serialization. * - * @param ex - * @throws IOException + * @param ex The exporter (not null). + * @throws IOException If an I/O error occurs during serialization. */ @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(cameraName, "cameraName", null); - } /** - * used internally for serialization + * Used internally for deserialization. * - * @param im - * @throws IOException + * @param im The importer (not null). + * @throws IOException If an I/O error occurs during deserialization. */ @Override public void read(JmeImporter im) throws IOException { diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java index d5355c9c15..4515ca8a3a 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/CinematicEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ public interface CinematicEvent extends Savable { * this method can be implemented if the event needs different handling when * stopped naturally (when the event reach its end) * or when it was forced stopped during playback - * otherwise it just call regular stop() + * otherwise it just calls regular stop() */ public void forceStop(); @@ -74,7 +74,8 @@ public interface CinematicEvent extends Savable { /** * Sets the speed of the animation (1 is normal speed, 2 is twice faster) - * @param speed + * + * @param speed the desired speed (default=1) */ public void setSpeed(float speed); @@ -117,8 +118,9 @@ public interface CinematicEvent extends Savable { public float getInitialDuration(); /** - * Sets the duration of the antionamtion at speed = 1 in seconds - * @param initialDuration + * Sets the duration of the animation at speed = 1, in seconds. + * + * @param initialDuration the desired duration (in de-scaled seconds) */ public void setInitialDuration(float initialDuration); @@ -136,8 +138,9 @@ public interface CinematicEvent extends Savable { public void initEvent(Application app, Cinematic cinematic); /** - * When this method is invoked, the event should fast forward to the given time according time 0 is the start of the event. - * @param time the time to fast forward to + * Fast-forwards to the given time, where time=0 is the start of the event. + * + * @param time the time to fast-forward to */ public void setTime(float time); diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java index e7d217f45f..d4751807ef 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,8 +52,8 @@ import java.io.IOException; /** - * A MotionEvent is a control over the spatial that manages the position and direction of the spatial while following a motion Path. - * + * A MotionEvent is a control over the spatial that manages + * the position and direction of the spatial while following a motion Path. * You must first create a MotionPath and then create a MotionEvent to associate a spatial and the path. * * @author Nehon @@ -70,7 +70,7 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC protected Direction directionType = Direction.None; protected MotionPath path; private boolean isControl = true; - private int travelDirection = 1; + private final Quaternion tempRotation = new Quaternion(); /** * the distance traveled by the spatial on the path */ @@ -80,7 +80,6 @@ public class MotionEvent extends AbstractCinematicEvent implements Control, JmeC * Enum for the different type of target direction behavior. */ public enum Direction { - /** * The target stays in the starting direction. */ @@ -112,11 +111,12 @@ public enum Direction { public MotionEvent() { super(); } - + /** * Creates a MotionPath for the given spatial on the given motion path. - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) */ public MotionEvent(Spatial spatial, MotionPath path) { super(); @@ -126,8 +126,10 @@ public MotionEvent(Spatial spatial, MotionPath path) { /** * Creates a MotionPath for the given spatial on the given motion path. - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param initialDuration the desired duration (in seconds, default=10) */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) { super(initialDuration); @@ -137,8 +139,10 @@ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration) { /** * Creates a MotionPath for the given spatial on the given motion path. - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param loopMode (default=DontLoop) */ public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) { super(); @@ -149,8 +153,11 @@ public MotionEvent(Spatial spatial, MotionPath path, LoopMode loopMode) { /** * Creates a MotionPath for the given spatial on the given motion path. - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param initialDuration the desired duration (in seconds, default=10) + * @param loopMode (default=DontLoop) */ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) { super(initialDuration); @@ -159,6 +166,7 @@ public MotionEvent(Spatial spatial, MotionPath path, float initialDuration, Loop this.loopMode = loopMode; } + @Override public void update(float tpf) { if (isControl) { internalUpdate(tpf); @@ -171,7 +179,7 @@ public void internalUpdate(float tpf) { time = time + (tpf * speed); if (loopMode == LoopMode.Loop && time < 0) { time = initialDuration; - } + } if ((time >= initialDuration || time < 0) && loopMode == LoopMode.DontLoop) { if (time >= initialDuration) { path.triggerWayPointReach(path.getNbWayPoints() - 1, this); @@ -179,9 +187,9 @@ public void internalUpdate(float tpf) { stop(); } else { time = AnimationUtils.clampWrapTime(time, initialDuration, loopMode); - if(time<0){ - speed = - speed; - time = - time; + if (time < 0) { + speed = -speed; + time = -time; } onUpdate(tpf); } @@ -200,6 +208,7 @@ public void setTime(float time) { onUpdate(0); } + @Override public void onUpdate(float tpf) { traveledDistance = path.interpolatePath(time, this, tpf); computeTargetDirection(); @@ -220,13 +229,13 @@ public void write(JmeExporter ex) throws IOException { @Override public void read(JmeImporter im) throws IOException { super.read(im); - InputCapsule in = im.getCapsule(this); - lookAt = (Vector3f) in.readSavable("lookAt", null); - upVector = (Vector3f) in.readSavable("upVector", Vector3f.UNIT_Y); - rotation = (Quaternion) in.readSavable("rotation", null); - directionType = in.readEnum("directionType", Direction.class, Direction.None); - path = (MotionPath) in.readSavable("path", null); - spatial = (Spatial) in.readSavable("spatial", null); + InputCapsule ic = im.getCapsule(this); + lookAt = (Vector3f) ic.readSavable("lookAt", null); + upVector = (Vector3f) ic.readSavable("upVector", Vector3f.UNIT_Y); + rotation = (Quaternion) ic.readSavable("rotation", null); + directionType = ic.readEnum("directionType", Direction.class, Direction.None); + path = (MotionPath) ic.readSavable("path", null); + spatial = (Spatial) ic.readSavable("spatial", null); } /** @@ -240,9 +249,8 @@ public boolean needsDirection() { private void computeTargetDirection() { switch (directionType) { case Path: - Quaternion q = new Quaternion(); - q.lookAt(direction, upVector); - spatial.setLocalRotation(q); + tempRotation.lookAt(direction, upVector); + spatial.setLocalRotation(tempRotation); break; case LookAt: if (lookAt != null) { @@ -251,10 +259,9 @@ private void computeTargetDirection() { break; case PathAndRotation: if (rotation != null) { - Quaternion q2 = new Quaternion(); - q2.lookAt(direction, upVector); - q2.multLocal(rotation); - spatial.setLocalRotation(q2); + tempRotation.lookAt(direction, upVector); + tempRotation.multLocal(rotation); + spatial.setLocalRotation(tempRotation); } break; case Rotation: @@ -263,6 +270,7 @@ private void computeTargetDirection() { } break; case None: + // no-op break; default: break; @@ -271,7 +279,8 @@ private void computeTargetDirection() { /** * Clone this control for the given spatial. - * @param spatial + * + * @param spatial ignored * @return never */ @Deprecated @@ -280,7 +289,7 @@ public Control cloneForSpatial(Spatial spatial) { throw new UnsupportedOperationException(); } - @Override + @Override public Object jmeClone() { MotionEvent control = new MotionEvent(); control.path = path; @@ -298,13 +307,16 @@ public Object jmeClone() { control.spatial = spatial; return control; - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { + this.lookAt = cloner.clone(lookAt); + this.path = cloner.clone(path); + this.rotation = cloner.clone(rotation); this.spatial = cloner.clone(spatial); } - + @Override public void onPlay() { traveledDistance = 0; @@ -330,6 +342,7 @@ public float getCurrentValue() { /** * This method is meant to be called by the motion path only. * + * @param currentValue the desired value */ public void setCurrentValue(float currentValue) { this.currentValue = currentValue; @@ -346,6 +359,7 @@ public int getCurrentWayPoint() { /** * This method is meant to be called by the motion path only. * + * @param currentWayPoint the desired waypoint index */ public void setCurrentWayPoint(int currentWayPoint) { this.currentWayPoint = currentWayPoint; @@ -361,22 +375,23 @@ public Vector3f getDirection() { /** * Sets the direction of the spatial, using the Y axis as the up vector. - * Use MotionEvent#setDirection((Vector3f direction,Vector3f upVector) if - * you want a custum up vector. + * If a custom up vector is desired, use {@link #setDirection(Vector3f, Vector3f)}. * This method is used by the motion path. - * @param direction + * + * @param direction the desired forward direction (not null, unaffected) */ public void setDirection(Vector3f direction) { - setDirection(direction, Vector3f.UNIT_Y); - } - + setDirection(direction, Vector3f.UNIT_Y); + } + /** * Sets the direction of the spatial with the given up vector. * This method is used by the motion path. - * @param direction + * + * @param direction the desired forward direction (not null, unaffected) * @param upVector the up vector to consider for this direction. */ - public void setDirection(Vector3f direction,Vector3f upVector) { + public void setDirection(Vector3f direction, Vector3f upVector) { this.direction.set(direction); this.upVector.set(upVector); } @@ -439,7 +454,8 @@ public MotionPath getPath() { /** * Sets the motion path to follow. - * @param path + * + * @param path the desired path (alias created) */ public void setPath(MotionPath path) { this.path = path; @@ -457,9 +473,11 @@ public boolean isEnabled() { return playState != PlayState.Stopped; } + @Override public void render(RenderManager rm, ViewPort vp) { } + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java index 2958241bbe..eda7f9c709 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/MotionTrack.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,8 +53,9 @@ public MotionTrack() { /** * Creates a MotionPath for the given spatial on the given motion path - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) */ public MotionTrack(Spatial spatial, MotionPath path) { super(spatial, path); @@ -62,8 +63,10 @@ public MotionTrack(Spatial spatial, MotionPath path) { /** * Creates a MotionPath for the given spatial on the given motion path - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param initialDuration the desired duration (in seconds, default=10) */ public MotionTrack(Spatial spatial, MotionPath path, float initialDuration) { super(spatial, path, initialDuration); @@ -71,8 +74,10 @@ public MotionTrack(Spatial spatial, MotionPath path, float initialDuration) { /** * Creates a MotionPath for the given spatial on the given motion path - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param loopMode (default=DontLoop) */ public MotionTrack(Spatial spatial, MotionPath path, LoopMode loopMode) { super(spatial, path, loopMode); @@ -81,8 +86,11 @@ public MotionTrack(Spatial spatial, MotionPath path, LoopMode loopMode) { /** * Creates a MotionPath for the given spatial on the given motion path - * @param spatial - * @param path + * + * @param spatial the Spatial to move (not null) + * @param path the path to be taken (alias created) + * @param initialDuration the desired duration (in seconds, default=10) + * @param loopMode (default=DontLoop) */ public MotionTrack(Spatial spatial, MotionPath path, float initialDuration, LoopMode loopMode) { super(spatial, path, initialDuration, loopMode); diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java index fe31678c3b..b899781bbe 100644 --- a/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/SoundEvent.java @@ -33,6 +33,7 @@ import com.jme3.animation.LoopMode; import com.jme3.app.Application; +import com.jme3.audio.AudioData; import com.jme3.audio.AudioNode; import com.jme3.audio.AudioSource; import com.jme3.cinematic.Cinematic; @@ -86,7 +87,7 @@ public SoundEvent(String path, boolean stream, float initialDuration) { * creates a sound track from the given resource path * @param path the path to an audio file (ie : "Sounds/mySound.wav") * @param stream true to make the audio data streamed - * @param loopMode the loopMode + * @param loopMode the loopMode * @see LoopMode */ public SoundEvent(String path, boolean stream, LoopMode loopMode) { @@ -95,12 +96,12 @@ public SoundEvent(String path, boolean stream, LoopMode loopMode) { this.stream = stream; } - /** + /** * creates a sound track from the given resource path * @param path the path to an audio file (ie : "Sounds/mySound.wav") * @param stream true to make the audio data streamed * @param initialDuration the initial duration of the event - * @param loopMode the loopMode + * @param loopMode the loopMode * @see LoopMode */ public SoundEvent(String path, boolean stream, float initialDuration, LoopMode loopMode) { @@ -109,9 +110,9 @@ public SoundEvent(String path, boolean stream, float initialDuration, LoopMode l this.stream = stream; } - /** + /** * creates a sound track from the given resource path - * @param path the path to an audio file (ie : "Sounds/mySound.wav") + * @param path the path to an audio file (ie : "Sounds/mySound.wav") * @param initialDuration the initial duration of the event */ public SoundEvent(String path, float initialDuration) { @@ -119,10 +120,10 @@ public SoundEvent(String path, float initialDuration) { this.path = path; } - /** + /** * creates a sound track from the given resource path - * @param path the path to an audio file (ie : "Sounds/mySound.wav") - * @param loopMode the loopMode + * @param path the path to an audio file (ie : "Sounds/mySound.wav") + * @param loopMode the loopMode * @see LoopMode */ public SoundEvent(String path, LoopMode loopMode) { @@ -130,11 +131,11 @@ public SoundEvent(String path, LoopMode loopMode) { this.path = path; } - /** + /** * creates a sound track from the given resource path - * @param path the path to an audio file (ie : "Sounds/mySound.wav") + * @param path the path to an audio file (ie : "Sounds/mySound.wav") * @param initialDuration the initial duration of the event - * @param loopMode the loopMode + * @param loopMode the loopMode * @see LoopMode */ public SoundEvent(String path, float initialDuration, LoopMode loopMode) { @@ -153,7 +154,7 @@ public SoundEvent() { @Override public void initEvent(Application app, Cinematic cinematic) { super.initEvent(app, cinematic); - audioNode = new AudioNode(app.getAssetManager(), path, stream); + audioNode = new AudioNode(app.getAssetManager(), path, stream ? AudioData.DataType.Stream : AudioData.DataType.Buffer); audioNode.setPositional(false); setLoopMode(loopMode); } @@ -162,9 +163,9 @@ public void initEvent(Application app, Cinematic cinematic) { public void setTime(float time) { super.setTime(time); //can occur on rewind - if (time < 0f) { + if (time < 0f) { stop(); - }else{ + } else { audioNode.setTimeOffset(time); } } @@ -177,7 +178,6 @@ public void onPlay() { @Override public void onStop() { audioNode.stop(); - } @Override @@ -193,7 +193,7 @@ public void onUpdate(float tpf) { } /** - * Returns the underlying audio node of this sound track + * Returns the underlying audio node of this sound track * @return the pre-existing instance */ public AudioNode getAudioNode() { @@ -225,6 +225,5 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); path = ic.readString("path", ""); stream = ic.readBoolean("stream", false); - } } diff --git a/jme3-core/src/main/java/com/jme3/cinematic/events/package-info.java b/jme3-core/src/main/java/com/jme3/cinematic/events/package-info.java new file mode 100644 index 0000000000..d76b44a7b1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/events/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * events for use in cinematics + */ +package com.jme3.cinematic.events; diff --git a/jme3-core/src/main/java/com/jme3/cinematic/package-info.java b/jme3-core/src/main/java/com/jme3/cinematic/package-info.java new file mode 100644 index 0000000000..214604771f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/cinematic/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * cinematics, for programming cutscenes and the like + */ +package com.jme3.cinematic; diff --git a/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java index d66ec62cfc..95739f5a1c 100644 --- a/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java +++ b/jme3-core/src/main/java/com/jme3/collision/CollisionResult.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,30 +63,30 @@ public CollisionResult(Vector3f contactPoint, float distance) { this.distance = distance; } - public CollisionResult(){ + public CollisionResult() { } - public void setGeometry(Geometry geom){ + public void setGeometry(Geometry geom) { this.geometry = geom; } - public void setContactNormal(Vector3f norm){ + public void setContactNormal(Vector3f norm) { this.contactNormal = norm; } - public void setContactPoint(Vector3f point){ + public void setContactPoint(Vector3f point) { this.contactPoint = point; } - public void setDistance(float dist){ + public void setDistance(float dist) { this.distance = dist; } - public void setTriangleIndex(int index){ + public void setTriangleIndex(int index) { this.triangleIndex = index; } - public Triangle getTriangle(Triangle store){ + public Triangle getTriangle(Triangle store) { if (store == null) store = new Triangle(); @@ -97,14 +97,15 @@ public Triangle getTriangle(Triangle store){ return store; } + @Override public int compareTo(CollisionResult other) { return Float.compare(distance, other.distance); } @Override public boolean equals(Object obj) { - if(obj instanceof CollisionResult){ - return ((CollisionResult)obj).compareTo(this) == 0; + if (obj instanceof CollisionResult) { + return ((CollisionResult) obj).compareTo(this) == 0; } return super.equals(obj); } @@ -134,6 +135,7 @@ public int getTriangleIndex() { return triangleIndex; } + @Override public String toString() { return "CollisionResult[geometry=" + geometry + ", contactPoint=" + contactPoint diff --git a/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java b/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java index 93c5c81b58..032e464307 100644 --- a/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java +++ b/jme3-core/src/main/java/com/jme3/collision/CollisionResults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,9 +37,9 @@ import java.util.List; /** - * CollisionResults is a collection returned as a result of a + * CollisionResults is a collection returned as a result of a * collision detection operation done by {@link Collidable}. - * + * * @author Kirill Vainer */ public class CollisionResults implements Iterable { @@ -50,7 +50,7 @@ public class CollisionResults implements Iterable { /** * Clears all collision results added to this list */ - public void clear(){ + public void clear() { if (results != null) { results.clear(); } @@ -58,16 +58,17 @@ public void clear(){ /** * Iterator for iterating over the collision results. - * + * * @return the iterator */ + @Override public Iterator iterator() { if (results == null) { - List dumbCompiler = Collections.emptyList(); + List dumbCompiler = Collections.emptyList(); return dumbCompiler.iterator(); } - - if (!sorted){ + + if (!sorted) { Collections.sort(results); sorted = true; } @@ -75,7 +76,7 @@ public Iterator iterator() { return results.iterator(); } - public void addCollision(CollisionResult result){ + public void addCollision(CollisionResult result) { if (results == null) { results = new ArrayList(); } @@ -83,18 +84,18 @@ public void addCollision(CollisionResult result){ sorted = false; } - public int size(){ + public int size() { if (results == null) { return 0; } return results.size(); } - public CollisionResult getClosestCollision(){ + public CollisionResult getClosestCollision() { if (results == null || size() == 0) return null; - if (!sorted){ + if (!sorted) { Collections.sort(results); sorted = true; } @@ -102,24 +103,24 @@ public CollisionResult getClosestCollision(){ return results.get(0); } - public CollisionResult getFarthestCollision(){ + public CollisionResult getFarthestCollision() { if (results == null || size() == 0) return null; - if (!sorted){ + if (!sorted) { Collections.sort(results); sorted = true; } - return results.get(size()-1); + return results.get(size() - 1); } - public CollisionResult getCollision(int index){ + public CollisionResult getCollision(int index) { if (results == null) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); } - - if (!sorted){ + + if (!sorted) { Collections.sort(results); sorted = true; } @@ -129,10 +130,11 @@ public CollisionResult getCollision(int index){ /** * Internal use only. - * @param index + * + * @param index the zero-based index of the desired result * @return the pre-existing instance */ - public CollisionResult getCollisionDirect(int index){ + public CollisionResult getCollisionDirect(int index) { if (results == null) { throw new IndexOutOfBoundsException("Index: " + index + ", Size: 0"); } @@ -140,19 +142,18 @@ public CollisionResult getCollisionDirect(int index){ } @Override - public String toString(){ + public String toString() { StringBuilder sb = new StringBuilder(); sb.append("CollisionResults["); if (results != null) { - for (CollisionResult result : results){ + for (CollisionResult result : results) { sb.append(result).append(", "); } if (results.size() > 0) - sb.setLength(sb.length()-2); - } + sb.setLength(sb.length() - 2); + } sb.append("]"); return sb.toString(); } - } diff --git a/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java b/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java index fa4dda3394..6e057336da 100644 --- a/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java +++ b/jme3-core/src/main/java/com/jme3/collision/MotionAllowedListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,8 +40,8 @@ public interface MotionAllowedListener { * Check if motion allowed. Modify position and velocity vectors * appropriately if not allowed.. * - * @param position - * @param velocity + * @param position the position vector (modified) + * @param velocity the velocity vector (modified) */ public void checkMotionAllowed(Vector3f position, Vector3f velocity); diff --git a/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java b/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java index 75470dcb57..42a62ea84f 100644 --- a/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java +++ b/jme3-core/src/main/java/com/jme3/collision/SweepSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,17 +34,17 @@ import com.jme3.math.*; /** - * No longer public .. + * No longer public. * * @author Kirill Vainer */ @Deprecated class SweepSphere implements Collidable { - private Vector3f velocity = new Vector3f(); - private Vector3f center = new Vector3f(); - private Vector3f dimension = new Vector3f(); - private Vector3f invDim = new Vector3f(); + final private Vector3f velocity = new Vector3f(); + final private Vector3f center = new Vector3f(); + final private Vector3f dimension = new Vector3f(); + final private Vector3f invDim = new Vector3f(); private final Triangle scaledTri = new Triangle(); private final Plane triPlane = new Plane(); @@ -68,17 +68,17 @@ public Vector3f getDimension() { public void setDimension(Vector3f dimension) { this.dimension.set(dimension); - this.invDim.set(1,1,1).divideLocal(dimension); + this.invDim.set(1, 1, 1).divideLocal(dimension); } - public void setDimension(float x, float y, float z){ - this.dimension.set(x,y,z); - this.invDim.set(1,1,1).divideLocal(dimension); + public void setDimension(float x, float y, float z) { + this.dimension.set(x, y, z); + this.invDim.set(1, 1, 1).divideLocal(dimension); } - public void setDimension(float dim){ + public void setDimension(float dim) { this.dimension.set(dim, dim, dim); - this.invDim.set(1,1,1).divideLocal(dimension); + this.invDim.set(1, 1, 1).divideLocal(dimension); } public Vector3f getVelocity() { @@ -106,34 +106,35 @@ private boolean pointsOnSameSide(Vector3f p1, Vector3f p2, Vector3f line1, Vecto } private boolean isPointInTriangle(Vector3f point, AbstractTriangle tri) { - if (pointsOnSameSide(point, tri.get1(), tri.get2(), tri.get3()) - && pointsOnSameSide(point, tri.get2(), tri.get1(), tri.get3()) - && pointsOnSameSide(point, tri.get3(), tri.get1(), tri.get2())) - return true; - return false; + if (pointsOnSameSide(point, tri.get1(), tri.get2(), tri.get3()) + && pointsOnSameSide(point, tri.get2(), tri.get1(), tri.get3()) + && pointsOnSameSide(point, tri.get3(), tri.get1(), tri.get2())) { + return true; + } + return false; } private static float getLowestRoot(float a, float b, float c, float maxR) { float determinant = b * b - 4f * a * c; - if (determinant < 0){ + if (determinant < 0) { return Float.NaN; } - float sqrtd = FastMath.sqrt(determinant); - float r1 = (-b - sqrtd) / (2f * a); - float r2 = (-b + sqrtd) / (2f * a); + float sqrtDet = FastMath.sqrt(determinant); + float r1 = (-b - sqrtDet) / (2f * a); + float r2 = (-b + sqrtDet) / (2f * a); - if (r1 > r2){ + if (r1 > r2) { float temp = r2; r2 = r1; r1 = temp; } - if (r1 > 0 && r1 < maxR){ + if (r1 > 0 && r1 < maxR) { return r1; } - if (r2 > 0 && r2 < maxR){ + if (r2 > 0 && r2 < maxR) { return r2; } @@ -159,7 +160,7 @@ private float collideWithVertex(Vector3f sCenter, Vector3f sVelocity, // float newT = getLowestRoot(A, B, C, Float.MAX_VALUE); // if (newT > 1.0f) // newT = Float.NaN; - + return newT; } @@ -184,9 +185,9 @@ private float collideWithSegment(Vector3f sCenter, - (2f * EdotV * EdotB); float c = (edgeSquared * (1f - baseSquared)) + EdotB * EdotB; float newT = getLowestRoot(a, b, c, t); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { float f = (EdotV * newT - EdotB) / edgeSquared; - if (f >= 0f && f < 1f){ + if (f >= 0f && f < 1f) { store.scaleAdd(f, edge, l1); return newT; } @@ -194,7 +195,7 @@ private float collideWithSegment(Vector3f sCenter, return Float.NaN; } - private CollisionResult collideWithTriangle(AbstractTriangle tri){ + private CollisionResult collideWithTriangle(AbstractTriangle tri) { // scale scaledTriangle based on dimension scaledTri.get1().set(tri.get1()).multLocal(invDim); scaledTri.get2().set(tri.get2()).multLocal(invDim); @@ -216,12 +217,12 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ boolean embedded = false; float signedDistanceToPlane = triPlane.pseudoDistance(sCenter); - if (normalDotVelocity == 0.0f){ + if (normalDotVelocity == 0.0f) { // we are travelling exactly parallel to the plane - if (FastMath.abs(signedDistanceToPlane) >= 1.0f){ + if (FastMath.abs(signedDistanceToPlane) >= 1.0f) { // no collision possible return null; - }else{ + } else { // we are embedded t0 = 0; t1 = 1; @@ -229,17 +230,17 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ System.out.println("EMBEDDED"); return null; } - }else{ + } else { t0 = (-1f - signedDistanceToPlane) / normalDotVelocity; - t1 = ( 1f - signedDistanceToPlane) / normalDotVelocity; + t1 = (1f - signedDistanceToPlane) / normalDotVelocity; - if (t0 > t1){ + if (t0 > t1) { float tf = t1; t1 = t0; t0 = tf; } - if (t0 > 1.0f || t1 < 0.0f){ + if (t0 > 1.0f || t1 < 0.0f) { // collision is out of this sVelocity range return null; } @@ -254,35 +255,35 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ Vector3f contactPoint = new Vector3f(); Vector3f contactNormal = new Vector3f(); - -// if (!embedded){ - // check against the inside of the scaledTriangle - // contactPoint = sCenter - p.normal + t0 * sVelocity - contactPoint.set(sVelocity); - contactPoint.multLocal(t0); - contactPoint.addLocal(sCenter); - contactPoint.subtractLocal(triPlane.getNormal()); - // test to see if the collision is on a scaledTriangle interior - if (isPointInTriangle(contactPoint, scaledTri) && !embedded){ - foundCollision = true; +// if (!embedded){ + // check against the inside of the scaledTriangle + // contactPoint = sCenter - p.normal + t0 * sVelocity + contactPoint.set(sVelocity); + contactPoint.multLocal(t0); + contactPoint.addLocal(sCenter); + contactPoint.subtractLocal(triPlane.getNormal()); + + // test to see if the collision is on a scaledTriangle interior + if (isPointInTriangle(contactPoint, scaledTri) && !embedded) { + foundCollision = true; - minT = t0; + minT = t0; - // scale collision point back into R3 - contactPoint.multLocal(dimension); - contactNormal.set(velocity).multLocal(t0); - contactNormal.addLocal(center); - contactNormal.subtractLocal(contactPoint).normalizeLocal(); + // scale collision point back into R3 + contactPoint.multLocal(dimension); + contactNormal.set(velocity).multLocal(t0); + contactNormal.addLocal(center); + contactNormal.subtractLocal(contactPoint).normalizeLocal(); // contactNormal.set(triPlane.getNormal()); - - CollisionResult result = new CollisionResult(); - result.setContactPoint(contactPoint); - result.setContactNormal(contactNormal); - result.setDistance(minT * velocity.length()); - return result; - } + + CollisionResult result = new CollisionResult(); + result.setContactPoint(contactPoint); + result.setContactNormal(contactNormal); + result.setDistance(minT * velocity.length()); + return result; + } // } float velocitySquared = sVelocity.lengthSquared(); @@ -294,7 +295,7 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ // vertex 1 float newT; newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v1, minT); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; contactPoint.set(v1); foundCollision = true; @@ -302,7 +303,7 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ // vertex 2 newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v2, minT); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; contactPoint.set(v2); foundCollision = true; @@ -310,7 +311,7 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ // vertex 3 newT = collideWithVertex(sCenter, sVelocity, velocitySquared, v3, minT); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; contactPoint.set(v3); foundCollision = true; @@ -318,26 +319,26 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ // edge 1-2 newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v1, v2, minT, contactPoint); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; foundCollision = true; } // edge 2-3 newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v2, v3, minT, contactPoint); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; foundCollision = true; } // edge 3-1 newT = collideWithSegment(sCenter, sVelocity, velocitySquared, v3, v1, minT, contactPoint); - if (!Float.isNaN(newT)){ + if (!Float.isNaN(newT)) { minT = newT; foundCollision = true; } - if (foundCollision){ + if (foundCollision) { // compute contact normal based on minimum t contactPoint.multLocal(dimension); contactNormal.set(velocity).multLocal(t0); @@ -350,12 +351,12 @@ private CollisionResult collideWithTriangle(AbstractTriangle tri){ result.setDistance(minT * velocity.length()); return result; - }else{ + } else { return null; } } - public CollisionResult collideWithSweepSphere(SweepSphere other){ + public CollisionResult collideWithSweepSphere(SweepSphere other) { temp1.set(velocity).subtractLocal(other.velocity); temp2.set(center).subtractLocal(other.center); temp3.set(dimension).addLocal(other.dimension); @@ -387,11 +388,11 @@ public CollisionResult collideWithSweepSphere(SweepSphere other){ // temp3 is contact point temp3.set(temp2).multLocal(dimension).addLocal(temp1); result.setContactPoint(new Vector3f(temp3)); - + return result; } - public static void main(String[] args){ + public static void main(String[] args) { SweepSphere ss = new SweepSphere(); ss.setCenter(Vector3f.ZERO); ss.setDimension(1); @@ -404,36 +405,36 @@ public static void main(String[] args){ CollisionResults cr = new CollisionResults(); ss.collideWith(ss2, cr); - if (cr.size() > 0){ + if (cr.size() > 0) { CollisionResult c = cr.getClosestCollision(); - System.out.println("D = "+c.getDistance()); - System.out.println("P = "+c.getContactPoint()); - System.out.println("N = "+c.getContactNormal()); + System.out.println("D = " + c.getDistance()); + System.out.println("P = " + c.getContactPoint()); + System.out.println("N = " + c.getContactNormal()); } } + @Override public int collideWith(Collidable other, CollisionResults results) throws UnsupportedCollisionException { - if (other instanceof AbstractTriangle){ + if (other instanceof AbstractTriangle) { AbstractTriangle tri = (AbstractTriangle) other; CollisionResult result = collideWithTriangle(tri); - if (result != null){ + if (result != null) { results.addCollision(result); return 1; } return 0; - }else if (other instanceof SweepSphere){ + } else if (other instanceof SweepSphere) { SweepSphere sph = (SweepSphere) other; CollisionResult result = collideWithSweepSphere(sph); - if (result != null){ + if (result != null) { results.addCollision(result); return 1; } return 0; - }else{ + } else { throw new UnsupportedCollisionException(); } } - } diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java index 52e4f91ca8..12bf88b23a 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -111,6 +111,7 @@ public void setRightPlane(float rightPlane) { this.rightPlane = rightPlane; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(leftIndex, "left_index", 0); @@ -122,6 +123,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(right, "right_node", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); leftIndex = ic.readInt("left_index", 0); @@ -409,15 +411,20 @@ public final int intersectWhere(Ray r, t = t_world; } - Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); - Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); - float worldSpaceDist = o.distance(contactPoint); - - CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); - cr.setContactNormal(contactNormal); - cr.setTriangleIndex(tree.getTriangleIndex(i)); - results.addCollision(cr); - cols++; + // this second isInfinite test is unlikely to fail but due to numeric precision it might + // be the case that in local coordinates it just hits and in world coordinates it just misses + // this filters those cases out (treating them as misses). + if (!Float.isInfinite(t)){ + Vector3f contactNormal = Triangle.computeTriangleNormal(v1, v2, v3, null); + Vector3f contactPoint = new Vector3f(d).multLocal(t).addLocal(o); + float worldSpaceDist = o.distance(contactPoint); + + CollisionResult cr = new CollisionResult(contactPoint, worldSpaceDist); + cr.setContactNormal(contactNormal); + cr.setTriangleIndex(tree.getTriangleIndex(i)); + results.addCollision(cr); + cols++; + } } } } diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java index 2d7a4dc24e..fca434f750 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTree.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,16 +68,9 @@ public class BIHTree implements CollisionData { private int numTris; private float[] pointData; private int[] triIndices; - + // private transient CollisionResults boundResults = new CollisionResults(); private transient float[] bihSwapTmp; - - private static final TriangleAxisComparator[] comparators = new TriangleAxisComparator[] - { - new TriangleAxisComparator(0), - new TriangleAxisComparator(1), - new TriangleAxisComparator(2) - }; private void initTriList(FloatBuffer vb, IndexBuffer ib) { pointData = new float[numTris * 3 * 3]; @@ -119,12 +112,12 @@ public BIHTree(Mesh mesh, int maxTrisPerNode) { bihSwapTmp = new float[9]; VertexBuffer vBuffer = mesh.getBuffer(Type.Position); - if(vBuffer == null){ + if (vBuffer == null) { throw new IllegalArgumentException("A mesh should at least contain a Position buffer"); - } + } IndexBuffer ib = mesh.getIndexBuffer(); FloatBuffer vb = (FloatBuffer) vBuffer.getData(); - + if (ib == null) { ib = new VirtualIndexBuffer(mesh.getVertexCount(), mesh.getMode()); } else if (mesh.getMode() != Mode.Triangles) { @@ -333,7 +326,7 @@ private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) { pivot = (r + l) / 2; } - //If one of the partitions is empty, continue with recursion: same level but different bbox + // If one of the partitions is empty, continue with recursion: same level but different bbox if (pivot < l) { //Only right BoundingBox rbbox = new BoundingBox(currentBox); @@ -345,21 +338,21 @@ private BIHNode createNode(int l, int r, BoundingBox nodeBbox, int depth) { setMinMax(lbbox, false, axis, split); return createNode(l, r, lbbox, depth + 1); } else { - //Build the node + // Build the node BIHNode node = new BIHNode(axis); - //Left child + // Left child BoundingBox lbbox = new BoundingBox(currentBox); setMinMax(lbbox, false, axis, split); - //The left node right border is the plane most right + // The left node right border is the plane most right node.setLeftPlane(getMinMax(createBox(l, max(l, pivot - 1)), false, axis)); node.setLeftChild(createNode(l, max(l, pivot - 1), lbbox, depth + 1)); //Recursive call - //Right Child + // Right Child BoundingBox rbbox = new BoundingBox(currentBox); setMinMax(rbbox, true, axis, split); - //The right node left border is the plane most left + // The right node left border is the plane most left node.setRightPlane(getMinMax(createBox(pivot, r), true, axis)); node.setRightChild(createNode(pivot, r, rbbox, depth + 1)); //Recursive call @@ -428,7 +421,7 @@ private int collideWithRay(Ray r, if (r.getLimit() < Float.POSITIVE_INFINITY) { tMax = Math.min(tMax, r.getLimit()); - if (tMin > tMax){ + if (tMin > tMax) { return 0; } } @@ -461,6 +454,7 @@ private int collideWithBoundingVolume(BoundingVolume bv, return root.intersectWhere(bv, bbox, worldMatrix, this, results); } + @Override public int collideWith(Collidable other, Matrix4f worldMatrix, BoundingVolume worldBound, @@ -477,6 +471,7 @@ public int collideWith(Collidable other, } } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(mesh, "mesh", null); @@ -486,6 +481,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(triIndices, "indices", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); mesh = (Mesh) ic.readSavable("mesh", null); diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java index bff45b5d86..d654e4014a 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/BIHTriangle.java @@ -36,69 +36,84 @@ public final class BIHTriangle { - private final Vector3f pointa = new Vector3f(); - private final Vector3f pointb = new Vector3f(); - private final Vector3f pointc = new Vector3f(); + private final Vector3f pointA = new Vector3f(); + private final Vector3f pointB = new Vector3f(); + private final Vector3f pointC = new Vector3f(); private final Vector3f center = new Vector3f(); public BIHTriangle(Vector3f p1, Vector3f p2, Vector3f p3) { - pointa.set(p1); - pointb.set(p2); - pointc.set(p3); - center.set(pointa); - center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD); + pointA.set(p1); + pointB.set(p2); + pointC.set(p3); + center.set(pointA); + center.addLocal(pointB).addLocal(pointC).multLocal(FastMath.ONE_THIRD); } - public Vector3f get1(){ - return pointa; + public Vector3f get1() { + return pointA; } - public Vector3f get2(){ - return pointb; + public Vector3f get2() { + return pointB; } - public Vector3f get3(){ - return pointc; + public Vector3f get3() { + return pointC; } public Vector3f getCenter() { return center; } - public Vector3f getNormal(){ - Vector3f normal = new Vector3f(pointb); - normal.subtractLocal(pointa).crossLocal(pointc.x-pointa.x, pointc.y-pointa.y, pointc.z-pointa.z); + public Vector3f getNormal() { + Vector3f normal = new Vector3f(pointB); + normal.subtractLocal(pointA) + .crossLocal(pointC.x - pointA.x, pointC.y - pointA.y, pointC.z - pointA.z); normal.normalizeLocal(); return normal; } - public float getExtreme(int axis, boolean left){ + public float getExtreme(int axis, boolean left) { float v1, v2, v3; - switch (axis){ - case 0: v1 = pointa.x; v2 = pointb.x; v3 = pointc.x; break; - case 1: v1 = pointa.y; v2 = pointb.y; v3 = pointc.y; break; - case 2: v1 = pointa.z; v2 = pointb.z; v3 = pointc.z; break; - default: assert false; return 0; + switch (axis) { + case 0: + v1 = pointA.x; + v2 = pointB.x; + v3 = pointC.x; + break; + case 1: + v1 = pointA.y; + v2 = pointB.y; + v3 = pointC.y; + break; + case 2: + v1 = pointA.z; + v2 = pointB.z; + v3 = pointC.z; + break; + default: + assert false; + return 0; } - if (left){ - if (v1 < v2){ + if (left) { + if (v1 < v2) { if (v1 < v3) return v1; else return v3; - }else{ + } else { if (v2 < v3) return v2; else return v3; } - }else{ - if (v1 > v2){ + } else { + if (v1 > v2) { if (v1 > v3) return v1; else return v3; - }else{ + } else { if (v2 > v3) return v2; else @@ -106,5 +121,4 @@ public float getExtreme(int axis, boolean left){ } } } - } diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java b/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java index 836a972e96..a409eefb0f 100644 --- a/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java +++ b/jme3-core/src/main/java/com/jme3/collision/bih/TriangleAxisComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,19 +38,31 @@ public class TriangleAxisComparator implements Comparator { private final int axis; - public TriangleAxisComparator(int axis){ + public TriangleAxisComparator(int axis) { this.axis = axis; } + @Override public int compare(BIHTriangle o1, BIHTriangle o2) { float v1, v2; Vector3f c1 = o1.getCenter(); Vector3f c2 = o2.getCenter(); - switch (axis){ - case 0: v1 = c1.x; v2 = c2.x; break; - case 1: v1 = c1.y; v2 = c2.y; break; - case 2: v1 = c1.z; v2 = c2.z; break; - default: assert false; return 0; + switch (axis) { + case 0: + v1 = c1.x; + v2 = c2.x; + break; + case 1: + v1 = c1.y; + v2 = c2.y; + break; + case 2: + v1 = c1.z; + v2 = c2.z; + break; + default: + assert false; + return 0; } if (v1 > v2) return 1; @@ -59,4 +71,4 @@ else if (v1 < v2) else return 0; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/collision/bih/package-info.java b/jme3-core/src/main/java/com/jme3/collision/bih/package-info.java new file mode 100644 index 0000000000..5344a0101a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/bih/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * bounding interval hierarchies (BIH) for use in collision detection + */ +package com.jme3.collision.bih; diff --git a/jme3-core/src/main/java/com/jme3/collision/package-info.java b/jme3-core/src/main/java/com/jme3/collision/package-info.java new file mode 100644 index 0000000000..4dad5eef61 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/collision/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * scene-based (non-physics) collision detection + */ +package com.jme3.collision; diff --git a/jme3-core/src/main/java/com/jme3/effect/Particle.java b/jme3-core/src/main/java/com/jme3/effect/Particle.java index 522306ad91..674ed802a6 100644 --- a/jme3-core/src/main/java/com/jme3/effect/Particle.java +++ b/jme3-core/src/main/java/com/jme3/effect/Particle.java @@ -86,7 +86,7 @@ public class Particle { */ public int imageIndex = 0; - /** + /* * Distance to camera. Only used for sorted particles. */ //public float distToCam; 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 ea0a2a3b4d..338894bbdf 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-2019 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 @@ -69,7 +70,7 @@ * simulation. The interpretation of these properties depends on the * {@link ParticleInfluencer} that has been assigned to the emitter via * {@link ParticleEmitter#setParticleInfluencer(com.jme3.effect.influencers.ParticleInfluencer) }. - * By default the implementation {@link DefaultParticleInfluencer} is used. + * By default, the {@link DefaultParticleInfluencer} implementation is used. * * @author Kirill Vainer */ @@ -131,16 +132,17 @@ public Control cloneForSpatial(Spatial spatial) { public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning", e); } } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.parentEmitter = cloner.clone(parentEmitter); } + @Override public void setSpatial(Spatial spatial) { } @@ -152,17 +154,21 @@ public boolean isEnabled() { return parentEmitter.isEnabled(); } + @Override public void update(float tpf) { parentEmitter.updateFromControl(tpf); } + @Override public void render(RenderManager rm, ViewPort vp) { parentEmitter.renderFromControl(rm, vp); } + @Override public void write(JmeExporter ex) throws IOException { } + @Override public void read(JmeImporter im) throws IOException { } } @@ -174,12 +180,13 @@ public ParticleEmitter clone() { @Override public ParticleEmitter clone(boolean cloneMaterial) { - return (ParticleEmitter)super.clone(cloneMaterial); + return (ParticleEmitter) super.clone(cloneMaterial); } /** * The old clone() method that did not use the new Cloner utility. */ + @Override public ParticleEmitter oldClone(boolean cloneMaterial) { ParticleEmitter clone = (ParticleEmitter) super.clone(cloneMaterial); clone.shape = shape.deepClone(); @@ -222,7 +229,7 @@ public ParticleEmitter oldClone(boolean cloneMaterial) { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.shape = cloner.clone(shape); @@ -242,12 +249,12 @@ public void cloneFields( Cloner cloner, Object original ) { // 3) the particles array is recreated because setNumParticles() // // ...so this should be equivalent but simpler than half of the old clone() - // method. Note: we do not ever want to share particleMesh so we do not + // method. Note: we do not ever want to share particleMesh, so we do not // clone it at all. setMeshType(meshType); // change in behavior: temp and lastPos were not cloned before... - // perhaps because it was believed that 'transient' fields were exluded + // perhaps because it was believed that 'transient' fields were excluded // from cloning? (they aren't) // If it was ok for these to be shared because of how they are used // then they could just as well be made static... else I think it's clearer @@ -278,6 +285,20 @@ public ParticleEmitter(String name, Type type, int numParticles) { control = new ParticleEmitterControl(this); controls.add(control); + this.initParticleMesh(); + this.setNumParticles(numParticles); +// particleMesh.initParticleData(this, particles.length); + } + + /** + * For serialization only. Do not use. + */ + protected ParticleEmitter() { + super(); + setBatchHint(BatchHint.Never); + } + + private void initParticleMesh() { switch (meshType) { case Point: particleMesh = new ParticlePointMesh(); @@ -290,16 +311,6 @@ public ParticleEmitter(String name, Type type, int numParticles) { default: throw new IllegalStateException("Unrecognized particle type: " + meshType); } - this.setNumParticles(numParticles); -// particleMesh.initParticleData(this, particles.length); - } - - /** - * For serialization only. Do not use. - */ - protected ParticleEmitter() { - super(); - setBatchHint(BatchHint.Never); } public void setShape(EmitterShape shape) { @@ -354,18 +365,7 @@ public ParticleMesh.Type getMeshType() { */ public void setMeshType(ParticleMesh.Type meshType) { this.meshType = meshType; - switch (meshType) { - case Point: - particleMesh = new ParticlePointMesh(); - this.setMesh(particleMesh); - break; - case Triangle: - particleMesh = new ParticleTriMesh(); - this.setMesh(particleMesh); - break; - default: - throw new IllegalStateException("Unrecognized particle type: " + meshType); - } + this.initParticleMesh(); this.setNumParticles(particles.length); } @@ -412,14 +412,14 @@ public int getNumVisibleParticles() { * Calling this method many times is not recommended. * * @param numParticles the maximum amount of particles that - * can exist at the same time with this emitter. + * can exist at the same time with this emitter. */ public final void setNumParticles(int numParticles) { particles = new Particle[numParticles]; for (int i = 0; i < numParticles; i++) { particles[i] = new Particle(); } - //We have to reinit the mesh's buffers with the new size + // We must reinitialize the mesh's buffers to the new size. particleMesh.initParticleData(this, particles.length); particleMesh.setImagesXY(this.imagesX, this.imagesY); firstUnUsed = 0; @@ -464,7 +464,7 @@ public Vector3f getFaceNormal() { * Sets the normal which particles are facing. * *

    By default, particles - * will face the camera, but for some effects (e.g shockwave) it may + * will face the camera, but for some effects (e.g. shockwave) it may * be necessary to face a specific direction instead. To restore * normal functionality, provide null as the argument for * faceNormal. @@ -507,7 +507,7 @@ public void setRotateSpeed(float rotateSpeed) { * should have a random facing angle. * * @return true if every particle spawned - * should have a random facing angle. + * should have a random facing angle. * * @see ParticleEmitter#setRandomAngle(boolean) */ @@ -520,7 +520,7 @@ public boolean isRandomAngle() { * should have a random facing angle. * * @param randomAngle if every particle spawned - * should have a random facing angle. + * should have a random facing angle. */ public void setRandomAngle(boolean randomAngle) { this.randomAngle = randomAngle; @@ -531,7 +531,7 @@ public void setRandomAngle(boolean randomAngle) { * image. * * @return True if every particle spawned should get a random - * image. + * image. * * @see ParticleEmitter#setSelectRandomImage(boolean) */ @@ -553,7 +553,7 @@ public boolean isSelectRandomImage() { * the particle reaches its end of life. * * @param selectRandomImage True if every particle spawned should get a random - * image. + * image. */ public void setSelectRandomImage(boolean selectRandomImage) { this.selectRandomImage = selectRandomImage; @@ -574,7 +574,7 @@ public boolean isFacingVelocity() { * Set to true if particles spawned should face * their velocity (or direction to which they are moving towards). * - *

    This is typically used for e.g spark effects. + *

    This is typically used for e.g. spark effects. * * @param followVelocity True if particles spawned should face their velocity. * @@ -766,7 +766,7 @@ public void setLowLife(float lowLife) { * second. * * @return the number of particles to spawn per - * second. + * second. * * @see ParticleEmitter#setParticlesPerSec(float) */ @@ -779,11 +779,12 @@ public float getParticlesPerSec() { * second. * * @param particlesPerSec the number of particles to spawn per - * second. + * second. */ public void setParticlesPerSec(float particlesPerSec) { this.particlesPerSec = particlesPerSec; - timeDifference = Math.min(timeDifference,1f / particlesPerSec); //prevent large accumulated timeDifference from causing a huge number of particles to be emitted + timeDifference = Math.min(timeDifference, 1f / particlesPerSec); + //prevent large accumulated timeDifference from causing a huge number of particles to be emitted } /** @@ -836,6 +837,8 @@ public void setStartSize(float startSize) { /** * @deprecated Use ParticleEmitter.getParticleInfluencer().getInitialVelocity() instead. + * + * @return the pre-existing velocity vector */ @Deprecated public Vector3f getInitialVelocity() { @@ -844,17 +847,17 @@ public Vector3f getInitialVelocity() { /** * @param initialVelocity Set the initial velocity a particle is spawned with, - * the initial velocity given in the parameter will be varied according - * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }. - * The particle will move with this velocity unless it is affected by - * gravity. + * the initial velocity given in the parameter will be varied according + * to the velocity variation set in {@link ParticleEmitter#setVelocityVariation(float) }. + * The particle will move with this velocity unless it is affected by + * gravity. * * @deprecated - * This method is deprecated. - * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead. + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().setInitialVelocity(initialVelocity); instead. * * @see ParticleEmitter#setVelocityVariation(float) - * @see #setGravity(com.jme3.math.Vector3f) + * @see #setGravity(com.jme3.math.Vector3f) */ @Deprecated public void setInitialVelocity(Vector3f initialVelocity) { @@ -863,8 +866,8 @@ public void setInitialVelocity(Vector3f initialVelocity) { /** * @deprecated - * This method is deprecated. - * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead. + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().getVelocityVariation(); instead. * @return the initial velocity variation factor */ @Deprecated @@ -874,15 +877,15 @@ public float getVelocityVariation() { /** * @param variation Set the variation by which the initial velocity - * of the particle is determined. variation should be a value - * from 0 to 1, where 0 means particles are to spawn with exactly - * the velocity specified in - * {@link com.jme3.effect.influencers.ParticleInfluencer#setInitialVelocity(com.jme3.math.Vector3f)}, - * and 1 means particles are to spawn with a completely random velocity. + * of the particle is determined. variation should be a value + * from 0 to 1, where 0 means particles are to spawn with exactly + * the velocity specified in + * {@link com.jme3.effect.influencers.ParticleInfluencer#setInitialVelocity(com.jme3.math.Vector3f)}, + * and 1 means particles are to spawn with a completely random velocity. * * @deprecated - * This method is deprecated. - * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead. + * This method is deprecated. + * Use ParticleEmitter.getParticleInfluencer().setVelocityVariation(variation); instead. */ @Deprecated public void setVelocityVariation(float variation) { @@ -897,7 +900,8 @@ private Particle emitParticle(Vector3f min, Vector3f max) { Particle p = particles[idx]; if (selectRandomImage) { - p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) * imagesX + FastMath.nextRandomInt(0, imagesX - 1); + p.imageIndex = FastMath.nextRandomInt(0, imagesY - 1) + * imagesX + FastMath.nextRandomInt(0, imagesX - 1); } p.startlife = lowLife + FastMath.nextRandomFloat() * (highLife - lowLife); @@ -906,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); @@ -918,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; @@ -938,6 +941,8 @@ public void emitAllParticles() { /** * Instantly emits available particles, up to num. + * + * @param num the maximum number of particles to emit */ public void emitParticles(int num) { // Force world transform to update @@ -960,8 +965,9 @@ public void emitParticles(int num) { max.set(Vector3f.NEGATIVE_INFINITY); } - for(int i=0;i interval){ + while (tpf > interval) { tpf -= interval; Particle p = emitParticle(min, max); - if (p != null){ + 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){ + if (p.life <= 0) { freeParticle(lastUsed); - }else{ + } else { updateParticle(p, tpf, min, max); } } @@ -1103,7 +1124,7 @@ private void updateParticleState(float tpf) { lastPos.set(getWorldTranslation()); //This check avoids a NaN bounds when all the particles are dead during the first update. - if (!min.equals(Vector3f.POSITIVE_INFINITY) && !max.equals(Vector3f.NEGATIVE_INFINITY)) { + if (Vector3f.isValidVector(min) && Vector3f.isValidVector(max)) { BoundingBox bbox = (BoundingBox) this.getMesh().getBound(); bbox.setMinMax(min, max); this.setBoundRefresh(); @@ -1137,7 +1158,8 @@ public boolean isEnabled() { /** * Callback from Control.update(), do not use. - * @param tpf + * + * @param tpf time per frame (in seconds) */ public void updateFromControl(float tpf) { if (enabled) { @@ -1148,8 +1170,8 @@ public void updateFromControl(float tpf) { /** * Callback from Control.render(), do not use. * - * @param rm - * @param vp + * @param rm the RenderManager rendering this Emitter (not null) + * @param vp the ViewPort being rendered (not null) */ private void renderFromControl(RenderManager rm, ViewPort vp) { Camera cam = vp.getCamera(); @@ -1162,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); } } @@ -1223,7 +1243,6 @@ public void read(JmeImporter im) throws IOException { meshType = ic.readEnum("meshType", ParticleMesh.Type.class, ParticleMesh.Type.Triangle); int numParticles = ic.readInt("numParticles", 0); - enabled = ic.readBoolean("enabled", true); particlesPerSec = ic.readFloat("particlesPerSec", 0); lowLife = ic.readFloat("lowLife", 0); @@ -1239,23 +1258,12 @@ public void read(JmeImporter im) throws IOException { worldSpace = ic.readBoolean("worldSpace", false); this.setIgnoreTransform(worldSpace); facingVelocity = ic.readBoolean("facingVelocity", false); - faceNormal = (Vector3f)ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN)); + faceNormal = (Vector3f) ic.readSavable("faceNormal", new Vector3f(Vector3f.NAN)); selectRandomImage = ic.readBoolean("selectRandomImage", false); randomAngle = ic.readBoolean("randomAngle", false); rotateSpeed = ic.readFloat("rotateSpeed", 0); - switch (meshType) { - case Point: - particleMesh = new ParticlePointMesh(); - this.setMesh(particleMesh); - break; - case Triangle: - particleMesh = new ParticleTriMesh(); - this.setMesh(particleMesh); - break; - default: - throw new IllegalStateException("Unrecognized particle type: " + meshType); - } + this.initParticleMesh(); this.setNumParticles(numParticles); // particleMesh.initParticleData(this, particles.length); // particleMesh.setImagesXY(imagesX, imagesY); @@ -1279,7 +1287,7 @@ public void read(JmeImporter im) throws IOException { } } - // compatability before gravity was not a vector but a float + // compatibility before gravity was not a vector but a float if (gravity == null) { gravity = new Vector3f(); gravity.y = ic.readFloat("gravity", 0); @@ -1289,7 +1297,6 @@ public void read(JmeImporter im) throws IOException { // loaded separately control = getControl(ParticleEmitterControl.class); control.parentEmitter = this; - } } } diff --git a/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java b/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java index 11e05555d9..b4333ae9f8 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleMesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +66,7 @@ public enum Type { * Initialize mesh data. * * @param emitter The emitter which will use this ParticleMesh. - * @param numParticles The maxmimum number of particles to simulate + * @param numParticles The maximum number of particles to simulate */ public abstract void initParticleData(ParticleEmitter emitter, int numParticles); @@ -79,6 +79,10 @@ public enum Type { /** * Update the particle visual data. Typically called every frame. + * + * @param particles the particles to update + * @param cam the camera to use for billboarding + * @param inverseRotation the inverse rotation matrix */ public abstract void updateParticleData(Particle[] particles, Camera cam, Matrix3f inverseRotation); 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 b9c0f43d8b..8f43bec5b2 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticlePointMesh.java @@ -61,8 +61,8 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { // set positions FloatBuffer pb = BufferUtils.createVector3Buffer(numParticles); - - //if the buffer is already set only update the data + + // if the buffer is already set only update the data VertexBuffer buf = getBuffer(VertexBuffer.Type.Position); if (buf != null) { buf.updateData(pb); @@ -74,7 +74,7 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { // set colors ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4); - + buf = getBuffer(VertexBuffer.Type.Color); if (buf != null) { buf.updateData(cb); @@ -87,7 +87,7 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { // set sizes FloatBuffer sb = BufferUtils.createFloatBuffer(numParticles); - + buf = getBuffer(VertexBuffer.Type.Size); if (buf != null) { buf.updateData(sb); @@ -98,8 +98,8 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { } // set UV-scale - FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles*4); - + FloatBuffer tb = BufferUtils.createFloatBuffer(numParticles * 4); + buf = getBuffer(VertexBuffer.Type.TexCoord); if (buf != null) { buf.updateData(tb); @@ -108,7 +108,7 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { tvb.setupData(Usage.Stream, 4, Format.Float, tb); setBuffer(tvb); } - + updateCounts(); } @@ -133,9 +133,10 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers colors.rewind(); sizes.rewind(); texcoords.rewind(); - for (int i = 0; i < particles.length; i++){ + + for (int i = 0; i < particles.length; i++) { Particle p = particles[i]; - + positions.put(p.position.x) .put(p.position.y) .put(p.position.z); @@ -144,15 +145,16 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers colors.putInt(p.color.asIntABGR()); int imgX = p.imageIndex % imagesX; - int imgY = p.imageIndex/imagesX; + int imgY = p.imageIndex / imagesX; float startX = ((float) imgX) / imagesX; float startY = ((float) imgY) / imagesY; - float endX = startX + (1f / imagesX); - float endY = startY + (1f / imagesY); + float endX = startX + (1f / imagesX); + float endY = startY + (1f / imagesY); 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 16b9071d6d..62a214e2fd 100644 --- a/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java +++ b/jme3-core/src/main/java/com/jme3/effect/ParticleTriMesh.java @@ -72,7 +72,7 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { pvb.setupData(Usage.Stream, 3, Format.Float, pb); setBuffer(pvb); } - + // set colors ByteBuffer cb = BufferUtils.createByteBuffer(numParticles * 4 * 4); buf = getBuffer(VertexBuffer.Type.Color); @@ -88,14 +88,14 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { // set texcoords FloatBuffer tb = BufferUtils.createVector2Buffer(numParticles * 4); uniqueTexCoords = false; - for (int i = 0; i < numParticles; i++){ + for (int i = 0; i < numParticles; i++) { tb.put(0f).put(1f); tb.put(1f).put(1f); tb.put(0f).put(0f); tb.put(1f).put(0f); } tb.flip(); - + buf = getBuffer(VertexBuffer.Type.TexCoord); if (buf != null) { buf.updateData(tb); @@ -107,18 +107,18 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { // set indices ShortBuffer ib = BufferUtils.createShortBuffer(numParticles * 6); - for (int i = 0; i < numParticles; i++){ + for (int i = 0; i < numParticles; i++) { int startIdx = (i * 4); // triangle 1 - ib.put((short)(startIdx + 1)) - .put((short)(startIdx + 0)) - .put((short)(startIdx + 2)); + ib.put((short) (startIdx + 1)) + .put((short) (startIdx + 0)) + .put((short) (startIdx + 2)); // triangle 2 - ib.put((short)(startIdx + 1)) - .put((short)(startIdx + 2)) - .put((short)(startIdx + 3)); + ib.put((short) (startIdx + 1)) + .put((short) (startIdx + 2)) + .put((short) (startIdx + 3)); } ib.flip(); @@ -130,15 +130,15 @@ public void initParticleData(ParticleEmitter emitter, int numParticles) { ivb.setupData(Usage.Static, 3, Format.UnsignedShort, ib); setBuffer(ivb); } - + updateCounts(); } - + @Override public void setImagesXY(int imagesX, int imagesY) { this.imagesX = imagesX; this.imagesY = imagesY; - if (imagesX != 1 || imagesY != 1){ + if (imagesX != 1 || imagesY != 1) { uniqueTexCoords = true; getBuffer(VertexBuffer.Type.TexCoord).setUsage(Usage.Stream); } @@ -162,9 +162,9 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers VertexBuffer tvb = getBuffer(VertexBuffer.Type.TexCoord); FloatBuffer texcoords = (FloatBuffer) tvb.getData(); - Vector3f camUp = cam.getUp(); + Vector3f camUp = cam.getUp(); Vector3f camLeft = cam.getLeft(); - Vector3f camDir = cam.getDirection(); + Vector3f camDir = cam.getDirection(); inverseRotation.multLocal(camUp); inverseRotation.multLocal(camLeft); @@ -172,10 +172,10 @@ 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){ + if (!facingVelocity) { up.set(camUp); left.set(camLeft); } @@ -185,28 +185,31 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers colors.clear(); texcoords.clear(); Vector3f faceNormal = emitter.getFaceNormal(); - - for (int i = 0; i < particles.length; i++){ + + for (int i = 0; i < particles.length; i++) { Particle p = particles[i]; boolean dead = p.life == 0; - if (dead){ + + if (dead) { positions.put(0).put(0).put(0); positions.put(0).put(0).put(0); positions.put(0).put(0).put(0); positions.put(0).put(0).put(0); continue; } - - if (facingVelocity){ + + if (facingVelocity) { left.set(p.velocity).normalizeLocal(); camDir.cross(left, up); up.multLocal(p.size); left.multLocal(p.size); - }else if (faceNormal != null){ + + } 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(); @@ -215,7 +218,7 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers vars.quat1.multLocal(up); vars.release(); } - }else if (p.angle != 0){ + } else if (p.angle != 0) { float cos = FastMath.cos(p.angle) * p.size; float sin = FastMath.sin(p.angle) * p.size; @@ -226,7 +229,8 @@ 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{ + + } else { up.set(camUp); left.set(camLeft); up.multLocal(p.size); @@ -249,14 +253,14 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers .put(p.position.y - left.y - up.y) .put(p.position.z - left.z - up.z); - if (uniqueTexCoords){ + if (uniqueTexCoords) { int imgX = p.imageIndex % imagesX; int imgY = p.imageIndex / imagesX; float startX = ((float) imgX) / imagesX; float startY = ((float) imgY) / imagesY; - float endX = startX + (1f / imagesX); - float endY = startY + (1f / imagesY); + float endX = startX + (1f / imagesX); + float endY = startY + (1f / imagesY); texcoords.put(startX).put(endY); texcoords.put(endX).put(endY); @@ -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); } @@ -284,5 +287,4 @@ public void updateParticleData(Particle[] particles, Camera cam, Matrix3f invers pvb.updateData(positions); cvb.updateData(colors); } - } diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java index efe26af495..ba12a90e44 100644 --- a/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/DefaultParticleInfluencer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,17 +40,18 @@ import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.util.clone.Cloner; + import java.io.IOException; /** * This emitter influences the particles so that they move all in the same direction. - * The direction may vary a little if the velocity variation is non zero. + * The direction may vary a little if the velocity variation is non-zero. * This influencer is default for the particle emitter. * @author Marcin Roguski (Kaelthas) */ public class DefaultParticleInfluencer implements ParticleInfluencer { - //Version #1 : changed startVelocity to initialvelocity for consistency with accessors + //Version #1 : changed startVelocity to initialVelocity for consistency with accessors //and also changed it in serialization public static final int SAVABLE_VERSION = 1; /** Temporary variable used to help with calculations. */ @@ -72,7 +73,7 @@ public void influenceParticle(Particle particle, EmitterShape emitterShape) { * the particle to be affected */ protected void applyVelocityVariation(Particle particle) { - particle.velocity.set(initialVelocity); + particle.velocity.set(initialVelocity); temp.set(FastMath.nextRandomFloat(), FastMath.nextRandomFloat(), FastMath.nextRandomFloat()); temp.multLocal(2f); temp.subtractLocal(1f, 1f, 1f); @@ -91,23 +92,20 @@ public void write(JmeExporter ex) throws IOException { public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); // NOTE: In previous versions of jME3, initialVelocity was called startVelocity - if (ic.getSavableVersion(DefaultParticleInfluencer.class) == 0){ + if (ic.getSavableVersion(DefaultParticleInfluencer.class) == 0) { initialVelocity = (Vector3f) ic.readSavable("startVelocity", Vector3f.ZERO.clone()); - }else{ + } else { initialVelocity = (Vector3f) ic.readSavable("initialVelocity", Vector3f.ZERO.clone()); } velocityVariation = ic.readFloat("variation", 0.2f); } @Override - public ParticleInfluencer clone() { - try { - DefaultParticleInfluencer clone = (DefaultParticleInfluencer) super.clone(); - clone.initialVelocity = initialVelocity.clone(); - return clone; - } catch (CloneNotSupportedException e) { - throw new AssertionError(); - } + public DefaultParticleInfluencer clone() { + // Set up the cloner for the type of cloning we want to do. + Cloner cloner = new Cloner(); + DefaultParticleInfluencer clone = cloner.clone(this); + return clone; } /** @@ -126,7 +124,7 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.initialVelocity = cloner.clone(initialVelocity); // Change in behavior: I'm cloning 'for real' the 'temp' field because diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java index 0271df1106..398ee1fd9c 100644 --- a/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/EmptyParticleInfluencer.java @@ -77,9 +77,9 @@ public float getVelocityVariation() { } @Override - public ParticleInfluencer clone() { + public EmptyParticleInfluencer clone() { try { - return (ParticleInfluencer) super.clone(); + return (EmptyParticleInfluencer) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } @@ -101,6 +101,6 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { } } diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java index b2f81f9a8d..614285b38f 100644 --- a/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/NewtonianParticleInfluencer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,6 +54,8 @@ public class NewtonianParticleInfluencer extends DefaultParticleInfluencer { /** Emitters tangent rotation factor. */ protected float surfaceTangentRotation; + protected Matrix3f tempMat3 = new Matrix3f(); + /** * Constructor. Sets velocity variation to 0.0f. */ @@ -71,15 +73,15 @@ public void influenceParticle(Particle particle, EmitterShape emitterShape) { // calculating surface tangent (velocity contains the 'normal' value) temp.set(particle.velocity.z * surfaceTangentFactor, particle.velocity.y * surfaceTangentFactor, -particle.velocity.x * surfaceTangentFactor); if (surfaceTangentRotation != 0.0f) {// rotating the tangent - Matrix3f m = new Matrix3f(); - m.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity); - temp = m.multLocal(temp); + tempMat3.fromAngleNormalAxis(FastMath.PI * surfaceTangentRotation, particle.velocity); + temp = tempMat3.multLocal(temp); } // applying normal factor (this must be done first) particle.velocity.multLocal(normalVelocity); // adding tangent vector particle.velocity.addLocal(temp); } + particle.velocity.addLocal(initialVelocity); if (velocityVariation != 0.0f) { this.applyVelocityVariation(particle); } @@ -142,17 +144,6 @@ protected void applyVelocityVariation(Particle particle) { particle.velocity.addLocal(temp); } - @Override - public ParticleInfluencer clone() { - NewtonianParticleInfluencer result = new NewtonianParticleInfluencer(); - result.normalVelocity = normalVelocity; - result.initialVelocity = initialVelocity; - result.velocityVariation = velocityVariation; - result.surfaceTangentFactor = surfaceTangentFactor; - result.surfaceTangentRotation = surfaceTangentRotation; - return result; - } - @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java index 797e9c7231..9fc273a121 100644 --- a/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/ParticleInfluencer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,14 +42,14 @@ * An interface that defines the methods to affect initial velocity of the particles. * @author Marcin Roguski (Kaelthas) */ -public interface ParticleInfluencer extends Savable, Cloneable, JmeCloneable { +public interface ParticleInfluencer extends Savable, JmeCloneable { /** * This method influences the particle. * @param particle * particle to be influenced * @param emitterShape - * the shape of it emitter + * the shape of its emitter */ void influenceParticle(Particle particle, EmitterShape emitterShape); @@ -57,7 +57,7 @@ public interface ParticleInfluencer extends Savable, Cloneable, JmeCloneable { * This method clones the influencer instance. * @return cloned instance */ - public ParticleInfluencer clone(); + ParticleInfluencer clone(); /** * @param initialVelocity diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java index 567763c9fa..6f62832dd1 100644 --- a/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/RadialParticleInfluencer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -82,7 +82,8 @@ public Vector3f getOrigin() { /** * the origin used for computing the radial velocity direction - * @param origin + * + * @param origin the desired origin location (alias created) */ public void setOrigin(Vector3f origin) { this.origin = origin; @@ -98,7 +99,7 @@ public float getRadialVelocity() { /** * the radial velocity - * @param radialVelocity + * @param radialVelocity the desired speed */ public void setRadialVelocity(float radialVelocity) { this.radialVelocity = radialVelocity; @@ -114,7 +115,7 @@ public boolean isHorizontal() { /** * nullify y component of particle velocity to make the effect expand only on x and z axis - * @param horizontal + * @param horizontal true to zero the Y component, false to preserve it */ public void setHorizontal(boolean horizontal) { this.horizontal = horizontal; @@ -124,14 +125,13 @@ public void setHorizontal(boolean horizontal) { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); // Change in behavior: the old origin was not cloned -pspeed this.origin = cloner.clone(origin); } - @Override public void write(JmeExporter ex) throws IOException { super.write(ex); diff --git a/jme3-core/src/main/java/com/jme3/effect/influencers/package-info.java b/jme3-core/src/main/java/com/jme3/effect/influencers/package-info.java new file mode 100644 index 0000000000..a312336e71 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/influencers/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * particle influencers for use in special effects + */ +package com.jme3.effect.influencers; diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java index be9d0257da..696ca5a83f 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterBoxShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,13 +40,35 @@ import com.jme3.util.clone.Cloner; import java.io.IOException; +/** + * An {@link EmitterShape} that emits particles randomly within the bounds of an axis-aligned box. + * The box is defined by a minimum corner and a length vector. + */ public class EmitterBoxShape implements EmitterShape { - private Vector3f min, len; + /** + * The minimum corner of the box. + */ + private Vector3f min; + /** + * The length of the box along each axis. The x, y, and z components of this + * vector represent the width, height, and depth of the box, respectively. + */ + private Vector3f len; + /** + * For serialization only. Do not use. + */ public EmitterBoxShape() { } + /** + * Constructs an {@code EmitterBoxShape} defined by a minimum and maximum corner. + * + * @param min The minimum corner of the box. + * @param max The maximum corner of the box. + * @throws IllegalArgumentException If either {@code min} or {@code max} is null. + */ public EmitterBoxShape(Vector3f min, Vector3f max) { if (min == null || max == null) { throw new IllegalArgumentException("min or max cannot be null"); @@ -57,6 +79,11 @@ public EmitterBoxShape(Vector3f min, Vector3f max) { this.len.set(max).subtractLocal(min); } + /** + * Generates a random point within the bounds of the box. + * + * @param store The {@link Vector3f} to store the generated point in. + */ @Override public void getRandomPoint(Vector3f store) { store.x = min.x + len.x * FastMath.nextRandomFloat(); @@ -65,10 +92,11 @@ public void getRandomPoint(Vector3f store) { } /** - * This method fills the point with data. - * It does not fill the normal. - * @param store the variable to store the point data - * @param normal not used in this class + * For a box shape, the normal is not well-defined for points within the volume. + * This implementation simply calls {@link #getRandomPoint(Vector3f)} and does not modify the provided normal. + * + * @param store The {@link Vector3f} to store the generated point in. + * @param normal The {@link Vector3f} to store the generated normal in (unused). */ @Override public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { @@ -103,23 +131,45 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.min = cloner.clone(min); this.len = cloner.clone(len); } + /** + * Returns the minimum corner of the emitting box. + * + * @return The minimum corner. + */ public Vector3f getMin() { return min; } + /** + * Sets the minimum corner of the emitting box. + * + * @param min The new minimum corner. + */ public void setMin(Vector3f min) { this.min = min; } + /** + * Returns the length vector of the emitting box. This vector represents the + * extent of the box along each axis (length = max - min). + * + * @return The length vector. + */ public Vector3f getLen() { return len; } + /** + * Sets the length vector of the emitting box. This vector should represent + * the extent of the box along each axis (length = max - min). + * + * @param len The new length vector. + */ public void setLen(Vector3f len) { this.len = len; } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java index a18c7e1a8c..e7ccd18f33 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshConvexHullShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,34 +60,34 @@ public EmitterMeshConvexHullShape(List meshes) { } /** - * This method fills the point with coordinates of randomly selected point inside a convex hull - * of randomly selected mesh. + * Randomly selects a point inside the convex hull + * of a randomly selected mesh. + * * @param store - * the variable to store with coordinates of randomly selected selected point inside a convex hull - * of randomly selected mesh + * storage for the coordinates of the selected point */ @Override public void getRandomPoint(Vector3f store) { super.getRandomPoint(store); - // now move the point from the meshe's face towards the center of the mesh + // now move the point from the mesh's face toward the center of the mesh // the center is in (0, 0, 0) in the local coordinates store.multLocal(FastMath.nextRandomFloat()); } /** - * This method fills the point with coordinates of randomly selected point inside a convex hull - * of randomly selected mesh. - * The normal param is not used. + * Randomly selects a point inside the convex hull + * of a randomly selected mesh. + * The {@code normal} argument is not used. + * * @param store - * the variable to store with coordinates of randomly selected selected point inside a convex hull - * of randomly selected mesh + * storage for the coordinates of the selected point * @param normal * not used in this class */ @Override public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { super.getRandomPointAndNormal(store, normal); - // now move the point from the meshe's face towards the center of the mesh + // now move the point from the mesh's face toward the center of the mesh // the center is in (0, 0, 0) in the local coordinates store.multLocal(FastMath.nextRandomFloat()); } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java index 77bc2852f6..cdaefa40de 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshFaceShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.BufferUtils; + import java.util.ArrayList; import java.util.List; @@ -52,77 +53,131 @@ public EmitterMeshFaceShape() { } /** - * Constructor. It stores a copy of vertex list of all meshes. - * @param meshes - * a list of meshes that will form the emitter's shape + * Constructor. Initializes the emitter shape with a list of meshes. + * The vertices and normals for all triangles of these meshes are + * extracted and stored internally. + * + * @param meshes a list of {@link Mesh} objects that will define the + * shape from which particles are emitted. */ public EmitterMeshFaceShape(List meshes) { super(meshes); } + /** + * Sets the meshes for this emitter shape. This method extracts all + * triangle vertices and computes their normals, storing them internally + * for subsequent particle emission. + * + * @param meshes a list of {@link Mesh} objects to set as the emitter's shape. + */ @Override public void setMeshes(List meshes) { this.vertices = new ArrayList>(meshes.size()); this.normals = new ArrayList>(meshes.size()); + for (Mesh mesh : meshes) { Vector3f[] vertexTable = BufferUtils.getVector3Array(mesh.getFloatBuffer(Type.Position)); int[] indices = new int[3]; - List vertices = new ArrayList(mesh.getTriangleCount() * 3); - List normals = new ArrayList(mesh.getTriangleCount()); + List meshVertices = new ArrayList<>(mesh.getTriangleCount() * 3); + List meshNormals = new ArrayList<>(mesh.getTriangleCount()); + for (int i = 0; i < mesh.getTriangleCount(); ++i) { mesh.getTriangle(i, indices); - vertices.add(vertexTable[indices[0]]); - vertices.add(vertexTable[indices[1]]); - vertices.add(vertexTable[indices[2]]); - normals.add(FastMath.computeNormal(vertexTable[indices[0]], vertexTable[indices[1]], vertexTable[indices[2]])); + + Vector3f v1 = vertexTable[indices[0]]; + Vector3f v2 = vertexTable[indices[1]]; + Vector3f v3 = vertexTable[indices[2]]; + + // Add all three vertices of the triangle + meshVertices.add(v1); + meshVertices.add(v2); + meshVertices.add(v3); + + // Compute and add the normal for the current triangle face + meshNormals.add(FastMath.computeNormal(v1, v2, v3)); } - this.vertices.add(vertices); - this.normals.add(normals); + this.vertices.add(meshVertices); + this.normals.add(meshNormals); } } /** - * This method fills the point with coordinates of randomly selected point on a random face. - * @param store - * the variable to store with coordinates of randomly selected selected point on a random face + * Randomly selects a point on a random face of one of the stored meshes. + * The point is generated using barycentric coordinates to ensure uniform + * distribution within the selected triangle. + * + * @param store a {@link Vector3f} object where the coordinates of the + * selected point will be stored. */ @Override public void getRandomPoint(Vector3f store) { int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + List currVertices = vertices.get(meshIndex); + int numVertices = currVertices.size(); + // the index of the first vertex of a face (must be dividable by 3) - int vertIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1) * 3; - // put the point somewhere between the first and the second vertex of a face - float moveFactor = FastMath.nextRandomFloat(); - store.set(Vector3f.ZERO); - store.addLocal(vertices.get(meshIndex).get(vertIndex)); - store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); - // move the result towards the last face vertex - moveFactor = FastMath.nextRandomFloat(); - store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + int faceIndex = FastMath.nextRandomInt(0, numVertices / 3 - 1); + int vertIndex = faceIndex * 3; + + // Generate the random point on the triangle + generateRandomPointOnTriangle(currVertices, vertIndex, store); } /** - * This method fills the point with coordinates of randomly selected point on a random face. - * The normal param is filled with selected face's normal. - * @param store - * the variable to store with coordinates of randomly selected selected point on a random face - * @param normal - * filled with selected face's normal + * Randomly selects a point on a random face of one of the stored meshes, + * and also sets the normal of that selected face. + * The point is generated using barycentric coordinates for uniform distribution. + * + * @param store a {@link Vector3f} object where the coordinates of the + * selected point will be stored. + * @param normal a {@link Vector3f} object where the normal of the + * selected face will be stored. */ @Override public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { int meshIndex = FastMath.nextRandomInt(0, vertices.size() - 1); + List currVertices = vertices.get(meshIndex); + int numVertices = currVertices.size(); + // the index of the first vertex of a face (must be dividable by 3) - int faceIndex = FastMath.nextRandomInt(0, vertices.get(meshIndex).size() / 3 - 1); + int faceIndex = FastMath.nextRandomInt(0, numVertices / 3 - 1); int vertIndex = faceIndex * 3; - // put the point somewhere between the first and the second vertex of a face - float moveFactor = FastMath.nextRandomFloat(); - store.set(Vector3f.ZERO); - store.addLocal(vertices.get(meshIndex).get(vertIndex)); - store.addLocal((vertices.get(meshIndex).get(vertIndex + 1).x - vertices.get(meshIndex).get(vertIndex).x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).y - vertices.get(meshIndex).get(vertIndex).y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 1).z - vertices.get(meshIndex).get(vertIndex).z) * moveFactor); - // move the result towards the last face vertex - moveFactor = FastMath.nextRandomFloat(); - store.addLocal((vertices.get(meshIndex).get(vertIndex + 2).x - store.x) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).y - store.y) * moveFactor, (vertices.get(meshIndex).get(vertIndex + 2).z - store.z) * moveFactor); + + // Generate the random point on the triangle + generateRandomPointOnTriangle(currVertices, vertIndex, store); + // Set the normal from the pre-computed normals list for the selected face normal.set(normals.get(meshIndex).get(faceIndex)); } + + /** + * Internal method to generate a random point within a specific triangle + * using barycentric coordinates. + * + * @param currVertices The list of vertices for the current mesh. + * @param vertIndex The starting index of the triangle's first vertex + * within the {@code currVertices} list. + * @param store A {@link Vector3f} object where the calculated point will be stored. + */ + private void generateRandomPointOnTriangle(List currVertices, int vertIndex, Vector3f store) { + + Vector3f v1 = currVertices.get(vertIndex); + Vector3f v2 = currVertices.get(vertIndex + 1); + Vector3f v3 = currVertices.get(vertIndex + 2); + + // Generate random barycentric coordinates + float u = FastMath.nextRandomFloat(); + float v = FastMath.nextRandomFloat(); + + if ((u + v) > 1) { + u = 1 - u; + v = 1 - v; + } + + // P = v1 + u * (v2 - v1) + v * (v3 - v1) + store.x = v1.x + u * (v2.x - v1.x) + v * (v3.x - v1.x); + store.y = v1.y + u * (v2.y - v1.y) + v * (v3.y - v1.y); + store.z = v1.z + u * (v2.z - v1.z) + v * (v3.z - v1.z); + } + } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java index 5d209b5583..cadb07ca48 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterMeshVertexShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -78,7 +78,7 @@ public EmitterMeshVertexShape(List meshes) { * a list of meshes that will form the emitter's shape */ public void setMeshes(List meshes) { - Map vertToNormalMap = new HashMap(); + Map vertToNormalMap = new HashMap<>(); this.vertices = new ArrayList>(meshes.size()); this.normals = new ArrayList>(meshes.size()); @@ -100,8 +100,8 @@ public void setMeshes(List meshes) { } // adding data to vertices and normals - List vertices = new ArrayList(vertToNormalMap.size()); - List normals = new ArrayList(vertToNormalMap.size()); + List vertices = new ArrayList<>(vertToNormalMap.size()); + List normals = new ArrayList<>(vertToNormalMap.size()); for (Entry entry : vertToNormalMap.entrySet()) { vertices.add(entry.getKey()); normals.add(entry.getValue().normalizeLocal()); @@ -146,7 +146,7 @@ public EmitterShape deepClone() { if (this.vertices != null) { clone.vertices = new ArrayList>(vertices.size()); for (List list : vertices) { - List vectorList = new ArrayList(list.size()); + List vectorList = new ArrayList<>(list.size()); for (Vector3f vector : list) { vectorList.add(vector.clone()); } @@ -156,7 +156,7 @@ public EmitterShape deepClone() { if (this.normals != null) { clone.normals = new ArrayList>(normals.size()); for (List list : normals) { - List vectorList = new ArrayList(list.size()); + List vectorList = new ArrayList<>(list.size()); for (Vector3f vector : list) { vectorList.add(vector.clone()); } @@ -185,7 +185,7 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.vertices = cloner.clone(vertices); this.normals = cloner.clone(normals); } @@ -204,7 +204,7 @@ public void read(JmeImporter im) throws IOException { this.vertices = ic.readSavableArrayList("vertices", null); List> tmpNormals = ic.readSavableArrayList("normals", null); - if (tmpNormals != null){ + if (tmpNormals != null) { this.normals = tmpNormals; } } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java index fb89be826b..d4b3f44d70 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterPointShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.effect.shapes; +import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; @@ -38,13 +39,27 @@ import com.jme3.util.clone.Cloner; import java.io.IOException; +/** + * An {@link EmitterShape} that emits particles from a single point in space. + */ public class EmitterPointShape implements EmitterShape { + /** + * The point in space from which particles are emitted. + */ private Vector3f point; + /** + * For serialization only. Do not use. + */ public EmitterPointShape() { } + /** + * Constructs an {@code EmitterPointShape} with the given point. + * + * @param point The point from which particles are emitted. + */ public EmitterPointShape(Vector3f point) { this.point = point; } @@ -76,30 +91,47 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.point = cloner.clone(point); } + /** + * For a point shape, the generated point is + * always the shape's defined point. + * + * @param store The {@link Vector3f} to store the generated point in. + */ @Override public void getRandomPoint(Vector3f store) { store.set(point); } /** - * This method fills the point with data. - * It does not fill the normal. - * @param store the variable to store the point data - * @param normal not used in this class + * For a point shape, the generated point is always the shape's defined point. + * The normal is not defined for a point shape, so this method does not modify the normal parameter. + * + * @param store The {@link Vector3f} to store the generated point in. + * @param normal The {@link Vector3f} to store the generated normal in (unused). */ @Override public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { store.set(point); } + /** + * Returns the point from which particles are emitted. + * + * @return The point. + */ public Vector3f getPoint() { return point; } + /** + * Sets the point from which particles are emitted. + * + * @param point The new point. + */ public void setPoint(Vector3f point) { this.point = point; } @@ -112,6 +144,7 @@ public void write(JmeExporter ex) throws IOException { @Override public void read(JmeImporter im) throws IOException { - this.point = (Vector3f) im.getCapsule(this).readSavable("point", null); + InputCapsule ic = im.getCapsule(this); + this.point = (Vector3f) ic.readSavable("point", null); } } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java index f247412d04..776a4e33ee 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,14 +39,14 @@ * This interface declares methods used by all shapes that represent particle emitters. * @author Kirill */ -public interface EmitterShape extends Savable, Cloneable, JmeCloneable { +public interface EmitterShape extends Savable, JmeCloneable { /** * This method fills in the initial position of the particle. * @param store * store variable for initial position */ - public void getRandomPoint(Vector3f store); + void getRandomPoint(Vector3f store); /** * This method fills in the initial position of the particle and its normal vector. @@ -55,11 +55,11 @@ public interface EmitterShape extends Savable, Cloneable, JmeCloneable { * @param normal * store variable for initial normal */ - public void getRandomPointAndNormal(Vector3f store, Vector3f normal); + void getRandomPointAndNormal(Vector3f store, Vector3f normal); /** * This method creates a deep clone of the current instance of the emitter shape. * @return deep clone of the current instance of the emitter shape */ - public EmitterShape deepClone(); + EmitterShape deepClone(); } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java index 015be8b835..30ec7357d4 100644 --- a/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/EmitterSphereShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,19 +40,38 @@ import com.jme3.util.clone.Cloner; import java.io.IOException; +/** + * An {@link EmitterShape} that emits particles randomly from within the volume of a sphere. + * The sphere is defined by a center point and a radius. + */ public class EmitterSphereShape implements EmitterShape { + /** + * The center point of the sphere. + */ private Vector3f center; + /** + * The radius of the sphere. + */ private float radius; + /** + * For serialization only. Do not use. + */ public EmitterSphereShape() { } + /** + * Constructs an {@code EmitterSphereShape} with the given center and radius. + * + * @param center The center point of the sphere. + * @param radius The radius of the sphere. + * @throws IllegalArgumentException If {@code center} is null, or if {@code radius} is not greater than 0. + */ public EmitterSphereShape(Vector3f center, float radius) { if (center == null) { throw new IllegalArgumentException("center cannot be null"); } - if (radius <= 0) { throw new IllegalArgumentException("Radius must be greater than 0"); } @@ -88,10 +107,15 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.center = cloner.clone(center); } + /** + * Generates a random point within the volume of the sphere. + * + * @param store The {@link Vector3f} to store the generated point in. + */ @Override public void getRandomPoint(Vector3f store) { do { @@ -103,23 +127,51 @@ public void getRandomPoint(Vector3f store) { store.addLocal(center); } + /** + * For a sphere shape, the normal is not well-defined for points within the volume. + * This implementation simply calls {@link #getRandomPoint(Vector3f)} and does not modify the provided normal. + * + * @param store The {@link Vector3f} to store the generated point in. + * @param normal The {@link Vector3f} to store the generated normal in (unused). + */ @Override public void getRandomPointAndNormal(Vector3f store, Vector3f normal) { this.getRandomPoint(store); + normal.set(store).subtractLocal(center).normalizeLocal(); } + /** + * Returns the center point of the sphere. + * + * @return The center point. + */ public Vector3f getCenter() { return center; } + /** + * Sets the center point of the sphere. + * + * @param center The new center point. + */ public void setCenter(Vector3f center) { this.center = center; } + /** + * Returns the radius of the sphere. + * + * @return The radius. + */ public float getRadius() { return radius; } + /** + * Sets the radius of the sphere. + * + * @param radius The new radius. + */ public void setRadius(float radius) { this.radius = radius; } diff --git a/jme3-core/src/main/java/com/jme3/effect/shapes/package-info.java b/jme3-core/src/main/java/com/jme3/effect/shapes/package-info.java new file mode 100644 index 0000000000..99d30d6ab5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/effect/shapes/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * emitter shapes for use in particle effects + */ +package com.jme3.effect.shapes; diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java index 025d9a7b46..2f8ee78d9b 100644 --- a/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentCamera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -111,7 +111,7 @@ public class EnvironmentCamera extends BaseAppState { */ protected int size = 256; - private final List jobs = new ArrayList(); + private final List jobs = new ArrayList<>(); /** * Creates an EnvironmentCamera with a size of 256 @@ -172,28 +172,48 @@ public Void call() throws Exception { @Override public void render(final RenderManager renderManager) { + if (isBusy()) { + final SnapshotJob job = jobs.get(0); + + for (int i = 0; i < 6; i++) { + viewports[i].clearScenes(); + viewports[i].attachScene(job.scene); + renderManager.renderViewPort(viewports[i], 0.16f); + buffers[i] = BufferUtils.createByteBuffer( + size * size * imageFormat.getBitsPerPixel() / 8); + renderManager.getRenderer().readFrameBufferWithFormat( + framebuffers[i], buffers[i], imageFormat); + images[i] = new Image(imageFormat, size, size, buffers[i], + ColorSpace.Linear); + MipMapGenerator.generateMipMaps(images[i]); + } - if (jobs.isEmpty()) { - return; + final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], + images[1], images[2], images[3], images[4], images[5], + imageFormat); + debugEnv = map; + job.callback.done(map); + map.getImage().dispose(); + jobs.remove(0); } + } - final SnapshotJob job = jobs.get(0); - - for (int i = 0; i < 6; i++) { - viewports[i].clearScenes(); - viewports[i].attachScene(job.scene); - renderManager.renderViewPort(viewports[i], 0.16f); - buffers[i] = BufferUtils.createByteBuffer(size * size * imageFormat.getBitsPerPixel() / 8); - renderManager.getRenderer().readFrameBufferWithFormat(framebuffers[i], buffers[i], imageFormat); - images[i] = new Image(imageFormat, size, size, buffers[i], ColorSpace.Linear); - MipMapGenerator.generateMipMaps(images[i]); + /** + * Alter the background color of an initialized EnvironmentCamera. + * + * @param bgColor the desired color (not null, unaffected, default is the + * background color of the application's default viewport) + */ + public void setBackGroundColor(ColorRGBA bgColor) { + if (!isInitialized()) { + throw new IllegalStateException( + "The EnvironmentCamera is uninitialized."); } - final TextureCubeMap map = EnvMapUtils.makeCubeMap(images[0], images[1], images[2], images[3], images[4], images[5], imageFormat); - debugEnv = map; - job.callback.done(map); - map.getImage().dispose(); - jobs.remove(0); + backGroundColor.set(bgColor); + for (int i = 0; i < 6; ++i) { + viewports[i].setBackgroundColor(bgColor); + } } /** @@ -249,9 +269,29 @@ public void setPosition(final Vector3f position) { } } + /** + * Returns an array of the 6 ViewPorts used to capture the snapshot. + * Note that this will be null until after initialize() is called. + * @return array of ViewPorts + */ + public ViewPort[] getViewPorts() { + return viewports; + } + + /** + * Test whether this EnvironmentCamera is busy. Avoid reconfiguring while + * busy! + * + * @return true if busy, otherwise false + */ + public boolean isBusy() { + boolean result = !jobs.isEmpty(); + return result; + } + @Override protected void initialize(Application app) { - this.backGroundColor = app.getViewPort().getBackgroundColor(); + this.backGroundColor = app.getViewPort().getBackgroundColor().clone(); final Camera[] cameras = new Camera[6]; final Texture2D[] textures = new Texture2D[6]; @@ -270,7 +310,6 @@ protected void initialize(Application app) { } } - @Override protected void cleanup(Application app) { this.backGroundColor = null; @@ -280,7 +319,7 @@ protected void cleanup(Application app) { } for (final Image image : images) { - if( image != null){ + if (image != null) { image.dispose(); } } @@ -313,7 +352,8 @@ protected void onDisable() { * @param axisZ tha z axis * @return a new instance */ - protected Camera createOffCamera(final int mapSize, final Vector3f worldPos, final Vector3f axisX, final Vector3f axisY, final Vector3f axisZ) { + protected Camera createOffCamera(final int mapSize, final Vector3f worldPos, + final Vector3f axisX, final Vector3f axisY, final Vector3f axisZ) { final Camera offCamera = new Camera(mapSize, mapSize); offCamera.setLocation(worldPos); offCamera.setAxes(axisX, axisY, axisZ); @@ -323,10 +363,10 @@ protected Camera createOffCamera(final int mapSize, final Vector3f worldPos, fin } /** - * creates an offsceen VP + * creates an off-screen VP * - * @param name - * @param offCamera + * @param name the desired name for the offscreen viewport + * @param offCamera the Camera to be used (alias created) * @return a new instance */ protected ViewPort createOffViewPort(final String name, final Camera offCamera) { @@ -339,8 +379,8 @@ protected ViewPort createOffViewPort(final String name, final Camera offCamera) /** * create an offscreen frame buffer. * - * @param mapSize - * @param offView + * @param mapSize the desired size (pixels per side) + * @param offView the off-screen viewport to be used (alias created) * @return a new instance */ protected FrameBuffer createOffScreenFrameBuffer(int mapSize, ViewPort offView) { @@ -359,6 +399,7 @@ protected class SnapshotJob { JobProgressListener callback; Spatial scene; + @SuppressWarnings("unchecked") public SnapshotJob(JobProgressListener callback, Spatial scene) { this.callback = callback; this.scene = scene; diff --git a/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java new file mode 100644 index 0000000000..6f07fd1c1c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/EnvironmentProbeControl.java @@ -0,0 +1,352 @@ +/* + * 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.environment; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; +import com.jme3.asset.AssetManager; +import com.jme3.environment.baker.IBLGLEnvBakerLight; +import com.jme3.environment.baker.IBLHybridEnvBakerLight; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.LightProbe; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.Control; +import com.jme3.texture.Image.Format; + +/** + * A control that automatically handles environment bake and rebake including + * only tagged spatials. + * + * Simple usage example: + * 1. Load a scene + * Node scene=(Node)assetManager.loadModel("Scenes/MyScene.j3o"); + * 2. Add one or more EnvironmentProbeControl to the root of the scene + * EnvironmentProbeControl ec1=new EnvironmentProbeControl(assetManager, 512); + * // EnvironmentProbeControl ec2=new EnvironmentProbeControl(assetManager, 512); + * 2b. (optional) Set the position of the probes + * ec1.setPosition(new Vector3f(0,0,0)); + * // ec2.setPosition(new Vector3f(0,0,10)); + * 3. Tag the spatials that are part of the environment + * scene.deepFirstTraversal(s->{ + * if(s.getUserData("isEnvNode")!=null){ + * EnvironmentProbeControl.tagGlobal(s); + * // or ec1.tag(s); + * // ec2.tag(s); + * } + * }); + * + * + * @author Riccardo Balbo + */ +public class EnvironmentProbeControl extends LightProbe implements Control { + private static AtomicInteger instanceCounter = new AtomicInteger(0); + + private AssetManager assetManager; + private boolean bakeNeeded = true; + private int envMapSize = 256; + private Spatial spatial; + private boolean requiredSavableResults = false; + private float frustumNear = 0.001f, frustumFar = 1000f; + private String uuid = "none"; + private boolean enabled = true; + + private Predicate filter = (s) -> { + return s.getUserData("tags.env") != null || s.getUserData("tags.env.env" + uuid) != null; + }; + + protected EnvironmentProbeControl() { + super(); + uuid = System.currentTimeMillis() + "_" + instanceCounter.getAndIncrement(); + this.setAreaType(AreaType.Spherical); + this.getArea().setRadius(Float.MAX_VALUE); + } + + /** + * Creates a new environment probe control. + * + * @param assetManager + * the asset manager used to load the shaders needed for the + * baking + * @param size + * the size of side of the resulting cube map (eg. 1024) + */ + public EnvironmentProbeControl(AssetManager assetManager, int size) { + this(); + this.envMapSize = size; + this.assetManager = assetManager; + } + + /** + * Tags the specified spatial as part of the environment for this EnvironmentProbeControl. + * Only tagged spatials will be rendered in the environment map. + * + * @param s + * the spatial + */ + public void tag(Spatial s) { + if (s instanceof Node) { + Node n = (Node) s; + for (Spatial sx : n.getChildren()) { + tag(sx); + } + } else if (s instanceof Geometry) { + s.setUserData("tags.env.env" + uuid, true); + } + } + + /** + * Untags the specified spatial as part of the environment for this + * EnvironmentProbeControl. + * + * @param s + * the spatial + */ + public void untag(Spatial s) { + if (s instanceof Node) { + Node n = (Node) s; + for (Spatial sx : n.getChildren()) { + untag(sx); + } + } else if (s instanceof Geometry) { + s.setUserData("tags.env.env" + uuid, null); + } + } + + /** + * Tags the specified spatial as part of the environment for every EnvironmentProbeControl. + * Only tagged spatials will be rendered in the environment map. + * + * @param s + * the spatial + */ + public static void tagGlobal(Spatial s) { + if (s instanceof Node) { + Node n = (Node) s; + for (Spatial sx : n.getChildren()) { + tagGlobal(sx); + } + } else if (s instanceof Geometry) { + s.setUserData("tags.env", true); + } + } + + /** + * Untags the specified spatial as part of the environment for every + * EnvironmentProbeControl. + * + * @param s the spatial + */ + public static void untagGlobal(Spatial s) { + if (s instanceof Node) { + Node n = (Node) s; + for (Spatial sx : n.getChildren()) { + untagGlobal(sx); + } + } else if (s instanceof Geometry) { + s.setUserData("tags.env", null); + } + } + + @Override + public Control cloneForSpatial(Spatial spatial) { + throw new UnsupportedOperationException(); + } + + /** + * Requests savable results from the baking process. This will make the + * baking process slower and more memory intensive but will allow to + * serialize the results with the control. + * + * @param v + * true to enable (default: false) + */ + public void setRequiredSavableResults(boolean v) { + requiredSavableResults = v; + } + + /** + * Returns true if savable results are required by this control. + * + * @return true if savable results are required. + */ + public boolean isRequiredSavableResults() { + return requiredSavableResults; + } + + @Override + public void setSpatial(Spatial spatial) { + if (this.spatial != null && spatial != null && spatial != this.spatial) { + throw new IllegalStateException("This control has already been added to a Spatial"); + } + this.spatial = spatial; + if (spatial != null) spatial.addLight(this); + } + + @Override + public void update(float tpf) { + + } + + @Override + public void render(RenderManager rm, ViewPort vp) { + if (!isEnabled()) return; + if (bakeNeeded) { + bakeNeeded = false; + rebakeNow(rm); + } + } + + /** + * Schedules a rebake of the environment map. + */ + public void rebake() { + bakeNeeded = true; + } + + /** + * Sets the minimum distance to render. + * + * @param frustumNear the minimum distance to render + */ + public void setFrustumNear(float frustumNear) { + this.frustumNear = frustumNear; + } + + /** + * Sets the maximum distance to render. + * + * @param frustumFar the maximum distance to render + */ + public void setFrustumFar(float frustumFar) { + this.frustumFar = frustumFar; + } + + /** + * Gets the minimum distance to render. + * + * @return frustum near + */ + public float getFrustumNear() { + return frustumNear; + } + + /** + * Gets the maximum distance to render. + * + * @return frustum far + */ + public float getFrustumFar() { + return frustumFar; + } + + /** + * Sets the asset manager used to load the shaders needed for the baking. + * + * @param assetManager the asset manager + */ + public void setAssetManager(AssetManager assetManager) { + this.assetManager = assetManager; + } + + void rebakeNow(RenderManager renderManager) { + IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(renderManager, assetManager, Format.RGB16F, + Format.Depth, + envMapSize, envMapSize); + + baker.setTexturePulling(isRequiredSavableResults()); + baker.bakeEnvironment(spatial, getPosition(), frustumNear, frustumFar, filter); + baker.bakeSpecularIBL(); + baker.bakeSphericalHarmonicsCoefficients(); + + setPrefilteredMap(baker.getSpecularIBL()); + + int[] mipSizes = getPrefilteredEnvMap().getImage().getMipMapSizes(); + setNbMipMaps(mipSizes != null ? mipSizes.length : 1); + + setShCoeffs(baker.getSphericalHarmonicsCoefficients()); + setPosition(Vector3f.ZERO); + setReady(true); + + baker.clean(); + } + + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + public boolean isEnabled() { + return enabled; + } + + public Spatial getSpatial() { + return spatial; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(enabled, "enabled", true); + oc.write(spatial, "spatial", null); + oc.write(envMapSize, "size", 256); + oc.write(requiredSavableResults, "requiredSavableResults", false); + oc.write(bakeNeeded, "bakeNeeded", true); + oc.write(frustumFar, "frustumFar", 1000f); + oc.write(frustumNear, "frustumNear", 0.001f); + oc.write(uuid, "envProbeControlUUID", "none"); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + enabled = ic.readBoolean("enabled", true); + spatial = (Spatial) ic.readSavable("spatial", null); + envMapSize = ic.readInt("size", 256); + requiredSavableResults = ic.readBoolean("requiredSavableResults", false); + bakeNeeded = ic.readBoolean("bakeNeeded", true); + assetManager = im.getAssetManager(); + frustumFar = ic.readFloat("frustumFar", 1000f); + frustumNear = ic.readFloat("frustumNear", 0.001f); + uuid = ic.readString("envProbeControlUUID", "none"); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java new file mode 100644 index 0000000000..12ba5e99c1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/FastLightProbeFactory.java @@ -0,0 +1,124 @@ +/* + * 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.environment; + +import com.jme3.asset.AssetManager; +import com.jme3.environment.baker.IBLGLEnvBakerLight; +import com.jme3.environment.baker.IBLHybridEnvBakerLight; +import com.jme3.environment.util.EnvMapUtils; +import com.jme3.light.LightProbe; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.texture.Image.Format; + +/** + * A faster LightProbeFactory that uses GPU accelerated algorithms. + * This is the GPU version of @{link LightProbeFactory} and should be generally preferred. + * + * For common use cases where the probe is baking the scene or part of the scene around it, it + * is advised to use the @{link EnvironmentProbeControl} instead since it does automatically most of the + * boilerplate work. + * + * + * @author Riccardo Balbo + */ +public class FastLightProbeFactory { + + /** + * Creates a LightProbe with the given EnvironmentCamera in the given scene. + * + * @param rm + * The RenderManager + * @param am + * The AssetManager + * @param size + * The size of the probe + * @param pos + * The position of the probe + * @param frustumNear + * The near frustum of the probe + * @param frustumFar + * The far frustum of the probe + * @param scene + * The scene to bake + * @return The baked LightProbe + */ + public static LightProbe makeProbe(RenderManager rm, AssetManager am, int size, Vector3f pos, float frustumNear, float frustumFar, Spatial scene) { + IBLHybridEnvBakerLight baker = new IBLGLEnvBakerLight(rm, am, Format.RGB16F, Format.Depth, size, + size); + + baker.setTexturePulling(true); + baker.bakeEnvironment(scene, pos, frustumNear, frustumFar, null); + baker.bakeSpecularIBL(); + baker.bakeSphericalHarmonicsCoefficients(); + + LightProbe probe = new LightProbe(); + + probe.setPosition(pos); + probe.setPrefilteredMap(baker.getSpecularIBL()); + + int[] mipSizes = probe.getPrefilteredEnvMap().getImage().getMipMapSizes(); + probe.setNbMipMaps(mipSizes != null ? mipSizes.length : 1); + + probe.setShCoeffs(baker.getSphericalHarmonicsCoefficients()); + probe.setReady(true); + + baker.clean(); + + return probe; + + } + + /** + * For debuging purposes only Will return a Node meant to be added to a GUI + * presenting the 2 cube maps in a cross pattern with all the mip maps. + * + * @param manager + * the asset manager + * @return a debug node + */ + public static Node getDebugGui(AssetManager manager, LightProbe probe) { + if (!probe.isReady()) { + throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()"); + } + + Node debugNode = new Node("debug gui probe"); + Node debugPfemCm = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(probe.getPrefilteredEnvMap(), manager); + debugNode.attachChild(debugPfemCm); + debugPfemCm.setLocalTranslation(520, 0, 0); + + return debugNode; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java index 537c94cc8a..7becab82e8 100644 --- a/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java +++ b/jme3-core/src/main/java/com/jme3/environment/LightProbeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,9 +43,9 @@ import java.util.concurrent.ScheduledThreadPoolExecutor; /** - * This Factory allows to create LightProbes within a scene given an EnvironmentCamera. + * Creates LightProbes within a scene, given an EnvironmentCamera. * - * Since the process can be long, you can provide a JobProgressListener that + * Since this process can take a long time, you can provide a JobProgressListener that * will be notified of the ongoing generation process when calling the makeProbe method. * * The process is as follows: @@ -62,16 +62,22 @@ * * This class is entirely thread safe and can be called from any thread. * - * Note that in case you are using a {@link JobProgressListener} all the its - * method will be called inside and app.enqueue callable. + * Note that in case you are using a {@link JobProgressListener}, all its + * methods will be called inside an app.enqueue callable. * This means that it's completely safe to modify the scenegraph within the - * Listener method, but also means that the even will be delayed until next update loop. + * Listener method, but also means that the event will be delayed until next update loop. * * @see EnvironmentCamera * @author bouquet */ public class LightProbeFactory { + /** + * A private constructor to inhibit instantiation of this class. + */ + private LightProbeFactory() { + } + /** * Creates a LightProbe with the giver EnvironmentCamera in the given scene. * @@ -208,15 +214,17 @@ private static void generatePbrMaps(TextureCubeMap envMap, final LightProbe prob } /** - * For debuging porpose only + * For debugging purposes only. * Will return a Node meant to be added to a GUI presenting the 2 cube maps in a cross pattern with all the mip maps. * * @param manager the asset manager + * @param probe the LightProbe to be debugged (not null) * @return a debug node */ public static Node getDebugGui(AssetManager manager, LightProbe probe) { if (!probe.isReady()) { - throw new UnsupportedOperationException("This EnvProbe is not ready yet, try to test isReady()"); + throw new IllegalStateException( + "The LightProbe is not ready yet, please test isReady()."); } Node debugNode = new Node("debug gui probe"); @@ -252,8 +260,8 @@ boolean isDone() { float getProgress() { float mean = 0; - for (double progres : progress) { - mean += progres; + for (double faceProgress : progress) { + mean += faceProgress; } return mean / 7f; } diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java new file mode 100644 index 0000000000..65ee9805f1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/EnvBaker.java @@ -0,0 +1,89 @@ +/* + * 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.environment.baker; + +import java.util.function.Predicate; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.texture.TextureCubeMap; + +/** + * An environment baker to bake a 3d environment into a cubemap + * + * @author Riccardo Balbo + */ +public interface EnvBaker { + /** + * Bakes the environment. + * + * @param scene + * The scene to bake + * @param position + * The position of the camera + * @param frustumNear + * The near frustum + * @param frustumFar + * The far frustum + * @param filter + * A filter to select which geometries to bake + */ + public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter); + + /** + * Gets the environment map. + * + * @return The environment map + */ + public TextureCubeMap getEnvMap(); + + /** + * Cleans the environment baker This method should be called when the baker + * is no longer needed It will clean up all the resources. + */ + public void clean(); + + /** + * Specifies whether textures should be pulled from the GPU. + * + * @param v + */ + public void setTexturePulling(boolean v); + + /** + * Gets if textures should be pulled from the GPU. + * + * @return + */ + public boolean isTexturePulling(); +} diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java new file mode 100644 index 0000000000..6831914945 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/GenericEnvBaker.java @@ -0,0 +1,293 @@ +/* + * 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.environment.baker; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.jme3.asset.AssetManager; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; + +/** + * Render the environment into a cubemap + * + * @author Riccardo Balbo + */ +public abstract class GenericEnvBaker implements EnvBaker { + private static final Logger LOG = Logger.getLogger(GenericEnvBaker.class.getName()); + + protected static Vector3f[] axisX = new Vector3f[6]; + protected static Vector3f[] axisY = new Vector3f[6]; + protected static Vector3f[] axisZ = new Vector3f[6]; + static { + // PositiveX axis(left, up, direction) + axisX[0] = Vector3f.UNIT_Z.mult(1.0F); + axisY[0] = Vector3f.UNIT_Y.mult(-1.0F); + axisZ[0] = Vector3f.UNIT_X.mult(1.0F); + // NegativeX + axisX[1] = Vector3f.UNIT_Z.mult(-1.0F); + axisY[1] = Vector3f.UNIT_Y.mult(-1.0F); + axisZ[1] = Vector3f.UNIT_X.mult(-1.0F); + // PositiveY + axisX[2] = Vector3f.UNIT_X.mult(-1.0F); + axisY[2] = Vector3f.UNIT_Z.mult(1.0F); + axisZ[2] = Vector3f.UNIT_Y.mult(1.0F); + // NegativeY + axisX[3] = Vector3f.UNIT_X.mult(-1.0F); + axisY[3] = Vector3f.UNIT_Z.mult(-1.0F); + axisZ[3] = Vector3f.UNIT_Y.mult(-1.0F); + // PositiveZ + axisX[4] = Vector3f.UNIT_X.mult(-1.0F); + axisY[4] = Vector3f.UNIT_Y.mult(-1.0F); + axisZ[4] = Vector3f.UNIT_Z; + // NegativeZ + axisX[5] = Vector3f.UNIT_X.mult(1.0F); + axisY[5] = Vector3f.UNIT_Y.mult(-1.0F); + axisZ[5] = Vector3f.UNIT_Z.mult(-1.0F); + } + + protected TextureCubeMap envMap; + protected Format depthFormat; + + protected final RenderManager renderManager; + protected final AssetManager assetManager; + protected final Camera cam; + protected boolean texturePulling = false; + protected List bos = new ArrayList<>(); + + protected GenericEnvBaker(RenderManager rm, AssetManager am, Format colorFormat, Format depthFormat, int env_size) { + this.depthFormat = depthFormat; + + renderManager = rm; + assetManager = am; + + cam = new Camera(128, 128); + + envMap = new TextureCubeMap(env_size, env_size, colorFormat); + envMap.setMagFilter(MagFilter.Bilinear); + envMap.setMinFilter(MinFilter.BilinearNoMipMaps); + envMap.setWrap(WrapMode.EdgeClamp); + envMap.getImage().setColorSpace(ColorSpace.Linear); + } + + @Override + public void setTexturePulling(boolean v) { + texturePulling = v; + } + + @Override + public boolean isTexturePulling() { + return texturePulling; + } + + public TextureCubeMap getEnvMap() { + return envMap; + } + + /** + * Updates the internal camera to face the given cubemap face + * and return it. + * + * @param faceId + * the id of the face (0-5) + * @param w + * width of the camera + * @param h + * height of the camera + * @param position + * position of the camera + * @param frustumNear + * near frustum + * @param frustumFar + * far frustum + * @return The updated camera + */ + protected Camera updateAndGetInternalCamera(int faceId, int w, int h, Vector3f position, float frustumNear, float frustumFar) { + cam.resize(w, h, false); + cam.setLocation(position); + cam.setFrustumPerspective(90.0F, 1F, frustumNear, frustumFar); + cam.setRotation(new Quaternion().fromAxes(axisX[faceId], axisY[faceId], axisZ[faceId])); + return cam; + } + + @Override + public void clean() { + + } + + @Override + public void bakeEnvironment(Spatial scene, Vector3f position, float frustumNear, float frustumFar, Predicate filter) { + FrameBuffer envbakers[] = new FrameBuffer[6]; + for (int i = 0; i < 6; i++) { + envbakers[i] = new FrameBuffer(envMap.getImage().getWidth(), envMap.getImage().getHeight(), 1); + envbakers[i].setDepthTarget(FrameBufferTarget.newTarget(depthFormat)); + envbakers[i].setSrgb(false); + envbakers[i].addColorTarget(FrameBufferTarget.newTarget(envMap).face(TextureCubeMap.Face.values()[i])); + } + + if (isTexturePulling()) { + startPulling(); + } + + for (int i = 0; i < 6; i++) { + FrameBuffer envbaker = envbakers[i]; + + ViewPort viewPort = new ViewPort("EnvBaker", updateAndGetInternalCamera(i, envbaker.getWidth(), envbaker.getHeight(), position, frustumNear, frustumFar)); + viewPort.setClearFlags(true, true, true); + viewPort.setBackgroundColor(ColorRGBA.Pink); + + viewPort.setOutputFrameBuffer(envbaker); + viewPort.clearScenes(); + viewPort.attachScene(scene); + + scene.updateLogicalState(0); + scene.updateGeometricState(); + + Predicate ofilter = renderManager.getRenderFilter(); + + renderManager.setRenderFilter(filter); + renderManager.renderViewPort(viewPort, 0.16f); + renderManager.setRenderFilter(ofilter); + + if (isTexturePulling()) { + pull(envbaker, envMap, i); + } + + } + + if (isTexturePulling()) { + endPulling(envMap); + } + + envMap.getImage().clearUpdateNeeded(); + + for (int i = 0; i < 6; i++) { + envbakers[i].dispose(); + } + } + + /** + * Starts pulling the data from the framebuffer into the texture. + */ + protected void startPulling() { + bos.clear(); + } + + /** + * Pulls the data from the framebuffer into the texture Nb. mipmaps must be + * pulled sequentially on the same faceId. + * + * @param fb + * the framebuffer to pull from + * @param env + * the texture to pull into + * @param faceId + * id of face if cubemap or 0 otherwise + * @return the ByteBuffer containing the pulled data + */ + protected ByteBuffer pull(FrameBuffer fb, Texture env, int faceId) { + + if (fb.getColorTarget().getFormat() != env.getImage().getFormat()) + throw new IllegalArgumentException("Format mismatch: " + fb.getColorTarget().getFormat() + "!=" + env.getImage().getFormat()); + + ByteBuffer face = BufferUtils.createByteBuffer(fb.getWidth() * fb.getHeight() * (fb.getColorTarget().getFormat().getBitsPerPixel() / 8)); + renderManager.getRenderer().readFrameBufferWithFormat(fb, face, fb.getColorTarget().getFormat()); + face.rewind(); + + while (bos.size() <= faceId) { + bos.add(null); + } + + ByteArrayOutputStream bo = bos.get(faceId); + if (bo == null) { + bos.set(faceId, bo = new ByteArrayOutputStream()); + } + try { + byte array[] = new byte[face.limit()]; + face.get(array); + bo.write(array); + } catch (Exception ex) { + LOG.log(Level.SEVERE, null, ex); + } + return face; + } + + /** + * Ends pulling the data into the texture + * + * @param tx + * the texture to pull into + */ + protected void endPulling(Texture tx) { + for (int i = 0; i < bos.size(); i++) { + ByteArrayOutputStream bo = bos.get(i); + if (bo != null) { + ByteBuffer faceMip = ByteBuffer.wrap(bo.toByteArray()); + tx.getImage().setData(i, faceMip); + } else { + LOG.log(Level.SEVERE, "Missing face {0}. Pulling incomplete!", i); + } + } + bos.clear(); + tx.getImage().clearUpdateNeeded(); + } + + protected int limitMips(int nbMipMaps, int baseW, int baseH, RenderManager rm) { + if (nbMipMaps > 6) { + nbMipMaps = 6; + } + return nbMipMaps; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java new file mode 100644 index 0000000000..982ccc79df --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBaker.java @@ -0,0 +1,74 @@ +/* + * 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.environment.baker; + +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; + +/** + * An environment baker, but this one is for Imaged Base Lighting. + * + * @author Riccardo Balbo + */ +public interface IBLEnvBaker extends EnvBaker { + /** + * Generates the BRDF texture. + * + * @return The BRDF texture + */ + public Texture2D genBRTF(); + + /** + * Bakes the irradiance map. + */ + public void bakeIrradiance(); + + /** + * Bakes the specular IBL map. + */ + public void bakeSpecularIBL(); + + /** + * Gets the specular IBL map. + * + * @return The specular IBL map + */ + public TextureCubeMap getSpecularIBL(); + + /** + * Gets the irradiance map. + * + * @return The irradiance map + */ + public TextureCubeMap getIrradiance(); +} diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java new file mode 100644 index 0000000000..19275d514e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLEnvBakerLight.java @@ -0,0 +1,52 @@ +/* + * 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.environment.baker; + +import com.jme3.math.Vector3f; +import com.jme3.texture.TextureCubeMap; + +/** + * An environment baker for IBL, that uses spherical harmonics for irradiance. + * + * @author Riccardo Balbo + */ +public interface IBLEnvBakerLight extends EnvBaker { + + public void bakeSpecularIBL(); + + public void bakeSphericalHarmonicsCoefficients(); + + public TextureCubeMap getSpecularIBL(); + + public Vector3f[] getSphericalHarmonicsCoefficients(); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java new file mode 100644 index 0000000000..5b79a4922f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBaker.java @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.environment.baker; + +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.image.ColorSpace; +import com.jme3.ui.Picture; + +/** + * Fully accelerated env baker for IBL that runs entirely on the GPU + * + * @author Riccardo Balbo + */ +public class IBLGLEnvBaker extends GenericEnvBaker implements IBLEnvBaker { + private static final Logger LOGGER = Logger.getLogger(IBLGLEnvBakerLight.class.getName()); + + protected Texture2D brtf; + protected TextureCubeMap irradiance; + protected TextureCubeMap specular; + + /** + * Create a new IBL env baker + * @param rm The render manager used to render the env scene + * @param am The asset manager used to load the baking shaders + * @param format The format of the color buffers + * @param depthFormat The format of the depth buffers + * @param env_size The size in pixels of the output environment cube map (eg. 1024) + * @param specular_size The size in pixels of the output specular cube map (eg. 1024) + * @param irradiance_size The size in pixels of the output irradiance cube map (eg. 512) + * @param brtf_size The size in pixels of the output brtf map (eg. 512) + */ + public IBLGLEnvBaker(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size, int irradiance_size, int brtf_size) { + super(rm, am, format, depthFormat, env_size); + + irradiance = new TextureCubeMap(irradiance_size, irradiance_size, format); + irradiance.setMagFilter(MagFilter.Bilinear); + irradiance.setMinFilter(MinFilter.BilinearNoMipMaps); + irradiance.setWrap(WrapMode.EdgeClamp); + irradiance.getImage().setColorSpace(ColorSpace.Linear); + + specular = new TextureCubeMap(specular_size, specular_size, format); + specular.setMagFilter(MagFilter.Bilinear); + specular.setMinFilter(MinFilter.Trilinear); + specular.setWrap(WrapMode.EdgeClamp); + specular.getImage().setColorSpace(ColorSpace.Linear); + + int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1); + nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm); + + int[] sizes = new int[nbMipMaps]; + for (int i = 0; i < nbMipMaps; i++) { + int size = (int) FastMath.pow(2, nbMipMaps - 1 - i); + sizes[i] = size * size * (specular.getImage().getFormat().getBitsPerPixel() / 8); + } + specular.getImage().setMipMapSizes(sizes); + + brtf = new Texture2D(brtf_size, brtf_size, format); + brtf.setMagFilter(MagFilter.Bilinear); + brtf.setMinFilter(MinFilter.BilinearNoMipMaps); + brtf.setWrap(WrapMode.EdgeClamp); + brtf.getImage().setColorSpace(ColorSpace.Linear); + } + + @Override + public TextureCubeMap getSpecularIBL() { + return specular; + } + + @Override + public TextureCubeMap getIrradiance() { + return irradiance; + } + + private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception { + mat.setFloat("Roughness", roughness); + + int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip)); + int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip)); + + FrameBuffer specularbakers[] = new FrameBuffer[6]; + for (int i = 0; i < 6; i++) { + specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1); + specularbakers[i].setSrgb(false); + specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i)); + specularbakers[i].setMipMapsGenerationHint(false); + } + + for (int i = 0; i < 6; i++) { + FrameBuffer specularbaker = specularbakers[i]; + mat.setInt("FaceId", i); + + screen.updateLogicalState(0); + screen.updateGeometricState(); + + renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(specularbaker); + renderManager.renderGeometry(screen); + + if (isTexturePulling()) { + pull(specularbaker, specular, i); + } + + } + for (int i = 0; i < 6; i++) { + specularbakers[i].dispose(); + } + } + + @Override + public void bakeSpecularIBL() { + Box boxm = new Box(1, 1, 1); + Geometry screen = new Geometry("BakeBox", boxm); + + Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md"); + mat.setBoolean("UseSpecularIBL", true); + mat.setTexture("EnvMap", envMap); + screen.setMaterial(mat); + + if (isTexturePulling()) { + startPulling(); + } + + int mip = 0; + for (; mip < specular.getImage().getMipMapSizes().length; mip++) { + try { + float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1); + bakeSpecularIBL(mip, roughness, mat, screen); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e); + break; + } + } + + if (mip < specular.getImage().getMipMapSizes().length) { + + int[] sizes = specular.getImage().getMipMapSizes(); + sizes = Arrays.copyOf(sizes, mip); + specular.getImage().setMipMapSizes(sizes); + specular.getImage().setMipmapsGenerated(true); + if (sizes.length <= 1) { + try { + LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)"); + bakeSpecularIBL(0, 1f, mat, screen); + } catch (Exception e) { + LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e); + } + } + } + + if (isTexturePulling()) { + endPulling(specular); + } + specular.getImage().clearUpdateNeeded(); + + } + + @Override + public Texture2D genBRTF() { + + Picture screen = new Picture("BakeScreen", true); + screen.setWidth(1); + screen.setHeight(1); + + FrameBuffer brtfbaker = new FrameBuffer(brtf.getImage().getWidth(), brtf.getImage().getHeight(), 1); + brtfbaker.setSrgb(false); + brtfbaker.addColorTarget(FrameBufferTarget.newTarget(brtf)); + + if (isTexturePulling()) { + startPulling(); + } + + Camera envcam = updateAndGetInternalCamera(0, brtf.getImage().getWidth(), brtf.getImage().getHeight(), Vector3f.ZERO, 1, 1000); + + Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md"); + mat.setBoolean("UseBRDF", true); + screen.setMaterial(mat); + + renderManager.getRenderer().setFrameBuffer(brtfbaker); + renderManager.setCamera(envcam, false); + + screen.updateLogicalState(0); + screen.updateGeometricState(); + renderManager.renderGeometry(screen); + + if (isTexturePulling()) { + pull(brtfbaker, brtf, 0); + } + + brtfbaker.dispose(); + + if (isTexturePulling()) { + endPulling(brtf); + } + brtf.getImage().clearUpdateNeeded(); + + return brtf; + } + + @Override + public void bakeIrradiance() { + + Box boxm = new Box(1, 1, 1); + Geometry screen = new Geometry("BakeBox", boxm); + + FrameBuffer irradiancebaker = new FrameBuffer(irradiance.getImage().getWidth(), irradiance.getImage().getHeight(), 1); + irradiancebaker.setSrgb(false); + + if (isTexturePulling()) { + startPulling(); + } + + for (int i = 0; i < 6; i++) { + irradiancebaker.addColorTarget( + FrameBufferTarget.newTarget(irradiance).face(TextureCubeMap.Face.values()[i])); + } + + Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md"); + mat.setBoolean("UseIrradiance", true); + mat.setTexture("EnvMap", envMap); + screen.setMaterial(mat); + + for (int i = 0; i < 6; i++) { + irradiancebaker.setTargetIndex(i); + + mat.setInt("FaceId", i); + + screen.updateLogicalState(0); + screen.updateGeometricState(); + + renderManager.setCamera(updateAndGetInternalCamera(i, irradiancebaker.getWidth(), irradiancebaker.getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(irradiancebaker); + renderManager.renderGeometry(screen); + + if (isTexturePulling()) { + pull(irradiancebaker, irradiance, i); + } + } + + irradiancebaker.dispose(); + + if (isTexturePulling()) { + endPulling(irradiance); + } + irradiance.getImage().clearUpdateNeeded(); + + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java new file mode 100644 index 0000000000..f6284f14ea --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLGLEnvBakerLight.java @@ -0,0 +1,178 @@ +/* + * 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.environment.baker; + +import java.nio.ByteBuffer; +import java.util.logging.Logger; +import com.jme3.asset.AssetManager; +import com.jme3.environment.util.EnvMapUtils; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.Image.Format; +import com.jme3.texture.image.ColorSpace; +import com.jme3.texture.image.ImageRaster; +import com.jme3.util.BufferUtils; + +/** + * Fully accelerated env baker for IBL that bakes the specular map and spherical + * harmonics on the GPU. + * + * This is lighter on VRAM but it is not as parallelized as IBLGLEnvBaker + * + * @author Riccardo Balbo + */ +public class IBLGLEnvBakerLight extends IBLHybridEnvBakerLight { + private static final int NUM_SH_COEFFICIENT = 9; + private static final Logger LOG = Logger.getLogger(IBLGLEnvBakerLight.class.getName()); + + /** + * Create a new IBL env baker + * + * @param rm + * The render manager used to render the env scene + * @param am + * The asset manager used to load the baking shaders + * @param format + * The format of the color buffers + * @param depthFormat + * The format of the depth buffers + * @param env_size + * The size in pixels of the output environment cube map (eg. + * 1024) + * @param specular_size + * The size in pixels of the output specular cube map (eg. 1024) + */ + public IBLGLEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) { + super(rm, am, format, depthFormat, env_size, specular_size); + } + + @Override + public boolean isTexturePulling() { + return this.texturePulling; + } + + @Override + public void bakeSphericalHarmonicsCoefficients() { + Box boxm = new Box(1, 1, 1); + Geometry screen = new Geometry("BakeBox", boxm); + + Material mat = new Material(assetManager, "Common/IBLSphH/IBLSphH.j3md"); + mat.setTexture("Texture", envMap); + mat.setVector2("Resolution", new Vector2f(envMap.getImage().getWidth(), envMap.getImage().getHeight())); + screen.setMaterial(mat); + + float remapMaxValue = 0; + Format format = Format.RGBA32F; + if (!renderManager.getRenderer().getCaps().contains(Caps.FloatColorBufferRGBA)) { + LOG.warning("Float textures not supported, using RGB8 instead. This may cause accuracy issues."); + format = Format.RGBA8; + remapMaxValue = 0.05f; + } + + if (remapMaxValue > 0) { + mat.setFloat("RemapMaxValue", remapMaxValue); + } else { + mat.clearParam("RemapMaxValue"); + } + + Texture2D shCoefTx[] = { new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format), new Texture2D(NUM_SH_COEFFICIENT, 1, 1, format) }; + + FrameBuffer shbaker[] = { new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1), new FrameBuffer(NUM_SH_COEFFICIENT, 1, 1) }; + shbaker[0].setSrgb(false); + shbaker[0].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[0])); + + shbaker[1].setSrgb(false); + shbaker[1].addColorTarget(FrameBufferTarget.newTarget(shCoefTx[1])); + + int renderOnT = -1; + + for (int faceId = 0; faceId < 6; faceId++) { + if (renderOnT != -1) { + int s = renderOnT; + renderOnT = renderOnT == 0 ? 1 : 0; + mat.setTexture("ShCoef", shCoefTx[s]); + } else { + renderOnT = 0; + } + + mat.setInt("FaceId", faceId); + + screen.updateLogicalState(0); + screen.updateGeometricState(); + + renderManager.setCamera(updateAndGetInternalCamera(0, shbaker[renderOnT].getWidth(), shbaker[renderOnT].getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(shbaker[renderOnT]); + renderManager.renderGeometry(screen); + } + + ByteBuffer shCoefRaw = BufferUtils.createByteBuffer(NUM_SH_COEFFICIENT * 1 * (shbaker[renderOnT].getColorTarget().getFormat().getBitsPerPixel() / 8)); + renderManager.getRenderer().readFrameBufferWithFormat(shbaker[renderOnT], shCoefRaw, shbaker[renderOnT].getColorTarget().getFormat()); + shCoefRaw.rewind(); + + Image img = new Image(format, NUM_SH_COEFFICIENT, 1, shCoefRaw, ColorSpace.Linear); + ImageRaster imgr = ImageRaster.create(img); + + shCoef = new Vector3f[NUM_SH_COEFFICIENT]; + float weightAccum = 0.0f; + + for (int i = 0; i < shCoef.length; i++) { + ColorRGBA c = imgr.getPixel(i, 0); + shCoef[i] = new Vector3f(c.r, c.g, c.b); + if (weightAccum == 0) weightAccum = c.a; + else if (weightAccum != c.a) { + LOG.warning("SH weight is not uniform, this may cause issues."); + } + + } + + if (remapMaxValue > 0) weightAccum /= remapMaxValue; + + for (int i = 0; i < NUM_SH_COEFFICIENT; ++i) { + if (remapMaxValue > 0) shCoef[i].divideLocal(remapMaxValue); + shCoef[i].multLocal(4.0f * FastMath.PI / weightAccum); + } + EnvMapUtils.prepareShCoefs(shCoef); + img.dispose(); + + } +} diff --git a/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java new file mode 100644 index 0000000000..26b3c1cd65 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/baker/IBLHybridEnvBakerLight.java @@ -0,0 +1,210 @@ +/* + * 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.environment.baker; + +import java.util.Arrays; +import java.util.logging.Level; +import java.util.logging.Logger; +import com.jme3.asset.AssetManager; +import com.jme3.environment.util.EnvMapUtils; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.renderer.RenderManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.Image.Format; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.image.ColorSpace; + +/** + * An env baker for IBL that bakes the specular map on the GPU and uses + * spherical harmonics generated on the CPU for the irradiance map. + * + * This is lighter on VRAM but uses the CPU to compute the irradiance map. + * + * @author Riccardo Balbo + */ +public class IBLHybridEnvBakerLight extends GenericEnvBaker implements IBLEnvBakerLight { + private static final Logger LOGGER = Logger.getLogger(IBLHybridEnvBakerLight.class.getName()); + protected TextureCubeMap specular; + protected Vector3f[] shCoef; + + /** + * Create a new IBL env baker + * + * @param rm + * The render manager used to render the env scene + * @param am + * The asset manager used to load the baking shaders + * @param format + * The format of the color buffers + * @param depthFormat + * The format of the depth buffers + * @param env_size + * The size in pixels of the output environment cube map (eg. + * 1024) + * @param specular_size + * The size in pixels of the output specular cube map (eg. 1024) + */ + public IBLHybridEnvBakerLight(RenderManager rm, AssetManager am, Format format, Format depthFormat, int env_size, int specular_size) { + super(rm, am, format, depthFormat, env_size); + + specular = new TextureCubeMap(specular_size, specular_size, format); + specular.setWrap(WrapMode.EdgeClamp); + specular.setMagFilter(MagFilter.Bilinear); + specular.setMinFilter(MinFilter.Trilinear); + specular.getImage().setColorSpace(ColorSpace.Linear); + + int nbMipMaps = (int) (Math.log(specular_size) / Math.log(2) + 1); + nbMipMaps = limitMips(nbMipMaps, specular.getImage().getWidth(), specular.getImage().getHeight(), rm); + + int[] sizes = new int[nbMipMaps]; + for (int i = 0; i < nbMipMaps; i++) { + int size = (int) FastMath.pow(2, nbMipMaps - 1 - i); + sizes[i] = size * size * (specular.getImage().getFormat().getBitsPerPixel() / 8); + } + specular.getImage().setMipMapSizes(sizes); + specular.getImage().setMipmapsGenerated(true); + + } + + @Override + public boolean isTexturePulling() { // always pull textures from gpu + return true; + } + + private void bakeSpecularIBL(int mip, float roughness, Material mat, Geometry screen) throws Exception { + mat.setFloat("Roughness", roughness); + + int mipWidth = (int) (specular.getImage().getWidth() * FastMath.pow(0.5f, mip)); + int mipHeight = (int) (specular.getImage().getHeight() * FastMath.pow(0.5f, mip)); + + FrameBuffer specularbakers[] = new FrameBuffer[6]; + for (int i = 0; i < 6; i++) { + specularbakers[i] = new FrameBuffer(mipWidth, mipHeight, 1); + specularbakers[i].setSrgb(false); + specularbakers[i].addColorTarget(FrameBufferTarget.newTarget(specular).level(mip).face(i)); + specularbakers[i].setMipMapsGenerationHint(false); + } + + for (int i = 0; i < 6; i++) { + FrameBuffer specularbaker = specularbakers[i]; + mat.setInt("FaceId", i); + + screen.updateLogicalState(0); + screen.updateGeometricState(); + + renderManager.setCamera(updateAndGetInternalCamera(i, specularbaker.getWidth(), specularbaker.getHeight(), Vector3f.ZERO, 1, 1000), false); + renderManager.getRenderer().setFrameBuffer(specularbaker); + renderManager.renderGeometry(screen); + + if (isTexturePulling()) { + pull(specularbaker, specular, i); + } + + } + for (int i = 0; i < 6; i++) { + specularbakers[i].dispose(); + } + } + + @Override + public void bakeSpecularIBL() { + Box boxm = new Box(1, 1, 1); + Geometry screen = new Geometry("BakeBox", boxm); + + Material mat = new Material(assetManager, "Common/IBL/IBLKernels.j3md"); + mat.setBoolean("UseSpecularIBL", true); + mat.setTexture("EnvMap", envMap); + screen.setMaterial(mat); + + if (isTexturePulling()) { + startPulling(); + } + + int mip = 0; + for (; mip < specular.getImage().getMipMapSizes().length; mip++) { + try { + float roughness = (float) mip / (float) (specular.getImage().getMipMapSizes().length - 1); + bakeSpecularIBL(mip, roughness, mat, screen); + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Error while computing mip level " + mip, e); + break; + } + } + + if (mip < specular.getImage().getMipMapSizes().length) { + + int[] sizes = specular.getImage().getMipMapSizes(); + sizes = Arrays.copyOf(sizes, mip); + specular.getImage().setMipMapSizes(sizes); + specular.getImage().setMipmapsGenerated(true); + if (sizes.length <= 1) { + try { + LOGGER.log(Level.WARNING, "Workaround driver BUG: only one mip level available, regenerate it with higher roughness (shiny fix)"); + bakeSpecularIBL(0, 1f, mat, screen); + } catch (Exception e) { + LOGGER.log(Level.FINE, "Error while recomputing mip level 0", e); + } + } + } + + if (isTexturePulling()) { + endPulling(specular); + } + specular.getImage().clearUpdateNeeded(); + + } + + @Override + public TextureCubeMap getSpecularIBL() { + return specular; + } + + @Override + public void bakeSphericalHarmonicsCoefficients() { + shCoef = EnvMapUtils.getSphericalHarmonicsCoefficents(getEnvMap()); + EnvMapUtils.prepareShCoefs(shCoef); + } + + @Override + public Vector3f[] getSphericalHarmonicsCoefficients() { + return shCoef; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java index 7a3f637ff9..9122638265 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/IrradianceSphericalHarmonicsGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,7 +59,7 @@ public class IrradianceSphericalHarmonicsGenerator extends RunnableWithProgress * process is thread safe. * * @param app the Application - * @param listener + * @param listener to monitor progress (alias created) */ public IrradianceSphericalHarmonicsGenerator(Application app, JobProgressListener listener) { super(listener); @@ -91,9 +91,9 @@ public Void call() throws Exception { } }); try { - Vector3f[] shCoeffs = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap); - EnvMapUtils.prepareShCoefs(shCoeffs); - store.setShCoeffs(shCoeffs); + Vector3f[] shCoefficients = EnvMapUtils.getSphericalHarmonicsCoefficents(sourceMap); + EnvMapUtils.prepareShCoefs(shCoefficients); + store.setShCoeffs(shCoefficients); } catch (Exception e) { e.printStackTrace(); @@ -101,6 +101,7 @@ public Void call() throws Exception { app.enqueue(new Callable() { @Override + @SuppressWarnings("unchecked") public Void call() throws Exception { listener.done(6); return null; diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java index 6b19d6b706..b99774ab1b 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ * only a subset of method implemented. * * @author nehon - * @param + * @param the type of result */ public abstract class JobProgressAdapter implements JobProgressListener{ diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java index 6c5b93d7c2..2db94146a0 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/JobProgressListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ public interface JobProgressListener { /** * Can be called when a step of the process has been completed with a relevant message. - * @param message the message stating of the paricular step completion. + * @param message the message stating of the particular step completion. */ public void step(String message); diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java index cd4f969981..0cedd62526 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/PrefilteredEnvMapFaceGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,7 +81,7 @@ public class PrefilteredEnvMapFaceGenerator extends RunnableWithProgress { * * @param app the Application * @param face the face to generate - * @param listener + * @param listener to monitor progress (alias created) */ public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressListener listener) { super(listener); @@ -98,7 +98,7 @@ public PrefilteredEnvMapFaceGenerator(Application app, int face, JobProgressList * pixel) * @param fixSeamsMethod the method used to fix seams as described in * {@link com.jme3.environment.util.EnvMapUtils.FixSeamsMethod} - * @param genType + * @param genType select Fast or HighQuality * @param store The cube map to store the result in. */ public void setGenerationParam(TextureCubeMap sourceMap, int targetMapSize, EnvMapUtils.FixSeamsMethod fixSeamsMethod, EnvMapUtils.GenerationType genType, TextureCubeMap store) { @@ -137,6 +137,7 @@ public Void call() throws Exception { app.enqueue(new Callable() { @Override + @SuppressWarnings("unchecked") public Void call() throws Exception { listener.done(face); return null; @@ -146,7 +147,7 @@ public Void call() throws Exception { /** * Generates the prefiltered env map (used for image based specular - * lighting) With the GGX/Shlick brdf + * lighting) With the GGX/Schlick brdf * {@link EnvMapUtils#getSphericalHarmonicsCoefficents(com.jme3.texture.TextureCubeMap)} * Note that the output cube map is in RGBA8 format. * @@ -211,7 +212,7 @@ private Vector3f prefilterEnvMapTexel(CubeMapWrapper envMapReader, float roughne nbRotations = numSamples == 1 ? 1 : 18; } - float rad = 2f * FastMath.PI / (float) nbRotations; + float rad = 2f * FastMath.PI / nbRotations; // offset rotation to avoid sampling pattern float gi = (float) (FastMath.abs(N.z + N.x) * 256.0); float offset = rad * (FastMath.cos((gi * 0.5f) % (2f * FastMath.PI)) * 0.5f + 0.5f); @@ -330,8 +331,8 @@ private Vector3f rotateDirection(float angle, Vector3f l, Vector3f store) { /** * Computes GGX half vector in local space * - * @param xi - * @param a2 + * @param xi (not null) + * @param a2 fourth power of roughness * @param store caller-provided storage * @return either store or a new vector (not null) */ diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java index dec5abd2a5..5a11ef6fcc 100644 --- a/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java +++ b/jme3-core/src/main/java/com/jme3/environment/generation/RunnableWithProgress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ public RunnableWithProgress(JobProgressListener listener) { /** * set the end step value of the process. * - * @param end + * @param end the desired end value (default=0) */ protected void setEnd(int end) { this.end = end; @@ -66,7 +66,7 @@ protected void setEnd(int end) { * @return fraction (≥0, ≤1) */ public double getProgress() { - return (double) progress / (double) end; + return progress / (double) end; } /** diff --git a/jme3-core/src/main/java/com/jme3/environment/generation/package-info.java b/jme3-core/src/main/java/com/jme3/environment/generation/package-info.java new file mode 100644 index 0000000000..5dea4f92e6 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/generation/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * supplemental classes for generating lighting environments + */ +package com.jme3.environment.generation; diff --git a/jme3-core/src/main/java/com/jme3/environment/package-info.java b/jme3-core/src/main/java/com/jme3/environment/package-info.java new file mode 100644 index 0000000000..a0e7fc0d3b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * generate lighting environments for physically-based rendering (PBR) + */ +package com.jme3.environment; diff --git a/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java b/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java index 8cb56536c9..622164e4a1 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/BoundingSphereDebug.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ /** * - * A debuging shape for a BoundingSphere + * A debugging shape for a BoundingSphere * Consists of 3 axis aligned circles. * * @author nehon @@ -56,14 +56,6 @@ public class BoundingSphereDebug extends Mesh { protected int radialSamples = 32; protected boolean useEvenSlices; protected boolean interior; - /** - * the distance from the center point each point falls on - */ - public float radius; - - public float getRadius() { - return radius; - } public BoundingSphereDebug() { setGeometryData(); @@ -151,27 +143,23 @@ private void setIndexData() { if (segDone == radialSamples || segDone == radialSamples * 2) { idx++; } - } - } - /** - * Convenience factory method that creates a debuging bounding sphere geometry + * Convenience factory method that creates a debug bounding-sphere geometry + * * @param assetManager the assetManager * @return the bounding sphere debug geometry. */ public static Geometry createDebugSphere(AssetManager assetManager) { - BoundingSphereDebug b = new BoundingSphereDebug(); - Geometry geom = new Geometry("BoundingDebug", b); - + BoundingSphereDebug mesh = new BoundingSphereDebug(); + Geometry geom = new Geometry("BoundingDebug", mesh); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.setBoolean("VertexColor", true); mat.getAdditionalRenderState().setWireframe(true); - geom.setMaterial(mat); return geom; - } + } diff --git a/jme3-core/src/main/java/com/jme3/environment/util/Circle.java b/jme3-core/src/main/java/com/jme3/environment/util/Circle.java new file mode 100644 index 0000000000..34dac36f7c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/Circle.java @@ -0,0 +1,167 @@ + /* + * Copyright (c) 2009-2025 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.environment.util; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; + +import java.io.IOException; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; + +/** + *

    A `Circle` is a 2D mesh representing a circular outline (wireframe). + * It's defined by a specified number of radial samples, which determine its smoothness.

    + * + *

    The circle is centered at (0,0,0) in its local coordinate space and has a radius of 1.0.

    + * + * @author capdevon + */ +public class Circle extends Mesh { + + // The number of segments used to approximate the circle. + protected int radialSamples = 256; + + /** + * Creates a new `Circle` mesh. + */ + public Circle() { + setGeometryData(); + setIndexData(); + } + + /** + * Initializes the vertex buffers for the circle mesh. + */ + private void setGeometryData() { + + int numVertices = radialSamples + 1; + + FloatBuffer posBuf = BufferUtils.createVector3Buffer(numVertices); + FloatBuffer colBuf = BufferUtils.createFloatBuffer(numVertices * 4); + FloatBuffer texBuf = BufferUtils.createVector2Buffer(numVertices); + + // --- Generate Geometry Data --- + float angleStep = FastMath.TWO_PI / radialSamples; + + // Define the color for the entire circle. + ColorRGBA color = ColorRGBA.Orange; + + // Populate the position, color, and texture coordinate buffers. + for (int i = 0; i < numVertices; i++) { + float angle = angleStep * i; + float cos = FastMath.cos(angle); + float sin = FastMath.sin(angle); + + posBuf.put(cos).put(sin).put(0); + colBuf.put(color.r).put(color.g).put(color.b).put(color.a); + texBuf.put(i % 2f).put(i % 2f); + } + + setBuffer(Type.Position, 3, posBuf); + setBuffer(Type.Color, 4, colBuf); + setBuffer(Type.TexCoord, 2, texBuf); + + setMode(Mode.Lines); + updateBound(); + setStatic(); + } + + /** + * Initializes the index buffer for the circle mesh. + */ + private void setIndexData() { + // allocate connectivity + int numIndices = radialSamples * 2; + + ShortBuffer idxBuf = BufferUtils.createShortBuffer(numIndices); + setBuffer(Type.Index, 2, idxBuf); + + // --- Generate Index Data --- + for (int i = 0; i < radialSamples; i++) { + idxBuf.put((short) i); // Start of segment + idxBuf.put((short) (i + 1)); // End of segment + } + } + + /** + * Creates a {@link Geometry} object representing a dashed wireframe circle. + * + * @param assetManager The application's AssetManager to load materials. + * @param name The desired name for the Geometry. + * @return A new Geometry instance with a `Circle` mesh. + */ + public static Geometry createShape(AssetManager assetManager, String name) { + Circle mesh = new Circle(); + Geometry geom = new Geometry(name, mesh); + geom.setQueueBucket(RenderQueue.Bucket.Transparent); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Dashed.j3md"); + mat.getAdditionalRenderState().setWireframe(true); + mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + mat.getAdditionalRenderState().setDepthWrite(false); + mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setLineWidth(2f); + mat.setColor("Color", ColorRGBA.Orange); + mat.setFloat("DashSize", 0.5f); + geom.setMaterial(mat); + + return geom; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(radialSamples, "radialSamples", 256); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + radialSamples = ic.readInt("radialSamples", 256); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java index d03dbe88c3..322caa92d1 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/CubeMapWrapper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,9 +41,9 @@ import static com.jme3.math.FastMath.pow; /** - * Wraps a Cube map and allows to read from or write pixels into it. + * Wraps a cube map and allows reading or writing pixels. * - * It uses the ImageRaster class to tailor the read write operations. + * It uses the ImageRaster class to tailor the read/write operations. * * @author Nehon */ @@ -148,7 +148,7 @@ public ColorRGBA getPixel(int x, int y, int face, ColorRGBA store) { store = new ColorRGBA(); } raster.setSlice(face); - return raster.getPixel((int) x, (int) y, store); + return raster.getPixel(x, y, store); } /** @@ -170,7 +170,7 @@ public ColorRGBA getPixel(int x, int y, int face, int mipLevel, ColorRGBA store) } mipMapRaster.setSlice(face); mipMapRaster.setMipLevel(mipLevel); - return mipMapRaster.getPixel((int) x, (int) y, store); + return mipMapRaster.getPixel(x, y, store); } /** @@ -201,7 +201,7 @@ public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) { } /** - * Writes a pixel given the 2D cordinates and the color + * Writes a pixel given the 2-D coordinates and the color * @param x the x tex coord (from 0 to width) * @param y the y tex coord (from 0 to height) * @param face the face to write to @@ -209,11 +209,11 @@ public void setPixel(Vector3f vector, int mipLevel, ColorRGBA color) { */ public void setPixel(int x, int y, int face, ColorRGBA color) { raster.setSlice(face); - raster.setPixel((int) x, (int) y, color); + raster.setPixel(x, y, color); } /** - * Writes a pixel given the 2D cordinates, the mip level and the color + * Writes a pixel given the 2-D coordinates, the mip level and the color * @param x the x tex coord (from 0 to width) * @param y the y tex coord (from 0 to height) * @param face the face to write to @@ -227,7 +227,7 @@ public void setPixel(int x, int y, int face, int mipLevel, ColorRGBA color) { mipMapRaster.setSlice(face); mipMapRaster.setMipLevel(mipLevel); - mipMapRaster.setPixel((int) x, (int) y, color); + mipMapRaster.setPixel(x, y, color); } /** diff --git a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java index 8f7ad841c6..81c12e4a2c 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/EnvMapUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,8 +49,8 @@ /** * - * This class holds several utility method unseful for Physically Based - * Rendering. It alloaws to compute useful pre filtered maps from an env map. + * This class holds several utility method useful for Physically Based + * Rendering. It allows us to compute useful prefiltered maps from an env map. * * @author Nehon */ @@ -90,6 +90,12 @@ public static enum GenerationType { HighQuality } + /** + * A private constructor to inhibit instantiation of this class. + */ + private EnvMapUtils() { + } + /** * Creates a cube map from 6 images * @@ -99,7 +105,7 @@ public static enum GenerationType { * right image * @param downImg the bottom side image, also called negative y (negY) or * down image - * @param upImg the up side image, also called positive y (posY) or up image + * @param upImg the top side image, also called positive y (posY) or up image * @param backImg the south side image, also called positive z (posZ) or * back image * @param frontImg the north side image, also called negative z (negZ) or @@ -141,7 +147,7 @@ public static TextureCubeMap makeCubeMap(Image rightImg, Image leftImg, Image up * the same area of the buffer. The position, limit and mark are not an * issue. * - * @param sourceMap + * @param sourceMap the map to be copied (not null, unaffected) * @return a new instance */ public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) { @@ -174,7 +180,7 @@ public static TextureCubeMap duplicateCubeMap(TextureCubeMap sourceMap) { * * * Original solid angle calculation code is from Ignacio Castaño. This - * formula is from Manne Öhrström's thesis. It takes two coordiantes in the + * formula is from Manne Öhrström's thesis. It takes two coordinates in the * range [-1, 1] that define a portion of a cube face and return the area of * the projection of that portion on the surface of the sphere. * @@ -194,8 +200,8 @@ static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3 /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] (+ 0.5f is for texel center addressing) */ - float u = (2.0f * ((float) x + 0.5f) / (float) mapSize) - 1.0f; - float v = (2.0f * ((float) y + 0.5f) / (float) mapSize) - 1.0f; + float u = (2.0f * (x + 0.5f) / mapSize) - 1.0f; + float v = (2.0f * (y + 0.5f) / mapSize) - 1.0f; getVectorFromCubemapFaceTexCoord(x, y, mapSize, face, store, fixSeamsMethod); @@ -203,7 +209,7 @@ static float getSolidAngleAndVector(int x, int y, int mapSize, int face, Vector3 * U and V are the -1..1 texture coordinate on the current face. * Get projected area for this texel */ float x0, y0, x1, y1; - float invRes = 1.0f / (float) mapSize; + float invRes = 1.0f / mapSize; x0 = u - invRes; y0 = v - invRes; x1 = u + invRes; @@ -246,19 +252,19 @@ public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSiz if (fixSeamsMethod == FixSeamsMethod.Stretch) { /* Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L77 * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */ - u = (2.0f * (float) x / ((float) mapSize - 1.0f)) - 1.0f; - v = (2.0f * (float) y / ((float) mapSize - 1.0f)) - 1.0f; + u = (2.0f * x / (mapSize - 1.0f)) - 1.0f; + v = (2.0f * y / (mapSize - 1.0f)) - 1.0f; } else { //Done if any other fix method or no fix method is set /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] * (+ 0.5f is for texel center addressing) */ - u = (2.0f * ((float) x + 0.5f) / (float) (mapSize)) - 1.0f; - v = (2.0f * ((float) y + 0.5f) / (float) (mapSize)) - 1.0f; + u = (2.0f * (x + 0.5f) / mapSize) - 1.0f; + v = (2.0f * (y + 0.5f) / mapSize) - 1.0f; } if (fixSeamsMethod == FixSeamsMethod.Wrap) { // Warp texel centers in the proximity of the edges. - float a = pow((float) mapSize, 2.0f) / pow(((float) mapSize - 1f), 3.0f); + float a = pow(mapSize, 2.0f) / pow(mapSize - 1f, 3.0f); u = a * pow(u, 3f) + u; v = a * pow(v, 3f) + v; } @@ -291,10 +297,10 @@ public static Vector3f getVectorFromCubemapFaceTexCoord(int x, int y, int mapSiz /** * - * Computes the texture coortinates and the face of the cube map from the + * Computes the texture coordinates and the face of the cube map from the * given vector * - * @param texelVect the vector to fetch texelt from the cube map + * @param texelVect the vector to fetch texels from the cube map * @param fixSeamsMethod the method to fix the seams * @param mapSize the size of one face of the cube map * @param store a Vector2f where the texture coordinates will be stored @@ -317,7 +323,7 @@ public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSi } //compute vector depending on the face - // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + // Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp switch (face) { case 0: //store.set(1f, -v, -u, 0); @@ -360,16 +366,16 @@ public static int getCubemapFaceTexCoordFromVector(Vector3f texelVect, int mapSi v *= bias; if (fixSeamsMethod == FixSeamsMethod.Stretch) { - /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp + /* Code from Nvtt : http://code.google.com/p/nvidia-texture-tools/source/browse/trunk/src/nvtt/CubeSurface.cpp * transform from [0..res - 1] to [-1 .. 1], match up edges exactly. */ - u = Math.round((u + 1.0f) * ((float) mapSize - 1.0f) * 0.5f); - v = Math.round((v + 1.0f) * ((float) mapSize - 1.0f) * 0.5f); + u = Math.round((u + 1.0f) * (mapSize - 1.0f) * 0.5f); + v = Math.round((v + 1.0f) * (mapSize - 1.0f) * 0.5f); } else { //Done if any other fix method or no fix method is set /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] * (+ 0.5f is for texel center addressing) */ - u = Math.round((u + 1.0f) * ((float) mapSize) * 0.5f - 0.5f); - v = Math.round((v + 1.0f) * ((float) mapSize) * 0.5f - 0.5f); + u = Math.round((u + 1.0f) * mapSize * 0.5f - 0.5f); + v = Math.round((v + 1.0f) * mapSize * 0.5f - 0.5f); } @@ -382,11 +388,11 @@ public static int getSampleFromMip(int mipLevel, int miptot) { } - //see lagarde's paper https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf + //see Lagarde's paper https://seblagarde.files.wordpress.com/2015/07/course_notes_moving_frostbite_to_pbr_v32.pdf //linear roughness - public static float getRoughnessFromMip(int miplevel, int miptot) { + public static float getRoughnessFromMip(int mipLevel, int miptot) { float step = 1f / ((float) miptot - 1); - step *= miplevel; + step *= mipLevel; return step * step; } @@ -410,10 +416,10 @@ public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap /** * Returns the Spherical Harmonics coefficients for this cube map. * - * The method used is the one from this article : + * The method used is the one from this article: * http://graphics.stanford.edu/papers/envmap/envmap.pdf * - * Also good resources on spherical harmonics + * Another good resource for spherical harmonics: * http://dickyjim.wordpress.com/2013/09/04/spherical-harmonics-for-beginners/ * * @param cubeMap the environment cube map to compute SH for @@ -479,8 +485,8 @@ public static Vector3f[] getSphericalHarmonicsCoefficents(TextureCubeMap cubeMap * Computes SH coefficient for a given textel dir The method used is the one * from this article : http://graphics.stanford.edu/papers/envmap/envmap.pdf * - * @param texelVect - * @param shDir + * @param texelVect the input texel (not null, unaffected) + * @param shDir storage for the results */ public static void evalShBasis(Vector3f texelVect, float[] shDir) { @@ -533,7 +539,7 @@ public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f s } float phi; long ui = i; - store.setX((float) i / (float) nbrSample); + store.setX(i / (float) nbrSample); /* From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html * Radical Inverse : Van der Corput */ @@ -544,7 +550,7 @@ public static Vector4f getHammersleyPoint(int i, final int nbrSample, Vector4f s ui = ((ui & 0x00FF00FF) << 8) | ((ui & 0xFF00FF00) >>> 8); ui = ui & 0xffffffff; - store.setY(2.3283064365386963e-10f * (float) (ui)); /* 0x100000000 */ + store.setY(2.3283064365386963e-10f * ui); /* 0x100000000 */ phi = 2.0f * PI * store.y; store.setZ(cos(phi)); @@ -611,7 +617,7 @@ public static Node getCubeMapCrossDebugView(TextureCubeMap cubeMap, AssetManager int size = cubeMap.getImage().getWidth(); Picture[] pics = new Picture[6]; - float ratio = 128f / (float) size; + float ratio = 128f / size; for (int i = 0; i < 6; i++) { pics[i] = new Picture("bla"); @@ -710,7 +716,7 @@ public static Node getCubeMapCrossDebugViewWithMipMaps(TextureCubeMap cubeMap, A } /** - * initialize the Irradiancemap + * initialize the irradiance map * @param size the size of the map * @param imageFormat the format of the image * @return the initialized Irradiance map diff --git a/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java index 31c51c9714..3653c35ec3 100644 --- a/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java +++ b/jme3-core/src/main/java/com/jme3/environment/util/LightsDebugState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,157 +33,427 @@ import com.jme3.app.Application; import com.jme3.app.state.BaseAppState; -import com.jme3.light.*; +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.LightProbe; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.scene.control.BillboardControl; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Sphere; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; +import com.jme3.texture.Texture; + +import java.util.ArrayDeque; +import java.util.Iterator; import java.util.Map; +import java.util.WeakHashMap; +import java.util.function.Predicate; /** - * A debug state that will display LIght gizmos on screen. - * Still a wip and for now it only displays light probes. - * + * A debug state that visualizes different types of lights in the scene with gizmos. + * This state is useful for debugging light positions, ranges, and other properties. + * * @author nehon + * @author capdevon */ public class LightsDebugState extends BaseAppState { - private Node debugNode; - private final Map probeMapping = new HashMap(); - private final List garbage = new ArrayList(); - private Geometry debugGeom; - private Geometry debugBounds; + private static final String PROBE_GEOMETRY_NAME = "DebugProbeGeometry"; + private static final String PROBE_BOUNDS_NAME = "DebugProbeBounds"; + private static final String SPOT_LIGHT_INNER_RADIUS_NAME = "SpotLightInnerRadius"; + private static final String SPOT_LIGHT_OUTER_RADIUS_NAME = "SpotLightOuterRadius"; + private static final String SPOT_LIGHT_RADIUS_NAME = "RadiusNode"; + private static final String POINT_LIGHT_RADIUS_NAME = "PointLightRadius"; + private static final String LIGHT_DIR_ARROW_NAME = "LightDirection"; + + private final Map lightGizmoMap = new WeakHashMap<>(); + private final ArrayDeque lightDeque = new ArrayDeque<>(); + private Predicate lightFilter = x -> true; // Identity Function + + private ViewPort viewPort; + private AssetManager assetManager; private Material debugMaterial; - private float probeScale = 1.0f; - private Spatial scene = null; - private final List probes = new ArrayList(); + private Node debugNode; + private Spatial scene; // The scene whose lights will be debugged + + private boolean showOnTop = true; + private float lightProbeScale = 1.0f; + private final ColorRGBA debugColor = ColorRGBA.DarkGray; + private final Quaternion tempRotation = new Quaternion(); @Override protected void initialize(Application app) { - debugNode = new Node("Environment debug Node"); - Sphere s = new Sphere(16, 16, 0.15f); - debugGeom = new Geometry("debugEnvProbe", s); - debugMaterial = new Material(app.getAssetManager(), "Common/MatDefs/Misc/reflect.j3md"); - debugGeom.setMaterial(debugMaterial); - debugBounds = BoundingSphereDebug.createDebugSphere(app.getAssetManager()); + + viewPort = app.getRenderManager().createMainView("LightsDebugView", app.getCamera()); + viewPort.setClearFlags(false, showOnTop, true); + + assetManager = app.getAssetManager(); + debugNode = new Node("LightsDebugNode"); + + debugMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + debugMaterial.setColor("Color", debugColor); + debugMaterial.getAdditionalRenderState().setWireframe(true); + if (scene == null) { scene = app.getViewPort().getScenes().get(0); } } + private Spatial createBulb() { + Quad q = new Quad(0.5f, 0.5f); + Geometry lightBulb = new Geometry("LightBulb", q); + lightBulb.move(-q.getHeight() / 2f, -q.getWidth() / 2f, 0); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Texture tex = assetManager.loadTexture("Common/Textures/lightbulb32.png"); + mat.setTexture("ColorMap", tex); + mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + lightBulb.setMaterial(mat); + lightBulb.setQueueBucket(RenderQueue.Bucket.Transparent); + + Node billboard = new Node("Billboard"); + billboard.addControl(new BillboardControl()); + billboard.attachChild(lightBulb); + + return billboard; + } + + private Geometry createRadiusShape(String name, float dashSize) { + Geometry radius = Circle.createShape(assetManager, name); + Material mat = radius.getMaterial(); + mat.setColor("Color", debugColor); + mat.setFloat("DashSize", dashSize); + return radius; + } + + private Spatial createPointGizmo() { + Node gizmo = new Node("PointLightNode"); + gizmo.attachChild(createBulb()); + + Geometry radius = new Geometry(POINT_LIGHT_RADIUS_NAME, new BoundingSphereDebug()); + radius.setMaterial(debugMaterial); + gizmo.attachChild(radius); + + return gizmo; + } + + private Spatial createDirectionalGizmo() { + Node gizmo = new Node("DirectionalLightNode"); + gizmo.move(0, 5, 0); + gizmo.attachChild(createBulb()); + + Geometry arrow = new Geometry(LIGHT_DIR_ARROW_NAME, new Arrow(Vector3f.UNIT_Z.mult(5f))); + arrow.setMaterial(debugMaterial); + gizmo.attachChild(arrow); + + return gizmo; + } + + private Spatial createSpotGizmo() { + Node gizmo = new Node("SpotLightNode"); + gizmo.attachChild(createBulb()); + + Node radiusNode = new Node(SPOT_LIGHT_RADIUS_NAME); + gizmo.attachChild(radiusNode); + + Geometry inRadius = createRadiusShape(SPOT_LIGHT_INNER_RADIUS_NAME, 0.725f); + radiusNode.attachChild(inRadius); + + Geometry outRadius = createRadiusShape(SPOT_LIGHT_OUTER_RADIUS_NAME, 0.325f); + radiusNode.attachChild(outRadius); + + Geometry arrow = new Geometry(LIGHT_DIR_ARROW_NAME, new Arrow(Vector3f.UNIT_Z)); + arrow.setMaterial(debugMaterial); + gizmo.attachChild(arrow); + + return gizmo; + } + + private Spatial createLightProbeGizmo() { + Node gizmo = new Node("LightProbeNode"); + + Sphere sphere = new Sphere(32, 32, lightProbeScale); + Geometry probeGeom = new Geometry(PROBE_GEOMETRY_NAME, sphere); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/reflect.j3md"); + probeGeom.setMaterial(mat); + gizmo.attachChild(probeGeom); + + Geometry probeBounds = BoundingSphereDebug.createDebugSphere(assetManager); + probeBounds.setName(PROBE_BOUNDS_NAME); + gizmo.attachChild(probeBounds); + + return gizmo; + } + + /** + * Updates the light gizmos based on the current state of lights in the scene. + * This method is called every frame when the state is enabled. + * + * @param tpf The time per frame. + */ @Override public void update(float tpf) { - if(!isEnabled()){ - return; - } - updateLights(scene); + updateLightGizmos(scene); debugNode.updateLogicalState(tpf); + cleanUpRemovedLights(); + } + + /** + * Renders the debug gizmos onto the screen. + * + * @param rm The render manager. + */ + @Override + public void render(RenderManager rm) { debugNode.updateGeometricState(); - cleanProbes(); - } - - public void updateLights(Spatial scene) { - for (Light light : scene.getWorldLightList()) { - switch (light.getType()) { - - case Probe: - LightProbe probe = (LightProbe) light; - probes.add(probe); - Node n = probeMapping.get(probe); - if (n == null) { - n = new Node("DebugProbe"); - n.attachChild(debugGeom.clone(true)); - n.attachChild(debugBounds.clone(false)); - debugNode.attachChild(n); - probeMapping.put(probe, n); - } - Geometry probeGeom = ((Geometry) n.getChild(0)); - Material m = probeGeom.getMaterial(); - probeGeom.setLocalScale(probeScale); - if (probe.isReady()) { - m.setTexture("CubeMap", probe.getPrefilteredEnvMap()); - } - n.setLocalTranslation(probe.getPosition()); - n.getChild(1).setLocalScale(probe.getArea().getRadius()); - break; - default: - break; + } + + /** + * Recursively traverses the scene graph to find and update light gizmos. + * New gizmos are created for new lights, and existing gizmos are updated. + * + * @param spatial The current spatial to process for lights. + */ + private void updateLightGizmos(Spatial spatial) { + // Add or update gizmos for lights attached to the current spatial + for (Light light : spatial.getLocalLightList()) { + if (!lightFilter.test(light)) { + continue; + } + + lightDeque.add(light); + Spatial gizmo = lightGizmoMap.get(light); + + if (gizmo == null) { + gizmo = createLightGizmo(light); + if (gizmo != null) { + debugNode.attachChild(gizmo); + lightGizmoMap.put(light, gizmo); + updateGizmoProperties(light, gizmo); // Set initial properties + } + } else { + updateGizmoProperties(light, gizmo); } } - if( scene instanceof Node){ - Node n = (Node)scene; - for (Spatial spatial : n.getChildren()) { - updateLights(spatial); + + // Recursively call for children if it's a Node + if (spatial instanceof Node) { + Node node = (Node) spatial; + for (Spatial child : node.getChildren()) { + updateLightGizmos(child); } } } /** - * Set the scenes for which to render light gizmos. - * @param scene + * Creates a new gizmo spatial for a given light based on its type. + * + * @param light The light for which to create a gizmo. + * @return A spatial representing the gizmo, or null if the light type is not supported. */ - public void setScene(Spatial scene) { - this.scene = scene; + private Spatial createLightGizmo(Light light) { + switch (light.getType()) { + case Probe: + return createLightProbeGizmo(); + case Point: + return createPointGizmo(); + case Directional: + return createDirectionalGizmo(); + case Spot: + return createSpotGizmo(); + default: + // Unsupported light type + return null; + } } - private void cleanProbes() { - if (probes.size() != probeMapping.size()) { - for (LightProbe probe : probeMapping.keySet()) { - if (!probes.contains(probe)) { - garbage.add(probe); + /** + * Updates the visual properties and position of a light gizmo based on its corresponding light. + * + * @param light The light whose properties are used for updating the gizmo. + * @param gizmo The spatial representing the light gizmo. + */ + private void updateGizmoProperties(Light light, Spatial gizmo) { + Node lightNode = (Node) gizmo; + + switch (light.getType()) { + case Probe: + LightProbe probe = (LightProbe) light; + Geometry probeGeom = (Geometry) lightNode.getChild(PROBE_GEOMETRY_NAME); + Geometry probeBounds = (Geometry) lightNode.getChild(PROBE_BOUNDS_NAME); + + // Update texture if probe is ready + if (probe.isReady()) { + Material mat = probeGeom.getMaterial(); + if (mat.getTextureParam("CubeMap") == null) { + mat.setTexture("CubeMap", probe.getPrefilteredEnvMap()); + } } - } - for (LightProbe probe : garbage) { - probeMapping.remove(probe); - } - garbage.clear(); - probes.clear(); + probeGeom.setLocalScale(lightProbeScale); + probeBounds.setLocalScale(probe.getArea().getRadius()); + gizmo.setLocalTranslation(probe.getPosition()); + break; + + case Point: + PointLight pl = (PointLight) light; + Geometry radius = (Geometry) lightNode.getChild(POINT_LIGHT_RADIUS_NAME); + radius.setLocalScale(pl.getRadius()); + gizmo.setLocalTranslation(pl.getPosition()); + break; + + case Spot: + SpotLight sl = (SpotLight) light; + gizmo.setLocalTranslation(sl.getPosition()); + + tempRotation.lookAt(sl.getDirection(), Vector3f.UNIT_Y); + gizmo.setLocalRotation(tempRotation); + + float spotRange = sl.getSpotRange(); + float innerAngle = sl.getSpotInnerAngle(); + float outerAngle = sl.getSpotOuterAngle(); + float innerRadius = spotRange * FastMath.tan(innerAngle); + float outerRadius = spotRange * FastMath.tan(outerAngle); + + lightNode.getChild(SPOT_LIGHT_INNER_RADIUS_NAME).setLocalScale(innerRadius); + lightNode.getChild(SPOT_LIGHT_OUTER_RADIUS_NAME).setLocalScale(outerRadius); + lightNode.getChild(SPOT_LIGHT_RADIUS_NAME).setLocalTranslation(0, 0, spotRange); + lightNode.getChild(LIGHT_DIR_ARROW_NAME).setLocalScale(spotRange); + break; + + case Directional: + DirectionalLight dl = (DirectionalLight) light; + tempRotation.lookAt(dl.getDirection(), Vector3f.UNIT_Y); + gizmo.setLocalRotation(tempRotation); + break; + + default: + // Unsupported light type + break; } } - @Override - public void render(RenderManager rm) { - if(!isEnabled()){ - return; + /** + * Cleans up gizmos for lights that have been removed from the scene. + */ + private void cleanUpRemovedLights() { + + Iterator> iterator = lightGizmoMap.entrySet().iterator(); + + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + Light light = entry.getKey(); + + if (!lightDeque.contains(light)) { + Spatial gizmo = entry.getValue(); + gizmo.removeFromParent(); + iterator.remove(); + } } - rm.renderScene(debugNode, getApplication().getViewPort()); + + lightDeque.clear(); + } + + /** + * Sets the scene for which to render light gizmos. + * If no scene is set, it defaults to the first scene in the viewport. + * + * @param scene The root of the desired scene. + */ + public void setScene(Spatial scene) { + this.scene = scene; + // Clear existing gizmos when the scene changes to avoid displaying gizmos from the old scene + debugNode.detachAllChildren(); + lightGizmoMap.clear(); + lightDeque.clear(); + } + + /** + * Returns the current scale of the light probe's debug sphere. + * + * @return The scale factor. + */ + public float getLightProbeScale() { + return lightProbeScale; } + /** + * Sets the scale of the light probe's debug sphere. + * + * @param scale The scale factor (default is 1.0). + */ + public void setLightProbeScale(float scale) { + this.lightProbeScale = scale; + } /** - * returns the scale of the probe's debug sphere - * @return the scale factor + * Checks if the light debug gizmos are set to always + * render on top of other scene geometry. + * + * @return true if gizmos always render on top, false otherwise. */ - public float getProbeScale() { - return probeScale; + public boolean isShowOnTop() { + return showOnTop; } /** - * sets the scale of the probe's debug sphere - * @param probeScale + * Sets whether light debug gizmos should always + * render on top of other scene geometry. + * + * @param showOnTop true to always show gizmos on top, false to respect depth. */ - public void setProbeScale(float probeScale) { - this.probeScale = probeScale; + public void setShowOnTop(boolean showOnTop) { + this.showOnTop = showOnTop; + if (viewPort != null) { + viewPort.setClearDepth(showOnTop); + } } + /** + * Sets a filter to control which lights are displayed by the debug state. + * By default, no filter is applied, meaning all lights are displayed. + * + * @param lightFilter A {@link Predicate} that tests a {@link Light} object. + */ + public void setLightFilter(Predicate lightFilter) { + this.lightFilter = lightFilter; + } + + /** + * Cleans up resources when the app state is detached. + * + * @param app The application instance. + */ @Override protected void cleanup(Application app) { - + debugNode.detachAllChildren(); + lightGizmoMap.clear(); + lightDeque.clear(); + debugMaterial = null; + app.getRenderManager().removeMainView(viewPort); } @Override protected void onEnable() { - + viewPort.attachScene(debugNode); } @Override protected void onDisable() { - + viewPort.detachScene(debugNode); } } diff --git a/jme3-core/src/main/java/com/jme3/environment/util/package-info.java b/jme3-core/src/main/java/com/jme3/environment/util/package-info.java new file mode 100644 index 0000000000..a642f3420d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/environment/util/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * utilities for generating lighting environments + */ +package com.jme3.environment.util; diff --git a/jme3-core/src/main/java/com/jme3/export/FormatVersion.java b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java index 053b5066d2..2e34f2365b 100644 --- a/jme3-core/src/main/java/com/jme3/export/FormatVersion.java +++ b/jme3-core/src/main/java/com/jme3/export/FormatVersion.java @@ -33,21 +33,28 @@ /** * Specifies the version of the format for jME3 object (j3o) files. - * + * * @author Kirill Vainer */ public final class FormatVersion { /** - * Version number of the format + * Version number of the format. + *

    + * Changes for each version: + *

      + *
    1. Undocumented + *
    2. Undocumented + *
    3. XML prefixes "jme-" to all key names + *
    */ - public static final int VERSION = 2; - + public static final int VERSION = 3; + /** - * Signature of the format. Currently "JME3" as ASCII + * Signature of the format: currently, "JME3" as ASCII. */ public static final int SIGNATURE = 0x4A4D4533; - - private FormatVersion(){ + + private FormatVersion() { } } diff --git a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java index b8c3abc605..f93cb2560d 100644 --- a/jme3-core/src/main/java/com/jme3/export/JmeExporter.java +++ b/jme3-core/src/main/java/com/jme3/export/JmeExporter.java @@ -51,14 +51,29 @@ public interface JmeExporter { public void save(Savable object, OutputStream f) throws IOException; /** - * Export the {@link Savable} to a file. - * + * Export the {@link Savable} to a file. If the path to the file doesn't exist, the directories are + * made. + * * @param object The savable to export * @param f The file to export to * @throws IOException If an io exception occurs during export */ - public void save(Savable object, File f) throws IOException; - + default void save(Savable object, File f) throws IOException { + save(object, f, true); + } + + /** + * Export the {@link Savable} to a file. If the path to the file doesn't exist, the parent + * directories can be created if the createDirectories flag is true. If the path does + * not exist and createDirectories is false, then an exception is thrown. + * + * @param object The savable to export + * @param f The file to export to + * @param createDirectories flag to indicate if the directories should be created + * @throws IOException If an io exception occurs during export + */ + public void save(Savable object, File f, boolean createDirectories) throws IOException; + /** * Returns the {@link OutputCapsule} for the given savable object. * diff --git a/jme3-core/src/main/java/com/jme3/export/NullSavable.java b/jme3-core/src/main/java/com/jme3/export/NullSavable.java index a57dcc7012..e3384a4ca7 100644 --- a/jme3-core/src/main/java/com/jme3/export/NullSavable.java +++ b/jme3-core/src/main/java/com/jme3/export/NullSavable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,8 +41,10 @@ * @author Kirill Vainer */ public class NullSavable implements Savable { + @Override public void write(JmeExporter ex) throws IOException { } + @Override public void read(JmeImporter im) throws IOException { } } diff --git a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java index 2e206a416b..79364c96ea 100644 --- a/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java +++ b/jme3-core/src/main/java/com/jme3/export/SavableClassUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,12 +57,12 @@ */ public class SavableClassUtil { - private final static HashMap CLASS_REMAPPINGS = new HashMap<>(); - - private static void addRemapping(String oldClass, Class newClass){ + private static final HashMap CLASS_REMAPPINGS = new HashMap<>(); + + private static void addRemapping(String oldClass, Class newClass) { CLASS_REMAPPINGS.put(oldClass, newClass.getName()); } - + static { addRemapping("com.jme3.effect.EmitterSphereShape", EmitterSphereShape.class); addRemapping("com.jme3.effect.EmitterBoxShape", EmitterBoxShape.class); @@ -73,9 +73,19 @@ private static void addRemapping(String oldClass, Class newCl addRemapping("com.jme3.material.Material$MatParamTexture", MatParamTexture.class); addRemapping("com.jme3.animation.BoneAnimation", Animation.class); addRemapping("com.jme3.animation.SpatialAnimation", Animation.class); + + // Even though we no longer include Blender loading as part of the engine, + // we leave this line in so that old j3os will still work and just not + // load that data. -pspeed:2020-04-19 addRemapping("com.jme3.scene.plugins.blender.objects.Properties", NullSavable.class); } - + + /** + * A private constructor to inhibit instantiation of this class. + */ + private SavableClassUtil() { + } + private static String remapClass(String className) throws ClassNotFoundException { String result = CLASS_REMAPPINGS.get(className); if (result == null) { @@ -84,34 +94,36 @@ private static String remapClass(String className) throws ClassNotFoundException return result; } } - - public static boolean isImplementingSavable(Class clazz){ + + public static boolean isImplementingSavable(Class clazz) { boolean result = Savable.class.isAssignableFrom(clazz); return result; } - public static int[] getSavableVersions(Class clazz) throws IOException{ - ArrayList versionList = new ArrayList(); + @SuppressWarnings("unchecked") + public static int[] getSavableVersions(Class clazz) throws IOException { + ArrayList versionList = new ArrayList<>(); Class superclass = clazz; do { versionList.add(getSavableVersion(superclass)); superclass = superclass.getSuperclass(); } while (superclass != null && SavableClassUtil.isImplementingSavable(superclass)); - + int[] versions = new int[versionList.size()]; - for (int i = 0; i < versionList.size(); i++){ + for (int i = 0; i < versionList.size(); i++) { versions[i] = versionList.get(i); } return versions; } - - public static int getSavableVersion(Class clazz) throws IOException{ + + @SuppressWarnings("unchecked") + public static int getSavableVersion(Class clazz) throws IOException { try { Field field = clazz.getField("SAVABLE_VERSION"); Class declaringClass = (Class) field.getDeclaringClass(); - if (declaringClass == clazz){ - return field.getInt(null); - }else{ + if (declaringClass == clazz) { + return field.getInt(null); + } else { return 0; // This class doesn't declare this field, e.g. version == 0 } } catch (IllegalAccessException ex) { @@ -124,46 +136,47 @@ public static int getSavableVersion(Class clazz) throws IOExc return 0; // not using versions } } - - public static int getSavedSavableVersion(Object savable, Class desiredClass, int[] versions, int formatVersion){ + + public static int getSavedSavableVersion(Object savable, + Class desiredClass, int[] versions, int formatVersion) { Class thisClass = savable.getClass(); int count = 0; - + while (thisClass != desiredClass) { thisClass = thisClass.getSuperclass(); - if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)){ - count ++; - }else{ + if (thisClass != null && SavableClassUtil.isImplementingSavable(thisClass)) { + count++; + } else { break; } } - if (thisClass == null){ - throw new IllegalArgumentException(savable.getClass().getName() + - " does not extend " + + if (thisClass == null) { + throw new IllegalArgumentException(savable.getClass().getName() + + " does not extend " + desiredClass.getName() + "!"); - }else if (count >= versions.length){ - if (formatVersion <= 1){ + } else if (count >= versions.length) { + if (formatVersion <= 1) { return 0; // for buggy versions of j3o - }else{ - throw new IllegalArgumentException(savable.getClass().getName() + + } else { + throw new IllegalArgumentException(savable.getClass().getName() + " cannot access version of " + - desiredClass.getName() + + desiredClass.getName() + " because it doesn't implement Savable"); } } return versions[count]; } - + /** * fromName creates a new Savable from the provided class name. First registered modules * are checked to handle special cases, if the modules do not handle the class name, the - * class is instantiated directly. + * class is instantiated directly. * @param className the class name to create. * @return the Savable instance of the class. * @throws InstantiationException thrown if the class does not have an empty constructor. - * @throws IllegalAccessException thrown if the class is not accessable. - * @throws java.lang.reflect.InvocationTargetException + * @throws IllegalAccessException thrown if the class is not accessible. + * @throws InvocationTargetException if the underlying constructor throws an exception * @throws ClassNotFoundException thrown if the class name is not in the classpath. */ public static Savable fromName(String className) @@ -178,16 +191,20 @@ public static Savable fromName(String className) } catch (InvocationTargetException | InstantiationException e) { Logger.getLogger(SavableClassUtil.class.getName()).log( Level.SEVERE, "Could not access constructor of class ''{0}" + "''! \n" - + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", className); + + "Some types need to have the BinaryImporter set up in a special way. Please double-check the setup.", className); throw e; } catch (IllegalAccessException e) { Logger.getLogger(SavableClassUtil.class.getName()).log( Level.SEVERE, "{0} \n" - + "Some types need to have the BinaryImporter set up in a special way. Please doublecheck the setup.", e.getMessage()); + + "Some types need to have the BinaryImporter set up in a special way. Please double-check the setup.", e.getMessage()); throw e; } } + /** + * @deprecated use {@link #fromName(java.lang.String)} instead + */ + @Deprecated public static Savable fromName(String className, List loaders) throws InstantiationException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, ClassNotFoundException, IOException { @@ -206,8 +223,7 @@ public static Savable fromName(String className, List loaders) thro } try { return (Savable) loadedClass.newInstance(); - } catch (InstantiationException e) { - } catch (IllegalAccessException e) { + } catch (InstantiationException | IllegalAccessException e) { } } } @@ -221,6 +237,7 @@ public static Savable fromName(String className, List loaders) thro * * @return the pre-existing constructor (not null) */ + @SuppressWarnings("unchecked") private static Constructor findNoArgConstructor(String className) throws ClassNotFoundException, InstantiationException { Class clazz = Class.forName(className); diff --git a/jme3-core/src/main/java/com/jme3/export/package-info.java b/jme3-core/src/main/java/com/jme3/export/package-info.java new file mode 100644 index 0000000000..daf436d128 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/export/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * load and save assets in jMonkeyEngine's native formats, including J3O + */ +package com.jme3.export; diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java index 1fb13ab934..f398e71d03 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,11 +48,11 @@ public class BitmapCharacter implements Savable, Cloneable { private int xOffset; private int yOffset; private int xAdvance; - private IntMap kerning = new IntMap(); + private IntMap kerning = new IntMap<>(); private int page; - + public BitmapCharacter() {} - + public BitmapCharacter(char c) { this.c = c; } @@ -131,20 +131,20 @@ public void setPage(int page) { public int getPage() { return page; } - + public char getChar() { return c; } - + public void setChar(char c) { this.c = c; } - public void addKerning(int second, int amount){ + public void addKerning(int second, int amount) { kerning.put(second, amount); } - public int getKerning(int second){ + public int getKerning(int second) { Integer i = kerning.get(second); if (i == null) return 0; @@ -152,6 +152,7 @@ public int getKerning(int second){ return i.intValue(); } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(c, "c", 0); @@ -167,7 +168,7 @@ public void write(JmeExporter ex) throws IOException { int[] amounts = new int[seconds.length]; int i = 0; - for (Entry entry : kerning){ + for (Entry entry : kerning) { seconds[i] = entry.getKey(); amounts[i] = entry.getValue(); i++; @@ -177,6 +178,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(amounts, "amounts", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); c = (char) ic.readInt("c", 0); @@ -191,7 +193,7 @@ public void read(JmeImporter im) throws IOException { int[] seconds = ic.readIntArray("seconds", null); int[] amounts = ic.readIntArray("amounts", null); - for (int i = 0; i < seconds.length; i++){ + for (int i = 0; i < seconds.length; i++) { kerning.put(seconds[i], amounts[i]); } } diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java index f1c6f644ed..13b16785d7 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapCharacterSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ public class BitmapCharacterSet implements Savable { private int renderedSize; private int width; private int height; - private IntMap> characters; + private final IntMap> characters; private int pageSize; @Override @@ -73,7 +73,7 @@ protected void writeCharset(OutputCapsule oc, int style, IntMap short[] indexes = new short[size]; BitmapCharacter[] chars = new BitmapCharacter[size]; int i = 0; - for (Entry chr : charset){ + for (Entry chr : charset) { indexes[i] = (short) chr.getKey(); chars[i] = chr.getValue(); i++; @@ -100,11 +100,11 @@ public void read(JmeImporter im) throws IOException { } private IntMap readCharset(InputCapsule ic, int style) throws IOException { - IntMap charset = new IntMap(); + IntMap charset = new IntMap<>(); short[] indexes = ic.readShortArray("indexes"+style, null); Savable[] chars = ic.readSavableArray("chars"+style, null); - for (int i = 0; i < indexes.length; i++){ + for (int i = 0; i < indexes.length; i++) { int index = indexes[i] & 0xFFFF; BitmapCharacter chr = (BitmapCharacter) chars[i]; charset.put(index, chr); @@ -116,11 +116,11 @@ public BitmapCharacterSet() { characters = new IntMap>(); } - public BitmapCharacter getCharacter(int index){ + public BitmapCharacter getCharacter(int index) { return getCharacter(index, 0); } - - public BitmapCharacter getCharacter(int index, int style){ + + public BitmapCharacter getCharacter(int index, int style) { IntMap map = getCharacterSet(style); return map.get(index); } @@ -132,7 +132,7 @@ private IntMap getCharacterSet(int style) { return characters.get(style); } - public void addCharacter(int index, BitmapCharacter ch){ + public void addCharacter(int index, BitmapCharacter ch) { getCharacterSet(0).put(index, ch); } @@ -175,7 +175,7 @@ public int getHeight() { public void setHeight(int height) { this.height = height; } - + /** * Merge two fonts. * If two font have the same style, merge will fail. @@ -197,7 +197,7 @@ public void merge(BitmapCharacterSet styleSet) { if (old != null) { throw new RuntimeException("Can't override old style"); } - + for (Entry charEntry : charset) { BitmapCharacter ch = charEntry.getValue(); ch.setPage(ch.getPage() + this.pageSize); diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapFont.java b/jme3-core/src/main/java/com/jme3/font/BitmapFont.java index 694278057d..867206c2a7 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapFont.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,109 +31,210 @@ */ package com.jme3.font; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.material.Material; + import java.io.IOException; /** - * Represents a font within jME that is generated with the AngelCode Bitmap Font Generator + * Represents a font loaded from a bitmap font definition + * (e.g., generated by AngelCode Bitmap Font Generator). + * It manages character sets, font pages (textures), and provides utilities for text measurement and rendering. + * * @author dhdd + * @author Yonghoon */ public class BitmapFont implements Savable { /** * Specifies horizontal alignment for text. - * - * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align) + * + * @see BitmapText#setAlignment(com.jme3.font.BitmapFont.Align) */ public enum Align { - + /** * Align text on the left of the text block */ - Left, - + Left, + /** * Align text in the center of the text block */ - Center, - + Center, + /** * Align text on the right of the text block */ Right } - + /** * Specifies vertical alignment for text. - * - * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign) + * + * @see BitmapText#setVerticalAlignment(com.jme3.font.BitmapFont.VAlign) */ public enum VAlign { /** * Align text on the top of the text block */ - Top, - + Top, + /** * Align text in the center of the text block */ - Center, - + Center, + /** * Align text at the bottom of the text block */ Bottom } + // The character set containing definitions for each character (glyph) in the font. private BitmapCharacterSet charSet; + // An array of materials, where each material corresponds to a font page (texture). private Material[] pages; + // Indicates whether this font is designed for right-to-left (RTL) text rendering. + private boolean rightToLeft = false; + // For cursive bitmap fonts in which letter shape is determined by the adjacent glyphs. + private GlyphParser glyphParser; + /** + * Creates a new instance of `BitmapFont`. + * This constructor is primarily used for deserialization. + */ public BitmapFont() { } - public BitmapText createLabel(String content){ + /** + * Creates a new {@link BitmapText} instance initialized with this font. + * The label's size will be set to the font's rendered size, and its text content + * will be set to the provided string. + * + * @param content The initial text content for the label. + * @return A new {@link BitmapText} instance. + */ + public BitmapText createLabel(String content) { BitmapText label = new BitmapText(this); label.setSize(getCharSet().getRenderedSize()); label.setText(content); return label; } - public float getPreferredSize(){ + /** + * Checks if this font is configured for right-to-left (RTL) text rendering. + * + * @return true if this is a right-to-left font, otherwise false (default is left-to-right). + */ + public boolean isRightToLeft() { + return rightToLeft; + } + + /** + * Specifies whether this font should be rendered as right-to-left (RTL). + * By default, it is set to false (left-to-right). + * + * @param rightToLeft true to enable right-to-left rendering; false for left-to-right. + */ + public void setRightToLeft(boolean rightToLeft) { + this.rightToLeft = rightToLeft; + } + + /** + * Returns the preferred size of the font, which is typically its rendered size. + * + * @return The preferred size of the font in font units. + */ + public float getPreferredSize() { return getCharSet().getRenderedSize(); } + /** + * Sets the character set for this font. The character set contains + * information about individual glyphs, their positions, and kerning data. + * + * @param charSet The {@link BitmapCharacterSet} to associate with this font. + */ public void setCharSet(BitmapCharacterSet charSet) { this.charSet = charSet; } + /** + * Sets the array of materials (font pages) for this font. Each material + * corresponds to a texture page containing character bitmaps. + * The character set's page size is also updated based on the number of pages. + * + * @param pages An array of {@link Material} objects representing the font pages. + */ public void setPages(Material[] pages) { this.pages = pages; charSet.setPageSize(pages.length); } + /** + * Retrieves a specific font page material by its index. + * + * @param index The index of the font page to retrieve. + * @return The {@link Material} for the specified font page. + * @throws IndexOutOfBoundsException if the index is out of bounds. + */ public Material getPage(int index) { return pages[index]; } + /** + * Returns the total number of font pages (materials) associated with this font. + * + * @return The number of font pages. + */ public int getPageSize() { return pages.length; } + /** + * Retrieves the character set associated with this font. + * + * @return The {@link BitmapCharacterSet} of this font. + */ public BitmapCharacterSet getCharSet() { return charSet; } - + + /** + * For cursive fonts a GlyphParser needs to be specified which is used + * to determine glyph shape by the adjacent glyphs. If nothing is set, + * all glyphs will be rendered isolated. + * + * @param glyphParser the desired parser (alias created) or null for none + * (default=null) + */ + public void setGlyphParser(GlyphParser glyphParser) { + this.glyphParser = glyphParser; + } + + /** + * @return The GlyphParser set on the font, or null if it has no glyph parser. + */ + public GlyphParser getGlyphParser() { + return glyphParser; + } + /** * Gets the line height of a StringBlock. - * @param sb + * + * @param sb the block to measure (not null, unaffected) * @return the line height */ - public float getLineHeight(StringBlock sb) { + float getLineHeight(StringBlock sb) { return charSet.getLineHeight() * (sb.getSize() / charSet.getRenderedSize()); } - public float getCharacterAdvance(char curChar, char nextChar, float size){ + public float getCharacterAdvance(char curChar, char nextChar, float size) { BitmapCharacter c = charSet.getCharacter(curChar); if (c == null) return 0f; @@ -150,26 +251,22 @@ private int findKerningAmount(int newLineLastChar, int nextChar) { return c.getKerning(nextChar); } - @Override - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); - oc.write(charSet, "charSet", null); - oc.write(pages, "pages", null); - } - - @Override - public void read(JmeImporter im) throws IOException { - InputCapsule ic = im.getCapsule(this); - charSet = (BitmapCharacterSet) ic.readSavable("charSet", null); - Savable[] pagesSavable = ic.readSavableArray("pages", null); - pages = new Material[pagesSavable.length]; - System.arraycopy(pagesSavable, 0, pages, 0, pages.length); - } - - public float getLineWidth(CharSequence text){ - + /** + * Calculates the width of the given text in font units. + * This method accounts for character advances, kerning, and line breaks. + * It also attempts to skip custom color tags (e.g., "\#RRGGBB#" or "\#RRGGBBAA#") + * based on a specific format. + *

    + * Note: This method calculates width in "font units" where the font's + * {@link BitmapCharacterSet#getRenderedSize() rendered size} is the base. + * Actual pixel scaling for display is typically handled by {@link BitmapText}. + * + * @param text The text to measure. + * @return The maximum line width of the text in font units. + */ + public float getLineWidth(CharSequence text) { // This method will probably always be a bit of a maintenance - // nightmare since it basis its calculation on a different + // nightmare since it bases its calculation on a different // routine than the Letters class. The ideal situation would // be to abstract out letter position and size into its own // class that both BitmapFont and Letters could use for @@ -178,8 +275,8 @@ public float getLineWidth(CharSequence text){ // than Letters does with the same text then it might be better // just to create a Letters object for the sole purpose of // getting a text size. It's less efficient but at least it - // would be accurate. - + // would be accurate. + // And here I am mucking around in here again... // // A font character has a few values that are pertinent to the @@ -190,65 +287,86 @@ public float getLineWidth(CharSequence text){ // // The way BitmapText ultimately works is that the first character // starts with xOffset included (ie: it is rendered at -xOffset). - // Its xAdvance is wider to accomodate that initial offset. + // Its xAdvance is wider to accommodate that initial offset. // The cursor position is advanced by xAdvance each time. // // So, a width should be calculated in a similar way. Start with // -xOffset + xAdvance for the first character and then each subsequent // character is just xAdvance more 'width'. - // + // // The kerning amount from one character to the next affects the - // cursor position of that next character and thus the ultimate width + // cursor position of that next character and thus the ultimate width // and so must be factored in also. - + float lineWidth = 0f; float maxLineWidth = 0f; char lastChar = 0; boolean firstCharOfLine = true; // float sizeScale = (float) block.getSize() / charSet.getRenderedSize(); float sizeScale = 1f; - for (int i = 0; i < text.length(); i++){ - char theChar = text.charAt(i); - if (theChar == '\n'){ + + // Use GlyphParser if available for complex script shaping (e.g., cursive fonts). + CharSequence processedText = glyphParser != null ? glyphParser.parse(text) : text; + + for (int i = 0; i < processedText.length(); i++) { + char currChar = processedText.charAt(i); + if (currChar == '\n') { maxLineWidth = Math.max(maxLineWidth, lineWidth); lineWidth = 0f; firstCharOfLine = true; continue; } - BitmapCharacter c = charSet.getCharacter((int) theChar); - if (c != null){ - if (theChar == '\\' && i scale it with Font.getPreferredSize()/BitMaptext.getSize() return letters.getTotalWidth(); } /** - * @return line count + * Returns the number of lines the text currently occupies. + * + * @return The total number of lines. */ public int getLineCount() { if (needRefresh) { @@ -280,13 +326,21 @@ public int getLineCount() { return block.getLineCount(); } + /** + * Returns the current line wrapping mode set for this text. + * + * @return The {@link LineWrapMode} enum value. + */ public LineWrapMode getLineWrapMode() { return block.getLineWrapMode(); } /** - * Set horizontal alignment. Applicable only when text bound is set. - * @param align + * Sets the horizontal alignment for the text within its bounding box. + * This is only applicable if a text bounding box has been set using {@link #setBox(Rectangle)}. + * + * @param align The desired horizontal alignment (e.g., {@link Align#Left}, {@link Align#Center}, {@link Align#Right}). + * @throws RuntimeException If a bounding box is not set and `align` is not `Align.Left`. */ public void setAlignment(BitmapFont.Align align) { if (block.getTextBox() == null && align != Align.Left) { @@ -298,8 +352,11 @@ public void setAlignment(BitmapFont.Align align) { } /** - * Set vertical alignment. Applicable only when text bound is set. - * @param align + * Sets the vertical alignment for the text within its bounding box. + * This is only applicable if a text bounding box has been set using {@link #setBox(Rectangle)}. + * + * @param align The desired vertical alignment (e.g., {@link VAlign#Top}, {@link VAlign#Center}, {@link VAlign#Bottom}). + * @throws RuntimeException If a bounding box is not set and `align` is not `VAlign.Top`. */ public void setVerticalAlignment(BitmapFont.VAlign align) { if (block.getTextBox() == null && align != VAlign.Top) { @@ -310,28 +367,42 @@ public void setVerticalAlignment(BitmapFont.VAlign align) { needRefresh = true; } + /** + * Returns the current horizontal alignment set for the text. + * + * @return The current {@link Align} value. + */ public BitmapFont.Align getAlignment() { return block.getAlignment(); } + /** + * Returns the current vertical alignment set for the text. + * + * @return The current {@link VAlign} value. + */ public BitmapFont.VAlign getVerticalAlignment() { return block.getVerticalAlignment(); } /** - * Set the font style of substring. If font doesn't contain style, default style is used - * @param start start index to set style. inclusive. - * @param end end index to set style. EXCLUSIVE. - * @param style + * Sets the font style for a specific substring of the text. + * If the font does not contain the specified style, the default style will be used. + * + * @param start The starting index of the substring (inclusive). + * @param end The ending index of the substring (exclusive). + * @param style The integer style identifier to apply. */ public void setStyle(int start, int end, int style) { letters.setStyle(start, end, style); } /** - * Set the font style of substring. If font doesn't contain style, default style is applied - * @param regexp regular expression - * @param style + * Sets the font style for all substrings matching a given regular expression. + * If the font does not contain the specified style, the default style will be used. + * + * @param regexp The regular expression string to match against the text. + * @param style The integer style identifier to apply. */ public void setStyle(String regexp, int style) { Pattern p = Pattern.compile(regexp); @@ -342,10 +413,11 @@ public void setStyle(String regexp, int style) { } /** - * Set the color of substring. - * @param start start index to set style. inclusive. - * @param end end index to set style. EXCLUSIVE. - * @param color + * Sets the color for a specific substring of the text. + * + * @param start The starting index of the substring (inclusive). + * @param end The ending index of the substring (exclusive). + * @param color The desired {@link ColorRGBA} to apply to the substring. */ public void setColor(int start, int end, ColorRGBA color) { letters.setColor(start, end, color); @@ -354,9 +426,10 @@ public void setColor(int start, int end, ColorRGBA color) { } /** - * Set the color of substring. - * @param regexp regular expression - * @param color + * Sets the color for all substrings matching a given regular expression. + * + * @param regexp The regular expression string to match against the text. + * @param color The desired {@link ColorRGBA} to apply. */ public void setColor(String regexp, ColorRGBA color) { Pattern p = Pattern.compile(regexp); @@ -369,7 +442,10 @@ public void setColor(String regexp, ColorRGBA color) { } /** - * @param tabs tab positions + * Sets custom tab stop positions for the text. + * Tab characters (`\t`) will align to these specified positions. + * + * @param tabs An array of float values representing the horizontal tab stop positions. */ public void setTabPosition(float... tabs) { block.setTabPosition(tabs); @@ -378,8 +454,10 @@ public void setTabPosition(float... tabs) { } /** - * used for the tabs over the last tab position. - * @param width tab size + * Sets the default width for tabs that extend beyond the last defined tab position. + * This value is used if a tab character is encountered after all custom tab stops have been passed. + * + * @param width The default width for tabs in font units. */ public void setTabWidth(float width) { block.setTabWidth(width); @@ -388,9 +466,10 @@ public void setTabWidth(float width) { } /** - * for setLineWrapType(LineWrapType.NoWrap), - * set the last character when the text exceeds the bound. - * @param c + * When {@link LineWrapMode#NoWrap} is used and the text exceeds the bounding box, + * this character will be appended to indicate truncation (e.g., '...'). + * + * @param c The character to use as the ellipsis. */ public void setEllipsisChar(char c) { block.setEllipsisChar(c); @@ -399,12 +478,18 @@ public void setEllipsisChar(char c) { } /** - * Available only when bounding is set. setBox() method call is needed in advance. - * true when - * @param wrap NoWrap : Letters over the text bound is not shown. the last character is set to '...'(0x2026) - * Character: Character is split at the end of the line. - * Word : Word is split at the end of the line. - * Clip : The text is hard-clipped at the border including showing only a partial letter if it goes beyond the text bound. + * Sets the line wrapping mode for the text. This is only applicable when + * a text bounding box has been set using {@link #setBox(Rectangle)}. + * + * @param wrap The desired {@link LineWrapMode}: + *

      + *
    • {@link LineWrapMode#NoWrap}: Letters exceeding the text bound are not shown. + * The last visible character might be replaced by an ellipsis character + * (set via {@link #setEllipsisChar(char)}).
    • + *
    • {@link LineWrapMode#Character}: Text is split at the end of the line, even in the middle of a word.
    • + *
    • {@link LineWrapMode#Word}: Words are split at the end of the line.
    • + *
    • {@link LineWrapMode#Clip}: The text is hard-clipped at the border, potentially showing only a partial letter.
    • + *
    */ public void setLineWrapMode(LineWrapMode wrap) { if (block.getLineWrapMode() != wrap) { @@ -422,36 +507,38 @@ public void updateLogicalState(float tpf) { } } + /** + * Assembles the text by generating the quad list (character positions and sizes) + * and then populating the vertex buffers of each `BitmapTextPage`. + * This method is called internally when `needRefresh` is true. + */ private void assemble() { - // first generate quadlist + // First, generate or update the list of letter quads + // based on current text and layout properties. letters.update(); - for (int i = 0; i < textPages.length; i++) { - textPages[i].assemble(letters); + // Then, for each font page, assemble its mesh data from the generated quads. + for (BitmapTextPage textPage : textPages) { + textPage.assemble(letters); } needRefresh = false; } - private ColorRGBA getColor( Material mat, String name ) { - MatParam mp = mat.getParam(name); - if( mp == null ) { - return null; - } - return (ColorRGBA)mp.getValue(); - } - + /** + * Renders the `BitmapText` spatial. This method iterates through each + * `BitmapTextPage`, sets its texture, and renders it using the provided + * `RenderManager`. + * + * @param rm The `RenderManager` responsible for drawing. + * @param color The base color to apply during rendering. Note that colors + * set per-substring will override this for those parts. + */ public void render(RenderManager rm, ColorRGBA color) { for (BitmapTextPage page : textPages) { Material mat = page.getMaterial(); mat.setTexture("ColorMap", page.getTexture()); - //ColorRGBA original = getColor(mat, "Color"); - //mat.setColor("Color", color); + // mat.setColor("Color", color); // If the material supports a "Color" parameter mat.render(page, rm); - - //if( original == null ) { - // mat.clearParam("Color"); - //} else { - // mat.setColor("Color", original); - //} } } + } diff --git a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java index 104e059843..c4ac9b26f0 100644 --- a/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java +++ b/jme3-core/src/main/java/com/jme3/font/BitmapTextPage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,7 +56,7 @@ class BitmapTextPage extends Geometry { private final byte[] color; private final int page; private final Texture2D texture; - private final LinkedList pageQuads = new LinkedList(); + private final LinkedList pageQuads = new LinkedList<>(); BitmapTextPage(BitmapFont font, boolean arrayBased, int page) { super("BitmapFont", new Mesh()); @@ -93,10 +93,10 @@ class BitmapTextPage extends Geometry { * - Skye (sbook) */ if (arrayBased) { - pos = new float[4 * 3]; // 4 verticies * 3 floats - tc = new float[4 * 2]; // 4 verticies * 2 floats + pos = new float[4 * 3]; // 4 vertices * 3 floats + tc = new float[4 * 2]; // 4 vertices * 2 floats idx = new short[2 * 3]; // 2 triangles * 3 indices - color = new byte[4 * 4]; // 4 verticies * 4 bytes + color = new byte[4 * 4]; // 4 vertices * 4 bytes } else { pos = null; tc = null; @@ -128,7 +128,7 @@ public BitmapTextPage clone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { Mesh originalMesh = this.mesh; @@ -138,7 +138,7 @@ public void cloneFields( Cloner cloner, Object original ) { // BitmapText instances will clobber one another. // But if we were already deep cloning meshes then we don't // want to do it again... so we'll check first. - if( this.mesh == originalMesh ) { + if (this.mesh == originalMesh) { this.mesh = mesh.deepClone(); } } @@ -146,7 +146,7 @@ public void cloneFields( Cloner cloner, Object original ) { // Here is where one might add JmeCloneable related stuff except // the old clone() method doesn't actually bother to clone anything. // The arrays and the pageQuads are shared across all BitmapTextPage - // clones and it doesn't seem to bother anything. That means the + // clones, and it doesn't seem to bother anything. That means the // fields could probably just as well be static... but this code is // all very fragile. I'm not tipping that particular boat today. -pspeed diff --git a/jme3-core/src/main/java/com/jme3/font/ColorTags.java b/jme3-core/src/main/java/com/jme3/font/ColorTags.java index 83f46b3d0f..304b9832a6 100644 --- a/jme3-core/src/main/java/com/jme3/font/ColorTags.java +++ b/jme3-core/src/main/java/com/jme3/font/ColorTags.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,8 +46,8 @@ */ class ColorTags { private static final Pattern colorPattern = Pattern.compile("\\\\#([0-9a-fA-F]{8})#|\\\\#([0-9a-fA-F]{6})#|" + - "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#"); - private LinkedList colors = new LinkedList(); + "\\\\#([0-9a-fA-F]{4})#|\\\\#([0-9a-fA-F]{3})#"); + private final LinkedList colors = new LinkedList<>(); private String text; private String original; private float baseAlpha = -1; @@ -57,14 +57,14 @@ class ColorTags { ColorTags(String seq) { setText(seq); } - + /** * @return text without color tags */ String getPlainText() { return text; } - + LinkedList getTags() { return colors; } @@ -77,7 +77,7 @@ void setText(final String charSeq) { } Matcher m = colorPattern.matcher(charSeq); if (m.find()) { - StringBuilder builder = new StringBuilder(charSeq.length()-7); + StringBuilder builder = new StringBuilder(); int startIndex = 0; do { String colorStr = null; @@ -96,34 +96,34 @@ void setText(final String charSeq) { } } - void setBaseAlpha( float alpha ) { + void setBaseAlpha(float alpha) { this.baseAlpha = alpha; - if( alpha == -1 ) { + if (alpha == -1) { // Need to reinitialize from the original text setText(original); return; } - - // Else set the alpha for all of them - for( Range r : colors ) { + + // Else set the alpha for all of them + for (Range r : colors) { r.color.a = alpha; } } - + /** * Sets the colors of all ranges, overriding any color tags * that were in the original text. - */ - void setBaseColor( ColorRGBA color ) { + */ + void setBaseColor(ColorRGBA color) { // There are times when the alpha is directly modified // and the caller may have passed a constant... so we // should clone it. color = color.clone(); - for( Range r : colors ) { + for (Range r : colors) { r.color = color; } } - + class Range { int start; ColorRGBA color; @@ -140,7 +140,7 @@ class Range { } else if (colorStr.length() == 8) { color.a = Integer.parseInt(colorStr.subSequence(6,8).toString(), 16) / 255f; - } + } } else { color.set(Integer.parseInt(Character.toString(colorStr.charAt(0)), 16) / 15f, Integer.parseInt(Character.toString(colorStr.charAt(1)), 16) / 15f, diff --git a/jme3-core/src/main/java/com/jme3/font/GlyphParser.java b/jme3-core/src/main/java/com/jme3/font/GlyphParser.java new file mode 100644 index 0000000000..56e1443008 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/GlyphParser.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2009-2021 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.font; + +import com.jme3.export.Savable; + +/** + * Used for selecting character shape in cursive bitmap text. In cursive scripts, + * the appearance of a letter changes depending on its position: + * isolated, initial (joined on the left), medial (joined on both sides) + * and final (joined on the right) of a word. + * + * For an example implementation see: https://github.com/Ali-RS/JME-PersianGlyphParser + * + * @author Ali-RS + */ +public interface GlyphParser extends Savable { + + public CharSequence parse(CharSequence text); + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/font/Kerning.java b/jme3-core/src/main/java/com/jme3/font/Kerning.java index 3a6daeed31..5c4130d312 100644 --- a/jme3-core/src/main/java/com/jme3/font/Kerning.java +++ b/jme3-core/src/main/java/com/jme3/font/Kerning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,12 +59,14 @@ public void setAmount(int amount) { this.amount = amount; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(second, "second", 0); oc.write(amount, "amount", 0); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); second = ic.readInt("second", 0); diff --git a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java index 7977a154e0..ff647ab423 100644 --- a/jme3-core/src/main/java/com/jme3/font/LetterQuad.java +++ b/jme3-core/src/main/java/com/jme3/font/LetterQuad.java @@ -66,11 +66,11 @@ class LetterQuad { private LetterQuad next; private int colorInt = 0xFFFFFFFF; - private boolean rightToLeft; + private final boolean rightToLeft; private float alignX; private float alignY; private float sizeScale = 1; - + /** * create head / tail * @param font @@ -86,7 +86,7 @@ protected LetterQuad(BitmapFont font, boolean rightToLeft) { /** * create letter and append to previous LetterQuad - * + * * @param c * @param prev previous character */ @@ -99,7 +99,7 @@ protected LetterQuad(char c, LetterQuad prev) { setBitmapChar(c); prev.insert(this); } - + LetterQuad addNextCharacter(char c) { LetterQuad n = new LetterQuad(c, this); return n; @@ -108,11 +108,11 @@ LetterQuad addNextCharacter(char c) { BitmapCharacter getBitmapChar() { return bitmapChar; } - + char getChar() { return c; } - + int getIndex() { return index; } @@ -147,11 +147,11 @@ float getV0() { float getV1() { return v1; } - - boolean isRightToLeft(){ + + boolean isRightToLeft() { return rightToLeft; } - + boolean isInvalid() { return x0 == Integer.MIN_VALUE; } @@ -159,7 +159,7 @@ boolean isInvalid() { boolean isInvalid(StringBlock block) { return isInvalid(block, 0); } - + boolean isInvalid(StringBlock block, float gap) { if (isHead() || isTail()) return false; @@ -170,38 +170,43 @@ boolean isInvalid(StringBlock block, float gap) { if (bound == null) { return false; } - return x0 > 0 && bound.x+bound.width-gap < getX1(); + if (isRightToLeft()) { + return x0 <0 && x0 0 && bound.x+bound.width-gap < getX1(); + } } - + void clip(StringBlock block) { Rectangle bound = block.getTextBox(); if (bound == null) return; - + // Clip the right x position and texture coordinate // to the string block float x1 = Math.min(bound.x + bound.width, x0 + width); float newWidth = x1 - x0; - if( newWidth == width ) + if (isRightToLeft()) newWidth = x1; // only the available space to the left + if (newWidth == width) return; - + float rescale = newWidth / width; u1 = u0 + (u1 - u0) * rescale; - width = newWidth; + width = newWidth; } - + float getX0() { return x0; } float getX1() { - return x0+width; + return x0 + width; } - float getNextX() { - return x0+xAdvance; + return rightToLeft ? x0 - xAdvance : x0 + xAdvance; } - + float getNextLine() { return lineY+LINE_DIR*font.getCharSet().getLineHeight() * sizeScale; } @@ -213,11 +218,11 @@ float getY0() { float getY1() { return y0-height; } - + float getWidth() { return width; } - + float getHeight() { return height; } @@ -229,7 +234,7 @@ void insert(LetterQuad ins) { ins.previous = this; n.previous = ins; } - + void invalidate() { eol = isLineFeed(); setBitmapChar(font.getCharSet().getCharacter(c, style)); @@ -255,20 +260,20 @@ LetterQuad remove() { void setPrevious(LetterQuad before) { this.previous = before; } - + void setStyle(int style) { this.style = style; invalidate(); } - + void setColor(ColorRGBA color) { this.colorInt = color.asIntRGBA(); invalidate(); } void setAlpha(float alpha) { - int i = (int)(alpha * 255) & 0xFF; - colorInt = (colorInt & 0xffffff00) | i; + int i = (int) (alpha * 255) & 0xFF; + colorInt = (colorInt & 0xffffff00) | i; invalidate(); } @@ -277,7 +282,7 @@ void setBitmapChar(char c) { BitmapCharacter bm = charSet.getCharacter(c, style); setBitmapChar(bm); } - + void setBitmapChar(BitmapCharacter bitmapChar) { x0 = Integer.MIN_VALUE; y0 = Integer.MIN_VALUE; @@ -285,7 +290,7 @@ void setBitmapChar(BitmapCharacter bitmapChar) { height = Integer.MIN_VALUE; alignX = 0; alignY = 0; - + BitmapCharacterSet charSet = font.getCharSet(); this.bitmapChar = bitmapChar; if (bitmapChar != null) { @@ -314,6 +319,9 @@ void update(StringBlock block) { if (isHead()) { x0 = getBound(block).x; + if (isRightToLeft() && getBound(block) != UNBOUNDED) { + x0 += getBound(block).width; + } y0 = lineY; width = 0; height = 0; @@ -333,6 +341,7 @@ void update(StringBlock block) { xAdvance = width; } else if (bitmapChar == null) { x0 = getPrevious().getX1(); + if (rightToLeft) x0 = getPrevious().getX0(); y0 = lineY; width = 0; height = 0; @@ -347,30 +356,56 @@ void update(StringBlock block) { float kernAmount = 0f; if (previous.isHead() || previous.eol) { - x0 = bound.x; - - // The first letter quad will be drawn right at the first - // position... but it does not offset by the characters offset - // amount. This means that we've potentially accumulated extra - // pixels and the next letter won't get drawn far enough unless - // we add this offset back into xAdvance.. by subtracting it. - // This is the same thing that's done below because we've - // technically baked the offset in just like below. It doesn't - // look like it at first glance so I'm keeping it separate with - // this comment. - xAdvance -= xOffset * incrScale; - + if (rightToLeft) { + // In RTL text we advance toward left by the letter xAdvance. (subtract xAdvance) + // Note, positive offset will move the letter quad toward right and negative offset + // will move it toward left. + if (previous.isHead()) { + x0 = previous.getNextX() - xAdvance - xOffset * incrScale; + } else if (previous.eol) { + // For bounded bitmap text the first letter of a line is always + // on the right end of the textbox and for unbounded bitmap text + // we start from the x=0 and advance toward left. + x0 = getBound(block).x + (getBound(block) != UNBOUNDED ? getBound(block).width : 0) - xAdvance - xOffset * incrScale; + } + // Since x0 has xAdvance baked into it, we need to zero out xAdvance. + // Since x0 will have offset baked into it, we need to counteract that + // in xAdvance. The next x position will be (x0 - xAdvance). + xAdvance = -xOffset * incrScale; + } else { + x0 = bound.x; + + // The first letter quad will be drawn right at the first + // position, but it does not offset by the character's offset + // amount. This means that we've potentially accumulated extra + // pixels, and the next letter won't get drawn far enough unless + // we add this offset back into xAdvance, by subtracting it. + // This is the same thing that's done below, because we've + // technically baked the offset in just like below. It doesn't + // look like it at first glance, so I'm keeping it separate with + // this comment. + xAdvance -= xOffset * incrScale; + } } else { - x0 = previous.getNextX() + xOffset * incrScale; - - // Since x0 will have offset baked into it then we - // need to counteract that in xAdvance. This is better - // than removing it in getNextX() because we also need - // to take kerning into account below... which will also - // get baked in. - // Without this, getNextX() will return values too far to - // the left, for example. - xAdvance -= xOffset * incrScale; + if (isRightToLeft()) { + // For RTL text the xAdvance of the current letter is deducted, + // while for LTR text the advance of the letter before is added. + x0 = previous.getNextX() - xAdvance - xOffset * incrScale; + // Since x0 has xAdvance baked into it, we need to zero out xAdvance. + // Since x0 will have offset baked into it we need to counteract that + // in xAdvance. The next x position will be (x0 - xAdvance) + xAdvance = - xOffset * incrScale; + } else { + x0 = previous.getNextX() + xOffset * incrScale; + // Since x0 will have offset baked into it, we + // need to counteract that in xAdvance. This is better + // than removing it in getNextX() because we also need + // to take kerning into account below, which will also + // get baked in. + // Without this, getNextX() will return values too far to + // the left, for example. + xAdvance -= xOffset * incrScale; + } } y0 = lineY + LINE_DIR*yOffset; @@ -379,34 +414,33 @@ void update(StringBlock block) { if (lastChar != null && block.isKerning()) { kernAmount = lastChar.getKerning(c) * sizeScale; x0 += kernAmount * incrScale; - - // Need to unbake the kerning from xAdvance since it + // Need to unbake the kerning from xAdvance since it // is baked into x0... see above. //xAdvance -= kernAmount * incrScale; // No, kerning is an inter-character spacing and _does_ affect - // all subsequent cursor positions. + // all subsequent cursor positions. } } if (isEndOfLine()) { xAdvance = bound.x-x0; } } - + /** * add temporary linewrap indicator */ void setEndOfLine() { this.eol = true; } - + boolean isEndOfLine() { return eol; } - + boolean isLineWrap() { return !isHead() && !isTail() && bitmapChar == null && c == Character.MIN_VALUE; } - + private float computeLineY(StringBlock block) { if (isHead()) { return getBound(block).y; @@ -417,16 +451,16 @@ private float computeLineY(StringBlock block) { } } - + boolean isLineStart() { return x0 == 0 || (previous != null && previous.eol); } - + boolean isBlank() { return c == ' ' || isTab(); } - - public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx){ + + public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, int quadIdx) { float x = x0+alignX; float y = y0-alignY; float xpw = x+width; @@ -461,8 +495,8 @@ public void storeToArrays(float[] pos, float[] tc, short[] idx, byte[] colors, i idx[0] = i0; idx[1] = i1; idx[2] = i2; idx[3] = i0; idx[4] = i2; idx[5] = i3; } - - public void appendPositions(FloatBuffer fb){ + + public void appendPositions(FloatBuffer fb) { float sx = x0+alignX; float sy = y0-alignY; float ex = sx+width; @@ -475,21 +509,21 @@ public void appendPositions(FloatBuffer fb){ fb.put(ex).put(sy).put(0f); } - public void appendPositions(ShortBuffer sb){ + public void appendPositions(ShortBuffer sb) { final float x1 = getX1(); final float y1 = getY1(); short x = (short) x0; short y = (short) y0; short xpw = (short) (x1); short ymh = (short) (y1); - + sb.put(x).put(y).put((short)0); sb.put(x).put(ymh).put((short)0); sb.put(xpw).put(ymh).put((short)0); sb.put(xpw).put(y).put((short)0); } - public void appendTexCoords(FloatBuffer fb){ + public void appendTexCoords(FloatBuffer fb) { // flip coords to be compatible with OGL float v0 = 1 - this.v0; float v1 = 1 - this.v1; @@ -504,14 +538,14 @@ public void appendTexCoords(FloatBuffer fb){ fb.put(u1).put(v0); } - public void appendColors(ByteBuffer bb){ + public void appendColors(ByteBuffer bb) { bb.putInt(colorInt); bb.putInt(colorInt); bb.putInt(colorInt); bb.putInt(colorInt); } - public void appendIndices(ShortBuffer sb, int quadIndex){ + public void appendIndices(ShortBuffer sb, int quadIndex) { // each quad has 4 indices short v0 = (short) (quadIndex * 4); short v1 = (short) (v0 + 1); @@ -546,9 +580,9 @@ float getAlignY() { boolean isLineFeed() { return c == '\n'; } - + boolean isTab() { return c == '\t'; } - + } diff --git a/jme3-core/src/main/java/com/jme3/font/Letters.java b/jme3-core/src/main/java/com/jme3/font/Letters.java index 718ebd532c..b545dee035 100644 --- a/jme3-core/src/main/java/com/jme3/font/Letters.java +++ b/jme3-core/src/main/java/com/jme3/font/Letters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,10 +47,10 @@ class Letters { private final LetterQuad tail; private final BitmapFont font; private LetterQuad current; - private StringBlock block; + private final StringBlock block; private float totalWidth; private float totalHeight; - private ColorTags colorTags = new ColorTags(); + private final ColorTags colorTags = new ColorTags(); private ColorRGBA baseColor = null; private float baseAlpha = -1; private String plainText; @@ -73,12 +73,17 @@ void setText(final String text) { current = head; if (text != null && plainText.length() > 0) { LetterQuad l = head; - for (int i = 0; i < plainText.length(); i++) { - l = l.addNextCharacter(plainText.charAt(i)); + CharSequence characters = plainText; + if (font.getGlyphParser() != null) { + characters = font.getGlyphParser().parse(plainText); + } + + for (int i = 0; i < characters.length(); i++) { + l = l.addNextCharacter(characters.charAt(i)); if (baseColor != null) { // Give the letter a default color if // one has been provided. - l.setColor( baseColor ); + l.setColor(baseColor); } } } @@ -114,7 +119,7 @@ void update() { while (!l.isTail()) { if (l.isInvalid()) { l.update(block); - + // Without a text block, the next line always returns false = no text wrap will be applied. if (l.isInvalid(block)) { switch (block.getLineWrapMode()) { case Character: @@ -164,7 +169,8 @@ void update() { l.clip(block); // Clear the rest up to the next line feed. - for( LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext() ) { + // = for texts attached to a text block, all coming characters are cleared except a linefeed is explicitly used + for (LetterQuad q = l.getNext(); !q.isTail() && !q.isLineFeed(); q = q.getNext()) { q.setBitmapChar(null); q.update(block); } @@ -186,44 +192,80 @@ void update() { } private void align() { + if (block.getTextBox() == null) { + // Without a text block, there is no alignment. + return; + + // For unbounded left-to-right texts the letters will simply be shown starting from + // x0 = 0 and advance toward right as line length is considered to be infinite. + // For unbounded right-to-left texts the letters will be shown starting from x0 = 0 + // (at the same position as left-to-right texts) but move toward the left from there. + } + final Align alignment = block.getAlignment(); final VAlign valignment = block.getVerticalAlignment(); - if (block.getTextBox() == null || (alignment == Align.Left && valignment == VAlign.Top)) - return; - LetterQuad cursor = tail.getPrevious(); - cursor.setEndOfLine(); final float width = block.getTextBox().width; final float height = block.getTextBox().height; float lineWidth = 0; float gapX = 0; float gapY = 0; + validateSize(); if (totalHeight < height) { // align vertically only for no overflow switch (valignment) { - case Top: - gapY = 0; - break; - case Center: - gapY = (height - totalHeight) * 0.5f; - break; - case Bottom: - gapY = height - totalHeight; - break; + case Top: + gapY = 0; + break; + case Center: + gapY = (height - totalHeight) * 0.5f; + break; + case Bottom: + gapY = height - totalHeight; + break; } } - while (!cursor.isHead()) { - if (cursor.isEndOfLine()) { - lineWidth = cursor.getX1()-block.getTextBox().x; - if (alignment == Align.Center) { - gapX = (width-lineWidth)/2; - } else if (alignment == Align.Right) { - gapX = width-lineWidth; - } else { - gapX = 0; + + if (font.isRightToLeft()) { + if ((alignment == Align.Right && valignment == VAlign.Top)) { + return; + } + LetterQuad cursor = tail.getPrevious(); + // Temporary set the flag, it will be reset when invalidated. + cursor.setEndOfLine(); + while (!cursor.isHead()) { + if (cursor.isEndOfLine()) { + if (alignment == Align.Left) { + gapX = block.getTextBox().x - cursor.getX0(); + } else if (alignment == Align.Center) { + gapX = (block.getTextBox().x - cursor.getX0()) / 2; + } else { + gapX = 0; + } } + cursor.setAlignment(gapX, gapY); + cursor = cursor.getPrevious(); + } + } else { // left-to-right + if (alignment == Align.Left && valignment == VAlign.Top) { + return; + } + LetterQuad cursor = tail.getPrevious(); + // Temporary set the flag, it will be reset when invalidated. + cursor.setEndOfLine(); + while (!cursor.isHead()) { + if (cursor.isEndOfLine()) { + lineWidth = cursor.getX1() - block.getTextBox().x; + if (alignment == Align.Center) { + gapX = (width - lineWidth) / 2; + } else if (alignment == Align.Right) { + gapX = width - lineWidth; + } else { + gapX = 0; + } + } + cursor.setAlignment(gapX, gapY); + cursor = cursor.getPrevious(); } - cursor.setAlignment(gapX, gapY); - cursor = cursor.getPrevious(); } } @@ -232,7 +274,7 @@ private void lineWrap(LetterQuad l) { return; l.getPrevious().setEndOfLine(); l.invalidate(); - l.update(block); // TODO: update from l + l.update(block); } float getCharacterX0() { @@ -319,10 +361,15 @@ float getTotalHeight() { } void validateSize() { + // also called from BitMaptext.getLineWidth() via getTotalWidth() if (totalWidth < 0) { LetterQuad l = head; while (!l.isTail()) { - totalWidth = Math.max(totalWidth, l.getX1()); + if (font.isRightToLeft()) { + totalWidth = Math.max(totalWidth, Math.abs(l.getX0())); + } else { + totalWidth = Math.max(totalWidth, l.getX1()); + } l = l.getNext(); } } @@ -348,10 +395,10 @@ void setStyle(int start, int end, int style) { * Sets the base color for all new letter quads and resets * the color of existing letter quads. */ - void setColor( ColorRGBA color ) { + void setColor(ColorRGBA color) { baseColor = color; colorTags.setBaseColor(color); - setColor( 0, block.getText().length(), color ); + setColor(0, block.getText().length(), color); } ColorRGBA getBaseColor() { @@ -377,7 +424,8 @@ float getBaseAlpha() { return baseAlpha; } - void setBaseAlpha( float alpha ) { this.baseAlpha = alpha; + void setBaseAlpha(float alpha) { + this.baseAlpha = alpha; colorTags.setBaseAlpha(alpha); if (alpha == -1) { @@ -391,8 +439,8 @@ float getBaseAlpha() { cursor = cursor.getNext(); } - // If the alpha was reset to "default", ie: -1 - // then the color tags are potentially reset and + // If the alpha was reset to "default" (-1), + // then the color tags are potentially reset, and // we need to reapply them. This has to be done // second since it may override any alpha values // set above... but you still need to do the above diff --git a/jme3-core/src/main/java/com/jme3/font/Rectangle.java b/jme3-core/src/main/java/com/jme3/font/Rectangle.java index e3bb90dd8b..e40aeb0563 100644 --- a/jme3-core/src/main/java/com/jme3/font/Rectangle.java +++ b/jme3-core/src/main/java/com/jme3/font/Rectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,15 +54,17 @@ public Rectangle(float x, float y, float width, float height) { } @Override - public Rectangle clone(){ + public Rectangle clone() { try { return (Rectangle) super.clone(); } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } - + + @Override public String toString() { - return getClass().getSimpleName() + "[x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; + return getClass().getSimpleName() + + "[x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + "]"; } } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/font/StringBlock.java b/jme3-core/src/main/java/com/jme3/font/StringBlock.java index 85fc9ab703..ccbd163143 100644 --- a/jme3-core/src/main/java/com/jme3/font/StringBlock.java +++ b/jme3-core/src/main/java/com/jme3/font/StringBlock.java @@ -46,7 +46,7 @@ class StringBlock implements Cloneable { private String text; private Rectangle textBox; private Align alignment = Align.Left; - private VAlign valignment = VAlign.Top; + private VAlign vAlignment = VAlign.Top; private float size; private ColorRGBA color = new ColorRGBA(ColorRGBA.White); private boolean kerning; @@ -75,7 +75,7 @@ class StringBlock implements Cloneable { this.kerning = kerning; } - StringBlock(){ + StringBlock() { this.text = ""; this.textBox = null; this.alignment = Align.Left; @@ -85,7 +85,7 @@ class StringBlock implements Cloneable { } @Override - public StringBlock clone(){ + public StringBlock clone() { try { StringBlock clone = (StringBlock) super.clone(); clone.color = color.clone(); @@ -101,7 +101,7 @@ String getText() { return text; } - void setText(String text){ + void setText(String text) { this.text = text == null ? "" : text; } @@ -116,17 +116,17 @@ void setTextBox(Rectangle textBox) { BitmapFont.Align getAlignment() { return alignment; } - + BitmapFont.VAlign getVerticalAlignment() { - return valignment; + return vAlignment; } void setAlignment(BitmapFont.Align alignment) { this.alignment = alignment; } - + void setVerticalAlignment(BitmapFont.VAlign alignment) { - this.valignment = alignment; + this.vAlignment = alignment; } float getSize() { @@ -160,19 +160,19 @@ int getLineCount() { void setLineCount(int lineCount) { this.lineCount = lineCount; } - + LineWrapMode getLineWrapMode() { return wrapType; } - + /** - * available only when bounding is set. setBox() method call is needed in advance. + * available only when bounding is set. setBox() method call is needed in advance. * @param wrap true when word need not be split at the end of the line. */ void setLineWrapMode(LineWrapMode wrap) { this.wrapType = wrap; } - + void setTabWidth(float tabWidth) { this.tabWidth = tabWidth; } @@ -180,11 +180,11 @@ void setTabWidth(float tabWidth) { void setTabPosition(float[] tabs) { this.tabPos = tabs; } - + float getTabWidth() { return tabWidth; } - + float[] getTabPosition() { return tabPos; } diff --git a/jme3-core/src/main/java/com/jme3/font/package-info.java b/jme3-core/src/main/java/com/jme3/font/package-info.java new file mode 100644 index 0000000000..2aa5916203 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/font/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * render letters, numbers, and other glyphs + */ +package com.jme3.font; diff --git a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java index ee0d4b9e86..7eab91ee12 100644 --- a/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java +++ b/jme3-core/src/main/java/com/jme3/input/AbstractJoystick.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,16 +42,21 @@ */ public abstract class AbstractJoystick implements Joystick { - private InputManager inputManager; - private JoyInput joyInput; - private int joyId; - private String name; - - private List axes = new ArrayList(); - private List buttons = new ArrayList(); + private final InputManager inputManager; + private final JoyInput joyInput; + private final int joyId; + private final String name; + + private final List axes = new ArrayList<>(); + private final List buttons = new ArrayList<>(); /** * Creates a new joystick instance. Only used internally. + * + * @param inputManager (alias created) + * @param joyInput (alias created) + * @param joyId ID to assign to the instance + * @param name name to assign to the instance */ protected AbstractJoystick(InputManager inputManager, JoyInput joyInput, int joyId, String name) { @@ -60,20 +65,20 @@ protected AbstractJoystick(InputManager inputManager, JoyInput joyInput, this.joyId = joyId; this.name = name; } - + protected InputManager getInputManager() { return inputManager; } - + protected JoyInput getJoyInput() { - return joyInput; + return joyInput; } - protected void addAxis( JoystickAxis axis ) { + protected void addAxis(JoystickAxis axis) { axes.add(axis); } - protected void addButton( JoystickButton button ) { + protected void addButton(JoystickButton button) { buttons.add(button); } @@ -83,7 +88,7 @@ protected void addButton( JoystickButton button ) { * @param amount The amount to rumble. Should be between 0 and 1. */ @Override - public void rumble(float amount){ + public void rumble(float amount) { joyInput.setJoyRumble(joyId, amount); } @@ -99,7 +104,7 @@ public void rumble(float amount){ */ @Override @Deprecated - public void assignButton(String mappingName, int buttonId){ + public void assignButton(String mappingName, int buttonId) { if (buttonId < 0 || buttonId >= getButtonCount()) throw new IllegalArgumentException(); @@ -118,23 +123,23 @@ public void assignButton(String mappingName, int buttonId){ */ @Override @Deprecated - public void assignAxis(String positiveMapping, String negativeMapping, int axisId){ - + public void assignAxis(String positiveMapping, String negativeMapping, int axisId) { + // For backwards compatibility - if( axisId == JoyInput.AXIS_POV_X ) { + if (axisId == JoyInput.AXIS_POV_X) { axisId = getPovXAxis().getAxisId(); - } else if( axisId == JoyInput.AXIS_POV_Y ) { + } else if (axisId == JoyInput.AXIS_POV_Y) { axisId = getPovYAxis().getAxisId(); } - + inputManager.addMapping(positiveMapping, new JoyAxisTrigger(joyId, axisId, false)); inputManager.addMapping(negativeMapping, new JoyAxisTrigger(joyId, axisId, true)); } @Override public JoystickAxis getAxis(String logicalId) { - for( JoystickAxis axis : axes ) { - if( axis.getLogicalId().equals(logicalId) ) + for (JoystickAxis axis : axes) { + if (axis.getLogicalId().equals(logicalId)) return axis; } return null; @@ -156,12 +161,12 @@ public List getAxes() { @Override public int getAxisCount() { return axes.size(); - } + } @Override public JoystickButton getButton(String logicalId) { - for( JoystickButton b : buttons ) { - if( b.getLogicalId().equals(logicalId) ) + for (JoystickButton b : buttons) { + if (b.getLogicalId().equals(logicalId)) return b; } return null; @@ -173,7 +178,7 @@ public JoystickButton getButton(String logicalId) { @Override public List getButtons() { return Collections.unmodifiableList(buttons); - } + } /** * Returns the number of buttons on this joystick. @@ -184,7 +189,7 @@ public List getButtons() { public int getButtonCount() { return buttons.size(); } - + /** * Returns the name of this joystick. * @@ -215,7 +220,7 @@ public int getJoyId() { * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) */ @Override - public int getXAxisIndex(){ + public int getXAxisIndex() { return getXAxis().getAxisId(); } @@ -229,12 +234,12 @@ public int getXAxisIndex(){ * @see Joystick#assignAxis(java.lang.String, java.lang.String, int) */ @Override - public int getYAxisIndex(){ + public int getYAxisIndex() { return getYAxis().getAxisId(); } @Override - public String toString(){ + public String toString() { return "Joystick[name=" + name + ", id=" + joyId + ", buttons=" + getButtonCount() + ", axes=" + getAxisCount() + "]"; } diff --git a/jme3-core/src/main/java/com/jme3/input/CameraInput.java b/jme3-core/src/main/java/com/jme3/input/CameraInput.java index 0c2e41b7f1..b4e46c8ad2 100644 --- a/jme3-core/src/main/java/com/jme3/input/CameraInput.java +++ b/jme3-core/src/main/java/com/jme3/input/CameraInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,100 +44,106 @@ public class CameraInput { * Chase camera mapping for moving down. Default assigned to * MouseInput.AXIS_Y direction depending on the invertYaxis configuration */ - public final static String CHASECAM_DOWN = "ChaseCamDown"; + public static final String CHASECAM_DOWN = "ChaseCamDown"; /** * Chase camera mapping for moving up. Default assigned to MouseInput.AXIS_Y * direction depending on the invertYaxis configuration */ - public final static String CHASECAM_UP = "ChaseCamUp"; + public static final String CHASECAM_UP = "ChaseCamUp"; /** * Chase camera mapping for zooming in. Default assigned to * MouseInput.AXIS_WHEEL direction positive */ - public final static String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; + public static final String CHASECAM_ZOOMIN = "ChaseCamZoomIn"; /** * Chase camera mapping for zooming out. Default assigned to * MouseInput.AXIS_WHEEL direction negative */ - public final static String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; + public static final String CHASECAM_ZOOMOUT = "ChaseCamZoomOut"; /** * Chase camera mapping for moving left. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; + public static final String CHASECAM_MOVELEFT = "ChaseCamMoveLeft"; /** * Chase camera mapping for moving right. Default assigned to * MouseInput.AXIS_X direction depending on the invertXaxis configuration */ - public final static String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; + public static final String CHASECAM_MOVERIGHT = "ChaseCamMoveRight"; /** * Chase camera mapping to initiate the rotation of the cam. Default assigned * to MouseInput.BUTTON_LEFT and MouseInput.BUTTON_RIGHT */ - public final static String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; + public static final String CHASECAM_TOGGLEROTATE = "ChaseCamToggleRotate"; - //fly cameara constants + //fly camera constants /** * Fly camera mapping to look left. Default assigned to MouseInput.AXIS_X, * direction negative */ - public final static String FLYCAM_LEFT = "FLYCAM_Left"; + public static final String FLYCAM_LEFT = "FLYCAM_Left"; /** * Fly camera mapping to look right. Default assigned to MouseInput.AXIS_X, * direction positive */ - public final static String FLYCAM_RIGHT = "FLYCAM_Right"; + public static final String FLYCAM_RIGHT = "FLYCAM_Right"; /** * Fly camera mapping to look up. Default assigned to MouseInput.AXIS_Y, * direction positive */ - public final static String FLYCAM_UP = "FLYCAM_Up"; + public static final String FLYCAM_UP = "FLYCAM_Up"; /** * Fly camera mapping to look down. Default assigned to MouseInput.AXIS_Y, * direction negative */ - public final static String FLYCAM_DOWN = "FLYCAM_Down"; + public static final String FLYCAM_DOWN = "FLYCAM_Down"; /** * Fly camera mapping to move left. Default assigned to KeyInput.KEY_A */ - public final static String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; + public static final String FLYCAM_STRAFELEFT = "FLYCAM_StrafeLeft"; /** * Fly camera mapping to move right. Default assigned to KeyInput.KEY_D */ - public final static String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; + public static final String FLYCAM_STRAFERIGHT = "FLYCAM_StrafeRight"; /** * Fly camera mapping to move forward. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_FORWARD = "FLYCAM_Forward"; + public static final String FLYCAM_FORWARD = "FLYCAM_Forward"; /** * Fly camera mapping to move backward. Default assigned to KeyInput.KEY_S */ - public final static String FLYCAM_BACKWARD = "FLYCAM_Backward"; + public static final String FLYCAM_BACKWARD = "FLYCAM_Backward"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction positive */ - public final static String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; + public static final String FLYCAM_ZOOMIN = "FLYCAM_ZoomIn"; /** * Fly camera mapping to zoom in. Default assigned to MouseInput.AXIS_WHEEL, * direction negative */ - public final static String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; + public static final String FLYCAM_ZOOMOUT = "FLYCAM_ZoomOut"; /** * Fly camera mapping to toggle rotation. Default assigned to * MouseInput.BUTTON_LEFT */ - public final static String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; + public static final String FLYCAM_ROTATEDRAG = "FLYCAM_RotateDrag"; /** * Fly camera mapping to move up. Default assigned to KeyInput.KEY_Q */ - public final static String FLYCAM_RISE = "FLYCAM_Rise"; + public static final String FLYCAM_RISE = "FLYCAM_Rise"; /** * Fly camera mapping to move down. Default assigned to KeyInput.KEY_W */ - public final static String FLYCAM_LOWER = "FLYCAM_Lower"; + public static final String FLYCAM_LOWER = "FLYCAM_Lower"; - public final static String FLYCAM_INVERTY = "FLYCAM_InvertY"; + public static final String FLYCAM_INVERTY = "FLYCAM_InvertY"; + + /** + * A private constructor to inhibit instantiation of this class. + */ + private CameraInput() { + } } diff --git a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java index 43f0941fc3..fe494801b3 100644 --- a/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/ChaseCamera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme protected float maxVerticalRotation = FastMath.PI / 2; protected float minDistance = 1.0f; protected float maxDistance = 40.0f; - protected float distance = 20; + protected float distance = 20; protected float rotationSpeed = 1.0f; protected float rotation = 0; protected float trailingRotationInertia = 0.05f; @@ -100,42 +100,42 @@ public class ChaseCamera implements ActionListener, AnalogListener, Control, Jme protected Vector3f temp = new Vector3f(0, 0, 0); protected boolean invertYaxis = false; protected boolean invertXaxis = false; - + /** * @deprecated use {@link CameraInput#CHASECAM_DOWN} */ @Deprecated - public final static String ChaseCamDown = "ChaseCamDown"; + public static final String ChaseCamDown = "ChaseCamDown"; /** * @deprecated use {@link CameraInput#CHASECAM_UP} */ @Deprecated - public final static String ChaseCamUp = "ChaseCamUp"; + public static final String ChaseCamUp = "ChaseCamUp"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMIN} */ @Deprecated - public final static String ChaseCamZoomIn = "ChaseCamZoomIn"; + public static final String ChaseCamZoomIn = "ChaseCamZoomIn"; /** * @deprecated use {@link CameraInput#CHASECAM_ZOOMOUT} */ @Deprecated - public final static String ChaseCamZoomOut = "ChaseCamZoomOut"; + public static final String ChaseCamZoomOut = "ChaseCamZoomOut"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVELEFT} */ @Deprecated - public final static String ChaseCamMoveLeft = "ChaseCamMoveLeft"; + public static final String ChaseCamMoveLeft = "ChaseCamMoveLeft"; /** * @deprecated use {@link CameraInput#CHASECAM_MOVERIGHT} */ @Deprecated - public final static String ChaseCamMoveRight = "ChaseCamMoveRight"; + public static final String ChaseCamMoveRight = "ChaseCamMoveRight"; /** * @deprecated use {@link CameraInput#CHASECAM_TOGGLEROTATE} */ @Deprecated - public final static String ChaseCamToggleRotate = "ChaseCamToggleRotate"; + public static final String ChaseCamToggleRotate = "ChaseCamToggleRotate"; protected boolean zoomin; protected boolean hideCursorOnRotate = true; @@ -184,6 +184,7 @@ public ChaseCamera(Camera cam, final Spatial target, InputManager inputManager) registerWithInput(inputManager); } + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (dragToRotate) { if (name.equals(CameraInput.CHASECAM_TOGGLEROTATE) && enabled) { @@ -200,10 +201,9 @@ public void onAction(String name, boolean keyPressed, float tpf) { } } } - } - + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals(CameraInput.CHASECAM_MOVELEFT)) { rotateCamera(-value); @@ -230,10 +230,10 @@ public void onAnalog(String name, float value, float tpf) { /** * Registers inputs with the input manager - * @param inputManager + * + * @param inputManager (alias created) */ public final void registerWithInput(InputManager inputManager) { - String[] inputs = {CameraInput.CHASECAM_TOGGLEROTATE, CameraInput.CHASECAM_DOWN, CameraInput.CHASECAM_UP, @@ -244,33 +244,45 @@ public final void registerWithInput(InputManager inputManager) { this.inputManager = inputManager; if (!invertYaxis) { - inputManager.addMapping(CameraInput.CHASECAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); - inputManager.addMapping(CameraInput.CHASECAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); + inputManager.addMapping(CameraInput.CHASECAM_DOWN, + new MouseAxisTrigger(MouseInput.AXIS_Y, true)); + inputManager.addMapping(CameraInput.CHASECAM_UP, + new MouseAxisTrigger(MouseInput.AXIS_Y, false)); } else { - inputManager.addMapping(CameraInput.CHASECAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, false)); - inputManager.addMapping(CameraInput.CHASECAM_UP, new MouseAxisTrigger(MouseInput.AXIS_Y, true)); + inputManager.addMapping(CameraInput.CHASECAM_DOWN, + new MouseAxisTrigger(MouseInput.AXIS_Y, false)); + inputManager.addMapping(CameraInput.CHASECAM_UP, + new MouseAxisTrigger(MouseInput.AXIS_Y, true)); } - inputManager.addMapping(CameraInput.CHASECAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); - inputManager.addMapping(CameraInput.CHASECAM_ZOOMOUT, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); + inputManager.addMapping(CameraInput.CHASECAM_ZOOMIN, + new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); + inputManager.addMapping(CameraInput.CHASECAM_ZOOMOUT, + new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); if (!invertXaxis) { - inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true)); - inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, false)); + inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, + new MouseAxisTrigger(MouseInput.AXIS_X, true)); + inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, + new MouseAxisTrigger(MouseInput.AXIS_X, false)); } else { - inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, new MouseAxisTrigger(MouseInput.AXIS_X, false)); - inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, new MouseAxisTrigger(MouseInput.AXIS_X, true)); + inputManager.addMapping(CameraInput.CHASECAM_MOVELEFT, + new MouseAxisTrigger(MouseInput.AXIS_X, false)); + inputManager.addMapping(CameraInput.CHASECAM_MOVERIGHT, + new MouseAxisTrigger(MouseInput.AXIS_X, true)); } - inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); - inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); + inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, + new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping(CameraInput.CHASECAM_TOGGLEROTATE, + new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); inputManager.addListener(this, inputs); } - + /** * Cleans up the input mappings from the input manager. * Undoes the work of registerWithInput(). * @param mgr the InputManager to clean up */ - public void cleanupWithInput(InputManager mgr){ + public void cleanupWithInput(InputManager mgr) { mgr.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE); mgr.deleteMapping(CameraInput.CHASECAM_DOWN); mgr.deleteMapping(CameraInput.CHASECAM_UP); @@ -286,7 +298,8 @@ public void cleanupWithInput(InputManager mgr){ * default are * new MouseButtonTrigger(MouseInput.BUTTON_LEFT) left mouse button * new MouseButtonTrigger(MouseInput.BUTTON_RIGHT) right mouse button - * @param triggers + * + * @param triggers the triggers to assign */ public void setToggleRotationTrigger(Trigger... triggers) { inputManager.deleteMapping(CameraInput.CHASECAM_TOGGLEROTATE); @@ -298,7 +311,8 @@ public void setToggleRotationTrigger(Trigger... triggers) { * Sets custom triggers for zooming in the cam * default is * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true) mouse wheel up - * @param triggers + * + * @param triggers the triggers to assign */ public void setZoomInTrigger(Trigger... triggers) { inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMIN); @@ -310,7 +324,8 @@ public void setZoomInTrigger(Trigger... triggers) { * Sets custom triggers for zooming out the cam * default is * new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false) mouse wheel down - * @param triggers + * + * @param triggers the triggers to assign */ public void setZoomOutTrigger(Trigger... triggers) { inputManager.deleteMapping(CameraInput.CHASECAM_ZOOMOUT); @@ -383,6 +398,8 @@ protected void vRotateCamera(float value) { /** * Updates the camera, should only be called internally + * + * @param tpf time per frame (in seconds) */ protected void updateCamera(float tpf) { if (enabled) { @@ -393,7 +410,7 @@ protected void updateCamera(float tpf) { targetDir.set(targetLocation).subtractLocal(prevPos); float dist = targetDir.length(); - //Low pass filtering on the target postition to avoid shaking when physics are enabled. + //Low pass filtering on the target position to avoid shaking when physics are enabled. if (offsetDistance < dist) { //target moves, start chasing. chasing = true; @@ -404,8 +421,8 @@ protected void updateCamera(float tpf) { //target moves... targetMoves = true; } else { - //if target was moving, we compute a slight offset in rotation to avoid a rought stop of the cam - //We do not if the player is rotationg the cam + //if target was moving, we compute a slight offset in rotation to avoid a rough stop of the cam + //We do not if the player is rotating the cam if (targetMoves && !canRotate) { if (targetRotation - rotation > trailingRotationInertia) { targetRotation = rotation + trailingRotationInertia; @@ -428,13 +445,13 @@ protected void updateCamera(float tpf) { if (trailingEnabled && trailing) { if (targetMoves) { - //computation if the inverted direction of the target + // Compute the reversed direction of the target. Vector3f a = targetDir.negate().normalizeLocal(); - //the x unit vector + // the x unit vector Vector3f b = Vector3f.UNIT_X; - //2d is good enough + // 2-D is good enough. a.y = 0; - //computation of the rotation angle between the x axis and the trail + // Compute the angle between the X axis and the trail. if (targetDir.z > 0) { targetRotation = FastMath.TWO_PI - FastMath.acos(a.dot(b)); } else { @@ -444,7 +461,8 @@ protected void updateCamera(float tpf) { targetRotation -= FastMath.TWO_PI; } - //if there is an important change in the direction while trailing reset of the lerp factor to avoid jumpy movements + // If there is an important change in the direction while trailing, + // reset the lerp factor to avoid jumpy movements. if (targetRotation != previousTargetRotation && FastMath.abs(targetRotation - previousTargetRotation) > FastMath.PI / 8) { trailingLerpFactor = 0; } @@ -455,7 +473,7 @@ protected void updateCamera(float tpf) { //computing rotation by linear interpolation rotation = FastMath.interpolateLinear(trailingLerpFactor, rotation, targetRotation); - //if the rotation is near the target rotation we're good, that's over + // If the rotation is near the target rotation, we're good, that's over. if (targetRotation + 0.01f >= rotation && targetRotation - 0.01f <= rotation) { trailing = false; trailingLerpFactor = 0; @@ -537,14 +555,17 @@ public boolean isEnabled() { */ public void setEnabled(boolean enabled) { this.enabled = enabled; - if (!enabled) { + if (enabled) { + this.canRotate = !dragToRotate; //On enable, set back to correct state + } else { canRotate = false; // reset this flag in-case it was on before } } /** * Returns the max zoom distance of the camera (default is 40) - * @return maxDistance + * + * @return maxDistance the configured distance (in world units) */ public float getMaxDistance() { return maxDistance; @@ -552,7 +573,8 @@ public float getMaxDistance() { /** * Sets the max zoom distance of the camera (default is 40) - * @param maxDistance + * + * @param maxDistance the desired distance (in world units, default=40) */ public void setMaxDistance(float maxDistance) { this.maxDistance = maxDistance; @@ -563,7 +585,8 @@ public void setMaxDistance(float maxDistance) { /** * Returns the min zoom distance of the camera (default is 1) - * @return minDistance + * + * @return minDistance the configured distance (in world units) */ public float getMinDistance() { return minDistance; @@ -571,6 +594,8 @@ public float getMinDistance() { /** * Sets the min zoom distance of the camera (default is 1) + * + * @param minDistance the desired distance (in world units, default=1) */ public void setMinDistance(float minDistance) { this.minDistance = minDistance; @@ -582,7 +607,7 @@ public void setMinDistance(float minDistance) { /** * clone this camera for a spatial * - * @param spatial + * @param spatial ignored * @return never */ @Deprecated @@ -598,20 +623,22 @@ public Object jmeClone() { cc.setMaxDistance(getMaxDistance()); cc.setMinDistance(getMinDistance()); return cc; - } + } - @Override - public void cloneFields( Cloner cloner, Object original ) { + @Override + public void cloneFields(Cloner cloner, Object original) { this.target = cloner.clone(target); computePosition(); prevPos = new Vector3f(target.getWorldTranslation()); cam.setLocation(pos); } - + /** - * Sets the spacial for the camera control, should only be used internally - * @param spatial + * Sets the spatial for the camera control, should only be used internally + * + * @param spatial the desired camera target, or null for none */ + @Override public void setSpatial(Spatial spatial) { target = spatial; if (spatial == null) { @@ -624,17 +651,21 @@ public void setSpatial(Spatial spatial) { /** * update the camera control, should only be used internally - * @param tpf + * + * @param tpf time per frame (in seconds) */ + @Override public void update(float tpf) { updateCamera(tpf); } /** * renders the camera control, should only be used internally - * @param rm - * @param vp + * + * @param rm ignored + * @param vp ignored */ + @Override public void render(RenderManager rm, ViewPort vp) { //nothing to render } @@ -642,17 +673,20 @@ public void render(RenderManager rm, ViewPort vp) { /** * Write the camera * @param ex the exporter - * @throws IOException + * @throws IOException from the exporter */ + @Override public void write(JmeExporter ex) throws IOException { throw new UnsupportedOperationException("remove ChaseCamera before saving"); } /** * Read the camera - * @param im - * @throws IOException + * + * @param im the importer (not null) + * @throws IOException from the importer */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); maxDistance = ic.readFloat("maxDistance", 40); @@ -668,7 +702,8 @@ public float getMaxVerticalRotation() { /** * Sets the maximal vertical rotation angle in radian of the camera around the target. Default is Pi/2; - * @param maxVerticalRotation + * + * @param maxVerticalRotation the desired angle (in radians, default=Pi/2) */ public void setMaxVerticalRotation(float maxVerticalRotation) { this.maxVerticalRotation = maxVerticalRotation; @@ -684,7 +719,8 @@ public float getMinVerticalRotation() { /** * Sets the minimal vertical rotation angle in radian of the camera around the target default is 0; - * @param minHeight + * + * @param minHeight the desired angle (in radians, default=0) */ public void setMinVerticalRotation(float minHeight) { this.minVerticalRotation = minHeight; @@ -699,7 +735,8 @@ public boolean isSmoothMotion() { /** * Enables smooth motion for this chase camera - * @param smoothMotion + * + * @param smoothMotion true to enable, false to disable (default=false) */ public void setSmoothMotion(boolean smoothMotion) { this.smoothMotion = smoothMotion; @@ -718,7 +755,8 @@ public float getChasingSensitivity() { * Sets the chasing sensitivity, the lower the value the slower the camera will follow the target when it moves * default is 5 * Only has an effect if smoothMotion is set to true and trailing is enabled - * @param chasingSensitivity + * + * @param chasingSensitivity the desired value (default=5) */ public void setChasingSensitivity(float chasingSensitivity) { this.chasingSensitivity = chasingSensitivity; @@ -733,11 +771,12 @@ public float getRotationSensitivity() { } /** - * Sets the rotation sensitivity, the lower the value the slower the camera will rotates around the target when dragging with the mouse + * Sets the rotation sensitivity. The lower the value, the slower the camera will rotate around the target when dragging with the mouse. * default is 5, values over 5 should have no effect. * If you want a significant slow down try values below 1. * Only has an effect if smoothMotion is set to true - * @param rotationSensitivity + * + * @param rotationSensitivity the desired value (default=5) */ public void setRotationSensitivity(float rotationSensitivity) { this.rotationSensitivity = rotationSensitivity; @@ -754,7 +793,8 @@ public boolean isTrailingEnabled() { /** * Enable the camera trailing : The camera smoothly go in the targets trail when it moves. * Only has an effect if smoothMotion is set to true - * @param trailingEnabled + * + * @param trailingEnabled true to enable, false to disable (default=true) */ public void setTrailingEnabled(boolean trailingEnabled) { this.trailingEnabled = trailingEnabled; @@ -770,10 +810,11 @@ public float getTrailingRotationInertia() { } /** - * Sets the trailing rotation inertia : default is 0.1. This prevent the camera to roughtly stop when the target stops moving - * before the camera reached the trail position. + * Sets the trailing rotation inertia : default is 0.1. This causes the camera to stop roughly when the target stops moving + * before the camera reaches the trail position. * Only has an effect if smoothMotion is set to true and trailing is enabled - * @param trailingRotationInertia + * + * @param trailingRotationInertia the desired value (default=0.05) */ public void setTrailingRotationInertia(float trailingRotationInertia) { this.trailingRotationInertia = trailingRotationInertia; @@ -791,7 +832,8 @@ public float getTrailingSensitivity() { * Only has an effect if smoothMotion is set to true and trailing is enabled * Sets the trailing sensitivity, the lower the value, the slower the camera will go in the target trail when it moves. * default is 0.5; - * @param trailingSensitivity + * + * @param trailingSensitivity the desired value (default=0.5) */ public void setTrailingSensitivity(float trailingSensitivity) { this.trailingSensitivity = trailingSensitivity; @@ -808,12 +850,13 @@ public float getZoomSensitivity() { /** * Sets the zoom sensitivity, the lower the value, the slower the camera will zoom in and out. * default is 2. - * @param zoomSensitivity + * + * @param zoomSensitivity the desired factor (default=2) */ public void setZoomSensitivity(float zoomSensitivity) { this.zoomSensitivity = zoomSensitivity; } - + /** * Returns the rotation speed when the mouse is moved. * @@ -824,8 +867,8 @@ public float getRotationSpeed() { } /** - * Sets the rotate amount when user moves his mouse, the lower the value, - * the slower the camera will rotate. default is 1. + * Sets the rotate amount when user moves his mouse. The lower the value, + * the slower the camera will rotate. Default is 1. * * @param rotationSpeed Rotation speed on mouse movement, default is 1. */ @@ -835,7 +878,8 @@ public void setRotationSpeed(float rotationSpeed) { /** * Sets the default distance at start of application - * @param defaultDistance + * + * @param defaultDistance the desired distance (in world units, default=20) */ public void setDefaultDistance(float defaultDistance) { distance = defaultDistance; @@ -844,7 +888,8 @@ public void setDefaultDistance(float defaultDistance) { /** * sets the default horizontal rotation in radian of the camera at start of the application - * @param angleInRad + * + * @param angleInRad the desired angle (in radians, default=0) */ public void setDefaultHorizontalRotation(float angleInRad) { rotation = angleInRad; @@ -853,7 +898,8 @@ public void setDefaultHorizontalRotation(float angleInRad) { /** * sets the default vertical rotation in radian of the camera at start of the application - * @param angleInRad + * + * @param angleInRad the desired angle (in radians, default=Pi/6) */ public void setDefaultVerticalRotation(float angleInRad) { vRotation = angleInRad; @@ -936,7 +982,8 @@ public Vector3f getLookAtOffset() { /** * Sets the offset from the target's position where the camera looks at - * @param lookAtOffset + * + * @param lookAtOffset the desired offset (alias created) */ public void setLookAtOffset(Vector3f lookAtOffset) { this.lookAtOffset = lookAtOffset; @@ -944,7 +991,8 @@ public void setLookAtOffset(Vector3f lookAtOffset) { /** * Sets the up vector of the camera used for the lookAt on the target - * @param up + * + * @param up the desired direction (alias created) */ public void setUpVector(Vector3f up) { initialUpVec = up; @@ -968,7 +1016,8 @@ public void setHideCursorOnRotate(boolean hideCursorOnRotate) { /** * invert the vertical axis movement of the mouse - * @param invertYaxis + * + * @param invertYaxis true→invert, false→don't invert */ public void setInvertVerticalAxis(boolean invertYaxis) { this.invertYaxis = invertYaxis; @@ -986,7 +1035,8 @@ public void setInvertVerticalAxis(boolean invertYaxis) { /** * invert the Horizontal axis movement of the mouse - * @param invertXaxis + * + * @param invertXaxis true→invert, false→don't invert */ public void setInvertHorizontalAxis(boolean invertXaxis) { this.invertXaxis = invertXaxis; diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java index 7d79019115..ceb19bc280 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickAxis.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,21 +40,31 @@ */ public class DefaultJoystickAxis implements JoystickAxis { - private InputManager inputManager; - private Joystick parent; - private int axisIndex; - private String name; - private String logicalId; - private boolean isAnalog; - private boolean isRelative; + private final InputManager inputManager; + private final Joystick parent; + private final int axisIndex; + private final String name; + private final String logicalId; + private final boolean isAnalog; + private final boolean isRelative; private float deadZone; + private float jitterThreshold = 0f; /** * Creates a new joystick axis instance. Only used internally. + * + * @param inputManager (alias created) + * @param parent (alias created) + * @param axisIndex index for the new axis + * @param name name for the new axis + * @param logicalId logical identifier for the new axis + * @param isAnalog true→continuous range, false→discrete values + * @param isRelative true→presents relative values + * @param deadZone the radius of the dead zone */ public DefaultJoystickAxis(InputManager inputManager, Joystick parent, - int axisIndex, String name, String logicalId, - boolean isAnalog, boolean isRelative, float deadZone ) { + int axisIndex, String name, String logicalId, + boolean isAnalog, boolean isRelative, float deadZone) { this.inputManager = inputManager; this.parent = parent; this.axisIndex = axisIndex; @@ -71,7 +81,8 @@ public DefaultJoystickAxis(InputManager inputManager, Joystick parent, * @param positiveMapping The mapping to receive events when the axis is negative * @param negativeMapping The mapping to receive events when the axis is positive */ - public void assignAxis(String positiveMapping, String negativeMapping){ + @Override + public void assignAxis(String positiveMapping, String negativeMapping) { if (axisIndex != -1) { inputManager.addMapping(positiveMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, false)); inputManager.addMapping(negativeMapping, new JoyAxisTrigger(parent.getJoyId(), axisIndex, true)); @@ -81,6 +92,7 @@ public void assignAxis(String positiveMapping, String negativeMapping){ /** * Returns the joystick to which this axis object belongs. */ + @Override public Joystick getJoystick() { return parent; } @@ -90,6 +102,7 @@ public Joystick getJoystick() { * * @return the name of this joystick. */ + @Override public String getName() { return name; } @@ -99,15 +112,17 @@ public String getName() { * * @return the logical identifier of this joystick. */ + @Override public String getLogicalId() { return logicalId; - } + } /** * Returns the axisId of this joystick axis. * * @return the axisId of this joystick axis. */ + @Override public int getAxisId() { return axisIndex; } @@ -116,37 +131,48 @@ public int getAxisId() { * Returns true if this is an analog axis, meaning the values * are a continuous range instead of 1, 0, and -1. */ + @Override public boolean isAnalog() { return isAnalog; } - + /** * Returns true if this axis presents relative values. */ + @Override public boolean isRelative() { return isRelative; } - + /** * Returns the suggested dead zone for this axis. Values less than this * can be safely ignored. */ + @Override public float getDeadZone() { return deadZone; - } + } /** * Sets/overrides the dead zone for this axis. This indicates that values * within +/- deadZone should be ignored. + * + * @param f the desired radius */ - public void setDeadZone( float f ) { + public void setDeadZone(float f) { this.deadZone = f; - } + } @Override - public String toString(){ - return "JoystickAxis[name=" + name + ", parent=" + parent.getName() + ", id=" + axisIndex + public String toString() { + return "JoystickAxis[name=" + name + ", parent=" + parent.getName() + ", id=" + axisIndex + ", logicalId=" + logicalId + ", isAnalog=" + isAnalog - + ", isRelative=" + isRelative + ", deadZone=" + deadZone + "]"; + + ", isRelative=" + isRelative + ", deadZone=" + deadZone + + ", jitterThreshold=" + jitterThreshold + "]"; + } + + @Override + public float getJitterThreshold() { + return jitterThreshold; } } diff --git a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java index 827f33c6e6..2dd45d4191 100644 --- a/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java +++ b/jme3-core/src/main/java/com/jme3/input/DefaultJoystickButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,21 +39,21 @@ * @author Paul Speed */ public class DefaultJoystickButton implements JoystickButton { - - private InputManager inputManager; - private Joystick parent; - private int buttonIndex; - private String name; - private String logicalId; - public DefaultJoystickButton( InputManager inputManager, Joystick parent, int buttonIndex, - String name, String logicalId ) { + private final InputManager inputManager; + private final Joystick parent; + private final int buttonIndex; + private final String name; + private final String logicalId; + + public DefaultJoystickButton(InputManager inputManager, Joystick parent, int buttonIndex, + String name, String logicalId) { this.inputManager = inputManager; this.parent = parent; this.buttonIndex = buttonIndex; this.name = name; - this.logicalId = logicalId; - } + this.logicalId = logicalId; + } /** * Assign the mapping name to receive events from the given button index @@ -61,48 +61,53 @@ public DefaultJoystickButton( InputManager inputManager, Joystick parent, int bu * * @param mappingName The mapping to receive joystick button events. */ + @Override public void assignButton(String mappingName) { inputManager.addMapping(mappingName, new JoyButtonTrigger(parent.getJoyId(), buttonIndex)); } - + /** * Returns the joystick to which this axis object belongs. */ + @Override public Joystick getJoystick() { return parent; - } + } /** * Returns the name of this joystick. * * @return the name of this joystick. */ + @Override public String getName() { return name; - } + } /** * Returns the logical identifier of this joystick axis. * * @return the logical identifier of this joystick. */ + @Override public String getLogicalId() { return logicalId; - } + } /** - * Returns the unique buttonId of this joystick axis within a given + * Returns the unique buttonId of this joystick axis within a given * InputManager context. * * @return the buttonId of this joystick axis. */ + @Override public int getButtonId() { return buttonIndex; } - + @Override - public String toString(){ - return "JoystickButton[name=" + getName() + ", parent=" + parent.getName() + ", id=" + getButtonId() + public String toString() { + return "JoystickButton[name=" + getName() + ", parent=" + parent.getName() + ", id=" + getButtonId() + ", logicalId=" + getLogicalId() + "]"; } } diff --git a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java index f7ffa78f11..1a39ee9820 100644 --- a/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java +++ b/jme3-core/src/main/java/com/jme3/input/FlyByCamera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.MouseAxisTrigger; import com.jme3.input.controls.MouseButtonTrigger; -import com.jme3.math.FastMath; import com.jme3.math.Matrix3f; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -45,10 +44,10 @@ /** * A first-person camera controller. - * + *

    * After creation, you (or FlyCamAppState) must register the controller using * {@link #registerWithInput(com.jme3.input.InputManager)}. - * + *

    * Controls: * - Move (or, in drag-to-rotate mode, drag) the mouse to rotate the camera * - Mouse wheel for zooming in or out @@ -57,25 +56,25 @@ */ public class FlyByCamera implements AnalogListener, ActionListener { - private static String[] mappings = new String[]{ - CameraInput.FLYCAM_LEFT, - CameraInput.FLYCAM_RIGHT, - CameraInput.FLYCAM_UP, - CameraInput.FLYCAM_DOWN, + private static final String[] mappings = new String[]{ + CameraInput.FLYCAM_LEFT, + CameraInput.FLYCAM_RIGHT, + CameraInput.FLYCAM_UP, + CameraInput.FLYCAM_DOWN, - CameraInput.FLYCAM_STRAFELEFT, - CameraInput.FLYCAM_STRAFERIGHT, - CameraInput.FLYCAM_FORWARD, - CameraInput.FLYCAM_BACKWARD, + CameraInput.FLYCAM_STRAFELEFT, + CameraInput.FLYCAM_STRAFERIGHT, + CameraInput.FLYCAM_FORWARD, + CameraInput.FLYCAM_BACKWARD, - CameraInput.FLYCAM_ZOOMIN, - CameraInput.FLYCAM_ZOOMOUT, - CameraInput.FLYCAM_ROTATEDRAG, + CameraInput.FLYCAM_ZOOMIN, + CameraInput.FLYCAM_ZOOMOUT, + CameraInput.FLYCAM_ROTATEDRAG, - CameraInput.FLYCAM_RISE, - CameraInput.FLYCAM_LOWER, + CameraInput.FLYCAM_RISE, + CameraInput.FLYCAM_LOWER, - CameraInput.FLYCAM_INVERTY + CameraInput.FLYCAM_INVERTY }; /** * camera controlled by this controller (not null) @@ -84,7 +83,7 @@ public class FlyByCamera implements AnalogListener, ActionListener { /** * normalized "up" direction (a unit vector) */ - protected Vector3f initialUpVec; + protected Vector3f initialUpVec = new Vector3f(); /** * rotation-rate multiplier (1=default) */ @@ -110,82 +109,89 @@ public class FlyByCamera implements AnalogListener, ActionListener { protected boolean invertY = false; protected InputManager inputManager; + // Reusable temporary objects to reduce allocations during updates + private final Matrix3f tempMat = new Matrix3f(); + private final Quaternion tempQuat = new Quaternion(); + private final Vector3f tempUp = new Vector3f(); + private final Vector3f tempLeft = new Vector3f(); + private final Vector3f tempDir = new Vector3f(); + private final Vector3f tempVel = new Vector3f(); + private final Vector3f tempPos = new Vector3f(); + /** * Creates a new FlyByCamera to control the specified camera. * * @param cam camera to be controlled (not null) */ - public FlyByCamera(Camera cam){ + public FlyByCamera(Camera cam) { this.cam = cam; - initialUpVec = cam.getUp().clone(); + cam.getUp(initialUpVec); } /** * Sets the up vector that should be used for the camera. * - * @param upVec + * @param upVec the desired direction (not null, unaffected) */ public void setUpVector(Vector3f upVec) { initialUpVec.set(upVec); } - public void setMotionAllowedListener(MotionAllowedListener listener){ + public void setMotionAllowedListener(MotionAllowedListener listener) { this.motionAllowed = listener; } /** - * Set the translation speed. + * Sets the translation speed of the camera. * - * @param moveSpeed new speed (in world units per second) + * @param moveSpeed The new translation speed in world units per second. Must be non-negative. */ - public void setMoveSpeed(float moveSpeed){ + public void setMoveSpeed(float moveSpeed) { this.moveSpeed = moveSpeed; } /** - * Read the translation speed. + * Retrieves the current translation speed of the camera. * - * @return current speed (in world units per second) + * @return The current speed in world units per second. */ - public float getMoveSpeed(){ + public float getMoveSpeed() { return moveSpeed; } /** - * Set the rotation-rate multiplier. The bigger the multiplier, the more - * rotation for a given movement of the mouse. + * Sets the rotation-rate multiplier for mouse input. A higher value + * means the camera rotates more for a given mouse movement. * - * @param rotationSpeed new rate multiplier (1=default) + * @param rotationSpeed The new rate multiplier (1.0 is default). Must be non-negative. */ - public void setRotationSpeed(float rotationSpeed){ + public void setRotationSpeed(float rotationSpeed) { this.rotationSpeed = rotationSpeed; } /** - * Read the rotation-rate multiplier. The bigger the multiplier, the more - * rotation for a given movement of the mouse. + * Retrieves the current rotation-rate multiplier. * - * @return current rate multiplier (1=default) + * @return The current rate multiplier. */ - public float getRotationSpeed(){ + public float getRotationSpeed() { return rotationSpeed; } /** - * Set the zoom-rate multiplier. The bigger the multiplier, the more zoom - * for a given movement of the mouse wheel. + * Sets the zoom-rate multiplier for mouse wheel input. A higher value + * means the camera zooms more for a given mouse wheel scroll. * - * @param zoomSpeed new rate multiplier (1=default) + * @param zoomSpeed The new rate multiplier (1.0 is default). Must be non-negative. */ public void setZoomSpeed(float zoomSpeed) { this.zoomSpeed = zoomSpeed; } /** - * Read the zoom-rate multiplier. The bigger the multiplier, the more zoom - * for a given movement of the mouse wheel. + * Retrieves the current zoom-rate multiplier. * - * @return current rate multiplier (1=default) + * @return The current rate multiplier. */ public float getZoomSpeed() { return zoomSpeed; @@ -197,9 +203,9 @@ public float getZoomSpeed() { * * @param enable true to enable, false to disable */ - public void setEnabled(boolean enable){ - if (enabled && !enable){ - if (inputManager!= null && (!dragToRotate || (dragToRotate && canRotate))){ + public void setEnabled(boolean enable) { + if (enabled && !enable) { + if (inputManager != null && (!dragToRotate || (dragToRotate && canRotate))) { inputManager.setCursorVisible(true); } } @@ -207,20 +213,19 @@ public void setEnabled(boolean enable){ } /** - * Test whether this controller is enabled. + * Checks whether this camera controller is currently enabled. * - * @return true if enabled, otherwise false + * @return {@code true} if enabled, {@code false} otherwise. * @see #setEnabled(boolean) */ - public boolean isEnabled(){ + public boolean isEnabled() { return enabled; } /** - * Test whether drag-to-rotate mode is enabled. - * - * @return If drag to rotate feature is enabled. + * Checks whether drag-to-rotate mode is currently enabled. * + * @return {@code true} if drag-to-rotate is enabled, {@code false} otherwise. * @see #setDragToRotate(boolean) */ public boolean isDragToRotate() { @@ -246,15 +251,16 @@ public void setDragToRotate(boolean dragToRotate) { } /** - * Register this controller to receive input events from the specified input - * manager. + * Registers this controller to receive input events from the specified + * {@link InputManager}. This method sets up all the necessary input mappings + * for mouse, keyboard, and joysticks. * - * @param inputManager + * @param inputManager The InputManager instance to register with (must not be null). */ - public void registerWithInput(InputManager inputManager){ + public void registerWithInput(InputManager inputManager) { this.inputManager = inputManager; - // both mouse and button - rotation of cam + // Mouse and Keyboard Mappings for Rotation inputManager.addMapping(CameraInput.FLYCAM_LEFT, new MouseAxisTrigger(MouseInput.AXIS_X, true), new KeyTrigger(KeyInput.KEY_LEFT)); @@ -267,7 +273,7 @@ public void registerWithInput(InputManager inputManager){ inputManager.addMapping(CameraInput.FLYCAM_DOWN, new MouseAxisTrigger(MouseInput.AXIS_Y, true), new KeyTrigger(KeyInput.KEY_DOWN)); - // mouse only - zoom in/out with wheel, and rotate drag + // Mouse Mappings for Zoom and Drag-to-Rotate inputManager.addMapping(CameraInput.FLYCAM_ZOOMIN, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, false)); inputManager.addMapping(CameraInput.FLYCAM_ZOOMOUT, new MouseAxisTrigger(MouseInput.AXIS_WHEEL, true)); inputManager.addMapping(CameraInput.FLYCAM_ROTATEDRAG, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); @@ -284,32 +290,40 @@ public void registerWithInput(InputManager inputManager){ inputManager.setCursorVisible(dragToRotate || !isEnabled()); Joystick[] joysticks = inputManager.getJoysticks(); - if (joysticks != null && joysticks.length > 0){ + if (joysticks != null && joysticks.length > 0) { for (Joystick j : joysticks) { mapJoystick(j); } } } - protected void mapJoystick( Joystick joystick ) { - + /** + * Configures joystick input mappings for the camera controller. This method + * attempts to map joystick axes and buttons to camera actions. + * + * @param joystick The {@link Joystick} to map (not null). + */ + protected void mapJoystick(Joystick joystick) { // Map it differently if there are Z axis - if( joystick.getAxis( JoystickAxis.Z_ROTATION ) != null && joystick.getAxis( JoystickAxis.Z_AXIS ) != null ) { + if (joystick.getAxis(JoystickAxis.Z_ROTATION) != null + && joystick.getAxis(JoystickAxis.Z_AXIS) != null) { // Make the left stick move - joystick.getXAxis().assignAxis( CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT ); - joystick.getYAxis().assignAxis( CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD ); + joystick.getXAxis().assignAxis(CameraInput.FLYCAM_STRAFERIGHT, CameraInput.FLYCAM_STRAFELEFT); + joystick.getYAxis().assignAxis(CameraInput.FLYCAM_BACKWARD, CameraInput.FLYCAM_FORWARD); // And the right stick control the camera - joystick.getAxis( JoystickAxis.Z_ROTATION ).assignAxis( CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP ); - joystick.getAxis( JoystickAxis.Z_AXIS ).assignAxis( CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT ); + joystick.getAxis(JoystickAxis.Z_ROTATION) + .assignAxis(CameraInput.FLYCAM_DOWN, CameraInput.FLYCAM_UP); + joystick.getAxis(JoystickAxis.Z_AXIS) + .assignAxis(CameraInput.FLYCAM_RIGHT, CameraInput.FLYCAM_LEFT); // And let the dpad be up and down joystick.getPovYAxis().assignAxis(CameraInput.FLYCAM_RISE, CameraInput.FLYCAM_LOWER); - if( joystick.getButton( "Button 8" ) != null ) { - // Let the stanard select button be the y invert toggle - joystick.getButton( "Button 8" ).assignButton( CameraInput.FLYCAM_INVERTY ); + if (joystick.getButton("Button 8") != null) { + // Let the standard select button be the y invert toggle + joystick.getButton("Button 8").assignButton(CameraInput.FLYCAM_INVERTY); } } else { @@ -321,131 +335,128 @@ protected void mapJoystick( Joystick joystick ) { } /** - * Unregister this controller from its input manager. + * Unregisters this controller from its currently associated {@link InputManager}. */ - public void unregisterInput(){ + public void unregisterInput() { if (inputManager == null) { return; } for (String s : mappings) { if (inputManager.hasMapping(s)) { - inputManager.deleteMapping( s ); + inputManager.deleteMapping(s); } } inputManager.removeListener(this); inputManager.setCursorVisible(!dragToRotate); - Joystick[] joysticks = inputManager.getJoysticks(); - if (joysticks != null && joysticks.length > 0){ - // No way to unassign axis - } + // Joysticks cannot be "unassigned" in the same way, but mappings are removed with listener. + // Joystick-specific mapping might persist but won't trigger this listener. + inputManager = null; // Clear reference } /** - * Rotate the camera by the specified amount around the specified axis. + * Rotates the camera by the specified amount around the given axis. * - * @param value rotation amount - * @param axis direction of rotation (a unit vector) + * @param value The amount of rotation. + * @param axis The axis around which to rotate (a unit vector, unaffected). */ - protected void rotateCamera(float value, Vector3f axis){ - if (dragToRotate){ - if (canRotate){ -// value = -value; - }else{ - return; - } + protected void rotateCamera(float value, Vector3f axis) { + if (dragToRotate && !canRotate) { + return; // In drag-to-rotate mode, only rotate if canRotate is true. } - Matrix3f mat = new Matrix3f(); - mat.fromAngleNormalAxis(rotationSpeed * value, axis); + tempMat.fromAngleNormalAxis(rotationSpeed * value, axis); - Vector3f up = cam.getUp(); - Vector3f left = cam.getLeft(); - Vector3f dir = cam.getDirection(); + // Get current camera axes into temporary vectors + cam.getUp(tempUp); + cam.getLeft(tempLeft); + cam.getDirection(tempDir); - mat.mult(up, up); - mat.mult(left, left); - mat.mult(dir, dir); + // Apply rotation to the camera's axes + tempMat.mult(tempUp, tempUp); + tempMat.mult(tempLeft, tempLeft); + tempMat.mult(tempDir, tempDir); - Quaternion q = new Quaternion(); - q.fromAxes(left, up, dir); - q.normalizeLocal(); + // Set camera axes using a temporary Quaternion + tempQuat.fromAxes(tempLeft, tempUp, tempDir); + tempQuat.normalizeLocal(); // Ensure quaternion is normalized - cam.setAxes(q); + cam.setAxes(tempQuat); } /** - * Zoom the camera by the specified amount. + * Zooms the camera by the specified amount. This method handles both + * perspective and parallel projections. * - * @param value zoom amount + * @param value The amount to zoom. Positive values typically zoom in, negative out. */ - protected void zoomCamera(float value){ - // derive fovY value - float h = cam.getFrustumTop(); - float w = cam.getFrustumRight(); - float aspect = w / h; - - float near = cam.getFrustumNear(); - - float fovY = FastMath.atan(h / near) - / (FastMath.DEG_TO_RAD * .5f); - float newFovY = fovY + value * 0.1f * zoomSpeed; - if (newFovY > 0f) { - // Don't let the FOV go zero or negative. - fovY = newFovY; - } - - h = FastMath.tan( fovY * FastMath.DEG_TO_RAD * .5f) * near; - w = h * aspect; + protected void zoomCamera(float value) { + if (cam.isParallelProjection()) { + float zoomFactor = 1.0F + value * 0.01F * zoomSpeed; + if (zoomFactor > 0F) { + float left = zoomFactor * cam.getFrustumLeft(); + float right = zoomFactor * cam.getFrustumRight(); + float top = zoomFactor * cam.getFrustumTop(); + float bottom = zoomFactor * cam.getFrustumBottom(); + float near = cam.getFrustumNear(); + float far = cam.getFrustumFar(); + cam.setFrustum(near, far, left, right, top, bottom); + } - cam.setFrustumTop(h); - cam.setFrustumBottom(-h); - cam.setFrustumLeft(-w); - cam.setFrustumRight(w); + } else { // perspective projection + float newFov = cam.getFov() + value * 0.1F * zoomSpeed; + // Use a small epsilon to prevent near-zero FoV issues + if (newFov > 0.01f) { + cam.setFov(newFov); + } + } } /** - * Translate the camera upward by the specified amount. + * Translates the camera vertically (up or down) by the specified amount, + * considering the {@code initialUpVec}. * - * @param value translation amount + * @param value The translation amount. Positive values move the camera up, negative down. */ - protected void riseCamera(float value){ - Vector3f vel = initialUpVec.mult(value * moveSpeed); - Vector3f pos = cam.getLocation().clone(); + protected void riseCamera(float value) { + tempVel.set(initialUpVec).multLocal(value * moveSpeed); + tempPos.set(cam.getLocation()); - if (motionAllowed != null) - motionAllowed.checkMotionAllowed(pos, vel); - else - pos.addLocal(vel); + if (motionAllowed != null) { + motionAllowed.checkMotionAllowed(tempPos.clone(), tempVel.clone()); + } else { + tempPos.addLocal(tempVel); + } - cam.setLocation(pos); + cam.setLocation(tempPos); } /** - * Translate the camera left or forward by the specified amount. + * Translates the camera left/right or forward/backward by the specified amount. * - * @param value translation amount - * @param sideways true→left, false→forward + * @param value The translation amount. Positive values move in the primary + * direction (right/forward), negative in the opposite. + * @param sideways If {@code true}, the camera moves left/right (strafes). + * If {@code false}, the camera moves forward/backward. */ - protected void moveCamera(float value, boolean sideways){ - Vector3f vel = new Vector3f(); - Vector3f pos = cam.getLocation().clone(); - - if (sideways){ - cam.getLeft(vel); - }else{ - cam.getDirection(vel); + protected void moveCamera(float value, boolean sideways) { + if (sideways) { + cam.getLeft(tempVel); + } else { + cam.getDirection(tempVel); } - vel.multLocal(value * moveSpeed); + tempVel.multLocal(value * moveSpeed); + tempPos.set(cam.getLocation()); - if (motionAllowed != null) - motionAllowed.checkMotionAllowed(pos, vel); - else - pos.addLocal(vel); + if (motionAllowed != null) { + motionAllowed.checkMotionAllowed(tempPos.clone(), tempVel.clone()); + } else { + tempPos.addLocal(tempVel); + } - cam.setLocation(pos); + cam.setLocation(tempPos); } /** @@ -460,29 +471,29 @@ public void onAnalog(String name, float value, float tpf) { if (!enabled) return; - if (name.equals(CameraInput.FLYCAM_LEFT)){ + if (name.equals(CameraInput.FLYCAM_LEFT)) { rotateCamera(value, initialUpVec); - }else if (name.equals(CameraInput.FLYCAM_RIGHT)){ + } else if (name.equals(CameraInput.FLYCAM_RIGHT)) { rotateCamera(-value, initialUpVec); - }else if (name.equals(CameraInput.FLYCAM_UP)){ - rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft()); - }else if (name.equals(CameraInput.FLYCAM_DOWN)){ - rotateCamera(value * (invertY ? -1 : 1), cam.getLeft()); - }else if (name.equals(CameraInput.FLYCAM_FORWARD)){ + } else if (name.equals(CameraInput.FLYCAM_UP)) { + rotateCamera(-value * (invertY ? -1 : 1), cam.getLeft(tempLeft)); + } else if (name.equals(CameraInput.FLYCAM_DOWN)) { + rotateCamera(value * (invertY ? -1 : 1), cam.getLeft(tempLeft)); + } else if (name.equals(CameraInput.FLYCAM_FORWARD)) { moveCamera(value, false); - }else if (name.equals(CameraInput.FLYCAM_BACKWARD)){ + } else if (name.equals(CameraInput.FLYCAM_BACKWARD)) { moveCamera(-value, false); - }else if (name.equals(CameraInput.FLYCAM_STRAFELEFT)){ + } else if (name.equals(CameraInput.FLYCAM_STRAFELEFT)) { moveCamera(value, true); - }else if (name.equals(CameraInput.FLYCAM_STRAFERIGHT)){ + } else if (name.equals(CameraInput.FLYCAM_STRAFERIGHT)) { moveCamera(-value, true); - }else if (name.equals(CameraInput.FLYCAM_RISE)){ + } else if (name.equals(CameraInput.FLYCAM_RISE)) { riseCamera(value); - }else if (name.equals(CameraInput.FLYCAM_LOWER)){ + } else if (name.equals(CameraInput.FLYCAM_LOWER)) { riseCamera(-value); - }else if (name.equals(CameraInput.FLYCAM_ZOOMIN)){ + } else if (name.equals(CameraInput.FLYCAM_ZOOMIN)) { zoomCamera(value); - }else if (name.equals(CameraInput.FLYCAM_ZOOMOUT)){ + } else if (name.equals(CameraInput.FLYCAM_ZOOMOUT)) { zoomCamera(-value); } } @@ -491,20 +502,20 @@ public void onAnalog(String name, float value, float tpf) { * Callback to notify this controller of an action input event. * * @param name name of the input event - * @param value true if the action is "pressed", false otherwise + * @param isPressed true if the action is "pressed", false otherwise * @param tpf time per frame (in seconds) */ @Override - public void onAction(String name, boolean value, float tpf) { + public void onAction(String name, boolean isPressed, float tpf) { if (!enabled) return; - if (name.equals(CameraInput.FLYCAM_ROTATEDRAG) && dragToRotate){ - canRotate = value; - inputManager.setCursorVisible(!value); + if (name.equals(CameraInput.FLYCAM_ROTATEDRAG) && dragToRotate) { + canRotate = isPressed; + inputManager.setCursorVisible(!isPressed); } else if (name.equals(CameraInput.FLYCAM_INVERTY)) { // Invert the "up" direction. - if( !value ) { + if (!isPressed) { invertY = !invertY; } } diff --git a/jme3-core/src/main/java/com/jme3/input/Input.java b/jme3-core/src/main/java/com/jme3/input/Input.java index 924065932e..169dfa7154 100644 --- a/jme3-core/src/main/java/com/jme3/input/Input.java +++ b/jme3-core/src/main/java/com/jme3/input/Input.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -69,7 +69,8 @@ public interface Input { * Sets the input listener to receive events from this device. The * appropriate events should be dispatched through the callbacks * in RawInputListener. - * @param listener + * + * @param listener the desired listener */ public void setInputListener(RawInputListener listener); diff --git a/jme3-core/src/main/java/com/jme3/input/InputManager.java b/jme3-core/src/main/java/com/jme3/input/InputManager.java index a8f56c3b9a..b45433d1d9 100644 --- a/jme3-core/src/main/java/com/jme3/input/InputManager.java +++ b/jme3-core/src/main/java/com/jme3/input/InputManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -94,26 +94,25 @@ public class InputManager implements RawInputListener { private long lastLastUpdateTime = 0; private long lastUpdateTime = 0; private long frameDelta = 0; - private long firstTime = 0; private boolean eventsPermitted = false; private boolean mouseVisible = true; private boolean safeMode = false; private float globalAxisDeadZone = 0.05f; private final Vector2f cursorPos = new Vector2f(); private Joystick[] joysticks; - private final IntMap> bindings = new IntMap>(); - private final HashMap mappings = new HashMap(); - private final IntMap pressedButtons = new IntMap(); - private final IntMap axisValues = new IntMap(); - private final SafeArrayList rawListeners = new SafeArrayList(RawInputListener.class); - private final ArrayList inputQueue = new ArrayList(); + private final IntMap> bindings = new IntMap<>(); + private final HashMap mappings = new HashMap<>(); + private final IntMap pressedButtons = new IntMap<>(); + private final IntMap axisValues = new IntMap<>(); + private final SafeArrayList rawListeners = new SafeArrayList<>(RawInputListener.class); + private final ArrayList inputQueue = new ArrayList<>(); private final List joystickConnectionListeners = new ArrayList<>(); private static class Mapping { private final String name; - private final ArrayList triggers = new ArrayList(); - private final ArrayList listeners = new ArrayList(); + private final ArrayList triggers = new ArrayList<>(); + private final ArrayList listeners = new ArrayList<>(); public Mapping(String name) { this.name = name; @@ -125,10 +124,10 @@ public Mapping(String name) { * *

    This should only be called internally in {@link Application}. * - * @param mouse - * @param keys - * @param joystick - * @param touch + * @param mouse (not null, alias created) + * @param keys (not null, alias created) + * @param joystick (may be null, alias created) + * @param touch (may be null, alias created) * @throws IllegalArgumentException If either mouseInput or keyInput are null. */ public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInput touch) { @@ -151,7 +150,11 @@ public InputManager(MouseInput mouse, KeyInput keys, JoyInput joystick, TouchInp touch.setInputListener(this); } - firstTime = keys.getInputTimeNanos(); + keys.getInputTimeNanos(); + } + + public String getKeyName(int key){ + return keys.getKeyName(key); } private void invokeActions(int hash, boolean pressed) { @@ -178,7 +181,7 @@ private float computeAnalogValue(long timeDelta) { if (safeMode || frameDelta == 0) { return 1f; } else { - return FastMath.clamp((float) timeDelta / (float) frameDelta, 0, 1); + return FastMath.clamp(timeDelta / (float) frameDelta, 0, 1); } } @@ -457,7 +460,7 @@ public void onMouseButtonEvent(MouseButtonEvent evt) { if (!eventsPermitted) { throw new UnsupportedOperationException("MouseInput has raised an event at an illegal time."); } - //updating cursor pos on click, so that non android touch events can properly update cursor position. + // Update cursor pos on click, so that non-Android touch events can properly update cursor position. cursorPos.set(evt.getX(), evt.getY()); inputQueue.add(evt); } @@ -595,7 +598,7 @@ public void addMapping(String mappingName, Trigger... triggers) { * for the given mappingName. * * @param mappingName The mapping name to check. - * + * @return true if the mapping is registered, otherwise false * @see InputManager#addMapping(java.lang.String, com.jme3.input.controls.Trigger[]) * @see InputManager#deleteMapping(java.lang.String) */ @@ -654,7 +657,7 @@ public void deleteTrigger(String mappingName, Trigger trigger) { /** * Clears all the input mappings from this InputManager. - * Consequently, also clears all of the + * Consequently, this clears all of the * InputListeners as well. */ public void clearMappings() { @@ -775,6 +778,7 @@ public void setSimulateMouse(boolean value) { * @deprecated Use isSimulateMouse * Returns state of simulation of mouse events. Used for touchscreen input only. * + * @return true if a mouse is simulated, otherwise false */ @Deprecated public boolean getSimulateMouse() { @@ -788,6 +792,7 @@ public boolean getSimulateMouse() { /** * Returns state of simulation of mouse events. Used for touchscreen input only. * + * @return true if a mouse is simulated, otherwise false */ public boolean isSimulateMouse() { if (touch != null) { @@ -811,6 +816,7 @@ public void setSimulateKeyboard(boolean value) { /** * Returns state of simulation of key events. Used for touchscreen input only. * + * @return true if a keyboard is simulated, otherwise false */ public boolean isSimulateKeyboard() { if (touch != null) { @@ -961,7 +967,7 @@ public void onTouchEvent(TouchEvent evt) { * Re-sets the joystick list when a joystick is added or removed. * This should only be called internally. * - * @param joysticks + * @param joysticks (alias created) */ public void setJoysticks(Joystick[] joysticks) { this.joysticks = joysticks; @@ -969,8 +975,10 @@ public void setJoysticks(Joystick[] joysticks) { /** * Add a listener that reports when a joystick has been added or removed. - * Currently only implemented in LWJGL3 - * @param listener the listner. + * Currently implemented only in LWJGL3. + * + * @param listener the listener + * @return true */ public boolean addJoystickConnectionListener(JoystickConnectionListener listener) { return joystickConnectionListeners.add(listener); diff --git a/jme3-core/src/main/java/com/jme3/input/Joystick.java b/jme3-core/src/main/java/com/jme3/input/Joystick.java index e172224c2f..967422fc6f 100644 --- a/jme3-core/src/main/java/com/jme3/input/Joystick.java +++ b/jme3-core/src/main/java/com/jme3/input/Joystick.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -77,11 +77,13 @@ public interface Joystick { * Returns the JoystickAxis with the specified logical ID. * * @param logicalId The id of the axis to search for as returned by JoystickAxis.getLogicalId(). + * @return the pre-existing instance, or null if not found */ public JoystickAxis getAxis(String logicalId); /** * Returns a read-only list of all joystick axes for this Joystick. + * @return an unmodifiable list of pre-existing instances */ public List getAxes(); @@ -89,11 +91,13 @@ public interface Joystick { * Returns the JoystickButton with the specified logical ID. * * @param logicalId The id of the axis to search for as returned by JoystickButton.getLogicalId(). + * @return the pre-existing instance, or null if not found */ public JoystickButton getButton(String logicalId); /** * Returns a read-only list of all joystick buttons for this Joystick. + * @return an unmodifiable list of pre-existing instances */ public List getButtons(); @@ -102,6 +106,7 @@ public interface Joystick { * *

    E.g. for most gamepads, the left control stick X axis will be returned. * + * @return the pre-existing instance * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public JoystickAxis getXAxis(); @@ -111,6 +116,7 @@ public interface Joystick { * *

    E.g. for most gamepads, the left control stick Y axis will be returned. * + * @return the pre-existing instance * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public JoystickAxis getYAxis(); @@ -119,6 +125,7 @@ public interface Joystick { * Returns the POV X axis for this joystick. This is a convenience axis * providing an x-axis subview of the HAT axis. * + * @return the pre-existing instance * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public JoystickAxis getPovXAxis(); @@ -127,6 +134,7 @@ public interface Joystick { * Returns the POV Y axis for this joystick. This is a convenience axis * providing a y-axis subview of the HAT axis. * + * @return the pre-existing instance * @see JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public JoystickAxis getPovYAxis(); diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java index 45a0733115..4168e32711 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickAxis.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,27 +44,14 @@ public interface JoystickAxis { public static final String Z_ROTATION = "rz"; public static final String LEFT_TRIGGER = "rx"; public static final String RIGHT_TRIGGER = "ry"; - - // Note: the left/right trigger bit may be a bit controversial in - // the sense that this is one case where XBox controllers make a lot - // more sense. - // I've seen the following mappings for various things: - // - // Axis | XBox | Non-Xbox (generally) (includes actual Sony PS4 controllers) - // --------------+-------+--------------- - // left trigger | z | rx (also button 6) - // right trigger | rz | ry (also button 7) - // left stick x | x | x - // left stick y | y | y - // right stick x | rx | z - // right stick y | ry | rz - // - // The issue is that in all cases I've seen, the XBox controllers will - // use the name "xbox" somewhere in their name. The Non-XBox controllers - // never mention anything uniform... even the PS4 controller only calls - // itself "Wireless Controller". In that light, it seems easier to make - // the default the ugly case and the "XBox" way the exception because it - // can more easily be identified. + + public static final String AXIS_XBOX_LEFT_TRIGGER = LEFT_TRIGGER; + public static final String AXIS_XBOX_RIGHT_TRIGGER = RIGHT_TRIGGER; + public static final String AXIS_XBOX_LEFT_THUMB_STICK_X = X_AXIS; + public static final String AXIS_XBOX_LEFT_THUMB_STICK_Y = Y_AXIS; + public static final String AXIS_XBOX_RIGHT_THUMB_STICK_X = Z_AXIS; + public static final String AXIS_XBOX_RIGHT_THUMB_STICK_Y = Z_ROTATION; + public static final String POV_X = "pov_x"; public static final String POV_Y = "pov_y"; @@ -79,6 +66,8 @@ public interface JoystickAxis { /** * Returns the joystick to which this axis object belongs. + * + * @return the pre-existing instance */ public Joystick getJoystick(); @@ -107,17 +96,34 @@ public interface JoystickAxis { /** * Returns true if this is an analog axis, meaning the values * are a continuous range instead of 1, 0, and -1. + * + * @return true if analog, otherwise false */ public boolean isAnalog(); /** * Returns true if this axis presents relative values. + * + * @return true if relative, otherwise false */ public boolean isRelative(); /** * Returns the suggested dead zone for this axis. Values less than this * can be safely ignored. + * + * @return the radius of the dead zone */ public float getDeadZone(); + + + /** + * Returns the suggested jitter threshold for this axis. Movements with a delta + * smaller than this threshold will be ignored by the backend input system + * + * @return the jitter threshold + */ + public default float getJitterThreshold(){ + return 0; + } } diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickButton.java b/jme3-core/src/main/java/com/jme3/input/JoystickButton.java index 5e7d3706a4..11c0155087 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickButton.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickButton.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,30 @@ public interface JoystickButton { public static final String BUTTON_9 = "9"; public static final String BUTTON_10 = "10"; public static final String BUTTON_11 = "11"; + public static final String BUTTON_12 = "12"; + public static final String BUTTON_13 = "13"; + public static final String BUTTON_14 = "14"; + public static final String BUTTON_15 = "15"; + + + public static final String BUTTON_XBOX_A = BUTTON_2; + public static final String BUTTON_XBOX_B = BUTTON_1; + public static final String BUTTON_XBOX_X = BUTTON_3; + public static final String BUTTON_XBOX_Y = BUTTON_0; + public static final String BUTTON_XBOX_LB = BUTTON_4; + public static final String BUTTON_XBOX_RB = BUTTON_5; + public static final String BUTTON_XBOX_LT = BUTTON_6; + public static final String BUTTON_XBOX_RT = BUTTON_7; + public static final String BUTTON_XBOX_BACK = BUTTON_8; + public static final String BUTTON_XBOX_START = BUTTON_9; + public static final String BUTTON_XBOX_L3 = BUTTON_10; + public static final String BUTTON_XBOX_R3 = BUTTON_11; + + public static final String BUTTON_XBOX_DPAD_UP = BUTTON_12; + public static final String BUTTON_XBOX_DPAD_DOWN = BUTTON_13; + public static final String BUTTON_XBOX_DPAD_LEFT = BUTTON_14; + public static final String BUTTON_XBOX_DPAD_RIGHT = BUTTON_15; + /** * Assign the mapping name to receive events from the given button index @@ -61,6 +85,8 @@ public interface JoystickButton { /** * Returns the joystick to which this axis object belongs. + * + * @return the pre-existing instance */ public Joystick getJoystick(); diff --git a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java index db3b4aec99..d2385be4f9 100644 --- a/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java +++ b/jme3-core/src/main/java/com/jme3/input/JoystickCompatibilityMappings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,16 +44,19 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import com.jme3.util.res.Resources; + /** - * Provides compatibility mapping to different joysticks - * that both report their name in a unique way and require - * remapping to achieve a proper default layout. + * Provides compatibility mapping to different joysticks + * that both report their name in a unique way and require + * remapping to achieve a proper default layout. * - *

    All mappings MUST be defined before the joystick support - * has been initialized in the InputManager.

    + *

    All mappings MUST be defined before the joystick support + * has been initialized in the InputManager.

    * - * @author Paul Speed + * @author Paul Speed + * @author Markil3 */ public class JoystickCompatibilityMappings { @@ -61,171 +64,532 @@ public class JoystickCompatibilityMappings { // List of resource paths to check for the joystick-mapping.properties // files. - private static String[] searchPaths = { "joystick-mapping.properties" }; + private static String[] searchPaths = {"joystick-mapping.properties"}; - private static Map> joystickMappings = new HashMap>(); + private static Map> joystickMappings = new HashMap>(); + private static Map> axisMappings = new HashMap>(); + private static Map axisRangeMappings = new HashMap<>(); + private static Map> buttonMappings = new HashMap>(); // Remaps names by regex. - private static Map nameRemappings = new HashMap<>(); - private static Map nameCache = new HashMap<>(); + private static final Map nameRemappings = new HashMap<>(); + private static final Map nameCache = new HashMap<>(); static { loadDefaultMappings(); } - protected static Map getMappings( String joystickName, boolean create ) { - Map result = joystickMappings.get(joystickName.trim()); - if( result == null && create ) { - result = new HashMap(); - joystickMappings.put(joystickName.trim(),result); + /** + * A private constructor to inhibit instantiation of this class. + */ + private JoystickCompatibilityMappings() { + } + + protected static Map getMappings(String joystickName, boolean create) { + Map result = joystickMappings.get(joystickName.trim()); + if (result == null && create) { + result = new HashMap(); + joystickMappings.put(joystickName.trim(), result); + } + return result; + } + + /** + * Obtains mappings specific to the joystick axis + * + * @param joystickName - The name of the joystick type to obtain mappings for. + * @param create - If there are no mappings present and this parameter is true, then a new entry for this joystick is created. + * @return The various axis remappings for the requested joystick, or null of there are none. + */ + private static Map getAxisMappings(String joystickName, boolean create) { + Map result = axisMappings.get(joystickName.trim()); + if (result == null && create) { + result = new HashMap(); + axisMappings.put(joystickName.trim(), result); + } + return result; + } + + /** + * Obtains mappings specific to the joystick buttons + * + * @param joystickName - The name of the joystick type to obtain mappings for. + * @param create - If there are no mappings present and this parameter is true, then a new entry for this joystick is created. + * @return The various button remappings for the requested joystick, or null of there are none. + */ + protected static Map getButtonMappings(String joystickName, boolean create) { + Map result = buttonMappings.get(joystickName.trim()); + if (result == null && create) { + result = new HashMap(); + buttonMappings.put(joystickName.trim(), result); + } + return result; + } + + /** + * This method will take a "raw" axis value from the system and rescale it based on what the remapper has specified. For example, if the remapper specified an axis to be scaled to [0.0,1.0], then a raw value of -0.5 would be converted to 0.25. + * + * @param axis - The axis to remap. + * @param currentValue - The raw value the system is outputting, on a scale of -1.0 to 1.0. + * @return The new value that will be provided to listeners, on a scale specified by the remappings file. + */ + public static float remapAxisRange(JoystickAxis axis, float currentValue) { + String joyName = axis.getJoystick().getName(); + Map map; + float[] range = axisRangeMappings.get(axis); + if (range == null) { + map = getAxisMappings(joyName, false); + if (map != null && map.containsKey(axis.getName())) { + range = map.get(axis.getName()).range; + axisRangeMappings.put(axis, range); + } else { + // Try the normalized name + joyName = getNormalizedName(joyName); + if (joyName != null) { + map = getAxisMappings(joyName, false); + if (map != null && map.containsKey(axis.getName())) { + range = map.get(axis.getName()).range; + axisRangeMappings.put(axis, range); + } + } + } + } + if (range == null) { + axisRangeMappings.put(axis, new float[0]); + return currentValue; + } + + /* + * If we have an array of size 0, that means we have acknowledged this axis (so we don't + * need to go searching for it every tick), but that there is no remapping. + */ + if (range.length == 0) { + return currentValue; + } + + return (currentValue + range[1] + range[0]) * ((range[1] - range[0]) / 2); + } + + /** + * Takes the original name of an axis, specifically, and returns the new name it will function under. + * + * @param joystickName - The joystick type the axis comes from. + * @param componentId - The system-provided name for the axis. + * @return The new name for the axis, or just componentId if no remapping was provided. + */ + public static String remapAxis(String joystickName, String componentId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "remapAxis({0}, {1})", new Object[]{joystickName, componentId}); + } + + // Always try the specific name first. + joystickName = joystickName.trim(); + Map map = getAxisMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped axis:{0}", map.get(componentId)); + } + return ((AxisData) map.get(componentId)).name; + } + + map = getMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped axis:{0}", map.get(componentId)); + } + return ((String) map.get(componentId)); + } + + // Try the normalized name + joystickName = getNormalizedName(joystickName); + logger.log(Level.FINE, "normalized joystick name:{0}", joystickName); + if (joystickName == null) { + return componentId; + } + + map = getAxisMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } + return ((AxisData) map.get(componentId)).name; + } + + map = getMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } + return ((String) map.get(componentId)); + } + + return componentId; + } + + /** + * Takes the original name of a button, specifically, and returns the new name it will function under. + * + * @param joystickName - The joystick type the axis comes from. + * @param componentId - The system-provided name for the button. + * @return The new name for the button, or just componentId if no remapping was provided. + */ + public static String remapButton(String joystickName, String componentId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "remapAxis({0}, {1})", new Object[]{joystickName, componentId}); + } + + // Always try the specific name first. + joystickName = joystickName.trim(); + Map map = getButtonMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped axis:{0}", map.get(componentId)); + } + return map.get(componentId); + } + + map = getMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped axis:{0}", map.get(componentId)); + } + return map.get(componentId); + } + + // Try the normalized name + joystickName = getNormalizedName(joystickName); + logger.log(Level.FINE, "normalized joystick name:{0}", joystickName); + if (joystickName == null) { + return componentId; } - return result; + + map = getButtonMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } + return map.get(componentId); + } + + map = getMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } + return map.get(componentId); + } + + return componentId; } - + /** - * Returns the remapped version of the axis/button name if there - * is a mapping for it otherwise it returns the original name. + * Returns the remapped version of the axis/button name if there + * is a mapping for it otherwise it returns the original name. + * + * @param joystickName which joystick (not null) + * @param componentId the unmapped axis/button name + * @return the resulting axis/button name */ - public static String remapComponent( String joystickName, String componentId ) { - logger.log(Level.FINE, "remapComponent(" + joystickName + ", " + componentId + ")"); - + public static String remapComponent(String joystickName, String componentId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "remapComponent({0}, {1})", new Object[]{joystickName, componentId}); + } + // Always try the specific name first. - joystickName = joystickName.trim(); - Map map = getMappings(joystickName, false); - if( map != null && map.containsKey(componentId) ) { - logger.log(Level.FINE, "returning remapped:" + map.get(componentId)); + joystickName = joystickName.trim(); + Map map = getMappings(joystickName, false); + if (map != null && map.containsKey(componentId)) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } return map.get(componentId); } // Try the normalized name joystickName = getNormalizedName(joystickName); - logger.log(Level.FINE, "normalized joystick name:" + joystickName); - if( joystickName == null ) { + logger.log(Level.FINE, "normalized joystick name:{0}", joystickName); + if (joystickName == null) { return componentId; } map = getMappings(joystickName, false); - if( map == null ) { + if (map == null) { return componentId; - } - if( !map.containsKey(componentId) ) { + } + if (!map.containsKey(componentId)) { return componentId; } - logger.log(Level.FINE, "returning remapped:" + map.get(componentId)); - return map.get(componentId); - } - + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "returning remapped:{0}", map.get(componentId)); + } + return map.get(componentId); + } + /** - * Returns a set of Joystick axis/button name remappings if they exist otherwise - * it returns an empty map. + * Returns a set of Joystick button name remappings if they exist otherwise + * it returns an empty map. + * + * @param joystickName which joystick (not null) + * @return an unmodifiable map */ - public static Map getJoystickMappings( String joystickName ) { - Map result = getMappings(joystickName.trim(), false); - if( result == null ) + public static Map getJoystickButtonMappings(String joystickName) { + Map result = getButtonMappings(joystickName.trim(), false); + if (result == null) return Collections.emptyMap(); return Collections.unmodifiableMap(result); } - + + /** + * Returns a set of Joystick axis/button name remappings if they exist otherwise + * it returns an empty map. + * + * @param joystickName which joystick (not null) + * @return an unmodifiable map + */ + public static Map getJoystickMappings(String joystickName) { + Map result = getMappings(joystickName.trim(), false); + if (result == null) + return Collections.emptyMap(); + return Collections.unmodifiableMap(result); + } + + /** + * Adds a single Joystick axis or button remapping based on the + * joystick's name and axis/button name. The "remap" value will be + * used instead. + * + * @param stickName which joystick (not null) + * @param sourceComponentId the name to be remapped + * @param remapId the remapped name + */ + public static void addAxisMapping(String stickName, String sourceComponentId, String remapId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "addAxisMapping({0}, {1}, {2})", + new Object[]{stickName, sourceComponentId, remapId}); + } + getAxisMappings(stickName, true).put(sourceComponentId, new AxisData(remapId, new float[0])); + } + + /** + * Adds a single Joystick axis or button remapping based on the + * joystick's name and axis/button name. The "remap" value will be + * used instead. + * + * @param stickName which joystick (not null) + * @param sourceComponentId the name to be remapped + * @param remapId the remapped name + * @param range the desired range (not null, exactly 2 elements) + */ + public static void addAxisMapping(String stickName, String sourceComponentId, String remapId, float[] range) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "addAxisMapping({0}, {1}, {2})", + new Object[]{stickName, sourceComponentId, remapId}); + } + if (range.length != 2) { + throw new IllegalArgumentException("The range must have exactly 2 elements"); + } + getAxisMappings(stickName, true).put(sourceComponentId, new AxisData(remapId, range)); + } + /** - * Adds a single Joystick axis or button remapping based on the - * joystick's name and axis/button name. The "remap" value will be - * used instead. + * Adds a single Joystick axis or button remapping based on the + * joystick's name and axis/button name. The "remap" value will be + * used instead. + * + * @param stickName which joystick (not null) + * @param sourceComponentId the name to be remapped + * @param remapId the remapped name */ - public static void addMapping( String stickName, String sourceComponentId, String remapId ) { - logger.log(Level.FINE, "addMapping(" + stickName + ", " + sourceComponentId + ", " + remapId + ")" ); - getMappings(stickName, true).put( sourceComponentId, remapId ); - } - + public static void addButtonMapping(String stickName, String sourceComponentId, String remapId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "addButtonMapping({0}, {1}, {2})", + new Object[]{stickName, sourceComponentId, remapId}); + } + getButtonMappings(stickName, true).put(sourceComponentId, remapId); + } + /** - * Adds a preconfigured set of mappings in Properties object - * form where the names are dot notation "joystick"."axis/button" - * and the values are the remapped component name. This calls - * addMapping(stickName, sourceComponent, remap) for every property - * that it is able to parse. + * Adds a single Joystick axis or button remapping based on the + * joystick's name and axis/button name. The "remap" value will be + * used instead. + * + * @param stickName which joystick (not null) + * @param sourceComponentId the name to be remapped + * @param remapId the remapped name */ - public static void addMappings( Properties p ) { - for( Map.Entry e : p.entrySet() ) { + public static void addMapping(String stickName, String sourceComponentId, String remapId) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "addMapping({0}, {1}, {2})", + new Object[]{stickName, sourceComponentId, remapId}); + } + getMappings(stickName, true).put(sourceComponentId, remapId); + } + + /** + * Adds a preconfigured set of mappings in Properties object + * form where the names are dot notation + * "axis"/"button"/"". "joystick"."axis/button name" + * and the values are the remapped component name. This calls + * addMapping(stickName, sourceComponent, remap) for every property + * that it is able to parse. + * + * @param p (not null) + */ + public static void addMappings(Properties p) { + final String AXIS_LABEL = "axis"; + final String BUTTON_LABEL = "button"; + + float[] range; + int lBracketIndex, rBracketIndex, commaIndex; + + for (Map.Entry e : p.entrySet()) { + range = null; String key = String.valueOf(e.getKey()).trim(); - - int split = key.lastIndexOf( '.' ); - if( split < 0 ) { + + int firstSplit = key.indexOf('.'); + int split = key.lastIndexOf('.'); + if (split < 0) { logger.log(Level.WARNING, "Skipping mapping:{0}", e); continue; } - - String stick = key.substring(0, split).trim(); - String component = key.substring(split+1).trim(); + + String type; + if (firstSplit >= 0 && firstSplit != split) { + type = key.substring(0, firstSplit).trim(); + if (!type.equals(AXIS_LABEL) && !type.equals(BUTTON_LABEL)) { + /* + * In this case, the "type" is probably a part of the + * joystick name. + */ + firstSplit = -1; + type = ""; + } + } else { + firstSplit = -1; + type = ""; + } + String stick = key.substring(firstSplit + 1, split).trim(); + String component = key.substring(split + 1).trim(); String value = String.valueOf(e.getValue()).trim(); - if( "regex".equals(component) ) { + if ("regex".equals(component)) { // It's a name remapping addJoystickNameRegex(value, stick); } - addMapping(stick, component, value); + if ((lBracketIndex = value.indexOf('[')) > 0) { + /* + * This means that there is an axis range. + */ + range = new float[2]; + rBracketIndex = value.indexOf(']'); + commaIndex = value.indexOf(','); + if (rBracketIndex > -1 && commaIndex > -1) { + try { + range[0] = Float.parseFloat(value.substring(lBracketIndex + 1, commaIndex).trim()); + range[1] = Float.parseFloat(value.substring(commaIndex + 1, rBracketIndex).trim()); + value = value.substring(0, lBracketIndex).trim(); + type = AXIS_LABEL; + } catch (NumberFormatException nfe) { + logger.log(Level.SEVERE, "Could not parse axis range \"" + value.substring(lBracketIndex) + "\"", nfe); + } + } + } + switch (type) { + case AXIS_LABEL: + if (range == null) { + addAxisMapping(stick, component, value); + } else { + addAxisMapping(stick, component, value, range); + } + break; + case BUTTON_LABEL: + addButtonMapping(stick, component, value); + break; + default: + addMapping(stick, component, value); + } } } - + /** - * Maps a regular expression to a normalized name for that joystick. + * Maps a regular expression to a normalized name for that joystick. + * + * @param regex the regular expression to be matched + * @param name the remapped name */ - public static void addJoystickNameRegex( String regex, String name ) { - logger.log(Level.FINE, "addJoystickNameRegex(" + regex + ", " + name + ")"); - nameRemappings.put(Pattern.compile(regex), name); + public static void addJoystickNameRegex(String regex, String name) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "addJoystickNameRegex({0}, {1})", new Object[]{regex, name}); + } + nameRemappings.put(Pattern.compile(regex), name); } - - protected static String getNormalizedName( String name ) { + + protected static String getNormalizedName(String name) { String result = nameCache.get(name); - if( result != null ) { + if (result != null) { return result; } - for( Map.Entry e : nameRemappings.entrySet() ) { + for (Map.Entry e : nameRemappings.entrySet()) { Pattern p = e.getKey(); Matcher m = p.matcher(name); - if( m.matches() ) { + if (m.matches()) { nameCache.put(name, e.getValue()); return e.getValue(); } } return null; } - + /** - * Loads a set of compatibility mappings from the property file - * specified by the given URL. - */ - public static void loadMappingProperties( URL u ) throws IOException { + * Loads a set of compatibility mappings from the property file + * specified by the given URL. + * + * @param u the URL of the properties file (not null) + * @throws IOException if an I/O exception occurs + */ + public static void loadMappingProperties(URL u) throws IOException { logger.log(Level.FINE, "Loading mapping properties:{0}", u); InputStream in = u.openStream(); - try { + try { Properties p = new Properties(); p.load(in); - addMappings(p); + addMappings(p); } finally { in.close(); - } + } } - protected static void loadMappings( ClassLoader cl, String path ) throws IOException { + protected static void loadMappings(String path) throws IOException { logger.log(Level.FINE, "Searching for mappings for path:{0}", path); - for( Enumeration en = cl.getResources(path); en.hasMoreElements(); ) { + for (Enumeration en = Resources.getResources(path); en.hasMoreElements(); ) { URL u = en.nextElement(); - try { + try { loadMappingProperties(u); - } catch( IOException e ) { - logger.log(Level.SEVERE, "Error loading:" + u, e); - } - } - + } catch (IOException e) { + logger.log(Level.SEVERE, "Error loading:" + u, e); + } + } + } /** - * Loads the default compatibility mappings by looking for - * joystick-mapping.properties files on the classpath. + * Loads the default compatibility mappings by looking for + * joystick-mapping.properties files on the classpath. */ protected static void loadDefaultMappings() { - for( String s : searchPaths ) { - try { - loadMappings(JoystickCompatibilityMappings.class.getClassLoader(), s); - } catch( IOException e ) { + for (String s : searchPaths) { + try { + loadMappings(s); + } catch (IOException e) { logger.log(Level.SEVERE, "Error searching resource path:{0}", s); } } - } + } + + private static class AxisData { + String name; + float[] range; + + AxisData(String name, float[] range) { + this.name = name; + this.range = range; + } + } } diff --git a/jme3-core/src/main/java/com/jme3/input/KeyInput.java b/jme3-core/src/main/java/com/jme3/input/KeyInput.java index 8a0f722453..26bcfa2a5b 100644 --- a/jme3-core/src/main/java/com/jme3/input/KeyInput.java +++ b/jme3-core/src/main/java/com/jme3/input/KeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -558,4 +558,12 @@ public interface KeyInput extends Input { * the last key. */ public static final int KEY_LAST = 0xE0; + + /** + * Determine the name of the specified key in the current system language. + * + * @param key The keycode from {@link com.jme3.input.KeyInput} + * @return the name of the key + */ + public String getKeyName(int key); } diff --git a/jme3-core/src/main/java/com/jme3/input/KeyNames.java b/jme3-core/src/main/java/com/jme3/input/KeyNames.java index 563c3a5f87..63d65ca010 100644 --- a/jme3-core/src/main/java/com/jme3/input/KeyNames.java +++ b/jme3-core/src/main/java/com/jme3/input/KeyNames.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,15 @@ import static com.jme3.input.KeyInput.*; +/** + * Translate key codes (from {@link KeyInput}) to descriptive names. + * + * This class has only static methods, yet it can be instantiated. Here's why: + * + * It used to be that there was no static getName() method, so the only way to + * get names was to instantiate a KeyNames. As a consequence, we have + * applications and libraries that rely on being able to instantiate this class. + */ public class KeyNames { private static final String[] KEY_NAMES = new String[0xFF]; @@ -180,6 +189,13 @@ public class KeyNames { KEY_NAMES[KEY_UNLABELED] = "Unlabeled"; } + /** + * Obtain a descriptive name for the specified key code. Key codes are + * defined in {@link KeyInput}. + * + * @param keyId a key code (≥0, ≤255) + * @return the corresponding name, or null if not named + */ public static String getName(int keyId) { return KEY_NAMES[keyId]; } diff --git a/jme3-core/src/main/java/com/jme3/input/RawInputListener.java b/jme3-core/src/main/java/com/jme3/input/RawInputListener.java index 6ba37ec2ef..7120809c19 100644 --- a/jme3-core/src/main/java/com/jme3/input/RawInputListener.java +++ b/jme3-core/src/main/java/com/jme3/input/RawInputListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,35 +56,35 @@ public interface RawInputListener { /** * Invoked on joystick axis events. * - * @param evt + * @param evt information about the event */ public void onJoyAxisEvent(JoyAxisEvent evt); /** * Invoked on joystick button presses. * - * @param evt + * @param evt information about the event */ public void onJoyButtonEvent(JoyButtonEvent evt); /** * Invoked on mouse movement/motion events. * - * @param evt + * @param evt information about the event */ public void onMouseMotionEvent(MouseMotionEvent evt); /** * Invoked on mouse button events. * - * @param evt + * @param evt information about the event */ public void onMouseButtonEvent(MouseButtonEvent evt); /** * Invoked on keyboard key press or release events. * - * @param evt + * @param evt information about the event */ public void onKeyEvent(KeyInputEvent evt); @@ -92,7 +92,7 @@ public interface RawInputListener { /** * Invoked on touchscreen touch events. * - * @param evt + * @param evt information about the event */ public void onTouchEvent(TouchEvent evt); diff --git a/jme3-core/src/main/java/com/jme3/input/RawInputListenerAdapter.java b/jme3-core/src/main/java/com/jme3/input/RawInputListenerAdapter.java new file mode 100644 index 0000000000..3c59c112eb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/input/RawInputListenerAdapter.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.input; + +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.input.event.KeyInputEvent; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; +import com.jme3.input.event.TouchEvent; + +/** + * An abstract adapter class for {@link RawInputListener}. + * + * This class provides empty implementations for all methods in the + * {@link RawInputListener} interface, making it easier to create custom + * listeners by extending this class and overriding only the methods of + * interest. + */ +public abstract class RawInputListenerAdapter implements RawInputListener { + + @Override + public void beginInput() { + // No-op implementation + } + + @Override + public void endInput() { + // No-op implementation + } + + @Override + public void onJoyAxisEvent(JoyAxisEvent evt) { + // No-op implementation + } + + @Override + public void onJoyButtonEvent(JoyButtonEvent evt) { + // No-op implementation + } + + @Override + public void onMouseMotionEvent(MouseMotionEvent evt) { + // No-op implementation + } + + @Override + public void onMouseButtonEvent(MouseButtonEvent evt) { + // No-op implementation + } + + @Override + public void onKeyEvent(KeyInputEvent evt) { + // No-op implementation + } + + @Override + public void onTouchEvent(TouchEvent evt) { + // No-op implementation + } + +} diff --git a/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java b/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java index d508ac2a0d..be4f549419 100644 --- a/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java +++ b/jme3-core/src/main/java/com/jme3/input/SensorJoystickAxis.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ /** * Represents a joystick axis based on an external sensor - * (ie. Android Device Orientation sensors) + * (i.e. Android Device Orientation sensors) * Sensor joystick axes can be calibrated to * set the zero position dynamically * @@ -47,7 +47,7 @@ public interface SensorJoystickAxis { /** * Calibrates the axis to the current value. Future axis values will be - * sent as a delta from the calibratation value. + * sent as a delta from the calibration value. */ public void calibrateCenter(); diff --git a/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java index 846c207dcf..d09c9d1019 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/JoyAxisTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,10 @@ public class JoyAxisTrigger implements Trigger { /** * Use {@link Joystick#assignAxis(java.lang.String, java.lang.String, int) } * instead. + * + * @param joyId which joystick + * @param axisId which joystick axis + * @param negative true to negate input values, false to leave unchanged */ public JoyAxisTrigger(int joyId, int axisId, boolean negative) { this.joyId = joyId; @@ -48,7 +52,7 @@ public JoyAxisTrigger(int joyId, int axisId, boolean negative) { this.negative = negative; } - public static int joyAxisHash(int joyId, int joyAxis, boolean negative){ + public static int joyAxisHash(int joyId, int joyAxis, boolean negative) { assert joyAxis >= 0 && joyAxis <= 255; return (2048 * joyId) | (negative ? 1280 : 1024) | (joyAxis & 0xff); } @@ -65,12 +69,14 @@ public boolean isNegative() { return negative; } + @Override public String getName() { return "JoyAxis[joyId="+joyId+", axisId="+axisId+", neg="+negative+"]"; } + @Override public int triggerHashCode() { return joyAxisHash(joyId, axisId, negative); } - + } diff --git a/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java index e1b2eb1609..7863ee8eee 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/JoyButtonTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,16 +39,16 @@ public class JoyButtonTrigger implements Trigger { /** * Use {@link Joystick#assignButton(java.lang.String, int) } instead. - * - * @param joyId - * @param axisId + * + * @param joyId the ID of a joystick + * @param buttonId the index of a joystick button */ - public JoyButtonTrigger(int joyId, int axisId) { + public JoyButtonTrigger(int joyId, int buttonId) { this.joyId = joyId; - this.buttonId = axisId; + this.buttonId = buttonId; } - public static int joyButtonHash(int joyId, int joyButton){ + public static int joyButtonHash(int joyId, int joyButton) { assert joyButton >= 0 && joyButton <= 255; return (2048 * joyId) | 1536 | (joyButton & 0xff); } @@ -61,10 +61,12 @@ public int getJoyId() { return joyId; } + @Override public String getName() { return "JoyButton[joyId="+joyId+", axisId="+buttonId+"]"; } + @Override public int triggerHashCode() { return joyButtonHash(joyId, buttonId); } diff --git a/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java index eaf10aa6aa..18a5401511 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/KeyTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,7 @@ public KeyTrigger(int keyCode){ this.keyCode = keyCode; } + @Override public String getName() { return "KeyCode " + keyCode; } @@ -64,6 +65,7 @@ public static int keyHash(int keyCode){ return keyCode & 0xff; } + @Override public int triggerHashCode() { return keyHash(keyCode); } diff --git a/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java index 72c231d1f8..95a7871ae1 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/MouseAxisTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,6 +68,7 @@ public boolean isNegative() { return negative; } + @Override public String getName() { String sign = negative ? "Negative" : "Positive"; switch (mouseAxis){ @@ -83,6 +84,7 @@ public static int mouseAxisHash(int mouseAxis, boolean negative){ return (negative ? 768 : 512) | (mouseAxis & 0xff); } + @Override public int triggerHashCode() { return mouseAxisHash(mouseAxis, negative); } diff --git a/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java index 4badddf20e..66cb055ea3 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/MouseButtonTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,6 +62,7 @@ public int getMouseButton() { return mouseButton; } + @Override public String getName() { return "Mouse Button " + mouseButton; } @@ -71,6 +72,7 @@ public static int mouseButtonHash(int mouseButton){ return 256 | (mouseButton & 0xff); } + @Override public int triggerHashCode() { return mouseButtonHash(mouseButton); } diff --git a/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java b/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java index 2592249bef..9e0c22a8eb 100644 --- a/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java +++ b/jme3-core/src/main/java/com/jme3/input/controls/TouchTrigger.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,12 +56,13 @@ public String getName() { else return "TouchInput KeyCode " + keyCode; } - - public static int touchHash(int keyCode){ - assert keyCode >= 0 && keyCode <= 255; - return 0xfedcba98 + keyCode; + + public static int touchHash(int keyCode) { + int result = 0xfedcba98 + keyCode; + return result; } + @Override public int triggerHashCode() { return touchHash(keyCode); } diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java index 6525ac365c..ec10be2ec4 100644 --- a/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,7 @@ public class DummyInput implements Input { protected boolean inited = false; + @Override public void initialize() { if (inited) throw new IllegalStateException("Input already initialized."); @@ -51,11 +52,13 @@ public void initialize() { inited = true; } + @Override public void update() { if (!inited) throw new IllegalStateException("Input not initialized."); } + @Override public void destroy() { if (!inited) throw new IllegalStateException("Input not initialized."); @@ -63,13 +66,16 @@ public void destroy() { inited = false; } + @Override public boolean isInitialized() { return inited; } + @Override public void setInputListener(RawInputListener listener) { } + @Override public long getInputTimeNanos() { return System.currentTimeMillis() * 1000000; } diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java index 92ed940319..c7ae723e06 100644 --- a/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyKeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,4 +48,10 @@ public int getKeyCount() { return 0; } + + @Override + public String getKeyName(int key){ + return "Unknown"; + } + } diff --git a/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java b/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java index 497490e395..f8d18be2e1 100644 --- a/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java +++ b/jme3-core/src/main/java/com/jme3/input/dummy/DummyMouseInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,15 +42,18 @@ */ public class DummyMouseInput extends DummyInput implements MouseInput { + @Override public void setCursorVisible(boolean visible) { if (!inited) throw new IllegalStateException("Input not initialized."); } + @Override public int getButtonCount() { return 0; } + @Override public void setNativeCursor(JmeCursor cursor) { } diff --git a/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java index 7e63fc68fb..8f56686df8 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyAxisEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,22 +36,42 @@ /** * Joystick axis event. - * + * * @author Kirill Vainer, Paul Speed */ public class JoyAxisEvent extends InputEvent { private JoystickAxis axis; private float value; + private float rawValue; + /** + * Creates a new event for a joystick axis. + * + * @param axis - The axis that generated this event. + * @param value - The value of the axis. + */ public JoyAxisEvent(JoystickAxis axis, float value) { + this(axis, value, value); + } + + /** + * Creates a new event for a joystick axis. + * + * @param axis - The axis that generated this event. + * @param value - The value of the axis, after rescaling took place. + * @param rawValue - The original axis value before it was rescaled by {@link com.jme3.input.JoystickCompatibilityMappings JoystickCompatibilityMappings}. + */ + public JoyAxisEvent(JoystickAxis axis, float value, float rawValue) { this.axis = axis; this.value = value; + this.rawValue = rawValue; } /** * Returns the JoystickAxis that triggered this event. * + * @return the pre-existing instance * @see com.jme3.input.JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public JoystickAxis getAxis() { @@ -60,9 +80,8 @@ public JoystickAxis getAxis() { /** * Returns the joystick axis index. - * + * * @return joystick axis index. - * * @see com.jme3.input.JoystickAxis#assignAxis(java.lang.String, java.lang.String) */ public int getAxisIndex() { @@ -71,10 +90,9 @@ public int getAxisIndex() { /** * The joystick index. - * + * * @return joystick index. - * - * @see InputManager#getJoysticks() + * @see InputManager#getJoysticks() */ public int getJoyIndex() { return axis.getJoystick().getJoyId(); @@ -82,10 +100,19 @@ public int getJoyIndex() { /** * The value of the axis. - * + * * @return value of the axis. */ public float getValue() { return value; } + + /** + * The value of the axis before it was remapped. + * + * @return value of the axis. + */ + public float getRawValue() { + return rawValue; + } } diff --git a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java index f69e3f0299..615bb90212 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/JoyButtonEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,8 +41,8 @@ */ public class JoyButtonEvent extends InputEvent { - private JoystickButton button; - private boolean pressed; + private final JoystickButton button; + private final boolean pressed; public JoyButtonEvent(JoystickButton button, boolean pressed) { this.button = button; diff --git a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java index a804be2f91..2fb0294730 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/KeyInputEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,15 +35,15 @@ /** * Keyboard key event. - * + * * @author Kirill Vainer */ public class KeyInputEvent extends InputEvent { - private int keyCode; - private char keyChar; - private boolean pressed; - private boolean repeating; + private final int keyCode; + private final char keyChar; + private final boolean pressed; + private final boolean repeating; public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeating) { this.keyCode = keyCode; @@ -54,7 +54,7 @@ public KeyInputEvent(int keyCode, char keyChar, boolean pressed, boolean repeati /** * Returns the key character. Returns 0 if the key has no character. - * + * * @return the key character. 0 if the key has no character. */ public char getKeyChar() { @@ -65,7 +65,7 @@ public char getKeyChar() { * The key code. *

    * See KEY_*** constants in {@link KeyInput}. - * + * * @return key code. */ public int getKeyCode() { @@ -74,7 +74,7 @@ public int getKeyCode() { /** * Returns true if this event is key press, false is it was a key release. - * + * * @return true if this event is key press, false is it was a key release. */ public boolean isPressed() { @@ -82,8 +82,8 @@ public boolean isPressed() { } /** - * Returns true if this event is a repeat event. Not used anymore. - * + * Returns true if this event is a repeat event. + * * @return true if this event is a repeat event */ public boolean isRepeating() { @@ -92,7 +92,7 @@ public boolean isRepeating() { /** * Returns true if this event is a key release, false if it was a key press. - * + * * @return true if this event is a key release, false if it was a key press. */ public boolean isReleased() { @@ -100,16 +100,16 @@ public boolean isReleased() { } @Override - public String toString(){ + public String toString() { String str = "Key(CODE="+keyCode; if (keyChar != '\0') str = str + ", CHAR=" + keyChar; - - if (repeating){ + + if (repeating) { return str + ", REPEATING)"; - }else if (pressed){ + } else if (pressed) { return str + ", PRESSED)"; - }else{ + } else { return str + ", RELEASED)"; } } diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java index 104957b2ec..0d5ddc0a52 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseButtonEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,15 +35,15 @@ /** * Mouse button press/release event. - * + * * @author Kirill Vainer */ public class MouseButtonEvent extends InputEvent { - private int x; - private int y; - private int btnIndex; - private boolean pressed; + private final int x; + private final int y; + private final int btnIndex; + private final boolean pressed; public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { this.btnIndex = btnIndex; @@ -56,7 +56,7 @@ public MouseButtonEvent(int btnIndex, boolean pressed, int x, int y) { * Returns the mouse button index. *

    * See constants in {@link MouseInput}. - * + * * @return the mouse button index. */ public int getButtonIndex() { @@ -65,7 +65,7 @@ public int getButtonIndex() { /** * Returns true if the mouse button was pressed, false if it was released. - * + * * @return true if the mouse button was pressed, false if it was released. */ public boolean isPressed() { @@ -74,13 +74,13 @@ public boolean isPressed() { /** * Returns true if the mouse button was released, false if it was pressed. - * + * * @return true if the mouse button was released, false if it was pressed. */ public boolean isReleased() { return !pressed; } - + /** * The X coordinate of the mouse when the event was generated. * @return X coordinate of the mouse when the event was generated. @@ -88,7 +88,7 @@ public boolean isReleased() { public int getX() { return x; } - + /** * The Y coordinate of the mouse when the event was generated. * @return Y coordinate of the mouse when the event was generated. @@ -96,15 +96,14 @@ public int getX() { public int getY() { return y; } - + @Override public String toString(){ String str = "MouseButton(BTN="+btnIndex; - if (pressed){ + if (pressed) { return str + ", PRESSED)"; - }else{ + } else { return str + ", RELEASED)"; } } - } diff --git a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java index 141077f5ee..0bf269f3cc 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/MouseMotionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,7 @@ */ public class MouseMotionEvent extends InputEvent { - private int x, y, dx, dy, wheel, deltaWheel; + private final int x, y, dx, dy, wheel, deltaWheel; public MouseMotionEvent(int x, int y, int dx, int dy, int wheel, int deltaWheel) { this.x = x; diff --git a/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java b/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java index f8463d48cb..6cdf26a3ff 100644 --- a/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java +++ b/jme3-core/src/main/java/com/jme3/input/event/TouchEvent.java @@ -103,7 +103,11 @@ public enum Type { SHOWPRESS, // Others OUTSIDE, - IDLE + IDLE, + /** + * Virtual keyboard or hardware key event up, fields: keyCode, characters + */ + KEY_MULTIPLE } private Type type = Type.IDLE; private int pointerId; diff --git a/jme3-core/src/main/java/com/jme3/light/AmbientLight.java b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java index 531c7b5d0b..78fc69c41a 100644 --- a/jme3-core/src/main/java/com/jme3/light/AmbientLight.java +++ b/jme3-core/src/main/java/com/jme3/light/AmbientLight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2015 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -83,4 +83,13 @@ public Type getType() { return Type.Ambient; } + @Override + public String toString() { + return getClass().getSimpleName() + + "[name=" + name + + ", color=" + color + + ", enabled=" + enabled + + "]"; + } + } diff --git a/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java index e4c16728ab..f9fe6af280 100644 --- a/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java +++ b/jme3-core/src/main/java/com/jme3/light/BasicProbeBlendingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,14 +39,14 @@ * This strategy returns the closest probe from the rendered object. * * This is the most basic strategy : The fastest and the easiest. - * Though it has severe graphical draw backs as there might be very visible seams - * on static object and some "poping" on dynamic objects. + * Though it has severe graphical drawbacks as there might be very visible seams + * on static object and some "popping" on dynamic objects. * * @author Nehon */ public class BasicProbeBlendingStrategy implements LightProbeBlendingStrategy { - List lightProbes = new ArrayList(); + List lightProbes = new ArrayList<>(); @Override public void registerProbe(LightProbe probe) { 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 df735095ee..d6f688662f 100644 --- a/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java +++ b/jme3-core/src/main/java/com/jme3/light/DefaultLightFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,23 +42,24 @@ public final class DefaultLightFilter implements LightFilter { private Camera camera; - private final HashSet processedLights = new HashSet(); - private LightProbeBlendingStrategy probeBlendStrat; + private final HashSet processedLights = new HashSet<>(); + private LightProbeBlendingStrategy probeBlendStrategy; public DefaultLightFilter() { - probeBlendStrat = new WeightedProbeBlendingStrategy(); + probeBlendStrategy = new WeightedProbeBlendingStrategy(); } - public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrat) { - this.probeBlendStrat = probeBlendStrat; + public DefaultLightFilter(LightProbeBlendingStrategy probeBlendStrategy) { + this.probeBlendStrategy = probeBlendStrategy; } - + @Override public void setCamera(Camera camera) { this.camera = camera; for (Light light : processedLights) { light.frustumCheckNeeded = true; } + processedLights.clear(); } @Override @@ -66,7 +67,7 @@ public void filterLights(Geometry geometry, LightList filteredLightList) { TempVars vars = TempVars.get(); try { LightList worldLights = geometry.getWorldLightList(); - + for (int i = 0; i < worldLights.size(); i++) { Light light = worldLights.get(i); @@ -86,36 +87,36 @@ public void filterLights(Geometry geometry, LightList filteredLightList) { } BoundingVolume bv = geometry.getWorldBound(); - + if (bv instanceof BoundingBox) { - if (!light.intersectsBox((BoundingBox)bv, vars)) { + if (!light.intersectsBox((BoundingBox) bv, vars)) { continue; } } else if (bv instanceof BoundingSphere) { - if (!Float.isInfinite( ((BoundingSphere)bv).getRadius() )) { - if (!light.intersectsSphere((BoundingSphere)bv, vars)) { + if (!Float.isInfinite(((BoundingSphere) bv).getRadius())) { + if (!light.intersectsSphere((BoundingSphere) bv, vars)) { continue; } } } - + if (light.getType() == Light.Type.Probe) { - probeBlendStrat.registerProbe((LightProbe) light); + probeBlendStrategy.registerProbe((LightProbe) light); } else { filteredLightList.add(light); } - + } - - probeBlendStrat.populateProbes(geometry, filteredLightList); + + probeBlendStrategy.populateProbes(geometry, filteredLightList); } finally { vars.release(); } } - public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy){ - probeBlendStrat = strategy; + public void setLightProbeBlendingStrategy(LightProbeBlendingStrategy strategy) { + probeBlendStrategy = strategy; } } diff --git a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java index d5b28fe55c..ba142071f4 100644 --- a/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java +++ b/jme3-core/src/main/java/com/jme3/light/DirectionalLight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,7 @@ /** * DirectionalLight is a light coming from a certain direction in world space. - * E.g sun or moon light. + * E.g. sun or moon light. *

    * Directional lights have no specific position in the scene, they always * come from their direction regardless of where an object is placed. @@ -132,11 +132,6 @@ public Type getType() { return Type.Directional; } - @Override - public String toString() { - return getClass().getSimpleName() + "[name=" + name + ", direction=" + direction + ", color=" + color + ", enabled=" + enabled + "]"; - } - @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -157,4 +152,14 @@ public DirectionalLight clone() { l.direction = direction.clone(); return l; } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[name=" + name + + ", direction=" + direction + + ", color=" + color + + ", enabled=" + enabled + + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/light/Light.java b/jme3-core/src/main/java/com/jme3/light/Light.java index 0a66fb729a..cebb4ae2a0 100644 --- a/jme3-core/src/main/java/com/jme3/light/Light.java +++ b/jme3-core/src/main/java/com/jme3/light/Light.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2015-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -87,7 +87,7 @@ public enum Type { Probe(4); - private int typeId; + private final int typeId; Type(int type){ this.typeId = type; @@ -259,6 +259,7 @@ public Light clone(){ } } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(color, "color", null); @@ -266,6 +267,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(name, "name", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); color = (ColorRGBA) ic.readSavable("color", null); @@ -275,6 +277,8 @@ public void read(JmeImporter im) throws IOException { /** * Used internally to compute the last distance value. + * + * @param owner the Spatial whose distance is to be determined */ protected abstract void computeLastDistance(Spatial owner); diff --git a/jme3-core/src/main/java/com/jme3/light/LightList.java b/jme3-core/src/main/java/com/jme3/light/LightList.java index cdb96e3a54..97c4c29820 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightList.java +++ b/jme3-core/src/main/java/com/jme3/light/LightList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,6 +58,7 @@ public final class LightList implements Iterable, Savable, Cloneable, Jme /** * This assumes lastDistance have been computed in a previous step. */ + @Override public int compare(Light l1, Light l2) { if (l1.lastDistance < l2.lastDistance) return -1; @@ -71,7 +72,7 @@ else if (l1.lastDistance > l2.lastDistance) /** * constructor for serialization. Do not use */ - protected LightList(){ + protected LightList() { } /** @@ -89,13 +90,14 @@ public LightList(Spatial owner) { /** * Set the owner of the LightList. Only used for cloning. - * @param owner + * + * @param owner the desired owner (alias created) */ - public void setOwner(Spatial owner){ + public void setOwner(Spatial owner) { this.owner = owner; } - private void doubleSize(){ + private void doubleSize() { Light[] temp = new Light[list.length * 2]; float[] temp2 = new float[list.length * 2]; System.arraycopy(list, 0, temp, 0, list.length); @@ -121,19 +123,19 @@ public void add(Light l) { /** * Remove the light at the given index. * - * @param index + * @param index the zero-based index of the Light to be removed (≥0) */ - public void remove(int index){ + public void remove(int index) { if (index >= listSize || index < 0) throw new IndexOutOfBoundsException(); listSize --; - if (index == listSize){ + if (index == listSize) { list[listSize] = null; return; } - for (int i = index; i < listSize; i++){ + for (int i = index; i < listSize; i++) { list[i] = list[i+1]; } list[listSize] = null; @@ -144,9 +146,9 @@ public void remove(int index){ * * @param l the light to remove */ - public void remove(Light l){ - for (int i = 0; i < listSize; i++){ - if (list[i] == l){ + public void remove(Light l) { + for (int i = 0; i < listSize; i++) { + if (list[i] == l) { remove(i); return; } @@ -156,15 +158,16 @@ public void remove(Light l){ /** * @return The size of the list. */ - public int size(){ + public int size() { return listSize; } /** + * @param num the zero-based index of the light to be accessed (≥0) * @return the light at the given index. * @throws IndexOutOfBoundsException If the given index is outside bounds. */ - public Light get(int num){ + public Light get(int num) { if (num >= listSize || num < 0) throw new IndexOutOfBoundsException(); @@ -207,9 +210,9 @@ public void sort(boolean transformChanged) { System.arraycopy(list, 0, tlist, 0, list.length); } - if (transformChanged){ + if (transformChanged) { // check distance of each light - for (int i = 0; i < listSize; i++){ + for (int i = 0; i < listSize; i++) { list[i].computeLastDistance(owner); } } @@ -223,40 +226,40 @@ public void sort(boolean transformChanged) { * Updates a "world-space" light list, using the spatial's local-space * light list and its parent's world-space light list. * - * @param local - * @param parent + * @param local the local-space LightList (not null) + * @param parent the parent's world-space LightList */ - public void update(LightList local, LightList parent){ + public void update(LightList local, LightList parent) { // clear the list as it will be reconstructed // using the arguments clear(); - while (list.length <= local.listSize){ + while (list.length <= local.listSize) { doubleSize(); } // add the lights from the local list System.arraycopy(local.list, 0, list, 0, local.listSize); - for (int i = 0; i < local.listSize; i++){ + for (int i = 0; i < local.listSize; i++) { // list[i] = local.list[i]; distToOwner[i] = Float.NEGATIVE_INFINITY; } // if the spatial has a parent node, add the lights // from the parent list as well - if (parent != null){ + if (parent != null) { int sz = local.listSize + parent.listSize; while (list.length <= sz) doubleSize(); - for (int i = 0; i < parent.listSize; i++){ + for (int i = 0; i < parent.listSize; i++) { int p = i + local.listSize; list[p] = parent.list[i]; distToOwner[p] = Float.NEGATIVE_INFINITY; } listSize = local.listSize + parent.listSize; - }else{ + } else { listSize = local.listSize; } } @@ -266,15 +269,18 @@ public void update(LightList local, LightList parent){ * * @return an iterator that can be used to iterate over this LightList. */ + @Override public Iterator iterator() { - return new Iterator(){ + return new Iterator() { int index = 0; + @Override public boolean hasNext() { return index < size(); } + @Override public Light next() { if (!hasNext()) throw new NoSuchElementException(); @@ -282,6 +288,7 @@ public Light next() { return list[index++]; } + @Override public void remove() { LightList.this.remove(--index); } @@ -289,8 +296,8 @@ public void remove() { } @Override - public LightList clone(){ - try{ + public LightList clone() { + try { LightList clone = (LightList) super.clone(); clone.owner = null; @@ -299,40 +306,43 @@ public LightList clone(){ clone.tlist = null; // list used for sorting only return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } @Override public LightList jmeClone() { - try{ + try { LightList clone = (LightList)super.clone(); clone.tlist = null; // list used for sorting only return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.owner = cloner.clone(owner); this.list = cloner.clone(list); this.distToOwner = cloner.clone(distToOwner); } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); // oc.write(owner, "owner", null); - ArrayList lights = new ArrayList(); - for (int i = 0; i < listSize; i++){ + ArrayList lights = new ArrayList<>(); + for (int i = 0; i < listSize; i++) { lights.add(list[i]); } oc.writeSavableArrayList(lights, "lights", null); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); // owner = (Spatial) ic.readSavable("owner", null); @@ -345,11 +355,10 @@ public void read(JmeImporter im) throws IOException { list = new Light[arraySize]; distToOwner = new float[arraySize]; - for (int i = 0; i < listSize; i++){ + for (int i = 0; i < listSize; i++) { list[i] = lights.get(i); } Arrays.fill(distToOwner, Float.NEGATIVE_INFINITY); } - } diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbe.java b/jme3-core/src/main/java/com/jme3/light/LightProbe.java index a53c4ddbd7..7ab654f352 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightProbe.java +++ b/jme3-core/src/main/java/com/jme3/light/LightProbe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ /** * A LightProbe is not exactly a light. It holds environment map information used for Image Based Lighting. * This is used for indirect lighting in the Physically Based Rendering pipeline. - * + *

    * A light probe has a position in world space. This is the position from where the Environment Map are rendered. * There are two environment data structure held by the LightProbe : * - The irradiance spherical harmonics factors (used for indirect diffuse lighting in the PBR pipeline). @@ -57,9 +57,9 @@ * To compute them see * {@link com.jme3.environment.LightProbeFactory#makeProbe(com.jme3.environment.EnvironmentCamera, com.jme3.scene.Spatial)} * and {@link EnvironmentCamera}. - * + *

    * The light probe has an area of effect centered on its position. It can have a Spherical area or an Oriented Box area - * + *

    * A LightProbe will only be taken into account when it's marked as ready and enabled. * A light probe is ready when it has valid environment map data set. * Note that you should never call setReady yourself. @@ -71,9 +71,10 @@ public class LightProbe extends Light implements Savable { private static final Logger logger = Logger.getLogger(LightProbe.class.getName()); - public static final Matrix4f FALLBACK_MATRIX = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1); + public static final Matrix4f FALLBACK_MATRIX = new Matrix4f( + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1); - private Vector3f[] shCoeffs; + private Vector3f[] shCoefficients; private TextureCubeMap prefilteredEnvMap; private ProbeArea area = new SphereProbeArea(Vector3f.ZERO, 1.0f); private boolean ready = false; @@ -105,10 +106,10 @@ public TextureCubeMap getPrefilteredEnvMap() { /** * Sets the prefiltered environment map - * @param prefileteredEnvMap the prefiltered environment map + * @param prefilteredEnvMap the prefiltered environment map */ - public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) { - this.prefilteredEnvMap = prefileteredEnvMap; + public void setPrefilteredMap(TextureCubeMap prefilteredEnvMap) { + this.prefilteredEnvMap = prefilteredEnvMap; } /** @@ -134,9 +135,9 @@ public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) { * Like in a valid 4x4 transform matrix. *

    * (sx, sy, sy) is the extent of the probe ( the scale ) - * In a standard transform matrix the scale is applied to the rotation matrix part. - * In the shader we need the rotation and the scale to be separated, doing this avoid to extract - * the scale from a classic transform matrix in the shader + * In a standard transform matrix, the scale is applied to the rotation matrix part. + * In the shader, we need the rotation and the scale to be separated. Doing so avoids extracting + * the scale from a classic transform matrix in the shader. *

    * (sp) is a special entry, it contains the packed number of mip maps of the probe and the inverse radius for the probe. * since the inverse radius in lower than 1, it's packed in the decimal part of the float. @@ -144,24 +145,22 @@ public void setPrefilteredMap(TextureCubeMap prefileteredEnvMap) { * (ie: for 6 mip maps and a radius of 3, sp= 6.3333333) *

    * The radius is obvious for a SphereProbeArea, - * but in the case of a OrientedBoxProbeArea it's the max of the extent vector's components. + * but in the case of an OrientedBoxProbeArea it's the max of the extent vector's components. + * + * @return the pre-existing matrix */ public Matrix4f getUniformMatrix(){ - Matrix4f mat = area.getUniformMatrix(); - // setting the (sp) entry of the matrix mat.m33 = nbMipMaps + 1f / area.getRadius(); - return mat; } - @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(shCoeffs, "shCoeffs", null); + oc.write(shCoefficients, "shCoeffs", null); oc.write(prefilteredEnvMap, "prefilteredEnvMap", null); oc.write(position, "position", null); oc.write(area, "area", new SphereProbeArea(Vector3f.ZERO, 1.0f)); @@ -178,7 +177,7 @@ public void read(JmeImporter im) throws IOException { position = (Vector3f) ic.readSavable("position", null); area = (ProbeArea)ic.readSavable("area", null); if(area == null) { - // retro compat + // retro compatibility BoundingSphere bounds = (BoundingSphere) ic.readSavable("bounds", new BoundingSphere(1.0f, Vector3f.ZERO)); area = new SphereProbeArea(bounds.getCenter(), bounds.getRadius()); } @@ -191,14 +190,13 @@ public void read(JmeImporter im) throws IOException { ready = false; logger.log(Level.WARNING, "LightProbe is missing parameters, it should be recomputed. Please use lightProbeFactory.updateProbe()"); } else { - shCoeffs = new Vector3f[coeffs.length]; + shCoefficients = new Vector3f[coeffs.length]; for (int i = 0; i < coeffs.length; i++) { - shCoeffs[i] = (Vector3f) coeffs[i]; + shCoefficients[i] = (Vector3f) coeffs[i]; } } } - /** * returns the bounding volume of this LightProbe * @return a bounding volume. @@ -206,18 +204,7 @@ public void read(JmeImporter im) throws IOException { */ @Deprecated public BoundingVolume getBounds() { - return new BoundingSphere(((SphereProbeArea)area).getRadius(), ((SphereProbeArea)area).getCenter()); - } - - /** - * Sets the bounds of this LightProbe - * Note that for now only BoundingSphere is supported and this method will - * throw an UnsupportedOperationException with any other BoundingVolume type - * @param bounds the bounds of the LightProbe - * @deprecated - */ - @Deprecated - public void setBounds(BoundingVolume bounds) { + return new BoundingSphere(area.getRadius(), ((SphereProbeArea)area).getCenter()); } public ProbeArea getArea() { @@ -231,9 +218,9 @@ public void setAreaType(AreaType type){ break; case OrientedBox: area = new OrientedBoxProbeArea(new Transform()); - area.setCenter(position); break; } + area.setCenter(position); } public AreaType getAreaType(){ @@ -245,7 +232,7 @@ public AreaType getAreaType(){ /** * return true if the LightProbe is ready, meaning the Environment maps have - * been loaded or rnedered and are ready to be used by a material + * been loaded or rendered and are ready to be used by a material * @return the LightProbe ready state */ public boolean isReady() { @@ -263,16 +250,16 @@ public void setReady(boolean ready) { } public Vector3f[] getShCoeffs() { - return shCoeffs; + return shCoefficients; } - public void setShCoeffs(Vector3f[] shCoeffs) { - this.shCoeffs = shCoeffs; + public void setShCoeffs(Vector3f[] shCoefficients) { + this.shCoefficients = shCoefficients; } /** * Returns the position of the LightProbe in world space - * @return the wolrd space position + * @return the world-space position */ public Vector3f getPosition() { return position; @@ -280,7 +267,7 @@ public Vector3f getPosition() { /** * Sets the position of the LightProbe in world space - * @param position the wolrd space position + * @param position the world-space position */ public void setPosition(Vector3f position) { this.position.set(position); @@ -327,8 +314,12 @@ public Type getType() { @Override public String toString() { - return "Light Probe : " + name + " at " + position + " / " + area; + return getClass().getSimpleName() + + "[name=" + name + + ", position=" + position + + ", area=" + area + + ", enabled=" + enabled + + "]"; } - } diff --git a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java index 824c94bb16..5516231b3e 100644 --- a/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java +++ b/jme3-core/src/main/java/com/jme3/light/LightProbeBlendingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,8 @@ public interface LightProbeBlendingStrategy { /** * Registers a probe with this strategy - * @param probe + * + * @param probe the probe to be registered */ public void registerProbe(LightProbe probe); /** 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/light/OrientedBoxProbeArea.java b/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java index 6b1e1b5940..03b8aa6197 100644 --- a/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java +++ b/jme3-core/src/main/java/com/jme3/light/OrientedBoxProbeArea.java @@ -17,7 +17,7 @@ public class OrientedBoxProbeArea implements ProbeArea { * for this Area type, the matrix is updated when the probe is transformed, * and its data is used for bound checks in the light culling process. */ - private Matrix4f uniformMatrix = new Matrix4f(); + private final Matrix4f uniformMatrix = new Matrix4f(); public OrientedBoxProbeArea() { } @@ -61,9 +61,7 @@ public boolean intersectsBox(BoundingBox box, TempVars vars) { p.setNormal(1, 0, 0); p.setConstant(c.x - box.getXExtent()); - if (!insidePlane(p, axis1, axis2, axis3, tn)) return false; - - return true; + return insidePlane(p, axis1, axis2, axis3, tn); } @@ -79,18 +77,13 @@ public void setRadius(float radius) { @Override public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { - Vector3f closestPoint = getClosestPoint(vars, sphere.getCenter()); // check if the point intersects with the sphere bound - if (sphere.intersects(closestPoint)) { - return true; - } - return false; + return sphere.intersects(closestPoint); } @Override public boolean intersectsFrustum(Camera camera, TempVars vars) { - // extract the scaled axis // this allows a small optimization. Vector3f axis1 = getScaledAxis(0, vars.vect1); @@ -108,7 +101,7 @@ public boolean intersectsFrustum(Camera camera, TempVars vars) { private Vector3f getScaledAxis(int index, Vector3f store) { Matrix4f u = uniformMatrix; - float x = 0, y = 0, z = 0, s = 1; + float x, y, z, s; switch (index) { case 0: x = u.m00; @@ -127,6 +120,9 @@ private Vector3f getScaledAxis(int index, Vector3f store) { y = u.m12; z = u.m22; s = u.m32; + break; + default: + throw new IllegalArgumentException("Invalid axis, not in range [0, 2]"); } return store.set(x, y, z).multLocal(s); } @@ -141,11 +137,7 @@ private boolean insidePlane(Plane p, Vector3f axis1, Vector3f axis2, Vector3f ax FastMath.abs(tn.z); float distance = p.pseudoDistance(transform.getTranslation()); - - if (distance < -radius) { - return false; - } - return true; + return distance >= -radius; } private Vector3f getClosestPoint(TempVars vars, Vector3f point) { @@ -160,11 +152,11 @@ private Vector3f getClosestPoint(TempVars vars, Vector3f point) { r[1] = transform.getScale().y; r[2] = transform.getScale().z; - // computing closest point to sphere center + // Compute the closest point to sphere's center. for (int i = 0; i < 3; i++) { // extract the axis from the 3x3 matrix Vector3f axis = getScaledAxis(i, vars.vect1); - // nomalize (here we just divide by the extent + // normalize (here we just divide by the extent) axis.divideLocal(r[i]); // distance to the closest point on this axis. float d = FastMath.clamp(dir.dot(axis), -r[i], r[i]); @@ -202,6 +194,7 @@ private void updateMatrix() { vars.release(); } + @Override public Matrix4f getUniformMatrix() { return uniformMatrix; } @@ -219,6 +212,7 @@ public Vector3f getCenter() { return transform.getTranslation(); } + @Override public void setCenter(Vector3f center) { transform.setTranslation(center); updateMatrix(); @@ -250,5 +244,4 @@ public void read(JmeImporter i) throws IOException { transform = (Transform) ic.readSavable("transform", new Transform()); updateMatrix(); } - } diff --git a/jme3-core/src/main/java/com/jme3/light/PointLight.java b/jme3-core/src/main/java/com/jme3/light/PointLight.java index 4d9f306a06..37748f48d1 100644 --- a/jme3-core/src/main/java/com/jme3/light/PointLight.java +++ b/jme3-core/src/main/java/com/jme3/light/PointLight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2015-2016, 2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,10 +51,10 @@ * A point light emits light from a given position into all directions in space. * E.g a lamp or a bright effect. Point light positions are in world space. *

    - * In addition to a position, point lights also have a radius which - * can be used to attenuate the influence of the light depending on the + * In addition to a position, point lights also have a radius which + * can be used to attenuate the influence of the light depending on the * distance between the light and the affected object. - * + * */ public class PointLight extends Light { @@ -85,9 +85,9 @@ public PointLight(Vector3f position, ColorRGBA color) { super(color); setPosition(position); } - + /** - * Creates a PointLight at the given position, with the given color and the + * Creates a PointLight at the given position, with the given color and the * given radius * @param position the position in world space * @param color the light color @@ -97,7 +97,7 @@ public PointLight(Vector3f position, ColorRGBA color, float radius) { this(position, color); setRadius(radius); } - + /** * Creates a PointLight at the given position, with the given radius * @param position the position in world space @@ -120,10 +120,10 @@ public void computeLastDistance(Spatial owner) { /** * Returns the world space position of the light. - * + * * @return the world space position of the light. - * - * @see PointLight#setPosition(com.jme3.math.Vector3f) + * + * @see PointLight#setPosition(com.jme3.math.Vector3f) */ public Vector3f getPosition() { return position; @@ -131,7 +131,7 @@ public Vector3f getPosition() { /** * Set the world space position of the light. - * + * * @param position the world space position of the light. */ public final void setPosition(Vector3f position) { @@ -141,7 +141,7 @@ public final void setPosition(Vector3f position) { /** * Returns the radius of the light influence. A radius of 0 means * the light has no attenuation. - * + * * @return the radius of the light */ public float getRadius() { @@ -156,15 +156,22 @@ public float getRadius() { * is greater than the light's radius, then the pixel will not be * affected by this light, if the distance is less than the radius, then * the magnitude of the influence is equal to distance / radius. - * + * * @param radius the radius of the light influence. - * + * * @throws IllegalArgumentException If radius is negative */ public final void setRadius(float radius) { if (radius < 0) { throw new IllegalArgumentException("Light radius cannot be negative"); } + if(Float.isNaN(radius)){ + throw new IllegalArgumentException("Light radius cannot be a NaN (Not a Number) value"); + } + + float maxSafeRadius = Float.MAX_VALUE / 4.0f; + radius = Math.min(radius, maxSafeRadius); // Caps radius to a safe large value; avoids overflow in shaders from values reaching max float value + this.radius = radius; if (radius != 0f) { this.invRadius = 1f / radius; @@ -195,7 +202,7 @@ public boolean intersectsBox(BoundingBox box, TempVars vars) { return Intersection.intersect(box, position, radius); } } - + @Override public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { if (this.radius == 0) { @@ -205,7 +212,7 @@ public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { return Intersection.intersect(sphere, position, radius); } } - + @Override public boolean intersectsFrustum(Camera camera, TempVars vars) { if (this.radius == 0) { @@ -214,7 +221,7 @@ public boolean intersectsFrustum(Camera camera, TempVars vars) { return Intersection.intersect(camera, position, radius); } } - + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); @@ -229,9 +236,9 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); position = (Vector3f) ic.readSavable("position", null); radius = ic.readFloat("radius", 0f); - if(radius!=0){ + if (radius!=0) { this.invRadius = 1 / radius; - }else{ + } else { this.invRadius = 0; } } @@ -242,4 +249,17 @@ public PointLight clone() { p.position = position.clone(); return p; } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[name=" + name + + ", position=" + position + + ", radius=" + radius + + ", color=" + color + + ", enabled=" + enabled + + "]"; + } } + + diff --git a/jme3-core/src/main/java/com/jme3/light/ProbeArea.java b/jme3-core/src/main/java/com/jme3/light/ProbeArea.java index 28ca0c4e70..8a3d510d06 100644 --- a/jme3-core/src/main/java/com/jme3/light/ProbeArea.java +++ b/jme3-core/src/main/java/com/jme3/light/ProbeArea.java @@ -19,16 +19,25 @@ public interface ProbeArea extends Savable, Cloneable{ public Matrix4f getUniformMatrix(); /** + * @param box the BoundingBox to test for intersection + * @param vars storage for temporary data + * @return true if the area and the box intersect, otherwise false * @see Light#intersectsBox(BoundingBox, TempVars) */ public boolean intersectsBox(BoundingBox box, TempVars vars); /** + * @param sphere the BoundingSphere to test for intersection + * @param vars storage for temporary data + * @return true if the area and the sphere intersect, otherwise false * @see Light#intersectsSphere(BoundingSphere, TempVars) */ public boolean intersectsSphere(BoundingSphere sphere, TempVars vars); /** + * @param camera the Camera whose frustum will be tested for intersection + * @param vars storage for temporary data + * @return true if the area and the frustum intersect, otherwise false * @see Light#intersectsFrustum(Camera, TempVars) */ public abstract boolean intersectsFrustum(Camera camera, TempVars vars); diff --git a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java index ded9bfffa3..bf5bd1d539 100644 --- a/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java +++ b/jme3-core/src/main/java/com/jme3/light/SphereProbeArea.java @@ -13,7 +13,7 @@ public class SphereProbeArea implements ProbeArea { private Vector3f center = new Vector3f(); private float radius = 1; - private Matrix4f uniformMatrix = new Matrix4f(); + private final Matrix4f uniformMatrix = new Matrix4f(); public SphereProbeArea() { } @@ -28,11 +28,13 @@ public Vector3f getCenter() { return center; } + @Override public void setCenter(Vector3f center) { this.center.set(center); updateMatrix(); } + @Override public float getRadius() { return radius; } diff --git a/jme3-core/src/main/java/com/jme3/light/SpotLight.java b/jme3-core/src/main/java/com/jme3/light/SpotLight.java index 2025ce4146..e122fe2cbc 100644 --- a/jme3-core/src/main/java/com/jme3/light/SpotLight.java +++ b/jme3-core/src/main/java/com/jme3/light/SpotLight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012, 2015-2016, 2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,17 +46,17 @@ import java.io.IOException; /** - * Represents a spot light. - * A spot light emits a cone of light from a position and in a direction. + * Represents a spotlight. + * A spotlight emits a cone of light from a position and in a direction. * It can be used to fake torch lights or car's lights. *

    - * In addition to a position and a direction, spot lights also have a range which + * In addition to a position and a direction, spotlights also have a range which * can be used to attenuate the influence of the light depending on the * distance between the light and the affected object. - * Also the angle of the cone can be tweaked by changing the spot inner angle and the spot outer angle. - * the spot inner angle determines the cone of light where light has full influence. - * the spot outer angle determines the cone global cone of light of the spot light. - * the light intensity slowly decreases between the inner cone and the outer cone. + * Also, the angle of the cone can be tweaked by changing the spot inner angle and the spot outer angle. + * The spot inner angle determines the cone where light has full influence. + * The spot outer angle determines the global cone of light. + * The light intensity slowly decreases from the inner cone to the outer cone. * @author Nehon */ public class SpotLight extends Light { @@ -96,7 +96,7 @@ public SpotLight(Vector3f position, Vector3f direction) { * given range. * @param position the position in world space. * @param direction the direction of the light. - * @param range the spot light range + * @param range the spotlight range */ public SpotLight(Vector3f position, Vector3f direction, float range) { this(); @@ -118,14 +118,13 @@ public SpotLight(Vector3f position, Vector3f direction, ColorRGBA color) { setPosition(position); setDirection(direction); } - - + /** * Creates a SpotLight at the given position, with the given direction, * the given range and the given color. * @param position the position in world space. * @param direction the direction of the light. - * @param range the spot light range + * @param range the spotlight range * @param color the light's color. */ public SpotLight(Vector3f position, Vector3f direction, float range, ColorRGBA color) { @@ -143,10 +142,10 @@ public SpotLight(Vector3f position, Vector3f direction, float range, ColorRGBA c * * @param position the position in world space. * @param direction the direction of the light. - * @param range the spot light range + * @param range the spotlight range * @param color the light's color. - * @param innerAngle the inner angle of the spot light. - * @param outerAngle the outer angle of the spot light. + * @param innerAngle the inner angle of the spotlight. + * @param outerAngle the outer angle of the spotlight. * * @see SpotLight#setSpotInnerAngle(float) * @see SpotLight#setSpotOuterAngle(float) @@ -160,7 +159,6 @@ public SpotLight(Vector3f position, Vector3f direction, float range, ColorRGBA c setDirection(direction); setSpotRange(range); } - private void computeAngleParameters() { float innerCos = FastMath.cos(spotInnerAngle); @@ -168,7 +166,7 @@ private void computeAngleParameters() { packedAngleCos = (int) (innerCos * 1000); //due to approximations, very close angles can give the same cos - //here we make sure outer cos is bellow inner cos. + //here we make sure outer cos is below inner cos. if (((int) packedAngleCos) == ((int) (outerAngleCos * 1000))) { outerAngleCos -= 0.001f; } @@ -207,16 +205,16 @@ public boolean intersectsBox(BoundingBox box, TempVars vars) { Vector3f U = position.subtract(E, vars.vect2); Vector3f D = otherCenter.subtract(U, vars.vect3); - float dsqr = D.dot(D); + float dSquared = D.dot(D); float e = direction.dot(D); - if (e > 0f && e * e >= dsqr * outerAngleCosSqr) { + if (e > 0f && e * e >= dSquared * outerAngleCosSqr) { D = otherCenter.subtract(position, vars.vect3); - dsqr = D.dot(D); + dSquared = D.dot(D); e = -direction.dot(D); - if (e > 0f && e * e >= dsqr * outerAngleSinSqr) { - return dsqr <= otherRadiusSquared; + if (e > 0f && e * e >= dSquared * outerAngleSinSqr) { + return dSquared <= otherRadiusSquared; } else { return true; } @@ -244,16 +242,16 @@ public boolean intersectsSphere(BoundingSphere sphere, TempVars vars) { Vector3f U = position.subtract(E, vars.vect2); Vector3f D = sphere.getCenter().subtract(U, vars.vect3); - float dsqr = D.dot(D); + float dSquared = D.dot(D); float e = direction.dot(D); - if (e > 0f && e * e >= dsqr * outerAngleCosSqr) { + if (e > 0f && e * e >= dSquared * outerAngleCosSqr) { D = sphere.getCenter().subtract(position, vars.vect3); - dsqr = D.dot(D); + dSquared = D.dot(D); e = -direction.dot(D); - if (e > 0f && e * e >= dsqr * outerAngleSinSqr) { - return dsqr <= otherRadiusSquared; + if (e > 0f && e * e >= dSquared * outerAngleSinSqr) { + return dSquared <= otherRadiusSquared; } else { return true; } @@ -378,7 +376,8 @@ public float getSpotInnerAngle() { * Must be between 0 and pi/2. *

    * This angle is the angle between the spot direction axis and the inner border of the cone of influence. - * @param spotInnerAngle + * + * @param spotInnerAngle the desired angle (in radians, ≥0, ≤Pi/2) */ public void setSpotInnerAngle(float spotInnerAngle) { if (spotInnerAngle < 0f || spotInnerAngle >= FastMath.HALF_PI) { @@ -403,7 +402,8 @@ public float getSpotOuterAngle() { *

    * This angle is the angle between the spot direction axis and the outer border of the cone of influence. * this should be greater than the inner angle or the result will be unexpected. - * @param spotOuterAngle + * + * @param spotOuterAngle the desired angle (in radians, ≥0, ≤Pi/2) */ public void setSpotOuterAngle(float spotOuterAngle) { if (spotOuterAngle < 0f || spotOuterAngle >= FastMath.HALF_PI) { @@ -456,5 +456,18 @@ public SpotLight clone() { s.position = position.clone(); return s; } -} + @Override + public String toString() { + return getClass().getSimpleName() + + "[name=" + name + + ", direction=" + direction + + ", position=" + position + + ", range=" + spotRange + + ", innerAngle=" + spotInnerAngle + + ", outerAngle=" + spotOuterAngle + + ", color=" + color + + ", enabled=" + enabled + + "]"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java index 7e7f2f92f8..5c5da45d8e 100644 --- a/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java +++ b/jme3-core/src/main/java/com/jme3/light/WeightedProbeBlendingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,8 +45,8 @@ */ public class WeightedProbeBlendingStrategy implements LightProbeBlendingStrategy { - private final static int MAX_PROBES = 3; - List lightProbes = new ArrayList(); + private static final int MAX_PROBES = 3; + List lightProbes = new ArrayList<>(); @Override public void registerProbe(LightProbe probe) { 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 f274d93404..8e4168133e 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParam.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParam.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,26 @@ */ package com.jme3.material; +import java.io.IOException; +import java.util.Arrays; + import com.jme3.asset.TextureKey; -import com.jme3.export.*; -import com.jme3.math.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; import com.jme3.shader.VarType; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; -import java.io.IOException; - /** * Describes a material parameter. This is used for both defining a name and type * as well as a material parameter value. @@ -52,9 +63,14 @@ 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. + * + * @param type the type of the parameter + * @param name the desired parameter name + * @param value the desired parameter value (alias created) */ public MatParam(VarType type, String name, Object value) { this.type = type; @@ -69,6 +85,21 @@ 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 typeCheck (default = true) + */ + public void setTypeCheckEnabled(boolean typeCheck) { + this.typeCheck = typeCheck; + } + /** * Returns the material parameter type. * @@ -80,6 +111,7 @@ public VarType getVarType() { /** * Returns the name of the material parameter. + * * @return the name of the material parameter. */ public String getName() { @@ -126,21 +158,37 @@ 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; } - /** * Returns the material parameter value as it would appear in a J3M - * file. E.g.
    - * - * MaterialParameters {
    - * ABC : 1 2 3 4
    - * }
    - *
    + * file. E.g. + *

    +     * MaterialParameters {
    +     *     ABC : 1 2 3 4
    +     * }
    +     * 
    * Assuming "ABC" is a Vector4 parameter, then the value * "1 2 3 4" would be returned by this method. - *

    + * * @return material parameter value as it would appear in a J3M file. */ public String getValueAsString() { @@ -152,7 +200,7 @@ public String getValueAsString() { case Vector2: Vector2f v2 = (Vector2f) value; return v2.getX() + " " + v2.getY(); -/* +/* This may get used at a later point of time When arrays can be inserted in J3M files @@ -237,12 +285,12 @@ public String getValueAsString() { case TextureCubeMap: Texture texVal = (Texture) value; TextureKey texKey = (TextureKey) texVal.getKey(); - if (texKey == null){ - //throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M"); - // this is used in toString and the above line causes blender materials to throw this exception. + if (texKey == null) { + // throw new UnsupportedOperationException("The specified MatParam cannot be represented in J3M"); + // this is used in toString and the above line causes blender materials to throw this exception. // toStrings should be very robust IMO as even debuggers often invoke toString and logging code - // often does as well, even implicitly. - return texVal+":returned null key"; + // often does as well, even implicitly. + return texVal + ":returned null key"; } String ret = ""; @@ -250,22 +298,22 @@ public String getValueAsString() { ret += "Flip "; } - //Wrap mode + // Wrap mode ret += getWrapMode(texVal, Texture.WrapAxis.S); ret += getWrapMode(texVal, Texture.WrapAxis.T); ret += getWrapMode(texVal, Texture.WrapAxis.R); - //Min and Mag filter - Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; - if(texVal.getImage().hasMipmaps() || texKey.isGenerateMips()){ + // Min and Mag filter + Texture.MinFilter def = Texture.MinFilter.BilinearNoMipMaps; + if (texVal.getImage().hasMipmaps() || texKey.isGenerateMips()) { def = Texture.MinFilter.Trilinear; } - if(texVal.getMinFilter() != def){ - ret += "Min" + texVal.getMinFilter().name()+ " "; + if (texVal.getMinFilter() != def) { + ret += "Min" + texVal.getMinFilter().name() + " "; } - if(texVal.getMagFilter() != Texture.MagFilter.Bilinear){ - ret += "Mag" + texVal.getMagFilter().name()+ " "; + if (texVal.getMagFilter() != Texture.MagFilter.Bilinear) { + ret += "Mag" + texVal.getMagFilter().name() + " "; } return ret + "\"" + texKey.getName() + "\""; @@ -276,14 +324,14 @@ public String getValueAsString() { private String getWrapMode(Texture texVal, Texture.WrapAxis axis) { WrapMode mode = WrapMode.EdgeClamp; - try{ + try { mode = texVal.getWrap(axis); - }catch (IllegalArgumentException e){ - //this axis doesn't exist on the texture + } catch (IllegalArgumentException ex) { + // this axis doesn't exist on the texture return ""; } - if(mode != WrapMode.EdgeClamp){ - return"Wrap"+ mode.name() + "_" + axis.name() + " "; + if (mode != WrapMode.EdgeClamp) { + return "Wrap" + mode.name() + "_" + axis.name() + " "; } return ""; } @@ -298,6 +346,7 @@ public MatParam clone() { } } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(type, "varType", null); @@ -318,8 +367,11 @@ 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 public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readEnum("varType", VarType.class, null); @@ -374,6 +426,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/MatParamTexture.java b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java index 4fffd1616f..58bd44418d 100644 --- a/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java +++ b/jme3-core/src/main/java/com/jme3/material/MatParamTexture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,50 +40,67 @@ import com.jme3.texture.image.ColorSpace; import java.io.IOException; +/** + * A material parameter that holds a reference to a texture and its required color space. + * This class extends {@link MatParam} to provide texture specific functionalities. + */ public class MatParamTexture extends MatParam { - private Texture texture; private ColorSpace colorSpace; + /** + * Constructs a new MatParamTexture instance with the specified type, name, + * texture, and color space. + * + * @param type the type of the material parameter + * @param name the name of the parameter + * @param texture the texture associated with this parameter + * @param colorSpace the required color space for the texture + */ public MatParamTexture(VarType type, String name, Texture texture, ColorSpace colorSpace) { super(type, name, texture); - this.texture = texture; this.colorSpace = colorSpace; } + /** + * Serialization only. Do not use. + */ public MatParamTexture() { } + /** + * Retrieves the texture associated with this material parameter. + * + * @return the texture object + */ public Texture getTextureValue() { - return texture; + return (Texture) getValue(); } + /** + * Sets the texture associated with this material parameter. + * + * @param value the texture object to set + * @throws RuntimeException if the provided value is not a {@link Texture} + */ public void setTextureValue(Texture value) { - this.value = value; - this.texture = value; - } - - @Override - public void setValue(Object value) { - if (!(value instanceof Texture)) { - throw new IllegalArgumentException("value must be a texture object"); - } - this.value = value; - this.texture = (Texture) value; + setValue(value); } /** + * Gets the required color space for this texture parameter. * - * @return the color space required by this texture param + * @return the required color space ({@link ColorSpace}) */ public ColorSpace getColorSpace() { return colorSpace; } /** - * Set to {@link ColorSpace#Linear} if the texture color space has to be forced to linear - * instead of sRGB - * @param colorSpace @see ColorSpace + * Set to {@link ColorSpace#Linear} if the texture color space has to be forced + * to linear instead of sRGB. + * + * @param colorSpace the desired color space */ public void setColorSpace(ColorSpace colorSpace) { this.colorSpace = colorSpace; @@ -93,17 +110,17 @@ public void setColorSpace(ColorSpace colorSpace) { public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(0, "texture_unit", -1); - oc.write(texture, "texture", null); // For backwards compatibility - oc.write(colorSpace, "colorSpace", null); + // For backwards compatibility + oc.write(0, "texture_unit", -1); + oc.write((Texture) value, "texture", null); } @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - texture = (Texture) value; - colorSpace = (ColorSpace) ic.readEnum("colorSpace", ColorSpace.class, null); + colorSpace = ic.readEnum("colorSpace", ColorSpace.class, null); } -} \ No newline at end of file + +} diff --git a/jme3-core/src/main/java/com/jme3/material/Material.java b/jme3-core/src/main/java/com/jme3/material/Material.java index 15bd3c17df..0c4317a307 100644 --- a/jme3-core/src/main/java/com/jme3/material/Material.java +++ b/jme3-core/src/main/java/com/jme3/material/Material.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,26 +34,41 @@ import com.jme3.asset.AssetKey; import com.jme3.asset.AssetManager; import com.jme3.asset.CloneableSmartAsset; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.light.LightList; import com.jme3.material.RenderState.BlendMode; import com.jme3.material.RenderState.FaceCullMode; import com.jme3.material.TechniqueDef.LightMode; -import com.jme3.math.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; +import com.jme3.renderer.TextureUnitException; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; import com.jme3.shader.*; +import com.jme3.shader.bufferobject.BufferObject; import com.jme3.texture.Image; import com.jme3.texture.Texture; +import com.jme3.texture.TextureImage; import com.jme3.texture.image.ColorSpace; import com.jme3.util.ListMap; import com.jme3.util.SafeArrayList; import java.io.IOException; -import java.util.*; +import java.util.Collection; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -64,7 +79,7 @@ * those parameters map to uniforms which are defined in a shader. * Setting the parameters can modify the behavior of a * shader. - *

    + *

    * * @author Kirill Vainer */ @@ -74,21 +89,40 @@ public class Material implements CloneableSmartAsset, Cloneable, Savable { public static final int SAVABLE_VERSION = 2; private static final Logger logger = Logger.getLogger(Material.class.getName()); - private AssetKey key; + private AssetKey key; private String name; private MaterialDef def; - private ListMap paramValues = new ListMap(); + private ListMap paramValues = new ListMap<>(); private Technique technique; - private HashMap techniques = new HashMap(); + private HashMap techniques = new HashMap<>(); private RenderState additionalState = null; - private RenderState mergedRenderState = new RenderState(); + private final RenderState mergedRenderState = new RenderState(); private boolean transparent = false; private boolean receivesShadows = false; private int sortingId = -1; + /** + * Manages and tracks texture and buffer binding units for rendering. + * Used internally by the Material class. + */ + public static class BindUnits { + /** The current texture unit counter. */ + public int textureUnit = 0; + /** The current buffer unit counter. */ + public int bufferUnit = 0; + } + private BindUnits bindUnits = new BindUnits(); + + /** + * Constructs a new Material instance based on a provided MaterialDef. + * The material's parameters will be initialized with default values from the definition. + * + * @param def The material definition to use (cannot be null). + * @throws IllegalArgumentException if def is null. + */ public Material(MaterialDef def) { if (def == null) { - throw new NullPointerException("Material definition cannot be null"); + throw new IllegalArgumentException("Material definition cannot be null"); } this.def = def; @@ -100,49 +134,59 @@ public Material(MaterialDef def) { } } - public Material(AssetManager contentMan, String defName) { - this((MaterialDef) contentMan.loadAsset(new AssetKey(defName))); + /** + * Constructs a new Material by loading its MaterialDef from the asset manager. + * + * @param assetManager The asset manager to load the MaterialDef from. + * @param defName The asset path of the .j3md file. + */ + public Material(AssetManager assetManager, String defName) { + this(assetManager.loadAsset(new AssetKey(defName))); } /** - * Do not use this constructor. Serialization purposes only. + * For serialization only. Do not use. */ public Material() { } /** * Returns the asset key name of the asset from which this material was loaded. + *

    This value will be null unless this material was loaded from a .j3m file.

    * - *

    This value will be null unless this material was loaded - * from a .j3m file. - * - * @return Asset key name of the j3m file + * @return Asset key name of the .j3m file, or null if not loaded from a file. */ public String getAssetName() { return key != null ? key.getName() : null; } /** - * @return the name of the material (not the same as the asset name), the returned value can be null + * Returns the user-defined name of the material. + * This name is distinct from the asset name and may be null or not unique. + * + * @return The name of the material, or null. */ public String getName() { return name; } /** - * This method sets the name of the material. + * Sets the user-defined name of the material. * The name is not the same as the asset name. - * It can be null and there is no guarantee of its uniqueness. - * @param name the name of the material + * It can be null, and there is no guarantee of its uniqueness. + * + * @param name The name of the material. */ public void setName(String name) { this.name = name; } + @Override public void setKey(AssetKey key) { this.key = key; } + @Override public AssetKey getKey() { return key; } @@ -213,7 +257,7 @@ public Material clone() { } /** - * Compares two materials and returns true if they are equal. + * Compares two materials for content equality. * This methods compare definition, parameters, additional render states. * Since materials are mutable objects, implementing equals() properly is not possible, * hence the name contentEquals(). @@ -293,6 +337,8 @@ public boolean contentEquals(Object otherObj) { /** * Works like {@link Object#hashCode() } except it may change together with the material as the material is mutable by definition. + * + * @return value for use in hashing */ public int contentHashCode() { int hash = 7; @@ -388,7 +434,7 @@ public RenderState getAdditionalRenderState() { } /** - * Get the material definition (j3md file info) that this + * Get the material definition (.j3md file info) that this * material is implementing. * * @return the material definition this material implements. @@ -411,9 +457,11 @@ public MatParam getParam(String name) { /** * Returns the current parameter's value. * + * @param the expected type of the parameter value * @param name the parameter name to look up. * @return current value or null if the parameter wasn't set. */ + @SuppressWarnings("unchecked") public T getParamValue(final String name) { final MatParam param = paramValues.get(name); return param == null ? null : (T) param.getValue(); @@ -433,7 +481,7 @@ public MatParamTexture getTextureParam(String name) { } return null; } - + /** * Returns a collection of all parameters set on this material. * @@ -475,7 +523,7 @@ private void checkSetParam(VarType type, String name) { /** * Pass a parameter to the material shader. * - * @param name the name of the parameter defined in the material definition (j3md) + * @param name the name of the parameter defined in the material definition (.j3md) * @param type the type of the parameter {@link VarType} * @param value the value of the parameter */ @@ -487,7 +535,6 @@ public void setParam(String name, VarType type, Object value) { } else { MatParam val = getParam(name); if (val == null) { - MatParam paramDef = def.getMaterialParam(name); paramValues.put(name, new MatParam(type, name, value)); } else { val.setValue(value); @@ -496,9 +543,24 @@ public void setParam(String name, VarType type, Object value) { if (technique != null) { technique.notifyParamChanged(name, type, value); } + if (type.isImageType()) { + // recompute sort id + sortingId = -1; + } } } + /** + * Pass a parameter to the material shader. + * + * @param name the name of the parameter defined in the material definition (j3md) + * @param value the value of the parameter + */ + public void setParam(String name, Object value) { + MatParam p = getMaterialDef().getMaterialParam(name); + setParam(name, p.getVarType(), value); + } + /** * Clear a parameter from this material. The parameter must exist * @param name the name of the parameter to clear @@ -534,14 +596,17 @@ public void setTextureParam(String name, VarType type, Texture value) { } checkSetParam(type, name); - MatParamTexture val = getTextureParam(name); - if (val == null) { - checkTextureParamColorSpace(name, value); - paramValues.put(name, new MatParamTexture(type, name, value, value.getImage() != null ? value.getImage().getColorSpace() : null)); + MatParamTexture param = getTextureParam(name); + + checkTextureParamColorSpace(name, value); + ColorSpace colorSpace = value.getImage() != null ? value.getImage().getColorSpace() : null; + + if (param == null) { + param = new MatParamTexture(type, name, value, colorSpace); + paramValues.put(name, param); } else { - checkTextureParamColorSpace(name, value); - val.setTextureValue(value); - val.setColorSpace(value.getImage() != null ? value.getImage().getColorSpace() : null); + param.setTextureValue(value); + param.setColorSpace(colorSpace); } if (technique != null) { @@ -556,11 +621,13 @@ private void checkTextureParamColorSpace(String name, Texture value) { MatParamTexture paramDef = (MatParamTexture) def.getMaterialParam(name); if (paramDef.getColorSpace() != null && paramDef.getColorSpace() != value.getImage().getColorSpace()) { value.getImage().setColorSpace(paramDef.getColorSpace()); - logger.log(Level.FINE, "Material parameter {0} needs a {1} texture, " - + "texture {2} was switched to {3} color space.", - new Object[]{name, paramDef.getColorSpace().toString(), - value.getName(), - value.getImage().getColorSpace().name()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Material parameter {0} needs a {1} texture, " + + "texture {2} was switched to {3} color space.", + new Object[]{name, paramDef.getColorSpace().toString(), + value.getName(), + value.getImage().getColorSpace().name()}); + } } else if (paramDef.getColorSpace() == null && value.getName() != null && value.getImage().getColorSpace() == ColorSpace.Linear) { logger.log(Level.WARNING, "The texture {0} has linear color space, but the material " @@ -576,8 +643,8 @@ private void checkTextureParamColorSpace(String name, Texture value) { /** * Pass a texture to the material shader. * - * @param name the name of the texture defined in the material definition - * (j3md) (for example Texture for Lighting.j3md) + * @param name the name of the texture defined in the material definition + * (.j3md) (e.g. Texture for Lighting.j3md) * @param value the Texture object previously loaded by the asset manager */ public void setTexture(String name, Texture value) { @@ -670,14 +737,13 @@ public void setColor(String name, ColorRGBA value) { } /** - * Pass an uniform buffer object to the material shader. + * Pass a uniform buffer object to the material shader. * * @param name the name of the buffer object defined in the material definition (j3md). * @param value the buffer object. */ public void setUniformBufferObject(final String name, final BufferObject value) { - value.setBufferType(BufferObject.BufferType.UniformBufferObject); - setParam(name, VarType.BufferObject, value); + setParam(name, VarType.UniformBufferObject, value); } /** @@ -687,8 +753,7 @@ public void setUniformBufferObject(final String name, final BufferObject value) * @param value the buffer object. */ public void setShaderStorageBufferObject(final String name, final BufferObject value) { - value.setBufferType(BufferObject.BufferType.ShaderStorageBufferObject); - setParam(name, VarType.BufferObject, value); + setParam(name, VarType.ShaderStorageBufferObject, value); } /** @@ -772,7 +837,9 @@ public void selectTechnique(String name, final RenderManager renderManager) { + "The capabilities %s are required.", name, def.getName(), lastTech.getRequiredCaps())); } - logger.log(Level.FINE, this.getMaterialDef().getName() + " selected technique def " + tech.getDef()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, this.getMaterialDef().getName() + " selected technique def " + tech.getDef()); + } } else if (technique == tech) { // attempting to switch to an already // active technique. @@ -786,7 +853,7 @@ public void selectTechnique(String name, final RenderManager renderManager) { sortingId = -1; } - private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList overrides, int unit) { + private void applyOverrides(Renderer renderer, Shader shader, SafeArrayList overrides, BindUnits bindUnits) { for (MatParamOverride override : overrides.getArray()) { VarType type = override.getVarType(); @@ -799,82 +866,110 @@ private int applyOverrides(Renderer renderer, Shader shader, SafeArrayList worldOverrides, SafeArrayList forcedOverrides) { + private void updateShaderMaterialParameter(Renderer renderer, VarType type, Shader shader, MatParam param, BindUnits unit, boolean override) { + if (type == VarType.UniformBufferObject || type == VarType.ShaderStorageBufferObject) { + ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName()); + BufferObject bufferObject = (BufferObject) param.getValue(); + + ShaderBufferBlock.BufferType btype; + if (type == VarType.ShaderStorageBufferObject) { + btype = ShaderBufferBlock.BufferType.ShaderStorageBufferObject; + bufferBlock.setBufferObject(btype, bufferObject); + renderer.setShaderStorageBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed + } else { + btype = ShaderBufferBlock.BufferType.UniformBufferObject; + bufferBlock.setBufferObject(btype, bufferObject); + renderer.setUniformBufferObject(unit.bufferUnit, bufferObject); // TODO: probably not needed + } + unit.bufferUnit++; + } else { + Uniform uniform = shader.getUniform(param.getPrefixedName()); + if (!override && uniform.isSetByCurrentMaterial()) + return; + + if (type.isTextureType() || type.isImageType()) { + try { + if (type.isTextureType()) { + renderer.setTexture(unit.textureUnit, (Texture) param.getValue()); + } else { + renderer.setTextureImage(unit.textureUnit, (TextureImage) param.getValue()); + } + } catch (TextureUnitException ex) { + int numTexParams = unit.textureUnit + 1; + String message = "Too many texture parameters (" + numTexParams + ") assigned\n to " + this.toString(); + throw new IllegalStateException(message); + } + uniform.setValue(VarType.Int, unit.textureUnit); + unit.textureUnit++; + } else { + uniform.setValue(type, param.getValue()); + } + } + } + + private BindUnits updateShaderMaterialParameters(Renderer renderer, Shader shader, + SafeArrayList worldOverrides, SafeArrayList forcedOverrides) { + + bindUnits.textureUnit = 0; + bindUnits.bufferUnit = 0; - int unit = 0; if (worldOverrides != null) { - unit = applyOverrides(renderer, shader, worldOverrides, unit); + applyOverrides(renderer, shader, worldOverrides, bindUnits); } if (forcedOverrides != null) { - unit = applyOverrides(renderer, shader, forcedOverrides, unit); + applyOverrides(renderer, shader, forcedOverrides, bindUnits); } for (int i = 0; i < paramValues.size(); i++) { - MatParam param = paramValues.getValue(i); VarType type = param.getVarType(); + updateShaderMaterialParameter(renderer, type, shader, param, bindUnits, false); + } - if (isBO(type)) { - - final ShaderBufferBlock bufferBlock = shader.getBufferBlock(param.getPrefixedName()); - bufferBlock.setBufferObject((BufferObject) param.getValue()); - - } else { - - Uniform uniform = shader.getUniform(param.getPrefixedName()); - if (uniform.isSetByCurrentMaterial()) { - continue; - } + // TODO: HACKY HACK remove this when texture unit is handled by the uniform. + return bindUnits; + } - if (type.isTextureType()) { - renderer.setTexture(unit, (Texture) param.getValue()); - uniform.setValue(VarType.Int, unit); - unit++; - } else { - uniform.setValue(type, param.getValue()); - } - } + private void updateRenderState(Geometry geometry, RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) { + RenderState finalRenderState; + if (renderManager.getForcedRenderState() != null) { + finalRenderState = mergedRenderState.copyFrom(renderManager.getForcedRenderState()); + } else if (techniqueDef.getRenderState() != null) { + finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT); + finalRenderState = techniqueDef.getRenderState().copyMergedTo(additionalState, finalRenderState); + } else { + finalRenderState = mergedRenderState.copyFrom(RenderState.DEFAULT); + finalRenderState = RenderState.DEFAULT.copyMergedTo(additionalState, finalRenderState); } - - //TODO HACKY HACK remove this when texture unit is handled by the uniform. - return unit; + // test if the face cull mode should be flipped before render + if (finalRenderState.isFaceCullFlippable() && isNormalsBackward(geometry.getWorldScale())) { + finalRenderState.flipFaceCull(); + } + renderer.applyRenderState(finalRenderState); } /** - * Returns true if the type is Buffer Object's type. + * Returns true if the geometry world scale indicates that normals will be backward. * - * @param type the material parameter type. - * @return true if the type is Buffer Object's type. + * @param scalar The geometry's world scale vector. + * @return true if the normals are effectively backward; false otherwise. */ - private boolean isBO(final VarType type) { - return type == VarType.BufferObject; - } - - private void updateRenderState(RenderManager renderManager, Renderer renderer, TechniqueDef techniqueDef) { - if (renderManager.getForcedRenderState() != null) { - renderer.applyRenderState(renderManager.getForcedRenderState()); - } else { - if (techniqueDef.getRenderState() != null) { - renderer.applyRenderState(techniqueDef.getRenderState().copyMergedTo(additionalState, mergedRenderState)); - } else { - renderer.applyRenderState(RenderState.DEFAULT.copyMergedTo(additionalState, mergedRenderState)); - } - } + private boolean isNormalsBackward(Vector3f scalar) { + // count number of negative scalar vector components + int n = 0; + if (scalar.x < 0) n++; + if (scalar.y < 0) n++; + if (scalar.z < 0) n++; + // An odd number of negative components means the normal vectors + // are backward to what they should be. + return n == 1 || n == 3; } /** @@ -885,6 +980,7 @@ private void updateRenderState(RenderManager renderManager, Renderer renderer, T * been already been setup for rendering. * * @param renderManager The render manager to preload for + * @param geometry to determine the applicable parameter overrides, if any */ public void preload(RenderManager renderManager, Geometry geometry) { if (technique == null) { @@ -953,11 +1049,11 @@ private void resetUniformsNotSetByCurrent(Shader shader) { *

  • Set the {@link RenderState} to use for rendering. The render states are * applied in this order (later RenderStates override earlier RenderStates):
      *
    1. {@link TechniqueDef#getRenderState() Technique Definition's RenderState} - * - i.e. specific renderstate that is required for the shader.
    2. + * - i.e. specific RenderState that is required for the shader. *
    3. {@link #getAdditionalRenderState() Material Instance Additional RenderState} - * - i.e. ad-hoc renderstate set per model
    4. + * - i.e. ad-hoc RenderState set per model *
    5. {@link RenderManager#getForcedRenderState() RenderManager's Forced RenderState} - * - i.e. renderstate requested by a {@link com.jme3.post.SceneProcessor} or + * - i.e. RenderState requested by a {@link com.jme3.post.SceneProcessor} or * post-processing filter.
    *
  • If the technique uses a shader, then the uniforms of the shader must be updated.
      *
    • Uniforms bound to material parameters are updated based on the current material parameter values.
    • @@ -1002,7 +1098,7 @@ public void render(Geometry geometry, LightList lights, RenderManager renderMana } // Apply render state - updateRenderState(renderManager, renderer, techniqueDef); + updateRenderState(geometry, renderManager, renderer, techniqueDef); // Get world overrides SafeArrayList overrides = geometry.getWorldMatParamOverrides(); @@ -1017,13 +1113,13 @@ public void render(Geometry geometry, LightList lights, RenderManager renderMana renderManager.updateUniformBindings(shader); // Set material parameters - int unit = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams()); + BindUnits units = updateShaderMaterialParameters(renderer, shader, overrides, renderManager.getForcedMatParams()); // Clear any uniforms not changed by material. resetUniformsNotSetByCurrent(shader); // Delegate rendering to the technique - technique.render(renderManager, shader, geometry, lights, unit); + technique.render(renderManager, shader, geometry, lights, units); } /** @@ -1040,35 +1136,40 @@ public void render(Geometry geom, RenderManager rm) { render(geom, geom.getWorldLightList(), rm); } + @Override + public String toString() { + return "Material[name=" + name + + ", def=" + (def != null ? def.getName() : null) + + ", tech=" + (technique != null && technique.getDef() != null ? technique.getDef().getName() : null) + + "]"; + } + + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(def.getAssetName(), "material_def", null); oc.write(additionalState, "render_state", null); oc.write(transparent, "is_transparent", false); + oc.write(receivesShadows, "receives_shadows", false); oc.write(name, "name", null); oc.writeStringSavableMap(paramValues, "parameters", null); } @Override - public String toString() { - return "Material[name=" + name + - ", def=" + (def != null ? def.getName() : null) + - ", tech=" + (technique != null && technique.getDef() != null ? technique.getDef().getName() : null) + - "]"; - } - + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", null); additionalState = (RenderState) ic.readSavable("render_state", null); transparent = ic.readBoolean("is_transparent", false); + receivesShadows = ic.readBoolean("receives_shadows", false); // Load the material def String defName = ic.readString("material_def", null); HashMap params = (HashMap) ic.readStringSavableMap("parameters", null); - boolean enableVcolor = false; + boolean enableVertexColor = false; boolean separateTexCoord = false; boolean applyDefaultValues = false; boolean guessRenderStateApply = false; @@ -1084,14 +1185,14 @@ public void read(JmeImporter im) throws IOException { // Enable compatibility with old models if (defName.equalsIgnoreCase("Common/MatDefs/Misc/VertexColor.j3md")) { // Using VertexColor, switch to Unshaded and set VertexColor=true - enableVcolor = true; + enableVertexColor = true; defName = "Common/MatDefs/Misc/Unshaded.j3md"; } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/SimpleTextured.j3md") || defName.equalsIgnoreCase("Common/MatDefs/Misc/SolidColor.j3md")) { // Using SimpleTextured/SolidColor, just switch to Unshaded defName = "Common/MatDefs/Misc/Unshaded.j3md"; } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/WireColor.j3md")) { - // Using WireColor, set wireframe renderstate = true and use Unshaded + // Using WireColor, set wireframe render state = true and use Unshaded getAdditionalRenderState().setWireframe(true); defName = "Common/MatDefs/Misc/Unshaded.j3md"; } else if (defName.equalsIgnoreCase("Common/MatDefs/Misc/Unshaded.j3md")) { @@ -1105,7 +1206,7 @@ public void read(JmeImporter im) throws IOException { assert applyDefaultValues && guessRenderStateApply; } - def = (MaterialDef) im.getAssetManager().loadAsset(new AssetKey(defName)); + def = im.getAssetManager().loadAsset(new AssetKey(defName)); paramValues = new ListMap(); // load the textures and update nextTexUnit @@ -1136,8 +1237,7 @@ public void read(JmeImporter im) throws IOException { } if (applyDefaultValues) { - // compatability with old versions where default vars were - // not available + // compatibility with old versions where default vars were not available for (MatParam param : def.getMaterialParams()) { if (param.getValue() != null && paramValues.get(param.getName()) == null) { setParam(param.getName(), param.getVarType(), param.getValue()); @@ -1156,7 +1256,7 @@ public void read(JmeImporter im) throws IOException { additionalState.applyStencilTest = additionalState.stencilTest; additionalState.applyWireFrame = additionalState.wireframe; } - if (enableVcolor) { + if (enableVertexColor) { setBoolean("VertexColor", true); } if (separateTexCoord) { diff --git a/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java b/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java index 49d04c5db9..ce119cf6b5 100644 --- a/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java +++ b/jme3-core/src/main/java/com/jme3/material/MaterialProcessor.java @@ -36,10 +36,12 @@ public class MaterialProcessor implements AssetProcessor { + @Override public Object postProcess(AssetKey key, Object obj) { return null; } + @Override public Object createClone(Object obj) { return ((Material) obj).clone(); } diff --git a/jme3-core/src/main/java/com/jme3/material/Materials.java b/jme3-core/src/main/java/com/jme3/material/Materials.java index 00959d15ec..0b0003323e 100644 --- a/jme3-core/src/main/java/com/jme3/material/Materials.java +++ b/jme3-core/src/main/java/com/jme3/material/Materials.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,8 +38,17 @@ */ public class Materials { - public static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; - public static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; - public static final String PBR = "Common/MatDefs/Light/PBRLighting.j3md"; + public static final String SHOW_NORMALS = "Common/MatDefs/Misc/ShowNormals.j3md"; + public static final String UNSHADED = "Common/MatDefs/Misc/Unshaded.j3md"; + public static final String LIGHTING = "Common/MatDefs/Light/Lighting.j3md"; + public static final String PBR = "Common/MatDefs/Light/PBRLighting.j3md"; + public static final String PARTICLE = "Common/MatDefs/Misc/Particle.j3md"; + public static final String BILLBOARD = "Common/MatDefs/Misc/Billboard.j3md"; + public static final String GUI = "Common/MatDefs/Gui/Gui.j3md"; -} \ No newline at end of file + /** + * A private constructor to inhibit instantiation of this class. + */ + private Materials() { + } +} 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 5413c3880c..2e93a4e1f3 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-2019 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, /** @@ -346,12 +345,12 @@ public enum BlendMode { *

      * These attributes can be set by using the following methods: *

        - *
      • {@link RenderState#setBlendEquation(BlendEquation)}
        - *
      • {@link RenderState#setBlendEquationAlpha(BlendEquationAlpha)}
        - *
      • {@link RenderState#setCustomBlendFactors(BlendFunc, BlendFunc, BlendFunc, BlendFunc)}
        + *
      • {@link RenderState#setBlendEquation(BlendEquation)} + *
      • {@link RenderState#setBlendEquationAlpha(BlendEquationAlpha)} + *
      • {@link RenderState#setCustomBlendFactors(BlendFunc, BlendFunc, BlendFunc, BlendFunc)} *
      *

      - * Result.RGB = BlendEquation( sfactorRGB * Source.RGB , dfactorRGB * Destination.RGB )
      + * Result.RGB = BlendEquation( sfactorRGB * Source.RGB , dfactorRGB * Destination.RGB )
      * Result.A = BlendEquationAlpha( sfactorAlpha * Source.A , dfactorAlpha * Destination.A ) */ Custom @@ -480,12 +479,17 @@ 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; BlendFunc sfactorAlpha = BlendFunc.One; BlendFunc dfactorAlpha = BlendFunc.One; + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(true, "pointSprite", false); @@ -502,19 +506,23 @@ public void write(JmeExporter ex) throws IOException { oc.write(frontStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); oc.write(frontStencilDepthFailOperation, "frontStencilDepthFailOperation", StencilOperation.Keep); oc.write(frontStencilDepthPassOperation, "frontStencilDepthPassOperation", StencilOperation.Keep); - oc.write(backStencilStencilFailOperation, "frontStencilStencilFailOperation", StencilOperation.Keep); + oc.write(backStencilStencilFailOperation, "backStencilStencilFailOperation", StencilOperation.Keep); oc.write(backStencilDepthFailOperation, "backStencilDepthFailOperation", StencilOperation.Keep); 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); oc.write(lineWidth, "lineWidth", 1); - oc.write(sfactorRGB, "sfactorRGB", sfactorRGB); - oc.write(dfactorRGB, "dfactorRGB", dfactorRGB); - oc.write(sfactorAlpha, "sfactorAlpha", sfactorAlpha); - oc.write(dfactorAlpha, "dfactorAlpha", dfactorAlpha); + oc.write(sfactorRGB, "sfactorRGB", BlendFunc.One); + oc.write(dfactorRGB, "dfactorRGB", BlendFunc.One); + oc.write(sfactorAlpha, "sfactorAlpha", BlendFunc.One); + oc.write(dfactorAlpha, "dfactorAlpha", BlendFunc.One); // Only "additional render state" has them set to false by default oc.write(applyWireFrame, "applyWireFrame", true); @@ -529,6 +537,7 @@ public void write(JmeExporter ex) throws IOException { } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); wireframe = ic.readBoolean("wireframe", false); @@ -549,13 +558,17 @@ 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); lineWidth = ic.readFloat("lineWidth", 1); sfactorRGB = ic.readEnum("sfactorRGB", BlendFunc.class, BlendFunc.One); - dfactorAlpha = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.One); - sfactorRGB = ic.readEnum("sfactorAlpha", BlendFunc.class, BlendFunc.One); + dfactorRGB = ic.readEnum("dfactorRGB", BlendFunc.class, BlendFunc.One); + sfactorAlpha = ic.readEnum("sfactorAlpha", BlendFunc.class, BlendFunc.One); dfactorAlpha = ic.readEnum("dfactorAlpha", BlendFunc.class, BlendFunc.One); @@ -586,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) { @@ -693,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){ @@ -702,33 +730,6 @@ public boolean equals(Object o) { return true; } - /** - * @deprecated Does nothing. Point sprite is already enabled by default for - * all supported platforms. jME3 does not support rendering conventional - * point clouds. - */ - @Deprecated - public void setPointSprite(boolean pointSprite) { - } - - /** - * @deprecated Does nothing. To use alpha test, set the - * AlphaDiscardThreshold material parameter. - * @param alphaFallOff does nothing - */ - @Deprecated - public void setAlphaFallOff(float alphaFallOff) { - } - - /** - * @deprecated Does nothing. To use alpha test, set the - * AlphaDiscardThreshold material parameter. - * @param alphaTest does nothing - */ - @Deprecated - public void setAlphaTest(boolean alphaTest) { - } - /** * Enable writing color. * @@ -770,7 +771,7 @@ public void setFaceCullMode(FaceCullMode cullMode) { * already in the color buffer. The blending operation is determined * by the {@link BlendMode}. For example, the {@link BlendMode#Additive} * will add the input pixel's color to the color already in the color buffer: - *
      + *
      * Result = Source Color + Destination Color * * @param blendMode The blend mode to use. Set to {@link BlendMode#Off} @@ -786,10 +787,10 @@ public void setBlendMode(BlendMode blendMode) { * Set the blending equation for the color component (RGB). *

      * The blending equation determines, how the RGB values of the input pixel - * will be blended with the RGB values of the pixel already in the color buffer.
      + * will be blended with the RGB values of the pixel already in the color buffer.
      * For example, {@link BlendEquation#Add} will add the input pixel's color * to the color already in the color buffer: - *
      + *
      * Result = Source Color + Destination Color *

      * Note: This gets only used in {@link BlendMode#Custom} mode. @@ -806,10 +807,10 @@ public void setBlendEquation(BlendEquation blendEquation) { * Set the blending equation for the alpha component. *

      * The alpha blending equation determines, how the alpha values of the input pixel - * will be blended with the alpha values of the pixel already in the color buffer.
      + * will be blended with the alpha values of the pixel already in the color buffer.
      * For example, {@link BlendEquationAlpha#Add} will add the input pixel's color * to the color already in the color buffer: - *
      + *
      * Result = Source Color + Destination Color *

      * Note: This gets only used in {@link BlendMode#Custom} mode. @@ -899,10 +900,10 @@ public void setWireframe(boolean wireframe) { * typically with positive Z pointing into the screen. * Typical values are (1.0f, 1.0f) or (-1.0f, -1.0f) * - * @see http://www.opengl.org/resources/faq/technical/polygonoffset.htm + * @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) { @@ -965,8 +966,9 @@ public void setStencil(boolean enabled, } /** - * Set the depth conparison function to the given TestFunction + * 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 @@ -977,17 +979,13 @@ public void setDepthFunc(TestFunction depthFunc) { cachedHashCode = -1; } - /** - * @deprecated - */ - @Deprecated - public void setAlphaFunc(TestFunction alphaFunc) { - } - /** * 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) { @@ -1161,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. */ @@ -1170,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. */ @@ -1179,7 +1251,7 @@ public BlendEquationAlpha getBlendEquationAlpha() { } /** - * Retrieve the blend mode. + * Returns the blend mode. * * @return the blend mode. */ @@ -1188,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. @@ -1198,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. @@ -1208,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. @@ -1218,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. @@ -1230,7 +1302,6 @@ public BlendFunc getCustomDfactorAlpha() { /** * @return true * @deprecated Always returns true since point sprite is always enabled. - * @see #setPointSprite(boolean) */ @Deprecated public boolean isPointSprite() { @@ -1414,7 +1485,7 @@ public boolean isApplyLineWidth() { } /** - * + * @return value for use in hashing */ public int contentHashCode() { if (cachedHashCode == -1){ @@ -1440,6 +1511,10 @@ public int contentHashCode() { hash = 79 * hash + (this.backStencilDepthPassOperation != null ? this.backStencilDepthPassOperation.hashCode() : 0); hash = 79 * hash + (this.frontStencilFunction != null ? this.frontStencilFunction.hashCode() : 0); hash = 79 * hash + (this.backStencilFunction != null ? this.backStencilFunction.hashCode() : 0); + hash = 79 * hash + (this.frontStencilMask); + hash = 79 * hash + (this.frontStencilReference); + hash = 79 * hash + (this.backStencilMask); + hash = 79 * hash + (this.backStencilReference); hash = 79 * hash + Float.floatToIntBits(this.lineWidth); hash = 79 * hash + this.sfactorRGB.hashCode(); @@ -1455,7 +1530,7 @@ public int contentHashCode() { * Merges this state and additionalState into * the parameter state based on a specific criteria. * - *

      The criteria for this merge is the following:
      + *

      The criteria for this merge is the following:
      * For every given property, such as alpha test or depth write, check * if it was modified from the original in the additionalState * if it was modified, then copy the property from the additionalState @@ -1552,6 +1627,11 @@ public RenderState copyMergedTo(RenderState additionalState, RenderState state) state.frontStencilFunction = additionalState.frontStencilFunction; state.backStencilFunction = additionalState.backStencilFunction; + + state.frontStencilMask = additionalState.frontStencilMask; + state.frontStencilReference = additionalState.frontStencilReference; + state.backStencilMask = additionalState.backStencilMask; + state.backStencilReference = additionalState.backStencilReference; } else { state.stencilTest = stencilTest; @@ -1565,6 +1645,11 @@ public RenderState copyMergedTo(RenderState additionalState, RenderState state) state.frontStencilFunction = frontStencilFunction; state.backStencilFunction = backStencilFunction; + + state.frontStencilMask = frontStencilMask; + state.frontStencilReference = frontStencilReference; + state.backStencilMask = backStencilMask; + state.backStencilReference = backStencilReference; } if (additionalState.applyLineWidth) { state.lineWidth = additionalState.lineWidth; @@ -1594,6 +1679,10 @@ public void set(RenderState state) { backStencilDepthPassOperation = state.backStencilDepthPassOperation; frontStencilFunction = state.frontStencilFunction; backStencilFunction = state.backStencilFunction; + frontStencilMask = state.frontStencilMask; + frontStencilReference = state.frontStencilReference; + backStencilMask = state.backStencilMask; + backStencilReference = state.backStencilReference; blendEquationAlpha = state.blendEquationAlpha; blendEquation = state.blendEquation; depthFunc = state.depthFunc; @@ -1614,6 +1703,57 @@ public void set(RenderState state) { sfactorAlpha = state.sfactorAlpha; dfactorAlpha = state.dfactorAlpha; } + + /** + * Copy all values from the given state to this state. + *

      + * This method is more precise than {@link #set(com.jme3.material.RenderState)}. + * @param state state to copy from + */ + public RenderState copyFrom(RenderState state) { + this.applyBlendMode = state.applyBlendMode; + this.applyColorWrite = state.applyColorWrite; + this.applyCullMode = state.applyCullMode; + this.applyDepthFunc = state.applyDepthFunc; + this.applyDepthTest = state.applyDepthTest; + this.applyDepthWrite = state.applyDepthWrite; + this.applyLineWidth = state.applyLineWidth; + this.applyPolyOffset = state.applyPolyOffset; + this.applyStencilTest = state.applyStencilTest; + this.applyWireFrame = state.applyWireFrame; + this.backStencilDepthFailOperation = state.backStencilDepthFailOperation; + this.backStencilDepthPassOperation = state.backStencilDepthPassOperation; + this.backStencilFunction = state.backStencilFunction; + this.backStencilMask = state.backStencilMask; + this.backStencilReference = state.backStencilReference; + this.backStencilStencilFailOperation = state.backStencilStencilFailOperation; + this.blendEquation = state.blendEquation; + this.blendEquationAlpha = state.blendEquationAlpha; + this.blendMode = state.blendMode; + this.cachedHashCode = state.cachedHashCode; + this.colorWrite = state.colorWrite; + this.cullMode = state.cullMode; + this.depthFunc = state.depthFunc; + this.depthTest = state.depthTest; + this.depthWrite = state.depthWrite; + this.dfactorAlpha = state.dfactorAlpha; + this.dfactorRGB = state.dfactorRGB; + this.frontStencilDepthFailOperation = state.frontStencilDepthFailOperation; + this.frontStencilDepthPassOperation = state.frontStencilDepthPassOperation; + this.frontStencilFunction = state.frontStencilFunction; + this.frontStencilMask = state.frontStencilMask; + this.frontStencilReference = state.frontStencilReference; + this.frontStencilStencilFailOperation = state.frontStencilStencilFailOperation; + this.lineWidth = state.lineWidth; + this.offsetEnabled = state.offsetEnabled; + this.offsetFactor = state.offsetFactor; + this.offsetUnits = state.offsetUnits; + this.sfactorAlpha = state.sfactorAlpha; + this.sfactorRGB = state.sfactorRGB; + this.stencilTest = state.stencilTest; + this.wireframe = state.wireframe; + return this; + } @Override public String toString() { @@ -1640,4 +1780,27 @@ public String toString() { + (blendMode.equals(BlendMode.Custom)? "\ncustomBlendFactors=("+sfactorRGB+", "+dfactorRGB+", "+sfactorAlpha+", "+dfactorAlpha+")":"") +"\n]"; } + + /** + * Flips the given face cull mode so that {@code Back} becomes + * {@code Front} and {@code Front} becomes {@code Back}. + *

      {@code FrontAndBack} and {@code Off} are unaffected. This is important + * for flipping the cull mode when normal vectors are found to be backward. + */ + public void flipFaceCull() { + switch (cullMode) { + case Back: cullMode = FaceCullMode.Front; break; + case Front: cullMode = FaceCullMode.Back; break; + } + } + + /** + * Checks if the face cull mode is "flippable". + *

      The cull mode is flippable when it is either {@code Front} or {@code Back}. + * @return + */ + public boolean isFaceCullFlippable() { + return cullMode == FaceCullMode.Front || cullMode == FaceCullMode.Back; + } + } diff --git a/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java b/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java index 3a42ee3281..786173659a 100644 --- a/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java +++ b/jme3-core/src/main/java/com/jme3/material/ShaderGenerationInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ import java.util.List; /** - * this class is basically a struct that contains the ShaderNodes informations + * This class is basically a struct that contains the ShaderNodes information * in an appropriate way to ease the shader generation process and make it * faster. * @@ -53,11 +53,11 @@ public class ShaderGenerationInfo implements Savable, Cloneable { /** * the list of attributes of the vertex shader */ - protected List attributes = new ArrayList(); + protected List attributes = new ArrayList<>(); /** * the list of all the uniforms to declare in the vertex shader */ - protected List vertexUniforms = new ArrayList(); + protected List vertexUniforms = new ArrayList<>(); /** * the global output of the vertex shader (to assign ot gl_Position) */ @@ -65,19 +65,19 @@ public class ShaderGenerationInfo implements Savable, Cloneable { /** * the list of varyings */ - protected List varyings = new ArrayList(); + protected List varyings = new ArrayList<>(); /** * the list of all the uniforms to declare in the fragment shader */ - protected List fragmentUniforms = new ArrayList(); + protected List fragmentUniforms = new ArrayList<>(); /** * the list of all the fragment shader global outputs (to assign ot gl_FragColor or gl_Fragdata[n]) */ - protected List fragmentGlobals = new ArrayList(); + protected List fragmentGlobals = new ArrayList<>(); /** * the unused node names of this shader (node whose output are never used) */ - protected List unusedNodes = new ArrayList(); + protected List unusedNodes = new ArrayList<>(); /** * @@ -105,7 +105,7 @@ public List getFragmentUniforms() { /** * - * @return the vertex shader global ouput + * @return the vertex shader global output */ public ShaderNodeVariable getVertexGlobal() { return vertexGlobal; @@ -138,7 +138,7 @@ public void setVertexGlobal(ShaderNodeVariable vertexGlobal) { /** * - * @return the list on unused node names + * @return the list of unused node names */ public List getUnusedNodes() { return unusedNodes; @@ -146,7 +146,8 @@ public List getUnusedNodes() { /** * the list of unused node names - * @param unusedNodes + * + * @param unusedNodes the new list (alias created) */ public void setUnusedNodes(List unusedNodes) { this.unusedNodes = unusedNodes; @@ -155,7 +156,7 @@ public void setUnusedNodes(List unusedNodes) { /** * convenient toString method * - * @return the informations + * @return the information */ @Override public String toString() { @@ -177,6 +178,7 @@ public void write(JmeExporter ex) throws IOException { } @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); attributes = ic.readSavableArrayList("attributes", new ArrayList()); diff --git a/jme3-core/src/main/java/com/jme3/material/Technique.java b/jme3-core/src/main/java/com/jme3/material/Technique.java index a2fb1a0371..5091028a85 100644 --- a/jme3-core/src/main/java/com/jme3/material/Technique.java +++ b/jme3-core/src/main/java/com/jme3/material/Technique.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.LightList; +import com.jme3.material.Material.BindUnits; import com.jme3.material.TechniqueDef.LightMode; import com.jme3.material.logic.TechniqueDefLogic; import com.jme3.renderer.Caps; @@ -157,13 +158,14 @@ Shader makeCurrent(RenderManager renderManager, SafeArrayList * * @param renderManager The render manager to perform the rendering against. * @param shader The shader that was selected in - * {@link #makeCurrent(com.jme3.renderer.RenderManager, java.util.EnumSet)}. + * {@link #makeCurrent(RenderManager, SafeArrayList, SafeArrayList, LightList, EnumSet)}. * @param geometry The geometry to render * @param lights Lights which influence the geometry. + * @param lastTexUnit the index of the most recently used texture unit */ - void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { TechniqueDefLogic logic = def.getLogic(); - logic.render(renderManager, shader, geometry, lights, lastTexUnit); + logic.render(renderManager, shader, geometry, lights, lastBindUnits); } /** diff --git a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java index 913fe59ce1..6e40ff366e 100644 --- a/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java +++ b/jme3-core/src/main/java/com/jme3/material/TechniqueDef.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -96,7 +96,7 @@ public enum LightMode { *

      * An array of light positions and light colors is passed to the shader * containing the world light list for the geometry being rendered. - * Also Light probes are passed to the shader. + * Light probes are also passed to the shader. */ SinglePassAndImageBased, @@ -123,7 +123,7 @@ public enum ShadowMode { InPass, PostPass, } - + /** * Define in what space the light data should be sent to the shader. */ @@ -136,7 +136,7 @@ public enum LightSpace { private final EnumSet requiredCaps = EnumSet.noneOf(Caps.class); private String name; private int sortId; - + private EnumMap shaderLanguages; private EnumMap shaderNames; @@ -145,7 +145,7 @@ public enum LightSpace { private ArrayList defineTypes; private HashMap paramToDefineId; private final HashMap definesToShaderMap; - + private boolean usesNodes = false; private List shaderNodes; private ShaderGenerationInfo shaderGenerationInfo; @@ -171,8 +171,9 @@ public enum LightSpace { * Used internally by the J3M/J3MD loader. * * @param name The name of the technique + * @param sortId a unique ID for sorting */ - public TechniqueDef(String name, int sortId){ + public TechniqueDef(String name, int sortId) { this(); this.sortId = sortId; this.name = name; @@ -190,9 +191,9 @@ protected TechniqueDef() { definesToShaderMap = new HashMap(); worldBinds = new ArrayList<>(); } - + /** - * @return A unique sort ID. + * @return A unique sort ID. * No other technique definition can have the same ID. */ public int getSortId() { @@ -206,7 +207,7 @@ public int getSortId() { * * @return the name of this technique */ - public String getName(){ + public String getName() { return name; } @@ -229,15 +230,15 @@ public LightMode getLightMode() { public void setLightMode(LightMode lightMode) { this.lightMode = lightMode; //if light space is not specified we set it to Legacy - if(lightSpace == null){ - if(lightMode== LightMode.MultiPass){ + if (lightSpace == null) { + if (lightMode== LightMode.MultiPass) { lightSpace = LightSpace.Legacy; - }else{ + } else { lightSpace = LightSpace.World; } } } - + public void setLogic(TechniqueDefLogic logic) { this.logic = logic; } @@ -245,7 +246,7 @@ public void setLogic(TechniqueDefLogic logic) { public TechniqueDefLogic getLogic() { return logic; } - + /** * Returns the shadow mode. * @return the shadow mode. @@ -298,12 +299,12 @@ public void setNoRender(boolean noRender) { /** * Returns true if this technique should not be used to render. - * (eg. to not render a material with default technique) + * (e.g. to not render a material with default technique) * * @return true if this technique should not be rendered, false otherwise. * */ - public boolean isNoRender(){ + public boolean isNoRender() { return noRender; } @@ -313,7 +314,7 @@ public boolean isNoRender(){ * @return true if this technique uses Shader Nodes, false otherwise. * */ - public boolean isUsingShaderNodes(){ + public boolean isUsingShaderNodes() { return usesNodes; } @@ -352,22 +353,22 @@ public void setShaderFile(String vertexShader, String fragmentShader, String ver /** * Set a string which is prepended to every shader used by this technique. - * + * * Typically this is used for preset defines. - * + * * @param shaderPrologue The prologue to append before the technique's shaders. */ public void setShaderPrologue(String shaderPrologue) { this.shaderPrologue = shaderPrologue; } - + /** * @return the shader prologue which is prepended to every shader. */ public String getShaderPrologue() { return shaderPrologue; } - + /** * Returns the define name which the given material parameter influences. * @@ -376,7 +377,7 @@ public String getShaderPrologue() { * * @see #addShaderParamDefine(java.lang.String, com.jme3.shader.VarType, java.lang.String) */ - public String getShaderParamDefine(String paramName){ + public String getShaderParamDefine(String paramName) { Integer defineId = paramToDefineId.get(paramName); if (defineId != null) { return defineNames.get(defineId); @@ -384,7 +385,7 @@ public String getShaderParamDefine(String paramName){ return null; } } - + /** * Get the define ID for a given material parameter. * @@ -404,28 +405,23 @@ public Integer getShaderParamDefineId(String paramName) { public VarType getDefineIdType(int defineId) { return defineId < defineTypes.size() ? defineTypes.get(defineId) : null; } - + /** * Adds a define linked to a material parameter. *

      * Any time the material parameter on the parent material is altered, * the appropriate define on the technique will be modified as well. - * When set, the material parameter will be mapped to an integer define, + * When set, the material parameter will be mapped to an integer define, * typically 1 if it is set, unless it is an integer or a float, - * in which case it will converted into an integer. + * in which case it will be converted into an integer. * * @param paramName The name of the material parameter to link to. * @param paramType The type of the material parameter to link to. * @param defineName The name of the define parameter, e.g. USE_LIGHTING */ - public void addShaderParamDefine(String paramName, VarType paramType, String defineName){ + public void addShaderParamDefine(String paramName, VarType paramType, String defineName) { int defineId = defineNames.size(); - - if (defineId >= DefineList.MAX_DEFINES) { - throw new IllegalStateException("Cannot have more than " + - DefineList.MAX_DEFINES + " defines on a technique."); - } - + paramToDefineId.put(paramName, defineId); defineNames.add(defineName); defineTypes.add(paramType); @@ -433,21 +429,17 @@ public void addShaderParamDefine(String paramName, VarType paramType, String def /** * Add an unmapped define which can only be set by define ID. - * - * Unmapped defines are used by technique renderers to + * + * Unmapped defines are used by technique renderers to * configure the shader internally before rendering. - * + * * @param defineName The define name to create + * @param defineType the type for the new define * @return The define ID of the created define */ public int addShaderUnmappedDefine(String defineName, VarType defineType) { int defineId = defineNames.size(); - - if (defineId >= DefineList.MAX_DEFINES) { - throw new IllegalStateException("Cannot have more than " + - DefineList.MAX_DEFINES + " defines on a technique."); - } - + defineNames.add(defineName); defineTypes.add(defineType); return defineId; @@ -474,18 +466,18 @@ public String[] getDefineNames() { public VarType[] getDefineTypes() { return defineTypes.toArray(new VarType[0]); } - + /** * Create a define list with the size matching the number * of defines on this technique. - * + * * @return a define list with the size matching the number * of defines on this technique. */ public DefineList createDefineList() { return new DefineList(defineNames.size()); } - + private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { StringBuilder sb = new StringBuilder(); sb.append(shaderPrologue); @@ -517,10 +509,10 @@ private Shader loadShader(AssetManager assetManager, EnumSet rendererCaps, for (final UniformBinding binding : getWorldBindings()) { shader.addUniformBinding(binding); } - + return shader; } - + public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, DefineList defines) { Shader shader = definesToShaderMap.get(defines); if (shader == null) { @@ -529,14 +521,15 @@ public Shader getShader(AssetManager assetManager, EnumSet rendererCaps, D } return shader; } - + /** * Sets the shaders that this technique definition will use. * * @param shaderNames EnumMap containing all shader names for this stage * @param shaderLanguages EnumMap containing all shader languages for this stage */ - public void setShaderFile(EnumMap shaderNames, EnumMap shaderLanguages) { + public void setShaderFile(EnumMap shaderNames, + EnumMap shaderLanguages) { requiredCaps.clear(); weight = 0; @@ -582,6 +575,8 @@ public String getVertexShaderName() { /** * Returns the language of the fragment shader used in this technique. + * + * @return the name of the language (such as "GLSL100") */ public String getFragmentShaderLanguage() { return shaderLanguages.get(Shader.ShaderType.Fragment); @@ -589,21 +584,25 @@ public String getFragmentShaderLanguage() { /** * Returns the language of the vertex shader used in this technique. + * + * @return the name of the language (such as "GLSL100") */ public String getVertexShaderLanguage() { return shaderLanguages.get(Shader.ShaderType.Vertex); } /**Returns the language for each shader program - * @param shaderType + * @param shaderType Fragment/Vertex/etcetera + * @return the name of the language */ - public String getShaderProgramLanguage(Shader.ShaderType shaderType){ + public String getShaderProgramLanguage(Shader.ShaderType shaderType) { return shaderLanguages.get(shaderType); } /**Returns the name for each shader program - * @param shaderType + * @param shaderType Fragment/Vertex/etcetera + * @return the name of the program */ - public String getShaderProgramName(Shader.ShaderType shaderType){ + public String getShaderProgramName(Shader.ShaderType shaderType) { return shaderNames.get(shaderType); } @@ -650,7 +649,8 @@ public List getWorldBindings() { return worldBinds; } - public void write(JmeExporter ex) throws IOException{ + @Override + public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", null); @@ -680,7 +680,9 @@ public void write(JmeExporter ex) throws IOException{ // oc.write(worldBinds, "worldBinds", null); } - public void read(JmeImporter im) throws IOException{ + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", null); shaderNames.put(Shader.ShaderType.Vertex,ic.readString("vertName", null)); @@ -812,7 +814,8 @@ public TechniqueDef clone() throws CloneNotSupportedException { try { clone.logic = logic.getClass().getConstructor(TechniqueDef.class).newInstance(clone); - } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { + } catch (InstantiationException | IllegalAccessException + | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } @@ -821,4 +824,4 @@ public TechniqueDef clone() throws CloneNotSupportedException { return clone; } -} +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java index 86ce66391f..ca8f7d1efa 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/DefaultTechniqueDefLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.*; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; @@ -64,8 +65,10 @@ public static void renderMeshFromGeometry(Renderer renderer, Geometry geom) { int lodLevel = geom.getLodLevel(); if (geom instanceof InstancedGeometry) { InstancedGeometry instGeom = (InstancedGeometry) geom; - renderer.renderMesh(mesh, lodLevel, instGeom.getActualNumInstances(), - instGeom.getAllInstanceData()); + int numVisibleInstances = instGeom.getNumVisibleInstances(); + if (numVisibleInstances > 0) { + renderer.renderMesh(mesh, lodLevel, numVisibleInstances, instGeom.getAllInstanceData()); + } } else { renderer.renderMesh(mesh, lodLevel, 1, null); } @@ -89,7 +92,7 @@ protected static ColorRGBA getAmbientColor(LightList lightList, boolean removeLi @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer renderer = renderManager.getRenderer(); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java index 8d4b639567..9340d3560d 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/MultiPassLightingLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,7 @@ import com.jme3.light.SpotLight; import com.jme3.material.RenderState; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; @@ -67,7 +68,7 @@ public MultiPassLightingLogic(TechniqueDef techniqueDef) { } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer r = renderManager.getRenderer(); Uniform lightDir = shader.getUniform("g_LightDirection"); Uniform lightColor = shader.getUniform("g_LightColor"); @@ -111,7 +112,7 @@ public void render(RenderManager renderManager, Shader shader, Geometry geometry case Directional: DirectionalLight dl = (DirectionalLight) l; Vector3f dir = dl.getDirection(); - //FIXME : there is an inconstency here due to backward + //FIXME : there is an inconsistency here due to backward //compatibility of the lighting shader. //The directional light direction is passed in the //LightPosition uniform. The lighting shader needs to be diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java index 513c35d6d9..8e38f2e6ca 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassAndImageBasedLightingLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,11 +34,13 @@ import com.jme3.asset.AssetManager; import com.jme3.light.*; import com.jme3.material.*; +import com.jme3.material.Material.BindUnits; import com.jme3.material.RenderState.BlendMode; import com.jme3.math.*; import com.jme3.renderer.*; import com.jme3.scene.Geometry; import com.jme3.shader.*; +import com.jme3.texture.TextureCubeMap; import com.jme3.util.TempVars; import java.util.*; @@ -53,7 +55,7 @@ public final class SinglePassAndImageBasedLightingLogic extends DefaultTechnique private boolean useAmbientLight; private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); - private List lightProbes = new ArrayList<>(3); + private final List lightProbes = new ArrayList<>(3); static { ADDITIVE_LIGHT.setBlendMode(BlendMode.AlphaAdditive); @@ -94,17 +96,25 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager } /** - * Uploads the lights in the light list as two uniform arrays.

      * + * Uploads the lights in the light list as two uniform arrays. *

      - * uniform vec4 g_LightColor[numLights];
      // - * g_LightColor.rgb is the diffuse/specular color of the light.
      // - * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
      // - * 2 = Spot.

      - * uniform vec4 g_LightPosition[numLights];
      // - * g_LightPosition.xyz is the position of the light (for point lights)
      - * // or the direction of the light (for directional lights).
      // + * uniform vec4 g_LightColor[numLights]; // + * g_LightColor.rgb is the diffuse/specular color of the light. // + * g_LightColor.a is the type of light, 0 = Directional, 1 = Point, 2 = Spot. + * uniform vec4 g_LightPosition[numLights]; // + * g_LightPosition.xyz is the position of the light (for point lights) + * // or the direction of the light (for directional lights). // * g_LightPosition.w is the inverse radius (1/r) of the light (for - * attenuation)

      + * attenuation)

      + * + * @param shader the Shader being used + * @param g the Geometry being rendered + * @param lightList the list of lights + * @param numLights the number of lights to upload + * @param rm to manage rendering + * @param startIndex the starting index in the LightList + * @param lastTexUnit the index of the most recently-used texture unit + * @return the next starting index in the LightList */ protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex, int lastTexUnit) { if (numLights == 0) { // this shader does not do lighting, ignore. @@ -132,7 +142,7 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light // apply additive blending for 2nd and future passes rm.getRenderer().applyRenderState(ADDITIVE_LIGHT); ambientColor.setValue(VarType.Vector4, ColorRGBA.Black); - } else{ + } else { extractIndirectLights(lightList,true); ambientColor.setValue(VarType.Vector4, ambientLightColor); } @@ -160,8 +170,6 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light int curIndex; int endIndex = numLights + startIndex; for (curIndex = startIndex; curIndex < endIndex && curIndex < lightList.size(); curIndex++) { - - Light l = lightList.get(curIndex); if(l.getType() == Light.Type.Ambient){ endIndex++; @@ -224,43 +232,52 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light } vars.release(); - //Padding of unsued buffer space - while(lightDataIndex < numLights * 3) { + // pad unused buffer space + while (lightDataIndex < numLights * 3) { lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); lightDataIndex++; } return curIndex; } - private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, Uniform lightProbePemMap, LightProbe lightProbe) { - + private int setProbeData(RenderManager rm, int lastTexUnit, Uniform lightProbeData, Uniform shCoeffs, + Uniform lightProbePemMap, LightProbe lightProbe) { lightProbeData.setValue(VarType.Matrix4, lightProbe.getUniformMatrix()); - //setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0); + //setVector4InArray(lightProbe.getPosition().x, lightProbe.getPosition().y, lightProbe.getPosition().z, 1f / area.getRadius() + lightProbe.getNbMipMaps(), 0); shCoeffs.setValue(VarType.Vector3Array, lightProbe.getShCoeffs()); - //assigning new texture indexes + /* + * Assign the prefiltered env map to the next available texture unit. + */ int pemUnit = lastTexUnit++; - rm.getRenderer().setTexture(pemUnit, lightProbe.getPrefilteredEnvMap()); + Renderer renderer = rm.getRenderer(); + TextureCubeMap pemTexture = lightProbe.getPrefilteredEnvMap(); + try { + renderer.setTexture(pemUnit, pemTexture); + } catch (TextureUnitException exception) { + String message = "Can't assign texture unit for LightProbe." + + " lastTexUnit=" + lastTexUnit; + throw new IllegalArgumentException(message); + } lightProbePemMap.setValue(VarType.Int, pemUnit); return lastTexUnit; } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { int nbRenderedLights = 0; Renderer renderer = renderManager.getRenderer(); int batchSize = renderManager.getSinglePassLightBatchSize(); if (lights.size() == 0) { - updateLightListUniforms(shader, geometry, lights,batchSize, renderManager, 0, lastTexUnit); + updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, 0, lastBindUnits.textureUnit); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); } else { while (nbRenderedLights < lights.size()) { - nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastTexUnit); + nbRenderedLights = updateLightListUniforms(shader, geometry, lights, batchSize, renderManager, nbRenderedLights, lastBindUnits.textureUnit); renderer.setShader(shader); renderMeshFromGeometry(renderer, geometry); } } - return; } protected void extractIndirectLights(LightList lightList, boolean removeLights) { diff --git a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java index 67261742ec..58240569ef 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/SinglePassLightingLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import com.jme3.material.RenderState; import com.jme3.material.RenderState.BlendMode; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; import com.jme3.math.Vector4f; @@ -85,17 +86,25 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager } /** - * Uploads the lights in the light list as two uniform arrays.

      * + * Uploads the lights in the light list as two uniform arrays. *

      - * uniform vec4 g_LightColor[numLights];
      // - * g_LightColor.rgb is the diffuse/specular color of the light.
      // - * g_Lightcolor.a is the type of light, 0 = Directional, 1 = Point,
      // - * 2 = Spot.

      - * uniform vec4 g_LightPosition[numLights];
      // - * g_LightPosition.xyz is the position of the light (for point lights)
      - * // or the direction of the light (for directional lights).
      // + * uniform vec4 g_LightColor[numLights];
      // + * g_LightColor.rgb is the diffuse/specular color of the light.
      // + * g_LightColor.a is the type of light, 0 = Directional, 1 = Point,
      // + * 2 = Spot.

      + * uniform vec4 g_LightPosition[numLights];
      // + * g_LightPosition.xyz is the position of the light (for point lights)
      + * // or the direction of the light (for directional lights).
      // * g_LightPosition.w is the inverse radius (1/r) of the light (for - * attenuation)

      + * attenuation)

      + * + * @param shader the Shader being used + * @param g the Geometry being rendered + * @param lightList the list of lights + * @param numLights the number of lights to upload + * @param rm to manage rendering + * @param startIndex the starting index in the LightList + * @return the next starting index in the LightList */ protected int updateLightListUniforms(Shader shader, Geometry g, LightList lightList, int numLights, RenderManager rm, int startIndex) { if (numLights == 0) { // this shader does not do lighting, ignore. @@ -189,8 +198,8 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light } } vars.release(); - //Padding of unsued buffer space - while(lightDataIndex < numLights * 3) { + // pad unused buffer space + while (lightDataIndex < numLights * 3) { lightData.setVector4InArray(0f, 0f, 0f, 0f, lightDataIndex); lightDataIndex++; } @@ -198,7 +207,7 @@ protected int updateLightListUniforms(Shader shader, Geometry g, LightList light } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { int nbRenderedLights = 0; Renderer renderer = renderManager.getRenderer(); int batchSize = renderManager.getSinglePassLightBatchSize(); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java index 48995ba4f5..fe35dc4c4d 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/StaticPassLightingLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,7 @@ import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.material.TechniqueDef; +import com.jme3.material.Material.BindUnits; import com.jme3.math.ColorRGBA; import com.jme3.math.Matrix4f; import com.jme3.math.Vector3f; @@ -67,9 +68,9 @@ public final class StaticPassLightingLogic extends DefaultTechniqueDefLogic { private final int numPointLightsDefineId; private final int numSpotLightsDefineId; - private final ArrayList tempDirLights = new ArrayList(); - private final ArrayList tempPointLights = new ArrayList(); - private final ArrayList tempSpotLights = new ArrayList(); + private final ArrayList tempDirLights = new ArrayList<>(); + private final ArrayList tempPointLights = new ArrayList<>(); + private final ArrayList tempSpotLights = new ArrayList<>(); private final ColorRGBA ambientLightColor = new ColorRGBA(0, 0, 0, 1); private final Vector3f tempPosition = new Vector3f(); @@ -171,7 +172,7 @@ private void updateLightListUniforms(Matrix4f viewMatrix, Shader shader, LightLi } @Override - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit) { + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits) { Renderer renderer = renderManager.getRenderer(); Matrix4f viewMatrix = renderManager.getCurrentCamera().getViewMatrix(); updateLightListUniforms(viewMatrix, shader, lights); diff --git a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java index e0921e869d..31f970a176 100644 --- a/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java +++ b/jme3-core/src/main/java/com/jme3/material/logic/TechniqueDefLogic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ import com.jme3.asset.AssetManager; import com.jme3.light.LightList; +import com.jme3.material.Material.BindUnits; import com.jme3.renderer.Caps; import com.jme3.renderer.RenderManager; import com.jme3.scene.Geometry; @@ -60,7 +61,7 @@ public interface TechniqueDefLogic { * Determine the shader to use for the given geometry / material combination. * * @param assetManager The asset manager to use for loading shader source code, - * shader nodes, and and lookup textures. + * shader nodes, and lookup textures. * @param renderManager The render manager for which rendering is to be performed. * @param rendererCaps Renderer capabilities. The returned shader must * support these capabilities. @@ -90,6 +91,7 @@ public Shader makeCurrent(AssetManager assetManager, RenderManager renderManager * {@link #makeCurrent(com.jme3.asset.AssetManager, com.jme3.renderer.RenderManager, java.util.EnumSet, com.jme3.light.LightList, com.jme3.shader.DefineList)}. * @param geometry The geometry to render * @param lights Lights which influence the geometry. + * @param lastTexUnit the index of the most recently used texture unit */ - public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, int lastTexUnit); + public void render(RenderManager renderManager, Shader shader, Geometry geometry, LightList lights, BindUnits lastBindUnits); } diff --git a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java index 5bd6442b6a..df1ef1aa01 100644 --- a/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java +++ b/jme3-core/src/main/java/com/jme3/math/AbstractTriangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,15 +34,65 @@ import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; +/** + * A Collidable with a triangular shape. + */ public abstract class AbstractTriangle implements Collidable { - + /** + * Determine the location of the first vertex. + * + * @return a location vector + */ public abstract Vector3f get1(); + + /** + * Determine the location of the 2nd vertex. + * + * @return a location vector + */ public abstract Vector3f get2(); + + /** + * Determine the location of the 3rd vertex. + * + * @return a location vector + */ public abstract Vector3f get3(); + + /** + * Alter all 3 vertex locations. + * + * @param v1 the location for the first vertex + * @param v2 the location for the 2nd vertex + * @param v3 the location for the 3rd vertex + */ public abstract void set(Vector3f v1, Vector3f v2, Vector3f v3); - public int collideWith(Collidable other, CollisionResults results){ + /** + * Generate collision results for this triangle with another Collidable. + * + * @param other the other Collidable + * @param results storage for collision results + * @return the number of collisions found + */ + @Override + public int collideWith(Collidable other, CollisionResults results) { return other.collideWith(this, results); } + /** + * Returns a string representation of the triangle, which is unaffected. For + * example, a {@link com.jme3.math.Triangle} joining (1,0,0) and (0,1,0) + * with (0,0,1) is represented by: + *
      +     * Triangle [V1: (1.0, 0.0, 0.0)  V2: (0.0, 1.0, 0.0)  V3: (0.0, 0.0, 1.0)]
      +     * 
      + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [V1: " + get1() + " V2: " + + get2() + " V3: " + get3() + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java index f403bbaa6c..a70a01bd21 100644 --- a/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java +++ b/jme3-core/src/main/java/com/jme3/math/ColorRGBA.java @@ -1,9 +1,10 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine All rights reserved. + * Copyright (c) 2009-2025 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. * @@ -29,19 +30,23 @@ */ package com.jme3.math; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; + import java.io.IOException; /** * ColorRGBA defines a color made from a collection of red, green - * and blue values stored in Linear color space. An alpha value determines is - * transparency. + * and blue values stored in Linear color space. An alpha value determines its + * transparency. * * @author Mark Powell - * @version $Id: ColorRGBA.java,v 1.29 2007/09/09 18:25:14 irrisor Exp $ */ public final class ColorRGBA implements Savable, Cloneable, java.io.Serializable { - + static final float GAMMA = 2.2f; static final long serialVersionUID = 1; @@ -132,10 +137,11 @@ public ColorRGBA() { /** * Constructor instantiates a new ColorRGBA object. The - * values are defined as passed parameters. + * values are defined as passed parameters. * these values are assumed to be in linear space and stored as is. - * If you want to assign sRGB values use + * If you want to assign sRGB values use * {@link ColorRGBA#setAsSrgb(float, float, float, float) } + * * @param r The red component of this color. * @param g The green component of this ColorRGBA. * @param b The blue component of this ColorRGBA. @@ -151,21 +157,45 @@ public ColorRGBA(float r, float g, float b, float a) { /** * Copy constructor creates a new ColorRGBA object, based on * a provided color. - * @param rgba The ColorRGBA object to copy. + * + * @param color The ColorRGBA object to copy. + */ + public ColorRGBA(ColorRGBA color) { + this.a = color.a; + this.r = color.r; + this.g = color.g; + this.b = color.b; + } + + /** + * Constructor creates a new ColorRGBA object, based on + * a provided Vector4f. + * + * @param vec4 The Vector4f object that will have its x, y, z, and w + * values copied to this color's r, g, b, and a values respectively. */ - public ColorRGBA(ColorRGBA rgba) { - this.a = rgba.a; - this.r = rgba.r; - this.g = rgba.g; - this.b = rgba.b; + public ColorRGBA(Vector4f vec4) { + set(vec4); } /** - * set sets the RGBA values of this ColorRGBA. + * Constructor creates a new ColorRGBA object, based on + * a provided Vector3f, at full opacity with a 1.0 alpha value by default + * + * @param vec3 The Vector3f object that will have its x, y, and z + * values copied to this color's r, g, and b values respectively. + */ + public ColorRGBA(Vector3f vec3) { + this.a = 1.0f; + set(vec3); + } + + /** + * set sets the RGBA values of this ColorRGBA. * these values are assumed to be in linear space and stored as is. - * If you want to assign sRGB values use + * If you want to assign sRGB values use * {@link ColorRGBA#setAsSrgb(float, float, float, float) } - * + * * @param r The red component of this color. * @param g The green component of this color. * @param b The blue component of this color. @@ -181,27 +211,113 @@ public ColorRGBA set(float r, float g, float b, float a) { } /** - * set sets the values of this ColorRGBA to those + * set sets the values of this ColorRGBA to those * set by a parameter color. * - * @param rgba The color to set this ColorRGBA to. + * @param color The color to set this ColorRGBA to. * @return this */ - public ColorRGBA set(ColorRGBA rgba) { - if (rgba == null) { + public ColorRGBA set(ColorRGBA color) { + if (color == null) { r = 0; g = 0; b = 0; a = 0; } else { - r = rgba.r; - g = rgba.g; - b = rgba.b; - a = rgba.a; + r = color.r; + g = color.g; + b = color.b; + a = color.a; + } + return this; + } + + /** + * set sets the values of this ColorRGBA to those + * set by a parameter Vector4f. + * + * @param vec4 The 4 component vector that will have its x, y, z, and w values copied to + * this ColorRGBA's r, g, b, and a values respectively. + * + * @return this + */ + public ColorRGBA set(Vector4f vec4) { + if (vec4 == null) { + r = 0; + g = 0; + b = 0; + a = 0; + } else { + r = vec4.x; + g = vec4.y; + b = vec4.z; + a = vec4.w; + } + return this; + } + + /** + * set sets the values of this ColorRGBA to those + * set by a parameter Vector3f. + * + * @param vec3 The 3 component vector that will have its x, y, and z values copied to + * this ColorRGBA's r, g, and b values respectively. + * + * @return this + */ + public ColorRGBA set(Vector3f vec3) { + if (vec3 == null) { + r = 0; + g = 0; + b = 0; + } else { + r = vec3.x; + g = vec3.y; + b = vec3.z; } return this; } + /** + * Sets the red color to the specified value. + * @param value the value to set the red channel. + * @return the ColorRGBA object with the modified value. + */ + public ColorRGBA setRed(float value) { + r = value; + return this; + } + + /** + * Sets the green color to the specified value. + * @param value the value to set the green channel. + * @return the ColorRGBA object with the modified value. + */ + public ColorRGBA setGreen(float value) { + g = value; + return this; + } + + /** + * Sets the blue color to the specified value. + * @param value the value to set the blue channel. + * @return the ColorRGBA object with the modified value. + */ + public ColorRGBA setBlue(float value) { + b = value; + return this; + } + + /** + * Sets the alpha color to the specified value. + * @param value the value to set the alpha channel. + * @return the ColorRGBA object with the modified value. + */ + public ColorRGBA setAlpha(float value) { + a = value; + return this; + } + /** * Saturate that color ensuring all channels have a value between 0 and 1 */ @@ -213,31 +329,30 @@ public void clamp() { } /** - * getColorArray retrieves the color values of this - * ColorRGBA as a four element float array in the + * getColorArray retrieves the color values of this + * ColorRGBA as a four element float array in the * order: r,g,b,a. + * * @return The float array that contains the color components. */ public float[] getColorArray() { - return new float[]{r, g, b, a}; + return getColorArray(null); } /** * Stores the current r,g,b,a values into the given array. The given array must have a * length of 4 or greater, or an array index out of bounds exception will be thrown. - * @param store The float array to store the values into. + * + * @param store The float array to store the values into. If null, a new array is created. * @return The float array after storage. */ public float[] getColorArray(float[] store) { - store[0] = r; - store[1] = g; - store[2] = b; - store[3] = a; - return store; + return toArray(store); } /** * Retrieves the alpha component value of this ColorRGBA. + * * @return The alpha component value. */ public float getAlpha() { @@ -246,6 +361,7 @@ public float getAlpha() { /** * Retrieves the red component value of this ColorRGBA. + * * @return The red component value. */ public float getRed() { @@ -254,6 +370,7 @@ public float getRed() { /** * Retrieves the blue component value of this ColorRGBA. + * * @return The blue component value. */ public float getBlue() { @@ -262,6 +379,7 @@ public float getBlue() { /** * Retrieves the green component value of this ColorRGBA. + * * @return The green component value. */ public float getGreen() { @@ -269,57 +387,57 @@ public float getGreen() { } /** - * Sets this ColorRGBA to the interpolation by changeAmnt from + * Sets this ColorRGBA to the interpolation by changeAmount from * this to the finalColor: - * this=(1-changeAmnt)*this + changeAmnt * finalColor + * this=(1-changeAmount)*this + changeAmount * finalColor + * * @param finalColor The final color to interpolate towards. - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage - * change from this towards finalColor. + * @param changeAmount An amount between 0.0 - 1.0 representing a percentage + * change from this towards finalColor. * @return this ColorRGBA */ - public ColorRGBA interpolateLocal(ColorRGBA finalColor, float changeAmnt) { - this.r = (1 - changeAmnt) * this.r + changeAmnt * finalColor.r; - this.g = (1 - changeAmnt) * this.g + changeAmnt * finalColor.g; - this.b = (1 - changeAmnt) * this.b + changeAmnt * finalColor.b; - this.a = (1 - changeAmnt) * this.a + changeAmnt * finalColor.a; - return this; + public ColorRGBA interpolateLocal(ColorRGBA finalColor, float changeAmount) { + return interpolateLocal(this, finalColor, changeAmount); } /** - * Sets this ColorRGBA to the interpolation by changeAmnt from + * Sets this ColorRGBA to the interpolation by changeAmount from * beginColor to finalColor: - * this=(1-changeAmnt)*beginColor + changeAmnt * finalColor - * @param beginColor The beginning color (changeAmnt=0). - * @param finalColor The final color to interpolate towards (changeAmnt=1). - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage - * change from beginColor towards finalColor. + * this=(1-changeAmount)*beginColor + changeAmount * finalColor + * + * @param beginColor The beginning color (changeAmount=0). + * @param finalColor The final color to interpolate towards (changeAmount=1). + * @param changeAmount An amount between 0.0 - 1.0 representing a percentage + * change from beginColor towards finalColor. * @return this ColorRGBA */ - public ColorRGBA interpolateLocal(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmnt) { - this.r = (1 - changeAmnt) * beginColor.r + changeAmnt * finalColor.r; - this.g = (1 - changeAmnt) * beginColor.g + changeAmnt * finalColor.g; - this.b = (1 - changeAmnt) * beginColor.b + changeAmnt * finalColor.b; - this.a = (1 - changeAmnt) * beginColor.a + changeAmnt * finalColor.a; + public ColorRGBA interpolateLocal(ColorRGBA beginColor, ColorRGBA finalColor, float changeAmount) { + this.r = (1 - changeAmount) * beginColor.r + changeAmount * finalColor.r; + this.g = (1 - changeAmount) * beginColor.g + changeAmount * finalColor.g; + this.b = (1 - changeAmount) * beginColor.b + changeAmount * finalColor.b; + this.a = (1 - changeAmount) * beginColor.a + changeAmount * finalColor.a; return this; } /** * randomColor is a utility method that generates a random * opaque color. + * * @return a random ColorRGBA with an alpha set to 1. */ public static ColorRGBA randomColor() { - ColorRGBA rVal = new ColorRGBA(0, 0, 0, 1); - rVal.r = FastMath.nextRandomFloat(); - rVal.g = FastMath.nextRandomFloat(); - rVal.b = FastMath.nextRandomFloat(); - return rVal; + float r = FastMath.nextRandomFloat(); + float g = FastMath.nextRandomFloat(); + float b = FastMath.nextRandomFloat(); + float a = 1.0f; + return new ColorRGBA(r, g, b, a); } /** - * Multiplies each r,g,b,a of this ColorRGBA by the corresponding - * r,g,b,a of the given color and returns the result as a new ColorRGBA. + * Multiplies each r,g,b,a of this ColorRGBA by the corresponding + * r,g,b,a of the given color and returns the result as a new ColorRGBA. * Used as a way of combining colors and lights. + * * @param c The color to multiply by. * @return The new ColorRGBA. this*c */ @@ -329,8 +447,9 @@ public ColorRGBA mult(ColorRGBA c) { /** * Multiplies each r,g,b,a of this ColorRGBA by the given scalar and - * returns the result as a new ColorRGBA. + * returns the result as a new ColorRGBA. * Used as a way of making colors dimmer or brighter. + * * @param scalar The scalar to multiply by. * @return The new ColorRGBA. this*scalar */ @@ -340,8 +459,9 @@ public ColorRGBA mult(float scalar) { /** * Multiplies each r,g,b,a of this ColorRGBA by the given scalar and - * returns the result (this). + * returns the result (this). * Used as a way of making colors dimmer or brighter. + * * @param scalar The scalar to multiply by. * @return this*c */ @@ -354,9 +474,10 @@ public ColorRGBA multLocal(float scalar) { } /** - * Adds each r,g,b,a of this ColorRGBA by the corresponding + * Adds each r,g,b,a of this ColorRGBA by the corresponding * r,g,b,a of the given color and returns the result as a new ColorRGBA. * Used as a way of combining colors and lights. + * * @param c The color to add. * @return The new ColorRGBA. this+c */ @@ -365,9 +486,10 @@ public ColorRGBA add(ColorRGBA c) { } /** - * Adds each r,g,b,a of this ColorRGBA by the r,g,b,a the given - * color and returns the result (this). + * Adds each component to the corresponding component of the argument + * and returns the result (this). * Used as a way of combining colors and lights. + * * @param c The color to add. * @return this+c */ @@ -379,7 +501,8 @@ public ColorRGBA addLocal(ColorRGBA c) { /** * toString returns the string representation of this ColorRGBA. * The format of the string is:
      - * : [R=RR.RRRR, G=GG.GGGG, B=BB.BBBB, A=AA.AAAA] + * Color[R.RRRR, G.GGGG, B.BBBB, A.AAAA] + * * @return The string representation of this ColorRGBA. */ @Override @@ -387,6 +510,11 @@ public String toString() { return "Color[" + r + ", " + g + ", " + b + ", " + a + "]"; } + /** + * Create a copy of this color. + * + * @return a new instance, equivalent to this one + */ @Override public ColorRGBA clone() { try { @@ -398,25 +526,27 @@ public ColorRGBA clone() { /** * Saves this ColorRGBA into the given float array. - * @param floats The float array to take this ColorRGBA. + * + * @param store The float array to take this ColorRGBA. * If null, a new float[4] is created. * @return The array, with r,g,b,a float values in that order. */ - public float[] toArray(float[] floats) { - if (floats == null) { - floats = new float[4]; + public float[] toArray(float[] store) { + if (store == null) { + store = new float[4]; } - floats[0] = r; - floats[1] = g; - floats[2] = b; - floats[3] = a; - return floats; + store[0] = r; + store[1] = g; + store[2] = b; + store[3] = a; + return store; } /** * equals returns true if this ColorRGBA is logically equivalent * to a given color. That is, if all the components of the two colors are the same. * False is returned otherwise. + * * @param o The object to compare against. * @return true if the colors are equal, false otherwise. */ @@ -450,6 +580,7 @@ public boolean equals(Object o) { * hashCode returns a unique code for this ColorRGBA based * on its values. If two colors are logically equivalent, they will return * the same hash code value. + * * @return The hash code value of this ColorRGBA. */ @Override @@ -461,121 +592,150 @@ public int hashCode() { hash += 37 * hash + Float.floatToIntBits(a); return hash; } - - public void write(JmeExporter e) throws IOException { - OutputCapsule capsule = e.getCapsule(this); - capsule.write(r, "r", 0); - capsule.write(g, "g", 0); - capsule.write(b, "b", 0); - capsule.write(a, "a", 0); + + /** + * Serialize this color to the specified exporter, for example when + * saving to a J3O file. + * + * @param ex (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(r, "r", 0); + oc.write(g, "g", 0); + oc.write(b, "b", 0); + oc.write(a, "a", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - r = capsule.readFloat("r", 0); - g = capsule.readFloat("g", 0); - b = capsule.readFloat("b", 0); - a = capsule.readFloat("a", 0); + /** + * De-serialize this color from the specified importer, for example when + * loading from a J3O file. + * + * @param im (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + r = ic.readFloat("r", 0); + g = ic.readFloat("g", 0); + b = ic.readFloat("b", 0); + a = ic.readFloat("a", 0); } + /** * Retrieves the component values of this ColorRGBA as * a four element byte array in the order: r,g,b,a. + * * @return the byte array that contains the color components. */ public byte[] asBytesRGBA() { byte[] store = new byte[4]; - store[0] = (byte) ((int) (r * 255) & 0xFF); - store[1] = (byte) ((int) (g * 255) & 0xFF); - store[2] = (byte) ((int) (b * 255) & 0xFF); - store[3] = (byte) ((int) (a * 255) & 0xFF); + store[0] = toByte(r); + store[1] = toByte(g); + store[2] = toByte(b); + store[3] = toByte(a); return store; } /** - * Retrieves the component values of this ColorRGBA as an - * int in a,r,g,b order. + * Retrieves the component values of this ColorRGBA as an + * int in a,r,g,b order. * Bits 24-31 are alpha, 16-23 are red, 8-15 are green, 0-7 are blue. + * * @return The integer representation of this ColorRGBA in a,r,g,b order. */ public int asIntARGB() { - int argb = (((int) (a * 255) & 0xFF) << 24) - | (((int) (r * 255) & 0xFF) << 16) - | (((int) (g * 255) & 0xFF) << 8) - | (((int) (b * 255) & 0xFF)); - return argb; + return toInt(a, r, g, b); } /** - * Retrieves the component values of this ColorRGBA as an + * Retrieves the component values of this ColorRGBA as an * int in r,g,b,a order. * Bits 24-31 are red, 16-23 are green, 8-15 are blue, 0-7 are alpha. + * * @return The integer representation of this ColorRGBA in r,g,b,a order. */ public int asIntRGBA() { - int rgba = (((int) (r * 255) & 0xFF) << 24) - | (((int) (g * 255) & 0xFF) << 16) - | (((int) (b * 255) & 0xFF) << 8) - | (((int) (a * 255) & 0xFF)); - return rgba; + return toInt(r, g, b, a); } + /** - * Retrieves the component values of this ColorRGBA as an + * Retrieves the component values of this ColorRGBA as an * int in a,b,g,r order. * Bits 24-31 are alpha, 16-23 are blue, 8-15 are green, 0-7 are red. + * * @return The integer representation of this ColorRGBA in a,b,g,r order. */ public int asIntABGR() { - int abgr = (((int) (a * 255) & 0xFF) << 24) - | (((int) (b * 255) & 0xFF) << 16) - | (((int) (g * 255) & 0xFF) << 8) - | (((int) (r * 255) & 0xFF)); - return abgr; + return toInt(a, b, g, r); } + /** - * Sets the component values of this ColorRGBA with the given + * Sets the component values of this ColorRGBA with the given * combined ARGB int. * Bits 24-31 are alpha, bits 16-23 are red, bits 8-15 are green, bits 0-7 are blue. + * * @param color The integer ARGB value used to set this ColorRGBA. * @return this */ public ColorRGBA fromIntARGB(int color) { - a = ((byte) (color >> 24) & 0xFF) / 255f; - r = ((byte) (color >> 16) & 0xFF) / 255f; - g = ((byte) (color >> 8) & 0xFF) / 255f; - b = ((byte) (color) & 0xFF) / 255f; + a = fromByte(color >> 24); + r = fromByte(color >> 16); + g = fromByte(color >> 8); + b = fromByte(color); return this; } + /** - * Sets the RGBA values of this ColorRGBA with the given combined RGBA value + * Sets the RGBA values of this ColorRGBA with the given combined RGBA value * Bits 24-31 are red, bits 16-23 are green, bits 8-15 are blue, bits 0-7 are alpha. + * * @param color The integer RGBA value used to set this object. * @return this */ public ColorRGBA fromIntRGBA(int color) { - r = ((byte) (color >> 24) & 0xFF) / 255f; - g = ((byte) (color >> 16) & 0xFF) / 255f; - b = ((byte) (color >> 8) & 0xFF) / 255f; - a = ((byte) (color) & 0xFF) / 255f; + r = fromByte(color >> 24); + g = fromByte(color >> 16); + b = fromByte(color >> 8); + a = fromByte(color); return this; } + /** - * Sets the RGBA values of this ColorRGBA with the given combined ABGR value + * Sets the RGBA values of this ColorRGBA with the given combined ABGR value * Bits 24-31 are alpha, bits 16-23 are blue, bits 8-15 are green, bits 0-7 are red. + * * @param color The integer ABGR value used to set this object. * @return this */ public ColorRGBA fromIntABGR(int color) { - a = ((byte) (color >> 24) & 0xFF) / 255f; - b = ((byte) (color >> 16) & 0xFF) / 255f; - g = ((byte) (color >> 8) & 0xFF) / 255f; - r = ((byte) (color) & 0xFF) / 255f; + a = fromByte(color >> 24); + b = fromByte(color >> 16); + g = fromByte(color >> 8); + r = fromByte(color); return this; } + /** + * Converts a color from RGBA 255 values. + * @param r the red value in 0-255 range. + * @param g the green value in 0-255 range. + * @param b the blue value in 0-255 range. + * @param a the alpha value in 0-255 range. + * @return the ColorRGBA equivalent of the RGBA 255 color. + */ + public static ColorRGBA fromRGBA255(int r, int g, int b, int a) { + return new ColorRGBA(r / 255.0F, g / 255.0F, b / 255.0F, a / 255.0F); + } + /** * Transform this ColorRGBA to a Vector3f using * x = r, y = g, z = b. The Alpha value is not used. * This method is useful for shader assignments. + * * @return A Vector3f containing the RGB value of this ColorRGBA. */ public Vector3f toVector3f() { @@ -586,63 +746,88 @@ public Vector3f toVector3f() { * Transform this ColorRGBA to a Vector4f using * x = r, y = g, z = b, w = a. * This method is useful for shader assignments. + * * @return A Vector4f containing the RGBA value of this ColorRGBA. */ public Vector4f toVector4f() { return new Vector4f(r, g, b, a); } - + /** * Sets the rgba channels of this color in sRGB color space. * You probably want to use this method if the color is picked by the use * in a color picker from a GUI. - * + * * Note that the values will be gamma corrected to be stored in linear space * GAMMA value is 2.2 - * + * * Note that no correction will be performed on the alpha channel as it * conventionally doesn't represent a color itself - * + * * @param r the red value in sRGB color space * @param g the green value in sRGB color space * @param b the blue value in sRGB color space - * @param a the alpha value - * + * @param a the alpha value + * * @return this ColorRGBA with updated values. */ - public ColorRGBA setAsSrgb(float r, float g, float b, float a){ - this.r = (float)Math.pow(r, GAMMA); - this.b = (float)Math.pow(b, GAMMA); - this.g = (float)Math.pow(g, GAMMA); + public ColorRGBA setAsSrgb(float r, float g, float b, float a) { + this.r = (float) Math.pow(r, GAMMA); + this.b = (float) Math.pow(b, GAMMA); + this.g = (float) Math.pow(g, GAMMA); this.a = a; - + return this; } - + /** * Get the color in sRGB color space as a ColorRGBA. - * - * Note that linear values stored in the ColorRGBA will be gamma corrected + * + * Note that linear values stored in the ColorRGBA will be gamma corrected * and returned as a ColorRGBA. - * + * * The x attribute will be fed with the r channel in sRGB space. * The y attribute will be fed with the g channel in sRGB space. * The z attribute will be fed with the b channel in sRGB space. * The w attribute will be fed with the a channel. - * - * Note that no correction will be performed on the alpha channel as it + * + * Note that no correction will be performed on the alpha channel as it * conventionally doesn't represent a color itself. - * + * * @return the color in sRGB color space as a ColorRGBA. */ public ColorRGBA getAsSrgb() { ColorRGBA srgb = new ColorRGBA(); - float invGama = 1f / GAMMA; - srgb.r = (float) Math.pow(r, invGama); - srgb.g = (float) Math.pow(g, invGama); - srgb.b = (float) Math.pow(b, invGama); + float invGamma = 1f / GAMMA; + srgb.r = (float) Math.pow(r, invGamma); + srgb.g = (float) Math.pow(g, invGamma); + srgb.b = (float) Math.pow(b, invGamma); srgb.a = a; return srgb; } - + + /** + * Helper method to convert a float (0-1) to a byte (0-255). + */ + private byte toByte(float channel) { + return (byte) ((int) (channel * 255) & 0xFF); + } + + /** + * Helper method to convert an int (shifted byte) to a float (0-1). + */ + private float fromByte(int channelByte) { + return ((byte) (channelByte) & 0xFF) / 255f; + } + + /** + * Helper method to combine four float channels into an int. + */ + private int toInt(float c1, float c2, float c3, float c4) { + int r = ((int) (c1 * 255) & 0xFF); + int g = ((int) (c2 * 255) & 0xFF); + int b = ((int) (c3 * 255) & 0xFF); + int a = ((int) (c4 * 255) & 0xFF); + return (r << 24) | (g << 16) | (b << 8) | a; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java b/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java index 522821e035..8765c3aba2 100644 --- a/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java +++ b/jme3-core/src/main/java/com/jme3/math/CurveAndSurfaceMath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,130 +36,129 @@ /** * This class offers methods to help with curves and surfaces calculations. + * * @author Marcin Roguski (Kealthas) */ public class CurveAndSurfaceMath { - private static final float KNOTS_MINIMUM_DELTA = 0.0001f; + private static final float KNOTS_MINIMUM_DELTA = 0.0001f; + + /** + * A private constructor is defined to avoid instantiation of this class. + */ + private CurveAndSurfaceMath() { + } - /** - * A private constructor is defined to avoid instantiation of this - * class. - */ - private CurveAndSurfaceMath() {} - - /** - * This method interpolates the data for the nurbs curve. - * @param u - * the u value - * @param nurbSpline - * the nurbs spline definition - * @param store - * the resulting point in 3D space - */ - public static void interpolateNurbs(float u, Spline nurbSpline, Vector3f store) { - if (nurbSpline.getType() != SplineType.Nurb) { - throw new IllegalArgumentException("Given spline is not of a NURB type!"); - } - List controlPoints = nurbSpline.getControlPoints(); - float[] weights = nurbSpline.getWeights(); - List knots = nurbSpline.getKnots(); - int controlPointAmount = controlPoints.size(); + /** + * This method interpolates the data for the nurbs curve. + * + * @param u the u value + * @param nurbSpline + * the nurbs spline definition + * @param store + * the resulting point in 3D space + */ + public static void interpolateNurbs(float u, Spline nurbSpline, Vector3f store) { + if (nurbSpline.getType() != SplineType.Nurb) { + throw new IllegalArgumentException("Given spline is not of a NURB type!"); + } + List controlPoints = nurbSpline.getControlPoints(); + float[] weights = nurbSpline.getWeights(); + List knots = nurbSpline.getKnots(); + int controlPointAmount = controlPoints.size(); + store.set(Vector3f.ZERO); + float delimiter = 0; + for (int i = 0; i < controlPointAmount; ++i) { + float val = weights[i] * CurveAndSurfaceMath.computeBaseFunctionValue(i, nurbSpline.getBasisFunctionDegree(), u, knots); + store.addLocal(nurbSpline.getControlPoints().get(i) + .mult(val)); + delimiter += val; + } + store.divideLocal(delimiter); + } - store.set(Vector3f.ZERO); - float delimeter = 0; - for (int i = 0; i < controlPointAmount; ++i) { - float val = weights[i] * CurveAndSurfaceMath.computeBaseFunctionValue(i, nurbSpline.getBasisFunctionDegree(), u, knots); - store.addLocal(nurbSpline.getControlPoints().get(i) - .mult(val)); - delimeter += val; - } - store.divideLocal(delimeter); - } + /** + * This method interpolates the data for the nurbs surface. + * + * @param u the u value + * @param v the v value + * @param controlPoints + * the nurbs' control points + * @param knots + * the nurbs' knots + * @param basisUFunctionDegree + * the degree of basis U function + * @param basisVFunctionDegree + * the degree of basis V function + * @param store + * the resulting point in 3D space + */ + public static void interpolate(float u, float v, List> controlPoints, List[] knots, + int basisUFunctionDegree, int basisVFunctionDegree, Vector3f store) { + store.set(Vector3f.ZERO); + float delimiter = 0; + int vControlPointsAmount = controlPoints.size(); + int uControlPointsAmount = controlPoints.get(0).size(); + for (int i = 0; i < vControlPointsAmount; ++i) { + for (int j = 0; j < uControlPointsAmount; ++j) { + Vector4f controlPoint = controlPoints.get(i).get(j); + float val = controlPoint.w + * CurveAndSurfaceMath.computeBaseFunctionValue(i, basisVFunctionDegree, v, knots[1]) + * CurveAndSurfaceMath.computeBaseFunctionValue(j, basisUFunctionDegree, u, knots[0]); + store.addLocal(controlPoint.x * val, controlPoint.y * val, controlPoint.z * val); + delimiter += val; + } + } + store.divideLocal(delimiter); + } - /** - * This method interpolates the data for the nurbs surface. - * - * @param u - * the u value - * @param v - * the v value - * @param controlPoints - * the nurbs' control points - * @param knots - * the nurbs' knots - * @param basisUFunctionDegree - * the degree of basis U function - * @param basisVFunctionDegree - * the degree of basis V function - * @param store - * the resulting point in 3D space - */ - public static void interpolate(float u, float v, List> controlPoints, List[] knots, - int basisUFunctionDegree, int basisVFunctionDegree, Vector3f store) { - store.set(Vector3f.ZERO); - float delimeter = 0; - int vControlPointsAmount = controlPoints.size(); - int uControlPointsAmount = controlPoints.get(0).size(); - for (int i = 0; i < vControlPointsAmount; ++i) { - for (int j = 0; j < uControlPointsAmount; ++j) { - Vector4f controlPoint = controlPoints.get(i).get(j); - float val = controlPoint.w - * CurveAndSurfaceMath.computeBaseFunctionValue(i, basisVFunctionDegree, v, knots[1]) - * CurveAndSurfaceMath.computeBaseFunctionValue(j, basisUFunctionDegree, u, knots[0]); - store.addLocal(controlPoint.x * val, controlPoint.y * val, controlPoint.z * val); - delimeter += val; - } - } - store.divideLocal(delimeter); - } + /** + * This method prepares the knots to be used. If the knots represent + * non-uniform B-splines (first and last knot values are being repeated) it + * leads to NaN results during calculations. This method adds a small number + * to each of such knots to avoid NaN's. + * + * @param knots + * the knots to be prepared to use + * @param basisFunctionDegree + * the degree of basis function + */ + // TODO: improve this; constant delta may lead to errors if the difference between tha last repeated + // point and the following one is lower than it + public static void prepareNurbsKnots(List knots, int basisFunctionDegree) { + float delta = KNOTS_MINIMUM_DELTA; + float prevValue = knots.get(0).floatValue(); + for (int i = 1; i < knots.size(); ++i) { + float value = knots.get(i).floatValue(); + if (value <= prevValue) { + value += delta; + knots.set(i, Float.valueOf(value)); + delta += KNOTS_MINIMUM_DELTA; + } else { + delta = KNOTS_MINIMUM_DELTA;//reset the delta's value + } - /** - * This method prepares the knots to be used. If the knots represent non-uniform B-splines (first and last knot values are being - * repeated) it leads to NaN results during calculations. This method adds a small number to each of such knots to avoid NaN's. - * @param knots - * the knots to be prepared to use - * @param basisFunctionDegree - * the degree of basis function - */ - // TODO: improve this; constant delta may lead to errors if the difference between tha last repeated - // point and the following one is lower than it - public static void prepareNurbsKnots(List knots, int basisFunctionDegree) { - float delta = KNOTS_MINIMUM_DELTA; - float prevValue = knots.get(0).floatValue(); - for(int i=1;i knots) { - if (k == 1) { - return knots.get(i) <= t && t < knots.get(i + 1) ? 1.0f : 0.0f; - } else { - return (t - knots.get(i)) / (knots.get(i + k - 1) - knots.get(i)) * - CurveAndSurfaceMath.computeBaseFunctionValue(i, k - 1, t, knots) - + (knots.get(i + k) - t) / (knots.get(i + k) - knots.get(i + 1)) * - CurveAndSurfaceMath.computeBaseFunctionValue(i + 1, k - 1, t, knots); - } - } + /** + * This method computes the base function value for the NURB curve. + * + * @param i the knot index + * @param k the base function degree + * @param t the knot value + * @param knots + * the knots' values + * @return the base function value + */ + private static float computeBaseFunctionValue(int i, int k, float t, List knots) { + if (k == 1) { + return knots.get(i) <= t && t < knots.get(i + 1) ? 1.0f : 0.0f; + } else { + return (t - knots.get(i)) / (knots.get(i + k - 1) - knots.get(i)) + * CurveAndSurfaceMath.computeBaseFunctionValue(i, k - 1, t, knots) + + (knots.get(i + k) - t) / (knots.get(i + k) - knots.get(i + 1)) + * CurveAndSurfaceMath.computeBaseFunctionValue(i + 1, k - 1, t, knots); + } + } } diff --git a/jme3-core/src/main/java/com/jme3/math/EaseFunction.java b/jme3-core/src/main/java/com/jme3/math/EaseFunction.java index 76d6a7dedd..32cfc138c1 100644 --- a/jme3-core/src/main/java/com/jme3/math/EaseFunction.java +++ b/jme3-core/src/main/java/com/jme3/math/EaseFunction.java @@ -1,10 +1,40 @@ +/* + * Copyright (c) 2017-2021 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; /** * Created by Nehon on 26/03/2017. */ public interface EaseFunction { - /** * @param value a value from 0 to 1. Passing a value out of this range will have unexpected behavior. * @return the blended value diff --git a/jme3-core/src/main/java/com/jme3/math/Easing.java b/jme3-core/src/main/java/com/jme3/math/Easing.java index e9969c91ae..09686478b4 100644 --- a/jme3-core/src/main/java/com/jme3/math/Easing.java +++ b/jme3-core/src/main/java/com/jme3/math/Easing.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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; /** @@ -5,8 +36,9 @@ * Created by Nehon on 26/03/2017. */ public class Easing { - - + /** + * a function that always returns 0 + */ public static EaseFunction constant = new EaseFunction() { @Override public float apply(float value) { @@ -23,6 +55,9 @@ public float apply(float value) { } }; + /** + * a function that returns the square of its input + */ public static EaseFunction inQuad = new EaseFunction() { @Override public float apply(float value) { @@ -30,6 +65,9 @@ public float apply(float value) { } }; + /** + * a function that returns the cube of its input + */ public static EaseFunction inCubic = new EaseFunction() { @Override public float apply(float value) { @@ -37,6 +75,9 @@ public float apply(float value) { } }; + /** + * a function that returns the 4th power of its input + */ public static EaseFunction inQuart = new EaseFunction() { @Override public float apply(float value) { @@ -44,6 +85,9 @@ public float apply(float value) { } }; + /** + * a function that returns the 5th power of its input + */ public static EaseFunction inQuint = new EaseFunction() { @Override public float apply(float value) { @@ -51,7 +95,6 @@ public float apply(float value) { } }; - /** * Out Elastic and bounce */ @@ -62,6 +105,9 @@ public float apply(float value) { } }; + /** + * a function that starts quickly, then bounces several times + */ public static EaseFunction outBounce = new EaseFunction() { @Override public float apply(float value) { @@ -81,6 +127,9 @@ public float apply(float value) { * In Elastic and bounce */ public static EaseFunction inElastic = new Invert(outElastic); + /** + * a function containing a series of increasing bounces + */ public static EaseFunction inBounce = new Invert(outBounce); /** @@ -101,11 +150,9 @@ public float apply(float value) { public static EaseFunction inOutElastic = new InOut(inElastic, outElastic); public static EaseFunction inOutBounce = new InOut(inBounce, outBounce); - /** * Extra functions */ - public static EaseFunction smoothStep = new EaseFunction() { @Override public float apply(float t) { @@ -120,14 +167,26 @@ public float apply(float t) { } }; + /** + * A private constructor to inhibit instantiation of this class. + */ + private Easing() { + } + /** * An Ease function composed of 2 sb function for custom in and out easing */ public static class InOut implements EaseFunction { - private EaseFunction in; - private EaseFunction out; + final private EaseFunction in; + final private EaseFunction out; + /** + * Instantiate a function that blends 2 pre-existing functions. + * + * @param in the function to use at value=0 + * @param out the function to use at value=1 + */ public InOut(EaseFunction in, EaseFunction out) { this.in = in; this.out = out; @@ -137,17 +196,17 @@ public InOut(EaseFunction in, EaseFunction out) { public float apply(float value) { if (value < 0.5) { value = value * 2; - return inQuad.apply(value) / 2; + return in.apply(value) / 2; } else { value = (value - 0.5f) * 2; - return outQuad.apply(value) / 2 + 0.5f; + return out.apply(value) / 2 + 0.5f; } } } private static class Invert implements EaseFunction { - private EaseFunction func; + final private EaseFunction func; public Invert(EaseFunction func) { this.func = func; @@ -158,6 +217,4 @@ public float apply(float value) { return 1f - func.apply(1f - value); } } - - } diff --git a/jme3-core/src/main/java/com/jme3/math/Eigen3f.java b/jme3-core/src/main/java/com/jme3/math/Eigen3f.java index b43731badc..66e973a82c 100644 --- a/jme3-core/src/main/java/com/jme3/math/Eigen3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Eigen3f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,10 +34,12 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * A calculator for the eigenvectors and eigenvalues of a Matrix3f. + */ public class Eigen3f implements java.io.Serializable { - static final long serialVersionUID = 1; - + private static final Logger logger = Logger.getLogger(Eigen3f.class .getName()); @@ -47,17 +49,29 @@ public class Eigen3f implements java.io.Serializable { static final double ONE_THIRD_DOUBLE = 1.0 / 3.0; static final double ROOT_THREE_DOUBLE = Math.sqrt(3.0); - + /** + * Instantiate an empty calculator. + */ public Eigen3f() { - + } - + + /** + * Calculate the eigenvalues and eigenvectors of the specified matrix. + * + * @param data the input Matrix3f + */ public Eigen3f(Matrix3f data) { calculateEigen(data); } - public void calculateEigen(Matrix3f data) { - // prep work... + /** + * Calculate the eigenvalues and eigenvectors of the specified matrix. + * + * @param data the input Matrix3f + */ + public void calculateEigen(Matrix3f data) { + // prep work... eigenVectors[0] = new Vector3f(); eigenVectors[1] = new Vector3f(); eigenVectors[2] = new Vector3f(); @@ -77,7 +91,7 @@ public void calculateEigen(Matrix3f data) { maxRows[0] = new Vector3f(); maxRows[1] = new Vector3f(); maxRows[2] = new Vector3f(); - + for (int i = 0; i < 3; i++) { Matrix3f tempMatrix = new Matrix3f(scaledData); tempMatrix.m00 -= eigenValues[i]; @@ -134,12 +148,12 @@ public void calculateEigen(Matrix3f data) { eigenValues[i] *= maxMagnitude; } } - } + } /** * Scale the matrix so its entries are in [-1,1]. The scaling is applied * only when at least one matrix entry has magnitude larger than 1. - * + * * @return the max magnitude in this matrix */ private float scaleMatrix(Matrix3f mat) { @@ -176,7 +190,8 @@ private float scaleMatrix(Matrix3f mat) { } /** - * Compute the eigenvectors of the given Matrix, using the + * Compute the eigenvectors of the given Matrix, using the + * * @param mat * @param vect * @param index1 @@ -264,14 +279,14 @@ private void computeVectors(Matrix3f mat, Vector3f vect, int index1, } } - eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]); + eigenVectors[index3].cross(eigenVectors[index1], eigenVectors[index2]); } /** * Check the rank of the given Matrix to determine if it is positive. While * doing so, store the max magnitude entry in the given float store and the * max row of the matrix in the Vector store. - * + * * @param matrix * the Matrix3f to analyze. * @param maxMagnitudeStore @@ -306,7 +321,7 @@ private boolean positiveRank(Matrix3f matrix, float[] maxMagnitudeStore, Vector3 /** * Generate the base eigen values of the given matrix using double precision * math. - * + * * @param mat * the Matrix3f to analyze. * @param rootsStore @@ -378,13 +393,19 @@ private void computeRoots(Matrix3f mat, double[] rootsStore) { } } + /** + * Test the Eigen3f class. + * + * @param args ignored + */ public static void main(String[] args) { Matrix3f mat = new Matrix3f(2, 1, 1, 1, 2, 1, 1, 1, 2); Eigen3f eigenSystem = new Eigen3f(mat); logger.info("eigenvalues = "); - for (int i = 0; i < 3; i++) + for (int i = 0; i < 3; i++) { logger.log(Level.FINE, "{0} ", eigenSystem.getEigenValue(i)); + } logger.info("eigenvectors = "); for (int i = 0; i < 3; i++) { @@ -402,18 +423,40 @@ public static void main(String[] args) { // -0.816485 0.004284 0.577350 } + /** + * Read the indexed eigenvalue. + * + * @param i which value to read (0, 1, or 2) + * @return the previously calculated eigenvalue + */ public float getEigenValue(int i) { return eigenValues[i]; } + /** + * Access the indexed eigenvector. + * + * @param i which vector to read (0, 1, or 2) + * @return the pre-existing eigenvector + */ public Vector3f getEigenVector(int i) { return eigenVectors[i]; } + /** + * Access the array of eigenvalues. + * + * @return the pre-existing array + */ public float[] getEigenValues() { return eigenValues; } + /** + * Access the array of eigenvectors. + * + * @return the pre-existing array of vectors + */ public Vector3f[] getEigenVectors() { return eigenVectors; } diff --git a/jme3-core/src/main/java/com/jme3/math/FastMath.java b/jme3-core/src/main/java/com/jme3/math/FastMath.java index 49c993a091..d7b533fbd9 100644 --- a/jme3-core/src/main/java/com/jme3/math/FastMath.java +++ b/jme3-core/src/main/java/com/jme3/math/FastMath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,45 +40,72 @@ * @author Various * @version $Id: FastMath.java,v 1.45 2007/08/26 08:44:20 irrisor Exp $ */ -final public class FastMath { +public final class FastMath { - private FastMath() { - } - /** A "close to zero" double epsilon value for use*/ + private FastMath() {} + + /** + * A "close to zero" double epsilon value for use + */ public static final double DBL_EPSILON = 2.220446049250313E-16d; - /** A "close to zero" float epsilon value for use*/ + /** + * A "close to zero" float epsilon value for use + */ public static final float FLT_EPSILON = 1.1920928955078125E-7f; - /** A "close to zero" float epsilon value for use*/ + /** + * A "close to zero" float epsilon value for use + */ public static final float ZERO_TOLERANCE = 0.0001f; + /** + * The value 1/3, as a float. + */ public static final float ONE_THIRD = 1f / 3f; - /** The value PI as a float. (180 degrees) */ + /** + * The value PI as a float. (180 degrees) + */ public static final float PI = (float) Math.PI; - /** The value 2PI as a float. (360 degrees) */ + /** + * The value 2PI as a float. (360 degrees) + */ public static final float TWO_PI = 2.0f * PI; - /** The value PI/2 as a float. (90 degrees) */ + /** + * The value PI/2 as a float. (90 degrees) + */ public static final float HALF_PI = 0.5f * PI; - /** The value PI/4 as a float. (45 degrees) */ + /** + * The value PI/4 as a float. (45 degrees) + */ public static final float QUARTER_PI = 0.25f * PI; - /** The value 1/PI as a float. */ + /** + * The value 1/PI as a float. + */ public static final float INV_PI = 1.0f / PI; - /** The value 1/(2PI) as a float. */ + /** + * The value 1/(2PI) as a float. + */ public static final float INV_TWO_PI = 1.0f / TWO_PI; - /** A value to multiply a degree value by, to convert it to radians. */ + /** + * A value to multiply a degree value by, to convert it to radians. + */ public static final float DEG_TO_RAD = PI / 180.0f; - /** A value to multiply a radian value by, to convert it to degrees. */ + /** + * A value to multiply a radian value by, to convert it to degrees. + */ public static final float RAD_TO_DEG = 180.0f / PI; - /** A precreated random object for random numbers. */ + /** + * A precreated random object for random numbers. + */ public static final Random rand = new Random(System.currentTimeMillis()); /** * Returns true if the number is a power of 2 (2,4,8,16...) - * + * * A good implementation found on the Java boards. note: a number is a power * of two if and only if it is the smallest number with that number of * significant bits. Therefore, if you subtract 1, you know that the new * number will have fewer bits, so ANDing the original number with anything * less than it will give 0. - * + * * @param number * The number to test. * @return True if it is a power of two. @@ -89,10 +116,10 @@ public static boolean isPowerOfTwo(int number) { /** * Get the next power of two of the given number. - * + * * E.g. for an input 100, this returns 128. * Returns 1 for all numbers less than or equal to 1. - * + * * @param number The number to obtain the POT for. * @return The next power of two. */ @@ -111,7 +138,7 @@ public static int nearestPowerOfTwo(int number) { /** * Linear interpolation from startValue to endValue by the given percent. * Basically: ((1 - percent) * startValue) + (percent * endValue) - * + * * @param scale * scale value to use. if 1, use endValue, if 0, use startValue. * @param startValue @@ -146,7 +173,8 @@ public static float interpolateLinear(float scale, float startValue, float endVa * @param store a vector3f to store the result * @return The interpolated value between startValue and endValue. */ - public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) { + public static Vector3f interpolateLinear(float scale, Vector3f startValue, + Vector3f endValue, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -177,6 +205,7 @@ public static Vector3f interpolateLinear(float scale, Vector3f startValue, Vecto * if scale is between 0 and 1 this method returns the same result as interpolateLinear * if the scale is over 1 the value is linearly extrapolated. * Note that the end value is the value for a scale of 1. + * * @param scale the scale for extrapolation * @param startValue the starting value (scale = 0) * @param endValue the end value (scale = 1) @@ -193,14 +222,16 @@ public static float extrapolateLinear(float scale, float startValue, float endVa * Linear extrapolation from startValue to endValue by the given scale. * if scale is between 0 and 1 this method returns the same result as interpolateLinear * if the scale is over 1 the value is linearly extrapolated. - * Note that the end value is the value for a scale of 1. + * Note that the end value is the value for a scale of 1. + * * @param scale the scale for extrapolation * @param startValue the starting value (scale = 0) * @param endValue the end value (scale = 1) * @param store an initialized vector to store the return value * @return an extrapolation for the given parameters */ - public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vector3f endValue, Vector3f store) { + public static Vector3f extrapolateLinear(float scale, Vector3f startValue, + Vector3f endValue, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -218,6 +249,7 @@ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vecto * if scale is between 0 and 1 this method returns the same result as interpolateLinear * if the scale is over 1 the value is linearly extrapolated. * Note that the end value is the value for a scale of 1. + * * @param scale the scale for extrapolation * @param startValue the starting value (scale = 0) * @param endValue the end value (scale = 1) @@ -227,7 +259,8 @@ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vecto return extrapolateLinear(scale, startValue, endValue, null); } - /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. + /** + * Interpolate a spline between at least 4 control points following the Catmull-Rom equation. * here is the interpolation matrix * m = [ 0.0 1.0 0.0 0.0 ] * [-T 0.0 T 0.0 ] @@ -235,6 +268,7 @@ public static Vector3f extrapolateLinear(float scale, Vector3f startValue, Vecto * [-T 2-T T-2 T ] * where T is the curve tension * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * * @param u value from 0 to 1 * @param T The tension of the curve * @param p0 control point 0 @@ -250,10 +284,11 @@ public static float interpolateCatmullRom(float u, float T, float p0, float p1, c3 = 2 * T * p0 + (T - 3) * p1 + (3 - 2 * T) * p2 + -T * p3; c4 = -T * p0 + (2 - T) * p1 + (T - 2) * p2 + T * p3; - return (float) (((c4 * u + c3) * u + c2) * u + c1); + return ((c4 * u + c3) * u + c2) * u + c1; } - /**Interpolate a spline between at least 4 control points following the Catmull-Rom equation. + /** + * Interpolate a spline between at least 4 control points following the Catmull-Rom equation. * here is the interpolation matrix * m = [ 0.0 1.0 0.0 0.0 ] * [-T 0.0 T 0.0 ] @@ -261,6 +296,7 @@ public static float interpolateCatmullRom(float u, float T, float p0, float p1, * [-T 2-T T-2 T ] * where T is the tension of the curve * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * * @param u value from 0 to 1 * @param T The tension of the curve * @param p0 control point 0 @@ -270,7 +306,8 @@ public static float interpolateCatmullRom(float u, float T, float p0, float p1, * @param store a Vector3f to store the result * @return Catmull–Rom interpolation */ - public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { + public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, + Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -282,13 +319,14 @@ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vect /** * Interpolate a spline between at least 4 control points using the - * Catmull-Rom equation. Here is the interpolation matrix: + * Catmull-Rom equation. Here is the interpolation matrix: * m = [ 0.0 1.0 0.0 0.0 ] * [-T 0.0 T 0.0 ] * [ 2T T-3 3-2T -T ] * [-T 2-T T-2 T ] * where T is the tension of the curve * the result is a value between p1 and p2, t=0 for p1, t=1 for p2 + * * @param u value from 0 to 1 * @param T The tension of the curve * @param p0 control point 0 @@ -297,7 +335,8 @@ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vect * @param p3 control point 3 * @return Catmull–Rom interpolation */ - public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3) { + public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, + Vector3f p1, Vector3f p2, Vector3f p3) { return interpolateCatmullRom(u, T, p0, p1, p2, p3, null); } @@ -309,6 +348,7 @@ public static Vector3f interpolateCatmullRom(float u, float T, Vector3f p0, Vect * [ 1.0 0.0 0.0 0.0 ] * where T is the curve tension * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * * @param u value from 0 to 1 * @param p0 control point 0 * @param p1 control point 1 @@ -326,7 +366,8 @@ public static float interpolateBezier(float u, float p0, float p1, float p2, flo + p3 * u2 * u; } - /**Interpolate a spline between at least 4 control points following the Bezier equation. + /** + * Interpolate a spline between at least 4 control points following the Bezier equation. * here is the interpolation matrix * m = [ -1.0 3.0 -3.0 1.0 ] * [ 3.0 -6.0 3.0 0.0 ] @@ -334,6 +375,7 @@ public static float interpolateBezier(float u, float p0, float p1, float p2, flo * [ 1.0 0.0 0.0 0.0 ] * where T is the tension of the curve * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * * @param u value from 0 to 1 * @param p0 control point 0 * @param p1 control point 1 @@ -342,7 +384,8 @@ public static float interpolateBezier(float u, float p0, float p1, float p2, flo * @param store a Vector3f to store the result * @return Bezier interpolation */ - public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, Vector3f store) { + public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, + Vector3f p2, Vector3f p3, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -352,7 +395,8 @@ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vect return store; } - /**Interpolate a spline between at least 4 control points following the Bezier equation. + /** + * Interpolate a spline between at least 4 control points following the Bezier equation. * here is the interpolation matrix * m = [ -1.0 3.0 -3.0 1.0 ] * [ 3.0 -6.0 3.0 0.0 ] @@ -360,6 +404,7 @@ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vect * [ 1.0 0.0 0.0 0.0 ] * where T is the tension of the curve * the result is a value between p1 and p3, t=0 for p1, t=1 for p3 + * * @param u value from 0 to 1 * @param p0 control point 0 * @param p1 control point 1 @@ -373,6 +418,7 @@ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vect /** * Compute the length of a Catmull–Rom spline between control points 1 and 2 + * * @param p0 control point 0 * @param p1 control point 1 * @param p2 control point 2 @@ -382,7 +428,8 @@ public static Vector3f interpolateBezier(float u, Vector3f p0, Vector3f p1, Vect * @param curveTension the curve tension * @return the length of the segment */ - public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, Vector3f p3, float startRange, float endRange, float curveTension) { + public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, + Vector3f p2, Vector3f p3, float startRange, float endRange, float curveTension) { float epsilon = 0.001f; float middleValue = (startRange + endRange) * 0.5f; @@ -409,6 +456,7 @@ public static float getCatmullRomP1toP2Length(Vector3f p0, Vector3f p1, Vector3f /** * Compute the length on a Bezier spline between control points 1 and 2. + * * @param p0 control point 0 * @param p1 control point 1 * @param p2 control point 2 @@ -432,6 +480,7 @@ public static float getBezierP1toP2Length(Vector3f p0, Vector3f p1, Vector3f p2, * Special cases: *
      • If fValue is smaller than -1, then the result is PI. *
      • If the argument is greater than 1, then the result is 0.
      + * * @param fValue The value to arc cosine. * @return The angle, in radians. * @see java.lang.Math#acos(double) @@ -441,10 +490,8 @@ public static float acos(float fValue) { if (fValue < 1.0f) { return (float) Math.acos(fValue); } - return 0.0f; } - return PI; } @@ -453,6 +500,7 @@ public static float acos(float fValue) { * Special cases: *
      • If fValue is smaller than -1, then the result is -HALF_PI. *
      • If the argument is greater than 1, then the result is HALF_PI.
      + * * @param fValue The value to arc sine. * @return the angle in radians. * @see java.lang.Math#asin(double) @@ -462,15 +510,14 @@ public static float asin(float fValue) { if (fValue < 1.0f) { return (float) Math.asin(fValue); } - return HALF_PI; } - return -HALF_PI; } /** * Returns the arc tangent of an angle given in radians.
      + * * @param fValue The angle, in radians. * @return fValue's atan * @see java.lang.Math#atan(double) @@ -481,8 +528,9 @@ public static float atan(float fValue) { /** * A direct call to Math.atan2. - * @param fY - * @param fX + * + * @param fY ordinate + * @param fX abscissa * @return Math.atan2(fY,fX) * @see java.lang.Math#atan2(double, double) */ @@ -491,7 +539,8 @@ public static float atan2(float fY, float fX) { } /** - * Rounds a fValue up. A call to Math.ceil + * Rounds a fValue up. A call to Math.ceil + * * @param fValue The value. * @return The fValue rounded up * @see java.lang.Math#ceil(double) @@ -502,9 +551,10 @@ public static float ceil(float fValue) { /** * Returns cosine of an angle. Direct call to java.lang.Math - * @see Math#cos(double) + * + * @see Math#cos(double) * @param v The angle to cosine. - * @return the cosine of the angle. + * @return the cosine of the angle. */ public static float cos(float v) { return (float) Math.cos(v); @@ -512,7 +562,8 @@ public static float cos(float v) { /** * Returns the sine of an angle. Direct call to java.lang.Math - * @see Math#sin(double) + * + * @see Math#sin(double) * @param v The angle to sine. * @return the sine of the angle. */ @@ -522,6 +573,7 @@ public static float sin(float v) { /** * Returns E^fValue + * * @param fValue Value to raise to a power. * @return The value E^fValue * @see java.lang.Math#exp(double) @@ -532,6 +584,7 @@ public static float exp(float fValue) { /** * Returns Absolute value of a float. + * * @param fValue The value to abs. * @return The abs of the value. * @see java.lang.Math#abs(float) @@ -545,6 +598,7 @@ public static float abs(float fValue) { /** * Returns a number rounded down. + * * @param fValue The value to round * @return The given number rounded down * @see java.lang.Math#floor(double) @@ -555,6 +609,7 @@ public static float floor(float fValue) { /** * Returns 1/sqrt(fValue) + * * @param fValue The value to process. * @return 1/sqrt(fValue) * @see java.lang.Math#sqrt(double) @@ -563,17 +618,24 @@ public static float invSqrt(float fValue) { return (float) (1.0f / Math.sqrt(fValue)); } + /** + * Quickly estimate 1/sqrt(fValue). + * + * @param x the input value (≥0) + * @return an approximate value for 1/sqrt(x) + */ public static float fastInvSqrt(float x) { - float xhalf = 0.5f * x; + float halfX = 0.5f * x; int i = Float.floatToIntBits(x); // get bits for floating value i = 0x5f375a86 - (i >> 1); // gives initial guess y0 x = Float.intBitsToFloat(i); // convert bits back to float - x = x * (1.5f - xhalf * x * x); // Newton step, repeating increases accuracy + x = x * (1.5f - halfX * x * x); // Newton step, repeating increases accuracy return x; } /** * Returns the log base E of a value. + * * @param fValue The value to log. * @return The log of fValue base E * @see java.lang.Math#log(double) @@ -583,8 +645,9 @@ public static float log(float fValue) { } /** - * Returns the logarithm of value with given base, calculated as log(value)/log(base), + * Returns the logarithm of value with given base, calculated as log(value)/log(base), * so that pow(base, return)==value (contributed by vear) + * * @param value The value to log. * @param base Base of logarithm. * @return The logarithm of value with given base @@ -594,7 +657,8 @@ public static float log(float value, float base) { } /** - * Returns a number raised to an exponent power. fBase^fExponent + * Returns a number raised to an exponent power. fBase^fExponent + * * @param fBase The base value (IE 2) * @param fExponent The exponent value (IE 3) * @return base raised to exponent (IE 8) @@ -605,7 +669,8 @@ public static float pow(float fBase, float fExponent) { } /** - * Returns the value squared. fValue ^ 2 + * Returns the value squared. fValue ^ 2 + * * @param fValue The value to square. * @return The square of the given value. */ @@ -615,6 +680,7 @@ public static float sqr(float fValue) { /** * Returns the square root of a given value. + * * @param fValue The value to sqrt. * @return The square root of the given value. * @see java.lang.Math#sqrt(double) @@ -624,8 +690,8 @@ public static float sqrt(float fValue) { } /** - * Returns the tangent of a value. If USE_FAST_TRIG is enabled, an approximate value - * is returned. Otherwise, a direct value is used. + * Returns the tangent of the specified angle. + * * @param fValue The value to tangent, in radians. * @return The tangent of fValue. * @see java.lang.Math#tan(double) @@ -636,6 +702,7 @@ public static float tan(float fValue) { /** * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise + * * @param iValue The integer to examine. * @return The integer's sign. */ @@ -651,6 +718,7 @@ public static int sign(int iValue) { /** * Returns 1 if the number is positive, -1 if the number is negative, and 0 otherwise + * * @param fValue The float to examine. * @return The float's sign. */ @@ -661,6 +729,7 @@ public static float sign(float fValue) { /** * Given 3 points in a 2d plane, this function computes if the points going from A-B-C * are moving counter clock wise. + * * @param p0 Point 0. * @param p1 Point 1. * @param p2 Point 2. @@ -690,6 +759,7 @@ public static int counterClockwise(Vector2f p0, Vector2f p1, Vector2f p2) { /** * Test if a point is inside a triangle. 1 if the point is on the ccw side, * -1 if the point is on the cw side, and 0 if it is on neither. + * * @param t0 First point of the triangle. * @param t1 Second point of the triangle. * @param t2 Third point of the triangle. @@ -720,6 +790,7 @@ public static int pointInsideTriangle(Vector2f t0, Vector2f t1, Vector2f t2, Vec /** * A method that computes normal for a triangle defined by three vertices. + * * @param v1 first vertex * @param v2 second vertex * @param v3 third vertex @@ -733,6 +804,24 @@ public static Vector3f computeNormal(Vector3f v1, Vector3f v2, Vector3f v3) { /** * Returns the determinant of a 4x4 matrix. + * + * @param m00 the element in row 0, column 0 of the matrix + * @param m01 the element in row 0, column 1 of the matrix + * @param m02 the element in row 0, column 2 of the matrix + * @param m03 the element in row 0, column 3 of the matrix + * @param m10 the element in row 1, column 0 of the matrix + * @param m11 the element in row 1, column 1 of the matrix + * @param m12 the element in row 1, column 2 of the matrix + * @param m13 the element in row 1, column 3 of the matrix + * @param m20 the element in row 2, column 0 of the matrix + * @param m21 the element in row 2, column 1 of the matrix + * @param m22 the element in row 2, column 2 of the matrix + * @param m23 the element in row 2, column 3 of the matrix + * @param m30 the element in row 3, column 0 of the matrix + * @param m31 the element in row 3, column 1 of the matrix + * @param m32 the element in row 3, column 2 of the matrix + * @param m33 the element in row 3, column 3 of the matrix + * @return the determinant */ public static float determinant(double m00, double m01, double m02, double m03, double m10, double m11, double m12, double m13, @@ -752,35 +841,122 @@ public static float determinant(double m00, double m01, double m02, } /** - * Returns a random float between 0 and 1. - * - * @return A random float between 0.0f (inclusive) to - * 1.0f (exclusive). + * Generates a pseudorandom {@code float} in the range [0.0, 1.0). + * + * @return A random {@code float} value. */ public static float nextRandomFloat() { return rand.nextFloat(); } /** - * Returns a random integer between min and max. - * - * @return A random int between min (inclusive) to - * max (inclusive). + * Generates a pseudorandom {@code float} in the range [min, max) + * + * @param min The lower bound (inclusive). + * @param max The upper bound (exclusive). + * @return A random {@code float} value within the specified range. */ - public static int nextRandomInt(int min, int max) { - return (int) (nextRandomFloat() * (max - min + 1)) + min; + public static float nextRandomFloat(float min, float max) { + return min + (max - min) * nextRandomFloat(); } + /** + * Generates a pseudorandom, uniformly-distributed {@code int} value. + * + * @return The next pseudorandom {@code int} value. + */ public static int nextRandomInt() { return rand.nextInt(); } + /** + * Generates a pseudorandom {@code int} in the range [min, max] (inclusive). + * + * @param min The lower bound (inclusive). + * @param max The upper bound (inclusive). + * @return A random {@code int} value within the specified range. + */ + public static int nextRandomInt(int min, int max) { + return (int) (nextRandomFloat() * (max - min + 1)) + min; + } + + /** + * Returns a random point on the surface of a sphere with radius 1.0 + * + * @return A new {@link Vector3f} representing a random point on the surface of the unit sphere. + */ + public static Vector3f onUnitSphere() { + + float u = nextRandomFloat(); + float v = nextRandomFloat(); + + // azimuthal angle: The angle between x-axis in radians [0, 2PI] + float theta = FastMath.TWO_PI * u; + // polar angle: The angle between z-axis in radians [0, PI] + float phi = (float) Math.acos(2f * v - 1f); + + float cosPolar = FastMath.cos(phi); + float sinPolar = FastMath.sin(phi); + float cosAzim = FastMath.cos(theta); + float sinAzim = FastMath.sin(theta); + + return new Vector3f(cosAzim * sinPolar, sinAzim * sinPolar, cosPolar); + } + + /** + * Returns a random point inside or on a sphere with radius 1.0 + * This method uses spherical coordinates combined with a cubed-root radius. + * + * @return A new {@link Vector3f} representing a random point within the unit sphere. + */ + public static Vector3f insideUnitSphere() { + float u = nextRandomFloat(); + // Azimuthal angle [0, 2PI] + float theta = FastMath.TWO_PI * nextRandomFloat(); + // Polar angle [0, PI] for uniform surface distribution + float phi = FastMath.acos(2f * nextRandomFloat() - 1f); + + // For uniform distribution within the volume, radius R should be such that R^3 is uniformly distributed. + // So, R = cbrt(random_uniform_0_to_1) + float radius = (float) Math.cbrt(u); + + float sinPhi = FastMath.sin(phi); + float x = radius * sinPhi * FastMath.cos(theta); + float y = radius * sinPhi * FastMath.sin(theta); + float z = radius * FastMath.cos(phi); + + return new Vector3f(x, y, z); + } + + /** + * Returns a random point inside or on a circle with radius 1.0. + * This method uses polar coordinates combined with a square-root radius. + * + * @return A new {@link Vector2f} representing a random point within the unit circle. + */ + public static Vector2f insideUnitCircle() { + // Angle [0, 2PI] + float angle = FastMath.TWO_PI * nextRandomFloat(); + // For uniform distribution, R^2 is uniform + float radius = FastMath.sqrt(nextRandomFloat()); + + float x = radius * FastMath.cos(angle); + float y = radius * FastMath.sin(angle); + + return new Vector2f(x, y); + } + /** * Converts a point from Spherical coordinates to Cartesian (using positive * Y as up) and stores the results in the store var. + * + * @param sphereCoords the input spherical coordinates: x=distance from + * origin, y=longitude in radians, z=latitude in radians (not null, + * unaffected) + * @param store storage for the result (modified if not null) + * @return the Cartesian coordinates (either store or a new vector) */ - public static Vector3f sphericalToCartesian(Vector3f sphereCoords, - Vector3f store) { + public static Vector3f sphericalToCartesian(Vector3f sphereCoords, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -796,9 +972,13 @@ public static Vector3f sphericalToCartesian(Vector3f sphereCoords, * Converts a point from Cartesian coordinates (using positive Y as up) to * Spherical and stores the results in the store var. (Radius, Azimuth, * Polar) + * + * @param cartCoords the input Cartesian coordinates (not null, unaffected) + * @param store storage for the result (modified if not null) + * @return the Cartesian coordinates: x=distance from origin, y=longitude in + * radians, z=latitude in radians (either store or a new vector) */ - public static Vector3f cartesianToSpherical(Vector3f cartCoords, - Vector3f store) { + public static Vector3f cartesianToSpherical(Vector3f cartCoords, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -820,16 +1000,21 @@ public static Vector3f cartesianToSpherical(Vector3f cartCoords, /** * Converts a point from Spherical coordinates to Cartesian (using positive * Z as up) and stores the results in the store var. + * + * @param sphereCoords the input spherical coordinates: x=distance from + * origin, y=latitude in radians, z=longitude in radians (not null, + * unaffected) + * @param store storage for the result (modified if not null) + * @return the Cartesian coordinates (either store or a new vector) */ - public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, - Vector3f store) { + public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, Vector3f store) { if (store == null) { store = new Vector3f(); } - store.z = sphereCoords.x * FastMath.sin(sphereCoords.z); - float a = sphereCoords.x * FastMath.cos(sphereCoords.z); - store.x = a * FastMath.cos(sphereCoords.y); - store.y = a * FastMath.sin(sphereCoords.y); + store.y = sphereCoords.x * FastMath.sin(sphereCoords.y); + float a = sphereCoords.x * FastMath.cos(sphereCoords.y); + store.x = a * FastMath.cos(sphereCoords.z); + store.z = a * FastMath.sin(sphereCoords.z); return store; } @@ -838,9 +1023,13 @@ public static Vector3f sphericalToCartesianZ(Vector3f sphereCoords, * Converts a point from Cartesian coordinates (using positive Z as up) to * Spherical and stores the results in the store var. (Radius, Azimuth, * Polar) + * + * @param cartCoords the input Cartesian coordinates (not null, unaffected) + * @param store storage for the result (modified if not null) + * @return the Cartesian coordinates: x=distance from origin, y=latitude in + * radians, z=longitude in radians (either store or a new vector) */ - public static Vector3f cartesianZToSpherical(Vector3f cartCoords, - Vector3f store) { + public static Vector3f cartesianZToSpherical(Vector3f cartCoords, Vector3f store) { if (store == null) { store = new Vector3f(); } @@ -861,9 +1050,10 @@ public static Vector3f cartesianZToSpherical(Vector3f cartCoords, /** * Takes a value and expresses it in terms of min to max. - * - * @param val - - * the angle to normalize (in radians) + * + * @param val the angle to normalize (in radians) + * @param min the lower limit of the range + * @param max the upper limit of the range * @return the normalized angle (also in radians) */ public static float normalize(float val, float min, float max) { @@ -881,10 +1071,8 @@ public static float normalize(float val, float min, float max) { } /** - * @param x - * the value whose sign is to be adjusted. - * @param y - * the value whose sign is to be used. + * @param x the value whose sign is to be adjusted. + * @param y the value whose sign is to be used. * @return x with its sign changed to match the sign of y. */ public static float copysign(float x, float y) { @@ -899,10 +1087,10 @@ public static float copysign(float x, float y) { /** * Take a float input and clamp it between min and max. - * - * @param input - * @param min - * @param max + * + * @param input the value to be clamped + * @param min the minimum output value + * @param max the maximum output value * @return clamped input */ public static float clamp(float input, float min, float max) { @@ -912,7 +1100,7 @@ public static float clamp(float input, float min, float max) { /** * Clamps the given float to be between 0 and 1. * - * @param input + * @param input the value to be clamped * @return input clamped between 0 and 1. */ public static float saturate(float input) { @@ -923,9 +1111,9 @@ public static float saturate(float input) { * Determine if two floats are approximately equal. * This takes into account the magnitude of the floats, since * large numbers will have larger differences be close to each other. - * + * * Should return true for a=100000, b=100001, but false for a=10000, b=10001. - * + * * @param a The first float to compare * @param b The second float to compare * @return True if a and b are approximately equal, false otherwise. @@ -937,7 +1125,7 @@ public static boolean approximateEquals(float a, float b) { return (abs(a - b) / Math.max(abs(a), abs(b))) <= 0.00001f; } } - + /** * Converts a single precision (32 bit) floating point value * into half precision (16 bit). @@ -966,6 +1154,14 @@ public static float convertHalfToFloat(short half) { } } + /** + * Convert a single-precision (32-bit) floating-point value + * to half precision. + * + * @param flt the input value (not a NaN) + * @return a near-equivalent value in half precision + * @throws UnsupportedOperationException if flt is a NaN + */ public static short convertFloatToHalf(float flt) { if (Float.isNaN(flt)) { throw new UnsupportedOperationException("NaN to half conversion not supported!"); @@ -996,6 +1192,7 @@ public static short convertFloatToHalf(float flt) { /** * Converts a range of min/max to a 0-1 range. + * * @param value the value between min-max (inclusive). * @param min the minimum of the range. * @param max the maximum of the range. @@ -1005,4 +1202,11 @@ public static float unInterpolateLinear(float value, float min, float max) { return (value - min) / (max - min); } + /** + * Round n to a multiple of p + */ + public static int toMultipleOf(int n, int p) { + return ((n - 1) | (p - 1)) + 1; + } + } diff --git a/jme3-core/src/main/java/com/jme3/math/Line.java b/jme3-core/src/main/java/com/jme3/math/Line.java index 1ca99b4be7..122b05225e 100644 --- a/jme3-core/src/main/java/com/jme3/math/Line.java +++ b/jme3-core/src/main/java/com/jme3/math/Line.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,12 +40,11 @@ /** * Line defines a line. Where a line is defined as infinite along * two points. The two points of the line are defined as the origin and direction. - * + * * @author Mark Powell * @author Joshua Slack */ public class Line implements Savable, Cloneable, java.io.Serializable { - static final long serialVersionUID = 1; private Vector3f origin; @@ -53,7 +52,7 @@ public class Line implements Savable, Cloneable, java.io.Serializable { /** * Constructor instantiates a new Line object. The origin and - * direction are set to defaults (0,0,0). + * direction both default to (0,0,0). * */ public Line() { @@ -64,6 +63,7 @@ public Line() { /** * Constructor instantiates a new Line object. The origin * and direction are set via the parameters. + * * @param origin the origin of the line. * @param direction the direction of the line. */ @@ -73,8 +73,8 @@ public Line(Vector3f origin, Vector3f direction) { } /** - * * getOrigin returns the origin of the line. + * * @return the origin of the line. */ public Vector3f getOrigin() { @@ -82,8 +82,8 @@ public Vector3f getOrigin() { } /** - * * setOrigin sets the origin of the line. + * * @param origin the origin of the line. */ public void setOrigin(Vector3f origin) { @@ -91,8 +91,8 @@ public void setOrigin(Vector3f origin) { } /** - * * getDirection returns the direction of the line. + * * @return the direction of the line. */ public Vector3f getDirection() { @@ -100,14 +100,20 @@ public Vector3f getDirection() { } /** - * * setDirection sets the direction of the line. + * * @param direction the direction of the line. */ public void setDirection(Vector3f direction) { this.direction = direction; } + /** + * Calculate the squared distance from this line to the specified point. + * + * @param point location vector of the input point (not null, unaffected) + * @return the square of the minimum distance (≥0) + */ public float distanceSquared(Vector3f point) { TempVars vars = TempVars.get(); @@ -123,10 +129,21 @@ public float distanceSquared(Vector3f point) { return len; } + /** + * Calculate the distance from this line to the specified point. + * + * @param point location vector of the input point (not null, unaffected) + * @return the minimum distance (≥0) + */ public float distance(Vector3f point) { return FastMath.sqrt(distanceSquared(point)); } + /** + * Fit this line to the specified points. + * + * @param points a buffer containing location vectors, or null + */ public void orthogonalLineFit(FloatBuffer points) { if (points == null) { return; @@ -150,7 +167,7 @@ public void orthogonalLineFit(FloatBuffer points) { origin.addLocal(compVec1); } - origin.multLocal(1f / (float) length); + origin.multLocal(1f / length); // compute sums of products float sumXX = 0.0f, sumXY = 0.0f, sumXZ = 0.0f; @@ -186,8 +203,8 @@ public void orthogonalLineFit(FloatBuffer points) { } /** - * * random determines a random point along the line. + * * @return a random point on the line. */ public Vector3f random() { @@ -196,7 +213,7 @@ public Vector3f random() { /** * random determines a random point along the line. - * + * * @param result Vector to store result in * @return a random point on the line. */ @@ -213,18 +230,39 @@ public Vector3f random(Vector3f result) { return result; } + /** + * Serialize this line to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(origin, "origin", Vector3f.ZERO); capsule.write(direction, "direction", Vector3f.ZERO); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this line from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); } + /** + * Create a copy of this line. + * + * @return a new instance, equivalent to this one + */ @Override public Line clone() { try { @@ -236,4 +274,20 @@ public Line clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Line, which is unaffected. For + * example, a line with origin (1,0,0) and direction (0,1,0) is represented + * by: + *
      +     * Line [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)]
      +     * 
      + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/LineSegment.java b/jme3-core/src/main/java/com/jme3/math/LineSegment.java index b01edeb503..a840f1cd09 100644 --- a/jme3-core/src/main/java/com/jme3/math/LineSegment.java +++ b/jme3-core/src/main/java/com/jme3/math/LineSegment.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,18 +50,25 @@ * @author Joshua Slack */ public class LineSegment implements Cloneable, Savable, java.io.Serializable { - static final long serialVersionUID = 1; private Vector3f origin; private Vector3f direction; private float extent; + /** + * Instantiate a zero-length segment at the origin. + */ public LineSegment() { origin = new Vector3f(); direction = new Vector3f(); } + /** + * Instantiate a copy of the specified segment. + * + * @param ls the LineSegment to copy (not null, unaffected) + */ public LineSegment(LineSegment ls) { this.origin = new Vector3f(ls.getOrigin()); this.direction = new Vector3f(ls.getDirection()); @@ -71,6 +78,11 @@ public LineSegment(LineSegment ls) { /** *

      Creates a new LineSegment with the given origin, direction and extent.

      *

      Note that the origin is not one of the ends of the LineSegment, but its center.

      + * + * @param origin the location of the desired midpoint (alias created) + * @param direction the desired direction vector (alias created) + * @param extent the extent: 1/2 of the desired length, assuming direction + * is a unit vector */ public LineSegment(Vector3f origin, Vector3f direction, float extent) { this.origin = origin; @@ -81,6 +93,9 @@ public LineSegment(Vector3f origin, Vector3f direction, float extent) { /** *

      Creates a new LineSegment with a given origin and end. This constructor will calculate the * center, the direction and the extent.

      + * + * @param start location of the negative endpoint (not null, unaffected) + * @param end location of the negative endpoint (not null, unaffected) */ public LineSegment(Vector3f start, Vector3f end) { this.origin = new Vector3f(0.5f * (start.x + end.x), 0.5f * (start.y + end.y), 0.5f * (start.z + end.z)); @@ -89,24 +104,54 @@ public LineSegment(Vector3f start, Vector3f end) { direction.normalizeLocal(); } + /** + * Copy the specified segment to this one. + * + * @param ls the LineSegment to copy (not null, unaffected) + */ public void set(LineSegment ls) { this.origin = new Vector3f(ls.getOrigin()); this.direction = new Vector3f(ls.getDirection()); this.extent = ls.getExtent(); } + /** + * Calculate the distance between this segment and the specified point. + * + * @param point a location vector (not null, unaffected) + * @return the minimum distance (≥0) + */ public float distance(Vector3f point) { return FastMath.sqrt(distanceSquared(point)); } + /** + * Calculate the distance between this segment and another. + * + * @param ls the other LineSegment (not null, unaffected) + * @return the minimum distance (≥0) + */ public float distance(LineSegment ls) { return FastMath.sqrt(distanceSquared(ls)); } + /** + * Calculate the distance between this segment and the specified Ray. + * + * @param r the input Ray (not null, unaffected) + * @return the minimum distance (≥0) + */ public float distance(Ray r) { return FastMath.sqrt(distanceSquared(r)); } + /** + * Calculate the squared distance between this segment and the specified + * point. + * + * @param point location vector of the input point (not null, unaffected) + * @return the square of the minimum distance (≥0) + */ public float distanceSquared(Vector3f point) { TempVars vars = TempVars.get(); Vector3f compVec1 = vars.vect1; @@ -131,6 +176,12 @@ public float distanceSquared(Vector3f point) { return len; } + /** + * Calculate the squared distance between this segment and another. + * + * @param test the other LineSegment (not null, unaffected) + * @return the square of the minimum distance (≥0) + */ public float distanceSquared(LineSegment test) { TempVars vars = TempVars.get(); Vector3f compVec1 = vars.vect1; @@ -416,6 +467,13 @@ public float distanceSquared(LineSegment test) { return FastMath.abs(squareDistance); } + /** + * Calculate the squared distance between this segment and the specified + * Ray. + * + * @param r the input Ray (not null, unaffected) + * @return the square of the minimum distance (≥0) + */ public float distanceSquared(Ray r) { Vector3f kDiff = r.getOrigin().subtract(origin); float fA01 = -r.getDirection().dot(direction); @@ -535,31 +593,66 @@ public float distanceSquared(Ray r) { return FastMath.abs(fSqrDist); } + /** + * Access the direction of this segment. + * + * @return the pre-existing direction vector + */ public Vector3f getDirection() { return direction; } + /** + * Alter the direction of this segment. + * + * @param direction the desired direction vector (alias created!) + */ public void setDirection(Vector3f direction) { this.direction = direction; } + /** + * Read the extent of this segment. + * + * @return the extent + */ public float getExtent() { return extent; } + /** + * Alter the extent of this segment. + * + * @param extent the desired extent + */ public void setExtent(float extent) { this.extent = extent; } + /** + * Access the origin of this segment. + * + * @return the pre-existing location vector + */ public Vector3f getOrigin() { return origin; } + /** + * Alter the origin of this segment. + * + * @param origin the desired location vector (alias created!) + */ public void setOrigin(Vector3f origin) { this.origin = origin; } - // P+e*D + /** + * Determine the location of this segment's positive end. + * + * @param store storage for the result (modified if not null) + * @return a location vector (either store or a new vector) + */ public Vector3f getPositiveEnd(Vector3f store) { if (store == null) { store = new Vector3f(); @@ -567,7 +660,12 @@ public Vector3f getPositiveEnd(Vector3f store) { return origin.add((direction.mult(extent, store)), store); } - // P-e*D + /** + * Determine the location of this segment's negative end. + * + * @param store storage for the result (modified if not null) + * @return a location vector (either store or a new vector) + */ public Vector3f getNegativeEnd(Vector3f store) { if (store == null) { store = new Vector3f(); @@ -575,6 +673,14 @@ public Vector3f getNegativeEnd(Vector3f store) { return origin.subtract((direction.mult(extent, store)), store); } + /** + * Serialize this segment to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(origin, "origin", Vector3f.ZERO); @@ -582,13 +688,26 @@ public void write(JmeExporter e) throws IOException { capsule.write(extent, "extent", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this segment from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); extent = capsule.readFloat("extent", 0); } + /** + * Create a copy of this segment. + * + * @return a new instance, equivalent to this one + */ @Override public LineSegment clone() { try { @@ -601,9 +720,29 @@ public LineSegment clone() { } } + /** + * Returns a string representation of the LineSegment, which is unaffected. + * For example, a segment extending from (1,0,0) to (1,1,0) is represented + * by: + *
      +     * LineSegment [Origin: (1.0, 0.0, 0.0)  Direction: (0.0, 1.0, 0.0)  Extent: 1.0]
      +     * 
      + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [Origin: " + origin + + " Direction: " + direction + " Extent: " + extent + "]"; + } + + /** /** *

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

      This function is float error aware.

      + * + * @param point the location of the input point (not null, unaffected) + * @return true if contained in the box, otherwise false */ public boolean isPointInsideBounds(Vector3f point) { return isPointInsideBounds(point, Float.MIN_VALUE); @@ -613,9 +752,12 @@ public boolean isPointInsideBounds(Vector3f point) { *

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

      This function accepts an error parameter, which * is added to the extent of the bounding box.

      + * + * @param point the location of the input point (not null, unaffected) + * @param error the desired margin for error + * @return true if contained in the box, otherwise false */ public boolean isPointInsideBounds(Vector3f point, float error) { - if (FastMath.abs(point.x - origin.x) > FastMath.abs(direction.x * extent) + error) { return false; } diff --git a/jme3-core/src/main/java/com/jme3/math/MathUtils.java b/jme3-core/src/main/java/com/jme3/math/MathUtils.java index 3f60d2fffd..04709d5eb2 100644 --- a/jme3-core/src/main/java/com/jme3/math/MathUtils.java +++ b/jme3-core/src/main/java/com/jme3/math/MathUtils.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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 com.jme3.renderer.Camera; @@ -7,7 +38,19 @@ * Created by Nehon on 23/04/2017. */ public class MathUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private MathUtils() { + } + /** + * Calculate the natural logarithm of a unit quaternion. + * + * @param q the input Quaternion (not null, normalized, unaffected) + * @param store storage for the result (not null, modified) + * @return the logarithm (store) + */ public static Quaternion log(Quaternion q, Quaternion store) { float a = FastMath.acos(q.w); float sina = FastMath.sin(a); @@ -25,6 +68,13 @@ public static Quaternion log(Quaternion q, Quaternion store) { return store; } + /** + * Calculate the exponential of a pure quaternion. + * + * @param q the input Quaternion (not null, w=0, unaffected) + * @param store storage for the result (not null, modified) + * @return the exponential (store) + */ public static Quaternion exp(Quaternion q, Quaternion store) { float len = FastMath.sqrt(q.x * q.x + q.y * q.y + q.z * q.z); @@ -66,6 +116,15 @@ public static Quaternion slerpNoInvert(Quaternion q1, Quaternion q2, float t, Qu return store; } + /** + * Interpolate between 2 quaternions using Slerp. + * + * @param q1 the desired value for t=0 + * @param q2 the desired value for t=1 + * @param t the fractional parameter (≥0, ≤1) + * @param store storage for the result (not null, modified) + * @return the interpolated Quaternion (store) + */ public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion store) { float dot = (q1.x * q2.x) + (q1.y * q2.y) + (q1.z * q2.z) @@ -134,7 +193,6 @@ public static Quaternion slerp(Quaternion q1, Quaternion q2, float t, Quaternion private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1, Quaternion store, Quaternion tmp) { Quaternion invQn = new Quaternion(-qn.x, -qn.y, -qn.z, qn.w); - log(invQn.mult(qnp1), tmp); log(invQn.mult(qnm1), store); store.addLocal(tmp).multLocal(-1f / 4f); @@ -145,7 +203,6 @@ private static Quaternion spline(Quaternion qnm1, Quaternion qn, Quaternion qnp1 //return qn * (((qni * qnm1).log() + (qni * qnp1).log()) / -4).exp(); } - //! spherical cubic interpolation public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quaternion q3, Quaternion a, Quaternion b, float t, Quaternion store) { @@ -164,7 +221,6 @@ public static Quaternion squad(Quaternion q0, Quaternion q1, Quaternion q2, Quat // return slerpNoInvert(c, d, 2 * t * (1 - t)); } - /** * Returns the shortest distance between a Ray and a segment. * The segment is defined by a start position and an end position in world space @@ -207,14 +263,14 @@ public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vecto double d4343 = p43.x * (double) p43.x + (double) p43.y * p43.y + (double) p43.z * p43.z; double d2121 = p21.x * (double) p21.x + (double) p21.y * p21.y + (double) p21.z * p21.z; - double denom = d2121 * d4343 - d4321 * d4321; - if (Math.abs(denom) < 0.0001) { + double denominator = d2121 * d4343 - d4321 * d4321; + if (Math.abs(denominator) < 0.0001) { vars.release(); return -1; } - double numer = d1343 * d4321 - d1321 * d4343; + double numerator = d1343 * d4321 - d1321 * d4343; - double mua = numer / denom; + double mua = numerator / denominator; double mub = (d1343 + d4321 * (mua)) / d4343; resultSegmentPoint1.x = (float) (p1.x + mua * p21.x); @@ -243,5 +299,4 @@ public static float raySegmentShortestDistance(Ray ray, Vector3f segStart, Vecto vars.release(); return length; } - } 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 ca9e7287b8..5b4d6d2634 100644 --- a/jme3-core/src/main/java/com/jme3/math/Matrix3f.java +++ b/jme3-core/src/main/java/com/jme3/math/Matrix3f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,11 +39,19 @@ import java.util.logging.Logger; /** - * Matrix3f defines a 3x3 matrix. Matrix data is maintained - * internally and is accessible via the get and set methods. Convenience methods - * are used for matrix operations as well as generating a matrix from a given - * set of values. - * + * A 3x3 matrix composed of 9 single-precision elements, used to represent + * linear transformations of 3-D coordinates, such as rotations, reflections, + * and scaling. + * + *

      Element numbering is (row, column), so m01 is the element in row 0, + * column 1. + * + *

      For pure rotations, the {@link com.jme3.math.Quaternion} class provides a + * more efficient representation. + * + *

      With one exception, the methods with names ending in "Local" modify the + * current instance. They are used to avoid creating garbage. + * * @author Mark Powell * @author Joshua Slack */ @@ -52,46 +60,74 @@ public final class Matrix3f implements Savable, Cloneable, java.io.Serializable static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Matrix3f.class.getName()); - protected float m00, m01, m02; - protected float m10, m11, m12; - protected float m20, m21, m22; + /** + * The element in row 0, column 0. + */ + protected float m00; + /** + * The element in row 0, column 1. + */ + protected float m01; + /** + * The element in row 0, column 2. + */ + protected float m02; + /** + * The element in row 1, column 0. + */ + protected float m10; + /** + * The element in row 1, column 1. + */ + protected float m11; + /** + * The element in row 1, column 2. + */ + protected float m12; + /** + * The element in row 2, column 0. + */ + protected float m20; + /** + * The element in row 2, column 1. + */ + protected float m21; + /** + * The element in row 2, column 2. + */ + protected float m22; + /** + * Shared instance of the all-zero matrix. Do not modify! + */ public static final Matrix3f ZERO = new Matrix3f(0, 0, 0, 0, 0, 0, 0, 0, 0); + /** + * Shared instance of the identity matrix (diagonals = 1, other elements = + * 0). Do not modify! + */ public static final Matrix3f IDENTITY = new Matrix3f(); /** - * Constructor instantiates a new Matrix3f object. The - * initial values for the matrix is that of the identity matrix. - * + * Instantiates an identity matrix (diagonals = 1, other elements = 0). */ public Matrix3f() { loadIdentity(); } /** - * constructs a matrix with the given values. - * - * @param m00 - * 0x0 in the matrix. - * @param m01 - * 0x1 in the matrix. - * @param m02 - * 0x2 in the matrix. - * @param m10 - * 1x0 in the matrix. - * @param m11 - * 1x1 in the matrix. - * @param m12 - * 1x2 in the matrix. - * @param m20 - * 2x0 in the matrix. - * @param m21 - * 2x1 in the matrix. - * @param m22 - * 2x2 in the matrix. + * Instantiates a matrix with specified elements. + * + * @param m00 the desired value for row 0, column 0 + * @param m01 the desired value for row 0, column 1 + * @param m02 the desired value for row 0, column 2 + * @param m10 the desired value for row 1, column 0 + * @param m11 the desired value for row 1, column 1 + * @param m12 the desired value for row 1, column 2 + * @param m20 the desired value for row 2, column 0 + * @param m21 the desired value for row 2, column 1 + * @param m22 the desired value for row 2, column 2 */ public Matrix3f(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { - this.m00 = m00; this.m01 = m01; this.m02 = m02; @@ -104,18 +140,17 @@ public Matrix3f(float m00, float m01, float m02, float m10, float m11, } /** - * Copy constructor that creates a new Matrix3f object that - * is the same as the provided matrix. - * - * @param mat - * the matrix to copy. + * Instantiates a copy of the matrix argument. If the argument is null, an + * identity matrix is produced. + * + * @param mat the matrix to copy (unaffected) or null for identity */ public Matrix3f(Matrix3f mat) { set(mat); } /** - * Takes the absolute value of all matrix fields locally. + * Replaces all 9 elements with their absolute values. */ public void absoluteLocal() { m00 = FastMath.abs(m00); @@ -130,13 +165,11 @@ public void absoluteLocal() { } /** - * copy transfers the contents of a given matrix to this - * matrix. If a null matrix is supplied, this matrix is set to the identity - * matrix. - * - * @param matrix - * the matrix to copy. - * @return this + * Copies the matrix argument. If the argument is null, the current instance + * is set to identity (diagonals = 1, other elements = 0). + * + * @param matrix the matrix to copy (unaffected) or null for identity + * @return the (modified) current instance (for chaining) */ public Matrix3f set(Matrix3f matrix) { if (null == matrix) { @@ -156,15 +189,12 @@ public Matrix3f set(Matrix3f matrix) { } /** - * get retrieves a value from the matrix at the given - * position. If the position is invalid a JmeException is - * thrown. - * - * @param i - * the row index. - * @param j - * the colum index. - * @return the value at (i, j). + * Returns the element at the specified position. The matrix is unaffected. + * + * @param i the row index (0, 1, or 2) + * @param j the column index (0, 1, or 2) + * @return the value of the element at (i, j) + * @throws IllegalArgumentException if either index isn't 0, 1, or 2 */ @SuppressWarnings("fallthrough") public float get(int i, int j) { @@ -203,14 +233,17 @@ public float get(int i, int j) { } /** - * get(float[]) returns the matrix in row-major or column-major order. + * Copies the matrix to the specified array. The matrix is unaffected. * - * @param data - * The array to return the data into. This array can be 9 or 16 floats in size. - * Only the upper 3x3 are assigned to in the case of a 16 element array. - * @param rowMajor - * True for row major storage in the array (translation in elements 3, 7, 11 for a 4x4), - * false for column major (translation in elements 12, 13, 14 for a 4x4). + *

      If the array has 16 elements, then the matrix is treated as if it + * contained the 1st 3 rows and 1st 3 columns of a 4x4 matrix. + * + * @param data storage for the elements (not null, length=9 or 16) + * @param rowMajor true to store the elements in row-major order (m00, m01, + * ...) or false to store them in column-major order (m00, m10, ...) + * @throws IndexOutOfBoundsException if {@code data} doesn't have 9 or 16 + * elements + * @see #fillFloatArray(float[], boolean) */ public void get(float[] data, boolean rowMajor) { if (data.length == 9) { @@ -261,16 +294,13 @@ public void get(float[] data, boolean rowMajor) { throw new IndexOutOfBoundsException("Array size must be 9 or 16 in Matrix3f.get()."); } } - + /** - * Normalize this matrix and store the result in the store parameter that is - * returned. - * - * Note that the original matrix is not altered. + * Normalizes the matrix and returns the result in the argument. The current + * instance is unaffected, unless it's {@code store}. * - * @param store the matrix to store the result of the normalization. If this - * parameter is null a new one is created - * @return the normalized matrix + * @param store storage for the result, or null for a new Matrix3f + * @return either {@code store} or a new Matrix3f */ public Matrix3f normalize(Matrix3f store) { if (store == null) { @@ -278,7 +308,7 @@ public Matrix3f normalize(Matrix3f store) { } float mag = 1.0f / FastMath.sqrt( - m00 * m00 + m00 * m00 + m10 * m10 + m20 * m20); @@ -287,7 +317,7 @@ public Matrix3f normalize(Matrix3f store) { store.m20 = m20 * mag; mag = 1.0f / FastMath.sqrt( - m01 * m01 + m01 * m01 + m11 * m11 + m21 * m21); @@ -302,35 +332,40 @@ public Matrix3f normalize(Matrix3f store) { } /** - * Normalize this matrix - * @return this matrix once normalized. + * Normalizes the matrix and returns the (modified) current instance. + * + * @return the (modified) current instance (for chaining) */ public Matrix3f normalizeLocal() { return normalize(this); } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a Vector3f object. - * - * @param i - * the column to retrieve. Must be between 0 and 2. - * @return the column specified by the index. + * Returns the specified column. The matrix is unaffected. + * + *

      If the matrix is a pure rotation, each column contains one of the + * basis vectors. + * + * @param i the column index (0, 1, or 2) + * @return a new Vector3f + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 + * @see Quaternion#getRotationColumn(int) */ public Vector3f getColumn(int i) { return getColumn(i, null); } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a Vector3f object. - * - * @param i - * the column to retrieve. Must be between 0 and 2. - * @param store - * the vector object to store the result in. if null, a new one - * is created. - * @return the column specified by the index. + * Returns the specified column. The matrix is unaffected. + * + *

      If the matrix is a pure rotation, each column contains one of the + * basis vectors. + * + * @param i the column index (0, 1, or 2) + * @param store storage for the result, or null for a new Vector3f + * @return either {@code store} or a new Vector3f + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 + * @see Quaternion#getRotationColumn(int, com.jme3.math.Vector3f) */ public Vector3f getColumn(int i, Vector3f store) { if (store == null) { @@ -360,27 +395,23 @@ public Vector3f getColumn(int i, Vector3f store) { } /** - * getColumn returns one of three rows as specified by the - * parameter. This row is returned as a Vector3f object. - * - * @param i - * the row to retrieve. Must be between 0 and 2. - * @return the row specified by the index. + * Returns the specified row. The matrix is unaffected. + * + * @param i the row index (0, 1, or 2) + * @return a new Vector3f + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 */ public Vector3f getRow(int i) { return getRow(i, null); } /** - * getRow returns one of three rows as specified by the - * parameter. This row is returned as a Vector3f object. - * - * @param i - * the row to retrieve. Must be between 0 and 2. - * @param store - * the vector object to store the result in. if null, a new one - * is created. - * @return the row specified by the index. + * Returns the specified row. The matrix is unaffected. + * + * @param i the row index (0, 1, or 2) + * @param store storage for the result, or null for a new Vector3f + * @return either {@code store} or a new Vector3f + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 */ public Vector3f getRow(int i, Vector3f store) { if (store == null) { @@ -410,10 +441,10 @@ public Vector3f getRow(int i, Vector3f store) { } /** - * toFloatBuffer returns a FloatBuffer object that contains - * the matrix data. - * - * @return matrix data as a FloatBuffer. + * Copies the matrix to a new FloatBuffer. The matrix is unaffected. + * + * @return a new, rewound FloatBuffer containing all 9 elements in row-major + * order (m00, m01, ...) */ public FloatBuffer toFloatBuffer() { FloatBuffer fb = BufferUtils.createFloatBuffer(9); @@ -426,14 +457,14 @@ public FloatBuffer toFloatBuffer() { } /** - * fillFloatBuffer fills a FloatBuffer object with the matrix - * data. - * - * @param fb - * the buffer to fill, starting at current position. Must have - * room for 9 more floats. - * @return matrix data as a FloatBuffer. (position is advanced by 9 and any - * limit set is not changed). + * Copies the matrix to the specified FloatBuffer, starting at its current + * position. The matrix is unaffected. + * + * @param fb storage for the elements (not null, must have space to put 9 + * more floats) + * @param columnMajor true to store the elements in column-major order (m00, + * m10, ...) or false to store them in row-major order (m00, m01, ...) + * @return {@code fb}, its position advanced by 9 */ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { // if (columnMajor){ @@ -448,7 +479,6 @@ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { TempVars vars = TempVars.get(); - fillFloatArray(vars.matrixWrite, columnMajor); fb.put(vars.matrixWrite, 0, 9); @@ -457,43 +487,48 @@ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { return fb; } + /** + * Copies the matrix to the 1st 9 elements of the specified array. The + * matrix is unaffected. + * + * @param f storage for the elements (not null, length≥9) + * @param columnMajor true to store the elements in column-major order (m00, + * m10, ...) or false to store them in row-major order (m00, m01, ...) + * @see #get(float[], boolean) + */ public void fillFloatArray(float[] f, boolean columnMajor) { if (columnMajor) { - f[ 0] = m00; - f[ 1] = m10; - f[ 2] = m20; - f[ 3] = m01; - f[ 4] = m11; - f[ 5] = m21; - f[ 6] = m02; - f[ 7] = m12; - f[ 8] = m22; + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m01; + f[4] = m11; + f[5] = m21; + f[6] = m02; + f[7] = m12; + f[8] = m22; } else { - f[ 0] = m00; - f[ 1] = m01; - f[ 2] = m02; - f[ 3] = m10; - f[ 4] = m11; - f[ 5] = m12; - f[ 6] = m20; - f[ 7] = m21; - f[ 8] = m22; + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m10; + f[4] = m11; + f[5] = m12; + f[6] = m20; + f[7] = m21; + f[8] = m22; } } /** - * - * setColumn sets a particular column of this matrix to that - * represented by the provided vector. - * - * @param i - * the column to set. - * @param column - * the data to set. - * @return this + * Sets the specified column. + * + * @param i which column to set (0, 1, or 2) + * @param column the desired element values (unaffected) or null for none + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 */ public Matrix3f setColumn(int i, Vector3f column) { - if (column == null) { logger.warning("Column is null. Ignoring."); return this; @@ -522,18 +557,14 @@ public Matrix3f setColumn(int i, Vector3f column) { } /** - * - * setRow sets a particular row of this matrix to that - * represented by the provided vector. - * - * @param i - * the row to set. - * @param row - * the data to set. - * @return this + * Sets the specified row. + * + * @param i which row to set (0, 1, or 2) + * @param row the desired element values (unaffected) or null for none + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if {@code i} isn't 0, 1, or 2 */ public Matrix3f setRow(int i, Vector3f row) { - if (row == null) { logger.warning("Row is null. Ignoring."); return this; @@ -562,17 +593,13 @@ public Matrix3f setRow(int i, Vector3f row) { } /** - * set places a given value into the matrix at the given - * position. If the position is invalid a JmeException is - * thrown. - * - * @param i - * the row index. - * @param j - * the colum index. - * @param value - * the value for (i, j). - * @return this + * Sets the specified element. + * + * @param i the row index (0, 1, or 2) + * @param j the column index (0, 1, or 2) + * @param value desired value for the element at (i, j) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if either index isn't 0, 1, or 2 */ @SuppressWarnings("fallthrough") public Matrix3f set(int i, int j, float value) { @@ -620,15 +647,12 @@ public Matrix3f set(int i, int j, float value) { } /** - * - * set sets the values of the matrix to those supplied by the - * 3x3 two dimenion array. - * - * @param matrix - * the new values of the matrix. - * @throws JmeException - * if the array is not of size 9. - * @return this + * Copies all 9 elements from the specified 2-dimensional array. + * + * @param matrix the input array (not null, length=3, the first element + * having length=3, the other elements having length≥3, unaffected) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if the array is the wrong size */ public Matrix3f set(float[][] matrix) { if (matrix.length != 3 || matrix[0].length != 3) { @@ -650,14 +674,13 @@ public Matrix3f set(float[][] matrix) { } /** - * Recreate Matrix using the provided axis. - * - * @param uAxis - * Vector3f - * @param vAxis - * Vector3f - * @param wAxis - * Vector3f + * Configures from the specified column vectors. If the vectors form an + * orthonormal basis, the result will be a pure rotation matrix. + * + * @param uAxis the desired value for column 0 (not null, unaffected) + * @param vAxis the desired value for column 1 (not null, unaffected) + * @param wAxis the desired value for column 2 (not null, unaffected) + * @see Quaternion#fromAxes(com.jme3.math.Vector3f[]) */ public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) { m00 = uAxis.x; @@ -674,26 +697,24 @@ public void fromAxes(Vector3f uAxis, Vector3f vAxis, Vector3f wAxis) { } /** - * set sets the values of this matrix from an array of - * values assuming that the data is rowMajor order; - * - * @param matrix - * the matrix to set the value to. - * @return this + * Copies all 9 elements from the array argument, in row-major order. + * + * @param matrix the input array (not null, length=9, unaffected) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if the array has length != 9 */ public Matrix3f set(float[] matrix) { return set(matrix, true); } /** - * set sets the values of this matrix from an array of - * values; - * - * @param matrix - * the matrix to set the value to. - * @param rowMajor - * whether the incoming data is in row or column major order. - * @return this + * Copies all 9 elements from the specified array. + * + * @param matrix the input array (not null, length=9, unaffected) + * @param rowMajor true to read the elements in row-major order (m00, m01, + * ...) or false to read them in column-major order (m00, m10, ...) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if the array has length != 9 */ public Matrix3f set(float[] matrix, boolean rowMajor) { if (matrix.length != 9) { @@ -726,23 +747,17 @@ public Matrix3f set(float[] matrix, boolean rowMajor) { } /** - * - * set defines the values of the matrix based on a supplied - * Quaternion. It should be noted that all previous values - * will be overridden. - * - * @param quaternion - * the quaternion to create a rotational matrix from. - * @return this + * Configures as a rotation matrix equivalent to the argument. + * + * @param quaternion the input quaternion (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Matrix3f set(Quaternion quaternion) { return quaternion.toRotationMatrix(this); } /** - * loadIdentity sets this matrix to the identity matrix. - * Where all values are zero except those along the diagonal which are one. - * + * Configures as an identity matrix (diagonals = 1, other elements = 0). */ public void loadIdentity() { m01 = m02 = m10 = m12 = m20 = m21 = 0; @@ -750,7 +765,10 @@ public void loadIdentity() { } /** - * @return true if this matrix is identity + * Tests for exact identity. The matrix is unaffected. + * + * @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) @@ -759,14 +777,13 @@ public boolean isIdentity() { } /** - * fromAngleAxis sets this matrix4f to the values specified - * by an angle and an axis of rotation. This method creates an object, so - * use fromAngleNormalAxis if your axis is already normalized. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation. + * Sets all 9 elements to form a pure rotation matrix with the specified + * rotation angle and axis of rotation. This method creates garbage, so use + * {@link #fromAngleNormalAxis(float, com.jme3.math.Vector3f)} if the axis + * is known to be normalized. + * + * @param angle the desired rotation angle (in radians) + * @param axis the desired axis of rotation (not null, unaffected) */ public void fromAngleAxis(float angle, Vector3f axis) { Vector3f normAxis = axis.normalize(); @@ -774,13 +791,13 @@ public void fromAngleAxis(float angle, Vector3f axis) { } /** - * fromAngleNormalAxis sets this matrix4f to the values - * specified by an angle and a normalized axis of rotation. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation (already normalized). + * Sets all 9 elements to form a rotation matrix with the specified rotation + * angle and normalized axis of rotation. If the axis might not be + * normalized, use {@link #fromAngleAxis(float, com.jme3.math.Vector3f)} + * instead. + * + * @param angle the desired rotation angle (in radians) + * @param axis the desired axis of rotation (not null, length=1, unaffected) */ public void fromAngleNormalAxis(float angle, Vector3f axis) { float fCos = FastMath.cos(angle); @@ -808,31 +825,35 @@ public void fromAngleNormalAxis(float angle, Vector3f axis) { } /** - * mult multiplies this matrix by a given matrix. The result - * matrix is returned as a new object. If the given matrix is null, a null - * matrix is returned. - * - * @param mat - * the matrix to multiply this matrix by. - * @return the result matrix. + * Multiplies with the argument matrix and returns the product as a new + * instance. The current instance is unaffected. + * + *

      Note that matrix multiplication is noncommutative, so generally + * q * p != p * q. + * + * @param mat the right factor (not null, unaffected) + * @return {@code this} times {@code mat} (a new Matrix3f) */ public Matrix3f mult(Matrix3f mat) { return mult(mat, null); } /** - * mult multiplies this matrix by a given matrix. The result - * matrix is returned as a new object. - * - * @param mat - * the matrix to multiply this matrix by. - * @param product - * the matrix to store the result in. if null, a new matrix3f is - * created. It is safe for mat and product to be the same object. - * @return a matrix3f object containing the result of this operation + * Multiplies with the specified matrix and returns the product in a 3rd + * matrix. The current instance is unaffected unless it's {@code product}. + * + *

      Note that matrix multiplication is noncommutative, so generally + * q * p != p * q. + * + *

      It is safe for {@code mat} and {@code product} to be the same object. + * + * @param mat the right factor (not null, unaffected unless it's {@code + * product}) + * @param product storage for the product, or null for a new Matrix3f + * @return {@code this} times {@code mat} (either {@code product} or a new + * Matrix3f) */ public Matrix3f mult(Matrix3f mat, Matrix3f product) { - float temp00, temp01, temp02; float temp10, temp11, temp12; float temp20, temp21, temp22; @@ -864,31 +885,34 @@ public Matrix3f mult(Matrix3f mat, Matrix3f product) { } /** - * mult multiplies this matrix by a given - * Vector3f object. The result vector is returned. If the - * given vector is null, null will be returned. - * - * @param vec - * the vector to multiply this matrix by. - * @return the result vector. + * Applies the linear transformation to the vector argument and returns the + * result as a new vector. The matrix is unaffected. + * + *

      This can also be described as multiplying the matrix by a column + * vector. + * + * @param vec the coordinates to transform (not null, unaffected) + * @return a new Vector3f */ public Vector3f mult(Vector3f vec) { return mult(vec, null); } /** - * Multiplies this 3x3 matrix by the 1x3 Vector vec and stores the result in - * product. - * - * @param vec - * The Vector3f to multiply. - * @param product - * The Vector3f to store the result, it is safe for this to be - * the same as vec. - * @return The given product vector. + * Applies the linear transformation to specified vector and stores the + * result in another vector. The matrix is unaffected. + * + *

      This can also be described as multiplying the matrix by a column + * vector. + * + *

      It is safe for {@code vec} and {@code product} to be the same object. + * + * @param vec the coordinates to transform (not null, unaffected unless it's + * {@code product}) + * @param product storage for the result, or null for a new Vector3f + * @return either {@code product} or a new Vector3f */ public Vector3f mult(Vector3f vec, Vector3f product) { - if (null == product) { product = new Vector3f(); } @@ -904,12 +928,11 @@ public Vector3f mult(Vector3f vec, Vector3f product) { } /** - * multLocal multiplies this matrix internally by - * a given float scale factor. - * - * @param scale - * the value to scale by. - * @return this Matrix3f + * Multiplies by the scalar argument and returns the (modified) current + * instance. + * + * @param scale the scaling factor + * @return the (modified) current instance (for chaining) */ public Matrix3f multLocal(float scale) { m00 *= scale; @@ -925,14 +948,13 @@ public Matrix3f multLocal(float scale) { } /** - * multLocal multiplies this matrix by a given - * Vector3f object. The result vector is stored inside the - * passed vector, then returned . If the given vector is null, null will be - * returned. - * - * @param vec - * the vector to multiply this matrix by. - * @return The passed vector after multiplication + * Applies the linear transformation to the vector argument and returns the + * (modified) argument. If the argument is null, null is returned. + * + *

      Despite the name, the current instance is unaffected. + * + * @param vec the vector to transform (modified if not null) + * @return {@code vec} or null */ public Vector3f multLocal(Vector3f vec) { if (vec == null) { @@ -947,23 +969,24 @@ public Vector3f multLocal(Vector3f vec) { } /** - * mult multiplies this matrix by a given matrix. The result - * matrix is saved in the current matrix. If the given matrix is null, - * nothing happens. The current matrix is returned. This is equivalent to - * this*=mat - * - * @param mat - * the matrix to multiply this matrix by. - * @return This matrix, after the multiplication + * Multiplies by the matrix argument and returns the (modified) current + * instance. + * + *

      Note that matrix multiplication is noncommutative, so generally + * q * p != p * q. + * + * @param mat the right factor (not null, unaffected unless it's + * {@code this}) + * @return the (modified) current instance */ public Matrix3f multLocal(Matrix3f mat) { return mult(mat, this); } /** - * Transposes this matrix in place. Returns this matrix for chaining - * - * @return This matrix after transpose + * Transposes the matrix and returns the (modified) current instance. + * + * @return the (modified) current instance */ public Matrix3f transposeLocal() { // float[] tmp = new float[9]; @@ -986,18 +1009,26 @@ public Matrix3f transposeLocal() { } /** - * Inverts this matrix as a new Matrix3f. - * - * @return The new inverse matrix + * Returns the multiplicative inverse as a new matrix. If the current + * instance is singular, an all-zero matrix is returned. In either case, the + * current instance is unaffected. + * + * @return a new Matrix3f */ public Matrix3f invert() { return invert(null); } /** - * Inverts this matrix and stores it in the given store. - * - * @return The store + * Returns the multiplicative inverse in the specified storage. If the + * current instance is singular, an all-zero matrix is returned. In either + * case, the current instance is unaffected. + * + *

      If {@code this} and {@code store} are the same object, the result is + * undefined. Use {@link #invertLocal()} instead. + * + * @param store storage for the result, or null for a new Matrix3f + * @return either {@code store} or a new Matrix3f */ public Matrix3f invert(Matrix3f store) { if (store == null) { @@ -1024,9 +1055,10 @@ public Matrix3f invert(Matrix3f store) { } /** - * Inverts this matrix locally. - * - * @return this + * Inverts the matrix and returns the (modified) current instance. If the + * current instance is singular, all elements will be set to zero. + * + * @return the (modified) current instance (for chaining) */ public Matrix3f invertLocal() { float det = determinant(); @@ -1059,20 +1091,23 @@ public Matrix3f invertLocal() { } /** - * Returns a new matrix representing the adjoint of this matrix. - * - * @return The adjoint matrix + * Returns the adjoint as a new matrix. The current instance is unaffected. + * + * @return a new Matrix3f */ public Matrix3f adjoint() { return adjoint(null); } /** - * Places the adjoint of this matrix in store (creates store if null.) - * - * @param store - * The matrix to store the result in. If null, a new matrix is created. - * @return store + * Returns the adjoint in the specified storage. The current instance is + * unaffected. + * + *

      If {@code this} and {@code store} are the same object, the result is + * undefined. + * + * @param store storage for the result, or null for a new Matrix3f + * @return either {@code store} or a new Matrix3f */ public Matrix3f adjoint(Matrix3f store) { if (store == null) { @@ -1093,8 +1128,8 @@ public Matrix3f adjoint(Matrix3f store) { } /** - * determinant generates the determinant of this matrix. - * + * Returns the determinant. The matrix is unaffected. + * * @return the determinant */ public float determinant() { @@ -1106,9 +1141,9 @@ public float determinant() { } /** - * Sets all of the values in this matrix to zero. - * - * @return this matrix + * Sets all elements to zero. + * + * @return the (modified) current instance (for chaining) */ public Matrix3f zero() { m00 = m01 = m02 = m10 = m11 = m12 = m20 = m21 = m22 = 0.0f; @@ -1116,21 +1151,25 @@ public Matrix3f zero() { } /** - * transpose locally transposes this Matrix. - * This is inconsistent with general value vs local semantics, but is - * preserved for backwards compatibility. Use transposeNew() to transpose - * to a new object (value). - * - * @return this object for chaining. + * Transposes the matrix and returns the (modified) current instance. + * + *

      This method is inconsistent with JME naming conventions, but has been + * preserved for backwards compatibility. To preserve the current instance, + * {@link #transposeNew()}. + * + *

      TODO deprecate in favor of transposeLocal() + * + * @return the (modified) current instance (for chaining) */ public Matrix3f transpose() { return transposeLocal(); } /** - * transposeNew returns a transposed version of this matrix. + * Returns the transpose as a new instance. The current instance is + * unaffected. * - * @return The new Matrix3f object. + * @return a new Matrix3f */ public Matrix3f transposeNew() { Matrix3f ret = new Matrix3f(m00, m10, m20, m01, m11, m21, m02, m12, m22); @@ -1138,14 +1177,18 @@ public Matrix3f transposeNew() { } /** - * toString returns the string representation of this object. - * It is in a format of a 3x3 matrix. For example, an identity matrix would - * be represented by the following string. com.jme.math.Matrix3f
      [
      - * 1.0 0.0 0.0
      - * 0.0 1.0 0.0
      - * 0.0 0.0 1.0
      ]
      - * - * @return the string representation of this object. + * Returns a string representation of the matrix, which is unaffected. For + * example, the identity matrix is represented by: + *

      +     * Matrix3f
      +     * [
      +     *  1.0  0.0  0.0
      +     *  0.0  1.0  0.0
      +     *  0.0  0.0  1.0
      +     * ]
      +     * 
      + * + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -1175,12 +1218,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. - * - * @return the hashcode for this instance of Matrix4f. + * Returns a hash code. If two matrices have identical values, they will + * have the same hash code. The matrix is unaffected. + * + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override @@ -1202,15 +1243,17 @@ public int hashCode() { } /** - * are these two matrices the same? they are is they both have the same mXX values. + * 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 for equality - * @return true if they are equal + * @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) { - if (!(o instanceof Matrix3f) || o == null) { + if (o == null || o.getClass() != getClass()) { return false; } @@ -1252,6 +1295,14 @@ public boolean equals(Object o) { return true; } + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param e the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule cap = e.getCapsule(this); cap.write(m00, "m00", 1); @@ -1265,8 +1316,16 @@ public void write(JmeExporter e) throws IOException { cap.write(m22, "m22", 1); } - public void read(JmeImporter e) throws IOException { - InputCapsule cap = e.getCapsule(this); + /** + * De-serializes from the specified importer, for example when loading from a + * J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule cap = importer.getCapsule(this); m00 = cap.readFloat("m00", 1); m01 = cap.readFloat("m01", 0); m02 = cap.readFloat("m02", 0); @@ -1279,15 +1338,15 @@ public void read(JmeImporter e) throws IOException { } /** - * A function for creating a rotation matrix that rotates a vector called - * "start" into another vector called "end". - * - * @param start - * normalized non-zero starting vector - * @param end - * normalized non-zero ending vector - * @see "Tomas M�ller, John Hughes \"Efficiently Building a Matrix to Rotate \ - * One Vector to Another\" Journal of Graphics Tools, 4(4):1-4, 1999" + * Configures a rotation matrix that rotates the specified start direction + * to the specified end direction. + * + *

      See Tomas Möller, John F. Hughes "Efficiently Building a Matrix to + * Rotate One Vector to Another" Journal of Graphics Tools, 4(4):1-4, 1999. + * + * @param start the start direction (not null, length=1, unaffected) + * @param end the end direction (not null, length=1, unaffected) + * */ public void fromStartEndVectors(Vector3f start, Vector3f end) { Vector3f v = new Vector3f(); @@ -1370,11 +1429,10 @@ public void fromStartEndVectors(Vector3f start, Vector3f end) { } /** - * scale scales the operation performed by this matrix on a - * per-component basis. + * Scales each column by the corresponding element of the argument. * - * @param scale - * The scale applied to each of the X, Y and Z output values. + * @param scale the scale factors: X scales column 0, Y scales column 1, Z + * scales column 2 (not null, unaffected) */ public void scale(Vector3f scale) { m00 *= scale.x; @@ -1388,6 +1446,12 @@ public void scale(Vector3f scale) { m22 *= scale.z; } + /** + * Tests for an identity matrix, with 0.0001 tolerance. The current instance + * is unaffected. + * + * @return true if all elements are within 0.0001 of an identity matrix + */ static boolean equalIdentity(Matrix3f mat) { if (Math.abs(mat.m00 - 1) > 1e-4) { return false; @@ -1423,6 +1487,11 @@ static boolean equalIdentity(Matrix3f mat) { return true; } + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance, equivalent to the current one + */ @Override public Matrix3f clone() { try { 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 ff51e3c7bf..4f9a6e2f35 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,22 +34,20 @@ import com.jme3.export.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; - import java.io.IOException; import java.nio.FloatBuffer; import java.util.logging.Logger; /** - * Matrix4f defines and maintains a 4x4 matrix in row major order. - * This matrix is intended for use in a translation and rotational capacity. - * It provides convenience methods for creating the matrix from a multitude - * of sources. - * - * Matrices are stored assuming column vectors on the right, with the translation - * in the rightmost column. Element numbering is row,column, so m03 is the zeroth - * row, third column, which is the "x" translation part. This means that the implicit - * storage order is column major. However, the get() and set() functions on float - * arrays default to row major order! + * 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 + * numbering is (row,column), so m03 is the row 0, column 3, which is the X + * translation. + * + *

      Methods with names ending in "Local" modify the current instance. They are + * used to avoid creating garbage. * * @author Mark Powell * @author Joshua Slack @@ -59,24 +57,105 @@ public final class Matrix4f implements Savable, Cloneable, java.io.Serializable static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Matrix4f.class.getName()); - public float m00, m01, m02, m03; - public float m10, m11, m12, m13; - public float m20, m21, m22, m23; - public float m30, m31, m32, m33; + /** + * The element in row 0, column 0. + */ + public float m00; + /** + * The element in row 0, column 1. + */ + public float m01; + /** + * The element in row 0, column 2. + */ + public float m02; + /** + * The element in row 0, column 3 (the X translation). + */ + public float m03; + /** + * The element in row 1, column 0. + */ + public float m10; + /** + * The element in row 1, column 1. + */ + public float m11; + /** + * The element in row 1, column 2. + */ + public float m12; + /** + * The element in row 1, column 3 (the Y translation). + */ + public float m13; + /** + * The element in row 2, column 0. + */ + public float m20; + /** + * The element in row 2, column 1. + */ + public float m21; + /** + * The element in row 2, column 2. + */ + public float m22; + /** + * The element in row 2, column 3 (the Z translation). + */ + public float m23; + /** + * The element in row 3, column 0. + */ + public float m30; + /** + * The element in row 3, column 1. + */ + public float m31; + /** + * The element in row 3, column 2. + */ + public float m32; + /** + * The element in row 3, column 3. + */ + public float m33; + /** + * an instance of the zero matrix (all elements = 0) + */ public static final Matrix4f ZERO = new Matrix4f(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0); + /** + * an instance of the identity matrix (diagonals = 1, other elements = 0) + */ public static final Matrix4f IDENTITY = new Matrix4f(); /** - * Constructor instantiates a new Matrix that is set to the - * identity matrix. - * + * Instantiates an identity matrix (diagonals = 1, other elements = 0). */ public Matrix4f() { loadIdentity(); } /** - * constructs a matrix with the given values. + * Create a Matrix4f with the specified element values. + * + * @param m00 the desired value for row 0, column 0 + * @param m01 the desired value for row 0, column 1 + * @param m02 the desired value for row 0, column 2 + * @param m03 the desired value for row 0, column 3 + * @param m10 the desired value for row 1, column 0 + * @param m11 the desired value for row 1, column 1 + * @param m12 the desired value for row 1, column 2 + * @param m13 the desired value for row 1, column 3 + * @param m20 the desired value for row 2, column 0 + * @param m21 the desired value for row 2, column 1 + * @param m22 the desired value for row 2, column 2 + * @param m23 the desired value for row 2, column 3 + * @param m30 the desired value for row 3, column 0 + * @param m31 the desired value for row 3, column 1 + * @param m32 the desired value for row 3, column 2 + * @param m33 the desired value for row 3, column 3 */ public Matrix4f(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, @@ -102,34 +181,31 @@ public Matrix4f(float m00, float m01, float m02, float m03, } /** - * Create a new Matrix4f, given data in column-major format. + * Create a Matrix4f from the specified array. * - * @param array - * An array of 16 floats in column-major format (translation in elements 12, 13 and 14). + * @param array the source array: 16 floats in column-major order + * (translation in elements 12, 13, and 14) */ public Matrix4f(float[] array) { set(array, false); } /** - * Constructor instantiates a new Matrix that is set to the - * provided matrix. This constructor copies a given Matrix. If the provided - * matrix is null, the constructor sets the matrix to the identity. - * - * @param mat - * the matrix to copy. + * Create a Matrix4f that duplicates the specified matrix. If + * null is specified, the new matrix is initialized to identity (diagonals = + * 1, other elements = 0). + * + * @param mat the source matrix (unaffected, may be null) */ public Matrix4f(Matrix4f mat) { copy(mat); } /** - * copy transfers the contents of a given matrix to this - * matrix. If a null matrix is supplied, this matrix is set to the identity - * matrix. - * - * @param matrix - * the matrix to copy. + * Copy all elements of the specified matrix to this matrix. If null is + * specified, load identity (diagonals = 1, other elements = 0). + * + * @param matrix the source matrix (may be null, unaffected) */ public void copy(Matrix4f matrix) { if (null == matrix) { @@ -186,24 +262,20 @@ public void fromFrame(Vector3f location, Vector3f direction, Vector3f up, Vector } /** - * get retrieves the values of this object into - * a float array in row-major order. - * - * @param matrix - * the matrix to set the values into. + * Copy all elements to a float array, in row-major order. + * + * @param matrix the destination array (not null, length=16) */ public void get(float[] matrix) { get(matrix, true); } /** - * set retrieves the values of this object into - * a float array. - * - * @param matrix - * the matrix to set the values into. - * @param rowMajor - * whether the outgoing data is in row or column major order. + * Copy all elements to a float array. + * + * @param matrix the destination array (not null, length=16) + * @param rowMajor true to store in row-major order, false to store in + * column-major order */ public void get(float[] matrix, boolean rowMajor) { if (matrix.length != 16) { @@ -249,15 +321,12 @@ public void get(float[] matrix, boolean rowMajor) { } /** - * get retrieves a value from the matrix at the given - * position. If the position is invalid a JmeException is - * thrown. - * - * @param i - * the row index. - * @param j - * the colum index. - * @return the value at (i, j). + * Retrieve the element at the specified position. + * + * @param i the row index of the element to retrieve (0, 1, 2, or 3) + * @param j the column index of the element to retrieve (0, 1, 2, or 3) + * @return the value at (i, j) + * @throws IllegalArgumentException if either index is invalid. */ @SuppressWarnings("fallthrough") public float get(int i, int j) { @@ -313,27 +382,22 @@ public float get(int i, int j) { } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a float array of length 4. - * - * @param i - * the column to retrieve. Must be between 0 and 3. - * @return the column specified by the index. + * Copy the specified column to a new float array. + * + * @param i the index of the column to copy (0, 1, 2, or 3) + * @return a new array with length=4 */ public float[] getColumn(int i) { return getColumn(i, null); } /** - * getColumn returns one of three columns specified by the - * parameter. This column is returned as a float[4]. - * - * @param i - * the column to retrieve. Must be between 0 and 3. - * @param store - * the float array to store the result in. if null, a new one - * is created. - * @return the column specified by the index. + * Copy the specified column to a float array. + * + * @param i the index of the column to copy (0, 1, 2, or 3) + * @param store storage for the result (modified) or null to create a new + * array + * @return either store or a new array with length=4 */ public float[] getColumn(int i, float[] store) { if (store == null) { @@ -372,14 +436,10 @@ public float[] getColumn(int i, float[] store) { } /** - * - * setColumn sets a particular column of this matrix to that - * represented by the provided vector. - * - * @param i - * the column to set. - * @param column - * the data to set. + * Load the specified column from the specified array. + * + * @param i the index of the column to fill (0, 1, 2, or 3) + * @param column the source array (unaffected) or null */ public void setColumn(int i, float[] column) { @@ -419,16 +479,12 @@ public void setColumn(int i, float[] column) { } /** - * set places a given value into the matrix at the given - * position. If the position is invalid a JmeException is - * thrown. - * - * @param i - * the row index. - * @param j - * the colum index. - * @param value - * the value for (i, j). + * Store the specified value at the specified position. + * + * @param i the row index of the element to set (0, 1, 2, or 3) + * @param j the column index of the element to set (0, 1, 2, or 3) + * @param value the value for element (i, j) + * @throws IllegalArgumentException if either index is invalid. */ @SuppressWarnings("fallthrough") public void set(int i, int j, float value) { @@ -500,13 +556,10 @@ public void set(int i, int j, float value) { } /** - * set sets the values of this matrix from an array of - * values. - * - * @param matrix - * the matrix to set the value to. - * @throws JmeException - * if the array is not of size 16. + * Load all elements from the specified 4x4 array. + * + * @param matrix the source array (not null, unaffected) + * @throws IllegalArgumentException if the source array isn't 4x4. */ public void set(float[][] matrix) { if (matrix.length != 4 || matrix[0].length != 4) { @@ -531,10 +584,26 @@ public void set(float[][] matrix) { m32 = matrix[3][2]; m33 = matrix[3][3]; } - - + /** - * Sets the values of this matrix + * Load the specified element values. + * + * @param m00 the desired value for row 0, column 0 + * @param m01 the desired value for row 0, column 1 + * @param m02 the desired value for row 0, column 2 + * @param m03 the desired value for row 0, column 3 + * @param m10 the desired value for row 1, column 0 + * @param m11 the desired value for row 1, column 1 + * @param m12 the desired value for row 1, column 2 + * @param m13 the desired value for row 1, column 3 + * @param m20 the desired value for row 2, column 0 + * @param m21 the desired value for row 2, column 1 + * @param m22 the desired value for row 2, column 2 + * @param m23 the desired value for row 2, column 3 + * @param m30 the desired value for row 3, column 0 + * @param m31 the desired value for row 3, column 1 + * @param m32 the desired value for row 3, column 2 + * @param m33 the desired value for row 3, column 3 */ public void set(float m00, float m01, float m02, float m03, float m10, float m11, float m12, float m13, @@ -560,10 +629,10 @@ public void set(float m00, float m01, float m02, float m03, } /** - * set sets the values of this matrix from another matrix. + * Copy all elements of the specified matrix to this matrix. * - * @param matrix - * the matrix to read the value from. + * @param matrix the source matrix (not null, unaffected) + * @return this (modified) */ public Matrix4f set(Matrix4f matrix) { m00 = matrix.m00; @@ -586,24 +655,21 @@ public Matrix4f set(Matrix4f matrix) { } /** - * set sets the values of this matrix from an array of - * values assuming that the data is rowMajor order; - * - * @param matrix - * the matrix to set the value to. + * Load all elements from the specified array. + * + * @param matrix the source array, in row-major order (not null, length=16, + * unaffected) */ public void set(float[] matrix) { set(matrix, true); } /** - * set sets the values of this matrix from an array of - * values; - * - * @param matrix - * the matrix to set the value to. - * @param rowMajor - * whether the incoming data is in row or column major order. + * Load all elements from the specified array. + * + * @param matrix the source array (not null, length=16, unaffected) + * @param rowMajor true if the source array is in row-major order, false if + * it's in column-major order */ public void set(float[] matrix, boolean rowMajor) { if (matrix.length != 16) { @@ -648,6 +714,11 @@ public void set(float[] matrix, boolean rowMajor) { } } + /** + * Generate the transpose. + * + * @return a new Matrix4f with the rows and columns transposed + */ public Matrix4f transpose() { float[] tmp = new float[16]; get(tmp, true); @@ -656,9 +727,9 @@ public Matrix4f transpose() { } /** - * transpose locally transposes this Matrix. - * - * @return this object for chaining. + * Transpose in place. + * + * @return this (transposed) */ public Matrix4f transposeLocal() { float tmp = m01; @@ -689,24 +760,21 @@ public Matrix4f transposeLocal() { } /** - * toFloatBuffer returns a FloatBuffer object that contains - * the matrix data. - * - * @return matrix data as a FloatBuffer. + * Copy all elements to a new, direct FloatBuffer. + * + * @return a rewound buffer containing all 16 element values in row-major + * order */ public FloatBuffer toFloatBuffer() { return toFloatBuffer(false); } /** - * toFloatBuffer returns a FloatBuffer object that contains the - * matrix data. - * - * @param columnMajor - * if true, this buffer should be filled with column major data, - * otherwise it will be filled row major. - * @return matrix data as a FloatBuffer. The position is set to 0 for - * convenience. + * Copy all elements to a new, direct FloatBuffer. + * + * @param columnMajor true to store in column-major order, false to store in + * row-major order + * @return a rewound buffer containing all 16 element values */ public FloatBuffer toFloatBuffer(boolean columnMajor) { FloatBuffer fb = BufferUtils.createFloatBuffer(16); @@ -716,27 +784,26 @@ public FloatBuffer toFloatBuffer(boolean columnMajor) { } /** - * fillFloatBuffer fills a FloatBuffer object with - * the matrix data. - * @param fb the buffer to fill, must be correct size - * @return matrix data as a FloatBuffer. + * Copy all elements to an existing FloatBuffer, starting at its current + * position, in row-major order. + * + * @param fb the destination buffer (not null, must have space remaining for + * 16 floats) + * @return the destination buffer, its position advanced by 16 */ public FloatBuffer fillFloatBuffer(FloatBuffer fb) { return fillFloatBuffer(fb, false); } /** - * fillFloatBuffer fills a FloatBuffer object with the matrix - * data. - * - * @param fb - * the buffer to fill, starting at current position. Must have - * room for 16 more floats. - * @param columnMajor - * if true, this buffer should be filled with column major data, - * otherwise it will be filled row major. - * @return matrix data as a FloatBuffer. (position is advanced by 16 and any - * limit set is not changed). + * Copy all elements to an existing FloatBuffer, starting at its current + * position. + * + * @param fb the destination buffer (not null, must have space remaining for + * 16 floats) + * @param columnMajor true to store in column-major order, false to store in + * row-major order + * @return the destination buffer, its position advanced by 16 */ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { // if (columnMajor) { @@ -753,7 +820,6 @@ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { TempVars vars = TempVars.get(); - fillFloatArray(vars.matrixWrite, columnMajor); fb.put(vars.matrixWrite, 0, 16); @@ -762,18 +828,25 @@ public FloatBuffer fillFloatBuffer(FloatBuffer fb, boolean columnMajor) { return fb; } + /** + * Copy all elements to a float array. + * + * @param f the destination array (not null, length≥16, modified) + * @param columnMajor true → column-major order, false → row-major + * order + */ public void fillFloatArray(float[] f, boolean columnMajor) { if (columnMajor) { - f[ 0] = m00; - f[ 1] = m10; - f[ 2] = m20; - f[ 3] = m30; - f[ 4] = m01; - f[ 5] = m11; - f[ 6] = m21; - f[ 7] = m31; - f[ 8] = m02; - f[ 9] = m12; + f[0] = m00; + f[1] = m10; + f[2] = m20; + f[3] = m30; + f[4] = m01; + f[5] = m11; + f[6] = m21; + f[7] = m31; + f[8] = m02; + f[9] = m12; f[10] = m22; f[11] = m32; f[12] = m03; @@ -781,16 +854,16 @@ public void fillFloatArray(float[] f, boolean columnMajor) { f[14] = m23; f[15] = m33; } else { - f[ 0] = m00; - f[ 1] = m01; - f[ 2] = m02; - f[ 3] = m03; - f[ 4] = m10; - f[ 5] = m11; - f[ 6] = m12; - f[ 7] = m13; - f[ 8] = m20; - f[ 9] = m21; + f[0] = m00; + f[1] = m01; + f[2] = m02; + f[3] = m03; + f[4] = m10; + f[5] = m11; + f[6] = m12; + f[7] = m13; + f[8] = m20; + f[9] = m21; f[10] = m22; f[11] = m23; f[12] = m30; @@ -801,20 +874,22 @@ public void fillFloatArray(float[] f, boolean columnMajor) { } /** - * readFloatBuffer reads value for this matrix from a FloatBuffer. - * @param fb the buffer to read from, must be correct size - * @return this data as a FloatBuffer. + * Load from the specified FloatBuffer, in row-major order. + * + * @param fb the source buffer, must have 16 floats remaining to get + * @return this (modified) */ public Matrix4f readFloatBuffer(FloatBuffer fb) { return readFloatBuffer(fb, false); } /** - * readFloatBuffer reads value for this matrix from a FloatBuffer. - * @param fb the buffer to read from, must be correct size - * @param columnMajor if true, this buffer should be filled with column - * major data, otherwise it will be filled row major. - * @return this data as a FloatBuffer. + * Load from the specified FloatBuffer. + * + * @param fb the source buffer, must have 16 floats remaining to get + * @param columnMajor if true, the buffer contains column-major data, + * otherwise it contains row-major data. + * @return this (modified) */ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { @@ -857,9 +932,7 @@ public Matrix4f readFloatBuffer(FloatBuffer fb, boolean columnMajor) { } /** - * loadIdentity sets this matrix to the identity matrix, - * namely all zeros with ones along the diagonal. - * + * Configures as an identity matrix (diagonals = 1, other elements = 0). */ public void loadIdentity() { m01 = m02 = m03 = 0.0f; @@ -869,7 +942,19 @@ public void loadIdentity() { m00 = m11 = m22 = m33 = 1.0f; } - public void fromFrustum(float near, float far, float left, float right, float top, float bottom, boolean parallel) { + /** + * Load a perspective-view transform with the specified clipping planes. + * + * @param near the coordinate of the near plane + * @param far the coordinate of the far plane + * @param left the coordinate of the left plane + * @param right the coordinate of the right plane + * @param top the coordinate of the top plane + * @param bottom the coordinate of the bottom plane + * @param parallel true → parallel sides, false → perspective + */ + public void fromFrustum(float near, float far, float left, float right, + float top, float bottom, boolean parallel) { loadIdentity(); if (parallel) { // scale @@ -893,7 +978,7 @@ public void fromFrustum(float near, float far, float left, float right, float to // A m02 = (right + left) / (right - left); - // B + // B m12 = (top + bottom) / (top - bottom); // C @@ -905,14 +990,13 @@ public void fromFrustum(float near, float far, float left, float right, float to } /** - * fromAngleAxis sets this matrix4f to the values specified - * by an angle and an axis of rotation. This method creates an object, so - * use fromAngleNormalAxis if your axis is already normalized. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation. + * Load a 3-D rotation specified by an angle and axis. If the axis is + * already normalized, use + * {@link #fromAngleNormalAxis(float, com.jme3.math.Vector3f)} instead + * because it's more efficient. + * + * @param angle the angle to rotate (in radians) + * @param axis the axis of rotation (not null) */ public void fromAngleAxis(float angle, Vector3f axis) { Vector3f normAxis = axis.normalize(); @@ -920,13 +1004,11 @@ public void fromAngleAxis(float angle, Vector3f axis) { } /** - * fromAngleNormalAxis sets this matrix4f to the values - * specified by an angle and a normalized axis of rotation. - * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation (already normalized). + * Load a 3-D rotation specified by an angle and axis. Assumes the axis is + * already normalized. + * + * @param angle the angle to rotate (in radians) + * @param axis the axis of rotation (not null, already normalized) */ public void fromAngleNormalAxis(float angle, Vector3f axis) { zero(); @@ -957,10 +1039,9 @@ public void fromAngleNormalAxis(float angle, Vector3f axis) { } /** - * mult multiplies this matrix by a scalar. - * - * @param scalar - * the scalar to multiply this matrix by. + * Multiplies in place by the scalar argument. + * + * @param scalar the scaling factor to apply to all elements */ public void multLocal(float scalar) { m00 *= scalar; @@ -981,6 +1062,12 @@ public void multLocal(float scalar) { m33 *= scalar; } + /** + * Multiply by the specified scalar. + * + * @param scalar the scale factor to apply to all elements + * @return a new Matrix4f with every element scaled + */ public Matrix4f mult(float scalar) { Matrix4f out = new Matrix4f(); out.set(this); @@ -988,6 +1075,14 @@ public Matrix4f mult(float scalar) { return out; } + /** + * Multiply by the specified scalar. + * + * @param scalar the scale factor to apply to all elements + * @param store storage for the result (modified) or null to create a new + * matrix + * @return a scaled matrix (either store or a new instance) + */ public Matrix4f mult(float scalar, Matrix4f store) { store.set(this); store.multLocal(scalar); @@ -995,29 +1090,22 @@ public Matrix4f mult(float scalar, Matrix4f store) { } /** - * mult multiplies this matrix with another matrix. The - * result matrix will then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. - * - * @param in2 - * the matrix to multiply this matrix by. - * @return the resultant matrix + * Right-multiply by the specified matrix. (This matrix is the left factor.) + * + * @param in2 the right factor (not null, unaffected) + * @return the product, this times in2 (a new instance) */ public Matrix4f mult(Matrix4f in2) { return mult(in2, null); } /** - * mult multiplies this matrix with another matrix. The - * result matrix will then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. - * - * @param in2 - * the matrix to multiply this matrix by. - * @param store - * where to store the result. It is safe for in2 and store to be - * the same object. - * @return the resultant matrix + * Right-multiply by the specified matrix. (This matrix is the left factor.) + * + * @param in2 the right factor (not null) + * @param store storage for the result (modified) or null to create a new + * matrix. It is safe for in2 and store to be the same object. + * @return the product, this times in2 (either store or a new instance) */ public Matrix4f mult(Matrix4f in2, Matrix4f store) { if (store == null) { @@ -1095,7 +1183,6 @@ public Matrix4f mult(Matrix4f in2, Matrix4f store) { + m32 * in2.m23 + m33 * in2.m33; - store.m00 = m[0]; store.m01 = m[1]; store.m02 = m[2]; @@ -1117,40 +1204,33 @@ public Matrix4f mult(Matrix4f in2, Matrix4f store) { } /** - * mult multiplies this matrix with another matrix. The - * results are stored internally and a handle to this matrix will - * then be returned. This matrix will be on the left hand - * side, while the parameter matrix will be on the right. - * - * @param in2 - * the matrix to multiply this matrix by. - * @return the resultant matrix + * Right-multiply in place, by the specified matrix. (This matrix is the + * left factor.) + * + * @param in2 the right factor (not null) + * @return this (modified) */ public Matrix4f multLocal(Matrix4f in2) { return mult(in2, this); } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned as a new Vector3f. - * - * @param vec - * vec to multiply against. - * @return the rotated vector. + * Apply this 3-D coordinate transform to the specified Vector3f. + * + * @param vec the vector to transform (not null) + * @return a new vector */ public Vector3f mult(Vector3f vec) { return mult(vec, null); } /** - * mult multiplies a vector about a rotation matrix and adds - * translation. The resulting vector is returned. - * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * Apply this 3-D coordinate transform to the specified Vector3f. + * + * @param vec the vector to transform (not null) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f mult(Vector3f vec, Vector3f store) { if (store == null) { @@ -1166,26 +1246,22 @@ public Vector3f mult(Vector3f vec, Vector3f store) { } /** - * mult multiplies a Vector4f about a rotation - * matrix. The resulting vector is returned as a new Vector4f. + * Multiply the specified Vector4f by this matrix. * - * @param vec - * vec to multiply against. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @return a new vector or null */ public Vector4f mult(Vector4f vec) { return mult(vec, null); } /** - * mult multiplies a Vector4f about a rotation - * matrix. The resulting vector is returned. + * Multiply the specified Vector4f by this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the product (either store or a new vector) or null */ public Vector4f mult(Vector4f vec, Vector4f store) { if (null == vec) { @@ -1206,27 +1282,22 @@ public Vector4f mult(Vector4f vec, Vector4f store) { } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. + * Multiply the specified Vector4f by the transform of this matrix. * - * @param vec - * vec to multiply against. - * - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @return a new vector or null */ public Vector4f multAcross(Vector4f vec) { return multAcross(vec, null); } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. + * Multiply the specified Vector4f by the transform of this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. created if null is passed. - * @return the rotated vector. + * @param vec the vector to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the product (either store or a new vector) or null */ public Vector4f multAcross(Vector4f vec, Vector4f store) { if (null == vec) { @@ -1247,14 +1318,12 @@ public Vector4f multAcross(Vector4f vec, Vector4f store) { } /** - * multNormal multiplies a vector about a rotation matrix, but - * does not add translation. The resulting vector is returned. + * Rotate and scale the specified vector, but don't translate it. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f multNormal(Vector3f vec, Vector3f store) { if (store == null) { @@ -1270,14 +1339,13 @@ public Vector3f multNormal(Vector3f vec, Vector3f store) { } /** - * multNormal multiplies a vector about a rotation matrix, but - * does not add translation. The resulting vector is returned. + * Rotate and scale the specified vector by the transpose, but don't + * translate it. * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. Created if null is passed. - * @return the rotated vector. + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) */ public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { if (store == null) { @@ -1293,14 +1361,11 @@ public Vector3f multNormalAcross(Vector3f vec, Vector3f store) { } /** - * mult multiplies a vector about a rotation matrix and adds - * translation. The w value is returned as a result of - * multiplying the last column of the matrix by 1.0 - * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. + * Apply this perspective transform to the specified Vector3f. Return the W + * value, calculated by dotting the vector with the last row. + * + * @param vec the vector to transform (not null, unaffected) + * @param store storage for the result (not null, modified) * @return the W value */ public float multProj(Vector3f vec, Vector3f store) { @@ -1312,14 +1377,13 @@ public float multProj(Vector3f vec, Vector3f store) { } /** - * mult multiplies a vector about a rotation matrix. The - * resulting vector is returned. - * - * @param vec - * vec to multiply against. - * @param store - * a vector to store the result in. created if null is passed. - * @return the rotated vector. + * Apply the transform of this 3-D coordinate transform to the specified + * Vector3f. + * + * @param vec the vector to transform (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * vector + * @return the transformed vector (either store or a new vector) or null */ public Vector3f multAcross(Vector3f vec, Vector3f store) { if (null == vec) { @@ -1339,14 +1403,12 @@ public Vector3f multAcross(Vector3f vec, Vector3f store) { } /** - * mult multiplies a quaternion about a matrix. The - * resulting vector is returned. + * Multiply the specified Quaternion by this matrix. * - * @param vec - * vec to multiply against. - * @param store - * a quaternion to store the result in. created if null is passed. - * @return store = this * vec + * @param vec the Quaternion to multiply (unaffected) or null + * @param store storage for the result (modified) or null to create a new + * Quaternion + * @return the product (either store or a new Quaternion) or null */ public Quaternion mult(Quaternion vec, Quaternion store) { @@ -1371,12 +1433,10 @@ public Quaternion mult(Quaternion vec, Quaternion store) { } /** - * mult multiplies an array of 4 floats against this rotation - * matrix. The results are stored directly in the array. (vec4f x mat4f) - * - * @param vec4f - * float array (size 4) to multiply against the matrix. - * @return the vec4f for chaining. + * Multiply the specified float array by this matrix. + * + * @param vec4f the array to multiply or null + * @return vec4f (modified) or null */ public float[] mult(float[] vec4f) { if (null == vec4f || vec4f.length != 4) { @@ -1395,12 +1455,10 @@ public float[] mult(float[] vec4f) { } /** - * mult multiplies an array of 4 floats against this rotation - * matrix. The results are stored directly in the array. (vec4f x mat4f) - * - * @param vec4f - * float array (size 4) to multiply against the matrix. - * @return the vec4f for chaining. + * Multiply the specified float array by the transform of this matrix. + * + * @param vec4f the array to multiply or null + * @return vec4f (modified) or null */ public float[] multAcross(float[] vec4f) { if (null == vec4f || vec4f.length != 4) { @@ -1419,18 +1477,21 @@ public float[] multAcross(float[] vec4f) { } /** - * Inverts this matrix as a new Matrix4f. - * - * @return The new inverse matrix + * Generate the inverse. + * + * @return a new instance */ public Matrix4f invert() { return invert(null); } /** - * Inverts this matrix and stores it in the given store. - * - * @return The store + * Generate the inverse. + * + * @param store storage for the result (modified) or null to create a new + * matrix + * @return either store or a new instance + * @throws ArithmeticException if cannot be inverted */ public Matrix4f invert(Matrix4f store) { if (store == null) { @@ -1479,9 +1540,10 @@ public Matrix4f invert(Matrix4f store) { } /** - * Inverts this matrix locally. - * - * @return this + * Inverts in place. If the current instance is singular, the matrix is + * zeroed. + * + * @return the (inverted) current instance (for chaining) */ public Matrix4f invertLocal() { @@ -1544,14 +1606,22 @@ public Matrix4f invertLocal() { } /** - * Returns a new matrix representing the adjoint of this matrix. - * - * @return The adjoint matrix + * Generate the adjoint. + * + * @return a new instance */ public Matrix4f adjoint() { return adjoint(null); } + /** + * Load with the specified coordinate transform. The effective sequence of + * operations is: scale, then rotate, then translate. + * + * @param position the desired translation (not null, unaffected) + * @param scale the desired scale factors (not null, unaffected) + * @param rotMat the desired rotation (not null, unaffected) + */ public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { // Ordering: // 1. Scale @@ -1580,11 +1650,11 @@ public void setTransform(Vector3f position, Vector3f scale, Matrix3f rotMat) { } /** - * Places the adjoint of this matrix in store (creates store if null.) - * - * @param store - * The matrix to store the result in. If null, a new matrix is created. - * @return store + * Generate the adjoint. + * + * @param store storage for the result (modified) or null to create a new + * matrix + * @return either store or a new instance */ public Matrix4f adjoint(Matrix4f store) { if (store == null) { @@ -1625,9 +1695,9 @@ public Matrix4f adjoint(Matrix4f store) { } /** - * determinant generates the determinate of this matrix. - * - * @return the determinate + * Calculate the determinant. + * + * @return the determinant */ public float determinant() { float fA0 = m00 * m11 - m01 * m10; @@ -1647,9 +1717,9 @@ public float determinant() { } /** - * Sets all of the values in this matrix to zero. - * - * @return this matrix + * Sets all elements to zero. + * + * @return the (modified) current instance (for chaining) */ public Matrix4f zero() { m00 = m01 = m02 = m03 = 0.0f; @@ -1659,6 +1729,12 @@ public Matrix4f zero() { return this; } + /** + * Add the specified matrix. + * + * @param mat the matrix to add (not null) + * @return the sum (a new instance) + */ public Matrix4f add(Matrix4f mat) { Matrix4f result = new Matrix4f(); result.m00 = this.m00 + mat.m00; @@ -1681,10 +1757,9 @@ public Matrix4f add(Matrix4f mat) { } /** - * add adds the values of a parameter matrix to this matrix. - * - * @param mat - * the matrix to add to this. + * Sum in place, with the specified matrix. + * + * @param mat the matrix to add (not null) */ public void addLocal(Matrix4f mat) { m00 += mat.m00; @@ -1705,29 +1780,73 @@ public void addLocal(Matrix4f mat) { m33 += mat.m33; } + /** + * Determine the translation component of this 3-D coordinate transform. + * + * @return a new translation vector + */ public Vector3f toTranslationVector() { return new Vector3f(m03, m13, m23); } + /** + * Returns the translation component of the coordinate transform. + * + * @param vector storage for the result (not null, modified) + * @return the translation component (in {@code vector}) for chaining + */ public Vector3f toTranslationVector(Vector3f vector) { return vector.set(m03, m13, m23); } + /** + * Determine the rotation component of this 3-D coordinate transform. + * + *

      Assumes (but does not verify) that the transform consists entirely of + * translation, rotation, and positive scaling -- no reflection or shear. + * + * @return a new rotation Quaternion + */ public Quaternion toRotationQuat() { Quaternion quat = new Quaternion(); quat.fromRotationMatrix(toRotationMatrix()); return quat; } + /** + * Returns the rotation component of the coordinate transform. + * + *

      Assumes (but does not verify) that the transform consists entirely of + * translation, rotation, and positive scaling -- no reflection or shear. + * + * @param q storage for the result (not null, modified) + * @return the rotation component (in {@code q}) for chaining + */ public Quaternion toRotationQuat(Quaternion q) { return q.fromRotationMatrix(m00, m01, m02, m10, m11, m12, m20, m21, m22); } + /** + * Determine the rotation component of this 3-D coordinate transform. + * + *

      If the transform includes scaling or reflection or shear, the result + * might not be a valid rotation matrix. + * + * @return a new Matrix3f + */ public Matrix3f toRotationMatrix() { return new Matrix3f(m00, m01, m02, m10, m11, m12, m20, m21, m22); } + /** + * Determines the rotation component of the coordinate transform. + * + *

      If the transform includes scaling or reflection or shear, the result + * might not be a valid rotation matrix. + * + * @param mat storage for the result (not null, modified) + */ public void toRotationMatrix(Matrix3f mat) { mat.m00 = m00; mat.m01 = m01; @@ -1738,43 +1857,45 @@ public void toRotationMatrix(Matrix3f mat) { mat.m20 = m20; mat.m21 = m21; mat.m22 = m22; - } + } - /** - * Retrieves the scale vector from the matrix. - * - * @return the scale vector - */ - public Vector3f toScaleVector() { - Vector3f result = new Vector3f(); - this.toScaleVector(result); - return result; - } + /** + * Determine the scale component of this 3-D coordinate transform. + * + *

      All components of the result will be non-negative, even if the + * coordinate transform includes reflection. + * + * @return a new Vector3f + */ + public Vector3f toScaleVector() { + Vector3f result = new Vector3f(); + this.toScaleVector(result); + return result; + } /** - * Retrieves the scale vector from the matrix and stores it into a given - * vector. + * Determines the scale component of the coordinate transform. + * + *

      All components of the result will be non-negative, even if the + * coordinate transform includes reflection. * - * @param store the vector where the scale will be stored - * @return the store vector + * @param store storage for the result (not null, modified) + * @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); - float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); - float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); + float scaleX = (float) Math.sqrt(m00 * m00 + m10 * m10 + m20 * m20); + float scaleY = (float) Math.sqrt(m01 * m01 + m11 * m11 + m21 * m21); + float scaleZ = (float) Math.sqrt(m02 * m02 + m12 * m12 + m22 * m22); store.set(scaleX, scaleY, scaleZ); return store; } /** - * Sets the scale. - * - * @param x - * the X scale - * @param y - * the Y scale - * @param z - * the Z scale + * 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 + * @param z the desired scale factor for the Z axis */ public void setScale(float x, float y, float z) { @@ -1804,22 +1925,20 @@ public void setScale(float x, float y, float z) { } /** - * Sets the scale. - * - * @param scale - * the scale vector to set + * Alters the scale component of the coordinate transform. + * + * @param scale the desired scale factors (not null, unaffected) */ public void setScale(Vector3f scale) { this.setScale(scale.x, scale.y, scale.z); } /** - * setTranslation will set the matrix's translation values. - * - * @param translation - * the new values for the translation. - * @throws JmeException - * if translation is not size 3. + * Alter the translation component of this 3-D coordinate transform. + * + * @param translation the desired translation (not null, length=3, + * unaffected) + * @throws IllegalArgumentException if translation doesn't have length=3. */ public void setTranslation(float[] translation) { if (translation.length != 3) { @@ -1832,14 +1951,11 @@ public void setTranslation(float[] translation) { } /** - * setTranslation will set the matrix's translation values. - * - * @param x - * value of the translation on the x axis - * @param y - * value of the translation on the y axis - * @param z - * value of the translation on the z axis + * Alter the translation component of this 3-D coordinate transform. + * + * @param x the desired X-axis offset + * @param y the desired Y-axis offset + * @param z the desired Z-axis offset */ public void setTranslation(float x, float y, float z) { m03 = x; @@ -1848,10 +1964,9 @@ public void setTranslation(float x, float y, float z) { } /** - * setTranslation will set the matrix's translation values. + * Alters the translation component of the coordinate transform. * - * @param translation - * the new values for the translation. + * @param translation the desired translation (not null, unaffected) */ public void setTranslation(Vector3f translation) { m03 = translation.x; @@ -1860,13 +1975,11 @@ public void setTranslation(Vector3f translation) { } /** - * setInverseTranslation will set the matrix's inverse - * translation values. - * - * @param translation - * the new values for the inverse translation. - * @throws JmeException - * if translation is not size 3. + * Alter the inverse-translation component of this 3-D coordinate transform. + * + * @param translation the desired inverse translation (not null, length=3, + * unaffected) + * @throws IllegalArgumentException if translation doesn't have length=3. */ public void setInverseTranslation(float[] translation) { if (translation.length != 3) { @@ -1879,13 +1992,11 @@ public void setInverseTranslation(float[] translation) { } /** - * angleRotation sets this matrix to that of a rotation about - * three axes (x, y, z). Where each axis has a specified rotation in - * degrees. These rotations are expressed in a single Vector3f - * object. - * - * @param angles - * the angles to rotate. + * Load a rotation around three axes (x, y, z). Where each axis has a + * specified rotation in degrees. These rotations are expressed in a single + * Vector3f object. + * + * @param angles the desired rotation angles for each axis (in degrees) */ public void angleRotation(Vector3f angles) { float angle; @@ -1917,26 +2028,20 @@ public void angleRotation(Vector3f angles) { } /** - * setRotationQuaternion builds a rotation from a - * Quaternion. - * - * @param quat - * the quaternion to build the rotation from. - * @throws NullPointerException - * if quat is null. + * Load a rotation from a Quaternion. + * + * @param quat the desired rotation (not null, unaffected) + * @throws NullPointerException if quat is null. */ public void setRotationQuaternion(Quaternion quat) { quat.toRotationMatrix(this); } /** - * setInverseRotationRadians builds an inverted rotation from - * Euler angles that are in radians. - * - * @param angles - * the Euler angles in radians. - * @throws JmeException - * if angles is not size 3. + * Load an inverted rotation from Euler angles in radians. + * + * @param angles the desired Euler angles (in radians, not null, length=3) + * @throws IllegalArgumentException if angles doesn't have length=3. */ public void setInverseRotationRadians(float[] angles) { if (angles.length != 3) { @@ -1967,13 +2072,10 @@ public void setInverseRotationRadians(float[] angles) { } /** - * setInverseRotationDegrees builds an inverted rotation from - * Euler angles that are in degrees. - * - * @param angles - * the Euler angles in degrees. - * @throws JmeException - * if angles is not size 3. + * Load an inverted rotation from Euler angles in degrees. + * + * @param angles the desired Euler angles (in degrees, not null, length=3) + * @throws IllegalArgumentException if angles doesn't have length=3. */ public void setInverseRotationDegrees(float[] angles) { if (angles.length != 3) { @@ -1988,14 +2090,11 @@ public void setInverseRotationDegrees(float[] angles) { } /** - * - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. - * - * @param vec - * the Vector3f data to be translated. - * @throws JmeException - * if the size of the Vector3f is not 3. + * Inverse translate the specified vector using the translation component of + * this 3-D coordinate transform. + * + * @param vec the vector to translate (not null, length=3, modified) + * @throws IllegalArgumentException if vec doesn't have length=3. */ public void inverseTranslateVect(float[] vec) { if (vec.length != 3) { @@ -2009,14 +2108,10 @@ public void inverseTranslateVect(float[] vec) { } /** - * - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. - * - * @param data - * the Vector3f to be translated. - * @throws JmeException - * if the size of the Vector3f is not 3. + * Inverse translate the specified Vector3f using the translation component + * of this 3-D coordinate transform. + * + * @param data the Vector3f to translate (not null, modified) */ public void inverseTranslateVect(Vector3f data) { data.x -= m03; @@ -2025,14 +2120,10 @@ public void inverseTranslateVect(Vector3f data) { } /** - * - * inverseTranslateVect translates a given Vector3f by the - * translation part of this matrix. - * - * @param data - * the Vector3f to be translated. - * @throws JmeException - * if the size of the Vector3f is not 3. + * Translate the specified Vector3f using the translation component of this + * 3-D coordinate transform. + * + * @param data the Vector3f to translate (not null, modified) */ public void translateVect(Vector3f data) { data.x += m03; @@ -2041,12 +2132,10 @@ public void translateVect(Vector3f data) { } /** - * - * inverseRotateVect rotates a given Vector3f by the rotation - * part of this matrix. - * - * @param vec - * the Vector3f to be rotated. + * Inverse rotate the specified Vector3f using the rotation component of + * this 3-D coordinate transform. + * + * @param vec the Vector3f to inverse rotate (not null, modified) */ public void inverseRotateVect(Vector3f vec) { float vx = vec.x, vy = vec.y, vz = vec.z; @@ -2056,6 +2145,12 @@ public void inverseRotateVect(Vector3f vec) { vec.z = vx * m02 + vy * m12 + vz * m22; } + /** + * Rotate the specified Vector3f using the rotation component of this 3-D + * coordinate transform. + * + * @param vec the Vector3f to rotate (not null, modified) + */ public void rotateVect(Vector3f vec) { float vx = vec.x, vy = vec.y, vz = vec.z; @@ -2065,15 +2160,19 @@ public void rotateVect(Vector3f vec) { } /** - * toString returns the string representation of this object. - * It is in a format of a 4x4 matrix. For example, an identity matrix would - * be represented by the following string. com.jme.math.Matrix3f
      [
      - * 1.0 0.0 0.0 0.0
      - * 0.0 1.0 0.0 0.0
      - * 0.0 0.0 1.0 0.0
      - * 0.0 0.0 0.0 1.0
      ]
      - * - * @return the string representation of this object. + * Returns a string representation of the matrix, which is unaffected. For + * example, the identity matrix is represented by: + *

      +     * Matrix4f
      +     * [
      +     *  1.0  0.0  0.0  0.0
      +     *  0.0  1.0  0.0  0.0
      +     *  0.0  0.0  1.0  0.0
      +     *  0.0  0.0  0.0  1.0
      +     * ]
      +     * 
      + * + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -2118,12 +2217,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. - * - * @return the hashcode for this instance of Matrix4f. + * Returns a hash code. If two matrices have identical values, they will + * have the same hash code. The matrix is unaffected. + * + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override @@ -2153,15 +2250,17 @@ public int hashCode() { } /** - * are these two matrices the same? they are is they both have the same mXX values. + * 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 for equality - * @return true if they are equal + * @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) { - if (!(o instanceof Matrix4f) || o == null) { + if (o == null || o.getClass() != getClass()) { return false; } @@ -2225,6 +2324,14 @@ public boolean equals(Object o) { return true; } + /** + * Serialize to the specified exporter, for example when saving to a J3O + * file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule cap = e.getCapsule(this); cap.write(m00, "m00", 1); @@ -2245,8 +2352,16 @@ public void write(JmeExporter e) throws IOException { cap.write(m33, "m33", 1); } - public void read(JmeImporter e) throws IOException { - InputCapsule cap = e.getCapsule(this); + /** + * De-serialize from the specified importer, for example when loading from a + * J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule cap = importer.getCapsule(this); m00 = cap.readFloat("m00", 1); m01 = cap.readFloat("m01", 0); m02 = cap.readFloat("m02", 0); @@ -2266,7 +2381,9 @@ public void read(JmeImporter e) throws IOException { } /** - * @return true if this matrix is identity + * Test for exact identity. + * + * @return true if this is an exact identity, otherwise false */ public boolean isIdentity() { return (m00 == 1 && m01 == 0 && m02 == 0 && m03 == 0) @@ -2276,10 +2393,9 @@ public boolean isIdentity() { } /** - * Apply a scale to this matrix. - * - * @param scale - * the scale to apply + * Scale by the specified Vector3f. + * + * @param scale the scale factors to apply */ public void scale(Vector3f scale) { m00 *= scale.getX(); @@ -2362,6 +2478,80 @@ public void multLocal(Quaternion rotation) { multLocal(matrix4f); } + /** + * Tests for approximate equality with the specified matrix, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param other the matrix to compare (unaffected) or null for none + * @param epsilon the tolerance for each element + * @return true if all 16 elements are within tolerance, otherwise false + */ + public boolean isSimilar(Matrix4f other, float epsilon) { + if (other == null) { + return false; + } + + if (Float.compare(Math.abs(other.m00 - m00), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m01 - m01), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m02 - m02), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m03 - m03), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m10 - m10), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m11 - m11), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m12 - m12), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m13 - m13), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m20 - m20), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m21 - m21), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m22 - m22), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m23 - m23), epsilon) > 0) { + return false; + } + + if (Float.compare(Math.abs(other.m30 - m30), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m31 - m31), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m32 - m32), epsilon) > 0) { + return false; + } + if (Float.compare(Math.abs(other.m33 - m33), epsilon) > 0) { + return false; + } + + return true; + } + + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance with the same element values + */ @Override public Matrix4f clone() { try { diff --git a/jme3-core/src/main/java/com/jme3/math/Plane.java b/jme3-core/src/main/java/com/jme3/math/Plane.java index 3da90b2bc3..82f577b350 100644 --- a/jme3-core/src/main/java/com/jme3/math/Plane.java +++ b/jme3-core/src/main/java/com/jme3/math/Plane.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,7 @@ * This provides methods for calculating a "distance" of a point from this * plane. The distance is pseudo due to the fact that it can be negative if the * point is on the non-normal side of the plane. - * + * * @author Mark Powell * @author Joshua Slack * @author Ian McClean @@ -52,18 +52,30 @@ public class Plane implements Savable, Cloneable, java.io.Serializable { private static final Logger logger = Logger .getLogger(Plane.class.getName()); + /** + * Describe the relationship between a point and a plane. + */ public static enum Side { + /** + * a point that lies in the plane + */ None, + /** + * a point on the side with positive pseudo-distance + */ Positive, + /** + * a point on the side with negative pseudo-distance + */ Negative } - /** + /** * Vector normal to the plane. */ protected Vector3f normal = new Vector3f(); - /** + /** * Constant of the plane. See formula in class definition. */ protected float constant; @@ -78,7 +90,7 @@ public Plane() { /** * Constructor instantiates a new Plane object. The normal * and constant values are set at creation. - * + * * @param normal * the normal of the plane. * @param constant @@ -105,7 +117,7 @@ public Plane(Vector3f normal, Vector3f displacement) { /** * setNormal sets the normal of the plane. - * + * * @param normal * the new normal of the plane. */ @@ -119,14 +131,17 @@ public void setNormal(Vector3f normal) { /** * setNormal sets the normal of the plane. * + * @param x the desired X component for the normal vector + * @param y the desired Y component for the normal vector + * @param z the desired Z component for the normal vector */ public void setNormal(float x, float y, float z) { - this.normal.set(x,y,z); + this.normal.set(x, y, z); } /** * getNormal retrieves the normal of the plane. - * + * * @return the normal of the plane. */ public Vector3f getNormal() { @@ -136,7 +151,7 @@ public Vector3f getNormal() { /** * setConstant sets the constant value that helps define the * plane. - * + * * @param constant * the new constant value. */ @@ -146,27 +161,49 @@ public void setConstant(float constant) { /** * getConstant returns the constant of the plane. - * + * * @return the constant of the plane. */ public float getConstant() { return constant; } - public Vector3f getClosestPoint(Vector3f point, Vector3f store){ + /** + * Find the point in this plane that's nearest to the specified point. + * + * @param point the location of the input point (not null, unaffected) + * @param store storage for the result (not null, modified) + * @return the location of the nearest point (store) + */ + public Vector3f getClosestPoint(Vector3f point, Vector3f store) { // float t = constant - normal.dot(point); // return store.set(normal).multLocal(t).addLocal(point); float t = (constant - normal.dot(point)) / normal.dot(normal); return store.set(normal).multLocal(t).addLocal(point); } - public Vector3f getClosestPoint(Vector3f point){ + /** + * Find the point in this plane that's nearest to the specified point. + * + * @param point location vector of the input point (not null, unaffected) + * @return a new location vector in this plane + */ + public Vector3f getClosestPoint(Vector3f point) { return getClosestPoint(point, new Vector3f()); } - public Vector3f reflect(Vector3f point, Vector3f store){ - if (store == null) + /** + * Reflect the specified point in this plane. + * + * @param point location vector of the input point (not null, unaffected) + * @param store storage for the result (modified if not null) + * @return a location vector for the reflected point (either store or a new + * vector) + */ + public Vector3f reflect(Vector3f point, Vector3f store) { + if (store == null) { store = new Vector3f(); + } float d = pseudoDistance(point); store.set(normal).negateLocal().multLocal(d * 2f); @@ -179,7 +216,7 @@ public Vector3f reflect(Vector3f point, Vector3f store){ * a provided point. If the point is on the negative side of the plane the * distance returned is negative, otherwise it is positive. If the point is * on the plane, it is zero. - * + * * @param point * the point to check. * @return the signed distance from the plane to a point. @@ -192,7 +229,7 @@ public float pseudoDistance(Vector3f point) { * whichSide returns the side at which a point lies on the * plane. The positive values returned are: NEGATIVE_SIDE, POSITIVE_SIDE and * NO_SIDE. - * + * * @param point * the point to check. * @return the side at which the point lies. @@ -208,19 +245,19 @@ public Side whichSide(Vector3f point) { } } - public boolean isOnPlane(Vector3f point){ + public boolean isOnPlane(Vector3f point) { float dist = pseudoDistance(point); - if (dist < FastMath.FLT_EPSILON && dist > -FastMath.FLT_EPSILON) + if (dist < FastMath.FLT_EPSILON && dist > -FastMath.FLT_EPSILON) { return true; - else + } else { return false; + } } /** * Initialize this plane using the three points of the given triangle. - * - * @param t - * the triangle + * + * @param t the triangle */ public void setPlanePoints(AbstractTriangle t) { setPlanePoints(t.get1(), t.get2(), t.get3()); @@ -229,17 +266,17 @@ public void setPlanePoints(AbstractTriangle t) { /** * Initialize this plane using a point of origin and a normal. * - * @param origin - * @param normal + * @param origin the desired origin location (not null, unaffected) + * @param normal the desired normal vector (not null, unaffected) */ - public void setOriginNormal(Vector3f origin, Vector3f normal){ + public void setOriginNormal(Vector3f origin, Vector3f normal) { this.normal.set(normal); this.constant = normal.x * origin.x + normal.y * origin.y + normal.z * origin.z; } /** * Initialize the Plane using the given 3 points as coplanar. - * + * * @param v1 * the first point * @param v2 @@ -255,12 +292,11 @@ public void setPlanePoints(Vector3f v1, Vector3f v2, Vector3f v3) { } /** - * toString returns a string that represents the string - * representation of this plane. It represents the normal as a - * Vector3f object, so the format is the following: - * com.jme.math.Plane [Normal: org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, - * Z=ZZ.ZZZZ] - Constant: CC.CCCCC] - * + * toString returns a string representation of this plane. + * It represents the normal as a Vector3f, so the format is: + * + * Plane [Normal: (X.XXXX, Y.YYYY, Z.ZZZZ) - Constant: C.CCCC] + * * @return the string representation of this plane. */ @Override @@ -269,18 +305,39 @@ public String toString() { + constant + "]"; } + /** + * Serialize this plane to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(normal, "normal", Vector3f.ZERO); capsule.write(constant, "constant", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this plane from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); normal = (Vector3f) capsule.readSavable("normal", Vector3f.ZERO.clone()); constant = capsule.readFloat("constant", 0); } + /** + * Create a copy of this plane. + * + * @return a new instance, equivalent to this one + */ @Override public Plane clone() { try { 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 1ce5a1c0df..3da3feb42f 100644 --- a/jme3-core/src/main/java/com/jme3/math/Quaternion.java +++ b/jme3-core/src/main/java/com/jme3/math/Quaternion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,14 +38,13 @@ import java.util.logging.Logger; /** - * Quaternion defines a single example of a more general class of - * hypercomplex numbers. Quaternions extends a rotation in three dimensions to a - * rotation in four dimensions. This avoids "gimbal lock" and allows for smooth - * continuous rotation. - * - * Quaternion is defined by four floating point numbers: {x y z - * w}. - * + * Used to efficiently represent rotations and orientations in 3-dimensional + * space, without risk of gimbal lock. Each instance has 4 single-precision + * components: 3 imaginary components (X, Y, and Z) and a real component (W). + * + *

      Mathematically, quaternions are an extension of complex numbers. In + * mathematics texts, W often appears first, but in JME it always comes last. + * * @author Mark Powell * @author Joshua Slack */ @@ -55,21 +54,46 @@ public final class Quaternion implements Savable, Cloneable, java.io.Serializabl private static final Logger logger = Logger.getLogger(Quaternion.class.getName()); /** - * Represents the identity quaternion rotation (0, 0, 0, 1). + * Shared instance of the identity quaternion (0, 0, 0, 1). Do not modify! + * + *

      This is the usual representation for a null rotation. */ public static final Quaternion IDENTITY = new Quaternion(); + /** + * Another shared instance of the identity quaternion (0, 0, 0, 1). Do not + * modify! + */ public static final Quaternion DIRECTION_Z = new Quaternion(); + /** + * Shared instance of the zero quaternion (0, 0, 0, 0). Do not modify! + * + *

      The zero quaternion doesn't represent any valid rotation. + */ public static final Quaternion ZERO = new Quaternion(0, 0, 0, 0); - + static { DIRECTION_Z.fromAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); } - protected float x, y, z, w; + /** + * The first imaginary (X) component. Not an angle! + */ + protected float x; + /** + * The 2nd imaginary (Y) component. Not an angle! + */ + protected float y; + /** + * The 3rd imaginary (Z) component. Not an angle! + */ + protected float z; + /** + * The real (W) component. Not an angle! + */ + protected float w; /** - * Constructor instantiates a new Quaternion object - * initializing all values to zero, except w which is initialized to 1. - * + * Instantiates an identity quaternion: all components zeroed except + * {@code w}, which is set to 1. */ public Quaternion() { x = 0; @@ -79,17 +103,12 @@ public Quaternion() { } /** - * Constructor instantiates a new Quaternion object from the - * given list of parameters. + * Instantiates a quaternion with the specified components. * - * @param x - * the x value of the quaternion. - * @param y - * the y value of the quaternion. - * @param z - * the z value of the quaternion. - * @param w - * the w value of the quaternion. + * @param x the desired X component + * @param y the desired Y component + * @param z the desired Z component + * @param w the desired W component */ public Quaternion(float x, float y, float z, float w) { this.x = x; @@ -98,35 +117,50 @@ public Quaternion(float x, float y, float z, float w) { this.w = w; } + /** + * Returns the X component. The quaternion is unaffected. + * + * @return the value of the {@link #x} component + */ public float getX() { return x; } + /** + * Returns the Y component. The quaternion is unaffected. + * + * @return the value of the {@link #y} component + */ public float getY() { return y; } + /** + * Returns the Z component. The quaternion is unaffected. + * + * @return the value of the {@link #z} component + */ public float getZ() { return z; } + /** + * Returns the W (real) component. The quaternion is unaffected. + * + * @return the value of the {@link #w} component + */ public float getW() { return w; } /** - * sets the data in a Quaternion object from the given list - * of parameters. + * Sets all 4 components to specified values. * - * @param x - * the x value of the quaternion. - * @param y - * the y value of the quaternion. - * @param z - * the z value of the quaternion. - * @param w - * the w value of the quaternion. - * @return this + * @param x the desired X component + * @param y the desired Y component + * @param z the desired Z component + * @param w the desired W component + * @return the (modified) current instance (for chaining) */ public Quaternion set(float x, float y, float z, float w) { this.x = x; @@ -137,13 +171,10 @@ public Quaternion set(float x, float y, float z, float w) { } /** - * Sets the data in this Quaternion object to be equal to the - * passed Quaternion object. The values are copied producing - * a new object. + * Copies all 4 components from the argument. * - * @param q - * The Quaternion to copy values from. - * @return this + * @param q the quaternion to copy (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Quaternion set(Quaternion q) { this.x = q.x; @@ -154,38 +185,38 @@ public Quaternion set(Quaternion q) { } /** - * Constructor instantiates a new Quaternion object from a - * collection of rotation angles. + * Instantiates a quaternion from Tait-Bryan angles, applying the rotations + * in x-z-y extrinsic order or y-z'-x" intrinsic order. * - * @param angles - * the angles of rotation (x, y, z) that will define the - * Quaternion. + * @param angles an array of Tait-Bryan angles (in radians, exactly 3 + * elements, the X angle in {@code angles[0]}, the Y angle in {@code + * angles[1]}, and the Z angle in {@code angles[2]}, not null, + * unaffected) */ public Quaternion(float[] angles) { fromAngles(angles); } /** - * Constructor instantiates a new Quaternion object from an - * interpolation between two other quaternions. + * Instantiates a quaternion by interpolating between the specified + * quaternions. * - * @param q1 - * the first quaternion. - * @param q2 - * the second quaternion. - * @param interp - * the amount to interpolate between the two quaternions. + *

      Uses + * {@link #slerp(com.jme3.math.Quaternion, com.jme3.math.Quaternion, float)}, + * which is fast but inaccurate. + * + * @param q1 the desired value when interp=0 (not null, unaffected) + * @param q2 the desired value when interp=1 (not null, may be modified) + * @param interp the fractional change amount */ public Quaternion(Quaternion q1, Quaternion q2, float interp) { slerp(q1, q2, interp); } /** - * Constructor instantiates a new Quaternion object from an - * existing quaternion, creating a copy. + * Instantiates a copy of the argument. * - * @param q - * the quaternion to copy. + * @param q the quaternion to copy (not null, unaffected) */ public Quaternion(Quaternion q) { this.x = q.x; @@ -195,7 +226,7 @@ public Quaternion(Quaternion q) { } /** - * Sets this Quaternion to {0, 0, 0, 1}. Same as calling set(0,0,0,1). + * Sets all components to zero except {@code w}, which is set to 1. */ public void loadIdentity() { x = y = z = 0; @@ -203,7 +234,11 @@ public void loadIdentity() { } /** - * @return true if this Quaternion is {0,0,0,1} + * Compares with the identity quaternion, without distinguishing -0 from 0. + * The current instance is unaffected. + * + * @return true if the current quaternion equals the identity, otherwise + * false */ public boolean isIdentity() { if (x == 0 && y == 0 && z == 0 && w == 1) { @@ -214,11 +249,15 @@ public boolean isIdentity() { } /** - * fromAngles builds a quaternion from the Euler rotation - * angles (x,y,z) aka (pitch, yaw, roll). + * Sets the quaternion from the specified Tait-Bryan angles, applying the + * rotations in x-z-y extrinsic order or y-z'-x" intrinsic order. * - * @param angles - * the Euler angles of rotation (in radians). + * @param angles an array of Tait-Bryan angles (in radians, exactly 3 + * elements, the X angle in {@code angles[0]}, the Y angle in {@code + * angles[1]}, and the Z angle in {@code angles[2]}, not null, + * unaffected) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if {@code angles.length != 3} */ public Quaternion fromAngles(float[] angles) { if (angles.length != 3) { @@ -230,20 +269,16 @@ public Quaternion fromAngles(float[] angles) { } /** - * fromAngles builds a Quaternion from the Euler rotation - * angles (x,y,z) aka (pitch, yaw, roll)). Note that we are applying in order: (y, z, x) aka (yaw, roll, pitch) but - * we've ordered them in x, y, and z for convenience. - * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm - * - * @param xAngle - * the Euler pitch of rotation (in radians). (aka Attitude, often rot - * around x) - * @param yAngle - * the Euler yaw of rotation (in radians). (aka Heading, often - * rot around y) - * @param zAngle - * the Euler roll of rotation (in radians). (aka Bank, often - * rot around z) + * Sets the quaternion from the specified Tait-Bryan angles, applying the + * rotations in x-z-y extrinsic order or y-z'-x" intrinsic order. + * + * @see + * http://www.euclideanspace.com/maths/geometry/rotations/conversions/eulerToQuaternion/index.htm + * + * @param xAngle the X angle (in radians) + * @param yAngle the Y angle (in radians) + * @param zAngle the Z angle (in radians) + * @return the (modified) current instance (for chaining) */ public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) { float angle; @@ -274,15 +309,21 @@ public Quaternion fromAngles(float xAngle, float yAngle, float zAngle) { } /** - * toAngles returns this quaternion converted to Euler rotation - * angles (x,y,z) aka (pitch, yaw, roll).
      - * Note that the result is not always 100% accurate due to the implications of euler angles. - * @see http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm - * - * @param angles - * the float[] in which the angles should be stored, or null if - * you want a new float[] to be created - * @return the float[] in which the angles are stored. + * Converts to equivalent Tait-Bryan angles, to be applied in x-z-y + * intrinsic order or y-z'-x" extrinsic order, for instance by + * {@link #fromAngles(float[])}. The current instance is unaffected. + * + * @see + * + * http://www.euclideanspace.com/maths/geometry/rotations/conversions/quaternionToEuler/index.htm + * + * + * @param angles storage for the result, or null for a new float[3] + * @return an array of 3 angles (in radians, either angles or a + * new float[3], the X angle in angles[0], the Y angle in angles[1], and + * the Z angle in angles[2]) + * @throws IllegalArgumentException if {@code angles.length != 3} */ public float[] toAngles(float[] angles) { if (angles == null) { @@ -307,7 +348,7 @@ public float[] toAngles(float[] angles) { angles[2] = -FastMath.HALF_PI; angles[0] = 0; } else { - angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw or heading + angles[1] = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // yaw or heading angles[2] = FastMath.asin(2 * test / unit); // roll or bank angles[0] = FastMath.atan2(2 * x * w - 2 * y * z, -sqx + sqy - sqz + sqw); // pitch or attitude } @@ -315,22 +356,40 @@ public float[] toAngles(float[] angles) { } /** - * - * fromRotationMatrix generates a quaternion from a supplied - * matrix. This matrix is assumed to be a rotational matrix. - * - * @param matrix - * the matrix that defines the rotation. + * Sets the quaternion from the specified rotation matrix. + * + *

      Does not verify that the argument is a valid rotation matrix. + * Positive scaling is compensated for, but not reflection or shear. + * + * @param matrix the input matrix (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Quaternion fromRotationMatrix(Matrix3f matrix) { return fromRotationMatrix(matrix.m00, matrix.m01, matrix.m02, matrix.m10, matrix.m11, matrix.m12, matrix.m20, matrix.m21, matrix.m22); } + /** + * Sets the quaternion from a rotation matrix with the specified elements. + * + *

      Does not verify that the arguments form a valid rotation matrix. + * Positive scaling is compensated for, but not reflection or shear. + * + * @param m00 the matrix element in row 0, column 0 + * @param m01 the matrix element in row 0, column 1 + * @param m02 the matrix element in row 0, column 2 + * @param m10 the matrix element in row 1, column 0 + * @param m11 the matrix element in row 1, column 1 + * @param m12 the matrix element in row 1, column 2 + * @param m20 the matrix element in row 2, column 0 + * @param m21 the matrix element in row 2, column 1 + * @param m22 the matrix element in row 2, column 2 + * @return the (modified) current instance (for chaining) + */ public Quaternion fromRotationMatrix(float m00, float m01, float m02, float m10, float m11, float m12, float m20, float m21, float m22) { // first normalize the forward (F), up (U) and side (S) vectors of the rotation matrix - // so that the scale does not affect the rotation + // so that positive scaling does not affect the rotation float lengthSquared = m00 * m00 + m10 * m10 + m20 * m20; if (lengthSquared != 1f && lengthSquared != 0f) { lengthSquared = 1.0f / FastMath.sqrt(lengthSquared); @@ -353,7 +412,7 @@ public Quaternion fromRotationMatrix(float m00, float m01, float m02, m22 *= lengthSquared; } - // Use the Graphics Gems code, from + // Use the Graphics Gems code, from // ftp://ftp.cis.upenn.edu/pub/graphics/shoemake/quatut.ps.Z // *NOT* the "Matrix and Quaternions FAQ", which has errors! @@ -396,10 +455,13 @@ public Quaternion fromRotationMatrix(float m00, float m01, float m02, } /** - * toRotationMatrix converts this quaternion to a rotational - * matrix. Note: the result is created from a normalized version of this quat. - * - * @return the rotation matrix representation of this quaternion. + * Converts to an equivalent rotation matrix. The current instance is + * unaffected. + * + *

      Note: the result is created from a normalized version of the current + * instance. + * + * @return a new 3x3 rotation matrix */ public Matrix3f toRotationMatrix() { Matrix3f matrix = new Matrix3f(); @@ -407,12 +469,14 @@ public Matrix3f toRotationMatrix() { } /** - * toRotationMatrix converts this quaternion to a rotational - * matrix. The result is stored in result. - * - * @param result - * The Matrix3f to store the result in. - * @return the rotation matrix representation of this quaternion. + * Converts to an equivalent rotation matrix. The current instance is + * unaffected. + * + *

      Note: the result is created from a normalized version of the current + * instance. + * + * @param result storage for the result (not null) + * @return {@code result}, configured as a 3x3 rotation matrix */ public Matrix3f toRotationMatrix(Matrix3f result) { @@ -451,12 +515,17 @@ public Matrix3f toRotationMatrix(Matrix3f result) { } /** - * toTransformMatrix converts this quaternion to a transform - * matrix. The result is stored in result. - * Note this method won't preserve the scale of the given matrix. + * Sets the rotation component of the specified transform matrix. The + * current instance is unaffected. + * + *

      Note: preserves the translation component of {@code store} but not + * its scaling component. * - * @param store The Matrix3f to store the result in. - * @return the transform matrix with the rotation representation of this quaternion. + *

      Note: the result is created from a normalized version of the current + * instance. + * + * @param store storage for the result (not null) + * @return {@code store}, with 9 of its 16 elements modified */ public Matrix4f toTransformMatrix(Matrix4f store) { @@ -495,14 +564,17 @@ public Matrix4f toTransformMatrix(Matrix4f store) { } /** - * toRotationMatrix converts this quaternion to a rotational - * matrix. The result is stored in result. 4th row and 4th column values are - * untouched. Note: the result is created from a normalized version of this quat. - * Note that this method will preserve the scale of the given matrix + * Sets the rotation component of the specified transform matrix. The + * current instance is unaffected. + * + *

      Note: preserves the translation and scaling components of + * {@code result} unless {@code result} includes reflection. * - * @param result - * The Matrix4f to store the result in. - * @return the rotation matrix representation of this quaternion. + *

      Note: the result is created from a normalized version of the current + * instance. + * + * @param result storage for the result (not null) + * @return {@code result}, with 9 of its 16 elements modified */ public Matrix4f toRotationMatrix(Matrix4f result) { TempVars tempv = TempVars.get(); @@ -549,29 +621,32 @@ public Matrix4f toRotationMatrix(Matrix4f result) { } /** - * getRotationColumn returns one of three columns specified - * by the parameter. This column is returned as a Vector3f - * object. + * Calculates one of the basis vectors of the rotation. The current instance + * is unaffected. + * + *

      Note: the result is created from a normalized version of the current + * instance. * - * @param i - * the column to retrieve. Must be between 0 and 2. - * @return the column specified by the index. + * @param i which basis vector to retrieve (≥0, <3, 0→X-axis, + * 1→Y-axis, 2→Z-axis) + * @return the basis vector (a new Vector3f) */ public Vector3f getRotationColumn(int i) { return getRotationColumn(i, null); } /** - * getRotationColumn returns one of three columns specified - * by the parameter. This column is returned as a Vector3f - * object. The value is retrieved as if this quaternion was first normalized. + * Calculates one of the basis vectors of the rotation. The current instance + * is unaffected. + * + *

      Note: the result is created from a normalized version of the current + * instance. * - * @param i - * the column to retrieve. Must be between 0 and 2. - * @param store - * the vector object to store the result in. if null, a new one - * is created. - * @return the column specified by the index. + * @param i which basis vector to retrieve (≥0, <3, 0→X-axis, + * 1→Y-axis, 2→Z-axis) + * @param store storage for the result, or null for a new Vector3f + * @return the basis vector (either store or a new Vector3f) + * @throws IllegalArgumentException if index is not 0, 1, or 2 */ public Vector3f getRotationColumn(int i, Vector3f store) { if (store == null) { @@ -580,7 +655,7 @@ public Vector3f getRotationColumn(int i, Vector3f store) { float norm = norm(); if (norm != 1.0f) { - norm = FastMath.invSqrt(norm); + norm = 1.0f / norm; } float xx = x * x * norm; @@ -618,15 +693,14 @@ public Vector3f getRotationColumn(int i, Vector3f store) { } /** - * fromAngleAxis sets this quaternion to the values specified - * by an angle and an axis of rotation. This method creates an object, so - * use fromAngleNormalAxis if your axis is already normalized. + * Sets the quaternion from the specified rotation angle and axis of + * rotation. This method creates garbage, so use + * {@link #fromAngleNormalAxis(float, com.jme3.math.Vector3f)} if the axis + * is known to be normalized. * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation. - * @return this quaternion + * @param angle the desired rotation angle (in radians) + * @param axis the desired axis of rotation (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Quaternion fromAngleAxis(float angle, Vector3f axis) { Vector3f normAxis = axis.normalize(); @@ -635,13 +709,13 @@ public Quaternion fromAngleAxis(float angle, Vector3f axis) { } /** - * fromAngleNormalAxis sets this quaternion to the values - * specified by an angle and a normalized axis of rotation. + * Sets the quaternion from the specified rotation angle and normalized axis + * of rotation. If the axis might not be normalized, use + * {@link #fromAngleAxis(float, com.jme3.math.Vector3f)} instead. * - * @param angle - * the angle to rotate (in radians). - * @param axis - * the axis of rotation (already normalized). + * @param angle the desired rotation angle (in radians) + * @param axis the desired axis of rotation (not null, length=1, unaffected) + * @return the (modified) current instance (for chaining) */ public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) { if (axis.x == 0 && axis.y == 0 && axis.z == 0) { @@ -658,14 +732,21 @@ public Quaternion fromAngleNormalAxis(float angle, Vector3f axis) { } /** - * toAngleAxis sets a given angle and axis to that - * represented by the current quaternion. The values are stored as - * follows: The axis is provided as a parameter and built by the method, - * the angle is returned as a float. + * Converts the quaternion to a rotation angle and axis of rotation, storing + * the axis in the argument (if it's non-null) and returning the angle. * - * @param axisStore - * the object we'll store the computed axis in. - * @return the angle of rotation in radians. + *

      If the quaternion has {@code x*x + y*y + z*z == 0}, then (1,0,0) is + * stored and 0 is returned. (This might happen if the rotation angle is + * very close to 0.) + * + *

      Otherwise, the quaternion is assumed to be normalized (norm=1). No + * error checking is performed; the caller must ensure that the quaternion + * is normalized. + * + *

      In all cases, the current instance is unaffected. + * + * @param axisStore storage for the axis (modified if not null) + * @return the rotation angle (in radians) */ public float toAngleAxis(Vector3f axisStore) { float sqrLength = x * x + y * y + z * z; @@ -691,15 +772,13 @@ public float toAngleAxis(Vector3f axisStore) { } /** - * slerp sets this quaternion's value as an interpolation - * between two other quaternions. + * Interpolates between the specified quaternions and stores the result in + * the current instance. * - * @param q1 - * the first quaternion. - * @param q2 - * the second quaternion. - * @param t - * the amount to interpolate between the two quaternions. + * @param q1 the desired value when interp=0 (not null, unaffected) + * @param q2 the desired value when interp=1 (not null, may be modified) + * @param t the fractional change amount + * @return the (modified) current instance (for chaining) */ public Quaternion slerp(Quaternion q1, Quaternion q2, float t) { // Create a local quaternion to store the interpolated quaternion @@ -750,15 +829,16 @@ public Quaternion slerp(Quaternion q1, Quaternion q2, float t) { } /** - * Sets the values of this quaternion to the slerp from itself to q2 by - * changeAmnt + * Interpolates between the current instance and {@code q2} and stores the + * result in the current instance. * - * @param q2 - * Final interpolation value - * @param changeAmnt - * The amount difference + *

      This method is often more accurate than + * {@link #nlerp(com.jme3.math.Quaternion, float)}, but slower. + + * @param q2 the desired value when changeAmnt=1 (not null, may be modified) + * @param changeAmount the fractional change amount */ - public void slerp(Quaternion q2, float changeAmnt) { + public void slerp(Quaternion q2, float changeAmount) { if (this.x == q2.x && this.y == q2.y && this.z == q2.z && this.w == q2.w) { return; @@ -777,8 +857,8 @@ public void slerp(Quaternion q2, float changeAmnt) { } // Set the first and second scale for the interpolation - float scale0 = 1 - changeAmnt; - float scale1 = changeAmnt; + float scale0 = 1 - changeAmount; + float scale1 = changeAmount; // Check if the angle between the 2 quaternions was big enough to // warrant such calculations @@ -790,8 +870,8 @@ public void slerp(Quaternion q2, float changeAmnt) { // Calculate the scale for q1 and q2, according to the angle and // its sine - scale0 = FastMath.sin((1 - changeAmnt) * theta) * invSinTheta; - scale1 = FastMath.sin((changeAmnt * theta)) * invSinTheta; + scale0 = FastMath.sin((1 - changeAmount) * theta) * invSinTheta; + scale1 = FastMath.sin((changeAmount * theta)) * invSinTheta; } // Calculate the x, y, z and w values for the quaternion by using a @@ -804,9 +884,14 @@ public void slerp(Quaternion q2, float changeAmnt) { } /** - * Sets the values of this quaternion to the nlerp from itself to q2 by blend. - * @param q2 - * @param blend + * Interpolates quickly between the current instance and {@code q2} using + * nlerp, and stores the result in the current instance. + * + *

      This method is often faster than + * {@link #slerp(com.jme3.math.Quaternion, float)}, but less accurate. + * + * @param q2 the desired value when blend=1 (not null, unaffected) + * @param blend the fractional change amount */ public void nlerp(Quaternion q2, float blend) { float dot = dot(q2); @@ -826,24 +911,30 @@ public void nlerp(Quaternion q2, float blend) { } /** - * add adds the values of this quaternion to those of the - * parameter quaternion. The result is returned as a new quaternion. + * Adds the argument and returns the sum as a new instance. The current + * instance is unaffected. + * + *

      Seldom used. To combine rotations, use + * {@link #mult(com.jme3.math.Quaternion)} instead of this method. * - * @param q - * the quaternion to add to this. - * @return the new quaternion. + * @param q the quaternion to add (not null, unaffected) + * @return a new Quaternion */ public Quaternion add(Quaternion q) { return new Quaternion(x + q.x, y + q.y, z + q.z, w + q.w); } /** - * add adds the values of this quaternion to those of the - * parameter quaternion. The result is stored in this Quaternion. + * Adds the argument and returns the (modified) current instance. * - * @param q - * the quaternion to add to this. - * @return This Quaternion after addition. + *

      Seldom used. To combine rotations, use + * {@link #multLocal(com.jme3.math.Quaternion)} or + * {@link #mult(com.jme3.math.Quaternion, com.jme3.math.Quaternion)} + * instead of this method. + * + * @param q the quaternion to add (not null, unaffected unless it's + * {@code this}) + * @return the (modified) current instance (for chaining) */ public Quaternion addLocal(Quaternion q) { this.x += q.x; @@ -854,25 +945,25 @@ public Quaternion addLocal(Quaternion q) { } /** - * subtract subtracts the values of the parameter quaternion - * from those of this quaternion. The result is returned as a new - * quaternion. + * Subtracts the argument and returns difference as a new instance. The + * current instance is unaffected. * - * @param q - * the quaternion to subtract from this. - * @return the new quaternion. + * @param q the quaternion to subtract (not null, unaffected) + * @return a new Quaternion */ public Quaternion subtract(Quaternion q) { return new Quaternion(x - q.x, y - q.y, z - q.z, w - q.w); } /** - * subtract subtracts the values of the parameter quaternion - * from those of this quaternion. The result is stored in this Quaternion. + * Subtracts the argument and returns the (modified) current instance. + * + *

      To quantify the similarity of 2 normalized quaternions, use + * {@link #dot(com.jme3.math.Quaternion)}. * - * @param q - * the quaternion to subtract from this. - * @return This Quaternion after subtraction. + * @param q the quaternion to subtract (not null, unaffected unless it's + * {@code this}) + * @return the (modified) current instance */ public Quaternion subtractLocal(Quaternion q) { this.x -= q.x; @@ -883,50 +974,53 @@ public Quaternion subtractLocal(Quaternion q) { } /** - * mult multiplies this quaternion by a parameter quaternion. - * The result is returned as a new quaternion. It should be noted that - * quaternion multiplication is not commutative so q * p != p * q. + * Multiplies by the argument and returns the product as a new instance. + * The current instance is unaffected. * - * @param q - * the quaternion to multiply this quaternion by. - * @return the new quaternion. + *

      This method is used to combine rotations. Note that quaternion + * multiplication is noncommutative, so generally q * p != p * q. + * + * @param q the right factor (not null, unaffected) + * @return {@code this * q} (a new Quaternion) */ public Quaternion mult(Quaternion q) { return mult(q, null); } /** - * mult multiplies this quaternion by a parameter quaternion. - * The result is returned as a new quaternion. It should be noted that - * quaternion multiplication is not commutative so q * p != p * q. + * Multiplies by the specified quaternion and returns the product in a 3rd + * quaternion. The current instance is unaffected, unless it's {@code storeResult}. + * + *

      This method is used to combine rotations. Note that quaternion + * multiplication is noncommutative, so generally q * p != p * q. * - * It IS safe for q and res to be the same object. - * It IS NOT safe for this and res to be the same object. + *

      It is safe for {@code q} and {@code storeResult} to be the same object. + * However, if {@code this} and {@code storeResult} are the same object, the result + * is undefined. * - * @param q - * the quaternion to multiply this quaternion by. - * @param res - * the quaternion to store the result in. - * @return the new quaternion. + * @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) */ - public Quaternion mult(Quaternion q, Quaternion res) { - if (res == null) { - res = new Quaternion(); + public Quaternion mult(Quaternion q, Quaternion storeResult) { + if (storeResult == null) { + storeResult = new Quaternion(); } float qw = q.w, qx = q.x, qy = q.y, qz = q.z; - res.x = x * qw + y * qz - z * qy + w * qx; - res.y = -x * qz + y * qw + z * qx + w * qy; - res.z = x * qy - y * qx + z * qw + w * qz; - res.w = -x * qx - y * qy - z * qz + w * qw; - return res; + storeResult.x = x * qw + y * qz - z * qy + w * qx; + storeResult.y = -x * qz + y * qw + z * qx + w * qy; + storeResult.z = x * qy - y * qx + z * qw + w * qz; + storeResult.w = -x * qx - y * qy - z * qz + w * qw; + return storeResult; } /** - * apply multiplies this quaternion by a parameter matrix - * internally. + * Applies the rotation represented by the argument to the current instance. * - * @param matrix - * the matrix to apply to this quaternion. + *

      Does not verify that {@code matrix} is a valid rotation matrix. + * Positive scaling is compensated for, but not reflection or shear. + * + * @param matrix the rotation matrix to apply (not null, unaffected) */ public void apply(Matrix3f matrix) { float oldX = x, oldY = y, oldZ = z, oldW = w; @@ -940,16 +1034,17 @@ public void apply(Matrix3f matrix) { } /** + * Sets the quaternion from the specified orthonormal basis. * - * fromAxes creates a Quaternion that - * represents the coordinate system defined by three axes. These axes are - * assumed to be orthogonal and no error checking is applied. Thus, the user - * must insure that the three axes being provided indeed represents a proper - * right handed coordinate system. + *

      The 3 basis vectors describe the axes of a rotated coordinate system. + * They are assumed to be normalized, mutually orthogonal, and in right-hand + * order. No error checking is performed; the caller must ensure that the + * specified vectors represent a right-handed coordinate system. * - * @param axis - * the array containing the three vectors representing the - * coordinate system. + * @param axis the array of desired basis vectors (not null, array length=3, + * each vector having length=1, unaffected) + * @return the (modified) current instance (for chaining) + * @throws IllegalArgumentException if {@code axis.length != 3} */ public Quaternion fromAxes(Vector3f[] axis) { if (axis.length != 3) { @@ -960,16 +1055,20 @@ public Quaternion fromAxes(Vector3f[] axis) { } /** + * Sets the quaternion from the specified orthonormal basis. * - * fromAxes creates a Quaternion that - * represents the coordinate system defined by three axes. These axes are - * assumed to be orthogonal and no error checking is applied. Thus, the user - * must insure that the three axes being provided indeed represents a proper - * right handed coordinate system. + *

      The 3 basis vectors describe the axes of a rotated coordinate system. + * They are assumed to be normalized, mutually orthogonal, and in right-hand + * order. No error checking is performed; the caller must ensure that the + * specified vectors represent a right-handed coordinate system. * - * @param xAxis vector representing the x-axis of the coordinate system. - * @param yAxis vector representing the y-axis of the coordinate system. - * @param zAxis vector representing the z-axis of the coordinate system. + * @param xAxis the X axis of the desired coordinate system (not null, + * length=1, unaffected) + * @param yAxis the Y axis of the desired coordinate system (not null, + * length=1, unaffected) + * @param zAxis the Z axis of the desired coordinate system (not null, + * length=1, unaffected) + * @return the (modified) current instance (for chaining) */ public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) { return fromRotationMatrix(xAxis.x, yAxis.x, zAxis.x, xAxis.y, yAxis.y, @@ -977,40 +1076,59 @@ public Quaternion fromAxes(Vector3f xAxis, Vector3f yAxis, Vector3f zAxis) { } /** + * Converts the quaternion to a rotated coordinate system and stores the + * resulting axes in the argument. The current instance is unaffected. * - * toAxes takes in an array of three vectors. Each vector - * corresponds to an axis of the coordinate system defined by the quaternion - * rotation. + *

      The resulting vectors form the basis of a rotated coordinate system. + * They will be normalized, mutually orthogonal, and in right-hand order. * - * @param axis - * the array of vectors to be filled. + * @param axes storage for the results (not null, length=3, each element + * non-null, elements modified) + * @throws IllegalArgumentException if {@code axes.length != 3} */ - public void toAxes(Vector3f axis[]) { + public void toAxes(Vector3f axes[]) { + if (axes.length != 3) { + throw new IllegalArgumentException( + "Axes array must have three elements"); + } + Matrix3f tempMat = toRotationMatrix(); - axis[0] = tempMat.getColumn(0, axis[0]); - axis[1] = tempMat.getColumn(1, axis[1]); - axis[2] = tempMat.getColumn(2, axis[2]); + axes[0] = tempMat.getColumn(0, axes[0]); + axes[1] = tempMat.getColumn(1, axes[1]); + axes[2] = tempMat.getColumn(2, axes[2]); } /** - * mult multiplies this quaternion by a parameter vector. The - * result is returned as a new vector. + * Rotates the argument vector and returns the result as a new vector. The + * current instance is unaffected. * - * @param v - * the vector to multiply this quaternion by. - * @return the new vector. + *

      The quaternion is assumed to be normalized (norm=1). No error checking + * is performed; the caller must ensure that the norm is approximately equal + * to 1. + * + *

      Despite the name, the result differs from the mathematical definition + * of vector-quaternion multiplication. + * + * @param v the vector to rotate (not null, unaffected) + * @return a new Vector3f */ public Vector3f mult(Vector3f v) { return mult(v, null); } /** - * mult multiplies this quaternion by a parameter vector. The - * result is stored in the supplied vector + * Rotates the argument vector. Despite the name, the current instance is + * unaffected. + * + *

      The quaternion is assumed to be normalized (norm=1). No error checking + * is performed; the caller must ensure that the norm is approximately equal + * to 1. + * + *

      Despite the name, the result differs from the mathematical definition + * of vector-quaternion multiplication. * - * @param v - * the vector to multiply this quaternion by. - * @return v + * @param v the vector to rotate (not null) + * @return the (modified) vector {@code v} */ public Vector3f multLocal(Vector3f v) { float tempX, tempY; @@ -1027,13 +1145,13 @@ public Vector3f multLocal(Vector3f v) { } /** - * Multiplies this Quaternion by the supplied quaternion. The result is - * stored in this Quaternion, which is also returned for chaining. Similar - * to this *= q. + * Multiplies by the argument and returns the (modified) current instance. * - * @param q - * The Quaternion to multiply this one by. - * @return This Quaternion, after multiplication. + *

      This method is used to combine rotations. Note that quaternion + * multiplication is noncommutative, so generally q * p != p * q. + * + * @param q the right factor (not null, unaffected unless it's {@code this}) + * @return the (modified) current instance (for chaining) */ public Quaternion multLocal(Quaternion q) { float x1 = x * q.w + y * q.z - z * q.y + w * q.x; @@ -1047,20 +1165,17 @@ public Quaternion multLocal(Quaternion q) { } /** - * Multiplies this Quaternion by the supplied quaternion. The result is - * stored in this Quaternion, which is also returned for chaining. Similar - * to this *= q. + * Multiplies by a quaternion with the specified components and returns the + * (modified) current instance. * - * @param qx - - * quat x value - * @param qy - - * quat y value - * @param qz - - * quat z value - * @param qw - - * quat w value + *

      This method is used to combine rotations. Note that quaternion + * multiplication is noncommutative, so generally q * p != p * q. * - * @return This Quaternion, after multiplication. + * @param qx the X component of the right factor + * @param qy the Y component of the right factor + * @param qz the Z component of the right factor + * @param qw the W component of the right factor + * @return the (modified) current instance (for chaining) */ public Quaternion multLocal(float qx, float qy, float qz, float qw) { float x1 = x * qw + y * qz - z * qy + w * qx; @@ -1074,15 +1189,22 @@ public Quaternion multLocal(float qx, float qy, float qz, float qw) { } /** - * mult multiplies this quaternion by a parameter vector. The - * result is returned as a new vector. - * - * @param v - * the vector to multiply this quaternion by. - * @param store - * the vector to store the result in. It IS safe for v and store - * to be the same object. - * @return the result vector. + * Rotates a specified vector and returns the result in another vector. The + * current instance is unaffected. + * + *

      The quaternion is assumed to be normalized (norm=1). No error checking + * is performed; the caller must ensure that the norm is approximately equal + * to 1. + * + *

      It is safe for {@code v} and {@code store} to be the same object. + * + *

      Despite the name, the result differs from the mathematical definition + * of vector-quaternion multiplication. + * + * @param v the vector to rotate (not null, unaffected unless it's + * {@code store}) + * @param store storage for the result, or null for a new Vector3f + * @return the rotated vector (either {@code store} or a new Vector3f) */ public Vector3f mult(Vector3f v, Vector3f store) { if (store == null) { @@ -1106,24 +1228,22 @@ public Vector3f mult(Vector3f v, Vector3f store) { } /** - * mult multiplies this quaternion by a parameter scalar. The - * result is returned as a new quaternion. + * Multiplies with the scalar argument and returns the product as a new + * instance. The current instance is unaffected. * - * @param scalar - * the quaternion to multiply this quaternion by. - * @return the new quaternion. + * @param scalar the scaling factor + * @return a new Quaternion */ public Quaternion mult(float scalar) { return new Quaternion(scalar * x, scalar * y, scalar * z, scalar * w); } /** - * mult multiplies this quaternion by a parameter scalar. The - * result is stored locally. + * Multiplies by the scalar argument and returns the (modified) current + * instance. * - * @param scalar - * the quaternion to multiply this quaternion by. - * @return this. + * @param scalar the scaling factor + * @return the (modified) current instance (for chaining) */ public Quaternion multLocal(float scalar) { w *= scalar; @@ -1134,22 +1254,24 @@ public Quaternion multLocal(float scalar) { } /** - * dot calculates and returns the dot product of this - * quaternion with that of the parameter quaternion. + * Returns the dot product with the argument. The current instance is + * unaffected. * - * @param q - * the quaternion to calculate the dot product of. - * @return the dot product of this and the parameter quaternion. + *

      This method can be used to quantify the similarity of 2 normalized + * quaternions. + * + * @param q the quaternion to multiply (not null, unaffected) + * @return the dot product */ public float dot(Quaternion q) { return w * q.w + x * q.x + y * q.y + z * q.z; } /** - * norm returns the norm of this quaternion. This is the dot - * product of this quaternion with itself. + * Returns the norm, defined as the dot product of the quaternion with + * itself. The current instance is unaffected. * - * @return the norm of the quaternion. + * @return the sum of the squared components (not negative) */ public float norm() { return w * w + x * x + y * y + z * z; @@ -1170,8 +1292,10 @@ public float norm() { // } /** - * normalize normalizes the current Quaternion. - * The result is stored internally. + * Scales the quaternion to have norm=1 and returns the (modified) current + * instance. For a quaternion with norm=0, the result is undefined. + * + * @return the (modified) current instance (for chaining) */ public Quaternion normalizeLocal() { float n = FastMath.invSqrt(norm()); @@ -1183,12 +1307,10 @@ public Quaternion normalizeLocal() { } /** - * inverse returns the inverse of this quaternion as a new - * quaternion. If this quaternion does not have an inverse (if its normal is - * 0 or less), then null is returned. + * Returns the multiplicative inverse. For a quaternion with norm=0, null is + * returned. Either way, the current instance is unaffected. * - * @return the inverse of this quaternion or null if the inverse does not - * exist. + * @return a new Quaternion or null */ public Quaternion inverse() { float norm = norm(); @@ -1202,11 +1324,10 @@ public Quaternion inverse() { } /** - * inverse calculates the inverse of this quaternion and - * returns this quaternion after it is calculated. If this quaternion does - * not have an inverse (if its normal is 0 or less), nothing happens + * Inverts the quaternion and returns the (modified) current instance. For + * a quaternion with norm=0, the current instance is unchanged. * - * @return the inverse of this quaternion + * @return the current instance (for chaining) */ public Quaternion inverseLocal() { float norm = norm(); @@ -1221,25 +1342,38 @@ public Quaternion inverseLocal() { } /** - * negate inverts the values of the quaternion. + * Negates all 4 components. * + * @deprecated The naming of this method doesn't follow convention. Please + * use {@link #negateLocal()} instead. */ + @Deprecated public void negate() { - x *= -1; - y *= -1; - z *= -1; - w *= -1; + negateLocal(); } /** + * Negates all 4 components and returns the (modified) current instance. * - * toString creates the string representation of this - * Quaternion. The values of the quaternion are displaced (x, - * y, z, w), in the following manner:
      - * (x, y, z, w) + * @return the (modified) current instance (for chaining) + */ + public Quaternion negateLocal() { + x = -x; + y = -y; + z = -z; + w = -w; + + return this; + } + + /** + * 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 of this object. - * @see java.lang.Object#toString() + * @return the string representation (not null, not empty) */ @Override public String toString() { @@ -1247,12 +1381,13 @@ public String toString() { } /** - * equals determines if two quaternions are logically equal, - * that is, if the values of (x, y, z, w) are the same for both quaternions. + * 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 for equality - * @return true if they are equal, false otherwise. + * @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) { @@ -1279,10 +1414,17 @@ public boolean equals(Object o) { } return true; } - + /** - * Returns true if this quaternion is similar to the specified quaternion - * within some value of epsilon. + * Tests for approximate equality with the specified quaternion, using the + * specified tolerance. The current instance is unaffected. + * + *

      To quantify the similarity of 2 normalized quaternions, use + * {@link #dot(com.jme3.math.Quaternion)}. + * + * @param other the quaternion to compare (not null, unaffected) + * @param epsilon the tolerance for each component + * @return true if all 4 components are within tolerance, otherwise false */ public boolean isSimilar(Quaternion other, float epsilon) { if (other == null) { @@ -1304,12 +1446,10 @@ public boolean isSimilar(Quaternion other, float epsilon) { } /** - * - * 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. - * - * @return the hashcode for this instance of Quaternion. + * Returns a hash code. If two quaternions have identical values, they + * will have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing * @see java.lang.Object#hashCode() */ @Override @@ -1324,14 +1464,13 @@ public int hashCode() { } /** - * readExternal builds a quaternion from an - * ObjectInput object.
      - * NOTE: Used with serialization. Not to be called manually. - * - * @param in - * the ObjectInput value to read from. - * @throws IOException - * if the ObjectInput value has problems reading a float. + * Sets the quaternion from an {@code ObjectInput} object. + * + *

      Used with serialization. Should not be invoked directly by application + * code. + * + * @param in the object to read from (not null) + * @throws IOException if the ObjectInput cannot read a float * @see java.io.Externalizable */ public void readExternal(ObjectInput in) throws IOException { @@ -1342,14 +1481,13 @@ public void readExternal(ObjectInput in) throws IOException { } /** - * writeExternal writes this quaternion out to a - * ObjectOutput object. NOTE: Used with serialization. Not to - * be called manually. - * - * @param out - * the object to write to. - * @throws IOException - * if writing to the ObjectOutput fails. + * Writes the quaternion to an {@code ObjectOutput} object. + * + *

      Used with serialization. Should not be invoked directly by application + * code. + * + * @param out the object to write to (not null) + * @throws IOException if the ObjectOutput cannot write a float * @see java.io.Externalizable */ public void writeExternal(ObjectOutput out) throws IOException { @@ -1360,16 +1498,18 @@ public void writeExternal(ObjectOutput out) throws IOException { } /** - * lookAt is a convienence method for auto-setting the - * quaternion based on a direction and an up vector. It computes - * the rotation to transform the z-axis to point into 'direction' - * and the y-axis to 'up'. + * Convenience method to set the quaternion based on a "look" (Z-axis) + * direction and an "up" (Y-axis) direction. * - * @param direction - * where to look at in terms of local coordinates - * @param up - * a vector indicating the local up direction. - * (typically {0, 1, 0} in jME.) + *

      If either vector has length=0, the result is undefined. + * + *

      If the vectors are parallel, the result is undefined. + * + * @param direction the desired Z-axis direction (in local coordinates, not + * null, length>0, unaffected) + * @param up the desired Y-axis direction (in local coordinates, not null, + * length>0, unaffected, typically (0,1,0) ) + * @return the (modified) current instance (for chaining) */ public Quaternion lookAt(Vector3f direction, Vector3f up) { TempVars vars = TempVars.get(); @@ -1381,6 +1521,14 @@ public Quaternion lookAt(Vector3f direction, Vector3f up) { return this; } + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param e the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule cap = e.getCapsule(this); cap.write(x, "x", 0); @@ -1389,8 +1537,16 @@ public void write(JmeExporter e) throws IOException { cap.write(w, "w", 1); } - public void read(JmeImporter e) throws IOException { - InputCapsule cap = e.getCapsule(this); + /** + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule cap = importer.getCapsule(this); x = cap.readFloat("x", 0); y = cap.readFloat("y", 0); z = cap.readFloat("z", 0); @@ -1406,13 +1562,15 @@ public Quaternion opposite() { } /** - * FIXME: This seems to have singularity type issues with angle == 0, possibly others such as PI. - * @param store - * A Quaternion to store our result in. If null, a new one is - * created. - * @return The store quaternion (or a new Quaternion, if store is null) that - * describes a rotation that would point you in the exact opposite - * direction of this Quaternion. + * Returns a rotation with the same axis and the angle increased by 180 + * degrees. If the quaternion isn't normalized, or if the rotation angle is + * very small, the result is undefined. + * + *

      The current instance is unaffected, unless {@code store} is + * {@code this}. + * + * @param store storage for the result, or null for a new Quaternion + * @return either {@code store} or a new Quaternion */ public Quaternion opposite(Quaternion store) { if (store == null) { @@ -1427,14 +1585,21 @@ public Quaternion opposite(Quaternion store) { } /** - * @return This Quaternion, altered to describe a rotation that would point - * you in the exact opposite direction of where it is pointing - * currently. + * Changes the quaternion to a rotation with the same axis and the angle + * increased by 180 degrees. If the quaternion isn't normalized, or if the + * rotation angle is very small, the result is undefined. + * + * @return the (modified) current instance */ public Quaternion oppositeLocal() { return opposite(this); } + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance, equivalent to the current one + */ @Override public Quaternion clone() { try { @@ -1443,4 +1608,27 @@ public Quaternion clone() { throw new AssertionError(); // can not happen } } + + /** + * Tests whether the argument is a valid quaternion, returning false if it's + * null or if any component is NaN or infinite. + * + * @param quaternion the quaternion to test (unaffected) + * @return true if non-null and finite, otherwise false + */ + public static boolean isValidQuaternion(Quaternion quaternion) { + if (quaternion == null) { + return false; + } + if (Float.isNaN(quaternion.x) + || Float.isNaN(quaternion.y) + || Float.isNaN(quaternion.z) + || Float.isNaN(quaternion.w)) { + return false; + } + return !Float.isInfinite(quaternion.x) + && !Float.isInfinite(quaternion.y) + && !Float.isInfinite(quaternion.z) + && !Float.isInfinite(quaternion.w); + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Ray.java b/jme3-core/src/main/java/com/jme3/math/Ray.java index 81e54d99ff..7b42102fef 100644 --- a/jme3-core/src/main/java/com/jme3/math/Ray.java +++ b/jme3-core/src/main/java/com/jme3/math/Ray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,24 +46,23 @@ * defined by the following equation: {@literal * R(t) = origin + t*direction for t >= 0. * } + * * @author Mark Powell * @author Joshua Slack */ public final class Ray implements Savable, Cloneable, Collidable, java.io.Serializable { - static final long serialVersionUID = 1; - - /** - * The ray's beginning point. + /** + * The ray's beginning point. */ public Vector3f origin = new Vector3f(); - - /** - * The direction of the ray. + /** + * The direction of the ray. */ public Vector3f direction = new Vector3f(0, 0, 1); - - + /** + * The length of the ray (defaults to +Infinity). + */ public float limit = Float.POSITIVE_INFINITY; /** @@ -77,6 +76,7 @@ public Ray() { /** * Constructor instantiates a new Ray object. The origin and * direction are given. + * * @param origin the origin of the ray. * @param direction the direction the ray travels in. */ @@ -85,15 +85,17 @@ public Ray(Vector3f origin, Vector3f direction) { setDirection(direction); } - /** + /* * intersect determines if the Ray intersects a triangle. + * * @param t the Triangle to test against. * @return true if the ray collides. */ // public boolean intersect(Triangle t) { // return intersect(t.get(0), t.get(1), t.get(2)); // } - /** + + /* * intersect determines if the Ray intersects a triangle * defined by the specified points. * @@ -108,13 +110,14 @@ public Ray(Vector3f origin, Vector3f direction) { // public boolean intersect(Vector3f v0,Vector3f v1,Vector3f v2){ // return intersectWhere(v0, v1, v2, null); // } + /** - * intersectWhere determines if the Ray intersects a triangle. It then - * stores the point of intersection in the given loc vector + * intersectWhere determines if the Ray intersects a triangle. + * It then stores the point of intersection in the given loc vector + * * @param t the Triangle to test against. - * @param loc - * storage vector to save the collision point in (if the ray - * collides) + * @param loc storage vector to save the collision point in (if the ray + * collides) * @return true if the ray collides. */ public boolean intersectWhere(Triangle t, Vector3f loc) { @@ -252,7 +255,7 @@ private boolean intersects(Vector3f v0, Vector3f v1, Vector3f v2, } else { // these weights can be used to determine // interpolated values, such as texture coord. - // eg. texcoord s,t at intersection point: + // e.g. texcoord s,t at intersection point: // s = w0*s0 + w1*s1 + w2*s2; // t = w0*t0 + w1*t1 + w2*t2; float w1 = dirDotDiffxEdge2 * inv; @@ -361,9 +364,9 @@ public boolean intersectWherePlanarQuad(Vector3f v0, Vector3f v1, Vector3f v2, } /** - * - * @param p - * @param loc + * @param p the Plane to test for collision (not null, unaffected) + * @param loc storage for the location of the intersection (not null, + * modified when returning true) * @return true if the ray collides with the given Plane */ public boolean intersectsWherePlane(Plane p, Vector3f loc) { @@ -383,6 +386,7 @@ public boolean intersectsWherePlane(Plane p, Vector3f loc) { return true; } + @Override public int collideWith(Collidable other, CollisionResults results) { if (other instanceof BoundingVolume) { BoundingVolume bv = (BoundingVolume) other; @@ -402,6 +406,12 @@ public int collideWith(Collidable other, CollisionResults results) { } } + /** + * Calculate the squared distance from this ray to the specified point. + * + * @param point location vector of the input point (not null, unaffected) + * @return the square of the minimum distance (≥0) + */ public float distanceSquared(Vector3f point) { TempVars vars = TempVars.get(); @@ -434,8 +444,8 @@ public Vector3f getOrigin() { } /** - * * setOrigin sets the origin of the ray. + * * @param origin the origin of the ray. */ public void setOrigin(Vector3f origin) { @@ -446,7 +456,7 @@ public void setOrigin(Vector3f origin) { * getLimit returns the limit of the ray, aka the length. * If the limit is not infinity, then this ray is a line with length * limit. - * + * * @return the limit of the ray, aka the length. */ public float getLimit() { @@ -455,16 +465,17 @@ public float getLimit() { /** * setLimit sets the limit of the ray. + * * @param limit the limit of the ray. - * @see Ray#getLimit() + * @see Ray#getLimit() */ public void setLimit(float limit) { this.limit = limit; } /** - * * getDirection retrieves the direction vector of the ray. + * * @return the direction of the ray. */ public Vector3f getDirection() { @@ -472,8 +483,8 @@ public Vector3f getDirection() { } /** - * * setDirection sets the direction vector of the ray. + * * @param direction the direction of the ray. */ public void setDirection(Vector3f direction) { @@ -483,7 +494,7 @@ public void setDirection(Vector3f direction) { /** * Copies information from a source ray into this ray. - * + * * @param source * the ray to copy information from */ @@ -492,22 +503,51 @@ public void set(Ray source) { direction.set(source.getDirection()); } + /** + * Represent this ray as a String. The format is: + * + * Ray [Origin: (X.X, Y.Y, Z.Z), Direction: (X.X, Y.Y, Z.Z)] + * + * @return a descriptive string of text (not null, not empty) + */ + @Override public String toString() { return getClass().getSimpleName() + " [Origin: " + origin + ", Direction: " + direction + "]"; } + /** + * Serialize this ray to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(origin, "origin", Vector3f.ZERO); capsule.write(direction, "direction", Vector3f.ZERO); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this ray from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); origin = (Vector3f) capsule.readSavable("origin", Vector3f.ZERO.clone()); direction = (Vector3f) capsule.readSavable("direction", Vector3f.ZERO.clone()); } + /** + * Create a copy of this ray. + * + * @return a new instance, equivalent to this one + */ @Override public Ray clone() { try { diff --git a/jme3-core/src/main/java/com/jme3/math/Rectangle.java b/jme3-core/src/main/java/com/jme3/math/Rectangle.java index e8ba4c23d3..67cfcb15d7 100644 --- a/jme3-core/src/main/java/com/jme3/math/Rectangle.java +++ b/jme3-core/src/main/java/com/jme3/math/Rectangle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,19 +34,29 @@ import com.jme3.export.*; import java.io.IOException; - /** - * - * Rectangle defines a finite plane within three dimensional space + * Rectangle defines a finite plane within three-dimensional space * that is specified via three points (A, B, C). These three points define a - * triangle with the fourth point defining the rectangle ((B + C) - A. - * + * triangle with the fourth point defining the rectangle (B + C) - A. + * + *

      The corner points are named as follows: + * + *

      + *     C +---+ D
      + *       |   |
      + *       |   |
      + *       |   |
      + *       |   |
      + *     A +---+ B
      + * 
      + * + *

      If angle BAC isn't exactly 90 degrees, then the resulting shape is + * actually parallelogram, not a rectangle. + * * @author Mark Powell * @author Joshua Slack */ - public final class Rectangle implements Savable, Cloneable, java.io.Serializable { - static final long serialVersionUID = 1; private Vector3f a, b, c; @@ -54,7 +64,6 @@ public final class Rectangle implements Savable, Cloneable, java.io.Serializable /** * Constructor creates a new Rectangle with no defined corners. * A, B, and C must be set to define a valid rectangle. - * */ public Rectangle() { a = new Vector3f(); @@ -65,13 +74,10 @@ public Rectangle() { /** * Constructor creates a new Rectangle with defined A, B, and C * points that define the area of the rectangle. - * - * @param a - * the first corner of the rectangle. - * @param b - * the second corner of the rectangle. - * @param c - * the third corner of the rectangle. + * + * @param a the first corner of the rectangle. + * @param b the second corner of the rectangle. + * @param c the third corner of the rectangle. */ public Rectangle(Vector3f a, Vector3f b, Vector3f c) { this.a = a; @@ -81,7 +87,7 @@ public Rectangle(Vector3f a, Vector3f b, Vector3f c) { /** * getA returns the first point of the rectangle. - * + * * @return the first point of the rectangle. */ public Vector3f getA() { @@ -90,9 +96,8 @@ public Vector3f getA() { /** * setA sets the first point of the rectangle. - * - * @param a - * the first point of the rectangle. + * + * @param a the first point of the rectangle. */ public void setA(Vector3f a) { this.a = a; @@ -100,7 +105,7 @@ public void setA(Vector3f a) { /** * getB returns the second point of the rectangle. - * + * * @return the second point of the rectangle. */ public Vector3f getB() { @@ -109,9 +114,8 @@ public Vector3f getB() { /** * setB sets the second point of the rectangle. - * - * @param b - * the second point of the rectangle. + * + * @param b the second point of the rectangle. */ public void setB(Vector3f b) { this.b = b; @@ -119,7 +123,7 @@ public void setB(Vector3f b) { /** * getC returns the third point of the rectangle. - * + * * @return the third point of the rectangle. */ public Vector3f getC() { @@ -128,18 +132,53 @@ public Vector3f getC() { /** * setC sets the third point of the rectangle. - * - * @param c - * the third point of the rectangle. + * + * @param c the third point of the rectangle. */ public void setC(Vector3f c) { this.c = c; } + /** + * Returns the coordinates of the 4th corner, calculated by the formula + * D = (B + C) - A . + * + * @return the corner location (a new Vector3f) + */ + public Vector3f calculateD() { + float x = b.x + c.x - a.x; + float y = b.y + c.y - a.y; + float z = b.z + c.z - a.z; + return new Vector3f(x, y, z); + } + + /** + * Returns a normal vector, calculated by the formula + *

      +     *      (C - B) x (B - A)
      +     * N = -------------------
      +     *     |(C - B) x (B - A)|
      +     * 
      + * + * @param normal storage for the normal, or null for a new Vector3f + * @return the normal direction (either {@code normal} or a new Vector3f) + */ + public Vector3f calculateNormal(Vector3f normal) { + if (normal == null) { + normal = new Vector3f(); + } + + Vector3f v1 = c.subtract(b); + Vector3f v2 = a.subtract(b); + normal.set(v1.crossLocal(v2).normalizeLocal()); + + return normal; + } + /** * random returns a random point within the plane defined by: * A, B, C, and (B + C) - A. - * + * * @return a random point within the rectangle. */ public Vector3f random() { @@ -149,7 +188,7 @@ public Vector3f random() { /** * random returns a random point within the plane defined by: * A, B, C, and (B + C) - A. - * + * * @param result * Vector to store result in * @return a random point within the rectangle. @@ -167,6 +206,14 @@ public Vector3f random(Vector3f result) { return result; } + /** + * Serialize this rectangle to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(a, "a", Vector3f.ZERO); @@ -174,13 +221,26 @@ public void write(JmeExporter e) throws IOException { capsule.write(c, "c", Vector3f.ZERO); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this rectangle from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); a = (Vector3f) capsule.readSavable("a", Vector3f.ZERO.clone()); b = (Vector3f) capsule.readSavable("b", Vector3f.ZERO.clone()); c = (Vector3f) capsule.readSavable("c", Vector3f.ZERO.clone()); } + /** + * Create a copy of this rectangle. + * + * @return a new instance, equivalent to this one + */ @Override public Rectangle clone() { try { @@ -193,4 +253,19 @@ public Rectangle clone() { throw new AssertionError(); } } + + /** + * Returns a string representation of the Recatangle, which is unaffected. + * For example, a rectangle with vertices at (1,0,0), (2,0,0), (1,2,0), and + * (2,2,0) is represented by: + *
      +     * Rectangle [A: (1.0, 0.0, 0.0)  B: (2.0, 0.0, 0.0)  C: (1.0, 2.0, 0.0)]
      +     * 
      + * + * @return the string representation (not null, not empty) + */ + @Override + public String toString() { + return getClass().getSimpleName() + " [A: " + a + " B: " + b + " C: " + c + "]"; + } } diff --git a/jme3-core/src/main/java/com/jme3/math/Ring.java b/jme3-core/src/main/java/com/jme3/math/Ring.java index 1c9eebc457..c1a17f3cf4 100644 --- a/jme3-core/src/main/java/com/jme3/math/Ring.java +++ b/jme3-core/src/main/java/com/jme3/math/Ring.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,23 +34,20 @@ import com.jme3.export.*; import java.io.IOException; - /** - * Ring defines a flat ring or disk within three dimensional + * Ring defines a flat ring or disc in three-dimensional * space that is specified via the ring's center point, an up vector, an inner * radius, and an outer radius. - * + * * @author Andrzej Kapolka * @author Joshua Slack */ - public final class Ring implements Savable, Cloneable, java.io.Serializable { - static final long serialVersionUID = 1; - + private Vector3f center, up; private float innerRadius, outerRadius; - private transient static Vector3f b1 = new Vector3f(), b2 = new Vector3f(); + private static transient Vector3f b1 = new Vector3f(), b2 = new Vector3f(); /** * Constructor creates a new Ring lying on the XZ plane, @@ -67,7 +64,7 @@ public Ring() { /** * Constructor creates a new Ring with defined center point, * up vector, and inner and outer radii. - * + * * @param center * the center of the ring. * @param up @@ -87,7 +84,7 @@ public Ring(Vector3f center, Vector3f up, float innerRadius, /** * getCenter returns the center of the ring. - * + * * @return the center of the ring. */ public Vector3f getCenter() { @@ -96,7 +93,7 @@ public Vector3f getCenter() { /** * setCenter sets the center of the ring. - * + * * @param center * the center of the ring. */ @@ -106,7 +103,7 @@ public void setCenter(Vector3f center) { /** * getUp returns the ring's up vector. - * + * * @return the ring's up vector. */ public Vector3f getUp() { @@ -115,7 +112,7 @@ public Vector3f getUp() { /** * setUp sets the ring's up vector. - * + * * @param up * the ring's up vector. */ @@ -125,7 +122,7 @@ public void setUp(Vector3f up) { /** * getInnerRadius returns the ring's inner radius. - * + * * @return the ring's inner radius. */ public float getInnerRadius() { @@ -134,7 +131,7 @@ public float getInnerRadius() { /** * setInnerRadius sets the ring's inner radius. - * + * * @param innerRadius * the ring's inner radius. */ @@ -144,7 +141,7 @@ public void setInnerRadius(float innerRadius) { /** * getOuterRadius returns the ring's outer radius. - * + * * @return the ring's outer radius. */ public float getOuterRadius() { @@ -153,7 +150,7 @@ public float getOuterRadius() { /** * setOuterRadius sets the ring's outer radius. - * + * * @param outerRadius * the ring's outer radius. */ @@ -162,9 +159,8 @@ public void setOuterRadius(float outerRadius) { } /** - * * random returns a random point within the ring. - * + * * @return a random point within the ring. */ public Vector3f random() { @@ -172,9 +168,8 @@ public Vector3f random() { } /** - * * random returns a random point within the ring. - * + * * @param result Vector to store result in * @return a random point within the ring. */ @@ -182,13 +177,12 @@ public Vector3f random(Vector3f result) { if (result == null) { result = new Vector3f(); } - + // compute a random radius according to the ring area distribution - float inner2 = innerRadius * innerRadius, outer2 = outerRadius - * outerRadius, r = FastMath.sqrt(inner2 - + FastMath.nextRandomFloat() * (outer2 - inner2)), theta = FastMath - .nextRandomFloat() - * FastMath.TWO_PI; + float inner2 = innerRadius * innerRadius, + outer2 = outerRadius * outerRadius, + r = FastMath.sqrt(inner2 + FastMath.nextRandomFloat() * (outer2 - inner2)), + theta = FastMath.nextRandomFloat() * FastMath.TWO_PI; up.cross(Vector3f.UNIT_X, b1); if (b1.lengthSquared() < FastMath.FLT_EPSILON) { up.cross(Vector3f.UNIT_Y, b1); @@ -200,6 +194,14 @@ public Vector3f random(Vector3f result) { return result; } + /** + * Serialize this ring to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(center, "center", Vector3f.ZERO); @@ -208,16 +210,27 @@ public void write(JmeExporter e) throws IOException { capsule.write(outerRadius, "outerRadius", 1f); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - center = (Vector3f) capsule.readSavable("center", - Vector3f.ZERO.clone()); - up = (Vector3f) capsule - .readSavable("up", Vector3f.UNIT_Z.clone()); + /** + * De-serialize this ring from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + center = (Vector3f) capsule.readSavable("center", Vector3f.ZERO.clone()); + up = (Vector3f) capsule.readSavable("up", Vector3f.UNIT_Z.clone()); innerRadius = capsule.readFloat("innerRadius", 0f); outerRadius = capsule.readFloat("outerRadius", 1f); } + /** + * Create a copy of this ring. + * + * @return a new instance, equivalent to this one + */ @Override public Ring clone() { try { @@ -229,4 +242,4 @@ public Ring clone() { throw new AssertionError(); } } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/math/Spline.java b/jme3-core/src/main/java/com/jme3/math/Spline.java index 2f3155c55a..cdd7f48c77 100644 --- a/jme3-core/src/main/java/com/jme3/math/Spline.java +++ b/jme3-core/src/main/java/com/jme3/math/Spline.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,8 @@ package com.jme3.math; import com.jme3.export.*; +import com.jme3.util.clone.Cloner; +import com.jme3.util.clone.JmeCloneable; import java.io.IOException; import java.util.ArrayList; import java.util.Iterator; @@ -41,7 +43,7 @@ * * @author Nehon */ -public class Spline implements Savable { +public class Spline implements JmeCloneable, Savable { public enum SplineType { Linear, @@ -49,11 +51,11 @@ public enum SplineType { Bezier, Nurb } - - private List controlPoints = new ArrayList(); - private List knots; //knots of NURBS spline - private float[] weights; //weights of NURBS spline - private int basisFunctionDegree; //degree of NURBS spline basis function (computed automatically) + + private List controlPoints = new ArrayList<>(); + private List knots; //knots of NURBS spline + private float[] weights; //weights of NURBS spline + private int basisFunctionDegree; //degree of NURBS spline basis function (computed automatically) private boolean cycle; private List segmentsLength; private float totalLength; @@ -66,23 +68,24 @@ public Spline() { /** * Create a spline + * * @param splineType the type of the spline @see {SplineType} * @param controlPoints an array of vector to use as control points of the spline - * If the type of the curve is Bezier curve the control points should be provided + * If the type of the curve is Bézier curve the control points should be provided * in the appropriate way. Each point 'p' describing control position in the scene * should be surrounded by two handler points. This applies to every point except * for the border points of the curve, who should have only one handle point. * The pattern should be as follows: * P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn - * + * * n is the amount of 'P' - points. * @param curveTension the tension of the spline * @param cycle true if the spline cycle. */ public Spline(SplineType splineType, Vector3f[] controlPoints, float curveTension, boolean cycle) { - if(splineType==SplineType.Nurb) { - throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); - } + if (splineType == SplineType.Nurb) { + throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); + } for (int i = 0; i < controlPoints.length; i++) { Vector3f vector3f = controlPoints[i]; this.controlPoints.add(vector3f); @@ -95,53 +98,55 @@ public Spline(SplineType splineType, Vector3f[] controlPoints, float curveTensio /** * Create a spline + * * @param splineType the type of the spline @see {SplineType} * @param controlPoints a list of vector to use as control points of the spline - * If the type of the curve is Bezier curve the control points should be provided + * If the curve is a Bézier curve, the control points should be provided * in the appropriate way. Each point 'p' describing control position in the scene * should be surrounded by two handler points. This applies to every point except * for the border points of the curve, who should have only one handle point. * The pattern should be as follows: * P0 - H0 : H1 - P1 - H1 : ... : Hn - Pn - * + * * n is the amount of 'P' - points. * @param curveTension the tension of the spline * @param cycle true if the spline cycle. */ public Spline(SplineType splineType, List controlPoints, float curveTension, boolean cycle) { - if(splineType==SplineType.Nurb) { - throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); - } + if (splineType == SplineType.Nurb) { + throw new IllegalArgumentException("To create NURBS spline use: 'public Spline(Vector3f[] controlPoints, float[] weights, float[] nurbKnots)' constructor!"); + } type = splineType; this.controlPoints.addAll(controlPoints); this.curveTension = curveTension; this.cycle = cycle; this.computeTotalLength(); } - + /** * Create a NURBS spline. A spline type is automatically set to SplineType.Nurb. * The cycle is set to false by default. + * * @param controlPoints a list of vector to use as control points of the spline - * @param nurbKnots the nurb's spline knots + * @param nurbKnots the nurb's spline knots */ public Spline(List controlPoints, List nurbKnots) { - //input data control - for(int i=0;inurbKnots.get(i+1)) { - throw new IllegalArgumentException("The knots values cannot decrease!"); - } - } - - //storing the data + //input data control + for (int i = 0; i < nurbKnots.size() - 1; ++i) { + if (nurbKnots.get(i) > nurbKnots.get(i + 1)) { + throw new IllegalArgumentException("The knots values cannot decrease!"); + } + } + + //storing the data type = SplineType.Nurb; this.weights = new float[controlPoints.size()]; this.knots = nurbKnots; this.basisFunctionDegree = nurbKnots.size() - weights.length; - for(int i=0;i list) { /** * Adds a controlPoint to the spline + * * @param controlPoint a position in world space */ public void addControlPoint(Vector3f controlPoint) { @@ -192,6 +198,7 @@ public void addControlPoint(Vector3f controlPoint) { /** * remove the controlPoint from the spline + * * @param controlPoint the controlPoint to remove */ public void removeControlPoint(Vector3f controlPoint) { @@ -200,8 +207,8 @@ public void removeControlPoint(Vector3f controlPoint) { this.computeTotalLength(); } } - - public void clearControlPoints(){ + + public void clearControlPoints() { controlPoints.clear(); totalLength = 0; } @@ -225,10 +232,10 @@ private void computeTotalLength() { totalLength += l; } } - } else if(type == SplineType.Bezier) { - this.computeBezierLength(); - } else if(type == SplineType.Nurb) { - this.computeNurbLength(); + } else if (type == SplineType.Bezier) { + this.computeBezierLength(); + } else if (type == SplineType.Nurb) { + this.computeNurbLength(); } else { this.initCatmullRomWayPoints(controlPoints); this.computeCatmulLength(); @@ -249,34 +256,37 @@ private void computeCatmulLength() { } } } - + /** - * This method calculates the Bezier curve length. + * This method calculates the Bézier curve length. */ private void computeBezierLength() { - float l = 0; + float l = 0; if (controlPoints.size() > 1) { - for (int i = 0; i < controlPoints.size() - 1; i+=3) { + for (int i = 0; i < controlPoints.size() - 1; i += 3) { l = FastMath.getBezierP1toP2Length(controlPoints.get(i), - controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3)); + controlPoints.get(i + 1), controlPoints.get(i + 2), controlPoints.get(i + 3)); segmentsLength.add(l); totalLength += l; } } } - + /** * This method calculates the NURB curve length. */ private void computeNurbLength() { - //TODO: implement + //TODO: implement } /** * Interpolate a position on the spline - * @param value a value from 0 to 1 that represent the position between the current control point and the next one + * + * @param value a value from 0 to 1 that represent the position between the + * current control point and the next one * @param currentControlPoint the current control point - * @param store a vector to store the result (use null to create a new one that will be returned by the method) + * @param store a vector to store the result (use null to create a new one + * that will be returned by the method) * @return the position */ public Vector3f interpolate(float value, int currentControlPoint, Vector3f store) { @@ -291,11 +301,11 @@ public Vector3f interpolate(float value, int currentControlPoint, Vector3f store FastMath.interpolateLinear(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), store); break; case Bezier: - FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store); - break; + FastMath.interpolateBezier(value, controlPoints.get(currentControlPoint), controlPoints.get(currentControlPoint + 1), controlPoints.get(currentControlPoint + 2), controlPoints.get(currentControlPoint + 3), store); + break; case Nurb: - CurveAndSurfaceMath.interpolateNurbs(value, this, store); - break; + CurveAndSurfaceMath.interpolateNurbs(value, this, store); + break; default: break; } @@ -304,6 +314,8 @@ public Vector3f interpolate(float value, int currentControlPoint, Vector3f store /** * returns the curve tension + * + * @return the value of the tension parameter */ public float getCurveTension() { return curveTension; @@ -316,13 +328,13 @@ public float getCurveTension() { */ public void setCurveTension(float curveTension) { this.curveTension = curveTension; - if(type==SplineType.CatmullRom && !getControlPoints().isEmpty()) { - this.computeTotalLength(); + if (type == SplineType.CatmullRom && !getControlPoints().isEmpty()) { + this.computeTotalLength(); } } /** - * returns true if the spline cycle + * @return true if the spline cycles */ public boolean isCycle() { return cycle; @@ -330,27 +342,28 @@ public boolean isCycle() { /** * set to true to make the spline cycle - * @param cycle + * + * @param cycle true for cyclic, false for acyclic */ public void setCycle(boolean cycle) { - if(type!=SplineType.Nurb) { - if (controlPoints.size() >= 2) { - if (this.cycle && !cycle) { - controlPoints.remove(controlPoints.size() - 1); - } - if (!this.cycle && cycle) { - controlPoints.add(controlPoints.get(0)); - } - this.cycle = cycle; - this.computeTotalLength(); - } else { - this.cycle = cycle; - } - } + if (type != SplineType.Nurb) { + if (controlPoints.size() >= 2) { + if (this.cycle && !cycle) { + controlPoints.remove(controlPoints.size() - 1); + } + if (!this.cycle && cycle) { + controlPoints.add(controlPoints.get(0)); + } + this.cycle = cycle; + this.computeTotalLength(); + } else { + this.cycle = cycle; + } + } } /** - * return the total length of the spline + * @return the total length of the spline */ public float getTotalLength() { return totalLength; @@ -358,6 +371,8 @@ public float getTotalLength() { /** * return the type of the spline + * + * @return the enum value */ public SplineType getType() { return type; @@ -365,7 +380,8 @@ public SplineType getType() { /** * Sets the type of the spline - * @param type + * + * @param type Linear/CatmullRom/Bezier/Nurb */ public void setType(SplineType type) { this.type = type; @@ -374,6 +390,8 @@ public void setType(SplineType type) { /** * returns this spline control points + * + * @return the pre-existing list */ public List getControlPoints() { return controlPoints; @@ -381,61 +399,76 @@ public List getControlPoints() { /** * returns a list of float representing the segments length + * + * @return the pre-existing list */ public List getSegmentsLength() { return segmentsLength; } - + //////////// NURBS getters ///////////////////// - - /** - * This method returns the minimum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb - * type - NPE will be thrown. - * @return the minimum nurb curve knot value - */ + /** + * This method returns the minimum nurb curve knot value. Check the nurb + * type before calling this method. If the curve is not of a Nurb type, an NPE + * will be thrown. + * + * @return the minimum nurb curve knot value + */ public float getMinNurbKnot() { - return knots.get(basisFunctionDegree - 1); + return knots.get(basisFunctionDegree - 1); } - + /** - * This method returns the maximum nurb curve knot value. Check the nurb type before calling this method. It the curve is not of a Nurb - * type - NPE will be thrown. - * @return the maximum nurb curve knot value - */ + * This method returns the maximum nurb curve knot value. Check the nurb + * type before calling this method. If the curve is not of a Nurb type, an NPE + * will be thrown. + * + * @return the maximum nurb curve knot value + */ public float getMaxNurbKnot() { - return knots.get(weights.length); + return knots.get(weights.length); } - + /** * This method returns NURBS' spline knots. + * * @return NURBS' spline knots */ public List getKnots() { - return knots; - } - + return knots; + } + /** * This method returns NURBS' spline weights. + * * @return NURBS' spline weights */ public float[] getWeights() { - return weights; - } - + return weights; + } + /** * This method returns NURBS' spline basis function degree. + * * @return NURBS' spline basis function degree */ public int getBasisFunctionDegree() { - return basisFunctionDegree; - } + return basisFunctionDegree; + } + /** + * Serialize this spline to the specified exporter, for example when + * saving to a J3O file. + * + * @param ex (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.writeSavableArrayList((ArrayList) controlPoints, "controlPoints", null); oc.write(type, "type", SplineType.CatmullRom); - + float list[] = null; if (segmentsLength != null) { list = new float[segmentsLength.size()]; @@ -449,30 +482,97 @@ public void write(JmeExporter ex) throws IOException { oc.writeSavableArrayList((ArrayList) CRcontrolPoints, "CRControlPoints", null); oc.write(curveTension, "curveTension", 0.5f); oc.write(cycle, "cycle", false); - oc.writeSavableArrayList((ArrayList)knots, "knots", null); + + float[] knotArray; + if (knots == null) { + knotArray = null; + } else { + knotArray = new float[knots.size()]; + for (int i = 0; i < knotArray.length; ++i) { + knotArray[i] = knots.get(i); + } + } + oc.write(knotArray, "knots", null); + oc.write(weights, "weights", null); oc.write(basisFunctionDegree, "basisFunctionDegree", 0); } + /** + * De-serialize this spline from the specified importer, for example + * when loading from a J3O file. + * + * @param im (not null) + * @throws IOException from the importer + */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); - controlPoints = (ArrayList) in.readSavableArrayList("controlPoints", new ArrayList()); /* Empty List as default, prevents null pointers */ + controlPoints = in.readSavableArrayList("controlPoints", new ArrayList<>()); + /* Empty List as default, prevents null pointers */ float list[] = in.readFloatArray("segmentsLength", null); if (list != null) { - segmentsLength = new ArrayList(); + segmentsLength = new ArrayList<>(list.length); for (int i = 0; i < list.length; i++) { - segmentsLength.add(new Float(list[i])); + segmentsLength.add(list[i]); } } - type = in.readEnum("pathSplineType", SplineType.class, SplineType.CatmullRom); + type = in.readEnum("type", SplineType.class, SplineType.CatmullRom); totalLength = in.readFloat("totalLength", 0); - CRcontrolPoints = (ArrayList) in.readSavableArrayList("CRControlPoints", null); + CRcontrolPoints = in.readSavableArrayList("CRControlPoints", null); curveTension = in.readFloat("curveTension", 0.5f); cycle = in.readBoolean("cycle", false); - knots = in.readSavableArrayList("knots", null); + + float[] knotArray = in.readFloatArray("knots", null); + if (knotArray == null) { + this.knots = null; + } else { + this.knots = new ArrayList<>(knotArray.length); + for (float knot : knotArray) { + knots.add(knot); + } + } + weights = in.readFloatArray("weights", null); basisFunctionDegree = in.readInt("basisFunctionDegree", 0); } + + /** + * Callback from {@link com.jme3.util.clone.Cloner} to convert this + * shallow-cloned spline into a deep-cloned one, using the specified cloner + * and original to resolve copied fields. + * + * @param cloner the cloner that's cloning this spline (not null) + * @param original the object from which this spline was shallow-cloned (not + * null, unaffected) + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + this.controlPoints = cloner.clone(controlPoints); + if (segmentsLength != null) { + this.segmentsLength = new ArrayList<>(segmentsLength); + } + this.CRcontrolPoints = cloner.clone(CRcontrolPoints); + if (knots != null) { + this.knots = new ArrayList<>(knots); + } + this.weights = cloner.clone(weights); + } + + /** + * Creates a shallow clone for the JME cloner. + * + * @return a new object + */ + @Override + public Spline jmeClone() { + try { + Spline clone = (Spline) clone(); + return clone; + } catch (CloneNotSupportedException exception) { + throw new RuntimeException(exception); + } + } } 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 d9ac33ef0d..de5bfe8ce8 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-2018 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,161 +37,233 @@ import java.io.IOException; /** - * Started Date: Jul 16, 2004

      - * Represents a translation, rotation and scale in one object. - * + * A 3-D coordinate transform composed of translation, rotation, and scaling. + * The order of application is: scale, then rotate, then translate. + * + *

      Started July 16, 2004 + * * @author Jack Lindamood * @author Joshua Slack */ public final class Transform implements Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; - + /** + * Shared instance of the identity transform. Do not modify! + */ public static final Transform IDENTITY = new Transform(); - + /** + * Rotation component. + */ private Quaternion rot = new Quaternion(); + /** + * Translation component: an offset for each local axis. + */ private Vector3f translation = new Vector3f(); + /** + * Scaling component: a scale factor for each local axis. + */ private Vector3f scale = new Vector3f(1, 1, 1); - public Transform(Vector3f translation, Quaternion rot){ + /** + * Instantiates a coordinate transform without scaling. + * + * @param translation the desired translation (not null, unaffected) + * @param rot the desired rotation (not null, unaffected) + */ + public Transform(Vector3f translation, Quaternion rot) { this.translation.set(translation); this.rot.set(rot); } - - public Transform(Vector3f translation, Quaternion rot, Vector3f scale){ + + /** + * Instantiates a coordinate transform with scaling. + * + * @param translation the desired translation (not null, unaffected) + * @param rot the desired rotation (not null, unaffected) + * @param scale the desired scaling (not null, unaffected) + */ + public Transform(Vector3f translation, Quaternion rot, Vector3f scale) { this(translation, rot); this.scale.set(scale); } - public Transform(Vector3f translation){ + /** + * Instantiates a translation-only transform. + * + * @param translation the desired translation (not null, unaffected) + */ + public Transform(Vector3f translation) { this(translation, Quaternion.IDENTITY); } - public Transform(Quaternion rot){ + /** + * Instantiates a rotation-only transform. + * + * @param rot the desired rotation (not null, unaffected) + */ + public Transform(Quaternion rot) { this(Vector3f.ZERO, rot); } - public Transform(){ + /** + * Instantiates an identity transform: no translation, no rotation, and no + * scaling. + */ + public Transform() { this(Vector3f.ZERO, Quaternion.IDENTITY); } /** - * Sets this rotation to the given Quaternion value. - * @param rot The new rotation for this matrix. - * @return this + * Sets the rotation component to the argument. + * + * @param rot the desired rotation value (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Transform setRotation(Quaternion rot) { + assert Quaternion.isValidQuaternion(rot) : "Invalid rotation " + rot; this.rot.set(rot); return this; } /** - * Sets this translation to the given value. - * @param trans The new translation for this matrix. - * @return this + * Sets the translation component to the argument. + * + * @param trans the desired offsets (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Transform setTranslation(Vector3f trans) { + assert Vector3f.isValidVector(trans) : "Invalid translation " + trans; this.translation.set(trans); return this; } /** - * Return the translation vector in this matrix. - * @return translation vector. + * Returns the translation component. + * + * @return the pre-existing instance (not null) */ public Vector3f getTranslation() { return translation; } /** - * Sets this scale to the given value. - * @param scale The new scale for this matrix. - * @return this + * Sets the scaling component to the argument. + * + * @param scale the desired scale factors (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Transform setScale(Vector3f scale) { + assert Vector3f.isValidVector(scale) : "Invalid scale " + scale; this.scale.set(scale); return this; } /** - * Sets this scale to the given value. - * @param scale The new scale for this matrix. - * @return this + * Sets the scaling component to the argument. This yields uniform scaling. + * + * @param scale the desired scale factor for all local axes + * @return the (modified) current instance (for chaining) */ public Transform setScale(float scale) { + assert Float.isFinite(scale) : "Invalid scale " + scale; this.scale.set(scale, scale, scale); return this; } /** - * Return the scale vector in this matrix. - * @return scale vector. + * Returns the scaling component. + * + * @return the pre-existing instance (not null) */ public Vector3f getScale() { return scale; } /** - * Stores this translation value into the given vector3f. If trans is null, a new vector3f is created to - * hold the value. The value, once stored, is returned. - * @param trans The store location for this matrix's translation. - * @return The value of this matrix's translation. + * Copies the translation component to the argument. If the argument is + * null, a new Vector3f is created to hold the value. Either way, the + * current instance is unaffected, unless the argument is its scaling + * component. + * + * @param trans storage for the result (modified if not null) + * @return the translation offsets (either trans or a new + * Vector3f) */ public Vector3f getTranslation(Vector3f trans) { - if (trans==null) trans=new Vector3f(); + if (trans == null) { + trans = new Vector3f(); + } trans.set(this.translation); return trans; } /** - * Stores this rotation value into the given Quaternion. If quat is null, a new Quaternion is created to - * hold the value. The value, once stored, is returned. - * @param quat The store location for this matrix's rotation. - * @return The value of this matrix's rotation. + * Copies the rotation component to the argument. If the argument is null, a + * new Quaternion is created to hold the value. Either way, the current + * instance is unaffected. + * + * @param quat storage for the result (modified if not null) + * @return the rotation value (either quat or a new Quaternion) */ public Quaternion getRotation(Quaternion quat) { - if (quat==null) quat=new Quaternion(); + if (quat == null) { + quat = new Quaternion(); + } quat.set(rot); return quat; } - + /** - * Return the rotation quaternion in this matrix. - * @return rotation quaternion. + * Returns the rotation component. + * + * @return the pre-existing instance (not null) */ public Quaternion getRotation() { return rot; - } - + } + /** - * Stores this scale value into the given vector3f. If scale is null, a new vector3f is created to - * hold the value. The value, once stored, is returned. - * @param scale The store location for this matrix's scale. - * @return The value of this matrix's scale. + * Copies the scaling component to the argument. If the argument is null, a + * new Vector3f is created to hold the value. Either way, the current + * instance is unaffected, unless the argument is its translation component. + * + * @param scale storage for the result (modified if not null) + * @return the scale factors (either scale or a new Vector3f) */ public Vector3f getScale(Vector3f scale) { - if (scale==null) scale=new Vector3f(); + if (scale == null) { + scale = new Vector3f(); + } scale.set(this.scale); return scale; } /** - * Sets this transform to the interpolation between the first transform and the second by delta amount. - * @param t1 The beginning transform. - * @param t2 The ending transform. - * @param delta An amount between 0 and 1 representing how far to interpolate from t1 to t2. + * Interpolates quickly between the specified transforms, using + * {@link Quaternion#nlerp(com.jme3.math.Quaternion, float)} and + * {@link Vector3f#interpolateLocal(com.jme3.math.Vector3f, com.jme3.math.Vector3f, float)}. + * + * @param t1 the desired value when delta=0 (not null, + * unaffected unless it's this) + * @param t2 the desired value when delta=1 (not null, + * unaffected unless it's this) + * @param delta the fractional change amount */ public void interpolateTransforms(Transform t1, Transform t2, float delta) { - t1.rot.nlerp(t2.rot, delta); this.rot.set(t1.rot); - this.translation.interpolateLocal(t1.translation,t2.translation,delta); - this.scale.interpolateLocal(t1.scale,t2.scale,delta); + this.rot.nlerp(t2.rot, delta); + this.translation.interpolateLocal(t1.translation, t2.translation, delta); + this.scale.interpolateLocal(t1.scale, t2.scale, delta); } /** - * Changes the values of this matrix according to its parent. Very similar to the concept of Node/Spatial transforms. - * @param parent The parent matrix. - * @return This matrix, after combining. + * Combines with the argument and returns the (modified) current instance. + * This method is used to combine Node and Spatial transforms. + * + * @param parent the parent transform (not null, {@code parent.rot.norm()} + * approximately equal to 1, unaffected unless it's this) + * @return the (modified) current instance (for chaining) */ public Transform combineWithParent(Transform parent) { //applying parent scale to local scale @@ -202,56 +274,92 @@ public Transform combineWithParent(Transform parent) { translation.multLocal(parent.scale); //applying parent rotation to local translation, then applying parent translation to local translation. //Note that parent.rot.multLocal(translation) doesn't modify "parent.rot" but "translation" - parent - .rot - .multLocal(translation) - .addLocal(parent.translation); + parent.rot + .multLocal(translation) + .addLocal(parent.translation); return this; } /** - * Sets this matrix's translation to the given x,y,z values. - * @param x This matrix's new x translation. - * @param y This matrix's new y translation. - * @param z This matrix's new z translation. - * @return this + * Sets the translation component to the specified values. + * + * @param x the desired X offset + * @param y the desired Y offset + * @param z the desired Z offset + * @return the (modified) current instance (for chaining) */ - public Transform setTranslation(float x,float y, float z) { - translation.set(x,y,z); + public Transform setTranslation(float x, float y, float z) { + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid translation " + x + ", " + y + ", " + z; + translation.set(x, y, z); return this; } /** - * Sets this matrix's scale to the given x,y,z values. - * @param x This matrix's new x scale. - * @param y This matrix's new y scale. - * @param z This matrix's new z scale. - * @return this + * Sets the scaling component to the specified values. + * + * @param x the desired X scale factor + * @param y the desired Y scale factor + * @param z the desired Z scale factor + * @return the (modified) current instance (for chaining) */ public Transform setScale(float x, float y, float z) { - scale.set(x,y,z); + assert Float.isFinite(x) && Float.isFinite(y) && Float.isFinite(z) : "Invalid scale " + x + ", " + y + ", " + z; + scale.set(x, y, z); return this; } - public Vector3f transformVector(final Vector3f in, Vector3f store){ - if (store == null) + /** + * Transforms the specified coordinates and returns the result in + * store. If the store is null, a new Vector3f is + * created to hold the value. Either way, the current instance is + * unaffected, unless store is its translation or scaling. + *

      + * The transform's quaternion is assumed to be normalized. No error checking + * is performed; the caller should ensure that {@code rot.norm()} is + * approximately equal to 1. + * + * @param in the coordinates to transform (not null, unaffected) + * @param store storage for the result (modified if not null) + * @return the transformed coordinates (either store or a new + * Vector3f) + */ + public Vector3f transformVector(final Vector3f in, Vector3f store) { + if (store == null) { store = new Vector3f(); + } // multiply with scale first, then rotate, finally translate (cf. // Eberly) return rot.mult(store.set(in).multLocal(scale), store).addLocal(translation); } - public Vector3f transformInverseVector(final Vector3f in, Vector3f store){ - if (store == null) + /** + * Applies the inverse transform to the specified coordinates and returns + * the result in store. If the store is null, a + * new Vector3f is created to hold the value. Either way, the current + * instance is unaffected, unless store is its translation or + * scaling. + *

      + * The transform's quaternion is assumed to be normalized. No error checking + * is performed; the caller should ensure that {@code rot.norm()} is + * approximately equal to 1. + * + * @param in the coordinates to transform (not null, unaffected unless it's + * store) + * @param store storage for the result (modified if not null) + * @return the transformed coordinates (either store or a new + * Vector3f) + */ + public Vector3f transformInverseVector(final Vector3f in, Vector3f store) { + if (store == null) { store = new Vector3f(); + } - // The author of this code should look above and take the inverse of that - // But for some reason, they didn't .. + // The author of this code should've looked above and taken the inverse of that, + // but for some reason, they didn't. // in.subtract(translation, store).divideLocal(scale); // rot.inverse().mult(store, store); - in.subtract(translation, store); rot.inverse().mult(store, store); store.divideLocal(scale); @@ -259,10 +367,23 @@ public Vector3f transformInverseVector(final Vector3f in, Vector3f store){ return store; } + /** + * Creates an equivalent transform matrix. The current instance is + * unaffected. + * + * @return a new Matrix4f + */ public Matrix4f toTransformMatrix() { return toTransformMatrix(null); } + /** + * Converts to an equivalent transform matrix. The current instance is + * unaffected. + * + * @param store storage for the result (modified if not null) + * @return a transform matrix (either store or a new Matrix4f) + */ public Matrix4f toTransformMatrix(Matrix4f store) { if (store == null) { store = new Matrix4f(); @@ -272,7 +393,17 @@ public Matrix4f toTransformMatrix(Matrix4f store) { store.setScale(scale); return store; } - + + /** + * Sets the current instance from a transform matrix. Any reflection or shear in the + * matrix is lost -- in other words, it may not be possible to recreate the + * original matrix from the result. + * + *

      After this method is invoked, all components of {@code scale} will be + * non-negative, even if {@code mat} includes reflection. + * + * @param mat the input matrix (not null, unaffected) + */ public void fromTransformMatrix(Matrix4f mat) { TempVars vars = TempVars.get(); translation.set(mat.toTranslationVector(vars.vect1)); @@ -280,15 +411,25 @@ public void fromTransformMatrix(Matrix4f mat) { scale.set(mat.toScaleVector(vars.vect2)); vars.release(); } - + + /** + * Returns the inverse. The current instance is unaffected. + * + *

      Assumes (but does not verify) that the scale factors are all positive. + * If any component of {@code scale} is negative or zero, the result is + * undefined. + * + * @return a new Transform + */ public Transform invert() { Transform t = new Transform(); t.fromTransformMatrix(toTransformMatrix().invertLocal()); return t; } - + /** - * Loads the identity. Equal to translation=0,0,0 scale=1,1,1 rot=0,0,0,1. + * Sets the current instance to the identity transform: translation=(0,0,0) + * scaling=(1,1,1) rotation=(0,0,0,1). */ public void loadIdentity() { translation.set(0, 0, 0); @@ -297,9 +438,9 @@ public void loadIdentity() { } /** - * Test for exact identity. + * Tests for exact identity. The current instance is unaffected. * - * @return true if exactly equal to {@link #IDENTITY}, otherwise false + * @return true if equal to {@link #IDENTITY}, otherwise false */ public boolean isIdentity() { return translation.x == 0f && translation.y == 0f && translation.z == 0f @@ -307,6 +448,12 @@ public boolean isIdentity() { && rot.w == 1f && rot.x == 0f && rot.y == 0f && rot.z == 0f; } + /** + * 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 + */ @Override public int hashCode() { int hash = 7; @@ -316,6 +463,15 @@ public int hashCode() { return hash; } + /** + * 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 (may be null, unaffected) + * @return true if {@code this} and {@code obj} have identical values, + * otherwise false + */ @Override public boolean equals(Object obj) { if (obj == null) { @@ -330,25 +486,45 @@ public boolean equals(Object obj) { && this.rot.equals(other.rot); } + /** + * Returns a string representation of the transform, which is unaffected. + * For example, the identity transform is represented by: + *

      +     * 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) + */ @Override - public String toString(){ - return getClass().getSimpleName() + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" - + "[ " + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "]\n" - + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]"; + public String toString() { + return getClass().getSimpleName() + + "[ " + translation.x + ", " + translation.y + ", " + translation.z + "]\n" + + "[ " + rot.x + ", " + rot.y + ", " + rot.z + ", " + rot.w + "]\n" + + "[ " + scale.x + " , " + scale.y + ", " + scale.z + "]"; } /** - * Sets this matrix to be equal to the given matrix. - * @param matrixQuat The matrix to be equal to. - * @return this + * Copies all 3 components from the argument. + * + * @param transform The Transform to copy (not null, unaffected) + * @return the (modified) current instance (for chaining) */ - public Transform set(Transform matrixQuat) { - this.translation.set(matrixQuat.translation); - this.rot.set(matrixQuat.rot); - this.scale.set(matrixQuat.scale); + public Transform set(Transform transform) { + this.translation.set(transform.translation); + this.rot.set(transform.rot); + this.scale.set(transform.scale); return this; } + /** + * Serializes to the argument, for example when saving to a J3O file. The + * current instance is unaffected. + * + * @param e (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); @@ -357,15 +533,27 @@ public void write(JmeExporter e) throws IOException { capsule.write(scale, "scale", Vector3f.UNIT_XYZ); } + /** + * De-serializes from the argument, for example when loading from a J3O + * file. + * + * @param importer (not null) + * @throws IOException from the importer + */ @Override - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); - - rot.set((Quaternion)capsule.readSavable("rot", Quaternion.IDENTITY)); - translation.set((Vector3f)capsule.readSavable("translation", Vector3f.ZERO)); - scale.set((Vector3f)capsule.readSavable("scale", Vector3f.UNIT_XYZ)); - } - + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); + + rot.set((Quaternion) capsule.readSavable("rot", Quaternion.IDENTITY)); + translation.set((Vector3f) capsule.readSavable("translation", Vector3f.ZERO)); + scale.set((Vector3f) capsule.readSavable("scale", Vector3f.UNIT_XYZ)); + } + + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance, equivalent to the current one + */ @Override public Transform clone() { try { 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 5902682131..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-2018 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,102 +37,110 @@ import java.io.IOException; /** - * Triangle defines an object for containing triangle information. - * The triangle is defined by a collection of three {@link Vector3f} - * objects. - * + * 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; - - private Vector3f pointa = new Vector3f(); - private Vector3f pointb = new Vector3f(); - private Vector3f pointc = new Vector3f(); + /** + * 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() { } /** - * Constructor instantiates a new Triangle object with the - * supplied vectors as the points. It is recommended that the vertices - * be supplied in a counter clockwise winding to support normals for a - * right handed coordinate system. - * @param p1 the first point of the triangle. - * @param p2 the second point of the triangle. - * @param p3 the third point of the triangle. + * 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) + * @param p2 the location of the 2nd vertex (not null, unaffected) + * @param p3 the location of the 3rd vertex (not null, unaffected) */ public Triangle(Vector3f p1, Vector3f p2, Vector3f p3) { - pointa.set(p1); - pointb.set(p2); - pointc.set(p3); + pointA.set(p1); + pointB.set(p2); + pointC.set(p3); } /** - * get retrieves a point on the triangle denoted by the index - * supplied. + * Accesses the location of the indexed vertex. * - * @param i the index of the point (0, 1, or 2) - * @return a pre-existing location vector + * @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 */ public Vector3f get(int i) { switch (i) { case 0: - return pointa; + return pointA; case 1: - return pointb; + return pointB; case 2: - return pointc; + return pointC; default: return null; } } /** - * Access the location of the 1st point (A). + * Accesses the location of the first vertex. * - * @return the pre-existing vector (not null) + * @return the pre-existing location vector (not null) */ @Override public Vector3f get1() { - return pointa; + return pointA; } /** - * Access the location of the 2nd point (B). + * Accesses the location of the 2nd vertex. * - * @return the pre-existing vector (not null) + * @return the pre-existing location vector (not null) */ @Override public Vector3f get2() { - return pointb; + return pointB; } /** - * Access the location of the 3rd point (C). + * Accesses the location of the 3rd vertex. * - * @return the pre-existing vector (not null) + * @return the pre-existing location vector (not null) */ @Override public Vector3f get3() { - return pointc; + return pointC; } /** - * set sets one of the triangle's points to that specified as a - * parameter. + * Alters the location of the indexed vertex and deletes the stored centroid + * and normal. * - * @param i the index to place the point (0, 1, or 2) - * @param point the desired location of the point (not null, unaffected) + * @param i the index of the vertex to alter (0, 1, or 2) + * @param point the desired location (not null, unaffected) */ public void set(int i, Vector3f point) { center = null; @@ -140,24 +148,25 @@ public void set(int i, Vector3f point) { switch (i) { case 0: - pointa.set(point); + pointA.set(point); break; case 1: - pointb.set(point); + pointB.set(point); break; case 2: - pointc.set(point); + pointC.set(point); break; } } /** - * set alters the location of one of the triangle's points. + * Alters the location of the indexed vertex and deletes the stored centroid + * and normal. * - * @param i the index to place the point (0, 1, or 2) - * @param x the desired X-component of the point's location - * @param y the desired Y-component of the point's location - * @param z the desired Z-component of the point's location + * @param i the index of the vertex to alter (0, 1, or 2) + * @param x the desired X coordinate + * @param y the desired Y coordinate + * @param z the desired Z coordinate */ public void set(int i, float x, float y, float z) { center = null; @@ -165,19 +174,20 @@ public void set(int i, float x, float y, float z) { switch (i) { case 0: - pointa.set(x, y, z); + pointA.set(x, y, z); break; case 1: - pointb.set(x, y, z); + pointB.set(x, y, z); break; case 2: - pointc.set(x, y, z); + pointC.set(x, y, z); break; } } /** - * set1 alters the location of the triangle's 1st point. + * Alters the location of the first vertex and deletes the stored centroid + * and normal. * * @param v the desired location (not null, unaffected) */ @@ -185,11 +195,12 @@ public void set1(Vector3f v) { center = null; normal = null; - pointa.set(v); + pointA.set(v); } /** - * set2 alters the location of the triangle's 2nd point. + * Alters the location of the 2nd vertex and deletes the stored centroid and + * normal. * * @param v the desired location (not null, unaffected) */ @@ -197,11 +208,12 @@ public void set2(Vector3f v) { center = null; normal = null; - pointb.set(v); + pointB.set(v); } /** - * set3 alters the location of the triangle's 3rd point. + * Alters the location of the 3rd vertex and deletes the stored centroid and + * normal. * * @param v the desired location (not null, unaffected) */ @@ -209,56 +221,58 @@ public void set3(Vector3f v) { center = null; normal = null; - pointc.set(v); + pointC.set(v); } /** - * set alters the locations of all 3 points. + * Alters the locations of all 3 vertices and deletes the stored centroid + * and normal. * - * @param v1 the desired location of the 1st point (not null, unaffected) - * @param v2 the desired location of the 2nd point (not null, unaffected) - * @param v3 the desired location of the 3rd point (not null, unaffected) + * @param v1 the desired location of the first vertex (not null, unaffected) + * @param v2 the desired location of the 2nd vertex (not null, unaffected) + * @param v3 the desired location of the 3rd vertex (not null, unaffected) */ @Override public void set(Vector3f v1, Vector3f v2, Vector3f v3) { center = null; normal = null; - pointa.set(v1); - pointb.set(v2); - pointc.set(v3); + pointA.set(v1); + pointB.set(v2); + pointC.set(v3); } /** - * calculateCenter finds the average point of the triangle. - * + * Recalculates the stored centroid based on the current vertex locations. */ public void calculateCenter() { if (center == null) { - center = new Vector3f(pointa); + center = new Vector3f(pointA); } else { - center.set(pointa); + center.set(pointA); } - center.addLocal(pointb).addLocal(pointc).multLocal(FastMath.ONE_THIRD); + center.addLocal(pointB).addLocal(pointC).multLocal(FastMath.ONE_THIRD); } /** - * calculateNormal generates the normal for this triangle - * + * Recalculates the stored normal based on the current vertex locations. */ public void calculateNormal() { if (normal == null) { - normal = new Vector3f(pointb); + normal = new Vector3f(pointB); } else { - normal.set(pointb); + normal.set(pointB); } - normal.subtractLocal(pointa).crossLocal(pointc.x - pointa.x, pointc.y - pointa.y, pointc.z - pointa.z); + normal.subtractLocal(pointA).crossLocal(pointC.x - pointA.x, pointC.y - pointA.y, pointC.z - pointA.z); normal.normalizeLocal(); } /** - * obtains the center point of this triangle (average of the three triangles) - * @return the center point. + * 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 + * re-use) */ public Vector3f getCenter() { if (center == null) { @@ -268,18 +282,19 @@ public Vector3f getCenter() { } /** - * sets the center point of this triangle (average of the three triangles) - * @param center the center point. + * Alters the stored centroid without affecting the stored normal or any + * vertex locations. + * + * @param center the desired value (alias created if not null) */ public void setCenter(Vector3f center) { this.center = center; } /** - * obtains the unit length normal vector of this triangle, if set or - * calculated - * - * @return the normal vector + * Accesses the stored normal, updating it if it is null. + * + * @return unit normal vector (an internal vector subject to re-use) */ public Vector3f getNormal() { if (normal == null) { @@ -289,40 +304,46 @@ public Vector3f getNormal() { } /** - * sets the normal vector of this triangle (to conform, must be unit length) - * @param normal the normal vector. + * Alters the stored normal without affecting the stored centroid or any + * vertex locations. + * + * @param normal the desired value (alias created if not null) */ public void setNormal(Vector3f normal) { this.normal = normal; } /** - * obtains the projection of the vertices relative to the line origin. - * @return the projection of the triangle. + * Returns the projection of the vertices relative to the line origin. + * + * @return the stored projection value */ public float getProjection() { return this.projection; } /** - * sets the projection of the vertices relative to the line origin. - * @param projection the projection of the triangle. + * Alters the projection of the vertices relative to the line origin. + * + * @param projection the desired projection value */ public void setProjection(float projection) { this.projection = projection; } /** - * obtains an index that this triangle represents if it is contained in a OBBTree. - * @return the index in an OBBtree + * Returns the index of this triangle, used to identify it in an OBBTree. + * + * @return the stored index */ public int getIndex() { return index; } /** - * sets an index that this triangle represents if it is contained in a OBBTree. - * @param index the index in an OBBtree + * Alters the index of this triangle, used to identify it in an OBBTree. + * + * @param index the desired index */ public void setIndex(int index) { this.index = index; @@ -339,27 +360,47 @@ public static Vector3f computeTriangleNormal(Vector3f v1, Vector3f v2, Vector3f return store.normalizeLocal(); } + /** + * Serializes this triangle to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ @Override public void write(JmeExporter e) throws IOException { - e.getCapsule(this).write(pointa, "pointa", Vector3f.ZERO); - e.getCapsule(this).write(pointb, "pointb", Vector3f.ZERO); - e.getCapsule(this).write(pointc, "pointc", Vector3f.ZERO); + e.getCapsule(this).write(pointA, "pointa", Vector3f.ZERO); + e.getCapsule(this).write(pointB, "pointb", Vector3f.ZERO); + e.getCapsule(this).write(pointC, "pointc", Vector3f.ZERO); } + /** + * De-serializes this triangle from the specified importer, for example when + * loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ @Override - public void read(JmeImporter e) throws IOException { - pointa = (Vector3f) e.getCapsule(this).readSavable("pointa", Vector3f.ZERO.clone()); - pointb = (Vector3f) e.getCapsule(this).readSavable("pointb", Vector3f.ZERO.clone()); - pointc = (Vector3f) e.getCapsule(this).readSavable("pointc", Vector3f.ZERO.clone()); + public void read(JmeImporter importer) throws IOException { + pointA = (Vector3f) importer.getCapsule(this).readSavable("pointa", Vector3f.ZERO.clone()); + pointB = (Vector3f) importer.getCapsule(this).readSavable("pointb", Vector3f.ZERO.clone()); + pointC = (Vector3f) importer.getCapsule(this).readSavable("pointc", Vector3f.ZERO.clone()); } + /** + * Creates a copy of this triangle. + * + * @return a new instance, equivalent to this one + */ @Override public Triangle clone() { try { Triangle t = (Triangle) super.clone(); - t.pointa = pointa.clone(); - t.pointb = pointb.clone(); - t.pointc = pointc.clone(); + t.pointA = pointA.clone(); + t.pointB = pointB.clone(); + t.pointC = pointC.clone(); + // XXX: the center and normal are not cloned! return t; } catch (CloneNotSupportedException e) { throw new AssertionError(); diff --git a/jme3-core/src/main/java/com/jme3/math/Vector2f.java b/jme3-core/src/main/java/com/jme3/math/Vector2f.java index e2c3a96c09..e32871d75b 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector2f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector2f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,35 +38,64 @@ import java.util.logging.Logger; /** - * Vector2f defines a Vector for a two float value vector. - * + * A vector composed of 2 single-precision components, used to represent + * locations, offsets, directions, and rotations in 2-dimensional space. + * + *

      Methods with names ending in "Local" modify the current instance. They are + * used to avoid creating garbage. + * * @author Mark Powell * @author Joshua Slack */ public final class Vector2f implements Savable, Cloneable, java.io.Serializable { - static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Vector2f.class.getName()); - + /** + * Shared instance of the all-zero vector (0,0). Do not modify! + */ public static final Vector2f ZERO = new Vector2f(0f, 0f); + /** + * Shared instance of the all-NaN vector (NaN,NaN). Do not modify! + */ + public static final Vector2f NAN = new Vector2f(Float.NaN, Float.NaN); + /** + * Shared instance of the +X direction (1,0). Do not modify! + */ + public static final Vector2f UNIT_X = new Vector2f(1, 0); + /** + * Shared instance of the +Y direction (0,1). Do not modify! + */ + public static final Vector2f UNIT_Y = new Vector2f(0, 1); + /** + * Shared instance of the all-ones vector (1,1). Do not modify! + */ public static final Vector2f UNIT_XY = new Vector2f(1f, 1f); - /** - * the x value of the vector. + * Shared instance of the all-plus-infinity vector (+Inf,+Inf). Do not modify! + */ + public static final Vector2f POSITIVE_INFINITY = new Vector2f( + Float.POSITIVE_INFINITY, + Float.POSITIVE_INFINITY); + /** + * Shared instance of the all-negative-infinity vector (-Inf,-Inf). Do not modify! + */ + public static final Vector2f NEGATIVE_INFINITY = new Vector2f( + Float.NEGATIVE_INFINITY, + Float.NEGATIVE_INFINITY); + /** + * The first (X) component. */ public float x; /** - * the y value of the vector. + * The 2nd (Y) component. */ public float y; /** - * Creates a Vector2f with the given initial x and y values. - * - * @param x - * The x value of this Vector2f. - * @param y - * The y value of this Vector2f. + * Instantiates a vector with specified components. + * + * @param x the desired X component + * @param y the desired Y component */ public Vector2f(float x, float y) { this.x = x; @@ -74,17 +103,16 @@ public Vector2f(float x, float y) { } /** - * Creates a Vector2f with x and y set to 0. Equivalent to Vector2f(0,0). + * Instantiates an all-zero vector (0,0). */ public Vector2f() { x = y = 0; } /** - * Creates a new Vector2f that contains the passed vector's information - * - * @param vector2f - * The vector to copy + * Instantiates a copy of the argument. + * + * @param vector2f the vector to copy (not null, unaffected) */ public Vector2f(Vector2f vector2f) { this.x = vector2f.x; @@ -92,13 +120,11 @@ public Vector2f(Vector2f vector2f) { } /** - * set the x and y values of the vector - * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @return this vector + * Sets both components to specified values. + * + * @param x the desired X component + * @param y the desired Y component + * @return the (modified) current instance (for chaining) */ public Vector2f set(float x, float y) { this.x = x; @@ -107,11 +133,10 @@ public Vector2f set(float x, float y) { } /** - * set the x and y values of the vector from another vector - * - * @param vec - * the vector to copy from - * @return this vector + * Copies both components from the argument. + * + * @param vec the Vector2f to copy (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Vector2f set(Vector2f vec) { this.x = vec.x; @@ -120,13 +145,12 @@ public Vector2f set(Vector2f vec) { } /** - * add adds a provided vector to this vector creating a - * resultant vector which is returned. If the provided vector is null, null - * is returned. - * - * @param vec - * the vector to add to this. - * @return the resultant vector. + * Adds the argument and returns the sum as a new instance. If the argument + * is null, null is returned. Either way, the current instance is + * unaffected. + * + * @param vec the vector to add (unaffected) or null for none + * @return a new Vector2f or null */ public Vector2f add(Vector2f vec) { if (null == vec) { @@ -137,13 +161,12 @@ public Vector2f add(Vector2f vec) { } /** - * addLocal adds a provided vector to this vector internally, - * and returns a handle to this vector for easy chaining of calls. If the - * provided vector is null, null is returned. - * - * @param vec - * the vector to add to this vector. - * @return this + * 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 {@code this}) or + * null for none + * @return the (modified) current instance or null */ public Vector2f addLocal(Vector2f vec) { if (null == vec) { @@ -156,15 +179,12 @@ public Vector2f addLocal(Vector2f vec) { } /** - * addLocal adds the provided values to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. - * - * @param addX - * value to add to x - * @param addY - * value to add to y - * @return this + * Adds specified amounts to the vector's components and returns the + * (modified) current instance. + * + * @param addX the amount to add to the X component + * @param addY the amount to add to the Y component + * @return the (modified) current instance (for chaining) */ public Vector2f addLocal(float addX, float addY) { x += addX; @@ -173,34 +193,34 @@ public Vector2f addLocal(float addX, float addY) { } /** - * add adds this vector by vec and stores the - * result in result. - * - * @param vec - * The vector to add. - * @param result - * The vector to store the result in. - * @return The result vector, after adding. + * Adds a specified vector and returns the sum in a 3rd vector. If the + * argument is null, null is returned. Either way, the current instance is + * unaffected unless it's {@code result}. + * + * @param vec the vector to add (unaffected unless it's {@code result}) or + * null for none + * @param result storage for the sum, or null for a new Vector2f + * @return the sum (either {@code result} or a new Vector2f) */ public Vector2f add(Vector2f vec, Vector2f result) { if (null == vec) { logger.warning("Provided vector is null, null returned."); return null; } - if (result == null) + if (result == null) { result = new Vector2f(); + } result.x = x + vec.x; result.y = y + vec.y; return result; } /** - * dot calculates the dot product of this vector with a - * provided vector. If the provided vector is null, 0 is returned. - * - * @param vec - * the vector to dot with this vector. - * @return the resultant dot product of this vector and a given vector. + * Returns the dot (or inner) product with the argument. If the argument is + * null, 0 is returned. Either way, the current instance is unaffected. + * + * @param vec the vector to multiply (unaffected) or null for none + * @return the product or 0 */ public float dot(Vector2f vec) { if (null == vec) { @@ -211,98 +231,110 @@ public float dot(Vector2f vec) { } /** - * cross calculates the cross product of this vector with a - * parameter vector v. - * - * @param v - * the vector to take the cross product of with this. - * @return the cross product vector. + * Calculates a cross product with the argument and returns the product as a + * new instance. The current instance is unaffected. + * + * @param v the right factor (not null, unaffected) + * @return {@code this} cross {@code v} (a new Vector3f) */ public Vector3f cross(Vector2f v) { return new Vector3f(0, 0, determinant(v)); } + /** + * Returns the Z component of the cross product with the argument. The + * current instance is unaffected. + * + * @param v the right factor (not null, unaffected) + * @return the Z component of {@code this} cross {@code v} + */ public float determinant(Vector2f v) { return (x * v.y) - (y * v.x); } - + /** - * Sets this vector to the interpolation by changeAmnt from this to the - * finalVec this=(1-changeAmnt)*this + changeAmnt * finalVec - * - * @param finalVec - * The final vector to interpolate towards - * @param changeAmnt - * An amount between 0.0 - 1.0 representing a percentage change - * from this towards finalVec - */ - public Vector2f interpolateLocal(Vector2f finalVec, float changeAmnt) { - this.x = (1 - changeAmnt) * this.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * this.y + changeAmnt * finalVec.y; + * Interpolates linearly between the current instance and the specified + * vector, returning the (modified) current instance. + * + *

      this = (1 - changeAmount) * this + changeAmount * finalVec + * + * @param finalVec the desired value when changeAmount=1 (not null, + * unaffected unless it's {@code this}) + * @param changeAmount the fractional change amount + * @return the (modified) current instance (for chaining) + */ + public Vector2f interpolateLocal(Vector2f finalVec, float changeAmount) { + this.x = (1 - changeAmount) * this.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * this.y + changeAmount * finalVec.y; return this; } /** - * Sets this vector to the interpolation by changeAmnt from beginVec to - * finalVec this=(1-changeAmnt)*beginVec + changeAmnt * finalVec - * - * @param beginVec - * The beginning vector (delta=0) - * @param finalVec - * The final vector to interpolate towards (delta=1) - * @param changeAmnt - * An amount between 0.0 - 1.0 representing a percentage change - * from beginVec towards finalVec + * Interpolates linearly between the specified beginning and final vectors, + * returning the (modified) current instance. + * + *

      this = (1 - changeAmount) * beginVec + changeAmount * finalVec + * + * @param beginVec the desired value when changeAmount=0 (not null, + * unaffected unless it's {@code this}) + * @param finalVec the desired value when changeAmount=1 (not null, + * unaffected unless it's {@code this}) + * @param changeAmount the fractional change amount + * @return the (modified) current instance (for chaining) */ public Vector2f interpolateLocal(Vector2f beginVec, Vector2f finalVec, - float changeAmnt) { - this.x = (1 - changeAmnt) * beginVec.x + changeAmnt * finalVec.x; - this.y = (1 - changeAmnt) * beginVec.y + changeAmnt * finalVec.y; + float changeAmount) { + this.x = (1 - changeAmount) * beginVec.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * beginVec.y + changeAmount * finalVec.y; return this; } /** - * Check a vector... if it is null or its floats are NaN or infinite, return - * false. Else return true. - * - * @param vector - * the vector to check - * @return true or false as stated above. + * Tests whether the argument is a valid vector, returning false if it's + * null or if any component is NaN or infinite. + * + * @param vector the vector to test (unaffected) + * @return true if non-null and finite, otherwise false */ public static boolean isValidVector(Vector2f vector) { - if (vector == null) return false; - if (Float.isNaN(vector.x) || - Float.isNaN(vector.y)) return false; - if (Float.isInfinite(vector.x) || - Float.isInfinite(vector.y)) return false; - return true; + if (vector == null) { + return false; + } + if (Float.isNaN(vector.x) + || Float.isNaN(vector.y)) { + return false; + } + if (Float.isInfinite(vector.x) + || Float.isInfinite(vector.y)) { + return false; + } + return true; } /** - * length calculates the magnitude of this vector. - * - * @return the length or magnitude of the vector. + * Returns the length (or magnitude). The current instance is unaffected. + * + * @return the root-sum of the squared components (not negative) */ public float length() { return FastMath.sqrt(lengthSquared()); } /** - * lengthSquared calculates the squared value of the - * magnitude of the vector. - * - * @return the magnitude squared of the vector. + * Returns the square of the length. The current instance is unaffected. + * + * @return the sum of the squared components (not negative) */ public float lengthSquared() { return x * x + y * y; } /** - * distanceSquared calculates the distance squared between - * this vector and vector v. + * Returns the square of the distance between the current instance and the + * argument. The current instance is unaffected. * - * @param v the second vector to determine the distance squared. - * @return the distance squared between the two vectors. + * @param v the vector to compare (not null, unaffected) + * @return the square of the Euclidean distance (not negative) */ public float distanceSquared(Vector2f v) { double dx = x - v.x; @@ -311,12 +343,12 @@ public float distanceSquared(Vector2f v) { } /** - * distanceSquared calculates the distance squared between - * this vector and vector v. + * Returns the square of the distance between the current instance and a + * vector with the specified components. The current instance is unaffected. * - * @param otherX The X coordinate of the v vector - * @param otherY The Y coordinate of the v vector - * @return the distance squared between the two vectors. + * @param otherX the X component of the vector to compare + * @param otherY the Y component of the vector to compare + * @return the square of the Euclidean distance (not negative) */ public float distanceSquared(float otherX, float otherY) { double dx = x - otherX; @@ -325,35 +357,33 @@ public float distanceSquared(float otherX, float otherY) { } /** - * distance calculates the distance between this vector and - * vector v. + * Returns the distance between the current instance and the argument. The + * current instance is unaffected. * - * @param v the second vector to determine the distance. - * @return the distance between the two vectors. + * @param v the vector to compare (not null, unaffected) + * @return the Euclidean distance (not negative) */ public float distance(Vector2f v) { return FastMath.sqrt(distanceSquared(v)); } /** - * mult multiplies this vector by a scalar. The resultant - * vector is returned. - * - * @param scalar - * the value to multiply this vector by. - * @return the new vector. + * Multiplies with the scalar argument and returns the product as a new + * instance. The current instance is unaffected. + * + * @param scalar the scaling factor + * @return a new {@code Vector2f} */ public Vector2f mult(float scalar) { return new Vector2f(x * scalar, y * scalar); } /** - * multLocal multiplies this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. - * - * @param scalar - * the value to multiply this vector by. - * @return this + * Multiplies by the scalar argument and returns the (modified) current + * instance. + * + * @param scalar the scaling factor + * @return the (modified) current instance (for chaining) */ public Vector2f multLocal(float scalar) { x *= scalar; @@ -362,13 +392,12 @@ public Vector2f multLocal(float scalar) { } /** - * multLocal multiplies a provided vector by this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to mult to this vector. - * @return this + * 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 {@code this}) or + * null for none + * @return the (modified) current instance (for chaining) or null */ public Vector2f multLocal(Vector2f vec) { if (null == vec) { @@ -381,15 +410,12 @@ public Vector2f multLocal(Vector2f vec) { } /** - * Multiplies this Vector2f's x and y by the scalar and stores the result in - * product. The result is returned for chaining. Similar to - * product=this*scalar; - * - * @param scalar - * The scalar to multiply by. - * @param product - * The vector2f to store the result in. - * @return product, after multiplication. + * Multiplies with the specified scalar and stores the product in the + * specified vector. The current instance is unaffected. + * + * @param scalar the scaling factor + * @param product storage for the product, or null for a new Vector2f + * @return either {@code product} or a new Vector2f */ public Vector2f mult(float scalar, Vector2f product) { if (null == product) { @@ -402,25 +428,48 @@ public Vector2f mult(float scalar, Vector2f product) { } /** - * divide divides the values of this vector by a scalar and - * returns the result. The values of this vector remain untouched. - * - * @param scalar - * the value to divide this vectors attributes by. - * @return the result Vector. + * Multiplies component-wise by the specified components and returns the + * product as a new instance. The current instance is unaffected. + * + * @param x the scale factor for the X component + * @param y the scale factor for the Y component + * @return a new Vector2f + */ + public Vector2f mult(float x, float y) { + return new Vector2f(this.x * x, this.y * y); + } + + /** + * Multiplies component-wise by the specified components and returns the + * (modified) current instance. + * + * @param x the scale factor for the X component + * @param y the scale factor for the Y component + * @return the (modified) current instance (for chaining) + */ + public Vector2f multLocal(float x, float y) { + this.x *= x; + this.y *= y; + return this; + } + + /** + * Divides by the scalar argument and returns the quotient as a new + * instance. The current instance is unaffected. + * + * @param scalar the divisor + * @return a new {@code Vector2f} */ public Vector2f divide(float scalar) { return new Vector2f(x / scalar, y / scalar); } /** - * divideLocal divides this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. Dividing - * by zero will result in an exception. - * - * @param scalar - * the value to divides this vector by. - * @return this + * Divides the vector by the scalar argument and returns the (modified) + * current instance. + * + * @param scalar the divisor + * @return the (modified) current instance (for chaining) */ public Vector2f divideLocal(float scalar) { x /= scalar; @@ -429,19 +478,44 @@ public Vector2f divideLocal(float scalar) { } /** - * negate returns the negative of this vector. All values are - * negated and set to a new vector. - * - * @return the negated vector. + * 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 + * @return a new Vector2f + */ + public Vector2f divide(float x, float y) { + return new Vector2f(this.x / x, this.y / y); + } + + /** + * Divides component-wise by the specified components returns the (modified) + * current instance. + * + * @param x the divisor for the X component + * @param y the divisor for the Y component + * @return the (modified) current instance (for chaining) + */ + public Vector2f divideLocal(float x, float y) { + this.x /= x; + this.y /= y; + return this; + } + + /** + * Returns the negative of the vector. The current instance is unaffected. + * + * @return a new Vector2f */ public Vector2f negate() { return new Vector2f(-x, -y); } /** - * negateLocal negates the internal values of this vector. - * - * @return this. + * Negates both components and returns the (modified) current instance. + * + * @return the (modified) current instance (for chaining) */ public Vector2f negateLocal() { x = -x; @@ -450,60 +524,55 @@ public Vector2f negateLocal() { } /** - * subtract subtracts the values of a given vector from those - * of this vector creating a new vector object. If the provided vector is - * null, an exception is thrown. - * - * @param vec - * the vector to subtract from this vector. - * @return the result vector. + * Subtracts the argument and returns the difference as a new instance. The + * current instance is unaffected. + * + * @param vec the vector to subtract (not null, unaffected) + * @return a new Vector2f */ public Vector2f subtract(Vector2f vec) { return subtract(vec, null); } /** - * subtract subtracts the values of a given vector from those - * of this vector storing the result in the given vector object. If the - * provided vector is null, an exception is thrown. - * - * @param vec - * the vector to subtract from this vector. - * @param store - * the vector to store the result in. It is safe for this to be - * the same as vec. If null, a new vector is created. - * @return the result vector. + * Subtracts the specified vector and returns the difference in a 3rd + * vector. The current instance is unaffected unless it's {@code store}. + * + *

      It is safe for {@code vec} and {@code store} to be the same object. + * + * @param vec the vector to subtract (not null, unaffected unless it's + * {@code store}) + * @param store storage for the difference, or null for a new Vector2f + * @return either {@code store} or a new Vector2f */ public Vector2f subtract(Vector2f vec, Vector2f store) { - if (store == null) + if (store == null) { store = new Vector2f(); + } store.x = x - vec.x; store.y = y - vec.y; return store; } /** - * subtract subtracts the given x,y values from those of this - * vector creating a new vector object. - * - * @param valX - * value to subtract from x - * @param valY - * value to subtract from y - * @return this + * Subtracts the specified amounts from the vector's components and returns + * the difference as a new instance. The current instance is unaffected. + * + * @param valX the amount to subtract from the X component + * @param valY the amount to subtract from the Y component + * @return a new Vector2f */ public Vector2f subtract(float valX, float valY) { return new Vector2f(x - valX, y - valY); } /** - * subtractLocal subtracts a provided vector to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. - * - * @param vec - * the vector to subtract - * @return this + * 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 {@code this}) + * or null for none + * @return the (modified) current instance or null */ public Vector2f subtractLocal(Vector2f vec) { if (null == vec) { @@ -516,15 +585,12 @@ public Vector2f subtractLocal(Vector2f vec) { } /** - * subtractLocal subtracts the provided values from this - * vector internally, and returns a handle to this vector for easy chaining - * of calls. - * - * @param valX - * value to subtract from x - * @param valY - * value to subtract from y - * @return this + * Subtracts the specified amounts from the vector's components and returns + * the (modified) current instance. + * + * @param valX the amount to subtract from the X component + * @param valY the amount to subtract from the Y component + * @return the (modified) current instance (for chaining) */ public Vector2f subtractLocal(float valX, float valY) { x -= valX; @@ -533,9 +599,11 @@ public Vector2f subtractLocal(float valX, float valY) { } /** - * normalize returns the unit vector of this vector. - * - * @return unit vector of this vector. + * Normalizes the vector to length=1 and returns the result as a new + * instance. If the vector has length=0, a clone is returned. Either way, + * the current instance is unaffected. + * + * @return a new Vector2f */ public Vector2f normalize() { float length = length(); @@ -547,10 +615,10 @@ public Vector2f normalize() { } /** - * normalizeLocal makes this vector into a unit vector of - * itself. - * - * @return this. + * Normalizes the vector to length=1 and returns the (modified) current + * instance. If the vector has length=0, it's unchanged. + * + * @return the (modified) current instance (for chaining) */ public Vector2f normalizeLocal() { float length = length(); @@ -562,13 +630,13 @@ public Vector2f normalizeLocal() { } /** - * smallestAngleBetween returns (in radians) the minimum - * angle between two vectors. It is assumed that both this vector and the - * given vector are unit vectors (iow, normalized). - * - * @param otherVector - * a unit vector to find the angle against - * @return the angle in radians. + * Returns the unsigned angle between the current instance and the argument, + * provided both vectors have length=1. If {@code otherVector} is null, Pi/2 + * is returned. The current instance is unaffected. + * + * @param otherVector the unit vector to compare (unaffected) or null for + * none + * @return the angle in radians (not negative) */ public float smallestAngleBetween(Vector2f otherVector) { float dotProduct = dot(otherVector); @@ -577,51 +645,74 @@ public float smallestAngleBetween(Vector2f otherVector) { } /** - * angleBetween returns (in radians) the angle required to - * rotate a ray represented by this vector to be colinear with a ray - * described by the given vector. It is assumed that both this vector and - * the given vector are unit vectors (iow, normalized). - * - * @param otherVector - * the "destination" unit vector - * @return the angle in radians. + * Returns the signed angle between the current instance and the argument. + * The current instance is unaffected. + * + * @param otherVector the vector to compare (not null, unaffected) + * @return the angle in radians, measured counter-clockwise from {@code + * this} to {@code otherVector} (≥-2*Pi, ≤2*Pi) */ public float angleBetween(Vector2f otherVector) { float angle = FastMath.atan2(otherVector.y, otherVector.x) - FastMath.atan2(y, x); return angle; } - + + /** + * Returns the X component. The vector is unaffected. + * + * @return the value of the {@link #x} component + */ public float getX() { return x; } + /** + * Sets the X component. + * + * @param x the desired value + * @return the (modified) current instance (for chaining) + */ public Vector2f setX(float x) { this.x = x; return this; } + /** + * Returns the Y component. The vector is unaffected. + * + * @return the value of the {@link #y} component + */ public float getY() { return y; } + /** + * Sets the Y component. + * + * @param y the desired value + * @return the (modified) current instance (for chaining) + */ public Vector2f setY(float y) { this.y = y; return this; } + /** - * getAngle returns (in radians) the angle represented by - * this Vector2f as expressed by a conversion from rectangular coordinates (xy) - * to polar coordinates (r, theta). - * - * @return the angle in radians. [-pi, pi) + * Returns the angle of the vector in polar coordinates. The current + * instance is unaffected. + * + * @return the polar angle in radians, measured counter-clockwise from the + * +X axis (≥-Pi, ≤Pi) */ public float getAngle() { return FastMath.atan2(y, x); } /** - * zero resets this vector's data to zero internally. + * Sets both components to zero. + * + * @return the (modified) current instance (for chaining) */ public Vector2f zero() { x = y = 0; @@ -629,12 +720,12 @@ public Vector2f zero() { } /** - * hashCode returns a unique code for this vector object - * based on its values. If two vectors are logically equivalent, they will - * return the same hash code value. - * - * @return the hash code value of this vector. + * Returns a hash code. If two vectors are logically equivalent, they will + * return the same hash code. The current instance is unaffected. + * + * @return the hash code value */ + @Override public int hashCode() { int hash = 37; hash += 37 * hash + Float.floatToIntBits(x); @@ -642,6 +733,11 @@ public int hashCode() { return hash; } + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance, equivalent to the current one + */ @Override public Vector2f clone() { try { @@ -652,12 +748,13 @@ public Vector2f clone() { } /** - * Saves this Vector2f into the given float[] object. - * - * @param floats - * The float[] to take this Vector2f. If null, a new float[2] is - * created. - * @return The array, with X, Y float values in that order + * Copies the vector to the array argument. The current instance is + * unaffected. + * + * @param floats storage for the components (must have length≥2) or null + * for a new float[2] + * @return an array containing the X and Y components in that order (either + * {@code floats} or a new float[2]) */ public float[] toArray(float[] floats) { if (floats == null) { @@ -669,13 +766,14 @@ public float[] toArray(float[] floats) { } /** - * are these two vectors the same? they are is they both have the same x and - * y values. - * - * @param o - * the object to compare for equality - * @return true if they are equal + * 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 (unaffected) or null for none + * @return true if equal, otherwise false */ + @Override public boolean equals(Object o) { if (!(o instanceof Vector2f)) { return false; @@ -686,16 +784,23 @@ public boolean equals(Object o) { } Vector2f comp = (Vector2f) o; - if (Float.compare(x, comp.x) != 0) + if (Float.compare(x, comp.x) != 0) { return false; - if (Float.compare(y, comp.y) != 0) + } + if (Float.compare(y, comp.y) != 0) { return false; + } return true; } - + /** - * Returns true if this vector is similar to the specified vector within - * some value of epsilon. + * Tests for approximate equality with the specified vector, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param other the vector to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if both components are within tolerance, otherwise false */ public boolean isSimilar(Vector2f other, float epsilon) { if (other == null) { @@ -711,23 +816,27 @@ public boolean isSimilar(Vector2f other, float epsilon) { } /** - * toString returns the string representation of this vector - * object. The format of the string is such: com.jme.math.Vector2f - * [X=XX.XXXX, Y=YY.YYYY] - * - * @return the string representation of this vector. + * Returns a string representation of the vector. The current instance is + * unaffected. The format is: + * + *

      (XX.XXXX, YY.YYYY) + * + * @return the string representation */ + @Override public String toString() { return "(" + x + ", " + y + ")"; } /** - * Used with serialization. Not to be called manually. - * - * @param in - * ObjectInput - * @throws IOException - * @throws ClassNotFoundException + * Sets the vector from an {@code ObjectInput} object. + * + *

      Used with serialization. Shouldn't be invoked directly by application + * code. + * + * @param in the object to read from (not null) + * @throws IOException if the ObjectInput cannot read a float + * @throws ClassNotFoundException never * @see java.io.Externalizable */ public void readExternal(ObjectInput in) throws IOException, @@ -737,11 +846,14 @@ public void readExternal(ObjectInput in) throws IOException, } /** - * Used with serialization. Not to be called manually. - * - * @param out - * ObjectOutput - * @throws IOException + * Writes the vector to an {@code ObjectOutput} object. The current instance + * is unaffected. + * + *

      Used with serialization. Shouldn't be invoked directly by application + * code. + * + * @param out the object to write to (not null) + * @throws IOException if the ObjectOuput cannot write a float * @see java.io.Externalizable */ public void writeExternal(ObjectOutput out) throws IOException { @@ -749,23 +861,48 @@ public void writeExternal(ObjectOutput out) throws IOException { out.writeFloat(y); } + /** + * Serializes the vector to the specified exporter, for example when saving + * to a J3O file. The current instance is unaffected. + * + * @param e the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(x, "x", 0); capsule.write(y, "y", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serializes the vector from the specified importer, for example + * when loading from a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); x = capsule.readFloat("x", 0); y = capsule.readFloat("y", 0); } + /** + * Rotates the vector around (0,0) by the specified angle. + * + * @param angle the rotation angle (in radians) + * @param cw true to rotate clockwise, false to rotate counter-clockwise + */ public void rotateAroundOrigin(float angle, boolean cw) { - if (cw) + if (cw) { angle = -angle; - float newX = FastMath.cos(angle) * x - FastMath.sin(angle) * y; - float newY = FastMath.sin(angle) * x + FastMath.cos(angle) * y; + } + float cos = FastMath.cos(angle); + float sin = FastMath.sin(angle); + float newX = cos * x - sin * y; + float newY = sin * x + cos * y; x = newX; y = newY; } 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 04638ef2b7..fa5a8539cd 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-2019 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,22 +29,18 @@ * 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 com.jme3.export.*; import java.io.IOException; import java.util.logging.Logger; -/* - * -- Added *Local methods to cut down on object creation - JS - */ - /** - * Vector3f defines a Vector for a three float value tuple. - * Vector3f can represent any three dimensional value, such as a - * vertex, a normal, etc. Utility methods are also included to aid in - * mathematical calculations. + * A vector composed of 3 single-precision components, used to represent + * 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. * * @author Mark Powell * @author Joshua Slack @@ -52,59 +48,73 @@ public final class Vector3f implements Savable, Cloneable, java.io.Serializable { static final long serialVersionUID = 1; - private static final Logger logger = Logger.getLogger(Vector3f.class.getName()); - - public final static Vector3f ZERO = new Vector3f(0, 0, 0); - public final static Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); - public final static Vector3f UNIT_X = new Vector3f(1, 0, 0); - public final static Vector3f UNIT_Y = new Vector3f(0, 1, 0); - public final static Vector3f UNIT_Z = new Vector3f(0, 0, 1); - public final static Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); - public final static Vector3f POSITIVE_INFINITY = new Vector3f( + /** + * Shared instance of the all-zero vector (0,0,0). Do not modify! + */ + public static final Vector3f ZERO = new Vector3f(0, 0, 0); + /** + * Shared instance of the all-NaN vector (NaN,NaN,NaN). Do not modify! + */ + public static final Vector3f NAN = new Vector3f(Float.NaN, Float.NaN, Float.NaN); + /** + * Shared instance of the +X direction (1,0,0). Do not modify! + */ + public static final Vector3f UNIT_X = new Vector3f(1, 0, 0); + /** + * Shared instance of the +Y direction (0,1,0). Do not modify! + */ + public static final Vector3f UNIT_Y = new Vector3f(0, 1, 0); + /** + * Shared instance of the +Z direction (0,0,1). Do not modify! + */ + public static final Vector3f UNIT_Z = new Vector3f(0, 0, 1); + /** + * Shared instance of the all-ones vector (1,1,1). Do not modify! + */ + public static final Vector3f UNIT_XYZ = new Vector3f(1, 1, 1); + /** + * Shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf). Do not + * modify! + */ + public static final Vector3f POSITIVE_INFINITY = new Vector3f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - public final static Vector3f NEGATIVE_INFINITY = new Vector3f( + /** + * Shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf). Do + * not modify! + */ + public static final Vector3f NEGATIVE_INFINITY = new Vector3f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - - - /** - * the x value of the vector. + /** + * The first (X) component. */ public float x; - /** - * the y value of the vector. + * The 2nd (Y) component. */ public float y; - /** - * the z value of the vector. + * The 3rd (Z) component. */ public float z; /** - * Constructor instantiates a new Vector3f with default - * values of (0,0,0). - * + * Instantiates an all-zero vector (0,0,0). */ public Vector3f() { x = y = z = 0; } /** - * Constructor instantiates a new Vector3f with provides - * values. + * Instantiates a vector with specified components. * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. + * @param x the desired X component + * @param y the desired Y component + * @param z the desired Z component */ public Vector3f(float x, float y, float z) { this.x = x; @@ -113,25 +123,21 @@ public Vector3f(float x, float y, float z) { } /** - * Constructor instantiates a new Vector3f that is a copy - * of the provided vector - * @param copy The Vector3f to copy + * Instantiates a copy of the argument. + * + * @param copy the vector to copy (not null, unaffected) */ public Vector3f(Vector3f copy) { this.set(copy); } /** - * set sets the x,y,z values of the vector based on passed - * parameters. + * Sets all 3 components to specified values. * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. - * @return this vector + * @param x the desired X component + * @param y the desired Y component + * @param z the desired Z component + * @return the (modified) current instance (for chaining) */ public Vector3f set(float x, float y, float z) { this.x = x; @@ -141,12 +147,10 @@ public Vector3f set(float x, float y, float z) { } /** - * set sets the x,y,z values of the vector by copying the - * supplied vector. + * Copies all 3 components from the argument. * - * @param vect - * the vector to copy. - * @return this vector + * @param vect the Vector3f to copy (not null, unaffected) + * @return the (modified) current instance (for chaining) */ public Vector3f set(Vector3f vect) { this.x = vect.x; @@ -156,14 +160,12 @@ public Vector3f set(Vector3f vect) { } /** + * Adds the argument and returns the sum as a new instance. If the argument + * is null, null is returned. Either way, the current instance is + * unaffected. * - * add adds a provided vector to this vector creating a - * resultant vector which is returned. If the provided vector is null, null - * is returned. - * - * @param vec - * the vector to add to this. - * @return the resultant vector. + * @param vec the vector to add (unaffected) or null for none + * @return a new Vector3f or null */ public Vector3f add(Vector3f vec) { if (null == vec) { @@ -174,15 +176,13 @@ 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 {@code result}. * - * add adds the values of a provided vector storing the - * values in the supplied vector. - * - * @param vec - * the vector to add to this - * @param result - * the vector to store the result in - * @return result returns the supplied result vector. + * @param vec the vector to add (not null, unaffected unless it's + * {@code result}) + * @param result storage for the sum (not null) + * @return {@code result} (for chaining) */ public Vector3f add(Vector3f vec, Vector3f result) { result.x = x + vec.x; @@ -192,13 +192,12 @@ public Vector3f add(Vector3f vec, Vector3f result) { } /** - * addLocal adds a provided vector to this vector internally, - * and returns a handle to this vector for easy chaining of calls. If the - * provided vector is null, null is returned. + * Adds the argument and returns the (modified) current instance. If the + * argument is null, null is returned. * - * @param vec - * the vector to add to this vector. - * @return this + * @param vec the vector to add (unaffected unless it's {@code this}) + * or null for none + * @return the (modified) current instance or null */ public Vector3f addLocal(Vector3f vec) { if (null == vec) { @@ -212,34 +211,26 @@ public Vector3f addLocal(Vector3f vec) { } /** + * Adds specified amounts to the vector's components and returns the sum as + * a new instance. The current instance is unaffected. * - * add adds the provided values to this vector, creating a - * new vector that is then returned. - * - * @param addX - * the x value to add. - * @param addY - * the y value to add. - * @param addZ - * the z value to add. - * @return the result vector. + * @param addX the amount to add to the X component + * @param addY the amount to add to the Y component + * @param addZ the amount to add to the Z component + * @return a new Vector3f */ public Vector3f add(float addX, float addY, float addZ) { return new Vector3f(x + addX, y + addY, z + addZ); } /** - * addLocal adds the provided values to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. + * Adds specified amounts to the vector's components and returns the + * (modified) current instance. * - * @param addX - * value to add to x - * @param addY - * value to add to y - * @param addZ - * value to add to z - * @return this + * @param addX the amount to add to the X component + * @param addY the amount to add to the Y component + * @param addZ the amount to add to the Z component + * @return the (modified) current instance (for chaining) */ public Vector3f addLocal(float addX, float addY, float addZ) { x += addX; @@ -249,14 +240,15 @@ public Vector3f addLocal(float addX, float addY, float addZ) { } /** + * Multiplies by the specified scalar, adds the specified vector, and + * returns the (modified) current instance. * - * scaleAdd multiplies this vector by a scalar then adds the - * given Vector3f. + *

      this = scalar * this + add * - * @param scalar - * the value to multiply this vector by. - * @param add - * the value to add + * @param scalar the scaling factor + * @param add the vector to add (not null, unaffected unless it's + * this) + * @return the (modified) current instance (for chaining) */ public Vector3f scaleAdd(float scalar, Vector3f add) { x = x * scalar + add.x; @@ -266,16 +258,18 @@ public Vector3f scaleAdd(float scalar, Vector3f add) { } /** + * Multiplies a specified vector by a specified scalar, then adds another + * specified vector to it, before storing the result in the current + * instance. * - * scaleAdd multiplies the given vector by a scalar then adds - * the given vector. + *

      this = scalar * mult + add * - * @param scalar - * the value to multiply this vector by. - * @param mult - * the value to multiply the scalar by - * @param add - * the value to add + * @param scalar the scaling factor + * @param mult the vector to scale (not null, unaffected unless it's + * this) + * @param add the vector to add (not null, unaffected unless it's + * this) + * @return the (modified) current instance (for chaining) */ public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) { this.x = mult.x * scalar + add.x; @@ -285,13 +279,11 @@ public Vector3f scaleAdd(float scalar, Vector3f mult, Vector3f add) { } /** + * Returns the dot (or inner) product with the argument. If the argument is + * null, 0 is returned. Either way, the current instance is unaffected. * - * dot calculates the dot product of this vector with a - * provided vector. If the provided vector is null, 0 is returned. - * - * @param vec - * the vector to dot with this vector. - * @return the resultant dot product of this vector and a given vector. + * @param vec the vector to multiply (unaffected) or null for none + * @return the product or 0 */ public float dot(Vector3f vec) { if (null == vec) { @@ -302,48 +294,48 @@ public float dot(Vector3f vec) { } /** - * cross calculates the cross product of this vector with a - * parameter vector v. + * Calculates a cross product with the argument and returns the product as a + * new instance. The current instance is unaffected. * - * @param v - * the vector to take the cross product of with this. - * @return the cross product vector. + * @param v the right factor (not null, unaffected) + * @return {@code this} cross {@code v} (a new Vector3f) */ public Vector3f cross(Vector3f v) { return cross(v, null); } /** - * cross calculates the cross product of this vector with a - * parameter vector v. The result is stored in result + * Calculates a cross product with a specified vector and returns the + * product in a 3rd vector. The current instance is unaffected unless it's + * {@code result}. * - * @param v - * the vector to take the cross product of with this. - * @param result - * the vector to store the cross product result. - * @return result, after receiving the cross product vector. + * @param v the right factor (not null, unaffected unless it's + * {@code result}) + * @param result storage for the product, or null for a new Vector3f + * @return {@code this} cross {@code v} (either {@code result} or a new + * Vector3f) */ - public Vector3f cross(Vector3f v,Vector3f result) { + public Vector3f cross(Vector3f v, Vector3f result) { return cross(v.x, v.y, v.z, result); } /** - * cross calculates the cross product of this vector with a - * parameter vector v. The result is stored in result + * Calculates a cross product with specified components and returns the + * product in the specified vector. The current instance is unaffected + * unless it's {@code result}. * - * @param otherX - * x component of the vector to take the cross product of with this. - * @param otherY - * y component of the vector to take the cross product of with this. - * @param otherZ - * z component of the vector to take the cross product of with this. - * @param result - * the vector to store the cross product result. - * @return result, after receiving the cross product vector. + * @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 {@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) result = new Vector3f(); - float resX = ((y * otherZ) - (z * otherY)); + if (result == null) { + result = new Vector3f(); + } + float resX = ((y * otherZ) - (z * otherY)); float resY = ((z * otherX) - (x * otherZ)); float resZ = ((x * otherY) - (y * otherX)); result.set(resX, resY, resZ); @@ -351,32 +343,29 @@ public Vector3f cross(float otherX, float otherY, float otherZ, Vector3f result) } /** - * crossLocal calculates the cross product of this vector - * with a parameter vector v. + * Right multiplies by the argument (cross product) and returns the + * (modified) current instance. * - * @param v - * the vector to take the cross product of with this. - * @return this. + * @param v the right factor (not null, unaffected unless it's + * this) + * @return the (modified) current instance (for chaining) */ public Vector3f crossLocal(Vector3f v) { return crossLocal(v.x, v.y, v.z); } /** - * crossLocal calculates the cross product of this vector - * with a parameter vector v. + * Right multiplies by the specified components (cross product) and returns + * the (modified) current instance. * - * @param otherX - * x component of the vector to take the cross product of with this. - * @param otherY - * y component of the vector to take the cross product of with this. - * @param otherZ - * z component of the vector to take the cross product of with this. - * @return this. + * @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 + * @return the (modified) current instance (for chaining) */ public Vector3f crossLocal(float otherX, float otherY, float otherZ) { - float tempx = ( y * otherZ ) - ( z * otherY ); - float tempy = ( z * otherX ) - ( x * otherZ ); + float tempx = (y * otherZ) - (z * otherY); + float tempy = (z * otherX) - (x * otherZ); z = (x * otherY) - (y * otherX); x = tempx; y = tempy; @@ -384,67 +373,78 @@ public Vector3f crossLocal(float otherX, float otherY, float otherZ) { } /** - * Projects this vector onto another vector + * Projects onto the argument and returns the result as a new vector. The + * current instance is unaffected. * - * @param other The vector to project this vector onto - * @return A new vector with the projection result + * @param other the vector to project onto (not null, unaffected) + * @return a new Vector3f */ - public Vector3f project(Vector3f other){ + public Vector3f project(Vector3f other) { float n = this.dot(other); // A . B float d = other.lengthSquared(); // |B|^2 - return new Vector3f(other).multLocal(n/d); + return new Vector3f(other).multLocal(n / d); } /** - * Projects this vector onto another vector, stores the result in this - * vector + * Projects onto the argument and returns the (modified) current instance. * - * @param other The vector to project this vector onto - * @return This Vector3f, set to the projection result + * @param other the vector to project onto (not null, unaffected unless it's + * this) + * @return the (modified) current instance (for chaining) */ - public Vector3f projectLocal(Vector3f other){ + public Vector3f projectLocal(Vector3f other) { float n = this.dot(other); // A . B float d = other.lengthSquared(); // |B|^2 - return set(other).multLocal(n/d); + return set(other).multLocal(n / d); } - + /** - * Returns true if this vector is a unit vector (length() ~= 1), - * returns false otherwise. - * - * @return true if this vector is a unit vector (length() ~= 1), - * or false otherwise. + * Tests for a unit vector, with 1% tolerance. The current instance is + * unaffected. + * + * @return true if the current vector's length is between 0.99 and 1.01 + * inclusive, otherwise false */ - public boolean isUnitVector(){ + public boolean isUnitVector() { float len = length(); return 0.99f < len && len < 1.01f; } /** - * length calculates the magnitude of this vector. + * Returns the length (or magnitude). The current instance is unaffected. * - * @return the length or magnitude of the vector. + * @return the root-sum of the squared components (not negative) */ public float length() { - return FastMath.sqrt(lengthSquared()); + /* + * Use double-precision arithmetic to reduce the chance of overflow + * (when lengthSquared > Float.MAX_VALUE) or underflow (when + * lengthSquared is < Float.MIN_VALUE). + */ + double xx = x; + double yy = y; + double zz = z; + double lengthSquared = xx * xx + yy * yy + zz * zz; + float result = (float) Math.sqrt(lengthSquared); + + return result; } /** - * lengthSquared calculates the squared value of the - * magnitude of the vector. + * Returns the square of the length. The current instance is unaffected. * - * @return the magnitude squared of the vector. + * @return the sum of the squared components (not negative) */ public float lengthSquared() { return x * x + y * y + z * z; } /** - * distanceSquared calculates the distance squared between - * this vector and vector v. + * Returns the square of the distance between this vector and the argument. + * The current instance is unaffected. * - * @param v the second vector to determine the distance squared. - * @return the distance squared between the two vectors. + * @param v the vector to compare (not null, unaffected) + * @return the square of the Euclidean distance (not negative) */ public float distanceSquared(Vector3f v) { double dx = x - v.x; @@ -454,37 +454,46 @@ public float distanceSquared(Vector3f v) { } /** - * distance calculates the distance between this vector and - * vector v. + * Returns the distance between this vector and the argument. The current + * instance is unaffected. * - * @param v the second vector to determine the distance. - * @return the distance between the two vectors. + * @param v the vector to compare (not null, unaffected) + * @return the Euclidean distance (not negative) */ public float distance(Vector3f v) { - return FastMath.sqrt(distanceSquared(v)); + /* + * Use double-precision arithmetic to reduce the chance of overflow + * (when distanceSquared > Float.MAX_VALUE) or underflow (when + * distanceSquared is < Float.MIN_VALUE). + */ + double dx = x - v.x; + double dy = y - v.y; + double dz = z - v.z; + double distanceSquared = dx * dx + dy * dy + dz * dz; + float result = (float) Math.sqrt(distanceSquared); + + return result; } /** + * Multiplies with the argument and returns the product as a new instance. + * The current instance is unaffected. * - * mult multiplies this vector by a scalar. The resultant - * vector is returned. - * - * @param scalar - * the value to multiply this vector by. - * @return the new vector. + * @param scalar the scaling factor + * @return a new Vector3f */ public Vector3f mult(float scalar) { return new Vector3f(x * scalar, y * scalar, z * scalar); } /** + * Multiplies with the specified scalar and returns the product in the + * specified vector. The current instance is unaffected, unless it's + * {@code product}. * - * mult multiplies this vector by a scalar. The resultant - * vector is supplied as the second parameter and returned. - * - * @param scalar the scalar to multiply this vector by. - * @param product the product to store the result in. - * @return product + * @param scalar the scaling factor + * @param product storage for the product, or null for a new Vector3f + * @return either {@code product} or a new Vector3f */ public Vector3f mult(float scalar, Vector3f product) { if (null == product) { @@ -498,12 +507,10 @@ public Vector3f mult(float scalar, Vector3f product) { } /** - * multLocal multiplies this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. + * Multiplies by the argument and returns the (modified) current instance. * - * @param scalar - * the value to multiply this vector by. - * @return this + * @param scalar the scaling factor + * @return the (modified) current instance (for chaining) */ public Vector3f multLocal(float scalar) { x *= scalar; @@ -513,13 +520,12 @@ public Vector3f multLocal(float scalar) { } /** - * multLocal multiplies a provided vector to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. + * Multiplies component-wise by the argument and returns the (modified) + * current instance. If the argument is null, null is returned. * - * @param vec - * the vector to mult to this vector. - * @return this + * @param vec the scale vector (unaffected unless it's {@code this}) or + * null for none + * @return the (modified) current instance (for chaining) or null */ public Vector3f multLocal(Vector3f vec) { if (null == vec) { @@ -533,14 +539,13 @@ public Vector3f multLocal(Vector3f vec) { } /** - * multLocal multiplies this vector by 3 scalars - * internally, and returns a handle to this vector for easy chaining of - * calls. + * Multiplies component-wise by the specified components and returns the + * (modified) current instance. * - * @param x - * @param y - * @param z - * @return this + * @param x the scale factor for the X component + * @param y the scale factor for the Y component + * @param z the scale factor for the Z component + * @return the (modified) current instance (for chaining) */ public Vector3f multLocal(float x, float y, float z) { this.x *= x; @@ -550,13 +555,12 @@ public Vector3f multLocal(float x, float y, float z) { } /** - * multLocal multiplies a provided vector to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. + * Multiplies component-wise with the argument and returns the product as a + * new instance. If the argument is null, null is returned. Either way, the + * current instance is unaffected. * - * @param vec - * the vector to mult to this vector. - * @return this + * @param vec the scale vector (unaffected) or null for none + * @return a new Vector3f or null */ public Vector3f mult(Vector3f vec) { if (null == vec) { @@ -567,100 +571,134 @@ public Vector3f mult(Vector3f vec) { } /** - * multLocal multiplies a provided vector to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. + * Multiplies component-wise by the specified components and returns the + * product as a new instance. The current instance is unaffected. + * + * @param x the scale factor for the X component + * @param y the scale factor for the Y component + * @param z the scale factor for the Z component + * @return a new Vector3f + */ + public Vector3f mult(float x, float y, float z) { + return new Vector3f(this.x * x, this.y * y, this.z * 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 + * {@code store}. * - * @param vec - * the vector to mult to this vector. - * @param store result vector (null to create a new vector) - * @return this + * @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 {@code store} or a new Vector3f or null */ public Vector3f mult(Vector3f vec, Vector3f store) { if (null == vec) { logger.warning("Provided vector is null, null returned."); return null; } - if (store == null) store = new Vector3f(); + if (store == null) { + store = new Vector3f(); + } return store.set(x * vec.x, y * vec.y, z * vec.z); } - /** - * divide divides the values of this vector by a scalar and - * returns the result. The values of this vector remain untouched. + * Divides by the argument and returns the quotient as a new instance. The + * current instance is unaffected. * - * @param scalar - * the value to divide this vectors attributes by. - * @return the result Vector. + * @param scalar the divisor + * @return a new Vector3f */ public Vector3f divide(float scalar) { - scalar = 1f/scalar; + scalar = 1f / scalar; return new Vector3f(x * scalar, y * scalar, z * scalar); } /** - * divideLocal divides this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. Dividing - * by zero will result in an exception. + * Divides by the argument and returns the (modified) current instance. * - * @param scalar - * the value to divides this vector by. - * @return this + * @param scalar the divisor + * @return the (modified) current instance (for chaining) */ public Vector3f divideLocal(float scalar) { - scalar = 1f/scalar; + scalar = 1f / scalar; x *= scalar; y *= scalar; z *= scalar; return this; } + /** + * Divides component-wise by the specified components returns the (modified) + * current instance. + * + * @param x the divisor for the X component + * @param y the divisor for the Y component + * @param z the divisor for the Z component + * @return the (modified) current instance (for chaining) + */ + public Vector3f divideLocal(float x, float y, float z) { + this.x /= x; + this.y /= y; + this.z /= z; + return this; + } /** - * divide divides the values of this vector by a scalar and - * returns the result. The values of this vector remain untouched. + * Divides component-wise by the argument and returns the quotient as a new + * instance. The current instance is unaffected. * - * @param scalar - * the value to divide this vectors attributes by. - * @return the result Vector. + * @param divisor the divisor (not null, unaffected) + * @return a new Vector3f */ - public Vector3f divide(Vector3f scalar) { - return new Vector3f(x / scalar.x, y / scalar.y, z / scalar.z); + public Vector3f divide(Vector3f divisor) { + return new Vector3f(x / divisor.x, y / divisor.y, z / divisor.z); } /** - * divideLocal divides this vector by a scalar internally, - * and returns a handle to this vector for easy chaining of calls. Dividing - * by zero will result in an exception. + * Divides component-wise by the specified components and returns the + * quotient as a new instance. The current instance is unaffected. * - * @param scalar - * the value to divides this vector by. - * @return this + * @param x the divisor for the X component + * @param y the divisor for the Y component + * @param z the divisor for the Z component + * @return a new Vector3f */ - public Vector3f divideLocal(Vector3f scalar) { - x /= scalar.x; - y /= scalar.y; - z /= scalar.z; - return this; + public Vector3f divide(float x, float y, float z) { + return new Vector3f(this.x / x, this.y / y, this.z / z); } /** + * Divides component-wise by the argument and returns the (modified) current + * instance. * - * negate returns the negative of this vector. All values are - * negated and set to a new vector. + * @param divisor the divisor (not null, unaffected unless it's + * {@code this}) + * @return the (modified) current instance (for chaining) + */ + public Vector3f divideLocal(Vector3f divisor) { + x /= divisor.x; + y /= divisor.y; + z /= divisor.z; + return this; + } + + /** + * Returns the negative. The current instance is unaffected. * - * @return the negated vector. + * @return a new Vector3f */ public Vector3f negate() { return new Vector3f(-x, -y, -z); } /** + * Negates all 3 components and returns the (modified) current instance. * - * negateLocal negates the internal values of this vector. - * - * @return this. + * @return the (modified) current instance (for chaining) */ public Vector3f negateLocal() { x = -x; @@ -670,27 +708,23 @@ public Vector3f negateLocal() { } /** + * Subtracts the argument and returns the difference as a new instance. The + * current instance is unaffected. * - * subtract subtracts the values of a given vector from those - * of this vector creating a new vector object. If the provided vector is - * null, null is returned. - * - * @param vec - * the vector to subtract from this vector. - * @return the result vector. + * @param vec the vector to subtract (not null, unaffected) + * @return a new Vector3f */ public Vector3f subtract(Vector3f vec) { return new Vector3f(x - vec.x, y - vec.y, z - vec.z); } /** - * subtractLocal subtracts a provided vector to this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. If the provided vector is null, null is returned. + * Subtracts the argument and returns the (modified) current instance. If + * the argument is null, null is returned. * - * @param vec - * the vector to subtract - * @return this + * @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) { if (null == vec) { @@ -704,17 +738,17 @@ 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 + * {@code result}. * - * subtract - * - * @param vec - * the vector to subtract from this - * @param result - * the vector to store the result in - * @return result + * @param vec the vector to subtract (not null, unaffected unless it's + * {@code result}) + * @param result storage for the difference, or null for a new Vector3f + * @return either {@code result} or a new Vector3f */ public Vector3f subtract(Vector3f vec, Vector3f result) { - if(result == null) { + if (result == null) { result = new Vector3f(); } result.x = x - vec.x; @@ -724,34 +758,26 @@ public Vector3f subtract(Vector3f vec, Vector3f result) { } /** + * Subtracts the specified amounts from the vector's components and returns + * the difference as a new instance. The current instance is unaffected. * - * subtract subtracts the provided values from this vector, - * creating a new vector that is then returned. - * - * @param subtractX - * the x value to subtract. - * @param subtractY - * the y value to subtract. - * @param subtractZ - * the z value to subtract. - * @return the result vector. + * @param subtractX the amount to subtract from the X component + * @param subtractY the amount to subtract from the Y component + * @param subtractZ the amount to subtract from the Z component + * @return a new Vector3f */ public Vector3f subtract(float subtractX, float subtractY, float subtractZ) { return new Vector3f(x - subtractX, y - subtractY, z - subtractZ); } /** - * subtractLocal subtracts the provided values from this vector - * internally, and returns a handle to this vector for easy chaining of - * calls. + * Subtracts the specified amounts from the vector's components and returns + * the (modified) current instance. * - * @param subtractX - * the x value to subtract. - * @param subtractY - * the y value to subtract. - * @param subtractZ - * the z value to subtract. - * @return this + * @param subtractX the amount to subtract from the X component + * @param subtractY the amount to subtract from the Y component + * @param subtractZ the amount to subtract from the Z component + * @return the (modified) current instance (for chaining) */ public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) { x -= subtractX; @@ -761,9 +787,11 @@ public Vector3f subtractLocal(float subtractX, float subtractY, float subtractZ) } /** - * normalize returns the unit vector of this vector. + * Normalizes the vector to length=1 and returns the result as a new + * instance. If the vector has length=0, a clone is returned. Either way, + * the current instance is unaffected. * - * @return unit vector of this vector. + * @return a new Vector3f */ public Vector3f normalize() { // float length = length(); @@ -773,7 +801,7 @@ public Vector3f normalize() { // // return divide(1); float length = x * x + y * y + z * z; - if (length != 1f && length != 0f){ + if (length != 1f && length != 0f) { length = 1.0f / FastMath.sqrt(length); return new Vector3f(x * length, y * length, z * length); } @@ -781,17 +809,17 @@ public Vector3f normalize() { } /** - * normalizeLocal makes this vector into a unit vector of - * itself. + * Normalizes the vector to length=1 and returns the (modified) current + * instance. If the vector has length=0, it's unchanged. * - * @return this. + * @return the (modified) current instance (for chaining) */ public Vector3f normalizeLocal() { // NOTE: this implementation is more optimized // than the old jme normalize as this method // is commonly used. float length = x * x + y * y + z * z; - if (length != 1f && length != 0f){ + if (length != 1f && length != 0f) { length = 1.0f / FastMath.sqrt(length); x *= length; y *= length; @@ -801,12 +829,14 @@ public Vector3f normalizeLocal() { } /** - * maxLocal computes the maximum value for each - * component in this and other vector. The result is stored - * in this vector. - * @param other + * Compares this vector component-wise with the argument (keeping the most + * positive value for each component) and returns the (modified) current + * instance. + * + * @param other the vector to compare (not null, unaffected) + * @return the (modified) current instance (for chaining) */ - public Vector3f maxLocal(Vector3f other){ + public Vector3f maxLocal(Vector3f other) { x = other.x > x ? other.x : x; y = other.y > y ? other.y : y; z = other.z > z ? other.z : z; @@ -814,12 +844,14 @@ public Vector3f maxLocal(Vector3f other){ } /** - * minLocal computes the minimum value for each - * component in this and other vector. The result is stored - * in this vector. - * @param other + * Compares this vector component-wise with the argument (keeping the most + * negative value for each component) and returns the (modified) current + * instance. + * + * @param other the vector to compare (not null, unaffected) + * @return the (modified) current instance (for chaining) */ - public Vector3f minLocal(Vector3f other){ + public Vector3f minLocal(Vector3f other) { x = other.x < x ? other.x : x; y = other.y < y ? other.y : y; z = other.z < z ? other.z : z; @@ -827,7 +859,9 @@ public Vector3f minLocal(Vector3f other){ } /** - * zero resets this vector's data to zero internally. + * Sets all 3 components to zero. + * + * @return the (modified) current instance (for chaining) */ public Vector3f zero() { x = y = z = 0; @@ -835,62 +869,78 @@ public Vector3f zero() { } /** - * angleBetween returns (in radians) the angle between two vectors. - * It is assumed that both this vector and the given vector are unit vectors (iow, normalized). - * - * @param otherVector a unit vector to find the angle against - * @return the angle in radians. + * Returns the angle (in radians) between this vector and the argument, + * provided both vectors have length=1. The current instance is unaffected. + * + * @param otherVector a unit vector to compare (not null, unaffected) + * @return the angle (in radians, not negative) */ public float angleBetween(Vector3f otherVector) { float dotProduct = dot(otherVector); float angle = FastMath.acos(dotProduct); return angle; } - + /** - * Sets this vector to the interpolation by changeAmnt from this to the finalVec - * this=(1-changeAmnt)*this + changeAmnt * finalVec - * @param finalVec The final vector to interpolate towards - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage - * change from this towards finalVec + * Interpolates linearly between this vector and the specified vector, + * returning the (modified) current instance. + * + *

      this = (1 - changeAmount) * this + changeAmount * finalVec + * + * @param finalVec the desired value when changeAmount=1 (not null, unaffected + * unless it's this) + * @param changeAmount the fractional change amount + * @return the (modified) current instance (for chaining) */ - public Vector3f interpolateLocal(Vector3f finalVec, float changeAmnt) { - this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x; - this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y; - this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z; + public Vector3f interpolateLocal(Vector3f finalVec, float changeAmount) { + this.x = (1 - changeAmount) * this.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * this.y + changeAmount * finalVec.y; + this.z = (1 - changeAmount) * this.z + changeAmount * finalVec.z; return this; } /** - * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec - * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec - * @param beginVec the beginning vector (changeAmnt=0) - * @param finalVec The final vector to interpolate towards - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage - * change from beginVec towards finalVec + * Interpolates linearly between the specified beginning and final vectors, + * returning the (modified) current instance. + * + *

      this = (1 - changeAmount) * beginVec + changeAmount * finalVec + * + * @param beginVec the desired value when changeAmount=0 (not null, unaffected + * unless it's this) + * @param finalVec the desired value when changeAmount=1 (not null, unaffected + * unless it's this) + * @param changeAmount the fractional change amount + * @return the (modified) current instance (for chaining) */ - public Vector3f interpolateLocal(Vector3f beginVec,Vector3f finalVec, float changeAmnt) { - this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x; - this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y; - this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z; + public Vector3f interpolateLocal(Vector3f beginVec, Vector3f finalVec, float changeAmount) { + this.x = (1 - changeAmount) * beginVec.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * beginVec.y + changeAmount * finalVec.y; + this.z = (1 - changeAmount) * beginVec.z + changeAmount * finalVec.z; return this; } /** - * Check a vector... if it is null or its floats are NaN or infinite, - * return false. Else return true. - * @param vector the vector to check - * @return true or false as stated above. + * Tests whether the argument is a valid vector, returning false if it's + * null or if any component is NaN or infinite. + * + * @param vector the vector to test (unaffected) + * @return true if non-null and finite, otherwise false */ public static boolean isValidVector(Vector3f vector) { - if (vector == null) return false; - if (Float.isNaN(vector.x) || - Float.isNaN(vector.y) || - Float.isNaN(vector.z)) return false; - if (Float.isInfinite(vector.x) || - Float.isInfinite(vector.y) || - Float.isInfinite(vector.z)) return false; - return true; + if (vector == null) { + return false; + } + if (Float.isNaN(vector.x) + || Float.isNaN(vector.y) + || Float.isNaN(vector.z)) { + return false; + } + if (Float.isInfinite(vector.x) + || Float.isInfinite(vector.y) + || Float.isInfinite(vector.z)) { + return false; + } + return true; } public static void generateOrthonormalBasis(Vector3f u, Vector3f v, Vector3f w) { @@ -923,6 +973,11 @@ public static void generateComplementBasis(Vector3f u, Vector3f v, } } + /** + * Creates a copy. The current instance is unaffected. + * + * @return a new instance, equivalent to the current one + */ @Override public Vector3f clone() { try { @@ -933,12 +988,12 @@ public Vector3f clone() { } /** - * Saves this Vector3f into the given float[] object. - * - * @param floats - * The float[] to take this Vector3f. If null, a new float[3] is - * created. - * @return The array, with X, Y, Z float values in that order + * Copies the vector into the argument. The current instance is unaffected. + * + * @param floats storage for the components (must have length≥3) or null + * for a new float[3] + * @return an array containing the X, Y, and Z components in that order + * (either floats or a new float[3]) */ public float[] toArray(float[] floats) { if (floats == null) { @@ -951,28 +1006,45 @@ public float[] toArray(float[] floats) { } /** - * are these two vectors the same? they are is they both have the same x,y, - * and z values. + * 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 for equality - * @return true if they are equal + * @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) { - if (!(o instanceof Vector3f)) { return false; } + if (!(o instanceof Vector3f)) { + return false; + } - if (this == o) { return true; } + if (this == o) { + return true; + } Vector3f comp = (Vector3f) o; - if (Float.compare(x,comp.x) != 0) return false; - if (Float.compare(y,comp.y) != 0) return false; - if (Float.compare(z,comp.z) != 0) return false; + if (Float.compare(x, comp.x) != 0) { + return false; + } + if (Float.compare(y, comp.y) != 0) { + return false; + } + if (Float.compare(z, comp.z) != 0) { + return false; + } return true; } - + /** - * Returns true if this vector is similar to the specified vector within - * some value of epsilon. + * Tests for approximate equality with the specified vector, using the + * specified tolerance. If {@code other} is null, false is returned. Either + * way, the current instance is unaffected. + * + * @param other the vector to compare (unaffected) or null for none + * @param epsilon the tolerance for each component + * @return true if all 3 components are within tolerance, otherwise false */ public boolean isSimilar(Vector3f other, float epsilon) { if (other == null) { @@ -991,11 +1063,12 @@ public boolean isSimilar(Vector3f other, float epsilon) { } /** - * hashCode returns a unique code for this vector object based - * on its values. If two vectors are logically equivalent, they will return - * the same hash code value. - * @return the hash code value of this vector. + * Returns a hash code. If two vectors have identical values, they will + * have the same hash code. The current instance is unaffected. + * + * @return a 32-bit value for use in hashing */ + @Override public int hashCode() { int hash = 37; hash += 37 * hash + Float.floatToIntBits(x); @@ -1005,17 +1078,27 @@ public int hashCode() { } /** - * toString returns the string representation of this vector. - * The format is: + * 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)
      +     * 
      * - * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ] - * - * @return the string representation of this vector. + * @return the string representation (not null, not empty) */ + @Override public String toString() { return "(" + x + ", " + y + ", " + z + ")"; } + /** + * Serializes to the argument, for example when saving to a J3O file. The + * current instance is unaffected. + * + * @param e the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(x, "x", 0); @@ -1023,46 +1106,88 @@ public void write(JmeExporter e) throws IOException { capsule.write(z, "z", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serializes from the argument, for example when loading from a J3O + * file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); x = capsule.readFloat("x", 0); y = capsule.readFloat("y", 0); z = capsule.readFloat("z", 0); } + /** + * Returns the X component. The vector is unaffected. + * + * @return the value of the {@link #x} component + */ public float getX() { return x; } + /** + * Sets the X component. + * + * @param x the desired value + * @return the (modified) current instance (for chaining) + */ public Vector3f setX(float x) { this.x = x; return this; } + /** + * Returns the Y component. The vector is unaffected. + * + * @return the value of the {@link #y} component + */ public float getY() { return y; } + /** + * Sets the Y component. + * + * @param y the desired value + * @return the (modified) current instance (for chaining) + */ public Vector3f setY(float y) { this.y = y; return this; } + /** + * Returns the Z component. The vector is unaffected. + * + * @return z the value of the {@link #z} component + */ public float getZ() { return z; } + /** + * Sets the Z component. + * + * @param z the desired value + * @return the (modified) current instance (for chaining) + */ public Vector3f setZ(float z) { this.z = z; return this; } - + /** - * @param index - * @return x value if index == 0, y value if index == 1 or z value if index == - * 2 - * @throws IllegalArgumentException - * if index is not one of 0, 1, 2. + * Returns the indexed component. The vector is unaffected. + * + * @param index 0, 1, or 2 + * @return the X component if index=0, the Y component if index=1, or the Z + * component if index=2 + * @throws IllegalArgumentException if index is not 0, 1, or 2 */ public float get(int index) { switch (index) { @@ -1075,14 +1200,14 @@ public float get(int index) { } throw new IllegalArgumentException("index must be either 0, 1 or 2"); } - + /** - * @param index - * which field index in this vector to set. - * @param value - * to set to one of x, y or z. - * @throws IllegalArgumentException - * if index is not one of 0, 1, 2. + * Sets the indexed component. + * + * @param index which component to set: 0 → the X component, 1 → + * the Y component, 2 → the Z component + * @param value the desired component value + * @throws IllegalArgumentException if index is not 0, 1, or 2 */ public void set(int index, float value) { switch (index) { @@ -1098,5 +1223,4 @@ public void set(int index, float value) { } throw new IllegalArgumentException("index must be either 0, 1 or 2"); } - } diff --git a/jme3-core/src/main/java/com/jme3/math/Vector4f.java b/jme3-core/src/main/java/com/jme3/math/Vector4f.java index d7c3e48f19..cf18c4be50 100644 --- a/jme3-core/src/main/java/com/jme3/math/Vector4f.java +++ b/jme3-core/src/main/java/com/jme3/math/Vector4f.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,49 +48,71 @@ public final class Vector4f implements Savable, Cloneable, java.io.Serializable static final long serialVersionUID = 1; private static final Logger logger = Logger.getLogger(Vector4f.class.getName()); - - public final static Vector4f ZERO = new Vector4f(0, 0, 0, 0); - public final static Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); - public final static Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); - public final static Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); - public final static Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); - public final static Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); - public final static Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); - public final static Vector4f POSITIVE_INFINITY = new Vector4f( + /** + * shared instance of the all-zero vector (0,0,0,0) - Do not modify! + */ + public static final Vector4f ZERO = new Vector4f(0, 0, 0, 0); + /** + * shared instance of the all-NaN vector (NaN,NaN,NaN,NaN) - Do not modify! + */ + public static final Vector4f NAN = new Vector4f(Float.NaN, Float.NaN, Float.NaN, Float.NaN); + /** + * shared instance of the +X direction (1,0,0,0) - Do not modify! + */ + public static final Vector4f UNIT_X = new Vector4f(1, 0, 0, 0); + /** + * shared instance of the +Y direction (0,1,0,0) - Do not modify! + */ + public static final Vector4f UNIT_Y = new Vector4f(0, 1, 0, 0); + /** + * shared instance of the +Z direction (0,0,1,0) - Do not modify! + */ + public static final Vector4f UNIT_Z = new Vector4f(0, 0, 1, 0); + /** + * shared instance of the +W direction (0,0,0,1) - Do not modify! + */ + public static final Vector4f UNIT_W = new Vector4f(0, 0, 0, 1); + /** + * shared instance of the all-ones vector (1,1,1,1) - Do not modify! + */ + public static final Vector4f UNIT_XYZW = new Vector4f(1, 1, 1, 1); + /** + * shared instance of the all-plus-infinity vector (+Inf,+Inf,+Inf,+Inf) + * - Do not modify! + */ + public static final Vector4f POSITIVE_INFINITY = new Vector4f( Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); - public final static Vector4f NEGATIVE_INFINITY = new Vector4f( + /** + * shared instance of the all-negative-infinity vector (-Inf,-Inf,-Inf,-Inf) + * - Do not modify! + */ + public static final Vector4f NEGATIVE_INFINITY = new Vector4f( Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NEGATIVE_INFINITY); - /** * the x value of the vector. */ public float x; - /** * the y value of the vector. */ public float y; - /** * the z value of the vector. */ public float z; - /** * the w value of the vector. */ public float w; /** - * Constructor instantiates a new Vector3f with default - * values of (0,0,0). - * + * Instantiate a Vector4f with the value (0,0,0,0). */ public Vector4f() { x = y = z = w = 0; @@ -100,14 +122,10 @@ public Vector4f() { * Constructor instantiates a new Vector4f with provides * values. * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. - * @param w - * the w value of the vector. + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. */ public Vector4f(float x, float y, float z, float w) { this.x = x; @@ -117,9 +135,9 @@ public Vector4f(float x, float y, float z, float w) { } /** - * Constructor instantiates a new Vector3f that is a copy - * of the provided vector - * @param copy The Vector3f to copy + * Instantiate a Vector4f that is a copy of the provided vector. + * + * @param copy The Vector4f to copy */ public Vector4f(Vector4f copy) { this.set(copy); @@ -129,14 +147,10 @@ public Vector4f(Vector4f copy) { * set sets the x,y,z,w values of the vector based on passed * parameters. * - * @param x - * the x value of the vector. - * @param y - * the y value of the vector. - * @param z - * the z value of the vector. - * @param w - * the w value of the vector. + * @param x the x value of the vector. + * @param y the y value of the vector. + * @param z the z value of the vector. + * @param w the w value of the vector. * @return this vector */ public Vector4f set(float x, float y, float z, float w) { @@ -164,7 +178,6 @@ public Vector4f set(Vector4f vect) { } /** - * * add adds a provided vector to this vector creating a * resultant vector which is returned. If the provided vector is null, null * is returned. @@ -182,7 +195,6 @@ public Vector4f add(Vector4f vec) { } /** - * * add adds the values of a provided vector storing the * values in the supplied vector. * @@ -222,7 +234,6 @@ public Vector4f addLocal(Vector4f vec) { } /** - * * add adds the provided values to this vector, creating a * new vector that is then returned. * @@ -232,6 +243,8 @@ public Vector4f addLocal(Vector4f vec) { * the y value to add. * @param addZ * the z value to add. + * @param addW + * the w value to add. * @return the result vector. */ public Vector4f add(float addX, float addY, float addZ, float addW) { @@ -249,6 +262,8 @@ public Vector4f add(float addX, float addY, float addZ, float addW) { * value to add to y * @param addZ * value to add to z + * @param addW + * the w value to add. * @return this */ public Vector4f addLocal(float addX, float addY, float addZ, float addW) { @@ -260,14 +275,14 @@ public Vector4f addLocal(float addX, float addY, float addZ, float addW) { } /** - * * scaleAdd multiplies this vector by a scalar then adds the - * given Vector3f. + * given Vector4f. * * @param scalar * the value to multiply this vector by. * @param add * the value to add + * @return this */ public Vector4f scaleAdd(float scalar, Vector4f add) { x = x * scalar + add.x; @@ -278,7 +293,6 @@ public Vector4f scaleAdd(float scalar, Vector4f add) { } /** - * * scaleAdd multiplies the given vector by a scalar then adds * the given vector. * @@ -288,6 +302,7 @@ public Vector4f scaleAdd(float scalar, Vector4f add) { * the value to multiply the scalar by * @param add * the value to add + * @return this */ public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) { this.x = mult.x * scalar + add.x; @@ -298,7 +313,6 @@ public Vector4f scaleAdd(float scalar, Vector4f mult, Vector4f add) { } /** - * * dot calculates the dot product of this vector with a * provided vector. If the provided vector is null, 0 is returned. * @@ -314,10 +328,10 @@ public float dot(Vector4f vec) { return x * vec.x + y * vec.y + z * vec.z + w * vec.w; } - public Vector4f project(Vector4f other){ + public Vector4f project(Vector4f other) { float n = this.dot(other); // A . B float d = other.lengthSquared(); // |B|^2 - return new Vector4f(other).multLocal(n/d); + return new Vector4f(other).multLocal(n / d); } /** @@ -327,7 +341,7 @@ public Vector4f project(Vector4f other){ * @return true if this vector is a unit vector (length() ~= 1), * or false otherwise. */ - public boolean isUnitVector(){ + public boolean isUnitVector() { float len = length(); return 0.99f < len && len < 1.01f; } @@ -378,7 +392,6 @@ public float distance(Vector4f v) { } /** - * * mult multiplies this vector by a scalar. The resultant * vector is returned. * @@ -391,7 +404,6 @@ public Vector4f mult(float scalar) { } /** - * * mult multiplies this vector by a scalar. The resultant * vector is supplied as the second parameter and returned. * @@ -411,6 +423,20 @@ public Vector4f mult(float scalar, Vector4f product) { return product; } + /** + * Multiplies component-wise by the specified components and returns the + * product as a new instance. The current instance is unaffected. + * + * @param x the scale factor for the X component + * @param y the scale factor for the Y component + * @param z the scale factor for the Z component + * @param w the scale factor for the W component + * @return a new Vector4f + */ + public Vector4f mult(float x, float y, float z, float w) { + return new Vector4f(this.x * x, this.y * y, this.z * z, this.w * w); + } + /** * multLocal multiplies this vector by a scalar internally, * and returns a handle to this vector for easy chaining of calls. @@ -449,14 +475,14 @@ public Vector4f multLocal(Vector4f vec) { } /** - * multLocal multiplies this vector by 3 scalars + * multLocal multiplies this vector by 4 scalars * internally, and returns a handle to this vector for easy chaining of * calls. * - * @param x - * @param y - * @param z - * @param w + * @param x the scaling factor for the X component + * @param y the scaling factor for the Y component + * @param z the scaling factor for the Z component + * @param w the scaling factor for the W component * @return this */ public Vector4f multLocal(float x, float y, float z, float w) { @@ -499,7 +525,9 @@ public Vector4f mult(Vector4f vec, Vector4f store) { logger.warning("Provided vector is null, null returned."); return null; } - if (store == null) store = new Vector4f(); + if (store == null) { + store = new Vector4f(); + } return store.set(x * vec.x, y * vec.y, z * vec.z, w * vec.w); } @@ -512,7 +540,7 @@ public Vector4f mult(Vector4f vec, Vector4f store) { * @return the result Vector. */ public Vector4f divide(float scalar) { - scalar = 1f/scalar; + scalar = 1f / scalar; return new Vector4f(x * scalar, y * scalar, z * scalar, w * scalar); } @@ -526,7 +554,7 @@ public Vector4f divide(float scalar) { * @return this */ public Vector4f divideLocal(float scalar) { - scalar = 1f/scalar; + scalar = 1f / scalar; x *= scalar; y *= scalar; z *= scalar; @@ -538,12 +566,12 @@ public Vector4f divideLocal(float scalar) { * divide divides the values of this vector by a scalar and * returns the result. The values of this vector remain untouched. * - * @param scalar + * @param divisor * the value to divide this vectors attributes by. * @return the result Vector. */ - public Vector4f divide(Vector4f scalar) { - return new Vector4f(x / scalar.x, y / scalar.y, z / scalar.z, w / scalar.w); + public Vector4f divide(Vector4f divisor) { + return new Vector4f(x / divisor.x, y / divisor.y, z / divisor.z, w / divisor.w); } /** @@ -551,20 +579,51 @@ public Vector4f divide(Vector4f scalar) { * and returns a handle to this vector for easy chaining of calls. Dividing * by zero will result in an exception. * - * @param scalar + * @param divisor * the value to divides this vector by. * @return this */ - public Vector4f divideLocal(Vector4f scalar) { - x /= scalar.x; - y /= scalar.y; - z /= scalar.z; - w /= scalar.w; + public Vector4f divideLocal(Vector4f divisor) { + x /= divisor.x; + y /= divisor.y; + z /= divisor.z; + w /= divisor.w; + return this; + } + + /** + * Divides component-wise by the specified components returns the (modified) + * current instance. + * + * @param x the divisor for the X component + * @param y the divisor for the Y component + * @param z the divisor for the Z component + * @param w the divisor for the W component + * @return the (modified) current instance (for chaining) + */ + public Vector4f divideLocal(float x, float y, float z, float w) { + this.x /= x; + this.y /= y; + this.z /= z; + this.w /= w; return this; } /** + * 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 + * @param z the divisor for the Z component + * @param w the divisor for the W component + * @return a new Vector4f + */ + public Vector4f divide(float x, float y, float z, float w) { + return new Vector4f(this.x / x, this.y / y, this.z / z, this.w / w); + } + + /** * negate returns the negative of this vector. All values are * negated and set to a new vector. * @@ -575,7 +634,6 @@ public Vector4f negate() { } /** - * * negateLocal negates the internal values of this vector. * * @return this. @@ -589,7 +647,6 @@ public Vector4f negateLocal() { } /** - * * subtract subtracts the values of a given vector from those * of this vector creating a new vector object. If the provided vector is * null, null is returned. @@ -624,7 +681,6 @@ public Vector4f subtractLocal(Vector4f vec) { } /** - * * subtract * * @param vec @@ -634,7 +690,7 @@ public Vector4f subtractLocal(Vector4f vec) { * @return result */ public Vector4f subtract(Vector4f vec, Vector4f result) { - if(result == null) { + if (result == null) { result = new Vector4f(); } result.x = x - vec.x; @@ -645,7 +701,6 @@ public Vector4f subtract(Vector4f vec, Vector4f result) { } /** - * * subtract subtracts the provided values from this vector, * creating a new vector that is then returned. * @@ -699,7 +754,7 @@ public Vector4f normalize() { // // return divide(1); float length = x * x + y * y + z * z + w * w; - if (length != 1f && length != 0f){ + if (length != 1f && length != 0f) { length = 1.0f / FastMath.sqrt(length); return new Vector4f(x * length, y * length, z * length, w * length); } @@ -717,7 +772,7 @@ public Vector4f normalizeLocal() { // than the old jme normalize as this method // is commonly used. float length = x * x + y * y + z * z + w * w; - if (length != 1f && length != 0f){ + if (length != 1f && length != 0f) { length = 1.0f / FastMath.sqrt(length); x *= length; y *= length; @@ -731,9 +786,11 @@ public Vector4f normalizeLocal() { * maxLocal computes the maximum value for each * component in this and other vector. The result is stored * in this vector. - * @param other + * + * @param other the vector to compare with (not null, unaffected) + * @return this */ - public Vector4f maxLocal(Vector4f other){ + public Vector4f maxLocal(Vector4f other) { x = other.x > x ? other.x : x; y = other.y > y ? other.y : y; z = other.z > z ? other.z : z; @@ -745,9 +802,11 @@ public Vector4f maxLocal(Vector4f other){ * minLocal computes the minimum value for each * component in this and other vector. The result is stored * in this vector. - * @param other + * + * @param other the vector to compare with (not null, unaffected) + * @return this */ - public Vector4f minLocal(Vector4f other){ + public Vector4f minLocal(Vector4f other) { x = other.x < x ? other.x : x; y = other.y < y ? other.y : y; z = other.z < z ? other.z : z; @@ -757,6 +816,8 @@ public Vector4f minLocal(Vector4f other){ /** * zero resets this vector's data to zero internally. + * + * @return this, with all components set to zero */ public Vector4f zero() { x = y = z = w = 0; @@ -777,55 +838,71 @@ public float angleBetween(Vector4f otherVector) { } /** - * Sets this vector to the interpolation by changeAmnt from this to the finalVec - * this=(1-changeAmnt)*this + changeAmnt * finalVec + * Sets this vector to the interpolation by changeAmount from this to the finalVec + * this=(1-changeAmount)*this + changeAmount * finalVec + * * @param finalVec The final vector to interpolate towards - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage + * @param changeAmount An amount between 0.0 - 1.0 representing a percentage * change from this towards finalVec + * @return this */ - public Vector4f interpolateLocal(Vector4f finalVec, float changeAmnt) { - this.x=(1-changeAmnt)*this.x + changeAmnt*finalVec.x; - this.y=(1-changeAmnt)*this.y + changeAmnt*finalVec.y; - this.z=(1-changeAmnt)*this.z + changeAmnt*finalVec.z; - this.w=(1-changeAmnt)*this.w + changeAmnt*finalVec.w; + public Vector4f interpolateLocal(Vector4f finalVec, float changeAmount) { + this.x = (1 - changeAmount) * this.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * this.y + changeAmount * finalVec.y; + this.z = (1 - changeAmount) * this.z + changeAmount * finalVec.z; + this.w = (1 - changeAmount) * this.w + changeAmount * finalVec.w; return this; } /** - * Sets this vector to the interpolation by changeAmnt from beginVec to finalVec - * this=(1-changeAmnt)*beginVec + changeAmnt * finalVec - * @param beginVec the beginning vector (changeAmnt=0) + * Sets this vector to the interpolation by changeAmount from beginVec to finalVec + * this=(1-changeAmount)*beginVec + changeAmount * finalVec + * + * @param beginVec the beginning vector (changeAmount=0) * @param finalVec The final vector to interpolate towards - * @param changeAmnt An amount between 0.0 - 1.0 representing a percentage + * @param changeAmount An amount between 0.0 - 1.0 representing a percentage * change from beginVec towards finalVec + * @return this */ - public Vector4f interpolateLocal(Vector4f beginVec,Vector4f finalVec, float changeAmnt) { - this.x=(1-changeAmnt)*beginVec.x + changeAmnt*finalVec.x; - this.y=(1-changeAmnt)*beginVec.y + changeAmnt*finalVec.y; - this.z=(1-changeAmnt)*beginVec.z + changeAmnt*finalVec.z; - this.w=(1-changeAmnt)*beginVec.w + changeAmnt*finalVec.w; + public Vector4f interpolateLocal(Vector4f beginVec, Vector4f finalVec, float changeAmount) { + this.x = (1 - changeAmount) * beginVec.x + changeAmount * finalVec.x; + this.y = (1 - changeAmount) * beginVec.y + changeAmount * finalVec.y; + this.z = (1 - changeAmount) * beginVec.z + changeAmount * finalVec.z; + this.w = (1 - changeAmount) * beginVec.w + changeAmount * finalVec.w; return this; } /** * Check a vector... if it is null or its floats are NaN or infinite, * return false. Else return true. + * * @param vector the vector to check * @return true or false as stated above. */ public static boolean isValidVector(Vector4f vector) { - if (vector == null) return false; - if (Float.isNaN(vector.x) || - Float.isNaN(vector.y) || - Float.isNaN(vector.z)|| - Float.isNaN(vector.w)) return false; - if (Float.isInfinite(vector.x) || - Float.isInfinite(vector.y) || - Float.isInfinite(vector.z) || - Float.isInfinite(vector.w)) return false; - return true; + if (vector == null) { + return false; + } + if (Float.isNaN(vector.x) + || Float.isNaN(vector.y) + || Float.isNaN(vector.z) + || Float.isNaN(vector.w)) { + return false; + } + if (Float.isInfinite(vector.x) + || Float.isInfinite(vector.y) + || Float.isInfinite(vector.z) + || Float.isInfinite(vector.w)) { + return false; + } + return true; } + /** + * Create a copy of this vector. + * + * @return a new instance, equivalent to this one + */ @Override public Vector4f clone() { try { @@ -836,12 +913,12 @@ public Vector4f clone() { } /** - * Saves this Vector3f into the given float[] object. + * Saves this Vector4f into the given float[] object. * * @param floats - * The float[] to take this Vector3f. If null, a new float[3] is + * The float[] to take this Vector4f. If null, a new float[4] is * created. - * @return The array, with X, Y, Z float values in that order + * @return The array, with X, Y, Z, W float values in that order */ public float[] toArray(float[] floats) { if (floats == null) { @@ -855,29 +932,45 @@ public float[] toArray(float[] floats) { } /** - * are these two vectors the same? they are is they both have the same x,y, - * and z values. + * Are these two vectors the same? They are if they have the same x, y, + * z, and w values. * - * @param o - * the object to compare for equality + * @param o the object to compare for equality * @return true if they are equal */ + @Override public boolean equals(Object o) { - if (!(o instanceof Vector4f)) { return false; } + if (!(o instanceof Vector4f)) { + return false; + } - if (this == o) { return true; } + if (this == o) { + return true; + } Vector4f comp = (Vector4f) o; - if (Float.compare(x,comp.x) != 0) return false; - if (Float.compare(y,comp.y) != 0) return false; - if (Float.compare(z,comp.z) != 0) return false; - if (Float.compare(w,comp.w) != 0) return false; + if (Float.compare(x, comp.x) != 0) { + return false; + } + if (Float.compare(y, comp.y) != 0) { + return false; + } + if (Float.compare(z, comp.z) != 0) { + return false; + } + if (Float.compare(w, comp.w) != 0) { + return false; + } return true; } - + /** * Returns true if this vector is similar to the specified vector within * some value of epsilon. + * + * @param other the vector to compare with (not null, unaffected) + * @param epsilon the desired error tolerance for each component + * @return true if all 4 components are within tolerance, otherwise false */ public boolean isSimilar(Vector4f other, float epsilon) { if (other == null) { @@ -902,8 +995,10 @@ public boolean isSimilar(Vector4f other, float epsilon) { * hashCode returns a unique code for this vector object based * on its values. If two vectors are logically equivalent, they will return * the same hash code value. + * * @return the hash code value of this vector. */ + @Override public int hashCode() { int hash = 37; hash += 37 * hash + Float.floatToIntBits(x); @@ -917,14 +1012,23 @@ public int hashCode() { * toString returns the string representation of this vector. * The format is: * - * org.jme.math.Vector3f [X=XX.XXXX, Y=YY.YYYY, Z=ZZ.ZZZZ, W=WW.WWWW] + * (XX.XXXX, YY.YYYY, ZZ.ZZZZ, WW.WWWW) * * @return the string representation of this vector. */ + @Override public String toString() { return "(" + x + ", " + y + ", " + z + ", " + w + ")"; } + /** + * Serialize this vector to the specified exporter, for example when + * saving to a J3O file. + * + * @param e (not null) + * @throws IOException from the exporter + */ + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(x, "x", 0); @@ -933,54 +1037,105 @@ public void write(JmeExporter e) throws IOException { capsule.write(w, "w", 0); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + /** + * De-serialize this vector from the specified importer, for example + * when loading from a J3O file. + * + * @param importer (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); x = capsule.readFloat("x", 0); y = capsule.readFloat("y", 0); z = capsule.readFloat("z", 0); w = capsule.readFloat("w", 0); } + /** + * Determine the X component of this vector. + * + * @return x + */ public float getX() { return x; } + /** + * Alter the X component of this vector. + * + * @param x the desired value + * @return this vector, modified + */ public Vector4f setX(float x) { this.x = x; return this; } + /** + * Determine the Y component of this vector. + * + * @return y + */ public float getY() { return y; } + /** + * Alter the Y component of this vector. + * + * @param y the desired value + * @return this vector, modified + */ public Vector4f setY(float y) { this.y = y; return this; } + /** + * Determine the Z component of this vector. + * + * @return z + */ public float getZ() { return z; } + /** + * Alter the Z component of this vector. + * + * @param z the desired value + * @return this vector, modified + */ public Vector4f setZ(float z) { this.z = z; return this; } + /** + * Determine the W component of this vector. + * + * @return w + */ public float getW() { return w; } + /** + * Alter the W component of this vector. + * + * @param w the desired value + * @return this vector, modified + */ public Vector4f setW(float w) { this.w = w; return this; } /** - * @param index - * @return x value if index == 0, y value if index == 1 or z value if index == - * 2 + * @param index which component (≥0, <4) + * @return x value if index == 0, y value if index == 1 or z value if index == 2 * @throws IllegalArgumentException * if index is not one of 0, 1, 2. */ @@ -1019,9 +1174,8 @@ public void set(int index, float value) { return; case 3: w = value; - return; + return; } throw new IllegalArgumentException("index must be either 0, 1, 2 or 3"); } - } diff --git a/jme3-core/src/main/java/com/jme3/math/package.html b/jme3-core/src/main/java/com/jme3/math/package.html index 64da8aaa68..dff00e56dc 100644 --- a/jme3-core/src/main/java/com/jme3/math/package.html +++ b/jme3-core/src/main/java/com/jme3/math/package.html @@ -7,7 +7,7 @@ -The com.jme3.math package provides mathematic data structures +The com.jme3.math package provides mathematical data structures and utilities which are used by the rest of the engine. The math package provides the following classes:

      General purpose vectors

      diff --git a/jme3-core/src/main/java/com/jme3/opencl/AbstractOpenCLObject.java b/jme3-core/src/main/java/com/jme3/opencl/AbstractOpenCLObject.java index 2bb80cff7c..2f602f757d 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/AbstractOpenCLObject.java +++ b/jme3-core/src/main/java/com/jme3/opencl/AbstractOpenCLObject.java @@ -45,7 +45,7 @@ protected AbstractOpenCLObject(ObjectReleaser releaser) { @Override public AbstractOpenCLObject register() { OpenCLObjectManager.getInstance().registerObject(this); - return this; + return this; } @Override public void release() { diff --git a/jme3-core/src/main/java/com/jme3/opencl/Buffer.java b/jme3-core/src/main/java/com/jme3/opencl/Buffer.java index ce3ff8fab9..bbe9a8c1c9 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Buffer.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Buffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,11 +40,11 @@ *
      * Buffers are created by the {@link Context}. *
      - * All access methods (read/write/copy/map) are available in both sychronized/blocking versions + * All access methods (read/write/copy/map) are available in both synchronized/blocking versions * and in async/non-blocking versions. The later ones always return an {@link Event} object * and have the prefix -Async in their name. - * - * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) + * + * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) * @author shaman */ public abstract class Buffer extends AbstractOpenCLObject { @@ -53,21 +53,21 @@ protected Buffer(ObjectReleaser releaser) { super(releaser); } - @Override - public Buffer register() { - super.register(); - return this; - } - + @Override + public Buffer register() { + super.register(); + return this; + } + /** * @return the size of the buffer in bytes. - * @see Context#createBuffer(long) + * @see Context#createBuffer(long) */ public abstract long getSize(); /** * @return the memory access flags set on creation. - * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) + * @see Context#createBuffer(long, com.jme3.opencl.MemoryAccess) */ public abstract MemoryAccess getMemoryAccessFlags(); @@ -75,6 +75,7 @@ public Buffer register() { * Performs a blocking read of the buffer. * The target buffer must have at least {@code size} bytes remaining. * This method may set the limit to the last byte read. + * * @param queue the command queue * @param dest the target buffer * @param size the size in bytes being read @@ -85,6 +86,10 @@ public Buffer register() { /** * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, * sets {@code offset} to zero. + * + * @param queue the command queue + * @param dest the target buffer + * @param size the number of bytes to read */ public void read(CommandQueue queue, ByteBuffer dest, long size) { read(queue, dest, size, 0); @@ -93,6 +98,9 @@ public void read(CommandQueue queue, ByteBuffer dest, long size) { /** * Alternative version of {@link #read(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, * sets {@code size} to {@link #getSize() }. + * + * @param queue the command queue + * @param dest the target buffer */ public void read(CommandQueue queue, ByteBuffer dest) { read(queue, dest, getSize()); @@ -102,6 +110,7 @@ public void read(CommandQueue queue, ByteBuffer dest) { * Performs an async/non-blocking read of the buffer. * The target buffer must have at least {@code size} bytes remaining. * This method may set the limit to the last byte read. + * * @param queue the command queue * @param dest the target buffer * @param size the size in bytes being read @@ -113,6 +122,11 @@ public void read(CommandQueue queue, ByteBuffer dest) { /** * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, * sets {@code offset} to zero. + * + * @param queue the command queue + * @param dest the target buffer + * @param size the number of bytes to read + * @return an Event to indicate completion */ public Event readAsync(CommandQueue queue, ByteBuffer dest, long size) { return readAsync(queue, dest, size, 0); @@ -121,6 +135,10 @@ public Event readAsync(CommandQueue queue, ByteBuffer dest, long size) { /** * Alternative version of {@link #readAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, * sets {@code size} to {@link #getSize() } + * + * @param queue the command queue + * @param dest the target buffer + * @return an Event to indicate completion */ public Event readAsync(CommandQueue queue, ByteBuffer dest) { return readAsync(queue, dest, getSize()); @@ -130,6 +148,7 @@ public Event readAsync(CommandQueue queue, ByteBuffer dest) { * Performs a blocking write to the buffer. * The target buffer must have at least {@code size} bytes remaining. * This method may set the limit to the last byte that will be written. + * * @param queue the command queue * @param src the source buffer, its data is written to this buffer * @param size the size in bytes to write @@ -140,6 +159,10 @@ public Event readAsync(CommandQueue queue, ByteBuffer dest) { /** * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, * sets {@code offset} to zero. + * + * @param queue the command queue + * @param src the source buffer, its data is written to this buffer + * @param size the number of bytes to write */ public void write(CommandQueue queue, ByteBuffer src, long size) { write(queue, src, size, 0); @@ -148,6 +171,9 @@ public void write(CommandQueue queue, ByteBuffer src, long size) { /** * Alternative version of {@link #write(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, * sets {@code size} to {@link #getSize() }. + * + * @param queue the command queue + * @param src the source buffer, its data is written to this buffer */ public void write(CommandQueue queue, ByteBuffer src) { write(queue, src, getSize()); @@ -157,17 +183,23 @@ public void write(CommandQueue queue, ByteBuffer src) { * Performs an async/non-blocking write to the buffer. * The target buffer must have at least {@code size} bytes remaining. * This method may set the limit to the last byte that will be written. + * * @param queue the command queue * @param src the source buffer, its data is written to this buffer * @param size the size in bytes to write * @param offset the offset into the target buffer - * @return the event object indicating when the write operation is completed + * @return an Event to indicate completion */ public abstract Event writeAsync(CommandQueue queue, ByteBuffer src, long size, long offset); /** * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long, long) }, * sets {@code offset} to zero. + * + * @param queue the command queue + * @param src the source buffer, its data is written to this buffer + * @param size the number of bytes to write + * @return an Event to indicate completion */ public Event writeAsync(CommandQueue queue, ByteBuffer src, long size) { return writeAsync(queue, src, size, 0); @@ -176,6 +208,10 @@ public Event writeAsync(CommandQueue queue, ByteBuffer src, long size) { /** * Alternative version of {@link #writeAsync(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer, long) }, * sets {@code size} to {@link #getSize() }. + * + * @param queue the command queue + * @param src the source buffer, its data is written to this buffer + * @return an Event to indicate completion */ public Event writeAsync(CommandQueue queue, ByteBuffer src) { return writeAsync(queue, src, getSize()); @@ -183,6 +219,7 @@ public Event writeAsync(CommandQueue queue, ByteBuffer src) { /** * Performs a blocking copy operation from this buffer to the specified buffer. + * * @param queue the command queue * @param dest the target buffer * @param size the size in bytes to copy @@ -194,6 +231,10 @@ public Event writeAsync(CommandQueue queue, ByteBuffer src) { /** * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) }, * sets {@code srcOffset} and {@code destOffset} to zero. + * + * @param queue the command queue + * @param dest the target buffer + * @param size the number of bytes to copy */ public void copyTo(CommandQueue queue, Buffer dest, long size) { copyTo(queue, dest, size, 0, 0); @@ -202,6 +243,9 @@ public void copyTo(CommandQueue queue, Buffer dest, long size) { /** * Alternative version of {@link #copyTo(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) }, * sets {@code size} to {@code this.getSize()}. + * + * @param queue the command queue + * @param dest the target buffer */ public void copyTo(CommandQueue queue, Buffer dest) { copyTo(queue, dest, getSize()); @@ -209,6 +253,7 @@ public void copyTo(CommandQueue queue, Buffer dest) { /** * Performs an async/non-blocking copy operation from this buffer to the specified buffer. + * * @param queue the command queue * @param dest the target buffer * @param size the size in bytes to copy @@ -221,6 +266,11 @@ public void copyTo(CommandQueue queue, Buffer dest) { /** * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long, long, long) }, * sets {@code srcOffset} and {@code destOffset} to zero. + * + * @param queue the command queue + * @param dest the target buffer + * @param size the number of bytes to copy + * @return an Event to indicate completion */ public Event copyToAsync(CommandQueue queue, Buffer dest, long size) { return copyToAsync(queue, dest, size, 0, 0); @@ -229,6 +279,10 @@ public Event copyToAsync(CommandQueue queue, Buffer dest, long size) { /** * Alternative version of {@link #copyToAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Buffer, long) }, * sets {@code size} to {@code this.getSize()}. + * + * @param queue the command queue + * @param dest the target buffer + * @return an Event to indicate completion */ public Event copyToAsync(CommandQueue queue, Buffer dest) { return copyToAsync(queue, dest, getSize()); @@ -238,8 +292,9 @@ public Event copyToAsync(CommandQueue queue, Buffer dest) { * Maps this buffer directly into host memory. This might be the fastest method * to access the contents of the buffer since the OpenCL implementation directly * provides the memory.
      - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * * @param queue the command queue * @param size the size in bytes to map * @param offset the offset into this buffer @@ -251,8 +306,13 @@ public Event copyToAsync(CommandQueue queue, Buffer dest) { /** * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) }, * sets {@code offset} to zero. - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * + * @param queue the command queue + * @param size the number of bytes to map + * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE + * @return the byte buffer directly reflecting the buffer contents */ public ByteBuffer map(CommandQueue queue, long size, MappingAccess access) { return map(queue, size, 0, access); @@ -261,8 +321,12 @@ public ByteBuffer map(CommandQueue queue, long size, MappingAccess access) { /** * Alternative version of {@link #map(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) }, * sets {@code size} to {@link #getSize() }. - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * + * @param queue the command queue + * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE + * @return the byte buffer directly reflecting the buffer contents */ public ByteBuffer map(CommandQueue queue, MappingAccess access) { return map(queue, getSize(), access); @@ -272,6 +336,7 @@ public ByteBuffer map(CommandQueue queue, MappingAccess access) { * Unmaps a previously mapped memory. * This releases the native resources and for WRITE_ONLY or READ_WRITE access, * the memory content is sent back to the GPU. + * * @param queue the command queue * @param ptr the buffer that was previously mapped */ @@ -281,8 +346,9 @@ public ByteBuffer map(CommandQueue queue, MappingAccess access) { * Maps this buffer asynchronously into host memory. This might be the fastest method * to access the contents of the buffer since the OpenCL implementation directly * provides the memory.
      - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * * @param queue the command queue * @param size the size in bytes to map * @param offset the offset into this buffer @@ -291,28 +357,42 @@ public ByteBuffer map(CommandQueue queue, MappingAccess access) { * and the event indicating when the buffer contents are available */ public abstract AsyncMapping mapAsync(CommandQueue queue, long size, long offset, MappingAccess access); + /** * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, long, com.jme3.opencl.MappingAccess) }, * sets {@code offset} to zero. - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * + * @param queue the command queue + * @param size the size in bytes to map + * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE + * @return the byte buffer directly reflecting the buffer contents + * and the event indicating when the buffer contents are available */ public AsyncMapping mapAsync(CommandQueue queue, long size, MappingAccess access) { return mapAsync(queue, size, 0, access); } + /** * Alternative version of {@link #mapAsync(com.jme3.opencl.CommandQueue, long, com.jme3.opencl.MappingAccess) }, * sets {@code size} to {@link #getSize() }. - * Important: The mapped memory MUST be released by calling + * Important: The mapped memory MUST be released by calling * {@link #unmap(com.jme3.opencl.CommandQueue, java.nio.ByteBuffer) }. + * + * @param queue the command queue + * @param access specifies the possible access to the memory: READ_ONLY, WRITE_ONLY, READ_WRITE + * @return the byte buffer directly reflecting the buffer contents + * and the event indicating when the buffer contents are available */ public AsyncMapping mapAsync(CommandQueue queue, MappingAccess access) { return mapAsync(queue, getSize(), 0, access); } - + /** * Enqueues a fill operation. This method can be used to initialize or clear * a buffer with a certain value. + * * @param queue the command queue * @param pattern the buffer containing the filling pattern. * The remaining bytes specify the pattern length @@ -324,7 +404,7 @@ public AsyncMapping mapAsync(CommandQueue queue, MappingAccess access) { /** * Result of an async mapping operation, contains the event and the target byte buffer. - * This is a work-around since no generic pair-structure is avaiable. + * This is a work-around since no generic pair-structure is available. * * @author shaman */ @@ -354,14 +434,14 @@ public ByteBuffer getBuffer() { return buffer; } } - + /** * Copies this buffer to the specified image. * Note that no format conversion is done. *
      - * For detailed description of the origin and region paramenter, see the + * For detailed description of the origin and region parameter, see the * documentation of the {@link Image} class. - * + * * @param queue the command queue * @param dest the target image * @param srcOffset the offset in bytes into this buffer @@ -370,58 +450,60 @@ public ByteBuffer getBuffer() { * @return the event object */ public abstract Event copyToImageAsync(CommandQueue queue, Image dest, long srcOffset, long[] destOrigin, long[] destRegion); - + /** - * Aquires this buffer object for using. Only call this method if this buffer + * Acquires this buffer object for using. Only call this method if this buffer * represents a shared object from OpenGL, created with e.g. * {@link Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) }. * This method must be called before the buffer is used. After the work is * done, the buffer must be released by calling * {@link #releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * so that OpenGL can use the VertexBuffer again. + * * @param queue the command queue * @return the event object */ public abstract Event acquireBufferForSharingAsync(CommandQueue queue); - + /** - * Aquires this buffer object for using. Only call this method if this buffer + * Acquires this buffer object for using. Only call this method if this buffer * represents a shared object from OpenGL, created with e.g. * {@link Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) }. * This method must be called before the buffer is used. After the work is * done, the buffer must be released by calling * {@link #releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * so that OpenGL can use the VertexBuffer again. - * + * * The generated event object is directly released. * This brings a performance improvement when the resource is e.g. directly * used by a kernel afterwards on the same queue (this implicitly waits for - * this action). If you need the event, use + * this action). If you need the event, use * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } instead. - * + * * @param queue the command queue */ public void acquireBufferForSharingNoEvent(CommandQueue queue) { //default implementation, overwrite for better performance acquireBufferForSharingAsync(queue).release(); } - + /** * Releases a shared buffer object. * Call this method after the buffer object was acquired by * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * to hand the control back to OpenGL. + * * @param queue the command queue * @return the event object */ public abstract Event releaseBufferForSharingAsync(CommandQueue queue); - + /** * Releases a shared buffer object. * Call this method after the buffer object was acquired by * {@link #acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * to hand the control back to OpenGL. - * The generated event object is directly released, resulting in + * The generated event object is directly released, resulting in * performance improvements. * @param queue the command queue */ @@ -430,9 +512,8 @@ public void releaseBufferForSharingNoEvent(CommandQueue queue) { releaseBufferForSharingAsync(queue).release(); } - @Override - public String toString() { - return "Buffer (" + getSize() + "B)"; - } - + @Override + public String toString() { + return "Buffer (" + getSize() + "B)"; + } } diff --git a/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java b/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java index 8b9f77ef24..001e74292b 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java +++ b/jme3-core/src/main/java/com/jme3/opencl/CommandQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,39 +34,40 @@ /** * Wrapper for an OpenCL command queue. * The command queue serializes every GPU function call: By passing the same - * queue to OpenCL function (buffer, image operations, kernel calls), it is + * queue to OpenCL function (buffer, image operations, kernel calls), it is * ensured that they are executed in the order in which they are passed. *
      - * Each command queue is associtated with exactly one device: that device + * Each command queue is associated with exactly one device: that device * is specified on creation ({@link Context#createQueue(com.jme3.opencl.Device) }) * and all commands are sent to this device. + * * @author shaman */ public abstract class CommandQueue extends AbstractOpenCLObject { - - protected Device device; + protected Device device; protected CommandQueue(ObjectReleaser releaser, Device device) { super(releaser); - this.device = device; + this.device = device; } - @Override - public CommandQueue register() { - super.register(); - return this; - } + @Override + public CommandQueue register() { + super.register(); + return this; + } + + /** + * Returns the device associated with this command queue. + * It can be used to query properties of the device that is used to execute + * the commands issued to this command queue. + * + * @return the associated device + */ + public Device getDevice() { + return device; + } - /** - * Returns the device associated with this command queue. - * It can be used to query properties of the device that is used to execute - * the commands issued to this command queue. - * @return the associated device - */ - public Device getDevice() { - return device; - } - /** * Issues all previously queued OpenCL commands in command_queue to the * device associated with command queue. Flush only guarantees that all @@ -83,5 +84,4 @@ public Device getDevice() { * processed and completed. Finish is also a synchronization point. */ public abstract void finish(); - } diff --git a/jme3-core/src/main/java/com/jme3/opencl/Context.java b/jme3-core/src/main/java/com/jme3/opencl/Context.java index 3b48c29d93..e25f893e24 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Context.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Context.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,6 +63,7 @@ *
    • Created buffers and images shared with OpenGL vertex buffers, textures and renderbuffers
    • *
    • Create program objects from source code and source files
    • *
    + * * @author shaman */ public abstract class Context extends AbstractOpenCLObject { @@ -72,11 +73,11 @@ protected Context(ObjectReleaser releaser) { super(releaser); } - @Override - public Context register() { - super.register(); - return this; - } + @Override + public Context register() { + super.register(); + return this; + } /** * Returns all available devices for this context. @@ -87,6 +88,7 @@ public Context register() { * memory size and so on, are queried over the Device instances. *
    * The available devices were specified by a {@link PlatformChooser}. + * * @return a list of devices */ public abstract List getDevices(); @@ -94,29 +96,35 @@ public Context register() { /** * Alternative version of {@link #createQueue(com.jme3.opencl.Device) }, * just uses the first device returned by {@link #getDevices() }. + * * @return the command queue */ public CommandQueue createQueue() { return createQueue(getDevices().get(0)); } + /** * Creates a command queue sending commands to the specified device. * The device must be an entry of {@link #getDevices() }. + * * @param device the target device * @return the command queue */ - public abstract CommandQueue createQueue(Device device); + public abstract CommandQueue createQueue(Device device); /** * Allocates a new buffer of the specific size and access type on the device. + * * @param size the size of the buffer in bytes * @param access the allowed access of this buffer from kernel code * @return the new buffer */ public abstract Buffer createBuffer(long size, MemoryAccess access); + /** * Alternative version of {@link #createBuffer(long, com.jme3.opencl.MemoryAccess) }, * creates a buffer with read and write access. + * * @param size the size of the buffer in bytes * @return the new buffer */ @@ -129,14 +137,17 @@ public Buffer createBuffer(long size) { * specified by a ByteBuffer can then be used directly by kernel code, * although the access might be slower than with native buffers * created by {@link #createBuffer(long, com.jme3.opencl.MemoryAccess) }. + * * @param data the host buffer to use * @param access the allowed access of this buffer from kernel code * @return the new buffer */ public abstract Buffer createBufferFromHost(ByteBuffer data, MemoryAccess access); + /** * Alternative version of {@link #createBufferFromHost(java.nio.ByteBuffer, com.jme3.opencl.MemoryAccess) }, * creates a buffer with read and write access. + * * @param data the host buffer to use * @return the new buffer */ @@ -152,14 +163,15 @@ public Buffer createBufferFromHost(ByteBuffer data) { * with row and slice pitches. This buffer is then used to store the image. * If no ByteBuffer is specified, a new buffer is allocated (this is the * normal behaviour). + * * @param access the allowed access of this image from kernel code * @param format the image format * @param descr the image descriptor * @return the new image object */ public abstract Image createImage(MemoryAccess access, ImageFormat format, ImageDescriptor descr); - //TODO: add simplified methods for 1D, 2D, 3D textures - + //TODO: add simplified methods for 1D, 2D, 3D textures + /** * Queries all supported image formats for a specified memory access and * image type. @@ -168,16 +180,17 @@ public Buffer createBufferFromHost(ByteBuffer data) { * where {@code ImageChannelType} or {@code ImageChannelOrder} are {@code null} * (or both). This is the case when the device supports new formats that * are not included in this wrapper yet. + * * @param access the memory access type * @param type the image type (1D, 2D, 3D, ...) * @return an array of all supported image formats */ public abstract ImageFormat[] querySupportedFormats(MemoryAccess access, ImageType type); - - //Interop + + //Interop /** - * Creates a shared buffer from a VertexBuffer. - * The returned buffer and the vertex buffer operate on the same memory, + * Creates a shared buffer from a VertexBuffer. + * The returned buffer and the vertex buffer operate on the same memory, * changes in one view are visible in the other view. * This can be used to modify meshes directly from OpenCL (e.g. for particle systems). *
    @@ -188,6 +201,7 @@ public Buffer createBufferFromHost(ByteBuffer data) { * by {@link Buffer#acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * and after modifying it, released by {@link Buffer#releaseBufferForSharingAsync(com.jme3.opencl.CommandQueue) }. * This is needed so that OpenGL and OpenCL operations do not interfere with each other. + * * @param vb the vertex buffer to share * @param access the memory access for the kernel * @return the new buffer @@ -199,7 +213,7 @@ public Buffer createBufferFromHost(ByteBuffer data) { * The returned image shares the same memory with the jME3-image, changes * in one view are visible in the other view. * This can be used to modify textures and images directly from OpenCL - * (e.g. for post processing effects and other texture effects). + * (e.g. for post-processing effects and other texture effects). *
    * Note: The image must already been uploaded to the GPU, * i.e. it must be used at least once for drawing. @@ -208,7 +222,7 @@ public Buffer createBufferFromHost(ByteBuffer data) { * by {@link Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } * and after modifying it, released by {@link Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - * + * * @param image the jME3 image object * @param textureType the texture type (1D, 2D, 3D), since this is not stored in the image * @param miplevel the mipmap level that should be shared @@ -216,12 +230,13 @@ public Buffer createBufferFromHost(ByteBuffer data) { * @return the OpenCL image */ public abstract Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int miplevel, MemoryAccess access); + /** * Creates a shared image object from a jME3 texture. * The returned image shares the same memory with the jME3 texture, changes * in one view are visible in the other view. * This can be used to modify textures and images directly from OpenCL - * (e.g. for post processing effects and other texture effects). + * (e.g. for post-processing effects and other texture effects). *
    * Note: The image must already been uploaded to the GPU, * i.e. it must be used at least once for drawing. @@ -233,7 +248,7 @@ public Buffer createBufferFromHost(ByteBuffer data) { *

    * This method is equivalent to calling * {@code bindImage(texture.getImage(), texture.getType(), miplevel, access)}. - * + * * @param texture the jME3 texture * @param miplevel the mipmap level that should be shared * @param access the allowed memory access for kernels @@ -242,9 +257,11 @@ public Buffer createBufferFromHost(ByteBuffer data) { public Image bindImage(Texture texture, int miplevel, MemoryAccess access) { return bindImage(texture.getImage(), texture.getType(), miplevel, access); } + /** * Alternative version to {@link #bindImage(com.jme3.texture.Texture, int, com.jme3.opencl.MemoryAccess) }, - * uses {@code miplevel=0}. + * uses {@code miplevel=0}. + * * @param texture the jME3 texture * @param access the allowed memory access for kernels * @return the OpenCL image @@ -252,12 +269,13 @@ public Image bindImage(Texture texture, int miplevel, MemoryAccess access) { public Image bindImage(Texture texture, MemoryAccess access) { return bindImage(texture, 0, access); } + /** * Creates a shared image object from a jME3 render buffer. * The returned image shares the same memory with the jME3 render buffer, changes * in one view are visible in the other view. *
    - * This can be used as an alternative to post processing effects + * This can be used as an alternative to post-processing effects * (e.g. reduce sum operations, needed e.g. for tone mapping). *
    * Note: The renderbuffer must already been uploaded to the GPU, @@ -267,9 +285,9 @@ public Image bindImage(Texture texture, MemoryAccess access) { * by {@link Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } * and after modifying it, released by {@link Image#releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } * This is needed so that OpenGL and OpenCL operations do not interfere with each other. - * - * @param buffer - * @param access + * + * @param buffer the buffer to bind + * @param access the kernel access permissions * @return an image */ public Image bindRenderBuffer(FrameBuffer.RenderBuffer buffer, MemoryAccess access) { @@ -279,22 +297,24 @@ public Image bindRenderBuffer(FrameBuffer.RenderBuffer buffer, MemoryAccess acce return bindImage(buffer.getTexture(), access); } } + protected abstract Image bindPureRenderBuffer(FrameBuffer.RenderBuffer buffer, MemoryAccess access); /** * Creates a program object from the provided source code. * The program still needs to be compiled using {@link Program#build() }. - * + * * @param sourceCode the source code * @return the program object */ public abstract Program createProgramFromSourceCode(String sourceCode); - + /** * Resolves dependencies (using {@code #include } in the source code) * and delegates the combined source code to * {@link #createProgramFromSourceCode(java.lang.String) }. * Important: only absolute paths are allowed. + * * @param sourceCode the original source code * @param assetManager the asset manager to load the files * @return the created program object @@ -310,6 +330,7 @@ public Program createProgramFromSourceCodeWithDependencies(String sourceCode, As } return createProgramFromSourceCode(builder.toString()); } + private void buildSourcesRec(BufferedReader reader, StringBuilder builder, AssetManager assetManager) throws IOException { String ln; while ((ln = reader.readLine()) != null) { @@ -319,11 +340,11 @@ private void buildSourcesRec(BufferedReader reader, StringBuilder builder, Asset ln = ln.substring(1); } if (ln.endsWith("\"")) { - ln = ln.substring(0, ln.length()-1); + ln = ln.substring(0, ln.length() - 1); } AssetInfo info = assetManager.locateAsset(new AssetKey(ln)); if (info == null) { - throw new AssetNotFoundException("Unable to load source file \""+ln+"\""); + throw new AssetNotFoundException("Unable to load source file \"" + ln + "\""); } try (BufferedReader r = new BufferedReader(new InputStreamReader(info.openStream()))) { builder.append("//-- begin import ").append(ln).append(" --\n"); @@ -335,10 +356,10 @@ private void buildSourcesRec(BufferedReader reader, StringBuilder builder, Asset } } } - + /** * Creates a program object from the provided source code and files. - * The source code is made up from the specified include string first, + * The source code is made up from the specified include string first, * then all files specified by the resource array (array of asset paths) * are loaded by the provided asset manager and appended to the source code. *

    @@ -348,10 +369,10 @@ private void buildSourcesRec(BufferedReader reader, StringBuilder builder, Asset *

  • Some common OpenCL files used as libraries (Convention: file names end with {@code .clh}
  • *
  • One main OpenCL file containing the actual kernels (Convention: file name ends with {@code .cl})
  • * - * + * * After the files were combined, additional include statements are resolved * by {@link #createProgramFromSourceCodeWithDependencies(java.lang.String, com.jme3.asset.AssetManager) }. - * + * * @param assetManager the asset manager used to load the files * @param include an additional include string * @param resources an array of asset paths pointing to OpenCL source files @@ -364,7 +385,7 @@ public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager /** * Creates a program object from the provided source code and files. - * The source code is made up from the specified include string first, + * The source code is made up from the specified include string first, * then all files specified by the resource array (array of asset paths) * are loaded by the provided asset manager and appended to the source code. *

    @@ -374,10 +395,10 @@ public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager *

  • Some common OpenCL files used as libraries (Convention: file names end with {@code .clh}
  • *
  • One main OpenCL file containing the actual kernels (Convention: file name ends with {@code .cl})
  • * - * + * * After the files were combined, additional include statements are resolved * by {@link #createProgramFromSourceCodeWithDependencies(java.lang.String, com.jme3.asset.AssetManager) }. - * + * * @param assetManager the asset manager used to load the files * @param include an additional include string * @param resources an array of asset paths pointing to OpenCL source files @@ -390,7 +411,7 @@ public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager for (String res : resources) { AssetInfo info = assetManager.locateAsset(new AssetKey(res)); if (info == null) { - throw new AssetNotFoundException("Unable to load source file \""+res+"\""); + throw new AssetNotFoundException("Unable to load source file \"" + res + "\""); } try (BufferedReader reader = new BufferedReader(new InputStreamReader(info.openStream()))) { while (true) { @@ -401,7 +422,7 @@ public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager str.append(line).append('\n'); } } catch (IOException ex) { - LOG.log(Level.WARNING, "unable to load source file '"+res+"'", ex); + LOG.log(Level.WARNING, "unable to load source file '" + res + "'", ex); } } return createProgramFromSourceCodeWithDependencies(str.toString(), assetManager); @@ -410,6 +431,10 @@ public Program createProgramFromSourceFilesWithInclude(AssetManager assetManager /** * Alternative version of {@link #createProgramFromSourceFilesWithInclude(com.jme3.asset.AssetManager, java.lang.String, java.lang.String...) } * with an empty include string + * + * @param assetManager for loading assets + * @param resources asset paths pointing to OpenCL source files + * @return a new instance * @throws AssetNotFoundException if a file could not be loaded */ public Program createProgramFromSourceFiles(AssetManager assetManager, String... resources) { @@ -419,12 +444,16 @@ public Program createProgramFromSourceFiles(AssetManager assetManager, String... /** * Alternative version of {@link #createProgramFromSourceFilesWithInclude(com.jme3.asset.AssetManager, java.lang.String, java.util.List) } * with an empty include string + * + * @param assetManager for loading assets + * @param resources a list of asset paths pointing to OpenCL source files + * @return a new instance * @throws AssetNotFoundException if a file could not be loaded */ public Program createProgramFromSourceFiles(AssetManager assetManager, List resources) { return createProgramFromSourceFilesWithInclude(assetManager, "", resources); } - + /** * Creates a program from the specified binaries. * The binaries are created by {@link Program#getBinary(com.jme3.opencl.Device) }. @@ -432,20 +461,19 @@ public Program createProgramFromSourceFiles(AssetManager assetManager, ListImportant:The device passed to {@code Program.getBinary(..)}, * this method and {@code Program#build(..)} must be the same. - * + * * The binaries are used to build a program cache across multiple launches * of the application. The programs build much faster from binaries than * from sources. - * + * * @param binaries the binaries * @param device the device to use * @return the new program */ public abstract Program createProgramFromBinary(ByteBuffer binaries, Device device); - @Override - public String toString() { - return "Context (" + getDevices() + ')'; - } - + @Override + public String toString() { + return "Context (" + getDevices() + ')'; + } } diff --git a/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java b/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java index 8f8a53505a..3f3cb2596f 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java +++ b/jme3-core/src/main/java/com/jme3/opencl/DefaultPlatformChooser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,14 +46,14 @@ public class DefaultPlatformChooser implements PlatformChooser { @Override public List chooseDevices(List platforms) { - ArrayList result = new ArrayList(); + ArrayList result = new ArrayList<>(); for (Platform p : platforms) { if (!p.hasOpenGLInterop()) { continue; //must support interop } for (Device d : p.getDevices()) { if (d.hasOpenGLInterop() && d.getDeviceType()==Device.DeviceType.GPU) { - result.add(d); //GPU prefered + result.add(d); //GPU preferred } } if (!result.isEmpty()) { diff --git a/jme3-core/src/main/java/com/jme3/opencl/Device.java b/jme3-core/src/main/java/com/jme3/opencl/Device.java index b9f083c572..b78d555bf9 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Device.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Device.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,185 +41,217 @@ * queue ({@link Context#createQueue(com.jme3.opencl.Device) }). *

    * This class is used to query the capabilities of the underlying device. - * + * * @author shaman */ public interface Device { - /** * @return the platform associated with this device */ Platform getPlatform(); - /** * The device type */ - public static enum DeviceType { - DEFAULT, - CPU, - GPU, - ACCELEARTOR, - ALL - } + public static enum DeviceType { + DEFAULT, + CPU, + GPU, + ACCELEARTOR, + ALL + } + /** * @return queries the device type */ - DeviceType getDeviceType(); + DeviceType getDeviceType(); + /** * @return the vendor id */ - int getVendorId(); + int getVendorId(); + /** * checks if this device is available at all, must always be tested + * * @return checks if this device is available at all, must always be tested */ - boolean isAvailable(); - + boolean isAvailable(); + /** * @return if this device has a compiler for kernel code */ - boolean hasCompiler(); + boolean hasCompiler(); + /** * @return supports double precision floats (64 bit) */ - boolean hasDouble(); + boolean hasDouble(); + /** * @return supports half precision floats (16 bit) */ - boolean hasHalfFloat(); + boolean hasHalfFloat(); + /** * @return supports error correction for every access to global or constant memory */ - boolean hasErrorCorrectingMemory(); + boolean hasErrorCorrectingMemory(); + /** * @return supports unified virtual memory (OpenCL 2.0) */ - boolean hasUnifiedMemory(); + boolean hasUnifiedMemory(); + /** * @return supports images */ - boolean hasImageSupport(); + boolean hasImageSupport(); + /** * @return supports writes to 3d images (this is an extension) */ boolean hasWritableImage3D(); + /** * @return supports sharing with OpenGL */ boolean hasOpenGLInterop(); + /** - * Explictly tests for the availability of the specified extension + * Explicitly tests for the availability of the specified extension + * * @param extension the name of the extension * @return {@code true} iff this extension is supported */ - boolean hasExtension(String extension); + boolean hasExtension(String extension); + /** * Lists all available extensions + * * @return all available extensions */ - Collection getExtensions(); - + Collection getExtensions(); + /** * Returns the number of parallel compute units on * the OpenCL device. A work-group * executes on a single compute unit. The * minimum value is 1. * @return the number of parallel compute units - * @see #getMaximumWorkItemDimensions() - * @see #getMaximumWorkItemSizes() + * @see #getMaximumWorkItemDimensions() + * @see #getMaximumWorkItemSizes() */ - int getComputeUnits(); + int getComputeUnits(); + /** * @return maximum clock frequency of the device in MHz */ - int getClockFrequency(); + int getClockFrequency(); + /** * Returns the default compute device address space * size specified as an unsigned integer value - * in bits. Currently supported values are 32 - * or 64 bits. + * in bits. The values currently supported are 32 + * and 64. + * * @return the size of an address */ - int getAddressBits(); + int getAddressBits(); + /** * @return {@code true} if this device is little endian */ - boolean isLittleEndian(); - + boolean isLittleEndian(); + /** * The maximum dimension that specify the local and global work item ids. * You can always assume to be this at least 3. * Therefore, the ids are always three integers x,y,z. + * * @return the maximum dimension of work item ids */ - long getMaximumWorkItemDimensions(); + long getMaximumWorkItemDimensions(); + /** * Maximum number of work-items that can be specified in each dimension of the * work-group to {@link Kernel#Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...)}. * The array has a length of at least 3. + * * @return the maximum size of the work group in each dimension */ - long[] getMaximumWorkItemSizes(); + long[] getMaximumWorkItemSizes(); + /** * Maximum number of work-items in a * work-group executing a kernel on a single * compute unit, using the data parallel * execution model. + * * @return maximum number of work-items in a work-group */ - long getMaxiumWorkItemsPerGroup(); - + long getMaxiumWorkItemsPerGroup(); + /** * @return the maximum number of samples that can be used in a kernel */ - int getMaximumSamplers(); + int getMaximumSamplers(); + /** * @return the maximum number of images that can be used for reading in a kernel */ - int getMaximumReadImages(); + int getMaximumReadImages(); + /** * @return the maximum number of images that can be used for writing in a kernel */ - int getMaximumWriteImages(); + int getMaximumWriteImages(); + /** * Queries the maximal size of a 2D image + * * @return an array of length 2 with the maximal size of a 2D image */ - long[] getMaximumImage2DSize(); + long[] getMaximumImage2DSize(); + /** * Queries the maximal size of a 3D image + * * @return an array of length 3 with the maximal size of a 3D image */ - long[] getMaximumImage3DSize(); - + long[] getMaximumImage3DSize(); + /** * @return the maximal size of a memory object (buffer and image) in bytes */ long getMaximumAllocationSize(); + /** * @return the total available global memory in bytes */ long getGlobalMemorySize(); + /** * @return the total available local memory in bytes */ long getLocalMemorySize(); + /** * Returns the maximal size of a constant buffer. *
    * Constant buffers are normal buffer objects, but passed to the kernel * with the special declaration {@code __constant BUFFER_TYPE* BUFFER_NAME}. * Because they have a special caching, their size is usually very limited. - * + * * @return the maximal size of a constant buffer */ long getMaximumConstantBufferSize(); + /** * @return the maximal number of constant buffer arguments in a kernel call */ int getMaximumConstantArguments(); - - //TODO: cache, prefered sizes properties + + //TODO: cache, preferred sizes properties /** * OpenCL profile string. Returns the profile name supported by the device. * The profile name returned can be one of the following strings:
    @@ -230,7 +262,8 @@ public static enum DeviceType { * * @return the profile string */ - String getProfile(); + String getProfile(); + /** * OpenCL version string. Returns the OpenCL version supported by the * device. This version string has the following format: OpenCL space @@ -240,20 +273,24 @@ public static enum DeviceType { * * @return the version string */ - String getVersion(); + String getVersion(); + /** * Extracts the major version from the version string + * * @return the major version - * @see #getVersion() + * @see #getVersion() */ - int getVersionMajor(); + int getVersionMajor(); + /** * Extracts the minor version from the version string + * * @return the minor version * @see #getVersion() } */ - int getVersionMinor(); - + int getVersionMinor(); + /** * OpenCL C version string. Returns the highest OpenCL C version supported * by the compiler for this device that is not of type @@ -268,44 +305,53 @@ public static enum DeviceType { * * @return the compiler version */ - String getCompilerVersion(); + String getCompilerVersion(); + /** * Extracts the major version from the compiler version + * * @return the major compiler version - * @see #getCompilerVersion() + * @see #getCompilerVersion() */ - int getCompilerVersionMajor(); + int getCompilerVersionMajor(); + /** * Extracts the minor version from the compiler version + * * @return the minor compiler version - * @see #getCompilerVersion() + * @see #getCompilerVersion() */ - int getCompilerVersionMinor(); - /** + int getCompilerVersionMinor(); + + /** * @return the OpenCL software driver version string in the form * major_number.minor_number */ - String getDriverVersion(); + String getDriverVersion(); + /** * Extracts the major version from the driver version + * * @return the major driver version - * @see #getDriverVersion() + * @see #getDriverVersion() */ - int getDriverVersionMajor(); + int getDriverVersionMajor(); + /** * Extracts the minor version from the driver version + * * @return the minor driver version - * @see #getDriverVersion() + * @see #getDriverVersion() */ - int getDriverVersionMinor(); - + int getDriverVersionMinor(); + /** * @return the device name */ - String getName(); + String getName(); + /** * @return the vendor */ - String getVendor(); - + String getVendor(); } diff --git a/jme3-core/src/main/java/com/jme3/opencl/Event.java b/jme3-core/src/main/java/com/jme3/opencl/Event.java index 66aa12a5ca..32d78714aa 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Event.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Event.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,8 +34,9 @@ /** * Wrapper for an OpenCL Event object. * Events are returned from kernel launches and all asynchronous operations. - * They allow to test if the action has completed and to block until the operation + * They allow us to test whether an action has completed and block until the operation * is done. + * * @author shaman */ public abstract class Event extends AbstractOpenCLObject { @@ -44,22 +45,23 @@ protected Event(ObjectReleaser releaser) { super(releaser); } - @Override - public Event register() { - super.register(); - return this; - } - + @Override + public Event register() { + super.register(); + return this; + } + /** * Waits until the action has finished (blocking). * This automatically releases the event. */ - public abstract void waitForFinished(); - + public abstract void waitForFinished(); + /** * Tests if the action is completed. * If the action is completed, the event is released. + * * @return {@code true} if the action is completed */ - public abstract boolean isCompleted(); + public abstract boolean isCompleted(); } diff --git a/jme3-core/src/main/java/com/jme3/opencl/Image.java b/jme3-core/src/main/java/com/jme3/opencl/Image.java index fd024000c3..5b07d0f376 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Image.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Image.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,16 +41,16 @@ * An image object is similar to a {@link Buffer}, but with a specific element * format and buffer structure. *
    - * The image is specified by the {@link ImageDescriptor}, specifying - * the extend and dimension of the image, and {@link ImageFormat}, specifying + * The image is specified by the {@link ImageDescriptor}, specifying + * the type and dimensions of the image, and {@link ImageFormat}, specifying * the type of each pixel. *
    - * An image is created from scratch using + * An image is created from scratch using * {@link Context#createImage(com.jme3.opencl.MemoryAccess, com.jme3.opencl.Image.ImageFormat, com.jme3.opencl.Image.ImageDescriptor) } * or from OpenGL by * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } * (and alternative versions). - * + * *

    * Most methods take long arrays as input: {@code long[] origin} and {@code long[] region}. * Both are arrays of length 3. @@ -75,7 +75,6 @@ * @author shaman */ public abstract class Image extends AbstractOpenCLObject { - /** * {@code ImageChannelType} describes the size of the channel data type. */ @@ -96,10 +95,10 @@ public static enum ImageChannelType { HALF_FLOAT, FLOAT } - + /** * {@code ImageChannelOrder} specifies the number of channels and the channel layout i.e. the -memory layout in which channels are stored in the image. + * memory layout in which channels are stored in the image. */ public static enum ImageChannelOrder { R, Rx, A, @@ -112,7 +111,7 @@ public static enum ImageChannelOrder { } /** - * Describes the image format, consisting of + * Describes the image format, consisting of * {@link ImageChannelOrder} and {@link ImageChannelType}. */ public static class ImageFormat { //Struct @@ -157,7 +156,6 @@ public boolean equals(Object obj) { } return true; } - } /** @@ -193,13 +191,14 @@ public static class ImageDescriptor { //Struct /* public int numMipLevels; //They must always be set to zero public int numSamples; - */ + */ public ImageDescriptor() { } /** - * Used to specify an image with the provided ByteBuffer as soruce + * Used to specify an image with the provided ByteBuffer as source + * * @param type the image type * @param width the width * @param height the height, unused for image types {@code ImageType.IMAGE_1D*} @@ -219,9 +218,11 @@ public ImageDescriptor(ImageType type, long width, long height, long depth, long this.slicePitch = slicePitch; this.hostPtr = hostPtr; } + /** - * Specifies an image without a host buffer, a new chunk of memory + * Specifies an image without a host buffer, a new chunk of memory * will be allocated. + * * @param type the image type * @param width the width * @param height the height, unused for image types {@code ImageType.IMAGE_1D*} @@ -243,60 +244,68 @@ public ImageDescriptor(ImageType type, long width, long height, long depth, long public String toString() { return "ImageDescriptor{" + "type=" + type + ", width=" + width + ", height=" + height + ", depth=" + depth + ", arraySize=" + arraySize + ", rowPitch=" + rowPitch + ", slicePitch=" + slicePitch + '}'; } - } protected Image(ObjectReleaser releaser) { super(releaser); } - - @Override - public Image register() { - super.register(); - return this; - } - + + @Override + public Image register() { + super.register(); + return this; + } + /** * @return the width of the image */ public abstract long getWidth(); + /** * @return the height of the image */ public abstract long getHeight(); + /** * @return the depth of the image */ public abstract long getDepth(); + /** * @return the row pitch when the image was created from a host buffer */ public abstract long getRowPitch(); + /** * @return the slice pitch when the image was created from a host buffer */ public abstract long getSlicePitch(); + /** * @return the number of elements in the image array * @see ImageType#IMAGE_1D_ARRAY * @see ImageType#IMAGE_2D_ARRAY */ public abstract long getArraySize(); + /** * @return the image format */ public abstract ImageFormat getImageFormat(); + /** * @return the image type */ public abstract ImageType getImageType(); + /** * @return the number of bytes per pixel */ public abstract int getElementSize(); - + /** * Performs a blocking read of the image into the specified byte buffer. + * * @param queue the command queue * @param dest the target byte buffer * @param origin the image origin location, see class description for the format @@ -307,8 +316,10 @@ public Image register() { * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} */ public abstract void readImage(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch); + /** * Performs an async/non-blocking read of the image into the specified byte buffer. + * * @param queue the command queue * @param dest the target byte buffer * @param origin the image origin location, see class description for the format @@ -320,9 +331,10 @@ public Image register() { * @return the event object indicating the status of the operation */ public abstract Event readImageAsync(CommandQueue queue, ByteBuffer dest, long[] origin, long[] region, long rowPitch, long slicePitch); - + /** * Performs a blocking write from the specified byte buffer into the image. + * * @param queue the command queue * @param src the source buffer * @param origin the image origin location, see class description for the format @@ -333,8 +345,10 @@ public Image register() { * If set to 0 for 3D images, the slice pitch is calculated as {@code rowPitch * height} */ public abstract void writeImage(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch); + /** * Performs an async/non-blocking write from the specified byte buffer into the image. + * * @param queue the command queue * @param src the source buffer * @param origin the image origin location, see class description for the format @@ -346,10 +360,11 @@ public Image register() { * @return the event object indicating the status of the operation */ public abstract Event writeImageAsync(CommandQueue queue, ByteBuffer src, long[] origin, long[] region, long rowPitch, long slicePitch); - + /** * Performs a blocking copy operation from one image to another. * Important: Both images must have the same format! + * * @param queue the command queue * @param dest the target image * @param srcOrigin the source image origin, see class description for the format @@ -357,9 +372,11 @@ public Image register() { * @param region the copied region, see class description for the format */ public abstract void copyTo(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region); + /** * Performs an async/non-blocking copy operation from one image to another. * Important: Both images must have the same format! + * * @param queue the command queue * @param dest the target image * @param srcOrigin the source image origin, see class description for the format @@ -368,20 +385,22 @@ public Image register() { * @return the event object indicating the status of the operation */ public abstract Event copyToAsync(CommandQueue queue, Image dest, long[] srcOrigin, long[] destOrigin, long[] region); - + /** * Maps the image into host memory. * The returned structure contains the mapped byte buffer and row and slice pitch. - * The event object is set to {@code null}, it is needed for the asnyc + * The event object is set to {@code null}, it is needed for the async * version {@link #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }. + * * @param queue the command queue * @param origin the image origin, see class description for the format * @param region the mapped region, see class description for the format * @param access the allowed memory access to the mapped memory * @return a structure describing the mapped memory - * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) + * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) */ public abstract ImageMapping map(CommandQueue queue, long[] origin, long[] region, MappingAccess access); + /** * Non-blocking version of {@link #map(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) }. * The returned structure contains the mapped byte buffer and row and slice pitch. @@ -391,16 +410,18 @@ public Image register() { * @param region the mapped region, see class description for the format * @param access the allowed memory access to the mapped memory * @return a structure describing the mapped memory - * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) + * @see #unmap(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image.ImageMapping) */ public abstract ImageMapping mapAsync(CommandQueue queue, long[] origin, long[] region, MappingAccess access); + /** * Unmaps the mapped memory + * * @param queue the command queue * @param mapping the mapped memory */ public abstract void unmap(CommandQueue queue, ImageMapping mapping); - + /** * Describes a mapped region of the image */ @@ -421,7 +442,8 @@ public static class ImageMapping { public final long slicePitch; /** * The event object used to detect when the memory is available. - * @see #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) + * + * @see #mapAsync(com.jme3.opencl.CommandQueue, long[], long[], com.jme3.opencl.MappingAccess) */ public final Event event; @@ -431,19 +453,20 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch, Event eve this.slicePitch = slicePitch; this.event = event; } + public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { this.buffer = buffer; this.rowPitch = rowPitch; this.slicePitch = slicePitch; this.event = null; } - } - + /** * Fills the image with the specified color. * Does only work if the image channel is {@link ImageChannelType#FLOAT} * or {@link ImageChannelType#HALF_FLOAT}. + * * @param queue the command queue * @param origin the image origin, see class description for the format * @param region the size of the region, see class description for the format @@ -455,6 +478,7 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { * Fills the image with the specified color given as four integer variables. * Does not work if the image channel is {@link ImageChannelType#FLOAT} * or {@link ImageChannelType#HALF_FLOAT}. + * * @param queue the command queue * @param origin the image origin, see class description for the format * @param region the size of the region, see class description for the format @@ -462,11 +486,12 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { * @return an event object to detect for the completion */ public abstract Event fillAsync(CommandQueue queue, long[] origin, long[] region, int[] color); - + /** * Copies this image into the specified buffer, no format conversion is done. - * This is the dual function to + * This is the dual function to * {@link Buffer#copyToImageAsync(com.jme3.opencl.CommandQueue, com.jme3.opencl.Image, long, long[], long[]) }. + * * @param queue the command queue * @param dest the target buffer * @param srcOrigin the image origin, see class description for the format @@ -475,9 +500,9 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { * @return the event object to detect the completion of the operation */ public abstract Event copyToBufferAsync(CommandQueue queue, Buffer dest, long[] srcOrigin, long[] srcRegion, long destOffset); - + /** - * Aquires this image object for using. Only call this method if this image + * Acquires this image object for using. Only call this method if this image * represents a shared object from OpenGL, created with e.g. * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } * or variations. @@ -485,13 +510,14 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { * done, the image must be released by calling * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } * so that OpenGL can use the image/texture/renderbuffer again. + * * @param queue the command queue * @return the event object */ public abstract Event acquireImageForSharingAsync(CommandQueue queue); - + /** - * Aquires this image object for using. Only call this method if this image + * Acquires this image object for using. Only call this method if this image * represents a shared object from OpenGL, created with e.g. * {@link Context#bindImage(com.jme3.texture.Image, com.jme3.texture.Texture.Type, int, com.jme3.opencl.MemoryAccess) } * or variations. @@ -499,37 +525,39 @@ public ImageMapping(ByteBuffer buffer, long rowPitch, long slicePitch) { * done, the image must be released by calling * {@link #releaseImageForSharingAsync(com.jme3.opencl.CommandQueue) } * so that OpenGL can use the image/texture/renderbuffer again. - * + * * The generated event object is directly released. * This brings a performance improvement when the resource is e.g. directly * used by a kernel afterwards on the same queue (this implicitly waits for - * this action). If you need the event, use + * this action). If you need the event, use * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }. - * + * * @param queue the command queue */ public void acquireImageForSharingNoEvent(CommandQueue queue) { //Default implementation, overwrite for performance acquireImageForSharingAsync(queue).release(); } - + /** * Releases a shared image object. * Call this method after the image object was acquired by * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } * to hand the control back to OpenGL. + * * @param queue the command queue * @return the event object */ public abstract Event releaseImageForSharingAsync(CommandQueue queue); - + /** * Releases a shared image object. * Call this method after the image object was acquired by * {@link #acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) } * to hand the control back to OpenGL. - * The generated event object is directly released, resulting in + * The generated event object is directly released, resulting in * performance improvements. + * * @param queue the command queue */ public void releaseImageForSharingNoEvent(CommandQueue queue) { @@ -537,25 +565,24 @@ public void releaseImageForSharingNoEvent(CommandQueue queue) { releaseImageForSharingAsync(queue).release(); } - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append("Image ("); - ImageType t = getImageType(); - str.append(t); - str.append(", w=").append(getWidth()); - if (t == ImageType.IMAGE_2D || t == ImageType.IMAGE_3D) { - str.append(", h=").append(getHeight()); - } - if (t == ImageType.IMAGE_3D) { - str.append(", d=").append(getDepth()); - } - if (t == ImageType.IMAGE_1D_ARRAY || t == ImageType.IMAGE_2D_ARRAY) { - str.append(", arrays=").append(getArraySize()); - } - str.append(", ").append(getImageFormat()); - str.append(')'); - return str.toString(); - } - + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append("Image ("); + ImageType t = getImageType(); + str.append(t); + str.append(", w=").append(getWidth()); + if (t == ImageType.IMAGE_2D || t == ImageType.IMAGE_3D) { + str.append(", h=").append(getHeight()); + } + if (t == ImageType.IMAGE_3D) { + str.append(", d=").append(getDepth()); + } + if (t == ImageType.IMAGE_1D_ARRAY || t == ImageType.IMAGE_2D_ARRAY) { + str.append(", arrays=").append(getArraySize()); + } + str.append(", ").append(getImageFormat()); + str.append(')'); + return str.toString(); + } } diff --git a/jme3-core/src/main/java/com/jme3/opencl/Kernel.java b/jme3-core/src/main/java/com/jme3/opencl/Kernel.java index 245657c638..a1882e7776 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Kernel.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Kernel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,10 +40,10 @@ * Wrapper for an OpenCL kernel, a piece of executable code on the GPU. *

    * Terminology:
    - * A Kernel is executed in parallel. In total number of parallel threads, + * A Kernel is executed in parallel. In total number of parallel threads, * called work items, are specified by the global work size (of type - * {@link WorkSize}. These threads are organized in a 1D, 2D or 3D grid - * (of coarse, this is only a logical view). Inside each kernel, + * {@link WorkSize}). These threads are organized in a 1-D, 2-D or 3-D grid + * (of course, this is only a logical view). Inside each kernel, * the id of each thread (i.e. the index inside this grid) can be requested * by {@code get_global_id(dimension)} with {@code dimension=0,1,2}. *
    @@ -54,7 +54,7 @@ * The maximal size of it can be queried by {@link Device#getMaxiumWorkItemsPerGroup() }. * Again, the threads inside the work group can be organized in a 1D, 2D or 3D * grid, but this is also just a logical view (specifying how the threads are - * indexed). + * indexed). * The work group is important for another concept: shared memory * Unlike the normal global or constant memory (passing a {@link Buffer} object * as argument), shared memory can't be set from outside. Shared memory is @@ -64,22 +64,22 @@ * {@link LocalMem} or {@link LocalMemPerElement} as argument.
    * Due to heavy register usage or other reasons, a kernel might not be able * to utilize a whole work group. Therefore, the actual number of threads - * that can be executed in a work group can be queried by - * {@link #getMaxWorkGroupSize(com.jme3.opencl.Device) }, which might differ from the + * that can be executed in a work group can be queried by + * {@link #getMaxWorkGroupSize(com.jme3.opencl.Device) }, which might differ from the * value returned from the Device. - * + * *

    * There are two ways to launch a kernel:
    - * First, arguments and the work group sizes can be set in advance + * First, arguments and the work group sizes can be set in advance * ({@code setArg(index, ...)}, {@code setGlobalWorkSize(...)} and {@code setWorkGroupSize(...)}. * Then a kernel is launched by {@link #Run(com.jme3.opencl.CommandQueue) }.
    * Second, two convenient functions are provided that set the arguments * and work sizes in one call: * {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) } * and {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. - * + * * @author shaman - * @see Program#createKernel(java.lang.String) + * @see Program#createKernel(java.lang.String) */ public abstract class Kernel extends AbstractOpenCLObject { /** @@ -97,12 +97,12 @@ protected Kernel(ObjectReleaser releaser) { this.workGroupSize = new WorkSize(0); } - @Override - public Kernel register() { - super.register(); - return this; - } - + @Override + public Kernel register() { + super.register(); + return this; + } + /** * @return the name of the kernel as defined in the program source code */ @@ -122,6 +122,7 @@ public WorkSize getGlobalWorkSize() { /** * Sets the global work size. + * * @param ws the work size to set */ public void setGlobalWorkSize(WorkSize ws) { @@ -130,6 +131,7 @@ public void setGlobalWorkSize(WorkSize ws) { /** * Sets the global work size to a 1D grid + * * @param size the size in 1D */ public void setGlobalWorkSize(int size) { @@ -138,6 +140,7 @@ public void setGlobalWorkSize(int size) { /** * Sets the global work size to be a 2D grid + * * @param width the width * @param height the height */ @@ -147,6 +150,7 @@ public void setGlobalWorkSize(int width, int height) { /** * Sets the global work size to be a 3D grid + * * @param width the width * @param height the height * @param depth the depth @@ -164,6 +168,7 @@ public WorkSize getWorkGroupSize() { /** * Sets the work group size + * * @param ws the work group size to set */ public void setWorkGroupSize(WorkSize ws) { @@ -172,6 +177,7 @@ public void setWorkGroupSize(WorkSize ws) { /** * Sets the work group size to be a 1D grid + * * @param size the size to set */ public void setWorkGroupSize(int size) { @@ -180,6 +186,7 @@ public void setWorkGroupSize(int size) { /** * Sets the work group size to be a 2D grid + * * @param width the width * @param height the height */ @@ -189,6 +196,7 @@ public void setWorkGroupSize(int width, int height) { /** * Sets the work group size to be a 3D grid + * * @param width the width * @param height the height * @param depth the depth @@ -196,7 +204,7 @@ public void setWorkGroupSize(int width, int height) { public void setWorkGroupSdize(int width, int height, int depth) { workGroupSize.set(3, width, height, depth); } - + /** * Tells the driver to figure out the work group size on their own. * Use this if you do not rely on specific work group layouts, i.e. @@ -207,10 +215,11 @@ public void setWorkGroupSdize(int width, int height, int depth) { public void setWorkGroupSizeToNull() { workGroupSize.set(1, 0, 0, 0); } - + /** * Returns the maximal work group size when this kernel is executed on * the specified device + * * @param device the device * @return the maximal work group size */ @@ -221,7 +230,7 @@ public void setWorkGroupSizeToNull() { public abstract void setArg(int index, LocalMem t); public abstract void setArg(int index, Buffer t); - + public abstract void setArg(int index, Image i); public abstract void setArg(int index, byte b); @@ -237,20 +246,20 @@ public void setWorkGroupSizeToNull() { public abstract void setArg(int index, double d); public abstract void setArg(int index, Vector2f v); - + public abstract void setArg(int index, Vector4f v); public abstract void setArg(int index, Quaternion q); - + public abstract void setArg(int index, Matrix4f mat); - + public void setArg(int index, Matrix3f mat) { TempVars vars = TempVars.get(); try { Matrix4f m = vars.tempMat4; m.zero(); - for (int i=0; i<3; ++i) { - for (int j=0; j<3; ++j) { + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { m.set(i, j, mat.get(i, j)); } } @@ -259,13 +268,14 @@ public void setArg(int index, Matrix3f mat) { vars.release(); } } - + /** * Raw version to set an argument. * {@code size} bytes of the provided byte buffer are copied to the kernel * argument. The size in bytes must match exactly the argument size * as defined in the kernel code. * Use this method to send custom structures to the kernel + * * @param index the index of the argument * @param buffer the raw buffer * @param size the size in bytes @@ -279,6 +289,7 @@ public void setArg(int index, Matrix3f mat) { * long, float, double, Vector2f, Vector4f, Quaternion, Matrix3f, Matrix4f}. *
    * Note: Matrix3f and Matrix4f will be mapped to a {@code float16} (row major). + * * @param index the index of the argument, from 0 to {@link #getArgCount()}-1 * @param arg the argument * @throws IllegalArgumentException if the argument type is not one of the listed ones @@ -331,24 +342,26 @@ private void setArgs(Object... args) { * If the returned event object is not needed and would otherwise be * released immediately, {@link #RunNoEvent(com.jme3.opencl.CommandQueue) } * might bring a better performance. + * * @param queue the command queue * @return an event object indicating when the kernel is finished - * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setArg(int, java.lang.Object) + * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) + * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) + * @see #setArg(int, java.lang.Object) */ public abstract Event Run(CommandQueue queue); - + /** * Launches the kernel with the current global work size, work group size * and arguments without returning an event object. * The generated event is directly released. Therefore, the performance * is better, but there is no way to detect when the kernel execution * has finished. For this purpose, use {@link #Run(com.jme3.opencl.CommandQueue) }. + * * @param queue the command queue - * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) - * @see #setArg(int, java.lang.Object) + * @see #setGlobalWorkSize(com.jme3.opencl.Kernel.WorkSize) + * @see #setWorkGroupSize(com.jme3.opencl.Kernel.WorkSize) + * @see #setArg(int, java.lang.Object) */ public void RunNoEvent(CommandQueue queue) { //Default implementation, overwrite to not allocate the event object @@ -361,11 +374,12 @@ public void RunNoEvent(CommandQueue queue) { * size is automatically determined by the driver. * Each object in the argument array is sent to the kernel by * {@link #setArg(int, java.lang.Object) }. + * * @param queue the command queue * @param globalWorkSize the global work size * @param args the kernel arguments * @return an event object indicating when the kernel is finished - * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) + * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) */ public Event Run1(CommandQueue queue, WorkSize globalWorkSize, Object... args) { setGlobalWorkSize(globalWorkSize); @@ -373,7 +387,7 @@ public Event Run1(CommandQueue queue, WorkSize globalWorkSize, Object... args) { setArgs(args); return Run(queue); } - + /** * Sets the work sizes and arguments in one call and launches the kernel. * The global work size is set to the specified size. The work group @@ -382,12 +396,13 @@ public Event Run1(CommandQueue queue, WorkSize globalWorkSize, Object... args) { * {@link #setArg(int, java.lang.Object) }. * The generated event is directly released. Therefore, the performance * is better, but there is no way to detect when the kernel execution - * has finished. For this purpose, use + * has finished. For this purpose, use * {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. + * * @param queue the command queue * @param globalWorkSize the global work size * @param args the kernel arguments - * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) + * @see #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) */ public void Run1NoEvent(CommandQueue queue, WorkSize globalWorkSize, Object... args) { setGlobalWorkSize(globalWorkSize); @@ -398,6 +413,7 @@ public void Run1NoEvent(CommandQueue queue, WorkSize globalWorkSize, Object... a /** * Sets the work sizes and arguments in one call and launches the kernel. + * * @param queue the command queue * @param globalWorkSize the global work size * @param workGroupSize the work group size @@ -416,8 +432,9 @@ public Event Run2(CommandQueue queue, WorkSize globalWorkSize, * Sets the work sizes and arguments in one call and launches the kernel. * The generated event is directly released. Therefore, the performance * is better, but there is no way to detect when the kernel execution - * has finished. For this purpose, use + * has finished. For this purpose, use * {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. + * * @param queue the command queue * @param globalWorkSize the global work size * @param workGroupSize the work group size @@ -431,22 +448,22 @@ public void Run2NoEvent(CommandQueue queue, WorkSize globalWorkSize, RunNoEvent(queue); } - @Override - public String toString() { - return "Kernel (" + getName() + ")"; - } - + @Override + public String toString() { + return "Kernel (" + getName() + ")"; + } + /** - * A placeholder for kernel arguments representing local kernel memory. - * This defines the size of available shared memory of a {@code __shared} kernel + * A placeholder for kernel arguments representing local kernel memory. This + * defines the size of available shared memory of a {@code __shared} kernel * argument */ public static final class LocalMem { - private int size; /** * Creates a new LocalMem instance + * * @param size the size of the available shared memory in bytes */ public LocalMem(int size) { @@ -480,11 +497,11 @@ public boolean equals(Object obj) { return true; } - @Override - public String toString() { - return "LocalMem (" + size + "B)"; - } - + @Override + public String toString() { + return "LocalMem (" + size + "B)"; + } + } /** @@ -495,14 +512,14 @@ public String toString() { * the work group size has been specified. This is * ensured by {@link #Run2(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. * This argument can't be used when no work group size was defined explicitly - * (e.g. by {@link #setWorkGroupSizeToNull()} or {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }. + * (e.g. by {@link #setWorkGroupSizeToNull()} or {@link #Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...) }). */ public static final class LocalMemPerElement { - private int size; /** * Creates a new LocalMemPerElement instance + * * @param size the number of bytes available for each thread within * a work group */ @@ -537,15 +554,16 @@ public boolean equals(Object obj) { return true; } - @Override - public String toString() { - return "LocalMemPerElement (" + size + "B)"; - } - + @Override + public String toString() { + return "LocalMemPerElement (" + size + "B)"; + } + } /** * The work size (global and local) for executing a kernel + * * @author shaman */ public static final class WorkSize { @@ -555,6 +573,7 @@ public static final class WorkSize { /** * Creates a new work size object + * * @param dimension the dimension (1,2,3) * @param sizes the sizes in each dimension, the length must match the specified dimension */ @@ -572,6 +591,7 @@ public WorkSize() { /** * Creates a 1D work size of the specified extend + * * @param size the size */ public WorkSize(long size) { @@ -580,6 +600,7 @@ public WorkSize(long size) { /** * Creates a 2D work size of the specified extend + * * @param width the width * @param height the height */ @@ -589,6 +610,7 @@ public WorkSize(long width, long height) { /** * Creates a 3D work size of the specified extend. + * * @param width the width * @param height the height * @param depth the depth @@ -647,20 +669,18 @@ public boolean equals(Object obj) { return true; } - @Override - public String toString() { - StringBuilder str = new StringBuilder(); - str.append("WorkSize["); - for (int i=0; i0) { - str.append(", "); - } - str.append(sizes[i]); - } - str.append(']'); - return str.toString(); - } - + @Override + public String toString() { + StringBuilder str = new StringBuilder(); + str.append("WorkSize["); + for (int i = 0; i < dimension; ++i) { + if (i > 0) { + str.append(", "); + } + str.append(sizes[i]); + } + str.append(']'); + return str.toString(); + } } - } diff --git a/jme3-core/src/main/java/com/jme3/opencl/KernelCompilationException.java b/jme3-core/src/main/java/com/jme3/opencl/KernelCompilationException.java index 6203dd641e..e022c51707 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/KernelCompilationException.java +++ b/jme3-core/src/main/java/com/jme3/opencl/KernelCompilationException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,23 +36,23 @@ * when the compilation failed. * The error log returned by {@link #getLog() } contains detailed information * where the error occurred. + * * @author shaman */ public class KernelCompilationException extends OpenCLException { + private final String log; - private final String log; - - public KernelCompilationException(String msg, int errorCode, String log) { - super(msg, errorCode); - this.log = log; - } + public KernelCompilationException(String msg, int errorCode, String log) { + super(msg, errorCode); + this.log = log; + } /** * The output of the compiler + * * @return the output text */ - public String getLog() { - return log; - } - -} \ No newline at end of file + public String getLog() { + return log; + } +} diff --git a/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java b/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java index 121c6c8cba..bbf7d4df67 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java +++ b/jme3-core/src/main/java/com/jme3/opencl/MappingAccess.java @@ -41,18 +41,18 @@ public enum MappingAccess { /** * Only read access is allowed to the mapped memory. */ - MAP_READ_ONLY, + MAP_READ_ONLY, /** * Only write access is allowed to the mapped memory. */ - MAP_WRITE_ONLY, + MAP_WRITE_ONLY, /** * Both read and write access is allowed. */ - MAP_READ_WRITE, + MAP_READ_WRITE, /** * The old memory content is completely discarded and the buffer is filled * completely with new data. This might be faster than {@link #MAP_WRITE_ONLY} */ - MAP_WRITE_INVALIDATE + MAP_WRITE_INVALIDATE } diff --git a/jme3-core/src/main/java/com/jme3/opencl/MemoryAccess.java b/jme3-core/src/main/java/com/jme3/opencl/MemoryAccess.java index d5071665e6..13193b400d 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/MemoryAccess.java +++ b/jme3-core/src/main/java/com/jme3/opencl/MemoryAccess.java @@ -40,13 +40,13 @@ public enum MemoryAccess { /** * A kernel can both read and write the buffer. */ - READ_WRITE, + READ_WRITE, /** * A kernel can only write this buffer. */ - WRITE_ONLY, + WRITE_ONLY, /** * A kernel can only read this buffer */ - READ_ONLY + READ_ONLY } diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLException.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLException.java index fd0836ac93..a78d52add6 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLException.java +++ b/jme3-core/src/main/java/com/jme3/opencl/OpenCLException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,43 +36,42 @@ * The error code and its name is reported in the message string as well as the OpenCL call that * causes this exception. Please refer to the official OpenCL specification * to see what might cause this exception. + * * @author shaman */ public class OpenCLException extends RuntimeException { private static final long serialVersionUID = 8471229972153694848L; - private final int errorCode; - - /** - * Creates a new instance of OpenCLExceptionn without detail - * message. - */ - public OpenCLException() { - errorCode = 0; - } + private final int errorCode; - /** - * Constructs an instance of OpenCLExceptionn with the - * specified detail message. - * - * @param msg the detail message. - */ - public OpenCLException(String msg) { - super(msg); - errorCode = 0; - } - - public OpenCLException(String msg, int errorCode) { - super(msg); - this.errorCode = errorCode; - } + /** + * Creates a new instance of OpenCLException without detail + * message. + */ + public OpenCLException() { + errorCode = 0; + } /** - * @return the error code + * Constructs an instance of OpenCLException with the + * specified detail message. + * + * @param msg the detail message. */ - public int getErrorCode() { - return errorCode; - } + public OpenCLException(String msg) { + super(msg); + errorCode = 0; + } - + public OpenCLException(String msg, int errorCode) { + super(msg); + this.errorCode = errorCode; + } + + /** + * @return the error code + */ + public int getErrorCode() { + return errorCode; + } } diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java index d2a45077d3..d030f0b6eb 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java +++ b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ /** * Base interface of all native OpenCL objects. - * This interface provides the functionality for savely release the object. + * This interface provides the functionality to safely release the object. * @author shaman */ public interface OpenCLObject { @@ -61,7 +61,7 @@ public static interface ObjectReleaser { ObjectReleaser getReleaser(); /** * Releases this native object. - * + * * Should delegate to {@code getReleaser().release()}. */ void release(); @@ -71,10 +71,10 @@ public static interface ObjectReleaser { * {@link OpenCLObjectManager}, you have to release it manually * by calling {@link #release() }. * Without registering or releasing, a memory leak might occur. - *
    - * Returns {@code this} to allow calls like - * {@code Buffer buffer = clContext.createBuffer(1024).register();}. - * @return {@code this} + *
    + * Returns {@code this} to allow calls like + * {@code Buffer buffer = clContext.createBuffer(1024).register();}. + * @return {@code this} */ OpenCLObject register(); } diff --git a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java index feed31dece..bb5d95b6dd 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/opencl/OpenCLObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,12 +53,12 @@ public static OpenCLObjectManager getInstance() { return INSTANCE; } - private ReferenceQueue refQueue = new ReferenceQueue(); - private HashSet activeObjects = new HashSet(); + private final ReferenceQueue refQueue = new ReferenceQueue<>(); + private final HashSet activeObjects = new HashSet<>(); private static class OpenCLObjectRef extends PhantomReference { - private OpenCLObject.ObjectReleaser releaser; + final private OpenCLObject.ObjectReleaser releaser; public OpenCLObjectRef(ReferenceQueue refQueue, OpenCLObject obj){ super(obj, refQueue); diff --git a/jme3-core/src/main/java/com/jme3/opencl/Program.java b/jme3-core/src/main/java/com/jme3/opencl/Program.java index 5c4342b0af..ce900547c3 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/Program.java +++ b/jme3-core/src/main/java/com/jme3/opencl/Program.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,9 +39,9 @@ *

    * Warning: Creating the same kernel more than one leads to undefined behaviour, * this is especially important for {@link #createAllKernels() } - * - * @see Context#createProgramFromSourceCode(java.lang.String) - * @see #createKernel(java.lang.String) + * + * @see Context#createProgramFromSourceCode(java.lang.String) + * @see #createKernel(java.lang.String) * @author shaman */ public abstract class Program extends AbstractOpenCLObject { @@ -49,13 +49,13 @@ public abstract class Program extends AbstractOpenCLObject { protected Program(ObjectReleaser releaser) { super(releaser); } - - @Override - public Program register() { - super.register(); - return this; - } - + + @Override + public Program register() { + super.register(); + return this; + } + /** * Builds this program with the specified argument string on the specified * devices. @@ -64,46 +64,50 @@ public Program register() { * The list of devices specify on which device the compiled program * can then be executed. It must be a subset of {@link Context#getDevices() }. * If {@code null} is passed, the program is built on all available devices. - * + * * @param args the compilation arguments * @param devices a list of devices on which the program is build. * @throws KernelCompilationException if the compilation fails - * @see #build() + * @see #build() */ - public abstract void build(String args, Device... devices) throws KernelCompilationException; + public abstract void build(String args, Device... devices) throws KernelCompilationException; + /** * Builds this program without additional arguments + * * @throws KernelCompilationException if the compilation fails */ - public void build() throws KernelCompilationException { + public void build() throws KernelCompilationException { build("", (Device[]) null); } /** * Creates the kernel with the specified name. + * * @param name the name of the kernel as defined in the source code * @return the kernel object - * @throws OpenCLException if the kernel was not found or some other - * error occurred + * @throws OpenCLException if the kernel was not found or some other error + * occurred */ - public abstract Kernel createKernel(String name); - + public abstract Kernel createKernel(String name); + /** * Creates all available kernels in this program. - * The names of the kernels can then by queried by {@link Kernel#getName() }. + * The names of the kernels can then be queried by {@link Kernel#getName() }. + * * @return an array of all kernels */ - public abstract Kernel[] createAllKernels(); - + public abstract Kernel[] createAllKernels(); + /** * Queries a compiled binary representation of this program for a particular * device. This binary can then be used e.g. in the next application launch * to create the program from the binaries and not from the sources. * This saves time. + * * @param device the device from which the binaries are taken * @return the binaries - * @see Context#createProgramFromBinary(java.nio.ByteBuffer, com.jme3.opencl.Device) + * @see Context#createProgramFromBinary(java.nio.ByteBuffer, com.jme3.opencl.Device) */ public abstract ByteBuffer getBinary(Device device); - } diff --git a/jme3-core/src/main/java/com/jme3/opencl/package-info.java b/jme3-core/src/main/java/com/jme3/opencl/package-info.java index 0a3552d5f8..0a19e3a24a 100644 --- a/jme3-core/src/main/java/com/jme3/opencl/package-info.java +++ b/jme3-core/src/main/java/com/jme3/opencl/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -30,7 +30,6 @@ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -package com.jme3.opencl; /** * This package contains an API for using OpenCL together with jME3. *

    @@ -64,7 +63,7 @@ * On the programming side, a {@link com.jme3.opencl.Kernel} instance is obtained * by calling {@link com.jme3.opencl.Program#createKernel(java.lang.String) }. * To execute the kernel, the method - * {@link Kernel#Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...)} + * {@link com.jme3.opencl.Kernel#Run1(com.jme3.opencl.CommandQueue, com.jme3.opencl.Kernel.WorkSize, java.lang.Object...)} * is provided. You first pass the command queue and the work size (i.e. the number of parallel executed threads) * followed by the kernel arguments. *
    @@ -76,9 +75,9 @@ * the argument. A buffer on its own is typeless. In the kernel, you then specify * the type of the buffer by argument declarations like {@code __global float* buffer}. * Note that OpenCL does not check buffer boundaries. If you read or write outside - * of the buffer, the behavior is completely undefined and may often result in + * the buffer, the behavior is completely undefined and may often result in * a program cache later on. - * {@link com.jme3.opencl.Image} objects are structured one, two or three dimensional + * {@link com.jme3.opencl.Image} objects are structured one-, two-, or three-dimensional * memory chunks of a fixed type. They are created by Context.createImage(). * They need special functions in the kernel code to write to or read from images. * Both buffer and image objects provide methods for copying between buffers and images, @@ -93,7 +92,7 @@ *
    * Some methods have the suffix {@code -NoEvent}. This means that these methods * don't return an event object even if the OpenCL function would return an event. - * There exists always an alternative version that does return an event. + * There's always an alternative version that does return an event. * These methods exist to increase the performance: since all actions (like multiple kernel calls) * that are sent to the same command queue are executed in order, there is no * need for intermediate events. (These intermediate events would be released @@ -103,7 +102,7 @@ * *

    * Interoperability between OpenCL and jME3:
    - * This Wrapper allows to share jME3 Images and VertexBuffers with OpenCL.
    + * This Wrapper allows sharing jME3 Images and VertexBuffers with OpenCL.
    * {@link com.jme3.scene.VertexBuffer} objects can be shared with OpenCL * by calling {@link com.jme3.opencl.Context#bindVertexBuffer(com.jme3.scene.VertexBuffer, com.jme3.opencl.MemoryAccess) } * resulting in a {@link com.jme3.opencl.Buffer} object. This buffer object @@ -113,10 +112,10 @@ * or variations of this method. The same holds for {@link com.jme3.texture.FrameBuffer.RenderBuffer} objects * using {@link com.jme3.opencl.Context#bindRenderBuffer(com.jme3.texture.FrameBuffer.RenderBuffer, com.jme3.opencl.MemoryAccess) }. * These methods result in an OpenCL-Image. Usages are e.g. animated textures, - * terrain based on height maps, post processing effects and so forth. + * terrain based on height maps, post-processing effects, and so forth. *
    * Important: Before shared objects can be used by any OpenCL function - * like kernel calls or read/write/copy methods, they must be aquired explicitly + * like kernel calls or read/write/copy methods, they must be acquired explicitly * by {@link com.jme3.opencl.Buffer#acquireBufferForSharingAsync(com.jme3.opencl.CommandQueue) } * or {@link com.jme3.opencl.Image#acquireImageForSharingAsync(com.jme3.opencl.CommandQueue) }. * After the work is done, release the resource with @@ -153,11 +152,8 @@ * thrown. The exception always records the error code and error name and the * OpenCL function call where the error was detected. Please check the official * OpenCL specification for the meanings of these errors for that particular function. - *

  • {@code UnsupportedOperationException}: the OpenCL implementation does not - * support some operations. This is currently only an issue for Jogamp's Jogl - * renderer, since Jocl only supports OpenCL 1.1. LWJGL has full support for - * OpenCL 1.2 and 2.0. * */ +package com.jme3.opencl; //TODO: add profiling to Kernel, CommandQueue diff --git a/jme3-core/src/main/java/com/jme3/post/Filter.java b/jme3-core/src/main/java/com/jme3/post/Filter.java index d594b7be8a..efdb19b04a 100644 --- a/jme3-core/src/main/java/com/jme3/post/Filter.java +++ b/jme3-core/src/main/java/com/jme3/post/Filter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import java.io.IOException; import java.util.Collection; import java.util.Iterator; @@ -75,9 +76,8 @@ public Filter(String name) { } /** - * Inner class Pass - * Pass are like filters in filters. - * Some filters will need multiple passes before the final render + * Passes are like filters within filters. + * Some filters will need multiple passes before the final render. */ public class Pass { @@ -96,45 +96,48 @@ public Pass() { /** * init the pass called internally - * @param renderer - * @param width - * @param height - * @param textureFormat - * @param depthBufferFormat - * @param numSamples + * + * @param renderer (not null) + * @param width the width (in pixels, ≥0) + * @param height the height (in pixels, ≥0) + * @param textureFormat format of the rendered texture + * @param depthBufferFormat format of the depth buffer + * @param numSamples the number of samples per pixel (for multisampling) + * @param renderDepth true to create a depth texture, false for none */ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSamples, boolean renderDepth) { Collection caps = renderer.getCaps(); if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample) && caps.contains(Caps.OpenGL31)) { - renderFrameBuffer = new FrameBuffer(width, height, numSamples); + renderFrameBuffer = new FrameBuffer(width, height, numSamples); renderedTexture = new Texture2D(width, height, numSamples, textureFormat); - renderFrameBuffer.setDepthBuffer(depthBufferFormat); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthBufferFormat)); if (renderDepth) { depthTexture = new Texture2D(width, height, numSamples, depthBufferFormat); - renderFrameBuffer.setDepthTexture(depthTexture); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthTexture)); } } else { renderFrameBuffer = new FrameBuffer(width, height, 1); renderedTexture = new Texture2D(width, height, textureFormat); - renderFrameBuffer.setDepthBuffer(depthBufferFormat); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthBufferFormat)); if (renderDepth) { depthTexture = new Texture2D(width, height, depthBufferFormat); - renderFrameBuffer.setDepthTexture(depthTexture); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthTexture)); } } - renderFrameBuffer.setColorTexture(renderedTexture); - + renderFrameBuffer.addColorTarget(FrameBufferTarget.newTarget(renderedTexture)); + renderFrameBuffer.setName(getClass().getSimpleName()); } /** * init the pass called internally - * @param renderer - * @param width - * @param height - * @param textureFormat - * @param depthBufferFormat + * + * @param renderer (not null) + * @param width the image width (in pixels, ≥0) + * @param height the image height (in pixels, ≥0) + * @param textureFormat the format of the rendered texture + * @param depthBufferFormat the format of the depth buffer */ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat) { init(renderer, width, height, textureFormat, depthBufferFormat, 1); @@ -146,13 +149,14 @@ public void init(Renderer renderer, int width, int height, Format textureFormat, /** * init the pass called internally - * @param renderer - * @param width - * @param height - * @param textureFormat - * @param depthBufferFormat - * @param numSample - * @param material + * + * @param renderer (not null) + * @param width the image width (in pixels, ≥0) + * @param height the image height (in pixels, ≥0) + * @param textureFormat the format of the rendered texture + * @param depthBufferFormat the format of the depth buffer + * @param numSample the number of samples per pixel (for multisampling) + * @param material the Material for this pass */ public void init(Renderer renderer, int width, int height, Format textureFormat, Format depthBufferFormat, int numSample, Material material) { init(renderer, width, height, textureFormat, depthBufferFormat, numSample); @@ -213,7 +217,7 @@ public String toString() { } /** - * returns the default pass texture format. + * @return the default pass texture format */ protected Format getDefaultPassTextureFormat() { return processor.getDefaultPassTextureFormat(); @@ -253,7 +257,8 @@ protected final void init(AssetManager manager, RenderManager renderManager, Vie /** * cleanup this filter - * @param r + * + * @param r the Renderer */ protected final void cleanup(Renderer r) { processor = null; @@ -270,7 +275,7 @@ protected final void cleanup(Renderer r) { } /** - * Initialization of sub classes filters + * Initialization of filter subclasses. * This method is called once when the filter is added to the FilterPostProcessor * It should contain Material initializations and extra passes initialization * @param manager the assetManager @@ -298,7 +303,8 @@ protected void cleanUpFilter(Renderer r) { /** * Override if you want to do something special with the depth texture; - * @param depthTexture + * + * @param depthTexture the desired Texture */ protected void setDepthTexture(Texture depthTexture){ getMaterial().setTexture("DepthTexture", depthTexture); @@ -306,7 +312,8 @@ protected void setDepthTexture(Texture depthTexture){ /** * Override this method if you want to make a pre pass, before the actual rendering of the frame - * @param queue + * + * @param queue the RenderQueue */ protected void postQueue(RenderQueue queue) { } @@ -322,10 +329,11 @@ protected void preFrame(float tpf) { /** * Override this method if you want to make a pass just after the frame has been rendered and just before the filter rendering - * @param renderManager - * @param viewPort - * @param prevFilterBuffer - * @param sceneBuffer + * + * @param renderManager for rendering + * @param viewPort for rendering + * @param prevFilterBuffer the FrameBuffer of the previous filter + * @param sceneBuffer the FrameBuffer of the scene */ protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { } @@ -333,9 +341,11 @@ protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBu /** * Override this method if you want to save extra properties when the filter is saved else only basic properties of the filter will be saved * This method should always begin by super.write(ex); - * @param ex - * @throws IOException + * + * @param ex the exporter (not null) + * @throws IOException from the exporter */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", ""); @@ -347,6 +357,7 @@ public void write(JmeExporter ex) throws IOException { * is loaded else only basic properties of the filter will be loaded * This method should always begin by super.read(im); */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", ""); @@ -363,7 +374,8 @@ public String getName() { /** * Sets the name of the filter - * @param name + * + * @param name the desired name (alias created) */ public void setName(String name) { this.name = name; @@ -379,7 +391,8 @@ protected FrameBuffer getRenderFrameBuffer() { /** * sets the default pass frame buffer - * @param renderFrameBuffer + * + * @param renderFrameBuffer the buffer to use (alias created) */ protected void setRenderFrameBuffer(FrameBuffer renderFrameBuffer) { this.defaultPass.renderFrameBuffer = renderFrameBuffer; @@ -395,7 +408,7 @@ protected Texture2D getRenderedTexture() { /** * sets the rendered texture of this filter - * @param renderedTexture + * @param renderedTexture the desired Texture (alias created) */ protected void setRenderedTexture(Texture2D renderedTexture) { this.defaultPass.renderedTexture = renderedTexture; @@ -423,8 +436,8 @@ protected boolean isRequiresSceneTexture() { * Override this method and return true if you want the scene (input) texture * to use bilinear filtering or false to use nearest filtering. * - * Typically filters that perform samples in between pixels - * should enable filtering. + * Typically, filters that perform samples in between pixels + * should enable bilinear filtering. * * @return true to use linear filtering, false to use nearest filtering. */ @@ -461,8 +474,9 @@ public boolean isEnabled() { } /** - * sets a reference to the FilterPostProcessor ti which this filter is attached - * @param proc + * sets a reference to the FilterPostProcessor to which this filter is attached + * + * @param proc the desired FPP (alias created) */ protected void setProcessor(FilterPostProcessor proc) { processor = proc; 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 b5db4d0f44..fa0e14ab77 100644 --- a/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/FilterPostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,17 +32,28 @@ package com.jme3.post; import com.jme3.asset.AssetManager; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.material.Material; -import com.jme3.profile.*; -import com.jme3.renderer.*; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.SpStep; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Caps; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; import com.jme3.ui.Picture; import com.jme3.util.SafeArrayList; + import java.io.IOException; import java.util.ArrayList; import java.util.Collection; @@ -51,13 +62,22 @@ import java.util.List; /** - * A FilterPostProcessor is a processor that can apply several {@link Filter}s to a rendered scene
    - * It manages a list of filters that will be applied in the order in which they've been added to the list - * @author Rémy Bouquet aka Nehon + * A `FilterPostProcessor` is a {@link SceneProcessor} that can apply several + * {@link Filter}s to a rendered scene. It manages a list of filters that will be + * applied in the order in which they have been added. This processor handles + * rendering the main scene to an offscreen framebuffer, then applying each enabled + * filter sequentially, optionally with anti-aliasing (multisampling) and depth texture + * support. + * + * @author Nehon */ public class FilterPostProcessor implements SceneProcessor, Savable { + /** + * The simple name of this class, used for profiling. + */ public static final String FPP = FilterPostProcessor.class.getSimpleName(); + private RenderManager renderManager; private Renderer renderer; private ViewPort viewPort; @@ -67,7 +87,7 @@ public class FilterPostProcessor implements SceneProcessor, Savable { private Texture2D filterTexture; private Texture2D depthTexture; private SafeArrayList filters = new SafeArrayList<>(Filter.class); - private AssetManager assetManager; + private AssetManager assetManager; private Picture fsQuad; private boolean computeDepth = false; private FrameBuffer outputBuffer; @@ -86,25 +106,30 @@ public class FilterPostProcessor implements SceneProcessor, Savable { private Format fbFormat = Format.RGB111110F; private Format depthFormat = Format.Depth; - + /** - * Create a FilterProcessor - * @param assetManager the assetManager + * Creates a new `FilterPostProcessor`. + * + * @param assetManager The asset manager to be used by filters for loading assets. */ public FilterPostProcessor(AssetManager assetManager) { this.assetManager = assetManager; } /** - * Don't use this constructor, use {@link #FilterPostProcessor(AssetManager assetManager)}
    - * This constructor is used for serialization only + * Serialization-only constructor. Do not use this constructor directly; + * use {@link #FilterPostProcessor(AssetManager)}. */ protected FilterPostProcessor() { } /** - * Adds a filter to the filters list
    - * @param filter the filter to add + * Adds a filter to the list of filters to be applied. Filters are applied + * in the order they are added. If the processor is already initialized, + * the filter is immediately initialized as well. + * + * @param filter The filter to add (not null). + * @throws IllegalArgumentException If the provided filter is null. */ public void addFilter(Filter filter) { if (filter == null) { @@ -117,12 +142,14 @@ public void addFilter(Filter filter) { } setFilterState(filter, filter.isEnabled()); - } /** - * removes this filters from the filters list - * @param filter + * Removes a specific filter from the list. The filter's `cleanup` method + * is called upon removal. + * + * @param filter The filter to remove (not null). + * @throws IllegalArgumentException If the provided filter is null. */ public void removeFilter(Filter filter) { if (filter == null) { @@ -133,10 +160,23 @@ public void removeFilter(Filter filter) { updateLastFilterIndex(); } + /** + * Returns an iterator over the filters currently managed by this processor. + * + * @return An `Iterator` of {@link Filter} objects. + */ public Iterator getFilterIterator() { return filters.iterator(); } + /** + * Initializes the `FilterPostProcessor`. This method is called by the + * `RenderManager` when the processor is added to a viewport. + * + * @param rm The `RenderManager` instance. + * @param vp The `ViewPort` this processor is attached to. + */ + @Override public void initialize(RenderManager rm, ViewPort vp) { renderManager = rm; renderer = rm.getRenderer(); @@ -145,44 +185,60 @@ public void initialize(RenderManager rm, ViewPort vp) { fsQuad.setWidth(1); fsQuad.setHeight(1); + // Determine optimal framebuffer format based on renderer capabilities if (!renderer.getCaps().contains(Caps.PackedFloatTexture)) { - if (!renderer.getCaps().contains(Caps.FloatTexture)) { - fbFormat = Format.RGB8; - } else { + if (renderer.getCaps().contains(Caps.FloatColorBufferRGB)) { fbFormat = Format.RGB16F; + } else if (renderer.getCaps().contains(Caps.FloatColorBufferRGBA)) { + fbFormat = Format.RGBA16F; + } else { + fbFormat = Format.RGB8; } } - + Camera cam = vp.getCamera(); - //save view port diensions + // Save original viewport dimensions left = cam.getViewPortLeft(); right = cam.getViewPortRight(); top = cam.getViewPortTop(); bottom = cam.getViewPortBottom(); originalWidth = cam.getWidth(); originalHeight = cam.getHeight(); - //first call to reshape + + // First call to reshape to set up internal framebuffers and textures reshape(vp, cam.getWidth(), cam.getHeight()); } + /** + * Returns the default color buffer format used for the internal rendering + * passes of the filters. This format is determined during initialization + * based on the renderer's capabilities. + * + * @return The default `Format` for the filter pass textures. + */ public Format getDefaultPassTextureFormat() { return fbFormat; } - + /** - * init the given filter - * @param filter - * @param vp + * Initializes a single filter. This method is called when a filter is added + * or when the post-processor is initialized/reshaped. It sets the processor + * for the filter, handles depth texture requirements, and calls the filter's + * `init` method. + * + * @param filter The {@link Filter} to initialize. + * @param vp The `ViewPort` associated with this processor. */ private void initFilter(Filter filter, ViewPort vp) { filter.setProcessor(this); if (filter.isRequiresDepthTexture()) { if (!computeDepth && renderFrameBuffer != null) { + // If depth texture is required and not yet created, create it depthTexture = new Texture2D(width, height, depthFormat); - renderFrameBuffer.setDepthTexture(depthTexture); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthTexture)); } - computeDepth = true; + computeDepth = true; // Mark that depth texture is needed filter.init(assetManager, renderManager, vp, width, height); filter.setDepthTexture(depthTexture); } else { @@ -191,76 +247,99 @@ private void initFilter(Filter filter, ViewPort vp) { } /** - * renders a filter on a fullscreen quad - * @param r - * @param buff - * @param mat + * Renders a filter's material onto a full-screen quad. This method + * handles setting up the rendering context (framebuffer, camera, material) + * for a filter pass. It correctly resizes the camera and adjusts material + * states based on whether the target buffer is the final output buffer or an + * intermediate filter buffer. + * + * @param r The `Renderer` instance. + * @param buff The `FrameBuffer` to render to. + * @param mat The `Material` to use for rendering the filter. */ private void renderProcessing(Renderer r, FrameBuffer buff, Material mat) { + // Adjust camera and viewport based on target framebuffer if (buff == outputBuffer) { viewPort.getCamera().resize(originalWidth, originalHeight, false); viewPort.getCamera().setViewPort(left, right, bottom, top); - // update is redundant because resize and setViewPort will both - // run the appropriate (and same) onXXXChange methods. - // Also, update() updates some things that don't need to be updated. - //viewPort.getCamera().update(); - renderManager.setCamera( viewPort.getCamera(), false); + // viewPort.getCamera().update(); // Redundant as resize and setViewPort call onXXXChange + renderManager.setCamera(viewPort.getCamera(), false); + // Disable depth test/write for final pass to prevent artifacts if (mat.getAdditionalRenderState().isDepthWrite()) { mat.getAdditionalRenderState().setDepthTest(false); mat.getAdditionalRenderState().setDepthWrite(false); } - }else{ + } else { + // Rendering to an intermediate framebuffer for a filter pass viewPort.getCamera().resize(buff.getWidth(), buff.getHeight(), false); viewPort.getCamera().setViewPort(0, 1, 0, 1); - // update is redundant because resize and setViewPort will both - // run the appropriate (and same) onXXXChange methods. - // Also, update() updates some things that don't need to be updated. - //viewPort.getCamera().update(); - renderManager.setCamera( viewPort.getCamera(), false); + // viewPort.getCamera().update(); // Redundant as resize and setViewPort call onXXXChange + renderManager.setCamera(viewPort.getCamera(), false); + // Enable depth test/write for intermediate passes if material needs it mat.getAdditionalRenderState().setDepthTest(true); mat.getAdditionalRenderState().setDepthWrite(true); } - - + fsQuad.setMaterial(mat); fsQuad.updateGeometricState(); - - r.setFrameBuffer(buff); - r.clearBuffers(true, true, true); + + r.setFrameBuffer(buff); + r.clearBuffers(true, true, true); // Clear color, depth, and stencil buffers renderManager.renderGeometry(fsQuad); } - + + /** + * Checks if the `FilterPostProcessor` has been initialized. + * + * @return True if initialized, false otherwise. + */ + @Override public boolean isInitialized() { return viewPort != null; } + @Override public void postQueue(RenderQueue rq) { for (Filter filter : filters.getArray()) { if (filter.isEnabled()) { - if (prof != null) prof.spStep(SpStep.ProcPostQueue, FPP, filter.getName()); + if (prof != null) { + prof.spStep(SpStep.ProcPostQueue, FPP, filter.getName()); + } filter.postQueue(rq); } } - } + } /** - * iterate through the filter list and renders filters - * @param r - * @param sceneFb + * Renders the chain of filters. This method is the core of the post-processing. + * It iterates through each enabled filter, handling pre-filter passes, + * setting up textures (scene, depth), performing the main filter rendering, + * and managing intermediate framebuffers. + * + * @param r The `Renderer` instance. + * @param sceneFb The framebuffer containing the rendered scene (either MS or single-sample). */ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { Texture2D tex = filterTexture; FrameBuffer buff = sceneFb; boolean msDepth = depthTexture != null && depthTexture.getImage().getMultiSamples() > 1; + for (int i = 0; i < filters.size(); i++) { Filter filter = filters.get(i); - if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName()); + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName()); + } + if (filter.isEnabled()) { + // Handle additional passes a filter might have (e.g., blur passes) if (filter.getPostRenderPasses() != null) { - for (Iterator it1 = filter.getPostRenderPasses().iterator(); it1.hasNext();) { - Filter.Pass pass = it1.next(); - if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), pass.toString()); + for (Filter.Pass pass : filter.getPostRenderPasses()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), pass.toString()); + } pass.beforeRender(); + + // Set scene texture if required by the pass if (pass.requiresSceneAsTexture()) { pass.getPassMaterial().setTexture("Texture", tex); if (tex.getImage().getMultiSamples() > 1) { @@ -270,6 +349,8 @@ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { } } + + // Set depth texture if required by the pass if (pass.requiresDepthAsTexture()) { pass.getPassMaterial().setTexture("DepthTexture", depthTexture); if (msDepth) { @@ -281,14 +362,16 @@ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { renderProcessing(r, pass.getRenderFrameBuffer(), pass.getPassMaterial()); } } - if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFrame"); + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFrame"); + } filter.postFrame(renderManager, viewPort, buff, sceneFb); Material mat = filter.getMaterial(); if (msDepth && filter.isRequiresDepthTexture()) { mat.setInt("NumSamplesDepth", depthTexture.getImage().getMultiSamples()); } - + if (filter.isRequiresSceneTexture()) { mat.setTexture("Texture", tex); if (tex.getImage().getMultiSamples() > 1) { @@ -297,24 +380,32 @@ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { mat.clearParam("NumSamples"); } } - + + // Apply bilinear filtering if requested by the filter boolean wantsBilinear = filter.isRequiresBilinear(); if (wantsBilinear) { tex.setMagFilter(Texture.MagFilter.Bilinear); tex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); } + // Determine target framebuffer and source texture for the next pass buff = outputBuffer; if (i != lastFilterIndex) { buff = filter.getRenderFrameBuffer(); tex = filter.getRenderedTexture(); - } - if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "render"); + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "render"); + } + // Render the main filter pass renderProcessing(r, buff, mat); - if (prof != null) prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFilter"); + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, FPP, filter.getName(), "postFilter"); + } + // Call filter's postFilter for final adjustments filter.postFilter(r, buff); - + + // Revert texture filtering if it was changed if (wantsBilinear) { tex.setMagFilter(Texture.MagFilter.Nearest); tex.setMinFilter(Texture.MinFilter.NearestNoMipMaps); @@ -323,58 +414,69 @@ private void renderFilterChain(Renderer r, FrameBuffer sceneFb) { } } + @Override public void postFrame(FrameBuffer out) { FrameBuffer sceneBuffer = renderFrameBuffer; if (renderFrameBufferMS != null && !renderer.getCaps().contains(Caps.OpenGL32)) { - renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer, true); + renderer.copyFrameBuffer(renderFrameBufferMS, renderFrameBuffer, true, true); } else if (renderFrameBufferMS != null) { sceneBuffer = renderFrameBufferMS; } + + // Execute the filter chain renderFilterChain(renderer, sceneBuffer); + + // Restore the original output framebuffer for the viewport renderer.setFrameBuffer(outputBuffer); - - //viewport can be null if no filters are enabled + + // viewport can be null if no filters are enabled if (viewPort != null) { renderManager.setCamera(viewPort.getCamera(), false); } } + @Override public void preFrame(float tpf) { if (filters.isEmpty() || lastFilterIndex == -1) { - //If the camera is initialized and there are no filter to render, the camera viewport is restored as it was + // If no filters are enabled, restore the camera's original viewport + // and output framebuffer to bypass the post-processor. if (cameraInit) { viewPort.getCamera().resize(originalWidth, originalHeight, true); viewPort.getCamera().setViewPort(left, right, bottom, top); viewPort.setOutputFrameBuffer(outputBuffer); cameraInit = false; } - } else { - setupViewPortFrameBuffer(); - //if we are ina multiview situation we need to resize the camera - //to the viewportsize so that the backbuffer is rendered correctly - if (multiView) { + setupViewPortFrameBuffer(); + // If in a multi-view situation, resize the camera to the viewport size + // so that the back buffer is rendered correctly for filtering. + if (multiView) { viewPort.getCamera().resize(width, height, false); viewPort.getCamera().setViewPort(0, 1, 0, 1); viewPort.getCamera().update(); renderManager.setCamera(viewPort.getCamera(), false); - } + } } + // Call preFrame on all enabled filters for (Filter filter : filters.getArray()) { if (filter.isEnabled()) { - if (prof != null) prof.spStep(SpStep.ProcPreFrame, FPP, filter.getName()); + if (prof != null) { + prof.spStep(SpStep.ProcPreFrame, FPP, filter.getName()); + } filter.preFrame(tpf); } } - } /** - * sets the filter to enabled or disabled - * @param filter - * @param enabled + * Sets the enabled state of a specific filter. If the filter is part of + * this processor's list, its `enabled` flag is updated, and the + * `lastFilterIndex` is recomputed. + * + * @param filter The {@link Filter} to modify (not null). + * @param enabled True to enable the filter, false to disable it. */ protected void setFilterState(Filter filter, boolean enabled) { if (filters.contains(filter)) { @@ -384,66 +486,85 @@ protected void setFilterState(Filter filter, boolean enabled) { } /** - * compute the index of the last filter to render + * Computes the index of the last enabled filter in the list. This is used + * to determine which filter should render to the final output framebuffer + * and which should render to intermediate framebuffers. If no filters are + * enabled, the viewport's output framebuffer is restored to its original. */ private void updateLastFilterIndex() { lastFilterIndex = -1; for (int i = filters.size() - 1; i >= 0 && lastFilterIndex == -1; i--) { if (filters.get(i).isEnabled()) { lastFilterIndex = i; - //the Fpp is initialized, but the viwport framebuffer is the - //original out framebuffer so we must recover from a situation - //where no filter was enabled. So we set the correct framebuffer - //on the viewport - if(isInitialized() && viewPort.getOutputFrameBuffer()==outputBuffer){ + // If the FPP is initialized but the viewport framebuffer is the + // original output framebuffer (meaning no filter was enabled + // previously), then redirect it to the FPP's internal framebuffer. + if (isInitialized() && viewPort.getOutputFrameBuffer() == outputBuffer) { setupViewPortFrameBuffer(); } return; } } + // If no filters are enabled, restore the original framebuffer to the viewport. if (isInitialized() && lastFilterIndex == -1) { - //There is no enabled filter, we restore the original framebuffer - //to the viewport to bypass the fpp. viewPort.setOutputFrameBuffer(outputBuffer); } } + @Override public void cleanup() { if (viewPort != null) { - //reset the viewport camera viewport to its initial value + // Reset the viewport camera and output framebuffer to their initial values viewPort.getCamera().resize(originalWidth, originalHeight, true); viewPort.getCamera().setViewPort(left, right, bottom, top); viewPort.setOutputFrameBuffer(outputBuffer); viewPort = null; - if(renderFrameBuffer != null){ + // Dispose of internal framebuffers and textures + if (renderFrameBuffer != null) { renderFrameBuffer.dispose(); } - if(depthTexture!=null){ - depthTexture.getImage().dispose(); + if (depthTexture != null) { + depthTexture.getImage().dispose(); } filterTexture.getImage().dispose(); - if(renderFrameBufferMS != null){ - renderFrameBufferMS.dispose(); + if (renderFrameBufferMS != null) { + renderFrameBufferMS.dispose(); } for (Filter filter : filters.getArray()) { filter.cleanup(renderer); } } - } + /** + * Sets the profiler instance for this processor. + * + * @param profiler The `AppProfiler` instance to use for performance monitoring. + */ @Override public void setProfiler(AppProfiler profiler) { this.prof = profiler; } + /** + * Reshapes the `FilterPostProcessor` when the viewport or canvas size changes. + * This method recalculates internal framebuffer dimensions, creates new + * framebuffers and textures if necessary (e.g., for anti-aliasing), and + * reinitializes all filters with the new dimensions. It also detects + * multi-view scenarios. + * + * @param vp The `ViewPort` being reshaped. + * @param w The new width of the viewport's canvas. + * @param h The new height of the viewport's canvas. + */ + @Override public void reshape(ViewPort vp, int w, int h) { Camera cam = vp.getCamera(); - //this has no effect at first init but is useful when resizing the canvas with multi views + // This sets the camera viewport to its full extent (0-1) for rendering to the FPP's internal buffer. cam.setViewPort(left, right, bottom, top); - //resizing the camera to fit the new viewport and saving original dimensions - cam.resize(w, h, false); + // Resizing the camera to fit the new viewport and saving original dimensions + cam.resize(w, h, true); left = cam.getViewPortLeft(); right = cam.getViewPortRight(); top = cam.getViewPortTop(); @@ -451,16 +572,16 @@ public void reshape(ViewPort vp, int w, int h) { originalWidth = w; originalHeight = h; - //computing real dimension of the viewport and resizing the camera + // Computing real dimension of the viewport based on its relative size within the canvas width = (int) (w * (Math.abs(right - left))); height = (int) (h * (Math.abs(bottom - top))); width = Math.max(1, width); height = Math.max(1, height); - - //Testing original versus actual viewport dimension. - //If they are different we are in a multiview situation and - //camera must be handled differently - if(originalWidth!=width || originalHeight!=height){ + + // Test if original dimensions differ from actual viewport dimensions. + // If they are different, we are in a multiview situation, and the + // camera must be handled differently (e.g., resized to the sub-viewport). + if (originalWidth != width || originalHeight != height) { multiView = true; } @@ -473,29 +594,44 @@ public void reshape(ViewPort vp, int w, int h) { Collection caps = renderer.getCaps(); - //antialiasing on filters only supported in opengl 3 due to depth read problem + // antialiasing on filters only supported in opengl 3 due to depth read problem if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)) { renderFrameBufferMS = new FrameBuffer(width, height, numSamples); + + // If OpenGL 3.2+ is supported, multisampled textures can be attached directly if (caps.contains(Caps.OpenGL32)) { Texture2D msColor = new Texture2D(width, height, numSamples, fbFormat); Texture2D msDepth = new Texture2D(width, height, numSamples, depthFormat); - renderFrameBufferMS.setDepthTexture(msDepth); - renderFrameBufferMS.setColorTexture(msColor); + renderFrameBufferMS.setDepthTarget(FrameBufferTarget.newTarget(msDepth)); + renderFrameBufferMS.addColorTarget(FrameBufferTarget.newTarget(msColor)); filterTexture = msColor; depthTexture = msDepth; } else { - renderFrameBufferMS.setDepthBuffer(depthFormat); - renderFrameBufferMS.setColorBuffer(fbFormat); + // Otherwise, multisampled framebuffer must use internal texture, which cannot be directly read + renderFrameBufferMS.setDepthTarget(FrameBufferTarget.newTarget(depthFormat)); + renderFrameBufferMS.addColorTarget(FrameBufferTarget.newTarget(fbFormat)); } } - if (numSamples <= 1 || !caps.contains(Caps.OpenGL32)) { + // Setup single-sampled framebuffer if no multisampling, or if OpenGL 3.2+ is not supported + // (because for non-GL32, a single-sampled buffer is still needed to copy MS content into). + if (numSamples <= 1 || !caps.contains(Caps.OpenGL32) || !caps.contains(Caps.FrameBufferMultisample)) { renderFrameBuffer = new FrameBuffer(width, height, 1); - renderFrameBuffer.setDepthBuffer(depthFormat); + renderFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthFormat)); filterTexture = new Texture2D(width, height, fbFormat); - renderFrameBuffer.setColorTexture(filterTexture); + renderFrameBuffer.addColorTarget(FrameBufferTarget.newTarget(filterTexture)); } + // Set names for debugging + if (renderFrameBufferMS != null) { + renderFrameBufferMS.setName("FilterPostProcessor MS"); + } + + if (renderFrameBuffer != null) { + renderFrameBuffer.setName("FilterPostProcessor"); + } + + // Initialize all existing filters with the new dimensions for (Filter filter : filters.getArray()) { initFilter(filter, vp); } @@ -503,16 +639,16 @@ public void reshape(ViewPort vp, int w, int h) { } /** - * return the number of samples for antialiasing - * @return numSamples + * Returns the number of samples used for anti-aliasing. + * + * @return The number of samples. */ public int getNumSamples() { return numSamples; } /** - * - * Removes all the filters from this processor + * Removes all filters currently added to this processor. */ public void removeAllFilters() { filters.clear(); @@ -520,8 +656,12 @@ public void removeAllFilters() { } /** - * Sets the number of samples for antialiasing - * @param numSamples the number of Samples + * Sets the number of samples for anti-aliasing. A value of 1 means no + * anti-aliasing. This method should generally be called before the + * processor is initialized to have an effect. + * + * @param numSamples The number of samples. Must be greater than 0. + * @throws IllegalArgumentException If `numSamples` is less than or equal to 0. */ public void setNumSamples(int numSamples) { if (numSamples <= 0) { @@ -533,26 +673,57 @@ public void setNumSamples(int numSamples) { /** * Sets the asset manager for this processor - * @param assetManager + * + * @param assetManager to load assets */ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; } + /** + * Sets the preferred `Image.Format` to be used for the internal frame buffer's + * color buffer. + * + * @param fbFormat The desired `Format` for the color buffer. + */ public void setFrameBufferFormat(Format fbFormat) { this.fbFormat = fbFormat; } + /** + * Sets the preferred `Image.Format` to be used for the internal frame buffer's + * depth buffer. + * + * @param depthFormat The desired `Format` for the depth buffer. + */ + public void setFrameBufferDepthFormat(Format depthFormat) { + this.depthFormat = depthFormat; + } + + /** + * Returns the `Image.Format` currently used for the internal frame buffer's + * depth buffer. + * + * @return The current depth `Format`. + */ + public Format getFrameBufferDepthFormat() { + return depthFormat; + } + + @Override + @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(numSamples, "numSamples", 0); oc.writeSavableArrayList(new ArrayList(filters), "filters", null); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); numSamples = ic.readInt("numSamples", 0); - filters = new SafeArrayList(Filter.class, ic.readSavableArrayList("filters", null)); + filters = new SafeArrayList<>(Filter.class, ic.readSavableArrayList("filters", null)); for (Filter filter : filters.getArray()) { filter.setProcessor(this); setFilterState(filter, filter.isEnabled()); @@ -561,41 +732,50 @@ public void read(JmeImporter im) throws IOException { } /** - * For internal use only
    - * returns the depth texture of the scene - * @return the depth texture + * For internal use only. + * Returns the depth texture generated from the scene's depth buffer. + * This texture is available if any filter requires a depth texture. + * + * @return The `Texture2D` containing the scene's depth information, or null if not computed. */ public Texture2D getDepthTexture() { return depthTexture; } /** - * For internal use only
    - * returns the rendered texture of the scene - * @return the filter texture + * For internal use only. + * Returns the color texture that contains the rendered scene or the output + * of the previous filter in the chain. This texture serves as input for subsequent filters. + * + * @return The `Texture2D` containing the scene's color information or the intermediate filter output. */ public Texture2D getFilterTexture() { return filterTexture; } - + /** - * returns the first filter in the list assignable form the given type - * @param - * @param filterType the filter type - * @return a filter assignable form the given type + * Returns the first filter in the managed list that is assignable from the + * given filter type. Useful for retrieving specific filters to modify their properties. + * + * @param The type of the filter to retrieve. + * @param filterType The `Class` object representing the filter type. + * @return A filter instance assignable from `filterType`, or null if no such filter is found. */ + @SuppressWarnings("unchecked") public T getFilter(Class filterType) { - for (Filter c : filters.getArray()) { - if (filterType.isAssignableFrom(c.getClass())) { - return (T) c; + for (Filter f : filters.getArray()) { + if (filterType.isAssignableFrom(f.getClass())) { + return (T) f; } } return null; } - + /** - * returns an unmodifiable version of the filter list. - * @return the filters list + * Returns an unmodifiable version of the list of filters currently + * managed by this processor. + * + * @return An unmodifiable `List` of {@link Filter} objects. */ public List getFilterList(){ return Collections.unmodifiableList(filters); @@ -608,4 +788,4 @@ private void setupViewPortFrameBuffer() { viewPort.setOutputFrameBuffer(renderFrameBuffer); } } - } +} diff --git a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java index 424f3b689a..857e78dbc3 100644 --- a/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java +++ b/jme3-core/src/main/java/com/jme3/post/HDRRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,8 +62,6 @@ public class HDRRenderer implements SceneProcessor { private RenderManager renderManager; private ViewPort viewPort; private static final Logger logger = Logger.getLogger(HDRRenderer.class.getName()); - private AppProfiler prof; - private Camera fbCam = new Camera(1, 1); private FrameBuffer msFB; @@ -93,24 +91,26 @@ public class HDRRenderer implements SceneProcessor { private float whiteLevel = 100f; private float throttle = -1; private int maxIterations = -1; - private Image.Format bufFormat = Format.RGB16F; + private Image.Format bufFormat = Format.RGB8; private MinFilter fbMinFilter = MinFilter.BilinearNoMipMaps; private MagFilter fbMagFilter = MagFilter.Bilinear; - private AssetManager manager; + private final AssetManager manager; private boolean enabled = true; - public HDRRenderer(AssetManager manager, Renderer renderer){ + public HDRRenderer(AssetManager manager, Renderer renderer) { this.manager = manager; this.renderer = renderer; - + Collection caps = renderer.getCaps(); if (caps.contains(Caps.PackedFloatColorBuffer)) bufFormat = Format.RGB111110F; - else if (caps.contains(Caps.FloatColorBuffer)) + else if (caps.contains(Caps.FloatColorBufferRGB)) bufFormat = Format.RGB16F; - else{ + else if (caps.contains(Caps.FloatColorBufferRGBA)) + bufFormat = Format.RGBA16F; + else { enabled = false; return; } @@ -120,19 +120,19 @@ public boolean isEnabled() { return enabled; } - public void setSamples(int samples){ + public void setSamples(int samples) { this.numSamples = samples; } - public void setExposure(float exp){ + public void setExposure(float exp) { this.exposure = exp; } - public void setWhiteLevel(float whiteLevel){ + public void setWhiteLevel(float whiteLevel) { this.whiteLevel = whiteLevel; } - public void setMaxIterations(int maxIterations){ + public void setMaxIterations(int maxIterations) { this.maxIterations = maxIterations; // regenerate shaders if needed @@ -140,21 +140,21 @@ public void setMaxIterations(int maxIterations){ createLumShaders(); } - public void setThrottle(float throttle){ + public void setThrottle(float throttle) { this.throttle = throttle; } - public void setUseFastFilter(boolean fastFilter){ + public void setUseFastFilter(boolean fastFilter) { if (fastFilter){ fbMagFilter = MagFilter.Nearest; fbMinFilter = MinFilter.NearestNoMipMaps; - }else{ + } else { fbMagFilter = MagFilter.Bilinear; fbMinFilter = MinFilter.BilinearNoMipMaps; } } - public Picture createDisplayQuad(/*int mode, Texture tex*/){ + public Picture createDisplayQuad(/*int mode, Texture tex*/) { if (scene64 == null) return null; @@ -165,16 +165,16 @@ public Picture createDisplayQuad(/*int mode, Texture tex*/){ mat.setBoolean("DecodeLum", true); mat.setTexture("Texture", scene64); // mat.setTexture("Texture", tex); - + Picture dispQuad = new Picture("Luminance Display"); dispQuad.setMaterial(mat); return dispQuad; } private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mode, - int iters, Texture tex){ + int iters, Texture tex) { Material mat = new Material(manager, "Common/MatDefs/Hdr/LogLum.j3md"); - + Vector2f blockSize = new Vector2f(1f / bufW, 1f / bufH); Vector2f pixelSize = new Vector2f(1f / srcW, 1f / srcH); Vector2f blocks = new Vector2f(); @@ -186,7 +186,7 @@ private Material createLumShader(int srcW, int srcH, int bufW, int bufH, int mod blockSize.y / pixelSize.y); numPixels = blocks.x * blocks.y; } while (numPixels > iters); - }else{ + } else { blocks.set(blockSize.x / pixelSize.x, blockSize.y / pixelSize.y); numPixels = blocks.x * blocks.y; @@ -206,7 +206,7 @@ else if (mode == LUMMODE_DECODE_LUM) return mat; } - private void createLumShaders(){ + private void createLumShaders() { int w = mainSceneFB.getWidth(); int h = mainSceneFB.getHeight(); hdr64 = createLumShader(w, h, 64, 64, LUMMODE_ENCODE_LUM, maxIterations, mainScene); @@ -214,16 +214,16 @@ private void createLumShaders(){ hdr1 = createLumShader(8, 8, 1, 1, LUMMODE_NONE, maxIterations, scene8); } - private int opposite(int i){ + private int opposite(int i) { return i == 1 ? 0 : 1; } - private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){ - if (dst == null){ + private void renderProcessing(Renderer r, FrameBuffer dst, Material mat) { + if (dst == null) { fsQuad.setWidth(mainSceneFB.getWidth()); fsQuad.setHeight(mainSceneFB.getHeight()); fbCam.resize(mainSceneFB.getWidth(), mainSceneFB.getHeight(), true); - }else{ + } else { fsQuad.setWidth(dst.getWidth()); fsQuad.setHeight(dst.getHeight()); fbCam.resize(dst.getWidth(), dst.getHeight(), true); @@ -237,7 +237,7 @@ private void renderProcessing(Renderer r, FrameBuffer dst, Material mat){ renderManager.renderGeometry(fsQuad); } - private void renderToneMap(Renderer r, FrameBuffer out){ + private void renderToneMap(Renderer r, FrameBuffer out) { tone.setFloat("A", exposure); tone.setFloat("White", whiteLevel); tone.setTexture("Lum", scene1[oppSrc]); @@ -246,17 +246,19 @@ private void renderToneMap(Renderer r, FrameBuffer out){ renderProcessing(r, out, tone); } - private void updateAverageLuminance(Renderer r){ + private void updateAverageLuminance(Renderer r) { renderProcessing(r, scene64FB, hdr64); renderProcessing(r, scene8FB, hdr8); renderProcessing(r, scene1FB[curSrc], hdr1); } - public boolean isInitialized(){ + @Override + public boolean isInitialized() { return viewPort != null; } - public void reshape(ViewPort vp, int w, int h){ + @Override + public void reshape(ViewPort vp, int w, int h) { if (mainSceneFB != null){ renderer.deleteFrameBuffer(mainSceneFB); } @@ -273,14 +275,14 @@ public void reshape(ViewPort vp, int w, int h){ } tone.setTexture("Texture", mainScene); - + Collection caps = renderer.getCaps(); if (numSamples > 1 && caps.contains(Caps.FrameBufferMultisample)){ msFB = new FrameBuffer(w, h, numSamples); msFB.setDepthBuffer(Format.Depth); msFB.setColorBuffer(bufFormat); vp.setOutputFrameBuffer(msFB); - }else{ + } else { if (numSamples > 1) logger.warning("FBO multisampling not supported on this GPU, request ignored."); @@ -290,7 +292,8 @@ public void reshape(ViewPort vp, int w, int h){ createLumShaders(); } - public void initialize(RenderManager rm, ViewPort vp){ + @Override + public void initialize(RenderManager rm, ViewPort vp) { if (!enabled) return; @@ -331,10 +334,9 @@ public void initialize(RenderManager rm, ViewPort vp){ int w = vp.getCamera().getWidth(); int h = vp.getCamera().getHeight(); reshape(vp, w, h); - - } + @Override public void preFrame(float tpf) { if (!enabled) return; @@ -343,14 +345,16 @@ public void preFrame(float tpf) { blendFactor = (time / throttle); } + @Override public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { if (!enabled) return; - if (msFB != null){ + if (msFB != null) { // first render to multisampled FB // renderer.setFrameBuffer(msFB); // renderer.clearBuffers(true,true,true); @@ -358,8 +362,8 @@ public void postFrame(FrameBuffer out) { // renderManager.renderViewPortRaw(viewPort); // render back to non-multisampled FB - renderer.copyFrameBuffer(msFB, mainSceneFB, true); - }else{ + renderer.copyFrameBuffer(msFB, mainSceneFB, true, true); + } else { // renderer.setFrameBuffer(mainSceneFB); // renderer.clearBuffers(true,true,false); // @@ -374,7 +378,7 @@ public void postFrame(FrameBuffer out) { blendFactor = 0; time = 0; updateAverageLuminance(renderer); - }else{ + } else { if (curSrc == -1){ curSrc = 0; oppSrc = 0; @@ -404,6 +408,7 @@ public void postFrame(FrameBuffer out) { renderManager.setCamera(viewPort.getCamera(), false); } + @Override public void cleanup() { if (!enabled) return; @@ -422,7 +427,6 @@ public void cleanup() { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } - } diff --git a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java index a3125dd1e1..1a7a7be94f 100644 --- a/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/PreDepthProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,13 +49,10 @@ public class PreDepthProcessor implements SceneProcessor { private RenderManager rm; private ViewPort vp; - private AssetManager assetManager; - private Material preDepth; - private RenderState forcedRS; - private AppProfiler prof; + private final Material preDepth; + private final RenderState forcedRS; public PreDepthProcessor(AssetManager assetManager){ - this.assetManager = assetManager; preDepth = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); preDepth.getAdditionalRenderState().setPolyOffset(0, 0); preDepth.getAdditionalRenderState().setFaceCullMode(FaceCullMode.Back); @@ -65,22 +62,27 @@ public PreDepthProcessor(AssetManager assetManager){ forcedRS.setDepthWrite(false); } + @Override public void initialize(RenderManager rm, ViewPort vp) { this.rm = rm; this.vp = vp; } + @Override public void reshape(ViewPort vp, int w, int h) { this.vp = vp; } + @Override public boolean isInitialized() { return vp != null; } + @Override public void preFrame(float tpf) { } + @Override public void postQueue(RenderQueue rq) { // lay depth first rm.setForcedMaterial(preDepth); @@ -90,17 +92,19 @@ public void postQueue(RenderQueue rq) { rm.setForcedRenderState(forcedRS); } + @Override public void postFrame(FrameBuffer out) { rm.setForcedRenderState(null); } + @Override public void cleanup() { vp = null; } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } } diff --git a/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java b/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java index 3a8ff16892..1fc437b758 100644 --- a/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java +++ b/jme3-core/src/main/java/com/jme3/post/SceneProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,10 +55,24 @@ public interface SceneProcessor { /** * Called when the resolution of the viewport has been changed. - * @param vp + * + * @param vp the affected ViewPort + * @param w the new width (in pixels) + * @param h the new height (in pixels) */ public void reshape(ViewPort vp, int w, int h); + /** + * Called when the scale of the viewport has been changed. + * + * @param vp the affected ViewPort + * @param x the new horizontal scale + * @param y the new vertical scale + */ + public default void rescale(ViewPort vp, float x, float y) { + + } + /** * @return True if initialize() has been called on this SceneProcessor, * false if otherwise. diff --git a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java index 480de08f43..e3efc8e3af 100644 --- a/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java +++ b/jme3-core/src/main/java/com/jme3/profile/AppProfiler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014 jMonkeyEngine + * Copyright (c) 2014-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ /** * Can be hooked into the application (and render manager) * to receive callbacks about specific frame steps. It is up - * to the specific implememtation to decide what to do with + * to the specific implementation to decide what to do with * the information. * * @author Paul Speed @@ -48,11 +48,15 @@ public interface AppProfiler { /** * Called at the beginning of the specified AppStep. + * + * @param step the application-level step that's about to begin */ public void appStep(AppStep step); /** * Called as a substep of the previous AppStep + * + * @param additionalInfo information about the substep */ public void appSubStep(String... additionalInfo); @@ -60,12 +64,19 @@ public interface AppProfiler { * Called at the beginning of the specified VpStep during * the rendering of the specified ViewPort. For bucket-specific * steps the Bucket parameter will be non-null. + * + * @param step the ViewPort-level step that's about to begin + * @param vp which ViewPort is being processed + * @param bucket which Bucket is being processed */ public void vpStep(VpStep step, ViewPort vp, Bucket bucket); /** * Called at the beginning of the specified SpStep (SceneProcessor step). * For more detailed steps it is possible to provide additional information as strings, like the name of the processor. + * + * @param step the SceneProcessor step that's about to begin + * @param additionalInfo information about the SceneProcessor step */ public void spStep(SpStep step, String... additionalInfo); } diff --git a/jme3-core/src/main/java/com/jme3/profile/SpStep.java b/jme3-core/src/main/java/com/jme3/profile/SpStep.java index 13d25d976d..6f33fa553f 100644 --- a/jme3-core/src/main/java/com/jme3/profile/SpStep.java +++ b/jme3-core/src/main/java/com/jme3/profile/SpStep.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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.profile; /** diff --git a/jme3-core/src/main/java/com/jme3/profile/package-info.java b/jme3-core/src/main/java/com/jme3/profile/package-info.java new file mode 100644 index 0000000000..19b1cb20b5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/profile/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * performance profiling + */ +package com.jme3.profile; diff --git a/jme3-core/src/main/java/com/jme3/renderer/Camera.java b/jme3-core/src/main/java/com/jme3/renderer/Camera.java index 79dd24059c..35f9b8d806 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Camera.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Camera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,24 +33,34 @@ import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingVolume; -import com.jme3.export.*; -import com.jme3.math.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.math.FastMath; +import com.jme3.math.Matrix4f; +import com.jme3.math.Plane; +import com.jme3.math.Quaternion; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; import com.jme3.util.TempVars; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; /** - * Camera is a standalone, purely mathematical class for doing + * A standalone, purely mathematical class for doing * camera-related computations. * - *

    - * Given input data such as location, orientation (direction, left, up), + *

    Given input data such as location, orientation (direction, left, up), * and viewport settings, it can compute data necessary to render objects * with the graphics library. Two matrices are generated, the view matrix * transforms objects from world space into eye space, while the projection - * matrix transforms objects from eye space into clip space. - *

    + * matrix transforms objects from eye space into clip space.

    + * *

    Another purpose of the camera class is to do frustum culling operations, * defined by six planes which define a 3D frustum shape, it is possible to * test if an object bounded by a mathematically defined volume is inside @@ -66,24 +76,23 @@ public class Camera implements Savable, Cloneable { private static final Logger logger = Logger.getLogger(Camera.class.getName()); /** - * The FrustumIntersect enum is returned as a result - * of a culling check operation, + * The result of a culling check operation. * see {@link #contains(com.jme3.bounding.BoundingVolume) } */ public enum FrustumIntersect { /** - * defines a constant assigned to spatials that are completely outside + * Defines a constant assigned to spatials that are completely outside * of this camera's view frustum. */ Outside, /** - * defines a constant assigned to spatials that are completely inside + * Defines a constant assigned to spatials that are completely inside * the camera's view frustum. */ Inside, /** - * defines a constant assigned to spatials that are intersecting one of + * Defines a constant assigned to spatials that are intersecting one of * the six planes that define the view frustum. */ Intersects; @@ -121,7 +130,7 @@ public enum FrustumIntersect { */ private static final int MAX_WORLD_PLANES = 6; /** - * Camera's location + * Camera's location. */ protected Vector3f location; /** @@ -152,31 +161,41 @@ public enum FrustumIntersect { * Distance from camera to bottom frustum plane. */ protected float frustumBottom; - //Temporary values computed in onFrustumChange that are needed if a - //call is made to onFrameChange. + /** + * Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange. + */ protected float[] coeffLeft; + /** + * Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange. + */ protected float[] coeffRight; + /** + * Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange. + */ protected float[] coeffBottom; + /** + * Temporary values computed in onFrustumChange that are needed if a call is made to onFrameChange. + */ protected float[] coeffTop; //view port coordinates /** * Percent value on display where horizontal viewing starts for this camera. - * Default is 0. + * Default is 0. Must be less than {@code viewPortRight}. */ protected float viewPortLeft; /** * Percent value on display where horizontal viewing ends for this camera. - * Default is 1. + * Default is 1. Must be greater than {@code viewPortLeft}. */ protected float viewPortRight; /** * Percent value on display where vertical viewing ends for this camera. - * Default is 1. + * Default is 1. Must be greater than {@code viewPortBottom}. */ protected float viewPortTop; /** * Percent value on display where vertical viewing begins for this camera. - * Default is 0. + * Default is 0. Must be less than {@code viewPortTop}. */ protected float viewPortBottom; /** @@ -188,17 +207,38 @@ public enum FrustumIntersect { * children. */ private int planeState; + /** + * The width of the viewport in pixels. + */ protected int width; + /** + * The height of the viewport in pixels. + */ protected int height; + /** + * True if the renderer needs to update its viewport boundaries. + */ protected boolean viewportChanged = true; /** * store the value for field parallelProjection */ private boolean parallelProjection = true; + /** + * Temporarily overrides the projection matrix. + */ protected Matrix4f projectionMatrixOverride = new Matrix4f(); private boolean overrideProjection; + /** + * Transforms world space into eye space. + */ protected Matrix4f viewMatrix = new Matrix4f(); + /** + * Transforms eye space into clip space, unless overridden by projectionMatrixOverride. + */ protected Matrix4f projectionMatrix = new Matrix4f(); + /** + * Transforms world space into clip space. + */ protected Matrix4f viewProjectionMatrix = new Matrix4f(); private BoundingBox guiBounding = new BoundingBox(); /** The camera's name. */ @@ -207,7 +247,7 @@ public enum FrustumIntersect { /** * Serialization only. Do not use. */ - public Camera() { + protected Camera() { worldPlane = new Plane[MAX_WORLD_PLANES]; for (int i = 0; i < MAX_WORLD_PLANES; i++) { worldPlane[i] = new Plane(); @@ -215,8 +255,11 @@ public Camera() { } /** - * Constructor instantiates a new Camera object. All + * Instantiates a new Camera object. All * values of the camera are set to default. + * + * @param width the desired width (in pixels) + * @param height the desired height (in pixels) */ public Camera(int width, int height) { this(); @@ -247,7 +290,9 @@ public Camera(int width, int height) { onViewPortChange(); onFrameChange(); - logger.log(Level.FINE, "Camera created (W: {0}, H: {1})", new Object[]{width, height}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Camera created (W: {0}, H: {1})", new Object[]{width, height}); + } } @Override @@ -286,15 +331,15 @@ public Camera clone() { throw new AssertionError(); } } - - /** - * This method copies the settings of the given camera. - * - * @param cam - * the camera we copy the settings from - */ + + /** + * Copies the settings of the given camera. + * + * @param cam + * the camera we copy the settings from + */ public void copyFrom(Camera cam) { - location.set(cam.location); + location.set(cam.location); rotation.set(cam.rotation); frustumNear = cam.frustumNear; @@ -320,18 +365,18 @@ public void copyFrom(Camera cam) { this.width = cam.width; this.height = cam.height; - + this.planeState = 0; this.viewportChanged = true; for (int i = 0; i < MAX_WORLD_PLANES; ++i) { worldPlane[i].setNormal(cam.worldPlane[i].getNormal()); worldPlane[i].setConstant(cam.worldPlane[i].getConstant()); } - + this.parallelProjection = cam.parallelProjection; this.overrideProjection = cam.overrideProjection; this.projectionMatrixOverride.set(cam.projectionMatrixOverride); - + this.viewMatrix.set(cam.viewMatrix); this.projectionMatrix.set(cam.projectionMatrix); this.viewProjectionMatrix.set(cam.viewProjectionMatrix); @@ -346,16 +391,18 @@ public void copyFrom(Camera cam) { } /** - * This method sets the cameras name. - * @param name the cameras name + * Sets the camera's name. + * + * @param name the camera's name */ public void setName(String name) { this.name = name; } /** - * This method returns the cameras name. - * @return the cameras name + * Returns the camera's name. + * + * @return the camera's name */ public String getName() { return name; @@ -364,21 +411,24 @@ public String getName() { /** * Sets a clipPlane for this camera. * The clipPlane is used to recompute the - * projectionMatrix using the plane as the near plane + * projectionMatrix using the plane as the near plane * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel * more info here *

    * - * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket. + *

    Note that this will work properly only if it's called on each update, + * and be aware that it won't work properly with the sky bucket. * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java + * * @param clipPlane the plane * @param side the side the camera stands from the plane */ - public void setClipPlane(Plane clipPlane, Plane.Side side) { + public void setClipPlane(Plane clipPlane, Plane.Side side) { float sideFactor = 1; if (side == Plane.Side.Negative) { sideFactor = -1; @@ -387,9 +437,9 @@ public void setClipPlane(Plane clipPlane, Plane.Side side) { if (clipPlane.whichSide(location) == side) { return; } - + TempVars vars = TempVars.get(); - try { + try { Matrix4f p = projectionMatrixOverride.set(projectionMatrix); Matrix4f ivm = viewMatrix; @@ -397,18 +447,20 @@ public void setClipPlane(Plane clipPlane, Plane.Side side) { Vector3f point = clipPlane.getNormal().mult(clipPlane.getConstant(), vars.vect1); Vector3f pp = ivm.mult(point, vars.vect2); Vector3f pn = ivm.multNormal(clipPlane.getNormal(), vars.vect3); - Vector4f clipPlaneV = vars.vect4f1.set(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, -(pp.dot(pn)) * sideFactor); - + Vector4f clipPlaneV = vars.vect4f1.set(pn.x * sideFactor, pn.y * sideFactor, pn.z * sideFactor, + -(pp.dot(pn)) * sideFactor); + Vector4f v = vars.vect4f2.set(0, 0, 0, 0); - + v.x = (Math.signum(clipPlaneV.x) + p.m02) / p.m00; v.y = (Math.signum(clipPlaneV.y) + p.m12) / p.m11; v.z = -1.0f; v.w = (1.0f + p.m22) / p.m23; - - float dot = clipPlaneV.dot(v);//clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w; + + float dot = clipPlaneV.dot(v); + //clipPlaneV.x * v.x + clipPlaneV.y * v.y + clipPlaneV.z * v.z + clipPlaneV.w * v.w; Vector4f c = clipPlaneV.multLocal(2.0f / dot); - + p.m20 = c.x - p.m30; p.m21 = c.y - p.m31; p.m22 = c.z - p.m32; @@ -416,7 +468,7 @@ public void setClipPlane(Plane clipPlane, Plane.Side side) { setProjectionMatrix(p); } finally { vars.release(); - } + } } /** @@ -425,14 +477,18 @@ public void setClipPlane(Plane clipPlane, Plane.Side side) { * This technique is known as the oblique near-plane clipping method introduced by Eric Lengyel * more info here *

    * - * Note that this will work properly only if it's called on each update, and be aware that it won't work properly with the sky bucket. + *

    Note that this will work properly only if it's called on each update, + * and be aware that it won't work properly with the sky bucket. * if you want to handle the sky bucket, look at how it's done in SimpleWaterProcessor.java + * * @param clipPlane the plane */ public void setClipPlane(Plane clipPlane) { @@ -440,14 +496,14 @@ public void setClipPlane(Plane clipPlane) { } /** - * Resize this camera's view for the specified display size. Invoked by an + * Resizes this camera's view for the specified display size. Invoked by an * associated {@link RenderManager} to notify the camera of changes to the * display dimensions. * * @param width the new width of the display, in pixels * @param height the new height of the display, in pixels * @param fixAspect if true, recompute the camera's frustum to preserve its - * prior aspect ratio + * prior aspect ratio */ public void resize(int width, int height, boolean fixAspect) { this.width = width; @@ -465,7 +521,7 @@ public void resize(int width, int height, boolean fixAspect) { } /** - * getFrustumBottom returns the value of the bottom frustum + * Returns the value of the bottom frustum * plane. * * @return the value of the bottom frustum plane. @@ -475,7 +531,7 @@ public float getFrustumBottom() { } /** - * setFrustumBottom sets the value of the bottom frustum + * Sets the value of the bottom frustum * plane. * * @param frustumBottom the value of the bottom frustum plane. @@ -486,7 +542,7 @@ public void setFrustumBottom(float frustumBottom) { } /** - * getFrustumFar gets the value of the far frustum plane. + * Gets the value of the far frustum plane. * * @return the value of the far frustum plane. */ @@ -495,7 +551,7 @@ public float getFrustumFar() { } /** - * setFrustumFar sets the value of the far frustum plane. + * Sets the value of the far frustum plane. * * @param frustumFar the value of the far frustum plane. */ @@ -505,7 +561,7 @@ public void setFrustumFar(float frustumFar) { } /** - * getFrustumLeft gets the value of the left frustum plane. + * Gets the value of the left frustum plane. * * @return the value of the left frustum plane. */ @@ -514,7 +570,7 @@ public float getFrustumLeft() { } /** - * setFrustumLeft sets the value of the left frustum plane. + * Sets the value of the left frustum plane. * * @param frustumLeft the value of the left frustum plane. */ @@ -524,7 +580,7 @@ public void setFrustumLeft(float frustumLeft) { } /** - * getFrustumNear gets the value of the near frustum plane. + * Gets the value of the near frustum plane. * * @return the value of the near frustum plane. */ @@ -533,7 +589,7 @@ public float getFrustumNear() { } /** - * setFrustumNear sets the value of the near frustum plane. + * Sets the value of the near frustum plane. * * @param frustumNear the value of the near frustum plane. */ @@ -543,7 +599,7 @@ public void setFrustumNear(float frustumNear) { } /** - * getFrustumRight gets the value of the right frustum plane. + * Gets the value of the right frustum plane. * * @return frustumRight the value of the right frustum plane. */ @@ -552,7 +608,7 @@ public float getFrustumRight() { } /** - * setFrustumRight sets the value of the right frustum plane. + * Sets the value of the right frustum plane. * * @param frustumRight the value of the right frustum plane. */ @@ -562,7 +618,7 @@ public void setFrustumRight(float frustumRight) { } /** - * getFrustumTop gets the value of the top frustum plane. + * Gets the value of the top frustum plane. * * @return the value of the top frustum plane. */ @@ -571,7 +627,7 @@ public float getFrustumTop() { } /** - * setFrustumTop sets the value of the top frustum plane. + * Sets the value of the top frustum plane. * * @param frustumTop the value of the top frustum plane. */ @@ -581,7 +637,55 @@ public void setFrustumTop(float frustumTop) { } /** - * getLocation retrieves the location vector of the camera. + * Obtains field of view when the camera is in perspective mode. + * + * @return Frame of view angle along the Y in degrees, or 0 if the camera is in orthogonal mode. + */ + public float getFov() { + if (!this.parallelProjection) { + float fovY = frustumTop / frustumNear; + fovY = FastMath.atan(fovY); + fovY /= 0.5F * FastMath.DEG_TO_RAD; + return fovY; + } + return 0; + } + + /** + * Sets the field of view when the camera is in perspective mode. Note that this method has no + * effect when the camera is in orthogonal mode. + * + * @param fovY Frame of view angle along the Y in degrees. This must be greater than 0. + */ + public void setFov(float fovY) { + if (fovY <= 0) { + throw new IllegalArgumentException("Field of view must be greater than 0"); + } + if (this.parallelProjection) { + throw new IllegalArgumentException("Cannot set field of view on orthogonal camera"); + } + float h = FastMath.tan(fovY * FastMath.DEG_TO_RAD * .5f) * frustumNear; + float w = h * getAspect(); + frustumLeft = -w; + frustumRight = w; + frustumBottom = -h; + frustumTop = h; + onFrustumChange(); + } + + /** + * Obtains the aspect ratio. + * + * @return Width:Height ratio. + */ + public float getAspect() { + float h = height * (viewPortTop - viewPortBottom); + float w = width * (viewPortRight - viewPortLeft); + return w / h; + } + + /** + * Retrieves the location vector of the camera. * * @return the position of the camera. * @see Camera#getLocation() @@ -591,7 +695,7 @@ public Vector3f getLocation() { } /** - * getRotation retrieves the rotation quaternion of the camera. + * Retrieves the rotation quaternion of the camera. * * @return the rotation of the camera. */ @@ -600,7 +704,7 @@ public Quaternion getRotation() { } /** - * getDirection retrieves the direction vector the camera is + * Retrieves the direction vector the camera is * facing. * * @return the direction the camera is facing. @@ -611,7 +715,7 @@ public Vector3f getDirection() { } /** - * getLeft retrieves the left axis of the camera. + * Retrieves the left axis of the camera. * * @return the left axis of the camera. * @see Camera#getLeft() @@ -621,7 +725,7 @@ public Vector3f getLeft() { } /** - * getUp retrieves the up axis of the camera. + * Retrieves the up axis of the camera. * * @return the up axis of the camera. * @see Camera#getUp() @@ -631,9 +735,10 @@ public Vector3f getUp() { } /** - * getDirection retrieves the direction vector the camera is + * Retrieves the direction vector the camera is * facing. * + * @param store storage for the result (modified if not null) * @return the direction the camera is facing. * @see Camera#getDirection() */ @@ -642,8 +747,9 @@ public Vector3f getDirection(Vector3f store) { } /** - * getLeft retrieves the left axis of the camera. + * Retrieves the left axis of the camera. * + * @param store storage for the result (modified if not null) * @return the left axis of the camera. * @see Camera#getLeft() */ @@ -652,8 +758,9 @@ public Vector3f getLeft(Vector3f store) { } /** - * getUp retrieves the up axis of the camera. + * Retrieves the up axis of the camera. * + * @param store storage for the result (modified if not null) * @return the up axis of the camera. * @see Camera#getUp() */ @@ -662,7 +769,7 @@ public Vector3f getUp(Vector3f store) { } /** - * setLocation sets the position of the camera. + * Sets the position of the camera. * * @param location the position of the camera. */ @@ -672,7 +779,7 @@ public void setLocation(Vector3f location) { } /** - * setRotation sets the orientation of this camera. This will + * Sets the orientation of this camera. This will * be equivalent to setting each of the axes: *
    * cam.setLeft(rotation.getRotationColumn(0));
    @@ -688,10 +795,12 @@ public void setRotation(Quaternion rotation) { } /** - * lookAtDirection sets the direction the camera is facing + * Sets the direction the camera is facing * given a direction and an up vector. * * @param direction the direction this camera is facing. + * @param up the desired "up" direction for the camera (not null, + * unaffected, typically (0,1,0)) */ public void lookAtDirection(Vector3f direction, Vector3f up) { this.rotation.lookAt(direction, up); @@ -699,14 +808,14 @@ public void lookAtDirection(Vector3f direction, Vector3f up) { } /** - * setAxes sets the axes (left, up and direction) for this + * Sets the axes (left, up and direction) for this * camera. * * @param left the left axis of the camera. * @param up the up axis of the camera. * @param direction the direction the camera is facing. - * - * @see Camera#setAxes(com.jme3.math.Quaternion) + * + * @see Camera#setAxes(com.jme3.math.Quaternion) */ public void setAxes(Vector3f left, Vector3f up, Vector3f direction) { this.rotation.fromAxes(left, up, direction); @@ -714,7 +823,7 @@ public void setAxes(Vector3f left, Vector3f up, Vector3f direction) { } /** - * setAxes uses a rotational matrix to set the axes of the + * Uses a rotational matrix to set the axes of the * camera. * * @param axes the matrix that defines the orientation of the camera. @@ -725,7 +834,7 @@ public void setAxes(Quaternion axes) { } /** - * normalize normalizes the camera vectors. + * Normalizes the camera vectors. */ public void normalize() { this.rotation.normalizeLocal(); @@ -733,7 +842,7 @@ public void normalize() { } /** - * setFrustum sets the frustum of this camera object. + * Sets the frustum of this camera object. * * @param near the near plane. * @param far the far plane. @@ -757,7 +866,7 @@ public void setFrustum(float near, float far, float left, float right, } /** - * setFrustumPerspective defines the frustum for the camera. This + * Defines the frustum for the camera. This * frustum is defined by a viewing angle, aspect ratio, and near/far planes * * @param fovY Frame of view angle along the Y in degrees. @@ -789,7 +898,7 @@ public void setFrustumPerspective(float fovY, float aspect, float near, } /** - * setFrame sets the orientation and location of the camera. + * Sets the orientation and location of the camera. * * @param location the point position of the camera. * @param left the left axis of the camera. @@ -807,7 +916,7 @@ public void setFrame(Vector3f location, Vector3f left, Vector3f up, } /** - * lookAt is a convenience method for auto-setting the frame + * A convenience method for auto-setting the frame * based on a world position the user desires the camera to look at. It * repoints the camera towards the given position using the difference * between the position and the current camera location as a direction @@ -849,8 +958,8 @@ public void lookAt(Vector3f pos, Vector3f worldUpVector) { } /** - * setFrame sets the orientation and location of the camera. - * + * Sets the orientation and location of the camera. + * * @param location * the point position of the camera. * @param axes @@ -863,7 +972,7 @@ public void setFrame(Vector3f location, Quaternion axes) { } /** - * update updates the camera parameters by calling + * Updates the camera parameters by calling * onFrustumChange,onViewPortChange and * onFrameChange. * @@ -877,7 +986,7 @@ public void update() { } /** - * getPlaneState returns the state of the frustum planes. So + * Returns the state of the frustum planes. So * checks can be made as to which frustum plane has been examined for * culling thus far. * @@ -888,7 +997,7 @@ public int getPlaneState() { } /** - * setPlaneState sets the state to keep track of tested + * Sets the state to keep track of tested * planes for culling. * * @param planeState the updated state. @@ -898,7 +1007,7 @@ public void setPlaneState(int planeState) { } /** - * getViewPortLeft gets the left boundary of the viewport + * Gets the left boundary of the viewport. * * @return the left boundary of the viewport */ @@ -907,9 +1016,10 @@ public float getViewPortLeft() { } /** - * setViewPortLeft sets the left boundary of the viewport + * Sets the left boundary of the viewport. * - * @param left the left boundary of the viewport + * @param left the left boundary of the viewport (<viewPortRight, + * default: 0) */ public void setViewPortLeft(float left) { viewPortLeft = left; @@ -917,7 +1027,7 @@ public void setViewPortLeft(float left) { } /** - * getViewPortRight gets the right boundary of the viewport + * Gets the right boundary of the viewport. * * @return the right boundary of the viewport */ @@ -926,9 +1036,10 @@ public float getViewPortRight() { } /** - * setViewPortRight sets the right boundary of the viewport + * Sets the right boundary of the viewport. * - * @param right the right boundary of the viewport + * @param right the right boundary of the viewport (>viewPortLeft, + * default: 1) */ public void setViewPortRight(float right) { viewPortRight = right; @@ -936,7 +1047,7 @@ public void setViewPortRight(float right) { } /** - * getViewPortTop gets the top boundary of the viewport + * Gets the top boundary of the viewport. * * @return the top boundary of the viewport */ @@ -945,9 +1056,10 @@ public float getViewPortTop() { } /** - * setViewPortTop sets the top boundary of the viewport + * Sets the top boundary of the viewport. * - * @param top the top boundary of the viewport + * @param top the top boundary of the viewport (>viewPortBottom, + * default: 1) */ public void setViewPortTop(float top) { viewPortTop = top; @@ -955,7 +1067,7 @@ public void setViewPortTop(float top) { } /** - * getViewPortBottom gets the bottom boundary of the viewport + * Gets the bottom boundary of the viewport. * * @return the bottom boundary of the viewport */ @@ -964,9 +1076,10 @@ public float getViewPortBottom() { } /** - * setViewPortBottom sets the bottom boundary of the viewport + * Sets the bottom boundary of the viewport. * - * @param bottom the bottom boundary of the viewport + * @param bottom the bottom boundary of the viewport (<viewPortTop, + * default: 0) */ public void setViewPortBottom(float bottom) { viewPortBottom = bottom; @@ -974,12 +1087,12 @@ public void setViewPortBottom(float bottom) { } /** - * setViewPort sets the boundaries of the viewport + * Sets the boundaries of the viewport. * - * @param left the left boundary of the viewport (default: 0) - * @param right the right boundary of the viewport (default: 1) - * @param bottom the bottom boundary of the viewport (default: 0) - * @param top the top boundary of the viewport (default: 1) + * @param left the left boundary of the viewport (<right, default: 0) + * @param right the right boundary of the viewport (>left, default: 1) + * @param bottom the bottom boundary of the viewport (<top, default: 0) + * @param top the top boundary of the viewport (>bottom, default: 1) */ public void setViewPort(float left, float right, float bottom, float top) { this.viewPortLeft = left; @@ -1000,19 +1113,19 @@ public float distanceToNearPlane(Vector3f pos) { } /** - * contains tests a bounding volume against the planes of the + * Tests a bounding volume against the planes of the * camera's frustum. The frustum's planes are set such that the normals all * face in towards the viewable scene. Therefore, if the bounding volume is * on the negative side of the plane is can be culled out. * * NOTE: This method is used internally for culling, for public usage, - * the plane state of the bounding volume must be saved and restored, e.g: + * the plane state of the camera must be saved and restored, e.g: * BoundingVolume bv;
    * Camera c;
    - * int planeState = bv.getPlaneState();
    - * bv.setPlaneState(0);
    + * int planeState = c.getPlaneState();
    + * c.setPlaneState(0);
    * c.contains(bv);
    - * bv.setPlaneState(plateState);
    + * c.setPlaneState(plateState);
    *
    * * @param bound the bound to check for culling @@ -1053,13 +1166,20 @@ public FrustumIntersect contains(BoundingVolume bound) { return rVal; } - + + /** + * Provides access to one of the planes used for culling. + * + * @param planeId the index of the Plane to access (0→left, 1→right, 2→bottom, 3→top, + * 4→far, 5→near) + * @return the pre-existing instance + */ public Plane getWorldPlane(int planeId) { return worldPlane[planeId]; } - + /** - * containsGui tests a bounding volume against the ortho + * Tests a bounding volume against the ortho * bounding box of the camera. A bounding box spanning from * 0, 0 to Width, Height. Constrained by the viewport settings on the * camera. @@ -1075,8 +1195,11 @@ public boolean containsGui(BoundingVolume bound) { } /** + * Provides access to the view matrix. + * * @return the view matrix of the camera. - * The view matrix transforms world space into eye space. + * + *

    The view matrix transforms world space into eye space. * This matrix is usually defined by the position and * orientation of the camera. */ @@ -1089,22 +1212,25 @@ public Matrix4f getViewMatrix() { * use the matrix for computing the view projection matrix as well. * Use null argument to return to normal functionality. * - * @param projMatrix + * @param projMatrix the desired projection matrix (unaffected) or null + * to cease the override */ public void setProjectionMatrix(Matrix4f projMatrix) { if (projMatrix == null) { overrideProjection = false; - projectionMatrixOverride.loadIdentity(); + projectionMatrixOverride.loadIdentity(); } else { - overrideProjection = true; + overrideProjection = true; projectionMatrixOverride.set(projMatrix); - } + } updateViewProjection(); } /** + * Provides access to the projection matrix. * @return the projection matrix of the camera. - * The view projection matrix transforms eye space into clip space. + * + *

    The view projection matrix transforms eye space into clip space. * This matrix is usually defined by the viewport and perspective settings * of the camera. */ @@ -1129,18 +1255,22 @@ public void updateViewProjection() { } /** - * @return The result of multiplying the projection matrix by the view - * matrix. This matrix is required for rendering an object. It is - * precomputed so as to not compute it every time an object is rendered. + * Provides access to the view projection matrix. + * + * @return The result of multiplying the projection matrix by the view + * matrix. This matrix is required for rendering an object. It is + * precomputed to avoid computing it every time an object is rendered. */ public Matrix4f getViewProjectionMatrix() { return viewProjectionMatrix; } /** - * @return True if the viewport (width, height, left, right, bottom, up) + * Tests whether the viewport (width, height, left, right, bottom, up) * has been changed. This is needed in the renderer so that the proper * viewport can be set-up. + * + * @return true if changed, otherwise false */ public boolean isViewportChanged() { return viewportChanged; @@ -1158,6 +1288,15 @@ public void clearViewportChanged() { * Called when the viewport has been changed. */ public void onViewPortChange() { + if (!(viewPortBottom < viewPortTop)) { + throw new IllegalArgumentException( + "Viewport must have bottom < top"); + } + if (!(viewPortLeft < viewPortRight)) { + throw new IllegalArgumentException( + "Viewport must have left < right"); + } + viewportChanged = true; setGuiBounding(); } @@ -1176,7 +1315,7 @@ private void setGuiBounding() { } /** - * onFrustumChange updates the frustum to reflect any changes + * Updates the frustum to reflect any changes * made to the planes. The new frustum values are kept in a temporary * location for use when calculating the new frame. The projection * matrix is updated to reflect the current values of the frustum. @@ -1218,7 +1357,8 @@ public void onFrustumChange() { coeffTop[1] = 0; } - projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight, frustumTop, frustumBottom, parallelProjection); + projectionMatrix.fromFrustum(frustumNear, frustumFar, frustumLeft, frustumRight, + frustumTop, frustumBottom, parallelProjection); // projectionMatrix.transposeLocal(); // The frame is affected by the frustum values @@ -1227,11 +1367,11 @@ public void onFrustumChange() { } /** - * onFrameChange updates the view frame of the camera. + * Updates the view frame of the camera. */ public void onFrameChange() { TempVars vars = TempVars.get(); - + Vector3f left = getLeft(vars.vect1); Vector3f direction = getDirection(vars.vect2); Vector3f up = getUp(vars.vect3); @@ -1291,15 +1431,17 @@ public void onFrameChange() { worldPlane[NEAR_PLANE].setConstant(dirDotLocation + frustumNear); viewMatrix.fromFrame(location, direction, up, left); - + vars.release(); - + // viewMatrix.transposeLocal(); updateViewProjection(); } /** - * @return true if parallel projection is enable, false if in normal perspective mode + * Determines whether the projection is parallel or perspective. + * + * @return true if parallel projection is enabled, false if in normal perspective mode * @see #setParallelProjection(boolean) */ public boolean isParallelProjection() { @@ -1307,9 +1449,10 @@ public boolean isParallelProjection() { } /** - * Enable/disable parallel projection. + * Enables/disables parallel projection. * - * @param value true to set up this camera for parallel projection is enable, false to enter normal perspective mode + * @param value true to set up this camera for parallel projection, + * false to enter perspective mode */ public void setParallelProjection(final boolean value) { this.parallelProjection = value; @@ -1317,10 +1460,11 @@ public void setParallelProjection(final boolean value) { } /** - * Computes the z value in projection space from the z value in view space - * Note that the returned value is going non linearly from 0 to 1. - * for more explanations on non linear z buffer see + * Computes the z value in projection space from the z value in view space + * Note that the returned value goes non-linearly from 0 to 1. + * For more explanation of non-linear z buffer see * http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html + * * @param viewZPos the z value in view space. * @return the z value in projection space. */ @@ -1334,13 +1478,14 @@ public float getViewToProjectionZ(float viewZPos) { /** * Computes a position in World space given a screen position in screen space (0,0 to width, height) - * and a z position in projection space ( 0 to 1 non linear). - * This former value is also known as the Z buffer value or non linear depth buffer. - * for more explanations on non linear z buffer see + * and a z position in projection space ( 0 to 1 non-linear). + * This former value is also known as the Z buffer value or non-linear depth buffer. + * For more explanation of non-linear Z buffer see * http://www.sjbaker.org/steve/omniv/love_your_z_buffer.html - * - * To compute the projection space z from the view space z (distance from cam to object) @see Camera#getViewToProjectionZ - * + * + *

    To compute the projection space z from the view space z (distance from cam to object) + * @see Camera#getViewToProjectionZ + * * @param screenPos 2d coordinate in screen space * @param projectionZPos non linear z value in projection space * @return the position in world space. @@ -1350,6 +1495,13 @@ public Vector3f getWorldCoordinates(Vector2f screenPos, float projectionZPos) { } /** + * Converts the given position from screen space to world space. + * + * @param screenPosition a (2-D) location in screen space (not null) + * @param projectionZPos a (non-linear) Z value in projection space + * @param store storage for the result (modified if not null) + * @return a location vector (in world coordinates, either + * store or a new vector) * @see Camera#getWorldCoordinates */ public Vector3f getWorldCoordinates(Vector2f screenPosition, @@ -1357,7 +1509,7 @@ public Vector3f getWorldCoordinates(Vector2f screenPosition, if (store == null) { store = new Vector3f(); } - + Matrix4f inverseMat = new Matrix4f(viewProjectionMatrix); inverseMat.invertLocal(); @@ -1366,7 +1518,7 @@ public Vector3f getWorldCoordinates(Vector2f screenPosition, (screenPosition.y / getHeight() - viewPortBottom) / (viewPortTop - viewPortBottom) * 2 - 1, projectionZPos * 2 - 1); - float w = inverseMat.multProj(store, store); + float w = inverseMat.multProj(store, store); store.multLocal(1f / w); return store; @@ -1374,7 +1526,9 @@ public Vector3f getWorldCoordinates(Vector2f screenPosition, /** * Converts the given position from world space to screen space. - * + * + * @param worldPos a location in world coordinates (not null, unaffected) + * @return a new (3-D) location vector (in screen coordinates) * @see Camera#getScreenCoordinates */ public Vector3f getScreenCoordinates(Vector3f worldPos) { @@ -1384,6 +1538,10 @@ public Vector3f getScreenCoordinates(Vector3f worldPos) { /** * Converts the given position from world space to screen space. * + * @param worldPosition a location in world coordinates (not null, unaffected) + * @param store storage for the result (modified if not null) + * @return a (3-D) location vector (in screen coordinates, either + * store or a new vector) * @see Camera#getScreenCoordinates(Vector3f, Vector3f) */ public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { @@ -1396,8 +1554,10 @@ public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { // tmp_quat.set( worldPosition.x, worldPosition.y, worldPosition.z, 1 ); // viewProjectionMatrix.mult(tmp_quat, tmp_quat); // tmp_quat.multLocal( 1.0f / tmp_quat.getW() ); -// store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) * getWidth(); -// store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) * getHeight(); +// store.x = ( ( tmp_quat.getX() + 1 ) * ( viewPortRight - viewPortLeft ) / 2 + viewPortLeft ) +// * getWidth(); +// store.y = ( ( tmp_quat.getY() + 1 ) * ( viewPortTop - viewPortBottom ) / 2 + viewPortBottom ) +// * getHeight(); // store.z = ( tmp_quat.getZ() + 1 ) / 2; // vars.release(); @@ -1412,6 +1572,39 @@ public Vector3f getScreenCoordinates(Vector3f worldPosition, Vector3f store) { } /** + * Returns a ray going from camera through a screen point. + *

    + * Resulting ray is in world space, starting on the near plane + * of the camera and going through position's (x,y) pixel coordinates on the screen. + * + * @param pos A {@link Vector2f} representing the 2D screen coordinates (in pixels) + * @return A {@link Ray} object representing the picking ray in world coordinates. + * + *

    {@code
    +     * // Usage Example:
    +     * Ray pickingRay = cam.screenPointToRay(inputManager.getCursorPosition());
    +     * }
    + */ + public Ray screenPointToRay(Vector2f pos) { + TempVars vars = TempVars.get(); + Vector3f nearPoint = vars.vect1; + Vector3f farPoint = vars.vect2; + + // Get the world coordinates for the near and far points + getWorldCoordinates(pos, 0, nearPoint); + getWorldCoordinates(pos, 1, farPoint); + + // Calculate direction and normalize + Vector3f direction = farPoint.subtractLocal(nearPoint).normalizeLocal(); + Ray ray = new Ray(nearPoint, direction); + + vars.release(); + return ray; + } + + /** + * Returns the display width. + * * @return the width/resolution of the display. */ public int getWidth() { @@ -1419,6 +1612,8 @@ public int getWidth() { } /** + * Returns the display height. + * * @return the height/resolution of the display. */ public int getHeight() { @@ -1427,11 +1622,13 @@ public int getHeight() { @Override public String toString() { - return "Camera[location=" + location + "\n, direction=" + getDirection() + "\n" + return "Camera[location=" + location + "\n" + + "direction=" + getDirection() + "\n" + "res=" + width + "x" + height + ", parallel=" + parallelProjection + "\n" + "near=" + frustumNear + ", far=" + frustumFar + "]"; } + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(location, "location", Vector3f.ZERO); @@ -1455,8 +1652,9 @@ public void write(JmeExporter e) throws IOException { capsule.write(name, "name", null); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); location = (Vector3f) capsule.readSavable("location", Vector3f.ZERO.clone()); rotation = (Quaternion) capsule.readSavable("rotation", Quaternion.DIRECTION_Z.clone()); frustumNear = capsule.readFloat("frustumNear", 1); diff --git a/jme3-core/src/main/java/com/jme3/renderer/Caps.java b/jme3-core/src/main/java/com/jme3/renderer/Caps.java index 7bda5f4ae8..647d44a9a6 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Caps.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Caps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,56 +41,41 @@ import java.util.Collection; /** - * Caps is an enum specifying a capability that the {@link Renderer} + * Specifies a capability that the {@link Renderer} * supports. - * + * * @author Kirill Vainer */ public enum Caps { /** * Supports {@link FrameBuffer FrameBuffers}. - *

    - * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    - * OpenGL ES: Renderer supports OpenGL ES 2.0. - *//** - * Supports {@link FrameBuffer FrameBuffers}. - *

    - * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    - * OpenGL ES: Renderer supports OpenGL ES 2.0. - *//** - * Supports {@link FrameBuffer FrameBuffers}. - *

    - * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    - * OpenGL ES: Renderer supports OpenGL ES 2.0. - *//** - * Supports {@link FrameBuffer FrameBuffers}. - *

    - * OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    + * + *

    OpenGL: Renderer exposes the GL_EXT_framebuffer_object extension.
    * OpenGL ES: Renderer supports OpenGL ES 2.0. */ FrameBuffer, /** - * Supports framebuffer Multiple Render Targets (MRT) - *

    - * OpenGL: Renderer exposes the GL_ARB_draw_buffers extension + * Supports framebuffer Multiple Render Targets (MRT). + * + *

    OpenGL: Renderer exposes the GL_ARB_draw_buffers extension */ FrameBufferMRT, /** - * Supports framebuffer multi-sampling - *

    - * OpenGL: Renderer exposes the GL EXT framebuffer multisample extension
    + * Supports framebuffer multi-sampling. + * + *

    OpenGL: Renderer exposes the GL EXT framebuffer multisample extension
    * OpenGL ES: Renderer exposes GL_APPLE_framebuffer_multisample or * GL_ANGLE_framebuffer_multisample. */ FrameBufferMultisample, /** - * Supports texture multi-sampling - *

    - * OpenGL: Renderer exposes the GL_ARB_texture_multisample extension
    + * Supports texture multi-sampling. + * + *

    OpenGL: Renderer exposes the GL_ARB_texture_multisample extension
    * OpenGL ES: Renderer exposes the GL_IMG_multisampled_render_to_texture * extension. */ @@ -100,118 +85,118 @@ public enum Caps { * Supports OpenGL 2.0 or OpenGL ES 2.0. */ OpenGL20, - + /** - * Supports OpenGL 2.1 + * Supports OpenGL 2.1. */ OpenGL21, - + /** - * Supports OpenGL 3.0 + * Supports OpenGL 3.0. */ OpenGL30, - + /** - * Supports OpenGL 3.1 + * Supports OpenGL 3.1. */ OpenGL31, - + /** - * Supports OpenGL 3.2 + * Supports OpenGL 3.2. */ OpenGL32, /** - * Supports OpenGL 3.3 + * Supports OpenGL 3.3. */ OpenGL33, /** - * Supports OpenGL 4.0 + * Supports OpenGL 4.0. */ OpenGL40, /** - * Supports OpenGL 4.1 + * Supports OpenGL 4.1. */ OpenGL41, /** - * Supports OpenGL 4.2 + * Supports OpenGL 4.2. */ OpenGL42, /** - * Supports OpenGL 4.3 + * Supports OpenGL 4.3. */ OpenGL43, /** - * Supports OpenGL 4.4 + * Supports OpenGL 4.4. */ OpenGL44, /** - * Supports OpenGL 4.5 + * Supports OpenGL 4.5. */ OpenGL45, /** * Do not use. - * + * * @deprecated do not use. */ @Deprecated Reserved0, - + /** - * Supports GLSL 1.0 + * Supports GLSL 1.0. */ GLSL100, - + /** - * Supports GLSL 1.1 + * Supports GLSL 1.1. */ GLSL110, - + /** - * Supports GLSL 1.2 + * Supports GLSL 1.2. */ GLSL120, - + /** - * Supports GLSL 1.3 + * Supports GLSL 1.3. */ GLSL130, - + /** - * Supports GLSL 1.4 + * Supports GLSL 1.4. */ GLSL140, - + /** - * Supports GLSL 1.5 + * Supports GLSL 1.5. */ GLSL150, - + /** - * Supports GLSL 3.3 + * Supports GLSL 3.3. */ GLSL330, /** - * Supports GLSL 4.0 + * Supports GLSL 4.0. */ GLSL400, /** - * Supports GLSL 4.1 + * Supports GLSL 4.1. */ GLSL410, /** - * Supports GLSL 4.2 + * Supports GLSL 4.2. */ GLSL420, /** - * Supports GLSL 4.3 + * Supports GLSL 4.3. */ GLSL430, /** - * Supports GLSL 4.4 + * Supports GLSL 4.4. */ GLSL440, /** - * Supports GLSL 4.5 + * Supports GLSL 4.5. */ GLSL450, /** @@ -224,69 +209,80 @@ public enum Caps { */ GeometryShader, /** - * Supports Tessellation shader + * Supports Tessellation shader. */ TesselationShader, /** - * Supports texture arrays + * Supports texture arrays. */ TextureArray, /** - * Supports texture buffers + * Supports texture buffers. */ TextureBuffer, /** - * Supports floating point and half textures (Format.RGB16F) + * Supports floating point and half textures (Format.RGB16F). */ FloatTexture, - + /** - * Supports integer textures + * Supports rendering on RGB floating point textures + */ + FloatColorBufferRGB, + + /** + * Supports rendering on RGBA floating point textures + */ + FloatColorBufferRGBA, + + /** + * Supports integer textures. */ IntegerTexture, - + /** - * Supports floating point FBO color buffers (Format.RGB16F) + * Supports floating point FBO color buffers (Format.RGB16F). */ FloatColorBuffer, + /** - * Supports floating point depth buffer + * Supports floating point depth buffer. */ FloatDepthBuffer, /** - * Supports Format.RGB111110F for textures + * Supports Format.RGB111110F for textures. */ PackedFloatTexture, /** - * Supports Format.RGB9E5 for textures + * Supports Format.RGB9E5 for textures. */ SharedExponentTexture, /** - * Supports Format.RGB111110F for FBO color buffers + * Supports Format.RGB111110F for FBO color buffers. */ PackedFloatColorBuffer, /** - * Supports Format.RGB9E5 for FBO color buffers + * Supports Format.RGB9E5 for FBO color buffers. */ SharedExponentColorBuffer, - + /** * Do not use. - * + * * @deprecated do not use. */ @Deprecated Reserved1, /** - * Supports Non-Power-Of-Two (NPOT) textures and framebuffers + * Supports Non-Power-Of-Two (NPOT) textures and framebuffers. */ NonPowerOfTwoTextures, @@ -296,104 +292,119 @@ public enum Caps { MeshInstancing, /** - * Supports VAO, or vertex buffer arrays + * Supports VAO, or vertex buffer arrays. */ VertexBufferArray, /** - * Supports multisampling on the screen + * Supports multisampling on the screen. */ Multisample, - + /** - * Supports FBO with Depth24Stencil8 image format + * Supports FBO with Depth24Stencil8 image format. */ PackedDepthStencilBuffer, - + /** - * Supports sRGB framebuffers and sRGB texture format + * Supports sRGB framebuffers and sRGB texture format. */ Srgb, - + /** * Supports blitting framebuffers. */ FrameBufferBlit, - + /** * Supports {@link Format#DXT1} and sister formats. */ TextureCompressionS3TC, - + /** * Supports anisotropic texture filtering. */ TextureFilterAnisotropic, - + /** * Supports {@link Format#ETC1} texture compression. */ TextureCompressionETC1, - + /** * Supports {@link Format#ETC1} texture compression by uploading * the texture as ETC2 (they are backwards compatible). */ TextureCompressionETC2, - + + /** + * Supports BPTC and sister formats. + */ + TextureCompressionBPTC, + + /** + * Supports {@link Format#RGTC1} and other RGTC compressed formats. + */ + TextureCompressionRGTC, + /** - * Supports OpenGL ES 2 + * Supports OpenGL ES 2. */ OpenGLES20, - + + /** + * Supports WebGL + */ + WebGL, + /** - * Supports RGB8 / RGBA8 textures + * Supports RGB8 / RGBA8 textures. */ Rgba8, - + /** * Supports depth textures. */ DepthTexture, - + /** * Supports 32-bit index buffers. */ IntegerIndexBuffer, - + /** * Partial support for non-power-of-2 textures, typically found * on OpenGL ES 2 devices. - *

    - * Use of NPOT textures is allowed iff: + * + *

    Use of NPOT textures is allowed iff: *

      - *
    • The {@link com.jme3.texture.Texture.WrapMode} is set to + *
    • The {@link com.jme3.texture.Texture.WrapMode} is set to * {@link com.jme3.texture.Texture.WrapMode#EdgeClamp}.
    • - *
    • Mip-mapping is not used, meaning + *
    • Mip-mapping is not used, meaning * {@link com.jme3.texture.Texture.MinFilter} is set to - * {@link com.jme3.texture.Texture.MinFilter#BilinearNoMipMaps} or + * {@link com.jme3.texture.Texture.MinFilter#BilinearNoMipMaps} or * {@link com.jme3.texture.Texture.MinFilter#NearestNoMipMaps}
    • *
    */ PartialNonPowerOfTwoTextures, - + /** - * When sampling cubemap edges, interpolate between the adjecent faces + * When sampling cubemap edges, interpolates between the adjacent faces * instead of just sampling one face. - *

    - * Improves the quality of environment mapping. + * + *

    Improves the quality of environment mapping. */ SeamlessCubemap, - + /** * Running with OpenGL 3.2+ core profile. - * + * * Compatibility features will not be available. */ CoreProfile, - + /** - * GPU can provide and accept binary shaders. + * GPU support for binary shaders. */ BinaryShader, /** @@ -406,67 +417,76 @@ public enum Caps { ShaderStorageBufferObject, /** - * Supports OpenGL ES 3.0 + * Supports OpenGL ES 3.0. */ OpenGLES30, /** - * Supports GLSL 3.0 + * Supports GLSL 3.0. */ GLSL300, /** - * Supports OpenGL ES 3.1 + * Supports OpenGL ES 3.1. */ OpenGLES31, /** - * Supports GLSL 3.1 + * Supports GLSL 3.1. */ GLSL310, /** - * Supports OpenGL ES 3.2 + * Supports OpenGL ES 3.2. */ OpenGLES32, /** - * Supports GLSL 3.2 + * Supports GLSL 3.2. */ GLSL320, /** - * Explicit support of depth 24 textures + * Explicit support of depth 24 textures. */ - Depth24, + Depth24, - - UnpackRowLength + /** + * Supports unpack row length (stride). + */ + UnpackRowLength, + + /** + * Supports debugging capabilities + */ + GLDebug ; /** * Returns true if given the renderer capabilities, the texture * can be supported by the renderer. - *

    - * This only checks the format of the texture, non-power-of-2 - * textures are scaled automatically inside the renderer + * + *

    This only checks the format of the texture, non-power-of-2 + * textures are scaled automatically inside the renderer * if are not supported natively. - * + * * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. * @param tex The texture to check * @return True if it is supported, false otherwise. */ - public static boolean supports(Collection caps, Texture tex){ + public static boolean supports(Collection caps, Texture tex) { if (tex.getType() == Texture.Type.TwoDimensionalArray - && !caps.contains(Caps.TextureArray)) + && !caps.contains(Caps.TextureArray)) { return false; + } Image img = tex.getImage(); - if (img == null) + if (img == null) { return true; + } Format fmt = img.getFormat(); - switch (fmt){ + switch (fmt) { case Depth24Stencil8: return caps.contains(Caps.PackedDepthStencilBuffer); case Depth32F: @@ -478,22 +498,25 @@ public static boolean supports(Collection caps, Texture tex){ case RGB9E5: return caps.contains(Caps.SharedExponentTexture); default: - if (fmt.isFloatingPont()) + if (fmt.isFloatingPont()) { return caps.contains(Caps.FloatTexture); - + } + return true; } } - - private static boolean supportsColorBuffer(Collection caps, RenderBuffer colorBuf){ + + private static boolean supportsColorBuffer(Collection caps, RenderBuffer colorBuf) { Format colorFmt = colorBuf.getFormat(); - if (colorFmt.isDepthFormat()) + if (colorFmt.isDepthFormat()) { return false; + } - if (colorFmt.isCompressed()) + if (colorFmt.isCompressed()) { return false; + } - switch (colorFmt){ + switch (colorFmt) { case RGB111110F: return caps.contains(Caps.PackedFloatColorBuffer); case RGB16F_to_RGB111110F: @@ -501,8 +524,9 @@ private static boolean supportsColorBuffer(Collection caps, RenderBuffer c case RGB9E5: return false; default: - if (colorFmt.isFloatingPont()) + if (colorFmt.isFloatingPont()) { return caps.contains(Caps.FloatColorBuffer); + } return true; } @@ -511,36 +535,40 @@ private static boolean supportsColorBuffer(Collection caps, RenderBuffer c /** * Returns true if given the renderer capabilities, the framebuffer * can be supported by the renderer. - * + * * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. * @param fb The framebuffer to check * @return True if it is supported, false otherwise. */ - public static boolean supports(Collection caps, FrameBuffer fb){ - if (!caps.contains(Caps.FrameBuffer)) + public static boolean supports(Collection caps, FrameBuffer fb) { + if (!caps.contains(Caps.FrameBuffer)) { return false; + } if (fb.getSamples() > 1 - && !caps.contains(Caps.FrameBufferMultisample)) + && !caps.contains(Caps.FrameBufferMultisample)) { return false; + } RenderBuffer depthBuf = fb.getDepthBuffer(); - if (depthBuf != null){ + if (depthBuf != null) { Format depthFmt = depthBuf.getFormat(); - if (!depthFmt.isDepthFormat()){ + if (!depthFmt.isDepthFormat()) { return false; - }else{ + } else { if (depthFmt == Format.Depth32F - && !caps.contains(Caps.FloatDepthBuffer)) + && !caps.contains(Caps.FloatDepthBuffer)) { return false; - + } + if (depthFmt == Format.Depth24Stencil8 - && !caps.contains(Caps.PackedDepthStencilBuffer)) + && !caps.contains(Caps.PackedDepthStencilBuffer)) { return false; + } } } - for (int i = 0; i < fb.getNumColorBuffers(); i++){ - if (!supportsColorBuffer(caps, fb.getColorBuffer(i))){ + for (int i = 0; i < fb.getNumColorBuffers(); i++) { + if (!supportsColorBuffer(caps, fb.getColorBuffer(i))) { return false; } } @@ -550,42 +578,81 @@ public static boolean supports(Collection caps, FrameBuffer fb){ /** * Returns true if given the renderer capabilities, the shader * can be supported by the renderer. - * + * * @param caps The collection of renderer capabilities {@link Renderer#getCaps() }. * @param shader The shader to check * @return True if it is supported, false otherwise. */ - public static boolean supports(Collection caps, Shader shader){ + public static boolean supports(Collection caps, Shader shader) { for (ShaderSource source : shader.getSources()) { if (source.getLanguage().startsWith("GLSL")) { int ver = Integer.parseInt(source.getLanguage().substring(4)); switch (ver) { case 100: - if (!caps.contains(Caps.GLSL100)) return false; + if (!caps.contains(Caps.GLSL100)) { + return false; + } + // fall through case 110: - if (!caps.contains(Caps.GLSL110)) return false; + if (!caps.contains(Caps.GLSL110)) { + return false; + } + // fall through case 120: - if (!caps.contains(Caps.GLSL120)) return false; + if (!caps.contains(Caps.GLSL120)) { + return false; + } + // fall through case 130: - if (!caps.contains(Caps.GLSL130)) return false; + if (!caps.contains(Caps.GLSL130)) { + return false; + } + // fall through case 140: - if (!caps.contains(Caps.GLSL140)) return false; + if (!caps.contains(Caps.GLSL140)) { + return false; + } + // fall through case 150: - if (!caps.contains(Caps.GLSL150)) return false; + if (!caps.contains(Caps.GLSL150)) { + return false; + } + // fall through case 330: - if (!caps.contains(Caps.GLSL330)) return false; + if (!caps.contains(Caps.GLSL330)) { + return false; + } + // fall through case 400: - if (!caps.contains(Caps.GLSL400)) return false; + if (!caps.contains(Caps.GLSL400)) { + return false; + } + // fall through case 410: - if (!caps.contains(Caps.GLSL410)) return false; + if (!caps.contains(Caps.GLSL410)) { + return false; + } + // fall through case 420: - if (!caps.contains(Caps.GLSL420)) return false; + if (!caps.contains(Caps.GLSL420)) { + return false; + } + // fall through case 430: - if (!caps.contains(Caps.GLSL430)) return false; + if (!caps.contains(Caps.GLSL430)) { + return false; + } + // fall through case 440: - if (!caps.contains(Caps.GLSL440)) return false; + if (!caps.contains(Caps.GLSL440)) { + return false; + } + // fall through case 450: - if (!caps.contains(Caps.GLSL450)) return false; + if (!caps.contains(Caps.GLSL450)) { + return false; + } + // fall through default: return false; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/IDList.java b/jme3-core/src/main/java/com/jme3/renderer/IDList.java index 70fea4218d..2f625fc6a3 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/IDList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/IDList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,19 +35,31 @@ /** * A specialized data-structure used to optimize state changes of "slot" - * based state. + * based state. */ public class IDList { + /** + * Indices belonging to the "new list". Valid data in elements 0 through newLen-1 only! + */ public int[] newList = new int[16]; + /** + * Indices belonging to the "old list". Valid data in elements 0 through oldLen-1 only! + */ public int[] oldList = new int[16]; + /** + * The number of valid elements in the new list. + */ public int newLen = 0; + /** + * The number of valid elements in the old list. + */ public int oldLen = 0; /** - * Reset all states to zero + * Resets all states to zero. */ - public void reset(){ + public void reset() { newLen = 0; oldLen = 0; Arrays.fill(newList, 0); @@ -59,24 +71,25 @@ public void reset(){ * If the index was not in the old list, false is returned, * if the index was in the old list, it is removed from the old * list and true is returned. - * + * * @param idx The index to move * @return True if it existed in old list and was removed - * from there, false otherwise. + * from there, false otherwise. */ - public boolean moveToNew(int idx){ - if (newLen == 0 || newList[newLen-1] != idx) + public boolean moveToNew(int idx) { + if (newLen == 0 || newList[newLen - 1] != idx) { // add item to newList first newList[newLen++] = idx; + } - // find idx in oldList, if removed successfuly, return true. - for (int i = 0; i < oldLen; i++){ - if (oldList[i] == idx){ - // found index in slot i - // delete index from old list - oldLen --; - for (int j = i; j < oldLen; j++){ - oldList[j] = oldList[j+1]; + // find idx in oldList, if removed successfully, return true. + for (int i = 0; i < oldLen; i++) { + if (oldList[i] == idx) { + // Found the index in slot i: + // delete the index from the old list. + oldLen--; + for (int j = i; j < oldLen; j++) { + oldList[j] = oldList[j + 1]; } return true; } @@ -87,32 +100,34 @@ public boolean moveToNew(int idx){ /** * Copies the new list to the old list, and clears the new list. */ - public void copyNewToOld(){ + public void copyNewToOld() { System.arraycopy(newList, 0, oldList, 0, newLen); oldLen = newLen; newLen = 0; } /** - * Prints the contents of the lists + * Prints the contents of the lists. */ - public void print(){ - if (newLen > 0){ + public void print() { + if (newLen > 0) { System.out.print("New List: "); - for (int i = 0; i < newLen; i++){ - if (i == newLen -1) + for (int i = 0; i < newLen; i++) { + if (i == newLen - 1) { System.out.println(newList[i]); - else - System.out.print(newList[i]+", "); + } else { + System.out.print(newList[i] + ", "); + } } } - if (oldLen > 0){ + if (oldLen > 0) { System.out.print("Old List: "); - for (int i = 0; i < oldLen; i++){ - if (i == oldLen -1) + for (int i = 0; i < oldLen; i++) { + if (i == oldLen - 1) { System.out.println(oldList[i]); - else - System.out.print(oldList[i]+", "); + } else { + System.out.print(oldList[i] + ", "); + } } } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Limits.java b/jme3-core/src/main/java/com/jme3/renderer/Limits.java index cd1bfec355..4e97843566 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Limits.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Limits.java @@ -32,10 +32,10 @@ package com.jme3.renderer; /** - * Limits allows querying the limits of certain features in + * Allows querying the limits of certain features in * {@link Renderer}. - *

    - * For example, maximum texture sizes or number of samples. + * + *

    For example, maximum texture sizes or number of samples. * * @author Kirill Vainer */ @@ -50,32 +50,104 @@ public enum Limits { * be used in the fragment shader. */ FragmentTextureUnits, + /** + * Maximum number of fragment uniform vectors. + */ FragmentUniformVectors, + /** + * Maximum number of vertex uniform vectors. + */ VertexUniformVectors, + /** + * Maximum number of vertex attributes. + */ VertexAttributes, + /** + * Maximum number of FrameBuffer samples. + */ FrameBufferSamples, + /** + * Maximum number of FrameBuffer attachments. + */ FrameBufferAttachments, + /** + * Maximum number of FrameBuffer MRT attachments. + */ FrameBufferMrtAttachments, + /** + * Maximum render buffer size. + */ RenderBufferSize, + /** + * Maximum texture size. + */ TextureSize, + /** + * Maximum cubemap size. + */ CubemapSize, + /** + * Maximum number of color texture samples. + */ ColorTextureSamples, + /** + * Maximum number of depth texture samples. + */ DepthTextureSamples, + /** + * Maximum degree of texture anisotropy. + */ TextureAnisotropy, // UBO + /** + * Maximum number of UBOs that may be accessed by a vertex shader. + */ UniformBufferObjectMaxVertexBlocks, + /** + * Maximum number of UBOs that may be accessed by a fragment shader. + */ UniformBufferObjectMaxFragmentBlocks, + /** + * Maximum number of UBOs that may be accessed by a geometry shader. + */ UniformBufferObjectMaxGeometryBlocks, + /** + * Maximum block size of a UBO. + */ UniformBufferObjectMaxBlockSize, // SSBO + /** + * Maximum size of an SSBO. + */ ShaderStorageBufferObjectMaxBlockSize, + /** + * Maximum number of active SSBOs that may be accessed by a vertex shader. + */ ShaderStorageBufferObjectMaxVertexBlocks, + /** + * Maximum number of active SSBOs that may be accessed by a fragment shader. + */ ShaderStorageBufferObjectMaxFragmentBlocks, + /** + * Maximum number of active SSBOs that may be accessed by a geometry shader. + */ ShaderStorageBufferObjectMaxGeometryBlocks, + /** + * Maximum number of active SSBOs that may be accessed by a tessellation control shader. + */ ShaderStorageBufferObjectMaxTessControlBlocks, + /** + * Maximum number of active SSBOs that may be accessed by a tessellation evaluation shader. + */ ShaderStorageBufferObjectMaxTessEvaluationBlocks, + /** + * Not implemented yet. + */ ShaderStorageBufferObjectMaxComputeBlocks, + /** + * Maximum number shader storage blocks across all active programs. + */ ShaderStorageBufferObjectMaxCombineBlocks, } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java index f7626f1bd5..ea6088afb9 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,293 +32,373 @@ package com.jme3.renderer; import com.jme3.material.RenderState; -import com.jme3.material.RenderState.BlendFunc; import com.jme3.math.ColorRGBA; -import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.shader.Shader; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; +import java.lang.ref.WeakReference; +import com.jme3.shader.bufferobject.BufferObject; /** * Represents the current state of the graphics library. This class is used * internally to reduce state changes. NOTE: This class is specific to OpenGL. */ public class RenderContext { + /** + * Number of texture units that JME supports. + */ + public static final int maxTextureUnits = 16; /** + * Number of buffer object units that JME supports. + */ + public static final int maxBufferObjectUnits = 8; + + /** + * Criteria for culling faces. + * * @see RenderState#setFaceCullMode(com.jme3.material.RenderState.FaceCullMode) */ - public RenderState.FaceCullMode cullMode = RenderState.FaceCullMode.Off; + public RenderState.FaceCullMode cullMode; /** - * @see RenderState#setDepthTest(boolean) + * Enables depth testing for color pixels. + * + * @see RenderState#setDepthTest(boolean) */ - public boolean depthTestEnabled = false; + public boolean depthTestEnabled; /** - * @see RenderState#setDepthWrite(boolean) + * Enables depth writing. + * + * @see RenderState#setDepthWrite(boolean) */ - public boolean depthWriteEnabled = true; + public boolean depthWriteEnabled; /** - * @see RenderState#setColorWrite(boolean) + * Enables color writing. + * + * @see RenderState#setColorWrite(boolean) */ - public boolean colorWriteEnabled = true; + public boolean colorWriteEnabled; /** - * @see Renderer#setClipRect(int, int, int, int) + * Enables the clipping rectangle. + * + * @see Renderer#setClipRect(int, int, int, int) */ - public boolean clipRectEnabled = false; + public boolean clipRectEnabled; /** - * @see RenderState#setPolyOffset(float, float) + * Enables z-order offset for polygons. + * + * @see RenderState#setPolyOffset(float, float) */ - public boolean polyOffsetEnabled = false; - + public boolean polyOffsetEnabled; + /** - * @see RenderState#setPolyOffset(float, float) + * Maximum Z slope for z-order offset. + * + * @see RenderState#setPolyOffset(float, float) */ - public float polyOffsetFactor = 0; - + public float polyOffsetFactor; + /** - * @see RenderState#setPolyOffset(float, float) + * Minimum resolvable depth buffer value for z-order offset. + * + * @see RenderState#setPolyOffset(float, float) */ - public float polyOffsetUnits = 0; + public float polyOffsetUnits; /** - * @see Mesh#setPointSize(float) + * No longer used. */ - public float pointSize = 1; - + public float pointSize; + /** + * Line width for meshes. + * * @see RenderState#setLineWidth(float) */ - public float lineWidth = 1; + public float lineWidth; /** - * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) + * How to blend input pixels with those already in the color buffer. + * + * @see RenderState#setBlendMode(com.jme3.material.RenderState.BlendMode) */ - public RenderState.BlendMode blendMode = RenderState.BlendMode.Off; + public RenderState.BlendMode blendMode; /** - * @see RenderState#setBlendEquation(com.jme3.material.RenderState.BlendEquation) + * RGB blend equation for BlendMode.Custom. + * + * @see RenderState#setBlendEquation(com.jme3.material.RenderState.BlendEquation) */ - public RenderState.BlendEquation blendEquation = RenderState.BlendEquation.Add; - + public RenderState.BlendEquation blendEquation; + /** - * @see RenderState#setBlendEquationAlpha(com.jme3.material.RenderState.BlendEquationAlpha) + * Alpha blend equation for BlendMode.Custom. + * + * @see RenderState#setBlendEquationAlpha(com.jme3.material.RenderState.BlendEquationAlpha) */ - public RenderState.BlendEquationAlpha blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor; + public RenderState.BlendEquationAlpha blendEquationAlpha; /** - * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * RGB source blend factor for BlendMode.Custom. + * + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) */ - public RenderState.BlendFunc sfactorRGB = RenderState.BlendFunc.One; + public RenderState.BlendFunc sfactorRGB; /** - * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * RGB destination blend factor for BlendMode.Custom. + * + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) */ - public RenderState.BlendFunc dfactorRGB = RenderState.BlendFunc.One; + public RenderState.BlendFunc dfactorRGB; /** - * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * Alpha source blend factor for BlendMode.Custom. + * + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) */ - public RenderState.BlendFunc sfactorAlpha = RenderState.BlendFunc.One; + public RenderState.BlendFunc sfactorAlpha; /** - * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc, + * Alpha destination blend factor for BlendMode.Custom. + * + * @see RenderState#setCustomBlendFactors(com.jme3.material.RenderState.BlendFunc, + * com.jme3.material.RenderState.BlendFunc, * com.jme3.material.RenderState.BlendFunc, com.jme3.material.RenderState.BlendFunc) */ - public RenderState.BlendFunc dfactorAlpha = RenderState.BlendFunc.One; + public RenderState.BlendFunc dfactorAlpha; /** - * @see RenderState#setWireframe(boolean) + * Enables wireframe rendering of triangle meshes. + * + * @see RenderState#setWireframe(boolean) */ - public boolean wireframe = false; + public boolean wireframe; /** - * @see Renderer#setShader(com.jme3.shader.Shader) + * ID of the shader for rendering. + * + * @see Renderer#setShader(com.jme3.shader.Shader) */ public int boundShaderProgram; - - /** - * @see Renderer#setShader(com.jme3.shader.Shader) - */ - public Shader boundShader; /** - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) - */ - public int boundFBO = 0; - - /** - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * Shader for rendering. + * + * @see Renderer#setShader(com.jme3.shader.Shader) */ - public FrameBuffer boundFB; + public Shader boundShader; /** - * Currently bound Renderbuffer - * - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * ID of the bound FrameBuffer. + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) */ - public int boundRB = 0; + public int boundFBO; /** - * Currently bound draw buffer - * -2 = GL_NONE - * -1 = GL_BACK - * 0 = GL_COLOR_ATTACHMENT0 - * n = GL_COLOR_ATTACHMENTn - * where n is an integer greater than 1 - * - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) - * @see FrameBuffer#setTargetIndex(int) + * Currently bound FrameBuffer. + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) */ - public int boundDrawBuf = -1; + public FrameBuffer boundFB; /** - * Currently bound read buffer + * Currently bound Renderbuffer. * - * @see RenderContext#boundDrawBuf - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) - * @see FrameBuffer#setTargetIndex(int) + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) */ - public int boundReadBuf = -1; + public int boundRB; + /** * Currently bound element array vertex buffer. - * - * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) */ public int boundElementArrayVBO; /** - * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) + * ID of the bound vertex array. + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) */ public int boundVertexArray; /** * Currently bound array vertex buffer. - * - * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) + * + * @see Renderer#renderMesh(com.jme3.scene.Mesh, int, int, com.jme3.scene.VertexBuffer[]) */ public int boundArrayVBO; - + /** * Currently bound pixel pack pixel buffer. */ public int boundPixelPackPBO; - public int numTexturesSet = 0; + /** + * No longer used. + */ + public int numTexturesSet; /** * Current bound texture IDs for each texture unit. - * - * @see Renderer#setTexture(int, com.jme3.texture.Texture) + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) */ - public Image[] boundTextures = new Image[16]; + public final WeakReference boundTextures[] + = new WeakReference[maxTextureUnits]; + /** - * IDList for texture units - * - * @see Renderer#setTexture(int, com.jme3.texture.Texture) + * Current bound buffer object IDs for each buffer object unit. + * + * @see Renderer#setUniformBufferObject(int, com.jme3.shader.BufferObject) + * @see Renderer#setShaderStorageBufferObject(int, com.jme3.shader.BufferObject) */ - public IDList textureIndexList = new IDList(); + public final WeakReference[] boundBO = new WeakReference[maxBufferObjectUnits]; /** - * Currently bound texture unit - * - * @see Renderer#setTexture(int, com.jme3.texture.Texture) + * IDList for texture units. + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) + */ + public final IDList textureIndexList = new IDList(); + + /** + * Currently bound texture unit. + * + * @see Renderer#setTexture(int, com.jme3.texture.Texture) */ - public int boundTextureUnit = 0; + public int boundTextureUnit; /** - * Stencil Buffer state + * Stencil Buffer state. + */ + public boolean stencilTest; + /** + * Action taken when the stencil test fails on a front-facing polygon. + */ + public RenderState.StencilOperation frontStencilStencilFailOperation; + /** + * Action taken when the stencil test passes but the depth test fails on a front-facing polygon. + */ + public RenderState.StencilOperation frontStencilDepthFailOperation; + /** + * Action taken when both tests pass on a front-facing polygon. + */ + public RenderState.StencilOperation frontStencilDepthPassOperation; + /** + * Action taken when the stencil test fails on a back-facing polygon. */ - public boolean stencilTest = false; - public RenderState.StencilOperation frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; - public RenderState.StencilOperation frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; - public RenderState.StencilOperation frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; - public RenderState.StencilOperation backStencilStencilFailOperation = RenderState.StencilOperation.Keep; - public RenderState.StencilOperation backStencilDepthFailOperation = RenderState.StencilOperation.Keep; - public RenderState.StencilOperation backStencilDepthPassOperation = RenderState.StencilOperation.Keep; - public RenderState.TestFunction frontStencilFunction = RenderState.TestFunction.Always; - public RenderState.TestFunction backStencilFunction = RenderState.TestFunction.Always; + public RenderState.StencilOperation backStencilStencilFailOperation; + /** + * Action taken when the stencil test passes but the depth test fails on a back-facing polygon. + */ + public RenderState.StencilOperation backStencilDepthFailOperation; + /** + * Action taken when both tests pass on a back-facing polygon. + */ + public RenderState.StencilOperation backStencilDepthPassOperation; + /** + * Stencil test function for front-facing polygons. + */ + public RenderState.TestFunction frontStencilFunction; + /** + * Stencil test function for back-facing polygons. + */ + public RenderState.TestFunction backStencilFunction; /** * Vertex attribs currently bound and enabled. If a slot is null, then * it is disabled. */ - public VertexBuffer[] boundAttribs = new VertexBuffer[16]; + public final WeakReference[] boundAttribs = new WeakReference[16]; /** - * IDList for vertex attributes + * IDList for vertex attributes. */ - public IDList attribIndexList = new IDList(); - + public final IDList attribIndexList = new IDList(); + /** - * depth test function + * Depth test function. */ - public RenderState.TestFunction depthFunc = RenderState.TestFunction.Less; - - /** - * alpha test function + public RenderState.TestFunction depthFunc; + + /** + * Alpha test function. */ - public RenderState.TestFunction alphaFunc = RenderState.TestFunction.Greater; + public RenderState.TestFunction alphaFunc; + /** + * ID of the initial draw buffer. + */ public int initialDrawBuf; + /** + * ID of the initial read buffer. + */ public int initialReadBuf; - - public ColorRGBA clearColor = new ColorRGBA(0,0,0,0); - + + /** + * Color applied when a color buffer is cleared. + */ + public ColorRGBA clearColor = new ColorRGBA(0, 0, 0, 0); + /** - * Reset the RenderContext to default GL state + * Instantiates a context with appropriate default values. */ - public void reset(){ + public RenderContext() { + init(); + } + + + private void init() { cullMode = RenderState.FaceCullMode.Off; depthTestEnabled = false; - depthWriteEnabled = false; - colorWriteEnabled = false; + depthWriteEnabled = true; + colorWriteEnabled = true; clipRectEnabled = false; polyOffsetEnabled = false; polyOffsetFactor = 0; polyOffsetUnits = 0; pointSize = 1; + lineWidth = 1; blendMode = RenderState.BlendMode.Off; blendEquation = RenderState.BlendEquation.Add; blendEquationAlpha = RenderState.BlendEquationAlpha.InheritColor; - sfactorRGB = BlendFunc.One; - dfactorRGB = BlendFunc.One; - sfactorAlpha = BlendFunc.One; - dfactorAlpha = BlendFunc.One; + sfactorRGB = RenderState.BlendFunc.One; + dfactorRGB = RenderState.BlendFunc.One; + sfactorAlpha = RenderState.BlendFunc.One; + dfactorAlpha = RenderState.BlendFunc.One; wireframe = false; + boundShaderProgram = 0; boundShader = null; boundFBO = 0; boundFB = null; boundRB = 0; - boundDrawBuf = -1; - boundReadBuf = -1; + boundElementArrayVBO = 0; boundVertexArray = 0; boundArrayVBO = 0; boundPixelPackPBO = 0; numTexturesSet = 0; - for (int i = 0; i < boundTextures.length; i++) - boundTextures[i] = null; - - textureIndexList.reset(); boundTextureUnit = 0; - for (int i = 0; i < boundAttribs.length; i++) - boundAttribs[i] = null; - - attribIndexList.reset(); - stencilTest = false; + frontStencilStencilFailOperation = RenderState.StencilOperation.Keep; frontStencilDepthFailOperation = RenderState.StencilOperation.Keep; frontStencilDepthPassOperation = RenderState.StencilOperation.Keep; @@ -327,9 +407,30 @@ public void reset(){ backStencilDepthPassOperation = RenderState.StencilOperation.Keep; frontStencilFunction = RenderState.TestFunction.Always; backStencilFunction = RenderState.TestFunction.Always; - - depthFunc = RenderState.TestFunction.LessOrEqual; + + depthFunc = RenderState.TestFunction.Less; alphaFunc = RenderState.TestFunction.Greater; - clearColor.set(0,0,0,0); + cullMode = RenderState.FaceCullMode.Off; + + clearColor.set(0, 0, 0, 0); + } + + /** + * Resets the RenderContext to default GL state. + */ + public void reset() { + init(); + + for (int i = 0; i < boundTextures.length; i++) { + boundTextures[i] = null; + } + + textureIndexList.reset(); + + for (int i = 0; i < boundAttribs.length; i++) { + boundAttribs[i] = null; + } + + attribIndexList.reset(); } } 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 54e661e66a..b6ee8c60f0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RenderManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,10 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.ForwardPipeline; +import com.jme3.renderer.pipeline.DefaultPipelineContext; +import com.jme3.renderer.pipeline.RenderPipeline; +import com.jme3.renderer.pipeline.PipelineContext; import com.jme3.light.DefaultLightFilter; import com.jme3.light.LightFilter; import com.jme3.light.LightList; @@ -40,74 +44,204 @@ import com.jme3.material.RenderState; import com.jme3.material.Technique; import com.jme3.material.TechniqueDef; -import com.jme3.math.*; +import com.jme3.math.Matrix4f; import com.jme3.post.SceneProcessor; -import com.jme3.profile.*; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.AppStep; +import com.jme3.profile.VpStep; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; import com.jme3.shader.Shader; import com.jme3.shader.UniformBinding; import com.jme3.shader.UniformBindingManager; +import com.jme3.shader.VarType; import com.jme3.system.NullRenderer; import com.jme3.system.Timer; +import com.jme3.texture.FrameBuffer; import com.jme3.util.SafeArrayList; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.logging.Logger; /** - * RenderManager is a high-level rendering interface that is - * above the Renderer implementation. RenderManager takes care - * of rendering the scene graphs attached to each viewport and - * handling SceneProcessors. - * - * @see SceneProcessor - * @see ViewPort - * @see Spatial + * The `RenderManager` is a high-level rendering interface that manages + * {@link ViewPort}s, {@link SceneProcessor}s, and the overall rendering pipeline. + * It is responsible for orchestrating the rendering of scenes into various + * viewports. */ public class RenderManager { private static final Logger logger = Logger.getLogger(RenderManager.class.getName()); + private final Renderer renderer; private final UniformBindingManager uniformBindingManager = new UniformBindingManager(); private final ArrayList preViewPorts = new ArrayList<>(); private final ArrayList viewPorts = new ArrayList<>(); private final ArrayList postViewPorts = new ArrayList<>(); + private final HashMap, PipelineContext> contexts = new HashMap<>(); + private final LinkedList usedContexts = new LinkedList<>(); + private final LinkedList> usedPipelines = new LinkedList<>(); + private RenderPipeline defaultPipeline = new ForwardPipeline(); private Camera prevCam = null; private Material forcedMaterial = null; private String forcedTechnique = null; private RenderState forcedRenderState = null; private final SafeArrayList forcedOverrides = new SafeArrayList<>(MatParamOverride.class); - private int viewX, viewY, viewWidth, viewHeight; + private final Matrix4f orthoMatrix = new Matrix4f(); private final LightList filteredLightList = new LightList(null); - private boolean handleTranlucentBucket = true; + private boolean handleTranslucentBucket = true; private AppProfiler prof; private LightFilter lightFilter = new DefaultLightFilter(); private TechniqueDef.LightMode preferredLightMode = TechniqueDef.LightMode.MultiPass; private int singlePassLightBatchSize = 1; + private final MatParamOverride boundDrawBufferId = new MatParamOverride(VarType.Int, "BoundDrawBuffer", 0); + private Predicate renderFilter; /** - * Create a high-level rendering interface over the + * Creates a high-level rendering interface over the * low-level rendering interface. - * @param renderer + * + * @param renderer The low-level renderer implementation. */ public RenderManager(Renderer renderer) { this.renderer = renderer; + this.forcedOverrides.add(boundDrawBufferId); + // register default pipeline context + contexts.put(PipelineContext.class, new DefaultPipelineContext()); + } + + /** + * Gets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + * + * @return The default {@link RenderPipeline}, which is {@link ForwardPipeline} by default. + */ + public RenderPipeline getPipeline() { + return defaultPipeline; + } + + /** + * Sets the default pipeline used when a ViewPort does not have a + * pipeline already assigned to it. + *

    + * default={@link ForwardPipeline} + * + * @param pipeline The default rendering pipeline (not null). + */ + public void setPipeline(RenderPipeline pipeline) { + assert pipeline != null; + this.defaultPipeline = pipeline; + } + + /** + * Gets the default pipeline context registered under + * {@link PipelineContext}. + * + * @return The default {@link PipelineContext}. + */ + public PipelineContext getDefaultContext() { + return getContext(PipelineContext.class); + } + + /** + * Gets the pipeline context registered under the given class type. + * + * @param type The class type of the context to retrieve. + * @param The type of the {@link PipelineContext}. + * @return The registered context instance, or null if not found. + */ + @SuppressWarnings("unchecked") + public T getContext(Class type) { + return (T) contexts.get(type); + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context from the supplier. + * + * @param The type of the {@link PipelineContext}. + * @param type The class type under which the context is registered. + * @param supplier A function interface for creating a new context + * if one is not already registered under the given type. + * @return The registered or newly created context. + */ + public T getOrCreateContext(Class type, Supplier supplier) { + T c = getContext(type); + if (c == null) { + c = supplier.get(); + registerContext(type, c); + } + return c; + } + + /** + * Gets the pipeline context registered under the class or creates + * and registers a new context from the function. + * + * @param The type of the {@link PipelineContext}. + * @param type The class type under which the context is registered. + * @param function A function interface for creating a new context, taking the {@code RenderManager} as an argument, + * if one is not already registered under the given type. + * @return The registered or newly created context. + */ + public T getOrCreateContext(Class type, Function function) { + T c = getContext(type); + if (c == null) { + c = function.apply(this); + registerContext(type, c); + } + return c; + } + + /** + * Registers a pipeline context under the given class type. + *

    + * If another context is already registered under the class, that + * context will be replaced by the given context. + * + * @param type The class type under which the context is registered. + * @param context The context instance to register. + * @param The type of the {@link PipelineContext}. + */ + public void registerContext(Class type, T context) { + assert type != null; + if (context == null) { + throw new NullPointerException("Context to register cannot be null."); + } + contexts.put(type, context); + } + + /** + * Gets the application profiler. + * + * @return The {@link AppProfiler} instance, or null if none is set. + */ + public AppProfiler getProfiler() { + return prof; } /** * Returns the pre ViewPort with the given name. - * + * * @param viewName The name of the pre ViewPort to look up * @return The ViewPort, or null if not found. - * - * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) */ public ViewPort getPreView(String viewName) { for (int i = 0; i < preViewPorts.size(); i++) { @@ -138,11 +272,11 @@ public boolean removePreView(String viewName) { /** * Removes the specified pre ViewPort. - * + * * @param view The pre ViewPort to remove * @return True if the ViewPort was removed successfully. - * - * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) */ public boolean removePreView(ViewPort view) { return preViewPorts.remove(view); @@ -150,11 +284,11 @@ public boolean removePreView(ViewPort view) { /** * Returns the main ViewPort with the given name. - * + * * @param viewName The name of the main ViewPort to look up * @return The ViewPort, or null if not found. - * - * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) */ public ViewPort getMainView(String viewName) { for (int i = 0; i < viewPorts.size(); i++) { @@ -167,11 +301,11 @@ public ViewPort getMainView(String viewName) { /** * Removes the main ViewPort with the specified name. - * + * * @param viewName The main ViewPort name to remove * @return True if the ViewPort was removed successfully. - * - * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) */ public boolean removeMainView(String viewName) { for (int i = 0; i < viewPorts.size(); i++) { @@ -185,11 +319,11 @@ public boolean removeMainView(String viewName) { /** * Removes the specified main ViewPort. - * + * * @param view The main ViewPort to remove * @return True if the ViewPort was removed successfully. - * - * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) */ public boolean removeMainView(ViewPort view) { return viewPorts.remove(view); @@ -197,11 +331,11 @@ public boolean removeMainView(ViewPort view) { /** * Returns the post ViewPort with the given name. - * + * * @param viewName The name of the post ViewPort to look up * @return The ViewPort, or null if not found. - * - * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) */ public ViewPort getPostView(String viewName) { for (int i = 0; i < postViewPorts.size(); i++) { @@ -214,11 +348,11 @@ public ViewPort getPostView(String viewName) { /** * Removes the post ViewPort with the specified name. - * + * * @param viewName The post ViewPort name to remove * @return True if the ViewPort was removed successfully. - * - * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) */ public boolean removePostView(String viewName) { for (int i = 0; i < postViewPorts.size(); i++) { @@ -233,38 +367,41 @@ public boolean removePostView(String viewName) { /** * Removes the specified post ViewPort. - * + * * @param view The post ViewPort to remove * @return True if the ViewPort was removed successfully. - * - * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + * + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) */ public boolean removePostView(ViewPort view) { return postViewPorts.remove(view); } /** - * Returns a read-only list of all pre ViewPorts + * Returns a read-only list of all pre ViewPorts. + * * @return a read-only list of all pre ViewPorts - * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) + * @see #createPreView(java.lang.String, com.jme3.renderer.Camera) */ public List getPreViews() { return Collections.unmodifiableList(preViewPorts); } /** - * Returns a read-only list of all main ViewPorts + * Returns a read-only list of all main ViewPorts. + * * @return a read-only list of all main ViewPorts - * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) + * @see #createMainView(java.lang.String, com.jme3.renderer.Camera) */ public List getMainViews() { return Collections.unmodifiableList(viewPorts); } /** - * Returns a read-only list of all post ViewPorts + * Returns a read-only list of all post ViewPorts. + * * @return a read-only list of all post ViewPorts - * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) + * @see #createPostView(java.lang.String, com.jme3.renderer.Camera) */ public List getPostViews() { return Collections.unmodifiableList(postViewPorts); @@ -272,8 +409,12 @@ public List getPostViews() { /** * Creates a new pre ViewPort, to display the given camera's content. - *

    - * The view will be processed before the main and post viewports. + * + *

    The view will be processed before the main and post viewports. + * + * @param viewName the desired viewport name + * @param cam the Camera to use for rendering (alias created) + * @return a new instance */ public ViewPort createPreView(String viewName, Camera cam) { ViewPort vp = new ViewPort(viewName, cam); @@ -283,9 +424,13 @@ public ViewPort createPreView(String viewName, Camera cam) { /** * Creates a new main ViewPort, to display the given camera's content. - *

    - * The view will be processed before the post viewports but after + * + *

    The view will be processed before the post viewports but after * the pre viewports. + * + * @param viewName the desired viewport name + * @param cam the Camera to use for rendering (alias created) + * @return a new instance */ public ViewPort createMainView(String viewName, Camera cam) { ViewPort vp = new ViewPort(viewName, cam); @@ -295,8 +440,12 @@ public ViewPort createMainView(String viewName, Camera cam) { /** * Creates a new post ViewPort, to display the given camera's content. - *

    - * The view will be processed after the pre and main viewports. + * + *

    The view will be processed after the pre and main viewports. + * + * @param viewName the desired viewport name + * @param cam the Camera to use for rendering (alias created) + * @return a new instance */ public ViewPort createPostView(String viewName, Camera cam) { ViewPort vp = new ViewPort(viewName, cam); @@ -315,10 +464,24 @@ private void notifyReshape(ViewPort vp, int w, int h) { } } + private void notifyRescale(ViewPort vp, float x, float y) { + List processors = vp.getProcessors(); + for (SceneProcessor proc : processors) { + if (!proc.isInitialized()) { + proc.initialize(this, vp); + } else { + proc.rescale(vp, x, y); + } + } + } + /** * Internal use only. * Updates the resolution of all on-screen cameras to match * the given width and height. + * + * @param w the new width (in pixels) + * @param h the new height (in pixels) */ public void notifyReshape(int w, int h) { for (ViewPort vp : preViewPorts) { @@ -345,20 +508,47 @@ public void notifyReshape(int w, int h) { } /** - * Set the material to use to render all future objects. - * This overrides the material set on the geometry and renders - * with the provided material instead. - * Use null to clear the material and return renderer to normal - * functionality. - * @param mat The forced material to set, or null to return to normal + * Internal use only. + * Updates the scale of all on-screen ViewPorts + * + * @param x the new horizontal scale + * @param y the new vertical scale */ - public void setForcedMaterial(Material mat) { - forcedMaterial = mat; + public void notifyRescale(float x, float y) { + for (ViewPort vp : preViewPorts) { + notifyRescale(vp, x, y); + } + for (ViewPort vp : viewPorts) { + notifyRescale(vp, x, y); + } + for (ViewPort vp : postViewPorts) { + notifyRescale(vp, x, y); + } + } + + /** + * Sets a material that will be forced on all rendered geometries. + * This can be used for debugging (e.g., solid color) or special effects. + * + * @param forcedMaterial The material to force, or null to disable forcing. + */ + public void setForcedMaterial(Material forcedMaterial) { + this.forcedMaterial = forcedMaterial; } /** - * Returns the forced render state previously set with + * Gets the forced material that overrides materials set on geometries. + * + * @return The forced {@link Material}, or null if no material is forced. + */ + public Material getForcedMaterial() { + return forcedMaterial; + } + + /** + * Returns the forced render state previously set with * {@link #setForcedRenderState(com.jme3.material.RenderState) }. + * * @return the forced render state */ public RenderState getForcedRenderState() { @@ -366,22 +556,22 @@ public RenderState getForcedRenderState() { } /** - * Set the render state to use for all future objects. + * Sets the render state to use for all future objects. * This overrides the render state set on the material and instead * forces this render state to be applied for all future materials * rendered. Set to null to return to normal functionality. - * + * * @param forcedRenderState The forced render state to set, or null - * to return to normal + * to return to normal */ public void setForcedRenderState(RenderState forcedRenderState) { this.forcedRenderState = forcedRenderState; } /** - * Set the timer that should be used to query the time based + * Sets the timer that should be used to query the time based * {@link UniformBinding}s for material world parameters. - * + * * @param timer The timer to query time world parameters */ public void setTimer(Timer timer) { @@ -392,17 +582,18 @@ public void setTimer(Timer timer) { * Sets an AppProfiler hook that will be called back for * specific steps within a single update frame. Value defaults * to null. + * + * @param prof the AppProfiler to use (alias created, default=null) */ public void setAppProfiler(AppProfiler prof) { this.prof = prof; } /** - * Returns the forced technique name set. - * - * @return the forced technique name set. - * - * @see #setForcedTechnique(java.lang.String) + * Returns the name of the forced technique. + * + * @return The name of the forced technique, or null if none is forced. + * @see #setForcedTechnique(java.lang.String) */ public String getForcedTechnique() { return forcedTechnique; @@ -410,17 +601,15 @@ public String getForcedTechnique() { /** * Sets the forced technique to use when rendering geometries. - *

    - * If the specified technique name is available on the geometry's - * material, then it is used, otherwise, the + * + *

    If the specified technique name is available on the geometry's + * material, then it is used, otherwise, the * {@link #setForcedMaterial(com.jme3.material.Material) forced material} is used. * If a forced material is not set and the forced technique name cannot * be found on the material, the geometry will not be rendered. - * - * @param forcedTechnique The forced technique name to use, set to null - * to return to normal functionality. - * - * @see #renderGeometry(com.jme3.scene.Geometry) + * + * @param forcedTechnique The technique to force, or null to disable forcing. + * @see #renderGeometry(com.jme3.scene.Geometry) */ public void setForcedTechnique(String forcedTechnique) { this.forcedTechnique = forcedTechnique; @@ -433,8 +622,7 @@ public void setForcedTechnique(String forcedTechnique) { * material or any overrides that exist in the scene graph that have the * same name. * - * @param override The override to add - * @see MatParamOverride + * @param override The material parameter override to add. * @see #removeForcedMatParam(com.jme3.material.MatParamOverride) */ public void addForcedMatParam(MatParamOverride override) { @@ -442,9 +630,9 @@ public void addForcedMatParam(MatParamOverride override) { } /** - * Remove a forced material parameter previously added. + * Removes a material parameter override. * - * @param override The override to remove. + * @param override The material parameter override to remove. * @see #addForcedMatParam(com.jme3.material.MatParamOverride) */ public void removeForcedMatParam(MatParamOverride override) { @@ -452,9 +640,9 @@ public void removeForcedMatParam(MatParamOverride override) { } /** - * Get the forced material parameters applied to rendered geometries. - *

    - * Forced parameters can be added via + * Gets the forced material parameters applied to rendered geometries. + * + *

    Forced parameters can be added via * {@link #addForcedMatParam(com.jme3.material.MatParamOverride)} or removed * via {@link #removeForcedMatParam(com.jme3.material.MatParamOverride)}. * @@ -465,14 +653,14 @@ public SafeArrayList getForcedMatParams() { } /** - * Enable or disable alpha-to-coverage. - *

    - * When alpha to coverage is enabled and the renderer implementation + * Enables or disables alpha-to-coverage. + * + *

    When alpha to coverage is enabled and the renderer implementation * supports it, then alpha blending will be replaced with alpha dissolve * if multi-sampling is also set on the renderer. * This feature allows avoiding of alpha blending artifacts due to * lack of triangle-level back-to-front sorting. - * + * * @param value True to enable alpha-to-coverage, false otherwise. */ public void setAlphaToCoverage(boolean value) { @@ -482,34 +670,33 @@ public void setAlphaToCoverage(boolean value) { /** * True if the translucent bucket should automatically be rendered * by the RenderManager. - * - * @return Whether or not the translucent bucket is rendered. - * - * @see #setHandleTranslucentBucket(boolean) + * + * @return true if the translucent bucket is rendered + * + * @see #setHandleTranslucentBucket(boolean) */ public boolean isHandleTranslucentBucket() { - return handleTranlucentBucket; + return handleTranslucentBucket; } /** - * Enable or disable rendering of the + * Enables or disables rendering of the * {@link Bucket#Translucent translucent bucket} * by the RenderManager. The default is enabled. - * - * @param handleTranslucentBucket Whether or not the translucent bucket should - * be rendered. + * + * @param handleTranslucentBucket true to render the translucent bucket */ public void setHandleTranslucentBucket(boolean handleTranslucentBucket) { - this.handleTranlucentBucket = handleTranslucentBucket; + this.handleTranslucentBucket = handleTranslucentBucket; } /** * Internal use only. Sets the world matrix to use for future * rendering. This has no effect unless objects are rendered manually * using {@link Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) }. - * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will + * Using {@link #renderGeometry(com.jme3.scene.Geometry) } will * override this value. - * + * * @param mat The world matrix to set */ public void setWorldMatrix(Matrix4f mat) { @@ -520,6 +707,8 @@ public void setWorldMatrix(Matrix4f mat) { * Internal use only. * Updates the given list of uniforms with {@link UniformBinding uniform bindings} * based on the current world state. + * + * @param shader (not null) */ public void updateUniformBindings(Shader shader) { uniformBindingManager.updateUniformBindings(shader); @@ -527,56 +716,84 @@ public void updateUniformBindings(Shader shader) { /** * Renders the given geometry. - *

    - * First the proper world matrix is set, if + * + *

    First the proper world matrix is set, if * the geometry's {@link Geometry#setIgnoreTransform(boolean) ignore transform} - * feature is enabled, the identity world matrix is used, otherwise, the - * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used. - *

    - * Once the world matrix is applied, the proper material is chosen for rendering. + * feature is enabled, the identity world matrix is used, otherwise, the + * geometry's {@link Geometry#getWorldMatrix() world transform matrix} is used. + * + *

    Once the world matrix is applied, the proper material is chosen for rendering. * If a {@link #setForcedMaterial(com.jme3.material.Material) forced material} is * set on this RenderManager, then it is used for rendering the geometry, * otherwise, the {@link Geometry#getMaterial() geometry's material} is used. - *

    - * If a {@link #setForcedTechnique(java.lang.String) forced technique} is + * + *

    If a {@link #setForcedTechnique(java.lang.String) forced technique} is * set on this RenderManager, then it is selected automatically * on the geometry's material and is used for rendering. Otherwise, one * of the {@link com.jme3.material.MaterialDef#getTechniqueDefsNames() default techniques} is * used. - *

    - * If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced + * + *

    If a {@link #setForcedRenderState(com.jme3.material.RenderState) forced * render state} is set on this RenderManager, then it is used * for rendering the material, and the material's own render state is ignored. * Otherwise, the material's render state is used as intended. - * + * * @param geom The geometry to render - * + * * @see Technique * @see RenderState - * @see com.jme3.material.Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) - * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) + * @see com.jme3.material.Material#selectTechnique(java.lang.String, com.jme3.renderer.RenderManager) + * @see com.jme3.material.Material#render(com.jme3.scene.Geometry, com.jme3.renderer.RenderManager) */ public void renderGeometry(Geometry geom) { - if (geom.isIgnoreTransform()) { - setWorldMatrix(Matrix4f.IDENTITY); - } else { - setWorldMatrix(geom.getWorldMatrix()); + + if (renderFilter != null && !renderFilter.test(geom)) { + return; } - - // Perform light filtering if we have a light filter. + LightList lightList = geom.getWorldLightList(); - if (lightFilter != null) { filteredLightList.clear(); lightFilter.filterLights(geom, filteredLightList); lightList = filteredLightList; } + renderGeometry(geom, lightList); + } + + /** + * Renders a single {@link Geometry} with a specific list of lights. + * This method applies the world transform, handles forced materials and techniques, + * and manages the `BoundDrawBuffer` parameter for multi-target frame buffers. + * + * @param geom The {@link Geometry} to render. + * @param lightList The {@link LightList} containing the lights that affect this geometry. + */ + public void renderGeometry(Geometry geom, LightList lightList) { + + if (renderFilter != null && !renderFilter.test(geom)) { + return; + } + + this.renderer.pushDebugGroup(geom.getName()); + if (geom.isIgnoreTransform()) { + setWorldMatrix(Matrix4f.IDENTITY); + } else { + setWorldMatrix(geom.getWorldMatrix()); + } + + // Use material override to pass the current target index (used in api such as GL ES + // that do not support glDrawBuffer) + FrameBuffer currentFb = this.renderer.getCurrentFrameBuffer(); + if (currentFb != null && !currentFb.isMultiTarget()) { + this.boundDrawBufferId.setValue(currentFb.getTargetIndex()); + } + Material material = geom.getMaterial(); - //if forcedTechnique we try to force it for render, - //if it does not exists in the mat def, we check for forcedMaterial and render the geom if not null - //else the geom is not rendered + // If forcedTechnique exists, we try to force it for the render. + // If it does not exist in the mat def, we check for forcedMaterial and render the geom if not null. + // Otherwise, the geometry is not rendered. if (forcedTechnique != null) { MaterialDef matDef = material.getMaterialDef(); if (matDef.getTechniqueDefs(forcedTechnique) != null) { @@ -602,7 +819,8 @@ public void renderGeometry(Geometry geom) { forcedRenderState = tmpRs; //Reverted this part from revision 6197 - //If forcedTechnique does not exists, and forcedMaterial is not set, the geom MUST NOT be rendered + // If forcedTechnique does not exist and forcedMaterial is not set, + // the geometry MUST NOT be rendered. } else if (forcedMaterial != null) { // use forced material forcedMaterial.render(geom, lightList, this); @@ -613,18 +831,19 @@ public void renderGeometry(Geometry geom) { } else { material.render(geom, lightList, this); } + this.renderer.popDebugGroup(); } /** * Renders the given GeometryList. - *

    - * For every geometry in the list, the + * + *

    For every geometry in the list, the * {@link #renderGeometry(com.jme3.scene.Geometry) } method is called. - * + * * @param gl The geometry list to render. - * + * * @see GeometryList - * @see #renderGeometry(com.jme3.scene.Geometry) + * @see #renderGeometry(com.jme3.scene.Geometry) */ public void renderGeometryList(GeometryList gl) { for (int i = 0; i < gl.size(); i++) { @@ -634,15 +853,15 @@ public void renderGeometryList(GeometryList gl) { /** * Preloads a scene for rendering. - *

    - * After invocation of this method, the underlying + * + *

    After invocation of this method, the underlying * renderer would have uploaded any textures, shaders and meshes - * used by the given scene to the video driver. + * used by the given scene to the video driver. * Using this method is useful when wishing to avoid the initial pause - * when rendering a scene for the first time. Note that it is not + * when rendering a scene for the first time. Note that it is not * guaranteed that the underlying renderer will actually choose to upload * the data to the GPU so some pause is still to be expected. - * + * * @param scene The scene to preload */ public void preloadScene(Spatial scene) { @@ -654,7 +873,7 @@ public void preloadScene(Spatial scene) { preloadScene(children.get(i)); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); @@ -677,47 +896,51 @@ public void preloadScene(Spatial scene) { /** * Flattens the given scene graph into the ViewPort's RenderQueue, * checking for culling as the call goes down the graph recursively. - *

    - * First, the scene is checked for culling based on the Spatials + * + *

    First, the scene is checked for culling based on the Spatials * {@link Spatial#setCullHint(com.jme3.scene.Spatial.CullHint) cull hint}, * if the camera frustum contains the scene, then this method is recursively * called on its children. - *

    - * When the scene's leaves or {@link Geometry geometries} are reached, - * they are each enqueued into the + * + *

    When the scene's leaves or {@link Geometry geometries} are reached, + * they are each enqueued into the * {@link ViewPort#getQueue() ViewPort's render queue}. - *

    - * In addition to enqueuing the visible geometries, this method + * + *

    In addition to enqueuing the visible geometries, this method * also scenes which cast or receive shadows, by putting them into the - * RenderQueue's - * {@link RenderQueue#addToQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.Bucket) - * shadow queue}. Each Spatial which has its + * RenderQueue's + * {@link RenderQueue#addToQueue(com.jme3.scene.Geometry, com.jme3.renderer.queue.RenderQueue.Bucket) + * shadow queue}. Each Spatial which has its * {@link Spatial#setShadowMode(com.jme3.renderer.queue.RenderQueue.ShadowMode) shadow mode} * set to not off, will be put into the appropriate shadow queue, note that - * this process does not check for frustum culling on any + * this process does not check for frustum culling on any * {@link ShadowMode#Cast shadow casters}, as they don't have to be * in the eye camera frustum to cast shadows on objects that are inside it. - * + * * @param scene The scene to flatten into the queue * @param vp The ViewPort provides the {@link ViewPort#getCamera() camera} - * used for culling and the {@link ViewPort#getQueue() queue} used to - * contain the flattened scene graph. + * used for culling and the {@link ViewPort#getQueue() queue} used to + * contain the flattened scene graph. */ public void renderScene(Spatial scene, ViewPort vp) { - //reset of the camera plane state for proper culling (must be 0 for the first note of the scene to be rendered) + // reset of the camera plane state for proper culling + // (must be 0 for the first note of the scene to be rendered) vp.getCamera().setPlaneState(0); - //rendering the scene + // queue the scene for rendering renderSubScene(scene, vp); } - - // recursively renders the scene - private void renderSubScene(Spatial scene, ViewPort vp) { - // check culling first. + /** + * Recursively renders the scene. + * + * @param scene the scene to be rendered (not null) + * @param vp the ViewPort to render in (not null) + */ + private void renderSubScene(Spatial scene, ViewPort vp) { + // check culling first if (!scene.checkCulling(vp.getCamera())) { return; } - scene.runControlRender(this, vp); if (scene instanceof Node) { // Recurse for all children @@ -731,21 +954,20 @@ private void renderSubScene(Spatial scene, ViewPort vp) { renderSubScene(children.get(i), vp); } } else if (scene instanceof Geometry) { - // add to the render queue + // addUserEvent to the render queue Geometry gm = (Geometry) scene; if (gm.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + gm.getName()); } - vp.getQueue().addToQueue(gm, scene.getQueueBucket()); } } /** * Returns the camera currently used for rendering. - *

    - * The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }. - * + * + *

    The camera can be set with {@link #setCamera(com.jme3.renderer.Camera, boolean) }. + * * @return the camera currently used for rendering. */ public Camera getCurrentCamera() { @@ -754,10 +976,10 @@ public Camera getCurrentCamera() { /** * The renderer implementation used for rendering operations. - * + * * @return The renderer implementation - * - * @see #RenderManager(com.jme3.renderer.Renderer) + * + * @see #RenderManager(com.jme3.renderer.Renderer) * @see Renderer */ public Renderer getRenderer() { @@ -767,13 +989,14 @@ public Renderer getRenderer() { /** * Flushes the ViewPort's {@link ViewPort#getQueue() render queue} * by rendering each of its visible buckets. - * By default the queues will automatically be cleared after rendering, + * By default, the queues will be cleared automatically after rendering, * so there's no need to clear them manually. - * + * * @param vp The ViewPort of which the queue will be flushed - * - * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) - * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList) + * + * @see RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, + * com.jme3.renderer.RenderManager, com.jme3.renderer.Camera) + * @see #renderGeometryList(com.jme3.renderer.queue.GeometryList) */ public void flushQueue(ViewPort vp) { renderViewPortQueues(vp, true); @@ -781,11 +1004,11 @@ public void flushQueue(ViewPort vp) { /** * Clears the queue of the given ViewPort. - * Simply calls {@link RenderQueue#clear() } on the ViewPort's + * Simply calls {@link RenderQueue#clear() } on the ViewPort's * {@link ViewPort#getQueue() render queue}. - * + * * @param vp The ViewPort of which the queue will be cleared. - * + * * @see RenderQueue#clear() * @see ViewPort#getQueue() */ @@ -795,18 +1018,18 @@ public void clearQueue(ViewPort vp) { /** * Sets the light filter to use when rendering lit Geometries. - * + * * @see LightFilter * @param lightFilter The light filter. Set it to null if you want all lights to be rendered. */ public void setLightFilter(LightFilter lightFilter) { this.lightFilter = lightFilter; } - + /** * Returns the current LightFilter. - * - * @return the current light filter + * + * @return the current light filter */ public LightFilter getLightFilter() { return this.lightFilter; @@ -814,6 +1037,7 @@ public LightFilter getLightFilter() { /** * Defines what light mode will be selected when a technique offers several light modes. + * * @param preferredLightMode The light mode to use. */ public void setPreferredLightMode(TechniqueDef.LightMode preferredLightMode) { @@ -821,7 +1045,8 @@ public void setPreferredLightMode(TechniqueDef.LightMode preferredLightMode) { } /** - * returns the preferred light mode. + * Returns the preferred light mode. + * * @return the light mode. */ public TechniqueDef.LightMode getPreferredLightMode() { @@ -829,7 +1054,8 @@ public TechniqueDef.LightMode getPreferredLightMode() { } /** - * returns the number of lights used for each pass when the light mode is single pass. + * Returns the number of lights used for each pass when the light mode is single pass. + * * @return the number of lights. */ public int getSinglePassLightBatchSize() { @@ -838,33 +1064,34 @@ public int getSinglePassLightBatchSize() { /** * Sets the number of lights to use for each pass when the light mode is single pass. + * * @param singlePassLightBatchSize the number of lights. */ public void setSinglePassLightBatchSize(int singlePassLightBatchSize) { // Ensure the batch size is no less than 1 - this.singlePassLightBatchSize = singlePassLightBatchSize < 1 ? 1 : singlePassLightBatchSize; + this.singlePassLightBatchSize = Math.max(singlePassLightBatchSize, 1); } - - + /** - * Render the given viewport queues. - *

    - * Changes the {@link Renderer#setDepthRange(float, float) depth range} - * appropriately as expected by each queue and then calls - * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) } - * on the queue. Makes sure to restore the depth range to [0, 1] + * Renders the given viewport queues. + * + *

    Changes the {@link Renderer#setDepthRange(float, float) depth range} + * appropriately as expected by each queue and then calls + * {@link RenderQueue#renderQueue(com.jme3.renderer.queue.RenderQueue.Bucket, + * com.jme3.renderer.RenderManager, com.jme3.renderer.Camera, boolean) } + * on the queue. Makes sure to restore the depth range to [0, 1] * at the end of the call. * Note that the {@link Bucket#Translucent translucent bucket} is NOT - * rendered by this method. Instead the user should call + * rendered by this method. Instead, the user should call * {@link #renderTranslucentQueue(com.jme3.renderer.ViewPort) } * after this call. - * + * * @param vp the viewport of which queue should be rendered * @param flush If true, the queues will be cleared after - * rendering. - * + * rendering. + * * @see RenderQueue - * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort) + * @see #renderTranslucentQueue(com.jme3.renderer.ViewPort) */ public void renderViewPortQueues(ViewPort vp, boolean flush) { RenderQueue rq = vp.getQueue(); @@ -873,33 +1100,39 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { // render opaque objects with default depth range // opaque objects are sorted front-to-back, reducing overdraw - if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Opaque); + if (prof != null) { + prof.vpStep(VpStep.RenderBucket, vp, Bucket.Opaque); + } rq.renderQueue(Bucket.Opaque, this, cam, flush); // render the sky, with depth range set to the farthest if (!rq.isQueueEmpty(Bucket.Sky)) { - if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Sky); + if (prof != null) { + prof.vpStep(VpStep.RenderBucket, vp, Bucket.Sky); + } renderer.setDepthRange(1, 1); rq.renderQueue(Bucket.Sky, this, cam, flush); depthRangeChanged = true; } - // transparent objects are last because they require blending with the // rest of the scene's objects. Consequently, they are sorted // back-to-front. if (!rq.isQueueEmpty(Bucket.Transparent)) { - if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Transparent); + if (prof != null) { + prof.vpStep(VpStep.RenderBucket, vp, Bucket.Transparent); + } if (depthRangeChanged) { renderer.setDepthRange(0, 1); depthRangeChanged = false; } - rq.renderQueue(Bucket.Transparent, this, cam, flush); } if (!rq.isQueueEmpty(Bucket.Gui)) { - if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Gui); + if (prof != null) { + prof.vpStep(VpStep.RenderBucket, vp, Bucket.Gui); + } renderer.setDepthRange(0, 0); setCamera(cam, true); rq.renderQueue(Bucket.Gui, this, cam, flush); @@ -915,34 +1148,36 @@ public void renderViewPortQueues(ViewPort vp, boolean flush) { /** * Renders the {@link Bucket#Translucent translucent queue} on the viewPort. - *

    - * This call does nothing unless {@link #setHandleTranslucentBucket(boolean) } + * + *

    This call does nothing unless {@link #setHandleTranslucentBucket(boolean) } * is set to true. This method clears the translucent queue after rendering * it. - * + * * @param vp The viewport of which the translucent queue should be rendered. - * - * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) - * @see #setHandleTranslucentBucket(boolean) + * + * @see #renderViewPortQueues(com.jme3.renderer.ViewPort, boolean) + * @see #setHandleTranslucentBucket(boolean) */ public void renderTranslucentQueue(ViewPort vp) { - if (prof!=null) prof.vpStep(VpStep.RenderBucket, vp, Bucket.Translucent); - + if (prof != null) { + prof.vpStep(VpStep.RenderBucket, vp, Bucket.Translucent); + } + RenderQueue rq = vp.getQueue(); - if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranlucentBucket) { + if (!rq.isQueueEmpty(Bucket.Translucent) && handleTranslucentBucket) { rq.renderQueue(Bucket.Translucent, this, vp.getCamera(), true); } } private void setViewPort(Camera cam) { - // this will make sure to update viewport only if needed + // this will make sure to clearReservations viewport only if needed if (cam != prevCam || cam.isViewportChanged()) { - viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); - viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); + int viewX = (int) (cam.getViewPortLeft() * cam.getWidth()); + int viewY = (int) (cam.getViewPortBottom() * cam.getHeight()); int viewX2 = (int) (cam.getViewPortRight() * cam.getWidth()); int viewY2 = (int) (cam.getViewPortTop() * cam.getHeight()); - viewWidth = viewX2 - viewX; - viewHeight = viewY2 - viewY; + int viewWidth = viewX2 - viewX; + int viewHeight = viewY2 - viewY; uniformBindingManager.setViewPort(viewX, viewY, viewWidth, viewHeight); renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); renderer.setClipRect(viewX, viewY, viewWidth, viewHeight); @@ -953,10 +1188,10 @@ private void setViewPort(Camera cam) { // float translateY = viewHeight == viewY ? 0 : -(viewHeight + viewY) / (viewHeight - viewY); // float scaleX = viewWidth == viewX ? 1f : 2f / (viewWidth - viewX); // float scaleY = viewHeight == viewY ? 1f : 2f / (viewHeight - viewY); -// +// // orthoMatrix.loadIdentity(); // orthoMatrix.setTranslation(translateX, translateY, 0); -// orthoMatrix.setScale(scaleX, scaleY, 0); +// orthoMatrix.setScale(scaleX, scaleY, 0); orthoMatrix.loadIdentity(); orthoMatrix.setTranslation(-1f, -1f, 0f); @@ -968,26 +1203,27 @@ private void setViewProjection(Camera cam, boolean ortho) { if (ortho) { uniformBindingManager.setCamera(cam, Matrix4f.IDENTITY, orthoMatrix, orthoMatrix); } else { - uniformBindingManager.setCamera(cam, cam.getViewMatrix(), cam.getProjectionMatrix(), cam.getViewProjectionMatrix()); + uniformBindingManager.setCamera(cam, cam.getViewMatrix(), cam.getProjectionMatrix(), + cam.getViewProjectionMatrix()); } } /** - * Set the camera to use for rendering. - *

    - * First, the camera's + * Sets the camera to use for rendering. + * + *

    First, the camera's * {@link Camera#setViewPort(float, float, float, float) view port parameters} - * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and + * are applied. Then, the camera's {@link Camera#getViewMatrix() view} and * {@link Camera#getProjectionMatrix() projection} matrices are set * on the renderer. If ortho is true, then * instead of using the camera's view and projection matrices, an ortho - * matrix is computed and used instead of the view projection matrix. + * matrix is computed and used instead of the view projection matrix. * The ortho matrix converts from the range (0 ~ Width, 0 ~ Height, -1 ~ +1) * to the clip range (-1 ~ +1, -1 ~ +1, -1 ~ +1). - * + * * @param cam The camera to set * @param ortho True if to use orthographic projection (for GUI rendering), - * false if to use the camera's view and projection matrices. + * false if to use the camera's view and projection matrices. */ public void setCamera(Camera cam, boolean ortho) { // Tell the light filter which camera to use for filtering. @@ -1001,170 +1237,181 @@ public void setCamera(Camera cam, boolean ortho) { /** * Draws the viewport but without notifying {@link SceneProcessor scene * processors} of any rendering events. - * + * * @param vp The ViewPort to render - * - * @see #renderViewPort(com.jme3.renderer.ViewPort, float) + * + * @see #renderViewPort(com.jme3.renderer.ViewPort, float) */ public void renderViewPortRaw(ViewPort vp) { setCamera(vp.getCamera(), false); List scenes = vp.getScenes(); - for (int i = scenes.size() - 1; i >= 0; i--) { + for (int i = scenes.size() - 1; i >= 0; i--) { renderScene(scenes.get(i), vp); } flushQueue(vp); } /** - * Renders the {@link ViewPort}. - *

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

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

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

    + * If the ViewPort is disabled, no rendering will occur. + * + * @param vp View port to render + * @param tpf Time per frame value + */ + public void renderViewPort(ViewPort vp, float tpf) { + if (!vp.isEnabled()) { + return; } - - if (processors != null) { - if (prof!=null) prof.vpStep(VpStep.PostQueue, vp, null); - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) prof.spStep(SpStep.ProcPostQueue, proc.getClass().getSimpleName()); - proc.postQueue(vp.getQueue()); - } + RenderPipeline pipeline = vp.getPipeline(); + if (pipeline == null) { + pipeline = defaultPipeline; } - if (prof!=null) prof.vpStep(VpStep.FlushQueue, vp, null); - flushQueue(vp); - - if (processors != null) { - if (prof!=null) prof.vpStep(VpStep.PostFrame, vp, null); - for (SceneProcessor proc : processors.getArray()) { - if (prof != null) prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); - proc.postFrame(vp.getOutputFrameBuffer()); - } - if (prof != null) prof.vpStep(VpStep.ProcEndRender, vp, null); + PipelineContext context = pipeline.fetchPipelineContext(this); + if (context == null) { + throw new NullPointerException("Failed to fetch pipeline context."); + } + if (!context.startViewPortRender(this, vp)) { + usedContexts.add(context); + } + if (!pipeline.hasRenderedThisFrame()) { + usedPipelines.add(pipeline); + pipeline.startRenderFrame(this); } - //renders the translucent objects queue after processors have been rendered - renderTranslucentQueue(vp); - // clear any remaining spatials that were not rendered. - clearQueue(vp); - if (prof!=null) prof.vpStep(VpStep.EndRender, vp, null); + pipeline.pipelineRender(this, context, vp, tpf); + context.endViewPortRender(this, vp); } - + /** * Called by the application to render any ViewPorts * added to this RenderManager. - *

    - * Renders any viewports that were added using the following methods: + * + *

    Renders any viewports that were added using the following methods: *

      *
    • {@link #createPreView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    • {@link #createMainView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    • {@link #createPostView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    - * + * * @param tpf Time per frame value + * @param mainFrameBufferActive true to render viewports with no output + * FrameBuffer, false to skip them */ public void render(float tpf, boolean mainFrameBufferActive) { if (renderer instanceof NullRenderer) { return; } - uniformBindingManager.newFrame(); + uniformBindingManager.newFrame(); - if (prof!=null) prof.appStep(AppStep.RenderPreviewViewPorts); + if (prof != null) { + prof.appStep(AppStep.RenderPreviewViewPorts); + } for (int i = 0; i < preViewPorts.size(); i++) { ViewPort vp = preViewPorts.get(i); if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { renderViewPort(vp, tpf); } } - - if (prof!=null) prof.appStep(AppStep.RenderMainViewPorts); + + if (prof != null) { + prof.appStep(AppStep.RenderMainViewPorts); + } for (int i = 0; i < viewPorts.size(); i++) { ViewPort vp = viewPorts.get(i); if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { renderViewPort(vp, tpf); } } - - if (prof!=null) prof.appStep(AppStep.RenderPostViewPorts); + + if (prof != null) { + prof.appStep(AppStep.RenderPostViewPorts); + } for (int i = 0; i < postViewPorts.size(); i++) { ViewPort vp = postViewPorts.get(i); if (vp.getOutputFrameBuffer() != null || mainFrameBufferActive) { renderViewPort(vp, tpf); } } + + // cleanup for used render pipelines and pipeline contexts only + for (int i = 0; i < usedContexts.size(); i++) { + usedContexts.get(i).endContextRenderFrame(this); + } + for (RenderPipeline p : usedPipelines) { + p.endRenderFrame(this); + } + usedContexts.clear(); + usedPipelines.clear(); + } + + /** + * Returns true if the draw buffer target id is passed to the shader. + * + * @return True if the draw buffer target id is passed to the shaders. + */ + public boolean getPassDrawBufferTargetIdToShaders() { + return forcedOverrides.contains(boundDrawBufferId); + } + + /** + * Enable or disable passing the draw buffer target id to the shaders. This + * is needed to handle FrameBuffer.setTargetIndex correctly in some + * backends. When enabled, a material parameter named "BoundDrawBuffer" of + * type Int will be added to forced material parameters. + * + * @param enable True to enable, false to disable (default is true) + */ + public void setPassDrawBufferTargetIdToShaders(boolean enable) { + if (enable) { + if (!forcedOverrides.contains(boundDrawBufferId)) { + forcedOverrides.add(boundDrawBufferId); + } + } else { + forcedOverrides.remove(boundDrawBufferId); + } + } + + /** + * Set a render filter. Every geometry will be tested against this filter + * before rendering and will only be rendered if the filter returns true. + * This allows for custom culling or selective rendering based on geometry properties. + * + * @param filter The render filter to apply, or null to remove any existing filter. + */ + public void setRenderFilter(Predicate filter) { + renderFilter = filter; } + + /** + * Returns the render filter that the RenderManager is currently using. + * + * @return The currently active render filter, or null if no filter is set. + */ + public Predicate getRenderFilter() { + return renderFilter; + } + } 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 ee11088789..00f2f8ac57 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Renderer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Renderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,54 +33,61 @@ import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; +import com.jme3.renderer.opengl.GLFence; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; -import com.jme3.shader.BufferObject; +import com.jme3.shader.bufferobject.BufferObject; import com.jme3.shader.Shader; import com.jme3.shader.Shader.ShaderSource; import com.jme3.system.AppSettings; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture; +import com.jme3.texture.TextureImage; import com.jme3.util.NativeObject; import java.nio.ByteBuffer; import java.util.EnumMap; import java.util.EnumSet; /** - * The Renderer is responsible for taking rendering commands and + * Responsible for taking rendering commands and * executing them on the underlying video hardware. - * + * * @author Kirill Vainer */ public interface Renderer { /** - * Detects available capabilities of the GPU. - * + * Detects available capabilities of the GPU. + * * Must be called prior to any other Renderer methods. */ public void initialize(); - + /** - * Get the capabilities of the renderer. + * Gets the capabilities of the renderer. + * * @return The capabilities of the renderer. */ public EnumSet getCaps(); /** - * Get the limits of the renderer. + * Gets the limits of the renderer. * * @return The limits of the renderer. */ public EnumMap getLimits(); /** - * The statistics allow tracking of how data + * Copies the render statistics. + * + *

    The statistics allow tracking of how data * per frame, such as number of objects rendered, number of triangles, etc. * These are updated when the Renderer's methods are used, make sure * to call {@link Statistics#clearFrame() } at the appropriate time * to get accurate info per frame. + * + * @return a new instance */ public Statistics getStatistics(); @@ -102,7 +109,7 @@ public interface Renderer { /** * Sets the background (aka clear) color. - * + * * @param color The background color to set */ public void setBackgroundColor(ColorRGBA color); @@ -110,13 +117,15 @@ public interface Renderer { /** * Applies the given {@link RenderState}, making the necessary * GL calls so that the state is applied. + * + * @param state the RenderState to apply */ public void applyRenderState(RenderState state); /** - * Set the range of the depth values for objects. All rendered + * Sets the range of the depth values for objects. All rendered * objects will have their depth clamped to this range. - * + * * @param start The range start * @param end The range end */ @@ -124,15 +133,15 @@ public interface Renderer { /** * Called when a new frame has been rendered. - * + * * Currently, this will simply delete any OpenGL objects from the GPU * which have been garbage collected by the GC. */ public void postFrame(); /** - * Set the viewport location and resolution on the screen. - * + * Sets the viewport location and resolution on the screen. + * * @param x The x coordinate of the viewport * @param y The y coordinate of the viewport * @param width Width of the viewport @@ -144,7 +153,7 @@ public interface Renderer { * Specifies a clipping rectangle. * For all future rendering commands, no pixels will be allowed * to be rendered outside of the clip rectangle. - * + * * @param x The x coordinate of the clip rect * @param y The y coordinate of the clip rect * @param width Width of the clip rect @@ -153,7 +162,7 @@ public interface Renderer { public void setClipRect(int x, int y, int width, int height); /** - * Clears the clipping rectangle set with + * Clears the clipping rectangle set with * {@link #setClipRect(int, int, int, int) }. */ public void clearClipRect(); @@ -161,9 +170,9 @@ public interface Renderer { /** * Sets the shader to use for rendering. * If the shader has not been uploaded yet, it is compiled - * and linked. If it has been uploaded, then the + * and linked. If it has been uploaded, then the * uniform data is updated and the shader is set. - * + * * @param shader The shader to use for rendering. */ public void setShader(Shader shader); @@ -171,15 +180,15 @@ public interface Renderer { /** * Deletes a shader. This method also deletes * the attached shader sources. - * + * * @param shader Shader to delete. - * @see #deleteShaderSource(com.jme3.shader.Shader.ShaderSource) + * @see #deleteShaderSource(com.jme3.shader.Shader.ShaderSource) */ public void deleteShader(Shader shader); /** * Deletes the provided shader source. - * + * * @param source The ShaderSource to delete. */ public void deleteShaderSource(ShaderSource source); @@ -187,46 +196,63 @@ public interface Renderer { /** * Copies contents from src to dst, scaling if necessary. * set copyDepth to false to only copy the color buffers. + * + * @param src the source FrameBuffer (unaffected) + * @param dst the destination FrameBuffer (modified) + * @param copyDepth true→copy depth info, false→don't copy it + * @deprecated Use {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, + * com.jme3.texture.FrameBuffer, boolean, boolean)}. */ + @Deprecated public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth); + /** + * Copies contents from src to dst, scaling if necessary. + * + * @param src the source FrameBuffer (unaffected) + * @param dst the destination FrameBuffer (modified) + * @param copyColor true→copy color info, false→don't copy it + * @param copyDepth true→copy depth info, false→don't copy it + */ + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyColor, boolean copyDepth); + /** * Sets the framebuffer that will be drawn to. - * + * * If the framebuffer has not been initialized yet, it will be created * and its render surfaces and attached textures will be allocated. - * + * * @param fb The framebuffer to set */ public void setFrameBuffer(FrameBuffer fb); - + /** - * Set the framebuffer that will be set instead of the main framebuffer + * Sets the framebuffer that will be set instead of the main framebuffer * when a call to setFrameBuffer(null) is made. - * + * * @param fb The framebuffer to override the main framebuffer. */ public void setMainFrameBufferOverride(FrameBuffer fb); /** * Reads the pixels currently stored in the specified framebuffer - * into the given ByteBuffer object. - * Only color pixels are transferred, the format is RGBA with 8 bits + * into the given ByteBuffer object. + * Only color pixels are transferred, the format is RGBA with 8 bits * per component. The given byte buffer should have at least * fb.getWidth() * fb.getHeight() * 4 bytes remaining. - * + * * @param fb The framebuffer to read from * @param byteBuf The bytebuffer to transfer color data to */ public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf); - + /** * Reads the pixels currently stored in the specified framebuffer - * into the given ByteBuffer object. - * Only color pixels are transferred, with the given format. + * into the given ByteBuffer object. + * Only color pixels are transferred, with the given format. * The given byte buffer should have at least * fb.getWidth() * fb.getHeight() * 4 bytes remaining. - * + * * @param fb The framebuffer to read from * @param byteBuf The bytebuffer to transfer color data to * @param format the image format to use when reading the frameBuffer. @@ -234,19 +260,35 @@ public interface Renderer { public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format); /** - * Deletes a framebuffer and all attached renderbuffers + * Deletes a framebuffer and all attached renderbuffers. + * + * @param fb the FrameBuffer to be deleted */ public void deleteFrameBuffer(FrameBuffer fb); /** - * Sets the texture to use for the given texture unit. + * Assigns a Texture to the specified texture unit. + * + * @param unit the index of the texture unit (≥0) + * @param tex the Texture to assign + * @throws TextureUnitException if the texture unit doesn't exist + */ + public void setTexture(int unit, Texture tex) + throws TextureUnitException; + + /** + * Assigns a TextureImage to the specified texture unit. + * + * @param unit the index of the texture unit (≥0) + * @param tex the texture image to assign + * @throws TextureUnitException if the texture unit does not exist */ - public void setTexture(int unit, Texture tex); + public void setTextureImage(int unit, TextureImage tex) throws TextureUnitException; /** - * Modify the given Texture with the given Image. + * Modifies the given Texture with the given Image. * The image will be put at x and y into the texture. - * + * * NOTE: this is only supported for uncompressed 2D images without mipmaps. * * @param tex the Texture that will be modified @@ -258,12 +300,14 @@ public interface Renderer { /** * Deletes a texture from the GPU. + * + * @param image the texture to delete */ public void deleteImage(Image image); /** * Uploads a vertex buffer to the GPU. - * + * * @param vb The vertex buffer to upload */ public void updateBufferData(VertexBuffer vb); @@ -273,10 +317,18 @@ public interface Renderer { * * @param bo the buffer object to upload. */ - public void updateBufferData(BufferObject bo); + public void updateShaderStorageBufferObjectData(BufferObject bo); + /** + * Uploads data of the buffer object on the GPU. + * + * @param bo the buffer object to upload. + */ + public void updateUniformBufferObjectData(BufferObject bo); + /** * Deletes a vertex buffer from the GPU. + * * @param vb The vertex buffer to delete */ public void deleteBuffer(VertexBuffer vb); @@ -303,7 +355,7 @@ public interface Renderer { * @param lod The LOD level to use, see {@link Mesh#setLodLevels(com.jme3.scene.VertexBuffer[]) }. * @param count Number of mesh instances to render * @param instanceData When count is greater than 1, these buffers provide - * the per-instance attributes. + * the per-instance attributes. */ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData); @@ -311,27 +363,27 @@ public interface Renderer { * Resets all previously used {@link NativeObject Native Objects} on this Renderer. * The state of the native objects is reset in such way, that using * them again will cause the renderer to reupload them. - * Call this method when you know the GL context is going to shutdown. - * - * @see NativeObject#resetObject() + * Call this method when you know the GL context is going to shut down. + * + * @see NativeObject#resetObject() */ public void resetGLObjects(); /** * Deletes all previously used {@link NativeObject Native Objects} on this Renderer, and * then resets the native objects. - * - * @see #resetGLObjects() - * @see NativeObject#deleteObject(java.lang.Object) + * + * @see #resetGLObjects() + * @see NativeObject#deleteObject(java.lang.Object) */ public void cleanup(); /** - * Set the default anisotropic filter level for textures. + * Sets the default anisotropic filter level for textures. * - * If the + *

    If the * {@link Texture#setAnisotropicFilter(int) texture anisotropic filter} is - * set to 0, then the default level is used. Otherwise if the texture level + * set to 0, then the default level is used. Otherwise, if the texture level * is 1 or greater, then the texture's value overrides the default value. * * @param level The default anisotropic filter level to use. Default: 1. @@ -342,69 +394,72 @@ public interface Renderer { /** * Sets the alpha to coverage state. - *

    - * When alpha coverage and multi-sampling is enabled, + * + *

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

    - *

    - * Alpha-to-coverage is useful for rendering transparent objects + * other future alpha-blended objects are rendered.

    + * + *

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

    + * + * @param value true to enable alpha coverage, otherwise false */ public void setAlphaToCoverage(boolean value); - - /** - * If enabled, color values rendered to the main framebuffer undergo - * linear -> sRGB conversion. - * - * This is identical to {@link FrameBuffer#setSrgb(boolean)} except it is toggled - * for the main framebuffer instead of an offscreen buffer. - * - * This should be set together with {@link Renderer#setLinearizeSrgbImages(boolean)} - * - * As a shorthand, the user can set {@link AppSettings#setGammaCorrection(boolean)} to true - * to toggle both {@link Renderer#setLinearizeSrgbImages(boolean)} and - * {@link Renderer#setMainFrameBufferSrgb(boolean)} if the - * {@link Caps#Srgb} is supported by the GPU. - * - * @throws RendererException If the GPU hardware does not support sRGB. - * - * @see FrameBuffer#setSrgb(boolean) - * @see Caps#Srgb - */ - public void setMainFrameBufferSrgb(boolean srgb); - - /** - * If enabled, all {@link Image images} with the {@link Image#setColorSpace(com.jme3.texture.image.ColorSpace) sRGB flag} - * set shall undergo an sRGB to linear RGB color conversion when read by a shader. - * - * The conversion is performed for the following formats: - * - {@link com.jme3.texture.Image.Format#RGB8} - * - {@link com.jme3.texture.Image.Format#RGBA8} - * - {@link com.jme3.texture.Image.Format#Luminance8} - * - {@link com.jme3.texture.Image.Format#Luminance8Alpha8} - * - {@link com.jme3.texture.Image.Format#DXT1} - * - {@link com.jme3.texture.Image.Format#DXT1A} - * - {@link com.jme3.texture.Image.Format#DXT3} - * - {@link com.jme3.texture.Image.Format#DXT5} - * - * For all other formats, no conversion is performed. - * - * If this option is toggled at runtime, textures must be reloaded for the change to take effect. - * - * @throws RendererException If the GPU hardware does not support sRGB. - * - * @param linearize If sRGB images undergo sRGB -> linear conversion prior to rendering. - * - * @see Caps#Srgb - */ - public void setLinearizeSrgbImages(boolean linearize); - - - /** - * Generates a pool of gpu queries meant to use as profiling tasks + + /** + * Specifies whether color values in the main framebuffer are in SRGB format. + * + *

    If enabled, color values rendered to the main framebuffer undergo + * linear -> sRGB conversion. + * + *

    This is identical to {@link FrameBuffer#setSrgb(boolean)} except it is toggled + * for the main framebuffer instead of an offscreen buffer. + * + *

    This should be set together with {@link Renderer#setLinearizeSrgbImages(boolean)} + * + *

    As a shorthand, the user can set {@link AppSettings#setGammaCorrection(boolean)} to true + * to toggle both {@link Renderer#setLinearizeSrgbImages(boolean)} and + * {@link Renderer#setMainFrameBufferSrgb(boolean)} if the + * {@link Caps#Srgb} is supported by the GPU. + * + * @param srgb true for sRGB colorspace, false for linear colorspace + * @throws RendererException If the GPU hardware does not support sRGB. + * + * @see FrameBuffer#setSrgb(boolean) + * @see Caps#Srgb + */ + public void setMainFrameBufferSrgb(boolean srgb); + + /** + * If enabled, all {@link Image images} with the + * {@link Image#setColorSpace(com.jme3.texture.image.ColorSpace) sRGB flag} + * set shall undergo an sRGB to linear RGB color conversion when read by a shader. + * + *

    The conversion is performed for the following formats: + * - {@link com.jme3.texture.Image.Format#RGB8} + * - {@link com.jme3.texture.Image.Format#RGBA8} + * - {@link com.jme3.texture.Image.Format#Luminance8} + * - {@link com.jme3.texture.Image.Format#Luminance8Alpha8} + * - {@link com.jme3.texture.Image.Format#DXT1} + * - {@link com.jme3.texture.Image.Format#DXT1A} + * - {@link com.jme3.texture.Image.Format#DXT3} + * - {@link com.jme3.texture.Image.Format#DXT5} + * + *

    For all other formats, no conversion is performed. + * + *

    If this option is toggled at runtime, textures must be reloaded for the change to take effect. + * + * @param linearize If sRGB images undergo sRGB -> linear conversion prior to rendering. + * @throws RendererException If the GPU hardware does not support sRGB. + * + * @see Caps#Srgb + */ + public void setLinearizeSrgbImages(boolean linearize); + + /** + * Generates a pool of gpu queries meant to use as profiling tasks. * * @param numTasks the number of task ids to generate * @return an array of tasks ids. @@ -420,41 +475,89 @@ public interface Renderer { public void startProfiling(int taskId); /** - * Will stop the last profiling task started with startProfiling + * Will stop the last profiling task started with startProfiling. */ public void stopProfiling(); /** - * Returns the time in nano seconds elapsed for the task with the given id. + * Returns the time in nanoseconds elapsed for the task with the given id. * Note that the result may not be available right after stopProfiling has been called. * You need to check if the result is available with isTaskResultAvailable. * Also note that it's guaranteed that the result will be available on next frame. - * If you use getProfilingTime on the next frame you called stopProfiling, you don't need to check the result availability with isTaskResultAvailable + * If you use getProfilingTime on the next frame you called stopProfiling, + * you don't need to check the result availability with isTaskResultAvailable * * @param taskId the id of the task given by startProfiling. - * @return the time in nano second of the profiling task with the given id. + * @return the time in nanosecond of the profiling task with the given id. */ public long getProfilingTime(int taskId); /** - * Check if the profiling results are available + * Checks if the profiling results are available. * * @param taskId the id of the task provided by startProfiling * @return true if the results of the task with the given task id are available. */ public boolean isTaskResultAvailable(int taskId); - - + /** * Gets the alpha to coverage state. - * + * + * @return true if alpha coverage is enabled, otherwise false */ - public boolean getAlphaToCoverage(); - + public boolean getAlphaToCoverage(); + /** - * Get the default anisotropic filter level for textures. + * Gets the default anisotropic filter level for textures. * + * @return the default filter level */ public int getDefaultAnisotropicFilter(); + /** + * Determines the maximum allowed width for lines. + * + * @return the maximum width (in pixels) + */ + public float getMaxLineWidth(); + + /** + * Tests whether images with the sRGB flag will be linearized when read by a + * shader. + * + * @return true for linearization, false for no linearization + */ + public boolean isLinearizeSrgbImages(); + + /** + * Tests whether colors rendered to the main framebuffer undergo + * linear-to-sRGB conversion. + * + * @return true for conversion, false for no conversion + */ + public boolean isMainFrameBufferSrgb(); + + public default void popDebugGroup() { + + } + + public default void pushDebugGroup(String name) { + + } + + /** + * Returns the current FrameBuffer that is being rendered to. + * @return the FrameBuffer or null if rendering to the screen. + */ + public FrameBuffer getCurrentFrameBuffer(); + + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) ; + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) ; + + public void deleteFence(GLFence fence); + + /** + * Registers a NativeObject to be cleaned up by this renderer. + */ + public void registerNativeObject(NativeObject nativeObject); } diff --git a/jme3-core/src/main/java/com/jme3/renderer/RendererException.java b/jme3-core/src/main/java/com/jme3/renderer/RendererException.java index 7781fae961..205ca64de0 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/RendererException.java +++ b/jme3-core/src/main/java/com/jme3/renderer/RendererException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,17 +32,22 @@ package com.jme3.renderer; /** - * RendererException is raised when a renderer encounters + * Raised when a renderer encounters * a fatal rendering error. - * + * * @author Kirill Vainer */ public class RendererException extends RuntimeException { - + /** - * Creates a new instance of RendererException + * Creates a new instance of RendererException. + * + * @param message the desired message text */ - public RendererException(String message){ + public RendererException(String message) { super(message); } + public RendererException(Exception e) { + super(e); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java index 8920932699..b2766df154 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/Statistics.java +++ b/jme3-core/src/main/java/com/jme3/renderer/Statistics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,40 +38,85 @@ import com.jme3.util.IntMap; /** - * The statistics class allows tracking of real-time rendering statistics. - *

    - * The Statistics can be retrieved by using {@link Renderer#getStatistics() }. - * + * Allows tracking of real-time rendering statistics. + * + *

    The Statistics can be retrieved by using {@link Renderer#getStatistics() }. + * * @author Kirill Vainer */ public class Statistics { + /** + * Enables or disables updates. + */ protected boolean enabled = false; + /** + * Number of object used during the current frame. + */ protected int numObjects; + /** + * Number of mesh primitives rendered during the current frame. + */ protected int numTriangles; + /** + * Number of mesh vertices rendered during the current frame. + */ protected int numVertices; + /** + * Number of shader switches during the current frame. + */ protected int numShaderSwitches; + /** + * Number of texture binds during the current frame. + */ protected int numTextureBinds; + /** + * Number of FBO switches during the current frame. + */ protected int numFboSwitches; + /** + * Number of uniforms set during the current frame. + */ protected int numUniformsSet; + /** + * Number of active shaders. + */ protected int memoryShaders; + /** + * Number of active frame buffers. + */ protected int memoryFrameBuffers; + /** + * Number of active textures. + */ protected int memoryTextures; - protected IntMap shadersUsed = new IntMap(); - protected IntMap texturesUsed = new IntMap(); - protected IntMap fbosUsed = new IntMap(); + /** + * IDs of all shaders in use. + */ + protected IntMap shadersUsed = new IntMap<>(); + /** + * IDs of all textures in use. + */ + protected IntMap texturesUsed = new IntMap<>(); + /** + * IDs of all FBOs in use. + */ + protected IntMap fbosUsed = new IntMap<>(); + /** + * ID of the most recently used shader. + */ protected int lastShader = -1; - + /** * Returns a list of labels corresponding to each statistic. - * + * * @return a list of labels corresponding to each statistic. - * - * @see #getData(int[]) + * + * @see #getData(int[]) */ public String[] getLabels(){ return new String[]{ "Vertices", @@ -96,12 +141,12 @@ public String[] getLabels(){ /** * Retrieves the statistics data into the given array. - * The array should be as large as the array given in + * The array should be as large as the array given in * {@link #getLabels() }. - * + * * @param data The data array to write to */ - public void getData(int[] data){ + public void getData(int[] data) { data[0] = numVertices; data[1] = numTriangles; data[2] = numUniformsSet; @@ -114,7 +159,7 @@ public void getData(int[] data){ data[7] = numTextureBinds; data[8] = texturesUsed.size(); data[9] = memoryTextures; - + data[10] = numFboSwitches; data[11] = fbosUsed.size(); data[12] = memoryFrameBuffers; @@ -122,35 +167,44 @@ public void getData(int[] data){ /** * Called by the Renderer when a mesh has been drawn. + * + * @param mesh the Mesh that was drawn (not null) + * @param lod which level of detail + * @param count multiplier for triangles and vertices */ - public void onMeshDrawn(Mesh mesh, int lod, int count){ - if( !enabled ) + public void onMeshDrawn(Mesh mesh, int lod, int count) { + if (!enabled) { return; - + } + numObjects += 1; numTriangles += mesh.getTriangleCount(lod) * count; numVertices += mesh.getVertexCount() * count; } - + /** * Called by the Renderer when a mesh has been drawn. + * + * @param mesh the Mesh that was drawn (not null) + * @param lod which level of detail */ - public void onMeshDrawn(Mesh mesh, int lod){ + public void onMeshDrawn(Mesh mesh, int lod) { onMeshDrawn(mesh, lod, 1); } /** * Called by the Renderer when a shader has been utilized. - * + * * @param shader The shader that was used * @param wasSwitched If true, the shader has required a state switch */ - public void onShaderUse(Shader shader, boolean wasSwitched){ + public void onShaderUse(Shader shader, boolean wasSwitched) { assert shader.getId() >= 1; - if( !enabled ) + if (!enabled) { return; - + } + // Reduces unnecessary hashmap lookups if // we already considered this shader. if (lastShader != shader.getId()) { @@ -160,63 +214,71 @@ public void onShaderUse(Shader shader, boolean wasSwitched){ } } - if (wasSwitched) + if (wasSwitched) { numShaderSwitches++; + } } /** * Called by the Renderer when a uniform was set. */ - public void onUniformSet(){ - if( !enabled ) + public void onUniformSet() { + if (!enabled) { return; - numUniformsSet ++; + } + numUniformsSet++; } /** * Called by the Renderer when a texture has been set. - * + * * @param image The image that was set * @param wasSwitched If true, the texture has required a state switch */ - public void onTextureUse(Image image, boolean wasSwitched){ + public void onTextureUse(Image image, boolean wasSwitched) { assert image.getId() >= 1; - if( !enabled ) + if (!enabled) { return; - - if (!texturesUsed.containsKey(image.getId())) + } + + if (!texturesUsed.containsKey(image.getId())) { texturesUsed.put(image.getId(), null); + } - if (wasSwitched) - numTextureBinds ++; + if (wasSwitched) { + numTextureBinds++; + } } /** * Called by the Renderer when a framebuffer has been set. - * + * * @param fb The framebuffer that was set * @param wasSwitched If true, the framebuffer required a state switch */ - public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched){ - if( !enabled ) + public void onFrameBufferUse(FrameBuffer fb, boolean wasSwitched) { + if (!enabled) { return; - - if (fb != null){ + } + + if (fb != null) { assert fb.getId() >= 1; - if (!fbosUsed.containsKey(fb.getId())) + if (!fbosUsed.containsKey(fb.getId())) { fbosUsed.put(fb.getId(), null); + } } - if (wasSwitched) - numFboSwitches ++; + if (wasSwitched) { + numFboSwitches++; + } } - + /** * Clears all frame-specific statistics such as objects used per frame. */ - public void clearFrame(){ + public void clearFrame() { shadersUsed.clear(); texturesUsed.clear(); fbosUsed.clear(); @@ -228,77 +290,93 @@ public void clearFrame(){ numTextureBinds = 0; numFboSwitches = 0; numUniformsSet = 0; - + lastShader = -1; } /** - * Called by the Renderer when it creates a new shader + * Called by the Renderer when it creates a new shader. */ - public void onNewShader(){ - if( !enabled ) + public void onNewShader() { + if (!enabled) { return; - memoryShaders ++; + } + memoryShaders++; } /** - * Called by the Renderer when it creates a new texture + * Called by the Renderer when it creates a new texture. */ - public void onNewTexture(){ - if( !enabled ) + public void onNewTexture() { + if (!enabled) { return; - memoryTextures ++; + } + memoryTextures++; } /** - * Called by the Renderer when it creates a new framebuffer + * Called by the Renderer when it creates a new framebuffer. */ - public void onNewFrameBuffer(){ - if( !enabled ) + public void onNewFrameBuffer() { + if (!enabled) { return; - memoryFrameBuffers ++; + } + memoryFrameBuffers++; } /** - * Called by the Renderer when it deletes a shader + * Called by the Renderer when it deletes a shader. */ - public void onDeleteShader(){ - if( !enabled ) + public void onDeleteShader() { + if (!enabled) { return; - memoryShaders --; + } + memoryShaders--; } /** - * Called by the Renderer when it deletes a texture + * Called by the Renderer when it deletes a texture. */ - public void onDeleteTexture(){ - if( !enabled ) + public void onDeleteTexture() { + if (!enabled) { return; - memoryTextures --; + } + memoryTextures--; } /** - * Called by the Renderer when it deletes a framebuffer + * Called by the Renderer when it deletes a framebuffer. */ - public void onDeleteFrameBuffer(){ - if( !enabled ) + public void onDeleteFrameBuffer() { + if (!enabled) { return; - memoryFrameBuffers --; + } + memoryFrameBuffers--; } /** * Called when video memory is cleared. */ - public void clearMemory(){ + public void clearMemory() { memoryFrameBuffers = 0; memoryShaders = 0; memoryTextures = 0; } - public void setEnabled( boolean f ) { + /** + * Enables or disables updates. + * + * @param f true to enable, false to disable + */ + public void setEnabled(boolean f) { this.enabled = f; } - + + /** + * Tests whether updates are enabled. + * + * @return true if enabled, otherwise false + */ public boolean isEnabled() { return enabled; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/TextureUnitException.java b/jme3-core/src/main/java/com/jme3/renderer/TextureUnitException.java new file mode 100644 index 0000000000..163c087eed --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/TextureUnitException.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +/** + * A checked exception, to be thrown (in place of an IndexOutOfBoundsException) + * when a non-existent texture unit is assigned. + */ +public class TextureUnitException extends Exception { +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java index ad85c0be64..f256405276 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java +++ b/jme3-core/src/main/java/com/jme3/renderer/ViewPort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.renderer; +import com.jme3.renderer.pipeline.RenderPipeline; import com.jme3.math.ColorRGBA; import com.jme3.post.SceneProcessor; import com.jme3.renderer.queue.RenderQueue; @@ -40,23 +41,23 @@ import com.jme3.util.SafeArrayList; /** - * A ViewPort represents a view inside the display - * window or a {@link FrameBuffer} to which scenes will be rendered. - *

    - * A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera} + * Represents a view inside the display + * window or a {@link FrameBuffer} to which scenes will be rendered. + * + *

    A viewport has a {@link #ViewPort(java.lang.String, com.jme3.renderer.Camera) camera} * which is used to render a set of {@link #attachScene(com.jme3.scene.Spatial) scenes}. - * A view port has a location on the screen as set by the + * A view port has a location on the screen as set by the * {@link Camera#setViewPort(float, float, float, float) } method. * By default, a view port does not clear the framebuffer, but it can be * set to {@link #setClearFlags(boolean, boolean, boolean) clear the framebuffer}. - * The background color which the color buffer is cleared to can be specified + * The background color which the color buffer is cleared to can be specified * via the {@link #setBackgroundColor(com.jme3.math.ColorRGBA)} method. - *

    - * A ViewPort has a list of {@link SceneProcessor}s which can + * + *

    A ViewPort has a list of {@link SceneProcessor}s which can * control how the ViewPort is rendered by the {@link RenderManager}. - * + * * @author Kirill Vainer - * + * * @see RenderManager * @see SceneProcessor * @see Spatial @@ -64,28 +65,64 @@ */ public class ViewPort { + /** + * Name for this viewport. + */ protected final String name; + /** + * Camera used for rendering. + */ protected final Camera cam; + /** + * Geometries for rendering, sorted. + */ protected final RenderQueue queue = new RenderQueue(); - protected final SafeArrayList sceneList = new SafeArrayList(Spatial.class); - protected final SafeArrayList processors = new SafeArrayList(SceneProcessor.class); + /** + * Scene-graph hierarchies for rendering. + */ + protected final SafeArrayList sceneList = new SafeArrayList<>(Spatial.class); + /** + * Scene processors currently applied. + */ + protected final SafeArrayList processors = new SafeArrayList<>(SceneProcessor.class); + /** + * Dedicated pipeline. + */ + protected RenderPipeline pipeline; + /** + * FrameBuffer for output. + */ protected FrameBuffer out = null; - protected final ColorRGBA backColor = new ColorRGBA(0,0,0,0); - protected boolean clearDepth = false, clearColor = false, clearStencil = false; + /** + * Color applied when the color buffer is cleared. + */ + protected final ColorRGBA backColor = new ColorRGBA(0, 0, 0, 0); + /** + * Enables clearing the depth buffer. + */ + protected boolean clearDepth = false; + /** + * Enables clearing the color buffer. + */ + protected boolean clearColor = false; + /** + * Enables clearing the stencil buffer. + */ + protected boolean clearStencil = false; private boolean enabled = true; /** - * Create a new viewport. User code should generally use these methods instead:
    + * Creates a new viewport. User code should generally use these methods instead:
    *

      *
    • {@link RenderManager#createPreView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    • {@link RenderManager#createMainView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    • {@link RenderManager#createPostView(java.lang.String, com.jme3.renderer.Camera) }
    • *
    - * + * * @param name The name of the viewport. Used for debugging only. * @param cam The camera through which the viewport is rendered. The camera - * cannot be swapped to a different one after creating the viewport. + * cannot be swapped to a different one after creating the viewport. */ public ViewPort(String name, Camera cam) { this.name = name; @@ -94,65 +131,65 @@ public ViewPort(String name, Camera cam) { /** * Returns the name of the viewport as set in the constructor. - * + * * @return the name of the viewport - * - * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera) + * + * @see #ViewPort(java.lang.String, com.jme3.renderer.Camera) */ public String getName() { return name; } /** - * Get the list of {@link SceneProcessor scene processors} that were - * added to this ViewPort - * + * Gets the list of {@link SceneProcessor scene processors} that were + * added to this ViewPort. + * * @return the list of processors attached to this ViewPort - * - * @see #addProcessor(com.jme3.post.SceneProcessor) + * + * @see #addProcessor(com.jme3.post.SceneProcessor) */ - public SafeArrayList getProcessors(){ + public SafeArrayList getProcessors() { return processors; } /** * Adds a {@link SceneProcessor} to this ViewPort. - *

    - * SceneProcessors that are added to the ViewPort will be notified + * + *

    SceneProcessors that are added to the ViewPort will be notified * of events as the ViewPort is being rendered by the {@link RenderManager}. - * + * * @param processor The processor to add - * + * * @see SceneProcessor */ - public void addProcessor(SceneProcessor processor){ + public void addProcessor(SceneProcessor processor) { if (processor == null) { - throw new IllegalArgumentException( "Processor cannot be null." ); + throw new IllegalArgumentException("Processor cannot be null."); } processors.add(processor); } /** * Removes a {@link SceneProcessor} from this ViewPort. - *

    - * The processor will no longer receive events occurring to this ViewPort. - * + * + *

    The processor will no longer receive events occurring to this ViewPort. + * * @param processor The processor to remove - * + * * @see SceneProcessor */ - public void removeProcessor(SceneProcessor processor){ + public void removeProcessor(SceneProcessor processor) { if (processor == null) { - throw new IllegalArgumentException( "Processor cannot be null." ); + throw new IllegalArgumentException("Processor cannot be null."); } processors.remove(processor); processor.cleanup(); } - + /** * Removes all {@link SceneProcessor scene processors} from this - * ViewPort. - * + * ViewPort. + * * @see SceneProcessor */ public void clearProcessors() { @@ -163,21 +200,21 @@ public void clearProcessors() { } /** - * Check if depth buffer clearing is enabled. - * + * Checks if depth buffer clearing is enabled. + * * @return true if depth buffer clearing is enabled. - * - * @see #setClearDepth(boolean) + * + * @see #setClearDepth(boolean) */ public boolean isClearDepth() { return clearDepth; } /** - * Enable or disable clearing of the depth buffer for this ViewPort. - *

    - * By default depth clearing is disabled. - * + * Enables or disables clearing of the depth buffer for this ViewPort. + * + *

    By default depth clearing is disabled. + * * @param clearDepth Enable/disable depth buffer clearing. */ public void setClearDepth(boolean clearDepth) { @@ -185,21 +222,21 @@ public void setClearDepth(boolean clearDepth) { } /** - * Check if color buffer clearing is enabled. - * + * Checks if color buffer clearing is enabled. + * * @return true if color buffer clearing is enabled. - * - * @see #setClearColor(boolean) + * + * @see #setClearColor(boolean) */ public boolean isClearColor() { return clearColor; } /** - * Enable or disable clearing of the color buffer for this ViewPort. - *

    - * By default color clearing is disabled. - * + * Enables or disables clearing of the color buffer for this ViewPort. + * + *

    By default color clearing is disabled. + * * @param clearColor Enable/disable color buffer clearing. */ public void setClearColor(boolean clearColor) { @@ -207,21 +244,21 @@ public void setClearColor(boolean clearColor) { } /** - * Check if stencil buffer clearing is enabled. - * + * Checks if stencil buffer clearing is enabled. + * * @return true if stencil buffer clearing is enabled. - * - * @see #setClearStencil(boolean) + * + * @see #setClearStencil(boolean) */ public boolean isClearStencil() { return clearStencil; } /** - * Enable or disable clearing of the stencil buffer for this ViewPort. - *

    - * By default stencil clearing is disabled. - * + * Enables or disables clearing of the stencil buffer for this ViewPort. + * + *

    By default stencil clearing is disabled. + * * @param clearStencil Enable/disable stencil buffer clearing. */ public void setClearStencil(boolean clearStencil) { @@ -229,17 +266,17 @@ public void setClearStencil(boolean clearStencil) { } /** - * Set the clear flags (color, depth, stencil) in one call. - * + * Sets the clear flags (color, depth, stencil) in one call. + * * @param color If color buffer clearing should be enabled. * @param depth If depth buffer clearing should be enabled. * @param stencil If stencil buffer clearing should be enabled. - * - * @see #setClearColor(boolean) - * @see #setClearDepth(boolean) - * @see #setClearStencil(boolean) + * + * @see #setClearColor(boolean) + * @see #setClearDepth(boolean) + * @see #setClearStencil(boolean) */ - public void setClearFlags(boolean color, boolean depth, boolean stencil){ + public void setClearFlags(boolean color, boolean depth, boolean stencil) { this.clearColor = color; this.clearDepth = depth; this.clearStencil = stencil; @@ -248,11 +285,11 @@ public void setClearFlags(boolean color, boolean depth, boolean stencil){ /** * Returns the framebuffer where this ViewPort's scenes are * rendered to. - * + * * @return the framebuffer where this ViewPort's scenes are - * rendered to. - * - * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer) + * rendered to. + * + * @see #setOutputFrameBuffer(com.jme3.texture.FrameBuffer) */ public FrameBuffer getOutputFrameBuffer() { return out; @@ -260,13 +297,13 @@ public FrameBuffer getOutputFrameBuffer() { /** * Sets the output framebuffer for the ViewPort. - *

    - * The output framebuffer specifies where the scenes attached - * to this ViewPort are rendered to. By default this is null + * + *

    The output framebuffer specifies where the scenes attached + * to this ViewPort are rendered to. By default, this is null, * which indicates the scenes are rendered to the display window. - * + * * @param out The framebuffer to render scenes to, or null if to render - * to the screen. + * to the screen. */ public void setOutputFrameBuffer(FrameBuffer out) { this.out = out; @@ -274,9 +311,9 @@ public void setOutputFrameBuffer(FrameBuffer out) { /** * Returns the camera which renders the attached scenes. - * + * * @return the camera which renders the attached scenes. - * + * * @see Camera */ public Camera getCamera() { @@ -285,6 +322,8 @@ public Camera getCamera() { /** * Internal use only. + * + * @return the pre-existing instance */ public RenderQueue getQueue() { return queue; @@ -292,14 +331,14 @@ public RenderQueue getQueue() { /** * Attaches a new scene to render in this ViewPort. - * + * * @param scene The scene to attach - * + * * @see Spatial */ - public void attachScene(Spatial scene){ + public void attachScene(Spatial scene) { if (scene == null) { - throw new IllegalArgumentException( "Scene cannot be null." ); + throw new IllegalArgumentException("Scene cannot be null."); } sceneList.add(scene); if (scene instanceof Geometry) { @@ -309,14 +348,14 @@ public void attachScene(Spatial scene){ /** * Detaches a scene from rendering. - * + * * @param scene The scene to detach - * - * @see #attachScene(com.jme3.scene.Spatial) + * + * @see #attachScene(com.jme3.scene.Spatial) */ - public void detachScene(Spatial scene){ + public void detachScene(Spatial scene) { if (scene == null) { - throw new IllegalArgumentException( "Scene cannot be null." ); + throw new IllegalArgumentException("Scene cannot be null."); } sceneList.remove(scene); if (scene instanceof Geometry) { @@ -326,8 +365,8 @@ public void detachScene(Spatial scene){ /** * Removes all attached scenes. - * - * @see #attachScene(com.jme3.scene.Spatial) + * + * @see #attachScene(com.jme3.scene.Spatial) */ public void clearScenes() { sceneList.clear(); @@ -335,59 +374,83 @@ public void clearScenes() { /** * Returns a list of all attached scenes. - * + * * @return a list of all attached scenes. - * - * @see #attachScene(com.jme3.scene.Spatial) + * + * @see #attachScene(com.jme3.scene.Spatial) */ - public SafeArrayList getScenes(){ + public SafeArrayList getScenes() { return sceneList; } /** * Sets the background color. - *

    - * When the ViewPort's color buffer is cleared - * (if {@link #setClearColor(boolean) color clearing} is enabled), + * + *

    When the ViewPort's color buffer is cleared + * (if {@link #setClearColor(boolean) color clearing} is enabled), * this specifies the color to which the color buffer is set to. - * By default the background color is black without alpha. - * + * By default, the background color is black without alpha. + * * @param background the background color. */ - public void setBackgroundColor(ColorRGBA background){ + public void setBackgroundColor(ColorRGBA background) { backColor.set(background); } /** - * Returns the background color of this ViewPort - * + * Returns the background color of this ViewPort. + * * @return the background color of this ViewPort - * - * @see #setBackgroundColor(com.jme3.math.ColorRGBA) + * + * @see #setBackgroundColor(com.jme3.math.ColorRGBA) */ - public ColorRGBA getBackgroundColor(){ + public ColorRGBA getBackgroundColor() { return backColor; } - + /** - * Enable or disable this ViewPort. - *

    - * Disabled ViewPorts are skipped by the {@link RenderManager} when - * rendering. By default all ViewPorts are enabled. - * + * Enables or disables this ViewPort. + * + *

    Disabled ViewPorts are skipped by the {@link RenderManager} when + * rendering. By default, all viewports are enabled. + * * @param enable If the viewport should be disabled or enabled. */ public void setEnabled(boolean enable) { this.enabled = enable; } - + /** * Returns true if the viewport is enabled, false otherwise. + * * @return true if the viewport is enabled, false otherwise. - * @see #setEnabled(boolean) + * @see #setEnabled(boolean) */ public boolean isEnabled() { return enabled; } + + /** + * Sets the pipeline used by this viewport for rendering. + *

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

    + * default=null + * + * @param pipeline pipeline, or null to use render manager's pipeline + */ + public void setPipeline(RenderPipeline pipeline) { + this.pipeline = pipeline; + } + + /** + * Gets the framegraph used by this viewport for rendering. + * + * @return + */ + public RenderPipeline getPipeline() { + return pipeline; + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/ComputeShader.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/ComputeShader.java new file mode 100644 index 0000000000..ae0b148329 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/ComputeShader.java @@ -0,0 +1,196 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.opengl; + +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.renderer.RendererException; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +/** + * A compute shader for general-purpose GPU computing (GPGPU). + *

    + * Compute shaders require OpenGL 4.3 or higher. + */ +public class ComputeShader extends NativeObject { + + private final GL4 gl; + private final String source; + /** + * Creates a new compute shader from GLSL source code. + */ + public ComputeShader(GL4 gl, String source) { + super(); + this.gl = gl; + this.source = source; + //Load this upfront to surface any problems at init time + createComputeShader(); + } + private ComputeShader(ComputeShader source){ + super(); + this.gl = source.gl; + this.id = source.id; + this.source = null; + } + + private void createComputeShader(){ + // Create and compile the shader + int shaderId = gl.glCreateShader(GL4.GL_COMPUTE_SHADER); + if (shaderId <= 0) { + throw new RendererException("Failed to create compute shader"); + } + + IntBuffer intBuf = BufferUtils.createIntBuffer(1); + intBuf.clear(); + intBuf.put(0, source.length()); + gl.glShaderSource(shaderId, new String[]{source}, intBuf); + gl.glCompileShader(shaderId); + + // Check compilation status + gl.glGetShader(shaderId, GL.GL_COMPILE_STATUS, intBuf); + if (intBuf.get(0) != GL.GL_TRUE) { + gl.glGetShader(shaderId, GL.GL_INFO_LOG_LENGTH, intBuf); + String infoLog = gl.glGetShaderInfoLog(shaderId, intBuf.get(0)); + gl.glDeleteShader(shaderId); + throw new RendererException("Compute shader compilation failed: " + infoLog); + } + + // Create program and link + id = gl.glCreateProgram(); + if (id <= 0) { + gl.glDeleteShader(shaderId); + throw new RendererException("Failed to create shader program"); + } + + gl.glAttachShader(id, shaderId); + gl.glLinkProgram(id); + + // Check link status + gl.glGetProgram(id, GL.GL_LINK_STATUS, intBuf); + if (intBuf.get(0) != GL.GL_TRUE) { + gl.glGetProgram(id, GL.GL_INFO_LOG_LENGTH, intBuf); + String infoLog = gl.glGetProgramInfoLog(id, intBuf.get(0)); + gl.glDeleteShader(shaderId); + gl.glDeleteProgram(id); + throw new RendererException("Compute shader program linking failed: " + infoLog); + } + + // Shader object can be deleted after linking + gl.glDeleteShader(shaderId); + + clearUpdateNeeded(); + } + + /** + * Activates this compute shader for use. + * Must be called before setting uniforms or dispatching. + */ + public void makeActive() { + if(isUpdateNeeded()){ + createComputeShader(); + } + gl.glUseProgram(id); + } + + /** + * Dispatches the compute shader with the specified number of work groups. + */ + public void dispatch(int numGroupsX, int numGroupsY, int numGroupsZ) { + gl.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ); + } + + public void setUniform(int location, int value) { + gl.glUniform1i(location, value); + } + + public void setUniform(int location, float value) { + gl.glUniform1f(location, value); + } + + public void setUniform(int location, Vector2f value) { + gl.glUniform2f(location, value.x, value.y); + } + + public void setUniform(int location, Vector3f value) { + gl.glUniform3f(location, value.x, value.y, value.z); + } + + public void setUniform(int location, Vector4f value) { + gl.glUniform4f(location, value.x, value.y, value.z, value.w); + } + + public void setUniform(int location, Matrix4f value) { + FloatBuffer floatBuf16 = BufferUtils.createFloatBuffer(16); + value.fillFloatBuffer(floatBuf16, true); + floatBuf16.clear(); + gl.glUniformMatrix4(location, false, floatBuf16); + } + + public int getUniformLocation(String name) { + return gl.glGetUniformLocation(id, name); + } + + public void bindShaderStorageBuffer(int location, ShaderStorageBufferObject ssbo) { + gl.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, location, ssbo.getId()); + } + + @Override + public void resetObject() { + id = INVALID_ID; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + if(id != INVALID_ID){ + gl.glDeleteProgram(id); + } + resetObject(); + } + + @Override + public NativeObject createDestructableClone() { + return new ComputeShader(this); + } + + @Override + public long getUniqueId() { + //Note this is the same type of ID as a regular shader. + return ((long)OBJTYPE_SHADER << 32) | (0xffffffffL & (long)id); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java index 9f7c5ac309..7712c52a29 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,6 +45,7 @@ */ public interface GL { + public static final int GL_ALIASED_LINE_WIDTH_RANGE = 0x846E; public static final int GL_ALPHA = 0x1906; public static final int GL_ALWAYS = 0x207; public static final int GL_ARRAY_BUFFER = 0x8892; @@ -67,6 +68,7 @@ public interface GL { public static final int GL_DST_COLOR = 0x306; public static final int GL_DYNAMIC_DRAW = 0x88E8; public static final int GL_DYNAMIC_COPY = 0x88EA; + public static final int GL_DYNAMIC_READ = 0x88E9; public static final int GL_ELEMENT_ARRAY_BUFFER = 0x8893; public static final int GL_EQUAL = 0x202; public static final int GL_EXTENSIONS = 0x1F03; @@ -97,6 +99,7 @@ public interface GL { public static final int GL_LINEAR_MIPMAP_NEAREST = 0x2701; public static final int GL_LINES = 0x1; public static final int GL_LINE_LOOP = 0x2; + public static final int GL_LINE_SMOOTH = 0xB20; public static final int GL_LINE_STRIP = 0x3; public static final int GL_LINK_STATUS = 0x8B82; public static final int GL_LUMINANCE = 0x1909; @@ -146,10 +149,13 @@ public interface GL { public static final int GL_SRC_ALPHA_SATURATE = 0x0308; public static final int GL_SRC_COLOR = 0x300; public static final int GL_STATIC_DRAW = 0x88E4; + public static final int GL_STATIC_READ = 0x88E5; + public static final int GL_STATIC_COPY = 0x88E6; public static final int GL_STENCIL_BUFFER_BIT = 0x400; public static final int GL_STENCIL_TEST = 0xB90; public static final int GL_STREAM_DRAW = 0x88E0; public static final int GL_STREAM_READ = 0x88E1; + public static final int GL_STREAM_COPY = 0x88E2; public static final int GL_TEXTURE = 0x1702; public static final int GL_TEXTURE0 = 0x84C0; public static final int GL_TEXTURE1 = 0x84C1; @@ -195,6 +201,7 @@ public interface GL { public static final int GL_VERTEX_SHADER = 0x8B31; public static final int GL_ZERO = 0x0; public static final int GL_UNPACK_ROW_LENGTH = 0x0CF2; + public static final int GL_FRAMEBUFFER_BINDING = 0x8CA6; public void resetStats(); @@ -204,7 +211,9 @@ public interface GL { * Selects which texture unit subsequent texture state calls will affect. The number of texture units an implementation supports is implementation * dependent. * - * @param texture which texture unit to make active. One of:
    {@link #GL_TEXTURE0 TEXTURE0}GL_TEXTURE[1-31]
    + * @param texture which texture unit to make active. One of: + * {@link #GL_TEXTURE0 TEXTURE0} + * GL_TEXTURE[1-31] */ public void glActiveTexture(int texture); @@ -212,12 +221,12 @@ public interface GL { *

    Reference Page

    *

    * Attaches a shader object to a program object. - *

    + * *

    In order to create a complete shader program, there must be a way to specify the list of things that will be linked together. Program objects provide * this mechanism. Shaders that are to be linked together in a program object must first be attached to that program object. glAttachShader attaches the * shader object specified by shader to the program object specified by program. This indicates that shader will be included in link operations that will * be performed on program.

    - *

    + * *

    All operations that can be performed on a shader object are valid whether or not the shader object is attached to a program object. It is permissible to * attach a shader object to a program object before source code has been loaded into the shader object or before the shader object has been compiled. It * is permissible to attach multiple shader objects of the same type because each may contain a portion of the complete shader. It is also permissible to @@ -253,7 +262,7 @@ public interface GL { *

    Reference Page

    *

    * Binds the a texture to a texture target. - *

    + * *

    While a texture object is bound, GL operations on the target to which it is bound affect the bound object, and queries of the target to which it is * bound return state from the bound object. If texture mapping of the dimensionality of the target to which a texture object is bound is enabled, the * state of the bound texture object directs the texturing operation.

    @@ -278,41 +287,41 @@ public interface GL { *

    * Specifies the weighting factors used by the blend equation, for both RGB and alpha functions and for all draw buffers. * - * @param sfactor the source weighting factor. - * @param dfactor the destination weighting factor. + * @param sFactor the source weighting factor. + * @param dFactor the destination weighting factor. */ - public void glBlendFunc(int sfactor, int dfactor); + public void glBlendFunc(int sFactor, int dFactor); /** *

    Reference Page

    *

    * Specifies pixel arithmetic for RGB and alpha components separately. * - * @param sfactorRGB how the red, green, and blue blending factors are computed. The initial value is GL_ONE. - * @param dfactorRGB how the red, green, and blue destination blending factors are computed. The initial value is GL_ZERO. - * @param sfactorAlpha how the alpha source blending factor is computed. The initial value is GL_ONE. - * @param dfactorAlpha how the alpha destination blending factor is computed. The initial value is GL_ZERO. + * @param sFactorRGB how the red, green, and blue blending factors are computed. The initial value is GL_ONE. + * @param dFactorRGB how the red, green, and blue destination blending factors are computed. The initial value is GL_ZERO. + * @param sFactorAlpha how the alpha source blending factor is computed. The initial value is GL_ONE. + * @param dFactorAlpha how the alpha destination blending factor is computed. The initial value is GL_ZERO. */ - public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha); + public void glBlendFuncSeparate(int sFactorRGB, int dFactorRGB, int sFactorAlpha, int dFactorAlpha); /** *

    Reference Page

    *

    * Creates and initializes a buffer object's data store. - *

    + * *

    {@code usage} is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make * more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. * {@code usage} can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. The * frequency of access may be one of these:

    - *

    + * *

      *
    • STREAM - The data store contents will be modified once and used at most a few times.
    • *
    • STATIC - The data store contents will be modified once and used many times.
    • *
    • DYNAMIC - The data store contents will be modified repeatedly and used many times.
    • *
    - *

    + * *

    The nature of access may be one of these:

    - *

    + * *

      *
    • DRAW - The data store contents are modified by the application, and used as the source for GL drawing and image specification commands.
    • *
    • READ - The data store contents are modified by reading data from the GL, and used to return that data when queried by the application.
    • @@ -329,20 +338,20 @@ public interface GL { *

      Reference Page

      *

      * Creates and initializes a buffer object's data store. - *

      + * *

      {@code usage} is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make * more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. * {@code usage} can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. The * frequency of access may be one of these:

      - *

      + * *

        *
      • STREAM - The data store contents will be modified once and used at most a few times.
      • *
      • STATIC - The data store contents will be modified once and used many times.
      • *
      • DYNAMIC - The data store contents will be modified repeatedly and used many times.
      • *
      - *

      + * *

      The nature of access may be one of these:

      - *

      + * *

        *
      • DRAW - The data store contents are modified by the application, and used as the source for GL drawing and image specification commands.
      • *
      • READ - The data store contents are modified by reading data from the GL, and used to return that data when queried by the application.
      • @@ -359,20 +368,20 @@ public interface GL { *

        Reference Page

        *

        * Creates and initializes a buffer object's data store. - *

        + * *

        {@code usage} is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make * more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. * {@code usage} can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. The * frequency of access may be one of these:

        - *

        + * *

          *
        • STREAM - The data store contents will be modified once and used at most a few times.
        • *
        • STATIC - The data store contents will be modified once and used many times.
        • *
        • DYNAMIC - The data store contents will be modified repeatedly and used many times.
        • *
        - *

        + * *

        The nature of access may be one of these:

        - *

        + * *

          *
        • DRAW - The data store contents are modified by the application, and used as the source for GL drawing and image specification commands.
        • *
        • READ - The data store contents are modified by reading data from the GL, and used to return that data when queried by the application.
        • @@ -389,20 +398,20 @@ public interface GL { *

          Reference Page

          *

          * Creates and initializes a buffer object's data store. - *

          + * *

          {@code usage} is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make * more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. * {@code usage} can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. The * frequency of access may be one of these:

          - *

          + * *

            *
          • STREAM - The data store contents will be modified once and used at most a few times.
          • *
          • STATIC - The data store contents will be modified once and used many times.
          • *
          • DYNAMIC - The data store contents will be modified repeatedly and used many times.
          • *
          - *

          + * *

          The nature of access may be one of these:

          - *

          + * *

            *
          • DRAW - The data store contents are modified by the application, and used as the source for GL drawing and image specification commands.
          • *
          • READ - The data store contents are modified by reading data from the GL, and used to return that data when queried by the application.
          • @@ -415,6 +424,11 @@ public interface GL { */ public void glBufferData(int target, ByteBuffer data, int usage); + /** + * See {@link #glBufferData(int, ByteBuffer, int)} + */ + public void glBufferData(int target, IntBuffer data, int usage); + /** *

            Reference Page

            *

            @@ -509,7 +523,7 @@ public void glCompressedTexImage2D(int target, int level, int internalFormat, in /** *

            Reference Page

            - *

            + * * Respecifies only a rectangular subregion of an existing 2D texel array, with incoming data stored in a specific compressed image format. * * @param target the target texture. @@ -526,17 +540,25 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo /** *

            Reference Page

            - *

            + * * Creates a program object. + * + * @return the ID of the new program, or 0 if unsuccessful */ public int glCreateProgram(); /** *

            Reference Page

            - *

            + * * Creates a shader object. * - * @param shaderType the type of shader to be created. One of:
            {@link #GL_VERTEX_SHADER VERTEX_SHADER}{@link #GL_FRAGMENT_SHADER FRAGMENT_SHADER}{@link GL3#GL_GEOMETRY_SHADER GEOMETRY_SHADER}{@link GL4#GL_TESS_CONTROL_SHADER TESS_CONTROL_SHADER}
            {@link GL4#GL_TESS_EVALUATION_SHADER TESS_EVALUATION_SHADER}
            + * @param shaderType the type of shader to be created. One of: + * {@link #GL_VERTEX_SHADER VERTEX_SHADER} + * {@link #GL_FRAGMENT_SHADER FRAGMENT_SHADER} + * {@link GL3#GL_GEOMETRY_SHADER GEOMETRY_SHADER} + * {@link GL4#GL_TESS_CONTROL_SHADER TESS_CONTROL_SHADER} + * {@link GL4#GL_TESS_EVALUATION_SHADER TESS_EVALUATION_SHADER} + * @return the ID of the new shader, or 0 if unsuccessful */ public int glCreateShader(int shaderType); @@ -547,7 +569,10 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * CullFace mode is {@link #GL_BACK BACK} while back-facing polygons are rasterized only if either culling is disabled or the CullFace mode is * {@link #GL_FRONT FRONT}. The initial setting of the CullFace mode is {@link #GL_BACK BACK}. Initially, culling is disabled. * - * @param mode the CullFace mode. One of:
            {@link #GL_FRONT FRONT}{@link #GL_BACK BACK}{@link #GL_FRONT_AND_BACK FRONT_AND_BACK}
            + * @param mode the CullFace mode. One of: + * {@link #GL_FRONT FRONT} + * {@link #GL_BACK BACK} + * {@link #GL_FRONT_AND_BACK FRONT_AND_BACK} */ public void glCullFace(int mode); @@ -585,7 +610,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * currently bound to any of the target bindings of {@link #glBindTexture BindTexture} is deleted, it is as though {@link #glBindTexture BindTexture} had been executed with the * same target and texture zero. Additionally, special care must be taken when deleting a texture if any of the images of the texture are attached to a * framebuffer object. - *

            + * *

            Unused names in textures that have been marked as used for the purposes of {@link #glGenTextures GenTextures} are marked as unused again. Unused names in textures are * silently ignored, as is the name zero.

            * @@ -598,7 +623,15 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo *

            * Specifies the comparison that takes place during the depth buffer test (when {@link #GL_DEPTH_TEST DEPTH_TEST} is enabled). * - * @param func the depth test comparison. One of:
            {@link #GL_NEVER NEVER}{@link #GL_ALWAYS ALWAYS}{@link #GL_LESS LESS}{@link #GL_LEQUAL LEQUAL}{@link #GL_EQUAL EQUAL}{@link #GL_GREATER GREATER}{@link #GL_GEQUAL GEQUAL}{@link #GL_NOTEQUAL NOTEQUAL}
            + * @param func the depth test comparison. One of: + * {@link #GL_NEVER NEVER} + * {@link #GL_ALWAYS ALWAYS} + * {@link #GL_LESS LESS} + * {@link #GL_LEQUAL LEQUAL} + * {@link #GL_EQUAL EQUAL} + * {@link #GL_GREATER GREATER} + * {@link #GL_GEQUAL GEQUAL} + * {@link #GL_NOTEQUAL NOTEQUAL} */ public void glDepthFunc(int func); @@ -654,7 +687,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo *

            * Constructs a sequence of geometric primitives by successively transferring elements for {@code count} vertices. Elements {@code first} through * first + count – 1 of each enabled non-instanced array are transferred to the GL. - *

            + * *

            If an array corresponding to an attribute required by a vertex shader is not enabled, then the corresponding element is taken from the current attribute * state. If an array is enabled, the corresponding current vertex attribute value is unaffected by the execution of this function.

            * @@ -666,25 +699,25 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo /** *

            Reference Page

            - *

            + * *

            Implementations denote recommended maximum amounts of vertex and index data, which may be queried by calling glGet with argument * {@link GL2#GL_MAX_ELEMENTS_VERTICES MAX_ELEMENTS_VERTICES} and {@link GL2#GL_MAX_ELEMENTS_INDICES MAX_ELEMENTS_INDICES}. If end - start + 1 is greater than the value of GL_MAX_ELEMENTS_VERTICES, or if * count is greater than the value of GL_MAX_ELEMENTS_INDICES, then the call may operate at reduced performance. There is no requirement that all vertices * in the range start end be referenced. However, the implementation may partially process unused vertices, reducing performance from what could be * achieved with an optimal index set.

            - *

            + * *

            When glDrawRangeElements is called, it uses count sequential elements from an enabled array, starting at start to construct a sequence of geometric * primitives. mode specifies what kind of primitives are constructed, and how the array elements construct these primitives. If more than one array is * enabled, each is used.

            - *

            + * *

            Vertex attributes that are modified by glDrawRangeElements have an unspecified value after glDrawRangeElements returns. Attributes that aren't modified * maintain their previous values.

            - *

            - *

            Errors
            - *

            + * + * Errors + * *

            It is an error for indices to lie outside the range start end, but implementations may not check for this situation. Such indices cause * implementation-dependent behavior.

            - *

            + * *

              *
            • GL_INVALID_ENUM is generated if mode is not an accepted value.
            • *
            • GL_INVALID_VALUE is generated if count is negative.
            • @@ -752,28 +785,30 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo /** *

              Reference Page

              - *

              + * * Generates query object names. * + * @param number the number of query object names to be generated * @param ids a buffer in which the generated query object names are stored. */ public void glGenQueries(int number, IntBuffer ids); /** *

              Reference Page

              - *

              + * * Returns the location of an attribute variable. * * @param program the program object to be queried. * @param name a null terminated string containing the name of the attribute variable whose location is to be queried. + * @return the location */ public int glGetAttribLocation(int program, String name); /** *

              Reference Page

              - *

              + * * Returns the current boolean value of the specified state variable. - *

              + * *

              LWJGL note: The state that corresponds to the state variable may be a single value or an array of values. In the case of an array of values, * LWJGL will not validate if {@code params} has enough space to store that array. Doing so would introduce significant overhead, as the * OpenGL state variables are too many. It is the user's responsibility to avoid JVM crashes by ensuring enough space for the returned values.

              @@ -785,7 +820,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo /** *

              Reference Page

              - *

              + * * Returns a subset of a buffer object's data store. * * @param target the target buffer object. @@ -794,21 +829,36 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo */ public void glGetBufferSubData(int target, long offset, ByteBuffer data); + /** + * See {@link #glGetBufferSubData(int, long, ByteBuffer)} + */ + public void glGetBufferSubData(int target, long offset, IntBuffer data); + /** *

              Reference Page

              - *

              + * * Returns error information. Each detectable error is assigned a numeric code. When an error is detected, a flag is set and the code is recorded. Further * errors, if they occur, do not affect this recorded code. When {@code GetError} is called, the code is returned and the flag is cleared, so that a * further error will again record its code. If a call to {@code GetError} returns {@link #GL_NO_ERROR NO_ERROR}, then there has been no detectable error since * the last call to {@code GetError} (or since the GL was initialized). + * @return the error code, or NO_ERROR if none */ public int glGetError(); + /** + * Determine the current single-precision floating-point value(s) of the + * specified parameter. + * + * @param parameterId which parameter + * @param storeValues storage for the value(s) + */ + public void glGetFloat(int parameterId, FloatBuffer storeValues); + /** *

              Reference Page

              - *

              + * * Returns the current integer value of the specified state variable. - *

              + * *

              LWJGL note: The state that corresponds to the state variable may be a single value or an array of values. In the case of an array of values, * LWJGL will not validate if {@code params} has enough space to store that array. Doing so would introduce significant overhead, as the * OpenGL state variables are too many. It is the user's responsibility to avoid JVM crashes by ensuring enough space for the returned values.

              @@ -836,6 +886,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * * @param program the program object whose information log is to be queried. * @param maxSize the size of the character buffer for storing the returned information log. + * @return the contents of the information log */ public String glGetProgramInfoLog(int program, int maxSize); @@ -844,6 +895,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * * @param query the name of a query object * @param pname the symbolic name of a query object parameter + * @return the value of the parameter */ public long glGetQueryObjectui64(int query, int pname); @@ -853,7 +905,10 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * Returns the integer value of a query object parameter. * * @param query the name of a query object - * @param pname the symbolic name of a query object parameter. One of:
              {@link #GL_QUERY_RESULT QUERY_RESULT}{@link #GL_QUERY_RESULT_AVAILABLE QUERY_RESULT_AVAILABLE}
              + * @param pname the symbolic name of a query object parameter. One of: + * {@link #GL_QUERY_RESULT QUERY_RESULT} + * {@link #GL_QUERY_RESULT_AVAILABLE QUERY_RESULT_AVAILABLE} + * @return the value of the parameter */ public int glGetQueryObjectiv(int query, int pname); @@ -875,6 +930,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * * @param shader the shader object whose information log is to be queried. * @param maxSize the size of the character buffer for storing the returned information log. + * @return the contents of the information log */ public String glGetShaderInfoLog(int shader, int maxSize); @@ -883,7 +939,13 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo *

              * Return strings describing properties of the current GL context. * - * @param name the property to query. One of:
              {@link #GL_RENDERER RENDERER}{@link #GL_VENDOR VENDOR}{@link #GL_EXTENSIONS EXTENSIONS}{@link #GL_VERSION VERSION}{@link GL2#GL_SHADING_LANGUAGE_VERSION SHADING_LANGUAGE_VERSION}
              + * @param name the property to query. One of: + * {@link #GL_RENDERER RENDERER} + * {@link #GL_VENDOR VENDOR} + * {@link #GL_EXTENSIONS EXTENSIONS} + * {@link #GL_VERSION VERSION} + * {@link GL2#GL_SHADING_LANGUAGE_VERSION SHADING_LANGUAGE_VERSION} + * @return the value of the property */ public String glGetString(int name); @@ -894,6 +956,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * * @param program the program object to be queried. * @param name a null terminated string containing the name of the uniform variable whose location is to be queried. + * @return the location */ public int glGetUniformLocation(int program, String name); @@ -903,6 +966,7 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * Determines if {@code cap} is currently enabled (as with {@link #glEnable Enable}) or disabled. * * @param cap the enable state to query. + * @return true if enabled, otherwise false */ public boolean glIsEnabled(int cap); @@ -936,10 +1000,10 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo /** *

              Reference Page

              - *

              + * * The depth values of all fragments generated by the rasterization of a polygon may be offset by a single value that is computed for that polygon. This * function determines that value. - *

              + * *

              {@code factor} scales the maximum depth slope of the polygon, and {@code units} scales an implementation-dependent constant that relates to the usable * resolution of the depth buffer. The resulting values are summed to produce the polygon offset value.

              * @@ -1014,6 +1078,8 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo * * @param shader the shader object whose source code is to be replaced, * @param strings an array of pointers to strings containing the source code to be loaded into the shader + * @param length storage for the string lengths, or null for + * null-terminated strings */ public void glShaderSource(int shader, String[] strings, IntBuffer length); @@ -1022,8 +1088,19 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo *

              * Sets front and/or back function and reference value for stencil testing. * - * @param face whether front and/or back stencil state is updated. One of:
              {@link GL#GL_FRONT FRONT}{@link GL#GL_BACK BACK}{@link GL#GL_FRONT_AND_BACK FRONT_AND_BACK}
              - * @param func the test function. The initial value is GL_ALWAYS. One of:
              {@link GL#GL_NEVER NEVER}{@link GL#GL_LESS LESS}{@link GL#GL_LEQUAL LEQUAL}{@link GL#GL_GREATER GREATER}{@link GL#GL_GEQUAL GEQUAL}{@link GL#GL_EQUAL EQUAL}{@link GL#GL_NOTEQUAL NOTEQUAL}{@link GL#GL_ALWAYS ALWAYS}
              + * @param face whether front and/or back stencil state is updated. One of: + * {@link GL#GL_FRONT FRONT} + * {@link GL#GL_BACK BACK} + * {@link GL#GL_FRONT_AND_BACK FRONT_AND_BACK} + * @param func the test function. The initial value is GL_ALWAYS. One of: + * {@link GL#GL_NEVER NEVER} + * {@link GL#GL_LESS LESS} + * {@link GL#GL_LEQUAL LEQUAL} + * {@link GL#GL_GREATER GREATER} + * {@link GL#GL_GEQUAL GEQUAL} + * {@link GL#GL_EQUAL EQUAL} + * {@link GL#GL_NOTEQUAL NOTEQUAL} + * {@link GL#GL_ALWAYS ALWAYS} * @param ref the reference value for the stencil test. {@code ref} is clamped to the range [0, 2n – 1], where {@code n} is the number of bitplanes in the stencil * buffer. The initial value is 0. * @param mask a mask that is ANDed with both the reference value and the stored stencil value when the test is done. The initial value is all 1's. @@ -1035,8 +1112,19 @@ public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yo *

              * Sets front and/or back stencil test actions. * - * @param face whether front and/or back stencil state is updated. One of:
              {@link GL#GL_FRONT FRONT}{@link GL#GL_BACK BACK}{@link GL#GL_FRONT_AND_BACK FRONT_AND_BACK}
              - * @param sfail the action to take when the stencil test fails. The initial value is GL_KEEP. One of:
              {@link GL#GL_KEEP KEEP}{@link GL#GL_ZERO ZERO}{@link GL#GL_REPLACE REPLACE}{@link GL#GL_INCR INCR}{@link GL#GL_INCR_WRAP INCR_WRAP}{@link GL#GL_DECR DECR}{@link GL#GL_DECR_WRAP DECR_WRAP}{@link GL#GL_INVERT INVERT}
              + * @param face whether front and/or back stencil state is updated. One of: + * {@link GL#GL_FRONT FRONT} + * {@link GL#GL_BACK BACK} + * {@link GL#GL_FRONT_AND_BACK FRONT_AND_BACK} + * @param sfail the action to take when the stencil test fails. The initial value is GL_KEEP. One of: + * {@link GL#GL_KEEP KEEP} + * {@link GL#GL_ZERO ZERO} + * {@link GL#GL_REPLACE REPLACE} + * {@link GL#GL_INCR INCR} + * {@link GL#GL_INCR_WRAP INCR_WRAP} + * {@link GL#GL_DECR DECR} + * {@link GL#GL_DECR_WRAP DECR_WRAP} + * {@link GL#GL_INVERT INVERT} * @param dpfail the stencil action when the stencil test passes, but the depth test fails. The initial value is GL_KEEP. * @param dppass the stencil action when both the stencil test and the depth test pass, or when the stencil test passes and either there is no depth buffer or depth * testing is not enabled. The initial value is GL_KEEP. @@ -1115,7 +1203,7 @@ public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int /** *

              Reference Page

              *

              - * Specifies the value of a single int uniform variable or a int uniform variable array for the current program object. + * Specifies the value of a single int uniform variable or an int uniform variable array for the current program object. * * @param location the location of the uniform variable to be modified. * @param value a pointer to an array of {@code count} values that will be used to update the specified uniform variable. @@ -1287,9 +1375,9 @@ public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int /** *

              Reference Page

              - *

              + * * Specifies the viewport transformation parameters for all viewports. - *

              + * *

              In the initial state, {@code width} and {@code height} for each viewport are set to the width and height, respectively, of the window into which the GL is to do * its rendering. If the default framebuffer is bound but no default framebuffer is associated with the GL context, then {@code width} and {@code height} are * initially set to zero.

              @@ -1300,4 +1388,4 @@ public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int * @param height the viewport height. */ public void glViewport(int x, int y, int width, int height); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java index fbd6e088d7..21be3e5c39 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -74,6 +74,10 @@ public interface GL2 extends GL { public static final int GL_TEXTURE_WRAP_R = 0x8072; public static final int GL_VERTEX_PROGRAM_POINT_SIZE = 0x8642; public static final int GL_UNSIGNED_INT_8_8_8_8 = 0x8035; + + public static final int GL_READ_ONLY = 35000; + public static final int GL_WRITE_ONLY = 35001; + public static final int GL_READ_WRITE = 35002; /** *

              Reference Page - This function is deprecated and unavailable in the Core profile

              @@ -82,7 +86,15 @@ public interface GL2 extends GL { * The comparison is enabled or disabled with the generic {@link #glEnable Enable} and {@link #glDisable Disable} commands using the symbolic constant {@link #GL_ALPHA_TEST ALPHA_TEST}. * When disabled, it is as if the comparison always passes. The test is controlled with this method. * - * @param func a symbolic constant indicating the alpha test function. One of:
              {@link #GL_NEVER NEVER}{@link #GL_ALWAYS ALWAYS}{@link #GL_LESS LESS}{@link #GL_LEQUAL LEQUAL}{@link #GL_EQUAL EQUAL}{@link #GL_GEQUAL GEQUAL}{@link #GL_GREATER GREATER}{@link #GL_NOTEQUAL NOTEQUAL}
              + * @param func a symbolic constant indicating the alpha test function. One of: + * {@link #GL_NEVER NEVER} + * {@link #GL_ALWAYS ALWAYS} + * {@link #GL_LESS LESS} + * {@link #GL_LEQUAL LEQUAL} + * {@link #GL_EQUAL EQUAL} + * {@link #GL_GEQUAL GEQUAL} + * {@link #GL_GREATER GREATER} + * {@link #GL_NOTEQUAL NOTEQUAL} * @param ref a reference value clamped to the range [0, 1]. When performing the alpha test, the GL will convert the reference value to the same representation as the fragment's alpha value (floating-point or fixed-point). */ public void glAlphaFunc(int func, float ref); @@ -105,8 +117,14 @@ public interface GL2 extends GL { * polygon's vertices are lit, and the polygon is clipped and possibly culled before these modes are applied. Polygon antialiasing applies only to the * {@link #GL_FILL FILL} state of PolygonMode. For {@link #GL_POINT POINT} or {@link #GL_LINE LINE}, point antialiasing or line segment antialiasing, respectively, apply.

              * - * @param face the face for which to set the rasterizing method. One of:
              {@link #GL_FRONT FRONT}{@link #GL_BACK BACK}{@link #GL_FRONT_AND_BACK FRONT_AND_BACK}
              - * @param mode the rasterization mode. One of:
              {@link #GL_POINT POINT}{@link #GL_LINE LINE}{@link #GL_FILL FILL}
              + * @param face the face for which to set the rasterizing method. One of: + * {@link #GL_FRONT FRONT} + * {@link #GL_BACK BACK} + * {@link #GL_FRONT_AND_BACK FRONT_AND_BACK} + * @param mode the rasterization mode. One of: + * {@link #GL_POINT POINT} + * {@link #GL_LINE LINE} + * {@link #GL_FILL FILL} */ public void glPolygonMode(int face, int mode); 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 1c7c995031..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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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

              *

              @@ -165,11 +170,14 @@ public interface GL3 extends GL2 { /** *

              Reference Page

              - *

              + * * Queries indexed string state. * - * @param name the indexed state to query. One of:
              {@link GL#GL_EXTENSIONS EXTENSIONS}{@link GL2#GL_SHADING_LANGUAGE_VERSION SHADING_LANGUAGE_VERSION}
              + * @param name the indexed state to query. One of: + * {@link GL#GL_EXTENSIONS EXTENSIONS} + * {@link GL2#GL_SHADING_LANGUAGE_VERSION SHADING_LANGUAGE_VERSION} * @param index the index of the particular element being queried. + * @return the value of the string state */ public String glGetString(int name, int index); /// GL3+ @@ -190,7 +198,11 @@ public interface GL3 extends GL2 { * * Binds a buffer object to an indexed buffer target. * - * @param target the target of the bind operation. One of:
              {@link #GL_TRANSFORM_FEEDBACK_BUFFER TRANSFORM_FEEDBACK_BUFFER}{@link #GL_UNIFORM_BUFFER UNIFORM_BUFFER}{@link GL4#GL_ATOMIC_COUNTER_BUFFER ATOMIC_COUNTER_BUFFER}{@link GL4#GL_SHADER_STORAGE_BUFFER SHADER_STORAGE_BUFFER}
              + * @param target the target of the bind operation. One of: + * {@link #GL_TRANSFORM_FEEDBACK_BUFFER TRANSFORM_FEEDBACK_BUFFER} + * {@link #GL_UNIFORM_BUFFER UNIFORM_BUFFER} + * {@link GL4#GL_ATOMIC_COUNTER_BUFFER ATOMIC_COUNTER_BUFFER} + * {@link GL4#GL_SHADER_STORAGE_BUFFER SHADER_STORAGE_BUFFER} * @param index the index of the binding point within the array specified by {@code target} * @param buffer a buffer object to bind to the specified binding point */ diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java index 821959aeed..5f734efcdf 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GL4.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,30 @@ public interface GL4 extends GL3 { public static final int GL_TESS_EVALUATION_SHADER = 0x8E87; public static final int GL_PATCHES = 0xE; + /** + * Accepted by the {@code shaderType} parameter of CreateShader. + */ + public static final int GL_COMPUTE_SHADER = 0x91B9; + + /** + * Accepted by the {@code barriers} parameter of MemoryBarrier. + */ + public static final int GL_SHADER_STORAGE_BARRIER_BIT = 0x00002000; + public static final int GL_TEXTURE_FETCH_BARRIER_BIT = 0x00000008; + + /** + * Accepted by the {@code condition} parameter of FenceSync. + */ + public static final int GL_SYNC_GPU_COMMANDS_COMPLETE = 0x9117; + + /** + * Returned by ClientWaitSync. + */ + public static final int GL_ALREADY_SIGNALED = 0x911A; + public static final int GL_TIMEOUT_EXPIRED = 0x911B; + public static final int GL_CONDITION_SATISFIED = 0x911C; + public static final int GL_WAIT_FAILED = 0x911D; + /** * Accepted by the {@code target} parameter of BindBufferBase and BindBufferRange. */ @@ -89,7 +113,7 @@ public interface GL4 extends GL3 { public int glGetProgramResourceIndex(int program, int programInterface, String name); /** - * Cchanges the active shader storage block with an assigned index of storageBlockIndex in program object program. + * Changes the active shader storage block with an assigned index of storageBlockIndex in program object program. * storageBlockIndex must be an active shader storage block index in program. storageBlockBinding must be less * than the value of {@code #GL_MAX_SHADER_STORAGE_BUFFER_BINDINGS}. If successful, glShaderStorageBlockBinding specifies * that program will use the data store of the buffer object bound to the binding point storageBlockBinding to @@ -100,4 +124,75 @@ public interface GL4 extends GL3 { * @param storageBlockBinding The index storage block binding to associate with the specified storage block. */ public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding); + + /** + * Binds a single level of a texture to an image unit for the purpose of reading + * and writing it from shaders. + * + * @param unit image unit to bind to + * @param texture texture to bind to the image unit + * @param level level of the texture to bind + * @param layered true to bind all array elements + * @param layer if not layered, the layer to bind + * @param access access types that may be performed + * @param format format to use when performing formatted stores + */ + public void glBindImageTexture(int unit, int texture, int level, boolean layered, int layer, int access, int format); + + /** + *

              Reference Page

              + *

              + * Launches one or more compute work groups. + * + * @param numGroupsX the number of work groups to be launched in the X dimension + * @param numGroupsY the number of work groups to be launched in the Y dimension + * @param numGroupsZ the number of work groups to be launched in the Z dimension + */ + public void glDispatchCompute(int numGroupsX, int numGroupsY, int numGroupsZ); + + /** + *

              Reference Page

              + *

              + * Defines a barrier ordering memory transactions. + * + * @param barriers the barriers to insert. One or more of: + * {@link #GL_SHADER_STORAGE_BARRIER_BIT} + * {@link #GL_TEXTURE_FETCH_BARRIER_BIT} + */ + public void glMemoryBarrier(int barriers); + + /** + *

              Reference Page

              + *

              + * Creates a new sync object and inserts it into the GL command stream. + * + * @param condition the condition that must be met to set the sync object's state to signaled. + * Must be {@link #GL_SYNC_GPU_COMMANDS_COMPLETE}. + * @param flags must be 0 + * @return the sync object handle + */ + public GLFence glFenceSync(int condition, int flags); + + /** + *

              Reference Page

              + *

              + * Causes the client to block and wait for a sync object to become signaled. + * + * @param sync the sync object to wait on + * @param flags flags controlling command flushing behavior. May be 0 or GL_SYNC_FLUSH_COMMANDS_BIT. + * @param timeout the timeout in nanoseconds for which to wait + * @return one of {@link #GL_ALREADY_SIGNALED}, {@link #GL_TIMEOUT_EXPIRED}, + * {@link #GL_CONDITION_SATISFIED}, or {@link #GL_WAIT_FAILED} + */ + public int glClientWaitSync(GLFence sync, int flags, long timeout); + + /** + *

              Reference Page

              + *

              + * Deletes a sync object. + * + * @param sync the sync object to delete + */ + public void glDeleteSync(GLFence sync); + } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebug.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebug.java index 5b9c94d44a..c0cfa6e97c 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebug.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebug.java @@ -1,11 +1,66 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.renderer.opengl; import com.jme3.renderer.RendererException; -public abstract class GLDebug { +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +/** + * This class uses Reflection to intercept method calls to the Proxy Object ({@link #createProxy(GL, Object, Class[])} + * and extends them with the Error Checking in {@link #checkError()}.
              + * This means we don't have to generate a class with overrides for every possible method just to add a + * {@link #checkError()} call.
              + * Note that we should not call {@link #checkError()} for {@link GL#glGetError()}, it doesn't make sense.
              + * Note that this class is general purpose and as such every class instance (every object) can be guarded as long as + * the passed gl instance is valid. + * + * @author MeFisto94 + */ +public class GLDebug implements InvocationHandler { + protected Object obj; protected GL gl; - + protected Method methodGlGetError; + + private GLDebug(GL gl, Object obj) throws NoSuchMethodException { + this.gl = gl; + this.obj = obj; + methodGlGetError = GL.class.getMethod("glGetError"); + /* The NoSuchMethodException shouldn't be thrown, but since we're in a constructor and cannot fail safe + * otherwise, we throw it. */ + } + protected String decodeError(int err) { String errMsg; switch (err) { @@ -46,4 +101,38 @@ protected void checkError() { throw new RendererException("An OpenGL error occurred - " + decodeError(err)); } } + + @Override + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + Object result = method.invoke(obj, args); + + if (method.equals(methodGlGetError)) { + return result; + } + + checkError(); + return result; + } + + /** + * Creates a debug-proxied object, which will call {@link GL#glGetError()} after every method invocation and throw + * a {@link com.jme3.renderer.RendererException} if there was a GL Error. + * + * @param gl The GL Context, required to call {@link GL#glGetError()} + * @param obj The object which methods will be proxied + * @param implementedInterfaces The interfaces/class this object implements + * @return The Proxy object (or null if an error occurred) + */ + public static Object createProxy(GL gl, Object obj, Class... implementedInterfaces) { + try { + return Proxy.newProxyInstance( + GLDebug.class.getClassLoader(), + implementedInterfaces, + new GLDebug(gl, obj) + ); + } catch (NoSuchMethodException nsme) { + throw new IllegalArgumentException ("Could not initialize the proxy because the glGetError method wasn't " + + "found!", nsme); + } + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java deleted file mode 100644 index 3dfd2a8ca7..0000000000 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugDesktop.java +++ /dev/null @@ -1,134 +0,0 @@ -package com.jme3.renderer.opengl; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; - -public class GLDebugDesktop extends GLDebugES implements GL2, GL3, GL4 { - - private final GL2 gl2; - private final GL3 gl3; - private final GL4 gl4; - - public GLDebugDesktop(GL gl, GLExt glext, GLFbo glfbo) { - super(gl, glext, glfbo); - this.gl2 = gl instanceof GL2 ? (GL2) gl : null; - this.gl3 = gl instanceof GL3 ? (GL3) gl : null; - this.gl4 = gl instanceof GL4 ? (GL4) gl : null; - } - - public void glAlphaFunc(int func, float ref) { - gl2.glAlphaFunc(func, ref); - checkError(); - } - - public void glPointSize(float size) { - gl2.glPointSize(size); - checkError(); - } - - public void glPolygonMode(int face, int mode) { - gl2.glPolygonMode(face, mode); - checkError(); - } - - public void glDrawBuffer(int mode) { - gl2.glDrawBuffer(mode); - checkError(); - } - - public void glReadBuffer(int mode) { - gl2.glReadBuffer(mode); - checkError(); - } - - public void glCompressedTexImage3D(int target, int level, int internalformat, int width, int height, int depth, int border, ByteBuffer data) { - gl2.glCompressedTexImage3D(target, level, internalformat, width, height, depth, border, data); - checkError(); - } - - public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, ByteBuffer data) { - gl2.glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, data); - checkError(); - } - - public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, int format, int type, ByteBuffer data) { - gl2.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data); - checkError(); - } - - public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, int depth, int format, int type, ByteBuffer data) { - gl2.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data); - checkError(); - } - - public void glBindFragDataLocation(int param1, int param2, String param3) { - gl3.glBindFragDataLocation(param1, param2, param3); - checkError(); - } - - public void glBindVertexArray(int param1) { - gl3.glBindVertexArray(param1); - checkError(); - } - - public void glGenVertexArrays(IntBuffer param1) { - gl3.glGenVertexArrays(param1); - checkError(); - } - - @Override - public String glGetString(int param1, int param2) { - String result = gl3.glGetString(param1, param2); - checkError(); - return result; - } - - @Override - public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { - final int result = gl3.glGetUniformBlockIndex(program, uniformBlockName); - checkError(); - return result; - } - - @Override - public void glBindBufferBase(final int target, final int index, final int buffer) { - gl3.glBindBufferBase(target, index, buffer); - checkError(); - } - - @Override - public void glDeleteVertexArrays(IntBuffer arrays) { - gl3.glDeleteVertexArrays(arrays); - checkError(); - } - - @Override - public void glPatchParameter(int count) { - gl4.glPatchParameter(count); - checkError(); - } - - @Override - public int glGetProgramResourceIndex(int program, int programInterface, String name) { - final int result = gl4.glGetProgramResourceIndex(program, programInterface, name); - checkError(); - return result; - } - - @Override - public void glShaderStorageBlockBinding(int program, int storageBlockIndex, int storageBlockBinding) { - gl4.glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); - checkError(); - } - - public void glBlendEquationSeparate(int colorMode, int alphaMode) { - gl.glBlendEquationSeparate(colorMode, alphaMode); - checkError(); - } - - @Override - public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { - gl3.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); - checkError(); - } -} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java deleted file mode 100644 index 19546f20f2..0000000000 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLDebugES.java +++ /dev/null @@ -1,662 +0,0 @@ -package com.jme3.renderer.opengl; - -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; - -public class GLDebugES extends GLDebug implements GL, GL2, GLES_30, GLFbo, GLExt { - - private final GLFbo glfbo; - private final GLExt glext; - - public GLDebugES(GL gl, GLExt glext, GLFbo glfbo) { - this.gl = gl; - this.glext = glext; - this.glfbo = glfbo; - } - - public void resetStats() { - gl.resetStats(); - } - - public void glActiveTexture(int texture) { - gl.glActiveTexture(texture); - checkError(); - } - - public void glAttachShader(int program, int shader) { - gl.glAttachShader(program, shader); - checkError(); - } - - @Override - public void glBeginQuery(int target, int query) { - gl.glBeginQuery(target, query); - checkError(); - } - - public void glBindBuffer(int target, int buffer) { - gl.glBindBuffer(target, buffer); - checkError(); - } - - public void glBindTexture(int target, int texture) { - gl.glBindTexture(target, texture); - checkError(); - } - - public void glBlendFunc(int sfactor, int dfactor) { - gl.glBlendFunc(sfactor, dfactor); - checkError(); - } - - public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dFactorAlpha) - { - gl.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dFactorAlpha); - checkError(); - } - - public void glBufferData(int target, FloatBuffer data, int usage) { - gl.glBufferData(target, data, usage); - checkError(); - } - - public void glBufferData(int target, ShortBuffer data, int usage) { - gl.glBufferData(target, data, usage); - checkError(); - } - - public void glBufferData(int target, ByteBuffer data, int usage) { - gl.glBufferData(target, data, usage); - checkError(); - } - - public void glBufferSubData(int target, long offset, FloatBuffer data) { - gl.glBufferSubData(target, offset, data); - checkError(); - } - - public void glBufferSubData(int target, long offset, ShortBuffer data) { - gl.glBufferSubData(target, offset, data); - checkError(); - } - - public void glBufferSubData(int target, long offset, ByteBuffer data) { - gl.glBufferSubData(target, offset, data); - checkError(); - } - - public void glClear(int mask) { - gl.glClear(mask); - checkError(); - } - - public void glClearColor(float red, float green, float blue, float alpha) { - gl.glClearColor(red, green, blue, alpha); - checkError(); - } - - public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) { - gl.glColorMask(red, green, blue, alpha); - checkError(); - } - - public void glCompileShader(int shader) { - gl.glCompileShader(shader); - checkError(); - } - - public void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, ByteBuffer data) { - gl.glCompressedTexImage2D(target, level, internalformat, width, height, border, data); - checkError(); - } - - public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data) { - gl.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, data); - checkError(); - } - - public int glCreateProgram() { - int program = gl.glCreateProgram(); - checkError(); - return program; - } - - public int glCreateShader(int shaderType) { - int shader = gl.glCreateShader(shaderType); - checkError(); - return shader; - } - - public void glCullFace(int mode) { - gl.glCullFace(mode); - checkError(); - } - - public void glDeleteBuffers(IntBuffer buffers) { - gl.glDeleteBuffers(buffers); - checkError(); - } - - public void glDeleteProgram(int program) { - gl.glDeleteProgram(program); - checkError(); - } - - public void glDeleteShader(int shader) { - gl.glDeleteShader(shader); - checkError(); - } - - public void glDeleteTextures(IntBuffer textures) { - gl.glDeleteTextures(textures); - checkError(); - } - - public void glDepthFunc(int func) { - gl.glDepthFunc(func); - checkError(); - } - - public void glDepthMask(boolean flag) { - gl.glDepthMask(flag); - checkError(); - } - - public void glDepthRange(double nearVal, double farVal) { - gl.glDepthRange(nearVal, farVal); - checkError(); - } - - public void glDetachShader(int program, int shader) { - gl.glDetachShader(program, shader); - checkError(); - } - - public void glDisable(int cap) { - gl.glDisable(cap); - checkError(); - } - - public void glDisableVertexAttribArray(int index) { - gl.glDisableVertexAttribArray(index); - checkError(); - } - - public void glDrawArrays(int mode, int first, int count) { - gl.glDrawArrays(mode, first, count); - checkError(); - } - - public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) { - gl.glDrawRangeElements(mode, start, end, count, type, indices); - checkError(); - } - - public void glEnable(int cap) { - gl.glEnable(cap); - checkError(); - } - - public void glEnableVertexAttribArray(int index) { - gl.glEnableVertexAttribArray(index); - checkError(); - } - - @Override - public void glEndQuery(int target) { - checkError(); - } - - public void glGenBuffers(IntBuffer buffers) { - gl.glGenBuffers(buffers); - checkError(); - } - - public void glGenTextures(IntBuffer textures) { - gl.glGenTextures(textures); - checkError(); - } - - @Override - public void glGenQueries(int num, IntBuffer ids) { - glGenQueries(num, ids); - checkError(); - } - - public int glGetAttribLocation(int program, String name) { - int location = gl.glGetAttribLocation(program, name); - checkError(); - return location; - } - - public void glGetBoolean(int pname, ByteBuffer params) { - gl.glGetBoolean(pname, params); - checkError(); - } - - public int glGetError() { - // No need to check for error here? Haha - return gl.glGetError(); - } - - public void glGetInteger(int pname, IntBuffer params) { - gl.glGetInteger(pname, params); - checkError(); - } - - public void glGetProgram(int program, int pname, IntBuffer params) { - gl.glGetProgram(program, pname, params); - checkError(); - } - - public String glGetProgramInfoLog(int program, int maxSize) { - String infoLog = gl.glGetProgramInfoLog(program, maxSize); - checkError(); - return infoLog; - } - - @Override - public long glGetQueryObjectui64(int query, int pname) { - long res = gl.glGetQueryObjectui64(query, pname); - checkError(); - return res; - } - - @Override - public int glGetQueryObjectiv(int query, int pname) { - int res = gl.glGetQueryObjectiv(query, pname); - checkError(); - return res; - } - - public void glGetShader(int shader, int pname, IntBuffer params) { - gl.glGetShader(shader, pname, params); - checkError(); - } - - public String glGetShaderInfoLog(int shader, int maxSize) { - String infoLog = gl.glGetShaderInfoLog(shader, maxSize); - checkError(); - return infoLog; - } - - public String glGetString(int name) { - String string = gl.glGetString(name); - checkError(); - return string; - } - - public int glGetUniformLocation(int program, String name) { - int location = gl.glGetUniformLocation(program, name); - checkError(); - return location; - } - - public boolean glIsEnabled(int cap) { - boolean enabled = gl.glIsEnabled(cap); - checkError(); - return enabled; - } - - public void glLineWidth(float width) { - gl.glLineWidth(width); - checkError(); - } - - public void glLinkProgram(int program) { - gl.glLinkProgram(program); - checkError(); - } - - public void glPixelStorei(int pname, int param) { - gl.glPixelStorei(pname, param); - checkError(); - } - - public void glPolygonOffset(float factor, float units) { - gl.glPolygonOffset(factor, units); - checkError(); - } - - public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) { - gl.glReadPixels(x, y, width, height, format, type, data); - checkError(); - } - - public void glReadPixels(int x, int y, int width, int height, int format, int type, long offset) { - gl.glReadPixels(x, y, width, height, format, type, offset); - checkError(); - } - - public void glScissor(int x, int y, int width, int height) { - gl.glScissor(x, y, width, height); - checkError(); - } - - public void glShaderSource(int shader, String[] string, IntBuffer length) { - gl.glShaderSource(shader, string, length); - checkError(); - } - - public void glStencilFuncSeparate(int face, int func, int ref, int mask) { - gl.glStencilFuncSeparate(face, func, ref, mask); - checkError(); - } - - public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass) { - gl.glStencilOpSeparate(face, sfail, dpfail, dppass); - checkError(); - } - - public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) { - gl.glTexImage2D(target, level, internalFormat, width, height, border, format, type, data); - checkError(); - } - - public void glTexParameterf(int target, int pname, float param) { - gl.glTexParameterf(target, pname, param); - checkError(); - } - - public void glTexParameteri(int target, int pname, int param) { - gl.glTexParameteri(target, pname, param); - checkError(); - } - - public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { - gl.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data); - checkError(); - } - - public void glUniform1(int location, FloatBuffer value) { - gl.glUniform1(location, value); - checkError(); - } - - public void glUniform1(int location, IntBuffer value) { - gl.glUniform1(location, value); - checkError(); - } - - public void glUniform1f(int location, float v0) { - gl.glUniform1f(location, v0); - checkError(); - } - - public void glUniform1i(int location, int v0) { - gl.glUniform1i(location, v0); - checkError(); - } - - public void glUniform2(int location, IntBuffer value) { - gl.glUniform2(location, value); - checkError(); - } - - public void glUniform2(int location, FloatBuffer value) { - gl.glUniform2(location, value); - checkError(); - } - - public void glUniform2f(int location, float v0, float v1) { - gl.glUniform2f(location, v0, v1); - checkError(); - } - - public void glUniform3(int location, IntBuffer value) { - gl.glUniform3(location, value); - checkError(); - } - - public void glUniform3(int location, FloatBuffer value) { - gl.glUniform3(location, value); - checkError(); - } - - public void glUniform3f(int location, float v0, float v1, float v2) { - gl.glUniform3f(location, v0, v1, v2); - checkError(); - } - - public void glUniform4(int location, FloatBuffer value) { - gl.glUniform4(location, value); - checkError(); - } - - public void glUniform4(int location, IntBuffer value) { - gl.glUniform4(location, value); - checkError(); - } - - public void glUniform4f(int location, float v0, float v1, float v2, float v3) { - gl.glUniform4f(location, v0, v1, v2, v3); - checkError(); - } - - public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) { - gl.glUniformMatrix3(location, transpose, value); - checkError(); - } - - public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) { - gl.glUniformMatrix4(location, transpose, value); - checkError(); - } - - public void glUseProgram(int program) { - gl.glUseProgram(program); - checkError(); - } - - public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) { - gl.glVertexAttribPointer(index, size, type, normalized, stride, pointer); - checkError(); - } - - public void glViewport(int x, int y, int width, int height) { - gl.glViewport(x, y, width, height); - checkError(); - } - - public void glBindFramebufferEXT(int param1, int param2) { - glfbo.glBindFramebufferEXT(param1, param2); - checkError(); - } - - public void glBindRenderbufferEXT(int param1, int param2) { - glfbo.glBindRenderbufferEXT(param1, param2); - checkError(); - } - - public int glCheckFramebufferStatusEXT(int param1) { - int result = glfbo.glCheckFramebufferStatusEXT(param1); - checkError(); - return result; - } - - public void glDeleteFramebuffersEXT(IntBuffer param1) { - glfbo.glDeleteFramebuffersEXT(param1); - checkError(); - } - - public void glDeleteRenderbuffersEXT(IntBuffer param1) { - glfbo.glDeleteRenderbuffersEXT(param1); - checkError(); - } - - public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { - glfbo.glFramebufferRenderbufferEXT(param1, param2, param3, param4); - checkError(); - } - - public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { - glfbo.glFramebufferTexture2DEXT(param1, param2, param3, param4, param5); - checkError(); - } - - public void glGenFramebuffersEXT(IntBuffer param1) { - glfbo.glGenFramebuffersEXT(param1); - checkError(); - } - - public void glGenRenderbuffersEXT(IntBuffer param1) { - glfbo.glGenRenderbuffersEXT(param1); - checkError(); - } - - public void glGenerateMipmapEXT(int param1) { - glfbo.glGenerateMipmapEXT(param1); - checkError(); - } - - public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { - glfbo.glRenderbufferStorageEXT(param1, param2, param3, param4); - checkError(); - } - - public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - glfbo.glBlitFramebufferEXT(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - checkError(); - } - - @Override - public void glBufferData(int target, long data_size, int usage) { - gl.glBufferData(target, data_size, usage); - checkError(); - } - - @Override - public void glGetBufferSubData(int target, long offset, ByteBuffer data) { - gl.glGetBufferSubData(target, offset, data); - checkError(); - } - - public void glBufferData(int target, IntBuffer data, int usage) { - glext.glBufferData(target, data, usage); - checkError(); - } - - public void glBufferSubData(int target, long offset, IntBuffer data) { - glext.glBufferSubData(target, offset, data); - checkError(); - } - - public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { - glext.glDrawArraysInstancedARB(mode, first, count, primcount); - checkError(); - } - - public void glDrawBuffers(IntBuffer bufs) { - glext.glDrawBuffers(bufs); - checkError(); - } - - public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { - glext.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount); - checkError(); - } - - public void glGetMultisample(int pname, int index, FloatBuffer val) { - glext.glGetMultisample(pname, index, val); - checkError(); - } - - public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { - glfbo.glRenderbufferStorageMultisampleEXT(target, samples, internalformat, width, height); - checkError(); - } - - public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { - glext.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); - checkError(); - } - - public void glVertexAttribDivisorARB(int index, int divisor) { - glext.glVertexAttribDivisorARB(index, divisor); - checkError(); - } - - @Override - public int glClientWaitSync(Object sync, int flags, long timeout) { - int result = glext.glClientWaitSync(sync, flags, timeout); - checkError(); - return result; - } - - @Override - public void glDeleteSync(Object sync) { - glext.glDeleteSync(sync); - checkError(); - } - - @Override - public Object glFenceSync(int condition, int flags) { - Object sync = glext.glFenceSync(condition, flags); - checkError(); - return sync; - } - - @Override - public void glBlendEquationSeparate(int colorMode, int alphaMode) { - gl.glBlendEquationSeparate(colorMode, alphaMode); - checkError(); - } - - @Override - public void glFramebufferTextureLayerEXT(int param1, int param2, int param3, int param4, int param5) { - glfbo.glFramebufferTextureLayerEXT(param1, param2, param3, param4, param5); - checkError(); - } - - public void glAlphaFunc(int func, float ref) { - ((GL2)gl).glAlphaFunc(func, ref); - checkError(); - } - - public void glPointSize(float size) { - ((GL2)gl).glPointSize(size); - checkError(); - } - - public void glPolygonMode(int face, int mode) { - ((GL2)gl).glPolygonMode(face, mode); - checkError(); - } - - public void glDrawBuffer(int mode) { - ((GL2)gl).glDrawBuffer(mode); - checkError(); - } - - public void glReadBuffer(int mode) { - ((GL2)gl).glReadBuffer(mode); - checkError(); - } - - public void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, - int border, ByteBuffer data) { - ((GL2)gl).glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, data); - checkError(); - } - - public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, - int height, int depth, int format, ByteBuffer data) { - ((GL2)gl).glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, data); - checkError(); - } - - public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, - int format, int type, ByteBuffer data) { - ((GL2)gl).glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data); - checkError(); - } - - public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, - int depth, int format, int type, ByteBuffer data) { - ((GL2)gl).glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data); - checkError(); - } - -} diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java index 017dae8163..755e076cac 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLES_30.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,7 @@ */ package com.jme3.renderer.opengl; -import java.nio.ByteBuffer; +import java.nio.IntBuffer; /** * GL functions and constants only available on vanilla OpenGL ES 3.0. @@ -42,5 +42,10 @@ public interface GLES_30 extends GL { public static final int GL_RGB10_A2 = 0x8059; public static final int GL_UNSIGNED_INT_2_10_10_10_REV = 0x8368; + + public void glBindVertexArray(int array); + public void glDeleteVertexArrays(IntBuffer arrays); + + public void glGenVertexArrays(IntBuffer arrays); } 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 642ac0c71b..3cc4f175fa 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,6 +45,11 @@ public interface GLExt { public static final int GL_ALREADY_SIGNALED = 0x911A; public static final int GL_COMPRESSED_RGB8_ETC2 = 0x9274; + public static final int GL_COMPRESSED_SRGB8_ETC2 = 0x9275; + public static final int GL_COMPRESSED_RGBA8_ETC2_EAC = 0x9278; + public static final int GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC = 0x9279; + public static final int GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9276; + public static final int GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2 = 0x9277; public static final int GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1; public static final int GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2; public static final int GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3; @@ -101,25 +106,46 @@ public interface GLExt { public static final int GL_UNSIGNED_INT_24_8_EXT = 0x84FA; public static final int GL_UNSIGNED_INT_5_9_9_9_REV_EXT = 0x8C3E; public static final int GL_WAIT_FAILED = 0x911D; + + // OpenGL 4.2 texture compression, we now check these through the extension + public static final int GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT = 0x8E8E; + public static final int GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT = 0x8E8F; + 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

              - *

              + * * Creates and initializes a buffer object's data store. - *

              + * *

              {@code usage} is a hint to the GL implementation as to how a buffer object's data store will be accessed. This enables the GL implementation to make * more intelligent decisions that may significantly impact buffer object performance. It does not, however, constrain the actual usage of the data store. * {@code usage} can be broken down into two parts: first, the frequency of access (modification and usage), and second, the nature of that access. The * frequency of access may be one of these:

              - *

              + * *

                *
              • STREAM - The data store contents will be modified once and used at most a few times.
              • *
              • STATIC - The data store contents will be modified once and used many times.
              • *
              • DYNAMIC - The data store contents will be modified repeatedly and used many times.
              • *
              - *

              + * *

              The nature of access may be one of these:

              - *

              + * *

                *
              • DRAW - The data store contents are modified by the application, and used as the source for GL drawing and image specification commands.
              • *
              • READ - The data store contents are modified by reading data from the GL, and used to return that data when queried by the application.
              • @@ -196,6 +222,7 @@ public interface GLExt { * * @param condition the condition that must be met to set the sync object's state to signaled. * @param flags a bitwise combination of flags controlling the behavior of the sync object. No flags are presently defined for this operation and {@code flags} must be zero. + * @return a new instance */ public Object glFenceSync(int condition, int flags); @@ -232,4 +259,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/GLFence.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFence.java new file mode 100644 index 0000000000..118237c22d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLFence.java @@ -0,0 +1,121 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.opengl; + +import com.jme3.renderer.Renderer; +import com.jme3.util.NativeObject; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Wrapper for an OpenGL sync object (fence). + *

                See here.

                + *

                + * A fence is a synchronization primitive that can be used to coordinate + * work between the CPU and GPU. Once inserted into the command stream, + * the GPU will signal the fence when all preceding commands have completed. + *

                + * This class wraps the native sync handle (either a long address or a GLSync) + * + * @see GL4#glFenceSync(int, int) + * @see GL4#glClientWaitSync(GLFence, int, long) + * @see GL4#glDeleteSync(GLFence) + */ +public class GLFence extends NativeObject { + private final static AtomicInteger nextUniqueId = new AtomicInteger(1); + private Object nativeSync; + + + /** + * Most NativeObject implementations use the int handle GL returns as the NativeObjectId. + * However, fence IDs are actually longs. + * (This probably won't cause overflow issues; you're not likely to have 2 billion fences usefully around at once.) + */ + private final long fenceId; + + /** + * Creates a new fence wrapper with the given handle and native sync object. + * + * @param fenceId the native sync object handle (pointer) + * @param nativeSync the backend-specific sync object (e.g., LWJGL2's GLSync) or null + */ + public GLFence(long fenceId, Object nativeSync) { + super(); + this.fenceId = fenceId; + this.id = nextUniqueId.getAndIncrement(); + this.nativeSync = nativeSync; + clearUpdateNeeded(); + } + private GLFence(GLFence source) { + super(); + this.fenceId = source.fenceId; + this.nativeSync = source.nativeSync; + this.id = source.id; + } + + /** + * Returns the backend-specific native sync object, if set. + *

                + * This is used by LWJGL2 that require their own GLSync + * object type rather than a raw pointer. + * + * @return the native sync object, or null if not set + */ + public Object getNativeSync() { + return nativeSync; + } + public long getFenceId() { + return fenceId; + } + + @Override + public void resetObject() { + this.nativeSync = null; + this.id = INVALID_ID; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + ((Renderer)rendererObject).deleteFence(this); + } + + @Override + public NativeObject createDestructableClone() { + return new GLFence(this); + } + + @Override + public long getUniqueId() { + return ((long) OBJTYPE_FENCE << 32) | (0xffffffffL & (long) id); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java index 6daee5aff8..c73943c54e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLImageFormats.java @@ -121,7 +121,7 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { formatSrgbSwiz(formatToGL, Format.Luminance8Alpha8, GLExt.GL_SRGB8_ALPHA8_EXT, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); } - if (caps.contains(Caps.OpenGL20)) { + if (caps.contains(Caps.OpenGL20)||caps.contains(Caps.OpenGLES30)) { if (!caps.contains(Caps.CoreProfile)) { format(formatToGL, Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); @@ -174,7 +174,7 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { formatSwiz(formatToGL, Format.BGRA8, GLExt.GL_RGBA8, GL2.GL_RGBA, GL.GL_UNSIGNED_BYTE); formatSwiz(formatToGL, Format.ABGR8, GLExt.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } else { - // Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above.. + // Actually, the internal format isn't used for OpenGL ES 2! This is the same as the above. if (!caps.contains(Caps.CoreProfile)) { format(formatToGL, Format.Alpha8, GL.GL_RGBA4, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE); format(formatToGL, Format.Luminance8, GL.GL_RGB565, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE); @@ -238,17 +238,21 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { format(formatToGL, Format.RGB111110F, GLExt.GL_R11F_G11F_B10F_EXT, GL.GL_RGB, GLExt.GL_UNSIGNED_INT_10F_11F_11F_REV_EXT); } - // Need to check if Caps.DepthTexture is supported prior to using for textures - // But for renderbuffers its OK. + // Need to check whether Caps.DepthTexture is supported before using it for textures. + // But for render buffers it's OK. format(formatToGL, Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT); - // NOTE: OpenGL ES 2.0 does not support DEPTH_COMPONENT as internal format -- fallback to 16-bit depth. - if (caps.contains(Caps.OpenGLES20)) { + + if (caps.contains(Caps.WebGL)) { + // NOTE: fallback to 24-bit depth as workaround for firefox bug in WebGL 2 where DEPTH_COMPONENT16 is not handled properly + format(formatToGL, Format.Depth, GL2.GL_DEPTH_COMPONENT24, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT); + } else if (caps.contains(Caps.OpenGLES20)) { + // NOTE: OpenGL ES 2.0 does not support DEPTH_COMPONENT as internal format -- fallback to 16-bit depth. format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT16, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT); } else { format(formatToGL, Format.Depth, GL.GL_DEPTH_COMPONENT, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE); } - if (caps.contains(Caps.OpenGL20) || caps.contains(Caps.Depth24)) { + if (caps.contains(Caps.OpenGLES30) || caps.contains(Caps.OpenGL20) || caps.contains(Caps.Depth24)) { format(formatToGL, Format.Depth24, GL2.GL_DEPTH_COMPONENT24, GL.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT); } if (caps.contains(Caps.FloatDepthBuffer)) { @@ -266,7 +270,7 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { formatComp(formatToGL, Format.DXT5, GLExt.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } - if(caps.contains(Caps.OpenGL30)){ + if(caps.contains(Caps.OpenGL30) || caps.contains(Caps.TextureCompressionRGTC)){ formatComp(formatToGL, Format.RGTC2, GL3.GL_COMPRESSED_RG_RGTC2, GL3.GL_RG, GL.GL_UNSIGNED_BYTE); formatComp(formatToGL, Format.SIGNED_RGTC2, GL3.GL_COMPRESSED_SIGNED_RG_RGTC2, GL3.GL_RG, GL.GL_BYTE); formatComp(formatToGL, Format.RGTC1, GL3.GL_COMPRESSED_RED_RGTC1, GL3.GL_RED, GL.GL_UNSIGNED_BYTE); @@ -274,9 +278,25 @@ public static GLImageFormat[][] getFormatsForCaps(EnumSet caps) { } if (caps.contains(Caps.TextureCompressionETC2)) { + formatComp(formatToGL, Format.ETC2, GLExt.GL_COMPRESSED_RGBA8_ETC2_EAC, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.ETC2_ALPHA1, GLExt.GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); formatComp(formatToGL, Format.ETC1, GLExt.GL_COMPRESSED_RGB8_ETC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + if (caps.contains(Caps.Srgb)) { + formatCompSrgb(formatToGL, Format.ETC2, GLExt.GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatCompSrgb(formatToGL, Format.ETC2_ALPHA1, GLExt.GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); + formatCompSrgb(formatToGL, Format.ETC1, GLExt.GL_COMPRESSED_SRGB8_ETC2, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + } } else if (caps.contains(Caps.TextureCompressionETC1)) { - formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.ETC1, GLExt.GL_ETC1_RGB8_OES, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + } + + + + if(caps.contains(Caps.OpenGL42) || caps.contains(Caps.TextureCompressionBPTC)) { + formatComp(formatToGL, Format.BC6H_SF16, GLExt.GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.BC6H_UF16, GLExt.GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE); + formatComp(formatToGL, Format.BC7_UNORM, GLExt.GL_COMPRESSED_RGBA_BPTC_UNORM, GL.GL_RGBA, GL.GL_UNSIGNED_INT); + formatComp(formatToGL, Format.BC7_UNORM_SRGB, GLExt.GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM, GL.GL_RGBA, GL.GL_UNSIGNED_INT); } // Integer formats 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 c61e54aecb..f4ae6fe0e1 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-2019 jMonkeyEngine + * Copyright (c) 2009-2026 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,12 @@ import com.jme3.shader.*; import com.jme3.shader.Shader.ShaderSource; import com.jme3.shader.Shader.ShaderType; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import com.jme3.shader.ShaderBufferBlock.BufferType; +import com.jme3.shader.bufferobject.BufferObject; +import com.jme3.shader.bufferobject.BufferRegion; +import com.jme3.shader.bufferobject.DirtyRegionsIterator; import com.jme3.texture.FrameBuffer; import com.jme3.texture.FrameBuffer.RenderBuffer; import com.jme3.texture.Image; @@ -55,13 +61,17 @@ import com.jme3.texture.Texture2D; import com.jme3.texture.Texture.ShadowCompareMode; import com.jme3.texture.Texture.WrapAxis; +import com.jme3.texture.TextureImage; import com.jme3.texture.image.LastTextureState; import com.jme3.util.BufferUtils; import com.jme3.util.ListMap; import com.jme3.util.MipMapGenerator; +import com.jme3.util.NativeObject; import com.jme3.util.NativeObjectManager; + import jme3tools.shader.ShaderDebug; +import java.lang.ref.WeakReference; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; @@ -80,22 +90,24 @@ public final class GLRenderer implements Renderer { private static final Pattern GLVERSION_PATTERN = Pattern.compile(".*?(\\d+)\\.(\\d+).*"); private final ByteBuffer nameBuf = BufferUtils.createByteBuffer(250); + private final FloatBuffer floatBuf16 = BufferUtils.createFloatBuffer(16); private final StringBuilder stringBuf = new StringBuilder(250); private final IntBuffer intBuf1 = BufferUtils.createIntBuffer(1); private final IntBuffer intBuf16 = BufferUtils.createIntBuffer(16); - private final FloatBuffer floatBuf16 = BufferUtils.createFloatBuffer(16); private final RenderContext context = new RenderContext(); private final NativeObjectManager objManager = new NativeObjectManager(); private final EnumSet caps = EnumSet.noneOf(Caps.class); - private final EnumMap limits = new EnumMap(Limits.class); + private final EnumMap limits = new EnumMap<>(Limits.class); private FrameBuffer mainFbOverride = null; + private int defaultFBO = 0; private final Statistics statistics = new Statistics(); private int vpX, vpY, vpW, vpH; private int clipX, clipY, clipW, clipH; private int defaultAnisotropicFilter = 1; private boolean linearizeSrgbImages; private HashSet extensions; + private boolean generateMipmapsForFramebuffers = true; private final GL gl; private final GL2 gl2; @@ -104,6 +116,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; @@ -114,24 +129,57 @@ public GLRenderer(GL gl, GLExt glext, GLFbo glfbo) { this.glext = glext; this.texUtil = new TextureUtil(gl, gl2, glext); } + + /** + * Enable/Disable default automatic generation of mipmaps for framebuffers + * @param v Default is true + */ + 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; } - @Override + @Override public EnumSet getCaps() { return caps; } // Not making public yet ... + @Override public EnumMap getLimits() { return limits; } private HashSet loadExtensions() { - HashSet extensionSet = new HashSet(64); + HashSet extensionSet = new HashSet<>(64); if (caps.contains(Caps.OpenGL30)) { // If OpenGL3+ is available, use the non-deprecated way // of getting supported extensions. @@ -147,13 +195,18 @@ private HashSet loadExtensions() { return extensionSet; } + public static boolean isWebGL(String version) { + return version.contains("WebGL"); + } + public static int extractVersion(String version) { + if (version.startsWith("WebGL 2.0")) return 300; Matcher m = GLVERSION_PATTERN.matcher(version); if (m.matches()) { int major = Integer.parseInt(m.group(1)); int minor = Integer.parseInt(m.group(2)); if (minor >= 10 && minor % 10 == 0) { - // some versions can look like "1.30" instead of "1.3". + // some versions can look like "1.30" instead of "1.3". // make sure to correct for this minor /= 10; } @@ -168,7 +221,11 @@ private boolean hasExtension(String extensionName) { } private void loadCapabilitiesES() { - int oglVer = extractVersion(gl.glGetString(GL.GL_VERSION)); + String version = gl.glGetString(GL.GL_VERSION); + int oglVer = extractVersion(version); + if (isWebGL(version)) { + caps.add(Caps.WebGL); + } caps.add(Caps.GLSL100); caps.add(Caps.OpenGLES20); @@ -338,14 +395,14 @@ private void loadCapabilitiesCommon() { hasExtension("GL_ARB_half_float_pixel"); if (!hasFloatTexture) { - hasFloatTexture = caps.contains(Caps.OpenGL30); + hasFloatTexture = caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30); } } if (hasFloatTexture) { caps.add(Caps.FloatTexture); } - + // integer texture format extensions if(hasExtension("GL_EXT_texture_integer") || caps.contains(Caps.OpenGL30)) caps.add(Caps.IntegerTexture); @@ -369,9 +426,14 @@ private void loadCapabilitiesCommon() { } if (hasExtension("GL_ARB_color_buffer_float") && - hasExtension("GL_ARB_half_float_pixel")) { - // XXX: Require both 16 and 32 bit float support for FloatColorBuffer. + hasExtension("GL_ARB_half_float_pixel") + ||caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30)) { + // XXX: Require both 16- and 32-bit float support for FloatColorBuffer. caps.add(Caps.FloatColorBuffer); + caps.add(Caps.FloatColorBufferRGBA); + if (!caps.contains(Caps.OpenGLES30)) { + caps.add(Caps.FloatColorBufferRGB); + } } if (caps.contains(Caps.OpenGLES30) || hasExtension("GL_ARB_depth_buffer_float")) { @@ -393,6 +455,14 @@ private void loadCapabilitiesCommon() { caps.add(Caps.TextureCompressionS3TC); } + if (hasExtension("GL_ARB_texture_compression_bptc")) { + caps.add(Caps.TextureCompressionBPTC); + } + + if (hasExtension("GL_EXT_texture_compression_rgtc")) { + caps.add(Caps.TextureCompressionRGTC); + } + if (hasExtension("GL_ARB_ES3_compatibility")) { caps.add(Caps.TextureCompressionETC2); caps.add(Caps.TextureCompressionETC1); @@ -402,13 +472,13 @@ private void loadCapabilitiesCommon() { // == end texture format extensions == - if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30)) { + if (hasExtension("GL_ARB_vertex_array_object") || caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30) ) { caps.add(Caps.VertexBufferArray); } if (hasExtension("GL_ARB_texture_non_power_of_two") || hasExtension("GL_OES_texture_npot") || - caps.contains(Caps.OpenGL30)) { + caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30)) { caps.add(Caps.NonPowerOfTwoTextures); } else { logger.log(Level.WARNING, "Your graphics card does not " @@ -430,7 +500,7 @@ private void loadCapabilitiesCommon() { limits.put(Limits.TextureAnisotropy, getInteger(GLExt.GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT)); } - if (hasExtension("GL_EXT_framebuffer_object") + if (hasExtension("GL_EXT_framebuffer_object") || caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES20)) { caps.add(Caps.FrameBuffer); @@ -447,7 +517,9 @@ private void loadCapabilitiesCommon() { limits.put(Limits.FrameBufferSamples, getInteger(GLExt.GL_MAX_SAMPLES_EXT)); } - if (hasExtension("GL_ARB_texture_multisample") || caps.contains(Caps.OpenGLES31)) { // GLES31 does not fully support it + if (hasExtension("GL_ARB_texture_multisample") || caps.contains(Caps.OpenGLES31) + || (JmeSystem.getPlatform().getOs() == Platform.Os.MacOS + && caps.contains(Caps.OpenGL32))) { // GLES31 does not fully support it caps.add(Caps.TextureMultisample); limits.put(Limits.ColorTextureSamples, getInteger(GLExt.GL_MAX_COLOR_TEXTURE_SAMPLES)); limits.put(Limits.DepthTextureSamples, getInteger(GLExt.GL_MAX_DEPTH_TEXTURE_SAMPLES)); @@ -456,7 +528,7 @@ private void loadCapabilitiesCommon() { limits.put(Limits.FrameBufferSamples, limits.get(Limits.ColorTextureSamples)); } } - + if (hasExtension("GL_ARB_draw_buffers") || caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30)) { limits.put(Limits.FrameBufferMrtAttachments, getInteger(GLExt.GL_MAX_DRAW_BUFFERS_ARB)); if (limits.get(Limits.FrameBufferMrtAttachments) > 1) { @@ -473,7 +545,7 @@ private void loadCapabilitiesCommon() { logger.log(Level.FINER, "Samples: {0}", samples); boolean enabled = gl.glIsEnabled(GLExt.GL_MULTISAMPLE_ARB); if (samples > 0 && available && !enabled) { - // Doesn't seem to be necessary .. OGL spec says it's always + // Doesn't seem to be necessary. OGL spec says it's always // set by default? gl.glEnable(GLExt.GL_MULTISAMPLE_ARB); } @@ -482,7 +554,7 @@ private void loadCapabilitiesCommon() { // Supports sRGB pipeline. if ( (hasExtension("GL_ARB_framebuffer_sRGB") && hasExtension("GL_EXT_texture_sRGB")) - || caps.contains(Caps.OpenGL30) ) { + || caps.contains(Caps.OpenGL30) || caps.contains(Caps.OpenGLES30)) { caps.add(Caps.Srgb); } @@ -491,8 +563,10 @@ private void loadCapabilitiesCommon() { caps.add(Caps.SeamlessCubemap); } - if (caps.contains(Caps.OpenGL32) && !hasExtension("GL_ARB_compatibility")) { - caps.add(Caps.CoreProfile); + if ((caps.contains(Caps.OpenGLES30) || caps.contains(Caps.OpenGL32)) && !hasExtension("GL_ARB_compatibility")) { + if (JmeSystem.getPlatform().getOs() != Platform.Os.iOS) { // some features are not supported on iOS + caps.add(Caps.CoreProfile); + } } if (hasExtension("GL_ARB_get_program_binary")) { @@ -502,36 +576,58 @@ private void loadCapabilitiesCommon() { } } + if (hasExtension("GL_OES_geometry_shader") || hasExtension("GL_EXT_geometry_shader")) { + caps.add(Caps.GeometryShader); + } + + if (hasExtension("GL_OES_tessellation_shader") || hasExtension("GL_EXT_tessellation_shader")) { + caps.add(Caps.TesselationShader); + } + if (hasExtension("GL_ARB_shader_storage_buffer_object")) { caps.add(Caps.ShaderStorageBufferObject); - limits.put(Limits.ShaderStorageBufferObjectMaxBlockSize, getInteger(GL4.GL_MAX_SHADER_STORAGE_BLOCK_SIZE)); - limits.put(Limits.ShaderStorageBufferObjectMaxComputeBlocks, getInteger(GL4.GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxGeometryBlocks, getInteger(GL4.GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxFragmentBlocks, getInteger(GL4.GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxVertexBlocks, getInteger(GL4.GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxTessControlBlocks, getInteger(GL4.GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxTessEvaluationBlocks, getInteger(GL4.GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS)); - limits.put(Limits.ShaderStorageBufferObjectMaxCombineBlocks, getInteger(GL4.GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxBlockSize, + getInteger(GL4.GL_MAX_SHADER_STORAGE_BLOCK_SIZE)); + // Commented out until we support ComputeShaders and the ComputeShader Cap + // limits.put(Limits.ShaderStorageBufferObjectMaxComputeBlocks, getInteger(GL4.GL_MAX_COMPUTE_SHADER_STORAGE_BLOCKS)); + if (caps.contains(Caps.GeometryShader)) { + limits.put(Limits.ShaderStorageBufferObjectMaxGeometryBlocks, + getInteger(GL4.GL_MAX_GEOMETRY_SHADER_STORAGE_BLOCKS)); + } + limits.put(Limits.ShaderStorageBufferObjectMaxFragmentBlocks, + getInteger(GL4.GL_MAX_FRAGMENT_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxVertexBlocks, + getInteger(GL4.GL_MAX_VERTEX_SHADER_STORAGE_BLOCKS)); + if (caps.contains(Caps.TesselationShader)) { + limits.put(Limits.ShaderStorageBufferObjectMaxTessControlBlocks, + getInteger(GL4.GL_MAX_TESS_CONTROL_SHADER_STORAGE_BLOCKS)); + limits.put(Limits.ShaderStorageBufferObjectMaxTessEvaluationBlocks, + getInteger(GL4.GL_MAX_TESS_EVALUATION_SHADER_STORAGE_BLOCKS)); + } + limits.put(Limits.ShaderStorageBufferObjectMaxCombineBlocks, + getInteger(GL4.GL_MAX_COMBINED_SHADER_STORAGE_BLOCKS)); } if (hasExtension("GL_ARB_uniform_buffer_object")) { caps.add(Caps.UniformBufferObject); - limits.put(Limits.UniformBufferObjectMaxBlockSize, getInteger(GL3.GL_MAX_UNIFORM_BLOCK_SIZE)); - limits.put(Limits.UniformBufferObjectMaxGeometryBlocks, getInteger(GL3.GL_MAX_GEOMETRY_UNIFORM_BLOCKS)); - limits.put(Limits.UniformBufferObjectMaxFragmentBlocks, getInteger(GL3.GL_MAX_FRAGMENT_UNIFORM_BLOCKS)); - limits.put(Limits.UniformBufferObjectMaxVertexBlocks, getInteger(GL3.GL_MAX_VERTEX_UNIFORM_BLOCKS)); + limits.put(Limits.UniformBufferObjectMaxBlockSize, + getInteger(GL3.GL_MAX_UNIFORM_BLOCK_SIZE)); + if (caps.contains(Caps.GeometryShader)) { + limits.put(Limits.UniformBufferObjectMaxGeometryBlocks, + getInteger(GL3.GL_MAX_GEOMETRY_UNIFORM_BLOCKS)); + } + limits.put(Limits.UniformBufferObjectMaxFragmentBlocks, + getInteger(GL3.GL_MAX_FRAGMENT_UNIFORM_BLOCKS)); + limits.put(Limits.UniformBufferObjectMaxVertexBlocks, + getInteger(GL3.GL_MAX_VERTEX_UNIFORM_BLOCKS)); } - if (hasExtension("GL_OES_geometry_shader") || hasExtension("GL_EXT_geometry_shader")) { - caps.add(Caps.GeometryShader); + if (caps.contains(Caps.OpenGL20)) { + caps.add(Caps.UnpackRowLength); } - if (hasExtension("GL_OES_tessellation_shader") || hasExtension("GL_EXT_tessellation_shader")) { - caps.add(Caps.TesselationShader); - } - - if(caps.contains(Caps.OpenGL20)){ - caps.add(Caps.UnpackRowLength); + if (caps.contains(Caps.OpenGL43) || hasExtension("GL_KHR_debug") || caps.contains(Caps.WebGL)) { + caps.add(Caps.GLDebug); } // Print context information @@ -557,7 +653,7 @@ private void loadCapabilitiesCommon() { { sb.append("\t").append(cap.toString()).append("\n"); } - + sb.append("\nHardware limits: \n"); for (Limits limit : Limits.values()) { Integer value = limits.get(limit); @@ -567,7 +663,7 @@ private void loadCapabilitiesCommon() { sb.append("\t").append(limit.name()).append(" = ") .append(value).append("\n"); } - + logger.log(Level.FINE, sb.toString()); } @@ -595,12 +691,13 @@ private boolean getBoolean(int en) { } @SuppressWarnings("fallthrough") + @Override public void initialize() { loadCapabilities(); // Initialize default state.. gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); - + if (caps.contains(Caps.SeamlessCubemap)) { // Enable this globally. Should be OK. gl.glEnable(GLExt.GL_TEXTURE_CUBE_MAP_SEAMLESS); @@ -608,9 +705,17 @@ public void initialize() { if (caps.contains(Caps.CoreProfile)) { // Core Profile requires VAO to be bound. - gl3.glGenVertexArrays(intBuf16); - int vaoId = intBuf16.get(0); - gl3.glBindVertexArray(vaoId); + if(gl3!=null){ + gl3.glGenVertexArrays(intBuf16); + int vaoId = intBuf16.get(0); + gl3.glBindVertexArray(vaoId); + }else if(gl instanceof GLES_30){ + ((GLES_30)gl).glGenVertexArrays(intBuf16); + int vaoId = intBuf16.get(0); + ((GLES_30)gl).glBindVertexArray(vaoId); + } else{ + throw new UnsupportedOperationException("Core profile not supported"); + } } if (gl2 != null && !(gl instanceof GLES_30)) { gl2.glEnable(GL2.GL_VERTEX_PROGRAM_POINT_SIZE); @@ -618,8 +723,19 @@ public void initialize() { gl2.glEnable(GL2.GL_POINT_SPRITE); } } + + IntBuffer tmp = BufferUtils.createIntBuffer(16); + gl.glGetInteger(GL.GL_FRAMEBUFFER_BINDING, tmp); + tmp.rewind(); + int fbOnLoad = tmp.get(); + if(fbOnLoad > 0) + { + // Override default FB to fbOnLoad. Mostly an iOS fix for scene processors and filters. + defaultFBO = fbOnLoad; + } } + @Override public void invalidateState() { context.reset(); if (gl2 != null) { @@ -628,13 +744,15 @@ public void invalidateState() { } } + @Override public void resetGLObjects() { - logger.log(Level.FINE, "Reseting objects and invalidating state"); + logger.log(Level.FINE, "Resetting objects and invalidating state"); objManager.resetObjects(); statistics.clearMemory(); invalidateState(); } + @Override public void cleanup() { logger.log(Level.FINE, "Deleting objects and invalidating state"); objManager.deleteAllObjects(this); @@ -646,10 +764,12 @@ public void cleanup() { /*********************************************************************\ |* Render State *| \*********************************************************************/ + @Override public void setDepthRange(float start, float end) { gl.glDepthRange(start, end); } + @Override public void clearBuffers(boolean color, boolean depth, boolean stencil) { int bits = 0; if (color) { @@ -661,10 +781,10 @@ public void clearBuffers(boolean color, boolean depth, boolean stencil) { bits = GL.GL_COLOR_BUFFER_BIT; } if (depth) { - // glClear(GL.GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false - // here s some link on openl board + // glClear(GL.GL_DEPTH_BUFFER_BIT) seems to not work when glDepthMask is false. + // Here is a link to the openGL discussion: // http://www.opengl.org/discussion_boards/ubbthreads.php?ubb=showflat&Number=257223 - // if depth clear is requested, we enable the depthMask + // If depth clear is requested, we enable the depth mask. if (context.depthWriteEnabled == false) { gl.glDepthMask(true); context.depthWriteEnabled = true; @@ -681,6 +801,7 @@ public void clearBuffers(boolean color, boolean depth, boolean stencil) { } } + @Override public void setBackgroundColor(ColorRGBA color) { if (!context.clearColor.equals(color)) { gl.glClearColor(color.r, color.g, color.b, color.a); @@ -696,6 +817,7 @@ public void setDefaultAnisotropicFilter(int level) { this.defaultAnisotropicFilter = level; } + @Override public void setAlphaToCoverage(boolean value) { if (caps.contains(Caps.Multisample)) { if (value) { @@ -706,6 +828,7 @@ public void setAlphaToCoverage(boolean value) { } } + @Override public void applyRenderState(RenderState state) { if (gl2 != null) { if (state.isWireframe() && !context.wireframe) { @@ -728,7 +851,7 @@ public void applyRenderState(RenderState state) { gl.glDepthFunc(convertTestFunction(state.getDepthFunc())); context.depthFunc = state.getDepthFunc(); } - + if (state.isDepthWrite() && !context.depthWriteEnabled) { gl.glDepthMask(true); context.depthWriteEnabled = true; @@ -827,10 +950,10 @@ public void applyRenderState(RenderState state) { break; case AlphaSumA: blendFuncSeparate( - RenderState.BlendFunc.Src_Alpha, + RenderState.BlendFunc.Src_Alpha, RenderState.BlendFunc.One_Minus_Src_Alpha, RenderState.BlendFunc.One, - RenderState.BlendFunc.One + RenderState.BlendFunc.One ); break; case PremultAlpha: @@ -854,7 +977,7 @@ public void applyRenderState(RenderState state) { + state.getBlendMode()); } - // All of the common modes requires the ADD equation. + // All of the common modes require the ADD equation. // (This might change in the future?) blendEquationSeparate(RenderState.BlendEquation.Add, RenderState.BlendEquationAlpha.InheritColor); } @@ -890,10 +1013,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); } @@ -979,7 +1102,7 @@ private int convertBlendEquation(RenderState.BlendEquation blendEquation) { throw new UnsupportedOperationException("Unrecognized blend operation: " + blendEquation); } } - + private int convertBlendEquationAlpha(RenderState.BlendEquationAlpha blendEquationAlpha) { //Note: InheritColor mode should already be handled, that is why it does not belong the switch case. switch (blendEquationAlpha) { @@ -997,7 +1120,7 @@ private int convertBlendEquationAlpha(RenderState.BlendEquationAlpha blendEquati throw new UnsupportedOperationException("Unrecognized alpha blend operation: " + blendEquationAlpha); } } - + private int convertBlendFunc(BlendFunc blendFunc) { switch (blendFunc) { case Zero: @@ -1020,7 +1143,7 @@ private int convertBlendFunc(BlendFunc blendFunc) { return GL.GL_DST_ALPHA; case One_Minus_Dst_Alpha: return GL.GL_ONE_MINUS_DST_ALPHA; - case Src_Alpha_Saturate: + case Src_Alpha_Saturate: return GL.GL_SRC_ALPHA_SATURATE; default: throw new UnsupportedOperationException("Unrecognized blend function operation: " + blendFunc); @@ -1076,6 +1199,7 @@ private int convertTestFunction(TestFunction testFunc) { /*********************************************************************\ |* Camera and World transforms *| \*********************************************************************/ + @Override public void setViewPort(int x, int y, int w, int h) { if (x != vpX || vpY != y || vpW != w || vpH != h) { gl.glViewport(x, y, w, h); @@ -1086,6 +1210,7 @@ public void setViewPort(int x, int y, int w, int h) { } } + @Override public void setClipRect(int x, int y, int width, int height) { if (!context.clipRectEnabled) { gl.glEnable(GL.GL_SCISSOR_TEST); @@ -1100,6 +1225,7 @@ public void setClipRect(int x, int y, int width, int height) { } } + @Override public void clearClipRect() { if (context.clipRectEnabled) { gl.glDisable(GL.GL_SCISSOR_TEST); @@ -1131,20 +1257,61 @@ protected void bindProgram(Shader shader) { } } - /*********************************************************************\ - |* Shaders *| - \*********************************************************************/ + /*=========*\ + |* Shaders *| + \*=========*/ + + /** + * Update the location of the specified Uniform in the specified Shader. + * + * @param shader the Shader containing the Uniform (not null) + * @param uniform the Uniform to update (not null) + */ protected void updateUniformLocation(Shader shader, Uniform uniform) { int loc = gl.glGetUniformLocation(shader.getId(), uniform.getName()); if (loc < 0) { uniform.setLocation(-1); // uniform is not declared in shader - logger.log(Level.FINE, "Uniform {0} is not declared in shader {1}.", new Object[]{uniform.getName(), shader.getSources()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Uniform {0} is not declared in shader {1}.", + new Object[]{uniform.getName(), shader.getSources()}); + } } else { uniform.setLocation(loc); } } + private boolean isValidNumber(float v) { + return !Float.isNaN(v); + } + + private boolean isValidNumber(FloatBuffer fb) { + for(int i = 0; i < fb.limit(); i++) { + if (!isValidNumber(fb.get(i))) return false; + } + return true; + } + + private boolean isValidNumber(Vector2f v) { + return isValidNumber(v.x) && isValidNumber(v.y); + } + + private boolean isValidNumber(Vector3f v) { + return isValidNumber(v.x) && isValidNumber(v.y) && isValidNumber(v.z); + } + + private boolean isValidNumber(Quaternion q) { + return isValidNumber(q.getX()) && isValidNumber(q.getY()) && isValidNumber(q.getZ()) && isValidNumber(q.getW()); + } + + private boolean isValidNumber(ColorRGBA c) { + return isValidNumber(c.r) && isValidNumber(c.g) && isValidNumber(c.b) && isValidNumber(c.a); + } + + private boolean isValidNumber(Vector4f c) { + return isValidNumber(c.x) && isValidNumber(c.y) && isValidNumber(c.z) && isValidNumber(c.w); + } + protected void updateUniform(Shader shader, Uniform uniform) { int shaderId = shader.getId(); @@ -1170,7 +1337,7 @@ protected void updateUniform(Shader shader, Uniform uniform) { } if (uniform.getVarType() == null) { - return; // value not set yet.. + return; // value not set yet } statistics.onUniformSet(); @@ -1180,26 +1347,33 @@ protected void updateUniform(Shader shader, Uniform uniform) { switch (uniform.getVarType()) { case Float: Float f = (Float) uniform.getValue(); + assert isValidNumber(f) : "Invalid float value " + f + " for " + uniform.getBinding(); gl.glUniform1f(loc, f.floatValue()); break; case Vector2: Vector2f v2 = (Vector2f) uniform.getValue(); + assert isValidNumber(v2) : "Invalid Vector2f value " + v2 + " for " + uniform.getBinding(); gl.glUniform2f(loc, v2.getX(), v2.getY()); break; case Vector3: Vector3f v3 = (Vector3f) uniform.getValue(); + assert isValidNumber(v3) : "Invalid Vector3f value " + v3 + " for " + uniform.getBinding(); gl.glUniform3f(loc, v3.getX(), v3.getY(), v3.getZ()); break; case Vector4: Object val = uniform.getValue(); if (val instanceof ColorRGBA) { ColorRGBA c = (ColorRGBA) val; + assert isValidNumber(c) : "Invalid ColorRGBA value " + c + " for " + uniform.getBinding(); gl.glUniform4f(loc, c.r, c.g, c.b, c.a); } else if (val instanceof Vector4f) { Vector4f c = (Vector4f) val; + assert isValidNumber(c) : "Invalid Vector4f value " + c + " for " + uniform.getBinding(); gl.glUniform4f(loc, c.x, c.y, c.z, c.w); } else { Quaternion c = (Quaternion) uniform.getValue(); + assert isValidNumber(c) : "Invalid Quaternion value " + c + " for " + + uniform.getBinding(); gl.glUniform4f(loc, c.getX(), c.getY(), c.getZ(), c.getW()); } break; @@ -1209,11 +1383,15 @@ protected void updateUniform(Shader shader, Uniform uniform) { break; case Matrix3: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Matrix3f value " + uniform.getValue() + " for " + + uniform.getBinding(); assert fb.remaining() == 9; gl.glUniformMatrix3(loc, false, fb); break; case Matrix4: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Matrix4f value " + uniform.getValue() + " for " + + uniform.getBinding(); assert fb.remaining() == 16; gl.glUniformMatrix4(loc, false, fb); break; @@ -1223,22 +1401,36 @@ protected void updateUniform(Shader shader, Uniform uniform) { break; case FloatArray: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid float array value " + + Arrays.asList((float[]) uniform.getValue()) + " for " + uniform.getBinding(); gl.glUniform1(loc, fb); break; case Vector2Array: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Vector2f array value " + + Arrays.deepToString((Vector2f[]) uniform.getValue()) + " for " + + uniform.getBinding(); gl.glUniform2(loc, fb); break; case Vector3Array: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Vector3f array value " + + Arrays.deepToString((Vector3f[]) uniform.getValue()) + " for " + + uniform.getBinding(); gl.glUniform3(loc, fb); break; case Vector4Array: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Vector4f array value " + + Arrays.deepToString((Vector4f[]) uniform.getValue()) + " for " + + uniform.getBinding(); gl.glUniform4(loc, fb); break; case Matrix4Array: fb = uniform.getMultiData(); + assert isValidNumber(fb) : "Invalid Matrix4f array value " + + Arrays.deepToString((Matrix4f[]) uniform.getValue()) + " for " + + uniform.getBinding(); gl.glUniformMatrix4(loc, false, fb); break; case Int: @@ -1246,7 +1438,8 @@ protected void updateUniform(Shader shader, Uniform uniform) { gl.glUniform1i(loc, i.intValue()); break; default: - throw new UnsupportedOperationException("Unsupported uniform type: " + uniform.getVarType()); + throw new UnsupportedOperationException( + "Unsupported uniform type: " + uniform.getVarType() + " for " + uniform.getBinding()); } } @@ -1262,44 +1455,61 @@ protected void updateShaderBufferBlock(final Shader shader, final ShaderBufferBl assert shader.getId() > 0; final BufferObject bufferObject = bufferBlock.getBufferObject(); - if (bufferObject.getUniqueId() == -1 || bufferObject.isUpdateNeeded()) { - updateBufferData(bufferObject); - } + final BufferType bufferType = bufferBlock.getType(); + - if (!bufferBlock.isUpdateNeeded()) { - return; + if (bufferObject.isUpdateNeeded()) { + if (bufferType == BufferType.ShaderStorageBufferObject) { + updateShaderStorageBufferObjectData(bufferObject); + } else { + updateUniformBufferObjectData(bufferObject); + } } + int usage = resolveUsageHint(bufferObject.getAccessHint(), bufferObject.getNatureHint()); + if (usage == -1) return; // cpu only + bindProgram(shader); final int shaderId = shader.getId(); - final BufferObject.BufferType bufferType = bufferObject.getBufferType(); - - bindBuffer(bufferBlock, bufferObject, shaderId, bufferType); - - bufferBlock.clearUpdateNeeded(); - } - private void bindBuffer(final ShaderBufferBlock bufferBlock, final BufferObject bufferObject, final int shaderId, - final BufferObject.BufferType bufferType) { + int bindingPoint = bufferObject.getBinding(); switch (bufferType) { case UniformBufferObject: { - final int blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName()); - gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bufferObject.getBinding(), bufferObject.getId()); - gl3.glUniformBlockBinding(GL3.GL_UNIFORM_BUFFER, blockIndex, bufferObject.getBinding()); + setUniformBufferObject(bindingPoint, bufferObject); // rebind buffer if needed + if (bufferBlock.isUpdateNeeded()) { + int blockIndex = bufferBlock.getLocation(); + if (blockIndex < 0) { + blockIndex = gl3.glGetUniformBlockIndex(shaderId, bufferBlock.getName()); + bufferBlock.setLocation(blockIndex); + } + if (bufferBlock.getLocation() != NativeObject.INVALID_ID) { + gl3.glUniformBlockBinding(shaderId, bufferBlock.getLocation(), bindingPoint); + } + } break; } case ShaderStorageBufferObject: { - final int blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName()); - gl4.glShaderStorageBlockBinding(shaderId, blockIndex, bufferObject.getBinding()); - gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bufferObject.getBinding(), bufferObject.getId()); + setShaderStorageBufferObject(bindingPoint, bufferObject); // rebind buffer if needed + if (bufferBlock.isUpdateNeeded() ) { + int blockIndex = bufferBlock.getLocation(); + if (blockIndex < 0) { + blockIndex = gl4.glGetProgramResourceIndex(shaderId, GL4.GL_SHADER_STORAGE_BLOCK, bufferBlock.getName()); + bufferBlock.setLocation(blockIndex); + } + if (bufferBlock.getLocation() != NativeObject.INVALID_ID) { + gl4.glShaderStorageBlockBinding(shaderId, bufferBlock.getLocation(), bindingPoint); + } + } break; } default: { throw new IllegalArgumentException("Doesn't support binding of " + bufferType); } } + + bufferBlock.clearUpdateNeeded(); } protected void updateShaderUniforms(Shader shader) { @@ -1359,6 +1569,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"); } @@ -1372,7 +1585,6 @@ public void updateShaderSourceData(ShaderSource source) { + "Only GLSL 1.00 shaders are supported."); } - boolean insertPrecision = false; // Upload shader source. // Merge the defines and source code. stringBuf.setLength(0); @@ -1394,7 +1606,7 @@ public void updateShaderSourceData(ShaderSource source) { if (gles2 || gles3) { // request GLSL ES (1.00) when compiling under GLES2. stringBuf.append("#version 100\n"); - + } else { // version 100 does not exist in desktop GLSL. // put version 110 in that case to enable strict checking @@ -1403,16 +1615,8 @@ public void updateShaderSourceData(ShaderSource source) { } } - if (gles2 || gles3) { - //Inserting precision only to fragment shaders creates some link failures because of different precision between shaders - //But adding the precision to all shaders generates rendering glitches in some devices if not set to highp - if (source.getType() == ShaderType.Fragment) { - // GLES requires precision qualifier. - insertPrecision = true; - } - } } - + if (linearizeSrgbImages) { stringBuf.append("#define SRGB 1\n"); } @@ -1421,24 +1625,6 @@ public void updateShaderSourceData(ShaderSource source) { stringBuf.append(source.getDefines()); stringBuf.append(source.getSource()); - if(insertPrecision){ - // default precision could be defined in GLSLCompat.glsllib so final users can use custom defined precision instead - // precision token is not a preprocessor dirrective therefore it must be placed after #extension tokens to avoid - // Error P0001: Extension directive must occur before any non-preprocessor tokens - int idx = stringBuf.lastIndexOf("#extension"); - idx = stringBuf.indexOf("\n", idx); - - if(version>=310) { - stringBuf.insert(idx + 1, "precision highp sampler2DMS;\n"); - } - if(version>=300) { - stringBuf.insert(idx + 1, "precision highp sampler2DArray;\n"); - stringBuf.insert(idx + 1, "precision highp sampler2DShadow;\n"); - stringBuf.insert(idx + 1, "precision highp sampler3D;\n"); - stringBuf.insert(idx + 1, "precision highp sampler2D;\n"); - } - stringBuf.insert(idx + 1, "precision highp float;\n"); - } intBuf1.clear(); intBuf1.put(0, stringBuf.length()); @@ -1465,7 +1651,7 @@ public void updateShaderSourceData(ShaderSource source) { if (infoLog != null) { logger.log(Level.WARNING, "{0} compiled successfully, compiler warnings: \n{1}", new Object[]{source.getName(), infoLog}); - } else { + } else if (logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "{0} compiled successfully.", source.getName()); } source.clearUpdateNeeded(); @@ -1559,10 +1745,11 @@ public void updateShaderData(Shader shader) { } } + @Override public void setShader(Shader shader) { if (shader == null) { throw new IllegalArgumentException("Shader cannot be null"); - } else { + } else { if (shader.isUpdateNeeded()) { updateShaderData(shader); } @@ -1578,6 +1765,7 @@ public void setShader(Shader shader) { } } + @Override public void deleteShaderSource(ShaderSource source) { if (source.getId() < 0) { logger.warning("Shader source is not uploaded to GPU, cannot delete."); @@ -1588,6 +1776,7 @@ public void deleteShaderSource(ShaderSource source) { source.resetObject(); } + @Override public void deleteShader(Shader shader) { if (shader.getId() == -1) { logger.warning("Shader is not uploaded to GPU, cannot delete."); @@ -1606,14 +1795,28 @@ public void deleteShader(Shader shader) { shader.resetObject(); } - /*********************************************************************\ - |* Framebuffers *| - \*********************************************************************/ + /*==============*\ + |* Framebuffers *| + \*==============*/ + + /** + * Copy the source buffer to the destination buffer, including both color + * and depth. + * + * @param src the source buffer (unaffected) + * @param dst the destination buffer + */ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { - copyFrameBuffer(src, dst, true); + copyFrameBuffer(src, dst, true, true); } + @Override public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { + copyFrameBuffer(src, dst, true, copyDepth); + } + + @Override + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyColor,boolean copyDepth) { if (caps.contains(Caps.FrameBufferBlit)) { int srcX0 = 0; int srcY0 = 0; @@ -1666,7 +1869,13 @@ public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) dstX1 = dst.getWidth(); dstY1 = dst.getHeight(); } - int mask = GL.GL_COLOR_BUFFER_BIT; + + int mask = 0; + + if(copyColor){ + mask|=GL.GL_COLOR_BUFFER_BIT; + } + if (copyDepth) { mask |= GL.GL_DEPTH_BUFFER_BIT; } @@ -1692,7 +1901,7 @@ private void checkFrameBufferError() { throw new IllegalStateException("Framebuffer object format is " + "unsupported by the video hardware."); case GLFbo.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT: - throw new IllegalStateException("Framebuffer has erronous attachment."); + throw new IllegalStateException("Framebuffer has erroneous attachment."); case GLFbo.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT: throw new IllegalStateException("Framebuffer doesn't have any renderbuffers attached."); case GLFbo.GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: @@ -1788,12 +1997,12 @@ public void updateRenderTexture(FrameBuffer fb, RenderBuffer rb) { convertAttachmentSlot(rb.getSlot()), convertTextureType(tex.getType(), image.getMultiSamples(), rb.getFace()), image.getId(), - 0); + rb.getLevel()); } else { - glfbo.glFramebufferTextureLayerEXT(GLFbo.GL_FRAMEBUFFER_EXT, - convertAttachmentSlot(rb.getSlot()), - image.getId(), - 0, + glfbo.glFramebufferTextureLayerEXT(GLFbo.GL_FRAMEBUFFER_EXT, + convertAttachmentSlot(rb.getSlot()), + image.getId(), + rb.getLevel(), rb.getLayer()); } } @@ -1815,13 +2024,13 @@ public void updateFrameBufferAttachment(FrameBuffer fb, RenderBuffer rb) { rb.getId()); } } - + private void bindFrameBuffer(FrameBuffer fb) { if (fb == null) { - if (context.boundFBO != 0) { - glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, 0); + if (context.boundFBO != defaultFBO) { + glfbo.glBindFramebufferEXT(GLFbo.GL_FRAMEBUFFER_EXT, defaultFBO); statistics.onFrameBufferUse(null, true); - context.boundFBO = 0; + context.boundFBO = defaultFBO; context.boundFB = null; } } else { @@ -1863,7 +2072,7 @@ public void updateFrameBuffer(FrameBuffer fb) { FrameBuffer.RenderBuffer colorBuf = fb.getColorBuffer(i); updateFrameBufferAttachment(fb, colorBuf); } - + setReadDrawBuffers(fb); checkFrameBufferError(); @@ -1891,6 +2100,7 @@ public Vector2f[] getFrameBufferSamplePositions(FrameBuffer fb) { return samplePositions; } + @Override public void setMainFrameBufferOverride(FrameBuffer fb) { mainFbOverride = null; if (context.boundFBO == 0) { @@ -1900,37 +2110,31 @@ public void setMainFrameBufferOverride(FrameBuffer fb) { mainFbOverride = fb; } + + @Override + public FrameBuffer getCurrentFrameBuffer() { + if(mainFbOverride!=null){ + return mainFbOverride; + } + return context.boundFB; + } + public void setReadDrawBuffers(FrameBuffer fb) { - if (gl2 == null || gl instanceof GLES_30) { + if (gl2 == null) { return; } - + final int NONE = -2; final int INITIAL = -1; final int MRT_OFF = 100; - - if (fb == null) { - // Set Read/Draw buffers to initial value. - if (context.boundDrawBuf != INITIAL) { - gl2.glDrawBuffer(context.initialDrawBuf); - context.boundDrawBuf = INITIAL; - } - if (context.boundReadBuf != INITIAL) { - gl2.glReadBuffer(context.initialReadBuf); - context.boundReadBuf = INITIAL; - } - } else { + + if (fb != null) { + if (fb.getNumColorBuffers() == 0) { // make sure to select NONE as draw buf - // no color buffer attached. - if (context.boundDrawBuf != NONE) { - gl2.glDrawBuffer(GL.GL_NONE); - context.boundDrawBuf = NONE; - } - if (context.boundReadBuf != NONE) { - gl2.glReadBuffer(GL.GL_NONE); - context.boundReadBuf = NONE; - } + // no color buffer attached. + gl2.glDrawBuffer(GL.GL_NONE); + gl2.glReadBuffer(GL.GL_NONE); } else { if (fb.getNumColorBuffers() > limits.get(Limits.FrameBufferAttachments)) { throw new RendererException("Framebuffer has more color " @@ -1955,21 +2159,19 @@ public void setReadDrawBuffers(FrameBuffer fb) { intBuf16.flip(); glext.glDrawBuffers(intBuf16); - context.boundDrawBuf = MRT_OFF + fb.getNumColorBuffers(); - } else { RenderBuffer rb = fb.getColorBuffer(fb.getTargetIndex()); // select this draw buffer - if (context.boundDrawBuf != rb.getSlot()) { - gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); - context.boundDrawBuf = rb.getSlot(); - } + gl2.glDrawBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); + // select this read buffer + gl2.glReadBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); } } } - + } - + + @Override public void setFrameBuffer(FrameBuffer fb) { if (fb == null && mainFbOverride != null) { fb = mainFbOverride; @@ -1987,29 +2189,34 @@ public void setFrameBuffer(FrameBuffer fb) { } // generate mipmaps for last FB if needed - if (context.boundFB != null) { + if (context.boundFB != null && (context.boundFB.getMipMapsGenerationHint()!=null?context.boundFB.getMipMapsGenerationHint():generateMipmapsForFramebuffers)) { for (int i = 0; i < context.boundFB.getNumColorBuffers(); i++) { RenderBuffer rb = context.boundFB.getColorBuffer(i); Texture tex = rb.getTexture(); - if (tex != null - && tex.getMinFilter().usesMipMapLevels()) { - setTexture(0, rb.getTexture()); - - int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); - glfbo.glGenerateMipmapEXT(textureType); + if (tex != null && tex.getMinFilter().usesMipMapLevels()) { + try { + final int textureUnitIndex = 0; + setTexture(textureUnitIndex, rb.getTexture()); + } catch (TextureUnitException exception) { + throw new RuntimeException("Renderer lacks texture units?"); + } + if (tex.getType() == Texture.Type.CubeMap) { + glfbo.glGenerateMipmapEXT(GL.GL_TEXTURE_CUBE_MAP); + } else { + int textureType = convertTextureType(tex.getType(), tex.getImage().getMultiSamples(), rb.getFace()); + glfbo.glGenerateMipmapEXT(textureType); + } } } } if (fb == null) { bindFrameBuffer(null); - setReadDrawBuffers(null); } else { if (fb.isUpdateNeeded()) { updateFrameBuffer(fb); } else { bindFrameBuffer(fb); - setReadDrawBuffers(fb); } // update viewport to reflect framebuffer's resolution @@ -2019,9 +2226,13 @@ 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()); + } } } + @Override public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { readFrameBufferWithGLFormat(fb, byteBuf, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE); } @@ -2035,13 +2246,8 @@ private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int } setFrameBuffer(fb); - if (gl2 != null) { - if (context.boundReadBuf != rb.getSlot()) { - gl2.glReadBuffer(GLFbo.GL_COLOR_ATTACHMENT0_EXT + rb.getSlot()); - context.boundReadBuf = rb.getSlot(); - } - } - + + } else { setFrameBuffer(null); } @@ -2049,6 +2255,7 @@ private void readFrameBufferWithGLFormat(FrameBuffer fb, ByteBuffer byteBuf, int gl.glReadPixels(vpX, vpY, vpW, vpH, glFormat, dataType, byteBuf); } + @Override public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { GLImageFormat glFormat = texUtil.getImageFormatWithError(format, false); readFrameBufferWithGLFormat(fb, byteBuf, glFormat.format, glFormat.dataType); @@ -2059,6 +2266,7 @@ private void deleteRenderBuffer(FrameBuffer fb, RenderBuffer rb) { glfbo.glDeleteRenderbuffersEXT(intBuf1); } + @Override public void deleteFrameBuffer(FrameBuffer fb) { if (fb.getId() != -1) { if (context.boundFBO == fb.getId()) { @@ -2175,8 +2383,8 @@ private int convertWrapMode(Texture.WrapMode mode) { switch (mode) { case BorderClamp: case Clamp: + // fall through case EdgeClamp: - // Falldown intentional. return GL.GL_CLAMP_TO_EDGE; case Repeat: return GL.GL_REPEAT; @@ -2202,7 +2410,7 @@ private void setupTextureParams(int unit, Texture tex) { if (image != null) { haveMips = image.isGeneratedMipmapsRequired() || image.hasMipmaps(); } - + LastTextureState curState = image.getLastTextureState(); if (curState.magFilter != tex.getMagFilter()) { @@ -2270,7 +2478,7 @@ private void setupTextureParams(int unit, Texture tex) { } curState.shadowCompareMode = texCompareMode; } - + // If at this point we didn't bind the texture, bind it now bindTextureOnly(target, image, unit); } @@ -2278,7 +2486,7 @@ private void setupTextureParams(int unit, Texture tex) { /** * Validates if a potentially NPOT texture is supported by the hardware. *

                - * Textures with power-of-2 dimensions are supported on all hardware, however + * Textures with power-of-2 dimensions are supported on all hardware, however * non-power-of-2 textures may or may not be supported depending on which * texturing features are used. * @@ -2292,7 +2500,7 @@ private void checkNonPowerOfTwo(Texture tex) { } if (caps.contains(Caps.NonPowerOfTwoTextures)) { - // Texture is NPOT but it is supported by video hardware. + // Texture is NPOT, but it is supported by video hardware. return; } @@ -2326,14 +2534,14 @@ private void checkNonPowerOfTwo(Texture tex) { } break; default: - throw new UnsupportedOperationException("unrecongized texture type"); + throw new UnsupportedOperationException("unrecognized texture type"); } } /** * Ensures that the texture is bound to the given unit * and that the unit is currently active (for modification). - * + * * @param target The texture target, one of GL_TEXTURE_*** * @param img The image texture to bind * @param unit At what unit to bind the texture. @@ -2343,37 +2551,37 @@ private void bindTextureAndUnit(int target, Image img, int unit) { gl.glActiveTexture(GL.GL_TEXTURE0 + unit); context.boundTextureUnit = unit; } - if (context.boundTextures[unit] != img) { + if (context.boundTextures[unit]==null||context.boundTextures[unit].get() != img.getWeakRef().get()) { gl.glBindTexture(target, img.getId()); - context.boundTextures[unit] = img; + context.boundTextures[unit] = img.getWeakRef(); statistics.onTextureUse(img, true); } else { statistics.onTextureUse(img, false); } } - + /** * Ensures that the texture is bound to the given unit, * but does not care if the unit is active (for rendering). - * + * * @param target The texture target, one of GL_TEXTURE_*** * @param img The image texture to bind * @param unit At what unit to bind the texture. */ private void bindTextureOnly(int target, Image img, int unit) { - if (context.boundTextures[unit] != img) { + if (context.boundTextures[unit] == null || context.boundTextures[unit].get() != img.getWeakRef().get()) { if (context.boundTextureUnit != unit) { gl.glActiveTexture(GL.GL_TEXTURE0 + unit); context.boundTextureUnit = unit; } gl.glBindTexture(target, img.getId()); - context.boundTextures[unit] = img; + context.boundTextures[unit] = img.getWeakRef(); statistics.onTextureUse(img, true); } else { statistics.onTextureUse(img, false); } } - + /** * Uploads the given image to the GL driver. * @@ -2414,12 +2622,12 @@ public void updateTexImageData(Image img, Texture.Type type, int unit, boolean s } } else if (caps.contains(Caps.OpenGL20) || caps.contains(Caps.OpenGLES30)) { if (img.hasMipmaps()) { - // Image already has mipmaps, set the max level based on the + // Image already has mipmaps, set the max level based on the // number of mipmaps we have. gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, img.getMipMapSizes().length - 1); } else { - // Image does not have mipmaps and they are not required. - // Specify that that the texture has no mipmaps. + // Image does not have mipmaps, and they are not required. + // Specify that the texture has no mipmaps. gl.glTexParameteri(target, GL2.GL_TEXTURE_MAX_LEVEL, 0); } } @@ -2513,7 +2721,11 @@ public void updateTexImageData(Image img, Texture.Type type, int unit, boolean s } @Override - public void setTexture(int unit, Texture tex) { + public void setTexture(int unit, Texture tex) throws TextureUnitException { + if (unit < 0 || unit >= RenderContext.maxTextureUnits) { + throw new TextureUnitException(); + } + Image image = tex.getImage(); if (image.isUpdateNeeded() || (image.isGeneratedMipmapsRequired() && !image.isMipmapsGenerated())) { // Check NPOT requirements @@ -2540,20 +2752,80 @@ public void setTexture(int unit, Texture tex) { 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()); + } + } + + @Override + public void setTextureImage(int unit, TextureImage tex) throws TextureUnitException { + if (unit < 0 || unit >= RenderContext.maxTextureUnits) { + throw new TextureUnitException(); + } + WeakReference ref = context.boundTextures[unit]; + boolean bindRequired = tex.clearUpdateNeeded() || ref == null || ref.get() != tex.getImage().getWeakRef().get(); + setTexture(unit, tex.getTexture()); + if (gl4 != null && bindRequired) { + tex.bindImage(gl4, texUtil, unit); + } } + + @Override + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) { + if (bufferObject.isUpdateNeeded()) { + updateUniformBufferObjectData(bufferObject); + } + if (context.boundBO[bindingPoint] == null || context.boundBO[bindingPoint].get() != bufferObject) { + gl3.glBindBufferBase(GL3.GL_UNIFORM_BUFFER, bindingPoint, bufferObject.getId()); + bufferObject.setBinding(bindingPoint); + context.boundBO[bindingPoint] = bufferObject.getWeakRef(); + } + + bufferObject.setBinding(bindingPoint); + + if (debug && caps.contains(Caps.GLDebug)) { + if (bufferObject.getName() != null) glext.glObjectLabel(GLExt.GL_BUFFER, bufferObject.getId(), bufferObject.getName()); + } + + } + + @Override + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) { + if (bufferObject.isUpdateNeeded()) { + updateShaderStorageBufferObjectData(bufferObject); + } + if (context.boundBO[bindingPoint] == null || context.boundBO[bindingPoint].get() != bufferObject) { + gl4.glBindBufferBase(GL4.GL_SHADER_STORAGE_BUFFER, bindingPoint, bufferObject.getId()); + bufferObject.setBinding(bindingPoint); + context.boundBO[bindingPoint] = bufferObject.getWeakRef(); + } + bufferObject.setBinding(bindingPoint); + + if (debug && caps.contains(Caps.GLDebug)) { + if (bufferObject.getName() != null) glext.glObjectLabel(GLExt.GL_BUFFER, bufferObject.getId(), bufferObject.getName()); + } + } /** * @deprecated Use modifyTexture(Texture2D dest, Image src, int destX, int destY, int srcX, int srcY, int areaW, int areaH) */ @Deprecated + @Override public void modifyTexture(Texture tex, Image pixels, int x, int y) { - setTexture(0, tex); - if(caps.contains(Caps.OpenGLES20) && pixels.getFormat()!=tex.getImage().getFormat() ) { + final int textureUnitIndex = 0; + try { + setTexture(textureUnitIndex, tex); + } catch (TextureUnitException exception) { + throw new RuntimeException("Renderer lacks texture units?"); + } + + if(caps.contains(Caps.OpenGLES20) && pixels.getFormat()!=tex.getImage().getFormat()) { logger.log(Level.WARNING, "Incompatible texture subimage"); } int target = convertTextureType(tex.getType(), pixels.getMultiSamples(), -1); - texUtil.uploadSubTexture(target,pixels, 0, x, y,0,0,pixels.getWidth(),pixels.getHeight(), linearizeSrgbImages); + texUtil.uploadSubTexture(target, pixels, 0, x, y, + 0, 0, pixels.getWidth(), pixels.getHeight(), linearizeSrgbImages); } /** @@ -2567,15 +2839,24 @@ public void modifyTexture(Texture tex, Image pixels, int x, int y) { * @param areaW Width of the area to copy * @param areaH Height of the area to copy */ - public void modifyTexture(Texture2D dest, Image src, int destX, int destY, int srcX, int srcY, int areaW, int areaH) { - setTexture(0, dest); - if(caps.contains(Caps.OpenGLES20) && src.getFormat()!=dest.getImage().getFormat() ) { + public void modifyTexture(Texture2D dest, Image src, int destX, int destY, + int srcX, int srcY, int areaW, int areaH) { + final int textureUnitIndex = 0; + try { + setTexture(textureUnitIndex, dest); + } catch (TextureUnitException exception) { + throw new RuntimeException("Renderer lacks texture units?"); + } + + if(caps.contains(Caps.OpenGLES20) && src.getFormat()!=dest.getImage().getFormat()) { logger.log(Level.WARNING, "Incompatible texture subimage"); } int target = convertTextureType(dest.getType(), src.getMultiSamples(), -1); - texUtil.uploadSubTexture(target, src, 0, destX, destY, srcX, srcY, areaW, areaH, linearizeSrgbImages); + texUtil.uploadSubTexture(target, src, 0, destX, destY, + srcX, srcY, areaW, areaH, linearizeSrgbImages); } - + + @Override public void deleteImage(Image image) { int texId = image.getId(); if (texId != -1) { @@ -2628,6 +2909,7 @@ private int convertFormat(Format format) { } } + @Override public void updateBufferData(VertexBuffer vb) { int bufId = vb.getId(); boolean created = false; @@ -2691,23 +2973,63 @@ public void updateBufferData(VertexBuffer vb) { vb.clearUpdateNeeded(); } - @Override - public void updateBufferData(final BufferObject bo) { - - int maxSize = Integer.MAX_VALUE; + private int resolveUsageHint(BufferObject.AccessHint ah, BufferObject.NatureHint nh) { + switch (ah) { + case Dynamic: { + switch (nh) { + case Draw: + return GL.GL_DYNAMIC_DRAW; + case Read: + return GL4.GL_DYNAMIC_READ; + case Copy: + return GL.GL_DYNAMIC_COPY; + } + } + case Stream: { + switch (nh) { + case Draw: + return GL.GL_STREAM_DRAW; + case Read: + return GL.GL_STREAM_READ; + case Copy: + return GL.GL_STREAM_COPY; + } + } + case Static: { + switch (nh) { + case Draw: + return GL.GL_STATIC_DRAW; + case Read: + return GL.GL_STATIC_READ; + case Copy: + return GL.GL_STATIC_COPY; + } + } + default: + } + return -1; + } - final BufferObject.BufferType bufferType = bo.getBufferType(); + @Override + public void updateShaderStorageBufferObjectData(BufferObject bo) { + if (!caps.contains(Caps.ShaderStorageBufferObject)) throw new IllegalArgumentException("The current video hardware doesn't support shader storage buffer objects "); + updateBufferData(GL4.GL_SHADER_STORAGE_BUFFER, bo); + } - if (!caps.contains(bufferType.getRequiredCaps())) { - throw new IllegalArgumentException("The current video hardware doesn't support " + bufferType); - } + @Override + public void updateUniformBufferObjectData(BufferObject bo) { + if (!caps.contains(Caps.UniformBufferObject)) throw new IllegalArgumentException("The current video hardware doesn't support uniform buffer objects"); + updateBufferData(GL4.GL_UNIFORM_BUFFER, bo); + } - final ByteBuffer data = bo.computeData(maxSize); - if (data == null) { - throw new IllegalArgumentException("Can't upload BO without data."); + private void updateBufferData(int type, BufferObject bo) { + int bufferId = bo.getId(); + int usage = resolveUsageHint(bo.getAccessHint(), bo.getNatureHint()); + if (usage == -1) { + deleteBuffer(bo); + return; // CpuOnly } - int bufferId = bo.getId(); if (bufferId == -1) { // create buffer @@ -2720,29 +3042,33 @@ public void updateBufferData(final BufferObject bo) { objManager.registerObject(bo); } - data.rewind(); + DirtyRegionsIterator it = bo.getDirtyRegions(); + BufferRegion reg; - switch (bufferType) { - case UniformBufferObject: { - gl3.glBindBuffer(GL3.GL_UNIFORM_BUFFER, bufferId); - gl3.glBufferData(GL4.GL_UNIFORM_BUFFER, data, GL3.GL_DYNAMIC_DRAW); - gl3.glBindBuffer(GL4.GL_UNIFORM_BUFFER, 0); - break; - } - case ShaderStorageBufferObject: { - gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, bufferId); - gl4.glBufferData(GL4.GL_SHADER_STORAGE_BUFFER, data, GL4.GL_DYNAMIC_COPY); - gl4.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, 0); + while ((reg = it.next()) != null) { + gl3.glBindBuffer(type, bufferId); + if (reg.isFullBufferRegion()) { + ByteBuffer bbf = bo.getData(); + if (logger.isLoggable(java.util.logging.Level.FINER)) { + logger.log(java.util.logging.Level.FINER, "Update full buffer {0} with {1} bytes", new Object[] { bo, bbf.remaining() }); + } + gl.glBufferData(type, bbf, usage); + gl3.glBindBuffer(type, 0); + reg.clearDirty(); break; - } - default: { - throw new IllegalArgumentException("Doesn't support binding of " + bufferType); + } else { + if (logger.isLoggable(java.util.logging.Level.FINER)) { + logger.log(java.util.logging.Level.FINER, "Update region {0} of {1}", new Object[] { reg, bo }); + } + gl.glBufferSubData(type, reg.getStart(), reg.getData()); + gl3.glBindBuffer(type, 0); + reg.clearDirty(); } } - bo.clearUpdateNeeded(); } + @Override public void deleteBuffer(VertexBuffer vb) { int bufId = vb.getId(); if (bufId != -1) { @@ -2778,12 +3104,16 @@ public void clearVertexAttribs() { for (int i = 0; i < attribList.oldLen; i++) { int idx = attribList.oldList[i]; gl.glDisableVertexAttribArray(idx); - if (context.boundAttribs[idx].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) { @@ -2833,13 +3163,13 @@ public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { updateBufferData(vb); } - VertexBuffer[] attribs = context.boundAttribs; + WeakReference[] attribs = context.boundAttribs; for (int i = 0; i < slotsRequired; i++) { if (!context.attribIndexList.moveToNew(loc + i)) { gl.glEnableVertexAttribArray(loc + i); } } - if (attribs[loc] != vb) { + if (attribs[loc]==null||attribs[loc].get() != vb) { // NOTE: Use id from interleaved buffer if specified int bufId = idb != null ? idb.getId() : vb.getId(); assert bufId != -1; @@ -2879,16 +3209,19 @@ public void setVertexAttrib(VertexBuffer vb, VertexBuffer idb) { for (int i = 0; i < slotsRequired; i++) { int slot = loc + i; - if (vb.isInstanced() && (attribs[slot] == null || !attribs[slot].isInstanced())) { + if (vb.isInstanced() && (attribs[slot] == null || attribs[slot].get() == null || !attribs[slot].get().isInstanced())) { // non-instanced -> instanced glext.glVertexAttribDivisorARB(slot, vb.getInstanceSpan()); - } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].isInstanced()) { + } else if (!vb.isInstanced() && attribs[slot] != null && attribs[slot].get() != null && attribs[slot].get().isInstanced()) { // instanced -> non-instanced glext.glVertexAttribDivisorARB(slot, 0); } - attribs[slot] = vb; + 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) { @@ -2998,9 +3331,16 @@ public void drawTriangleList(VertexBuffer indexBuf, Mesh mesh, int count) { } } - /*********************************************************************\ - |* Render Calls *| - \*********************************************************************/ + /*==============*\ + |* Render Calls *| + \*==============*/ + + /** + * Convert a mesh mode to the corresponding GL value. + * + * @param mode input enum value (not null) + * @return the corresponding GL value + */ public int convertElementMode(Mesh.Mode mode) { switch (mode) { case Points: @@ -3064,37 +3404,10 @@ public void updateVertexArray(Mesh mesh, VertexBuffer instanceData) { } } - private void renderMeshVertexArray(Mesh mesh, int lod, int count, VertexBuffer instanceData) { - if (mesh.getId() == -1) { - updateVertexArray(mesh, instanceData); - } else { - // TODO: Check if it was updated - } - - if (context.boundVertexArray != mesh.getId()) { - gl3.glBindVertexArray(mesh.getId()); - context.boundVertexArray = mesh.getId(); - } - -// IntMap buffers = mesh.getBuffers(); - VertexBuffer indices; - if (mesh.getNumLodLevels() > 0) { - indices = mesh.getLodLevel(lod); - } else { - indices = mesh.getBuffer(Type.Index); - } - if (indices != null) { - drawTriangleList(indices, mesh, count); - } else { - drawTriangleArray(mesh.getMode(), count, mesh.getVertexCount()); - } - clearVertexAttribs(); - } - private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { // Here while count is still passed in. Can be removed when/if - // the method is collapsed again. -pspeed + // the method is collapsed again. -pspeed count = Math.max(mesh.getInstanceCount(), count); VertexBuffer interleavedData = mesh.getBuffer(Type.InterleavedData); @@ -3132,7 +3445,7 @@ private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] ins } clearVertexAttribs(); - + if (indices != null) { drawTriangleList(indices, mesh, count); } else { @@ -3140,6 +3453,7 @@ private void renderMeshDefault(Mesh mesh, int lod, int count, VertexBuffer[] ins } } + @Override public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { if (mesh.getVertexCount() == 0 || mesh.getTriangleCount() == 0 || count == 0) { return; @@ -3165,6 +3479,7 @@ public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceDat // } } + @Override public void setMainFrameBufferSrgb(boolean enableSrgb) { // Gamma correction if (!caps.contains(Caps.Srgb) && enableSrgb) { @@ -3178,19 +3493,15 @@ public void setMainFrameBufferSrgb(boolean enableSrgb) { setFrameBuffer(null); if (enableSrgb) { - if (!getBoolean(GLExt.GL_FRAMEBUFFER_SRGB_CAPABLE_EXT)) { - logger.warning("Driver claims that default framebuffer " - + "is not sRGB capable. Enabling anyway."); - } - gl.glEnable(GLExt.GL_FRAMEBUFFER_SRGB_EXT); - - logger.log(Level.FINER, "SRGB FrameBuffer enabled (Gamma Correction)"); + logger.log(Level.FINER, "sRGB FrameBuffer enabled (Gamma Correction)"); } else { gl.glDisable(GLExt.GL_FRAMEBUFFER_SRGB_EXT); + logger.log(Level.FINER, "sRGB FrameBuffer disabled (Gamma Correction)"); } } + @Override public void setLinearizeSrgbImages(boolean linearize) { if (caps.contains(Caps.Srgb)) { linearizeSrgbImages = linearize; @@ -3237,4 +3548,72 @@ public boolean getAlphaToCoverage() { public int getDefaultAnisotropicFilter() { return this.defaultAnisotropicFilter; } + + /** + * Determine the maximum allowed width for lines. + * + * @return the maximum width (in pixels) + */ + @Override + public float getMaxLineWidth() { + // Since neither JMonkeyEngine nor LWJGL ever enables GL_LINE_SMOOTH, + // all lines are aliased, but just in case... + assert !gl.glIsEnabled(GL.GL_LINE_SMOOTH); + + // When running with OpenGL 3.2+ core profile, + // compatibility features such as multipixel lines aren't available. + if (caps.contains(Caps.CoreProfile)) { + return 1f; + } + + floatBuf16.clear(); + gl.glGetFloat(GL.GL_ALIASED_LINE_WIDTH_RANGE, floatBuf16); + float result = floatBuf16.get(1); + + return result; + } + + /** + * Test whether images with the sRGB flag will be linearized when read by a + * shader. + * + * @return true for linearization, false for no linearization + */ + @Override + public boolean isLinearizeSrgbImages() { + return linearizeSrgbImages; + } + + /** + * Test whether colors rendered to the main framebuffer undergo + * linear-to-sRGB conversion. + * + * @return true for conversion, false for no conversion + */ + @Override + public boolean isMainFrameBufferSrgb() { + if (!caps.contains(Caps.Srgb)) { + return false; + } else { + return gl.glIsEnabled(GLExt.GL_FRAMEBUFFER_SRGB_EXT); + } + } + + //TODO: How should the GL4 specific functionalities here be exposed? Via the renderer? + public GL4 getGl4(){ + return gl4; + } + + @Override + public void deleteFence(GLFence fence) { + if(gl4 != null && fence.getId() != NativeObject.INVALID_ID){ + gl4.glDeleteSync(fence); + fence.resetObject(); + } + } + + @Override + public void registerNativeObject(NativeObject nativeObject) { + objManager.registerObject(nativeObject); + } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java index 7e6833b8b8..1ba2356886 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTiming.java @@ -62,6 +62,7 @@ public int compare(Map.Entry o1, Map.Entry o2) { } @Override + @SuppressWarnings("unchecked") public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); if (methodName.equals("resetStats")) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java index ca25810d44..1e6d8ef64f 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTimingState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,5 +37,5 @@ public class GLTimingState { long timeSpentInGL = 0; int sampleCount = 0; long lastPrintOutTime = 0; - final HashMap callTiming = new HashMap(); + final HashMap callTiming = new HashMap<>(); } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java index ae3f17a8ca..ce9cddc795 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/GLTracer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,19 +59,17 @@ public final class GLTracer implements InvocationHandler { private static final String ANSI_RESET = "\u001B[0m"; private static final String ANSI_BRIGHT = "\u001B[1m"; - private static final String ANSI_BLACK = "\u001B[30m"; private static final String ANSI_RED = "\u001B[31m"; private static final String ANSI_GREEN = "\u001B[32m"; private static final String ANSI_YELLOW = "\u001B[33m"; private static final String ANSI_BLUE = "\u001B[34m"; private static final String ANSI_MAGENTA = "\u001B[35m"; private static final String ANSI_CYAN = "\u001B[36m"; - private static final String ANSI_WHITE = "\u001B[37m"; private static void noEnumArgs(String method, int... argSlots) { - IntMap argSlotsMap = new IntMap(); + IntMap argSlotsMap = new IntMap<>(); for (int argSlot : argSlots) { - argSlotsMap.put(argSlot, (Void) null); + argSlotsMap.put(argSlot, null); } nonEnumArgMap.put(method, argSlotsMap); } @@ -146,7 +144,7 @@ public GLTracer(Object obj, IntMap constMap) { } private static IntMap generateConstantMap(Class ... classes) { - IntMap constMap = new IntMap(); + IntMap constMap = new IntMap<>(); for (Class clazz : classes) { for (Field field : clazz.getFields()) { if (field.getType() == int.class) { @@ -154,13 +152,13 @@ private static IntMap generateConstantMap(Class ... classes) { int val = field.getInt(null); String name = field.getName(); constMap.put(val, name); - } catch (IllegalArgumentException ex) { - } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException + | IllegalAccessException ex) { } } } } - // GL_ONE is more common than GL_TRUE (which is a boolean anyway..) + // GL_ONE is more common than GL_TRUE (which is a boolean anyway) constMap.put(1, "GL_ONE"); return constMap; } diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/ShaderStorageBufferObject.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/ShaderStorageBufferObject.java new file mode 100644 index 0000000000..f1ca02364e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/ShaderStorageBufferObject.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2026 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.opengl; + +import com.jme3.renderer.RendererException; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; + +import java.nio.IntBuffer; + +/** + *

                Reference Page

                + * A Shader Storage Buffer Object (SSBO) for GPU read/write data storage. + *

                + * SSBOs are buffers that can be read from and written to by shaders. + * SSBOs require OpenGL 4.3 or higher. + */ +public class ShaderStorageBufferObject extends NativeObject { + + private final GL4 gl; + + /** + * Creates a new SSBO. + * + * @param gl the GL4 interface (required for glBindBufferBase) + */ + public ShaderStorageBufferObject(GL4 gl) { + super(); + this.gl = gl; + ensureBufferReady(); + } + private ShaderStorageBufferObject(ShaderStorageBufferObject source){ + super(); + this.gl = source.gl; + this.id = source.id; + } + + private void ensureBufferReady(){ + if(isUpdateNeeded()){ + IntBuffer buf = BufferUtils.createIntBuffer(1); + gl.glGenBuffers(buf); + this.id = buf.get(0); + clearUpdateNeeded(); + } + } + + /** + * Initializes the buffer with integer data. + * @param data the initial data to upload + */ + public void initialize(int[] data) { + IntBuffer buffer = BufferUtils.createIntBuffer(data.length); + buffer.put(data); + buffer.flip(); + initialize(buffer); + } + + /** + * Initializes the buffer with an IntBuffer. + * + * @param data the initial data to upload + */ + public void initialize(IntBuffer data) { + ensureBufferReady(); + gl.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, id); + gl.glBufferData(GL4.GL_SHADER_STORAGE_BUFFER, data, GL.GL_DYNAMIC_COPY); + } + + /** + * Reads integer data from the buffer. + * + * @param count the number of integers to read + * @return an array containing the buffer data + */ + public int[] read(int count) { + int[] result = new int[count]; + read(result); + return result; + } + + /** + * Reads integer data from the buffer into an existing array. + * + * @param destination the array to read into + */ + public void read(int[] destination) { + IntBuffer buffer = BufferUtils.createIntBuffer(destination.length); + read(buffer); + buffer.get(destination); + } + + /** + * Reads integer data from the buffer into an IntBuffer. + * + * @param destination the buffer to read into + */ + public void read(IntBuffer destination) { + if(isUpdateNeeded()){ + //If the SSBO was deleted from e.g. context restart, it probably isn't sensible to read from it. + //We could create a fresh empty buffer and read from that, but that might result in garbage data. + throw new RendererException("SSBO was not ready for read"); + } + gl.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, id); + gl.glGetBufferSubData(GL4.GL_SHADER_STORAGE_BUFFER, 0, destination); + gl.glBindBuffer(GL4.GL_SHADER_STORAGE_BUFFER, 0); + } + + + @Override + public void resetObject() { + this.id = INVALID_ID; + setUpdateNeeded(); + } + + @Override + public void deleteObject(Object rendererObject) { + if(id != INVALID_ID){ + IntBuffer buf = BufferUtils.createIntBuffer(1); + buf.put(id); + buf.flip(); + gl.glDeleteBuffers(buf); + } + resetObject(); + } + + @Override + public NativeObject createDestructableClone() { + return new ShaderStorageBufferObject(this); + } + + @Override + public long getUniqueId() { + return ((long) OBJTYPE_BO << 32) | (0xffffffffL & (long) id); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java index 7086eb0455..7064f7ce2e 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/TextureUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,7 @@ * * @author Kirill Vainer */ -final class TextureUtil { +public final class TextureUtil { private static final Logger logger = Logger.getLogger(TextureUtil.class.getName()); @@ -91,12 +91,12 @@ public GLImageFormat getImageFormat(Format fmt, boolean isSrgb) { } public GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) { - //if the passed format is one kind of depth there isno point in getting the srgb format; + //if the passed format is one kind of depth there is no point in getting the srgb format; isSrgb = isSrgb && !fmt.isDepthFormat(); GLImageFormat glFmt = getImageFormat(fmt, isSrgb); if (glFmt == null && isSrgb) { glFmt = getImageFormat(fmt, false); - logger.log(Level.WARNING, "No sRGB format available for ''{0}''. Failling back to linear.", fmt); + logger.log(Level.WARNING, "No sRGB format available for ''{0}''. Falling back to linear.", fmt); } if (glFmt == null) { throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); @@ -351,7 +351,7 @@ public void uploadSubTexture(int target, Image src, int index, int targetX, int } if (src.getMipMapSizes() != null) { - throw new UnsupportedOperationException("Updating mip-mappped images is not supported"); + throw new UnsupportedOperationException("Updating mip-mapped images is not supported"); } if (src.getMultiSamples() > 1) { diff --git a/jme3-core/src/main/java/com/jme3/renderer/opengl/package-info.java b/jme3-core/src/main/java/com/jme3/renderer/opengl/package-info.java new file mode 100644 index 0000000000..79fe65f465 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/opengl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * rendering based on OpenGL (Open Graphics Library) + */ +package com.jme3.renderer.opengl; diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java new file mode 100644 index 0000000000..d4af168a6d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/DefaultPipelineContext.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Default implementation of AbstractPipelineContext that + * does nothing extra. + * + * @author codex + */ +public class DefaultPipelineContext implements PipelineContext { + + // decided to use an atomic boolean, since it is less hassle + private final AtomicBoolean rendered = new AtomicBoolean(false); + + @Override + public boolean startViewPortRender(RenderManager rm, ViewPort vp) { + return rendered.getAndSet(true); + } + + @Override + public void endViewPortRender(RenderManager rm, ViewPort vp) {} + + @Override + public void endContextRenderFrame(RenderManager rm) { + rendered.set(false); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java new file mode 100644 index 0000000000..2f02ab3e82 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/ForwardPipeline.java @@ -0,0 +1,159 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.pipeline; + +import com.jme3.post.SceneProcessor; +import com.jme3.profile.AppProfiler; +import com.jme3.profile.SpStep; +import com.jme3.profile.VpStep; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Spatial; +import com.jme3.util.SafeArrayList; +import java.util.List; + +/** + * Port of the standard forward renderer to a pipeline. + * + * @author codex + */ +public class ForwardPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + + @Override + public void startRenderFrame(RenderManager rm) {} + + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + + AppProfiler prof = rm.getProfiler(); + + SafeArrayList processors = vp.getProcessors(); + if (processors.isEmpty()) { + processors = null; + } + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PreFrame, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (!p.isInitialized()) { + p.initialize(rm, vp); + } + p.setProfiler(prof); + if (prof != null) { + prof.spStep(SpStep.ProcPreFrame, p.getClass().getSimpleName()); + } + p.preFrame(tpf); + } + } + + rm.applyViewPort(vp); + + if (prof != null) { + prof.vpStep(VpStep.RenderScene, vp, null); + } + // flatten scenes into render queue + List scenes = vp.getScenes(); + for (int i = scenes.size() - 1; i >= 0; i--) { + rm.renderScene(scenes.get(i), vp); + } + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostQueue, vp, null); + } + for (SceneProcessor p : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostQueue, p.getClass().getSimpleName()); + } + p.postQueue(vp.getQueue()); + } + } + + if (prof != null) { + prof.vpStep(VpStep.FlushQueue, vp, null); + } + rm.flushQueue(vp); + + if (processors != null) { + if (prof != null) { + prof.vpStep(VpStep.PostFrame, vp, null); + } + for (SceneProcessor proc : processors.getArray()) { + if (prof != null) { + prof.spStep(SpStep.ProcPostFrame, proc.getClass().getSimpleName()); + } + proc.postFrame(vp.getOutputFrameBuffer()); + } + if (prof != null) { + prof.vpStep(VpStep.ProcEndRender, vp, null); + } + } + + // render the translucent objects queue after processors have been rendered + rm.renderTranslucentQueue(vp); + + // clear any remaining spatials that were not rendered. + rm.clearQueue(vp); + + rendered = true; + + /* + * the call to setCamera will indirectly cause a clipRect to be set, must be cleared to avoid surprising results + * if renderer#copyFrameBuffer is used later + */ + rm.getRenderer().clearClipRect(); + + if (prof != null) { + prof.vpStep(VpStep.EndRender, vp, null); + } + + } + + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java new file mode 100644 index 0000000000..b104e5bd5d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/NullPipeline.java @@ -0,0 +1,42 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package com.jme3.renderer.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Render pipeline that performs no rendering. + * + * @author codex + */ +public class NullPipeline implements RenderPipeline { + + private boolean rendered = false; + + @Override + public PipelineContext fetchPipelineContext(RenderManager rm) { + return rm.getDefaultContext(); + } + + @Override + public boolean hasRenderedThisFrame() { + return rendered; + } + + @Override + public void startRenderFrame(RenderManager rm) {} + + @Override + public void pipelineRender(RenderManager rm, PipelineContext context, ViewPort vp, float tpf) { + rendered = true; + } + + @Override + public void endRenderFrame(RenderManager rm) { + rendered = false; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java new file mode 100644 index 0000000000..32a4fa2e2d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/PipelineContext.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Handles objects globally for a single type of RenderPipeline. + * + * @author codex + */ +public interface PipelineContext { + + /** + * Called when a ViewPort rendering session starts that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + * @return true if this context has already rendered a viewport this frame + */ + public boolean startViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called when viewport rendering session ends that this context + * is participating in. + * + * @param rm + * @param vp viewport being rendered + */ + public void endViewPortRender(RenderManager rm, ViewPort vp); + + /** + * Called at the end of a render frame this context participated in. + * + * @param rm + */ + public void endContextRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java new file mode 100644 index 0000000000..44f76d6e86 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/pipeline/RenderPipeline.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer.pipeline; + +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; + +/** + * Pipeline for rendering a ViewPort. + * + * @author codex + * @param + */ +public interface RenderPipeline { + + /** + * Fetches the PipelineContext this pipeline requires for rendering + * from the RenderManager. + * + * @param rm + * @return pipeline context (not null) + */ + public T fetchPipelineContext(RenderManager rm); + + /** + * Returns true if this pipeline has rendered a viewport this render frame. + * + * @return + */ + public boolean hasRenderedThisFrame(); + + /** + * Called before this pipeline is rendered for the first time this frame. + *

                + * Only called if the pipeline will actually be rendered. + * + * @param rm + */ + public void startRenderFrame(RenderManager rm); + + /** + * Renders the pipeline. + * + * @param rm + * @param context + * @param vp + * @param tpf + */ + public void pipelineRender(RenderManager rm, T context, ViewPort vp, float tpf); + + /** + * Called after all rendering is complete in a rendering frame this + * pipeline participated in. + * + * @param rm + */ + public void endRenderFrame(RenderManager rm); + +} diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java index 09b43b9e84..546e766835 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GeometryList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,8 +50,8 @@ public class GeometryList implements Iterable{ private static final int DEFAULT_SIZE = 32; - private Geometry[] geometries; - private ListSort listSort; + private Geometry[] geometries; + private final ListSort listSort; private int size; private GeometryComparator comparator; @@ -63,7 +63,7 @@ public class GeometryList implements Iterable{ */ public GeometryList(GeometryComparator comparator) { size = 0; - geometries = new Geometry[DEFAULT_SIZE]; + geometries = new Geometry[DEFAULT_SIZE]; this.comparator = comparator; listSort = new ListSort(); } @@ -75,6 +75,8 @@ public void setComparator(GeometryComparator comparator) { /** * Returns the GeometryComparator that this Geometry list uses * for sorting. + * + * @return the pre-existing instance */ public GeometryComparator getComparator() { return comparator; @@ -86,7 +88,7 @@ public GeometryComparator getComparator() { * * @param cam Camera to use for sorting. */ - public void setCamera(Camera cam){ + public void setCamera(Camera cam) { this.comparator.setCamera(cam); } @@ -101,21 +103,21 @@ public int size(){ /** * Sets the element at the given index. - * + * * @param index The index to set * @param value The value */ public void set(int index, Geometry value) { geometries[index] = value; } - + /** * Returns the element at the given index. * * @param index The index to lookup * @return Geometry at the index */ - public Geometry get(int index){ + public Geometry get(int index) { return geometries[index]; } @@ -139,7 +141,7 @@ public void add(Geometry g) { * Resets list size to 0. */ public void clear() { - for (int i = 0; i < size; i++){ + for (int i = 0; i < size; i++) { geometries[i] = null; } @@ -149,37 +151,40 @@ public void clear() { /** * Sorts the elements in the list according to their Comparator. */ + @SuppressWarnings("unchecked") public void sort() { if (size > 1) { // sort the spatial list using the comparator - if(listSort.getLength() != size){ + if (listSort.getLength() != size) { listSort.allocateStack(size); - } + } listSort.sort(geometries,comparator); } } + @Override public Iterator iterator() { return new Iterator() { - int index = 0; - + + @Override public boolean hasNext() { return index < size(); } - + + @Override public Geometry next() { - if ( index >= size() ) { + if (index >= size()) { throw new NoSuchElementException("Geometry list has only " + size() + " elements"); } return get(index++); } - + + @Override public void remove() { throw new UnsupportedOperationException("Geometry list doesn't support iterator removal"); } - }; } } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java index 541b7eb127..7629afdf9d 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/GuiComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ */ public class GuiComparator implements GeometryComparator { + @Override public int compare(Geometry o1, Geometry o2) { float z1 = o1.getWorldTranslation().getZ(); float z2 = o2.getWorldTranslation().getZ(); @@ -53,6 +54,7 @@ else if (z1 < z2) return 0; } + @Override public void setCamera(Camera cam) { } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java index bd810ca81f..3fab4435c7 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/NullComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,10 +41,12 @@ * @author Kirill Vainer */ public class NullComparator implements GeometryComparator { + @Override public int compare(Geometry o1, Geometry o2) { return 0; } + @Override public void setCamera(Camera cam) { } } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java index 4b1305c604..1896ae9ace 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/OpaqueComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,30 +42,31 @@ public class OpaqueComparator implements GeometryComparator { private final Vector3f tempVec = new Vector3f(); private final Vector3f tempVec2 = new Vector3f(); - public void setCamera(Camera cam){ + @Override + public void setCamera(Camera cam) { this.cam = cam; } - public float distanceToCam(Geometry spat){ + public float distanceToCam(Geometry spat) { if (spat == null) return Float.NEGATIVE_INFINITY; - + if (spat.queueDistance != Float.NEGATIVE_INFINITY) return spat.queueDistance; - + Vector3f camPosition = cam.getLocation(); Vector3f viewVector = cam.getDirection(tempVec2); Vector3f spatPosition = null; - + if (spat.getWorldBound() != null){ spatPosition = spat.getWorldBound().getCenter(); - }else{ + } else { spatPosition = spat.getWorldTranslation(); } - + spatPosition.subtract(camPosition, tempVec); spat.queueDistance = tempVec.dot(viewVector); - + return spat.queueDistance; } @@ -73,7 +74,7 @@ public float distanceToCam(Geometry spat){ public int compare(Geometry o1, Geometry o2) { Material m1 = o1.getMaterial(); Material m2 = o2.getMaterial(); - + int compareResult = Integer.compare(m1.getSortId(), m2.getSortId()); if (compareResult == 0){ // use the same shader. @@ -87,9 +88,8 @@ else if (d1 < d2) return -1; else return 1; - }else{ + } else { return compareResult; } } - } diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java index 5fd2aade9b..a16dc18665 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/RenderQueue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -181,6 +181,10 @@ public enum ShadowMode { * at all. *

              • Bucket.Gui: {@link com.jme3.renderer.queue.GuiComparator} sorts geometries back to * front based on their Z values. + *
              + * + * @param bucket which Bucket to modify (not null) + * @param c the comparator to use (alias created) */ public void setGeometryComparator(Bucket bucket, GeometryComparator c) { switch (bucket) { @@ -207,6 +211,9 @@ public void setGeometryComparator(Bucket bucket, GeometryComparator c) { /** * Returns the current GeometryComparator used by the specified bucket, * one of Gui, Opaque, Sky, Transparent, or Translucent. + * + * @param bucket which Bucket to access (not null) + * @return the pre-existing instance */ public GeometryComparator getGeometryComparator(Bucket bucket) { switch (bucket) { @@ -257,7 +264,7 @@ public void addToQueue(Geometry g, Bucket bucket) { } } - private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, boolean flush) { list.setCamera(cam); // select camera for sorting list.sort(); for (int i = 0; i < list.size(); i++) { @@ -266,13 +273,15 @@ private void renderGeometryList(GeometryList list, RenderManager rm, Camera cam, rm.renderGeometry(obj); obj.queueDistance = Float.NEGATIVE_INFINITY; } - if (clear) { + if (flush) { list.clear(); } } public void renderShadowQueue(GeometryList list, RenderManager rm, Camera cam, boolean clear) { + rm.getRenderer().pushDebugGroup("ShadowQueue"); renderGeometryList(list, rm, cam, clear); + rm.getRenderer().popDebugGroup(); } public boolean isQueueEmpty(Bucket bucket) { @@ -297,6 +306,7 @@ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam) { } public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean clear) { + rm.getRenderer().pushDebugGroup(bucket.name()); switch (bucket) { case Gui: renderGeometryList(guiList, rm, cam, clear); @@ -317,6 +327,19 @@ public void renderQueue(Bucket bucket, RenderManager rm, Camera cam, boolean cle default: throw new UnsupportedOperationException("Unsupported bucket type: " + bucket); } + rm.getRenderer().popDebugGroup(); + } + + public GeometryList getList(Bucket bucket) { + switch (bucket) { + case Opaque: return opaqueList; + case Gui: return guiList; + case Transparent: return transparentList; + case Translucent: return translucentList; + case Sky: return skyList; + default: + throw new UnsupportedOperationException(); + } } public void clear() { diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java b/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java index c5c3fc79f2..fe729cff85 100644 --- a/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/TransparentComparator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,53 +40,18 @@ public class TransparentComparator implements GeometryComparator { private Camera cam; private final Vector3f tempVec = new Vector3f(); + @Override public void setCamera(Camera cam){ this.cam = cam; } - /** - * Calculates the distance from a spatial to the camera. Distance is a - * squared distance. - * - * @param spat - * Spatial to distancize. - * @return Distance from Spatial to camera. - */ - private float distanceToCam2(Geometry spat){ - if (spat == null) - return Float.NEGATIVE_INFINITY; - - if (spat.queueDistance != Float.NEGATIVE_INFINITY) - return spat.queueDistance; - - Vector3f camPosition = cam.getLocation(); - Vector3f viewVector = cam.getDirection(); - Vector3f spatPosition = null; - - if (spat.getWorldBound() != null){ - spatPosition = spat.getWorldBound().getCenter(); - }else{ - spatPosition = spat.getWorldTranslation(); - } - - spatPosition.subtract(camPosition, tempVec); - spat.queueDistance = tempVec.dot(tempVec); - - float retval = Math.abs(tempVec.dot(viewVector) - / viewVector.dot(viewVector)); - viewVector.mult(retval, tempVec); - - spat.queueDistance = tempVec.length(); - - return spat.queueDistance; - } - private float distanceToCam(Geometry spat){ // NOTE: It is best to check the distance // to the bound's closest edge vs. the bound's center here. return spat.getWorldBound().distanceToEdge(cam.getLocation()); } + @Override public int compare(Geometry o1, Geometry o2) { float d1 = distanceToCam(o1); float d2 = distanceToCam(o2); diff --git a/jme3-core/src/main/java/com/jme3/renderer/queue/package-info.java b/jme3-core/src/main/java/com/jme3/renderer/queue/package-info.java new file mode 100644 index 0000000000..7413cb8735 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/renderer/queue/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * determine the order in which geometries are rendered + */ +package com.jme3.renderer.queue; diff --git a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java index 82711ac6b2..1a94fde246 100644 --- a/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/AssetLinkNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,8 +55,8 @@ */ public class AssetLinkNode extends Node { - protected ArrayList assetLoaderKeys = new ArrayList(); - protected Map assetChildren = new HashMap(); + protected ArrayList assetLoaderKeys = new ArrayList<>(); + protected Map assetChildren = new HashMap<>(); public AssetLinkNode() { } @@ -74,7 +74,7 @@ public AssetLinkNode(String name, ModelKey key) { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); // This is a change in behavior because the old version did not clone @@ -87,7 +87,8 @@ public void cloneFields( Cloner cloner, Object original ) { /** * Add a "linked" child. These are loaded from the assetManager when the * AssetLinkNode is loaded from a binary file. - * @param key + * + * @param key the ModelKey to add */ public void addLinkedChild(ModelKey key) { if (assetLoaderKeys.contains(key)) { @@ -135,7 +136,8 @@ public void detachLinkedChild(Spatial child, ModelKey key) { /** * Loads the linked children AssetKeys from the AssetManager and attaches them to the Node
              * If they are already attached, they will be reloaded. - * @param manager + * + * @param manager for loading assets */ public void attachLinkedChildren(AssetManager manager) { detachLinkedChildren(); @@ -161,13 +163,14 @@ public void detachLinkedChildren() { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); + @SuppressWarnings("unchecked") + public void read(JmeImporter importer) throws IOException { + super.read(importer); - final InputCapsule capsule = e.getCapsule(this); - final AssetManager assetManager = e.getAssetManager(); + final InputCapsule capsule = importer.getCapsule(this); + final AssetManager assetManager = importer.getAssetManager(); - assetLoaderKeys = (ArrayList) capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList()); + assetLoaderKeys = capsule.readSavableArrayList("assetLoaderKeyList", new ArrayList<>()); for (final Iterator iterator = assetLoaderKeys.iterator(); iterator.hasNext(); ) { @@ -187,11 +190,11 @@ public void read(JmeImporter e) throws IOException { @Override public void write(JmeExporter e) throws IOException { - SafeArrayList childs = children; + SafeArrayList childList = children; children = new SafeArrayList<>(Spatial.class); super.write(e); OutputCapsule capsule = e.getCapsule(this); capsule.writeSavableArrayList(assetLoaderKeys, "assetLoaderKeyList", null); - children = childs; + children = childList; } } diff --git a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java index f50780967f..aa5d62a02a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/BatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/BatchNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,6 @@ */ package com.jme3.scene; -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; import com.jme3.material.Material; @@ -50,6 +41,14 @@ import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; +import java.nio.Buffer; +import java.nio.FloatBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * BatchNode holds geometries that are a batched version of all the geometries that are in its sub scenegraph. @@ -73,11 +72,11 @@ public class BatchNode extends GeometryGroupNode { /** * the list of geometry holding the batched meshes */ - protected SafeArrayList batches = new SafeArrayList(Batch.class); + protected SafeArrayList batches = new SafeArrayList<>(Batch.class); /** * a map for storing the batches by geometry to quickly access the batch when updating */ - protected Map batchesByGeom = new HashMap(); + protected Map batchesByGeom = new HashMap<>(); /** * used to store transformed vectors before proceeding to a bulk put into the FloatBuffer */ @@ -185,9 +184,14 @@ public void batch() { } protected void doBatch() { - Map> matMap = new HashMap>(); + Map> matMap = new HashMap<>(); int nbGeoms = 0; + // Recalculate the maxVertCount during gatherGeometries() so it's always + // accurate. Keep track of what it used to be so we know if temp arrays need + // to be reallocated. + int oldMaxVertCount = maxVertCount; + maxVertCount = 0; gatherGeometries(matMap, this, needsFullRebatch); if (needsFullRebatch) { for (Batch batch : batches.getArray()) { @@ -196,10 +200,6 @@ protected void doBatch() { batches.clear(); batchesByGeom.clear(); } - //only reset maxVertCount if there is something new to batch - if (matMap.size() > 0) { - maxVertCount = 0; - } for (Map.Entry> entry : matMap.entrySet()) { Mesh m = new Mesh(); @@ -240,21 +240,17 @@ protected void doBatch() { } - logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Batched {0} geometries in {1} batches.", new Object[]{nbGeoms, batches.size()}); + } - //init the temp arrays if something has been batched only. - if (matMap.size() > 0) { - //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. - //init temp float arrays - tmpFloat = new float[maxVertCount * 3]; - tmpFloatN = new float[maxVertCount * 3]; - if (useTangents) { - tmpFloatT = new float[maxVertCount * 4]; - } + //init the temp arrays if the size has changed + if (oldMaxVertCount != maxVertCount) { + initTempFloatArrays(); } } - //in case the detached spatial is a node, we unbatch all geometries in its subegraph + //in case the detached spatial is a node, we unbatch all geometries in its subgraph @Override public Spatial detachChildAt(int index) { Spatial s = super.detachChildAt(index); @@ -289,6 +285,13 @@ private void gatherGeometries(Map> map, Spatial n, bool if (!isBatch(n) && n.getBatchHint() != BatchHint.Never) { Geometry g = (Geometry) n; + + // Need to recalculate the max vert count whether we are rebatching this + // particular geometry or not. + if (maxVertCount < g.getVertexCount()) { + maxVertCount = g.getVertexCount(); + } + if (!g.isGrouped() || rebatch) { if (g.getMaterial() == null) { throw new IllegalStateException("No material is set for Geometry: " + g.getName() + " please set a material before batching"); @@ -341,8 +344,8 @@ public final boolean isBatch(Spatial s) { } /** - * Sets the material to the all the batches of this BatchNode - * use setMaterial(Material material,int batchIndex) to set a material to a specific batch + * Sets the material to the all the batches of this BatchNode. + * Use setMaterial(Material material,int batchIndex) to set a material to a specific batch. * * @param material the material to use for this geometry */ @@ -352,9 +355,9 @@ public void setMaterial(Material material) { } /** - * Returns the material that is used for the first batch of this BatchNode + * Returns the material that is used for the first batch of this BatchNode. *

              - * use getMaterial(Material material,int batchIndex) to get a material from a specific batch + * Use getMaterial(Material material,int batchIndex) to get a material from a specific batch. * * @return the material that is used for the first batch of this BatchNode * @see #setMaterial(com.jme3.material.Material) @@ -385,16 +388,12 @@ private void mergeGeometries(Mesh outMesh, List geometries) { int maxWeights = -1; Mesh.Mode mode = null; - float lineWidth = 1f; for (Geometry geom : geometries) { totalVerts += geom.getVertexCount(); totalTris += geom.getTriangleCount(); totalLodLevels = Math.min(totalLodLevels, geom.getMesh().getNumLodLevels()); - if (maxVertCount < geom.getVertexCount()) { - maxVertCount = geom.getVertexCount(); - } + Mesh.Mode listMode; - //float listLineWidth = 1f; int components; switch (geom.getMesh().getMode()) { case Points: @@ -444,7 +443,7 @@ private void mergeGeometries(Mesh outMesh, List geometries) { outMesh.setMode(mode); //outMesh.setLineWidth(lineWidth); if (totalVerts >= 65536) { - // make sure we create an UnsignedInt buffer so we can fit all of the meshes + // Make sure we create an UnsignedInt buffer, so we can fit all of the meshes. formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedInt; } else { formatForBuf[VertexBuffer.Type.Index.ordinal()] = VertexBuffer.Format.UnsignedShort; @@ -615,6 +614,15 @@ private void doTransforms(FloatBuffer bindBufPos, FloatBuffer bindBufNorm, Float } } + private void initTempFloatArrays() { + //TODO these arrays should be allocated by chunk instead to avoid recreating them each time the batch is changed. + tmpFloat = new float[maxVertCount * 3]; + tmpFloatN = new float[maxVertCount * 3]; + if (useTangents) { + tmpFloatT = new float[maxVertCount * 4]; + } + } + private void doCopyBuffer(FloatBuffer inBuf, int offset, FloatBuffer outBuf, int componentSize) { TempVars vars = TempVars.get(); Vector3f pos = vars.vect1; @@ -688,7 +696,7 @@ public Node clone(boolean cloneMaterials) { } } clone.needsFullRebatch = true; - clone.batches = new SafeArrayList(Batch.class); + clone.batches = new SafeArrayList<>(Batch.class); clone.batchesByGeom = new HashMap(); clone.batch(); } @@ -708,7 +716,7 @@ public void cloneFields(Cloner cloner, Object original) { this.tmpFloatT = cloner.clone(tmpFloatT); - HashMap newBatchesByGeom = new HashMap(); + HashMap newBatchesByGeom = new HashMap<>(); for (Map.Entry e : batchesByGeom.entrySet()) { newBatchesByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); } diff --git a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java index ab8a16e544..0f42325b8f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/CameraNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/CameraNode.java @@ -100,7 +100,7 @@ public Camera getCamera() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); // A change in behavior... I think previously CameraNode was probably diff --git a/jme3-core/src/main/java/com/jme3/scene/Geometry.java b/jme3-core/src/main/java/com/jme3/scene/Geometry.java index 424b445049..dd3da84c9e 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Geometry.java +++ b/jme3-core/src/main/java/com/jme3/scene/Geometry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,7 @@ import com.jme3.renderer.Camera; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.mesh.MorphTarget; +import com.jme3.scene.threadwarden.SceneGraphThreadWarden; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.IdentityCloneFunction; @@ -61,7 +62,6 @@ * @author Kirill Vainer */ public class Geometry extends Spatial { - // Version #1: removed shared meshes. // models loaded with shared mesh will be automatically fixed. public static final int SAVABLE_VERSION = 1; @@ -74,19 +74,16 @@ public class Geometry extends Spatial { */ protected boolean ignoreTransform = false; protected transient Matrix4f cachedWorldMat = new Matrix4f(); - /** * Specifies which {@link GeometryGroupNode} this Geometry * is managed by. */ protected GeometryGroupNode groupNode; - /** * The start index of this Geometry's inside * the {@link GeometryGroupNode}. */ protected int startIndex = -1; - /** * Morph state variable for morph animation */ @@ -140,6 +137,18 @@ public Geometry(String name, Mesh mesh) { this.mesh = mesh; } + /** + * Create a geometry node with mesh data and material. + * + * @param name The name of this geometry + * @param mesh The mesh data for this geometry + * @param material The material for this geometry + */ + public Geometry(String name, Mesh mesh, Material material) { + this(name, mesh); + setMaterial(material); + } + @Override public boolean checkCulling(Camera cam) { if (isGrouped()) { @@ -175,6 +184,7 @@ public void setIgnoreTransform(boolean ignoreTransform) { */ @Override public void setLodLevel(int lod) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (mesh.getNumLodLevels() == 0) { throw new IllegalStateException("LOD levels are not set on this mesh"); } @@ -206,6 +216,7 @@ public int getLodLevel() { * * @see Mesh#getVertexCount() */ + @Override public int getVertexCount() { return mesh.getVertexCount(); } @@ -217,6 +228,7 @@ public int getVertexCount() { * * @see Mesh#getTriangleCount() */ + @Override public int getTriangleCount() { return mesh.getTriangleCount(); } @@ -229,6 +241,7 @@ public int getTriangleCount() { * @throws IllegalArgumentException If mesh is null */ public void setMesh(Mesh mesh) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (mesh == null) { throw new IllegalArgumentException(); } @@ -259,6 +272,7 @@ public Mesh getMesh() { */ @Override public void setMaterial(Material material) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); this.material = material; nbSimultaneousGPUMorph = -1; if (isGrouped()) { @@ -288,6 +302,7 @@ public BoundingVolume getModelBound() { * Updates the bounding volume of the mesh. Should be called when the * mesh has been modified. */ + @Override public void updateModelBound() { mesh.updateBound(); setBoundRefresh(); @@ -304,7 +319,7 @@ public void updateModelBound() { protected void updateWorldBound() { super.updateWorldBound(); if (mesh == null) { - throw new NullPointerException("Geometry: " + getName() + " has null mesh"); + throw new IllegalStateException("Geometry \"" + getName() + "\" has null mesh."); } if (mesh.getBound() != null) { @@ -388,8 +403,7 @@ protected void setParent(Node parent) { } } - - /** + /* * Indicate that the transform of this spatial has changed and that * a refresh is required. */ @@ -457,6 +471,7 @@ public void setModelBound(BoundingVolume modelBound) { //updateModelBound(); } + @Override public int collideWith(Collidable other, CollisionResults results) { // Force bound to update checkDoBoundUpdate(); @@ -500,6 +515,7 @@ public boolean isGrouped() { /** * @deprecated Use {@link #isGrouped()} instead. + * @return true if managed by a {@link GeometryGroupNode} */ @Deprecated public boolean isBatched() { @@ -515,7 +531,7 @@ public boolean isBatched() { */ @Override public Geometry clone(boolean cloneMaterial) { - return (Geometry)super.clone(cloneMaterial); + return (Geometry) super.clone(cloneMaterial); } /** @@ -549,13 +565,13 @@ public Spatial oldDeepClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); // If this is a grouped node and if our group node is // also cloned then we'll grab its reference. - if( groupNode != null ) { - if( cloner.isCloned(groupNode) ) { + if (groupNode != null) { + if (cloner.isCloned(groupNode)) { // Then resolve the reference this.groupNode = cloner.clone(groupNode); } else { @@ -577,7 +593,7 @@ public void cloneFields( Cloner cloner, Object original ) { // See if we clone the mesh using the special animation // semi-deep cloning - if( shallowClone && mesh != null && mesh.getBuffer(Type.BindPosePosition) != null ) { + if (shallowClone && mesh != null && mesh.getBuffer(Type.BindPosePosition) != null) { // Then we need to clone the mesh a little deeper this.mesh = mesh.cloneForAnim(); } else { @@ -589,7 +605,7 @@ public void cloneFields( Cloner cloner, Object original ) { } public void setMorphState(float[] state) { - if (mesh == null || mesh.getMorphTargets().length == 0){ + if (mesh == null || mesh.getMorphTargets().length == 0) { return; } @@ -604,9 +620,9 @@ public void setMorphState(float[] state) { /** * Set the state of the morph with the given name. - * + * * If the name of the morph is not found, no state will be set. - * + * * @param morphTarget The name of the morph to set the state of * @param state The state to set the morph to */ @@ -620,6 +636,7 @@ public void setMorphState(String morphTarget, float state) { /** * returns true if the morph state has changed on the last frame. + * * @return true if changed, otherwise false */ public boolean isDirtyMorph() { @@ -627,9 +644,10 @@ public boolean isDirtyMorph() { } /** - * Seting this to true will stop this geometry morph buffer to be updated, + * Setting this to true will stop this geometry morph buffer to be updated, * unless the morph state changes - * @param dirtyMorph + * + * @param dirtyMorph true→prevent updating, false→allow updating */ public void setDirtyMorph(boolean dirtyMorph) { this.dirtyMorph = dirtyMorph; @@ -638,6 +656,7 @@ public void setDirtyMorph(boolean dirtyMorph) { /** * returns the morph state of this Geometry. * Used internally by the MorphControl. + * * @return an array */ public float[] getMorphState() { @@ -646,9 +665,10 @@ public float[] getMorphState() { } return morphState; } - + /** * Get the state of a morph + * * @param morphTarget the name of the morph to get the state of * @return the state of the morph, or -1 if the morph is not found */ @@ -656,16 +676,19 @@ public float getMorphState(String morphTarget) { int index = mesh.getMorphIndex(morphTarget); if (index < 0) { return -1; - } else { + } else { return morphState[index]; } } /** - * Return the number of morph targets that can be handled on the GPU simultaneously for this geometry. + * Return the number of morph targets that can be handled + * on the GPU simultaneously for this geometry. * Note that it depends on the material set on this geometry. - * This number is computed and set by the MorphControl, so it might be available only after the first frame. + * This number is computed and set by the MorphControl, + * so it might be available only after the first frame. * Else it's set to -1. + * * @return the number of simultaneous morph targets handled on the GPU */ public int getNbSimultaneousGPUMorph() { @@ -673,11 +696,15 @@ public int getNbSimultaneousGPUMorph() { } /** - * Sets the number of morph targets that can be handled on the GPU simultaneously for this geometry. + * Sets the number of morph targets that can be handled + * on the GPU simultaneously for this geometry. * Note that it depends on the material set on this geometry. - * This number is computed and set by the MorphControl, so it might be available only after the first frame. + * This number is computed and set by the MorphControl, + * so it might be available only after the first frame. * Else it's set to -1. - * WARNING: setting this manually might crash the shader compilation if set too high. Do it at your own risk. + * WARNING: setting this manually might crash the shader compilation if set too high. + * Do it at your own risk. + * * @param nbSimultaneousGPUMorph the number of simultaneous morph targets to be handled on the GPU. */ public void setNbSimultaneousGPUMorph(int nbSimultaneousGPUMorph) { @@ -719,7 +746,10 @@ public void read(JmeImporter im) throws IOException { material = im.getAssetManager().loadMaterial(matName); } catch (AssetNotFoundException ex) { // Cannot find J3M file. - logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", new Object[]{matName, key}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Cannot locate {0} for geometry {1}", + new Object[]{matName, key}); + } } } // If material is NULL, try to load it from the geometry diff --git a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java index a9dfefca15..db245392ca 100644 --- a/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/GeometryGroupNode.java @@ -72,7 +72,7 @@ public GeometryGroupNode(String name) { /** * Called by {@link Geometry geom} to specify that it - * has been unassociated from its GeoemtryGroupNode. + * has been unassociated from its GeometryGroupNode. * * Unassociation occurs when the {@link Geometry} is * {@link Spatial#removeFromParent() detached} from its parent diff --git a/jme3-core/src/main/java/com/jme3/scene/LightNode.java b/jme3-core/src/main/java/com/jme3/scene/LightNode.java index d03304176e..00cc652868 100644 --- a/jme3-core/src/main/java/com/jme3/scene/LightNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/LightNode.java @@ -44,7 +44,9 @@ * with a {@link Node} object. * * @author Tim8Dev + * @deprecated Use a {@link LightControl} attached to a {@link Node} directly. */ +@Deprecated public class LightNode extends Node { private LightControl lightControl; @@ -99,7 +101,7 @@ public Light getLight() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); // A change in behavior... I think previously LightNode was probably 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 326a6acc7d..2819c2838f 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-2019 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,6 @@ import com.jme3.util.IntMap.Entry; import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; - import java.io.IOException; import java.nio.*; import java.util.ArrayList; @@ -58,8 +57,6 @@ * Meshes may contain three types of geometric primitives: *

                *
              • Points - Every vertex represents a single point in space. - * Points can also be used for {@link RenderState#setPointSprite(boolean) point - * sprite} mode.
              • *
              • Lines - 2 vertices represent a line segment, with the width specified * via {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)}.
              • *
              • Triangles - 3 vertices represent a solid triangle primitive.
              • @@ -79,50 +76,46 @@ public enum Mode { * determined via the vertex shader's gl_PointSize output. */ Points(true), - /** * A primitive is a line segment. Every two vertices specify - * a single line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used + * a single line. {@link Material#getAdditionalRenderState()} + * and {@link RenderState#setLineWidth(float)} can be used * to set the width of the lines. */ Lines(true), - /** * A primitive is a line segment. The first two vertices specify * a single line, while subsequent vertices are combined with the - * previous vertex to make a line. {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can + * previous vertex to make a line. {@link Material#getAdditionalRenderState()} + * and {@link RenderState#setLineWidth(float)} can * be used to set the width of the lines. */ LineStrip(false), - /** * Identical to {@link #LineStrip} except that at the end * the last vertex is connected with the first to form a line. - * {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} can be used + * {@link Material#getAdditionalRenderState()} + * and {@link RenderState#setLineWidth(float)} can be used * to set the width of the lines. */ LineLoop(false), - /** * A primitive is a triangle. Each 3 vertices specify a single * triangle. */ Triangles(true), - /** * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, while subsequent vertices are combined with * the previous two to form a triangle. */ TriangleStrip(false), - /** * Similar to {@link #Triangles}, the first 3 vertices * specify a triangle, each 2 subsequent vertices are combined * with the very first vertex to make a triangle. */ TriangleFan(false), - /** * A combination of various triangle modes. It is best to avoid * using this mode as it may not be supported by all renderers. @@ -136,9 +129,10 @@ public enum Mode { * for each patch (default is 3 for triangle tessellation) */ Patch(true); + private boolean listMode = false; - private Mode(boolean listMode){ + private Mode(boolean listMode) { this.listMode = listMode; } @@ -151,32 +145,48 @@ private Mode(boolean listMode){ * * @return true if the mode is a list type mode */ - public boolean isListMode(){ + public boolean isListMode() { return listMode; } } + + /** + * Default Variables + */ + private static final int DEFAULT_VERTEX_ARRAY_ID = -1; + private static final CollisionData DEFAULT_COLLISION_TREE = null; + private static final float DEFAULT_POINT_SIZE = 1.0f; + private static final float DEFAULT_LINE_WIDTH = 1.0f; + + private static final int DEFAULT_VERT_COUNT = -1; + private static final int DEFAULT_ELEMENT_COUNT = -1; + private static final int DEFAULT_INSTANCE_COUNT = -1; + private static final int DEFAULT_PATCH_VERTEX_COUNT = 3; + private static final int DEFAULT_MAX_NUM_WEIGHTS = -1; + /** * The bounding volume that contains the mesh entirely. * By default a BoundingBox (AABB). */ - private BoundingVolume meshBound = new BoundingBox(); + private BoundingVolume meshBound = new BoundingBox(); - private CollisionData collisionTree = null; + private CollisionData collisionTree = DEFAULT_COLLISION_TREE; private SafeArrayList buffersList = new SafeArrayList<>(VertexBuffer.class); private IntMap buffers = new IntMap<>(); private VertexBuffer[] lodLevels; - private float pointSize = 1; - private float lineWidth = 1; + + private float pointSize = DEFAULT_POINT_SIZE; + private float lineWidth = DEFAULT_LINE_WIDTH; - private transient int vertexArrayID = -1; + private transient int vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; - private int vertCount = -1; - private int elementCount = -1; - private int instanceCount = -1; - private int patchVertexCount=3; //only used for tessellation - private int maxNumWeights = -1; // only if using skeletal animation + private int vertCount = DEFAULT_VERT_COUNT; + private int elementCount = DEFAULT_ELEMENT_COUNT; + private int instanceCount = DEFAULT_INSTANCE_COUNT; + private int patchVertexCount = DEFAULT_PATCH_VERTEX_COUNT; //only used for tessellation + private int maxNumWeights = DEFAULT_MAX_NUM_WEIGHTS; // only if using skeletal animation private int[] elementLengths; private int[] modeStart; @@ -188,7 +198,7 @@ public boolean isListMode(){ /** * Creates a new mesh with no {@link VertexBuffer vertex buffers}. */ - public Mesh(){ + public Mesh() { } /** @@ -206,7 +216,7 @@ public Mesh clone() { clone.collisionTree = collisionTree != null ? collisionTree : null; clone.buffers = buffers.clone(); clone.buffersList = new SafeArrayList<>(VertexBuffer.class, buffersList); - clone.vertexArrayID = -1; + clone.vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; if (elementLengths != null) { clone.elementLengths = elementLengths.clone(); } @@ -226,24 +236,24 @@ public Mesh clone() { * * @return a deep clone of this mesh. */ - public Mesh deepClone(){ - try{ + public Mesh deepClone() { + try { Mesh clone = (Mesh) super.clone(); clone.meshBound = meshBound != null ? meshBound.clone() : null; // TODO: Collision tree cloning //clone.collisionTree = collisionTree != null ? collisionTree : null; - clone.collisionTree = null; // it will get re-generated in any case + clone.collisionTree = DEFAULT_COLLISION_TREE; // it will get re-generated in any case clone.buffers = new IntMap<>(); clone.buffersList = new SafeArrayList<>(VertexBuffer.class); - for (VertexBuffer vb : buffersList.getArray()){ + for (VertexBuffer vb : buffersList.getArray()) { VertexBuffer bufClone = vb.clone(); clone.buffers.put(vb.getBufferType().ordinal(), bufClone); clone.buffersList.add(bufClone); } - clone.vertexArrayID = -1; + clone.vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; clone.vertCount = vertCount; clone.elementCount = elementCount; clone.instanceCount = instanceCount; @@ -255,7 +265,7 @@ public Mesh deepClone(){ clone.elementLengths = elementLengths != null ? elementLengths.clone() : null; clone.modeStart = modeStart != null ? modeStart.clone() : null; return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } @@ -269,9 +279,9 @@ public Mesh deepClone(){ * * @return A clone of the mesh for animation use. */ - public Mesh cloneForAnim(){ + public Mesh cloneForAnim() { Mesh clone = clone(); - if (getBuffer(Type.BindPosePosition) != null){ + if (getBuffer(Type.BindPosePosition) != null) { VertexBuffer oldPos = getBuffer(Type.Position); // NOTE: creates deep clone @@ -279,13 +289,13 @@ public Mesh cloneForAnim(){ clone.clearBuffer(Type.Position); clone.setBuffer(newPos); - if (getBuffer(Type.BindPoseNormal) != null){ + if (getBuffer(Type.BindPoseNormal) != null) { VertexBuffer oldNorm = getBuffer(Type.Normal); VertexBuffer newNorm = oldNorm.clone(); clone.clearBuffer(Type.Normal); clone.setBuffer(newNorm); - if (getBuffer(Type.BindPoseTangent) != null){ + if (getBuffer(Type.BindPoseTangent) != null) { VertexBuffer oldTang = getBuffer(Type.Tangent); VertexBuffer newTang = oldTang.clone(); clone.clearBuffer(Type.Tangent); @@ -302,8 +312,8 @@ public Mesh cloneForAnim(){ @Override public Mesh jmeClone() { try { - Mesh clone = (Mesh)super.clone(); - clone.vertexArrayID = -1; + Mesh clone = (Mesh) super.clone(); + clone.vertexArrayID = DEFAULT_VERTEX_ARRAY_ID; return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); @@ -314,10 +324,9 @@ public Mesh jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { - + public void cloneFields(Cloner cloner, Object original) { // Probably could clone this now but it will get regenerated anyway. - this.collisionTree = null; + this.collisionTree = DEFAULT_COLLISION_TREE; this.meshBound = cloner.clone(meshBound); this.buffersList = cloner.clone(buffersList); @@ -328,7 +337,7 @@ public void cloneFields( Cloner cloner, Object original ) { } /** - * @param forSoftwareAnim + * @param forSoftwareAnim ignored * @deprecated use generateBindPose(); */ @Deprecated @@ -393,7 +402,7 @@ public void generateBindPose() { * * @param forSoftwareAnim Should be true to enable the conversion. */ - public void prepareForAnim(boolean forSoftwareAnim){ + public void prepareForAnim(boolean forSoftwareAnim) { if (forSoftwareAnim) { // convert indices to ubytes on the heap VertexBuffer indices = getBuffer(Type.BoneIndex); @@ -425,7 +434,7 @@ public void prepareForAnim(boolean forSoftwareAnim){ weights.updateData(arrayWeight); } weights.setUsage(Usage.CpuOnly); - // position, normal, and tanget buffers to be in "Stream" mode + // position, normal, and tangent buffers to be in "Stream" mode VertexBuffer positions = getBuffer(Type.Position); VertexBuffer normals = getBuffer(Type.Normal); VertexBuffer tangents = getBuffer(Type.Tangent); @@ -445,32 +454,37 @@ public void prepareForAnim(boolean forSoftwareAnim){ VertexBuffer indices = getBuffer(Type.BoneIndex); if (indices.getFormat() == Format.UnsignedByte) { ByteBuffer originalIndex = (ByteBuffer) indices.getData(); - ByteBuffer directIndex = BufferUtils.createByteBuffer(originalIndex.capacity()); + ByteBuffer directIndex + = BufferUtils.createByteBuffer(originalIndex.capacity()); originalIndex.clear(); directIndex.put(originalIndex); result = directIndex; } else { //bone indices can be stored in an UnsignedShort buffer ShortBuffer originalIndex = (ShortBuffer) indices.getData(); - ShortBuffer directIndex = BufferUtils.createShortBuffer(originalIndex.capacity()); + ShortBuffer directIndex + = BufferUtils.createShortBuffer(originalIndex.capacity()); originalIndex.clear(); directIndex.put(originalIndex); result = directIndex; } - indicesHW.setupData(Usage.Static, indices.getNumComponents(), indices.getFormat(), result); + indicesHW.setupData(Usage.Static, indices.getNumComponents(), + indices.getFormat(), result); } VertexBuffer weightsHW = getBuffer(Type.HWBoneWeight); if (weightsHW.getData() == null) { VertexBuffer weights = getBuffer(Type.BoneWeight); FloatBuffer originalWeight = (FloatBuffer) weights.getData(); - FloatBuffer directWeight = BufferUtils.createFloatBuffer(originalWeight.capacity()); + FloatBuffer directWeight + = BufferUtils.createFloatBuffer(originalWeight.capacity()); originalWeight.clear(); directWeight.put(originalWeight); - weightsHW.setupData(Usage.Static, weights.getNumComponents(), weights.getFormat(), directWeight); + weightsHW.setupData(Usage.Static, weights.getNumComponents(), + weights.getFormat(), directWeight); } - // position, normal, and tanget buffers to be in "Static" mode + // position, normal, and tangent buffers to be in "Static" mode VertexBuffer positions = getBuffer(Type.Position); VertexBuffer normals = getBuffer(Type.Normal); VertexBuffer tangents = getBuffer(Type.Tangent); @@ -502,7 +516,7 @@ public void prepareForAnim(boolean forSoftwareAnim){ * * @param lodLevels The LOD levels to set */ - public void setLodLevels(VertexBuffer[] lodLevels){ + public void setLodLevels(VertexBuffer[] lodLevels) { this.lodLevels = lodLevels; } @@ -510,7 +524,7 @@ public void setLodLevels(VertexBuffer[] lodLevels){ * @return The number of LOD levels set on this mesh, including the main * index buffer, returns zero if there are no lod levels. */ - public int getNumLodLevels(){ + public int getNumLodLevels() { return lodLevels != null ? lodLevels.length : 0; } @@ -526,7 +540,7 @@ public int getNumLodLevels(){ * * @see #setLodLevels(com.jme3.scene.VertexBuffer[]) */ - public VertexBuffer getLodLevel(int lod){ + public VertexBuffer getLodLevel(int lod) { return lodLevels[lod]; } @@ -559,6 +573,8 @@ public int[] getModeStart() { /** * Get the mode start indices for {@link Mode#Hybrid} mesh mode. + * + * @param modeStart the pre-existing array */ public void setModeStart(int[] modeStart) { this.modeStart = modeStart; @@ -603,7 +619,7 @@ public int getMaxNumWeights() { * Only relevant if this mesh has bone index/weight buffers. * This value should be between 0 and 4. * - * @param maxNumWeights + * @param maxNumWeights the desired number (between 0 and 4, inclusive) */ public void setMaxNumWeights(int maxNumWeights) { this.maxNumWeights = maxNumWeights; @@ -614,29 +630,18 @@ public void setMaxNumWeights(int maxNumWeights) { * determined in the vertex shader. * * @return 1.0 - * - * @see #setPointSize(float) */ @Deprecated public float getPointSize() { - return 1.0f; - } - - /** - * @deprecated Does nothing, since the size of {@link Mode#Points points} is - * determined via the vertex shader's gl_PointSize output. - * - * @param pointSize ignored - */ - @Deprecated - public void setPointSize(float pointSize) { + return DEFAULT_POINT_SIZE; } /** * Returns the line width for line meshes. * * @return the line width - * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#getLineWidth()} + * @deprecated use {@link Material#getAdditionalRenderState()} + * and {@link RenderState#getLineWidth()} */ @Deprecated public float getLineWidth() { @@ -649,7 +654,8 @@ public float getLineWidth() { * the default value is 1.0. * * @param lineWidth The line width - * @deprecated use {@link Material#getAdditionalRenderState()} and {@link RenderState#setLineWidth(float)} + * @deprecated use {@link Material#getAdditionalRenderState()} + * and {@link RenderState#setLineWidth(float)} */ @Deprecated public void setLineWidth(float lineWidth) { @@ -665,7 +671,7 @@ public void setLineWidth(float lineWidth) { * for all {@link VertexBuffer vertex buffers} on this Mesh. */ public void setStatic() { - for (VertexBuffer vb : buffersList.getArray()){ + for (VertexBuffer vb : buffersList.getArray()) { vb.setUsage(Usage.Static); } } @@ -676,7 +682,7 @@ public void setStatic() { * for all {@link VertexBuffer vertex buffers} on this Mesh. */ public void setDynamic() { - for (VertexBuffer vb : buffersList.getArray()){ + for (VertexBuffer vb : buffersList.getArray()) { vb.setUsage(Usage.Dynamic); } } @@ -686,8 +692,8 @@ public void setDynamic() { * Sets the usage mode to {@link Usage#Stream} * for all {@link VertexBuffer vertex buffers} on this Mesh. */ - public void setStreamed(){ - for (VertexBuffer vb : buffersList.getArray()){ + public void setStreamed() { + for (VertexBuffer vb : buffersList.getArray()) { vb.setUsage(Usage.Stream); } } @@ -698,7 +704,7 @@ public void setStreamed(){ * to avoid using this method as it disables some engine features. */ @Deprecated - public void setInterleaved(){ + public void setInterleaved() { ArrayList vbs = new ArrayList<>(); vbs.addAll(buffersList); @@ -707,7 +713,7 @@ public void setInterleaved(){ vbs.remove(getBuffer(Type.Index)); int stride = 0; // aka bytes per vertex - for (int i = 0; i < vbs.size(); i++){ + for (int i = 0; i < vbs.size(); i++) { VertexBuffer vb = vbs.get(i); // if (vb.getFormat() != Format.Float){ // throw new UnsupportedOperationException("Cannot interleave vertex buffer.\n" + @@ -725,20 +731,20 @@ public void setInterleaved(){ buffers.put(Type.InterleavedData.ordinal(), allData); buffersList.add(allData); - for (int vert = 0; vert < getVertexCount(); vert++){ - for (int i = 0; i < vbs.size(); i++){ + for (int vert = 0; vert < getVertexCount(); vert++) { + for (int i = 0; i < vbs.size(); i++) { VertexBuffer vb = vbs.get(i); - switch (vb.getFormat()){ + switch (vb.getFormat()) { case Float: FloatBuffer fb = (FloatBuffer) vb.getData(); - for (int comp = 0; comp < vb.components; comp++){ + for (int comp = 0; comp < vb.components; comp++) { dataBuf.putFloat(fb.get()); } break; case Byte: case UnsignedByte: ByteBuffer bb = (ByteBuffer) vb.getData(); - for (int comp = 0; comp < vb.components; comp++){ + for (int comp = 0; comp < vb.components; comp++) { dataBuf.put(bb.get()); } break; @@ -746,20 +752,20 @@ public void setInterleaved(){ case Short: case UnsignedShort: ShortBuffer sb = (ShortBuffer) vb.getData(); - for (int comp = 0; comp < vb.components; comp++){ + for (int comp = 0; comp < vb.components; comp++) { dataBuf.putShort(sb.get()); } break; case Int: case UnsignedInt: IntBuffer ib = (IntBuffer) vb.getData(); - for (int comp = 0; comp < vb.components; comp++){ + for (int comp = 0; comp < vb.components; comp++) { dataBuf.putInt(ib.get()); } break; case Double: DoubleBuffer db = (DoubleBuffer) vb.getData(); - for (int comp = 0; comp < vb.components; comp++){ + for (int comp = 0; comp < vb.components; comp++) { dataBuf.putDouble(db.get()); } break; @@ -768,7 +774,7 @@ public void setInterleaved(){ } int offset = 0; - for (VertexBuffer vb : vbs){ + for (VertexBuffer vb : vbs) { vb.setOffset(offset); vb.setStride(stride); @@ -778,8 +784,8 @@ public void setInterleaved(){ } } - private int computeNumElements(int bufSize){ - switch (mode){ + private int computeNumElements(int bufSize) { + switch (mode) { case Triangles: return bufSize / 3; case TriangleFan: @@ -794,7 +800,7 @@ private int computeNumElements(int bufSize){ case LineStrip: return bufSize - 1; case Patch: - return bufSize/patchVertexCount; + return bufSize / patchVertexCount; default: throw new UnsupportedOperationException(); } @@ -803,8 +809,8 @@ private int computeNumElements(int bufSize){ private int computeInstanceCount() { // Whatever the max of the base instance counts int max = 0; - for( VertexBuffer vb : buffersList ) { - if( vb.getBaseInstanceCount() > max ) { + for (VertexBuffer vb : buffersList) { + if (vb.getBaseInstanceCount() > max) { max = vb.getBaseInstanceCount(); } } @@ -821,19 +827,19 @@ private int computeInstanceCount() { * @throws IllegalStateException If this mesh is in * {@link #setInterleaved() interleaved} format. */ - public void updateCounts(){ + public void updateCounts() { if (getBuffer(Type.InterleavedData) != null) { throw new IllegalStateException("Should update counts before interleave"); } VertexBuffer pb = getBuffer(Type.Position); VertexBuffer ib = getBuffer(Type.Index); - if (pb != null){ + if (pb != null) { vertCount = pb.getData().limit() / pb.getNumComponents(); } - if (ib != null){ + if (ib != null) { elementCount = computeNumElements(ib.getData().limit()); - }else{ + } else { elementCount = computeNumElements(vertCount); } instanceCount = computeInstanceCount(); @@ -845,8 +851,8 @@ public void updateCounts(){ * @param lod The lod level to look up * @return The triangle count for that LOD level */ - public int getTriangleCount(int lod){ - if (lodLevels != null){ + public int getTriangleCount(int lod) { + if (lodLevels != null) { if (lod < 0) { throw new IllegalArgumentException("LOD level cannot be < 0"); } @@ -856,9 +862,9 @@ public int getTriangleCount(int lod){ } return computeNumElements(lodLevels[lod].getData().limit()); - }else if (lod == 0){ + } else if (lod == 0) { return elementCount; - }else{ + } else { throw new IllegalArgumentException("There are no LOD levels on the mesh!"); } } @@ -872,7 +878,7 @@ public int getTriangleCount(int lod){ * * @return how many triangles/elements are on this Mesh. */ - public int getTriangleCount(){ + public int getTriangleCount() { return elementCount; } @@ -883,13 +889,15 @@ public int getTriangleCount(){ * * @return Number of vertices on the mesh */ - public int getVertexCount(){ + public int getVertexCount() { return vertCount; } /** * Returns the number of instances this mesh contains. The instance * count is based on any VertexBuffers with instancing set. + * + * @return the number of instances */ public int getInstanceCount() { return instanceCount; @@ -906,24 +914,24 @@ public int getInstanceCount() { * @param v2 Vector to contain second vertex position * @param v3 Vector to contain third vertex position */ - public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){ + public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3) { VertexBuffer pb = getBuffer(Type.Position); IndexBuffer ib = getIndicesAsList(); - if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3){ + if (pb != null && pb.getFormat() == Format.Float && pb.getNumComponents() == 3) { FloatBuffer fpb = (FloatBuffer) pb.getData(); - // aquire triangle's vertex indices + // acquire triangle's vertex indices int vertIndex = index * 3; int vert1 = ib.get(vertIndex); - int vert2 = ib.get(vertIndex+1); - int vert3 = ib.get(vertIndex+2); + int vert2 = ib.get(vertIndex + 1); + int vert3 = ib.get(vertIndex + 2); BufferUtils.populateFromBuffer(v1, fpb, vert1); BufferUtils.populateFromBuffer(v2, fpb, vert2); BufferUtils.populateFromBuffer(v3, fpb, vert3); - }else{ + } else { throw new UnsupportedOperationException("Position buffer not set or " - + " has incompatible format"); + + " has incompatible format"); } } @@ -937,9 +945,10 @@ public void getTriangle(int index, Vector3f v1, Vector3f v2, Vector3f v3){ * * @param tri The triangle to store the positions in */ - public void getTriangle(int index, Triangle tri){ + 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); } @@ -952,28 +961,32 @@ public void getTriangle(int index, Triangle tri){ * * @param indices Indices of the triangle's vertices */ - public void getTriangle(int index, int[] indices){ + public void getTriangle(int index, int[] indices) { IndexBuffer ib = getIndicesAsList(); // acquire triangle's vertex indices int vertIndex = index * 3; indices[0] = ib.get(vertIndex); - indices[1] = ib.get(vertIndex+1); - indices[2] = ib.get(vertIndex+2); + indices[1] = ib.get(vertIndex + 1); + indices[2] = ib.get(vertIndex + 2); } /** * Returns the mesh's VAO ID. Internal use only. + * + * @return the array ID */ - public int getId(){ + public int getId() { return vertexArrayID; } /** * Sets the mesh's VAO ID. Internal use only. + * + * @param id the array ID */ - public void setId(int id){ - if (vertexArrayID != -1) { + public void setId(int id) { + if (vertexArrayID != DEFAULT_VERTEX_ARRAY_ID) { throw new IllegalStateException("ID has already been set."); } @@ -987,7 +1000,7 @@ public void setId(int id){ * com.jme3.bounding.BoundingVolume, * com.jme3.collision.CollisionResults) }. */ - public void createCollisionData(){ + public void createCollisionData() { BIHTree tree = new BIHTree(this); tree.construct(); collisionTree = tree; @@ -999,18 +1012,24 @@ public void createCollisionData(){ * generated BIHTree. */ public void clearCollisionData() { - collisionTree = null; + collisionTree = DEFAULT_COLLISION_TREE; } /** * Handles collision detection, internal use only. * User code should only use collideWith() on scene * graph elements such as {@link Spatial}s. + * + * @param other the other Collidable + * @param worldMatrix the world matrix + * @param worldBound the world bound + * @param results storage for the results + * @return the number of collisions detected (≥0) */ public int collideWith(Collidable other, - Matrix4f worldMatrix, - BoundingVolume worldBound, - CollisionResults results){ + Matrix4f worldMatrix, + BoundingVolume worldBound, + CollisionResults results) { switch (mode) { case Points: @@ -1028,7 +1047,7 @@ public int collideWith(Collidable other, return 0; } - if (collisionTree == null){ + if (collisionTree == null) { createCollisionData(); } @@ -1042,7 +1061,7 @@ public int collideWith(Collidable other, * @param vb The buffer to set * @throws IllegalArgumentException If the buffer type is already set */ - public void setBuffer(VertexBuffer vb){ + public void setBuffer(VertexBuffer vb) { if (buffers.containsKey(vb.getBufferType().ordinal())) { throw new IllegalArgumentException("Buffer type already set: " + vb.getBufferType()); } @@ -1059,9 +1078,9 @@ public void setBuffer(VertexBuffer vb){ * * @param type The buffer type to remove */ - public void clearBuffer(VertexBuffer.Type type){ + public void clearBuffer(VertexBuffer.Type type) { VertexBuffer vb = buffers.remove(type.ordinal()); - if (vb != null){ + if (vb != null) { buffersList.remove(vb); updateCounts(); } @@ -1079,14 +1098,14 @@ public void clearBuffer(VertexBuffer.Type type){ * @throws UnsupportedOperationException If the buffer already set is * incompatible with the parameters given. */ - public void setBuffer(Type type, int components, Format format, Buffer buf){ + public void setBuffer(Type type, int components, Format format, Buffer buf) { VertexBuffer vb = buffers.get(type.ordinal()); - if (vb == null){ + if (vb == null) { vb = new VertexBuffer(type); vb.setupData(Usage.Dynamic, components, format, buf); setBuffer(vb); - }else{ - if (vb.getNumComponents() != components || vb.getFormat() != format){ + } else { + if (vb.getNumComponents() != components || vb.getFormat() != format) { throw new UnsupportedOperationException("The buffer already set " + "is incompatible with the given parameters"); } @@ -1110,7 +1129,7 @@ public void setBuffer(Type type, int components, FloatBuffer buf) { setBuffer(type, components, Format.Float, buf); } - public void setBuffer(Type type, int components, float[] buf){ + public void setBuffer(Type type, int components, float[] buf) { setBuffer(type, components, BufferUtils.createFloatBuffer(buf)); } @@ -1118,7 +1137,7 @@ public void setBuffer(Type type, int components, IntBuffer buf) { setBuffer(type, components, Format.UnsignedInt, buf); } - public void setBuffer(Type type, int components, int[] buf){ + public void setBuffer(Type type, int components, int[] buf) { setBuffer(type, components, BufferUtils.createIntBuffer(buf)); } @@ -1126,7 +1145,7 @@ public void setBuffer(Type type, int components, ShortBuffer buf) { setBuffer(type, components, Format.UnsignedShort, buf); } - public void setBuffer(Type type, int components, byte[] buf){ + public void setBuffer(Type type, int components, byte[] buf) { setBuffer(type, components, BufferUtils.createByteBuffer(buf)); } @@ -1134,7 +1153,7 @@ public void setBuffer(Type type, int components, ByteBuffer buf) { setBuffer(type, components, Format.UnsignedByte, buf); } - public void setBuffer(Type type, int components, short[] buf){ + public void setBuffer(Type type, int components, short[] buf) { setBuffer(type, components, BufferUtils.createShortBuffer(buf)); } @@ -1145,7 +1164,7 @@ public void setBuffer(Type type, int components, short[] buf){ * @param type The type of VertexBuffer * @return the VertexBuffer data, or null if not set */ - public VertexBuffer getBuffer(Type type){ + public VertexBuffer getBuffer(Type type) { return buffers.get(type.ordinal()); } @@ -1187,22 +1206,22 @@ public ShortBuffer getShortBuffer(Type type) { * * @return A virtual or wrapped index buffer to read the data as a list */ - public IndexBuffer getIndicesAsList(){ + public IndexBuffer getIndicesAsList() { if (mode == Mode.Hybrid) { throw new UnsupportedOperationException("Hybrid mode not supported"); } IndexBuffer ib = getIndexBuffer(); - if (ib != null){ - if (mode.isListMode()){ + if (ib != null) { + if (mode.isListMode()) { // already in list mode return ib; - }else{ + } else { // not in list mode but it does have an index buffer // wrap it so the data is converted to list format return new WrappedIndexBuffer(this); } - }else{ + } else { // return a virtual index buffer that will supply // "fake" indices in list format return new VirtualIndexBuffer(vertCount, mode); @@ -1289,9 +1308,9 @@ public void extractVertexData(Mesh other) { VertexBuffer newIdxBuf = new VertexBuffer(Type.Index); newIdxBuf.setupData(oldIdxBuf.getUsage(), - oldIdxBuf.getNumComponents(), - newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort, - newIndexBuf.getBuffer()); + oldIdxBuf.getNumComponents(), + newIndexBuf instanceof IndexIntBuffer ? Format.UnsignedInt : Format.UnsignedShort, + newIndexBuf.getBuffer()); clearBuffer(Type.Index); setBuffer(newIdxBuf); @@ -1308,11 +1327,13 @@ public void extractVertexData(Mesh other) { //check for data before copying, some buffers are just empty shells //for caching purpose (HW skinning buffers), and will be filled when //needed - if(oldVb.getData()!=null){ + if (oldVb.getData() != null) { // Create a new vertex buffer with similar configuration, but // with the capacity of number of unique vertices - Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), oldVb.getNumComponents(), newNumVerts); - newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), oldVb.getFormat(), buffer); + Buffer buffer = VertexBuffer.createBuffer(oldVb.getFormat(), + oldVb.getNumComponents(), newNumVerts); + newVb.setupData(oldVb.getUsage(), oldVb.getNumComponents(), + oldVb.getFormat(), buffer); // Copy the vertex data from the old buffer into the new buffer for (int i = 0; i < newNumVerts; i++) { @@ -1332,14 +1353,14 @@ public void extractVertexData(Mesh other) { // Copy max weights per vertex as well setMaxNumWeights(other.getMaxNumWeights()); - // The data has been copied over, update informations + // The data has been copied over, update information updateCounts(); updateBound(); } /** - * Scales the texture coordinate buffer on this mesh by the given - * scale factor. + * Scales the texture coordinate buffer on this mesh by the given scale + * factor. *

                * Note that values above 1 will cause the * texture to tile, while values below 1 will cause the texture @@ -1354,7 +1375,7 @@ public void extractVertexData(Mesh other) { * @throws UnsupportedOperationException If the texture coordinate * buffer is not in 2D float format. */ - public void scaleTextureCoordinates(Vector2f scaleFactor){ + public void scaleTextureCoordinates(Vector2f scaleFactor) { VertexBuffer tc = getBuffer(Type.TexCoord); if (tc == null) { throw new IllegalStateException("The mesh has no texture coordinates"); @@ -1370,10 +1391,10 @@ public void scaleTextureCoordinates(Vector2f scaleFactor){ FloatBuffer fb = (FloatBuffer) tc.getData(); fb.clear(); - for (int i = 0; i < fb.limit() / 2; i++){ + for (int i = 0; i < fb.limit() / 2; i++) { float x = fb.get(); float y = fb.get(); - fb.position(fb.position()-2); + fb.position(fb.position() - 2); x *= scaleFactor.getX(); y *= scaleFactor.getY(); fb.put(x).put(y); @@ -1387,10 +1408,10 @@ public void scaleTextureCoordinates(Vector2f scaleFactor){ * The method does nothing if the mesh has no {@link Type#Position} buffer. * It is expected that the position buffer is a float buffer with 3 components. */ - public void updateBound(){ + public void updateBound() { VertexBuffer posBuf = getBuffer(VertexBuffer.Type.Position); - if (meshBound != null && posBuf != null){ - meshBound.computeFromPoints((FloatBuffer)posBuf.getData()); + if (meshBound != null && posBuf != null) { + meshBound.computeFromPoints((FloatBuffer) posBuf.getData()); } } @@ -1423,7 +1444,7 @@ public void setBound(BoundingVolume modelBound) { * * @return map of vertex buffers on this mesh. */ - public IntMap getBuffers(){ + public IntMap getBuffers() { return buffers; } @@ -1436,7 +1457,7 @@ public IntMap getBuffers(){ * * @return list of vertex buffers on this mesh. */ - public SafeArrayList getBufferList(){ + public SafeArrayList getBufferList() { return buffersList; } @@ -1449,13 +1470,13 @@ public SafeArrayList getBufferList(){ * @return true if the mesh uses bone animation, false otherwise */ public boolean isAnimated() { - return getBuffer(Type.BoneIndex) != null || - getBuffer(Type.HWBoneIndex) != null; + return getBuffer(Type.BoneIndex) != null + || getBuffer(Type.HWBoneIndex) != null; } /** * @deprecated use isAnimatedByJoint - * @param boneIndex + * @param boneIndex the bone's index in its skeleton * @return true if animated by that bone, otherwise false */ @Deprecated @@ -1504,7 +1525,8 @@ public boolean isAnimatedByJoint(int jointIndex) { /** * Sets the count of vertices used for each tessellation patch - * @param patchVertexCount + * + * @param patchVertexCount the desired count */ public void setPatchVertexCount(int patchVertexCount) { this.patchVertexCount = patchVertexCount; @@ -1512,13 +1534,13 @@ public void setPatchVertexCount(int patchVertexCount) { /** * Gets the amount of vertices used for each patch; + * * @return the count (≥0) */ public int getPatchVertexCount() { return patchVertexCount; } - public void addMorphTarget(MorphTarget target) { if (morphTargets == null) { morphTargets = new SafeArrayList<>(MorphTarget.class); @@ -1526,6 +1548,41 @@ public void addMorphTarget(MorphTarget target) { morphTargets.add(target); } + /** + * Remove the given MorphTarget from the Mesh + * @param target The MorphTarget to remove + * @return If the MorphTarget was removed + */ + public boolean removeMorphTarget(MorphTarget target) { + return morphTargets != null ? morphTargets.remove(target) : false; + } + + /** + * Remove the MorphTarget from the Mesh at the given index + * @throws IndexOutOfBoundsException if the index outside the number of morph targets + * @param index Index of the MorphTarget to remove + * @return The MorphTarget that was removed + */ + public MorphTarget removeMorphTarget(int index) { + if (morphTargets == null) { + throw new IndexOutOfBoundsException("Index:" + index + ", Size:0"); + } + return morphTargets.remove(index); + } + + /** + * Get the MorphTarget at the given index + * @throws IndexOutOfBoundsException if the index outside the number of morph targets + * @param index The index of the morph target to get + * @return The MorphTarget at the index + */ + public MorphTarget getMorphTarget(int index) { + if (morphTargets == null) { + throw new IndexOutOfBoundsException("Index:" + index + ", Size:0"); + } + return morphTargets.get(index); + } + public MorphTarget[] getMorphTargets() { if (morphTargets == null) { return new MorphTarget[0]; @@ -1533,14 +1590,13 @@ public MorphTarget[] getMorphTargets() { return morphTargets.getArray(); } } - + /** * Get the name of all morphs in order. * Morphs without names will be null * @return an array */ public String[] getMorphTargetNames() { - MorphTarget[] nbMorphTargets = getMorphTargets(); if (nbMorphTargets.length == 0) { return new String[0]; @@ -1556,11 +1612,12 @@ public String[] getMorphTargetNames() { public boolean hasMorphTargets() { return morphTargets != null && !morphTargets.isEmpty(); } - + /** * Get the index of the morph that has the given name. + * * @param morphName The name of the morph to search for - * @return The index of the morph, or -1 if not found. + * @return The index of the morph, or -1 if not found. */ public int getMorphIndex(String morphName) { int index = -1; @@ -1575,19 +1632,20 @@ public int getMorphIndex(String morphName) { } @Override + @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { OutputCapsule out = ex.getCapsule(this); out.write(meshBound, "modelBound", null); - out.write(vertCount, "vertCount", -1); - out.write(elementCount, "elementCount", -1); - out.write(instanceCount, "instanceCount", -1); - out.write(maxNumWeights, "max_num_weights", -1); + out.write(vertCount, "vertCount", DEFAULT_VERT_COUNT); + out.write(elementCount, "elementCount", DEFAULT_ELEMENT_COUNT); + out.write(instanceCount, "instanceCount", DEFAULT_INSTANCE_COUNT); + out.write(maxNumWeights, "max_num_weights", DEFAULT_MAX_NUM_WEIGHTS); out.write(mode, "mode", Mode.Triangles); - out.write(collisionTree, "collisionTree", null); + out.write(collisionTree, "collisionTree", DEFAULT_COLLISION_TREE); out.write(elementLengths, "elementLengths", null); out.write(modeStart, "modeStart", null); - out.write(pointSize, "pointSize", 1f); + out.write(pointSize, "pointSize", DEFAULT_POINT_SIZE); //Removing HW skinning buffers to not save them VertexBuffer hwBoneIndex = null; @@ -1618,29 +1676,30 @@ public void write(JmeExporter ex) throws IOException { } @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule in = im.getCapsule(this); meshBound = (BoundingVolume) in.readSavable("modelBound", null); - vertCount = in.readInt("vertCount", -1); - elementCount = in.readInt("elementCount", -1); - instanceCount = in.readInt("instanceCount", -1); - maxNumWeights = in.readInt("max_num_weights", -1); + vertCount = in.readInt("vertCount", DEFAULT_VERT_COUNT); + elementCount = in.readInt("elementCount", DEFAULT_ELEMENT_COUNT); + instanceCount = in.readInt("instanceCount", DEFAULT_INSTANCE_COUNT); + maxNumWeights = in.readInt("max_num_weights", DEFAULT_MAX_NUM_WEIGHTS); mode = in.readEnum("mode", Mode.class, Mode.Triangles); elementLengths = in.readIntArray("elementLengths", null); modeStart = in.readIntArray("modeStart", null); - collisionTree = (BIHTree) in.readSavable("collisionTree", null); + collisionTree = (BIHTree) in.readSavable("collisionTree", DEFAULT_COLLISION_TREE); elementLengths = in.readIntArray("elementLengths", null); modeStart = in.readIntArray("modeStart", null); - pointSize = in.readFloat("pointSize", 1f); + pointSize = in.readFloat("pointSize", DEFAULT_POINT_SIZE); // in.readStringSavableMap("buffers", null); buffers = (IntMap) in.readIntSavableMap("buffers", null); - for (Entry entry : buffers){ + for (Entry entry : buffers) { buffersList.add(entry.getValue()); } //creating hw animation buffers empty so that they are put in the cache - if(isAnimated()){ + if (isAnimated()) { VertexBuffer hwBoneIndex = new VertexBuffer(Type.HWBoneIndex); hwBoneIndex.setUsage(Usage.CpuOnly); setBuffer(hwBoneIndex); @@ -1652,7 +1711,7 @@ public void read(JmeImporter im) throws IOException { Savable[] lodLevelsSavable = in.readSavableArray("lodLevels", null); if (lodLevelsSavable != null) { lodLevels = new VertexBuffer[lodLevelsSavable.length]; - System.arraycopy( lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); + System.arraycopy(lodLevelsSavable, 0, lodLevels, 0, lodLevels.length); } ArrayList l = in.readSavableArrayList("morphTargets", null); @@ -1660,5 +1719,4 @@ public void read(JmeImporter im) throws IOException { morphTargets = new SafeArrayList(MorphTarget.class, l); } } - } diff --git a/jme3-core/src/main/java/com/jme3/scene/Node.java b/jme3-core/src/main/java/com/jme3/scene/Node.java index 6fa7883921..46bb0829e8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/Node.java +++ b/jme3-core/src/main/java/com/jme3/scene/Node.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,7 @@ import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; import com.jme3.material.Material; +import com.jme3.scene.threadwarden.SceneGraphThreadWarden; import com.jme3.util.SafeArrayList; import com.jme3.util.clone.Cloner; import java.io.IOException; @@ -47,7 +48,6 @@ import java.util.logging.Level; import java.util.logging.Logger; - /** * Node defines an internal node of a scene graph. The internal * node maintains a collection of children and handles merging said children @@ -59,14 +59,11 @@ * @author Joshua Slack */ public class Node extends Spatial { - private static final Logger logger = Logger.getLogger(Node.class.getName()); - /** * This node's children. */ - protected SafeArrayList children = new SafeArrayList(Spatial.class); - + protected SafeArrayList children = new SafeArrayList<>(Spatial.class); /** * If this node is a root, this list will contain the current * set of children (and children of children) that require @@ -105,7 +102,6 @@ public Node(String name) { } /** - * * getQuantity returns the number of children this node * maintains. * @@ -116,22 +112,24 @@ public int getQuantity() { } @Override - protected void setTransformRefresh(){ + protected void setTransformRefresh() { super.setTransformRefresh(); - for (Spatial child : children.getArray()){ - if ((child.refreshFlags & RF_TRANSFORM) != 0) + for (Spatial child : children.getArray()) { + if ((child.refreshFlags & RF_TRANSFORM) != 0) { continue; + } child.setTransformRefresh(); } } @Override - protected void setLightListRefresh(){ + protected void setLightListRefresh() { super.setLightListRefresh(); - for (Spatial child : children.getArray()){ - if ((child.refreshFlags & RF_LIGHTLIST) != 0) + for (Spatial child : children.getArray()) { + if ((child.refreshFlags & RF_LIGHTLIST) != 0) { continue; + } child.setLightListRefresh(); } @@ -150,7 +148,7 @@ protected void setMatParamOverrideRefresh() { } @Override - protected void updateWorldBound(){ + protected void updateWorldBound() { super.updateWorldBound(); // for a node, the world bound is a combination of all its children // bounds @@ -176,7 +174,7 @@ protected void updateWorldBound(){ @Override protected void setParent(Node parent) { - if( this.parent == null && parent != null ) { + if (this.parent == null && parent != null) { // We were a root before and now we aren't... make sure if // we had an updateList then we clear it completely to // avoid holding the dead array. @@ -186,13 +184,13 @@ protected void setParent(Node parent) { super.setParent(parent); } - private void addUpdateChildren( SafeArrayList results ) { - for( Spatial child : children.getArray() ) { - if( child.requiresUpdates() ) { + private void addUpdateChildren(SafeArrayList results) { + for (Spatial child : children.getArray()) { + if (child.requiresUpdates()) { results.add(child); } - if( child instanceof Node ) { - ((Node)child).addUpdateChildren(results); + if (child instanceof Node) { + ((Node) child).addUpdateChildren(results); } } } @@ -204,18 +202,19 @@ private void addUpdateChildren( SafeArrayList results ) { * that would change state. */ void invalidateUpdateList() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); updateListValid = false; - if ( parent != null ) { + if (parent != null) { parent.invalidateUpdateList(); } } private SafeArrayList getUpdateList() { - if( updateListValid ) { + if (updateListValid) { return updateList; } - if( updateList == null ) { - updateList = new SafeArrayList(Spatial.class); + if (updateList == null) { + updateList = new SafeArrayList<>(Spatial.class); } else { updateList.clear(); } @@ -227,32 +226,32 @@ private SafeArrayList getUpdateList() { } @Override - public void updateLogicalState(float tpf){ + public void updateLogicalState(float tpf) { super.updateLogicalState(tpf); // Only perform updates on children if we are the - // root and then only peform updates on children we + // root and then only perform updates on children we // know to require updates. // So if this isn't the root, abort. - if( parent != null ) { + if (parent != null) { return; } - for( Spatial s : getUpdateList().getArray() ) { + for (Spatial s : getUpdateList().getArray()) { s.updateLogicalState(tpf); } } @Override - public void updateGeometricState(){ + public void updateGeometricState() { if (refreshFlags == 0) { // This branch has no geometric state that requires updates. return; } - if ((refreshFlags & RF_LIGHTLIST) != 0){ + if ((refreshFlags & RF_LIGHTLIST) != 0) { updateWorldLightList(); } - if ((refreshFlags & RF_TRANSFORM) != 0){ + if ((refreshFlags & RF_TRANSFORM) != 0) { // combine with parent transforms- same for all spatial // subclasses. updateWorldTransforms(); @@ -273,7 +272,7 @@ public void updateGeometricState(){ } } - if ((refreshFlags & RF_BOUND) != 0){ + if ((refreshFlags & RF_BOUND) != 0) { updateWorldBound(); } @@ -289,14 +288,15 @@ public void updateGeometricState(){ @Override public int getTriangleCount() { int count = 0; - if(children != null) { - for(int i = 0; i < children.size(); i++) { + if (children != null) { + for (int i = 0; i < children.size(); i++) { count += children.get(i).getTriangleCount(); } } return count; } + /** * getVertexCount returns the number of vertices contained * in all sub-branches of this node that contain geometry. @@ -306,9 +306,9 @@ public int getTriangleCount() { @Override public int getVertexCount() { int count = 0; - if(children != null) { - for(int i = 0; i < children.size(); i++) { - count += children.get(i).getVertexCount(); + if (children != null) { + for (int i = 0; i < children.size(); i++) { + count += children.get(i).getVertexCount(); } } @@ -330,8 +330,8 @@ public int getVertexCount() { public int attachChild(Spatial child) { return attachChildAt(child, children.size()); } + /** - * * attachChildAt attaches a child to this node at an index. This node * becomes the child's parent. The current number of children maintained is * returned. @@ -340,16 +340,19 @@ public int attachChild(Spatial child) { * * @param child * the child to attach to this node. + * @param index + * the position where the child should be attached * @return the number of children maintained by this node. - * @throws NullPointerException if child is null. + * @throws IllegalArgumentException if child is null or this */ public int attachChildAt(Spatial child, int index) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (child == null) { throw new IllegalArgumentException("child cannot be null"); } if (child == this) { throw new IllegalArgumentException("Cannot add child to itself"); - } + } if (child.getParent() != this) { if (child.getParent() != null) { child.getParent().detachChild(child); @@ -363,7 +366,7 @@ public int attachChildAt(Spatial child, int index) { child.setLightListRefresh(); child.setMatParamOverrideRefresh(); if (logger.isLoggable(Level.FINE)) { - logger.log(Level.FINE,"Child ({0}) attached to this node ({1})", + logger.log(Level.FINE, "Child ({0}) attached to this node ({1})", new Object[]{child.getName(), getName()}); } invalidateUpdateList(); @@ -376,12 +379,13 @@ public int attachChildAt(Spatial child, int index) { * This child will no longer be maintained. * * @param child - * the child to remove. + * the child to remove (not null) * @return the index the child was at. -1 if the child was not in the list. */ public int detachChild(Spatial child) { - if (child == null) - throw new NullPointerException(); + if (child == null) { + throw new IllegalArgumentException("child cannot be null"); + } if (child.getParent() == this) { int index = children.indexOf(child); @@ -396,21 +400,22 @@ public int detachChild(Spatial child) { /** * detachChild removes a given child from the node's list. - * This child will no longe be maintained. Only the first child with a + * This child will no longer be maintained. Only the first child with a * matching name is removed. * * @param childName - * the child to remove. + * the child to remove (not null) * @return the index the child was at. -1 if the child was not in the list. */ public int detachChildNamed(String childName) { - if (childName == null) - throw new NullPointerException(); + if (childName == null) { + throw new IllegalArgumentException("childName cannot be null"); + } for (int x = 0, max = children.size(); x < max; x++) { - Spatial child = children.get(x); + Spatial child = children.get(x); if (childName.equals(child.getName())) { - detachChildAt( x ); + detachChildAt(x); return x; } } @@ -418,7 +423,6 @@ public int detachChildNamed(String childName) { } /** - * * detachChildAt removes a child at a given index. That child * is returned for saving purposes. * @@ -427,10 +431,11 @@ public int detachChildNamed(String childName) { * @return the child at the supplied index. */ public Spatial detachChildAt(int index) { - Spatial child = children.remove(index); - if ( child != null ) { - child.setParent( null ); - logger.log(Level.FINE, "{0}: Child removed.", this.toString()); + assert SceneGraphThreadWarden.assertOnCorrectThread(this); + Spatial child = children.remove(index); + if (child != null) { + child.setParent(null); + logger.log(Level.FINE, "{0}: Child removed.", this); // since a child with a bound was detached; // our own bound will probably change. @@ -443,25 +448,25 @@ public Spatial detachChildAt(int index) { // lights are also inherited from parent child.setLightListRefresh(); child.setMatParamOverrideRefresh(); - + invalidateUpdateList(); } return child; } /** - * * detachAllChildren removes all children attached to this * node. */ public void detachAllChildren() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); // Note: this could be a bit more efficient if it delegated // to a private method that avoided setBoundRefresh(), etc. // for every child and instead did one in here at the end. - for ( int i = children.size() - 1; i >= 0; i-- ) { + for (int i = children.size() - 1; i >= 0; i--) { detachChildAt(i); } - logger.log(Level.FINE, "{0}: All children removed.", this.toString()); + logger.log(Level.FINE, "{0}: All children removed.", this); } /** @@ -469,8 +474,7 @@ public void detachAllChildren() { * in this node's list of children. * @param sp * The spatial to look up - * @return - * The index of the spatial in the node's children, or -1 + * @return The index of the spatial in the node's children, or -1 * if the spatial is not attached to this node */ public int getChildIndex(Spatial sp) { @@ -478,25 +482,24 @@ public int getChildIndex(Spatial sp) { } /** - * More efficient than e.g detaching and attaching as no updates are needed. + * More efficient than e.g. detaching and attaching, as no updates are needed. * * @param index1 The index of the first child to swap * @param index2 The index of the second child to swap */ public void swapChildren(int index1, int index2) { - Spatial c2 = children.get(index2); - Spatial c1 = children.remove(index1); + assert SceneGraphThreadWarden.assertOnCorrectThread(this); + Spatial c2 = children.get(index2); + Spatial c1 = children.remove(index1); children.add(index1, c2); children.remove(index2); children.add(index2, c1); } /** - * * getChild returns a child at a given index. * - * @param i - * the index to retrieve the child from. + * @param i the index to retrieve the child from. * @return the child at a specified index. */ public Spatial getChild(int i) { @@ -505,7 +508,7 @@ public Spatial getChild(int i) { /** * getChild returns the first child found with exactly the - * given name (case sensitive.) This method does a depth first recursive + * given name (case-sensitive). This method does a depth-first recursive * search of all descendants of this node, it will return the first spatial * found with a matching name. * @@ -514,21 +517,23 @@ public Spatial getChild(int i) { * @return the child if found, or null. */ public Spatial getChild(String name) { - if (name == null) + if (name == null) { return null; + } for (Spatial child : children.getArray()) { if (name.equals(child.getName())) { return child; - } else if(child instanceof Node) { - Spatial out = ((Node)child).getChild(name); - if(out != null) { + } else if (child instanceof Node) { + Spatial out = ((Node) child).getChild(name); + if (out != null) { return out; } } } return null; } + /** * determines if the provided Spatial is contained in the children list of * this node. @@ -538,12 +543,14 @@ public Spatial getChild(String name) { * @return true if the object is contained, false otherwise. */ public boolean hasChild(Spatial spat) { - if (children.contains(spat)) + if (children.contains(spat)) { return true; + } for (Spatial child : children.getArray()) { - if (child instanceof Node && ((Node) child).hasChild(spat)) + if (child instanceof Node && ((Node) child).hasChild(spat)) { return true; + } } return false; @@ -560,27 +567,31 @@ public List getChildren() { } @Override - public void setMaterial(Material mat){ - for (int i = 0; i < children.size(); i++){ + public void setMaterial(Material mat) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); + for (int i = 0; i < children.size(); i++) { children.get(i).setMaterial(mat); } } @Override - public void setLodLevel(int lod){ + public void setLodLevel(int lod) { super.setLodLevel(lod); for (Spatial child : children.getArray()) { child.setLodLevel(lod); } } - public int collideWith(Collidable other, CollisionResults results){ + @Override + public int collideWith(Collidable other, CollisionResults results) { int total = 0; // optimization: try collideWith BoundingVolume to avoid possibly redundant tests on children - // number 4 in condition is somewhat arbitrary. When there is only one child, the boundingVolume test is redundant at all. - // The idea is when there are few children, it can be too expensive to test boundingVolume first. + // number 4 in condition is somewhat arbitrary. + // When there is only one child, the boundingVolume test is redundant at all. + // The idea is when there are few children, + // it can be too expensive to test boundingVolume first. /* - I'm removing this change until some issues can be addressed and I really + I'm removing this change until some issues can be addressed, and I really think it needs to be implemented a better way anyway. First, it causes issues for anyone doing collideWith() with BoundingVolumes and expecting it to trickle down to the children. For example, children @@ -596,10 +607,10 @@ with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing all of that calculation. For example, if 'other' is also a BoundingVolume (ie: 99.9% of all non-Ray cases) then a direct BV to BV intersects() test can be done. So much faster. And if 'other' _is_ a Ray then the BV.intersects(Ray) call can be done. - I don't have time to do it right now but I'll at least un-break a bunch of peoples' + I don't have time to do it right now, but I'll at least un-break a bunch of people's code until it can be 'optimized' properly. Hopefully it's not too late to back out the other dodgy ripples this caused. -pspeed (hindsight-expert ;)) - Note: the code itself is relatively simple to implement but I don't have time to + Note: the code itself is relatively simple to implement, but I don't have time to a) test it, and b) see if '> 4' is still a decent check for it. Could be it's fast enough to do all the time for > 1. if (children.size() > 4) @@ -611,7 +622,7 @@ with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing if (bv.collideWith(other) == 0) return 0; } */ - for (Spatial child : children.getArray()){ + for (Spatial child : children.getArray()) { total += child.collideWith(other, results); } return total; @@ -621,43 +632,49 @@ with BoundingSphere bounding volumes and collideWith(BoundingSphere). Doing /** * Returns flat list of Spatials implementing the specified class AND * with name matching the specified pattern. - *

                + *

                * Note that we are matching the pattern, therefore the pattern * must match the entire pattern (i.e. it behaves as if it is sandwiched * between "^" and "$"). * You can set regex modes, like case insensitivity, by using the (?X) * or (?X:Y) constructs. *

                - * By design, it is always safe to code loops like:

                +     * By design, it is always safe to code loops like:
                      *     for (Spatial spatial : node.descendantMatches(AClass.class, "regex"))
                -     * 
                - *

                + *

                + *

                * "Descendants" does not include self, per the definition of the word. * To test for descendants AND self, you must do a * node.matches(aClass, aRegex) + * node.descendantMatches(aClass, aRegex). *

                * + * @param the type of Spatial returned * @param spatialSubclass Subclass which matching Spatials must implement. * Null causes all Spatials to qualify. * @param nameRegex Regular expression to match Spatial name against. * Null causes all Names to qualify. - * @return Non-null, but possibly 0-element, list of matching Spatials (also Instances extending Spatials). + * @return Non-null, but possibly 0-element, list of matching Spatials + * (also Instances extending Spatials). * * @see java.util.regex.Pattern * @see Spatial#matches(java.lang.Class, java.lang.String) */ @SuppressWarnings("unchecked") - public List descendantMatches( + public List descendantMatches( Class spatialSubclass, String nameRegex) { - List newList = new ArrayList(); - if (getQuantity() < 1) return newList; + List newList = new ArrayList<>(); + if (getQuantity() < 1) { + return newList; + } for (Spatial child : getChildren()) { - if (child.matches(spatialSubclass, nameRegex)) - newList.add((T)child); - if (child instanceof Node) + if (child.matches(spatialSubclass, nameRegex)) { + newList.add((T) child); + } + if (child instanceof Node) { newList.addAll(((Node) child).descendantMatches( spatialSubclass, nameRegex)); + } } return newList; } @@ -665,9 +682,13 @@ public List descendantMatches( /** * Convenience wrapper. * + * @param the type of Spatial returned + * @param spatialSubclass the type of Spatial returned, or null for all + * spatials + * @return a new list of pre-existing spatials (may be empty) * @see #descendantMatches(java.lang.Class, java.lang.String) */ - public List descendantMatches( + public List descendantMatches( Class spatialSubclass) { return descendantMatches(spatialSubclass, null); } @@ -675,14 +696,17 @@ public List descendantMatches( /** * Convenience wrapper. * + * @param the type of Spatial returned + * @param nameRegex regular expression to match Spatial names against, or null for all spatials + * @return a new list of pre-existing spatials (may be empty) * @see #descendantMatches(java.lang.Class, java.lang.String) */ - public List descendantMatches(String nameRegex) { + public List descendantMatches(String nameRegex) { return descendantMatches(null, nameRegex); } @Override - public Node clone(boolean cloneMaterials){ + public Node clone(boolean cloneMaterials) { Node nodeClone = (Node) super.clone(cloneMaterials); // nodeClone.children = new ArrayList(); // for (Spatial child : children){ @@ -699,7 +723,7 @@ public Node clone(boolean cloneMaterials){ @Override public Spatial deepClone() { - Node nodeClone = (Node)super.deepClone(); + Node nodeClone = (Node) super.deepClone(); // Reset the fields of the clone that should be in a 'new' state. nodeClone.updateList = null; @@ -708,10 +732,10 @@ public Spatial deepClone() { return nodeClone; } - public Spatial oldDeepClone(){ + public Spatial oldDeepClone() { Node nodeClone = (Node) super.clone(); - nodeClone.children = new SafeArrayList(Spatial.class); - for (Spatial child : children){ + nodeClone.children = new SafeArrayList<>(Spatial.class); + for (Spatial child : children) { Spatial childClone = child.deepClone(); childClone.parent = nodeClone; nodeClone.children.add(childClone); @@ -723,7 +747,7 @@ public Spatial oldDeepClone(){ * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.children = cloner.clone(children); @@ -733,19 +757,22 @@ public void cloneFields( Cloner cloner, Object original ) { // cloning this list is fine. this.updateList = cloner.clone(updateList); } + @Override + @SuppressWarnings("unchecked") public void write(JmeExporter e) throws IOException { super.write(e); e.getCapsule(this).writeSavableArrayList(new ArrayList(children), "children", null); } @Override - public void read(JmeImporter e) throws IOException { + @SuppressWarnings("unchecked") + public void read(JmeImporter importer) throws IOException { // XXX: Load children before loading itself!! // This prevents empty children list if controls query // it in Control.setSpatial(). - children = new SafeArrayList( Spatial.class, - e.getCapsule(this).readSavableArrayList("children", null) ); + children = new SafeArrayList(Spatial.class, + importer.getCapsule(this).readSavableArrayList("children", null)); // go through children and set parent to this node if (children != null) { @@ -753,12 +780,13 @@ public void read(JmeImporter e) throws IOException { child.parent = this; } } - super.read(e); + super.read(importer); } @Override public void setModelBound(BoundingVolume modelBound) { - if(children != null) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); + if (children != null) { for (Spatial child : children.getArray()) { child.setModelBound(modelBound != null ? modelBound.clone(null) : null); } @@ -767,7 +795,8 @@ public void setModelBound(BoundingVolume modelBound) { @Override public void updateModelBound() { - if(children != null) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); + if (children != null) { for (Spatial child : children.getArray()) { child.updateModelBound(); } diff --git a/jme3-core/src/main/java/com/jme3/scene/SceneGraphIterator.java b/jme3-core/src/main/java/com/jme3/scene/SceneGraphIterator.java new file mode 100644 index 0000000000..6ad426f28b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/SceneGraphIterator.java @@ -0,0 +1,159 @@ +/* + * 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 java.util.Iterator; +import java.util.LinkedList; + +/** + * Iterates over the scene graph with the depth-first traversal method. + *

                + * This method of scene traversal allows for more control of the iteration + * process than {@link Spatial#depthFirstTraversal(com.jme3.scene.SceneGraphVisitor)} + * because it implements {@link Iterator} (enabling it to be used in for-loops). + * + * @author codex + */ +public class SceneGraphIterator implements Iterable, Iterator { + + private Spatial current; + private Spatial main; + private int depth = 0; + private final LinkedList path = new LinkedList<>(); + + /** + * Instantiates a new {@code SceneGraphIterator} instance that + * starts iterating at the given main spatial. + * + * @param main the main spatial to start iteration from + */ + public SceneGraphIterator(Spatial main) { + if (main instanceof Node) { + path.add(new PathNode((Node)main)); + depth++; + } + this.main = main; + } + + @Override + public Iterator iterator() { + return this; + } + + @Override + public boolean hasNext() { + if (main != null) { + return true; + } + trim(); + return !path.isEmpty(); + } + + @Override + public Spatial next() { + if (main != null) { + current = main; + main = null; + } else { + current = path.getLast().iterator.next(); + if (current instanceof Node) { + Node n = (Node)current; + if (!n.getChildren().isEmpty()) { + path.addLast(new PathNode(n)); + depth++; + } + } + } + return current; + } + + /** + * Gets the spatial the iterator is currently on. + * + * @return current spatial + */ + public Spatial current() { + return current; + } + + /** + * Makes this iterator ignore all children of the current spatial. + * The children of the current spatial will not be iterated through. + */ + public void ignoreChildren() { + if (current instanceof Node) { + path.removeLast(); + depth--; + } + } + + /** + * Gets the current depth of the iterator. + *

                + * The depth is how far away from the main spatial the + * current spatial is. So, the main spatial's depth is 0, + * all its children's depths is 1, and all their + * children's depths is 2, etc. + * + * @return current depth, or distance from the main spatial. + */ + public int getDepth() { + // The depth field is not an accurate indicator of depth. + // Whenever the current spatial is an iterable node, the depth + // value is exactly 1 greater than it should be. + return !path.isEmpty() && current == path.getLast().node ? depth-1 : depth; + } + + /** + * Trims the path to the first unexhausted node. + */ + private void trim() { + if (!path.isEmpty() && !path.getLast().iterator.hasNext()) { + path.removeLast(); + depth--; + trim(); + } + } + + private static class PathNode { + + Node node; + Iterator iterator; + + PathNode(Node node) { + this.node = node; + iterator = this.node.getChildren().iterator(); + } + + } + +} diff --git a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java index 954161067c..426c636079 100644 --- a/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/SimpleBatchNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,9 @@ * SimpleBatchNode comes with some restrictions, but can yield better performances. * Geometries to be batched has to be attached directly to the BatchNode * You can't attach a Node to a SimpleBatchNode - * SimpleBatchNode is recommended when you have a large number of geometries using the same material that does not require a complex scene graph structure. + * SimpleBatchNode is recommended when you have a large number of geometries using the + * same material that does not require a complex scene graph structure. + * * @see BatchNode * @author Nehon */ @@ -55,9 +57,9 @@ public SimpleBatchNode(String name) { @Override public int attachChild(Spatial child) { - if (!(child instanceof Geometry)) { - throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child of type Geometry, use BatchMode.Complex to use a complex structure"); + throw new UnsupportedOperationException("BatchNode is BatchMode.Simple only support child " + + "of type Geometry, use BatchMode.Complex to use a complex structure"); } return super.attachChild(child); @@ -71,7 +73,8 @@ protected void setTransformRefresh() { batch.geometry.setTransformRefresh(); } } - private Matrix4f cachedLocalMat = new Matrix4f(); + + private final Matrix4f cachedLocalMat = new Matrix4f(); @Override protected Matrix4f getTransformMatrix(Geometry g){ @@ -89,7 +92,6 @@ protected Matrix4f getTransformMatrix(Geometry g){ return cachedLocalMat; } - @Override public void batch() { doBatch(); diff --git a/jme3-core/src/main/java/com/jme3/scene/Spatial.java b/jme3-core/src/main/java/com/jme3/scene/Spatial.java index 06bc63f7f6..f94ed817d2 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,7 @@ import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.control.Control; +import com.jme3.scene.threadwarden.SceneGraphThreadWarden; import com.jme3.util.SafeArrayList; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; @@ -68,8 +69,8 @@ * @author Joshua Slack * @version $Revision: 4075 $, $Data$ */ -public abstract class Spatial implements Savable, Cloneable, Collidable, CloneableSmartAsset, JmeCloneable, HasLocalTransform { - +public abstract class Spatial implements Savable, Cloneable, Collidable, + CloneableSmartAsset, JmeCloneable, HasLocalTransform { private static final Logger logger = Logger.getLogger(Spatial.class.getName()); /** @@ -77,15 +78,14 @@ public abstract class Spatial implements Savable, Cloneable, Collidable, Cloneab * this spatial. */ public enum CullHint { - /** * Do whatever our parent does. If no parent, default to {@link #Dynamic}. */ Inherit, /** * Do not draw if we are not at least partially within the view frustum - * of the camera. This is determined via the defined - * Camera planes whether or not this Spatial should be culled. + * of the camera. The defined + * Camera planes determine whether this Spatial should be culled. */ Dynamic, /** @@ -104,7 +104,6 @@ public enum CullHint { * Specifies if this spatial should be batched */ public enum BatchHint { - /** * Do whatever our parent does. If no parent, default to {@link #Always}. */ @@ -121,12 +120,13 @@ public enum BatchHint { /** * Refresh flag types */ - protected static final int RF_TRANSFORM = 0x01, // need light resort + combine transforms - RF_BOUND = 0x02, - RF_LIGHTLIST = 0x04, // changes in light lists - RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update - RF_MATPARAM_OVERRIDE = 0x10; - + protected static final int + RF_TRANSFORM = 0x01, // need light resort + combine transforms + RF_BOUND = 0x02, + RF_LIGHTLIST = 0x04, // changes in light lists + RF_CHILD_LIGHTLIST = 0x08, // some child need geometry update + RF_MATPARAM_OVERRIDE = 0x10; + protected CullHint cullHint = CullHint.Inherit; protected BatchHint batchHint = BatchHint.Inherit; /** @@ -147,13 +147,14 @@ public enum BatchHint { */ protected String name; // scale values - protected transient Camera.FrustumIntersect frustrumIntersects = Camera.FrustumIntersect.Intersects; + protected transient Camera.FrustumIntersect frustrumIntersects + = Camera.FrustumIntersect.Intersects; protected RenderQueue.Bucket queueBucket = RenderQueue.Bucket.Inherit; protected ShadowMode shadowMode = RenderQueue.ShadowMode.Inherit; public transient float queueDistance = Float.NEGATIVE_INFINITY; protected Transform localTransform; protected Transform worldTransform; - protected SafeArrayList controls = new SafeArrayList(Control.class); + protected SafeArrayList controls = new SafeArrayList<>(Control.class); protected HashMap userData = null; /** * Used for smart asset caching @@ -193,8 +194,8 @@ protected Spatial() { } /** - * Constructor instantiates a new Spatial object setting the - * rotation, translation and scale value to defaults. + * Constructor instantiates a new Spatial object, setting the + * rotation, translation, and scale values to their defaults. * * @param name * the name of the scene element. This is required for @@ -213,10 +214,12 @@ protected Spatial(String name) { refreshFlags |= RF_BOUND; } + @Override public void setKey(AssetKey key) { this.key = key; } + @Override public AssetKey getKey() { return key; } @@ -228,8 +231,9 @@ public AssetKey getKey() { * avoid exposing it to the public API since it is only used by Node. */ boolean requiresUpdates() { - return requiresUpdates | !controls.isEmpty(); + return requiresUpdates || !controls.isEmpty(); } + /** * Subclasses can call this with true to denote that they require * updateLogicalState() to be called even if they contain no controls. @@ -244,8 +248,10 @@ boolean requiresUpdates() { * call setRequiresUpdate(false) in their constructors to receive * optimal behavior if they don't require updateLogicalState() to be * called even if there are no controls. + * + * @param f true→require updates, false→don't require updates */ - protected void setRequiresUpdates( boolean f ) { + protected void setRequiresUpdates(boolean f) { // Note to explorers, the reason this was done as a protected setter // instead of passed on construction is because it frees all subclasses // from having to make sure to always pass the value up in case they @@ -254,15 +260,15 @@ protected void setRequiresUpdates( boolean f ) { // override (which would be more correct) is because the flag provides // some flexibility in how we break subclasses. A protected method // would require that all subclasses that required updates need implement - // this method or they would silently stop processing updates. A flag + // this method, or they would silently stop processing updates. A flag // lets us set a default when a subclass is detected that is different - // than the internal "more efficient" default. + // from the internal "more efficient" default. // Spatial's default is 'true' for this flag requiring subclasses to // override it for more optimal behavior. Node and Geometry will override // it to false if the class is Node.class or Geometry.class. // This means that all subclasses will default to the old behavior // unless they opt in. - if( parent != null ) { + if (parent != null) { throw new IllegalStateException("setRequiresUpdates() cannot be called once attached."); } this.requiresUpdates = f; @@ -273,11 +279,13 @@ protected void setRequiresUpdates( boolean f ) { * a refresh is required. */ protected void setTransformRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_TRANSFORM; setBoundRefresh(); } protected void setLightListRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_LIGHTLIST; // Make sure next updateGeometricState() visits this branch // to update lights. @@ -294,6 +302,7 @@ protected void setLightListRefresh() { } protected void setMatParamOverrideRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_MATPARAM_OVERRIDE; Spatial p = parent; while (p != null) { @@ -311,6 +320,7 @@ protected void setMatParamOverrideRefresh() { * a refresh is required. */ protected void setBoundRefresh() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); refreshFlags |= RF_BOUND; Spatial p = parent; @@ -323,6 +333,7 @@ protected void setBoundRefresh() { p = p.parent; } } + /** * (Internal use only) Forces a refresh of the given types of data. * @@ -358,11 +369,12 @@ public boolean checkCulling(Camera cam) { throw new IllegalStateException("Scene graph is not properly updated for rendering.\n" + "State was changed after rootNode.updateGeometricState() call. \n" + "Make sure you do not modify the scene from another thread!\n" - + "Problem spatial name: " + getName()); + + "Problem spatial name: " + getName() + "\n" + + SceneGraphThreadWarden.getTurnOnAssertsPrompt()); } 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; @@ -554,9 +566,9 @@ public void lookAt(Vector3f position, Vector3f upVector) { Vector3f compVecA = vars.vect4; compVecA.set(position).subtractLocal(worldTranslation); getLocalRotation().lookAt(compVecA, upVector); - if ( getParent() != null ) { - Quaternion rot=vars.quat1; - rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); + if (getParent() != null) { + Quaternion rot = vars.quat1; + rot = rot.set(parent.getWorldRotation()).inverseLocal().multLocal(getLocalRotation()); rot.normalizeLocal(); setLocalRotation(rot); } @@ -580,7 +592,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; } @@ -593,7 +605,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); } @@ -606,6 +618,7 @@ protected void updateMatParamOverrides() { * @see MatParamOverride */ public void addMatParamOverride(MatParamOverride override) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (override == null) { throw new IllegalArgumentException("override cannot be null"); } @@ -620,6 +633,7 @@ public void addMatParamOverride(MatParamOverride override) { * @see MatParamOverride */ public void removeMatParamOverride(MatParamOverride override) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (localOverrides.remove(override)) { setMatParamOverrideRefresh(); } @@ -631,6 +645,7 @@ public void removeMatParamOverride(MatParamOverride override) { * @see #addMatParamOverride(com.jme3.material.MatParamOverride) */ public void clearMatParamOverrides() { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); if (!localOverrides.isEmpty()) { setMatParamOverrideRefresh(); } @@ -647,7 +662,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; @@ -760,29 +775,65 @@ public void runControlRender(RenderManager rm, ViewPort vp) { /** * Add a control to the list of controls. + * * @param control The control to add. * * @see Spatial#removeControl(java.lang.Class) */ public void addControl(Control control) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); boolean before = requiresUpdates(); controls.add(control); control.setSpatial(this); boolean after = requiresUpdates(); - // If the requirement to be updated has changed - // then we need to let the parent node know so it + // If the requirement to be updated has changed, + // then we need to let the parent node know, so it // can rebuild its update list. - if( parent != null && before != after ) { + if (parent != null && before != after) { parent.invalidateUpdateList(); } } + /** + * 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. * + * @param controlType the type of Control to remove * @see Spatial#addControl(com.jme3.scene.control.Control) */ public void removeControl(Class controlType) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); boolean before = requiresUpdates(); for (int i = 0; i < controls.size(); i++) { if (controlType.isAssignableFrom(controls.get(i).getClass())) { @@ -792,10 +843,10 @@ public void removeControl(Class controlType) { } } boolean after = requiresUpdates(); - // If the requirement to be updated has changed - // then we need to let the parent node know so it + // If the requirement to be updated has changed, + // then we need to let the parent node know, so it // can rebuild its update list. - if( parent != null && before != after ) { + if (parent != null && before != after) { parent.invalidateUpdateList(); } } @@ -810,6 +861,7 @@ public void removeControl(Class controlType) { * @see Spatial#addControl(com.jme3.scene.control.Control) */ public boolean removeControl(Control control) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); boolean before = requiresUpdates(); boolean result = controls.remove(control); if (result) { @@ -817,10 +869,10 @@ public boolean removeControl(Control control) { } boolean after = requiresUpdates(); - // If the requirement to be updated has changed - // then we need to let the parent node know so it + // If the requirement to be updated has changed, + // then we need to let the parent node know, so it // can rebuild its update list. - if( parent != null && before != after ) { + if (parent != null && before != after) { parent.invalidateUpdateList(); } return result; @@ -830,11 +882,13 @@ public boolean removeControl(Control control) { * Returns the first control that is an instance of the given class, * or null if no such control exists. * + * @param the type of control to look for * @param controlType The superclass of the control to look for. * @return The first instance in the list of the controlType class, or null. * * @see Spatial#addControl(com.jme3.scene.control.Control) */ + @SuppressWarnings("unchecked") public T getControl(Class controlType) { for (Control c : controls.getArray()) { if (controlType.isAssignableFrom(c.getClass())) { @@ -881,7 +935,7 @@ public void updateLogicalState(float tpf) { } /** - * updateGeometricState updates the lightlist, + * updateGeometricState updates the light list, * computes the world transforms, and computes the world bounds * for this Spatial. * Calling this when the Spatial is attached to a node @@ -910,7 +964,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(); } /** @@ -944,6 +998,28 @@ public Vector3f worldToLocal(final Vector3f in, final Vector3f store) { return worldTransform.transformInverseVector(in, store); } + /** + * Transforms the given quaternion from world space to local space relative to this object's transform. + * + * @param in the input quaternion in world space that needs to be transformed + * @param store an optional Quaternion to store the result; if null, a new Quaternion will be created + * @return the transformed quaternion in local space, either stored in the provided Quaternion or a new one + */ + public Quaternion worldToLocal(final Quaternion in, Quaternion store){ + checkDoTransformUpdate(); + if(store == null){ + store=new Quaternion(in); + }else{ + store.set(in); + } + TempVars tempVars = TempVars.get(); + Quaternion worldRotation = tempVars.quat1.set(getWorldRotation()); + worldRotation.inverseLocal(); + store.multLocal(worldRotation); + tempVars.release(); + return store; + } + /** * getParent retrieves this node's parent. If the parent is * null this is the root node. @@ -963,6 +1039,7 @@ public Node getParent() { * the parent of this node. */ protected void setParent(Node parent) { + assert SceneGraphThreadWarden.updateRequirement(this, parent); this.parent = parent; } @@ -1021,8 +1098,8 @@ public void setLocalRotation(Matrix3f rotation) { /** * setLocalRotation sets the local rotation of this node. * - * @param quaternion - * the new local rotation. + * @param quaternion the new local rotation (not null, + * {@code quaternion.norm()} approximately equal to 1, unaffected) */ public void setLocalRotation(Quaternion quaternion) { localTransform.setRotation(quaternion); @@ -1051,6 +1128,10 @@ public void setLocalScale(float localScale) { /** * setLocalScale sets the local scale of this node. + * + * @param x the desired scale factor for the X axis + * @param y the desired scale factor for the Y axis + * @param z the desired scale factor for the Z axis */ public void setLocalScale(float x, float y, float z) { localTransform.setScale(x, y, z); @@ -1093,6 +1174,10 @@ public void setLocalTranslation(Vector3f localTranslation) { /** * setLocalTranslation sets the local translation of this * spatial. + * + * @param x the desired offset in the +X direction + * @param y the desired offset in the +Y direction + * @param z the desired offset in the +Z direction */ public void setLocalTranslation(float x, float y, float z) { this.localTransform.setTranslation(x, y, z); @@ -1102,7 +1187,11 @@ public void setLocalTranslation(float x, float y, float z) { /** * setLocalTransform sets the local transform of this * spatial. + * + * @param t the desired local transform (not null, {@code t.rot.norm()} + * approximately equal to 1, unaffected) */ + @Override public void setLocalTransform(Transform t) { this.localTransform.set(t); setTransformRefresh(); @@ -1114,6 +1203,7 @@ public void setLocalTransform(Transform t) { * * @return the local transform of this spatial. */ + @Override public Transform getLocalTransform() { return localTransform; } @@ -1152,6 +1242,9 @@ public void removeLight(Light light) { /** * Translates the spatial by the given translation vector. * + * @param x the offset to apply in the +X direction + * @param y the offset to apply in the +Y direction + * @param z the offset to apply in the +Z direction * @return The spatial on which this method is called, e.g this. */ public Spatial move(float x, float y, float z) { @@ -1164,6 +1257,7 @@ public Spatial move(float x, float y, float z) { /** * Translates the spatial by the given translation vector. * + * @param offset the desired offset (not null, unaffected) * @return The spatial on which this method is called, e.g this. */ public Spatial move(Vector3f offset) { @@ -1176,6 +1270,7 @@ public Spatial move(Vector3f offset) { /** * Scales the spatial by the given value * + * @param s the scaling factor to apply to all axes * @return The spatial on which this method is called, e.g this. */ public Spatial scale(float s) { @@ -1185,6 +1280,9 @@ public Spatial scale(float s) { /** * Scales the spatial by the given scale vector. * + * @param x the scaling factor to apply to the X axis + * @param y the scaling factor to apply to the Y axis + * @param z the scaling factor to apply to the Z axis * @return The spatial on which this method is called, e.g this. */ public Spatial scale(float x, float y, float z) { @@ -1197,6 +1295,7 @@ public Spatial scale(float x, float y, float z) { /** * Rotates the spatial by the given rotation. * + * @param rot the intrinsic rotation to apply (not null, unaffected) * @return The spatial on which this method is called, e.g this. */ public Spatial rotate(Quaternion rot) { @@ -1210,6 +1309,9 @@ public Spatial rotate(Quaternion rot) { * Rotates the spatial by the xAngle, yAngle and zAngle angles (in radians), * (aka pitch, yaw, roll) in the local coordinate space. * + * @param xAngle the angle of rotation around the +X axis (in radians) + * @param yAngle the angle of rotation around the +Y axis (in radians) + * @param zAngle the angle of rotation around the +Z axis (in radians) * @return The spatial on which this method is called, e.g this. */ public Spatial rotate(float xAngle, float yAngle, float zAngle) { @@ -1224,6 +1326,7 @@ public Spatial rotate(float xAngle, float yAngle, float zAngle) { /** * Centers the spatial in the origin of the world bound. + * * @return The spatial on which this method is called, e.g this. */ public Spatial center() { @@ -1262,10 +1365,10 @@ public BatchHint getBatchHint() { } /** - * Returns this spatial's renderqueue bucket. If the mode is set to inherit, - * then the spatial gets its renderqueue bucket from its parent. + * Returns this spatial's render-queue bucket. If the mode is set to inherit, + * then the spatial gets its render-queue bucket from its parent. * - * @return The spatial's current renderqueue mode. + * @return The spatial's current render-queue bucket. */ public RenderQueue.Bucket getQueueBucket() { if (queueBucket != RenderQueue.Bucket.Inherit) { @@ -1301,6 +1404,7 @@ public RenderQueue.ShadowMode getShadowMode() { * @param lod The lod level to set. */ public void setLodLevel(int lod) { + assert SceneGraphThreadWarden.assertOnCorrectThread(this); } /** @@ -1334,11 +1438,11 @@ public void setLodLevel(int lod) { * Note that meshes of geometries are not cloned explicitly, they * are shared if static, or specially cloned if animated. * + * @param cloneMaterial true to clone materials, false to share them * @see Mesh#cloneForAnim() */ - public Spatial clone( boolean cloneMaterial ) { - - // Setup the cloner for the type of cloning we want to do. + public Spatial clone(boolean cloneMaterial) { + // Set up the cloner for the type of cloning we want to do. Cloner cloner = new Cloner(); // First, we definitely do not want to clone our own parent @@ -1346,13 +1450,13 @@ public Spatial clone( boolean cloneMaterial ) { // If we aren't cloning materials then we will make sure those // aren't cloned also - if( !cloneMaterial ) { + if (!cloneMaterial) { cloner.setCloneFunction(Material.class, new IdentityCloneFunction()); } - // By default the meshes are not cloned. The geometry - // may choose to selectively force them to be cloned but - // normally they will be shared + // By default, the meshes are not cloned. The geometry + // may choose to selectively force them to be cloned, but + // normally they will be shared. cloner.setCloneFunction(Mesh.class, new IdentityCloneFunction()); // Clone it! @@ -1369,6 +1473,9 @@ public Spatial clone( boolean cloneMaterial ) { /** * The old clone() method that did not use the new Cloner utility. + * + * @param cloneMaterial ignored + * @return never */ @Deprecated public Spatial oldClone(boolean cloneMaterial) { @@ -1397,7 +1504,7 @@ public Spatial clone() { * @see Spatial#clone() */ public Spatial deepClone() { - // Setup the cloner for the type of cloning we want to do. + // Set up the cloner for the type of cloning we want to do. Cloner cloner = new Cloner(); // First, we definitely do not want to clone our own parent @@ -1420,7 +1527,7 @@ public Spatial deepClone() { @Override public Spatial jmeClone() { try { - Spatial clone = (Spatial)super.clone(); + Spatial clone = (Spatial) super.clone(); return clone; } catch (CloneNotSupportedException ex) { throw new AssertionError(); @@ -1431,8 +1538,8 @@ public Spatial jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { - + @SuppressWarnings("unchecked") + public void cloneFields(Cloner cloner, Object original) { // Clone all of the fields that need fix-ups and/or potential // sharing. this.parent = cloner.clone(parent); @@ -1451,11 +1558,11 @@ public void cloneFields( Cloner cloner, Object original ) { // to avoid all of the nasty cloneForSpatial() fixup style code that // used to inject stuff into the clone's user data. By using cloner // to clone the user data we get this automatically. - if( userData != null ) { - userData = (HashMap)userData.clone(); - for( Map.Entry e : userData.entrySet() ) { + if (userData != null) { + userData = (HashMap) userData.clone(); + for (Map.Entry e : userData.entrySet()) { Savable value = e.getValue(); - if( value instanceof Cloneable ) { + if (value instanceof Cloneable) { // Note: all JmeCloneable objects are also Cloneable so this // catches both cases. e.setValue(cloner.clone(value)); @@ -1468,7 +1575,7 @@ public void setUserData(String key, Object data) { if (data == null) { if (userData != null) { userData.remove(key); - if(userData.isEmpty()) { + if (userData.isEmpty()) { userData = null; } } @@ -1498,6 +1605,7 @@ public T getUserData(String key) { } } + @SuppressWarnings("unchecked") public Collection getUserDataKeys() { if (userData != null) { return userData.keySet(); @@ -1535,6 +1643,8 @@ public boolean matches(Class spatialSubclass, return true; } + @Override + @SuppressWarnings("unchecked") public void write(JmeExporter ex) throws IOException { OutputCapsule capsule = ex.getCapsule(this); capsule.write(name, "name", null); @@ -1552,6 +1662,8 @@ public void write(JmeExporter ex) throws IOException { capsule.writeStringSavableMap(userData, "user_data", null); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); @@ -1577,9 +1689,11 @@ public void read(JmeImporter im) throws IOException { } worldOverrides = new SafeArrayList<>(MatParamOverride.class); - //changed for backward compatibility with j3o files generated before the AnimControl/SkeletonControl split + //changed for backward compatibility with j3o files + //generated before the AnimControl/SkeletonControl split //the AnimControl creates the SkeletonControl for old files and add it to the spatial. - //The SkeletonControl must be the last in the stack so we add the list of all other control before it. + // The SkeletonControl must be the last in the stack, + // so we add the list of all other controls before it. //When backward compatibility won't be needed anymore this can be replaced by : //controls = ic.readSavableArrayList("controlsList", null)); controls.addAll(0, ic.readSavableArrayList("controlsList", null)); @@ -1627,14 +1741,14 @@ public void setBatchHint(BatchHint hint) { } /** - * @return the cullmode set on this Spatial + * @return the cull mode of this Spatial */ public CullHint getLocalCullHint() { return cullHint; } /** - * @return the batchHint set on this Spatial + * @return the batch hint for this Spatial */ public BatchHint getLocalBatchHint() { return batchHint; @@ -1711,7 +1825,7 @@ public void setLastFrustumIntersection(Camera.FrustumIntersect intersects) { /** * Returns the Spatial's name followed by the class of the spatial
                - * Example: "MyNode (com.jme3.scene.Spatial) + * Example: "MyNode (com.jme3.scene.Spatial)" * * @return Spatial's name followed by the class of the Spatial */ @@ -1748,13 +1862,14 @@ public Matrix4f getLocalToWorldMatrix(Matrix4f store) { /** * Visit each scene graph element ordered by DFS with the default post order mode. - * @param visitor - * @see #depthFirstTraversal(com.jme3.scene.SceneGraphVisitor, com.jme3.scene.Spatial.DFSMode) + * + * @param visitor the action to take for each visited Spatial + * @see #depthFirstTraversal(com.jme3.scene.SceneGraphVisitor, com.jme3.scene.Spatial.DFSMode) */ public void depthFirstTraversal(SceneGraphVisitor visitor) { depthFirstTraversal(visitor, DFSMode.POST_ORDER); } - + /** * Specifies the mode of the depth first search. */ @@ -1768,21 +1883,23 @@ public static enum DFSMode { */ POST_ORDER; } - + /** * Visit each scene graph element ordered by DFS. * There are two modes: pre order and post order. - * @param visitor + * + * @param visitor the action to take for each visited Spatial * @param mode the traversal mode: pre order or post order */ public abstract void depthFirstTraversal(SceneGraphVisitor visitor, DFSMode mode); /** * Visit each scene graph element ordered by BFS - * @param visitor + * + * @param visitor the action to take for each visited Spatial */ public void breadthFirstTraversal(SceneGraphVisitor visitor) { - Queue queue = new LinkedList(); + Queue queue = new LinkedList<>(); queue.add(this); while (!queue.isEmpty()) { diff --git a/jme3-core/src/main/java/com/jme3/scene/UserData.java b/jme3-core/src/main/java/com/jme3/scene/UserData.java index 0e94e2c0b2..2fb04f44d3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/UserData.java +++ b/jme3-core/src/main/java/com/jme3/scene/UserData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -134,10 +134,11 @@ public static byte getObjectType(Object type) { } } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(type, "type", (byte) 0); - + switch (type) { case TYPE_INTEGER: int i = (Integer) value; @@ -191,6 +192,7 @@ public void write(JmeExporter ex) throws IOException { } } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); type = ic.readByte("type", (byte) 0); @@ -217,7 +219,7 @@ public void read(JmeImporter im) throws IOException { value = this.readList(ic, "0"); break; case TYPE_MAP: - Map map = new HashMap(); + Map map = new HashMap<>(); List keys = this.readList(ic, "0"); List values = this.readList(ic, "1"); for (int i = 0; i < keys.size(); ++i) { @@ -249,6 +251,7 @@ public void read(JmeImporter im) throws IOException { * @param list * the list to be stored * @throws IOException + * from the capsule */ private void writeList(OutputCapsule oc, Collection list, String listName) throws IOException { if (list != null) { @@ -265,7 +268,7 @@ private void writeList(OutputCapsule oc, Collection list, String listName) th } else if (o instanceof Boolean) { oc.write(TYPE_BOOLEAN, listName + "t" + counter, 0); oc.write((Boolean) o, listName + "v" + counter, false); - } else if (o instanceof String || o == null) {// treat null's like Strings just to store them and keep the List like the user intended + } else if (o instanceof String || o == null) {// treat nulls like Strings just to store them and keep the List like the user intended oc.write(TYPE_STRING, listName + "t" + counter, 0); oc.write((String) o, listName + "v" + counter, null); } else if (o instanceof Long) { @@ -311,10 +314,11 @@ private void writeList(OutputCapsule oc, Collection list, String listName) th * the input capsule * @return loaded list (an empty list in case its size is 0) * @throws IOException + * from the capsule */ private List readList(InputCapsule ic, String listName) throws IOException { int size = ic.readInt(listName + "size", 0); - List list = new ArrayList(size); + List list = new ArrayList<>(size); for (int i = 0; i < size; ++i) { int type = ic.readInt(listName + "t" + i, 0); switch (type) { @@ -343,7 +347,7 @@ private List readList(InputCapsule ic, String listName) throws IOException { list.add(this.readList(ic, listName + "v" + i)); break; case TYPE_MAP: - Map map = new HashMap(); + Map map = new HashMap<>(); List keys = this.readList(ic, listName + "v(keys)" + i); List values = this.readList(ic, listName + "v(vals)" + i); for (int j = 0; j < keys.size(); ++j) { 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 7b0b4146c1..ddb34f43ee 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-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,41 +49,35 @@ *
              • Element - A single element is the largest individual object * inside a VertexBuffer. E.g. if the VertexBuffer is used to store 3D position * data, then an element will be a single 3D vector.
              • - *
              • Component - A component represents the parts inside an element. + *
              • Component - A component represents the parts inside an element. * For a 3D vector, a single component is one of the dimensions, X, Y or Z.
              • * */ public class VertexBuffer extends NativeObject implements Savable, Cloneable { - /** * Type of buffer. Specifies the actual attribute it defines. */ - public static enum Type { + public enum Type { /** * Position of the vertex (3 floats) */ Position, - /** * The size of the point when using point buffers (float). */ Size, - /** * Normal vector, normalized (3 floats). */ Normal, - /** * Texture coordinate (2 float) */ TexCoord, - /** * Color and Alpha (4 floats) */ Color, - /** * Tangent vector, normalized (4 floats) (x,y,z,w). The w component is * called the binormal parity, is not normalized, and is either 1f or @@ -91,19 +85,16 @@ public static enum Type { * GPU at render time. */ Tangent, - /** * Binormal vector, normalized (3 floats, optional) */ Binormal, - /** * Specifies the source data for various vertex buffers - * when interleaving is used. By default the format is + * when interleaving is used. By default, the format is * byte. */ InterleavedData, - /** * Do not use. */ @@ -114,106 +105,90 @@ public static enum Type { * (ubyte, ushort, or uint). */ Index, - - /** + /** * Initial vertex position, used with animation. * Should have the same format and size as {@link Type#Position}. - * If used with software skinning, the usage should be + * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BindPosePosition, - - /** + /** * Initial vertex normals, used with animation. * Should have the same format and size as {@link Type#Normal}. - * If used with software skinning, the usage should be + * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ - BindPoseNormal, - - /** + BindPoseNormal, + /** * Bone weights, used with animation (4 floats). - * Only used for software skinning, the usage should be + * Only used for software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BoneWeight, - - /** + /** * Bone indices, used with animation (4 ubytes). - * Only used for software skinning, the usage should be + * Only used for software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap as a ubytes buffer. */ BoneIndex, - /** * Texture coordinate #2 */ TexCoord2, - /** * Texture coordinate #3 */ TexCoord3, - /** * Texture coordinate #4 */ TexCoord4, - /** * Texture coordinate #5 */ TexCoord5, - /** * Texture coordinate #6 */ TexCoord6, - /** * Texture coordinate #7 */ TexCoord7, - /** * Texture coordinate #8 */ TexCoord8, - - /** + /** * Initial vertex tangents, used with animation. * Should have the same format and size as {@link Type#Tangent}. - * If used with software skinning, the usage should be + * If used with software skinning, the usage should be * {@link Usage#CpuOnly}, and the buffer should be allocated * on the heap. */ BindPoseTangent, - - /** + /** * Bone weights, used with animation (4 floats). * for Hardware Skinning only */ HWBoneWeight, - - /** + /** * Bone indices, used with animation (4 ubytes). * for Hardware Skinning only * either an int or float buffer due to shader attribute types restrictions. */ HWBoneIndex, - /** * Information about this instance. - * + * * Format should be {@link Format#Float} and number of components * should be 16. */ InstanceData, - /** * Morph animations targets. * Supports up tp 14 morph target buffers at the same time @@ -223,13 +198,17 @@ public static enum Type { * So we can support up to * 14 simultaneous POSITION targets * 7 simultaneous POSITION and NORMAL targets - * 4 simultaneous POSTION, NORMAL and TANGENT targets. + * 4 simultaneous POSITION, NORMAL and TANGENT targets. *

                - * Note that the MorphControl will find how many buffers can be supported for each mesh/material combination. - * Note that all buffers have 3 components (Vector3f) even the Tangent buffer that - * does not contain the w (handedness) component that will not be interpolated for morph animation. + * Note that the MorphControl will find how many buffers + * can be supported for each mesh/material combination. + * Note that all buffers have 3 components (Vector3f) + * even the Tangent buffer that + * does not contain the w (handedness) component + * that will not be interpolated for morph animation. *

                - * Note that those buffers contain the difference between the base buffer (POSITION, NORMAL or TANGENT) and the target value + * Note that those buffers contain the difference between + * the base buffer (POSITION, NORMAL or TANGENT) and the target value * So that you can interpolate with a MADD operation in the vertex shader * position = weight * diffPosition + basePosition; */ @@ -247,7 +226,6 @@ public static enum Type { MorphTarget11, MorphTarget12, MorphTarget13, - } /** @@ -255,23 +233,19 @@ public static enum Type { * is used. This can determine if a vertex buffer is placed in VRAM * or held in video memory, but no guarantees are made- it's only a hint. */ - public static enum Usage { - + public enum Usage { /** * Mesh data is sent once and very rarely updated. */ Static, - /** * Mesh data is updated occasionally (once per frame or less). */ Dynamic, - /** * Mesh data is updated every frame. */ Stream, - /** * Mesh data is not sent to GPU at all. It is only * used by the CPU. @@ -287,51 +261,40 @@ public static enum Usage { * For the {@link Format#Half} type, {@link ByteBuffer}s should * be used. */ - public static enum Format { + public enum Format { /** - * Half precision floating point. - * 2 bytes, signed. + * Half precision floating point. 2 bytes, signed. */ Half(2), - /** - * Single precision floating point. - * 4 bytes, signed + * Single precision floating point. 4 bytes, signed */ Float(4), - /** - * Double precision floating point. - * 8 bytes, signed. May not - * be supported by all GPUs. + * Double precision floating point. 8 bytes, signed. May not be + * supported by all GPUs. */ Double(8), - /** * 1 byte integer, signed. */ Byte(1), - /** * 1 byte integer, unsigned. */ UnsignedByte(1), - /** * 2 byte integer, signed. */ Short(2), - /** * 2 byte integer, unsigned. */ UnsignedShort(2), - /** * 4 byte integer, signed. */ Int(4), - /** * 4 byte integer, unsigned. */ @@ -339,16 +302,16 @@ public static enum Format { private int componentSize = 0; - Format(int componentSize){ + Format(int componentSize) { this.componentSize = componentSize; } /** * Returns the size in bytes of this data type. - * + * * @return Size in bytes of this data type. */ - public int getComponentSize(){ + public int getComponentSize() { return componentSize; } } @@ -369,12 +332,15 @@ public int getComponentSize(){ protected boolean normalized = false; protected int instanceSpan = 0; protected transient boolean dataSizeChanged = false; + protected String name; /** * Creates an empty, uninitialized buffer. * Must call setupData() to initialize. + * + * @param type the type of VertexBuffer, such as Position or Binormal */ - public VertexBuffer(Type type){ + public VertexBuffer(Type type) { super(); this.bufType = type; } @@ -382,11 +348,11 @@ public VertexBuffer(Type type){ /** * Serialization only. Do not use. */ - protected VertexBuffer(){ + protected VertexBuffer() { super(); } - protected VertexBuffer(int id){ + protected VertexBuffer(int id) { super(id); } @@ -408,7 +374,7 @@ public boolean invariant() { throw new AssertionError(); } // Are components between 1 and 4? - + // Are components between 1 and 4 and not InstanceData? if (bufType != Type.InstanceData) { if (components < 1 || components > 4) { @@ -442,11 +408,11 @@ public boolean invariant() { } return true; } - + /** * @return The offset after which the data is sent to the GPU. - * - * @see #setOffset(int) + * + * @see #setOffset(int) */ public int getOffset() { return offset; @@ -461,21 +427,21 @@ public void setOffset(int offset) { } /** - * @return The stride (in bytes) for the data. - * - * @see #setStride(int) + * @return The stride (in bytes) for the data. + * + * @see #setStride(int) */ public int getStride() { return stride; } /** - * Set the stride (in bytes) for the data. + * Set the stride (in bytes) for the data. *

                - * If the data is packed in the buffer, then stride is 0, if there's other - * data that is between the current component and the next component in the + * If the data is packed in the buffer, then stride is 0, if there's other + * data that is between the current component and the next component in the * buffer, then this specifies the size in bytes of that additional data. - * + * * @param stride the stride (in bytes) for the data */ public void setStride(int stride) { @@ -486,17 +452,17 @@ public void setStride(int stride) { * Returns the raw internal data buffer used by this VertexBuffer. * This buffer is not safe to call from multiple threads since buffers * have their own internal position state that cannot be shared. - * Call getData().duplicate(), getData().asReadOnlyBuffer(), or - * the more convenient getDataReadOnly() if the buffer may be accessed + * Call getData().duplicate(), getData().asReadOnlyBuffer(), or + * the more convenient getDataReadOnly() if the buffer may be accessed * from multiple threads. - * + * * @return A native buffer, in the specified {@link Format format}. */ - public Buffer getData(){ + public Buffer getData() { return data; } - - /** + + /** * Returns a safe read-only version of this VertexBuffer's data. The * contents of the buffer will reflect whatever changes are made on * other threads (eventually) but these should not be used in that way. @@ -504,38 +470,36 @@ public Buffer getData(){ * a separate thread since it has its own book-keeping state (position, limit, etc.) * * @return A rewound native buffer in the specified {@link Format format} - * that is safe to read from a separate thread from other readers. + * that is safe to read from a separate thread from other readers. */ public Buffer getDataReadOnly() { - if (data == null) { return null; } - + // Create a read-only duplicate(). Note: this does not copy // the underlying memory, it just creates a new read-only wrapper // with its own buffer position state. - // Unfortunately, this is not 100% straight forward since Buffer // does not have an asReadOnlyBuffer() method. Buffer result; - if( data instanceof ByteBuffer ) { - result = ((ByteBuffer)data).asReadOnlyBuffer(); - } else if( data instanceof FloatBuffer ) { - result = ((FloatBuffer)data).asReadOnlyBuffer(); - } else if( data instanceof ShortBuffer ) { - result = ((ShortBuffer)data).asReadOnlyBuffer(); - } else if( data instanceof IntBuffer ) { - result = ((IntBuffer)data).asReadOnlyBuffer(); + if (data instanceof ByteBuffer) { + result = ((ByteBuffer) data).asReadOnlyBuffer(); + } else if (data instanceof FloatBuffer) { + result = ((FloatBuffer) data).asReadOnlyBuffer(); + } else if (data instanceof ShortBuffer) { + result = ((ShortBuffer) data).asReadOnlyBuffer(); + } else if (data instanceof IntBuffer) { + result = ((IntBuffer) data).asReadOnlyBuffer(); } else { - throw new UnsupportedOperationException( "Cannot get read-only view of buffer type:" + data ); + throw new UnsupportedOperationException("Cannot get read-only view of buffer type:" + data); } - + // Make sure the caller gets a consistent view since we may // have grabbed this buffer while another thread was reading // the raw data. result.rewind(); - + return result; } @@ -543,7 +507,7 @@ public Buffer getDataReadOnly() { * @return The usage of this buffer. See {@link Usage} for more * information. */ - public Usage getUsage(){ + public Usage getUsage() { return usage; } @@ -551,7 +515,7 @@ public Usage getUsage(){ * @param usage The usage of this buffer. See {@link Usage} for more * information. */ - public void setUsage(Usage usage){ + public void setUsage(Usage usage) { // if (id != -1) // throw new UnsupportedOperationException("Data has already been sent. Cannot set usage."); @@ -567,15 +531,15 @@ public void setUsage(Usage usage){ * the components will be converted to the range 0.0 - 1.0 by dividing * every integer by 2^32. */ - public void setNormalized(boolean normalized){ + public void setNormalized(boolean normalized) { this.normalized = normalized; } /** * @return True if integer components should be converted to the range 0-1. - * @see VertexBuffer#setNormalized(boolean) + * @see VertexBuffer#setNormalized(boolean) */ - public boolean isNormalized(){ + public boolean isNormalized() { return normalized; } @@ -583,49 +547,52 @@ public boolean isNormalized(){ * Sets the instanceSpan to 1 or 0 depending on * the value of instanced and the existing value of * instanceSpan. + * + * @param instanced true for instanced, false for not instanced */ public void setInstanced(boolean instanced) { - if( instanced && instanceSpan == 0 ) { + if (instanced && instanceSpan == 0) { instanceSpan = 1; - } else if( !instanced ) { + } else if (!instanced) { instanceSpan = 0; } } /** - * Returns true if instanceSpan is more than 0 indicating - * that this vertex buffer contains per-instance data. + * @return true if buffer contains per-instance data, otherwise false */ public boolean isInstanced() { return instanceSpan > 0; } - + /** * Sets how this vertex buffer matches with rendered instances * where 0 means no instancing at all, ie: all elements are * per vertex. If set to 1 then each element goes with one * instance. If set to 2 then each element goes with two * instances and so on. + * + * @param i the desired number of instances per element */ public void setInstanceSpan(int i) { this.instanceSpan = i; } - + public int getInstanceSpan() { return instanceSpan; } - + /** * @return The type of information that this buffer has. */ - public Type getBufferType(){ + public Type getBufferType() { return bufType; } /** * @return The {@link Format format}, or data type of the data. */ - public Format getFormat(){ + public Format getFormat() { return format; } @@ -633,15 +600,15 @@ public Format getFormat(){ * @return The number of components of the given {@link Format format} per * element. */ - public int getNumComponents(){ + public int getNumComponents() { return components; } /** * @return The total number of data elements in the data buffer. */ - public int getNumElements(){ - if( data == null ) { + public int getNumElements() { + if (data == null) { return 0; } int elements = data.limit() / components; @@ -657,9 +624,11 @@ public int getNumElements(){ * is 0 then 'instances' is 1. Otherwise, instances is elements * * instanceSpan. It is possible to render a mesh with more instances * but the instance data begins to repeat. + * + * @return the number of instances */ public int getBaseInstanceCount() { - if( instanceSpan == 0 ) { + if (instanceSpan == 0) { return 1; } return getNumElements() * instanceSpan; @@ -668,7 +637,7 @@ public int getBaseInstanceCount() { /** * Called to initialize the data in the VertexBuffer. Must only * be called once. - * + * * @param usage The usage for the data, or how often will the data * be updated per frame. See the {@link Usage} enum. * @param components The number of components per element. @@ -677,7 +646,7 @@ public int getBaseInstanceCount() { * @param data A native buffer, the format of which matches the {@link Format} * argument. */ - public void setupData(Usage usage, int components, Format format, Buffer data){ + public void setupData(Usage usage, int components, Format format, Buffer data) { if (id != -1) { throw new UnsupportedOperationException("Data has already been sent. Cannot setupData again."); } @@ -695,7 +664,7 @@ public void setupData(Usage usage, int components, Format format, Buffer data){ throw new IllegalArgumentException("components must be between 1 and 4"); } } - + this.data = data; this.components = components; this.usage = usage; @@ -707,7 +676,8 @@ public void setupData(Usage usage, int components, Format format, Buffer data){ /** * Called to update the data in the buffer with new data. Can only - * be called after {@link VertexBuffer#setupData(com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) } + * be called after {@link VertexBuffer#setupData( + * com.jme3.scene.VertexBuffer.Usage, int, com.jme3.scene.VertexBuffer.Format, java.nio.Buffer) } * has been called. Note that it is fine to call this method on the * data already set, e.g. vb.updateData(vb.getData()), this will just * set the proper update flag indicating the data should be sent to the GPU @@ -716,26 +686,26 @@ public void setupData(Usage usage, int components, Format format, Buffer data){ * It is allowed to specify a buffer with different capacity than the * originally set buffer, HOWEVER, if you do so, you must * call Mesh.updateCounts() otherwise bizarre errors can occur. - * + * * @param data The data buffer to set */ - public void updateData(Buffer data){ - if (id != -1){ + public void updateData(Buffer data) { + if (id != -1) { // request to update data is okay } // Check if the data buffer is read-only which is a sign // of a bug on the part of the caller if (data != null && data.isReadOnly()) { - throw new IllegalArgumentException( "VertexBuffer data cannot be read-only." ); + throw new IllegalArgumentException("VertexBuffer data cannot be read-only."); } // will force renderer to call glBufferData again - if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)){ + if (data != null && (this.data.getClass() != data.getClass() || data.limit() != lastLimit)) { dataSizeChanged = true; lastLimit = data.limit(); } - + this.data = data; setUpdateNeeded(); } @@ -743,6 +713,7 @@ public void updateData(Buffer data){ /** * Returns true if the data size of the VertexBuffer has changed. * Internal use only. + * * @return true if the data size has changed */ public boolean hasDataSizeChanged() { @@ -750,7 +721,7 @@ public boolean hasDataSizeChanged() { } @Override - public void clearUpdateNeeded(){ + public void clearUpdateNeeded() { super.clearUpdateNeeded(); dataSizeChanged = false; } @@ -758,7 +729,7 @@ public void clearUpdateNeeded(){ /** * Converts single floating-point data to {@link Format#Half half} floating-point data. */ - public void convertToHalf(){ + public void convertToHalf() { if (id != -1) { throw new UnsupportedOperationException("Data has already been sent."); } @@ -770,14 +741,14 @@ public void convertToHalf(){ int numElements = data.limit() / components; format = Format.Half; this.componentsLength = components * format.getComponentSize(); - + ByteBuffer halfData = BufferUtils.createByteBuffer(componentsLength * numElements); halfData.rewind(); FloatBuffer floatData = (FloatBuffer) data; floatData.rewind(); - for (int i = 0; i < floatData.limit(); i++){ + for (int i = 0; i < floatData.limit(); i++) { float f = floatData.get(i); short half = FastMath.convertFloatToHalf(f); halfData.putShort(half); @@ -794,10 +765,10 @@ public void convertToHalf(){ * * @param numElements The number of elements to reduce to. */ - public void compact(int numElements){ + public void compact(int numElements) { int total = components * numElements; data.clear(); - switch (format){ + switch (format) { case Byte: case UnsignedByte: case Half: @@ -831,7 +802,7 @@ public void compact(int numElements){ data = fnewBuf; break; default: - throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } data.clear(); setUpdateNeeded(); @@ -842,69 +813,69 @@ public void compact(int numElements){ * Modify a component inside an element. * The val parameter must be in the buffer's format: * {@link Format}. - * + * * @param elementIndex The element index to modify * @param componentIndex The component index to modify * @param val The value to set, either byte, short, int or float depending * on the {@link Format}. */ - public void setElementComponent(int elementIndex, int componentIndex, Object val){ + public void setElementComponent(int elementIndex, int componentIndex, Object val) { int inPos = elementIndex * components; int elementPos = componentIndex; - if (format == Format.Half){ + if (format == Format.Half) { inPos *= 2; elementPos *= 2; } data.clear(); - switch (format){ + switch (format) { case Byte: case UnsignedByte: case Half: ByteBuffer bin = (ByteBuffer) data; - bin.put(inPos + elementPos, (Byte)val); + bin.put(inPos + elementPos, (Byte) val); break; case Short: case UnsignedShort: ShortBuffer sin = (ShortBuffer) data; - sin.put(inPos + elementPos, (Short)val); + sin.put(inPos + elementPos, (Short) val); break; case Int: case UnsignedInt: IntBuffer iin = (IntBuffer) data; - iin.put(inPos + elementPos, (Integer)val); + iin.put(inPos + elementPos, (Integer) val); break; case Float: FloatBuffer fin = (FloatBuffer) data; - fin.put(inPos + elementPos, (Float)val); + fin.put(inPos + elementPos, (Float) val); break; default: - throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } } /** * Get the component inside an element. - * + * * @param elementIndex The element index * @param componentIndex The component index * @return The component, as one of the primitive types, byte, short, * int or float. */ - public Object getElementComponent(int elementIndex, int componentIndex){ + public Object getElementComponent(int elementIndex, int componentIndex) { int inPos = elementIndex * components; int elementPos = componentIndex; - if (format == Format.Half){ + if (format == Format.Half) { inPos *= 2; elementPos *= 2; } Buffer srcData = getDataReadOnly(); - switch (format){ + switch (format) { case Byte: case UnsignedByte: case Half: @@ -922,47 +893,47 @@ public Object getElementComponent(int elementIndex, int componentIndex){ FloatBuffer fin = (FloatBuffer) srcData; return fin.get(inPos + elementPos); default: - throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } } /** * Copies a single element of data from this VertexBuffer * to the given output VertexBuffer. - * + * * @param inIndex The input element index * @param outVb The buffer to copy to * @param outIndex The output element index - * + * * @throws IllegalArgumentException If the formats of the buffers do not * match. */ - public void copyElement(int inIndex, VertexBuffer outVb, int outIndex){ + public void copyElement(int inIndex, VertexBuffer outVb, int outIndex) { copyElements(inIndex, outVb, outIndex, 1); } /** * Copies a sequence of elements of data from this VertexBuffer * to the given output VertexBuffer. - * + * * @param inIndex The input element index * @param outVb The buffer to copy to * @param outIndex The output element index * @param len The number of elements to copy - * + * * @throws IllegalArgumentException If the formats of the buffers do not * match. */ - public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len){ + public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) { if (outVb.format != format || outVb.components != components) { throw new IllegalArgumentException("Buffer format mismatch. Cannot copy"); } - int inPos = inIndex * components; + int inPos = inIndex * components; int outPos = outIndex * components; int elementSz = components; - if (format == Format.Half){ - // because half is stored as bytebuf but its 2 bytes long + if (format == Format.Half) { + // because half is stored as ByteBuffer, but it's 2 bytes long inPos *= 2; outPos *= 2; elementSz *= 2; @@ -974,7 +945,7 @@ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) Buffer srcData = getDataReadOnly(); outVb.data.clear(); - switch (format){ + switch (format) { case Byte: case UnsignedByte: case Half: @@ -982,7 +953,7 @@ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) ByteBuffer bout = (ByteBuffer) outVb.data; bin.position(inPos).limit(inPos + elementSz * len); bout.position(outPos).limit(outPos + elementSz * len); - bout.put(bin); + bout.put(bin); break; case Short: case UnsignedShort: @@ -1008,7 +979,7 @@ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) fout.put(fin); break; default: - throw new UnsupportedOperationException("Unrecognized buffer format: "+format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } // Clear the output buffer to rewind it and reset its @@ -1021,15 +992,20 @@ public void copyElements(int inIndex, VertexBuffer outVb, int outIndex, int len) * of the parameters. The buffer will be of the type specified by * {@link Format format} and would be able to contain the given number * of elements with the given number of components in each element. + * + * @param format the desired format of components, such as Float or Half + * @param components the number of components per element (≥1, ≤4) + * @param numElements the desired capacity (number of elements) + * @return a new Buffer */ - public static Buffer createBuffer(Format format, int components, int numElements){ + public static Buffer createBuffer(Format format, int components, int numElements) { if (components < 1 || components > 4) { throw new IllegalArgumentException("Num components must be between 1 and 4"); } int total = numElements * components; - switch (format){ + switch (format) { case Byte: case UnsignedByte: return BufferUtils.createByteBuffer(total); @@ -1046,19 +1022,19 @@ public static Buffer createBuffer(Format format, int components, int numElements case Double: return BufferUtils.createDoubleBuffer(total); default: - throw new UnsupportedOperationException("Unrecoginized buffer format: "+format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } } /** * Creates a deep clone of the {@link VertexBuffer}. - * + * * @return Deep clone of this buffer */ @Override - public VertexBuffer clone(){ + public VertexBuffer clone() { // NOTE: Superclass GLObject automatically creates shallow clone - // e.g re-use ID. + // e.g. re-use ID. VertexBuffer vb = (VertexBuffer) super.clone(); vb.handleRef = new Object(); vb.id = -1; @@ -1069,22 +1045,22 @@ public VertexBuffer clone(){ // a purely read-only operation. vb.updateData(BufferUtils.clone(getDataReadOnly())); } - + return vb; } /** * Creates a deep clone of this VertexBuffer but overrides the * {@link Type}. - * + * * @param overrideType The type of the cloned VertexBuffer * @return A deep clone of the buffer */ - public VertexBuffer clone(Type overrideType){ + public VertexBuffer clone(Type overrideType) { VertexBuffer vb = new VertexBuffer(overrideType); vb.components = components; vb.componentsLength = componentsLength; - + // Make sure to pass a read-only buffer to clone so that // the position information doesn't get clobbered by another // reading thread during cloning (and vice versa) since this is @@ -1103,15 +1079,15 @@ public VertexBuffer clone(Type overrideType){ } @Override - public String toString(){ + public String toString() { String dataTxt = null; - if (data != null){ - dataTxt = ", elements="+data.limit(); + if (data != null) { + dataTxt = ", elements=" + data.limit(); } - return getClass().getSimpleName() + "[fmt="+format.name() - +", type="+bufType.name() - +", usage="+usage.name() - +dataTxt+"]"; + return getClass().getSimpleName() + "[fmt=" + format.name() + + ", type=" + bufType.name() + + ", usage=" + usage.name() + + dataTxt + "]"; } @Override @@ -1123,29 +1099,28 @@ public void resetObject() { @Override public void deleteObject(Object rendererObject) { - ((Renderer)rendererObject).deleteBuffer(this); + ((Renderer) rendererObject).deleteBuffer(this); } - + @Override protected void deleteNativeBuffers() { if (data != null) { BufferUtils.destroyDirectBuffer(data); } } - + @Override - public NativeObject createDestructableClone(){ + public NativeObject createDestructableClone() { return new VertexBuffer(id); } @Override public long getUniqueId() { - return ((long)OBJTYPE_VERTEXBUFFER << 32) | ((long)id); + return ((long) OBJTYPE_VERTEXBUFFER << 32) | (0xffffffffL & (long) id); } - + @Override public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = ex.getCapsule(this); oc.write(components, "components", 0); oc.write(usage, "usage", Usage.Dynamic); @@ -1155,10 +1130,11 @@ public void write(JmeExporter ex) throws IOException { oc.write(offset, "offset", 0); oc.write(stride, "stride", 0); oc.write(instanceSpan, "instanceSpan", 0); + oc.write(name, "name", null); String dataName = "data" + format.name(); Buffer roData = getDataReadOnly(); - switch (format){ + switch (format) { case Float: oc.write((FloatBuffer) roData, dataName, null); break; @@ -1176,7 +1152,7 @@ public void write(JmeExporter ex) throws IOException { oc.write((IntBuffer) roData, dataName, null); break; default: - throw new IOException("Unsupported export buffer format: "+format); + throw new IOException("Unsupported export buffer format: " + format); } } @@ -1191,10 +1167,12 @@ public void read(JmeImporter im) throws IOException { offset = ic.readInt("offset", 0); stride = ic.readInt("stride", 0); instanceSpan = ic.readInt("instanceSpan", 0); + name = ic.readString("name", null); + componentsLength = components * format.getComponentSize(); String dataName = "data" + format.name(); - switch (format){ + switch (format) { case Float: data = ic.readFloatBuffer(dataName, null); break; @@ -1212,8 +1190,32 @@ public void read(JmeImporter im) throws IOException { data = ic.readIntBuffer(dataName, null); break; default: - throw new IOException("Unsupported import buffer format: "+format); + throw new IOException("Unsupported import buffer format: " + format); } } + /** + * Returns the name of this `VertexBuffer`. If no name has been explicitly + * set, a default name is generated based on its class name and buffer type + * (e.g., "VertexBuffer(Position)"). + * + * @return The name of the `VertexBuffer`. + */ + public String getName() { + if (name == null) { + return String.format("%s(%s)", getClass().getSimpleName(), getBufferType().name()); + } + return name; + } + + /** + * Sets a custom name for this `VertexBuffer`. + * + * @param name The new name for the `VertexBuffer`. Can be null to revert + * to the default generated name. + */ + public void setName(String name) { + this.name = name; + } + } diff --git a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java index 4afbb437bc..cc92767c35 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/AbstractControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,13 +55,14 @@ public abstract class AbstractControl implements Control, JmeCloneable { public AbstractControl(){ } + @Override public void setSpatial(Spatial spatial) { if (this.spatial != null && spatial != null && spatial != this.spatial) { throw new IllegalStateException("This control has already been added to a Spatial"); - } + } this.spatial = spatial; } - + public Spatial getSpatial(){ return spatial; } @@ -76,11 +77,16 @@ public boolean isEnabled() { /** * To be implemented in subclass. + * + * @param tpf time per frame (in seconds) */ protected abstract void controlUpdate(float tpf); /** * To be implemented in subclass. + * + * @param rm the RenderManager rendering the controlled Spatial (not null) + * @param vp the ViewPort being rendered (not null) */ protected abstract void controlRender(RenderManager rm, ViewPort vp); @@ -94,16 +100,17 @@ public Control cloneForSpatial(Spatial spatial) { public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { - throw new RuntimeException( "Can't clone control for spatial", e ); + } catch (CloneNotSupportedException e) { + throw new RuntimeException("Can't clone control for spatial", e); } - } + } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.spatial = cloner.clone(spatial); } - + + @Override public void update(float tpf) { if (!enabled) return; @@ -111,6 +118,7 @@ public void update(float tpf) { controlUpdate(tpf); } + @Override public void render(RenderManager rm, ViewPort vp) { if (!enabled) return; @@ -118,16 +126,17 @@ public void render(RenderManager rm, ViewPort vp) { controlRender(rm, vp); } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(enabled, "enabled", true); oc.write(spatial, "spatial", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); enabled = ic.readBoolean("enabled", true); spatial = (Spatial) ic.readSavable("spatial", null); } - } 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 dee565782e..0097191ca8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/AreaUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,53 +31,33 @@ */ package com.jme3.scene.control; -import com.jme3.bounding.BoundingBox; -import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; -import com.jme3.math.FastMath; /** * AreaUtils is used to calculate the area of various objects, such as bounding volumes. These * 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. + */ + 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/control/BillboardControl.java b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java index 96d7bdf759..f51e7460b4 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/BillboardControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -103,17 +103,17 @@ protected void controlRender(RenderManager rm, ViewPort vp) { Camera cam = vp.getCamera(); rotateBillboard(cam); } - + private void fixRefreshFlags(){ // force transforms to update below this node spatial.updateGeometricState(); - + // force world bound to update Spatial rootNode = spatial; while (rootNode.getParent() != null){ rootNode = rootNode.getParent(); } - rootNode.getWorldBound(); + rootNode.getWorldBound(); } /** @@ -148,7 +148,7 @@ private void rotateBillboard(Camera cam) { private void rotateCameraAligned(Camera camera) { look.set(camera.getLocation()).subtractLocal( spatial.getWorldTranslation()); - // coopt left for our own purposes. + // co-opt left for our own purposes. Vector3f xzp = left; // The xzp vector is the projection of the look vector on the xz plane xzp.set(look.x, 0, look.z); @@ -180,21 +180,21 @@ private void rotateCameraAligned(Camera camera) { } /** - * Rotate the billboard so it points directly opposite the direction the - * camera's facing + * Rotates the billboard so it points directly opposite the direction the + * camera is facing. * * @param camera * Camera */ private void rotateScreenAligned(Camera camera) { - // coopt diff for our in direction: + // co-opt diff for our in direction: look.set(camera.getDirection()).negateLocal(); - // coopt loc for our left direction: + // co-opt loc for our left direction: left.set(camera.getLeft()).negateLocal(); orient.fromAxes(left, camera.getUp(), look); Node parent = spatial.getParent(); - Quaternion rot=new Quaternion().fromRotationMatrix(orient); - if ( parent != null ) { + Quaternion rot = new Quaternion().fromRotationMatrix(orient); + if (parent != null) { rot = parent.getWorldRotation().inverse().multLocal(rot); rot.normalizeLocal(); } @@ -213,9 +213,8 @@ private void rotateAxial(Camera camera, Vector3f axis) { // the camera. To do this, the camera must be inverse-transformed into // the model space of the billboard. look.set(camera.getLocation()).subtractLocal( - spatial.getWorldTranslation()); - spatial.getParent().getWorldRotation().mult(look, left); // coopt left for our own - // purposes. + spatial.getWorldTranslation()); + spatial.getParent().getWorldRotation().mult(look, left); // co-opt left for our own purposes. left.x *= 1.0f / spatial.getWorldScale().x; left.y *= 1.0f / spatial.getWorldScale().y; left.z *= 1.0f / spatial.getWorldScale().z; @@ -280,6 +279,8 @@ public Alignment getAlignment() { * Sets the type of rotation this Billboard will have. The alignment can * be Camera, Screen, AxialY, or AxialZ. Invalid alignments will * assume no billboard rotation. + * + * @param alignment the desired alignment (Camera/Screen/AxialY/AxialZ) */ public void setAlignment(Alignment alignment) { this.alignment = alignment; @@ -296,9 +297,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); orient = (Matrix3f) capsule.readSavable("orient", null); look = (Vector3f) capsule.readSavable("look", null); left = (Vector3f) capsule.readSavable("left", null); diff --git a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java index 0b8606d23a..ba701bfdb9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/CameraControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -82,6 +82,7 @@ public CameraControl(Camera camera) { /** * @param camera The Camera to be synced. + * @param controlDir SpatialToCamera or CameraToSpatial */ public CameraControl(Camera camera, ControlDirection controlDir) { this.camera = camera; @@ -104,7 +105,7 @@ public void setControlDir(ControlDirection controlDir) { this.controlDir = controlDir; } - // fields used, when inversing ControlDirection: + // fields used, when inverting ControlDirection: @Override protected void controlUpdate(float tpf) { if (spatial != null && camera != null) { @@ -114,7 +115,7 @@ protected void controlUpdate(float tpf) { camera.setRotation(spatial.getWorldRotation()); break; case CameraToSpatial: - // set the localtransform, so that the worldtransform would be equal to the camera's transform. + // Set the local transform so that the world transform would be equal to the camera's transform. // Location: TempVars vars = TempVars.get(); diff --git a/jme3-core/src/main/java/com/jme3/scene/control/Control.java b/jme3-core/src/main/java/com/jme3/scene/control/Control.java index d3d0e221f6..ad13881eb3 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/Control.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/Control.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ public interface Control extends Savable { * Creates a clone of the Control, the given Spatial is the cloned version * of the spatial to which this control is attached to. * - * @param spatial + * @param spatial the Spatial to be controlled by the clone * @return A clone of this control for the spatial * @deprecated Use * {@link com.jme3.util.clone.JmeCloneable#cloneFields(com.jme3.util.clone.Cloner, java.lang.Object)} @@ -74,8 +74,8 @@ public interface Control extends Savable { * Should be called prior to queuing the spatial by the RenderManager. This * should not be called from user code. * - * @param rm - * @param vp + * @param rm the caller (not null) + * @param vp the relevant ViewPort (not null) */ public void render(RenderManager rm, ViewPort vp); } diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java index 1cdfdaf07c..3c0619b912 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/LightControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ import com.jme3.light.Light; import com.jme3.light.PointLight; import com.jme3.light.SpotLight; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; @@ -48,50 +49,77 @@ import java.io.IOException; /** - * This Control maintains a reference to a Camera, - * which will be synched with the position (worldTranslation) - * of the current spatial. - * @author tim + * `LightControl` synchronizes the world transformation (position and/or + * direction) of a `Light` with its attached `Spatial`. This control allows + * a light to follow a spatial or vice-versa, depending on the chosen + * {@link ControlDirection}. + *

                + * This is particularly useful for attaching lights to animated characters, + * moving vehicles, or dynamically controlled objects. + *

                + * + * @author Tim + * @author Markil 3 + * @author capdevon */ public class LightControl extends AbstractControl { - private static final String CONTROL_DIR_NAME = "controlDir"; - private static final String LIGHT_NAME = "light"; - + /** + * Defines the direction of synchronization between the light and the spatial. + */ public enum ControlDirection { - /** - * Means, that the Light's transform is "copied" - * to the Transform of the Spatial. + * The light's transform is copied to the spatial's transform. */ LightToSpatial, /** - * Means, that the Spatial's transform is "copied" - * to the Transform of the light. + * The spatial's transform is copied to the light's transform. */ - SpatialToLight; + SpatialToLight + } + + /** + * Represents the local axis of the spatial (X, Y, or Z) to be used + * for determining the light's direction when `ControlDirection` is + * `SpatialToLight`. + */ + public enum Axis { + X, Y, Z } private Light light; private ControlDirection controlDir = ControlDirection.SpatialToLight; + private Axis axisRotation = Axis.Z; + private boolean invertAxisDirection = false; /** - * Constructor used for Serialization. + * For serialization only. Do not use. */ public LightControl() { } /** + * Creates a new `LightControl` that synchronizes the light's transform to the spatial. + * * @param light The light to be synced. + * @throws IllegalArgumentException if the light type is not supported + * (only Point, Directional, and Spot lights are supported). */ public LightControl(Light light) { + validateSupportedLightType(light); this.light = light; } /** + * Creates a new `LightControl` with a specified synchronization direction. + * * @param light The light to be synced. + * @param controlDir The direction of synchronization (SpatialToLight or LightToSpatial). + * @throws IllegalArgumentException if the light type is not supported + * (only Point, Directional, and Spot lights are supported). */ public LightControl(Light light, ControlDirection controlDir) { + validateSupportedLightType(light); this.light = light; this.controlDir = controlDir; } @@ -101,6 +129,7 @@ public Light getLight() { } public void setLight(Light light) { + validateSupportedLightType(light); this.light = light; } @@ -112,64 +141,143 @@ public void setControlDir(ControlDirection controlDir) { this.controlDir = controlDir; } - // fields used, when inversing ControlDirection: + public Axis getAxisRotation() { + return axisRotation; + } + + public void setAxisRotation(Axis axisRotation) { + this.axisRotation = axisRotation; + } + + public boolean isInvertAxisDirection() { + return invertAxisDirection; + } + + public void setInvertAxisDirection(boolean invertAxisDirection) { + this.invertAxisDirection = invertAxisDirection; + } + + private void validateSupportedLightType(Light light) { + if (light == null) { + return; + } + + switch (light.getType()) { + case Point: + case Directional: + case Spot: + // These types are supported, validation passes. + break; + default: + throw new IllegalArgumentException( + "Unsupported Light type: " + light.getType()); + } + } + @Override protected void controlUpdate(float tpf) { - if (spatial != null && light != null) { - switch (controlDir) { - case SpatialToLight: - spatialToLight(light); - break; - case LightToSpatial: - lightToSpatial(light); - break; - } + if (light == null) { + return; + } + + switch (controlDir) { + case SpatialToLight: + spatialToLight(light); + break; + case LightToSpatial: + lightToSpatial(light); + break; } } + /** + * Updates the light's position and/or direction to match the spatial's + * world transformation. + * + * @param light The light whose properties will be set. + */ private void spatialToLight(Light light) { + TempVars vars = TempVars.get(); - final Vector3f worldTranslation = spatial.getWorldTranslation(); + final Vector3f worldPosition = vars.vect1; + worldPosition.set(spatial.getWorldTranslation()); - if (light instanceof PointLight) { - ((PointLight) light).setPosition(worldTranslation); - return; + final Vector3f lightDirection = vars.vect2; + spatial.getWorldRotation().getRotationColumn(axisRotation.ordinal(), lightDirection); + if (invertAxisDirection) { + lightDirection.negateLocal(); } - final TempVars vars = TempVars.get(); - final Vector3f vec = vars.vect1; + if (light instanceof PointLight) { + ((PointLight) light).setPosition(worldPosition); - if (light instanceof DirectionalLight) { - ((DirectionalLight) light).setDirection(vec.set(worldTranslation).multLocal(-1.0f)); - } + } else if (light instanceof DirectionalLight) { + ((DirectionalLight) light).setDirection(lightDirection); - if (light instanceof SpotLight) { - final SpotLight spotLight = (SpotLight) light; - spotLight.setPosition(worldTranslation); - spotLight.setDirection(spatial.getWorldRotation().multLocal(vec.set(Vector3f.UNIT_Y).multLocal(-1))); + } else if (light instanceof SpotLight) { + SpotLight sl = (SpotLight) light; + sl.setPosition(worldPosition); + sl.setDirection(lightDirection); } - vars.release(); } + /** + * Updates the spatial's local transformation (position and/or rotation) + * to match the light's world transformation. + * + * @param light The light from which properties will be read. + */ private void lightToSpatial(Light light) { TempVars vars = TempVars.get(); + Vector3f lightPosition = vars.vect1; + Vector3f lightDirection = vars.vect2; + Quaternion rotation = vars.quat1; + boolean rotateSpatial = false; + boolean translateSpatial = false; + if (light instanceof PointLight) { + PointLight pl = (PointLight) light; + lightPosition.set(pl.getPosition()); + translateSpatial = true; - PointLight pLight = (PointLight) light; + } else if (light instanceof DirectionalLight) { + DirectionalLight dl = (DirectionalLight) light; + lightDirection.set(dl.getDirection()); + if (invertAxisDirection) { + lightDirection.negateLocal(); + } + rotateSpatial = true; - Vector3f vecDiff = vars.vect1.set(pLight.getPosition()).subtractLocal(spatial.getWorldTranslation()); - spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation())); + } else if (light instanceof SpotLight) { + SpotLight sl = (SpotLight) light; + lightPosition.set(sl.getPosition()); + lightDirection.set(sl.getDirection()); + if (invertAxisDirection) { + lightDirection.negateLocal(); + } + translateSpatial = true; + rotateSpatial = true; } - if (light instanceof DirectionalLight) { - DirectionalLight dLight = (DirectionalLight) light; - vars.vect1.set(dLight.getDirection()).multLocal(-1.0f); - Vector3f vecDiff = vars.vect1.subtractLocal(spatial.getWorldTranslation()); - spatial.setLocalTranslation(vecDiff.addLocal(spatial.getLocalTranslation())); + // Transform light's world properties to spatial's parent's local space + if (spatial.getParent() != null) { + // Get inverse of parent's world matrix + spatial.getParent().getLocalToWorldMatrix(vars.tempMat4).invertLocal(); + vars.tempMat4.rotateVect(lightPosition); + vars.tempMat4.translateVect(lightPosition); + vars.tempMat4.rotateVect(lightDirection); + } + + // Apply transformed properties to spatial's local transformation + if (rotateSpatial) { + rotation.lookAt(lightDirection, Vector3f.UNIT_Y).normalizeLocal(); + spatial.setLocalRotation(rotation); + } + if (translateSpatial) { + spatial.setLocalTranslation(lightPosition); } vars.release(); - //TODO add code for Spot light here when it's done } @Override @@ -187,15 +295,31 @@ public void cloneFields(final Cloner cloner, final Object original) { public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule ic = im.getCapsule(this); - controlDir = ic.readEnum(CONTROL_DIR_NAME, ControlDirection.class, ControlDirection.SpatialToLight); - light = (Light) ic.readSavable(LIGHT_NAME, null); + light = (Light) ic.readSavable("light", null); + controlDir = ic.readEnum("controlDir", ControlDirection.class, ControlDirection.SpatialToLight); + axisRotation = ic.readEnum("axisRotation", Axis.class, Axis.Z); + invertAxisDirection = ic.readBoolean("invertAxisDirection", false); } @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); - oc.write(controlDir, CONTROL_DIR_NAME, ControlDirection.SpatialToLight); - oc.write(light, LIGHT_NAME, null); + oc.write(light, "light", null); + oc.write(controlDir, "controlDir", ControlDirection.SpatialToLight); + oc.write(axisRotation, "axisRotation", Axis.Z); + oc.write(invertAxisDirection, "invertAxisDirection", false); + } + + @Override + public String toString() { + return getClass().getSimpleName() + + "[light=" + (light != null ? light.getType() : null) + + ", controlDir=" + controlDir + + ", axisRotation=" + axisRotation + + ", invertAxisDirection=" + invertAxisDirection + + ", enabled=" + enabled + + ", spatial=" + spatial + + "]"; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java index fd74de5d17..fc03a584c8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/LodControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -153,6 +153,7 @@ public Object jmeClone() { protected void controlUpdate(float tpf) { } + @Override protected void controlRender(RenderManager rm, ViewPort vp) { BoundingVolume bv = spatial.getWorldBound(); diff --git a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java index aa84faf1cb..27b30c7b78 100644 --- a/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java +++ b/jme3-core/src/main/java/com/jme3/scene/control/UpdateControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,9 +59,13 @@ public class UpdateControl extends AbstractControl { /** * Enqueues a task/callable object to execute in the jME3 * rendering thread. + * + * @param type of result returned by the Callable + * @param callable the Callable to run + * @return a new instance */ public Future enqueue(Callable callable) { - AppTask task = new AppTask(callable); + AppTask task = new AppTask<>(callable); taskQueue.add(task); return task; } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java index 24efb597ec..8f46298693 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Arrow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,8 +47,8 @@ */ public class Arrow extends Mesh { - private Quaternion tempQuat = new Quaternion(); - private Vector3f tempVec = new Vector3f(); + private final Quaternion tempQuat = new Quaternion(); + private final Vector3f tempVec = new Vector3f(); private static final float[] positions = new float[]{ 0, 0, 0, diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java b/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java index f9e5e119eb..d2947e52a9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/Grid.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,7 @@ /** * Simple grid shape. - * + * * @author Kirill Vainer */ public class Grid extends Mesh { @@ -50,9 +50,10 @@ public Grid() { /** * Creates a grid debug shape. - * @param xLines - * @param yLines - * @param lineDist + * + * @param xLines number of lines parallel to the X axis + * @param yLines number of lines parallel to the Y axis + * @param lineDist the separation between consecutive lines (in world units) */ public Grid(int xLines, int yLines, float lineDist){ xLines -= 2; @@ -75,12 +76,12 @@ public Grid(int xLines, int yLines, float lineDist){ fpb.put(xLineLen).put(0).put(y); // indices - sib.put( (short) (curIndex++) ); - sib.put( (short) (curIndex++) ); + sib.put((short) (curIndex++)); + sib.put((short) (curIndex++)); } // add lines along Y - for (int i = 0; i < yLines + 2; i++){ + for (int i = 0; i < yLines + 2; i++) { float x = (i) * lineDist; // positions @@ -88,8 +89,8 @@ public Grid(int xLines, int yLines, float lineDist){ fpb.put(x).put(0).put(yLineLen); // indices - sib.put( (short) (curIndex++) ); - sib.put( (short) (curIndex++) ); + sib.put((short) (curIndex++)); + sib.put((short) (curIndex++)); } fpb.flip(); @@ -97,12 +98,11 @@ public Grid(int xLines, int yLines, float lineDist){ setBuffer(Type.Position, 3, fpb); setBuffer(Type.Index, 2, sib); - + setMode(Mode.Lines); updateBound(); updateCounts(); setStatic(); } - } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java index f764304c73..f8f450ab47 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonDebugger.java @@ -32,11 +32,14 @@ package com.jme3.scene.debug; import com.jme3.animation.Skeleton; +import com.jme3.export.JmeImporter; import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.util.clone.Cloner; +import java.io.IOException; import java.util.Map; /** @@ -83,16 +86,20 @@ public SkeletonDebugger(String name, Skeleton skeleton, Map bone wires = new SkeletonWire(skeleton, boneLengths); points = new SkeletonPoints(skeleton, boneLengths); - this.attachChild(new Geometry(name + "_wires", wires)); - this.attachChild(new Geometry(name + "_points", points)); + this.attachChild(new Geometry(getGeometryName("_wires"), wires)); + this.attachChild(new Geometry(getGeometryName("_points"), points)); if (boneLengths != null) { interBoneWires = new SkeletonInterBoneWire(skeleton, boneLengths); - this.attachChild(new Geometry(name + "_interwires", interBoneWires)); + this.attachChild(new Geometry(getGeometryName("_interwires"), interBoneWires)); } this.setQueueBucket(Bucket.Transparent); } + private String getGeometryName(String suffix) { + return name + suffix; + } + @Override public void updateLogicalState(float tpf) { super.updateLogicalState(tpf); @@ -132,4 +139,24 @@ public void cloneFields(Cloner cloner, Object original) { this.points = cloner.clone(points); this.interBoneWires = cloner.clone(interBoneWires); } + + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + + // Find our stuff + wires = getMesh("_wires"); + points = getMesh("_points"); + interBoneWires = getMesh("_interwires"); + } + + @SuppressWarnings("unchecked") + private T getMesh(String suffix) { + Geometry child = (Geometry)getChild(getGeometryName(suffix)); + if(child != null) { + return (T) child.getMesh(); + } + + return null; + } } \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java index 7367ff5124..6db686bed8 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonInterBoneWire.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,10 @@ import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; @@ -43,9 +47,11 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.util.HashMap; /** - * A class that displays a dotted line between a bone tail and its childrens' heads. + * A class that displays a dotted line between a bone tail and its children's heads. * * @author Marcin Roguski (Kaelthas) */ @@ -84,7 +90,13 @@ public SkeletonInterBoneWire(Skeleton skeleton, Map boneLengths) } /** - * The method updates the geometry according to the poitions of the bones. + * For serialization only. Do not use. + */ + protected SkeletonInterBoneWire() { + } + + /** + * This method updates the geometry according to the positions of the bones. */ public void updateGeometry() { VertexBuffer vb = this.getBuffer(Type.Position); @@ -113,7 +125,66 @@ public void updateGeometry() { } /** - * Th method couns the connections between bones. + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + connectionsAmount = capsule.readInt("connectionsAmount", 1); + skeleton = (Skeleton) capsule.readSavable("skeleton", null); + + int[] blKeys = capsule.readIntArray("blKeys", null); + float[] blValues = capsule.readFloatArray("blValues", null); + if (blKeys == null) { + boneLengths = null; + } else { + assert blValues.length == blKeys.length; + int numLengths = blKeys.length; + boneLengths = new HashMap<>(numLengths); + for (int i = 0; i < numLengths; ++i) { + boneLengths.put(blKeys[i], blValues[i]); + } + } + } + + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(connectionsAmount, "connectionsAmount", 1); + capsule.write(skeleton, "skeleton", null); + + if (boneLengths != null) { + int numLengths = boneLengths.size(); + int[] blKeys = new int[numLengths]; + float[] blValues = new float[numLengths]; + int i = 0; + for (Map.Entry entry : boneLengths.entrySet()) { + blKeys[i] = entry.getKey(); + blValues[i] = entry.getValue(); + ++i; + } + capsule.write(blKeys, "blKeys", null); + capsule.write(blValues, "blValues", null); + } + } + + /** + * This method counts the connections between bones. * @param bone * the bone where counting starts */ diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java index 4d95d34b22..878b611673 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonPoints.java @@ -33,6 +33,10 @@ import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; @@ -40,8 +44,10 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; +import java.io.IOException; import java.nio.FloatBuffer; +import java.util.HashMap; import java.util.Map; /** @@ -88,6 +94,12 @@ public SkeletonPoints(Skeleton skeleton, Map boneLengths) { } + /** + * For serialization only. Do not use. + */ + protected SkeletonPoints() { + } + /** * The method updates the geometry according to the positions of the bones. */ @@ -110,4 +122,61 @@ public void updateGeometry() { this.updateBound(); } + + /** + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + skeleton = (Skeleton) capsule.readSavable("skeleton", null); + + int[] blKeys = capsule.readIntArray("blKeys", null); + float[] blValues = capsule.readFloatArray("blValues", null); + if (blKeys == null) { + boneLengths = null; + } else { + assert blValues.length == blKeys.length; + int numLengths = blKeys.length; + boneLengths = new HashMap<>(numLengths); + for (int i = 0; i < numLengths; ++i) { + boneLengths.put(blKeys[i], blValues[i]); + } + } + } + + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(skeleton, "skeleton", null); + + if (boneLengths != null) { + int numLengths = boneLengths.size(); + int[] blKeys = new int[numLengths]; + float[] blValues = new float[numLengths]; + int i = 0; + for (Map.Entry entry : boneLengths.entrySet()) { + blKeys[i] = entry.getKey(); + blValues[i] = entry.getValue(); + ++i; + } + capsule.write(blKeys, "blKeys", null); + capsule.write(blValues, "blValues", null); + } + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java index b907a4c148..afcecce778 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/SkeletonWire.java @@ -37,6 +37,10 @@ import com.jme3.animation.Bone; import com.jme3.animation.Skeleton; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; @@ -44,6 +48,8 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; +import java.io.IOException; +import java.util.HashMap; /** * The class that displays either wires between the bones' heads if no length data is supplied and @@ -112,7 +118,13 @@ public SkeletonWire(Skeleton skeleton, Map boneLengths) { } /** - * The method updates the geometry according to the poitions of the bones. + * For serialization only. Do not use. + */ + protected SkeletonWire() { + } + + /** + * This method updates the geometry according to the positions of the bones. */ public void updateGeometry() { VertexBuffer vb = this.getBuffer(Type.Position); @@ -135,7 +147,66 @@ public void updateGeometry() { } /** - * Th method couns the connections between bones. + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + numConnections = capsule.readInt("numConnections", 1); + skeleton = (Skeleton) capsule.readSavable("skeleton", null); + + int[] blKeys = capsule.readIntArray("blKeys", null); + float[] blValues = capsule.readFloatArray("blValues", null); + if (blKeys == null) { + boneLengths = null; + } else { + assert blValues.length == blKeys.length; + int numLengths = blKeys.length; + boneLengths = new HashMap<>(numLengths); + for (int i = 0; i < numLengths; ++i) { + boneLengths.put(blKeys[i], blValues[i]); + } + } + } + + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(numConnections, "numConnections", 1); + capsule.write(skeleton, "skeleton", null); + + if (boneLengths != null) { + int numLengths = boneLengths.size(); + int[] blKeys = new int[numLengths]; + float[] blValues = new float[numLengths]; + int i = 0; + for (Map.Entry entry : boneLengths.entrySet()) { + blKeys[i] = entry.getKey(); + blValues[i] = entry.getValue(); + ++i; + } + capsule.write(blKeys, "blKeys", null); + capsule.write(blValues, "blValues", null); + } + } + + /** + * This method counts the connections between bones. * @param bone * the bone where counting starts */ diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java index d4e4e1d585..38593898c1 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireBox.java @@ -44,11 +44,11 @@ public class WireBox extends Mesh { - public WireBox(){ + public WireBox() { this(1,1,1); } - - public WireBox(float xExt, float yExt, float zExt){ + + public WireBox(float xExt, float yExt, float zExt) { updatePositions(xExt,yExt,zExt); setBuffer(Type.Index, 2, new short[]{ @@ -73,15 +73,15 @@ public WireBox(float xExt, float yExt, float zExt){ updateCounts(); } - public void updatePositions(float xExt, float yExt, float zExt){ + public void updatePositions(float xExt, float yExt, float zExt) { VertexBuffer pvb = getBuffer(Type.Position); FloatBuffer pb; - if (pvb == null){ + if (pvb == null) { pvb = new VertexBuffer(Type.Position); pb = BufferUtils.createVector3Buffer(8); pvb.setupData(Usage.Dynamic, 3, Format.Float, pb); setBuffer(pvb); - }else{ + } else { pb = (FloatBuffer) pvb.getData(); pvb.updateData(pb); } @@ -101,7 +101,7 @@ public void updatePositions(float xExt, float yExt, float zExt){ ); updateBound(); } - + /** * Create a geometry suitable for visualizing the specified bounding box. * diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java index 7fabbc5035..9a2de74927 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireFrustum.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,68 +32,160 @@ package com.jme3.scene.debug; import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; +import com.jme3.shadow.ShadowUtil; import com.jme3.util.BufferUtils; import java.nio.FloatBuffer; +/** + * A specialized Mesh that renders a camera frustum as a wireframe. + * This class extends jME3's Mesh and is designed to visually represent + * the viewing volume of a camera, which can be useful for debugging + * or visualization purposes. + *

                + * The frustum is defined by eight points: four for the near plane + * and four for the far plane. These points are connected by lines + * to form a wireframe cube-like structure. + */ public class WireFrustum extends Mesh { - public WireFrustum(Vector3f[] points){ - initGeom(this, points); + /** + * For Serialization only. Do not use. + */ + protected WireFrustum() { } - public static Mesh makeFrustum(Vector3f[] points){ - Mesh m = new Mesh(); - initGeom(m, points); - return m; + /** + * Constructs a new `WireFrustum` mesh using the specified frustum corner points. + * The points should represent the 8 corners of the frustum. + * The expected order of points is typically: + * 0-3: Near plane (e.g., bottom-left, bottom-right, top-right, top-left) + * 4-7: Far plane (e.g., bottom-left, bottom-right, top-right, top-left) + * + * @param points An array of 8 `Vector3f` objects representing the frustum's corners. + * If the array is null or does not contain 8 points, an + * `IllegalArgumentException` will be thrown. + */ + public WireFrustum(Vector3f[] points) { + if (points == null || points.length != 8) { + throw new IllegalArgumentException("Frustum points array must not be null and must contain 8 points."); + } + setGeometryData(points); } - private static void initGeom(Mesh m, Vector3f[] points) { - if (points != null) - m.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); + /** + * Initializes the mesh's geometric data, setting up the vertex positions and indices. + * This method is called during the construction of the `WireFrustum`. + * + * @param points The 8 `Vector3f` points defining the frustum's corners. + */ + private void setGeometryData(Vector3f[] points) { + // Set vertex positions + setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); - m.setBuffer(Type.Index, 2, + // Set indices to draw lines connecting the frustum corners + // The indices define 12 lines: 4 for near plane, 4 for far plane, and 4 connecting near to far. + setBuffer(Type.Index, 2, new short[]{ + // Near plane 0, 1, 1, 2, 2, 3, 3, 0, + // Far plane 4, 5, 5, 6, 6, 7, 7, 4, + // Connecting lines (near to far) 0, 4, 1, 5, 2, 6, 3, 7, } ); - m.getBuffer(Type.Index).setUsage(Usage.Static); - m.setMode(Mode.Lines); + getBuffer(Type.Index).setUsage(Usage.Static); + setMode(Mode.Lines); + updateBound(); } - public void update(Vector3f[] points){ + /** + * Updates the vertex positions of the existing `WireFrustum` mesh. + * This is more efficient than creating a new `WireFrustum` instance + * if only the frustum's position or orientation changes. + * + * @param points An array of 8 `Vector3f` objects representing the new frustum's corners. + * If the array is null or does not contain 8 points, an + * `IllegalArgumentException` will be thrown. + */ + public void update(Vector3f[] points) { + if (points == null || points.length != 8) { + throw new IllegalArgumentException("Frustum points array must not be null and must contain 8 points."); + } + VertexBuffer vb = getBuffer(Type.Position); - if (vb == null){ - setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(points)); + if (vb == null) { + // If for some reason the position buffer is missing, re-create it. + // This case should ideally not happen if the object is constructed properly. + setGeometryData(points); return; } - FloatBuffer b = BufferUtils.createFloatBuffer(points); - FloatBuffer a = (FloatBuffer) vb.getData(); - b.rewind(); - a.rewind(); - a.put(b); - a.rewind(); + // Create a new FloatBuffer from the updated points + FloatBuffer newBuff = BufferUtils.createFloatBuffer(points); + // Get the existing FloatBuffer from the VertexBuffer + FloatBuffer currBuff = (FloatBuffer) vb.getData(); + + currBuff.clear(); // Clear + currBuff.put(newBuff); // Copy + currBuff.rewind(); // Rewind + + // Update the VertexBuffer with the modified FloatBuffer data + vb.updateData(currBuff); - vb.updateData(a); - + // Update the mesh's bounding volume to reflect the new vertex positions updateBound(); } + /** + * A static factory method to create a new `WireFrustum` mesh. + * This method provides a cleaner way to instantiate a `WireFrustum`. + * + * @param points An array of 8 `Vector3f` objects representing the frustum's corners. + * @return A new `WireFrustum` instance. + */ + public static Mesh makeFrustum(Vector3f[] points) { + return new WireFrustum(points); + } + + /** + * Creates a `Geometry` object representing the wireframe frustum of a given camera. + * The frustum points are calculated based on the camera's current view settings. + * The returned `Geometry` can be directly attached to a scene graph. + * + * @param camera The `Camera` whose frustum is to be visualized. + * @return A `Geometry` object containing the `WireFrustum` mesh. + */ + public static Geometry makeGeometry(Camera camera) { + Vector3f[] frustumCorners = new Vector3f[8]; + for (int i = 0; i < 8; i++) { + frustumCorners[i] = new Vector3f(); + } + + Camera tempCam = camera.clone(); + tempCam.setLocation(new Vector3f(0, 0, 0)); + tempCam.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); + ShadowUtil.updateFrustumPoints2(tempCam, frustumCorners); + + WireFrustum mesh = new WireFrustum(frustumCorners); + return new Geometry("Viewing Frustum", mesh); + } + } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java b/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java index cf0244355b..72ee624bb0 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/WireSphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,13 +33,15 @@ import com.jme3.bounding.BoundingSphere; import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; import com.jme3.scene.Mesh; -import com.jme3.scene.Mesh.Mode; import com.jme3.scene.VertexBuffer; import com.jme3.scene.VertexBuffer.Format; import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.util.BufferUtils; + import java.nio.FloatBuffer; import java.nio.ShortBuffer; @@ -109,7 +111,7 @@ public void updatePositions(float radius) { /* * Update vertex positions for the great circle in the X-Y plane. */ - float rate = FastMath.TWO_PI / (float) samples; + float rate = FastMath.TWO_PI / samples; float angle = 0; for (int i = 0; i < samples; i++) { float x = radius * FastMath.cos(angle); @@ -130,7 +132,7 @@ public void updatePositions(float radius) { /* * Update vertex positions for 'zSamples' parallel circles. */ - float zRate = (radius * 2) / (float) (zSamples); + float zRate = (radius * 2) / zSamples; float zHeight = -radius + (zRate / 2f); float rb = 1f / zSamples; float b = rb / 2f; @@ -151,11 +153,25 @@ public void updatePositions(float radius) { /** * Create a WireSphere from a BoundingSphere * - * @param bsph - * BoundingSphere used to create the WireSphere - * + * @param bsph BoundingSphere used to create the WireSphere */ public void fromBoundingSphere(BoundingSphere bsph) { updatePositions(bsph.getRadius()); } + + /** + * Create a geometry suitable for visualizing the specified bounding sphere. + * + * @param bsph the bounding sphere (not null) + * @return a new Geometry instance in world space + */ + public static Geometry makeGeometry(BoundingSphere bsph) { + WireSphere mesh = new WireSphere(bsph.getRadius()); + Geometry result = new Geometry("bounding sphere", mesh); + + Vector3f center = bsph.getCenter(); + result.setLocalTranslation(center); + + return result; + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java index a3618b4442..d2adb0b0ee 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugAppState.java @@ -1,61 +1,145 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2025 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.debug.custom; -import com.jme3.anim.*; +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; import com.jme3.app.Application; import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; import com.jme3.collision.CollisionResults; +import com.jme3.input.InputManager; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; -import com.jme3.input.controls.*; -import com.jme3.light.DirectionalLight; -import com.jme3.math.*; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; -import com.jme3.scene.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.SceneGraphVisitorAdapter; +import com.jme3.scene.Spatial; +import com.jme3.scene.control.AbstractControl; +import com.jme3.util.TempVars; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; /** + * A debug application state for visualizing and interacting with JME3 armatures (skeletons). + * This state allows users to see the joints of an armature, select individual joints + * by clicking on them, and view their local and model transforms. + * It also provides a toggle to display non-deforming joints. + *

                + * This debug state operates on its own `ViewPort` and `debugNode` to prevent + * interference with the main scene's rendering. + * * @author Nehon + * @author capdevon */ public class ArmatureDebugAppState extends BaseAppState { + private static final Logger logger = Logger.getLogger(ArmatureDebugAppState.class.getName()); + + private static final String PICK_JOINT = "ArmatureDebugAppState_PickJoint"; + private static final String TOGGLE_JOINTS = "ArmatureDebugAppState_DisplayAllJoints"; + + /** + * The maximum delay for a mouse click to be registered as a single click. + */ public static final float CLICK_MAX_DELAY = 0.2f; - private Node debugNode = new Node("debugNode"); - private Map armatures = new HashMap<>(); - private Map selectedBones = new HashMap<>(); - private Application app; - private boolean displayAllJoints = false; + + private Node debugNode = new Node("ArmaturesDebugNode"); + private final Map armatures = new HashMap<>(); + private final List> selectionListeners = new ArrayList<>(); + private boolean displayNonDeformingJoints = false; private float clickDelay = -1; - Vector3f tmp = new Vector3f(); - Vector3f tmp2 = new Vector3f(); - ViewPort vp; + private ViewPort vp; + private Camera cam; + private InputManager inputManager; + private boolean showOnTop = true; + private boolean enableJointInfoLogging = true; @Override protected void initialize(Application app) { - vp = app.getRenderManager().createMainView("debug", app.getCamera()); + + inputManager = app.getInputManager(); + cam = app.getCamera(); + + vp = app.getRenderManager().createMainView("ArmatureDebugView", cam); vp.attachScene(debugNode); - vp.setClearDepth(true); - this.app = app; - for (ArmatureDebugger armatureDebugger : armatures.values()) { - armatureDebugger.initialize(app.getAssetManager(), app.getCamera()); - } - app.getInputManager().addListener(actionListener, "shoot", "toggleJoints"); - app.getInputManager().addMapping("shoot", new MouseButtonTrigger(MouseInput.BUTTON_LEFT), new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); - app.getInputManager().addMapping("toggleJoints", new KeyTrigger(KeyInput.KEY_F10)); + vp.setClearDepth(showOnTop); - debugNode.addLight(new DirectionalLight(new Vector3f(-1f, -1f, -1f).normalizeLocal())); + for (ArmatureDebugger debugger : armatures.values()) { + debugger.initialize(app.getAssetManager(), cam); + } - debugNode.addLight(new DirectionalLight(new Vector3f(1f, 1f, 1f).normalizeLocal(), new ColorRGBA(0.5f, 0.5f, 0.5f, 1.0f))); + // Initially disable the viewport until the state is enabled vp.setEnabled(false); + + registerInput(); + } + + private void registerInput() { + inputManager.addMapping(PICK_JOINT, new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addMapping(TOGGLE_JOINTS, new KeyTrigger(KeyInput.KEY_F10)); + inputManager.addListener(actionListener, PICK_JOINT, TOGGLE_JOINTS); + } + + private void unregisterInput() { + inputManager.deleteMapping(PICK_JOINT); + inputManager.deleteMapping(TOGGLE_JOINTS); + inputManager.removeListener(actionListener); } @Override protected void cleanup(Application app) { - + unregisterInput(); + app.getRenderManager().removeMainView(vp); + // Clear maps to release references + armatures.clear(); + selectionListeners.clear(); + debugNode.detachAllChildren(); } @Override @@ -74,135 +158,317 @@ public void update(float tpf) { clickDelay += tpf; } debugNode.updateLogicalState(tpf); - debugNode.updateGeometricState(); + } + @Override + public void render(RenderManager rm) { + debugNode.updateGeometricState(); } - public ArmatureDebugger addArmatureFrom(SkinningControl skinningControl) { - Armature armature = skinningControl.getArmature(); - Spatial forSpatial = skinningControl.getSpatial(); - return addArmatureFrom(armature, forSpatial); + /** + * Adds an ArmatureDebugger for the armature associated with a given SkinningControl. + * + * @param skControl The SkinningControl whose armature needs to be debugged. + * @return The newly created or existing ArmatureDebugger for the given armature. + */ + public ArmatureDebugger addArmatureFrom(SkinningControl skControl) { + return addArmatureFrom(skControl.getArmature(), skControl.getSpatial()); } - public ArmatureDebugger addArmatureFrom(Armature armature, Spatial forSpatial) { + /** + * Adds an ArmatureDebugger for a specific Armature, associating it with a Spatial. + * If an ArmatureDebugger for this armature already exists, it is returned. + * Otherwise, a new ArmatureDebugger is created, initialized, and attached to the debug node. + * + * @param armature The Armature to debug. + * @param sp The Spatial associated with this armature (used for determining world transform and deforming joints). + * @return The newly created or existing ArmatureDebugger for the given armature. + */ + public ArmatureDebugger addArmatureFrom(Armature armature, Spatial sp) { - ArmatureDebugger ad = armatures.get(armature); - if(ad != null){ - return ad; + ArmatureDebugger debugger = armatures.get(armature); + if (debugger != null) { + return debugger; } - JointInfoVisitor visitor = new JointInfoVisitor(armature); - forSpatial.depthFirstTraversal(visitor); + // Use a visitor to find joints that actually deform the mesh + JointInfoVisitor jointVisitor = new JointInfoVisitor(armature); + sp.depthFirstTraversal(jointVisitor); - ad = new ArmatureDebugger(forSpatial.getName() + "_Armature", armature, visitor.deformingJoints); - ad.setLocalTransform(forSpatial.getWorldTransform()); - if (forSpatial instanceof Node) { + Spatial target = sp; + + if (sp instanceof Node) { List geoms = new ArrayList<>(); - findGeoms((Node) forSpatial, geoms); + collectGeometries((Node) sp, geoms); if (geoms.size() == 1) { - ad.setLocalTransform(geoms.get(0).getWorldTransform()); + target = geoms.get(0); } } - armatures.put(armature, ad); - debugNode.attachChild(ad); + + // Create a new ArmatureDebugger + debugger = new ArmatureDebugger(sp.getName() + "_ArmatureDebugger", armature, jointVisitor.deformingJoints); + debugger.addControl(new ArmatureDebuggerLink(target)); + + // Store and attach the new debugger + armatures.put(armature, debugger); + debugNode.attachChild(debugger); + + // If the AppState is already initialized, initialize the new ArmatureDebugger immediately if (isInitialized()) { - ad.initialize(app.getAssetManager(), app.getCamera()); + AssetManager assetManager = getApplication().getAssetManager(); + debugger.initialize(assetManager, cam); } - return ad; + return debugger; } - private void findGeoms(Node node, List geoms) { - for (Spatial spatial : node.getChildren()) { - if (spatial instanceof Geometry) { - geoms.add((Geometry) spatial); - } else if (spatial instanceof Node) { - findGeoms((Node) spatial, geoms); + /** + * Recursively finds all `Geometry` instances within a given `Node` and its children. + * + * @param node The starting `Node` to search from. + * @param geometries The list to which found `Geometry` instances will be added. + */ + private void collectGeometries(Node node, List geometries) { + for (Spatial s : node.getChildren()) { + if (s instanceof Geometry) { + geometries.add((Geometry) s); + } else if (s instanceof Node) { + collectGeometries((Node) s, geometries); } } } - private ActionListener actionListener = new ActionListener() { + /** + * The ActionListener implementation to handle input events. + * Specifically, it processes mouse clicks for joint selection and + * the F10 key press for toggling display of all joints. + */ + private final ActionListener actionListener = new ActionListener() { + + private final CollisionResults results = new CollisionResults(); + + @Override public void onAction(String name, boolean isPressed, float tpf) { - if (name.equals("shoot") && isPressed) { - clickDelay = 0; + if (!isEnabled()) { + return; } - if (name.equals("shoot") && !isPressed && clickDelay < CLICK_MAX_DELAY) { - Vector2f click2d = app.getInputManager().getCursorPosition(); - CollisionResults results = new CollisionResults(); - - Vector3f click3d = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 0f, tmp); - Vector3f dir = app.getCamera().getWorldCoordinates(new Vector2f(click2d.x, click2d.y), 1f, tmp2).subtractLocal(click3d); - Ray ray = new Ray(click3d, dir); - debugNode.collideWith(ray, results); - - if (results.size() == 0) { - for (ArmatureDebugger ad : armatures.values()) { - ad.select(null); - } - return; - } - - // The closest result is the target that the player picked: - Geometry target = results.getClosestCollision().getGeometry(); - for (ArmatureDebugger ad : armatures.values()) { - Joint selectedjoint = ad.select(target); - if (selectedjoint != null) { - selectedBones.put(ad.getArmature(), selectedjoint); - System.err.println("-----------------------"); - System.err.println("Selected Joint : " + selectedjoint.getName() + " in armature " + ad.getName()); - System.err.println("Root Bone : " + (selectedjoint.getParent() == null)); - System.err.println("-----------------------"); - System.err.println("Local translation: " + selectedjoint.getLocalTranslation()); - System.err.println("Local rotation: " + selectedjoint.getLocalRotation()); - System.err.println("Local scale: " + selectedjoint.getLocalScale()); - System.err.println("---"); - System.err.println("Model translation: " + selectedjoint.getModelTransform().getTranslation()); - System.err.println("Model rotation: " + selectedjoint.getModelTransform().getRotation()); - System.err.println("Model scale: " + selectedjoint.getModelTransform().getScale()); - System.err.println("---"); - System.err.println("Bind inverse Transform: "); - System.err.println(selectedjoint.getInverseModelBindMatrix()); - return; + + if (name.equals(PICK_JOINT)) { + if (isPressed) { + // Start counting click delay on mouse press + clickDelay = 0; + + } else if (clickDelay < CLICK_MAX_DELAY) { + // Process click only if it's a quick release (not a hold) + Ray ray = screenPointToRay(cam, inputManager.getCursorPosition()); + results.clear(); + debugNode.collideWith(ray, results); + + if (results.size() == 0) { + // If no collision, deselect all joints in all armatures + for (ArmatureDebugger ad : armatures.values()) { + ad.select(null); + } + } else { + // Get the closest geometry hit by the ray + Geometry target = results.getClosestCollision().getGeometry(); + logger.log(Level.INFO, "Pick: {0}", target); + + for (ArmatureDebugger ad : armatures.values()) { + Joint selectedjoint = ad.select(target); + + if (selectedjoint != null) { + // If a joint was selected, notify it and print its properties + notifySelectionListeners(selectedjoint); + printJointInfo(selectedjoint, ad); + break; + } + } } } } - if (name.equals("toggleJoints") && isPressed) { - displayAllJoints = !displayAllJoints; + else if (name.equals(TOGGLE_JOINTS) && isPressed) { + displayNonDeformingJoints = !displayNonDeformingJoints; for (ArmatureDebugger ad : armatures.values()) { - ad.displayNonDeformingJoint(displayAllJoints); + ad.displayNonDeformingJoint(displayNonDeformingJoints); } } } + + private void printJointInfo(Joint selectedJoint, ArmatureDebugger ad) { + if (enableJointInfoLogging) { + String info = "\n-----------------------\n" + + "Selected Joint : " + selectedJoint.getName() + " in armature " + ad.getName() + "\n" + + "Root Bone : " + (selectedJoint.getParent() == null) + "\n" + + "-----------------------\n" + + "Local translation: " + selectedJoint.getLocalTranslation() + "\n" + + "Local rotation: " + selectedJoint.getLocalRotation() + "\n" + + "Local scale: " + selectedJoint.getLocalScale() + "\n" + + "---\n" + + "Model translation: " + selectedJoint.getModelTransform().getTranslation() + "\n" + + "Model rotation: " + selectedJoint.getModelTransform().getRotation() + "\n" + + "Model scale: " + selectedJoint.getModelTransform().getScale() + "\n" + + "---\n" + + "Bind inverse Transform: \n" + + selectedJoint.getInverseModelBindMatrix(); + + logger.log(Level.INFO, info); + } + } + + /** + * Creates a `Ray` from a 2D screen point (e.g., mouse cursor position). + * + * @param cam The camera to use for ray projection. + * @param screenPoint The 2D screen coordinates. + * @return A `Ray` originating from the near plane and extending into the scene. + */ + private Ray screenPointToRay(Camera cam, Vector2f screenPoint) { + TempVars vars = TempVars.get(); + Vector3f nearPoint = vars.vect1; + Vector3f farPoint = vars.vect2; + + // Get the world coordinates for the near and far points + cam.getWorldCoordinates(screenPoint, 0, nearPoint); + cam.getWorldCoordinates(screenPoint, 1, farPoint); + + // Calculate direction and normalize + Vector3f direction = farPoint.subtractLocal(nearPoint).normalizeLocal(); + Ray ray = new Ray(nearPoint, direction); + + vars.release(); + return ray; + } }; -// public Map getSelectedBones() { -// return selectedBones; -// } + /** + * Notifies all registered {@code Consumer} listeners about the selected joint. + * + * @param selectedJoint The joint that was selected. + */ + private void notifySelectionListeners(Joint selectedJoint) { + for (Consumer listener : selectionListeners) { + listener.accept(selectedJoint); + } + } + + /** + * Adds a listener that will be notified when a joint is selected. + * + * @param listener The {@code Consumer} listener to add. + */ + public void addSelectionListener(Consumer listener) { + selectionListeners.add(listener); + } + + /** + * Removes a previously added selection listener. + * + * @param listener The {@code Consumer} listener to remove. + */ + public void removeSelectionListener(Consumer listener) { + selectionListeners.remove(listener); + } - public Node getDebugNode() { - return debugNode; + /** + * Clears all registered selection listeners. + */ + public void clearSelectionListeners() { + selectionListeners.clear(); } - public void setDebugNode(Node debugNode) { - this.debugNode = debugNode; + /** + * Checks if the armature debug gizmos are set to always + * render on top of other scene geometry. + * + * @return true if gizmos always render on top, false otherwise. + */ + public boolean isShowOnTop() { + return showOnTop; } - private class JointInfoVisitor extends SceneGraphVisitorAdapter { + /** + * Sets whether armature debug gizmos should always + * render on top of other scene geometry. + * + * @param showOnTop true to always show gizmos on top, false to respect depth. + */ + public void setShowOnTop(boolean showOnTop) { + this.showOnTop = showOnTop; + if (vp != null) { + vp.setClearDepth(showOnTop); + } + } - List deformingJoints = new ArrayList<>(); - Armature armature; + /** + * Returns whether logging of detailed joint information to `System.err` is currently enabled. + * + * @return true if logging is enabled, false otherwise. + */ + public boolean isJointInfoLoggingEnabled() { + return enableJointInfoLogging; + } + /** + * Sets whether logging of detailed joint information to `System.err` should be enabled. + * + * @param enableJointInfoLogging true to enable logging, false to disable. + */ + public void setJointInfoLoggingEnabled(boolean enableJointInfoLogging) { + this.enableJointInfoLogging = enableJointInfoLogging; + } + + /** + * A utility visitor class to traverse the scene graph and identify + * which joints in a given armature are actually deforming a mesh. + */ + private static class JointInfoVisitor extends SceneGraphVisitorAdapter { + + private final List deformingJoints = new ArrayList<>(); + private final Armature armature; + + /** + * Constructs a JointInfoVisitor for a specific armature. + * + * @param armature The armature whose deforming joints are to be identified. + */ public JointInfoVisitor(Armature armature) { this.armature = armature; } + /** + * Visits a Geometry node in the scene graph. + * For each Geometry, it checks all joints in the associated armature + * to see if they influence this mesh. + * + * @param geo The Geometry node being visited. + */ @Override - public void visit(Geometry g) { + public void visit(Geometry geo) { for (Joint joint : armature.getJointList()) { - if (g.getMesh().isAnimatedByJoint(armature.getJointIndex(joint))) { + int index = armature.getJointIndex(joint); + if (geo.getMesh().isAnimatedByJoint(index)) { deformingJoints.add(joint); } } } + + } + + private static class ArmatureDebuggerLink extends AbstractControl { + + private final Spatial target; + + public ArmatureDebuggerLink(Spatial target) { + this.target = target; + } + + @Override + protected void controlUpdate(float tpf) { + spatial.setLocalTransform(target.getWorldTransform()); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) { + } } } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java index e06f3e248f..5bfaddb76f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureDebugger.java @@ -1,7 +1,5 @@ -package com.jme3.scene.debug.custom; - /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,9 +29,11 @@ * 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.debug.custom; import com.jme3.anim.Armature; import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; import com.jme3.asset.AssetManager; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; @@ -55,103 +55,176 @@ public class ArmatureDebugger extends Node { /** - * The lines of the bones or the wires between their heads. + * The node responsible for rendering the bones/wires and their outlines. */ private ArmatureNode armatureNode; - + /** + * The {@link Armature} instance being debugged. + */ private Armature armature; - + /** + * A node containing all {@link Geometry} objects representing the joint points. + */ private Node joints; + /** + * A node containing all {@link Geometry} objects representing the bone outlines. + */ private Node outlines; + /** + * A node containing all {@link Geometry} objects representing the bone wires/lines. + */ private Node wires; /** * The dotted lines between a bone's tail and the had of its children. Not * available if the length data was not provided. */ private ArmatureInterJointsWire interJointWires; - + /** + * Default constructor for `ArmatureDebugger`. + * Use {@link #ArmatureDebugger(String, Armature, List)} for a functional instance. + */ public ArmatureDebugger() { } /** - * Creates a debugger with no length data. The wires will be a connection - * between the bones' heads only. The points will show the bones' heads only - * and no dotted line of inter bones connection will be visible. + * Convenience constructor that creates an {@code ArmatureDebugger} and immediately + * initializes its materials based on the provided {@code AssetManager} + * and {@code SkinningControl}. + * + * @param assetManager The {@link AssetManager} used to load textures and materials + * for the debug visualization. + * @param skControl The {@link SkinningControl} from which to extract the + * {@link Armature} and its associated joints. + */ + public ArmatureDebugger(AssetManager assetManager, SkinningControl skControl) { + this(null, skControl.getArmature(), skControl.getArmature().getJointList()); + initialize(assetManager, null); + } + + /** + * Creates an `ArmatureDebugger` instance without explicit bone length data. + * In this configuration, the visual representation will consist of wires + * connecting the bone heads, and points representing the bone heads. + * No dotted lines for inter-bone connections will be visible. * - * @param name the name of the debugger's node - * @param armature the armature that will be shown + * @param name The name of this debugger's root node. + * @param armature The {@link Armature} to be visualized. + * @param deformingJoints A {@link List} of {@link Joint} objects that are + * considered deforming joints. */ public ArmatureDebugger(String name, Armature armature, List deformingJoints) { super(name); this.armature = armature; + // Ensure the armature's world transforms are up-to-date before visualization. armature.update(); + // Initialize the main container nodes for different visual elements. joints = new Node("joints"); outlines = new Node("outlines"); wires = new Node("bones"); this.attachChild(joints); this.attachChild(outlines); this.attachChild(wires); - Node ndJoints = new Node("non deforming Joints"); - Node ndOutlines = new Node("non deforming Joints outlines"); - Node ndWires = new Node("non deforming Joints wires"); - joints.attachChild(ndJoints); - outlines.attachChild(ndOutlines); - wires.attachChild(ndWires); - Node outlineDashed = new Node("Outlines Dashed"); - Node wiresDashed = new Node("Wires Dashed"); - wiresDashed.attachChild(new Node("dashed non defrom")); - outlineDashed.attachChild(new Node("dashed non defrom")); + + // Create child nodes specifically for non-deforming joints' visualization + joints.attachChild(new Node("NonDeformingJoints")); + outlines.attachChild(new Node("NonDeformingOutlines")); + wires.attachChild(new Node("NonDeformingWires")); + + Node outlineDashed = new Node("DashedOutlines"); + outlineDashed.attachChild(new Node("DashedNonDeformingOutlines")); outlines.attachChild(outlineDashed); + + Node wiresDashed = new Node("DashedWires"); + wiresDashed.attachChild(new Node("DashedNonDeformingWires")); wires.attachChild(wiresDashed); + // Initialize the core ArmatureNode which handles the actual mesh generation. armatureNode = new ArmatureNode(armature, joints, wires, outlines, deformingJoints); - this.attachChild(armatureNode); + // By default, non-deforming joints are hidden. displayNonDeformingJoint(false); } + /** + * Sets the visibility of non-deforming joints and their associated outlines and wires. + * + * @param display `true` to make non-deforming joints visible, `false` to hide them. + */ public void displayNonDeformingJoint(boolean display) { - joints.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); - outlines.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); - wires.getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); - ((Node) outlines.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); - ((Node) wires.getChild(1)).getChild(0).setCullHint(display ? CullHint.Dynamic : CullHint.Always); + CullHint cullHint = display ? CullHint.Dynamic : CullHint.Always; + + joints.getChild(0).setCullHint(cullHint); + outlines.getChild(0).setCullHint(cullHint); + wires.getChild(0).setCullHint(cullHint); + + ((Node) outlines.getChild(1)).getChild(0).setCullHint(cullHint); + ((Node) wires.getChild(1)).getChild(0).setCullHint(cullHint); } + /** + * Initializes the materials and camera for the debugger's visual components. + * This method should be called after the `ArmatureDebugger` is added to a scene graph + * and an {@link AssetManager} and {@link Camera} are available. + * + * @param assetManager The {@link AssetManager} to load textures and materials. + * @param camera The scene's primary {@link Camera}, used by the `ArmatureNode` + * for billboard rendering of joint points. + */ public void initialize(AssetManager assetManager, Camera camera) { armatureNode.setCamera(camera); - Material matJoints = new Material(assetManager, "Common/MatDefs/Misc/Billboard.j3md"); - Texture t = assetManager.loadTexture("Common/Textures/dot.png"); - matJoints.setTexture("Texture", t); - matJoints.getAdditionalRenderState().setDepthTest(false); - matJoints.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + // Material for joint points (billboard dots). + Material matJoints = getJointMaterial(assetManager); joints.setQueueBucket(RenderQueue.Bucket.Translucent); joints.setMaterial(matJoints); - Material matWires = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - matWires.setBoolean("VertexColor", true); - matWires.getAdditionalRenderState().setLineWidth(3); + // Material for bone wires/lines (unshaded, vertex colored). + Material matWires = getUnshadedMaterial(assetManager); wires.setMaterial(matWires); - Material matOutline = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - matOutline.setBoolean("VertexColor", true); - matOutline.getAdditionalRenderState().setLineWidth(5); + // Material for dashed wires ("DashedLine.j3md" shader). + Material matWires2 = getDashedMaterial(assetManager); + wires.getChild(1).setMaterial(matWires2); + + // Material for bone outlines (unshaded, vertex colored). + Material matOutline = getUnshadedMaterial(assetManager); outlines.setMaterial(matOutline); - Material matOutline2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); - matOutline2.getAdditionalRenderState().setLineWidth(1); + // Material for dashed outlines ("DashedLine.j3md" shader). + Material matOutline2 = getDashedMaterial(assetManager); outlines.getChild(1).setMaterial(matOutline2); + } - Material matWires2 = new Material(assetManager, "Common/MatDefs/Misc/DashedLine.j3md"); - matWires2.getAdditionalRenderState().setLineWidth(1); - wires.getChild(1).setMaterial(matWires2); + private Material getJointMaterial(AssetManager asm) { + Material mat = new Material(asm, "Common/MatDefs/Misc/Billboard.j3md"); + Texture tex = asm.loadTexture("Common/Textures/dot.png"); + mat.setTexture("Texture", tex); + mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + return mat; + } + + private Material getUnshadedMaterial(AssetManager asm) { + Material mat = new Material(asm, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setBoolean("VertexColor", true); + mat.getAdditionalRenderState().setDepthTest(false); + return mat; + } + private Material getDashedMaterial(AssetManager asm) { + Material mat = new Material(asm, "Common/MatDefs/Misc/DashedLine.j3md"); + mat.getAdditionalRenderState().setDepthTest(false); + return mat; } + /** + * Returns the {@link Armature} instance associated with this debugger. + * + * @return The {@link Armature} being debugged. + */ public Armature getArmature() { return armature; } @@ -167,21 +240,35 @@ public int collideWith(Collidable other, CollisionResults results) { return armatureNode.collideWith(other, results); } - protected Joint select(Geometry g) { - return armatureNode.select(g); + /** + * Selects and returns the {@link Joint} associated with a given {@link Geometry}. + * This is an internal helper method, likely used for picking operations. + * + * @param geo The {@link Geometry} representing a part of a joint. + * @return The {@link Joint} corresponding to the geometry, or `null` if not found. + */ + protected Joint select(Geometry geo) { + return armatureNode.select(geo); } /** - * @return the armature wires + * Returns the {@link ArmatureNode} which is responsible for generating and + * managing the visual mesh of the bones and wires. + * + * @return The {@link ArmatureNode} instance. */ public ArmatureNode getBoneShapes() { return armatureNode; } /** - * @return the dotted line between bones (can be null) + * Returns the {@link ArmatureInterJointsWire} instance, which represents the + * dotted lines connecting a bone's tail to the head of its children. + * This will be `null` if the debugger was created without bone length data. + * + * @return The {@link ArmatureInterJointsWire} instance, or `null` if not present. */ public ArmatureInterJointsWire getInterJointWires() { return interJointWires; } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java index b1c62a55b3..60d655e470 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureInterJointsWire.java @@ -1,7 +1,5 @@ -package com.jme3.scene.debug.custom; - /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,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.scene.debug.custom; import com.jme3.math.Vector3f; import com.jme3.scene.Mesh; @@ -41,19 +39,43 @@ import java.nio.FloatBuffer; /** - * A class that displays a dotted line between a bone tail and its childrens' heads. + * A class that displays a dotted line between a bone tail and its children's heads. * * @author Marcin Roguski (Kaelthas) */ public class ArmatureInterJointsWire extends Mesh { - private Vector3f tmp = new Vector3f(); + /** + * A temporary {@link Vector3f} used for calculations to avoid object allocation. + */ + private final Vector3f tempVec = new Vector3f(); + + /** + * For serialization only. Do not use. + */ + protected ArmatureInterJointsWire() { + } + /** + * Creates a new {@code ArmatureInterJointsWire} mesh. + * The mesh will be set up to draw lines from the {@code start} vector to each of the {@code ends} vectors. + * + * @param start The starting point of the lines (e.g., the bone tail's position). Not null. + * @param ends An array of ending points for the lines (e.g., the children's head positions). Not null. + */ public ArmatureInterJointsWire(Vector3f start, Vector3f[] ends) { setMode(Mode.Lines); updateGeometry(start, ends); } + /** + * Updates the geometry of this mesh based on the provided start and end points. + * This method re-generates the position, texture coordinate, normal, and index buffers + * for the mesh. + * + * @param start The new starting point for the lines. Not null. + * @param ends An array of new ending points for the lines. Not null. + */ protected void updateGeometry(Vector3f start, Vector3f[] ends) { float[] pos = new float[ends.length * 3 + 3]; pos[0] = start.x; @@ -72,7 +94,7 @@ protected void updateGeometry(Vector3f start, Vector3f[] ends) { texCoord[0] = 0; texCoord[1] = 0; for (int i = 0; i < ends.length * 2; i++) { - texCoord[i + 2] = tmp.set(start).subtractLocal(ends[i / 2]).length(); + texCoord[i + 2] = tempVec.set(start).subtractLocal(ends[i / 2]).length(); } setBuffer(Type.TexCoord, 2, texCoord); @@ -97,6 +119,9 @@ protected void updateGeometry(Vector3f start, Vector3f[] ends) { /** * Update the start and end points of the line. + * + * @param start location vector (not null, unaffected) + * @param ends array of location vectors (not null, unaffected) */ public void updatePoints(Vector3f start, Vector3f[] ends) { VertexBuffer posBuf = getBuffer(Type.Position); diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java index 0389360490..a4ae701f35 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/ArmatureNode.java @@ -1,7 +1,5 @@ -package com.jme3.scene.debug.custom; - /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,43 +29,73 @@ * 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.debug.custom; import com.jme3.anim.Armature; import com.jme3.anim.Joint; -import com.jme3.collision.*; -import com.jme3.math.*; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.math.ColorRGBA; +import com.jme3.math.MathUtils; +import com.jme3.math.Ray; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.*; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; import com.jme3.scene.shape.Line; import java.nio.FloatBuffer; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; /** - * The class that displays either wires between the bones' heads if no length - * data is supplied and full bones' shapes otherwise. + * Renders an {@link Armature} for debugging purposes. It can display either + * wires connecting the heads of bones (if no length data is available) or + * full bone shapes (from head to tail) when length data is supplied. */ public class ArmatureNode extends Node { + /** + * The size of the picking box in pixels for joint selection. + */ public static final float PIXEL_BOX = 10f; /** * The armature to be displayed. */ - private Armature armature; + private final Armature armature; /** - * The map between the bone index and its length. + * Maps a {@link Joint} to its corresponding {@link Geometry} array. + * The array typically contains [jointGeometry, boneWireGeometry, boneOutlineGeometry]. + */ + private final Map jointToGeoms = new HashMap<>(); + /** + * Maps a {@link Geometry} to its associated {@link Joint}. Used for picking. + */ + private final Map geomToJoint = new HashMap<>(); + /** + * The currently selected joint. */ - private Map jointToGeoms = new HashMap<>(); - private Map geomToJoint = new HashMap<>(); private Joint selectedJoint = null; - private Vector3f tmp = new Vector3f(); - private Vector2f tmpv2 = new Vector2f(); - private final static ColorRGBA selectedColor = ColorRGBA.Orange; - private final static ColorRGBA selectedColorJ = ColorRGBA.Yellow; - private final static ColorRGBA outlineColor = ColorRGBA.LightGray; - private final static ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + // Temporary vectors for calculations to avoid repeated allocations + private final Vector3f tempVec3f = new Vector3f(); + private final Vector2f tempVec2f = new Vector2f(); + + // Color constants for rendering + private static final ColorRGBA selectedColor = ColorRGBA.Orange; + private static final ColorRGBA selectedColorJoint = ColorRGBA.Yellow; + private static final ColorRGBA outlineColor = ColorRGBA.LightGray; + private static final ColorRGBA baseColor = new ColorRGBA(0.05f, 0.05f, 0.05f, 1f); + + /** + * The camera used for 2D picking calculations. + */ private Camera camera; @@ -76,6 +104,10 @@ public class ArmatureNode extends Node { * wires will show each full bone (from head to tail). * * @param armature the armature that will be shown + * @param joints the Node to visualize joints + * @param wires the Node to visualize wires + * @param outlines the Node to visualize outlines + * @param deformingJoints a list of joints */ public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines, List deformingJoints) { this.armature = armature; @@ -84,27 +116,36 @@ public ArmatureNode(Armature armature, Node joints, Node wires, Node outlines, L setColor(origin, ColorRGBA.Green); attach(joints, true, origin); + // Recursively create geometries for all joints and bones in the armature for (Joint joint : armature.getRoots()) { createSkeletonGeoms(joint, joints, wires, outlines, deformingJoints); } this.updateModelBound(); - } + /** + * Recursively creates the geometries for a given joint and its children. + * + * @param joint The current joint for which to create geometries. + * @param joints The node for joint geometries. + * @param wires The node for bone wire geometries. + * @param outlines The node for bone outline geometries. + * @param deformingJoints A list of deforming joints. + */ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, Node outlines, List deformingJoints) { Vector3f start = joint.getModelTransform().getTranslation().clone(); Vector3f[] ends = null; if (!joint.getChildren().isEmpty()) { ends = new Vector3f[joint.getChildren().size()]; - } - - for (int i = 0; i < joint.getChildren().size(); i++) { - ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone(); + for (int i = 0; i < ends.length; i++) { + ends[i] = joint.getChildren().get(i).getModelTransform().getTranslation().clone(); + } } boolean deforms = deformingJoints.contains(joint); + // Create geometry for the joint head Geometry jGeom = new Geometry(joint.getName() + "Joint", new JointShape()); jGeom.setLocalTranslation(start); attach(joints, deforms, jGeom); @@ -130,8 +171,8 @@ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, N setColor(bGeom, outlinesAttach == null ? outlineColor : baseColor); geomToJoint.put(bGeom, joint); bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); - for (int i = 0; i < ends.length; i++) { - getWorldTransform().transformVector(ends[i], ends[i]); + for (Vector3f end : ends) { + getWorldTransform().transformVector(end, end); } bGeom.setUserData("end", ends); bGeom.setQueueBucket(RenderQueue.Bucket.Transparent); @@ -144,11 +185,17 @@ protected final void createSkeletonGeoms(Joint joint, Node joints, Node wires, N } jointToGeoms.put(joint, new Geometry[]{jGeom, bGeom, bGeomO}); + // Recursively call for children for (Joint child : joint.getChildren()) { createSkeletonGeoms(child, joints, wires, outlines, deformingJoints); } } + /** + * Sets the camera to be used for 2D picking calculations. + * + * @param camera The camera to set. + */ public void setCamera(Camera camera) { this.camera = camera; } @@ -161,53 +208,83 @@ private void attach(Node parent, boolean deforms, Geometry geom) { } } - protected Joint select(Geometry g) { - if (g == null) { + /** + * Selects a joint based on its associated geometry. + * If the selected geometry is already the current selection, no change occurs. + * Resets the selection if {@code geometry} is null. + * + * @param geo The geometry representing the joint or bone to select. + * @return The newly selected {@link Joint}, or null if no joint was selected or the selection was reset. + */ + protected Joint select(Geometry geo) { + if (geo == null) { resetSelection(); return null; } - Joint j = geomToJoint.get(g); - if (j != null) { - if (selectedJoint == j) { + Joint jointToSelect = geomToJoint.get(geo); + if (jointToSelect != null) { + if (selectedJoint == jointToSelect) { return null; } resetSelection(); - selectedJoint = j; + selectedJoint = jointToSelect; Geometry[] geomArray = jointToGeoms.get(selectedJoint); - setColor(geomArray[0], selectedColorJ); + // Color the joint head + setColor(geomArray[0], selectedColorJoint); + // Color the bone wire if (geomArray[1] != null) { setColor(geomArray[1], selectedColor); } + // Restore outline color if present (as it's often the base color when bone is selected) if (geomArray[2] != null) { setColor(geomArray[2], baseColor); } - return j; + return jointToSelect; } return null; } + /** + * Resets the color of the currently selected joint and bone geometries to their default colors + * and clears the {@code selectedJoint}. + */ private void resetSelection() { if (selectedJoint == null) { return; } Geometry[] geoms = jointToGeoms.get(selectedJoint); + // Reset joint head color setColor(geoms[0], ColorRGBA.White); + + // Reset bone wire color (depends on whether it has an outline) if (geoms[1] != null) { setColor(geoms[1], geoms[2] == null ? outlineColor : baseColor); } + + // Reset bone outline color if (geoms[2] != null) { setColor(geoms[2], outlineColor); } selectedJoint = null; } + /** + * Returns the currently selected joint. + * + * @return The {@link Joint} that is currently selected, or null if no joint is selected. + */ protected Joint getSelectedJoint() { return selectedJoint; } - + /** + * Updates the geometries associated with a given joint and its children to reflect their + * current model transforms. This method is called recursively. + * + * @param joint The joint to update. + */ protected final void updateSkeletonGeoms(Joint joint) { Geometry[] geoms = jointToGeoms.get(joint); if (geoms != null) { @@ -228,70 +305,142 @@ protected final void updateSkeletonGeoms(Joint joint) { updateBoneMesh(bGeomO, start, ends); } bGeom.setUserData("start", getWorldTransform().transformVector(start, start)); - for (int i = 0; i < ends.length; i++) { - getWorldTransform().transformVector(ends[i], ends[i]); + for (Vector3f end : ends) { + getWorldTransform().transformVector(end, end); } bGeom.setUserData("end", ends); - } } } + // Recursively update children for (Joint child : joint.getChildren()) { updateSkeletonGeoms(child); } } + /** + * Sets the color of the head geometry for a specific joint. + * + * @param joint The joint whose head color is to be set. + * @param color The new color for the joint head. + */ + public void setHeadColor(Joint joint, ColorRGBA color) { + Geometry[] geomArray = jointToGeoms.get(joint); + setColor(geomArray[0], color); + } + + /** + * Sets the color of all joint head geometries. + * + * @param color The new color for all joint heads. + */ + public void setHeadColor(ColorRGBA color) { + for (Geometry[] geomArray : jointToGeoms.values()) { + setColor(geomArray[0], color); + } + } + + /** + * Sets the color of all bone line geometries. + * + * @param color The new color for all bone lines. + */ + public void setLineColor(ColorRGBA color) { + for (Geometry[] geomArray : jointToGeoms.values()) { + if (geomArray[1] != null) { + setColor(geomArray[1], color); + } + } + } + + /** + * Performs a 2D pick operation to find joints or bones near the given cursor position. + * This method primarily checks for joint heads within a {@link #PIXEL_BOX} box + * around the cursor, and then checks for bone wires. + * + * @param cursor The 2D screen coordinates of the pick ray origin. + * @param results The {@link CollisionResults} to store the pick results. + * @return The number of collisions found. + */ public int pick(Vector2f cursor, CollisionResults results) { + if (camera == null) { + return 0; + } - for (Geometry g : geomToJoint.keySet()) { - if (g.getMesh() instanceof JointShape) { - camera.getScreenCoordinates(g.getWorldTranslation(), tmp); - if (cursor.x <= tmp.x + PIXEL_BOX && cursor.x >= tmp.x - PIXEL_BOX - && cursor.y <= tmp.y + PIXEL_BOX && cursor.y >= tmp.y - PIXEL_BOX) { + int collisions = 0; + for (Geometry geo : geomToJoint.keySet()) { + if (geo.getMesh() instanceof JointShape) { + camera.getScreenCoordinates(geo.getWorldTranslation(), tempVec3f); + if (cursor.x <= tempVec3f.x + PIXEL_BOX && cursor.x >= tempVec3f.x - PIXEL_BOX + && cursor.y <= tempVec3f.y + PIXEL_BOX && cursor.y >= tempVec3f.y - PIXEL_BOX) { CollisionResult res = new CollisionResult(); - res.setGeometry(g); + res.setGeometry(geo); results.addCollision(res); + collisions++; } } } - return 0; + return collisions; } + /** + * Collides this {@code ArmatureNode} with a {@link Collidable} object, typically a {@link Ray}. + * It prioritizes 2D picking for joint heads and then performs a distance-based check for bone wires. + * + * @param other The {@link Collidable} object to collide with. + * @param results The {@link CollisionResults} to store the collision information. + * @return The number of collisions found. + */ @Override public int collideWith(Collidable other, CollisionResults results) { - if (!(other instanceof Ray)) { + if (!(other instanceof Ray) || camera == null) { return 0; } - // first try a 2D pick; - camera.getScreenCoordinates(((Ray)other).getOrigin(),tmp); - tmpv2.x = tmp.x; - tmpv2.y = tmp.y; - int nbHit = pick(tmpv2, results); - if (nbHit > 0) { - return nbHit; + // First, try a 2D pick for joint heads + camera.getScreenCoordinates(((Ray) other).getOrigin(), tempVec3f); + tempVec2f.x = tempVec3f.x; + tempVec2f.y = tempVec3f.y; + int hitCount = pick(tempVec2f, results); + + // If 2D pick found hits, return them. Otherwise, proceed with bone wire collision. + if (hitCount > 0) { + return hitCount; } + // Check for bone wire collisions for (Geometry g : geomToJoint.keySet()) { if (g.getMesh() instanceof JointShape) { + // Skip joint heads, already handled by 2D pick continue; } + Vector3f start = g.getUserData("start"); Vector3f[] ends = g.getUserData("end"); - for (int i = 0; i < ends.length; i++) { - float len = MathUtils.raySegmentShortestDistance((Ray) other, start, ends[i], camera); - if (len > 0 && len < PIXEL_BOX) { + + for (Vector3f end : ends) { + // Calculate the shortest distance from ray to bone segment + float dist = MathUtils.raySegmentShortestDistance((Ray) other, start, end, camera); + if (dist > 0 && dist < PIXEL_BOX) { CollisionResult res = new CollisionResult(); res.setGeometry(g); results.addCollision(res); - nbHit++; + hitCount++; } } } - return nbHit; + return hitCount; } + /** + * Updates the mesh of a bone geometry (either {@link ArmatureInterJointsWire} or {@link Line}) + * with new start and end points. + * + * @param geom The bone geometry whose mesh needs updating. + * @param start The new starting point of the bone. + * @param ends The new ending points of the bone (can be multiple for {@link ArmatureInterJointsWire}). + */ private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) { if (geom.getMesh() instanceof ArmatureInterJointsWire) { ((ArmatureInterJointsWire) geom.getMesh()).updatePoints(start, ends); @@ -301,18 +450,31 @@ private void updateBoneMesh(Geometry geom, Vector3f start, Vector3f[] ends) { geom.updateModelBound(); } - private void setColor(Geometry g, ColorRGBA color) { - float[] colors = new float[g.getMesh().getVertexCount() * 4]; - for (int i = 0; i < g.getMesh().getVertexCount() * 4; i += 4) { + /** + * Sets the color of a given geometry's vertex buffer. + * This method creates a new color buffer or updates an existing one with the specified color. + * + * @param geo The geometry whose color is to be set. + * @param color The {@link ColorRGBA} to apply. + */ + private void setColor(Geometry geo, ColorRGBA color) { + Mesh mesh = geo.getMesh(); + int vertexCount = mesh.getVertexCount(); + + float[] colors = new float[vertexCount * 4]; + for (int i = 0; i < colors.length; i += 4) { colors[i] = color.r; colors[i + 1] = color.g; colors[i + 2] = color.b; colors[i + 3] = color.a; } - VertexBuffer colorBuff = g.getMesh().getBuffer(VertexBuffer.Type.Color); + + VertexBuffer colorBuff = geo.getMesh().getBuffer(VertexBuffer.Type.Color); if (colorBuff == null) { - g.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors); + // If no color buffer exists, create a new one + geo.getMesh().setBuffer(VertexBuffer.Type.Color, 4, colors); } else { + // If a color buffer exists, update its data FloatBuffer cBuff = (FloatBuffer) colorBuff.getData(); cBuff.rewind(); cBuff.put(colors); diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java index 266607eb3d..989667eb23 100644 --- a/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/JointShape.java @@ -35,10 +35,8 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer.Type; - public class JointShape extends Mesh { - /** * Serialization only. Do not use. */ @@ -74,6 +72,4 @@ protected JointShape() { updateBound(); setStatic(); } - - } diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/custom/package-info.java b/jme3-core/src/main/java/com/jme3/scene/debug/custom/package-info.java new file mode 100644 index 0000000000..22f7f6b960 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/custom/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * visualize for animation armatures for debugging + */ +package com.jme3.scene.debug.custom; diff --git a/jme3-core/src/main/java/com/jme3/scene/debug/package-info.java b/jme3-core/src/main/java/com/jme3/scene/debug/package-info.java new file mode 100644 index 0000000000..aeea855bda --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/debug/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * meshes for debug visualizations + */ +package com.jme3.scene.debug; 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 b622ab6e30..e0a673838f 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,10 @@ */ package com.jme3.scene.instancing; +import com.jme3.bounding.BoundingBox; import com.jme3.bounding.BoundingVolume; +import com.jme3.collision.Collidable; +import com.jme3.collision.CollisionResults; import com.jme3.export.InputCapsule; import com.jme3.export.JmeExporter; import com.jme3.export.JmeImporter; @@ -40,6 +43,8 @@ import com.jme3.math.Matrix3f; import com.jme3.math.Matrix4f; import com.jme3.math.Quaternion; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Camera.FrustumIntersect; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.VertexBuffer; @@ -53,16 +58,24 @@ 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]; + // Keep track of both transformInstanceData and globalInstanceData + // that is used by renderer. + private VertexBuffer[] allInstanceData; private int firstUnusedIndex = 0; + private int numVisibleInstances = 0; + private Camera cam; public InstancedGeometry() { super(); @@ -85,6 +98,22 @@ public InstancedGeometry(String name) { setMaxNumInstances(1); } + /** + * Set the function used for culling instances from being rendered. + * Default is {@link DefaultInstanceCullingFunction}. + */ + public static void setInstanceCullingFunction(BiFunction instanceCullingFunction) { + InstancedGeometry.instanceCullingFunction = instanceCullingFunction; + } + + /** + * @return The instance culling function or null if there isn't any. + * Default is {@link DefaultInstanceCullingFunction}. + */ + public static BiFunction getInstanceCullingFunction() { + return instanceCullingFunction; + } + /** * Global user specified per-instance data. * @@ -111,6 +140,7 @@ public VertexBuffer[] getGlobalUserInstanceData() { */ public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) { this.globalInstanceData = globalInstanceData; + updateAllInstanceData(); } /** @@ -120,6 +150,7 @@ public void setGlobalUserInstanceData(VertexBuffer[] globalInstanceData) { */ public void setTransformUserInstanceData(VertexBuffer transformInstanceData) { this.transformInstanceData = transformInstanceData; + updateAllInstanceData(); } /** @@ -200,6 +231,7 @@ public final void setMaxNumInstances(int maxNumInstances) { INSTANCE_SIZE, Format.Float, BufferUtils.createFloatBuffer(geometries.length * INSTANCE_SIZE)); + updateAllInstanceData(); } } @@ -207,8 +239,28 @@ public int getMaxNumInstances() { return geometries.length; } - public int getActualNumInstances() { - return firstUnusedIndex; + /** + * @return The number of instances are visible by camera. + */ + public int getNumVisibleInstances() { + return numVisibleInstances; + } + + /** + * @return The number of instances are in this {@link InstancedGeometry} + */ + public int getNumInstances() { + int count = 0; + for (int i = 0; i < geometries.length; i++) { + if (geometries[i] != null) { + count++; + } + } + return count; + } + + public boolean isEmpty() { + return getNumInstances() == 0; } private void swap(int idx1, int idx2) { @@ -224,32 +276,19 @@ private void swap(int idx1, int idx2) { } } - private void sanitize(boolean insideEntriesNonNull) { - if (firstUnusedIndex >= geometries.length) { - throw new AssertionError(); - } - for (int i = 0; i < geometries.length; i++) { - if (i < firstUnusedIndex) { - if (geometries[i] == null) { - if (insideEntriesNonNull) { - throw new AssertionError(); - } - } else if (InstancedNode.getGeometryStartIndex2(geometries[i]) != i) { - throw new AssertionError(); - } - } else { - if (geometries[i] != null) { - throw new AssertionError(); - } - } - } + /** + * @deprecated use {@link #updateInstances(com.jme3.renderer.Camera)} + */ + public void updateInstances() { + updateInstances(cam); } - public void updateInstances() { + public void updateInstances(Camera cam) { FloatBuffer fb = (FloatBuffer) transformInstanceData.getData(); fb.limit(fb.capacity()); fb.position(0); + int numCulledGeometries = 0; TempVars vars = TempVars.get(); { float[] temp = vars.matrixWrite; @@ -271,6 +310,14 @@ public void updateInstances() { } } + if (cam != null && instanceCullingFunction != null) { + boolean culled = instanceCullingFunction.apply(cam, geom); + if (culled) { + numCulledGeometries++; + continue; + } + } + Matrix4f worldMatrix = geom.getWorldMatrix(); updateInstance(worldMatrix, temp, 0, vars.tempMat3, vars.quat1); fb.put(temp); @@ -280,7 +327,8 @@ public void updateInstances() { fb.flip(); - if (fb.limit() / INSTANCE_SIZE != firstUnusedIndex) { + numVisibleInstances = firstUnusedIndex - numCulledGeometries; + if (fb.limit() / INSTANCE_SIZE != numVisibleInstances) { throw new AssertionError(); } @@ -350,6 +398,9 @@ protected void updateWorldBound() { } } + if (resultBound == null) { + resultBound = new BoundingBox(getWorldTranslation(), 0f, 0f, 0f); + } this.worldBound = resultBound; } @@ -358,25 +409,41 @@ public Geometry[] getGeometries() { } public VertexBuffer[] getAllInstanceData() { - ArrayList allData = new ArrayList(); + return allInstanceData; + } + + private void updateAllInstanceData() { + ArrayList allData = new ArrayList<>(); if (transformInstanceData != null) { allData.add(transformInstanceData); } if (globalInstanceData != null) { allData.addAll(Arrays.asList(globalInstanceData)); } - return allData.toArray(new VertexBuffer[allData.size()]); + 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 } /** * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.globalInstanceData = cloner.clone(globalInstanceData); this.transformInstanceData = cloner.clone(transformInstanceData); + this.allInstanceData = cloner.clone(allInstanceData); this.geometries = cloner.clone(geometries); } @@ -399,5 +466,35 @@ public void read(JmeImporter importer) throws IOException { for (int i = 0; i < geometrySavables.length; i++) { geometries[i] = (Geometry) geometrySavables[i]; } + + updateAllInstanceData(); + } + + /** + * Destroy internal buffers. + */ + protected void cleanup() { + BufferUtils.destroyDirectBuffer(transformInstanceData.getData()); + transformInstanceData = null; + 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 2ddaa4cc1e..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-2018 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; @@ -108,13 +109,13 @@ public InstanceTypeKey clone() { public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new AssertionError(); } } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.mesh = cloner.clone(mesh); this.material = cloner.clone(material); } @@ -141,44 +142,52 @@ public Control cloneForSpatial(Spatial spatial) { public Object jmeClone() { try { return super.clone(); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new RuntimeException("Error cloning control", e); } } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields (Cloner cloner, Object original) { this.node = cloner.clone(node); } - public void setSpatial(Spatial spatial){ + @Override + public void setSpatial(Spatial spatial) { } + @Override public void update(float tpf){ } + @Override public void render(RenderManager rm, ViewPort vp) { - node.renderFromControl(); + node.renderFromControl(vp.getCamera()); } + @Override public void write(JmeExporter ex) throws IOException { } + @Override public void read(JmeImporter im) throws IOException { } } - protected InstancedNodeControl control; + private InstancedNodeControl control; protected HashMap igByGeom - = new HashMap(); + = new HashMap<>(); private InstanceTypeKey lookUp = new InstanceTypeKey(); private HashMap instancesMap = - new HashMap(); + new HashMap<>(); - public InstancedNode() { + /** + * Serialization only. Do not use. + */ + protected InstancedNode() { super(); // NOTE: since we are deserializing, // the control is going to be added automatically here. @@ -190,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); } } @@ -210,6 +219,7 @@ private InstancedGeometry lookUpByGeometry(Geometry geom) { + "lod-" + lookUp.lodLevel); ig.setMaterial(lookUp.material); ig.setMesh(lookUp.mesh); + if (lookUp.lodLevel > 0) ig.setLodLevel(lookUp.lodLevel); ig.setUserData(UserData.JME_PHYSICSIGNORE, true); ig.setCullHint(CullHint.Never); ig.setShadowMode(RenderQueue.ShadowMode.Inherit); @@ -239,6 +249,9 @@ private void removeFromInstancedGeometry(Geometry geom) { InstancedGeometry ig = igByGeom.remove(geom); if (ig != null) { ig.deleteInstance(geom); + if (ig.isEmpty()) { + detachChild(ig); + } } } @@ -250,6 +263,9 @@ private void relocateInInstancedGeometry(Geometry geom) { throw new AssertionError(); } oldIG.deleteInstance(geom); + if (oldIG.isEmpty()) { + detachChild(oldIG); + } newIG.addInstance(geom); igByGeom.put(geom, newIG); } @@ -278,6 +294,14 @@ public Spatial detachChildAt(int index) { Spatial s = super.detachChildAt(index); if (s instanceof Node) { ungroupSceneGraph(s); + } else if (s instanceof InstancedGeometry) { + InstancedGeometry ig = (InstancedGeometry) s; + lookUp.mesh = ig.getMesh(); + lookUp.material = ig.getMaterial(); + lookUp.lodLevel = ig.getLodLevel(); + + instancesMap.remove(lookUp, ig); + ig.cleanup(); } return s; } @@ -303,12 +327,12 @@ public void instance() { } @Override - public Node clone() { + public InstancedNode clone() { return clone(true); } @Override - public Node clone(boolean cloneMaterials) { + public InstancedNode clone(boolean cloneMaterials) { InstancedNode clone = (InstancedNode)super.clone(cloneMaterials); if (instancesMap.size() > 0) { @@ -345,20 +369,20 @@ public Node clone(boolean cloneMaterials) { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { super.cloneFields(cloner, original); this.control = cloner.clone(control); this.lookUp = cloner.clone(lookUp); - HashMap newIgByGeom = new HashMap(); - for( Map.Entry e : igByGeom.entrySet() ) { + HashMap newIgByGeom = new HashMap<>(); + for (Map.Entry e : igByGeom.entrySet()) { newIgByGeom.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); } this.igByGeom = newIgByGeom; - HashMap newInstancesMap = new HashMap(); - for( Map.Entry e : instancesMap.entrySet() ) { + HashMap newInstancesMap = new HashMap<>(); + for (Map.Entry e : instancesMap.entrySet()) { newInstancesMap.put(cloner.clone(e.getKey()), cloner.clone(e.getValue())); } this.instancesMap = newInstancesMap; diff --git a/jme3-core/src/main/java/com/jme3/scene/instancing/package-info.java b/jme3-core/src/main/java/com/jme3/scene/instancing/package-info.java new file mode 100644 index 0000000000..48da6a3f83 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/instancing/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * share mesh data across multiple geometries + */ +package com.jme3.scene.instancing; diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java index dd94761f77..29ff8050c7 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,24 +59,38 @@ public static IndexBuffer wrapIndexBuffer(Buffer buf) { throw new UnsupportedOperationException("Index buffer type unsupported: "+ buf.getClass()); } } - + /** - * Creates an index buffer that can contain the given amount of vertices. - *
                - * Returns either {@link IndexByteBuffer}, {@link IndexShortBuffer} or + * Create an IndexBuffer with the specified capacity. + * + * @param vertexCount the number of vertices that will be indexed into + * (determines number of bits per element) + * @param indexCount the number of indices the IndexBuffer must hold + * (determines number of elements in the buffer) + * @return a new, appropriately sized IndexBuffer, which may be an + * {@link IndexByteBuffer}, an {@link IndexShortBuffer}, or an * {@link IndexIntBuffer} - * - * @param vertexCount The amount of vertices to contain - * @param indexCount The amount of indices to contain - * @return A new, apropriately sized index buffer */ - public static IndexBuffer createIndexBuffer(int vertexCount, int indexCount){ - if (vertexCount < 128) - return new IndexByteBuffer(BufferUtils.createByteBuffer (indexCount)); - else if (vertexCount < 65536) - return new IndexShortBuffer(BufferUtils.createShortBuffer(indexCount)); - else - return new IndexIntBuffer(BufferUtils.createIntBuffer(indexCount)); + public static IndexBuffer createIndexBuffer(int vertexCount, + int indexCount) { + IndexBuffer result; + + if (vertexCount < 128) { // TODO: could be vertexCount <= 256 + ByteBuffer buffer = BufferUtils.createByteBuffer(indexCount); + int maxIndexValue = Math.max(0, vertexCount - 1); + result = new IndexByteBuffer(buffer, maxIndexValue); + + } else if (vertexCount < 65536) { // TODO: could be <= 65536 + ShortBuffer buffer = BufferUtils.createShortBuffer(indexCount); + int maxIndexValue = vertexCount - 1; + result = new IndexShortBuffer(buffer, maxIndexValue); + + } else { + IntBuffer buffer = BufferUtils.createIntBuffer(indexCount); + result = new IndexIntBuffer(buffer); + } + + return result; } /** @@ -161,7 +175,7 @@ public int remaining() { * mesh.setBuffer(Type.Index, 3, * indexBuffer.getFormat(), indexBuffer); * - * @return + * @return an enum value */ public abstract Format getFormat(); } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java index d27901673f..f7f66a2b04 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexByteBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,13 +43,38 @@ */ public class IndexByteBuffer extends IndexBuffer { - private ByteBuffer buf; + private final ByteBuffer buf; + /** + * the largest index value that can be put to the buffer + */ + private int maxValue = 255; + /** + * Instantiate an IndexBuffer using the specified ByteBuffer and a maximum + * index value of 255. + * + * @param buffer a pre-existing buffer (not null, alias created) + */ public IndexByteBuffer(ByteBuffer buffer) { buf = buffer; buf.rewind(); } + /** + * Instantiate an IndexBuffer using the specified ByteBuffer and set its + * maximum index value. + * + * @param buffer a pre-existing buffer (not null, alias created) + * @param maxValue the desired maximum index value (≥0, ≤255) + */ + public IndexByteBuffer(ByteBuffer buffer, int maxValue) { + assert maxValue >= 0 && maxValue <= 255 : "out of range: " + maxValue; + this.maxValue = maxValue; + + buf = buffer; + buf.rewind(); + } + @Override public int get() { return buf.get() & 0x000000FF; @@ -62,12 +87,20 @@ public int get(int i) { @Override public IndexByteBuffer put(int i, int value) { + assert value >= 0 && value <= maxValue + : "IndexBuffer was created with elements too small for value=" + + value; + buf.put(i, (byte) value); return this; } @Override public IndexByteBuffer put(int value) { + assert value >= 0 && value <= maxValue + : "IndexBuffer was created with elements too small for value=" + + value; + buf.put((byte) value); return this; } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java index d5f2b86a91..c8d691589f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexIntBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ */ public class IndexIntBuffer extends IndexBuffer { - private IntBuffer buf; + private final IntBuffer buf; public IndexIntBuffer(IntBuffer buffer) { buf = buffer; diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java index 3dfd560dea..c505947eb9 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/IndexShortBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,13 +43,38 @@ */ public class IndexShortBuffer extends IndexBuffer { - private ShortBuffer buf; + private final ShortBuffer buf; + /** + * the largest index value that can be put to the buffer + */ + private int maxValue = 65_535; + /** + * Instantiate an IndexBuffer using the specified ShortBuffer and a maximum + * index value of 65_535. + * + * @param buffer a pre-existing buffer (not null, alias created) + */ public IndexShortBuffer(ShortBuffer buffer) { buf = buffer; buf.rewind(); } + /** + * Instantiate an IndexBuffer using the specified ShortBuffer and set its + * maximum index value. + * + * @param buffer a pre-existing buffer (not null, alias created) + * @param maxValue the desired maximum index value (≥0, ≤65_535) + */ + public IndexShortBuffer(ShortBuffer buffer, int maxValue) { + assert maxValue >= 0 && maxValue <= 65_535 : "out of range: " + maxValue; + this.maxValue = maxValue; + + buf = buffer; + buf.rewind(); + } + @Override public int get() { return buf.get() & 0x0000FFFF; @@ -61,12 +86,20 @@ public int get(int i) { @Override public IndexShortBuffer put(int i, int value) { + assert value >= 0 && value <= maxValue + : "IndexBuffer was created with elements too small for value=" + + value; + buf.put(i, (short) value); return this; } @Override public IndexShortBuffer put(int value) { + assert value >= 0 && value <= maxValue + : "IndexBuffer was created with elements too small for value=" + + value; + buf.put((short) value); return this; } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java index fc80e21139..9d57b00588 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/MorphTarget.java @@ -1,46 +1,142 @@ +/* + * Copyright (c) 2009-2025 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.mesh; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; import com.jme3.scene.VertexBuffer; import java.io.IOException; -import java.nio.Buffer; import java.nio.FloatBuffer; import java.util.EnumMap; import java.util.Map; +/** + * `MorphTarget` represents a single morph target within a `Mesh`. + * A morph target contains a set of `FloatBuffer` instances, each corresponding + * to a `VertexBuffer.Type` (e.g., `POSITION`, `NORMAL`, `TANGENT`). + * These buffers store the delta (difference) values that, when added to the + * base mesh's corresponding vertex buffers, create a deformed version of the mesh. + *

                + * Morph targets are primarily used for skeletal animation blending, facial animation, + * or other mesh deformation effects. Each `MorphTarget` can optionally have a name + * for identification and control. + */ public class MorphTarget implements Savable { - private EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); - private String name = null; - + + /** + * Stores the `FloatBuffer` instances for each `VertexBuffer.Type` that + * this morph target affects. + */ + private final EnumMap buffers = new EnumMap<>(VertexBuffer.Type.class); + /** + * An optional name for this morph target, useful for identification + * and targeting in animations. + */ + private String name; + + /** + * Required for jME deserialization. + */ public MorphTarget() { - } - + + /** + * Creates a new `MorphTarget` with the specified name. + * + * @param name The name of this morph target (can be null). + */ public MorphTarget(String name) { this.name = name; } - + + /** + * Sets the name of this morph target. + * + * @param name The new name for this morph target (can be null). + */ public void setName(String name) { this.name = name; } - + + /** + * Returns the name of this morph target. + * + * @return The name of this morph target, or null if not set. + */ public String getName() { return name; } + /** + * Associates a `FloatBuffer` with a specific `VertexBuffer.Type` for this morph target. + * This buffer typically contains the delta values for the specified vertex attribute. + * + * @param type The type of vertex buffer (e.g., `POSITION`, `NORMAL`). + * @param buffer The `FloatBuffer` containing the delta data for the given type. + */ public void setBuffer(VertexBuffer.Type type, FloatBuffer buffer) { buffers.put(type, buffer); } + /** + * Retrieves the `FloatBuffer` associated with a specific `VertexBuffer.Type` for this morph target. + * + * @param type The type of vertex buffer. + * @return The `FloatBuffer` for the given type, or null if not set. + */ public FloatBuffer getBuffer(VertexBuffer.Type type) { return buffers.get(type); } + /** + * Returns the `EnumMap` containing all the `FloatBuffer` instances + * associated with their `VertexBuffer.Type` for this morph target. + * + * @return An `EnumMap` of vertex buffer types to their corresponding `FloatBuffer`s. + */ public EnumMap getBuffers() { return buffers; } + /** + * Returns the number of `FloatBuffer`s (i.e., vertex attribute types) + * contained within this morph target. + * + * @return The count of buffers in this morph target. + */ public int getNumBuffers() { return buffers.size(); } @@ -49,8 +145,9 @@ public int getNumBuffers() { public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); for (Map.Entry entry : buffers.entrySet()) { - Buffer roData = entry.getValue().asReadOnlyBuffer(); - oc.write((FloatBuffer) roData, entry.getKey().name(),null); + VertexBuffer.Type type = entry.getKey(); + FloatBuffer roData = entry.getValue().asReadOnlyBuffer(); + oc.write(roData, type.name(), null); } oc.write(name, "morphName", null); } @@ -59,9 +156,9 @@ public void write(JmeExporter ex) throws IOException { public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); for (VertexBuffer.Type type : VertexBuffer.Type.values()) { - FloatBuffer b = ic.readFloatBuffer(type.name(), null); - if(b!= null){ - setBuffer(type, b); + FloatBuffer fb = ic.readFloatBuffer(type.name(), null); + if (fb != null) { + setBuffer(type, fb); } } name = ic.readString("morphName", null); diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java index dd57abee43..c3144e6b97 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/VirtualIndexBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ *

              • {@link Mode#TriangleStrip}: 0, 1, 2 | 2, 1, 3 | 2, 3, 4 | ...
              • *
              • {@link Mode#TriangleFan}: 0, 1, 2 | 0, 2, 3 | 0, 3, 4 | ...
              • * - * + * * @author Kirill Vainer */ public class VirtualIndexBuffer extends IndexBuffer { @@ -58,8 +58,8 @@ public class VirtualIndexBuffer extends IndexBuffer { protected int numIndices = 0; protected Mode meshMode; protected int position = 0; - - public VirtualIndexBuffer(int numVerts, Mode meshMode){ + + public VirtualIndexBuffer(int numVerts, Mode meshMode) { this.numVerts = numVerts; this.meshMode = meshMode; switch (meshMode) { @@ -67,7 +67,7 @@ public VirtualIndexBuffer(int numVerts, Mode meshMode){ numIndices = numVerts; return; case LineLoop: - numIndices = (numVerts - 1) * 2 + 1; + numIndices = numVerts * 2; return; case LineStrip: numIndices = (numVerts - 1) * 2; @@ -108,33 +108,38 @@ public int remaining() { @Override public int get(int i) { - if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points){ + if (meshMode == Mode.Triangles || meshMode == Mode.Lines || meshMode == Mode.Points) { return i; - }else if (meshMode == Mode.LineStrip){ + } else if (meshMode == Mode.LineStrip) { return (i + 1) / 2; - }else if (meshMode == Mode.LineLoop){ - return (i == (numVerts-1)) ? 0 : ((i + 1) / 2); - }else if (meshMode == Mode.TriangleStrip){ - int triIndex = i/3; - int vertIndex = i%3; - boolean isBack = (i/3)%2==1; - if (!isBack){ + } else if (meshMode == Mode.LineLoop) { + return (i == (numIndices - 1)) ? 0 : ((i + 1) / 2); + } else if (meshMode == Mode.TriangleStrip) { + int triIndex = i / 3; + int vertIndex = i % 3; + boolean isBack = (i / 3) % 2 == 1; + if (!isBack) { return triIndex + vertIndex; - }else{ - switch (vertIndex){ - case 0: return triIndex + 1; - case 1: return triIndex; - case 2: return triIndex + 2; - default: throw new AssertionError(); - } + } else { + switch (vertIndex) { + case 0: + return triIndex + 1; + case 1: + return triIndex; + case 2: + return triIndex + 2; + default: + throw new AssertionError(); + } } - }else if (meshMode == Mode.TriangleFan){ - int vertIndex = i%3; - if (vertIndex == 0) + } else if (meshMode == Mode.TriangleFan) { + int vertIndex = i % 3; + if (vertIndex == 0) { return 0; - else + } else { return (i / 3) + vertIndex; - }else{ + } + } else { throw new UnsupportedOperationException(); } } @@ -154,15 +159,15 @@ public Buffer getBuffer() { return null; } - @Override - public IndexBuffer put (int value) { - throw new UnsupportedOperationException("Does not represent index buffer"); - } + @Override + public IndexBuffer put(int value) { + throw new UnsupportedOperationException("Does not represent index buffer"); + } - @Override - public Format getFormat () { - // return largest size - return Format.UnsignedInt; - } + @Override + public Format getFormat() { + // return largest size + return Format.UnsignedInt; + } } diff --git a/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java b/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java index 5a91050f2d..f6c9d4bfea 100644 --- a/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java +++ b/jme3-core/src/main/java/com/jme3/scene/mesh/WrappedIndexBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,8 +35,6 @@ import com.jme3.scene.Mesh.Mode; import com.jme3.scene.VertexBuffer.Type; import java.nio.Buffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; /** * WrappedIndexBuffer converts vertex indices from a non list based @@ -44,7 +42,7 @@ * into a list based mode such as {@link Mode#Triangles} or {@link Mode#Lines}. * As it is often more convenient to read vertex data in list format * than in a non-list format, using this class is recommended to avoid - * convoluting classes used to process mesh data from an external source. + * complicating classes used to process mesh data from an external source. * * @author Kirill Vainer */ diff --git a/jme3-core/src/main/java/com/jme3/scene/package.html b/jme3-core/src/main/java/com/jme3/scene/package.html index c0d24be3da..027105178a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/package.html +++ b/jme3-core/src/main/java/com/jme3/scene/package.html @@ -18,7 +18,7 @@ the "branches" in the graph, used to organize elements in a tree hierarchy. The {@link com.jme3.scene.Geometry} is the leaf class that will contain a {@link com.jme3.scene.Mesh} object (geometry data - such as vertex positions, normals, etc) and a {@link com.jme3.material.Material} + such as vertex positions, normals, etc.) and a {@link com.jme3.material.Material} object containing information on how the geometry should be shaded.

                diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java index 1571823a7f..e096a91a4c 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/AbstractBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -110,6 +110,8 @@ protected final Vector3f[] computeVertices() { /** * Get the center point of this box. + * + * @return the pre-existing location vector (in mesh coordinates) */ public final Vector3f getCenter() { return center; @@ -117,6 +119,8 @@ public final Vector3f getCenter() { /** * Get the x-axis size (extent) of this box. + * + * @return the radius parallel to the X axis (in mesh units) */ public final float getXExtent() { return xExtent; @@ -124,6 +128,8 @@ public final float getXExtent() { /** * Get the y-axis size (extent) of this box. + * + * @return the radius parallel to the Y axis (in mesh units) */ public final float getYExtent() { return yExtent; @@ -131,6 +137,8 @@ public final float getYExtent() { /** * Get the z-axis size (extent) of this box. + * + * @return the radius parallel to the Z axis (in mesh units) */ public final float getZExtent() { return zExtent; @@ -157,9 +165,9 @@ public final void updateGeometry() { * the box extends in both directions from the center for each extent. * * @param center the center of the box. - * @param x the x extent of the box, in each directions. - * @param y the y extent of the box, in each directions. - * @param z the z extent of the box, in each directions. + * @param x the X extent of the box in each direction. + * @param y the Y extent of the box in each direction. + * @param z the Z extent of the box in each direction. */ public final void updateGeometry(Vector3f center, float x, float y, float z) { if (center != null) {this.center.set(center); } @@ -187,9 +195,9 @@ public final void updateGeometry(Vector3f minPoint, Vector3f maxPoint) { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); xExtent = capsule.readFloat("xExtent", 0); yExtent = capsule.readFloat("yExtent", 0); zExtent = capsule.readFloat("zExtent", 0); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Box.java b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java index a36af31db4..40be05df6a 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Box.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Box.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -145,24 +145,28 @@ public Box clone() { return new Box(center.clone(), xExtent, yExtent, zExtent); } + @Override protected void doUpdateGeometryIndices() { if (getBuffer(Type.Index) == null){ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); } } + @Override protected void doUpdateGeometryNormals() { if (getBuffer(Type.Normal) == null){ setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(GEOMETRY_NORMALS_DATA)); } } + @Override protected void doUpdateGeometryTextures() { if (getBuffer(Type.TexCoord) == null){ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); } } + @Override protected void doUpdateGeometryVertices() { FloatBuffer fpb = BufferUtils.createVector3Buffer(24); Vector3f[] v = computeVertices(); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/CenterQuad.java b/jme3-core/src/main/java/com/jme3/scene/shape/CenterQuad.java new file mode 100644 index 0000000000..cfe51dbaff --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/CenterQuad.java @@ -0,0 +1,189 @@ +/* + * Copyright (c) 2009-2021 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.shape; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import java.io.IOException; + +/** + * A static, indexed, Triangles-mode mesh for an axis-aligned rectangle in the + * X-Y plane. + * + *

                The rectangle extends from (-width/2, -height/2, 0) to + * (width/2, height/2, 0) with normals set to (0,0,1). + * + *

                This differs from {@link com.jme3.scene.shape.Quad} because it puts + * (0,0,0) at the rectangle's center instead of in a corner. + * + * @author Kirill Vainer + */ +public class CenterQuad extends Mesh { + + private float width; + private float height; + + /** + * For serialization only. Do not use. + */ + protected CenterQuad() { + } + + /** + * Instantiate an unflipped quad in the X-Y plane with the specified width + * and height. + * + * @param width the desired X extent or width + * @param height the desired Y extent or height + */ + public CenterQuad(float width, float height) { + updateGeometry(width, height, false); + } + + /** + * Instantiate a quad in the X-Y plane with the specified width and height. + * + * @param width the desired X extent or width + * @param height the desired Y extent or height + * @param flipCoords true to flip the texture coordinates (v=0 when + * y=height/2) or false to leave them unflipped (v=1 when y=height/2) + */ + public CenterQuad(float width, float height, boolean flipCoords) { + updateGeometry(width, height, flipCoords); + } + + /** + * Returns the height (or Y extent). + * + * @return the height + */ + public float getHeight() { + return height; + } + + /** + * Returns the width (or X extent). + * + * @return the width + */ + public float getWidth() { + return width; + } + + /** + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + width = capsule.readFloat("width", 0f); + height = capsule.readFloat("height", 0f); + } + + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(width, "width", 0f); + capsule.write(height, "height", 0f); + } + + private void updateGeometry(float width, float height, boolean flipCoords) { + this.width = width; + this.height = height; + + float x = width / 2; + float y = height / 2; + setBuffer(Type.Position, 3, new float[]{ + -x, -y, 0f, + +x, -y, 0f, + +x, +y, 0f, + -x, +y, 0f + }); + + if (flipCoords) { + setBuffer(Type.TexCoord, 2, new float[]{ + 0f, 1f, + 1f, 1f, + 1f, 0f, + 0f, 0f + }); + } else { + setBuffer(Type.TexCoord, 2, new float[]{ + 0f, 0f, + 1f, 0f, + 1f, 1f, + 0f, 1f + }); + } + + setBuffer(Type.Normal, 3, new float[]{ + 0f, 0f, 1f, + 0f, 0f, 1f, + 0f, 0f, 1f, + 0f, 0f, 1f + }); + + if (width * height < 0f) { + setBuffer(Type.Index, 3, new byte[]{ + 0, 2, 1, + 0, 3, 2 + }); + } else { + setBuffer(Type.Index, 3, new byte[]{ + 0, 1, 2, + 0, 2, 3 + }); + } + + updateBound(); + setStatic(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java index 063bbab11f..e1b7dd994f 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Curve.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ /** * A * Curve is a visual, line-based representation of a {@link Spline}. - * The underlying Spline will be sampled N times where N is the number of + * The underlying Spline will be sampled N times, where N is the number of * segments as specified in the constructor. Each segment will represent one * line in the generated mesh. * @@ -198,12 +198,12 @@ private void createBezierMesh(int nbSubSegments) { * points */ private void createNurbMesh(int nbSubSegments) { - if(spline.getControlPoints() != null && spline.getControlPoints().size() > 0) { - if(nbSubSegments == 0) { - nbSubSegments = spline.getControlPoints().size() + 1; - } else { - nbSubSegments = spline.getControlPoints().size() * nbSubSegments + 1; - } + if (spline.getControlPoints() != null && spline.getControlPoints().size() > 0) { + if (nbSubSegments == 0) { + nbSubSegments = spline.getControlPoints().size() + 1; + } else { + nbSubSegments = spline.getControlPoints().size() * nbSubSegments + 1; + } float minKnot = spline.getMinNurbKnot(); float maxKnot = spline.getMaxNurbKnot(); float deltaU = (maxKnot - minKnot) / nbSubSegments; @@ -233,7 +233,7 @@ private void createNurbMesh(int nbSubSegments) { this.setBuffer(VertexBuffer.Type.Index, 2, indices); this.updateBound(); this.updateCounts(); - } + } } private void createLinearMesh() { diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java index 01d38a4958..36df9d9892 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Cylinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,9 +70,9 @@ protected Cylinder() { } /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information. + * Creates a Cylinder. By default, its center is the origin. More + * samples create a better looking cylinder, at the cost + * of more vertex data. * * @param axisSamples * Number of triangle samples along the axis. @@ -89,14 +89,13 @@ public Cylinder(int axisSamples, int radialSamples, } /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information.
                - * If the cylinder is closed the texture is split into axisSamples parts: - * top most and bottom most part is used for top and bottom of the cylinder, - * rest of the texture for the cylinder wall. The middle of the top is - * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need - * a suited distorted texture. + * Creates a Cylinder. By default, its center is the origin. More + * samples create a better looking cylinder, at the cost + * of more vertex data.
                + * If the cylinder is closed, the texture is split into axisSamples parts: + * the topmost and bottommost parts are used for top and bottom of the cylinder, + * and the rest of the texture is used for the cylinder wall. The middle of the top is + * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus, it requires * * @param axisSamples * Number of triangle samples along the axis. @@ -115,16 +114,15 @@ public Cylinder(int axisSamples, int radialSamples, } /** - * Creates a new Cylinder. By default its center is the origin. Usually, a - * higher sample number creates a better looking cylinder, but at the cost - * of more vertex information.
                - * If the cylinder is closed the texture is split into axisSamples parts: - * top most and bottom most part is used for top and bottom of the cylinder, - * rest of the texture for the cylinder wall. The middle of the top is - * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus you need - * a suited distorted texture. + * Creates a new Cylinder. By default, its center is the origin. More + * samples create a better looking cylinder, at the cost + * of more vertex data.
                + * If the cylinder is closed, the texture is split into axisSamples parts: + * the topmost and bottommost parts are used for top and bottom of the cylinder, + * and the rest of the texture is used for the cylinder wall. The middle of the top is + * mapped to texture coordinates (0.5, 1), bottom to (0.5, 0). Thus, it requires * - * @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so + * @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so * that, for instance, 4 samples mean the cylinder will be made of 3 segments. * @param radialSamples The number of triangle samples along the radius. For instance, 4 means that the sides of the * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles. @@ -198,10 +196,12 @@ public boolean isInverted() { /** * Rebuilds the cylinder based on a new set of parameters. * - * @param axisSamples The number of vertices samples along the axis. It is equal to the number of segments + 1; so - * that, for instance, 4 samples mean the cylinder will be made of 3 segments. - * @param radialSamples The number of triangle samples along the radius. For instance, 4 means that the sides of the - * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles. + * @param axisSamples The number of vertices samples along the axis. + * It is equal to the number of segments + 1; so + * that, for instance, 4 samples mean the cylinder will be made of 3 segments. + * @param radialSamples The number of triangle samples along the radius. + * For instance, 4 means that the sides of the + * cylinder are made of 4 rectangles, and the top and bottom are made of 4 triangles. * @param topRadius the radius of the top of the cylinder. * @param bottomRadius the radius of the bottom of the cylinder. * @param height the cylinder's height. @@ -211,14 +211,14 @@ public boolean isInverted() { public void updateGeometry(int axisSamples, int radialSamples, float topRadius, float bottomRadius, float height, boolean closed, boolean inverted) { // Ensure there's at least two axis samples and 3 radial samples, and positive dimensions. - if( axisSamples < 2 + if (axisSamples < 2 || radialSamples < 3 || topRadius <= 0 || bottomRadius <= 0 - || height <= 0 ) { + || height <= 0) { throw new IllegalArgumentException("Cylinders must have at least 2 axis samples and 3 radial samples, and positive dimensions."); } - + this.axisSamples = axisSamples; this.radialSamples = radialSamples; this.radius = bottomRadius; @@ -231,7 +231,7 @@ public void updateGeometry(int axisSamples, int radialSamples, int verticesCount = axisSamples * (radialSamples +1); // Triangles: Two per side rectangle, which is the product of numbers of samples. int trianglesCount = axisSamples * radialSamples * 2 ; - if( closed ) { + if (closed) { // If there are caps, add two additional rims and two summits. verticesCount += 2 + 2 * (radialSamples +1); // Add one triangle per radial sample, twice, to form the caps. @@ -245,12 +245,12 @@ public void updateGeometry(int axisSamples, int radialSamples, circlePoints[circlePoint][0] = FastMath.cos(angle); circlePoints[circlePoint][1] = FastMath.sin(angle); } - // Add an additional point for closing the texture around the side of the cylinder. + // Add a point to close the texture around the side of the cylinder. circlePoints[radialSamples][0] = circlePoints[0][0]; circlePoints[radialSamples][1] = circlePoints[0][1]; - + // Calculate normals. - // + // // A---------B // \ | // \ | @@ -265,7 +265,9 @@ public void updateGeometry(int axisSamples, int radialSamples, // The normal is the orthogonal to the side, which can be got without trigonometry. // The edge direction is oriented so that it goes up by Height, and out by the radius difference; let's use // those values in reverse order. - Vector3f normal = new Vector3f(height * circlePoints[circlePoint][0], height * circlePoints[circlePoint][1], bottomRadius - topRadius ); + Vector3f normal = new Vector3f(height * circlePoints[circlePoint][0], + height * circlePoints[circlePoint][1], + bottomRadius - topRadius); circleNormals[circlePoint] = normal.normalizeLocal(); } @@ -273,38 +275,39 @@ public void updateGeometry(int axisSamples, int radialSamples, float[] normals = new float[verticesCount * 3]; float[] textureCoords = new float[verticesCount * 2]; int currentIndex = 0; - + // Add a circle of points for each axis sample. - for(int axisSample = 0; axisSample < axisSamples; axisSample++ ) { - float currentHeight = -height / 2 + height * axisSample / (axisSamples-1); - float currentRadius = bottomRadius + (topRadius - bottomRadius) * axisSample / (axisSamples-1); - + for (int axisSample = 0; axisSample < axisSamples; axisSample++) { + float currentHeight = -height / 2 + height * axisSample / (axisSamples - 1); + float currentRadius = bottomRadius + (topRadius - bottomRadius) * axisSample / (axisSamples - 1); + for (int circlePoint = 0; circlePoint < radialSamples + 1; circlePoint++) { - // Position, by multipliying the position on a unit circle with the current radius. + // Position, by multiplying the position on a unit circle with the current radius. vertices[currentIndex*3] = circlePoints[circlePoint][0] * currentRadius; vertices[currentIndex*3 +1] = circlePoints[circlePoint][1] * currentRadius; vertices[currentIndex*3 +2] = currentHeight; - + // Normal Vector3f currentNormal = circleNormals[circlePoint]; normals[currentIndex*3] = currentNormal.x; normals[currentIndex*3+1] = currentNormal.y; normals[currentIndex*3+2] = currentNormal.z; - + // Texture // The X is the angular position of the point. - textureCoords[currentIndex *2] = (float) circlePoint / radialSamples; - // Depending on whether there is a cap, the Y is either the height scaled to [0,1], or the radii of + textureCoords[currentIndex * 2] = (float) circlePoint / radialSamples; + // Depending on whether there is a cap, the Y is either the height scaled to [0,1], or the radii of // the cap count as well. if (closed) - textureCoords[currentIndex *2 +1] = (bottomRadius + height / 2 + currentHeight) / (bottomRadius + height + topRadius); + textureCoords[currentIndex * 2 + 1] = (bottomRadius + height / 2 + currentHeight) + / (bottomRadius + height + topRadius); else - textureCoords[currentIndex *2 +1] = height / 2 + currentHeight; - + textureCoords[currentIndex * 2 + 1] = height / 2 + currentHeight; + currentIndex++; } } - + // If closed, add duplicate rims on top and bottom, with normals facing up and down. if (closed) { // Bottom @@ -337,29 +340,29 @@ public void updateGeometry(int axisSamples, int radialSamples, currentIndex++; } - + // Add the centers of the caps. vertices[currentIndex*3] = 0; vertices[currentIndex*3 +1] = 0; vertices[currentIndex*3 +2] = -height/2; - + normals[currentIndex*3] = 0; normals[currentIndex*3+1] = 0; normals[currentIndex*3+2] = -1; - + textureCoords[currentIndex *2] = 0.5f; textureCoords[currentIndex *2+1] = 0f; - + currentIndex++; - + vertices[currentIndex*3] = 0; vertices[currentIndex*3 +1] = 0; vertices[currentIndex*3 +2] = height/2; - + normals[currentIndex*3] = 0; normals[currentIndex*3+1] = 0; normals[currentIndex*3+2] = 1; - + textureCoords[currentIndex *2] = 0.5f; textureCoords[currentIndex *2+1] = 1f; } @@ -382,16 +385,16 @@ public void updateGeometry(int axisSamples, int radialSamples, if(closed) { short bottomCapIndex = (short) (verticesCount - 2); short topCapIndex = (short) (verticesCount - 1); - + int bottomRowOffset = (axisSamples) * (radialSamples +1 ); int topRowOffset = (axisSamples+1) * (radialSamples +1 ); - + for (int circlePoint = 0; circlePoint < radialSamples; circlePoint++) { indices[currentIndex++] = (short) (bottomRowOffset + circlePoint +1); indices[currentIndex++] = (short) (bottomRowOffset + circlePoint); indices[currentIndex++] = bottomCapIndex; - + indices[currentIndex++] = (short) (topRowOffset + circlePoint); indices[currentIndex++] = (short) (topRowOffset + circlePoint +1); indices[currentIndex++] = topCapIndex; @@ -405,26 +408,26 @@ public void updateGeometry(int axisSamples, int radialSamples, indices[i] = indices[indices.length - 1 - i]; indices[indices.length - 1 - i] = temp; } - - for(int i = 0; i< normals.length; i++) { + + for (int i = 0; i< normals.length; i++) { normals[i] = -normals[i]; } } - + // Fill in the buffers. setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(vertices)); setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normals)); setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(textureCoords)); setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(indices)); - + updateBound(); setStatic(); } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); axisSamples = capsule.readInt("axisSamples", 0); radialSamples = capsule.readInt("radialSamples", 0); radius = capsule.readFloat("radius", 0); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java index 6c44409a81..5cfd312f82 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Dome.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ /** * A hemisphere. - * + * * @author Peter Andersson * @author Joshua Slack (Original sphere code that was adapted) * @version $Revision: 4131 $, $Date: 2009-03-19 16:15:28 -0400 (Thu, 19 Mar 2009) $ @@ -70,8 +70,8 @@ protected Dome() { } /** - * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin - * and only visible from the inside. + * Constructs a dome for use as a SkyDome. The SkyDome is centered at the origin + * and only visible from the inside. * @param planes * The number of planes along the Z-axis. Must be >= 2. * Influences how round the arch of the dome is. @@ -87,18 +87,18 @@ public Dome(int planes, int radialSamples, float radius) { } /** - * Constructs a dome visible from the inside, e.g. for use as a SkyDome. + * Constructs a dome visible from the inside, e.g. for use as a SkyDome. * All geometry data buffers are updated automatically.
                * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2. * Increasing planes and radialSamples increase the quality of the dome. - * + * * @param center * Center of the dome. * @param planes * The number of planes along the Z-axis. Must be >= 2. * Influences how round the arch of the dome is. * @param radialSamples - * The number of samples along the radial. + * The number of samples along the radial. * Influences how round the base of the dome is. * @param radius * The radius of the dome. @@ -110,12 +110,12 @@ public Dome(Vector3f center, int planes, int radialSamples, } /** - * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones. + * Constructs a dome. Use this constructor for half-sphere, pyramids, or cones. * All geometry data buffers are updated automatically.
                * For a cone, set planes=2. For a pyramid, set radialSamples=4 and planes=2. - * Setting higher values for planes and radialSamples increases + * Setting higher values for planes and radialSamples increases * the quality of the half-sphere. - * + * * @param center * Center of the dome. * @param planes @@ -140,22 +140,28 @@ public Vector3f getCenter() { return center; } - /** - * Get the number of planar segments along the z-axis of the dome. + /** + * Get the number of planar segments along the z-axis of the dome. + * + * @return the count */ public int getPlanes() { return planes; } - /** - * Get the number of samples radially around the main axis of the dome. + /** + * Get the number of samples radially around the main axis of the dome. + * + * @return the count */ public int getRadialSamples() { return radialSamples; } - /** - * Get the radius of the dome. + /** + * Get the radius of the dome. + * + * @return the radius (in mesh units) */ public float getRadius() { return radius; @@ -163,6 +169,8 @@ public float getRadius() { /** * Are the triangles connected in such a way as to present a view out from the dome or not. + * + * @return true if visible from inside, false if visible from outside */ public boolean isInsideView() { return insideView; @@ -170,7 +178,7 @@ public boolean isInsideView() { /** * Rebuilds the dome with a new set of parameters. - * + * * @param center the new center of the dome. * @param planes the number of planes along the Z-axis. * @param radialSamples the new number of radial samples of the dome. @@ -282,14 +290,14 @@ public void updateGeometry(Vector3f center, int planes, int bottomPlaneStart = ((plane - 1) * (radialSamples + 1)); int topPlaneStart = (plane * (radialSamples + 1)); for (int sample = 0; sample < radialSamples; sample++, index += 6) { - if (insideView){ + if (insideView) { ib.put((short) (bottomPlaneStart + sample)); ib.put((short) (bottomPlaneStart + sample + 1)); ib.put((short) (topPlaneStart + sample)); ib.put((short) (bottomPlaneStart + sample + 1)); ib.put((short) (topPlaneStart + sample + 1)); ib.put((short) (topPlaneStart + sample)); - }else{ + } else { ib.put((short) (bottomPlaneStart + sample)); ib.put((short) (topPlaneStart + sample)); ib.put((short) (bottomPlaneStart + sample + 1)); @@ -303,11 +311,11 @@ public void updateGeometry(Vector3f center, int planes, // pole triangles int bottomPlaneStart = (planes - 2) * (radialSamples + 1); for (int samples = 0; samples < radialSamples; samples++, index += 3) { - if (insideView){ + if (insideView) { ib.put((short) (bottomPlaneStart + samples)); ib.put((short) (bottomPlaneStart + samples + 1)); ib.put((short) (vertCount - 1)); - }else{ + } else { ib.put((short) (bottomPlaneStart + samples)); ib.put((short) (vertCount - 1)); ib.put((short) (bottomPlaneStart + samples + 1)); @@ -318,9 +326,9 @@ public void updateGeometry(Vector3f center, int planes, } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); planes = capsule.readInt("planes", 0); radialSamples = capsule.readInt("radialSamples", 0); radius = capsule.readFloat("radius", 0); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java b/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java index fad5f731cc..d301a98675 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/PQTorus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -136,12 +136,12 @@ public void updateGeometry(float p, float q, float radius, float width, int step Vector3f pointB, T, N, B; Vector3f tempNorm = new Vector3f(); float r, x, y, z, theta = 0.0f, beta; - int nvertex = 0; + int nVertex = 0; // Move along the length of the pq torus for (int i = 0; i < steps; i++) { theta += thetaStep; - float circleFraction = ((float) i) / (float) steps; + float circleFraction = i / (float) steps; // Find the point on the torus r = (0.5f * (2.0f + FastMath.sin(q * theta)) * radius); @@ -167,7 +167,7 @@ public void updateGeometry(float p, float q, float radius, float width, int step N = N.normalize(); B = B.normalize(); beta = 0.0f; - for (int j = 0; j < radialSamples; j++, nvertex++) { + for (int j = 0; j < radialSamples; j++, nVertex++) { beta += betaStep; float cx = FastMath.cos(beta) * width; float cy = FastMath.sin(beta) * width; @@ -215,9 +215,9 @@ public void updateGeometry(float p, float q, float radius, float width, int step } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); p = capsule.readFloat("p", 0); q = capsule.readFloat("q", 0); radius = capsule.readFloat("radius", 0); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java b/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java index c5a4d0e139..07c161c301 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Quad.java @@ -45,7 +45,7 @@ * defined by 4 vertices. The quad's lower-left side is contained * at the local space origin (0, 0, 0), while the upper-right * side is located at the width/height coordinates (width, height, 0). - * + * * @author Kirill Vainer */ public class Quad extends Mesh { @@ -56,30 +56,30 @@ public class Quad extends Mesh { /** * Serialization only. Do not use. */ - protected Quad(){ + protected Quad() { } /** * Create a quad with the given width and height. The quad * is always created in the XY plane. - * + * * @param width The X extent or width * @param height The Y extent or width */ - public Quad(float width, float height){ + public Quad(float width, float height) { updateGeometry(width, height); } /** * Create a quad with the given width and height. The quad * is always created in the XY plane. - * + * * @param width The X extent or width * @param height The Y extent or width * @param flipCoords If true, the texture coordinates will be flipped * along the Y axis. */ - public Quad(float width, float height, boolean flipCoords){ + public Quad(float width, float height, boolean flipCoords) { updateGeometry(width, height, flipCoords); } @@ -91,7 +91,7 @@ public float getWidth() { return width; } - public void updateGeometry(float width, float height){ + public void updateGeometry(float width, float height) { updateGeometry(width, height, false); } @@ -103,14 +103,14 @@ public void updateGeometry(float width, float height, boolean flipCoords) { width, height, 0, 0, height, 0 }); - - if (flipCoords){ + + if (flipCoords) { setBuffer(Type.TexCoord, 2, new float[]{0, 1, 1, 1, 1, 0, 0, 0}); - }else{ + } else { setBuffer(Type.TexCoord, 2, new float[]{0, 0, 1, 0, 1, 1, @@ -120,22 +120,22 @@ public void updateGeometry(float width, float height, boolean flipCoords) { 0, 0, 1, 0, 0, 1, 0, 0, 1}); - if (height < 0){ + if (height < 0) { setBuffer(Type.Index, 3, new short[]{0, 2, 1, 0, 3, 2}); - }else{ + } else { setBuffer(Type.Index, 3, new short[]{0, 1, 2, 0, 2, 3}); } - + updateBound(); setStatic(); } - + @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); width = capsule.readFloat("width", 0); height = capsule.readFloat("height", 0); } diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/RectangleMesh.java b/jme3-core/src/main/java/com/jme3/scene/shape/RectangleMesh.java new file mode 100644 index 0000000000..6f16847390 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/RectangleMesh.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2021 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.shape; + +import java.io.IOException; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.math.Rectangle; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Mesh; +import com.jme3.scene.VertexBuffer.Type; +import com.jme3.util.BufferUtils; +import com.jme3.util.clone.Cloner; + +/** + * A static, indexed, Triangle-mode mesh that renders a rectangle or + * parallelogram, with customizable normals and texture coordinates. + * + *

                It uses a {@link com.jme3.math.Rectangle} to locate its vertices, which + * are named as follows: + * + *

                + *     C +----+ D
                + *       |\   |
                + *       | \  |
                + *       |  \ |
                + *       |   \|
                + *     A +----+ B
                + * 
                + * + *

                In the vertex buffers, the order of the vertices is A, B, D, then C. + * + *

                The default texture coordinates have:

                  + *
                • U=0 at vertices A and C
                • + *
                • U=1 at vertices B and D
                • + *
                • V=0 at vertices A and B
                • + *
                • V=1 at vertices C and D
                + * + * @author Francivan Bezerra + */ +public class RectangleMesh extends Mesh { + + /** + * Used to locate the vertices and calculate a default normal. + */ + private Rectangle rectangle; + + /** + * Texture coordinates in A-B-D-C order. + */ + private Vector2f[] texCoords; + + /** + * Normal direction for all 4 vertices. + */ + private Vector3f normal; + + /** + * Used to indicate whether this mesh is flipped. + */ + private boolean flipped; + + /** + * Instantiates a unit-square mesh in the X-Y plane, centered at (0.5, 0.5), + * with normals in the +Z direction. + * + */ + public RectangleMesh() { + this(new Rectangle(new Vector3f(), new Vector3f(1, 0, 0), new Vector3f(0, 1, 0))); + } + + /** + * Instantiates a rectangle or parallelogram mesh based on the specified + * {@link com.jme3.math.Rectangle}. + * + * @param rectangle to locate the vertices and set the normals (not null, + * alias created) + */ + public RectangleMesh(Rectangle rectangle) { + this.rectangle = rectangle; + this.texCoords = new Vector2f[] { + new Vector2f(0, 0), + new Vector2f(1, 0), + new Vector2f(1, 1), + new Vector2f(0, 1) + }; + flipped = false; + updateMesh(); + } + + /** + * Instantiates a rectangle or parallelogram mesh based on 3 specified + * vertex positions. + * + * @param a the mesh position of vertex A (not null, alias created) + * @param b the mesh position of vertex B (not null, alias created) + * @param c the mesh position of vertex C (not null, alias created) + */ + public RectangleMesh(Vector3f a, Vector3f b, Vector3f c) { + this(new Rectangle(a, b, c)); + } + + /** + * Provides access to the internal {@link com.jme3.math.Rectangle} on which + * the mesh is based. + * + * @return the pre-existing instance (do not modify!) + */ + public Rectangle getRectangle() { + return rectangle; + } + + /** + * Sets the {@link com.jme3.math.Rectangle} and updates the mesh + * accordingly. + * + * @param rectangle the desired Rectangle (not null, alias created) + */ + public void setRectangle(Rectangle rectangle) { + this.rectangle = rectangle; + updateMesh(); + } + + /** + * Provides access to the internal texture-coordinate array. + * + * @return the pre-existing array of length 4 (do not modify!) + */ + public Vector2f[] getTexCoords() { + return texCoords; + } + + /** + * Sets the texture coordinates and updates the mesh accordingly. + * + * @param texCoords the desired texture coordinates for each vertex (not + * null, alias created) + * @throws IllegalArgumentException if the array length isn't exactly 4 + */ + public void setTexCoords(Vector2f[] texCoords) throws IllegalArgumentException { + if (texCoords.length != 4) { + throw new IllegalArgumentException( + "Texture coordinates are 4 vertices, therefore a Vector2f array of length 4 must be provided."); + } + this.texCoords = texCoords; + updateMesh(); + } + + /** + * Provides access to the internal normal-direction vector. + * + * @return the pre-existing vector (do not modify!) + */ + public Vector3f getNormal() { + return normal; + } + + /** + * Flips this mesh by reversing its normal vector direction and + * setting the {@code flipped} variable accordingly. This variable + * will be used by the {@code updateMesh()} method to rearrange + * the index buffer. + */ + public void flip() { + normal.negateLocal(); + flipped = !flipped; + updateMesh(); + } + + protected void updateMesh() { + Vector3f a = rectangle.getA(); + Vector3f b = rectangle.getB(); + Vector3f c = rectangle.getC(); + Vector3f d = rectangle.calculateD(); + setBuffer(Type.Position, 3, new float[] { + a.x, a.y, a.z, + b.x, b.y, b.z, + d.x, d.y, d.z, + c.x, c.y, c.z + }); + + setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(texCoords)); + + if (normal == null) { + normal = rectangle.calculateNormal(null); + } + setBuffer(Type.Normal, 3, BufferUtils.createFloatBuffer(normal, normal, normal, normal)); + + if (flipped) { + setBuffer(Type.Index, 3, new short[]{1, 0, 3, 3, 2, 1}); + } else { + setBuffer(Type.Index, 3, new short[]{3, 0, 1, 1, 2, 3}); + } + + updateBound(); + setStatic(); + } + + /** + * Called internally by com.jme3.util.clone.Cloner. Do not call directly. + */ + @Override + public void cloneFields(Cloner cloner, Object original) { + super.cloneFields(cloner, original); + this.rectangle = cloner.clone(rectangle); + this.texCoords = cloner.clone(texCoords); + this.normal = cloner.clone(normal); + } + + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + final InputCapsule capsule = importer.getCapsule(this); + rectangle = (Rectangle) capsule.readSavable("rectangle", new Rectangle( + new Vector3f(), + new Vector3f(1, 0, 0), + new Vector3f(0, 1, 0))); + texCoords = (Vector2f[]) capsule.readSavableArray("texCoords", new Vector2f[] { + new Vector2f(0, 0), + new Vector2f(1, 0), + new Vector2f(1, 1), + new Vector2f(0, 1) }); + normal = (Vector3f) capsule.readSavable("normal", null); + flipped = capsule.readBoolean("flipped", false); + } + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + final OutputCapsule capsule = e.getCapsule(this); + capsule.write(rectangle, "rectangle", new Rectangle( + new Vector3f(), + new Vector3f(1, 0, 0), + new Vector3f(0, 1, 0))); + capsule.write(texCoords, "texCoords", new Vector2f[] { + new Vector2f(0, 0), + new Vector2f(1, 0), + new Vector2f(1, 1), + new Vector2f(0, 1) + }); + capsule.write(normal, "normal", null); + capsule.write(flipped, "flipped", false); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java b/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java index a9deee3674..f4b2ba34c6 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Sphere.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -337,7 +337,7 @@ private void setIndexData() { } } - // south pole triangles + // south-pole triangles for (int i = 0; i < radialSamples; i++, index += 3) { if (!interior) { idxBuf.put((short) i); @@ -350,7 +350,7 @@ private void setIndexData() { } } - // north pole triangles + // north-pole triangles int iOffset = (zSamples - 3) * (radialSamples + 1); for (int i = 0; i < radialSamples; i++, index += 3) { if (!interior) { @@ -402,9 +402,10 @@ public void updateGeometry(int zSamples, int radialSamples, float radius, boolea setStatic(); } - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); zSamples = capsule.readInt("zSamples", 0); radialSamples = capsule.readInt("radialSamples", 0); radius = capsule.readFloat("radius", 0); @@ -413,6 +414,7 @@ public void read(JmeImporter e) throws IOException { interior = capsule.readBoolean("interior", false); } + @Override public void write(JmeExporter e) throws IOException { super.write(e); OutputCapsule capsule = e.getCapsule(this); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java index b76168b75f..16d1eb31d6 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/StripBox.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -139,12 +139,14 @@ public StripBox clone() { return new StripBox(center.clone(), xExtent, yExtent, zExtent); } + @Override protected void doUpdateGeometryIndices() { if (getBuffer(Type.Index) == null){ setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(GEOMETRY_INDICES_DATA)); } } + @Override protected void doUpdateGeometryNormals() { if (getBuffer(Type.Normal) == null){ float[] normals = new float[8 * 3]; @@ -164,12 +166,14 @@ protected void doUpdateGeometryNormals() { } } + @Override protected void doUpdateGeometryTextures() { if (getBuffer(Type.TexCoord) == null){ setBuffer(Type.TexCoord, 2, BufferUtils.createFloatBuffer(GEOMETRY_TEXTURE_DATA)); } } + @Override protected void doUpdateGeometryVertices() { FloatBuffer fpb = BufferUtils.createVector3Buffer(8 * 3); Vector3f[] v = computeVertices(); diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java b/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java index a359c6d4fd..9beef259e4 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Surface.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,10 @@ */ package com.jme3.scene.shape; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.math.CurveAndSurfaceMath; import com.jme3.math.FastMath; import com.jme3.math.Spline.SplineType; @@ -39,6 +43,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.util.BufferUtils; +import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; @@ -68,7 +73,7 @@ public class Surface extends Mesh { * @param vSegments the amount of V segments * @param basisUFunctionDegree the degree of basis U function * @param basisVFunctionDegree the degree of basis V function - * @param smooth defines if the mesu should be smooth (true) or flat (false) + * @param smooth defines if the mesh should be smooth (true) or flat (false) */ private Surface(List> controlPoints, List[] nurbKnots, int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree, boolean smooth) { this.validateInputData(controlPoints, nurbKnots, uSegments, vSegments); @@ -87,6 +92,12 @@ private Surface(List> controlPoints, List[] nurbKnots, int this.buildSurface(smooth); } + /** + * For serialization only. Do not use. + */ + protected Surface() { + } + /** * This method creates a NURBS surface. The created mesh is smooth by default. * @param controlPoints @@ -115,6 +126,7 @@ public static final Surface createNurbsSurface(List> controlPoint * @param vSegments the amount of V segments * @param basisUFunctionDegree the degree of basis U function * @param basisVFunctionDegree the degree of basis V function + * @param smooth true for a smooth mesh * @return an instance of NURBS surface */ public static final Surface createNurbsSurface(List> controlPoints, List[] nurbKnots, int uSegments, int vSegments, int basisUFunctionDegree, int basisVFunctionDegree, boolean smooth) { @@ -126,7 +138,7 @@ public static final Surface createNurbsSurface(List> controlPoint /** * This method creates the surface. * @param smooth - * defines if the mesu should be smooth (true) or flat (false) + * defines if the mesh should be smooth (true) or flat (false) */ private void buildSurface(boolean smooth) { float minUKnot = this.getMinUNurbKnot(); @@ -137,7 +149,7 @@ private void buildSurface(boolean smooth) { float maxVKnot = this.getMaxVNurbKnot(); float deltaV = (maxVKnot - minVKnot) / vSegments; - List vertices = new ArrayList((uSegments + 1) * (vSegments + 1));// new Vector3f[(uSegments + 1) * (vSegments + 1)]; + List vertices = new ArrayList<>((uSegments + 1) * (vSegments + 1));// new Vector3f[(uSegments + 1) * (vSegments + 1)]; float u = minUKnot, v = minVKnot; for (int i = 0; i <= vSegments; ++i) { @@ -164,21 +176,28 @@ private void buildSurface(boolean smooth) { int uVerticesAmount = uSegments + 1; int vVerticesAmount = vSegments + 1; int newUVerticesAmount = 2 + (uVerticesAmount - 2) * 2; - List verticesWithUDuplicates = new ArrayList(vVerticesAmount * newUVerticesAmount); - for(int i=0;i verticesWithUDuplicates = new ArrayList<>(vVerticesAmount * newUVerticesAmount); + for (int i=0; i verticesWithVDuplicates = new ArrayList(verticesWithUDuplicates.size() * vVerticesAmount); + List verticesWithVDuplicates + = new ArrayList<>(verticesWithUDuplicates.size() * vVerticesAmount); verticesWithVDuplicates.addAll(verticesWithUDuplicates.subList(0, newUVerticesAmount)); - for(int i=1;i normalMap = new HashMap(verticesArray.length); + Map normalMap = new HashMap<>(verticesArray.length); for (int i = 0; i < indices.length; i += 3) { - Vector3f n = FastMath.computeNormal(verticesArray[indices[i]], verticesArray[indices[i + 1]], verticesArray[indices[i + 2]]); - this.addNormal(n, normalMap, smooth, verticesArray[indices[i]], verticesArray[indices[i + 1]], verticesArray[indices[i + 2]]); + Vector3f n = FastMath.computeNormal(verticesArray[indices[i]], + verticesArray[indices[i + 1]], verticesArray[indices[i + 2]]); + this.addNormal(n, normalMap, smooth, verticesArray[indices[i]], + verticesArray[indices[i + 1]], verticesArray[indices[i + 2]]); } // preparing normal list (the order of normals must match the order of vertices) float[] normals = new float[verticesArray.length * 3]; @@ -288,6 +309,88 @@ public SplineType getType() { return type; } + /** + * De-serializes from the specified importer, for example when loading from + * a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + @SuppressWarnings("unchecked") + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + type = capsule.readEnum("type", SplineType.class, null); + basisUFunctionDegree = capsule.readInt("basisUFunctionDegree", 0); + basisVFunctionDegree = capsule.readInt("basisVFunctionDegree", 0); + uSegments = capsule.readInt("uSegments", 0); + vSegments = capsule.readInt("vSegments", 0); + + float[][] knotArray2D = capsule.readFloatArray2D("knotArray2D", null); + int numKnotArrayLists = knotArray2D.length; + knots = new ArrayList[numKnotArrayLists]; + for (int i = 0; i < numKnotArrayLists; ++i) { + float[] knotArray = knotArray2D[i]; + knots[i] = new ArrayList<>(knotArray.length); + for (float knot : knotArray) { + knots[i].add(knot); + } + } + + List[] listArray = capsule.readSavableArrayListArray("listArray", null); + int numControlPointLists = listArray.length; + controlPoints = new ArrayList<>(numControlPointLists); + for (int i = 0; i < numControlPointLists; ++i) { + List list = listArray[i]; + controlPoints.add(list); + } + } + + /** + * Serializes to the specified exporter, for example when saving to a J3O + * file. The current instance is unaffected. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + @SuppressWarnings("unchecked") + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(type, "type", null); + capsule.write(basisUFunctionDegree, "basisUFunctionDegree", 0); + capsule.write(basisVFunctionDegree, "basisVFunctionDegree", 0); + capsule.write(uSegments, "uSegments", 0); + capsule.write(vSegments, "vSegments", 0); + + int numKnotArrayLists = knots.length; + float[][] knotArray2D = new float[numKnotArrayLists][]; + for (int i = 0; i < numKnotArrayLists; ++i) { + List list = knots[i]; + int numKnots = list.size(); + float[] array = new float[numKnots]; + for (int j = 0; j < numKnots; ++j) { + array[j] = list.get(j); + } + knotArray2D[i] = array; + } + capsule.write(knotArray2D, "knotArray2D", null); + + int numControlPointLists = controlPoints.size(); + ArrayList[] listArray = new ArrayList[numControlPointLists]; + for (int i = 0; i < numControlPointLists; ++i) { + List list = controlPoints.get(i); + int numVectors = list.size(); + listArray[i] = new ArrayList<>(numVectors); + listArray[i].addAll(list); + } + capsule.writeSavableArrayListArray(listArray, "listArray", null); + } + /** * This method returns the minimum nurb curve U knot value. * @return the minimum nurb curve knot value @@ -329,7 +432,7 @@ private float getMaxVNurbKnot() { * @param normalMap * merges normals of faces that will be rendered smooth; the key is the vertex and the value - its normal vector * @param smooth the variable that indicates whether to merge normals - * (creating the smooth mesh) or not + * (creating the smooth mesh) or not * @param vertices * a list of vertices read from the blender file */ diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java b/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java index 21d77a5412..d3e75d6faf 100644 --- a/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java +++ b/jme3-core/src/main/java/com/jme3/scene/shape/Torus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -103,13 +103,13 @@ public int getRadialSamples() { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); circleSamples = capsule.readInt("circleSamples", 0); radialSamples = capsule.readInt("radialSamples", 0); innerRadius = capsule.readFloat("innerRadius", 0); - outerRadius = capsule.readFloat("outerRaidus", 0); + outerRadius = capsule.readFloat("outerRadius", 0); } private void setGeometryData() { diff --git a/jme3-core/src/main/java/com/jme3/scene/shape/package-info.java b/jme3-core/src/main/java/com/jme3/scene/shape/package-info.java new file mode 100644 index 0000000000..b235cab7b9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/shape/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * generate meshes for various geometric shapes + */ +package com.jme3.scene.shape; diff --git a/jme3-core/src/main/java/com/jme3/scene/threadwarden/IllegalThreadSceneGraphMutation.java b/jme3-core/src/main/java/com/jme3/scene/threadwarden/IllegalThreadSceneGraphMutation.java new file mode 100644 index 0000000000..64fe4bee8a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/threadwarden/IllegalThreadSceneGraphMutation.java @@ -0,0 +1,7 @@ +package com.jme3.scene.threadwarden; + +public class IllegalThreadSceneGraphMutation extends IllegalStateException{ + public IllegalThreadSceneGraphMutation(String message){ + super(message); + } +} diff --git a/jme3-core/src/main/java/com/jme3/scene/threadwarden/SceneGraphThreadWarden.java b/jme3-core/src/main/java/com/jme3/scene/threadwarden/SceneGraphThreadWarden.java new file mode 100644 index 0000000000..72724c380d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/scene/threadwarden/SceneGraphThreadWarden.java @@ -0,0 +1,160 @@ +package com.jme3.scene.threadwarden; + +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Thread warden keeps track of mutations to the scene graph and ensures that they are only done on the main thread. + * IF the parent node is marked as being reserved for the main thread (which basically means it's connected to the + * root node) + *

                + * Only has an effect if asserts are on + *

                + */ +public class SceneGraphThreadWarden { + + private static final Logger logger = Logger.getLogger(SceneGraphThreadWarden.class.getName()); + + /** + * If THREAD_WARDEN_ENABLED is true AND asserts are on the checks are made. + * This parameter is here to allow asserts to run without thread warden checks (by setting this parameter to false) + */ + public static boolean THREAD_WARDEN_ENABLED = !Boolean.getBoolean("nothreadwarden"); + + public static boolean ASSERTS_ENABLED = false; + + static{ + //noinspection AssertWithSideEffects + assert ASSERTS_ENABLED = true; + } + + public static Thread mainThread; + public static final Set spatialsThatAreMainThreadReserved = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<>())); + + /** + * Marks the given node as being reserved for the main thread. + * Additionally, sets the current thread as the main thread (if it hasn't already been set) + * @param rootNode the root node of the scene graph. This is used to determine if a spatial is a child of the root node. + * (Can add multiple "root" nodes, e.g. gui nodes or overlay nodes) + */ + public static boolean setup(Node rootNode){ + if(checksDisabled()){ + return true; + } + Thread thisThread = Thread.currentThread(); + if(mainThread != null && mainThread != thisThread ){ + throw new IllegalStateException("The main thread has already been set to " + mainThread.getName() + " but now it's being set to " + Thread.currentThread().getName()); + } + mainThread = thisThread; + setTreeRestricted(rootNode); + + return true; // return true so can be a "side effect" of an assert + } + + /** + * Disables the thread warden checks (even when other asserts are on). + *

                + * Alternatively can be disabled by adding the -Dnothreadwarden=true parameter + *

                + */ + public static void disableChecks(){ + THREAD_WARDEN_ENABLED = false; + } + + /** + * Runs through the entire tree and sets the restriction state of all nodes below the given node + * @param spatial the node (and children) to set the restriction state of + */ + private static void setTreeRestricted(Spatial spatial){ + spatialsThatAreMainThreadReserved.add(spatial); + if(spatial instanceof Node){ + for(Spatial child : ((Node) spatial).getChildren()){ + setTreeRestricted(child); + } + } + } + + /** + * Releases this tree from being only allowed to be mutated on the main thread + * @param spatial the node (and children) to release the restriction state of. + */ + private static void setTreeNotRestricted(Spatial spatial){ + spatialsThatAreMainThreadReserved.remove(spatial); + if(spatial instanceof Node){ + for(Spatial child : ((Node) spatial).getChildren()){ + setTreeNotRestricted(child); + } + } + } + + @SuppressWarnings("SameReturnValue") + public static boolean updateRequirement(Spatial spatial, Node newParent){ + if(checksDisabled()){ + return true; + } + + boolean shouldNowBeRestricted = newParent !=null && spatialsThatAreMainThreadReserved.contains(newParent); + boolean wasPreviouslyRestricted = spatialsThatAreMainThreadReserved.contains(spatial); + + if(shouldNowBeRestricted || wasPreviouslyRestricted ){ + assertOnCorrectThread(spatial); + } + + if(shouldNowBeRestricted == wasPreviouslyRestricted){ + return true; + } + if(shouldNowBeRestricted){ + setTreeRestricted(spatial); + }else{ + setTreeNotRestricted(spatial); + } + + return true; // return true so can be a "side effect" of an assert + } + + public static boolean reset(){ + spatialsThatAreMainThreadReserved.clear(); + mainThread = null; + THREAD_WARDEN_ENABLED = !Boolean.getBoolean("nothreadwarden"); + return true; // return true so can be a "side effect" of an assert + } + + private static boolean checksDisabled(){ + return !THREAD_WARDEN_ENABLED || !ASSERTS_ENABLED; + } + + @SuppressWarnings("SameReturnValue") + public static boolean assertOnCorrectThread(Spatial spatial){ + if(checksDisabled()){ + return true; + } + if(spatialsThatAreMainThreadReserved.contains(spatial)){ + if(Thread.currentThread() != mainThread){ + // log as well as throw an exception because we are running in a thread, if we are in an executor service the exception + // might not make itself known until `get` is called on the future (and JME might crash before that happens). + String message = "The spatial " + spatial + " was mutated on a thread other than the main thread, was mutated on " + Thread.currentThread().getName(); + IllegalThreadSceneGraphMutation ex = new IllegalThreadSceneGraphMutation(message); + logger.log(Level.WARNING, message, ex); + + throw ex; + } + } + return true; // return true so can be a "side effect" of an assert + } + + public static String getTurnOnAssertsPrompt(){ + if(ASSERTS_ENABLED){ + return ""; + } else{ + return "To get more accurate debug consider turning on asserts. This will allow JME to do additional checks which *may* find the source of the problem. To do so, add -ea to the JVM arguments."; + } + } + +} + diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/BufferObject.java deleted file mode 100644 index 886f4a1abe..0000000000 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObject.java +++ /dev/null @@ -1,828 +0,0 @@ -package com.jme3.shader; - -import com.jme3.math.*; -import com.jme3.renderer.Caps; -import com.jme3.renderer.Renderer; -import com.jme3.util.BufferUtils; -import com.jme3.util.NativeObject; -import com.jme3.util.SafeArrayList; - -import java.nio.ByteBuffer; -import java.util.Collection; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * The base implementation of BO. - * - * @author JavaSaBr - */ -public class BufferObject extends NativeObject { - - private static final Map, VarType> CLASS_TO_VAR_TYPE = new HashMap<>(); - - static { - CLASS_TO_VAR_TYPE.put(Float.class, VarType.Float); - CLASS_TO_VAR_TYPE.put(Integer.class, VarType.Int); - CLASS_TO_VAR_TYPE.put(Boolean.class, VarType.Boolean); - CLASS_TO_VAR_TYPE.put(Vector2f.class, VarType.Vector2); - CLASS_TO_VAR_TYPE.put(Vector3f.class, VarType.Vector3); - CLASS_TO_VAR_TYPE.put(ColorRGBA.class, VarType.Vector4); - CLASS_TO_VAR_TYPE.put(Quaternion.class, VarType.Vector4); - CLASS_TO_VAR_TYPE.put(Vector4f.class, VarType.Vector4); - - CLASS_TO_VAR_TYPE.put(Vector2f[].class, VarType.Vector2Array); - CLASS_TO_VAR_TYPE.put(Vector3f[].class, VarType.Vector3Array); - CLASS_TO_VAR_TYPE.put(Vector4f[].class, VarType.Vector4Array); - CLASS_TO_VAR_TYPE.put(ColorRGBA[].class, VarType.Vector4Array); - CLASS_TO_VAR_TYPE.put(Quaternion[].class, VarType.Vector4Array); - - CLASS_TO_VAR_TYPE.put(Matrix3f.class, VarType.Matrix3); - CLASS_TO_VAR_TYPE.put(Matrix4f.class, VarType.Matrix4); - CLASS_TO_VAR_TYPE.put(Matrix3f[].class, VarType.Matrix3Array); - CLASS_TO_VAR_TYPE.put(Matrix4f[].class, VarType.Matrix4Array); - } - - protected static VarType getVarTypeByValue(final Object value) { - - final VarType varType = CLASS_TO_VAR_TYPE.get(value.getClass()); - if (varType != null) { - return varType; - } else if (value instanceof Collection && ((Collection) value).isEmpty()) { - throw new IllegalArgumentException("Can't calculate a var type for the empty collection value[" + value + "]."); - } else if (value instanceof List) { - return getVarTypeByValue(((List) value).get(0)); - } else if (value instanceof Collection) { - return getVarTypeByValue(((Collection) value).iterator().next()); - } - - throw new IllegalArgumentException("Can't calculate a var type for the value " + value); - } - - public enum Layout { - std140, - /** unsupported yet */ - @Deprecated - std430, - } - - public enum BufferType { - ShaderStorageBufferObject(Caps.ShaderStorageBufferObject), - UniformBufferObject(Caps.UniformBufferObject), - ; - - private final Caps requiredCaps; - - BufferType(final Caps requiredCaps) { - this.requiredCaps = requiredCaps; - } - - /** - * Get the required caps. - * - * @return the required caps. - */ - public Caps getRequiredCaps() { - return requiredCaps; - } - } - - /** - * The fields of this BO. - */ - private final Map fields; - - /** - * The field's array. - */ - private final SafeArrayList fieldArray; - - /** - * The buffer's data layout. - */ - private final Layout layout; - - /** - * The binding number. - */ - private final int binding; - - /** - * The buffer's type. - */ - private BufferType bufferType; - - /** - * The previous data buffer. - */ - private ByteBuffer previousData; - - public BufferObject(final int binding, final Layout layout, final BufferType bufferType) { - this.handleRef = new Object(); - this.bufferType = bufferType; - this.binding = binding; - this.layout = layout; - this.fields = new HashMap<>(); - this.fieldArray = new SafeArrayList<>(BufferObjectField.class); - } - - public BufferObject(final int binding, final Layout layout) { - this(binding, layout, BufferType.UniformBufferObject); - } - - public BufferObject(final int binding, final BufferType bufferType) { - this(binding, Layout.std140, bufferType); - } - - public BufferObject(final BufferType bufferType) { - this(1, Layout.std140, bufferType); - } - - public BufferObject(final Layout layout) { - this(1, layout, BufferType.UniformBufferObject); - } - - public BufferObject(final int binding) { - this(binding, Layout.std140, BufferType.UniformBufferObject); - } - - public BufferObject() { - this(1, Layout.std140, BufferType.UniformBufferObject); - } - - private BufferObject(final Void unused, final int id) { - super(id); - this.fieldArray = null; - this.fields = null; - this.layout = null; - this.binding = 0; - } - - /** - * Declares a filed in this BO. - * - * @param name the field's name. - * @param varType the field's type. - */ - public void declareField(final String name, final VarType varType) { - - if (fields.containsKey(name)) { - throw new IllegalArgumentException("The field " + name + " is already declared."); - } - - final BufferObjectField field = new BufferObjectField(name, varType); - - fields.put(name, field); - fieldArray.add(field); - } - - /** - * Gets the buffer's type. - * - * @return the buffer's type. - */ - public BufferType getBufferType() { - return bufferType; - } - - /** - * Sets the buffer's type. - * - * @param bufferType the buffer's type. - */ - public void setBufferType(final BufferType bufferType) { - - if (getId() != -1) { - throw new IllegalStateException("Can't change buffer's type when this buffer is already initialized."); - } - - this.bufferType = bufferType; - } - - /** - * Sets the value to the filed by the field's name. - * - * @param name the field's name. - * @param value the value. - */ - public void setFieldValue(final String name, final Object value) { - - BufferObjectField field = fields.get(name); - - if (field == null) { - declareField(name, getVarTypeByValue(value)); - field = fields.get(name); - } - - field.setValue(value); - setUpdateNeeded(); - } - - /** - * Gets the current value of the field by the name. - * - * @param name the field name. - * @param the value's type. - * @return the current value. - */ - public T getFieldValue(final String name) { - - final BufferObjectField field = fields.get(name); - if (field == null) { - throw new IllegalArgumentException("Unknown a field with the name " + name); - } - - return (T) field.getValue(); - } - - /** - * Get the binding number. - * - * @return the binding number. - */ - public int getBinding() { - return binding; - } - - @Override - public void resetObject() { - this.id = -1; - setUpdateNeeded(); - } - - /** - * Computes the current binary data of this BO. - * - * @param maxSize the max data size. - * @return the current binary data of this BO. - */ - public ByteBuffer computeData(final int maxSize) { - - int estimateSize = 0; - - for (final BufferObjectField field : fieldArray) { - estimateSize += estimateSize(field); - } - - if(maxSize < estimateSize) { - throw new IllegalStateException("The estimated size(" + estimateSize + ") of this BO is bigger than " + - "maximum available size " + maxSize); - } - - if (previousData != null) { - if (previousData.capacity() < estimateSize) { - BufferUtils.destroyDirectBuffer(previousData); - previousData = null; - } else { - previousData.clear(); - } - } - - final ByteBuffer data = previousData == null ? BufferUtils.createByteBuffer(estimateSize) : previousData; - - for (final BufferObjectField field : fieldArray) { - writeField(field, data); - } - - data.flip(); - - this.previousData = data; - - return data; - } - - /** - * Estimates size of the field. - * - * @param field the field. - * @return the estimated size. - */ - protected int estimateSize(final BufferObjectField field) { - - switch (field.getType()) { - case Float: - case Int: { - if (layout == Layout.std140) { - return 16; - } - return 4; - } - case Boolean: { - if (layout == Layout.std140) { - return 16; - } - return 1; - } - case Vector2: { - return 4 * 2; - } - case Vector3: { - final int multiplier = layout == Layout.std140 ? 4 : 3; - return 4 * multiplier; - } - case Vector4: - return 16; - case IntArray: { - return estimate((int[]) field.getValue()); - } - case FloatArray: { - return estimate((float[]) field.getValue()); - } - case Vector2Array: { - return estimateArray(field.getValue(), 8); - } - case Vector3Array: { - final int multiplier = layout == Layout.std140 ? 16 : 12; - return estimateArray(field.getValue(), multiplier); - } - case Vector4Array: { - return estimateArray(field.getValue(), 16); - } - case Matrix3: { - final int multiplier = layout == Layout.std140 ? 16 : 12; - return 3 * 3 * multiplier; - } - case Matrix4: { - return 4 * 4 * 4; - } - case Matrix3Array: { - int multiplier = layout == Layout.std140 ? 16 : 12; - multiplier = 3 * 3 * multiplier; - return estimateArray(field.getValue(), multiplier); - } - case Matrix4Array: { - final int multiplier = 4 * 4 * 16; - return estimateArray(field.getValue(), multiplier); - } - default: { - throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); - } - } - } - - /** - * Estimates bytes count to present the value on GPU. - * - * @param value the value. - * @param multiplier the multiplier. - * @return the estimated bytes cunt. - */ - protected int estimateArray(final Object value, final int multiplier) { - - if (value instanceof Object[]) { - return ((Object[]) value).length * multiplier; - } else if (value instanceof Collection) { - return ((Collection) value).size() * multiplier; - } - - throw new IllegalArgumentException("Unexpected value " + value); - } - - /** - * Estimates bytes count to present the values on GPU. - * - * @param values the values. - * @return the estimated bytes cunt. - */ - protected int estimate(final float[] values) { - return values.length * 4; - } - - /** - * Estimates bytes count to present the values on GPU. - * - * @param values the values. - * @return the estimated bytes cunt. - */ - protected int estimate(final int[] values) { - return values.length * 4; - } - - /** - * Writes the field to the data buffer. - * - * @param field the field. - * @param data the data buffer. - */ - protected void writeField(final BufferObjectField field, final ByteBuffer data) { - - final Object value = field.getValue(); - - switch (field.getType()) { - case Int: { - data.putInt(((Number) value).intValue()); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - } - break; - } - case Float: { - data.putFloat(((Number) value).floatValue()); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - } - break; - } - case Boolean: - data.put((byte) (((Boolean) value) ? 1 : 0)); - if (layout == Layout.std140) { - data.putInt(0); - data.putLong(0); - data.putShort((short) 0); - data.put((byte) 0); - } - break; - case Vector2: - write(data, (Vector2f) value); - break; - case Vector3: - write(data, (Vector3f) value); - break; - case Vector4: - writeVec4(data, value); - break; - case IntArray: { - write(data, (int[]) value); - break; - } - case FloatArray: { - write(data, (float[]) value); - break; - } - case Vector2Array: { - writeVec2Array(data, value); - break; - } - case Vector3Array: { - writeVec3Array(data, value); - break; - } - case Vector4Array: { - writeVec4Array(data, value); - break; - } - case Matrix3: { - write(data, (Matrix3f) value); - break; - } - case Matrix4: { - write(data, (Matrix4f) value); - break; - } - case Matrix3Array: { - writeMat3Array(data, value); - break; - } - case Matrix4Array: { - writeMat4Array(data, value); - break; - } - default: { - throw new IllegalArgumentException("The type of BO field " + field.getType() + " doesn't support."); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeMat3Array(final ByteBuffer data, final Object value) { - - if (value instanceof Matrix3f[]) { - - final Matrix3f[] values = (Matrix3f[]) value; - for (final Matrix3f mat : values) { - write(data, mat); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Matrix3f mat : values.getArray()) { - write(data, mat); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Matrix3f mat : values) { - write(data, mat); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeMat4Array(final ByteBuffer data, final Object value) { - - if (value instanceof Matrix4f[]) { - - final Matrix4f[] values = (Matrix4f[]) value; - for (final Matrix4f mat : values) { - write(data, mat); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Matrix4f mat : values.getArray()) { - write(data, mat); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Matrix4f mat : values) { - write(data, mat); - } - } - } - - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec4Array(final ByteBuffer data, final Object value) { - - if (value instanceof Object[]) { - - final Object[] values = (Object[]) value; - for (final Object vec : values) { - writeVec4(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Object vec : values.getArray()) { - writeVec4(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Object vec : values) { - writeVec4(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec3Array(final ByteBuffer data, final Object value) { - - if (value instanceof Vector3f[]) { - - final Vector3f[] values = (Vector3f[]) value; - for (final Vector3f vec : values) { - write(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Vector3f vec : values.getArray()) { - write(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Vector3f vec : values) { - write(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec2Array(final ByteBuffer data, final Object value) { - - if (value instanceof Vector2f[]) { - - final Vector2f[] values = (Vector2f[]) value; - for (final Vector2f vec : values) { - write(data, vec); - } - - } else if(value instanceof SafeArrayList) { - - final SafeArrayList values = (SafeArrayList) value; - for (final Vector2f vec : values.getArray()) { - write(data, vec); - } - - } else if(value instanceof Collection) { - - final Collection values = (Collection) value; - for (final Vector2f vec : values) { - write(data, vec); - } - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final float[] value) { - for (float val : value) { - data.putFloat(val); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final int[] value) { - for (int val : value) { - data.putInt(val); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void writeVec4(final ByteBuffer data, final Object value) { - - if (value == null) { - data.putLong(0).putLong(0); - } else if (value instanceof Vector4f) { - - final Vector4f vec4 = (Vector4f) value; - data.putFloat(vec4.getX()) - .putFloat(vec4.getY()) - .putFloat(vec4.getZ()) - .putFloat(vec4.getW()); - - } else if(value instanceof Quaternion) { - - final Quaternion vec4 = (Quaternion) value; - data.putFloat(vec4.getX()) - .putFloat(vec4.getY()) - .putFloat(vec4.getZ()) - .putFloat(vec4.getW()); - - } else if(value instanceof ColorRGBA) { - - final ColorRGBA vec4 = (ColorRGBA) value; - data.putFloat(vec4.getRed()) - .putFloat(vec4.getGreen()) - .putFloat(vec4.getBlue()) - .putFloat(vec4.getAlpha()); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Vector3f value) { - - if (value == null) { - data.putLong(0).putInt(0); - } else { - data.putFloat(value.getX()) - .putFloat(value.getY()) - .putFloat(value.getZ()); - } - - if (layout == Layout.std140) { - data.putInt(0); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param x the x value. - * @param y the y value. - * @param z the z value. - */ - protected void write(final ByteBuffer data, final float x, final float y, final float z) { - - data.putFloat(x) - .putFloat(y) - .putFloat(z); - - if (layout == Layout.std140) { - data.putInt(0); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param x the x value. - * @param y the y value. - * @param z the z value. - * @param w the w value. - */ - protected void write(final ByteBuffer data, final float x, final float y, final float z, final float w) { - data.putFloat(x) - .putFloat(y) - .putFloat(z) - .putFloat(w); - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Vector2f value) { - if (value == null) { - data.putLong(0); - } else { - data.putFloat(value.getX()).putFloat(value.getY()); - } - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Matrix3f value) { - write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0)); - write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1)); - write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2)); - } - - /** - * Writes the value to the data buffer. - * - * @param data the data buffer. - * @param value the value. - */ - protected void write(final ByteBuffer data, final Matrix4f value) { - write(data, value.get(0, 0), value.get(1, 0), value.get(2, 0), value.get(3, 0)); - write(data, value.get(0, 1), value.get(1, 1), value.get(2, 1), value.get(3, 1)); - write(data, value.get(0, 2), value.get(1, 2), value.get(2, 2), value.get(3, 2)); - write(data, value.get(0, 3), value.get(1, 3), value.get(2, 3), value.get(3, 3)); - } - - @Override - public void deleteObject(final Object rendererObject) { - - if (!(rendererObject instanceof Renderer)) { - throw new IllegalArgumentException("This bo can't be deleted from " + rendererObject); - } - - ((Renderer) rendererObject).deleteBuffer(this); - } - - @Override - public NativeObject createDestructableClone() { - return new BufferObject(null, getId()); - } - - @Override - protected void deleteNativeBuffers() { - super.deleteNativeBuffers(); - if (previousData != null) { - BufferUtils.destroyDirectBuffer(previousData); - previousData = null; - } - } - - @Override - public long getUniqueId() { - return ((long) OBJTYPE_BO << 32) | ((long) id); - } -} diff --git a/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java b/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java deleted file mode 100644 index 798b418fc4..0000000000 --- a/jme3-core/src/main/java/com/jme3/shader/BufferObjectField.java +++ /dev/null @@ -1,77 +0,0 @@ -package com.jme3.shader; - -import static java.util.Objects.requireNonNull; - -/** - * The class to describe a filed in BO. - * - * @author JavaSaBr - */ -public class BufferObjectField { - - - /** - * The field name. - */ - private final String name; - - /** - * The field type. - */ - private final VarType type; - - /** - * The field value. - */ - private Object value; - - public BufferObjectField(final String name, final VarType type) { - this.name = name; - this.type = type; - } - - /** - * Get the field name. - * - * @return the field name. - */ - public String getName() { - return name; - } - - /** - * Gets the field type. - * - * @return the field type. - */ - public VarType getType() { - return type; - } - - /** - * Gets the field value. - * - * @return the field value. - */ - public Object getValue() { - return value; - } - - /** - * Sets the field value. - * - * @param value the field value. - */ - public void setValue(final Object value) { - this.value = requireNonNull(value, "The field's value can't be null."); - } - - @Override - public String toString() { - return "BufferObjectField{" + - "name='" + name + '\'' + - ", type=" + type + - ", value=" + value + - '}'; - } -} diff --git a/jme3-core/src/main/java/com/jme3/shader/DefineList.java b/jme3-core/src/main/java/com/jme3/shader/DefineList.java index d368f24948..d4ee84fc51 100644 --- a/jme3-core/src/main/java/com/jme3/shader/DefineList.java +++ b/jme3-core/src/main/java/com/jme3/shader/DefineList.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 @@ -32,6 +32,7 @@ package com.jme3.shader; import java.util.Arrays; +import java.util.BitSet; import java.util.List; /** @@ -41,20 +42,19 @@ */ public final class DefineList { - public static final int MAX_DEFINES = 64; - - private long isSet; + private final BitSet isSet; private final int[] values; public DefineList(int numValues) { - if (numValues < 0 || numValues > MAX_DEFINES) { - throw new IllegalArgumentException("numValues must be between 0 and 64"); + if (numValues < 0) { + throw new IllegalArgumentException("numValues must be >= 0"); } values = new int[numValues]; + isSet = new BitSet(numValues); } private DefineList(DefineList original) { - this.isSet = original.isSet; + this.isSet = (BitSet) original.isSet.clone(); this.values = new int[original.values.length]; System.arraycopy(original.values, 0, values, 0, values.length); } @@ -65,18 +65,18 @@ private void rangeCheck(int id) { public boolean isSet(int id) { rangeCheck(id); - return (isSet & (1L << id)) != 0; + return isSet.get(id); } public void unset(int id) { rangeCheck(id); - isSet &= ~(1L << id); + isSet.clear(id); values[id] = 0; } public void set(int id, int val) { rangeCheck(id); - isSet |= (1L << id); + isSet.set(id, true); values[id] = val; } @@ -124,7 +124,7 @@ public void setAll(DefineList other) { } public void clear() { - isSet = 0; + isSet.clear(); Arrays.fill(values, 0); } @@ -142,21 +142,30 @@ public int getInt(int id) { @Override public int hashCode() { - return (int) ((isSet >> 32) ^ isSet); + return isSet.hashCode(); } @Override - public boolean equals(Object other) { - DefineList o = (DefineList) other; - if (isSet == o.isSet) { - for (int i = 0; i < values.length; i++) { - if (values[i] != o.values[i]) { - return false; - } - } + public boolean equals(Object object) { + if (this == object) { return true; } - return false; + if (object == null || object.getClass() != getClass()) { + return false; + } + DefineList otherDefineList = (DefineList) object; + if (values.length != otherDefineList.values.length) { + return false; + } + if (!isSet.equals(otherDefineList.isSet)) { + return false; + } + for (int i = 0; i < values.length; i++) { + if (values[i] != otherDefineList.values[i]) { + return false; + } + } + return true; } public DefineList deepClone() { diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java index 1300bf3616..425f0ad0bc 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl100ShaderGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ public class Glsl100ShaderGenerator extends ShaderGenerator { /** * the indentation characters 1à tabulation characters */ - private final static String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + private static final String INDENTCHAR = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; protected ShaderNodeVariable inPosTmp; @@ -192,7 +192,7 @@ protected void generateEndOfMainSection(StringBuilder source, ShaderGenerationIn * Appends an output assignment to a shader globalOutputName = * nameSpace_varName; * - * @param source the source StringBuilter to append the code. + * @param source the source StringBuilder to append the code. * @param globalOutputName the name of the global output (can be gl_Position * or gl_FragColor etc...). * @param var the variable to assign to the output. @@ -210,7 +210,7 @@ protected void appendOutput(StringBuilder source, String globalOutputName, Shade /** * {@inheritDoc} * - * this methods does things in this order : + * This method does things in the following order: * * 1. declaring and mapping input
                * variables : variable replaced with MatParams or WorldParams that are Samplers are not @@ -230,7 +230,7 @@ protected void appendOutput(StringBuilder source, String globalOutputName, Shade * * *
                - * All of this is embed in a #if conditional statement if needed + * All of this is embedded in a #if conditional statement if necessary. */ @Override protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info) { @@ -242,7 +242,7 @@ protected void generateNodeMainSection(StringBuilder source, ShaderNode shaderNo final List declaredInputs = new ArrayList<>(); - // Decalring variables with default values first + // Declaring variables with default values first final ShaderNodeDefinition definition = shaderNode.getDefinition(); for (final ShaderNodeVariable var : definition.getInputs()) { @@ -422,6 +422,7 @@ protected void endCondition(String condition, StringBuilder source) { * * @param mapping the VariableMapping to append * @param source the StringBuilder to use + * @param declare true to declare the variable, false if already declared */ protected void map(VariableMapping mapping, StringBuilder source, boolean declare) { @@ -605,7 +606,7 @@ protected String getLanguageAndVersion(ShaderType type) { /** * appends indentation. - * @param source + * @param source the builder to append to */ protected void appendIndent(StringBuilder source) { source.append(INDENTCHAR.substring(0, indent)); @@ -624,9 +625,9 @@ protected void declareAttribute(StringBuilder source, ShaderNodeVariable var) { * Declares a varying * @param source the StringBuilder to use * @param var the variable to declare as a varying - * @param input a boolean set to true if the this varying is an input. - * this in not used in this implementation but can be used in overriding - * implementation + * @param input a boolean set to true if the varying is an input. + * This in not used in this implementation, but can be used in overriding + * implementations. */ protected void declareVarying(StringBuilder source, ShaderNodeVariable var, boolean input) { declareVariable(source, var, true, "varying"); diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java index e3fb15a7b2..5562196044 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl150ShaderGenerator.java @@ -104,8 +104,8 @@ private void generateCompatibilityDefines(StringBuilder source, ShaderType type) * Fragment shader outputs are declared before the "void main(){" with the * "out" keyword. * - * after the "void main(){", the vertex output are declared and initialized - * and the fragment outputs are declared + * After the "void main(){", the vertex output are declared and initialized + * and the fragment outputs are declared. */ @Override protected void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { diff --git a/jme3-core/src/main/java/com/jme3/shader/Glsl300ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/Glsl300ShaderGenerator.java index 450a21a5ff..a8d3f0d75c 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Glsl300ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/Glsl300ShaderGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,14 +32,13 @@ package com.jme3.shader; import com.jme3.asset.AssetManager; -import com.jme3.material.ShaderGenerationInfo; import com.jme3.shader.Shader.ShaderType; /** * This shader Generator can generate Vertex and Fragment shaders from * ShaderNodes for GLESSL 3.0 - * Nowdays it's just a subclass of Glsl150ShaderGenerator overriding the version + * Nowadays it's just a subclass of Glsl150ShaderGenerator overriding the version * string because GLSL 1.5 is mostly compatible with GLESSL 3.0 * * @author Nehon 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 cd93ab9f37..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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ import java.util.Collection; public final class Shader extends NativeObject { - + /** * A list of all shader sources currently attached. */ @@ -56,10 +56,10 @@ public final class Shader extends NativeObject { * Maps storage block name to the buffer block variables. */ private final ListMap bufferBlocks; - + /** * Uniforms bound to {@link UniformBinding}s. - * + * * Managed by the {@link UniformBindingManager}. */ private final ArrayList boundUniforms; @@ -75,35 +75,35 @@ public final class Shader extends NativeObject { public static enum ShaderType { /** - * Control fragment rasterization. (e.g color of pixel). + * Control fragment rasterization. (e.g. color of pixel). */ Fragment("frag"), /** - * Control vertex processing. (e.g transform of model to clip space) + * Control vertex processing. (e.g. transform of model to clip space) */ Vertex("vert"), /** - * Control geometry assembly. (e.g compile a triangle list from input + * Control geometry assembly. (e.g. compile a triangle list from input * data) */ Geometry("geom"), /** - * Controls tessellation factor (e.g how often a input patch should be + * Controls tessellation factor (e.g. how often an input patch should be * subdivided) */ TessellationControl("tsctrl"), /** - * Controls tessellation transform (e.g similar to the vertex shader, but + * Controls tessellation transform (e.g. similar to the vertex shader, but * required to mix inputs manual) */ TessellationEvaluation("tseval"); private String extension; - + public String getExtension() { return extension; } - + private ShaderType(String extension) { this.extension = extension; } @@ -121,29 +121,29 @@ public static class ShaderSource extends NativeObject { String source; String defines; - public ShaderSource(ShaderType type){ + public ShaderSource(ShaderType type) { super(); this.sourceType = type; if (type == null) { throw new IllegalArgumentException("The shader type must be specified"); } } - - protected ShaderSource(ShaderSource ss){ + + protected ShaderSource(ShaderSource ss) { super(ss.id); // No data needs to be copied. // (This is a destructible clone) } - public ShaderSource(){ + public ShaderSource() { super(); } - public void setName(String name){ + public void setName(String name) { this.name = name; } - public String getName(){ + public String getName() { return name; } @@ -163,7 +163,7 @@ public void setLanguage(String language) { setUpdateNeeded(); } - public void setSource(String source){ + public void setSource(String source) { if (source == null) { throw new IllegalArgumentException("Shader source cannot be null"); } @@ -171,7 +171,7 @@ public void setSource(String source){ setUpdateNeeded(); } - public void setDefines(String defines){ + public void setDefines(String defines) { if (defines == null) { throw new IllegalArgumentException("Shader defines cannot be null"); } @@ -179,41 +179,44 @@ public void setDefines(String defines){ setUpdateNeeded(); } - public String getSource(){ + public String getSource() { return source; } - public String getDefines(){ + public String getDefines() { return defines; } - + @Override public long getUniqueId() { - return ((long)OBJTYPE_SHADERSOURCE << 32) | ((long)id); + return ((long)OBJTYPE_SHADERSOURCE << 32) | (0xffffffffL & (long)id); } - + @Override - public String toString(){ + public String toString() { String nameTxt = ""; if (name != null) nameTxt = "name="+name+", "; if (defines != null) nameTxt += "defines, "; - + return getClass().getSimpleName() + "["+nameTxt+"type=" + sourceType.name()+", language=" + language + "]"; } - public void resetObject(){ + @Override + public void resetObject() { id = -1; setUpdateNeeded(); } - public void deleteObject(Object rendererObject){ + @Override + public void deleteObject(Object rendererObject) { ((Renderer)rendererObject).deleteShaderSource(ShaderSource.this); } + @Override public NativeObject createDestructableClone(){ return new ShaderSource(ShaderSource.this); } @@ -223,7 +226,7 @@ public NativeObject createDestructableClone(){ * Creates a new shader, initialize() must be called * after this constructor for the shader to be usable. */ - public Shader(){ + public Shader() { super(); shaderSourceList = new ArrayList<>(); uniforms = new ListMap<>(); @@ -234,17 +237,19 @@ public Shader(){ /** * Do not use this constructor. Used for destructible clones only. + * + * @param s (not null) */ - protected Shader(Shader s){ + protected Shader(Shader s) { super(s.id); - + // Shader sources cannot be shared, therefore they must // be destroyed together with the parent shader. shaderSourceList = new ArrayList(); for (ShaderSource source : s.shaderSourceList){ - shaderSourceList.add( (ShaderSource)source.createDestructableClone() ); + shaderSourceList.add((ShaderSource) source.createDestructableClone()); } - + uniforms = null; bufferBlocks = null; boundUniforms = null; @@ -255,6 +260,7 @@ protected Shader(Shader s){ * Adds source code to a certain pipeline. * * @param type The pipeline to control + * @param name a name for the new shader object * @param source The shader source code (in GLSL). * @param defines Preprocessor defines (placed at the beginning of the shader) * @param language The shader source language, currently accepted is GLSL### @@ -283,7 +289,7 @@ public void addUniformBinding(UniformBinding binding){ boundUniforms.add(uniform); } } - + public Uniform getUniform(String name){ assert name.startsWith("m_") || name.startsWith("g_"); Uniform uniform = uniforms.get(name); @@ -363,7 +369,7 @@ public Collection getSources(){ @Override public String toString() { - return getClass().getSimpleName() + + return getClass().getSimpleName() + "[numSources=" + shaderSourceList.size() + ", numUniforms=" + uniforms.size() + ", numBufferBlocks=" + bufferBlocks.size() + @@ -373,7 +379,7 @@ public String toString() { /** * Removes the "set-by-current-material" flag from all uniforms. * When a uniform is modified after this call, the flag shall - * become "set-by-current-material". + * become "set-by-current-material". * A call to {@link #resetUniformsNotSetByCurrent() } will reset * all uniforms that do not have the "set-by-current-material" flag * to their default value (usually all zeroes or false). @@ -433,7 +439,7 @@ public void setUpdateNeeded(){ /** * Called by the object manager to reset all object IDs. This causes - * the shader to be reuploaded to the GPU incase the display was restarted. + * the shader to be reuploaded to the GPU in case the display was restarted. */ @Override public void resetObject() { @@ -449,12 +455,13 @@ public void deleteObject(Object rendererObject) { ((Renderer)rendererObject).deleteShader(this); } + @Override public NativeObject createDestructableClone(){ return new Shader(this); } @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/ShaderBufferBlock.java b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java index 27b44d18b3..20d2061420 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderBufferBlock.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,34 +31,49 @@ */ package com.jme3.shader; +import java.lang.ref.WeakReference; + +import com.jme3.shader.bufferobject.BufferObject; + /** * Implementation of shader's buffer block. * - * @author JavaSaBr + * @author JavaSaBr, Riccardo Balbo */ public class ShaderBufferBlock extends ShaderVariable { + + public static enum BufferType { + UniformBufferObject, ShaderStorageBufferObject + } /** * Current used buffer object. */ protected BufferObject bufferObject; + protected WeakReference bufferObjectRef; + protected BufferType type; /** * Set the new buffer object. * - * @param bufferObject the new buffer object. + * @param bufferObject + * the new buffer object. */ - public void setBufferObject(final BufferObject bufferObject) { - + public void setBufferObject(BufferType type, BufferObject bufferObject) { if (bufferObject == null) { throw new IllegalArgumentException("for storage block " + name + ": storageData cannot be null"); } - + if (bufferObject == this.bufferObject && type == this.type) return; this.bufferObject = bufferObject; - + this.bufferObjectRef = new WeakReference(bufferObject); + this.type = type; updateNeeded = true; } + public BufferType getType() { + return type; + } + /** * Return true if need to update this storage block. * @@ -78,7 +93,8 @@ public void clearUpdateNeeded(){ /** * Reset this storage block. */ - public void reset(){ + public void reset() { + location = -1; updateNeeded = true; } @@ -90,4 +106,13 @@ public void reset(){ public BufferObject getBufferObject() { return bufferObject; } + + public WeakReference getBufferObjectRef() { + return bufferObjectRef; + } + + public void setBufferObjectRef(WeakReference bufferObjectRef) { + this.bufferObjectRef = bufferObjectRef; + } + } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java index a86340ab90..3376234233 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,24 +72,25 @@ public abstract class ShaderGenerator { */ Pattern extensions = Pattern.compile("(#extension.*\\s+)"); - private Map imports = new LinkedHashMap<>(); + final private Map imports = new LinkedHashMap<>(); /** * Build a shaderGenerator * - * @param assetManager + * @param assetManager for loading assets (alias created) */ protected ShaderGenerator(AssetManager assetManager) { - this.assetManager = assetManager; + this.assetManager = assetManager; } - + public void initialize(TechniqueDef techniqueDef){ this.techniqueDef = techniqueDef; } - + /** * Generate vertex and fragment shaders for the given technique * + * @param definesSourceCode (may be null) * @return a Shader program */ public Shader generateShader(String definesSourceCode) { @@ -107,13 +108,13 @@ public Shader generateShader(String definesSourceCode) { String extension = type.getExtension(); String language = getLanguageAndVersion(type); String shaderSourceCode = buildShader(techniqueDef.getShaderNodes(), info, type); - + if (shaderSourceCode != null) { String shaderSourceAssetName = techniqueName + "." + extension; shader.addSource(type, shaderSourceAssetName, shaderSourceCode, definesSourceCode, language); } } - + techniqueDef = null; return shader; } @@ -128,7 +129,7 @@ public Shader generateShader(String definesSourceCode) { */ protected String buildShader(List shaderNodes, ShaderGenerationInfo info, ShaderType type) { if (type == ShaderType.TessellationControl || - type == ShaderType.TessellationEvaluation || + type == ShaderType.TessellationEvaluation || type == ShaderType.Geometry) { // TODO: Those are not supported. // Too much code assumes that type is either Vertex or Fragment @@ -173,9 +174,9 @@ protected String buildShader(List shaderNodes, ShaderGenerationInfo * @return */ private String moveExtensionsUp(StringBuilder sourceDeclaration) { - Matcher m = extensions.matcher( sourceDeclaration.toString()); + Matcher m = extensions.matcher(sourceDeclaration.toString()); StringBuilder finalSource = new StringBuilder(); - while(m.find()){ + while (m.find()) { finalSource.append(m.group()); } finalSource.append(m.replaceAll("")); @@ -194,6 +195,7 @@ private String moveExtensionsUp(StringBuilder sourceDeclaration) { * @param info the ShaderGenerationInfo * @param type the Shader type */ + @SuppressWarnings("unchecked") protected void generateDeclarationAndMainBody(List shaderNodes, StringBuilder sourceDeclaration, StringBuilder source, ShaderGenerationInfo info, Shader.ShaderType type) { for (ShaderNode shaderNode : shaderNodes) { if (info.getUnusedNodes().contains(shaderNode.getName())) { @@ -278,7 +280,7 @@ protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder s /** * generates the varyings for the given shader type shader. Note that * varyings are deprecated in glsl 1.3, but this method will still be called - * to generate all non global inputs and output of the shaders. + * to generate all non-global inputs and output of the shaders. * * @param source the source StringBuilder to append generated code. * @param info the ShaderGenerationInfo. @@ -293,13 +295,13 @@ protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder s * * @see ShaderNode#getDefinition() * @see ShaderNodeDefinition#getType() - * - * @param nodeDecalarationSource the declaration part of the node + * + * @param nodeDeclarationSource the declaration part of the node * @param source the StringBuilder to append generated code. * @param shaderNode the shaderNode. * @param info the ShaderGenerationInfo. */ - protected abstract void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeDecalarationSource, ShaderGenerationInfo info); + protected abstract void generateDeclarativeSection(StringBuilder source, ShaderNode shaderNode, String nodeDeclarationSource, ShaderGenerationInfo info); /** * generates the start of the shader main section. this method is @@ -313,9 +315,9 @@ protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder s protected abstract void generateStartOfMainSection(StringBuilder source, ShaderGenerationInfo info, ShaderType type); /** - * generates the end of the shader main section. this method is responsible - * of appending the last "}" in the shader and mapping all global outputs of - * the shader + * Generates the end of the shader main section. This method is responsible + * for appending the last "}" in the shader and mapping all global outputs of + * the shader. * * @param source the StringBuilder to append generated code. * @param info the ShaderGenerationInfo. @@ -339,15 +341,15 @@ protected void appendNodeDeclarationAndMain(String loadedSource, StringBuilder s protected abstract void generateNodeMainSection(StringBuilder source, ShaderNode shaderNode, String nodeSource, ShaderGenerationInfo info); /** - * returns the shaderpath index according to the version of the generator. - * This allow to select the higher version of the shader that the generator - * can handle + * Returns the shader-path index according to the version of the generator. + * This allows selecting the highest version of the shader that the generator + * can handle. * * @param shaderNode the shaderNode being processed * @param type the shaderType * @return the index of the shader path in ShaderNodeDefinition shadersPath * list - * @throws NumberFormatException + * @throws NumberFormatException for an invalid version */ protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) throws NumberFormatException { int index = 0; @@ -362,5 +364,5 @@ protected int findShaderIndexFromVersion(ShaderNode shaderNode, ShaderType type) } } return index; - } + } } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java index 5ef500ffd0..8dbc36dc83 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,7 @@ /** * A ShaderNode is the unit brick part of a shader program. A shader can be - * describe with several shader nodes that are plugged together through inputs + * described by several shader nodes that are plugged together through inputs * and outputs. * * A ShaderNode is based on a definition that has a shader code, inputs and @@ -175,7 +175,7 @@ public void setOutputMapping(List outputMapping) { * jme serialization * * @param ex the exporter - * @throws IOException + * @throws IOException from the exporter */ @Override public void write(JmeExporter ex) throws IOException { @@ -191,20 +191,21 @@ public void write(JmeExporter ex) throws IOException { * jme serialization * * @param im the importer - * @throws IOException + * @throws IOException from the importer */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", ""); definition = (ShaderNodeDefinition) ic.readSavable("definition", null); condition = ic.readString("condition", null); - inputMapping = (List) ic.readSavableArrayList("inputMapping", new ArrayList()); - outputMapping = (List) ic.readSavableArrayList("outputMapping", new ArrayList()); + inputMapping = ic.readSavableArrayList("inputMapping", new ArrayList<>()); + outputMapping = ic.readSavableArrayList("outputMapping", new ArrayList<>()); } /** - * convenience tostring + * convenience toString * * @return a string */ diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java index 68a7b80d8f..a6addf0cc0 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,11 +52,11 @@ public class ShaderNodeDefinition implements Savable { private String name; private Shader.ShaderType type; - private List shadersLanguage = new ArrayList(); - private List shadersPath = new ArrayList(); + private List shadersLanguage = new ArrayList<>(); + private List shadersPath = new ArrayList<>(); private String documentation; - private List inputs = new ArrayList(); - private List outputs = new ArrayList(); + private List inputs = new ArrayList<>(); + private List outputs = new ArrayList<>(); private String path = null; private boolean noOutput = false; @@ -178,30 +178,30 @@ public String getPath() { /** * sets the path of this definition - * @param path + * @param path the desired path */ public void setPath(String path) { this.path = path; } - - + + /** * jme serialization (not used) * * @param ex the exporter - * @throws IOException + * @throws IOException from the exporter */ @Override public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); + OutputCapsule oc = ex.getCapsule(this); oc.write(name, "name", ""); String[] str = new String[shadersLanguage.size()]; oc.write(shadersLanguage.toArray(str), "shadersLanguage", null); oc.write(shadersPath.toArray(str), "shadersPath", null); oc.write(type, "type", null); oc.writeSavableArrayList((ArrayList) inputs, "inputs", new ArrayList()); - oc.writeSavableArrayList((ArrayList) outputs, "inputs", new ArrayList()); + oc.writeSavableArrayList((ArrayList) outputs, "outputs", new ArrayList()); } public List getShadersLanguage() { @@ -220,17 +220,18 @@ public void setNoOutput(boolean noOutput) { this.noOutput = noOutput; } - - + + /** * jme serialization (not used) * * @param im the importer - * @throws IOException + * @throws IOException from the importer */ @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { - InputCapsule ic = (InputCapsule) im.getCapsule(this); + InputCapsule ic = im.getCapsule(this); name = ic.readString("name", ""); String[] str = ic.readStringArray("shadersLanguage", null); @@ -248,17 +249,19 @@ public void read(JmeImporter im) throws IOException { } type = ic.readEnum("type", Shader.ShaderType.class, null); - inputs = (List) ic.readSavableArrayList("inputs", new ArrayList()); - outputs = (List) ic.readSavableArrayList("outputs", new ArrayList()); + inputs = ic.readSavableArrayList("inputs", new ArrayList<>()); + outputs = ic.readSavableArrayList("outputs", new ArrayList<>()); } /** - * convenience tostring + * convenience toString * * @return a string */ @Override public String toString() { - return "\nShaderNodeDefinition{\n" + "name=" + name + "\ntype=" + type + "\nshaderPath=" + shadersPath + "\nshaderLanguage=" + shadersLanguage + "\ndocumentation=" + documentation + "\ninputs=" + inputs + ",\noutputs=" + outputs + '}'; + return "\nShaderNodeDefinition{\n" + "name=" + name + "\ntype=" + type + + "\nshaderPath=" + shadersPath + "\nshaderLanguage=" + shadersLanguage + + "\ndocumentation=" + documentation + "\ninputs=" + inputs + ",\noutputs=" + outputs + '}'; } } diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java index 5038efb706..e40dc4f6aa 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderNodeVariable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -166,7 +166,7 @@ public String getPrefix() { /** * Sets the variable prefix (m_ or g_) * - * @param prefix + * @param prefix the desired prefix */ public void setPrefix(String prefix) { this.prefix = prefix; @@ -176,7 +176,7 @@ public void setPrefix(String prefix) { * sets the nameSpace (can be the name of the shaderNode or * Global,Attr,MatParam,WorldParam) * - * @param nameSpace + * @param nameSpace the desired nameSpace */ public void setNameSpace(String nameSpace) { this.nameSpace = nameSpace; @@ -246,7 +246,7 @@ public boolean equals(Object obj) { * jme serialization (not used) * * @param ex the exporter - * @throws IOException + * @throws IOException from the exporter */ @Override public void write(JmeExporter ex) throws IOException { @@ -265,14 +265,14 @@ public void write(JmeExporter ex) throws IOException { * jme serialization (not used) * * @param im the importer - * @throws IOException + * @throws IOException from the importer */ @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); name = ic.readString("name", ""); type = ic.readString("type", ""); - prefix = ic.readString("pefix", ""); + prefix = ic.readString("prefix", ""); nameSpace = ic.readString("nameSpace", ""); condition = ic.readString("condition", null); shaderOutput = ic.readBoolean("shaderOutput", false); @@ -330,7 +330,8 @@ public String getMultiplicity() { /** * sets the number of elements of this variable making it an array * this value can be a number of can be a define - * @param multiplicity + * + * @param multiplicity the desired expression */ public void setMultiplicity(String multiplicity) { this.multiplicity = multiplicity; diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java index 13bde3c718..d1db582624 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,12 @@ public class ShaderUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private ShaderUtils() { + } + public static String convertToGLSL130(String input, boolean isFrag) { StringBuilder sb = new StringBuilder(); sb.append("#version 130\n"); diff --git a/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java b/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java index ade852e34e..8a9bbaf266 100644 --- a/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java +++ b/jme3-core/src/main/java/com/jme3/shader/ShaderVariable.java @@ -43,7 +43,7 @@ public class ShaderVariable { /** * Name of the uniform as was declared in the shader. - * E.g name = "g_WorldMatrix" if the declaration was + * E.g. name = "g_WorldMatrix" if the declaration was * "uniform mat4 g_WorldMatrix;". */ protected String name = null; diff --git a/jme3-core/src/main/java/com/jme3/shader/Uniform.java b/jme3-core/src/main/java/com/jme3/shader/Uniform.java index 30d5d72bf1..ebd42f9605 100644 --- a/jme3-core/src/main/java/com/jme3/shader/Uniform.java +++ b/jme3-core/src/main/java/com/jme3/shader/Uniform.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,10 +31,11 @@ */ package com.jme3.shader; +import com.jme3.material.Material.BindUnits; import com.jme3.math.*; import com.jme3.util.BufferUtils; import com.jme3.util.TempVars; - +import java.lang.reflect.InvocationTargetException; import java.nio.*; public class Uniform extends ShaderVariable { @@ -47,7 +48,7 @@ public class Uniform extends ShaderVariable { * Currently set value of the uniform. */ protected Object value = null; - + /** * For arrays or matrices, efficient format * that can be sent to GL faster. @@ -126,7 +127,7 @@ public VarType getVarType() { public Object getValue(){ return value; } - + public FloatBuffer getMultiData() { return multiData; } @@ -142,12 +143,12 @@ public void clearSetByCurrentMaterial(){ public void clearValue(){ updateNeeded = true; - if (multiData != null){ + if (multiData != null){ multiData.clear(); while (multiData.remaining() > 0){ ZERO_BUF.clear(); - ZERO_BUF.limit( Math.min(multiData.remaining(), 16) ); + ZERO_BUF.limit(Math.min(multiData.remaining(), 16)); multiData.put(ZERO_BUF); } @@ -159,7 +160,7 @@ public void clearValue(){ if (varType == null) { return; } - + switch (varType){ case Int: this.value = ZERO_INT; @@ -168,7 +169,7 @@ public void clearValue(){ this.value = Boolean.FALSE; break; case Float: - this.value = ZERO_FLT; + this.value = ZERO_FLT; break; case Vector2: if (this.value != null) { @@ -196,8 +197,9 @@ public void clearValue(){ // or multidata types } } - - public void setValue(VarType type, Object value){ + + public void setValue(VarType type, Object value) { + assert !(value instanceof BindUnits); if (location == LOC_NOT_DEFINED) { return; } @@ -251,6 +253,7 @@ public void setValue(VarType type, Object value){ this.value = BufferUtils.createIntBuffer(ia); } else { this.value = BufferUtils.ensureLargeEnough((IntBuffer)this.value, ia.length); + ((IntBuffer)this.value).put(ia); } ((IntBuffer)this.value).clear(); break; @@ -260,8 +263,8 @@ public void setValue(VarType type, Object value){ multiData = BufferUtils.createFloatBuffer(fa); } else { multiData = BufferUtils.ensureLargeEnough(multiData, fa.length); + multiData.put(fa); } - multiData.put(fa); multiData.clear(); break; case Vector2Array: @@ -270,9 +273,9 @@ public void setValue(VarType type, Object value){ multiData = BufferUtils.createFloatBuffer(v2a); } else { multiData = BufferUtils.ensureLargeEnough(multiData, v2a.length * 2); - } - for (int i = 0; i < v2a.length; i++) { - BufferUtils.setInBuffer(v2a[i], multiData, i); + for (int i = 0; i < v2a.length; i++) { + BufferUtils.setInBuffer(v2a[i], multiData, i); + } } multiData.clear(); break; @@ -282,9 +285,9 @@ public void setValue(VarType type, Object value){ multiData = BufferUtils.createFloatBuffer(v3a); } else { multiData = BufferUtils.ensureLargeEnough(multiData, v3a.length * 3); - } - for (int i = 0; i < v3a.length; i++) { - BufferUtils.setInBuffer(v3a[i], multiData, i); + for (int i = 0; i < v3a.length; i++) { + BufferUtils.setInBuffer(v3a[i], multiData, i); + } } multiData.clear(); break; @@ -294,9 +297,9 @@ public void setValue(VarType type, Object value){ multiData = BufferUtils.createFloatBuffer(v4a); } else { multiData = BufferUtils.ensureLargeEnough(multiData, v4a.length * 4); - } - for (int i = 0; i < v4a.length; i++) { - BufferUtils.setInBuffer(v4a[i], multiData, i); + for (int i = 0; i < v4a.length; i++) { + BufferUtils.setInBuffer(v4a[i], multiData, i); + } } multiData.clear(); break; @@ -354,9 +357,11 @@ public void setValue(VarType type, Object value){ //handle the null case if (this.value == null) { try { - this.value = value.getClass().newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - throw new IllegalArgumentException("Cannot instanciate param of class " + value.getClass().getCanonicalName()); + this.value = value.getClass().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException e) { + throw new IllegalArgumentException("Cannot instantiate param of class " + value.getClass().getCanonicalName(), e); } } //feed the pivot vec 4 with the correct value @@ -406,7 +411,7 @@ public void setVector4Length(int length){ if (location == -1) { return; } - + multiData = BufferUtils.ensureLargeEnough(multiData, length * 4); value = multiData; varType = VarType.Vector4Array; @@ -429,7 +434,7 @@ public void setVector4InArray(float x, float y, float z, float w, int index){ updateNeeded = true; setByCurrentMaterial = true; } - + public boolean isUpdateNeeded(){ return updateNeeded; } diff --git a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java index 6b9fc82be7..8d55ab4430 100644 --- a/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java +++ b/jme3-core/src/main/java/com/jme3/shader/UniformBindingManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,36 +55,38 @@ public class UniformBindingManager { private float near, far; private Float time, tpf; private int viewX, viewY, viewWidth, viewHeight; - private Vector3f camUp = new Vector3f(), + private final Vector3f camUp = new Vector3f(), camLeft = new Vector3f(), camDir = new Vector3f(), camLoc = new Vector3f(); - private Matrix4f tempMatrix = new Matrix4f(); - private Matrix4f viewMatrix = new Matrix4f(); - private Matrix4f projMatrix = new Matrix4f(); - private Matrix4f viewProjMatrix = new Matrix4f(); - private Matrix4f worldMatrix = new Matrix4f(); - private Matrix4f worldViewMatrix = new Matrix4f(); - private Matrix4f worldViewProjMatrix = new Matrix4f(); - private Matrix3f normalMatrix = new Matrix3f(); - private Matrix3f worldNormalMatrix = new Matrix3f(); - private Matrix4f worldMatrixInv = new Matrix4f(); - private Matrix3f worldMatrixInvTrsp = new Matrix3f(); - private Matrix4f viewMatrixInv = new Matrix4f(); - private Matrix4f projMatrixInv = new Matrix4f(); - private Matrix4f viewProjMatrixInv = new Matrix4f(); - private Matrix4f worldViewMatrixInv = new Matrix4f(); - private Matrix3f normalMatrixInv = new Matrix3f(); - private Matrix4f worldViewProjMatrixInv = new Matrix4f(); - private Vector4f viewPort = new Vector4f(); - private Vector2f resolution = new Vector2f(); - private Vector2f resolutionInv = new Vector2f(); - private Vector2f nearFar = new Vector2f(); + private final Matrix4f tempMatrix = new Matrix4f(); + private final Matrix4f viewMatrix = new Matrix4f(); + private final Matrix4f projMatrix = new Matrix4f(); + private final Matrix4f viewProjMatrix = new Matrix4f(); + private final Matrix4f worldMatrix = new Matrix4f(); + private final Matrix4f worldViewMatrix = new Matrix4f(); + private final Matrix4f worldViewProjMatrix = new Matrix4f(); + private final Matrix3f normalMatrix = new Matrix3f(); + private final Matrix3f worldNormalMatrix = new Matrix3f(); + private final Matrix4f worldMatrixInv = new Matrix4f(); + private final Matrix3f worldMatrixInvTrsp = new Matrix3f(); + private final Matrix4f viewMatrixInv = new Matrix4f(); + private final Matrix4f projMatrixInv = new Matrix4f(); + private final Matrix4f viewProjMatrixInv = new Matrix4f(); + private final Matrix4f worldViewMatrixInv = new Matrix4f(); + private final Matrix3f normalMatrixInv = new Matrix3f(); + private final Matrix4f worldViewProjMatrixInv = new Matrix4f(); + private final Vector4f viewPort = new Vector4f(); + private final Vector2f resolution = new Vector2f(); + private final Vector2f resolutionInv = new Vector2f(); + private final Vector2f nearFar = new Vector2f(); /** * Internal use only. * Updates the given list of uniforms with {@link UniformBinding uniform bindings} * based on the current world state. + * + * @param shader (not null) */ public void updateUniformBindings(Shader shader) { ArrayList params = shader.getBoundUniforms(); 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 2319e79037..1a864f9b1a 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VarType.java +++ b/jme3-core/src/main/java/com/jme3/shader/VarType.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,60 +31,169 @@ */ package com.jme3.shader; -public enum VarType { +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; +import com.jme3.shader.bufferobject.BufferObject; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture2D; +import com.jme3.texture.Texture3D; +import com.jme3.texture.TextureArray; +import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.TextureImage; - Float("float"), - Vector2("vec2"), - Vector3("vec3"), - Vector4("vec4"), +/** + * Enum representing various GLSL variable types and their corresponding Java types. + */ +public enum VarType { + + 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), + + Int("int", int.class, Integer.class), + Boolean("bool", boolean.class, Boolean.class), - Boolean("bool"), + Matrix3(true, false, "mat3", Matrix3f.class), + Matrix4(true, false, "mat4", Matrix4f.class), - Matrix3(true,false,"mat3"), - Matrix4(true,false,"mat4"), + Matrix3Array(true, false, "mat3", Matrix3f[].class), + Matrix4Array(true, false, "mat4", Matrix4f[].class), - Matrix3Array(true,false,"mat3"), - Matrix4Array(true,false,"mat4"), + 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), + + Image2D(false, false, true, "image2D", TextureImage.class), + Image3D(false, false, true, "image3D", TextureImage.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"); + UniformBufferObject(false, false, "custom", BufferObject.class), + ShaderStorageBufferObject(false, false, "custom", BufferObject.class); private boolean usesMultiData = false; private boolean textureType = false; - private String glslType; + private boolean imageType = false; + private final String glslType; + private final Class[] javaTypes; - - VarType(String glslType){ + /** + * Constructs a VarType with the specified GLSL type and corresponding Java types. + * + * @param glslType the GLSL type name(s) + * @param javaTypes the Java classes mapped to this GLSL type + */ + 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){ - usesMultiData = multiData; + /** + * Constructs a VarType with additional flags for multi-data and texture types. + * + * @param multiData true if this type uses multiple data elements (e.g. arrays, matrices) + * @param textureType true if this type represents a texture sampler + * @param glslType the GLSL type name(s) + * @param javaTypes the Java classes mapped to this GLSL type + */ + VarType(boolean multiData, boolean textureType, String glslType, Class... javaTypes) { + this.usesMultiData = multiData; this.textureType = textureType; this.glslType = glslType; + if (javaTypes != null) { + this.javaTypes = javaTypes; + } else { + this.javaTypes = new Class[0]; + } + } + + /** + * Constructs a VarType with flags for multi-data, texture, and image types. + * + * @param multiData true if this type uses multiple data elements + * @param textureType true if this type represents a texture sampler + * @param imageType true if this type represents an image + * @param glslType the GLSL type name(s) + * @param javaTypes the Java classes mapped to this GLSL type + */ + VarType(boolean multiData, boolean textureType, boolean imageType, String glslType, Class... javaTypes) { + this(multiData, textureType, glslType, javaTypes); + this.imageType = imageType; + } + + /** + * 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; } + /** + * Returns whether this VarType represents a texture sampler type. + * + * @return true if this is a texture type, false otherwise + */ public boolean isTextureType() { return textureType; } + /** + * Returns whether this VarType represents an image type. + * + * @return true if this is an image type, false otherwise + */ + public boolean isImageType() { + return imageType; + } + + /** + * Returns whether this VarType uses multiple data elements (e.g. arrays or matrices). + * + * @return true if this type uses multiple data elements, false otherwise + */ public boolean usesMultiData() { return usesMultiData; } + /** + * Returns the GLSL type name(s) associated with this VarType. + * + * @return the GLSL type string (e.g. "float", "vec3", "sampler2D") + */ public String getGlslType() { return glslType; - } + } } diff --git a/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java b/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java index a9a1e2366b..cdfe516be7 100644 --- a/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java +++ b/jme3-core/src/main/java/com/jme3/shader/VariableMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -186,7 +186,7 @@ public void setRightSwizzling(String rightSwizzling) { * jme serialization (not used) * * @param ex the exporter - * @throws IOException + * @throws IOException from the exporter */ @Override public void write(JmeExporter ex) throws IOException { @@ -203,7 +203,7 @@ public void write(JmeExporter ex) throws IOException { * jme serialization (not used) * * @param im the importer - * @throws IOException + * @throws IOException from the importer */ @Override public void read(JmeImporter im) throws IOException { diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java new file mode 100644 index 0000000000..cb7a87b89d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferObject.java @@ -0,0 +1,395 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.renderer.Renderer; +import com.jme3.util.BufferUtils; +import com.jme3.util.NativeObject; + +/** + * A generic memory buffer that can be divided in logical regions + * + * @author Riccardo Balbo + */ +public class BufferObject extends NativeObject implements Savable { + /** + * Hint to suggest the renderer how to access this buffer + */ + public static enum AccessHint { + /** + * The data store contents will be modified once and used many times. + */ + Static, + /** + * The data store contents will be modified once and used at most a few + * times. + */ + Stream, + /** + * The data store contents will be modified repeatedly and used many + * times. + */ + Dynamic, + /** + * Used only by the cpu. + */ + CpuOnly + } + + /** + * Hint to suggest the renderer how the data should be used + */ + public static enum NatureHint { + /** + * The data store contents are modified by the application, and used as + * the source for GL drawing and image specification commands. + */ + Draw, + /** + * The data store contents are modified by reading data from the GL, and + * used to return that data when queried by the application. + */ + Read, + /** + * The data store contents are modified by reading data from the GL, and + * used as the source for GL drawing and image specification commands. + */ + Copy + } + + private AccessHint accessHint = AccessHint.Dynamic; + private NatureHint natureHint = NatureHint.Draw; + + private transient WeakReference weakRef; + private transient int binding = -1; + protected transient DirtyRegionsIterator dirtyRegionsIterator; + + protected ByteBuffer data = null; + protected ArrayList regions = new ArrayList(); + private String name; + + public BufferObject() { + super(); + } + + + protected BufferObject(int id) { + super(id); + } + + + /** + * Internal use only. Indicates that the object has changed and its state + * needs to be updated. Mark all the regions as dirty. + */ + public final void setUpdateNeeded() { + setUpdateNeeded(true); + } + + /** + * Indicates that the object has changed and its state needs to be updated. + * + * @param dirtyAll + * mark all regions for update + */ + public void setUpdateNeeded(boolean dirtyAll) { + if (dirtyAll) markAllRegionsDirty(); + updateNeeded = true; + } + + + /** + * Get binding point + * + * @return the binding point + */ + public int getBinding() { + return binding; + } + + + /** + * Initialize an empty buffer object of the given length + * + * @param length expected length of the buffer object + */ + public void initializeEmpty(int length) { + if (data != null) { + BufferUtils.destroyDirectBuffer(data); + } + this.data = BufferUtils.createByteBuffer(length); + } + + + /** + * Transfer remaining bytes of passed buffer to the internal buffer of this buffer object + * + * @param data ByteBuffer containing the data to pass + */ + public void setData(ByteBuffer data) { + if (data != null) { + BufferUtils.destroyDirectBuffer(data); + } + this.data = BufferUtils.createByteBuffer(data.limit() - data.position()); + this.data.put(data); + } + + + + /** + * Rewind and return buffer data + * + * @return + */ + public ByteBuffer getData() { + if (regions.size() == 0) { + if (data == null) data = BufferUtils.createByteBuffer(0); + } else { + int regionsEnd = regions.get(regions.size() - 1).getEnd(); + if (data == null) { + data = BufferUtils.createByteBuffer(regionsEnd + 1); + } else if (data.limit() < regionsEnd) { + // new buffer + ByteBuffer newData = BufferUtils.createByteBuffer(regionsEnd + 1); + + // copy old buffer in new buffer + if (newData.limit() < data.limit()) data.limit(newData.limit()); + newData.put(data); + + // destroy old buffer + BufferUtils.destroyDirectBuffer(data); + + data = newData; + } + } + data.rewind(); + return data; + } + + /** + * Get dirty regions + * + * @return Helper object to iterate through dirty regions + */ + public DirtyRegionsIterator getDirtyRegions() { + if (dirtyRegionsIterator == null) dirtyRegionsIterator = new DirtyRegionsIterator(this); + dirtyRegionsIterator.rewind(); + return dirtyRegionsIterator; + } + + /** + * Reset layour definition + */ + public void unsetRegions() { + regions.clear(); + regions.trimToSize(); + } + + /** + * Add a region at the end of the layout + * + * @param lr + */ + public void setRegions(List lr) { + regions.clear(); + regions.addAll(lr); + regions.trimToSize(); + setUpdateNeeded(); + } + + + /** + * Return all the regions of this layout + * + * @return ordered list of regions + */ + public BufferRegion getRegion(int i) { + BufferRegion region = regions.get(i); + region.bo = this; + return region; + } + + /** + * Mark all regions as dirty + */ + public void markAllRegionsDirty() { + for (BufferRegion r : regions) r.markDirty(); + } + + + @Override + public void resetObject() { + this.id = -1; + } + + @Override + protected void deleteNativeBuffers() { + super.deleteNativeBuffers(); + if (data != null) BufferUtils.destroyDirectBuffer(data); + } + + @Override + public void deleteObject(final Object rendererObject) { + if (!(rendererObject instanceof Renderer)) { + throw new IllegalArgumentException("This bo can't be deleted from " + rendererObject); + } + ((Renderer) rendererObject).deleteBuffer(this); + } + + @Override + public NativeObject createDestructableClone() { + return new BufferObject(getId()); + } + + @Override + public long getUniqueId() { + return ((long) OBJTYPE_BO << 32) | (0xffffffffL & (long) id); + } + + /** + * Set binding point + * @param binding binding point + */ + public void setBinding(final int binding) { + this.binding = binding; + } + + public WeakReference getWeakRef() { + if (weakRef == null) weakRef = new WeakReference(this); + return weakRef; + } + + public AccessHint getAccessHint() { + return accessHint; + } + + /** + * Set AccessHint to hint the renderer on how to access this data. + * + * @param natureHint + */ + public void setAccessHint(AccessHint accessHint) { + this.accessHint = accessHint; + setUpdateNeeded(); + } + + public NatureHint getNatureHint() { + return natureHint; + } + + /** + * Set NatureHint to hint the renderer on how to use this data. + * + * @param natureHint + */ + public void setNatureHint(NatureHint natureHint) { + this.natureHint = natureHint; + setUpdateNeeded(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(accessHint.ordinal(), "accessHint", 0); + oc.write(natureHint.ordinal(), "natureHint", 0); + oc.writeSavableArrayList(regions, "regions", null); + oc.write(data, "data", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + accessHint = AccessHint.values()[ic.readInt("accessHint", 0)]; + natureHint = NatureHint.values()[ic.readInt("natureHint", 0)]; + regions.addAll(ic.readSavableArrayList("regions", null)); + data = ic.readByteBuffer("data", null); + setUpdateNeeded(true); + } + + @Override + public BufferObject clone() { + BufferObject clone = (BufferObject) super.clone(); + clone.binding = -1; + clone.weakRef = null; + clone.data = BufferUtils.clone(data); + clone.regions = new ArrayList(); + assert clone.regions != regions; + for (BufferRegion r : regions) { + clone.regions.add(r.clone()); + } + clone.dirtyRegionsIterator = null; + + clone.setUpdateNeeded(); + return clone; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(getClass().getSimpleName()).append("{\n"); + for (BufferRegion r : regions) { + sb.append(" ").append(r).append("\n"); + } + sb.append("}"); + return sb.toString(); + } + + /** + * Get name of the buffer object + * + * @return the name of this buffer object, can be null + */ + public String getName() { + return name; + } + + /** + * Set name for debugging purposes + * + * @param name + * the name of this buffer object + */ + public void setName(String name) { + this.name = name; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferRegion.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferRegion.java new file mode 100644 index 0000000000..dc19965a73 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/BufferRegion.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject; + +import java.io.IOException; +import java.nio.ByteBuffer; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; + +/** + * A slice of a buffer + * + * @author Riccardo Balbo + * + */ +public class BufferRegion implements Savable, Cloneable { + protected int start = -1; + protected int end = -1; + protected boolean dirty = true; + protected boolean fullBufferRegion = false; + protected BufferObject bo; + protected ByteBuffer slice; + protected ByteBuffer source; + + public BufferRegion(int start, int end) { + this.start = start; + this.end = end; + } + + public BufferRegion() { + + } + + /** + * Rewind and get a ByteBuffer pointing to this region of the main buffer + * + * @return ByteBuffer + */ + public ByteBuffer getData() { + ByteBuffer d = bo.getData(); + if (source == null || d != source || slice == null) { + source = d; + int currentPos = source.position(); + int currentLimit = source.limit(); + assert end < source.capacity() : "Can't set limit at " + end + " on capacity " + source.capacity(); + source.limit(end + 1); + source.position(start); + slice = source.slice(); + slice.order(source.order()); + assert slice.limit() == (end - start + 1) : "Capacity is " + slice.limit() + " but " + (end - start + 1) + " expected"; + source.limit(currentLimit); + source.position(currentPos); + } + slice.rewind(); + return slice; + } + + /** + * Get beginning of the region + * + * @return position in the buffer + */ + public int getStart() { + return start; + } + + /** + * Get end of the region + * + * @return position in the buffer + */ + public int getEnd() { + return end; + } + + /** + * Get the length of this region + * + * @return the length of this region + */ + public int length() { + return end - start + 1; + } + + /** + * Returns true of the region is dirty + * + * @return + */ + public boolean isDirty() { + return dirty; + } + + /** + * Mark this region for update + */ + public void markDirty() { + dirty = true; + } + + /** + * Clear this region mark + */ + public void clearDirty() { + dirty = false; + } + + /** + * Returns true if this region includes the entire buffer. Can be used for + * optimization. + * + * @return + */ + public boolean isFullBufferRegion() { + return fullBufferRegion; + } + + @Override + public String toString() { + return "Region [start=" + start + ", end=" + end + ", size=" + (end - start) + ", dirty=" + dirty + "]"; + } + + @Override + public void write(JmeExporter ex) throws IOException { + OutputCapsule oc = ex.getCapsule(this); + oc.write(start, "start", 0); + oc.write(end, "end", 0); + oc.write(dirty, "dirty", false); + } + + @Override + public void read(JmeImporter im) throws IOException { + InputCapsule ic = im.getCapsule(this); + start = ic.readInt("start", 0); + end = ic.readInt("end", 0); + dirty = ic.readBoolean("dirty", false); + } + + @Override + public BufferRegion clone() { + try { + return (BufferRegion) super.clone(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/DirtyRegionsIterator.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/DirtyRegionsIterator.java new file mode 100644 index 0000000000..2ed3c99a77 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/DirtyRegionsIterator.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * An helper class that iterates and merges dirty regions + * + * @author Riccardo Balbo + */ +public class DirtyRegionsIterator implements Iterator { + + private static class DirtyRegion extends BufferRegion { + List regions = new ArrayList(); + + @Override + public ByteBuffer getData() { + ByteBuffer d = bo.getData(); + if (source == null || d != source || slice == null) { + source = d; + int currentPos = source.position(); + int currentLimit = source.limit(); + source.limit(source.capacity()); + source.position(0); + slice = source.slice(); + source.limit(currentLimit); + source.position(currentPos); + } + slice.limit(end); + slice.position(start); + return slice; + } + + public void clearDirty() { + regions.forEach(BufferRegion::clearDirty); + super.clearDirty(); + } + } + + private BufferObject bufferObject; + + public DirtyRegionsIterator(BufferObject bufferObject) { + this.bufferObject = bufferObject; + } + + private final DirtyRegion dirtyRegion = new DirtyRegion(); + private int pos = 0; + + public void rewind() { + pos = 0; + } + + public boolean hasNext() { + return pos < bufferObject.regions.size(); + } + + public BufferRegion next() { + + dirtyRegion.bo = bufferObject; + dirtyRegion.regions.clear(); + + if (bufferObject.regions.size() == 0) { + if (!bufferObject.isUpdateNeeded()) return null; + dirtyRegion.fullBufferRegion = true; + dirtyRegion.end = bufferObject.getData().limit(); + dirtyRegion.start = 0; + return dirtyRegion; + } + + + while (pos < bufferObject.regions.size()) { + BufferRegion dr = bufferObject.regions.get(pos++); + if (dr.isDirty()) { + if (dirtyRegion.regions.size() == 0) dirtyRegion.start = dr.start; + dirtyRegion.end = dr.end; + dirtyRegion.regions.add(dr); + } else if (dirtyRegion.regions.size() != 0) break; + } + + if (dirtyRegion.regions.size() == 0) return null; + dirtyRegion.fullBufferRegion = dirtyRegion.regions.size() == bufferObject.regions.size(); + dirtyRegion.markDirty(); + + return dirtyRegion; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java new file mode 100644 index 0000000000..f8edf9a203 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/BufferLayout.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject.layout; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.math.FastMath; +import com.jme3.util.functional.Function; + +/** + * Layout serializer for buffers + * + * @author Riccardo Balbo + */ +public abstract class BufferLayout { + + public static abstract class ObjectSerializer { + private Function filter; + + public ObjectSerializer(Class cls) { + this(obj -> { + Class objc = obj instanceof Class ? (Class) obj : obj.getClass(); + return cls.isAssignableFrom(objc); + }); + + } + + public ObjectSerializer(Function filter) { + this.filter = filter; + } + + public final boolean canSerialize(Object obj) { + return filter.eval(obj); + } + + public abstract int length(BufferLayout layout, T obj); + + public abstract int basicAlignment(BufferLayout layout, T obj); + + public abstract void write(BufferLayout layout, ByteBuffer bbf, T obj); + } + + protected List> serializers = new ArrayList>(); + + protected ObjectSerializer getSerializer(Object obj) { + for (int i = serializers.size() - 1; i >= 0; i--) { + ObjectSerializer sr = serializers.get(i); + if (sr.canSerialize(obj)) return sr; + + } + throw new RuntimeException("Serializer not found for " + obj + " of type " + obj.getClass()); + } + + /** + * Register a serializer + * @param serializer An object of type {@link ObjectSerializer} + */ + protected void registerSerializer(ObjectSerializer serializer) { + serializers.add(serializer); + } + + /** + * Estimate size of Object when serialized accordingly with std140 + * + * @param o + * the object to serialize + * @return the size + */ + @SuppressWarnings("unchecked") + public int estimateSize(Object o) { + ObjectSerializer s = getSerializer(o); + return s.length(this, o); + } + /** + * Get basic alignment of Object when serialized accordingly with std140 + * + * @param o + * the object to serialize + * @return the basic alignment + */ + @SuppressWarnings("unchecked") + public int getBasicAlignment(Object o) { + ObjectSerializer s = getSerializer(o); + return s.basicAlignment(this, o); + } + /** + * Align a position to the given basicAlignment + * + * @param pos + * the position to align + * @param basicAlignment + * the basic alignment + * @return the aligned position + */ + public int align(int pos, int basicAlignment) { + return pos==0?pos:FastMath.toMultipleOf(pos, basicAlignment); + } + + /** + * Serialize an object accordingly with the std140 layout and write the + * result to a BufferObject + * + * @param out + * the output BufferObject where the object will be serialized + * (starting from the current position) + * @param o + * the Object to serialize + */ + @SuppressWarnings("unchecked") + public void write(ByteBuffer out, Object o) { + ObjectSerializer s = getSerializer(o); + s.write(this, out, o); + } + + + public abstract String getId(); +} diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/RawLayout.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/RawLayout.java new file mode 100644 index 0000000000..b1b54fcde9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/RawLayout.java @@ -0,0 +1,542 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject.layout; + +import java.nio.ByteBuffer; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; + +/** + * Simple serializer + * + * @author Riccardo Balbo + */ +public class RawLayout extends BufferLayout { + public RawLayout() { + // Init default serializers + registerSerializer(new ObjectSerializer(byte[].class) { + @Override + public int length(BufferLayout serializer, byte[] obj) { + return obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, byte[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, byte[] obj) { + bbf.put(obj); + } + }); + + registerSerializer(new ObjectSerializer(Integer.class) { + @Override + public int length(BufferLayout serializer, Integer obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Integer obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Integer obj) { + bbf.putInt(obj); + } + }); + + registerSerializer(new ObjectSerializer(Boolean.class) { + @Override + public int length(BufferLayout serializer, Boolean obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Boolean obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Boolean obj) { + bbf.putInt(obj ? 1 : 0); + } + }); + + registerSerializer(new ObjectSerializer(Float.class) { + @Override + public int length(BufferLayout serializer, Float obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Float obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Float obj) { + bbf.putFloat(obj); + } + }); + + registerSerializer(new ObjectSerializer(Vector2f.class) { + @Override + public int length(BufferLayout serializer, Vector2f obj) { + return 4 * 2; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector2f obj) { + return 1; + + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + } + }); + + registerSerializer(new ObjectSerializer(ColorRGBA.class) { + @Override + public int length(BufferLayout serializer, ColorRGBA obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, ColorRGBA obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA obj) { + bbf.putFloat(obj.r); + bbf.putFloat(obj.g); + bbf.putFloat(obj.b); + bbf.putFloat(obj.a); + } + }); + + registerSerializer(new ObjectSerializer(Quaternion.class) { + @Override + public int length(BufferLayout serializer, Quaternion obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Quaternion obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion obj) { + bbf.putFloat(obj.getX()); + bbf.putFloat(obj.getY()); + bbf.putFloat(obj.getZ()); + bbf.putFloat(obj.getW()); + } + }); + + registerSerializer(new ObjectSerializer(Vector4f.class) { + @Override + public int length(BufferLayout serializer, Vector4f obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector4f obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + bbf.putFloat(obj.w); + + } + }); + + registerSerializer(new ObjectSerializer(Vector3f.class) { + @Override + public int length(BufferLayout serializer, Vector3f obj) { + return 4 * 3; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector3f obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + } + }); + + registerSerializer(new ObjectSerializer(Integer[].class) { + @Override + public int length(BufferLayout serializer, Integer[] obj) { + return 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Integer[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Integer[] obj) { + for (int i : obj) { + bbf.putInt(i); + } + } + }); + + registerSerializer(new ObjectSerializer(Float[].class) { + @Override + public int length(BufferLayout serializer, Float[] obj) { + return 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Float[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Float[] obj) { + for (float i : obj) { + bbf.putFloat(i); + } + } + }); + + registerSerializer(new ObjectSerializer(Boolean[].class) { + @Override + public int length(BufferLayout serializer, Boolean[] obj) { + return 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Boolean[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Boolean[] obj) { + for (boolean i : obj) { + bbf.putInt(i ? 1 : 0); + + } + } + }); + + registerSerializer(new ObjectSerializer(Vector2f[].class) { + @Override + public int length(BufferLayout serializer, Vector2f[] obj) { + return 4 * obj.length * 2; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector2f[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f[] obj) { + for (Vector2f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + + } + } + }); + + registerSerializer(new ObjectSerializer(Vector3f[].class) { + @Override + public int length(BufferLayout serializer, Vector3f[] obj) { + return 4 * obj.length * 3; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector3f[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f[] obj) { + for (Vector3f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector4f[].class) { + @Override + public int length(BufferLayout serializer, Vector4f[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector4f[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f[] obj) { + for (Vector4f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + bbf.putFloat(i.w); + } + } + }); + + registerSerializer(new ObjectSerializer(ColorRGBA[].class) { + @Override + public int length(BufferLayout serializer, ColorRGBA[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, ColorRGBA[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA[] obj) { + for (ColorRGBA i : obj) { + bbf.putFloat(i.r); + bbf.putFloat(i.g); + bbf.putFloat(i.b); + bbf.putFloat(i.a); + } + } + }); + registerSerializer(new ObjectSerializer(Quaternion[].class) { + @Override + public int length(BufferLayout serializer, Quaternion[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Quaternion[] obj) { + return 1; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion[] obj) { + for (Quaternion i : obj) { + bbf.putFloat(i.getX()); + bbf.putFloat(i.getY()); + bbf.putFloat(i.getZ()); + bbf.putFloat(i.getW()); + } + } + }); + + registerSerializer(new ObjectSerializer(Matrix3f.class) { + @Override + public int length(BufferLayout serializer, Matrix3f obj) { + return 3 * 4 * 3; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix3f obj) { + return 1; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix3f obj) { + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + } + }); + + registerSerializer(new ObjectSerializer(Matrix4f.class) { + @Override + public int length(BufferLayout serializer, Matrix4f obj) { + return 4 * 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix4f obj) { + return 1; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix4f obj) { + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + } + }); + + registerSerializer(new ObjectSerializer(Matrix3f[].class) { + @Override + public int length(BufferLayout serializer, Matrix3f[] obj) { + return 3 * 4 * 3 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix3f[] obj) { + return 1; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix3f[] objs) { + for (Matrix3f obj : objs) { + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + } + } + }); + + registerSerializer(new ObjectSerializer(Matrix4f[].class) { + @Override + public int length(BufferLayout serializer, Matrix4f[] obj) { + return 4 * 4 * 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix4f[] obj) { + return 1; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix4f[] objs) { + for (Matrix4f obj : objs) { + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + } + + } + }); + + } + + @Override + public String getId() { + return "raw"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/Std140Layout.java b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/Std140Layout.java new file mode 100644 index 0000000000..62aa54e0fb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/bufferobject/layout/Std140Layout.java @@ -0,0 +1,614 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.shader.bufferobject.layout; + +import java.nio.ByteBuffer; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.math.Vector4f; + +/** + * Serializer that respects the Std140 layout + * (https://www.khronos.org/registry/OpenGL/specs/gl/glspec45.core.pdf#page=159) + * (www.opengl.org/registry/specs/ARB/uniform_buffer_object.txt) + * + * @author Riccardo Balbo + */ +public class Std140Layout extends BufferLayout { + public Std140Layout() { + // Init default serializers + // 1. If the member is a scalar consuming N basic machine units, the + // base alignment is N . + registerSerializer(new ObjectSerializer(Integer.class) { + @Override + public int length(BufferLayout serializer, Integer obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Integer obj) { + return 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Integer obj) { + bbf.putInt(obj); + } + }); + + registerSerializer(new ObjectSerializer(Boolean.class) { + @Override + public int length(BufferLayout serializer, Boolean obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Boolean obj) { + return 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Boolean obj) { + bbf.putInt(obj ? 1 : 0); + } + }); + + registerSerializer(new ObjectSerializer(Float.class) { + @Override + public int length(BufferLayout serializer, Float obj) { + return 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Float obj) { + return 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Float obj) { + bbf.putFloat(obj); + } + }); + + // 2. If the member is a two- or four-component vector with components + // consuming N basic machine units, the base alignment is 2N or 4N , + // respectively + registerSerializer(new ObjectSerializer(Vector2f.class) { + @Override + public int length(BufferLayout serializer, Vector2f obj) { + return 4 * 2; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector2f obj) { + return 4 * 2; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + } + }); + + registerSerializer(new ObjectSerializer(ColorRGBA.class) { + @Override + public int length(BufferLayout serializer, ColorRGBA obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, ColorRGBA obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA obj) { + bbf.putFloat(obj.r); + bbf.putFloat(obj.g); + bbf.putFloat(obj.b); + bbf.putFloat(obj.a); + } + }); + + registerSerializer(new ObjectSerializer(Quaternion.class) { + @Override + public int length(BufferLayout serializer, Quaternion obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Quaternion obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion obj) { + bbf.putFloat(obj.getX()); + bbf.putFloat(obj.getY()); + bbf.putFloat(obj.getZ()); + bbf.putFloat(obj.getW()); + } + }); + + registerSerializer(new ObjectSerializer(Vector4f.class) { + @Override + public int length(BufferLayout serializer, Vector4f obj) { + return 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector4f obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + bbf.putFloat(obj.w); + + } + }); + + // 3. If the member is a three-component vector with components + // consuming N + // basic machine units, the base alignment is 4N + registerSerializer(new ObjectSerializer(Vector3f.class) { + @Override + public int length(BufferLayout serializer, Vector3f obj) { + return 4 * 3; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector3f obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f obj) { + bbf.putFloat(obj.x); + bbf.putFloat(obj.y); + bbf.putFloat(obj.z); + // bbf.putFloat(0); + + } + }); + + // 4. If the member is an array of scalars or vectors, the base + // alignment and array + // stride are set to match the base alignment of a single array element, + // according + // to rules (1), (2), and (3), and rounded up to the base alignment of a + // vec4. The + // array may have padding at the end; the base offset of the member + // following + // the array is rounded up to the next multiple of the base alignment. + + registerSerializer(new ObjectSerializer(Integer[].class) { + @Override + public int length(BufferLayout serializer, Integer[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Integer[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Integer[] obj) { + for (int i : obj) { + bbf.putInt(i); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Float[].class) { + @Override + public int length(BufferLayout serializer, Float[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Float[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Float[] obj) { + for (float i : obj) { + bbf.putFloat(i); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Boolean[].class) { + @Override + public int length(BufferLayout serializer, Boolean[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Boolean[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Boolean[] obj) { + for (boolean i : obj) { + bbf.putInt(i ? 1 : 0); + bbf.putInt(0); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector2f[].class) { + @Override + public int length(BufferLayout serializer, Vector2f[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector2f[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector2f[] obj) { + for (Vector2f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putInt(0); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector3f[].class) { + @Override + public int length(BufferLayout serializer, Vector3f[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector3f[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector3f[] obj) { + for (Vector3f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + bbf.putInt(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Vector4f[].class) { + @Override + public int length(BufferLayout serializer, Vector4f[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Vector4f[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Vector4f[] obj) { + for (Vector4f i : obj) { + bbf.putFloat(i.x); + bbf.putFloat(i.y); + bbf.putFloat(i.z); + bbf.putFloat(i.w); + } + } + }); + + registerSerializer(new ObjectSerializer(ColorRGBA[].class) { + @Override + public int length(BufferLayout serializer, ColorRGBA[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, ColorRGBA[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, ColorRGBA[] obj) { + for (ColorRGBA i : obj) { + bbf.putFloat(i.r); + bbf.putFloat(i.g); + bbf.putFloat(i.b); + bbf.putFloat(i.a); + } + } + }); + registerSerializer(new ObjectSerializer(Quaternion[].class) { + @Override + public int length(BufferLayout serializer, Quaternion[] obj) { + return 4 * obj.length * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Quaternion[] obj) { + return 4 * 4; + } + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Quaternion[] obj) { + for (Quaternion i : obj) { + bbf.putFloat(i.getX()); + bbf.putFloat(i.getY()); + bbf.putFloat(i.getZ()); + bbf.putFloat(i.getW()); + } + } + }); + + // 5. If the member is a column-major matrix with C columns and R rows, + // the + // matrix is stored identically to an array of C column vectors with R + // compo- + // nents each, according to rule (4). + + registerSerializer(new ObjectSerializer(Matrix3f.class) { + @Override + public int length(BufferLayout serializer, Matrix3f obj) { + return 3 * 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix3f obj) { + return 4 * 4; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix3f obj) { + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + } + }); + + registerSerializer(new ObjectSerializer(Matrix4f.class) { + @Override + public int length(BufferLayout serializer, Matrix4f obj) { + return 4 * 4 * 4; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix4f obj) { + return 4 * 4; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix4f obj) { + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + } + }); + + // 6. If the member is an array of S column-major matrices with C + // columns and + // R rows, the matrix is stored identically to a row of S × C column + // vectors + // with R components each, according to rule (4). + + registerSerializer(new ObjectSerializer(Matrix3f[].class) { + @Override + public int length(BufferLayout serializer, Matrix3f[] obj) { + return 3 * 4 * 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix3f[] obj) { + return 4 * 4; + } + + final Vector3f tmp = new Vector3f(); + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix3f[] objs) { + for (Matrix3f obj : objs) { + obj.getColumn(0, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(1, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + + obj.getColumn(2, tmp); + bbf.putFloat(tmp.x); + bbf.putFloat(tmp.y); + bbf.putFloat(tmp.z); + bbf.putFloat(0); + } + } + }); + + registerSerializer(new ObjectSerializer(Matrix4f[].class) { + @Override + public int length(BufferLayout serializer, Matrix4f[] obj) { + return 4 * 4 * 4 * obj.length; + } + + @Override + public int basicAlignment(BufferLayout serializer, Matrix4f[] obj) { + return 4 * 4; + } + + final float[] tmpF = new float[4]; + + @Override + public void write(BufferLayout serializer, ByteBuffer bbf, Matrix4f[] objs) { + for (Matrix4f obj : objs) { + obj.getColumn(0, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(1, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(2, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + + obj.getColumn(3, tmpF); + bbf.putFloat(tmpF[0]); + bbf.putFloat(tmpF[1]); + bbf.putFloat(tmpF[2]); + bbf.putFloat(tmpF[3]); + } + + } + }); + + // 7. If the member is a row-major matrix with C columns and R rows, the + // matrix + // is stored identically to an array of R row vectors with C components + // each, + // according to rule (4). + + // Nothing: jme matrices are column-major + + // 8. If the member is an array of S row-major matrices with C columns + // and R + // rows, the matrix is stored identically to a row of S × R row vectors + // with C + // components each, according to rule (4) + + // Nothing: jme matrices are column-major + + // 9. If the member is a structure, the base alignment of the structure + // is N , where + // N is the largest base alignment value of any of its members, and + // rounded + // up to the base alignment of a vec4. The individual members of this + // sub- + // structure are then assigned offsets by applying this set of rules + // recursively, + // where the base offset of the first member of the sub-structure is + // equal to the + // aligned offset of the structure. The structure may have padding at + // the end; + // the base offset of the member following the sub-structure is rounded + // up to + // the next multiple of the base alignment of the structure. + + // IMPLEMENTED AT A HIGHER LEVEL + + // 10. If the member is an array of S structures, the S elements of the + // array are laid + // out in order, according to rule (9) + + // IMPLEMENTED AT A HIGHER LEVEL + + } + + @Override + public String getId() { + return "std140"; + } +} diff --git a/jme3-core/src/main/java/com/jme3/shader/package-info.java b/jme3-core/src/main/java/com/jme3/shader/package-info.java new file mode 100644 index 0000000000..80178882eb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shader/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * manage and manipulate OpenGL shaders + */ +package com.jme3.shader; diff --git a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java index abdbe054db..e1df51ac1f 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,10 +32,6 @@ package com.jme3.shadow; import com.jme3.asset.AssetManager; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.Matrix4f; @@ -48,42 +44,37 @@ import com.jme3.util.clone.Cloner; import com.jme3.util.clone.JmeCloneable; -import java.io.IOException; - /** - * * Generic abstract filter that holds common implementations for the different * shadow filters * * @author Rémy Bouquet aka Nehon */ -public abstract class AbstractShadowFilter extends Filter implements Cloneable, JmeCloneable { +public abstract class AbstractShadowFilter extends Filter implements JmeCloneable { protected T shadowRenderer; protected ViewPort viewPort; + private final Vector4f tempVec4 = new Vector4f(); + private final Matrix4f tempMat4 = new Matrix4f(); + /** - * used for serialization + * For serialization only. Do not use. */ - protected AbstractShadowFilter(){ + protected AbstractShadowFilter() { } - + /** - * Abstract class constructor + * Creates an AbstractShadowFilter. Subclasses invoke this constructor. * - * @param manager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param shadowRenderer the shadowRenderer to use for this Filter + * @param assetManager The application's asset manager. + * @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048). + * @param shadowRenderer The shadowRenderer to use for this Filter */ - @SuppressWarnings("all") - protected AbstractShadowFilter(AssetManager manager, int shadowMapSize, T shadowRenderer) { + protected AbstractShadowFilter(AssetManager assetManager, int shadowMapSize, T shadowRenderer) { super("Post Shadow"); - material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); this.shadowRenderer = shadowRenderer; - this.shadowRenderer.setPostShadowMaterial(material); - - //this is legacy setting for shadows with backface shadows + // this is legacy setting for shadows with backface shadows this.shadowRenderer.setRenderBackFacesShadows(true); } @@ -97,32 +88,35 @@ protected boolean isRequiresDepthTexture() { return true; } + /** + * @deprecated Use {@link #getMaterial()} instead. + * @return The Material used by this filter. + */ + @Deprecated public Material getShadowMaterial() { return material; } - Vector4f tmpv = new Vector4f(); @Override protected void preFrame(float tpf) { shadowRenderer.preFrame(tpf); - material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert()); Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); - material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23)); - + material.setMatrix4("ViewProjectionMatrixInverse", tempMat4.set(m).invertLocal()); + material.setVector4("ViewProjectionMatrixRow2", tempVec4.set(m.m20, m.m21, m.m22, m.m23)); } @Override protected void postQueue(RenderQueue queue) { shadowRenderer.postQueue(queue); - if(shadowRenderer.skipPostPass){ - //removing the shadow map so that the post pass is skipped - material.setTexture("ShadowMap0", null); - } + if (shadowRenderer.skipPostPass) { + // removing the shadow map so that the post pass is skipped + material.setTexture("ShadowMap0", null); + } } @Override protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { - if(!shadowRenderer.skipPostPass){ + if (!shadowRenderer.skipPostPass) { shadowRenderer.setPostShadowParams(); } } @@ -130,15 +124,17 @@ protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBu @Override protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { shadowRenderer.needsfallBackMaterial = true; + material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); + shadowRenderer.setPostShadowMaterial(material); shadowRenderer.initialize(renderManager, vp); this.viewPort = vp; } - - /** + + /** * How far the shadows are rendered in the view * - * @see #setShadowZExtend(float zFar) * @return shadowZExtend + * @see #setShadowZExtend(float zFar) */ public float getShadowZExtend() { return shadowRenderer.getShadowZExtend(); @@ -207,10 +203,10 @@ public int getEdgesThickness() { } /** - * Sets the shadow edges thickness. default is 1, setting it to lower values - * can help to reduce the jagged effect of the shadow edges + * Sets the shadow edges thickness. Default is 10. Setting it to lower values + * can help reduce the jagged effect of shadow edges. * - * @param edgesThickness + * @param edgesThickness the desired thickness (in tenths of a pixel, default=10) */ public void setEdgesThickness(int edgesThickness) { shadowRenderer.setEdgesThickness(edgesThickness); @@ -218,22 +214,18 @@ public void setEdgesThickness(int edgesThickness) { /** * isFlushQueues does nothing and is kept only for backward compatibility + * + * @return false */ @Deprecated public boolean isFlushQueues() { return shadowRenderer.isFlushQueues(); } - /** - * setFlushQueues does nothing now and is kept only for backward compatibility - */ - @Deprecated - public void setFlushQueues(boolean flushQueues) {} - /** * sets the shadow compare mode see {@link CompareMode} for more info * - * @param compareMode + * @param compareMode the desired mode */ final public void setShadowCompareMode(CompareMode compareMode) { shadowRenderer.setShadowCompareMode(compareMode); @@ -253,7 +245,7 @@ public CompareMode getShadowCompareMode() { * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} * for more info * - * @param filterMode + * @param filterMode the desired mode */ final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { shadowRenderer.setEdgeFilteringMode(filterMode); @@ -262,7 +254,7 @@ final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { /** * * !! WARNING !! this parameter is defaulted to true for the ShadowFilter. - * Setting it to true, may produce edges artifacts on shadows. * + * Setting it to true, may produce edges artifacts on shadows. * * Set to true if you want back faces shadows on geometries. * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. @@ -276,7 +268,7 @@ final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { * * @param renderBackFacesShadows true or false. */ - public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { + public void setRenderBackFacesShadows(boolean renderBackFacesShadows) { shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows); } @@ -298,7 +290,6 @@ public RenderState getPreShadowForcedRenderState() { return shadowRenderer.getPreShadowForcedRenderState(); } - /** * returns the edge filtering mode * @@ -327,7 +318,16 @@ public int getShadowMapSize() { return shadowRenderer.getShadowMapSize(); } + /** + * Displays the shadow frustums for debugging purposes. + * Creates geometry showing the shadow map camera frustums in the scene. + */ + public void displayFrustum() { + shadowRenderer.displayFrustum(); + } + @Override + @SuppressWarnings("unchecked") public AbstractShadowFilter jmeClone() { try { return (AbstractShadowFilter) super.clone(); @@ -343,15 +343,4 @@ public void cloneFields(final Cloner cloner, final Object original) { shadowRenderer.setPostShadowMaterial(material); } - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - } } 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 4c0df0bd66..b618302fe9 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/AbstractShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,13 @@ package com.jme3.shadow; import com.jme3.asset.AssetManager; -import com.jme3.export.*; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.export.Savable; +import com.jme3.light.LightFilter; +import com.jme3.light.NullLightFilter; import com.jme3.material.Material; import com.jme3.material.RenderState; import com.jme3.math.ColorRGBA; @@ -50,9 +56,11 @@ import com.jme3.renderer.queue.RenderQueue; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; +import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.debug.WireFrustum; import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; @@ -68,86 +76,102 @@ import java.util.logging.Logger; /** - * abstract shadow renderer that holds commons feature to have for a shadow - * renderer + * An abstract shadow renderer that provides common features for shadow rendering. * * @author Rémy Bouquet aka Nehon */ -public abstract class AbstractShadowRenderer implements SceneProcessor, Savable, JmeCloneable, Cloneable { +public abstract class AbstractShadowRenderer implements SceneProcessor, Savable, JmeCloneable { protected static final Logger logger = Logger.getLogger(AbstractShadowRenderer.class.getName()); + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); + // The number of shadow maps to render. protected int nbShadowMaps = 1; + // The resolution (width and height) of each shadow map. protected float shadowMapSize; + // The intensity of the shadows, ranging from 0.0 (fully transparent) to 1.0 (fully opaque). protected float shadowIntensity = 0.7f; + // The RenderManager instance used for rendering operations. protected RenderManager renderManager; + // The ViewPort associated with this shadow renderer. protected ViewPort viewPort; + // Array of frame buffers used for rendering shadow maps. protected FrameBuffer[] shadowFB; + // Array of 2D textures representing the generated shadow maps. protected Texture2D[] shadowMaps; + // A dummy texture used to prevent read-buffer crashes on certain platforms (e.g., OSX). protected Texture2D dummyTex; + // Material used for the pre-shadow pass (rendering occluders into the shadow map). protected Material preshadowMat; + // Material used for the post-shadow pass (applying shadows to the scene). protected Material postshadowMat; + // Array of light view projection matrices for each shadow map. protected Matrix4f[] lightViewProjectionsMatrices; + // The AssetManager instance used to load assets. protected AssetManager assetManager; + // Flag indicating whether debug visualizations (e.g., shadow maps) should be displayed. protected boolean debug = false; + // The thickness of shadow edges, influencing PCF (Percentage-Closer Filtering). Value is in tenths of a pixel. protected float edgesThickness = 1.0f; + // The filtering mode applied to shadow edges. protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; + // The shadow comparison mode (hardware or software). protected CompareMode shadowCompareMode = CompareMode.Hardware; + // Array of Picture objects used for debugging to display shadow maps. protected Picture[] dispPic; + // Forced RenderState used during the pre-shadow pass to render occluders. protected RenderState forcedRenderState = new RenderState(); + // Flag indicating whether back faces should cast shadows. protected boolean renderBackFacesShadows = true; + // The application profiler for performance monitoring. protected AppProfiler prof; - - /** - * true if the fallback material should be used, otherwise false - */ + // Flag indicating whether shadow frustums should be displayed for debugging. + protected boolean debugfrustums = false; + // True if a fallback material should be used for post-shadow rendering, otherwise false. + // This occurs if some scene materials do not support the post-shadow technique. protected boolean needsfallBackMaterial = false; - /** - * name of the post material technique - */ + // The name of the technique to use for the post-shadow material. protected String postTechniqueName = "PostShadow"; - /** - * list of materials for post shadow queue geometries - */ - protected List matCache = new ArrayList(); + // A cache of materials found on geometries in the post-shadow queue. + protected List matCache = new ArrayList<>(); + // List of geometries that receive shadows. protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); + // List of geometries that cast shadows (occluders). protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); - private String[] shadowMapStringCache; + // Internal cache for shadow map uniform names (e.g., "ShadowMap0", "ShadowMap1"). + protected String[] shadowMapStringCache; + // nternal cache for light view projection matrix uniform names (e.g., "LightViewProjectionMatrix0"). private String[] lightViewStringCache; - /** - * fade shadows at distance - */ + // The distance at which shadows start to fade out. A value of 0 means no override. protected float zFarOverride = 0; + // Vector containing information about shadow fading (start distance, inverse fade length). protected Vector2f fadeInfo; + // The length over which shadows fade out. protected float fadeLength; + // A camera used to define the frustum for shadow rendering, especially when `zFarOverride` is used. protected Camera frustumCam; - /** - * true to skip the post pass when there are no shadow casters - */ + // True to skip the post pass when there are no shadow casters. protected boolean skipPostPass; - + /** - * used for serialization + * For serialization only. Do not use. */ - protected AbstractShadowRenderer(){ - } - + protected AbstractShadowRenderer() { + } + /** - * Create an abstract shadow renderer. Subclasses invoke this constructor. + * Creates an AbstractShadowRenderer. Subclasses invoke this constructor. * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadow maps (512,1024,2048, - * etc...) - * @param nbShadowMaps the number of shadow maps rendered (the more shadow - * maps the more quality, the fewer fps). + * @param assetManager The application's asset manager. + * @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048). + * @param nbShadowMaps The number of shadow maps to render (1 to 4). More maps + * improve quality but can reduce performance. */ protected AbstractShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { - this.assetManager = assetManager; - this.nbShadowMaps = nbShadowMaps; this.shadowMapSize = shadowMapSize; + this.nbShadowMaps = nbShadowMaps; init(assetManager, nbShadowMaps, shadowMapSize); - } private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) { @@ -159,7 +183,7 @@ private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize shadowMapStringCache = new String[nbShadowMaps]; lightViewStringCache = new String[nbShadowMaps]; - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + //DO NOT COMMENT THIS (it prevents the OSX incomplete read-buffer crash) dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8); preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); @@ -170,16 +194,16 @@ private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1); shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, Format.Depth); - shadowFB[i].setDepthTexture(shadowMaps[i]); + shadowFB[i].setDepthTarget(FrameBufferTarget.newTarget(shadowMaps[i])); - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) - shadowFB[i].setColorTexture(dummyTex); - shadowMapStringCache[i] = "ShadowMap" + i; + //DO NOT COMMENT THIS (it prevents the OSX incomplete read-buffer crash) + shadowFB[i].addColorTarget(FrameBufferTarget.newTarget(dummyTex)); + shadowMapStringCache[i] = "ShadowMap" + i; lightViewStringCache[i] = "LightViewProjectionMatrix" + i; postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); - //quads for debuging purpose + //quads for debugging purposes dispPic[i] = new Picture("Picture" + i); dispPic[i].setTexture(assetManager, shadowMaps[i], false); } @@ -199,9 +223,10 @@ protected void initForcedRenderState() { } /** - * set the post shadow material for this renderer + * Sets the post-shadow material for this renderer. This material is used to apply + * the shadows to the main scene. * - * @param postShadowMat + * @param postShadowMat The desired Material instance to use (alias created). */ protected final void setPostShadowMaterial(Material postShadowMat) { this.postshadowMat = postShadowMat; @@ -215,14 +240,15 @@ protected final void setPostShadowMaterial(Material postShadowMat) { } /** - * Sets the filtering mode for shadow edges. See {@link EdgeFilteringMode} - * for more info. + * Sets the filtering mode for shadow edges. This affects the smoothness of + * shadow boundaries. * - * @param filterMode the desired filter mode (not null) + * @param filterMode The desired filtering mode (cannot be null). See {@link EdgeFilteringMode} + * for available options. */ final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { if (filterMode == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("filterMode cannot be null"); } this.edgeFilteringMode = filterMode; @@ -242,19 +268,21 @@ final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { } /** - * returns the edge filtering mode + * Returns the currently edge filtering mode for shadows. * + * @return The current {@link EdgeFilteringMode} enum value. * @see EdgeFilteringMode - * @return the enum value */ public EdgeFilteringMode getEdgeFilteringMode() { return edgeFilteringMode; } /** - * Sets the shadow compare mode. See {@link CompareMode} for more info. + * Sets the shadow comparison mode. This determines how shadow map values are + * compared to generate shadows. * - * @param compareMode the desired compare mode (not null) + * @param compareMode The desired compare mode (cannot be null). See {@link CompareMode} + * for available options. */ final public void setShadowCompareMode(CompareMode compareMode) { if (compareMode == null) { @@ -282,46 +310,52 @@ final public void setShadowCompareMode(CompareMode compareMode) { } /** - * returns the shadow compare mode + * Returns the currently shadow comparison mode. * + * @return The current {@link CompareMode} enum value. * @see CompareMode - * @return the shadowCompareMode */ public CompareMode getShadowCompareMode() { return shadowCompareMode; } /** - * debug function to create a visible frustum + * Debug function to create a visible wireframe frustum. This is useful for + * visualizing the shadow camera's view. + * + * @param pts Optional storage for vertex positions. If null, a new array will be created. + * @param i The index, used to assign a color to the frustum for differentiation (e.g., for multiple shadow maps). + * @return A new {@link Geometry} representing the wireframe frustum. */ protected Geometry createFrustum(Vector3f[] pts, int i) { WireFrustum frustum = new WireFrustum(pts); - Geometry frustumMdl = new Geometry("f", frustum); - frustumMdl.setCullHint(Spatial.CullHint.Never); - frustumMdl.setShadowMode(ShadowMode.Off); + Geometry geo = new Geometry("WireFrustum" + i, frustum); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); - frustumMdl.setMaterial(mat); + geo.setMaterial(mat); + geo.setCullHint(Spatial.CullHint.Never); + geo.setShadowMode(ShadowMode.Off); + switch (i) { case 0: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); + mat.setColor("Color", ColorRGBA.Pink); break; case 1: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); + mat.setColor("Color", ColorRGBA.Red); break; case 2: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); + mat.setColor("Color", ColorRGBA.Green); break; case 3: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); + mat.setColor("Color", ColorRGBA.Blue); break; default: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); + mat.setColor("Color", ColorRGBA.White); break; } - frustumMdl.updateGeometricState(); - return frustumMdl; + geo.updateGeometricState(); + return geo; } /** @@ -330,17 +364,19 @@ protected Geometry createFrustum(Vector3f[] pts, int i) { * @param rm the render manager * @param vp the viewport */ + @Override public void initialize(RenderManager rm, ViewPort vp) { renderManager = rm; viewPort = vp; postTechniqueName = "PostShadow"; - if(zFarOverride>0 && frustumCam == null){ + if (zFarOverride > 0 && frustumCam == null) { initFrustumCam(); } } - + /** - * delegates the initialization of the frustum cam to child renderers + * Delegates the initialization of the frustum camera to child renderers. + * This camera defines the view for calculating shadow frustums. */ protected abstract void initFrustumCam(); @@ -349,85 +385,92 @@ public void initialize(RenderManager rm, ViewPort vp) { * * @return true if initialized, otherwise false */ + @Override public boolean isInitialized() { return viewPort != null; } /** - * Invoked once per frame to update the shadow cams according to the light - * view. - * - * @param viewCam the scene cam + * Invoked once per frame to update the shadow cameras according to the light view. + * Subclasses must implement this method to define how shadow cameras are positioned + * and oriented. + * + * @param viewCam The main scene camera. */ protected abstract void updateShadowCams(Camera viewCam); /** - * Returns a subclass-specific geometryList containing the occluders to be - * rendered in the shadow map + * Returns a subclass-specific {@link GeometryList} containing the occluders + * that should be rendered into the shadow map. * - * @param shadowMapIndex the index of the shadow map being rendered - * @param shadowMapOccluders the list of occluders - * @return the geometryList + * @param shadowMapIndex The index of the shadow map being rendered. + * @param shadowMapOccluders An existing {@link GeometryList} that can be reused or populated. + * @return A {@link GeometryList} containing the geometries that cast shadows for the given map. */ protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders); /** - * return the shadow camera to use for rendering the shadow map according - * the given index + * Returns the shadow camera to use for rendering the shadow map according to the given index. + * Subclasses must implement this to provide the correct camera for each shadow map. * - * @param shadowMapIndex the index of the shadow map being rendered - * @return the shadowCam + * @param shadowMapIndex The index of the shadow map being rendered. + * @return The {@link Camera} instance representing the shadow's viewpoint. */ protected abstract Camera getShadowCam(int shadowMapIndex); /** - * responsible for displaying the frustum of the shadow cam for debug - * purpose + * Responsible for displaying the frustum of the shadow camera for debugging purposes. + * Subclasses can override this method to provide specific debug visualizations. * - * @param shadowMapIndex + * @param shadowMapIndex The index of the shadow map for which to display the frustum. */ protected void doDisplayFrustumDebug(int shadowMapIndex) { + // Default implementation does nothing. + } + + protected Node getSceneForDebug() { + return (Node) viewPort.getScenes().get(0); } - @SuppressWarnings("fallthrough") + @Override public void postQueue(RenderQueue rq) { lightReceivers.clear(); skipPostPass = false; - if ( !checkCulling(viewPort.getCamera()) ) { + if (!checkCulling(viewPort.getCamera())) { skipPostPass = true; return; } updateShadowCams(viewPort.getCamera()); - + Renderer r = renderManager.getRenderer(); renderManager.setForcedMaterial(preshadowMat); renderManager.setForcedTechnique("PreShadow"); for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { - - if (debugfrustums) { - doDisplayFrustumDebug(shadowMapIndex); - } - renderShadowMap(shadowMapIndex); - + if (debugfrustums) { + doDisplayFrustumDebug(shadowMapIndex); } + renderShadowMap(shadowMapIndex); + } - debugfrustums = false; + if (debugfrustums) { + debugfrustums = false; + getSceneForDebug().updateGeometricState(); + } //restore setting for future rendering r.setFrameBuffer(viewPort.getOutputFrameBuffer()); renderManager.setForcedMaterial(null); renderManager.setForcedTechnique(null); renderManager.setCamera(viewPort.getCamera(), false); - } protected void renderShadowMap(int shadowMapIndex) { shadowMapOccluders = getOccludersToRender(shadowMapIndex, shadowMapOccluders); Camera shadowCam = getShadowCam(shadowMapIndex); - //saving light view projection matrix for this split + //saving light view projection matrix for this split lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix()); renderManager.setCamera(shadowCam, false); @@ -435,18 +478,26 @@ 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 light filter + 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; + /** + * Enables debugging of shadow frustums, making them visible in the scene. + * Call this before {@link #postQueue(RenderQueue)} to see the frustums. + */ public void displayFrustum() { debugfrustums = true; } /** - * For debugging purposes, display depth shadow maps. + * For debugging purposes, displays the depth shadow maps on screen as Picture quads. + * + * @param r The current {@link Renderer} (ignored). */ protected void displayShadowMap(Renderer r) { Camera cam = viewPort.getCamera(); @@ -463,14 +514,22 @@ protected void displayShadowMap(Renderer r) { } /** - * For debugging purposes, "snapshot" the current frustum to the scene. + * For debugging purposes, "snapshots" the current state of the shadow maps + * and displays them on screen. */ public void displayDebug() { debug = true; } + /** + * Populates the provided {@link GeometryList} with geometries that are considered + * shadow receivers. Subclasses must implement this method. + * + * @param lightReceivers The {@link GeometryList} to populate with shadow-receiving geometries. + */ protected abstract void getReceivers(GeometryList lightReceivers); + @Override public void postFrame(FrameBuffer out) { if (skipPostPass) { return; @@ -478,7 +537,7 @@ public void postFrame(FrameBuffer out) { if (debug) { displayShadowMap(renderManager.getRenderer()); } - + getReceivers(lightReceivers); if (lightReceivers.size() != 0) { @@ -486,7 +545,7 @@ public void postFrame(FrameBuffer out) { setMatParams(lightReceivers); Camera cam = viewPort.getCamera(); - //some materials in the scene does not have a post shadow technique so we're using the fall back material + // Some materials in the scene do not have a post shadow technique, so we're using the fallback material. if (needsfallBackMaterial) { renderManager.setForcedMaterial(postshadowMat); } @@ -501,26 +560,32 @@ public void postFrame(FrameBuffer out) { renderManager.setForcedTechnique(null); renderManager.setForcedMaterial(null); renderManager.setCamera(cam, false); - + //clearing the params in case there are some other shadow renderers clearMatParams(); } } - + /** * This method is called once per frame and is responsible for clearing any - * material parameters that subclasses may need to clear on the post material. + * material parameters that subclasses may have set on the post-shadow material. + * This ensures that parameters from previous frames or other renderers do not + * interfere. * - * @param material the material that was used for the post shadow pass + * @param material The material that was used for the post-shadow pass. + */ + protected abstract void clearMaterialParameters(Material material); + + /** + * Clears common material parameters set by this renderer on materials in the cache. + * This is done to avoid interference with other shadow renderers or subsequent frames. */ - protected abstract void clearMaterialParameters(Material material); - - private void clearMatParams(){ + private void clearMatParams() { for (Material mat : matCache) { - - //clearing only necessary params, the others may be set by other - //renderers - //Note that j start at 1 because other shadow renderers will have + + //clearing only necessary params, the others may be set by other + //renderers + //Note that j start at 1 because other shadow renderers will have //at least 1 shadow map and will set it on each frame anyway. for (int j = 1; j < nbShadowMaps; j++) { mat.clearParam(lightViewStringCache[j]); @@ -530,8 +595,8 @@ private void clearMatParams(){ } mat.clearParam("FadeInfo"); clearMaterialParameters(mat); - } - //No need to clear the postShadowMat params as the instance is locale to each renderer + } + //No need to clear the postShadowMat params as the instance is locale to each renderer } /** @@ -542,14 +607,19 @@ private void clearMatParams(){ */ protected abstract void setMaterialParameters(Material material); - private void setMatParams(GeometryList l) { - //iteration throught all the geometries of the list to gather the materials + /** + * Iterates through the given {@link GeometryList} to gather unique materials + * and sets common shadow-related parameters on them. + * + * @param list The {@link GeometryList} containing geometries whose materials need parameters set. + */ + private void setMatParams(GeometryList list) { + //iterate through all the geometries of the list to gather the materials - buildMatCache(l); + buildMatCache(list); //iterating through the mat cache and setting the parameters for (Material mat : matCache) { - mat.setFloat("ShadowMapSize", shadowMapSize); for (int j = 0; j < nbShadowMaps; j++) { @@ -567,24 +637,30 @@ private void setMatParams(GeometryList l) { mat.setBoolean("BackfaceShadows", renderBackFacesShadows); if (fadeInfo != null) { - mat.setVector2("FadeInfo", fadeInfo); + mat.setVector2("FadeInfo", fadeInfo); } setMaterialParameters(mat); } - //At least one material of the receiving geoms does not support the post shadow techniques - //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) + // At least one material of the receiving geoms does not support the post shadow techniques, + // so we fall back to the forced material solution. (Transparent shadows won't be supported for these objects.) if (needsfallBackMaterial) { setPostShadowParams(); } - } - private void buildMatCache(GeometryList l) { + /** + * Builds a cache of unique materials from the provided {@link GeometryList} + * that support the post-shadow technique. If any material does not support + * it, the `needsfallBackMaterial` flag is set. + * + * @param list The {@link GeometryList} to extract materials from. + */ + private void buildMatCache(GeometryList list) { matCache.clear(); - for (int i = 0; i < l.size(); i++) { - Material mat = l.get(i).getMaterial(); + for (int i = 0; i < list.size(); i++) { + Material mat = list.get(i).getMaterial(); //checking if the material has the post technique and adding it to the material cache if (mat.getMaterialDef().getTechniqueDefs(postTechniqueName) != null) { if (!matCache.contains(mat)) { @@ -597,7 +673,8 @@ private void buildMatCache(GeometryList l) { } /** - * for internal use only + * For internal use only. Sets the common shadow parameters on the internal + * post-shadow material. This is used when a fallback material is needed. */ protected void setPostShadowParams() { setMaterialParameters(postshadowMat); @@ -610,45 +687,47 @@ protected void setPostShadowParams() { } postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows); } - + /** - * How far the shadows are rendered in the view + * Returns the maximum distance from the eye where shadows are rendered. + * A value of 0 indicates that the distance is dynamically computed based on scene bounds. * + * @return The shadow Z-extend distance in world units. * @see #setShadowZExtend(float zFar) - * @return shadowZExtend */ public float getShadowZExtend() { return zFarOverride; } /** - * Set the distance from the eye where the shadows will be rendered default - * value is dynamically computed to the shadow casters/receivers union bound - * zFar, capped to view frustum far value. + * Sets the distance from the camera where shadows will be rendered. + * By default (0), this value is dynamically computed based on the union bound + * of shadow casters and receivers, capped by the view frustum's far value. + * Setting a positive value overrides this dynamic computation. * - * @param zFar the zFar values that override the computed one + * @param zFar The zFar value that overrides the computed one. Set to 0 to use dynamic computation. */ public void setShadowZExtend(float zFar) { - this.zFarOverride = zFar; - if(zFarOverride == 0){ + this.zFarOverride = zFar; + if (zFarOverride == 0) { fadeInfo = null; frustumCam = null; - }else{ + } else { if (fadeInfo != null) { fadeInfo.set(zFarOverride - fadeLength, 1f / fadeLength); } - if(frustumCam == null && viewPort != null){ + if (frustumCam == null && viewPort != null) { initFrustumCam(); } } } - + /** - * Define the length over which the shadow will fade out when using a - * shadowZextend This is useful to make dynamic shadows fade into baked - * shadows in the distance. + * Defines the length over which the shadow will fade out when using a + * custom `shadowZextend`. This is useful for smoothly transitioning + * dynamic shadows into baked shadows or for preventing abrupt shadow cut-offs. * - * @param length the fade length in world units + * @param length The fade length in world units. Set to 0 to disable fading. */ public void setShadowZFadeLength(float length) { if (length == 0) { @@ -667,10 +746,10 @@ public void setShadowZFadeLength(float length) { } /** - * get the length over which the shadow will fade out when using a - * shadowZextend + * Returns the length over which the shadow will fade out when using a + * custom `shadowZextend`. * - * @return the fade length in world units + * @return The fade length in world units. Returns 0 if no fading is applied. */ public float getShadowZFadeLength() { if (fadeInfo != null) { @@ -678,37 +757,47 @@ public float getShadowZFadeLength() { } return 0f; } - + /** - * @return true if the light source bounding box is in the view frustum + * Abstract method to check if the light source's bounding box is within the view frustum + * of the given camera. This is used for culling to avoid unnecessary shadow computations. + * + * @param viewCam A {@link Camera} to define the view frustum against which to check. + * @return True if the light source's bounding box is in the view frustum, otherwise false. */ protected abstract boolean checkCulling(Camera viewCam); - - public void preFrame(float tpf) { + + @Override + public void preFrame(float tpf) { + // no-op } + @Override public void cleanup() { + // no-op } + @Override public void reshape(ViewPort vp, int w, int h) { + // no-op } /** - * Returns the shadow intensity. + * Returns the current shadow intensity. * + * @return The shadow intensity value, ranging from 0.0 to 1.0. * @see #setShadowIntensity(float shadowIntensity) - * @return shadowIntensity */ public float getShadowIntensity() { return shadowIntensity; } /** - * Set the shadowIntensity. The value should be between 0 and 1. A 0 value - * gives a bright and invisible shadow, a 1 value gives a pitch black - * shadow. The default is 0.7 + * Sets the shadow intensity. This value controls the darkness of the shadows. + * A value of 0.0 results in bright, almost invisible shadows, while 1.0 creates + * pitch-black shadows. The default value is 0.7. * - * @param shadowIntensity the darkness of the shadow + * @param shadowIntensity The desired darkness of the shadow, a float between 0.0 and 1.0. */ final public void setShadowIntensity(float shadowIntensity) { this.shadowIntensity = shadowIntensity; @@ -716,38 +805,41 @@ final public void setShadowIntensity(float shadowIntensity) { } /** - * returns the edges thickness + * Returns the configured shadow edges thickness. The value is returned + * as an integer representing tenths of a pixel (e.g., 10 for 1.0 pixel). * + * @return The edges thickness in tenths of a pixel. * @see #setEdgesThickness(int edgesThickness) - * @return edgesThickness */ public int getEdgesThickness() { return (int) (edgesThickness * 10); } /** - * Read the number of shadow maps rendered by this renderer. + * Returns the number of shadow maps currently rendered by this processor. * - * @return count + * @return The count of shadow maps. */ public int getNumShadowMaps() { return nbShadowMaps; } /** - * Read the size of each shadow map rendered by this renderer. + * Returns the size (width and height) of each shadow map rendered by this processor. * - * @return a map's height (which is also its width, in pixels) + * @return The resolution of a single shadow map in pixels. */ public int getShadowMapSize() { return (int) shadowMapSize; } /** - * Sets the shadow edges thickness. default is 10, setting it to lower values - * can help to reduce the jagged effect of the shadow edges + * Sets the shadow edges thickness. This parameter influences the + * smoothness of shadow edges, particularly with PCF (Percentage-Closer Filtering). + * Setting lower values can help reduce jagged artifacts. * - * @param edgesThickness + * @param edgesThickness The desired thickness in tenths of a pixel (e.g., 10 for 1.0 pixel). + * The value is clamped between 1 and 10. Default is 10. */ public void setEdgesThickness(int edgesThickness) { this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); @@ -757,53 +849,59 @@ public void setEdgesThickness(int edgesThickness) { /** * isFlushQueues does nothing now and is kept only for backward compatibility + * + * @return false */ @Deprecated - public boolean isFlushQueues() { return false; } - - /** - * setFlushQueues does nothing now and is kept only for backward compatibility - */ - @Deprecated - public void setFlushQueues(boolean flushQueues) {} + public boolean isFlushQueues() { + return false; + } /** - * returns the pre shadows pass render state. - * use it to adjust the RenderState parameters of the pre shadow pass. - * Note that this will be overridden if the preShadow technique in the material has a ForcedRenderState - * @return the pre shadow render state. + * Returns the {@link RenderState} that is forced during the pre-shadow pass. + * You can use this to adjust the rendering parameters for geometries that cast shadows. + * Note that this will be overridden if the "PreShadow" technique in the material definition + * has its own `ForcedRenderState`. + * + * @return The {@link RenderState} applied to the pre-shadow pass. */ public RenderState getPreShadowForcedRenderState() { return forcedRenderState; } /** - * Set to true if you want back faces shadows on geometries. - * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. + * Sets whether back faces of geometries should cast shadows. + * When enabled, shadows cast by the back side of an object can appear. + * Be aware that back face shadows can sometimes lead to overly dark lighting + * when blended with existing dark areas. * - * Also note that setting this parameter will override this parameter for ALL materials in the scene. - * You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)} + *

                Setting this parameter will globally override this setting for ALL materials + * in the scene for the shadow pass. Alternatively, you can control this on + * individual materials using {@link Material#setBoolean(String, boolean)} + * with the "BackfaceShadows" parameter. * - * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass. - * You can modify them by using {@link #getPreShadowForcedRenderState()} + *

                This method also automatically adjusts the {@link RenderState.FaceCullMode} + * and {@link RenderState#setPolyOffset(float, float)} of the pre-shadow pass + * to accommodate back face rendering. You can further modify these + * using {@link #getPreShadowForcedRenderState()}. * - * @param renderBackFacesShadows true or false. + * @param renderBackFacesShadows True to enable back face shadows, false to disable. */ - public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { + public void setRenderBackFacesShadows(boolean renderBackFacesShadows) { this.renderBackFacesShadows = renderBackFacesShadows; - if(renderBackFacesShadows) { + if (renderBackFacesShadows) { getPreShadowForcedRenderState().setPolyOffset(5, 3); getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back); - }else{ + } else { getPreShadowForcedRenderState().setPolyOffset(0, 0); getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front); } } /** - * if this processor renders back faces shadows + * Checks if this shadow processor is configured to render shadows from back faces. * - * @return true if this processor renders back faces shadows + * @return True if back face shadows are enabled, false otherwise. */ public boolean isRenderBackFacesShadows() { return renderBackFacesShadows; @@ -824,6 +922,7 @@ public void cloneFields(final Cloner cloner, final Object original) { init(assetManager, nbShadowMaps, (int) shadowMapSize); } + @Override public void setProfiler(AppProfiler profiler) { this.prof = profiler; } @@ -832,7 +931,9 @@ public void setProfiler(AppProfiler profiler) { * De-serialize this instance, for example when loading from a J3O file. * * @param im importer (not null) + * @throws IOException from the importer */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); assetManager = im.getAssetManager(); @@ -844,14 +945,15 @@ public void read(JmeImporter im) throws IOException { init(assetManager, nbShadowMaps, (int) shadowMapSize); edgesThickness = ic.readFloat("edgesThickness", 1.0f); postshadowMat.setFloat("PCFEdge", edgesThickness); - } /** * Serialize this instance, for example when saving to a J3O file. * * @param ex exporter (not null) + * @throws IOException from the exporter */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(nbShadowMaps, "nbShadowMaps", 1); @@ -862,3 +964,5 @@ public void write(JmeExporter ex) throws IOException { oc.write(edgesThickness, "edgesThickness", 1.0f); } } + + 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 54c1f3b4f2..3f41721b3c 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/BasicShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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,24 +61,23 @@ */ @Deprecated public class BasicShadowRenderer implements SceneProcessor { - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); private RenderManager renderManager; private ViewPort viewPort; - private FrameBuffer shadowFB; - private Texture2D shadowMap; - private Camera shadowCam; - private Material preshadowMat; - private Material postshadowMat; - private Picture dispPic = new Picture("Picture"); + private final FrameBuffer shadowFB; + private final Texture2D shadowMap; + private final Camera shadowCam; + private final Material preshadowMat; + private final Material postshadowMat; + private final Picture dispPic = new Picture("Picture"); private boolean noOccluders = false; - private Vector3f[] points = new Vector3f[8]; - private Vector3f direction = new Vector3f(); + private final Vector3f[] points = new Vector3f[8]; + private final Vector3f direction = new Vector3f(); protected Texture2D dummyTex; - private float shadowMapSize; + private final float shadowMapSize; protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); protected GeometryList shadowOccluders = new GeometryList(new OpaqueComparator()); - private AppProfiler prof; /** * Creates a BasicShadowRenderer @@ -89,10 +90,10 @@ public BasicShadowRenderer(AssetManager manager, int size) { shadowFB.setDepthTexture(shadowMap); shadowCam = new Camera(size, size); - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) + //DO NOT COMMENT THIS (It prevents the OSX incomplete read buffer crash.) dummyTex = new Texture2D(size, size, Format.RGBA8); shadowFB.setColorTexture(dummyTex); - shadowMapSize = (float)size; + shadowMapSize = size; preshadowMat = new Material(manager, "Common/MatDefs/Shadow/PreShadow.j3md"); postshadowMat = new Material(manager, "Common/MatDefs/Shadow/BasicPostShadow.j3md"); postshadowMat.setTexture("ShadowMap", shadowMap); @@ -104,6 +105,7 @@ public BasicShadowRenderer(AssetManager manager, int size) { } } + @Override public void initialize(RenderManager rm, ViewPort vp) { renderManager = rm; viewPort = vp; @@ -111,6 +113,7 @@ public void initialize(RenderManager rm, ViewPort vp) { reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); } + @Override public boolean isInitialized() { return viewPort != null; } @@ -124,8 +127,9 @@ public Vector3f getDirection() { } /** - * sets the light direction to use to computs shadows - * @param direction + * sets the light direction to use to compute shadows + * + * @param direction a direction vector (not null, unaffected) */ public void setDirection(Vector3f direction) { this.direction.set(direction).normalizeLocal(); @@ -148,6 +152,7 @@ public Camera getShadowCamera() { return shadowCam; } + @Override public void postQueue(RenderQueue rq) { for (Spatial scene : viewPort.getScenes()) { ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), ShadowMode.Receive, lightReceivers); @@ -193,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); @@ -208,6 +217,7 @@ public Picture getDisplayPicture() { return dispPic; } + @Override public void postFrame(FrameBuffer out) { if (!noOccluders) { postshadowMat.setMatrix4("LightViewProjectionMatrix", shadowCam.getViewProjectionMatrix()); @@ -217,17 +227,20 @@ public void postFrame(FrameBuffer out) { } } + @Override public void preFrame(float tpf) { } + @Override public void cleanup() { } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } + @Override public void reshape(ViewPort vp, int w, int h) { dispPic.setPosition(w / 20f, h / 20f); dispPic.setWidth(w / 5f); diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java index 77fea9db4f..4438e378c0 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,49 +40,43 @@ import java.io.IOException; /** - * * This Filter does basically the same as a DirectionalLightShadowRenderer * except it renders the post shadow pass as a fullscreen quad pass instead of a * geometry pass. It's mostly faster than PssmShadowRenderer as long as you have - * more than a about ten shadow receiving objects. The expense is the draw back + * more than about ten shadow receiving objects. The expense is the drawback * that the shadow Receive mode set on spatial is ignored. So basically all and - * only objects that render depth in the scene receive shadows. See this post - * for more details - * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * only objects that render depth in the scene receive shadows. * - * API is basically the same as the PssmShadowRenderer; + * API is basically the same as the PssmShadowRenderer. * * @author Rémy Bouquet aka Nehon */ public class DirectionalLightShadowFilter extends AbstractShadowFilter { /** - * Used for serialzation. - * Use DirectionalLightShadowFilter#DirectionalLightShadowFilter - * (AssetManager assetManager, int shadowMapSize, int nbSplits) - * instead. + * For serialization only. Do not use. + * + * @see #DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) */ public DirectionalLightShadowFilter() { super(); } /** - * Creates a DirectionalLightShadowFilter Shadow Filter More info on the - * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps - * the more quality, the less fps). + * Creates a DirectionalLightShadowFilter. + * + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...) + * @param nbSplits the number of shadow maps rendered (more shadow maps = better quality, but slower) + * + * @throws IllegalArgumentException if the provided 'nbSplits' is not within the valid range of 1 to 4. */ public DirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int nbSplits) { super(assetManager, shadowMapSize, new DirectionalLightShadowRenderer(assetManager, shadowMapSize, nbSplits)); } /** - * return the light used to cast shadows + * Returns the light used to cast shadows. * * @return the DirectionalLight */ @@ -91,7 +85,7 @@ public DirectionalLight getLight() { } /** - * Sets the light to use to cast shadows + * Sets the light to use to cast shadows. * * @param light a DirectionalLight */ @@ -100,7 +94,7 @@ public void setLight(DirectionalLight light) { } /** - * returns the lambda parameter + * Returns the lambda parameter. * * @see #setLambda(float lambda) * @return lambda @@ -110,22 +104,25 @@ public float getLambda() { } /** - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 a low value give a more linear repartition - * resulting in a constant quality in the shadow over the extends, but near - * shadows could look very jagged a high value give a more logarithmic - * repartition resulting in a high quality for near shadows, but the quality - * quickly decrease over the extend. the default value is set to 0.65f - * (theoretic optimal value). + * Adjusts the partition of the shadow extend into shadow maps. Lambda is + * usually between 0 and 1. + *

                + * A low value gives a more linear partition, resulting in consistent shadow + * quality over the extend, but near shadows could look very jagged. A high + * value gives a more logarithmic partition, resulting in high quality for near + * shadows, but quality decreases rapidly with distance. + *

                + * The default value is 0.65 (the theoretical optimum). * - * @param lambda the lambda value. + * @param lambda the lambda value */ public void setLambda(float lambda) { shadowRenderer.setLambda(lambda); } /** - * returns true if stabilization is enabled + * Returns true if stabilization is enabled. + * * @return true if stabilization is enabled */ public boolean isEnabledStabilization() { @@ -135,8 +132,9 @@ public boolean isEnabledStabilization() { /** * Enables the stabilization of the shadow's edges. (default is true) * This prevents shadow edges from flickering when the camera moves. - * However it can lead to some shadow quality loss in some particular scenes. - * @param stabilize + * However, it can lead to some loss of shadow quality in particular scenes. + * + * @param stabilize true to stabilize, false to disable stabilization */ public void setEnabledStabilization(boolean stabilize) { shadowRenderer.setEnabledStabilization(stabilize); @@ -147,7 +145,6 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(shadowRenderer, "shadowRenderer", null); - } @Override @@ -156,4 +153,5 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); shadowRenderer = (DirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null); } + } diff --git a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java index 6e5da5dd3d..b4d5faf24f 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/DirectionalLightShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,6 @@ import com.jme3.renderer.Camera; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.clone.Cloner; @@ -56,9 +55,9 @@ * a shadow map for each one.
                splits are distributed so that the closer they * are from the camera, the smaller they are to maximize the resolution used of * the shadow map.
                This results in a better quality shadow than standard - * shadow mapping.
                for more informations on this read this for more information on this read https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html
                - *

                + * * @author Rémy Bouquet aka Nehon */ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { @@ -69,7 +68,8 @@ public class DirectionalLightShadowRenderer extends AbstractShadowRenderer { protected float[] splitsArray; protected DirectionalLight light; protected Vector3f[] points = new Vector3f[8]; - //Holding the info for fading shadows in the far distance + protected final Vector3f tempVec = new Vector3f(); + private boolean stabilize = true; /** @@ -82,14 +82,14 @@ protected DirectionalLightShadowRenderer() { } /** - * Create a DirectionalLightShadowRenderer More info on the technique at https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps - * the more quality, the less fps). + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadowmaps (512, 1024, 2048, + * etcetera) + * @param nbSplits the number of shadow maps rendered (More shadow maps yield + * better quality, fewer fps.) */ public DirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbSplits) { super(assetManager, shadowMapSize, nbSplits); @@ -97,10 +97,12 @@ public DirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSi } private void init(int nbSplits, int shadowMapSize) { - nbShadowMaps = Math.max(Math.min(nbSplits, 4), 1); - if (nbShadowMaps != nbSplits) { - throw new IllegalArgumentException("Number of splits must be between 1 and 4. Given value : " + nbSplits); + // Ensure the number of shadow maps is within the valid range [1, 4] + if (nbSplits < 1 || nbSplits > 4) { + throw new IllegalArgumentException("Number of splits must be between 1 and 4. Given value: " + nbSplits); } + + nbShadowMaps = nbSplits; splits = new ColorRGBA(); splitsArray = new float[nbSplits + 1]; shadowCam = new Camera(shadowMapSize, shadowMapSize); @@ -151,7 +153,7 @@ protected void updateShadowCams(Camera viewCam) { ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); shadowCam.setFrustumFar(zFar); - shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp()); + shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp(tempVec)); shadowCam.update(); shadowCam.updateViewProjection(); @@ -212,9 +214,9 @@ protected Camera getShadowCam(int shadowMapIndex) { @Override protected void doDisplayFrustumDebug(int shadowMapIndex) { - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + getSceneForDebug().attachChild(createFrustum(points, shadowMapIndex)); ShadowUtil.updateFrustumPoints2(shadowCam, points); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + getSceneForDebug().attachChild(createFrustum(points, shadowMapIndex)); } @Override @@ -242,12 +244,17 @@ public float getLambda() { return lambda; } - /* - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 - * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged - * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. - * the default value is set to 0.65f (theoretic optimal value). + /** + * Adjusts the partition of the shadow extend into shadow maps. + * Lambda is usually between 0 and 1. + * A low value gives a more linear partition, + * resulting in consistent shadow quality over the extend, + * but near shadows could look very jagged. + * A high value gives a more logarithmic partition, + * resulting in high quality for near shadows, + * but quality decreases rapidly with distance. + * The default value is 0.65 (the theoretical optimum). + * * @param lambda the lambda value. */ public void setLambda(float lambda) { @@ -264,8 +271,9 @@ public boolean isEnabledStabilization() { /** * Enables the stabilization of the shadow's edges. (default is true) * This prevents shadow edges from flickering when the camera moves. - * However it can lead to some shadow quality loss in some particular scenes. - * @param stabilize + * However, it can lead to some loss of shadow quality in particular scenes. + * + * @param stabilize true to stabilize, false to disable stabilization */ public void setEnabledStabilization(boolean stabilize) { this.stabilize = stabilize; @@ -303,7 +311,8 @@ public void write(JmeExporter ex) throws IOException { /** * Directional light are always in the view frustum - * @param viewCam + * + * @param viewCam a Camera to define the view frustum * @return true */ @Override diff --git a/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java b/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java index a406032507..4dd7339ce4 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java +++ b/jme3-core/src/main/java/com/jme3/shadow/EdgeFilteringMode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ public enum EdgeFilteringMode { * is used. * http://devmag.org.za/2009/05/03/poisson-disk-sampling/ * The principle is to eliminate the regular blurring pattern that can be - * seen with pcf4x4 by randomizing the samble position with a poisson disc. + * seen with pcf4x4 by randomizing the sample position with a poisson disc. * Shadows will look smoother than 4x4 PCF but with slightly better or * similar performance. */ diff --git a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java index a2adb2cf4e..ef5e3dc19c 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,56 +40,51 @@ import java.io.IOException; /** - * * This Filter does basically the same as a PointLightShadowRenderer except it * renders the post shadow pass as a fullscreen quad pass instead of a geometry * pass. It's mostly faster than PointLightShadowRenderer as long as you have - * more than a about ten shadow receiving objects. The expense is the draw back + * more than about ten shadow receiving objects. The expense is the drawback * that the shadow Receive mode set on spatial is ignored. So basically all and - * only objects that render depth in the scene receive shadows. See this post - * for more details - * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * only objects that render depth in the scene receive shadows. * - * API is basically the same as the PssmShadowRenderer; + * API is basically the same as the PssmShadowRenderer. * * @author Rémy Bouquet aka Nehon */ public class PointLightShadowFilter extends AbstractShadowFilter { /** - * Used for serialization. - * Use PointLightShadowFilter#PointLightShadowFilter(AssetManager - * assetManager, int shadowMapSize) - * instead. + * For serialization only. Do not use. + * + * @see #PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) */ protected PointLightShadowFilter() { super(); } /** - * Creates a PointLightShadowFilter + * Creates a PointLightShadowFilter. * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...) */ public PointLightShadowFilter(AssetManager assetManager, int shadowMapSize) { super(assetManager, shadowMapSize, new PointLightShadowRenderer(assetManager, shadowMapSize)); } /** - * gets the point light used to cast shadows with this processor + * Returns the light used to cast shadows. * - * @return the point light + * @return the PointLight */ public PointLight getLight() { return shadowRenderer.getLight(); } /** - * sets the light to use for casting shadows with this processor + * Sets the light to use to cast shadows. * - * @param light the point light + * @param light the PointLight */ public void setLight(PointLight light) { shadowRenderer.setLight(light); @@ -100,7 +95,6 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(shadowRenderer, "shadowRenderer", null); - } @Override @@ -109,4 +103,5 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); shadowRenderer = (PointLightShadowRenderer) ic.readSavable("shadowRenderer", null); } + } diff --git a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java index 5b66c4bf2a..9bbb33a0dc 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PointLightShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,6 @@ import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; @@ -51,33 +50,39 @@ import java.io.IOException; /** - * PointLightShadowRenderer renders shadows for a point light + * Renders shadows for a {@link PointLight}. This renderer uses six cameras, + * one for each face of a cube map, to capture shadows from the point light's + * perspective. * * @author Rémy Bouquet aka Nehon */ public class PointLightShadowRenderer extends AbstractShadowRenderer { + /** + * The fixed number of cameras used for rendering point light shadows (6 for a cube map). + */ public static final int CAM_NUMBER = 6; + protected PointLight light; protected Camera[] shadowCams; - private Geometry[] frustums = null; + protected Geometry[] frustums = null; + protected final Vector3f X_NEG = Vector3f.UNIT_X.mult(-1f); + protected final Vector3f Y_NEG = Vector3f.UNIT_Y.mult(-1f); + protected final Vector3f Z_NEG = Vector3f.UNIT_Z.mult(-1f); /** - * Used for serialization. - * Use PointLightShadowRenderer#PointLightShadowRenderer(AssetManager - * assetManager, int shadowMapSize) - * instead. + * For serialization only. Do not use. */ protected PointLightShadowRenderer() { super(); } /** - * Creates a PointLightShadowRenderer + * Creates a new {@code PointLightShadowRenderer} instance. * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) + * @param assetManager The application's asset manager. + * @param shadowMapSize The size of the rendered shadow maps (e.g., 512, 1024, 2048). + * Higher values produce better quality shadows but may impact performance. */ public PointLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { super(assetManager, shadowMapSize, CAM_NUMBER); @@ -86,7 +91,7 @@ public PointLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { private void init(int shadowMapSize) { shadowCams = new Camera[CAM_NUMBER]; - for (int i = 0; i < CAM_NUMBER; i++) { + for (int i = 0; i < shadowCams.length; i++) { shadowCams[i] = new Camera(shadowMapSize, shadowMapSize); } } @@ -95,9 +100,9 @@ private void init(int shadowMapSize) { protected void initFrustumCam() { Camera viewCam = viewPort.getCamera(); frustumCam = viewCam.clone(); - frustumCam.setFrustum(viewCam.getFrustumNear(), zFarOverride, viewCam.getFrustumLeft(), viewCam.getFrustumRight(), viewCam.getFrustumTop(), viewCam.getFrustumBottom()); + frustumCam.setFrustum(viewCam.getFrustumNear(), zFarOverride, + viewCam.getFrustumLeft(), viewCam.getFrustumRight(), viewCam.getFrustumTop(), viewCam.getFrustumBottom()); } - @Override protected void updateShadowCams(Camera viewCam) { @@ -107,31 +112,21 @@ protected void updateShadowCams(Camera viewCam) { return; } - //bottom - shadowCams[0].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y.mult(-1f)); - - //top - shadowCams[1].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Z, Vector3f.UNIT_Y); - - //forward - shadowCams[2].setAxes(Vector3f.UNIT_X.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_Z.mult(-1f)); - - //backward - shadowCams[3].setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); - - //left - shadowCams[4].setAxes(Vector3f.UNIT_Z, Vector3f.UNIT_Y, Vector3f.UNIT_X.mult(-1f)); - - //right - shadowCams[5].setAxes(Vector3f.UNIT_Z.mult(-1f), Vector3f.UNIT_Y, Vector3f.UNIT_X); - - for (int i = 0; i < CAM_NUMBER; i++) { - shadowCams[i].setFrustumPerspective(90f, 1f, 0.1f, light.getRadius()); - shadowCams[i].setLocation(light.getPosition()); - shadowCams[i].update(); - shadowCams[i].updateViewProjection(); + // Configure axes for each of the six cube map cameras (positive/negative X, Y, Z) + shadowCams[0].setAxes(X_NEG, Z_NEG, Y_NEG); // -Y (bottom) + shadowCams[1].setAxes(X_NEG, Vector3f.UNIT_Z, Vector3f.UNIT_Y); // +Y (top) + shadowCams[2].setAxes(X_NEG, Vector3f.UNIT_Y, Z_NEG); // +Z (forward) + shadowCams[3].setAxes(Vector3f.UNIT_X, Vector3f.UNIT_Y, Vector3f.UNIT_Z); // -Z (backward) + shadowCams[4].setAxes(Vector3f.UNIT_Z, Vector3f.UNIT_Y, X_NEG); // -X (left) + shadowCams[5].setAxes(Z_NEG, Vector3f.UNIT_Y, Vector3f.UNIT_X); // +X (right) + + // Set perspective and location for all shadow cameras + for (Camera shadowCam : shadowCams) { + shadowCam.setFrustumPerspective(90f, 1f, 0.1f, light.getRadius()); + shadowCam.setLocation(light.getPosition()); + shadowCam.update(); + shadowCam.updateViewProjection(); } - } @Override @@ -160,7 +155,7 @@ protected void doDisplayFrustumDebug(int shadowMapIndex) { if (frustums == null) { frustums = new Geometry[CAM_NUMBER]; Vector3f[] points = new Vector3f[8]; - for (int i = 0; i < 8; i++) { + for (int i = 0; i < points.length; i++) { points[i] = new Vector3f(); } for (int i = 0; i < CAM_NUMBER; i++) { @@ -168,8 +163,9 @@ protected void doDisplayFrustumDebug(int shadowMapIndex) { frustums[i] = createFrustum(points, i); } } - if (frustums[shadowMapIndex].getParent() == null) { - ((Node) viewPort.getScenes().get(0)).attachChild(frustums[shadowMapIndex]); + Geometry geo = frustums[shadowMapIndex]; + if (geo.getParent() == null) { + getSceneForDebug().attachChild(geo); } } @@ -226,7 +222,7 @@ public void write(JmeExporter ex) throws IOException { /** * - * @param viewCam + * @param viewCam a Camera to define the view frustum * @return true if intersects */ @Override @@ -237,13 +233,13 @@ protected boolean checkCulling(Camera viewCam) { } Camera cam = viewCam; - if(frustumCam != null){ - cam = frustumCam; + if (frustumCam != null) { + cam = frustumCam; cam.setLocation(viewCam.getLocation()); cam.setRotation(viewCam.getRotation()); } TempVars vars = TempVars.get(); - boolean intersects = light.intersectsFrustum(cam,vars); + boolean intersects = light.intersectsFrustum(cam, vars); vars.release(); return intersects; } diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java index 74937eaeef..5c4e6186e4 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,8 +53,8 @@ * * This Filter does basically the same as a PssmShadowRenderer except it renders * the post shadow pass as a fullscreen quad pass instead of a geometry pass. - * It's mostly faster than PssmShadowRenderer as long as you have more than a about ten shadow receiving objects. - * The expense is the draw back that the shadow Receive mode set on spatial is ignored. + * It's mostly faster than PssmShadowRenderer as long as you have more than about ten shadow receiving objects. + * The expense is the drawback that the shadow Receive mode set on spatial is ignored. * So basically all and only objects that render depth in the scene receive shadows. * See this post for more details http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 * @@ -80,11 +80,13 @@ protected PssmShadowFilter() { } /** - * Creates a PSSM Shadow Filter + * Creates a PSSM shadow filter. * More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * @param manager the application asset manager - * @param size the size of the rendered shadowmaps (512,1024,2048, etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). + * + * @param manager the application's asset manager + * @param size the size of the rendered shadowmaps (512, 1024, 2048, etcetera) + * @param nbSplits the number of shadow maps rendered (More shadow maps mean + * better quality, fewer frames per second.) */ public PssmShadowFilter(AssetManager manager, int size, int nbSplits) { super("Post Shadow"); @@ -143,7 +145,8 @@ public Vector3f getDirection() { /** * Sets the light direction to use to compute shadows - * @param direction + * + * @param direction a direction vector (not null, unaffected) */ public void setDirection(Vector3f direction) { pssmRenderer.setDirection(direction); @@ -159,11 +162,16 @@ public float getLambda() { } /** - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 - * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged - * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. - * the default value is set to 0.65f (theoretic optimal value). + * Adjusts the partition of the shadow extend into shadow maps. + * Lambda is usually between 0 and 1. + * A low value gives a more linear partition, + * resulting in consistent shadow quality over the extend, + * but near shadows could look very jagged. + * A high value gives a more logarithmic partition, + * resulting in high quality for near shadows, + * but quality decreases rapidly with distance. + * The default value is 0.65 (the theoretical optimum). + * * @param lambda the lambda value. */ public void setLambda(float lambda) { @@ -218,8 +226,10 @@ public int getEdgesThickness() { } /** - * Sets the shadow edges thickness. default is 1, setting it to lower values can help to reduce the jagged effect of the shadow edges - * @param edgesThickness + * Sets the shadow edges thickness. Default is 1. + * Setting it to lower values can help to reduce the jagged effect of the shadow edges. + * + * @param edgesThickness the desired thickness (in tenths of a pixel, default=10) */ public void setEdgesThickness(int edgesThickness) { pssmRenderer.setEdgesThickness(edgesThickness); @@ -236,7 +246,8 @@ public boolean isFlushQueues() { /** * Set this to false if you want to use several PssmRenderers to have multiple shadows cast by multiple light sources. * Make sure the last PssmRenderer in the stack DOES flush the queues, but not the others - * @param flushQueues + * + * @param flushQueues true to flush the queues, false to avoid flushing them */ public void setFlushQueues(boolean flushQueues) { pssmRenderer.setFlushQueues(flushQueues); @@ -244,7 +255,8 @@ public void setFlushQueues(boolean flushQueues) { /** * sets the shadow compare mode see {@link CompareMode} for more info - * @param compareMode + * + * @param compareMode the desired mode (not null) */ final public void setCompareMode(CompareMode compareMode) { pssmRenderer.setCompareMode(compareMode); @@ -252,7 +264,8 @@ final public void setCompareMode(CompareMode compareMode) { /** * Sets the filtering mode for shadow edges see {@link FilterMode} for more info - * @param filterMode + * + * @param filterMode the desired mode (not null) */ final public void setFilterMode(FilterMode filterMode) { pssmRenderer.setFilterMode(filterMode); 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 0b36a56471..ef5bb782f6 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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; @@ -67,17 +69,15 @@ * one.
                splits are distributed so that the closer they are from the camera, * the smaller they are to maximize the resolution used of the shadow map.
                * This results in a better quality shadow than standard shadow mapping.
                for - * more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
                - *

                + * * @author Rémy Bouquet aka Nehon * @deprecated use {@link DirectionalLightShadowRenderer} */ @Deprecated public class PssmShadowRenderer implements SceneProcessor { - - private AppProfiler prof; - + private static final LightFilter NULL_LIGHT_FILTER = new NullLightFilter(); /** * FilterMode specifies how shadows are filtered * @deprecated use {@link EdgeFilteringMode} @@ -171,7 +171,7 @@ public enum CompareMode { protected boolean applyPCFEdge = true; protected boolean applyShadowIntensity = true; //a list of material of the post shadow queue geometries. - protected List matCache = new ArrayList(); + protected List matCache = new ArrayList<>(); //Holding the info for fading shadows in the far distance protected Vector2f fadeInfo; protected float fadeLength; @@ -236,7 +236,7 @@ protected PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Mater postshadowMat.setTexture("ShadowMap" + i, shadowMaps[i]); - //quads for debuging purpose + //quads for debugging purposes dispPic[i] = new Picture("Picture" + i); dispPic[i].setTexture(manager, shadowMaps[i], false); } @@ -258,11 +258,11 @@ protected PssmShadowRenderer(AssetManager manager, int size, int nbSplits, Mater * Sets the filtering mode for shadow edges see {@link FilterMode} for more * info * - * @param filterMode + * @param filterMode the desired mode (not null) */ final public void setFilterMode(FilterMode filterMode) { if (filterMode == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("filterMode cannot be null"); } if (this.filterMode == filterMode) { @@ -289,11 +289,11 @@ final public void setFilterMode(FilterMode filterMode) { /** * sets the shadow compare mode see {@link CompareMode} for more info * - * @param compareMode + * @param compareMode the desired mode (not null) */ final public void setCompareMode(CompareMode compareMode) { if (compareMode == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("compareMode cannot be null"); } if (this.compareMode == compareMode) { @@ -352,12 +352,14 @@ private Geometry createFrustum(Vector3f[] pts, int i) { return frustumMdl; } + @Override public void initialize(RenderManager rm, ViewPort vp) { renderManager = rm; viewPort = vp; postTechniqueName = "PostShadow"; } + @Override public boolean isInitialized() { return viewPort != null; } @@ -374,13 +376,14 @@ public Vector3f getDirection() { /** * Sets the light direction to use to compute shadows * - * @param direction + * @param direction a direction vector (not null, unaffected) */ public void setDirection(Vector3f direction) { this.direction.set(direction).normalizeLocal(); } @SuppressWarnings("fallthrough") + @Override public void postQueue(RenderQueue rq) { for (Spatial scene : viewPort.getScenes()) { ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), ShadowMode.Receive, lightReceivers); @@ -435,22 +438,25 @@ public void postQueue(RenderQueue rq) { renderManager.setCamera(shadowCam, false); if (debugfrustums) { -// frustrumFromBound(b.casterBB,ColorRGBA.Blue ); -// frustrumFromBound(b.receiverBB,ColorRGBA.Green ); -// frustrumFromBound(b.splitBB,ColorRGBA.Yellow ); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i)); + getSceneForDebug().attachChild(createFrustum(points, i)); ShadowUtil.updateFrustumPoints2(shadowCam, points); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, i)); - + getSceneForDebug().attachChild(createFrustum(points, i)); } 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); + } + + if (debugfrustums) { + debugfrustums = false; + getSceneForDebug().updateGeometricState(); } - debugfrustums = false; //restore setting for future rendering r.setFrameBuffer(viewPort.getOutputFrameBuffer()); @@ -459,6 +465,11 @@ public void postQueue(RenderQueue rq) { renderManager.setCamera(viewCam, false); } + + protected Node getSceneForDebug() { + return (Node) viewPort.getScenes().get(0); + } + boolean debugfrustums = false; public void displayFrustum() { @@ -488,6 +499,7 @@ public void displayDebug() { debug = true; } + @Override public void postFrame(FrameBuffer out) { if (debug) { @@ -522,7 +534,7 @@ private void setMatParams() { GeometryList l = lightReceivers; - //iteration throught all the geometries of the list to gather the materials + //iterate through all the geometries in the list to gather the materials matCache.clear(); for (int i = 0; i < l.size(); i++) { @@ -581,12 +593,15 @@ protected void setPostShadowParams() { } } + @Override public void preFrame(float tpf) { } + @Override public void cleanup() { } + @Override public void reshape(ViewPort vp, int w, int h) { } @@ -599,12 +614,17 @@ public float getLambda() { return lambda; } - /* - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 - * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged - * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. - * the default value is set to 0.65f (theoretic optimal value). + /** + * Adjusts the partition of the shadow extend into shadow maps. + * Lambda is usually between 0 and 1. + * A low value gives a more linear repartition, + * resulting in consistent shadow quality over the extend, + * but near shadows could look very jagged. + * A high value gives a more logarithmic repartition, + * resulting in high quality for near shadows, + * but quality decreases rapidly with distance. + * The default value is 0.65 (the theoretical optimum). + * * @param lambda the lambda value. */ public void setLambda(float lambda) { @@ -673,7 +693,7 @@ public int getEdgesThickness() { * Sets the shadow edges thickness. default is 1, setting it to lower values * can help to reduce the jagged effect of the shadow edges * - * @param edgesThickness + * @param edgesThickness the desired thickness (in tenths of a pixel, default=10) */ public void setEdgesThickness(int edgesThickness) { this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); @@ -696,7 +716,7 @@ public boolean isFlushQueues() { * multiple shadows cast by multiple light sources. Make sure the last * PssmRenderer in the stack DOES flush the queues, but not the others * - * @param flushQueues + * @param flushQueues true to flush the queues, false to avoid flushing them */ public void setFlushQueues(boolean flushQueues) { this.flushQueues = flushQueues; @@ -727,7 +747,7 @@ public void setShadowZFadeLength(float length) { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } /** diff --git a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java index 3764a4b09e..cb076556bd 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java +++ b/jme3-core/src/main/java/com/jme3/shadow/PssmShadowUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,8 +51,20 @@ */ public final class PssmShadowUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private PssmShadowUtil() { + } + /** * Updates the frustum splits stores in splits using PSSM. + * + * @param splits the array of splits (modified) + * @param near the distance to the camera's near plane + * @param far the distance to the camera's far plane + * @param lambda the mixing parameter (0→purely linear, + * 1→purely logarithmic) */ public static void updateFrustumSplits(float[] splits, float near, float far, float lambda) { for (int i = 0; i < splits.length; i++) { @@ -62,14 +74,19 @@ public static void updateFrustumSplits(float[] splits, float near, float far, fl splits[i] = log * lambda + uniform * (1.0f - lambda); } - // This is used to improve the correctness of the calculations. Our main near- and farplane + // This is used to improve the correctness of the calculations. The near and far planes // of the camera always stay the same, no matter what happens. splits[0] = near; splits[splits.length - 1] = far; } /** - * Compute the Zfar in the model vieuw to adjust the Zfar distance for the splits calculation + * Compute the Zfar in the model view to adjust the Zfar distance for the splits calculation + * + * @param occ a list of occluders + * @param recv a list of receivers + * @param cam the Camera (not null, unaffected) + * @return the Z-far distance */ public static float computeZFar(GeometryList occ, GeometryList recv, Camera cam) { Matrix4f mat = cam.getViewMatrix(); diff --git a/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowFilter.java new file mode 100644 index 0000000000..fa7105e6da --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowFilter.java @@ -0,0 +1,197 @@ +/* + * Copyright (c) 2009-2026 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.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.texture.FrameBuffer; +import com.jme3.texture.Texture; + +import java.io.IOException; + +/** + * SDSM (Sample Distribution Shadow Mapping) filter for directional lights. + *

                + * This filter uses GPU compute shaders to analyze the depth buffer and compute + * optimal cascade split positions for rendering shadow maps. + *

                + * Key benefits over {@link DirectionalLightShadowFilter}: + *

                  + *
                • Better shadow map utilization through sample-based fitting
                • + *
                • Dynamic cascade adaptation to scene geometry
                • + *
                • Reduced shadow pop-in artifacts
                • + *
                + *

                + * Requires OpenGL 4.3+ for compute shader support. Only works for filter-based shadow mapping. + */ +public class SdsmDirectionalLightShadowFilter extends AbstractShadowFilter { + /** + * For serialization only. Do not use. + * + * @see #SdsmDirectionalLightShadowFilter(AssetManager, int, int) + */ + public SdsmDirectionalLightShadowFilter() { + super(); + } + + /** + * Creates an SDSM directional light shadow filter. + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc.) + * @param splitCount the number of shadow map splits (1-4) + * @throws IllegalArgumentException if splitCount is not between 1 and 4 + */ + public SdsmDirectionalLightShadowFilter(AssetManager assetManager, int shadowMapSize, int splitCount) { + super(assetManager, shadowMapSize, new SdsmDirectionalLightShadowRenderer(assetManager, shadowMapSize, splitCount)); + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + shadowRenderer.needsfallBackMaterial = true; + material = new Material(manager, "Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.j3md"); + shadowRenderer.setPostShadowMaterial(material); + shadowRenderer.initialize(renderManager, vp); + this.viewPort = vp; + } + + /** + * Returns the light used to cast shadows. + * + * @return the DirectionalLight + */ + public DirectionalLight getLight() { + return shadowRenderer.getLight(); + } + + /** + * Sets the light to use for casting shadows. + * + * @param light a DirectionalLight + */ + public void setLight(DirectionalLight light) { + shadowRenderer.setLight(light); + } + + /** + * Gets the fit expansion factor. + * + * @return the expansion factor + * @see SdsmDirectionalLightShadowRenderer#getFitExpansionFactor() + */ + public float getFitExpansionFactor() { + return shadowRenderer.getFitExpansionFactor(); + } + + /** + * Sets the expansion factor for fitted shadow frustums. + * + * @param factor the expansion factor (default 1.0) + * @see SdsmDirectionalLightShadowRenderer#setFitExpansionFactor(float) + */ + public void setFitExpansionFactor(float factor) { + shadowRenderer.setFitExpansionFactor(factor); + } + + /** + * Gets the frame delay tolerance. + * + * @return the tolerance value + * @see SdsmDirectionalLightShadowRenderer#getFitFrameDelayTolerance() + */ + public float getFitFrameDelayTolerance() { + return shadowRenderer.getFitFrameDelayTolerance(); + } + + /** + * Sets the frame delay tolerance. + * + * @param tolerance the tolerance (default 0.05) + * @see SdsmDirectionalLightShadowRenderer#setFitFrameDelayTolerance(float) + */ + public void setFitFrameDelayTolerance(float tolerance) { + shadowRenderer.setFitFrameDelayTolerance(tolerance); + } + + @Override + public void setDepthTexture(Texture depthTexture) { + super.setDepthTexture(depthTexture); + shadowRenderer.setDepthTexture(depthTexture); + } + + @Override + protected void postQueue(RenderQueue queue) { + // We need the depth texture from the previous pass, so we defer + // shadow processing to postFrame + } + + @Override + protected void postFrame(RenderManager renderManager, ViewPort viewPort, + FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { + super.postQueue(null); + super.postFrame(renderManager, viewPort, prevFilterBuffer, sceneBuffer); + } + + @Override + protected void cleanUpFilter(com.jme3.renderer.Renderer r) { + super.cleanUpFilter(r); + if (shadowRenderer != null) { + shadowRenderer.cleanup(); + } + } + + public void displayAllFrustums(){ + shadowRenderer.displayAllDebugFrustums(); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(shadowRenderer, "shadowRenderer", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + shadowRenderer = (SdsmDirectionalLightShadowRenderer) ic.readSavable("shadowRenderer", null); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowRenderer.java new file mode 100644 index 0000000000..44e336684c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/SdsmDirectionalLightShadowRenderer.java @@ -0,0 +1,485 @@ +/* + * Copyright (c) 2009-2026 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.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.opengl.GL4; +import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.renderer.queue.GeometryList; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; +import com.jme3.util.clone.Cloner; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +/** + * See {@link SdsmDirectionalLightShadowFilter} + */ +public class SdsmDirectionalLightShadowRenderer extends AbstractShadowRenderer { + + private DirectionalLight light; + private final Matrix4f lightViewMatrix = new Matrix4f(); + private Camera[] shadowCameras; + private boolean[] shadowCameraEnabled; + + private SdsmFitter sdsmFitter; + private SdsmFitter.SplitFitResult lastFit; + private Texture depthTexture; + + private boolean glInitialized = false; + + /** + * Expansion factor for fitted shadow frustums. + * Larger values reduce shadow pop-in but may waste shadow map resolution. + */ + private float fitExpansionFactor = 1.0f; + + /** + * Tolerance for reusing old fit results when camera hasn't moved much. + * Reduce to eliminate screen-tearing artifacts when rapidly moving or rotating camera, at the cost of lower framerate caused by waiting for SDSM to complete. + */ + private float fitFrameDelayTolerance = 0.05f; + + /** + * Used for serialization. Do not use. + * + * @see #SdsmDirectionalLightShadowRenderer(AssetManager, int, int) + */ + protected SdsmDirectionalLightShadowRenderer() { + super(); + } + + /** + * Creates an SDSM directional light shadow renderer. + * You likely should not use this directly, as it requires an SdsmDirectionalLightShadowFilter. + */ + public SdsmDirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { + super(assetManager, shadowMapSize, nbShadowMaps); + init(nbShadowMaps, shadowMapSize); + } + + private void init(int splitCount, int shadowMapSize) { + if (splitCount < 1 || splitCount > 4) { + throw new IllegalArgumentException("Number of splits must be between 1 and 4. Given value: " + splitCount); + } + this.nbShadowMaps = splitCount; + + shadowCameras = new Camera[this.nbShadowMaps]; + shadowCameraEnabled = new boolean[this.nbShadowMaps]; + + for (int i = 0; i < this.nbShadowMaps; i++) { + shadowCameras[i] = new Camera(shadowMapSize, shadowMapSize); + shadowCameras[i].setParallelProjection(true); + shadowCameraEnabled[i] = false; + } + + needsfallBackMaterial = true; + } + + /** + * Initializes the GL interfaces for compute shader operations. + * Called on first frame when RenderManager is available. + */ + private void initGL() { + if (glInitialized) { + return; + } + + Renderer renderer = renderManager.getRenderer(); + if (!(renderer instanceof GLRenderer)) { + throw new UnsupportedOperationException("SdsmDirectionalLightShadowRenderer requires GLRenderer"); + } + + GLRenderer glRenderer = (GLRenderer) renderer; + + + GL4 gl4 = glRenderer.getGl4(); + + if (gl4 == null) { + throw new UnsupportedOperationException("SDSM shadows require OpenGL 4.3 or higher"); + } + + sdsmFitter = new SdsmFitter(gl4, renderer, assetManager); + glInitialized = true; + + } + + /** + * Returns the light used to cast shadows. + */ + public DirectionalLight getLight() { + return light; + } + + /** + * Sets the light to use for casting shadows. + */ + public void setLight(DirectionalLight light) { + this.light = light; + if (light != null) { + generateLightViewMatrix(); + } + } + + public void setDepthTexture(Texture depthTexture) { + this.depthTexture = depthTexture; + } + + /** + * Gets the fit expansion factor. + * + * @return the expansion factor + */ + public float getFitExpansionFactor() { + return fitExpansionFactor; + } + + /** + * Sets the expansion factor for fitted shadow frustums. + *

                + * A value of 1.0 uses the exact computed bounds. + * Larger values (e.g., 1.05) add some margin to reduce artifacts + * from frame delay or precision issues. + * + * @param fitExpansionFactor the expansion factor (default 1.0) + */ + public void setFitExpansionFactor(float fitExpansionFactor) { + this.fitExpansionFactor = fitExpansionFactor; + } + + /** + * Gets the frame delay tolerance for reusing old fit results. + * + * @return the tolerance value + */ + public float getFitFrameDelayTolerance() { + return fitFrameDelayTolerance; + } + + /** + * Sets the tolerance for reusing old fit results. + *

                + * When the camera hasn't moved significantly (within this tolerance), + * old fit results can be reused to avoid GPU stalls. + * + * @param fitFrameDelayTolerance the tolerance (default 0.05) + */ + public void setFitFrameDelayTolerance(float fitFrameDelayTolerance) { + this.fitFrameDelayTolerance = fitFrameDelayTolerance; + } + + private void generateLightViewMatrix() { + Vector3f lightDir = light.getDirection(); + Vector3f up = Math.abs(lightDir.y) < 0.9f ? Vector3f.UNIT_Y : Vector3f.UNIT_X; + Vector3f right = lightDir.cross(up).normalizeLocal(); + Vector3f actualUp = right.cross(lightDir).normalizeLocal(); + + lightViewMatrix.set( + right.x, right.y, right.z, 0f, + actualUp.x, actualUp.y, actualUp.z, 0f, + lightDir.x, lightDir.y, lightDir.z, 0f, + 0f, 0f, 0f, 1f + ); + } + + @Override + protected void initFrustumCam() {} + + @Override + protected void updateShadowCams(Camera viewCam) { + if (!glInitialized) { + initGL(); + } + + if (!tryFitShadowCams(viewCam)) { + skipPostPass = true; + } + } + + private boolean tryFitShadowCams(Camera viewCam) { + if (depthTexture == null || light == null) { + return false; + } + + Vector3f lightDir = light.getDirection(); + if(lightDir.x != lightViewMatrix.m30 || lightDir.y != lightViewMatrix.m31 || lightDir.z != lightViewMatrix.m32) { + generateLightViewMatrix(); + } + + // Compute camera-to-light transformation matrix + Matrix4f invViewProj = viewCam.getViewProjectionMatrix().invert(); + Matrix4f cameraToLight = lightViewMatrix.mult(invViewProj, invViewProj); + + // Submit fit request to GPU + sdsmFitter.fit( + depthTexture, + nbShadowMaps, + cameraToLight, + viewCam.getFrustumNear(), + viewCam.getFrustumFar() + ); + + // Try to get result without blocking + SdsmFitter.SplitFitResult fitCallResult = sdsmFitter.getResult(false); + + // If no result yet, try to reuse old fit or wait + if (fitCallResult == null) { + fitCallResult = lastFit; + while (fitCallResult == null || !isOldFitAcceptable(fitCallResult, cameraToLight)) { + fitCallResult = sdsmFitter.getResult(true); + } + } + + lastFit = fitCallResult; + SdsmFitter.SplitFit fitResult = fitCallResult.result; + + if (fitResult != null) { + for (int splitIndex = 0; splitIndex < nbShadowMaps; splitIndex++) { + shadowCameraEnabled[splitIndex] = false; + + SdsmFitter.SplitBounds bounds = fitResult.splits.get(splitIndex); + if (bounds == null) { + continue; + } + + Camera cam = shadowCameras[splitIndex]; + + float centerX = (bounds.minX + bounds.maxX) / 2f; + float centerY = (bounds.minY + bounds.maxY) / 2f; + + // Position in light space + Vector3f lightSpacePos = new Vector3f(centerX, centerY, bounds.minZ); + + // Transform back to world space + Matrix4f invLightView = lightViewMatrix.invert(); + Vector3f worldPos = invLightView.mult(lightSpacePos); + + cam.setLocation(worldPos); + // Use the same up vector that was used to compute the light view matrix + // Row 1 of lightViewMatrix contains the actualUp vector (Y axis in light space) + Vector3f actualUp = new Vector3f(lightViewMatrix.m10, lightViewMatrix.m11, lightViewMatrix.m12); + cam.lookAtDirection(light.getDirection(), actualUp); + + float width = (bounds.maxX - bounds.minX) * fitExpansionFactor; + float height = (bounds.maxY - bounds.minY) * fitExpansionFactor; + float far = (bounds.maxZ - bounds.minZ) * fitExpansionFactor; + + if (width <= 0f || height <= 0f || far <= 0f) { + continue; //Skip updating this particular shadowcam, it likely doesn't have any samples or is degenerate. + } + + cam.setFrustum( + -100f, //This will usually help out with clipping problems, where the shadow camera is positioned such that it would clip out a vertex that might cast a shadow. + far, + -width / 2f, + width / 2f, + height / 2f, + -height / 2f + ); + + shadowCameraEnabled[splitIndex] = true; + if(Float.isNaN(cam.getViewProjectionMatrix().m00)){ + throw new IllegalStateException("Invalid shadow projection detected"); + } + } + return true; + } + + return false; + } + + private boolean isOldFitAcceptable(SdsmFitter.SplitFitResult fit, Matrix4f newCameraToLight) { + return fit.parameters.cameraToLight.isSimilar(newCameraToLight, fitFrameDelayTolerance); + } + + @Override + protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders) { + if (shadowCameraEnabled[shadowMapIndex]) { + Camera camera = shadowCameras[shadowMapIndex]; + for (Spatial scene : viewPort.getScenes()) { + ShadowUtil.getGeometriesInCamFrustum(scene, camera, ShadowMode.Cast, shadowMapOccluders); + } + } + return shadowMapOccluders; + } + + @Override + protected void getReceivers(GeometryList lightReceivers) { throw new RuntimeException("Only filter mode is implemented for SDSM"); } + + @Override + protected Camera getShadowCam(int shadowMapIndex) { + return shadowCameras[shadowMapIndex]; + } + + @Override + protected void doDisplayFrustumDebug(int shadowMapIndex) { + if (shadowCameraEnabled[shadowMapIndex]) { + createDebugFrustum(shadowCameras[shadowMapIndex], shadowMapIndex); + } + } + + private Spatial cameraFrustumDebug = null; + private List shadowMapFrustumDebug = null; + public void displayAllDebugFrustums() { + if (cameraFrustumDebug != null) { + cameraFrustumDebug.removeFromParent(); + } + if (shadowMapFrustumDebug != null) { + for (Spatial s : shadowMapFrustumDebug) { + s.removeFromParent(); + } + } + + cameraFrustumDebug = createDebugFrustum(viewPort.getCamera(), 4); + shadowMapFrustumDebug = new ArrayList<>(); + for (int i = 0; i < nbShadowMaps; i++) { + if (shadowCameraEnabled[i]) { + shadowMapFrustumDebug.add(createDebugFrustum(shadowCameras[i], i)); + } + } + } + + private Geometry createDebugFrustum(Camera camera, int shadowMapColor) { + Vector3f[] points = new Vector3f[8]; + for (int i = 0; i < 8; i++) { + points[i] = new Vector3f(); + } + ShadowUtil.updateFrustumPoints2(camera, points); + Geometry geom = createFrustum(points, shadowMapColor); + geom.getMaterial().getAdditionalRenderState().setLineWidth(5f); + geom.getMaterial().getAdditionalRenderState().setDepthWrite(false); + ((Node) viewPort.getScenes().get(0)).attachChild(geom); + return geom; + } + + @Override + protected void setMaterialParameters(Material material) { + Vector2f[] splits = getSplits(); + material.setParam("Splits", VarType.Vector2Array, splits); + material.setVector3("LightDir", light == null ? new Vector3f() : light.getDirection()); + } + + private Vector2f[] getSplits() { + Vector2f[] result = new Vector2f[3]; + for (int i = 0; i < 3; i++) { + result[i] = new Vector2f(Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY); + } + + if (lastFit != null && lastFit.result != null) { + for (int split = 0; split < nbShadowMaps - 1; split++) { + if (split < lastFit.result.cascadeStarts.size()) { + SdsmFitter.SplitInfo splitInfo = lastFit.result.cascadeStarts.get(split); + result[split].set(splitInfo.start, splitInfo.end); + } + } + } + return result; + } + + @Override + protected void clearMaterialParameters(Material material) { + material.clearParam("Splits"); + material.clearParam("LightDir"); + } + + @Override + protected void setPostShadowParams() { + setMaterialParameters(postshadowMat); + postshadowMat.setParam("LightViewProjectionMatrices", VarType.Matrix4Array, lightViewProjectionsMatrices); + for (int j = 0; j < nbShadowMaps; j++) { + postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); + } + if (fadeInfo != null) { + postshadowMat.setVector2("FadeInfo", fadeInfo); + } + postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows); + } + + @Override + protected boolean checkCulling(Camera viewCam) { + // Directional lights are always visible + return true; + } + + /** + * Cleans up GPU resources used by the SDSM fitter. + */ + public void cleanup() { + if (sdsmFitter != null) { + sdsmFitter.cleanup(); + } + } + + @Override + public void cloneFields(final Cloner cloner, final Object original) { + light = cloner.clone(light); + init(nbShadowMaps, (int) shadowMapSize); + super.cloneFields(cloner, original); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + light = (DirectionalLight) ic.readSavable("light", null); + fitExpansionFactor = ic.readFloat("fitExpansionFactor", 1.0f); + fitFrameDelayTolerance = ic.readFloat("fitFrameDelayTolerance", 0.05f); + init(nbShadowMaps, (int) shadowMapSize); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(light, "light", null); + oc.write(fitExpansionFactor, "fitExpansionFactor", 1.0f); + oc.write(fitFrameDelayTolerance, "fitFrameDelayTolerance", 0.05f); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/shadow/SdsmFitter.java b/jme3-core/src/main/java/com/jme3/shadow/SdsmFitter.java new file mode 100644 index 0000000000..8bb2aad765 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/SdsmFitter.java @@ -0,0 +1,488 @@ +/* + * Copyright (c) 2009-2026 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.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector2f; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.RendererException; +import com.jme3.renderer.TextureUnitException; +import com.jme3.renderer.opengl.ComputeShader; +import com.jme3.renderer.opengl.GL4; +import com.jme3.renderer.opengl.GLFence; +import com.jme3.renderer.opengl.ShaderStorageBufferObject; +import com.jme3.texture.Texture; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + +/** + * Compute shader used in SDSM. + */ +public class SdsmFitter { + + private static final String REDUCE_DEPTH_SHADER = "Common/MatDefs/Shadow/Sdsm/ReduceDepth.comp"; + private static final String FIT_FRUSTUMS_SHADER = "Common/MatDefs/Shadow/Sdsm/FitLightFrustums.comp"; + + private final GL4 gl4; + private final Renderer renderer; + private int maxFrameLag = 3; + + private final ComputeShader depthReduceShader; + private final ComputeShader fitFrustumsShader; + + private final LinkedList resultHoldersInFlight = new LinkedList<>(); + private final LinkedList resultHoldersReady = new LinkedList<>(); + private SplitFitResult readyToYield; + + // Initial values for fit frustum SSBO + // 4 cascades x (minX, minY, maxX, maxY) + 4 x (minZ, maxZ) + globalMin + globalMax + 3 x (splitStart, blendEnd) + private static final int[] FIT_FRUSTUM_INIT = new int[32]; + static { + for (int i = 0; i < 4; i++) { + FIT_FRUSTUM_INIT[i * 4] = -1; //MinX (-1 == maximum UINT value) + FIT_FRUSTUM_INIT[i * 4 + 1] = -1; //MinY + FIT_FRUSTUM_INIT[i * 4 + 2] = 0; //MaxX + FIT_FRUSTUM_INIT[i * 4 + 3] = 0; //MaxY + } + for (int i = 0; i < 4; i++) { + FIT_FRUSTUM_INIT[16 + i * 2] = -1; //MinZ + FIT_FRUSTUM_INIT[16 + i * 2 + 1] = 0; //MaxZ + } + FIT_FRUSTUM_INIT[24] = -1; //Global min + FIT_FRUSTUM_INIT[25] = 0; //Global max + // Split starts (3 splits max) + for (int i = 0; i < 6; i++) { + FIT_FRUSTUM_INIT[26 + i] = 0; + } + } + + /** + * Parameters used for a fit operation. + */ + public static class FitParameters { + public final Matrix4f cameraToLight; + public final int splitCount; + public final float cameraNear; + public final float cameraFar; + + public FitParameters(Matrix4f cameraToLight, int splitCount, float cameraNear, float cameraFar) { + this.cameraToLight = cameraToLight; + this.splitCount = splitCount; + this.cameraNear = cameraNear; + this.cameraFar = cameraFar; + } + + @Override + public String toString() { + return "FitParameters{" + + "cameraToLight=" + cameraToLight + + ", splitCount=" + splitCount + + ", cameraNear=" + cameraNear + + ", cameraFar=" + cameraFar + + '}'; + } + } + + /** + * Bounds for a single cascade split in light space. + */ + public static class SplitBounds { + public final float minX, minY, maxX, maxY; + public final float minZ, maxZ; + + public SplitBounds(float minX, float minY, float maxX, float maxY, float minZ, float maxZ) { + this.minX = minX; + this.minY = minY; + this.maxX = maxX; + this.maxY = maxY; + this.minZ = minZ; + this.maxZ = maxZ; + } + + public boolean isValid() { + return minX != Float.POSITIVE_INFINITY && minY != Float.POSITIVE_INFINITY && minZ != Float.POSITIVE_INFINITY && maxX != Float.NEGATIVE_INFINITY && maxY != Float.NEGATIVE_INFINITY && maxZ != Float.NEGATIVE_INFINITY; + } + + @Override + public String toString() { + return "SplitBounds{" + + "minX=" + minX + + ", minY=" + minY + + ", maxX=" + maxX + + ", maxY=" + maxY + + ", minZ=" + minZ + + ", maxZ=" + maxZ + + '}'; + } + } + + /** + * Information about where a cascade split starts/ends for blending. + */ + public static class SplitInfo { + public final float start; + public final float end; + + public SplitInfo(float start, float end) { + this.start = start; + this.end = end; + } + + @Override + public String toString() { + return "SplitInfo{" + + "start=" + start + + ", end=" + end + + '}'; + } + } + + /** + * Complete fit result for all cascades. + */ + public static class SplitFit { + public final List splits; + public final float minDepth; + public final float maxDepth; + public final List cascadeStarts; + + public SplitFit(List splits, float minDepth, float maxDepth, List cascadeStarts) { + this.splits = splits; + this.minDepth = minDepth; + this.maxDepth = maxDepth; + this.cascadeStarts = cascadeStarts; + } + + @Override + public String toString() { + return "SplitFit{" + + "splits=" + splits + + ", minDepth=" + minDepth + + ", maxDepth=" + maxDepth + + ", cascadeStarts=" + cascadeStarts + + '}'; + } + } + + /** + * Result of a fit operation, including parameters and computed fit. + */ + public static class SplitFitResult { + public final FitParameters parameters; + public final SplitFit result; + + public SplitFitResult(FitParameters parameters, SplitFit result) { + this.parameters = parameters; + this.result = result; + } + + @Override + public String toString() { + return "SplitFitResult{" + + "parameters=" + parameters + + ", result=" + result + + '}'; + } + } + + /** + * Internal holder for in-flight fit operations. + */ + private class SdsmResultHolder { + ShaderStorageBufferObject minMaxDepthSsbo; + ShaderStorageBufferObject fitFrustumSsbo; + FitParameters parameters; + GLFence fence; + + SdsmResultHolder() { + this.minMaxDepthSsbo = new ShaderStorageBufferObject(gl4); + renderer.registerNativeObject(this.minMaxDepthSsbo); + this.fitFrustumSsbo = new ShaderStorageBufferObject(gl4); + renderer.registerNativeObject(this.fitFrustumSsbo); + } + + boolean isReady(boolean wait) { + if (fence == null) { + return true; + } + if(fence.isUpdateNeeded()){ + return true; + } + int status = gl4.glClientWaitSync(fence, 0, wait ? -1 : 0); + return status == GL4.GL_ALREADY_SIGNALED || status == GL4.GL_CONDITION_SATISFIED; + } + + SplitFitResult extract() { + if (fence != null) { + renderer.deleteFence(fence); + fence = null; + } + SplitFit fit = extractFit(); + return new SplitFitResult(parameters, fit); + } + + private SplitFit extractFit() { + if(fitFrustumSsbo.isUpdateNeeded()){ return null; } + int[] uintFit = fitFrustumSsbo.read(32); + float[] fitResult = new float[32]; + for(int i=0;i cascadeData = new ArrayList<>(); + for (int idx = 0; idx < parameters.splitCount; idx++) { + int start = idx * 4; + int zStart = 16 + idx * 2; + SplitBounds bounds = new SplitBounds( + fitResult[start], + fitResult[start + 1], + fitResult[start + 2], + fitResult[start + 3], + fitResult[zStart], + fitResult[zStart + 1] + ); + cascadeData.add(bounds.isValid() ? bounds : null); + } + + float minDepthView = getProjectionToViewZ(parameters.cameraNear, parameters.cameraFar, minDepth); + float maxDepthView = getProjectionToViewZ(parameters.cameraNear, parameters.cameraFar, maxDepth); + + List cascadeStarts = new ArrayList<>(); + for (int i = 0; i < parameters.splitCount - 1; i++) { + float splitStart = fitResult[26 + i * 2]; + float splitEnd = fitResult[26 + i * 2 + 1]; + assert !Float.isNaN(splitStart) && !Float.isNaN(splitEnd); + cascadeStarts.add(new SplitInfo(splitStart, splitEnd)); + } + + return new SplitFit(cascadeData, minDepthView, maxDepthView, cascadeStarts); + } + + void cleanup() { + minMaxDepthSsbo.deleteObject(renderer); + fitFrustumSsbo.deleteObject(renderer); + if (fence != null) { + fence.deleteObject(renderer); + } + } + } + + public SdsmFitter(GL4 gl, Renderer renderer, AssetManager assetManager) { + this.gl4 = gl; + this.renderer = renderer; + + // Load compute shaders + String reduceSource = (String)assetManager.loadAsset(REDUCE_DEPTH_SHADER); + String fitSource = (String)assetManager.loadAsset(FIT_FRUSTUMS_SHADER); + + depthReduceShader = new ComputeShader(gl, reduceSource); + renderer.registerNativeObject(depthReduceShader); + fitFrustumsShader = new ComputeShader(gl, fitSource); + renderer.registerNativeObject(fitFrustumsShader); + } + + /** + * Initiates an asynchronous fit operation on the given depth texture. + * + * @param depthTexture the depth texture to analyze + * @param splitCount number of cascade splits (1-4) + * @param cameraToLight transformation matrix from camera clip space to light view space + * @param cameraNear camera near plane distance + * @param cameraFar camera far plane distance + */ + public void fit(Texture depthTexture, int splitCount, Matrix4f cameraToLight, + float cameraNear, float cameraFar) { + + SdsmResultHolder holder = getResultHolderForUse(); + holder.parameters = new FitParameters(cameraToLight, splitCount, cameraNear, cameraFar); + + gl4.glMemoryBarrier(GL4.GL_TEXTURE_FETCH_BARRIER_BIT); + + int width = depthTexture.getImage().getWidth(); + int height = depthTexture.getImage().getHeight(); + int xGroups = divRoundUp(width, 32); + int yGroups = divRoundUp(height, 32); + + if (xGroups < 2) { + throw new RendererException("Depth texture too small for SDSM fit"); + } + + // Initialize SSBOs + holder.minMaxDepthSsbo.initialize(new int[]{-1, 0}); // max uint, 0 + + // Pass 1: Reduce depth to find min/max + depthReduceShader.makeActive(); + try { + renderer.setTexture(0, depthTexture); + } catch (TextureUnitException e) { + throw new RendererException(e); + } + depthReduceShader.bindShaderStorageBuffer(1, holder.minMaxDepthSsbo); + depthReduceShader.dispatch(xGroups, yGroups, 1); + gl4.glMemoryBarrier(GL4.GL_SHADER_STORAGE_BARRIER_BIT); + + // Pass 2: Fit cascade frustums + holder.fitFrustumSsbo.initialize(FIT_FRUSTUM_INIT); + + fitFrustumsShader.makeActive(); + try { + renderer.setTexture(0, depthTexture); + } catch (TextureUnitException e) { + throw new RendererException(e); + } + fitFrustumsShader.bindShaderStorageBuffer(1, holder.minMaxDepthSsbo); + fitFrustumsShader.bindShaderStorageBuffer(2, holder.fitFrustumSsbo); + + fitFrustumsShader.setUniform(3, cameraToLight); + fitFrustumsShader.setUniform(4, splitCount); + fitFrustumsShader.setUniform(5, new Vector2f(cameraNear, cameraFar)); + fitFrustumsShader.setUniform(6, 0.05f); + + fitFrustumsShader.dispatch(xGroups, yGroups, 1); + gl4.glMemoryBarrier(GL4.GL_SHADER_STORAGE_BARRIER_BIT); + + // Create fence for async readback + GLFence fence = gl4.glFenceSync(GL4.GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + renderer.registerNativeObject(fence); + holder.fence = fence; + resultHoldersInFlight.add(holder); + } + + /** + * Gets the next available fit result. + * + * @param wait if true, blocks until a result is available + * @return the fit result, or null if none available (and wait is false) + */ + public SplitFitResult getResult(boolean wait) { + if (readyToYield != null) { + SplitFitResult result = readyToYield; + readyToYield = null; + return result; + } + + SplitFitResult result = null; + Iterator iter = resultHoldersInFlight.iterator(); + while (iter.hasNext()) { + SdsmResultHolder next = iter.next(); + boolean mustHaveResult = result == null && wait; + if (next.isReady(mustHaveResult)) { + iter.remove(); + result = next.extract(); + resultHoldersReady.add(next); + } else { + break; + } + } + if(wait && result == null){ + throw new IllegalStateException(); + } + return result; + } + + /** + * Cleans up GPU resources. + */ + public void cleanup() { + for (SdsmResultHolder holder : resultHoldersInFlight) { + holder.cleanup(); + } + resultHoldersInFlight.clear(); + + for (SdsmResultHolder holder : resultHoldersReady) { + holder.cleanup(); + } + resultHoldersReady.clear(); + + if (depthReduceShader != null) { + depthReduceShader.deleteObject(renderer); + } + if (fitFrustumsShader != null) { + fitFrustumsShader.deleteObject(renderer); + } + } + + private SdsmResultHolder getResultHolderForUse() { + if (!resultHoldersReady.isEmpty()) { + return resultHoldersReady.removeFirst(); + } else if (resultHoldersInFlight.size() <= maxFrameLag) { + return new SdsmResultHolder(); + } else { + SdsmResultHolder next = resultHoldersInFlight.removeFirst(); + next.isReady(true); + readyToYield = next.extract(); + return next; + } + } + + private static float getProjectionToViewZ(float near, float far, float projZPos) { + float a = far / (far - near); + float b = far * near / (near - far); + return b / (projZPos - a); + } + + private static int divRoundUp(int value, int divisor) { + return (value + divisor - 1) / divisor; + } + + /** + * Converts a uint-encoded float back to float. + * This is the inverse of the floatFlip function in the shader. + */ + private static float uintFlip(int u) { + int flipped; + if ((u & 0x80000000) != 0) { + flipped = u ^ 0x80000000; // Was positive, flip sign bit + } else { + flipped = ~u; // Was negative, invert all bits + } + return Float.intBitsToFloat(flipped); + } + + public void setMaxFrameLag(int maxFrameLag) { + this.maxFrameLag = maxFrameLag; + } +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java index 7cad0a54c1..02df695803 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java +++ b/jme3-core/src/main/java/com/jme3/shadow/ShadowUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,25 +61,37 @@ */ public class ShadowUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private ShadowUtil() { + } + /** * Updates a points arrays with the frustum corners of the provided camera. * - * @param viewCam - * @param points + * @param viewCam the viewing Camera (not null, unaffected) + * @param points storage for the corner coordinates (not null, length≥8, + * modified) */ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) { int w = viewCam.getWidth(); int h = viewCam.getHeight(); - points[0].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 0)); - points[1].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 0)); - points[2].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 0)); - points[3].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 0)); + TempVars vars = TempVars.get(); + Vector2f tempVec2 = vars.vect2d; + + viewCam.getWorldCoordinates(tempVec2.set(0, 0), 0, points[0]); + viewCam.getWorldCoordinates(tempVec2.set(0, h), 0, points[1]); + viewCam.getWorldCoordinates(tempVec2.set(w, h), 0, points[2]); + viewCam.getWorldCoordinates(tempVec2.set(w, 0), 0, points[3]); + + viewCam.getWorldCoordinates(tempVec2.set(0, 0), 1, points[4]); + viewCam.getWorldCoordinates(tempVec2.set(0, h), 1, points[5]); + viewCam.getWorldCoordinates(tempVec2.set(w, h), 1, points[6]); + viewCam.getWorldCoordinates(tempVec2.set(w, 0), 1, points[7]); - points[4].set(viewCam.getWorldCoordinates(new Vector2f(0, 0), 1)); - points[5].set(viewCam.getWorldCoordinates(new Vector2f(0, h), 1)); - points[6].set(viewCam.getWorldCoordinates(new Vector2f(w, h), 1)); - points[7].set(viewCam.getWorldCoordinates(new Vector2f(w, 0), 1)); + vars.release(); } /** @@ -89,19 +101,23 @@ public static void updateFrustumPoints2(Camera viewCam, Vector3f[] points) { * * TODO: Reduce creation of new vectors * - * @param viewCam - * @param nearOverride - * @param farOverride + * @param viewCam the viewing Camera (not null, unaffected) + * @param nearOverride distance to the near plane (in world units) + * @param farOverride distance to the far plane (in world units) + * @param scale scale factor + * @param points storage for the corner coordinates (not null, length≥8, + * modified) */ public static void updateFrustumPoints(Camera viewCam, float nearOverride, float farOverride, float scale, Vector3f[] points) { + TempVars vars = TempVars.get(); Vector3f pos = viewCam.getLocation(); - Vector3f dir = viewCam.getDirection(); - Vector3f up = viewCam.getUp(); + Vector3f dir = viewCam.getDirection(vars.vect1); + Vector3f up = viewCam.getUp(vars.vect2); float depthHeightRatio = viewCam.getFrustumTop() / viewCam.getFrustumNear(); float near = nearOverride; @@ -127,18 +143,18 @@ public static void updateFrustumPoints(Camera viewCam, far_width = far_height * ratio; } - Vector3f right = dir.cross(up).normalizeLocal(); + Vector3f right = vars.vect3.set(dir).crossLocal(up).normalizeLocal(); - Vector3f temp = new Vector3f(); + Vector3f temp = vars.vect4; temp.set(dir).multLocal(far).addLocal(pos); - Vector3f farCenter = temp.clone(); + Vector3f farCenter = vars.vect5.set(temp); temp.set(dir).multLocal(near).addLocal(pos); - Vector3f nearCenter = temp.clone(); + Vector3f nearCenter = vars.vect6.set(temp); - Vector3f nearUp = temp.set(up).multLocal(near_height).clone(); - Vector3f farUp = temp.set(up).multLocal(far_height).clone(); - Vector3f nearRight = temp.set(right).multLocal(near_width).clone(); - Vector3f farRight = temp.set(right).multLocal(far_width).clone(); + Vector3f nearUp = vars.vect7.set(up).multLocal(near_height); + Vector3f farUp = vars.vect8.set(up).multLocal(far_height); + Vector3f nearRight = vars.vect9.set(right).multLocal(near_width); + Vector3f farRight = vars.vect10.set(right).multLocal(far_width); points[0].set(nearCenter).subtractLocal(nearUp).subtractLocal(nearRight); points[1].set(nearCenter).addLocal(nearUp).subtractLocal(nearRight); @@ -158,40 +174,43 @@ public static void updateFrustumPoints(Camera viewCam, } center.divideLocal(8f); - Vector3f cDir = new Vector3f(); + Vector3f cDir = temp; for (int i = 0; i < 8; i++) { cDir.set(points[i]).subtractLocal(center); cDir.multLocal(scale - 1.0f); points[i].addLocal(cDir); } } + vars.release(); } /** * Compute bounds of a geomList - * @param list - * @param transform + * + * @param list a list of geometries (not null) + * @param transform a coordinate transform * @return a new instance */ public static BoundingBox computeUnionBound(GeometryList list, Transform transform) { BoundingBox bbox = new BoundingBox(); - TempVars tempv = TempVars.get(); + TempVars tempVars = TempVars.get(); for (int i = 0; i < list.size(); i++) { BoundingVolume vol = list.get(i).getWorldBound(); - BoundingVolume newVol = vol.transform(transform, tempv.bbox); + BoundingVolume newVol = vol.transform(transform, tempVars.bbox); //Nehon : prevent NaN and infinity values to screw the final bounding box if (!Float.isNaN(newVol.getCenter().x) && !Float.isInfinite(newVol.getCenter().x)) { bbox.mergeLocal(newVol); } } - tempv.release(); + tempVars.release(); return bbox; } /** * Compute bounds of a geomList - * @param list - * @param mat + * + * @param list a list of geometries (not null) + * @param mat a coordinate-transform matrix * @return a new instance */ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) { @@ -212,7 +231,7 @@ public static BoundingBox computeUnionBound(GeometryList list, Matrix4f mat) { /** * Computes the bounds of multiple bounding volumes * - * @param bv + * @param bv a list of bounding volumes (not null) * @return a new instance */ public static BoundingBox computeUnionBound(List bv) { @@ -227,36 +246,40 @@ public static BoundingBox computeUnionBound(List bv) { /** * Compute bounds from an array of points * - * @param pts - * @param transform + * @param pts an array of location vectors (not null, unaffected) + * @param transform a coordinate transform * @return a new instance */ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Transform transform) { - Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); - Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); - Vector3f temp = new Vector3f(); + TempVars vars = TempVars.get(); + Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY); + Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY); + Vector3f temp = vars.vect3; for (int i = 0; i < pts.length; i++) { transform.transformVector(pts[i], temp); min.minLocal(temp); max.maxLocal(temp); } - Vector3f center = min.add(max).multLocal(0.5f); - Vector3f extent = max.subtract(min).multLocal(0.5f); - return new BoundingBox(center, extent.x, extent.y, extent.z); + Vector3f center = vars.vect4.set(min).addLocal(max).multLocal(0.5f); + Vector3f extent = vars.vect5.set(max).subtractLocal(min).multLocal(0.5f); + BoundingBox bbox = new BoundingBox(center, extent.x, extent.y, extent.z); + vars.release(); + return bbox; } /** * Compute bounds from an array of points - * @param pts - * @param mat + * + * @param pts an array of location vectors (not null, unaffected) + * @param mat a coordinate-transform matrix (not null, unaffected) * @return a new BoundingBox */ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) { - Vector3f min = new Vector3f(Vector3f.POSITIVE_INFINITY); - Vector3f max = new Vector3f(Vector3f.NEGATIVE_INFINITY); TempVars vars = TempVars.get(); - Vector3f temp = vars.vect1; + Vector3f min = vars.vect1.set(Vector3f.POSITIVE_INFINITY); + Vector3f max = vars.vect2.set(Vector3f.NEGATIVE_INFINITY); + Vector3f temp = vars.vect3; for (int i = 0; i < pts.length; i++) { float w = mat.multProj(pts[i], temp); @@ -269,19 +292,21 @@ public static BoundingBox computeBoundForPoints(Vector3f[] pts, Matrix4f mat) { min.minLocal(temp); max.maxLocal(temp); } + Vector3f center = vars.vect4.set(min).addLocal(max).multLocal(0.5f); + Vector3f extent = vars.vect5.set(max).subtractLocal(min).multLocal(0.5f); + // Nehon 08/18/2010 : Added an offset to the extend, to avoid banding artifacts when the frustums are + // aligned. + BoundingBox bbox = new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); vars.release(); - Vector3f center = min.add(max).multLocal(0.5f); - Vector3f extent = max.subtract(min).multLocal(0.5f); - //Nehon 08/18/2010 : Added an offset to the extend to avoid banding artifacts when the frustum are aligned - return new BoundingBox(center, extent.x + 2.0f, extent.y + 2.0f, extent.z + 2.5f); + return bbox; } /** * Updates the shadow camera to properly contain the given points (which * contain the eye camera frustum corners) * - * @param shadowCam - * @param points + * @param shadowCam the shadow camera (not null, modified) + * @param points an array of location vectors (not null, unaffected) */ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { boolean ortho = shadowCam.isParallelProjection(); @@ -322,13 +347,12 @@ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { 0f, 0f, scaleZ, offsetZ, 0f, 0f, 0f, 1f); - - Matrix4f result = new Matrix4f(); + Matrix4f result = vars.tempMat42; result.set(cropMatrix); result.multLocal(projMatrix); - vars.release(); shadowCam.setProjectionMatrix(result); + vars.release(); } /** @@ -338,20 +362,20 @@ public static void updateShadowCamera(Camera shadowCam, Vector3f[] points) { * all of them one by one against camera frustum the whole Node is checked first * to hopefully avoid the check on its children. */ - public static class OccludersExtractor - { + public static class OccludersExtractor { // global variables set in order not to have recursive process method with too many parameters Matrix4f viewProjMatrix; public Integer casterCount; BoundingBox splitBB, casterBB; GeometryList splitOccluders; TempVars vars; - + public OccludersExtractor() {} - + // initialize the global OccludersExtractor variables - public OccludersExtractor(Matrix4f vpm, int cc, BoundingBox sBB, BoundingBox cBB, GeometryList sOCC, TempVars v) { - viewProjMatrix = vpm; + public OccludersExtractor(Matrix4f vpm, int cc, BoundingBox sBB, BoundingBox cBB, + GeometryList sOCC, TempVars v) { + viewProjMatrix = vpm; casterCount = cc; splitBB = sBB; casterBB = cBB; @@ -363,29 +387,31 @@ public OccludersExtractor(Matrix4f vpm, int cc, BoundingBox sBB, BoundingBox cBB * Check the rootScene against camera frustum and if intersects process it recursively. * The global OccludersExtractor variables need to be initialized first. * Variables are updated and used in {@link ShadowUtil#updateShadowCamera} at last. + * + * @param scene the root of the scene to check (may be null) + * @return the number of shadow casters found */ public int addOccluders(Spatial scene) { - if ( scene != null ) process(scene); + if (scene != null) process(scene); return casterCount; } - + private void process(Spatial scene) { if (scene.getCullHint() == Spatial.CullHint.Always) return; RenderQueue.ShadowMode shadowMode = scene.getShadowMode(); - if ( scene instanceof Geometry ) - { + if (scene instanceof Geometry) { // convert bounding box to light's viewproj space Geometry occluder = (Geometry)scene; if (shadowMode != RenderQueue.ShadowMode.Off && shadowMode != RenderQueue.ShadowMode.Receive && !occluder.isGrouped() && occluder.getWorldBound()!=null) { BoundingVolume bv = occluder.getWorldBound(); BoundingVolume occBox = bv.transform(viewProjMatrix, vars.bbox); - + boolean intersects = splitBB.intersects(occBox); if (!intersects && occBox instanceof BoundingBox) { BoundingBox occBB = (BoundingBox) occBox; - //Kirill 01/10/2011 + // Kirill 01/10/2011 // Extend the occluder further into the frustum // This fixes shadow disappearing issues when // the caster itself is not in the view camera @@ -400,7 +426,7 @@ private void process(Spatial scene) { // We return the bound to its former shape // Before adding it occBB.setZExtent(occBB.getZExtent() - 50); - occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25)); + occBB.setCenter(occBB.getCenter().subtractLocal(0, 0, 25)); casterBB.mergeLocal(occBox); casterCount++; } @@ -416,15 +442,13 @@ private void process(Spatial scene) { } } } - } - else if ( scene instanceof Node && ((Node)scene).getWorldBound()!=null ) - { + } else if (scene instanceof Node && ((Node)scene).getWorldBound() != null) { Node nodeOcc = (Node)scene; boolean intersects = false; - // some + // some BoundingVolume bv = nodeOcc.getWorldBound(); BoundingVolume occBox = bv.transform(viewProjMatrix, vars.bbox); - + intersects = splitBB.intersects(occBox); if (!intersects && occBox instanceof BoundingBox) { BoundingBox occBB = (BoundingBox) occBox; @@ -438,8 +462,8 @@ else if ( scene instanceof Node && ((Node)scene).getWorldBound()!=null ) occBB.setCenter(occBB.getCenter().addLocal(0, 0, 25)); intersects = splitBB.intersects(occBB); } - - if ( intersects ) { + + if (intersects) { for (Spatial child : ((Node)scene).getChildren()) { process(child); } @@ -447,11 +471,18 @@ else if ( scene instanceof Node && ((Node)scene).getWorldBound()!=null ) } } } - + /** * Updates the shadow camera to properly contain the given points (which * contain the eye camera frustum corners) and the shadow occluder objects * collected through the traverse of the scene hierarchy + * + * @param viewPort the ViewPort + * @param receivers a list of receiving geometries + * @param shadowCam the shadow camera (not null, modified) + * @param points an array of location vectors (not null, unaffected) + * @param splitOccluders a list of occluding geometries + * @param shadowMapSize the size of each edge of the shadow map (in pixels) */ public static void updateShadowCamera(ViewPort viewPort, GeometryList receivers, @@ -459,7 +490,7 @@ public static void updateShadowCamera(ViewPort viewPort, Vector3f[] points, GeometryList splitOccluders, float shadowMapSize) { - + boolean ortho = shadowCam.isParallelProjection(); shadowCam.setProjectionMatrix(null); @@ -468,18 +499,18 @@ public static void updateShadowCamera(ViewPort viewPort, shadowCam.setFrustum(-shadowCam.getFrustumFar(), shadowCam.getFrustumFar(), -1, 1, 1, -1); } - // create transform to rotate points to viewspace + // create transform to rotate points to viewspace Matrix4f viewProjMatrix = shadowCam.getViewProjectionMatrix(); BoundingBox splitBB = computeBoundForPoints(points, viewProjMatrix); TempVars vars = TempVars.get(); - + BoundingBox casterBB = new BoundingBox(); BoundingBox receiverBB = new BoundingBox(); - + int casterCount = 0, receiverCount = 0; - + for (int i = 0; i < receivers.size(); i++) { // convert bounding box to light's viewproj space Geometry receiver = receivers.get(i); @@ -501,7 +532,12 @@ public static void updateShadowCamera(ViewPort viewPort, occExt.addOccluders(scene); } casterCount = occExt.casterCount; - + + if (casterCount == 0) { + vars.release(); + return; + } + //Nehon 08/18/2010 this is to avoid shadow bleeding when the ground is set to only receive shadows if (casterCount != receiverCount) { casterBB.setXExtent(casterBB.getXExtent() + 2.0f); @@ -544,8 +580,10 @@ public static void updateShadowCamera(ViewPort viewPort, float scaleX, scaleY, scaleZ; float offsetX, offsetY, offsetZ; - scaleX = (2.0f) / (cropMax.x - cropMin.x); - scaleY = (2.0f) / (cropMax.y - cropMin.y); + float deltaCropX = cropMax.x - cropMin.x; + float deltaCropY = cropMax.y - cropMin.y; + scaleX = deltaCropX == 0 ? 0 : 2.0f / deltaCropX; + scaleY = deltaCropY == 0 ? 0 : 2.0f / deltaCropY; //Shadow map stabilization approximation from shaderX 7 //from Practical Cascaded Shadow maps adapted to PSSM @@ -553,7 +591,7 @@ public static void updateShadowCamera(ViewPort viewPort, float halfTextureSize = shadowMapSize * 0.5f; if (halfTextureSize != 0 && scaleX >0 && scaleY>0) { - float scaleQuantizer = 0.1f; + float scaleQuantizer = 0.1f; scaleX = 1.0f / FastMath.ceil(1.0f / scaleX * scaleQuantizer) * scaleQuantizer; scaleY = 1.0f / FastMath.ceil(1.0f / scaleY * scaleQuantizer) * scaleQuantizer; } @@ -570,7 +608,8 @@ public static void updateShadowCamera(ViewPort viewPort, offsetY = FastMath.ceil(offsetY * halfTextureSize) / halfTextureSize; } - scaleZ = 1.0f / (cropMax.z - cropMin.z); + float deltaCropZ = cropMax.z - cropMin.z; + scaleZ = deltaCropZ == 0 ? 0 : 1.0f / deltaCropZ; offsetZ = -cropMin.z * scaleZ; @@ -582,15 +621,14 @@ public static void updateShadowCamera(ViewPort viewPort, 0f, 0f, scaleZ, offsetZ, 0f, 0f, 0f, 1f); - - Matrix4f result = new Matrix4f(); + Matrix4f result = vars.tempMat42; result.set(cropMatrix); result.multLocal(projMatrix); - vars.release(); shadowCam.setProjectionMatrix(result); + vars.release(); } - + /** * Populates the outputGeometryList with the geometry of the * inputGeometryList that are in the frustum of the given camera @@ -622,9 +660,10 @@ public static void getGeometriesInCamFrustum(GeometryList inputGeometryList, * * @param rootScene the rootNode of the scene to traverse * @param camera the camera to check geometries against + * @param mode the ShadowMode to test for * @param outputGeometryList the list of all geometries that are in the * camera frustum - */ + */ public static void getGeometriesInCamFrustum(Spatial rootScene, Camera camera, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { if (rootScene != null && rootScene instanceof Node) { int planeState = camera.getPlaneState(); @@ -632,12 +671,12 @@ public static void getGeometriesInCamFrustum(Spatial rootScene, Camera camera, R camera.setPlaneState(planeState); } } - + /** * Helper function to distinguish between Occluders and Receivers - * + * * @param shadowMode the ShadowMode tested - * @param desired the desired ShadowMode + * @param desired the desired ShadowMode * @return true if tested ShadowMode matches the desired one */ static private boolean checkShadowMode(RenderQueue.ShadowMode shadowMode, RenderQueue.ShadowMode desired) @@ -645,9 +684,9 @@ static private boolean checkShadowMode(RenderQueue.ShadowMode shadowMode, Render if (shadowMode != RenderQueue.ShadowMode.Off) { switch (desired) { - case Cast : + case Cast : return shadowMode==RenderQueue.ShadowMode.Cast || shadowMode==RenderQueue.ShadowMode.CastAndReceive; - case Receive: + case Receive: return shadowMode==RenderQueue.ShadowMode.Receive || shadowMode==RenderQueue.ShadowMode.CastAndReceive; case CastAndReceive: return true; @@ -655,14 +694,15 @@ static private boolean checkShadowMode(RenderQueue.ShadowMode shadowMode, Render } return false; } - + /** - * Helper function used to recursively populate the outputGeometryList + * Helper function used to recursively populate the outputGeometryList * with geometry children of scene node - * + * * @param camera - * @param scene - * @param outputGeometryList + * @param scene the root of the scene to traverse (may be null) + * @param mode the ShadowMode to test for + * @param outputGeometryList */ private static void addGeometriesInCamFrustumFromNode(Camera camera, Node scene, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { if (scene.getCullHint() == Spatial.CullHint.Always) return; @@ -681,11 +721,11 @@ else if (child instanceof Geometry && child.getCullHint() != Spatial.CullHint.Al } } } - + /** * Populates the outputGeometryList with the geometry of the * inputGeometryList that are in the radius of a light. - * The array of camera must be an array of 6 cameras initialized so they represent the light viewspace of a pointlight + * The array must contain 6 cameras, initialized to represent the viewspace of a point light. * * @param inputGeometryList The list containing all geometries to check * against the camera frustum @@ -714,24 +754,25 @@ public static void getGeometriesInLightRadius(GeometryList inputGeometryList, } /** - * Populates the outputGeometryList with the geometries of the children + * Populates the outputGeometryList with the geometries of the children * of OccludersExtractor.rootScene node that are both in the frustum of the given vpCamera and some camera inside cameras array. * The array of cameras must be initialized to represent the light viewspace of some light like pointLight or spotLight * - * @param rootScene - * @param vpCamera the viewPort camera + * @param rootScene the root of the scene to traverse (may be null) + * @param vpCamera the viewPort camera * @param cameras the camera array to check geometries against, representing the light viewspace + * @param mode the ShadowMode to test for * @param outputGeometryList the output list of all geometries that are in the camera frustum */ public static void getLitGeometriesInViewPort(Spatial rootScene, Camera vpCamera, Camera[] cameras, RenderQueue.ShadowMode mode, GeometryList outputGeometryList) { if (rootScene != null && rootScene instanceof Node) { - addGeometriesInCamFrustumAndViewPortFromNode(vpCamera, cameras, (Node)rootScene, mode, outputGeometryList); + addGeometriesInCamFrustumAndViewPortFromNode(vpCamera, cameras, rootScene, mode, outputGeometryList); } } /** * Helper function to recursively collect the geometries for getLitGeometriesInViewPort function. - * - * @param vpCamera the viewPort camera + * + * @param vpCamera the viewPort camera * @param cameras the camera array to check geometries against, representing the light viewspace * @param scene the Node to traverse or geometry to possibly add * @param outputGeometryList the output list of all geometries that are in the camera frustum @@ -756,7 +797,7 @@ private static void addGeometriesInCamFrustumAndViewPortFromNode(Camera vpCamera } } else if (scene instanceof Geometry) { - if (checkShadowMode(scene.getShadowMode(), mode) && !((Geometry)scene).isGrouped() ) { + if (checkShadowMode(scene.getShadowMode(), mode) && !((Geometry)scene).isGrouped()) { outputGeometryList.add((Geometry)scene); } } diff --git a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java index 91b006746e..5bc1b4a0ba 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,45 +40,40 @@ import java.io.IOException; /** - * * This Filter does basically the same as a SpotLightShadowRenderer except it * renders the post shadow pass as a fullscreen quad pass instead of a geometry * pass. It's mostly faster than PssmShadowRenderer as long as you have more - * than a about ten shadow receiving objects. The expense is the draw back that + * than about ten shadow receiving objects. The expense is the drawback that * the shadow Receive mode set on spatial is ignored. So basically all and only - * objects that render depth in the scene receive shadows. See this post for - * more details - * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 + * objects that render depth in the scene receive shadows. * - * API is basically the same as the PssmShadowRenderer; + * API is basically the same as the PssmShadowRenderer. * * @author Rémy Bouquet aka Nehon */ public class SpotLightShadowFilter extends AbstractShadowFilter { /** - * Used for serialization. - * Use SpotLightShadowFilter#SpotLightShadowFilter(AssetManager assetManager, - * int shadowMapSize) - * instead. + * For serialization only. Do not use. + * + * @see #SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize) */ protected SpotLightShadowFilter() { super(); } /** - * Creates a SpotLight Shadow Filter + * Creates a SpotLightShadowFilter. * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) the more quality, the less fps). + * @param assetManager the application's asset manager + * @param shadowMapSize the size of the rendered shadow maps (512, 1024, 2048, etc...) */ public SpotLightShadowFilter(AssetManager assetManager, int shadowMapSize) { super(assetManager, shadowMapSize, new SpotLightShadowRenderer(assetManager, shadowMapSize)); } /** - * return the light used to cast shadows + * Returns the light used to cast shadows. * * @return the SpotLight */ @@ -87,20 +82,19 @@ public SpotLight getLight() { } /** - * Sets the light to use to cast shadows + * Sets the light to use to cast shadows. * - * @param light a SpotLight + * @param light the SpotLight */ public void setLight(SpotLight light) { shadowRenderer.setLight(light); } - + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(shadowRenderer, "shadowRenderer", null); - } @Override @@ -109,4 +103,5 @@ public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); shadowRenderer = (SpotLightShadowRenderer) ic.readSavable("shadowRenderer", null); } + } diff --git a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java index 0e26bf0ca0..f102f00f78 100644 --- a/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java +++ b/jme3-core/src/main/java/com/jme3/shadow/SpotLightShadowRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,6 @@ import com.jme3.renderer.Camera; import com.jme3.renderer.queue.GeometryList; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TempVars; import com.jme3.util.clone.Cloner; @@ -57,9 +56,9 @@ * map for each one.
                splits are distributed so that the closer they are from * the camera, the smaller they are to maximize the resolution used of the * shadow map.
                This results in a better quality shadow than standard shadow - * mapping.
                for more informations on this read this for more information on this read http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
                - *

                + * * @author Rémy Bouquet aka Nehon */ public class SpotLightShadowRenderer extends AbstractShadowRenderer { @@ -82,14 +81,14 @@ protected SpotLightShadowRenderer() { * * @param assetManager the application asset manager * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) the more quality, the less fps). + * etc...) The more quality, the fewer fps. */ public SpotLightShadowRenderer(AssetManager assetManager, int shadowMapSize) { super(assetManager, shadowMapSize, 1); init(shadowMapSize); } - + private void init(int shadowMapSize) { shadowCam = new Camera(shadowMapSize, shadowMapSize); for (int i = 0; i < points.length; i++) { @@ -175,9 +174,9 @@ protected Camera getShadowCam(int shadowMapIndex) { protected void doDisplayFrustumDebug(int shadowMapIndex) { Vector3f[] points2 = points.clone(); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); + getSceneForDebug().attachChild(createFrustum(points, shadowMapIndex)); ShadowUtil.updateFrustumPoints2(shadowCam, points2); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points2, shadowMapIndex)); + getSceneForDebug().attachChild(createFrustum(points2, shadowMapIndex)); } @Override @@ -223,7 +222,7 @@ public void write(JmeExporter ex) throws IOException { /** * - * @param viewCam + * @param viewCam the viewing Camera to check against * @return true if intersects, otherwise false */ @Override diff --git a/jme3-core/src/main/java/com/jme3/shadow/package-info.java b/jme3-core/src/main/java/com/jme3/shadow/package-info.java new file mode 100644 index 0000000000..c706f0afcc --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/shadow/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * simulate shadows using filters or renderers + */ +package com.jme3.shadow; 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 bd8db3107c..92354ff91e 100644 --- a/jme3-core/src/main/java/com/jme3/system/AppSettings.java +++ b/jme3-core/src/main/java/com/jme3/system/AppSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,8 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.prefs.BackingStoreException; import java.util.prefs.Preferences; @@ -49,15 +51,17 @@ * By default only the {@link JmeContext context} uses the configuration, * however the user may set and retrieve the settings as well. * The settings can be stored either in the Java preferences - * (using {@link #save(java.lang.String) } or - * a .properties file (using {@link #save(java.io.OutputStream) }. + * (using {@link #save(java.lang.String) }) or + * a .properties file (using {@link #save(java.io.OutputStream) }). * * @author Kirill Vainer */ public final class AppSettings extends HashMap { private static final long serialVersionUID = 1L; - + + private static final Logger logger = Logger.getLogger(AppSettings.class.getName()); + private static final AppSettings defaults = new AppSettings(false); /** @@ -70,34 +74,47 @@ public final class AppSettings extends HashMap { */ public static final String LWJGL_OPENGL2 = "LWJGL-OpenGL2"; + /** + * Use LWJGL as the display system and force using the core OpenGL3.2 renderer. + *

                + * If the underlying system does not support OpenGL3.2, then the context + * initialization will throw an exception. Note that currently jMonkeyEngine + * does not have any shaders that support OpenGL3.2 therefore this + * option is not useful. + *

                + * Note: OpenGL 3.2 is used to give 3.x support to Mac users. + * + * @deprecated Previously meant 3.2, use LWJGL_OPENGL32 or LWJGL_OPENGL30 + * @see AppSettings#setRenderer(java.lang.String) + */ + @Deprecated + public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; + /** * Use LWJGL as the display system and force using the core OpenGL3.0 renderer. *

                * If the underlying system does not support OpenGL3.0, then the context * initialization will throw an exception. Note that currently jMonkeyEngine - * does not have any shaders that support OpenGL3.0 therefore this + * does not have any shaders that support OpenGL3.0 therefore this * option is not useful. - *

                + *

                * * @see AppSettings#setRenderer(java.lang.String) */ public static final String LWJGL_OPENGL30 = "LWJGL-OpenGL30"; /** - * Use LWJGL as the display system and force using the core OpenGL3.2 renderer. + * Use LWJGL as the display system and force using the core OpenGL3.1 renderer. *

                - * If the underlying system does not support OpenGL3.2, then the context + * If the underlying system does not support OpenGL3.1, then the context * initialization will throw an exception. Note that currently jMonkeyEngine - * does not have any shaders that support OpenGL3.2 therefore this + * does not have any shaders that support OpenGL3.0 therefore this * option is not useful. - *

                - * Note: OpenGL 3.2 is used to give 3.x support to Mac users. + *

                * - * @deprecated Previously meant 3.2, use LWJGL_OPENGL32 or LWJGL_OPENGL30 * @see AppSettings#setRenderer(java.lang.String) */ - @Deprecated - public static final String LWJGL_OPENGL3 = "LWJGL-OpenGL3"; + public static final String LWJGL_OPENGL31 = "LWJGL-OpenGL31"; /** * Use LWJGL as the display system and force using the core OpenGL3.2 renderer. @@ -223,7 +240,7 @@ public final class AppSettings extends HashMap { * @see AppSettings#setAudioRenderer(java.lang.String) */ public static final String ANDROID_OPENAL_SOFT = "OpenAL_SOFT"; - + /** * Use JogAmp's JOGL as the display system, with the OpenGL forward compatible profile *

                @@ -232,28 +249,57 @@ public final class AppSettings extends HashMap { * @see AppSettings#setRenderer(java.lang.String) */ public static final String JOGL_OPENGL_FORWARD_COMPATIBLE = "JOGL_OPENGL_FORWARD_COMPATIBLE"; - + /** - * Use JogAmp's JOGL as the display system with the backward compatible profile + * Use JogAmp's JOGL as the display system, with the backward compatible profile *

                * N.B: This backend is EXPERIMENTAL * * @see AppSettings#setRenderer(java.lang.String) */ public static final String JOGL_OPENGL_BACKWARD_COMPATIBLE = "JOGL_OPENGL_BACKWARD_COMPATIBLE"; - + /** - * Use JogAmp's JOAL as the display system + * Use JogAmp's JOAL as the audio renderer. *

                * N.B: This backend is EXPERIMENTAL * - * @see AppSettings#setRenderer(java.lang.String) + * @see AppSettings#setAudioRenderer(java.lang.String) */ public static final String JOAL = "JOAL"; + /** + * Map gamepads to Xbox-like layout. + */ + public static final String JOYSTICKS_XBOX_MAPPER = "JOYSTICKS_XBOX_MAPPER"; + + /** + * Map gamepads to an Xbox-like layout, with fallback to raw if the gamepad is not recognized. + */ + public static final String JOYSTICKS_XBOX_WITH_FALLBACK_MAPPER = "JOYSTICKS_XBOX_WITH_FALLBACK_MAPPER"; + + /** + * Map gamepads to an Xbox-like layout using the legacy jME input + */ + public static final String JOYSTICKS_XBOX_LEGACY_MAPPER = "JOYSTICKS_XBOX_LEGACY_MAPPER"; + + /** + * Map gamepads using the legacy jME mapper and input. + */ + public static final String JOYSTICKS_LEGACY_MAPPER = "JOYSTICKS_LEGACY_MAPPER"; + + /** + * Don't map gamepads, use raw events instead (ie. bring your own mapper) + */ + public static final String JOYSTICKS_RAW_MAPPER = "JOYSTICKS_RAW_MAPPER"; + static { + defaults.put("Display", 0); + defaults.put("CenterWindow", true); defaults.put("Width", 640); defaults.put("Height", 480); + defaults.put("WindowWidth", Integer.MIN_VALUE); + defaults.put("WindowHeight", Integer.MIN_VALUE); defaults.put("BitsPerPixel", 24); defaults.put("Frequency", 60); defaults.put("DepthBits", 24); @@ -261,20 +307,28 @@ public final class AppSettings extends HashMap { defaults.put("Samples", 0); defaults.put("Fullscreen", false); defaults.put("Title", JmeVersion.FULL_NAME); - defaults.put("Renderer", LWJGL_OPENGL2); + defaults.put("Renderer", LWJGL_OPENGL32); defaults.put("AudioRenderer", LWJGL_OPENAL); defaults.put("DisableJoysticks", true); defaults.put("UseInput", true); - defaults.put("VSync", false); + defaults.put("VSync", true); defaults.put("FrameRate", -1); defaults.put("SettingsDialogImage", "/com/jme3/app/Monkey.png"); defaults.put("MinHeight", 0); defaults.put("MinWidth", 0); - defaults.put("GammaCorrection", false); + defaults.put("GammaCorrection", true); defaults.put("Resizable", false); defaults.put("SwapBuffers", true); defaults.put("OpenCL", false); defaults.put("OpenCLPlatformChooser", DefaultPlatformChooser.class.getName()); + defaults.put("UseRetinaFrameBuffer", false); + defaults.put("WindowYPosition", 0); + defaults.put("WindowXPosition", 0); + defaults.put("X11PlatformPreferred", false); + defaults.put("JoysticksMapper", JOYSTICKS_XBOX_MAPPER); + defaults.put("JoysticksTriggerToButtonThreshold", 0.5f); + defaults.put("JoysticksAxisJitterThreshold", 0.0001f); + defaults.put("SDLGameControllerDBResourcePath", ""); // defaults.put("Icons", null); } @@ -315,7 +369,7 @@ public void copyFrom(AppSettings other) { */ public void mergeFrom(AppSettings other) { for (String key : other.keySet()) { - if( !this.containsKey(key) ) { + if (!this.containsKey(key)) { put(key, other.get(key)); } } @@ -404,19 +458,21 @@ public void load(String preferencesKey) throws BackingStoreException { // Try loading using new method switch (key.charAt(0)) { case 'I': - put(key.substring(2), prefs.getInt(key, (Integer) 0)); + put(key.substring(2), prefs.getInt(key, 0)); break; case 'F': - put(key.substring(2), prefs.getFloat(key, (Float) 0f)); + put(key.substring(2), prefs.getFloat(key, 0f)); break; case 'S': - put(key.substring(2), prefs.get(key, (String) null)); + put(key.substring(2), prefs.get(key, null)); break; case 'B': - put(key.substring(2), prefs.getBoolean(key, (Boolean) false)); + put(key.substring(2), prefs.getBoolean(key, false)); break; default: - throw new UnsupportedOperationException("Undefined setting type: " + key.charAt(0)); + throw new UnsupportedOperationException( + "Undefined setting type: " + key.charAt(0) + ); } } else { // Use old method for compatibility with older preferences @@ -480,74 +536,147 @@ public void save(String preferencesKey) throws BackingStoreException { * Get an integer from the settings. *

                * If the key is not set, then 0 is returned. + * + * @param key the key of an integer setting + * @return the corresponding value, or 0 if not set */ public int getInteger(String key) { - Integer i = (Integer) get(key); - if (i == null) { - return 0; - } + return getInteger(key, 0); + } - return i.intValue(); + /** + * Get an integer from the settings. + *

                + * If the key is not set, or the stored value is not an Integer, then the + * provided default value is returned. + * + * @param key the key of an integer setting + * @param defaultValue the value to return if the key is not found or the + * value is not an integer + */ + public int getInteger(String key, int defaultValue) { + Object val = get(key); + if (val == null) { + return defaultValue; + } + return (Integer) val; } /** * Get a boolean from the settings. *

                * If the key is not set, then false is returned. + * + * @param key the key of a boolean setting + * @return the corresponding value, or false if not set */ public boolean getBoolean(String key) { - Boolean b = (Boolean) get(key); - if (b == null) { - return false; - } + return getBoolean(key, false); + } - return b.booleanValue(); + /** + * Get a boolean from the settings. + *

                + * If the key is not set, or the stored value is not a Boolean, then the + * provided default value is returned. + * + * @param key the key of a boolean setting + * @param defaultValue the value to return if the key is not found or the + * value is not a boolean + */ + public boolean getBoolean(String key, boolean defaultValue) { + Object val = get(key); + if (val == null) { + return defaultValue; + } + return (Boolean) val; } /** * Get a string from the settings. *

                * If the key is not set, then null is returned. + * + * @param key the key of a string setting + * @return the corresponding value, or null if not set */ public String getString(String key) { - String s = (String) get(key); - if (s == null) { - return null; - } + return getString(key, null); + } - return s; + /** + * Get a string from the settings. + *

                + * If the key is not set, or the stored value is not a String, then the + * provided default value is returned. + * + * @param key the key of a string setting + * @param defaultValue the value to return if the key is not found or the + * value is not a string + */ + public String getString(String key, String defaultValue) { + Object val = get(key); + if (val == null) { + return defaultValue; + } + return (String) val; } /** * Get a float from the settings. *

                * If the key is not set, then 0.0 is returned. + * + * @param key the key of a float setting + * @return the corresponding value, or 0 if not set */ public float getFloat(String key) { - Float f = (Float) get(key); - if (f == null) { - return 0f; - } + return getFloat(key, 0f); + } - return f.floatValue(); + /** + * Get a float from the settings. + *

                + * If the key is not set, or the stored value is not a Float, then the + * provided default value is returned. + * + * @param key the key of a float setting + * @param defaultValue the value to return if the key is not found or the + * value is not a float + */ + public float getFloat(String key, float defaultValue) { + Object val = get(key); + if (val == null) { + return defaultValue; + } + return (Float) val; } /** * Set an integer on the settings. + * + * @param key the desired key + * @param value the desired integer value */ public void putInteger(String key, int value) { - put(key, Integer.valueOf(value)); + put(key, value); } /** * Set a boolean on the settings. + * + * @param key the desired key + * @param value the desired boolean value */ public void putBoolean(String key, boolean value) { - put(key, Boolean.valueOf(value)); + put(key, value); } /** * Set a string on the settings. + * + * @param key the desired key + * @param value the desired string value */ public void putString(String key, String value) { put(key, value); @@ -555,9 +684,12 @@ public void putString(String key, String value) { /** * Set a float on the settings. + * + * @param key the desired key + * @param value the desired float value */ public void putFloat(String key, float value) { - put(key, Float.valueOf(value)); + put(key, value); } /** @@ -652,15 +784,17 @@ public void setUseJoysticks(boolean use) { /** * Set the graphics renderer to use, one of:
                *

                  - *
                • AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatability
                • - *
                • AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatability
                • - *
                • AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatability
                • + *
                • AppSettings.LWJGL_OPENGL1 - Force OpenGL1.1 compatibility
                • + *
                • AppSettings.LWJGL_OPENGL2 - Force OpenGL2 compatibility
                • + *
                • AppSettings.LWJGL_OPENGL3 - Force OpenGL3.3 compatibility
                • *
                • AppSettings.LWJGL_OPENGL_ANY - Choose an appropriate * OpenGL version based on system capabilities
                • + *
                • AppSettings.JOGL_OPENGL_BACKWARD_COMPATIBLE
                • + *
                • AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE
                • *
                • null - Disable graphics rendering
                • *
                * @param renderer The renderer to set - * (Default: AppSettings.LWJGL_OPENGL2) + * (Default: AppSettings.LWJGL_OPENGL32) */ public void setRenderer(String renderer) { putString("Renderer", renderer); @@ -672,7 +806,7 @@ public void setRenderer(String renderer) { * @param clazz The custom context class. * (Default: not set) */ - public void setCustomRenderer(Class clazz){ + public void setCustomRenderer(Class clazz) { put("Renderer", "CUSTOM" + clazz.getName()); } @@ -680,6 +814,7 @@ public void setCustomRenderer(Class clazz){ * Set the audio renderer to use. One of:
                *
                  *
                • AppSettings.LWJGL_OPENAL - Default for LWJGL
                • + *
                • AppSettings.JOAL
                • *
                • null - Disable audio
                • *
                * @param audioRenderer @@ -690,7 +825,7 @@ public void setAudioRenderer(String audioRenderer) { } /** - * @param value the width for the rendering display. + * @param value the width for the default frame buffer. * (Default: 640) */ public void setWidth(int value) { @@ -698,7 +833,7 @@ public void setWidth(int value) { } /** - * @param value the height for the rendering display. + * @param value the height for the default frame buffer. * (Default: 480) */ public void setHeight(int value) { @@ -706,7 +841,8 @@ public void setHeight(int value) { } /** - * Set the resolution for the rendering display + * Set the resolution for the default frame buffer + * Use {@link #setWindowSize(int, int)} instead, for HiDPI display support. * @param width The width * @param height The height * (Default: 640x480) @@ -716,6 +852,16 @@ public void setResolution(int width, int height) { setHeight(height); } + /** + * Set the size of the window + * + * @param width The width in pixels (default = width of the default frame buffer) + * @param height The height in pixels (default = height of the default frame buffer) + */ + public void setWindowSize(int width, int height) { + putInteger("WindowWidth", width); + putInteger("WindowHeight", height); + } /** * @param value the minimum width the settings window will allow for the rendering display. @@ -744,8 +890,6 @@ public void setMinResolution(int width, int height) { setMinHeight(height); } - - /** * Set the frequency, also known as refresh rate, for the * rendering display. @@ -767,7 +911,7 @@ public void setFrequency(int value) { * * @param value The depth bits */ - public void setDepthBits(int value){ + public void setDepthBits(int value) { putInteger("DepthBits", value); } @@ -786,7 +930,7 @@ public void setDepthBits(int value){ * * @param value The alpha bits */ - public void setAlphaBits(int value){ + public void setAlphaBits(int value) { putInteger("AlphaBits", value); } @@ -801,7 +945,7 @@ public void setAlphaBits(int value){ * * @param value Number of stencil bits */ - public void setStencilBits(int value){ + public void setStencilBits(int value) { putInteger("StencilBits", value); } @@ -817,12 +961,12 @@ public void setBitsPerPixel(int value) { } /** - * Set the number of samples per pixel. A value of 1 indicates + * Set the number of samples per pixel. A value of 0 or 1 indicates * each pixel should be single-sampled, higher values indicate * a pixel should be multi-sampled. * * @param value The number of samples - * (Default: 1) + * (Default: 0) */ public void setSamples(int value) { putInteger("Samples", value); @@ -845,10 +989,10 @@ public void setFullscreen(boolean value) { } /** - * Set to true to enable vertical-synchronization, limiting and synchronizing - * every frame rendered to the monitor's refresh rate. - * @param value - * (Default: false) + * Enable or disable vertical synchronization. If enabled, rendering will be + * synchronized with the display's refresh interval. + * + * @param value true to enable, false to disable (Default : true) */ public void setVSync(boolean value) { putBoolean("VSync", value); @@ -860,20 +1004,21 @@ public void setVSync(boolean value) { * See http://en.wikipedia.org/wiki/Quad_buffering
                * Once enabled, filters or scene processors that handle 3D stereo rendering * could use this feature to render using hardware 3D stereo.

                - * (Default: false) + * + * @param value true to enable 3-D stereo, false to disable (default=false) */ - public void setStereo3D(boolean value){ + public void setStereo3D(boolean value) { putBoolean("Stereo3D", value); } /** * Sets the application icons to be used, with the most preferred first. - * For Windows you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar, + * For Windows, you should supply at least one 16x16 icon and one 32x32. The former is used for the title/task bar, * the latter for the alt-tab icon. * Linux (and similar platforms) expect one 32x32 icon. * Mac OS X should be supplied one 128x128 icon. - *
                - * The icon is used for the settings window, and the LWJGL render window. Not currently supported for JOGL. + *
                + * The icon is used for the settings window, and the LWJGL render window. * Note that a bug in Java 6 (bug ID 6445278, currently hidden but available in Google cache) currently prevents * the icon working for alt-tab on the settings dialog in Windows. * @@ -899,18 +1044,22 @@ public void setSettingsDialogImage(String path) { } /** - * Enables Gamma Correction - * This requires that the GPU supports GL_ARB_framebuffer_sRGB and will - * disabled otherwise. - * @param gammaCorrection - * (Default : true) + * Enable or disable gamma correction. If enabled, the main framebuffer will + * be configured for sRGB colors, and sRGB images will be linearized. + *

                + * Gamma correction requires a GPU that supports GL_ARB_framebuffer_sRGB; + * otherwise this setting will be ignored. + * + * @param gammaCorrection true to enable, false to disable (Default : true) */ public void setGammaCorrection(boolean gammaCorrection) { putBoolean("GammaCorrection", gammaCorrection); } /** - * Get the framerate. + * Get the frame rate. + * + * @return the maximum rate (in frames per second), or -1 for unlimited * @see #setFrameRate(int) */ public int getFrameRate() { @@ -919,6 +1068,8 @@ public int getFrameRate() { /** * Get the use input state. + * + * @return true if input is enabled, false if it's disabled * @see #setUseInput(boolean) */ public boolean useInput() { @@ -927,6 +1078,9 @@ public boolean useInput() { /** * Get the renderer + * + * @return the graphics renderer's name and version, + * for example "LWJGL-OpenGL33" * @see #setRenderer(java.lang.String) */ public String getRenderer() { @@ -935,6 +1089,8 @@ public String getRenderer() { /** * Get the width + * + * @return the width of the default frame buffer (in pixels) * @see #setWidth(int) */ public int getWidth() { @@ -943,14 +1099,40 @@ public int getWidth() { /** * Get the height + * + * @return the height of the default frame buffer (in pixels) * @see #setHeight(int) */ public int getHeight() { return getInteger("Height"); } + /** + * Get the width of the window + * + * @return the width of the window (in pixels) + * @see #setWindowSize(int, int) + */ + public int getWindowWidth() { + int w = getInteger("WindowWidth"); + return w != Integer.MIN_VALUE ? w : getWidth(); + } + + /** + * Get the height of the window + * + * @return the height of the window (in pixels) + * @see #setWindowSize(int, int) + */ + public int getWindowHeight() { + int h = getInteger("WindowHeight"); + return h != Integer.MIN_VALUE ? h : getHeight(); + } + /** * Get the width + * + * @return the minimum width for the rendering display (in pixels) * @see #setWidth(int) */ public int getMinWidth() { @@ -959,6 +1141,8 @@ public int getMinWidth() { /** * Get the height + * + * @return the minimum height for the rendering display (in pixels) * @see #setHeight(int) */ public int getMinHeight() { @@ -967,6 +1151,8 @@ public int getMinHeight() { /** * Get the bits per pixel + * + * @return the number of color bits per rendered pixel * @see #setBitsPerPixel(int) */ public int getBitsPerPixel() { @@ -975,6 +1161,8 @@ public int getBitsPerPixel() { /** * Get the frequency + * + * @return the refresh rate of the (full-screen) display (in Hertz) * @see #setFrequency(int) */ public int getFrequency() { @@ -983,6 +1171,8 @@ public int getFrequency() { /** * Get the number of depth bits + * + * @return the number of depth bits per rendered pixel * @see #setDepthBits(int) */ public int getDepthBits() { @@ -992,6 +1182,8 @@ public int getDepthBits() { /** * Android Only * Get the number of alpha bits for the surface view to use. + * + * @return the number of alpha bits per rendered pixel * @see #setAlphaBits(int) */ public int getAlphaBits() { @@ -1000,6 +1192,8 @@ public int getAlphaBits() { /** * Get the number of stencil bits + * + * @return the number of stencil bits per rendered pixel * @see #setStencilBits(int) */ public int getStencilBits() { @@ -1008,6 +1202,8 @@ public int getStencilBits() { /** * Get the number of samples + * + * @return the number of samples per pixel (for multisample anti-aliasing) * @see #setSamples(int) */ public int getSamples() { @@ -1016,6 +1212,8 @@ public int getSamples() { /** * Get the application title + * + * @return the title text * @see #setTitle(java.lang.String) */ public String getTitle() { @@ -1023,7 +1221,9 @@ public String getTitle() { } /** - * Get the vsync state + * Test whether vertical synchronization should be enabled. + * + * @return true for enabled, false for disabled * @see #setVSync(boolean) */ public boolean isVSync() { @@ -1032,6 +1232,8 @@ public boolean isVSync() { /** * Get the fullscreen state + * + * @return true for fullscreen display, false for windowed display * @see #setFullscreen(boolean) */ public boolean isFullscreen() { @@ -1040,6 +1242,8 @@ public boolean isFullscreen() { /** * Get the use joysticks state + * + * @return true to enable joystick input, false to disable it * @see #setUseJoysticks(boolean) */ public boolean useJoysticks() { @@ -1048,6 +1252,8 @@ public boolean useJoysticks() { /** * Get the audio renderer + * + * @return the audio renderer's name, for example "LWJGL" * @see #setAudioRenderer(java.lang.String) */ public String getAudioRenderer() { @@ -1056,14 +1262,18 @@ public String getAudioRenderer() { /** * Get the stereo 3D state + * + * @return true if 3-D stereo is enabled, otherwise false * @see #setStereo3D(boolean) */ - public boolean useStereo3D(){ + public boolean useStereo3D() { return getBoolean("Stereo3D"); } /** * Get the icon array + * + * @return the pre-existing array * @see #setIcons(java.lang.Object[]) */ public Object[] getIcons() { @@ -1072,64 +1282,71 @@ public Object[] getIcons() { /** * Get the settings dialog image + * + * @return a path to the image asset * @see #setSettingsDialogImage(java.lang.String) */ public String getSettingsDialogImage() { return getString("SettingsDialogImage"); } + /** + * Test whether gamma correction should be enabled. + * + * @return true for enabled, false for disabled + */ public boolean isGammaCorrection() { return getBoolean("GammaCorrection"); } - + /** * Allows the display window to be resized by dragging its edges. - * - * Only supported for {@link JmeContext.Type#Display} contexts which - * are in windowed mode, ignored for other types. + *

                + * Only supported for {@link JmeContext.Type#Display} contexts which + * are in windowed mode, ignored for other types. * The default value is false. - * + * * @param resizable True to make a resizable window, false to make a fixed * size window. */ public void setResizable(boolean resizable) { putBoolean("Resizable", resizable); } - + /** * Determine if the display window can be resized by dragging its edges. - * + * * @return True if the window is resizable, false if it is fixed size. - * - * @see #setResizable(boolean) + * + * @see #setResizable(boolean) */ public boolean isResizable() { return getBoolean("Resizable"); } - + /** * When enabled the display context will swap buffers every frame. - * + *

                * This may need to be disabled when integrating with an external * library that handles buffer swapping on its own, e.g. Oculus Rift. * When disabled, the engine will process window messages - * after each frame but it will not swap buffers - note that this + * after each frame, but it will not swap buffers. Note that this * will cause 100% CPU usage normally as there's no VSync or any framerate - * caps (unless set via {@link #setFrameRate(int) }. + * caps (unless set via {@link #setFrameRate(int) }). * The default is true. - * + * * @param swapBuffers True to enable buffer swapping, false to disable it. */ public void setSwapBuffers(boolean swapBuffers) { putBoolean("SwapBuffers", swapBuffers); } - + /** * Determine if the display context will swap buffers every frame. - * + * * @return True if buffer swapping is enabled, false otherwise. - * - * @see #setSwapBuffers(boolean) + * + * @see #setSwapBuffers(boolean) */ public boolean isSwapBuffers() { return getBoolean("SwapBuffers"); @@ -1138,7 +1355,7 @@ public boolean isSwapBuffers() { /** * True to enable the creation of an OpenCL context. * - * @param support + * @param support whether to create the context or not */ public void setOpenCLSupport(boolean support) { putBoolean("OpenCL", support); @@ -1147,20 +1364,362 @@ public void setOpenCLSupport(boolean support) { public boolean isOpenCLSupport() { return getBoolean("OpenCL"); } - + /** * Sets a custom platform chooser. This chooser specifies which platform and * which devices are used for the OpenCL context. - * + *

                * Default: an implementation defined one. - * + * * @param chooser the class of the chooser, must have a default constructor */ public void setOpenCLPlatformChooser(Class chooser) { putString("OpenCLPlatformChooser", chooser.getName()); } - + public String getOpenCLPlatformChooser() { return getString("OpenCLPlatformChooser"); } + + /** + * 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) + */ + public boolean isGraphicsDebug() { + return getBoolean("GraphicsDebug"); + } + + /** + * 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() + */ + public void setGraphicsDebug(boolean debug) { + putBoolean("GraphicsDebug", debug); + } + + /** + * Determine if the renderer will be run in Graphics Timing mode, which means every openGL call is checked and + * if it runs for longer than a millisecond, log it.
                + * It also keeps track of the time spent in GL Calls in general and displays them when + * {@link com.jme3.renderer.opengl.GL#resetStats()} is called. + * + * @return whether the context will be run in Graphics Timing Mode or not + * @see #setGraphicsTiming(boolean) + * @see com.jme3.renderer.opengl.GLTiming + */ + public boolean isGraphicsTiming() { + return getBoolean("GraphicsTiming"); + } + + /** + * Set whether the renderer will be run in Graphics Timing mode, which means every openGL call is checked and + * if it runs for longer than a millisecond, log it.
                + * It also keeps track of the time spent in GL Calls in general and displays them when + * {@link com.jme3.renderer.opengl.GL#resetStats()} is called. + * + * @param timing whether the context will be run in Graphics Timing Mode or not + * @see #isGraphicsTiming() + * @see com.jme3.renderer.opengl.GLTiming + */ + public void setGraphicsTiming(boolean timing) { + putBoolean("GraphicsTiming", timing); + } + + /** + * Determine if the renderer will be run in Graphics Trace mode, which means every openGL call is logged so one + * can trace what openGL commands where executed in which order by the engine. + * + * @return whether the context will be run in Graphics Trace Mode or not + * @see #setGraphicsTrace(boolean) + * @see com.jme3.renderer.opengl.GLTracer + */ + public boolean isGraphicsTrace() { + return getBoolean("GraphicsTrace"); + } + + /** + * Set whether the renderer will be run in Graphics Trace mode, which means every openGL call is logged so one + * can trace what openGL commands where executed in which order by the engine. + * + * @param trace whether the context will be run in Graphics Trace Mode or not + * @see #isGraphicsTrace() + * @see com.jme3.renderer.opengl.GLTracer + */ + public void setGraphicsTrace(boolean trace) { + putBoolean("GraphicsTrace", trace); + } + + /** + * Determine whether to use full resolution framebuffers on Retina displays. + * + * @return whether to use full resolution framebuffers on Retina displays. + */ + public boolean isUseRetinaFrameBuffer() { + return getBoolean("UseRetinaFrameBuffer"); + } + + /** + * Specifies whether to use full resolution framebuffers on Retina displays. This is ignored on other platforms. + * + * @param useRetinaFrameBuffer whether to use full resolution framebuffers on Retina displays. + */ + public void setUseRetinaFrameBuffer(boolean useRetinaFrameBuffer) { + putBoolean("UseRetinaFrameBuffer", useRetinaFrameBuffer); + } + + /** + * Tests the state of the Center Window flag. + * + *

                The Center Window flag is used only with LWJGL3 and has no effect on + * fullscreen windows. + * + * @return true to center the window on the desktop, false to position the + * window at (WindowXPosition, WindowYPosition) + * @see #setCenterWindow(boolean) + */ + public boolean getCenterWindow() { + return getBoolean("CenterWindow"); + } + + /** + * Enables or disables the Center Window flag. + * + *

                The Center Window flag is used only with LWJGL3 and has no effect on + * fullscreen windows. It defaults to true. + * + * @param center true to center the window on the desktop, false to position + * the window at (WindowXPosition, WindowYPosition) + */ + public void setCenterWindow(boolean center) { + putBoolean("CenterWindow", center); + } + + /** + * Gets the window's initial X position on the desktop. + * + *

                This setting is used only with LWJGL3, has no effect on fullscreen + * windows, and is ignored if the Center Window flag is true. + * + * @return the initial position of the window's left edge relative to the + * left edge of the desktop + * @see #setCenterWindow(boolean) + * @see #setWindowXPosition(int) + */ + public int getWindowXPosition() { + return getInteger("WindowXPosition"); + } + + /** + * Sets the window's initial X position on the desktop. + * + *

                This setting is used only with LWJGL3, has no effect on fullscreen + * windows, and is ignored if the Center Window flag is true. Its default + * value is 0. + * + * @param pos the desired initial position of the window's left edge + * relative to the left edge of the desktop + * @see #setCenterWindow(boolean) + */ + public void setWindowXPosition(int pos) { + putInteger("WindowXPosition", pos); + } + + /** + * Gets the window's initial Y position on the desktop. + * + *

                This setting is used only with LWJGL3, has no effect on fullscreen + * windows, and is ignored if the Center Window flag is true. + * + * @return the initial position of the window's upper edge relative to the + * upper edge of the desktop + * @see #setCenterWindow(boolean) + * @see #setWindowYPosition(int) + */ + public int getWindowYPosition() { + return getInteger("WindowYPosition"); + } + + /** + * Sets the window's initial Y position on the desktop. + * + *

                This setting is used only with LWJGL3, has no effect on fullscreen + * windows, and is ignored if the Center Window flag is true. Its default + * value is 0. + * + * @param pos the desired initial position of the window's upper edge + * relative to the upper edge of the desktop + * @see #setCenterWindow(boolean) + */ + public void setWindowYPosition(int pos) { + putInteger("WindowYPosition", pos); + } + + /** + * Gets the display number used when creating a window. + * + *

                + * This setting is used only with LWJGL3, it defines which display to use when creating a OpenGL + * window. + * + * @return the desired display used when creating a OpenGL window + */ + public int getDisplay() { + return getInteger("Display"); + } + + /** + * Sets the display number used when creating a window. The position number is the number in the + * list of monitors GlfwGetMonitors returns. + * + *

                + * This setting is used only with LWJGL3, it defines which display to use when creating a OpenGL + * window. its default value is 0. + * + * @param mon the desired display used when creating a OpenGL window + * + */ + public void setDisplay(int mon) { + putInteger("Display", mon); + } + + /** + * Prints all key-value pairs stored under a given preferences key + * in the Java Preferences API to standard output. + * + * @param preferencesKey The preferences key (node path) to inspect. + * @throws BackingStoreException If an exception occurs while accessing the preferences. + */ + public static void printPreferences(String preferencesKey) throws BackingStoreException { + Preferences prefs = Preferences.userRoot().node(preferencesKey); + String[] keys = prefs.keys(); + + if (keys == null || keys.length == 0) { + logger.log(Level.WARNING, "No Preferences found under key: {0}", preferencesKey); + } else { + StringBuilder sb = new StringBuilder(); + sb.append("Preferences for key: ").append(preferencesKey); + for (String key : keys) { + // Retrieve the value as a String (default fallback for Preferences API) + String value = prefs.get(key, "[Value Not Found]"); + sb.append("\n * ").append(key).append(" = ").append(value); + } + logger.log(Level.INFO, sb.toString()); + } + } + /** + * Sets the preferred native platform for creating the GL context on Linux distributions. + *

                + * This setting is relevant for Linux distributions or derivatives that utilize a Wayland session alongside an X11 via the XWayland bridge. + * Enabling this option allows the use of GLX for window positioning and/or icon configuration. + * + * @param preferred true to prefer GLX (native X11) for the GL context, false to prefer EGL (native Wayland). + */ + public void setX11PlatformPreferred(boolean preferred) { + putBoolean("X11PlatformPreferred", preferred); + } + + /** + * Determines which native platform is preferred for GL context creation on Linux distributions. + *

                + * This setting is only valid on Linux distributions or derivatives that support Wayland, + * and it indicates whether GLX (native X11) or EGL (native Wayland) is enabled for the GL context. + * + * @return true if GLX is preferred, otherwise false if EGL is preferred (native Wayland). + */ + public boolean isX11PlatformPreferred() { + return getBoolean("X11PlatformPreferred"); + } + + /** + * Set which joystick mapping to use for normalization of controller inputs + * + * @param mapper + * JOYSTICKS_MAPPER_* constant defining which mapping to use + */ + public void setJoysticksMapper(String mapper) { + putString("JoysticksMapper", mapper); + } + + /** + * Get which joystick mapping to use for normalization of controller inputs + */ + public String getJoysticksMapper() { + return getString("JoysticksMapper"); + } + + /** + * Sets the threshold above which an analog trigger should also generate a button-press event. + * If the value is set to -1, the trigger will never generate button-press events. + * + *

                + * This is intended to normalize behavior between controllers that expose triggers as analog + * axes and controllers that expose triggers as digital buttons. + * + * @param threshold the trigger threshold in the range [0, 1] (default: 0.5f) + */ + public void setJoysticksTriggerToButtonThreshold(float threshold) { + putFloat("JoysticksTriggerToButtonThreshold", threshold); + } + + /** + * Gets the threshold above which an analog trigger should also generate a button-press event. + * + * @return the trigger threshold in the range [0, 1] (default: 0.5f) + * @see #setJoysticksTriggerToButtonThreshold(float) + */ + public float getJoysticksTriggerToButtonThreshold() { + return getFloat("JoysticksTriggerToButtonThreshold"); + } + + /** + * Sets the jitter threshold for joystick axes. + * + *

                + * Axis movements with a delta smaller than this threshold will be ignored. This is intended to reduce + * noise from analog joysticks. + */ + public void setJoysticksAxisJitterThreshold(float threshold) { + putFloat("JoysticksAxisJitterThreshold", threshold); + } + + /** + * Gets the jitter threshold for joystick axes. + * + * @return the jitter threshold + * @see #setJoysticksAxisJitterThreshold(float) + */ + public float getJoysticksAxisJitterThreshold() { + return getFloat("JoysticksAxisJitterThreshold"); + } + + /** + * Set resource path for a custom SDL game controller database. + * + * @param path + */ + public void setSDLGameControllerDBResourcePath(String path) { + putString("SDLGameControllerDBResourcePath", path); + } + + /** + * Get resource path for a custom SDL game controller database. + * + * @return resource path + */ + public String getSDLGameControllerDBResourcePath() { + return getString("SDLGameControllerDBResourcePath"); + } } + diff --git a/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java b/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java new file mode 100644 index 0000000000..a80f59850b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/DisplayInfo.java @@ -0,0 +1,255 @@ +/* + * Copyright (c) 2009-2025 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.util.Objects; + +/** + * This class holds information about the display that was returned by glfwGetMonitors() calls in + * the context class + * + * @author Kevin Bales + * @author wil + */ +public final class DisplayInfo { + + /** display - display id that was return from Lwjgl3. */ + private long display; + + /** width - width that was return from Lwjgl3. */ + private int width; + + /** height - height that was return from Lwjgl3. */ + private int height; + + /** rate - refresh rate that was return from Lwjgl3. */ + private int rate; + + /** primary - indicates if the display is the primary monitor. */ + private boolean primary; + + /** + * name - display name that was return from Lwjgl3. + */ + private String name; + + /** + * Create a new display mode object with the default values + */ + DisplayInfo() { + this(0L /*NULL*/, 1080, 1920, 60, false, "Generic Monitor"); + } + + /** + * Create a new display mode object with the supplied parameters. + * @param display the monitor pointer (native), the virtual memory + * address used by lwjgl. + * @param width the width of the display, provided by lwjgl. + * @param height the height of the display, provided by lwjgl. + * @param rate the refresh rate of the display, in hertz. + * @param primary a logical value that determines whether this display is + * primary or not; {@code true | false} + * @param name display name + */ + DisplayInfo(long display, int width, int height, int rate, boolean primary, String name) { + this.display = display; + this.width = width; + this.height = height; + this.rate = rate; + this.primary = primary; + this.name = name; + } + + // === ----------------------------------------------------------------- === + // === SETTERS === + // === ----------------------------------------------------------------- === + + /** + * Sets the monitor pointer (native), the virtual memory address used by lwjgl. + * + * @param display the monitor pointer (native), the virtual memory + * address used by lwjgl + */ + void setDisplay(long display) { + this.display = display; + } + + /** + * Sets the width of the display. + * @param width the width of the display + */ + void setWidth(int width) { + this.width = width; + } + + /** + * Sets the height of the display. + * @param height the height of the display + */ + void setHeight(int height) { + this.height = height; + } + + /** + * Sets the refresh rate of the display, in hertz. + * @param rate the refresh rate of the display, in hertz + */ + void setRate(int rate) { + this.rate = rate; + } + + /** + * Set this display as primary or not. + * @param primary {@code true} if the display is primary, {@code false} otherwise. + */ + void setPrimary(boolean primary) { + this.primary = primary; + } + + /** + * Set the screen (display) name + * @param name display name + */ + void setName(String name) { + this.name = name; + } + + // === ----------------------------------------------------------------- === + // === GETTERS === + // === ----------------------------------------------------------------- === + + /** + * Returns the monitor pointer (native), the virtual memory address used by lwjgl. + * @return the monitor pointer (native), the virtual memory address used by lwjgl + */ + public long getDisplay() { + return display; + } + + /** + * Returns the width of the display. + * @return the width of the display. + */ + public int getWidth() { + return width; + } + + /** + * Returns the height of the display. + * @return the height of the display + */ + public int getHeight() { + return height; + } + + /** + * Returns the refresh rate of the display, in hertz. + * @return the refresh rate of the display, in hertz + */ + public int getRate() { + return rate; + } + + /** + * Determines if this display belongs to the main monitor. + * @return {@code true} if the display is primary, {@code false} otherwise. + */ + public boolean isPrimary() { + return primary; + } + + /** + * Returns the display name. + * @return display name + */ + public String getName() { + return name; + } + + /** + * {@inheritDoc } + */ + @Override + public int hashCode() { + int hash = 3; + hash = 97 * hash + (int) (this.display ^ (this.display >>> 32)); + hash = 97 * hash + this.width; + hash = 97 * hash + this.height; + hash = 97 * hash + this.rate; + hash = 97 * hash + (this.primary ? 1 : 0); + hash = 97 * hash + Objects.hashCode(this.name); + return hash; + } + + /** + * {@inheritDoc } + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final DisplayInfo other = (DisplayInfo) obj; + if (this.display != other.display) { + return false; + } + if (this.width != other.width) { + return false; + } + if (this.height != other.height) { + return false; + } + if (this.rate != other.rate) { + return false; + } + if (this.primary != other.primary) { + return false; + } + return Objects.equals(this.name, other.name); + } + + /** + * {@inheritDoc } + */ + @Override + public String toString() { + return getDisplay() == 0L ? "NULL" : ("(" + getName() + "|" + + getDisplay() + ")" + getWidth() + "x" + getHeight() + "@" + + (getRate() > 0 ? getRate() + "Hz" : "[Unknown refresh rate]")); + } +} diff --git a/jme3-core/src/main/java/com/jme3/system/Displays.java b/jme3-core/src/main/java/com/jme3/system/Displays.java new file mode 100644 index 0000000000..c0e24f549f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/Displays.java @@ -0,0 +1,107 @@ +/* + * 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.util.ArrayList; + +/** + * This class holds all information about all displays that where return from the glfwGetMonitors() + * call. It stores them into an ArrayList + * + * @author Kevin Bales + */ +public class Displays { + + /** List of monitors. */ + private ArrayList displays = new ArrayList<>(); + + /** + * Add a new monitor to the list + * + * @param displaysID the (native) pointer of the new monitor + * @return the position of the monitor (represents the index) + */ + public int addNewMonitor(long displaysID) { + DisplayInfo info = new DisplayInfo(); + info.setDisplay(displaysID); + displays.add(info); + return displays.size() - 1; + } + + /** + * This function returns the size of the display ArrayList + * + * @return the number of monitors available. + */ + public int size() { + return displays.size(); + } + + /** + * Call to get display information on a certain display. + * + * @param pos the position in the ArrayList of the display information that you want to get. + * @return returns the DisplayInfo data for the display called for. + */ + public DisplayInfo get(int pos) { + if (pos < displays.size()) return displays.get(pos); + + return null; + } + + /** + * Set information about this display stored in displayPos display in the array list. + * + * @param displayPos ArrayList position of display to update + * @param name name of the display + * @param width the current width the display is displaying + * @param height the current height the display is displaying + * @param rate the current refresh rate the display is set to + */ + public void setInfo(int displayPos, String name, int width, int height, int rate) { + if (displayPos < displays.size()) { + DisplayInfo info = displays.get(displayPos); + if (info != null) { + info.setWidth(width); + info.setHeight(height); + info.setRate(rate); + info.setName(name); + } + } + } + + /** + * This function will mark a certain display as the primary display. + * + * @param displayPos the position in the ArrayList of which display is the primary display + */ + public void setPrimaryDisplay(int displayPos) { + if (displayPos < displays.size()) { + DisplayInfo info = displays.get(displayPos); + if (info != null) info.setPrimary(true); + } + } +} 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 b31042128d..c2ffe912ef 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,6 @@ * Represents a rendering context within the engine. */ public interface JmeContext { - /** * The type of context. */ @@ -77,7 +76,7 @@ public enum Type { * any drawable surface. The implementation does not provide any * display, input, or sound support. */ - Headless; + Headless, } /** @@ -92,9 +91,18 @@ 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. + * + * @param listener the desired listener */ public void setSystemListener(SystemListener listener); @@ -185,4 +193,51 @@ 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(); + + /** + * This call will return a list of Monitors that glfwGetMonitors() returns and information about + * the monitor, like width, height, and refresh rate. + * + * @return returns a list of monitors and their information. + */ + public Displays getDisplays(); + + /** + * Use this to get the positional number of the primary monitor from the glfwGetMonitors() + * function call. + * + * @return the position of the value in the arraylist of the primary monitor. + */ + public int getPrimaryDisplay(); } 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 44ee834191..9def80e73a 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystem.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystem.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,25 +34,39 @@ 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; import java.io.OutputStream; +import java.lang.reflect.Constructor; 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; +/** + * Utility class to access platform-dependant features. + */ public class JmeSystem { private static final Logger logger = Logger.getLogger(JmeSystem.class.getName()); - public static enum StorageFolderType { + + public enum StorageFolderType { Internal, External, } private static JmeSystemDelegate systemDelegate; + /** + * A private constructor to inhibit instantiation of this class. + */ + private JmeSystem() { + } + public static void setSystemDelegate(JmeSystemDelegate systemDelegate) { JmeSystem.systemDelegate = systemDelegate; } @@ -103,6 +117,7 @@ public static void setSoftTextDialogInput(SoftTextDialogInput input) { /** * Displays or hides the onscreen soft keyboard + * * @param show If true, the keyboard is displayed, if false, the screen is hidden. */ public static void showSoftKeyboard(boolean show) { @@ -117,16 +132,16 @@ public static SoftTextDialogInput getSoftTextDialogInput() { /** * Compresses a raw image into a stream. - * + *

                * The encoding is performed via system libraries. On desktop, the encoding - * is performed via ImageIO, whereas on Android, is done via the + * is performed via ImageIO, whereas on Android, is done via the * Bitmap class. - * + * * @param outStream The stream where to write the image data. - * @param format The format to use, either "png" or "jpg". + * @param format The format to use, either "png" or "jpg". * @param imageData The image data in {@link com.jme3.texture.Image.Format#RGBA8} format. - * @param width The width of the image. - * @param height The height of the image. + * @param width The width of the image. + * @param height The height of the image. * @throws IOException If outStream throws an exception while writing. */ public static void writeImageFile(OutputStream outStream, String format, ByteBuffer imageData, int width, int height) throws IOException { @@ -144,11 +159,12 @@ 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 + * application is running on. + * + * @return an enum value (not null) + */ public static Platform getPlatform() { checkDelegate(); return systemDelegate.getPlatform(); @@ -168,18 +184,45 @@ public static URL getPlatformAssetConfigURL() { checkDelegate(); return systemDelegate.getPlatformAssetConfigURL(); } - + /** * Displays an error message to the user in whichever way the context * feels is appropriate. If this is a headless or an offscreen surface * context, this method should do nothing. * * @param message The error message to display. May contain new line - * characters. + * characters. + * @deprecated Use JmeSystem.handleErrorMessage(String) instead */ - public static void showErrorDialog(String message){ + @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.showErrorDialog(message); + 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(); + return systemDelegate.showSettingsDialog(sourceSettings, loadFromRegistry); } public static void initialize(AppSettings settings) { @@ -187,36 +230,37 @@ public static void initialize(AppSettings settings) { systemDelegate.initialize(settings); } - private static JmeSystemDelegate tryLoadDelegate(String className) throws InstantiationException, IllegalAccessException { - try { - return (JmeSystemDelegate) Class.forName(className).newInstance(); - } catch (ClassNotFoundException ex) { - return null; - } - } + private static final String[] delegateClassNames = { + "com.jme3.system.JmeDesktopSystem", + "com.jme3.system.android.JmeAndroidSystem", + "com.jme3.system.ios.JmeIosSystem" + }; - @SuppressWarnings("unchecked") private static void checkDelegate() { if (systemDelegate == null) { try { - systemDelegate = tryLoadDelegate("com.jme3.system.JmeDesktopSystem"); - if (systemDelegate == null) { - systemDelegate = tryLoadDelegate("com.jme3.system.android.JmeAndroidSystem"); - if (systemDelegate == null) { - systemDelegate = tryLoadDelegate("com.jme3.system.ios.JmeIosSystem"); - if (systemDelegate == null) { - // None of the system delegates were found .. - Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, - "Failed to find a JmeSystem delegate!\n" - + "Ensure either desktop or android jME3 jar is in the classpath."); - } + for (String className : delegateClassNames) { + systemDelegate = tryLoadDelegate(className); + if (systemDelegate != null) { + return; // Delegate found and loaded } } - } catch (InstantiationException ex) { - Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "Failed to create JmeSystem delegate:\n{0}", ex); - } catch (IllegalAccessException ex) { - Logger.getLogger(JmeSystem.class.getName()).log(Level.SEVERE, "Failed to create JmeSystem delegate:\n{0}", ex); + // None of the system delegates were found. + logger.log(Level.SEVERE, "Failed to find a JmeSystem delegate!\n" + + "Ensure either desktop or android jME3 jar is in the classpath."); + + } catch (ReflectiveOperationException | IllegalArgumentException ex) { + logger.log(Level.SEVERE, "Failed to create JmeSystem delegate:\n{0}", ex); } } } + + private static JmeSystemDelegate tryLoadDelegate(String className) throws ReflectiveOperationException, IllegalArgumentException { + try { + Constructor c = Class.forName(className).getDeclaredConstructor(); + return (JmeSystemDelegate) c.newInstance(); + } catch (ClassNotFoundException ex) { + return null; + } + } } 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 71c4176327..c9d8b79182 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeSystemDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,14 +35,19 @@ import com.jme3.asset.DesktopAssetManager; import com.jme3.audio.AudioRenderer; import com.jme3.input.SoftTextDialogInput; +import com.jme3.util.res.Resources; + 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; @@ -55,9 +60,35 @@ public abstract class JmeSystemDelegate { protected final Logger logger = Logger.getLogger(JmeSystem.class.getName()); protected boolean initialized = false; protected boolean lowPermissions = false; - protected Map storageFolders = new EnumMap(JmeSystem.StorageFolderType.class); + 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; @@ -82,7 +113,9 @@ public synchronized File getStorageFolder(JmeSystem.StorageFolderType type) { break; } if (storageFolder != null) { - logger.log(Level.FINE, "Storage Folder Path: {0}", storageFolder.getAbsolutePath()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Storage Folder Path: {0}", storageFolder.getAbsolutePath()); + } } else { logger.log(Level.FINE, "Storage Folder not found!"); } @@ -94,11 +127,11 @@ public String getFullName() { } public InputStream getResourceAsStream(String name) { - return this.getClass().getResourceAsStream(name); + return Resources.getResourceAsStream(name,this.getClass()); } public URL getResource(String name) { - return this.getClass().getResource(name); + return Resources.getResource(name,this.getClass()); } public boolean trackDirectMemory() { @@ -131,9 +164,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")) { @@ -168,10 +248,14 @@ public Platform getPlatform() { String arch = System.getProperty("os.arch").toLowerCase(); boolean is64 = is64Bit(arch); if (os.contains("windows")) { - return is64 ? Platform.Windows64 : Platform.Windows32; + if (arch.startsWith("arm") || arch.startsWith("aarch")) { + return is64 ? Platform.Windows_ARM64 : Platform.Windows_ARM32; + } else { + return is64 ? Platform.Windows64 : Platform.Windows32; + } } else if (os.contains("linux") || os.contains("freebsd") || os.contains("sunos") || os.contains("unix")) { - if (arch.startsWith("arm")) { + if (arch.startsWith("arm") || arch.startsWith("aarch")) { return is64 ? Platform.Linux_ARM64 : Platform.Linux_ARM32; } else { return is64 ? Platform.Linux64 : Platform.Linux32; @@ -179,6 +263,8 @@ public Platform getPlatform() { } else if (os.contains("mac os x") || os.contains("darwin")) { if (arch.startsWith("ppc")) { return is64 ? Platform.MacOSX_PPC64 : Platform.MacOSX_PPC32; + } else if (arch.startsWith("aarch")) { + return Platform.MacOSX_ARM64; // no 32-bit version } else { return is64 ? Platform.MacOSX64 : Platform.MacOSX32; } diff --git a/jme3-core/src/main/java/com/jme3/system/JmeVersion.java b/jme3-core/src/main/java/com/jme3/system/JmeVersion.java index 0492df950d..2599949d8f 100644 --- a/jme3-core/src/main/java/com/jme3/system/JmeVersion.java +++ b/jme3-core/src/main/java/com/jme3/system/JmeVersion.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,8 @@ import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.util.res.Resources; + /** * Pulls in version info from the version.properties file. * @@ -48,8 +50,8 @@ public class JmeVersion { static { try { - props.load(JmeVersion.class.getResourceAsStream("version.properties")); - } catch (IOException ex) { + props.load(Resources.getResourceAsStream("version.properties",JmeVersion.class)); + } catch (IOException | NullPointerException ex) { logger.log(Level.WARNING, "Unable to read version info!", ex); } } @@ -63,4 +65,10 @@ public class JmeVersion { public static final String VERSION_TAG = props.getProperty("version.tag", ""); public static final String VERSION_FULL = props.getProperty("version.full", ""); public static final String FULL_NAME = props.getProperty("name.full", "jMonkeyEngine (unknown version)"); + + /** + * A private constructor to inhibit instantiation of this class. + */ + private JmeVersion() { + } } diff --git a/jme3-core/src/main/java/com/jme3/system/NanoTimer.java b/jme3-core/src/main/java/com/jme3/system/NanoTimer.java index e0a4d8b469..0964857416 100644 --- a/jme3-core/src/main/java/com/jme3/system/NanoTimer.java +++ b/jme3-core/src/main/java/com/jme3/system/NanoTimer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,28 +62,34 @@ public float getTimeInSeconds() { return getTime() * INVERSE_TIMER_RESOLUTION; } + @Override public long getTime() { return System.nanoTime() - startTime; } + @Override public long getResolution() { return TIMER_RESOLUTION; } + @Override public float getFrameRate() { return fps; } + @Override public float getTimePerFrame() { return tpf; } + @Override public void update() { tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION); fps = 1.0f / tpf; previousTime = getTime(); } + @Override public void reset() { startTime = System.nanoTime(); previousTime = getTime(); 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 205d0a2c05..2f2518f8c3 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ public class NullContext implements JmeContext, Runnable { protected static final Logger logger = Logger.getLogger(NullContext.class.getName()); protected static final String THREAD_NAME = "jME3 Headless Main"; - + protected AtomicBoolean created = new AtomicBoolean(false); protected AtomicBoolean needClose = new AtomicBoolean(false); protected final Object createdLock = new Object(); @@ -59,27 +59,44 @@ public class NullContext implements JmeContext, Runnable { protected SystemListener listener; protected NullRenderer renderer; + @Override public Type getType() { return Type.Headless; } - public void setSystemListener(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; } - protected void initInThread(){ + protected void initInThread() { logger.fine("NullContext created."); - logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Running on thread: {0}", Thread.currentThread().getName()); + } - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); + } } - }); + ); timer = new NanoTimer(); renderer = new NullRenderer(); - synchronized (createdLock){ + synchronized (createdLock) { created.set(true); createdLock.notifyAll(); } @@ -87,10 +104,10 @@ public void uncaughtException(Thread thread, Throwable thrown) { listener.initialize(); } - protected void deinitInThread(){ + protected void deinitInThread() { listener.destroy(); timer = null; - synchronized (createdLock){ + synchronized (createdLock) { created.set(false); createdLock.notifyAll(); } @@ -126,7 +143,8 @@ public void sync(int fps) { timeThen = timeNow; } - public void run(){ + @Override + public void run() { initInThread(); do { @@ -142,97 +160,157 @@ public void run(){ logger.fine("NullContext destroyed."); } - public void destroy(boolean waitFor){ + @Override + public void destroy(boolean waitFor) { needClose.set(true); - if (waitFor) - waitFor(false); + if (waitFor) waitFor(false); } - public void create(boolean waitFor){ - if (created.get()){ + @Override + public void create(boolean waitFor) { + if (created.get()) { logger.warning("create() called when NullContext is already created!"); return; } new Thread(this, THREAD_NAME).start(); - if (waitFor) - waitFor(true); + if (waitFor) waitFor(true); } - public void restart() { - } + @Override + public void restart() {} - public void setAutoFlushFrames(boolean enabled){ - } + @Override + public void setAutoFlushFrames(boolean enabled) {} + @Override public MouseInput getMouseInput() { return new DummyMouseInput(); } + @Override public KeyInput getKeyInput() { return new DummyKeyInput(); } + @Override public JoyInput getJoyInput() { return null; } + @Override public TouchInput getTouchInput() { return null; } - public void setTitle(String title) { - } + @Override + public void setTitle(String title) {} - public void create(){ + public void create() { create(false); } - public void destroy(){ + public void destroy() { destroy(false); } - protected void waitFor(boolean createdVal){ - synchronized (createdLock){ - while (created.get() != createdVal){ + protected void waitFor(boolean createdVal) { + synchronized (createdLock) { + while (created.get() != createdVal) { try { createdLock.wait(); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } } - public boolean isCreated(){ + @Override + public boolean isCreated() { return created.get(); } + @Override public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); frameRate = settings.getFrameRate(); - if (frameRate <= 0) - frameRate = 60; // use default update rate. + if (frameRate <= 0) frameRate = 60; // use default update rate. } - public AppSettings getSettings(){ + @Override + public AppSettings getSettings() { return settings; } + @Override public Renderer getRenderer() { return renderer; } + @Override public Timer getTimer() { return timer; } + @Override public boolean isRenderable() { return true; // Doesn't really matter if true or false. Either way - // RenderManager won't render anything. + // RenderManager won't render anything. } @Override 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"); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java index 76b3fa1463..f220c50b98 100644 --- a/jme3-core/src/main/java/com/jme3/system/NullRenderer.java +++ b/jme3-core/src/main/java/com/jme3/system/NullRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,14 +39,18 @@ import com.jme3.renderer.Limits; import com.jme3.renderer.Renderer; import com.jme3.renderer.Statistics; +import com.jme3.renderer.TextureUnitException; +import com.jme3.renderer.opengl.GLFence; import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; -import com.jme3.shader.BufferObject; import com.jme3.shader.Shader; import com.jme3.shader.Shader.ShaderSource; +import com.jme3.shader.bufferobject.BufferObject; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture; +import com.jme3.texture.TextureImage; +import com.jme3.util.NativeObject; import java.nio.ByteBuffer; import java.util.EnumMap; @@ -58,6 +62,7 @@ public class NullRenderer implements Renderer { private final EnumMap limits = new EnumMap<>(Limits.class); private final Statistics stats = new Statistics(); + @Override public void initialize() { for (Limits limit : Limits.values()) { limits.put(limit, Integer.MAX_VALUE); @@ -69,29 +74,37 @@ public EnumMap getLimits() { return limits; } + @Override public EnumSet getCaps() { return caps; } + @Override public Statistics getStatistics() { return stats; } + @Override public void invalidateState(){ } + @Override public void clearBuffers(boolean color, boolean depth, boolean stencil) { } + @Override public void setBackgroundColor(ColorRGBA color) { } + @Override public void applyRenderState(RenderState state) { } + @Override public void setDepthRange(float start, float end) { } + @Override public void postFrame() { } @@ -101,57 +114,80 @@ public void setWorldMatrix(Matrix4f worldMatrix) { public void setViewProjectionMatrices(Matrix4f viewMatrix, Matrix4f projMatrix) { } + @Override public void setViewPort(int x, int y, int width, int height) { } + @Override public void setClipRect(int x, int y, int width, int height) { } + @Override public void clearClipRect() { } public void setLighting(LightList lights) { } + @Override public void setShader(Shader shader) { } + @Override public void deleteShader(Shader shader) { } + @Override public void deleteShaderSource(ShaderSource source) { } public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst) { } + @Override public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyDepth) { } + + @Override + public void copyFrameBuffer(FrameBuffer src, FrameBuffer dst, boolean copyColor, boolean copyDepth) { + } + + @Override public void setMainFrameBufferOverride(FrameBuffer fb) { } + @Override public void setFrameBuffer(FrameBuffer fb) { } + @Override public void readFrameBuffer(FrameBuffer fb, ByteBuffer byteBuf) { } + @Override public void deleteFrameBuffer(FrameBuffer fb) { } - public void setTexture(int unit, Texture tex) { + @Override + public void setTexture(int unit, Texture tex) throws TextureUnitException { + // do nothing + } + + @Override + public void setTextureImage(int unit, TextureImage tex) throws TextureUnitException { + // do nothing } + @Override public void modifyTexture(Texture tex, Image pixels, int x, int y) { } + @Override public void updateBufferData(VertexBuffer vb) { } @Override - public void updateBufferData(BufferObject bo) { - } public void deleteBuffer(VertexBuffer vb) { } @@ -160,24 +196,31 @@ public void deleteBuffer(BufferObject bo) { } + @Override public void renderMesh(Mesh mesh, int lod, int count, VertexBuffer[] instanceData) { } + @Override public void resetGLObjects() { } + @Override public void cleanup() { } + @Override public void deleteImage(Image image) { } + @Override public void setAlphaToCoverage(boolean value) { } + @Override public void setMainFrameBufferSrgb(boolean srgb) { } + @Override public void setLinearizeSrgbImages(boolean linearize) { } @@ -206,6 +249,7 @@ public boolean isTaskResultAvailable(int taskId) { return false; } + @Override public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image.Format format) { } @@ -213,6 +257,16 @@ public void readFrameBufferWithFormat(FrameBuffer fb, ByteBuffer byteBuf, Image. public void setDefaultAnisotropicFilter(int level) { } + /** + * Determine the maximum allowed width for lines. + * + * @return the maximum width (in pixels) + */ + @Override + public float getMaxLineWidth() { + return Float.MAX_VALUE; + } + @Override public boolean getAlphaToCoverage() { return false; @@ -222,4 +276,60 @@ public boolean getAlphaToCoverage() { public int getDefaultAnisotropicFilter() { return 0; } + + /** + * Test whether images with the sRGB flag will be linearized when read by a + * shader. + * + * @return true for linearization, false for no linearization + */ + @Override + public boolean isLinearizeSrgbImages() { + return false; + } + + /** + * Test whether colors rendered to the main framebuffer undergo + * linear-to-sRGB conversion. + * + * @return true for conversion, false for no conversion + */ + @Override + public boolean isMainFrameBufferSrgb() { + return false; + } + + @Override + public FrameBuffer getCurrentFrameBuffer() { + return null; + } + + public void updateShaderStorageBufferObjectData(BufferObject bo) { + + } + + @Override + public void updateUniformBufferObjectData(BufferObject bo) { + + } + + @Override + public void setShaderStorageBufferObject(int bindingPoint, BufferObject bufferObject) { + + } + + @Override + public void setUniformBufferObject(int bindingPoint, BufferObject bufferObject) { + + } + + @Override + public void deleteFence(GLFence fence) { + + } + + @Override + public void registerNativeObject(NativeObject nativeObject) { + + } } diff --git a/jme3-core/src/main/java/com/jme3/system/Platform.java b/jme3-core/src/main/java/com/jme3/system/Platform.java index 1b80ff2e25..64595a9106 100644 --- a/jme3-core/src/main/java/com/jme3/system/Platform.java +++ b/jme3-core/src/main/java/com/jme3/system/Platform.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 @@ -31,103 +31,180 @@ */ package com.jme3.system; +/** + * Enumerate known operating system/architecture pairs. + */ public enum Platform { /** - * Microsoft Windows 32 bit + * Microsoft Windows 32-bit AMD/Intel */ - Windows32, - + Windows32(Os.Windows), + /** - * Microsoft Windows 64 bit + * Microsoft Windows 64-bit AMD/Intel */ - Windows64(true), - + Windows64(Os.Windows, true), + + /** + * Microsoft Windows 32-bit ARM + */ + Windows_ARM32(Os.Windows), + + /** + * Microsoft Windows 64-bit ARM + */ + Windows_ARM64(Os.Windows, true), + /** * Linux 32-bit Intel */ - Linux32, - + Linux32(Os.Linux), + /** * Linux 64-bit Intel */ - Linux64(true), - + Linux64(Os.Linux, true), + /** * Linux 32-bit ARM */ - Linux_ARM32, - + Linux_ARM32(Os.Linux), + /** * Linux 64-bit ARM */ - Linux_ARM64(true), - + Linux_ARM64(Os.Linux, true), + /** * Apple Mac OS X 32-bit Intel */ - MacOSX32, - + MacOSX32(Os.MacOS), + /** * Apple Mac OS X 64-bit Intel */ - MacOSX64(true), - + MacOSX64(Os.MacOS, true), + + /** + * Apple Mac OS X 64-bit ARM + */ + MacOSX_ARM64(Os.MacOS, true), + /** * Apple Mac OS X 32 bit PowerPC */ - MacOSX_PPC32, - + MacOSX_PPC32(Os.MacOS), + /** * Apple Mac OS X 64 bit PowerPC */ - MacOSX_PPC64(true), - + MacOSX_PPC64(Os.MacOS, true), + /** * Android ARM5 */ - Android_ARM5, - + Android_ARM5(Os.Android), + /** * Android ARM6 */ - Android_ARM6, + Android_ARM6(Os.Android), /** * Android ARM7 */ - Android_ARM7, + Android_ARM7(Os.Android), /** * Android ARM8 */ - Android_ARM8, + Android_ARM8(Os.Android), /** * Android x86 */ - Android_X86, - - iOS_X86, - - iOS_ARM, - + Android_X86(Os.Android), + + /** + * iOS on x86 + */ + iOS_X86(Os.iOS), + + /** + * iOS on ARM + */ + iOS_ARM(Os.iOS), + /** * Android running on unknown platform (could be x86 or mips for example). */ - Android_Other; + Android_Other(Os.Android), - private final boolean is64bit; + /** + * Generic web platform on unknown architecture + */ + Web(Os.Web, true) // assume always 64-bit, it shouldn't matter for web + ; + + /** + * Enumerate generic names of operating systems + */ + public enum Os { + /** + * Linux operating systems + */ + Linux, + /** + * Microsoft Windows operating systems + */ + Windows, + /** + * iOS operating systems + */ + iOS, + /** + * macOS operating systems + */ + MacOS, + /** + * Android operating systems + */ + Android, + /** + * Generic web platform + */ + Web + } + + private final boolean is64bit; + private final Os os; + + /** + * Test for a 64-bit address space. + * + * @return true if 64 bits, otherwise false + */ public boolean is64Bit() { return is64bit; } - - private Platform(boolean is64bit) { + + /** + * Returns the operating system of this platform. + * + * @return the generic name of the operating system of this platform + */ + public Os getOs() { + return os; + } + + private Platform(Os os, boolean is64bit) { + this.os = os; this.is64bit = is64bit; } - - private Platform() { - this(false); + + private Platform(Os os) { + this(os, false); } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/java/com/jme3/system/SystemListener.java b/jme3-core/src/main/java/com/jme3/system/SystemListener.java index 132f475234..f6145c5783 100644 --- a/jme3-core/src/main/java/com/jme3/system/SystemListener.java +++ b/jme3-core/src/main/java/com/jme3/system/SystemListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,11 +46,21 @@ public interface SystemListener { /** * Called to notify the application that the resolution has changed. - * @param width - * @param height + * @param width the new width of the display (in pixels, ≥0) + * @param height the new height of the display (in pixels, ≥0) */ public void reshape(int width, int height); + /** + * Called to notify the application that the scale has changed. + * @param x the new horizontal scale of the display + * @param y the new vertical scale of the display + */ + public default void rescale(float x, float y){ + + } + + /** * Callback to update the application state, and render the scene * to the back buffer. @@ -60,7 +70,7 @@ public interface SystemListener { /** * Called when the user requests to close the application. This * could happen when he clicks the X button on the window, presses - * the Alt-F4 combination, attempts to shutdown the process from + * the Alt-F4 combination, attempts to shut down the process from * the task manager, or presses ESC. * @param esc If true, the user pressed ESC to close the application. */ diff --git a/jme3-core/src/main/java/com/jme3/system/package-info.java b/jme3-core/src/main/java/com/jme3/system/package-info.java new file mode 100644 index 0000000000..c755d2046f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/system/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * interface with the underlying operating system + */ +package com.jme3.system; 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 d5684e8854..f3cc721df9 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,38 +41,38 @@ *

                * FrameBuffers are rendering surfaces allowing * off-screen rendering and render-to-texture functionality. - * Instead of the scene rendering to the screen, it is rendered into the + * Instead of the scene rendering to the screen, it is rendered into the * FrameBuffer, the result can be either a texture or a buffer. *

                - * A FrameBuffer supports two methods of rendering, - * using a {@link Texture} or using a buffer. + * A FrameBuffer supports two methods of rendering, + * using a {@link Texture} or using a buffer. * When using a texture, the result of the rendering will be rendered * onto the texture, after which the texture can be placed on an object * and rendered as if the texture was uploaded from disk. - * When using a buffer, the result is rendered onto + * When using a buffer, the result is rendered onto * a buffer located on the GPU, the data of this buffer is not accessible * to the user. buffers are useful if one * wishes to retrieve only the color content of the scene, but still desires - * depth testing (which requires a depth buffer). + * depth testing (which requires a depth buffer). * Buffers can be copied to other framebuffers - * including the main screen, by using - * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer, boolean)}. - * The content of a {@link RenderBuffer} can be retrieved by using + * including the main screen, by using + * {@link Renderer#copyFrameBuffer(com.jme3.texture.FrameBuffer, com.jme3.texture.FrameBuffer, boolean, boolean)}. + * The content of a {@link RenderBuffer} can be retrieved by using * {@link Renderer#readFrameBuffer(com.jme3.texture.FrameBuffer, java.nio.ByteBuffer) }. *

                - * FrameBuffers have several attachment points, there are - * several color attachment points and a single depth + * FrameBuffers have several attachment points, there are + * several color attachment points and a single depth * attachment point. * The color attachment points support image formats such as * {@link Format#RGBA8}, allowing rendering the color content of the scene. - * The depth attachment point requires a depth image format. - * - * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) - * + * The depth attachment point requires a depth image format. + * + * @see Renderer#setFrameBuffer(com.jme3.texture.FrameBuffer) + * * @author Kirill Vainer */ public class FrameBuffer extends NativeObject { - + public static final int SLOT_UNDEF = -1; public static final int SLOT_DEPTH = -100; public static final int SLOT_DEPTH_STENCIL = -101; @@ -80,17 +80,19 @@ public class FrameBuffer extends NativeObject { private int width = 0; private int height = 0; private int samples = 1; - private ArrayList colorBufs = new ArrayList(); + private final ArrayList colorBufs = new ArrayList<>(); private RenderBuffer depthBuf = null; private int colorBufIndex = 0; private boolean srgb; + private String name; + private Boolean mipMapsGenerationHint = null; /** - * RenderBuffer represents either a texture or a + * RenderBuffer represents either a texture or a * buffer that will be rendered to. RenderBuffers * are attached to an attachment slot on a FrameBuffer. */ - public class RenderBuffer { + public static class RenderBuffer { Texture tex; Image.Format format; @@ -98,7 +100,13 @@ public class RenderBuffer { int slot = SLOT_UNDEF; int face = -1; int layer = -1; - + int level = 0; + + + public int getLevel() { + return this.level; + } + /** * @return The image format of the render buffer. */ @@ -110,12 +118,13 @@ public Format getFormat() { * @return The texture to render to for this RenderBuffer * or null if content should be rendered into a buffer. */ - public Texture getTexture(){ + public Texture getTexture() { return tex; } /** * Do not use. + * @return the buffer's ID */ public int getId() { return id; @@ -123,41 +132,45 @@ public int getId() { /** * Do not use. + * + * @param id the desired ID */ - public void setId(int id){ + public void setId(int id) { this.id = id; } /** * Do not use. + * + * @return the slot code, such as SLOT_DEPTH_STENCIL */ public int getSlot() { return slot; } - + public int getFace() { return face; } - public void resetObject(){ + public void resetObject() { id = -1; } - public RenderBuffer createDestructableClone(){ - if (tex != null){ + public RenderBuffer createDestructableClone() { + if (tex != null) { return null; - }else{ - RenderBuffer destructClone = new RenderBuffer(); + } else { + RenderBuffer destructClone = new RenderBuffer(); destructClone.id = id; return destructClone; } } @Override - public String toString(){ - if (tex != null){ + public String toString() { + if (tex != null) { return "TextureTarget[format=" + format + "]"; - }else{ + } else { return "BufferTarget[format=" + format + "]"; } } @@ -166,6 +179,174 @@ public int getLayer() { return this.layer; } } + + public static class FrameBufferTextureTarget extends RenderBuffer { + private FrameBufferTextureTarget(){} + void setTexture(Texture tx){ + this.tex=tx; + this.format=tx.getImage().getFormat(); + } + + void setFormat(Format f){ + this.format=f; + } + + public FrameBufferTextureTarget layer(int i){ + this.layer=i; + return this; + } + + public FrameBufferTextureTarget level(int i){ + this.level=i; + return this; + } + + public FrameBufferTextureTarget face(TextureCubeMap.Face f){ + return face(f.ordinal()); + } + + public FrameBufferTextureTarget face(int f){ + this.face=f; + return this; + } + + } + + public static class FrameBufferBufferTarget extends RenderBuffer { + private FrameBufferBufferTarget(){} + void setFormat(Format f){ + this.format=f; + } + } + + public static class FrameBufferTarget { + private FrameBufferTarget(){} + public static FrameBufferTextureTarget newTarget(Texture tx){ + FrameBufferTextureTarget t=new FrameBufferTextureTarget(); + t.setTexture(tx); + return t; + } + + public static FrameBufferBufferTarget newTarget(Format format){ + FrameBufferBufferTarget t=new FrameBufferBufferTarget(); + t.setFormat(format); + return t; + } + + /** + * Creates a frame buffer texture and sets the face position by using the face parameter. It uses + * {@link TextureCubeMap} ordinal number for the face position. + * + * @param tx texture to add to the frame buffer + * @param face face to add to the color buffer to + * @return FrameBufferTexture Target + */ + public static FrameBufferTextureTarget newTarget(Texture tx, TextureCubeMap.Face face) { + FrameBufferTextureTarget t = new FrameBufferTextureTarget(); + t.face = face.ordinal(); + t.setTexture(tx); + return t; + } + } + + /** + * A private constructor to inhibit instantiation of this class. + */ + private FrameBuffer() { + } + + public void addColorTarget(FrameBufferBufferTarget colorBuf){ + colorBuf.slot=colorBufs.size(); + colorBufs.add(colorBuf); + } + + public void addColorTarget(FrameBufferTextureTarget colorBuf){ + // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. + colorBuf.slot=colorBufs.size(); + colorBufs.add(colorBuf); + } + + /** + * Replaces the color target at the index. + *

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

                + * Color targets above the removed target will have their + * slot indices shifted accordingly. + * + * @param i + */ + public void removeColorTarget(int i) { + colorBufs.remove(i); + for (; i < colorBufs.size(); i++) { + colorBufs.get(i).slot = i; + } + } + + /** + * Adds a texture to one of the color Buffers Array. It uses {@link TextureCubeMap} ordinal number for the + * position in the color buffer ArrayList. + * + * @param colorBuf texture to add to the color Buffer + * @param face position to add to the color buffer + */ + public void addColorTarget(FrameBufferTextureTarget colorBuf, TextureCubeMap.Face face) { + // checkSetTexture(colorBuf.getTexture(), false); // TODO: this won't work for levels. + colorBuf.slot = colorBufs.size(); + colorBuf.face = face.ordinal(); + colorBufs.add(colorBuf); + } + + public void setDepthTarget(FrameBufferBufferTarget depthBuf){ + if (!depthBuf.getFormat().isDepthFormat()) + throw new IllegalArgumentException("Depth buffer format must be depth."); + this.depthBuf = depthBuf; + this.depthBuf.slot = this.depthBuf.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + } + + public void setDepthTarget(FrameBufferTextureTarget depthBuf){ + checkSetTexture(depthBuf.getTexture(), true); + this.depthBuf = depthBuf; + this.depthBuf.slot = depthBuf.getTexture().getImage().getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + } + + public int getNumColorTargets(){ + return colorBufs.size(); + } + + public RenderBuffer getColorTarget(int index){ + return colorBufs.get(index); + } + + public RenderBuffer getColorTarget() { + if (colorBufs.isEmpty()) + return null; + if (colorBufIndex<0 || colorBufIndex>=colorBufs.size()) { + return colorBufs.get(0); + } + return colorBufs.get(colorBufIndex); + } + + public RenderBuffer getDepthTarget() { + return depthBuf; + } + + /** *

                @@ -173,28 +354,29 @@ public int getLayer() { * of samples. If any textures are attached to this FrameBuffer, then * they must have the same number of samples as given in this constructor. *

                - * Note that if the {@link Renderer} does not expose the + * Note that if the {@link Renderer} does not expose the * {@link Caps#NonPowerOfTwoTextures}, then an exception will be thrown * if the width and height arguments are not power of two. - * + * * @param width The width to use * @param height The height to use * @param samples The number of samples to use for a multisampled - * framebuffer, or 1 if the framebuffer should be singlesampled. - * + * framebuffer, or 1 if the framebuffer should be single-sampled. + * * @throws IllegalArgumentException If width or height are not positive. */ - public FrameBuffer(int width, int height, int samples){ + public FrameBuffer(int width, int height, int samples) { super(); - if (width <= 0 || height <= 0) - throw new IllegalArgumentException("FrameBuffer must have valid size."); + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("FrameBuffer must have valid size."); + } this.width = width; this.height = height; this.samples = samples == 0 ? 1 : samples; } - protected FrameBuffer(FrameBuffer src){ + protected FrameBuffer(FrameBuffer src) { super(src.id); /* for (RenderBuffer renderBuf : src.colorBufs){ @@ -209,60 +391,73 @@ protected FrameBuffer(FrameBuffer src){ /** * Enables the use of a depth buffer for this FrameBuffer. - * + * * @param format The format to use for the depth buffer. * @throws IllegalArgumentException If format is not a depth format. + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferBufferTarget)} */ - public void setDepthBuffer(Image.Format format){ - if (id != -1) + @Deprecated + public void setDepthBuffer(Image.Format format) { + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } - if (!format.isDepthFormat()) + if (!format.isDepthFormat()) { throw new IllegalArgumentException("Depth buffer format must be depth."); - + } + depthBuf = new RenderBuffer(); - depthBuf.slot = format.isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + depthBuf.slot = format.isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; depthBuf.format = format; } /** * Enables the use of a color buffer for this FrameBuffer. - * + * * @param format The format to use for the color buffer. * @throws IllegalArgumentException If format is not a color format. + * @deprecated Use addColorTarget */ - public void setColorBuffer(Image.Format format){ - if (id != -1) + @Deprecated + public void setColorBuffer(Image.Format format) { + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } - if (format.isDepthFormat()) + if (format.isDepthFormat()) { throw new IllegalArgumentException("Color buffer format must be color/luminance."); - + } + RenderBuffer colorBuf = new RenderBuffer(); colorBuf.slot = 0; colorBuf.format = format; - + colorBufs.clear(); colorBufs.add(colorBuf); } - private void checkSetTexture(Texture tex, boolean depth){ + private void checkSetTexture(Texture tex, boolean depth) { Image img = tex.getImage(); - if (img == null) + if (img == null) { throw new IllegalArgumentException("Texture not initialized with RTT."); + } - if (depth && !img.getFormat().isDepthFormat()) + if (depth && !img.getFormat().isDepthFormat()) { throw new IllegalArgumentException("Texture image format must be depth."); - else if (!depth && img.getFormat().isDepthFormat()) + } else if (!depth && img.getFormat().isDepthFormat()) { throw new IllegalArgumentException("Texture image format must be color/luminance."); + } // check that resolution matches texture resolution - if (width != img.getWidth() || height != img.getHeight()) - throw new IllegalArgumentException("Texture image resolution " + - "must match FB resolution"); + if (width != img.getWidth() || height != img.getHeight()) { + throw new IllegalArgumentException("Texture image resolution " + + "must match FB resolution"); + } - if (samples != tex.getImage().getMultiSamples()) + if (samples != tex.getImage().getMultiSamples()) { throw new IllegalStateException("Texture samples must match framebuffer samples"); + } } /** @@ -270,38 +465,43 @@ else if (!depth && img.getFormat().isDepthFormat()) * will be able to write several results into the renderbuffers * by using the gl_FragData array. Every slot in that * array maps into a color buffer attached to this framebuffer. - * + * * @param enabled True to enable MRT (multiple rendering targets). */ - public void setMultiTarget(boolean enabled){ - if (enabled) colorBufIndex = -1; - else colorBufIndex = 0; + public void setMultiTarget(boolean enabled) { + if (enabled) { + colorBufIndex = -1; + } else { + colorBufIndex = 0; + } } /** * @return True if MRT (multiple rendering targets) is enabled. * @see FrameBuffer#setMultiTarget(boolean) */ - public boolean isMultiTarget(){ + public boolean isMultiTarget() { return colorBufIndex == -1; } - + /** * If MRT is not enabled ({@link FrameBuffer#setMultiTarget(boolean) } is false) * then this specifies the color target to which the scene should be rendered. *

                * By default the value is 0. - * + * * @param index The color attachment index. * @throws IllegalArgumentException If index is negative or doesn't map * to any attachment on this framebuffer. */ - public void setTargetIndex(int index){ - if (index < 0 || index >= 16) + public void setTargetIndex(int index) { + if (index < 0 || index >= 16) { throw new IllegalArgumentException("Target index must be between 0 and 16"); + } - if (colorBufs.size() < index) + if (colorBufs.size() < index) { throw new IllegalArgumentException("The target at " + index + " is not set!"); + } colorBufIndex = index; setUpdateNeeded(); @@ -309,10 +509,10 @@ public void setTargetIndex(int index){ /** * @return The color target to which the scene should be rendered. - * - * @see FrameBuffer#setTargetIndex(int) + * + * @see FrameBuffer#setTargetIndex(int) */ - public int getTargetIndex(){ + public int getTargetIndex() { return colorBufIndex; } @@ -321,27 +521,32 @@ public int getTargetIndex(){ * This automatically clears all existing textures added previously * with {@link FrameBuffer#addColorTexture } and adds this texture as the * only target. - * + * * @param tex The color texture to set. + * @deprecated Use addColorTarget */ - public void setColorTexture(Texture2D tex){ + @Deprecated + public void setColorTexture(Texture2D tex) { clearColorTargets(); addColorTexture(tex); } - + /** * Set the color texture array to use for this framebuffer. * This automatically clears all existing textures added previously * with {@link FrameBuffer#addColorTexture } and adds this texture as the * only target. - * + * * @param tex The color texture array to set. + * @param layer (default=-1) + * @deprecated Use addColorTarget */ - public void setColorTexture(TextureArray tex, int layer){ + @Deprecated + public void setColorTexture(TextureArray tex, int layer) { clearColorTargets(); addColorTexture(tex, layer); } - + /** * Set the color texture to use for this framebuffer. * This automatically clears all existing textures added previously @@ -350,7 +555,9 @@ public void setColorTexture(TextureArray tex, int layer){ * * @param tex The cube-map texture to set. * @param face The face of the cube-map to render to. + * @deprecated Use addColorTarget */ + @Deprecated public void setColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { clearColorTargets(); addColorTexture(tex, face); @@ -359,47 +566,54 @@ public void setColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { /** * Clears all color targets that were set or added previously. */ - public void clearColorTargets(){ + public void clearColorTargets() { colorBufs.clear(); } - /** + /** * Add a color buffer without a texture bound to it. * If MRT is enabled, then each subsequently added texture or buffer can be * rendered to through a shader that writes to the array gl_FragData. * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } * is rendered to by the shader. - * + * * @param format the format of the color buffer - * @see #addColorTexture(com.jme3.texture.Texture2D) + * @see #addColorTexture(com.jme3.texture.Texture2D) + * @deprecated Use addColorTarget */ - public void addColorBuffer(Image.Format format){ - if (id != -1) + @Deprecated + public void addColorBuffer(Image.Format format) { + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } - if (format.isDepthFormat()) + if (format.isDepthFormat()) { throw new IllegalArgumentException("Color buffer format must be color/luminance."); - + } + RenderBuffer colorBuf = new RenderBuffer(); colorBuf.slot = colorBufs.size(); colorBuf.format = format; - + colorBufs.add(colorBuf); } - + /** * Add a color texture to use for this framebuffer. * If MRT is enabled, then each subsequently added texture can be * rendered to through a shader that writes to the array gl_FragData. * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } * is rendered to by the shader. - * + * * @param tex The texture to add. - * @see #addColorBuffer(com.jme3.texture.Image.Format) + * @see #addColorBuffer(com.jme3.texture.Image.Format) + * @deprecated Use addColorTarget */ + @Deprecated public void addColorTexture(Texture2D tex) { - if (id != -1) + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } Image img = tex.getImage(); checkSetTexture(tex, false); @@ -411,19 +625,23 @@ public void addColorTexture(Texture2D tex) { colorBufs.add(colorBuf); } - + /** * Add a color texture array to use for this framebuffer. * If MRT is enabled, then each subsequently added texture can be * rendered to through a shader that writes to the array gl_FragData. * If MRT is not enabled, then the index set with {@link FrameBuffer#setTargetIndex(int) } * is rendered to by the shader. - * + * * @param tex The texture array to add. + * @param layer (default=-1) + * @deprecated Use addColorTarget */ + @Deprecated public void addColorTexture(TextureArray tex, int layer) { - if (id != -1) + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } Image img = tex.getImage(); checkSetTexture(tex, false); @@ -436,8 +654,8 @@ public void addColorTexture(TextureArray tex, int layer) { colorBufs.add(colorBuf); } - - /** + + /** * Add a color texture to use for this framebuffer. * If MRT is enabled, then each subsequently added texture can be * rendered to through a shader that writes to the array gl_FragData. @@ -446,10 +664,13 @@ public void addColorTexture(TextureArray tex, int layer) { * * @param tex The cube-map texture to add. * @param face The face of the cube-map to render to. + * @deprecated Use addColorTarget */ + @Deprecated public void addColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { - if (id != -1) + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } Image img = tex.getImage(); checkSetTexture(tex, false); @@ -465,68 +686,91 @@ public void addColorTexture(TextureCubeMap tex, TextureCubeMap.Face face) { /** * Set the depth texture to use for this framebuffer. - * + * * @param tex The color texture to set. + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferTextureTarget)} */ - public void setDepthTexture(Texture2D tex){ - if (id != -1) + @Deprecated + public void setDepthTexture(Texture2D tex) { + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } Image img = tex.getImage(); checkSetTexture(tex, true); - + depthBuf = new RenderBuffer(); - depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; depthBuf.tex = tex; depthBuf.format = img.getFormat(); } - public void setDepthTexture(TextureArray tex, int layer){ - if (id != -1) + + /** + * + * @param tex the TextureArray to apply + * @param layer (default=-1) + * @deprecated Use + * {@link #setDepthTarget(com.jme3.texture.FrameBuffer.FrameBufferTextureTarget)} + */ + @Deprecated + public void setDepthTexture(TextureArray tex, int layer) { + if (id != -1) { throw new UnsupportedOperationException("FrameBuffer already initialized."); + } Image img = tex.getImage(); checkSetTexture(tex, true); - + depthBuf = new RenderBuffer(); - depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; + depthBuf.slot = img.getFormat().isDepthStencilFormat() ? SLOT_DEPTH_STENCIL : SLOT_DEPTH; depthBuf.tex = tex; depthBuf.format = img.getFormat(); depthBuf.layer = layer; } - + /** - * @return The number of color buffers attached to this texture. + * @return The number of color buffers attached to this texture. + * @deprecated Use getNumColorTargets */ - public int getNumColorBuffers(){ + @Deprecated + public int getNumColorBuffers() { return colorBufs.size(); } /** - * @param index + * @param index the zero-base index (≥0) * @return The color buffer at the given index. + * @deprecated Use getColorTarget(int) */ - public RenderBuffer getColorBuffer(int index){ + @Deprecated + public RenderBuffer getColorBuffer(int index) { return colorBufs.get(index); } /** * @return The color buffer with the index set by {@link #setTargetIndex(int)}, or null * if no color buffers are attached. - * If MRT is disabled, the first color buffer is returned. + * If MRT is disabled, the first color buffer is returned. + * @deprecated Use getColorTarget() */ + @Deprecated public RenderBuffer getColorBuffer() { - if (colorBufs.isEmpty()) + if (colorBufs.isEmpty()) { return null; - if (colorBufIndex<0 || colorBufIndex>=colorBufs.size()) { - return colorBufs.get(0); - } + } + if (colorBufIndex < 0 || colorBufIndex >= colorBufs.size()) { + return colorBufs.get(0); + } return colorBufs.get(colorBufIndex); } /** * @return The depth buffer attached to this FrameBuffer, or null * if no depth buffer is attached + * @deprecated Use getDepthTarget() */ + @Deprecated public RenderBuffer getDepthBuffer() { return depthBuf; } @@ -547,23 +791,24 @@ public int getWidth() { /** * @return The number of samples when using a multisample framebuffer, or - * 1 if this is a singlesampled framebuffer. + * 1 if this is a single-sampled framebuffer. */ public int getSamples() { return samples; } @Override - public String toString(){ + public String toString() { StringBuilder sb = new StringBuilder(); String mrtStr = colorBufIndex >= 0 ? "" + colorBufIndex : "mrt"; sb.append("FrameBuffer[format=").append(width).append("x").append(height) - .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n"); - if (depthBuf != null) + .append("x").append(samples).append(", drawBuf=").append(mrtStr).append("]\n"); + if (depthBuf != null) { sb.append("Depth => ").append(depthBuf).append("\n"); - for (RenderBuffer colorBuf : colorBufs){ + } + for (RenderBuffer colorBuf : colorBufs) { sb.append("Color(").append(colorBuf.slot) - .append(") => ").append(colorBuf).append("\n"); + .append(") => ").append(colorBuf).append("\n"); } return sb.toString(); } @@ -571,31 +816,33 @@ public String toString(){ @Override public void resetObject() { this.id = -1; - + for (int i = 0; i < colorBufs.size(); i++) { colorBufs.get(i).resetObject(); } - - if (depthBuf != null) + + if (depthBuf != null) { depthBuf.resetObject(); + } setUpdateNeeded(); } @Override public void deleteObject(Object rendererObject) { - ((Renderer)rendererObject).deleteFrameBuffer(this); + ((Renderer) rendererObject).deleteFrameBuffer(this); } - public NativeObject createDestructableClone(){ + @Override + public NativeObject createDestructableClone() { return new FrameBuffer(this); } - + @Override public long getUniqueId() { - return ((long)OBJTYPE_FRAMEBUFFER << 32) | ((long)id); + return ((long) OBJTYPE_FRAMEBUFFER << 32) | (0xffffffffL & (long) id); } - + /** * Specifies that the color values stored in this framebuffer are in SRGB * format. @@ -632,4 +879,23 @@ 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 + * @param v true to enable, null to use the default value for the renderer (default to null) + */ + public void setMipMapsGenerationHint(Boolean v) { + mipMapsGenerationHint = v; + } + + public Boolean getMipMapsGenerationHint() { + return mipMapsGenerationHint; + } } 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 f7cee1b55e..3159f0856a 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-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -116,7 +116,7 @@ public enum Format { /** * 8-bit blue, green, and red. */ - BGR8(24), // BGR and ABGR formats are often used on windows systems + BGR8(24), // BGR and ABGR formats are often used on Windows systems /** * 8-bit red, green, and blue. @@ -194,6 +194,26 @@ public enum Format { SIGNED_RGTC1(4,false,true, false), + /** + * BPTC compression BC6 signed float RGB + */ + BC6H_SF16(8, false, true, true), + + /** + * BPTC compression BC6 unsigned float RGB + */ + BC6H_UF16(8, false, true, true), + + /** + * BPTC compression BC7 RGBA + */ + BC7_UNORM(8, false, true, false), + + /** + * BPTC compression BC7 SRGB Alpha + */ + BC7_UNORM_SRGB(8, false, true, false), + /** * Luminance-Alpha Texture Compression. * @@ -266,7 +286,7 @@ public enum Format { * half-precision floating point red, green, and blue. * * Requires {@link Caps#FloatTexture}. - * May be supported for renderbuffers, but the OpenGL specification does not require it. + * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB16F(48,true), @@ -281,7 +301,7 @@ public enum Format { * single-precision floating point red, green, and blue. * * Requires {@link Caps#FloatTexture}. - * May be supported for renderbuffers, but the OpenGL specification does not require it. + * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB32F(96,true), @@ -310,39 +330,56 @@ public enum Format { * Requires {@link Caps#TextureCompressionETC1}. */ ETC1(4, false, true, false), + + /** + * Ericsson Texture Compression 2. Typically used on Android. + * Same as {@link #ETC1} but with alpha. + * + * Requires {@link Caps#TextureCompressionETC2}. + */ + ETC2(8, false, true, false), + + /** + * Ericsson Texture Compression 2. Typically used on Android. + * Same as {@link #ETC2} but alpha can be either 1 or 0. + * + * Requires {@link Caps#TextureCompressionETC2}. + */ + ETC2_ALPHA1(4, false, true, false), + /** - * 8 bit signed int red. + * 8-bit signed int red. * * Requires {@link Caps#IntegerTexture}. */ R8I(8), /** - * 8 bit unsigned int red. + * 8-bit unsigned int red. * * Requires {@link Caps#IntegerTexture}. */ R8UI(8), /** - * 16 bit signed int red. + * 16-bit signed int red. * * Requires {@link Caps#IntegerTexture}. */ R16I(16), /** - * 16 bit unsigned int red. + * 16-bit unsigned int red. * * Requires {@link Caps#IntegerTexture}. */ R16UI(16), /** - * 32 bit signed int red. + * 32-bit signed int red. * * Requires {@link Caps#IntegerTexture}. */ R32I(32), /** - * 32 bit unsigned int red. + * 32-bit unsigned int red. * * Requires {@link Caps#IntegerTexture}. */ @@ -350,44 +387,44 @@ public enum Format { /** - * 8 bit signed int red and green. + * 8-bit signed int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG8I(16), /** - * 8 bit unsigned int red and green. + * 8-bit unsigned int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG8UI(16), /** - * 16 bit signed int red and green. + * 16-bit signed int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG16I(32), /** - * 16 bit unsigned int red and green. + * 16-bit unsigned int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG16UI(32), /** - * 32 bit signed int red and green. + * 32-bit signed int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG32I(64), /** - * 32 bit unsigned int red and green. + * 32-bit unsigned int red and green. * * Requires {@link Caps#IntegerTexture}. */ RG32UI(64), /** - * 8 bit signed int red, green and blue. + * 8-bit signed int red, green and blue. * * Requires {@link Caps#IntegerTexture} to be supported for textures. * May be supported for renderbuffers, but the OpenGL specification does not require it. @@ -401,28 +438,28 @@ public enum Format { */ RGB8UI(24), /** - * 16 bit signed int red, green and blue. + * 16-bit signed int red, green and blue. * * Requires {@link Caps#IntegerTexture} to be supported for textures. * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB16I(48), /** - * 16 bit unsigned int red, green and blue. + * 16-bit unsigned int red, green and blue. * * Requires {@link Caps#IntegerTexture} to be supported for textures. * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB16UI(48), /** - * 32 bit signed int red, green and blue. + * 32-bit signed int red, green and blue. * * Requires {@link Caps#IntegerTexture} to be supported for textures. * May be supported for renderbuffers, but the OpenGL specification does not require it. */ RGB32I(96), /** - * 32 bit unsigned int red, green and blue. + * 32-bit unsigned int red, green and blue. * * Requires {@link Caps#IntegerTexture} to be supported for textures. * May be supported for renderbuffers, but the OpenGL specification does not require it. @@ -431,43 +468,43 @@ public enum Format { /** - * 8 bit signed int red, green, blue and alpha. + * 8-bit signed int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA8I(32), /** - * 8 bit unsigned int red, green, blue and alpha. + * 8-bit unsigned int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA8UI(32), /** - * 16 bit signed int red, green, blue and alpha. + * 16-bit signed int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA16I(64), /** - * 16 bit unsigned int red, green, blue and alpha. + * 16-bit unsigned int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA16UI(64), /** - * 32 bit signed int red, green, blue and alpha. + * 32-bit signed int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA32I(128), /** - * 32 bit unsigned int red, green, blue and alpha. + * 32-bit unsigned int red, green, blue and alpha. * * Requires {@link Caps#IntegerTexture}. */ RGBA32UI(128), - - /** + + /** * half-precision floating point red. * * Requires {@link Caps#FloatTexture}. @@ -578,7 +615,7 @@ public boolean isFloatingPont(){ /** * Internal use only. - * The renderer stores the texture state set from the last texture + * The renderer stores the texture state set from the last texture, * so it doesn't have to change it unless necessary. * * @return The image parameter state. @@ -678,7 +715,7 @@ public NativeObject createDestructableClone() { @Override public long getUniqueId() { - return ((long)OBJTYPE_TEXTURE << 32) | ((long)id); + return ((long)OBJTYPE_TEXTURE << 32) | (0xffffffffL & (long)id); } /** @@ -717,12 +754,14 @@ protected Image(int id){ * the width of the image. * @param height * the height of the image. + * @param depth + * the desired image depth * @param data * the image data. * @param mipMapSizes * the array of mipmap sizes, or null for no mipmaps. * @param colorSpace - * @see ColorSpace the colorSpace of the image + * the colorSpace of the image */ public Image(Format format, int width, int height, int depth, ArrayList data, int[] mipMapSizes, ColorSpace colorSpace) { @@ -749,12 +788,12 @@ public Image(Format format, int width, int height, int depth, ArrayList data, ColorSpace colorSpace) { this(format, width, height, depth, data, null, colorSpace); @@ -839,11 +882,11 @@ public Image(Format format, int width, int height, int depth, ArrayList 0"); + } - if (getData(0) != null) - throw new IllegalArgumentException("Cannot upload data as multisample texture"); + if (multiSamples > 1) { + if (getData(0) != null) { + throw new IllegalArgumentException("Cannot upload data as multisample texture"); + } - if (hasMipmaps()) - throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + if (hasMipmaps()) { + throw new IllegalArgumentException("Multisample textures do not support mipmaps"); + } + } this.multiSamples = multiSamples; } @@ -956,13 +1004,7 @@ public void setData(int index, ByteBuffer data) { } /** - * @deprecated This feature is no longer used by the engine - */ - @Deprecated - public void setEfficentData(Object efficientData){ - } - - /** + * @return null * @deprecated This feature is no longer used by the engine */ @Deprecated @@ -1037,14 +1079,14 @@ public void setWidth(int width) { * setFormat sets the image format for this image. * * @param format - * the image format. - * @throws NullPointerException + * the image format (not null) + * @throws IllegalArgumentException * if format is null * @see Format */ public void setFormat(Format format) { if (format == null) { - throw new NullPointerException("format may not be null."); + throw new IllegalArgumentException("format may not be null."); } this.format = format; @@ -1102,6 +1144,7 @@ public List getData() { * getData returns the data for this image. If the data is * undefined, null will be returned. * + * @param index index of the data buffer to access * @return the data for this image. */ public ByteBuffer getData(int index) { @@ -1136,11 +1179,11 @@ public int[] getMipMapSizes() { * contain any color space information and the most frequently used colors * space is sRGB * - * The material loader may override this attribute to Lineat if it determines that + * The material loader may override this attribute to Linear if it determines that * such conversion must not be performed, for example, when loading normal * maps. * - * @param colorSpace @see ColorSpace. Set to sRGB to enable srgb -> linear + * @param colorSpace Set to sRGB to enable srgb -> linear * conversion, Linear otherwise. * * @see Renderer#setLinearizeSrgbImages(boolean) @@ -1232,6 +1275,7 @@ public int hashCode() { return hash; } + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(format, "format", Format.RGBA8); @@ -1244,17 +1288,17 @@ public void write(JmeExporter e) throws IOException { capsule.write(colorSpace, "colorSpace", null); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); format = capsule.readEnum("format", Format.class, Format.RGBA8); width = capsule.readInt("width", 0); height = capsule.readInt("height", 0); depth = capsule.readInt("depth", 0); mipMapSizes = capsule.readIntArray("mipMapSizes", null); multiSamples = capsule.readInt("multiSamples", 1); - data = (ArrayList) capsule.readByteBufferArrayList("data", null); + data = capsule.readByteBufferArrayList("data", null); colorSpace = capsule.readEnum("colorSpace", ColorSpace.class, null); - if (mipMapSizes != null) { needGeneratedMips = false; mipsWereGenerated = true; diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture.java b/jme3-core/src/main/java/com/jme3/texture/Texture.java index 65563928f6..3ba9b78128 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,14 +64,14 @@ public enum Type { * Two dimensional texture (default). A rectangle. */ TwoDimensional, - + /** - * An array of two dimensional textures. + * An array of two-dimensional textures. */ TwoDimensionalArray, /** - * Three dimensional texture. (A cube) + * Three-dimensional texture. (A cube) */ ThreeDimensional, @@ -180,17 +180,17 @@ public enum WrapMode { * Only the fractional portion of the coordinate is considered. */ Repeat, - + /** * Only the fractional portion of the coordinate is considered, but if * the integer portion is odd, we'll use 1 - the fractional portion. * (Introduced around OpenGL1.4) Falls back on Repeat if not supported. */ MirroredRepeat, - + /** * coordinate will be clamped to [0,1] - * + * * @deprecated Not supported by OpenGL 3 */ @Deprecated @@ -203,19 +203,19 @@ public enum WrapMode { * is the size of the one-, two-, or three-dimensional texture image in * the direction of wrapping. (Introduced after OpenGL1.4) Falls back on * Clamp if not supported. - * + * * @deprecated Not supported by OpenGL 3 */ @Deprecated MirrorClamp, - + /** * coordinate will be clamped to the range [-1/(2N), 1 + 1/(2N)] where N * is the size of the texture in the direction of clamping. Falls back * on Clamp if not supported. - * + * * @deprecated Not supported by OpenGL 3 or OpenGL ES 2 - */ + */ @Deprecated BorderClamp, /** @@ -224,9 +224,9 @@ public enum WrapMode { * computes: * mirrorClampToBorder(f) = min(1+1/(2*N), max(1/(2*N), abs(f))) * where N is the size of the one-, two-, or three-dimensional texture - * image in the direction of wrapping." (Introduced after OpenGL1.4) + * image in the direction of wrapping. (Introduced after OpenGL1.4) * Falls back on BorderClamp if not supported. - * + * * @deprecated Not supported by OpenGL 3 */ @Deprecated @@ -237,7 +237,7 @@ public enum WrapMode { * on Clamp if not supported. */ EdgeClamp, - + /** * mirrors and clamps to edge the texture coordinate, where mirroring * and clamping to edge a value f computes: @@ -245,7 +245,7 @@ public enum WrapMode { * where N is the size of the one-, two-, or three-dimensional texture * image in the direction of wrapping. (Introduced after OpenGL1.4) * Falls back on EdgeClamp if not supported. - * + * * @deprecated Not supported by OpenGL 3 */ @Deprecated @@ -270,7 +270,7 @@ public enum WrapAxis { /** * If this texture is a depth texture (the format is Depth*) then * this value may be used to compare the texture depth to the R texture - * coordinate. + * coordinate. */ public enum ShadowCompareMode { /** @@ -279,7 +279,7 @@ public enum ShadowCompareMode { */ Off, - /** + /** * {@code * Compares the 3rd texture coordinate R to the value * in this depth texture. If R <= texture value then result is 1.0, @@ -416,7 +416,7 @@ public void setShadowCompareMode(ShadowCompareMode compareMode){ */ public void setImage(Image image) { this.image = image; - + // Test if mipmap generation required. setMinFilter(getMinFilter()); } @@ -424,10 +424,12 @@ public void setImage(Image image) { /** * @param key The texture key that was used to load this texture */ + @Override public void setKey(AssetKey key){ this.key = (TextureKey) key; } + @Override public AssetKey getKey(){ return this.key; } @@ -447,7 +449,7 @@ public Image getImage() { * particular axis. * * @param axis - * the texture axis to define a wrapmode on. + * the texture axis to apply the wrap mode to. * @param mode * the wrap mode for the given axis of the texture. * @throws IllegalArgumentException @@ -489,7 +491,7 @@ public void setName(String name) { /** * @return the anisotropic filtering level for this texture. Default value - * is 0 (use value from config), + * is 0 (use value from config), * 1 means 1x (no anisotropy), 2 means x2, 4 is x4, etc. */ public int getAnisotropicFilter() { @@ -526,7 +528,7 @@ public boolean equals(Object obj) { return false; } final Texture other = (Texture) obj; - + // NOTE: Since images are generally considered unique assets in jME3, // using the image's equals() implementation is not necessary here // (would be too slow) @@ -565,8 +567,9 @@ public int hashCode() { /** Retrieve a basic clone of this Texture (ie, clone everything but the * image data, which is shared) * + * @param rVal storage for the clone (not null, modified) * @return Texture - * + * * @deprecated Use {@link Texture#clone()} instead. */ @Deprecated @@ -582,22 +585,24 @@ public Texture createSimpleClone(Texture rVal) { } /** + * @return a new Texture * @deprecated Use {@link Texture#clone()} instead. */ @Deprecated public abstract Texture createSimpleClone(); + @Override public void write(JmeExporter e) throws IOException { OutputCapsule capsule = e.getCapsule(this); capsule.write(name, "name", null); - + if (key == null){ // no texture key is set, try to save image instead then capsule.write(image, "image", null); }else{ capsule.write(key, "key", null); } - + capsule.write(anisotropicFilter, "anisotropicFilter", 1); capsule.write(minificationFilter, "minificationFilter", MinFilter.BilinearNoMipMaps); @@ -605,36 +610,38 @@ public void write(JmeExporter e) throws IOException { MagFilter.Bilinear); } - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + @Override + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); name = capsule.readString("name", null); key = (TextureKey) capsule.readSavable("key", null); - + // load texture from key, if available if (key != null) { // key is available, so try the texture from there. try { - Texture loadedTex = e.getAssetManager().loadTexture(key); + Texture loadedTex = importer.getAssetManager().loadTexture(key); image = loadedTex.getImage(); } catch (AssetNotFoundException ex){ Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot locate texture {0}", key); - image = PlaceholderAssets.getPlaceholderImage(e.getAssetManager()); + image = PlaceholderAssets.getPlaceholderImage(importer.getAssetManager()); } }else{ // no key is set on the texture. Attempt to load an embedded image image = (Image) capsule.readSavable("image", null); if (image == null){ - // TODO: what to print out here? the texture has no key or data, there's no useful information .. + // TODO: what to print out here? the texture has no key or data, there's no useful information .. // assume texture.name is set even though the key is null - Logger.getLogger(Texture.class.getName()).log(Level.SEVERE, "Cannot load embedded image {0}", toString() ); + Logger.getLogger(Texture.class.getName()) + .log(Level.SEVERE, "Cannot load embedded image {0}", toString()); } } - anisotropicFilter = capsule.readInt("anisotropicFilter", 1); - minificationFilter = capsule.readEnum("minificationFilter", + setAnisotropicFilter(capsule.readInt("anisotropicFilter", 1)); + setMinFilter(capsule.readEnum("minificationFilter", MinFilter.class, - MinFilter.BilinearNoMipMaps); - magnificationFilter = capsule.readEnum("magnificationFilter", - MagFilter.class, MagFilter.Bilinear); + MinFilter.BilinearNoMipMaps)); + setMagFilter(capsule.readEnum("magnificationFilter", + MagFilter.class, MagFilter.Bilinear)); } } diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture2D.java b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java index 441da13533..ab633aed49 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture2D.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture2D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,9 +72,9 @@ public Texture2D(Image img){ * * @see com.jme3.texture.FrameBuffer * - * @param width - * @param height - * @param format + * @param width the desired width (in pixels) + * @param height the desired height (in pixels) + * @param format the desired format */ public Texture2D(int width, int height, Image.Format format){ this(new Image(format, width, height, null, ColorSpace.Linear)); @@ -86,10 +86,10 @@ public Texture2D(int width, int height, Image.Format format){ * * @see com.jme3.texture.FrameBuffer * - * @param width - * @param height - * @param format - * @param numSamples + * @param width the desired width (in pixels) + * @param height the desired height (in pixels) + * @param numSamples the desired degree of multi-sampling (≥1) + * @param format the desired format */ public Texture2D(int width, int height, int numSamples, Image.Format format){ this(new Image(format, width, height, null, ColorSpace.Linear)); @@ -115,12 +115,13 @@ public Texture createSimpleClone(Texture rVal) { * particular axis. * * @param axis - * the texture axis to define a wrapmode on. + * the texture axis to apply the wrap mode to. * @param mode * the wrap mode for the given axis of the texture. * @throws IllegalArgumentException * if axis or mode are null */ + @Override public void setWrap(WrapAxis axis, WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -147,6 +148,7 @@ public void setWrap(WrapAxis axis, WrapMode mode) { * @throws IllegalArgumentException * if mode is null */ + @Override public void setWrap(WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -165,6 +167,7 @@ public void setWrap(WrapMode mode) { * @throws IllegalArgumentException * if axis is null */ + @Override public WrapMode getWrap(WrapAxis axis) { switch (axis) { case S: @@ -211,9 +214,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); } diff --git a/jme3-core/src/main/java/com/jme3/texture/Texture3D.java b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java index c94d7c3adb..bb12a858dd 100644 --- a/jme3-core/src/main/java/com/jme3/texture/Texture3D.java +++ b/jme3-core/src/main/java/com/jme3/texture/Texture3D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -73,10 +73,10 @@ public Texture3D(Image img) { * * @see com.jme3.texture.FrameBuffer * - * @param width - * @param height - * @param depth - * @param format + * @param width the desired width (in pixels) + * @param height the desired height (in pixels) + * @param depth the desired depth + * @param format the desired format */ public Texture3D(int width, int height, int depth, Image.Format format) { this(new Image(format, width, height, depth, null, ColorSpace.Linear)); @@ -88,10 +88,11 @@ public Texture3D(int width, int height, int depth, Image.Format format) { * * @see com.jme3.texture.FrameBuffer * - * @param width - * @param height - * @param format - * @param numSamples + * @param width the desired width (in pixels) + * @param height the desired height (in pixels) + * @param depth the desired depth + * @param numSamples the desired degree of multi-sampling (≥1) + * @param format the desired format */ public Texture3D(int width, int height, int depth, int numSamples, Image.Format format) { this(new Image(format, width, height, depth, null, ColorSpace.Linear)); @@ -118,12 +119,13 @@ public Texture createSimpleClone(Texture rVal) { * particular axis. * * @param axis - * the texture axis to define a wrapmode on. + * the texture axis to apply wrap mode to. * @param mode * the wrap mode for the given axis of the texture. * @throws IllegalArgumentException * if axis or mode are null */ + @Override public void setWrap(WrapAxis axis, WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -151,6 +153,7 @@ public void setWrap(WrapAxis axis, WrapMode mode) { * @throws IllegalArgumentException * if mode is null */ + @Override public void setWrap(WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -170,6 +173,7 @@ public void setWrap(WrapMode mode) { * @throws IllegalArgumentException * if axis is null */ + @Override public WrapMode getWrap(WrapAxis axis) { switch (axis) { case S: @@ -215,9 +219,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java index a7e24c35d7..e839399c33 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureArray.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureArray.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,13 @@ */ package com.jme3.texture; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.texture.Image.Format; import com.jme3.texture.image.ColorSpace; +import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -62,7 +67,8 @@ public TextureArray() { * Construct a TextureArray from the given list of images. * To check if a hardware supports TextureArray check : * renderManager.getRenderer().getCaps().contains(Caps.TextureArray) - * @param images + * + * @param images the images to use (not null) */ public TextureArray(List images) { super(); @@ -150,4 +156,44 @@ public void setWrap(WrapMode mode) { this.wrapS = mode; this.wrapT = mode; } -} \ No newline at end of file + + @Override + public void write(JmeExporter e) throws IOException { + super.write(e); + OutputCapsule capsule = e.getCapsule(this); + capsule.write(wrapS, "wrapS", WrapMode.EdgeClamp); + capsule.write(wrapT, "wrapT", WrapMode.EdgeClamp); + } + + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); + wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); + } + + @Override + public boolean equals(Object other) { + if (this == other) { + return true; + } + if (!(other instanceof TextureArray)) { + return false; + } + TextureArray that = (TextureArray) other; + if (this.getWrap(WrapAxis.S) != that.getWrap(WrapAxis.S)) + return false; + if (this.getWrap(WrapAxis.T) != that.getWrap(WrapAxis.T)) + return false; + return super.equals(other); + } + + @Override + public int hashCode() { + int hash = super.hashCode(); + hash = 79 * hash + (this.wrapS != null ? this.wrapS.hashCode() : 0); + hash = 79 * hash + (this.wrapT != null ? this.wrapT.hashCode() : 0); + return hash; + } +} diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java index f1a0f7d3bd..40e6c037c3 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureCubeMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,29 +70,30 @@ public enum Face { PositiveX, NegativeX, PositiveY, NegativeY, PositiveZ, NegativeZ; } - public TextureCubeMap(){ + public TextureCubeMap() { super(); } - public TextureCubeMap(Image img){ + public TextureCubeMap(Image img) { super(); setImage(img); } - - public TextureCubeMap(int width, int height, Image.Format format){ + + public TextureCubeMap(int width, int height, Image.Format format) { this(createEmptyLayeredImage(width, height, 6, format)); } private static Image createEmptyLayeredImage(int width, int height, int layerCount, Image.Format format) { - ArrayList layers = new ArrayList(); - for(int i = 0; i < layerCount; i++) { + ArrayList layers = new ArrayList<>(); + for (int i = 0; i < layerCount; i++) { layers.add(null); } Image image = new Image(format, width, height, 0, layers, ColorSpace.Linear); return image; } + @Override public Texture createSimpleClone() { return createSimpleClone(new TextureCubeMap()); } @@ -104,18 +105,19 @@ public Texture createSimpleClone(Texture rVal) { rVal.setWrap(WrapAxis.R, wrapR); return super.createSimpleClone(rVal); } - + /** * setWrap sets the wrap mode of this texture for a * particular axis. - * + * * @param axis - * the texture axis to define a wrapmode on. + * the texture axis to apply the wrap mode to. * @param mode * the wrap mode for the given axis of the texture. * @throws IllegalArgumentException * if axis or mode are null */ + @Override public void setWrap(WrapAxis axis, WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -137,12 +139,13 @@ public void setWrap(WrapAxis axis, WrapMode mode) { /** * setWrap sets the wrap mode of this texture for all axis. - * + * * @param mode * the wrap mode for the given axis of the texture. * @throws IllegalArgumentException * if mode is null */ + @Override public void setWrap(WrapMode mode) { if (mode == null) { throw new IllegalArgumentException("mode can not be null."); @@ -155,13 +158,14 @@ public void setWrap(WrapMode mode) { /** * getWrap returns the wrap mode for a given coordinate axis * on this texture. - * + * * @param axis * the axis to return for * @return the wrap mode of the texture. * @throws IllegalArgumentException * if axis is null */ + @Override public WrapMode getWrap(WrapAxis axis) { switch (axis) { case S: @@ -213,9 +217,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); wrapS = capsule.readEnum("wrapS", WrapMode.class, WrapMode.EdgeClamp); wrapT = capsule.readEnum("wrapT", WrapMode.class, WrapMode.EdgeClamp); wrapR = capsule.readEnum("wrapR", WrapMode.class, WrapMode.EdgeClamp); diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureImage.java b/jme3-core/src/main/java/com/jme3/texture/TextureImage.java new file mode 100644 index 0000000000..31b705fa5d --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/TextureImage.java @@ -0,0 +1,320 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.renderer.opengl.GL2; +import com.jme3.renderer.opengl.GL4; +import com.jme3.renderer.opengl.TextureUtil; +import java.util.Objects; + +/** + * Wraps a texture so that only a single level of the underlying image is bound + * instead of the entire image. + * + * @author codex + */ +public class TextureImage { + + /** + * Enum specifying the shader access hint of the image. + *

                + * Shader accesses that violate the hint may result in undefined behavior. + */ + public enum Access { + + /** + * The image can only be read from in a shader. + */ + ReadOnly(true, false, GL2.GL_READ_ONLY), + + /** + * The image can only be written to in a shader. + */ + WriteOnly(false, true, GL2.GL_WRITE_ONLY), + + /** + * The image can both be written to and read from in a shader. + */ + ReadWrite(true, true, GL2.GL_READ_WRITE); + + private final boolean read, write; + private final int glEnum; + + private Access(boolean read, boolean write, int glEnum) { + this.read = read; + this.write = write; + this.glEnum = glEnum; + } + + /** + * If true, the image can be read from in a shader. + * + * @return + */ + public boolean isRead() { + return read; + } + + /** + * If true, the image can be written to in a shader. + * + * @return + */ + public boolean isWrite() { + return write; + } + + /** + * Corresponding OpenGL enum. + * + * @return + */ + public int getGlEnum() { + return glEnum; + } + + } + + private Texture texture; + private int level, layer; + private Access access; + private boolean updateFlag = true; + + public TextureImage(Texture texture) { + this(texture, 0, -1, Access.ReadWrite); + } + + public TextureImage(Texture texture, Access access) { + this(texture, 0, -1, access); + } + + public TextureImage(Texture texture, int level, int layer) { + this(texture, level, layer, Access.ReadWrite); + } + + public TextureImage(Texture texture, int level, int layer, Access access) { + this.texture = Objects.requireNonNull(texture, "Underlying texture cannot be null"); + this.level = level; + this.layer = layer; + this.access = access; + if (this.level < 0) { + throw new IllegalArgumentException("Level cannot be less than zero."); + } + } + + /** + * Binds this texture image to the texture unit. + *

                + * Calling this is not completely sufficient for totally binding an image + * to an image unit. Additionally, the image must be bound beforehand using + * {@link GL2#glBindTexture(int, int)}. + * + * @param gl4 GL4 implementation (not null) + * @param texUtil utility used to convert JME's image format to the corresponding GL enum (not null) + * @param unit texture unit to bind to + */ + public void bindImage(GL4 gl4, TextureUtil texUtil, int unit) { + Image img = texture.getImage(); + gl4.glBindImageTexture(unit, img.getId(), level, isLayered(), Math.max(layer, 0), + access.getGlEnum(), texUtil.getImageFormat(img.getFormat(), false).internalFormat); + } + + /** + * Sets the update flag indicating this texture image needs rebinding. + */ + public void setUpdateNeeded() { + updateFlag = true; + } + + /** + * Clears the update flag and returns the update flag's value before + * it was cleared. + * + * @return + */ + public boolean clearUpdateNeeded() { + boolean f = updateFlag; + updateFlag = false; + return f; + } + + /** + * Sets the underlying texture wrapped by this TextureImage. + * + * @param texture wrapped texture (not null) + */ + public void setTexture(Texture texture) { + Objects.requireNonNull(texture, "Wrapped texture cannot be null."); + if (this.texture != texture) { + this.texture = texture; + updateFlag = true; + } + } + + /** + * Sets the image level to bind. + *

                + * The level controls which mipmap level will be bound to the texture unit, + * where zero corresponds to the base level of the texture. + *

                + * default=0 + * + * @param level level to bind (not negative) + */ + public void setLevel(int level) { + if (level < 0) { + throw new IllegalArgumentException("Texture image level cannot be negative."); + } + if (this.level != level) { + this.level = level; + updateFlag = true; + } + } + + /** + * Sets the image layer to bind. + *

                + * If the underlying texture is a one/two/three demensional array, + * cube map, cube map array, or two demensional multisample array, + * then this value specifies which layer of the array to bind. + *

                + * default=-1 + * + * @param layer layer to bind, or negative to bind all layers + */ + public void setLayer(int layer) { + if (this.layer != layer && (this.layer >= 0 || layer >= 0)) { + this.layer = layer; + updateFlag = true; + } + } + + /** + * Sets the shader access hint with which to bind the image. + * + * @param access + */ + public void setAccess(Access access) { + if (this.access != access) { + this.access = access; + updateFlag = true; + } + } + + /** + * Gets the underlying texture wrapped by this TextureImage. + * + * @return underlying texture + */ + public Texture getTexture() { + return texture; + } + + /** + * Gets the image belonging to the underlying texture. + * + * @return + */ + public Image getImage() { + return texture.getImage(); + } + + /** + * Gets the format of the image belonging to the underlying texture. + * + * @return + */ + public Image.Format getFormat() { + return texture.getImage().getFormat(); + } + + /** + * Gets the native id of the image belonging to the underlying texture. + * + * @return + */ + public int getImageId() { + return texture.getImage().getId(); + } + + /** + * Gets the level. + * + * @return + * @see #setLevel(int) + */ + public int getLevel() { + return level; + } + + /** + * Gets the layer. + * + * @return + * @see #setLayer(int) + */ + public int getLayer() { + return layer; + } + + /** + * Gets the access hint. + * + * @return + * @see #setAccess(com.jme3.texture.TextureImage.Access) + */ + public Access getAccess() { + return access; + } + + /** + * Returns true if all layers of an image will be bound, when + * {@code layer} is negative. + * + * @return + * @see #setLayer(int) + */ + public boolean isLayered() { + return layer < 0; + } + + /** + * Returns true if the update flag has been set indicating rebinding + * is required. + * + * @return + */ + public boolean isUpdateNeeded() { + return updateFlag; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java b/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java index 78107836ef..c0dc0ad359 100644 --- a/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java +++ b/jme3-core/src/main/java/com/jme3/texture/TextureProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -73,6 +73,7 @@ public Object postProcess(AssetKey key, Object obj) { return tex; } + @Override public Object createClone(Object obj) { Texture tex = (Texture) obj; return tex.clone(); diff --git a/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java index 3240526044..a44ffe7cb9 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/BitMaskImageCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,11 +34,11 @@ import java.nio.ByteBuffer; class BitMaskImageCodec extends ImageCodec { - + // Shifts final int as, rs, gs, bs; boolean be = false; - + public BitMaskImageCodec(int bpp, int flags, int ac, int rc, int gc, int bc, int as, int rs, int gs, int bs) { super(bpp, flags, (int) (((long) 1 << ac) - 1), @@ -49,13 +49,13 @@ public BitMaskImageCodec(int bpp, int flags, int ac, int rc, int gc, int bc, int if (bpp > 4) { throw new UnsupportedOperationException("Use ByteAlignedImageCodec for codecs with pixel sizes larger than 4 bytes"); } - + this.as = as; this.rs = rs; this.gs = gs; this.bs = bs; } - + private static int readPixelRaw(ByteBuffer buf, int idx, int bpp) { //idx += bpp; //int original = buf.get(--idx) & 0xff; @@ -71,7 +71,7 @@ private static int readPixelRaw(ByteBuffer buf, int idx, int bpp) { } return pixel; } - + private void writePixelRaw(ByteBuffer buf, int idx, int pixel, int bpp){ // buf.position(idx); // if (!be){ @@ -87,15 +87,16 @@ private void writePixelRaw(ByteBuffer buf, int idx, int pixel, int bpp){ // buf.put(idx + i, bt); // } // } - + buf.position(idx); for (int i = 0; i < bpp; i++) { - buf.put( (byte)((pixel >> (8 * i)) & 0xff) ); + buf.put((byte) ((pixel >> (8 * i)) & 0xff)); } } @Override - public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) { + public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, + int[] components, byte[] tmp) { int inputPixel = readPixelRaw(buf, (x + y * width) * bpp + offset, bpp); components[0] = (inputPixel >> as) & maxAlpha; components[1] = (inputPixel >> rs) & maxRed; @@ -103,14 +104,16 @@ public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, components[3] = (inputPixel >> bs) & maxBlue; } - public void writeComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) { + @Override + public void writeComponents(ByteBuffer buf, int x, int y, int width, int offset, + int[] components, byte[] tmp) { // Shift components then mask them // Map all components into a single bitspace int outputPixel = ((components[0] & maxAlpha) << as) | ((components[1] & maxRed) << rs) | ((components[2] & maxGreen) << gs) | ((components[3] & maxBlue) << bs); - + // Find index in image where to write pixel. // Write the resultant bitspace into the pixel. writePixelRaw(buf, (x + y * width) * bpp + offset, outputPixel, bpp); diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java index d00ab7e22f..3b9d2f3dcf 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/ByteAlignedImageCodec.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,17 +34,17 @@ import java.nio.ByteBuffer; class ByteAlignedImageCodec extends ImageCodec { - + private final int ap, az, rp, rz, gp, gz, bp, bz; boolean be; - + public ByteAlignedImageCodec(int bpp, int flags, int az, int rz, int gz, int bz, int ap, int rp, int gp, int bp) { - // Cast to long to compute max vals, since some components could be as high as 32 bits. - super(bpp, flags, - (int)(((long)1 << (az << 3)) - 1), - (int)(((long)1 << (rz << 3)) - 1), - (int)(((long)1 << (gz << 3)) - 1), - (int)(((long)1 << (bz << 3)) - 1)); + // Cast to long to compute max values, since some components could be as high as 32 bits. + super(bpp, flags, + (int) (((long)1 << (az << 3)) - 1), + (int) (((long)1 << (rz << 3)) - 1), + (int) (((long)1 << (gz << 3)) - 1), + (int) (((long)1 << (bz << 3)) - 1)); this.ap = ap; this.az = az; @@ -56,12 +56,12 @@ public ByteAlignedImageCodec(int bpp, int flags, int az, int rz, int gz, int bz, this.bp = bp; this.bz = bz; } - + private static void readPixelRaw(ByteBuffer buf, int idx, int bpp, byte[] result) { buf.position(idx); buf.get(result, 0, bpp); } - + private static void writePixelRaw(ByteBuffer buf, int idx, byte[] pixel, int bpp) { // try { buf.position(idx); @@ -70,7 +70,7 @@ private static void writePixelRaw(ByteBuffer buf, int idx, byte[] pixel, int bpp // System.out.println("!"); // } } - + private static int readComponent(byte[] encoded, int position, int size) { // int component = encoded[position] & 0xff; // while ((--size) > 0){ @@ -84,7 +84,7 @@ private static int readComponent(byte[] encoded, int position, int size) { } return component; // position += size - 1; -// +// // while ((--size) >= 0) { // component = (component << 8) | (encoded[position--] & 0xff); // } @@ -94,7 +94,7 @@ private static int readComponent(byte[] encoded, int position, int size) { return 0; } } - + private void writeComponent(int component, int position, int size, byte[] result) { // if (!be) { // while ((--size) >= 0){ @@ -108,15 +108,18 @@ private void writeComponent(int component, int position, int size, byte[] result } // } } - - public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) { - readPixelRaw(buf, (x + y * width ) * bpp + offset, bpp, tmp); + + @Override + public void readComponents(ByteBuffer buf, int x, int y, int width, int offset, + int[] components, byte[] tmp) { + readPixelRaw(buf, (x + y * width) * bpp + offset, bpp, tmp); components[0] = readComponent(tmp, ap, az); components[1] = readComponent(tmp, rp, rz); components[2] = readComponent(tmp, gp, gz); components[3] = readComponent(tmp, bp, bz); } - + + @Override public void writeComponents(ByteBuffer buf, int x, int y, int width, int offset, int[] components, byte[] tmp) { writeComponent(components[0], ap, az, tmp); writeComponent(components[1], rp, rz, tmp); diff --git a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java index 2d70d53d72..4de14566b4 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/DefaultImageRaster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ import java.nio.ByteBuffer; public class DefaultImageRaster extends ImageRaster { - + private final int[] components = new int[4]; private ByteBuffer buffer; private final Image image; @@ -48,60 +48,60 @@ public class DefaultImageRaster extends ImageRaster { private final byte[] temp; private final boolean convertToLinear; private int slice; - + private void rangeCheck(int x, int y) { if (x < 0 || y < 0 || x >= width || y >= height) { - throw new IllegalArgumentException("x and y must be inside the image dimensions:" + throw new IllegalArgumentException("x and y must be inside the image dimensions:" + x + ", " + y + " in:" + width + ", " + height); } } - + public DefaultImageRaster(Image image, int slice, int mipMapLevel, boolean convertToLinear) { int[] mipMapSizes = image.getMipMapSizes(); int availableMips = mipMapSizes != null ? mipMapSizes.length : 1; - + if (mipMapLevel >= availableMips) { throw new IllegalStateException("Cannot create image raster for mipmap level #" + mipMapLevel + ". " + "Image only has " + availableMips + " mipmap levels."); } - + if (image.hasMipmaps()) { this.width = Math.max(1, image.getWidth() >> mipMapLevel); this.height = Math.max(1, image.getHeight() >> mipMapLevel); - + int mipOffset = 0; for (int i = 0; i < mipMapLevel; i++) { mipOffset += mipMapSizes[i]; } - + this.offset = mipOffset; } else { this.width = image.getWidth(); this.height = image.getHeight(); this.offset = 0; } - + this.image = image; this.slice = slice; - + // Conversion to linear only needed if image's color space is sRGB. this.convertToLinear = convertToLinear && image.getColorSpace() == ColorSpace.sRGB; - + this.buffer = image.getData(slice); this.codec = ImageCodec.lookup(image.getFormat()); - + if (codec instanceof ByteAlignedImageCodec || codec instanceof ByteOffsetImageCodec) { this.temp = new byte[codec.bpp]; } else { this.temp = null; } } - + public void setSlice(int slice) { this.slice = slice; this.buffer = image.getData(slice); } - + @Override public int getWidth() { return width; @@ -115,13 +115,13 @@ public int getHeight() { @Override public void setPixel(int x, int y, ColorRGBA color) { rangeCheck(x, y); - + if (convertToLinear) { // Input is linear, needs to be converted to sRGB before writing // into image. color = color.getAsSrgb(); } - + // Check flags for grayscale if (codec.isGray) { float gray = color.r * 0.27f + color.g * 0.67f + color.b * 0.06f; @@ -130,16 +130,16 @@ public void setPixel(int x, int y, ColorRGBA color) { switch (codec.type) { case ImageCodec.FLAG_F16: - components[0] = (int) FastMath.convertFloatToHalf(color.a); - components[1] = (int) FastMath.convertFloatToHalf(color.r); - components[2] = (int) FastMath.convertFloatToHalf(color.g); - components[3] = (int) FastMath.convertFloatToHalf(color.b); + components[0] = FastMath.convertFloatToHalf(color.a); + components[1] = FastMath.convertFloatToHalf(color.r); + components[2] = FastMath.convertFloatToHalf(color.g); + components[3] = FastMath.convertFloatToHalf(color.b); break; case ImageCodec.FLAG_F32: - components[0] = (int) Float.floatToIntBits(color.a); - components[1] = (int) Float.floatToIntBits(color.r); - components[2] = (int) Float.floatToIntBits(color.g); - components[3] = (int) Float.floatToIntBits(color.b); + components[0] = Float.floatToIntBits(color.a); + components[1] = Float.floatToIntBits(color.r); + components[2] = Float.floatToIntBits(color.g); + components[3] = Float.floatToIntBits(color.b); break; case 0: // Convert color to bits by multiplying by size @@ -148,22 +148,29 @@ public void setPixel(int x, int y, ColorRGBA color) { components[2] = Math.min( (int) (color.g * codec.maxGreen + 0.5f), codec.maxGreen); components[3] = Math.min( (int) (color.b * codec.maxBlue + 0.5f), codec.maxBlue); break; - } + } codec.writeComponents(getBuffer(), x, y, width, offset, components, temp); image.setUpdateNeeded(); } - - private ByteBuffer getBuffer(){ - if(buffer == null){ - this.buffer = image.getData(slice); + + private ByteBuffer getBuffer() { + if (buffer == null) { + if (image.getDepth() > 1) { + int skip = image.getWidth() * image.getHeight() * codec.bpp * slice; + this.buffer = image.getData(0); + this.buffer.position(skip); + this.buffer = this.buffer.slice(); + } else { + this.buffer = image.getData(slice); + } } return buffer; } - + @Override public ColorRGBA getPixel(int x, int y, ColorRGBA store) { rangeCheck(x, y); - + codec.readComponents(getBuffer(), x, y, width, offset, components, temp); if (store == null) { store = new ColorRGBA(); @@ -176,17 +183,17 @@ public ColorRGBA getPixel(int x, int y, ColorRGBA store) { FastMath.convertHalfToFloat((short)components[0])); break; case ImageCodec.FLAG_F32: - store.set(Float.intBitsToFloat((int)components[1]), - Float.intBitsToFloat((int)components[2]), - Float.intBitsToFloat((int)components[3]), - Float.intBitsToFloat((int)components[0])); + store.set(Float.intBitsToFloat(components[1]), + Float.intBitsToFloat(components[2]), + Float.intBitsToFloat(components[3]), + Float.intBitsToFloat(components[0])); break; case 0: // Convert to float and divide by bitsize to get into range 0.0 - 1.0. - store.set((float)components[1] / codec.maxRed, - (float)components[2] / codec.maxGreen, - (float)components[3] / codec.maxBlue, - (float)components[0] / codec.maxAlpha); + store.set((float) components[1] / codec.maxRed, + (float) components[2] / codec.maxGreen, + (float) components[3] / codec.maxBlue, + (float) components[0] / codec.maxAlpha); break; } if (codec.isGray) { @@ -205,12 +212,12 @@ public ColorRGBA getPixel(int x, int y, ColorRGBA store) { store.a = 1; } } - + if (convertToLinear) { // Input image is sRGB, need to convert to linear. store.setAsSrgb(store.r, store.g, store.b, store.a); } - + return store; } } diff --git a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java index 82549df6df..1a75203225 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/ImageRaster.java @@ -37,12 +37,12 @@ /** * Utility class for reading and writing from jME3 {@link Image images}. *
                - * Allows directly manipulating pixels of the image by writing and + * Allows directly manipulating pixels of the image by writing and * reading {@link ColorRGBA colors} at any coordinate, without * regard to the underlying {@link com.jme3.texture.Image.Format format} of the image. * NOTE: compressed and depth formats are not supported. * Special RGB formats like RGB111110F and RGB9E5 are not supported - * at the moment, but may be added later on. For now + * at the moment, but may be added later on. For now * use RGB16F_to_RGB111110F and RGB16F_to_RGB9E5 to handle * the conversion on the GPU. *

                @@ -51,15 +51,15 @@ * all current instances of ImageReadWrite become invalid, and * new instances must be created in order to properly access * the image data. - * + * * Usage example:
                * * Image myImage = ... * ImageRaster raster = ImageRaster.create(myImage); * raster.setPixel(1, 5, ColorRGBA.Green); - * System.out.println( raster.getPixel(1, 5) ); // Will print [0.0, 1.0, 0.0, 1.0]. + * System.out.println(raster.getPixel(1, 5)); // Will print [0.0, 1.0, 0.0, 1.0]. * - * + * * @author Kirill Vainer */ public abstract class ImageRaster { @@ -70,8 +70,8 @@ public abstract class ImageRaster { * @param image The image to read / write to. * @param slice Which slice to use. Only applies to 3D images, 2D image * arrays or cubemaps. - * @param mipMapLevel The mipmap level to read / write to. To access levels - * other than 0, the image must have + * @param mipMapLevel The mipmap level to read / write to. To access levels + * other than 0, the image must have * {@link Image#setMipMapSizes(int[]) mipmap sizes} set. * @param convertToLinear If true, the application expects read or written * colors to be in linear color space (ImageRaster will @@ -82,7 +82,7 @@ public abstract class ImageRaster { public static ImageRaster create(Image image, int slice, int mipMapLevel, boolean convertToLinear) { return new DefaultImageRaster(image, slice, mipMapLevel, convertToLinear); } - + /** * Create new image reader / writer. * @@ -94,10 +94,10 @@ public static ImageRaster create(Image image, int slice, int mipMapLevel, boolea public static ImageRaster create(Image image, int slice) { return create(image, slice, 0, false); } - + /** * Create new image reader / writer for 2D images. - * + * * @param image The image to read / write to. * @return An ImageRaster to read / write to the image. */ @@ -107,30 +107,30 @@ public static ImageRaster create(Image image) { } return create(image, 0, 0, false); } - + public ImageRaster() { } - + /** * Returns the pixel width of the underlying image. - * + * * @return the pixel width of the underlying image. */ public abstract int getWidth(); - + /** * Returns the pixel height of the underlying image. - * + * * @return the pixel height of the underlying image. */ public abstract int getHeight(); - + /** * Sets the pixel at the given coordinate to the given color. *

                - * For all integer based formats (those not ending in "F"), the + * For all integer based formats (those not ending in "F"), the * color is first clamped to 0.0 - 1.0 before converting it to - * an integer to avoid overflow. For floating point based formats, + * an integer to avoid overflow. For floating point based formats, * components larger than 1.0 can be represented, but components * lower than 0.0 are still not allowed (as all formats are unsigned). *

                @@ -138,7 +138,7 @@ public ImageRaster() { * such as {@link com.jme3.texture.Image.Format#Luminance8}) then a color to grayscale * conversion is done first, before writing the result into the image. *

                - * If the image does not have some of the components in the color (such + * If the image lacks some components (such * as alpha, or any of the color components), then these components * will be ignored. The only exception to this is luminance formats * for which the color is converted to luminance first (see above). @@ -146,14 +146,14 @@ public ImageRaster() { * After writing the color, the image shall be marked as requiring an * update. The next time it is used for rendering, all pixel changes * will be reflected when the image is rendered. - * + * * @param x The x coordinate, from 0 to width - 1. * @param y The y coordinate, from 0 to height - 1. - * @param color The color to write. + * @param color The color to write. * @throws IllegalArgumentException If x or y are outside the image dimensions. */ public abstract void setPixel(int x, int y, ColorRGBA color); - + /** * Retrieve the color at the given coordinate. *

                @@ -164,7 +164,7 @@ public ImageRaster() { * the A component set to the alpha in the image. *

                * For grayscale or luminance formats, the luminance value is replicated - * in the R, G, and B components. + * in the R, G, and B components. *

                * Integer formats are converted to the range 0.0 - 1.0, based * on the maximum possible integer value that can be represented @@ -173,31 +173,31 @@ public ImageRaster() { * contain the integer values 0 - 31, a conversion to floating point * is done by diving the integer value by 31 (done with floating point * precision). - * + * * @param x The x coordinate, from 0 to width - 1. * @param y The y coordinate, from 0 to height - 1. - * @param store Storage location for the read color, if null, + * @param store Storage location for the read color, if null, * then a new ColorRGBA is created and returned with the read color. * @return The store parameter, if it is null, then a new ColorRGBA * with the read color. * @throws IllegalArgumentException If x or y are outside the image dimensions. */ public abstract ColorRGBA getPixel(int x, int y, ColorRGBA store); - + /** * Retrieve the color at the given coordinate. *

                * Convenience method that does not take a store argument. Equivalent - * to calling getPixel(x, y, null). + * to calling getPixel(x, y, null). * See {@link #getPixel(int, int, com.jme3.math.ColorRGBA) } for * more information. - * + * * @param x The x coordinate, from 0 to width - 1. * @param y The y coordinate, from 0 to height - 1. * @return A new ColorRGBA with the read color. * @throws IllegalArgumentException If x or y are outside the image dimensions */ - public ColorRGBA getPixel(int x, int y) { + public ColorRGBA getPixel(int x, int y) { return getPixel(x, y, null); } } diff --git a/jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java b/jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java index b39580210c..7a08e1cdde 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/LastTextureState.java @@ -35,8 +35,8 @@ import com.jme3.texture.Texture; /** - * Stores / caches texture state parameters so they don't have to be set - * each time by the {@link Renderer}. + * Stores/caches texture-state parameters so the {@link Renderer} doesn't have to + * set them each time. * * @author Kirill Vainer */ diff --git a/jme3-core/src/main/java/com/jme3/texture/image/MipMapImageRaster.java b/jme3-core/src/main/java/com/jme3/texture/image/MipMapImageRaster.java index 84ea1ea242..d37db00b67 100644 --- a/jme3-core/src/main/java/com/jme3/texture/image/MipMapImageRaster.java +++ b/jme3-core/src/main/java/com/jme3/texture/image/MipMapImageRaster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -108,16 +108,16 @@ public void setPixel(int x, int y, ColorRGBA color) { switch (codec.type) { case ImageCodec.FLAG_F16: - components[0] = (int) FastMath.convertFloatToHalf(color.a); - components[1] = (int) FastMath.convertFloatToHalf(color.r); - components[2] = (int) FastMath.convertFloatToHalf(color.g); - components[3] = (int) FastMath.convertFloatToHalf(color.b); + components[0] = FastMath.convertFloatToHalf(color.a); + components[1] = FastMath.convertFloatToHalf(color.r); + components[2] = FastMath.convertFloatToHalf(color.g); + components[3] = FastMath.convertFloatToHalf(color.b); break; case ImageCodec.FLAG_F32: - components[0] = (int) Float.floatToIntBits(color.a); - components[1] = (int) Float.floatToIntBits(color.r); - components[2] = (int) Float.floatToIntBits(color.g); - components[3] = (int) Float.floatToIntBits(color.b); + components[0] = Float.floatToIntBits(color.a); + components[1] = Float.floatToIntBits(color.r); + components[2] = Float.floatToIntBits(color.g); + components[3] = Float.floatToIntBits(color.b); break; case 0: // Convert color to bits by multiplying by size @@ -154,10 +154,10 @@ public ColorRGBA getPixel(int x, int y, ColorRGBA store) { FastMath.convertHalfToFloat((short) components[0])); break; case ImageCodec.FLAG_F32: - store.set(Float.intBitsToFloat((int) components[1]), - Float.intBitsToFloat((int) components[2]), - Float.intBitsToFloat((int) components[3]), - Float.intBitsToFloat((int) components[0])); + store.set(Float.intBitsToFloat(components[1]), + Float.intBitsToFloat(components[2]), + Float.intBitsToFloat(components[3]), + Float.intBitsToFloat(components[0])); break; case 0: // Convert to float and divide by bitsize to get into range 0.0 - 1.0. diff --git a/jme3-core/src/main/java/com/jme3/texture/package-info.java b/jme3-core/src/main/java/com/jme3/texture/package-info.java new file mode 100644 index 0000000000..a4b21c5e31 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/texture/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * textures for applying image data to 3-D geometries + */ +package com.jme3.texture; 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 71ff7487db..5da04461bb 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,7 +56,7 @@ public class Picture extends Geometry { /** * Create a named picture. * - * By default a picture's width and height are 1 + * By default, a picture's width and height are 1, * and its position is 0, 0. * * @param name the name of the picture in the scene graph @@ -70,7 +70,7 @@ public Picture(String name, boolean flipY){ /** * Creates a named picture. - * By default a picture's width and height are 1 + * By default, a picture's width and height are 1, * and its position is 0, 0. * The image texture coordinates will not be flipped. * @@ -80,12 +80,30 @@ public Picture(String name){ this(name, false); } - /* + /** * Serialization only. Do not use. */ 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 @@ -95,7 +113,7 @@ protected Picture(){ */ public void setWidth(float width){ this.width = width; - setLocalScale(new Vector3f(width, height, 1f)); + setLocalScale(width, height, 1f); } /** @@ -107,7 +125,7 @@ public void setWidth(float width){ */ public void setHeight(float height){ this.height = height; - setLocalScale(new Vector3f(width, height, 1f)); + setLocalScale(width, height, 1f); } /** diff --git a/jme3-core/src/main/java/com/jme3/ui/package-info.java b/jme3-core/src/main/java/com/jme3/ui/package-info.java new file mode 100644 index 0000000000..7f7bdc5f3c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/ui/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * display 2-D images + */ +package com.jme3.ui; 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/BufferAllocator.java b/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java index e7d429db6a..1833b30332 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferAllocator.java @@ -3,10 +3,22 @@ import java.nio.Buffer; import java.nio.ByteBuffer; +/** + * Interface to create/destroy direct buffers. + */ public interface BufferAllocator { - + /** + * De-allocate a direct buffer. + * + * @param toBeDestroyed the buffer to de-allocate (not null) + */ void destroyDirectBuffer(Buffer toBeDestroyed); + /** + * Allocate a direct ByteBuffer of the specified size. + * + * @param size in bytes (≥0) + * @return a new direct buffer + */ ByteBuffer allocate(int size); - } diff --git a/jme3-core/src/main/java/com/jme3/util/BufferAllocatorFactory.java b/jme3-core/src/main/java/com/jme3/util/BufferAllocatorFactory.java index dd77c4d1aa..246e83d2a5 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferAllocatorFactory.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferAllocatorFactory.java @@ -1,7 +1,6 @@ package com.jme3.util; import com.jme3.system.Annotations.Internal; - import java.util.logging.Level; import java.util.logging.Logger; @@ -17,12 +16,18 @@ public class BufferAllocatorFactory { private static final Logger LOGGER = Logger.getLogger(BufferAllocatorFactory.class.getName()); + /** + * A private constructor to inhibit instantiation of this class. + */ + private BufferAllocatorFactory() { + } + @Internal protected static BufferAllocator create() { final String className = System.getProperty(PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION, ReflectionAllocator.class.getName()); try { - return (BufferAllocator) Class.forName(className).newInstance(); + return (BufferAllocator) Class.forName(className).getDeclaredConstructor().newInstance(); } catch (final Throwable e) { LOGGER.log(Level.WARNING, "Unable to access {0}", className); return new PrimitiveAllocator(); diff --git a/jme3-core/src/main/java/com/jme3/util/BufferInputStream.java b/jme3-core/src/main/java/com/jme3/util/BufferInputStream.java new file mode 100644 index 0000000000..4a425df3cb --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/BufferInputStream.java @@ -0,0 +1,72 @@ +/* + * Copyright (c) 2009-2025 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.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +public class BufferInputStream extends InputStream { + + ByteBuffer input; + + public BufferInputStream(ByteBuffer input) { + this.input = input; + } + + @Override + public int read() throws IOException { + if (input.remaining() == 0) return -1; else return input.get() & 0xff; + } + + @Override + public int read(byte[] b) { + return read(b, 0, b.length); + } + + @Override + public int read(byte[] b, int off, int len) { + if (b == null) throw new NullPointerException("b == null"); + if (off < 0 || len < 0 || len > b.length - off) throw new IndexOutOfBoundsException(); + if (len == 0) return 0; + if (!input.hasRemaining()) return -1; + + int toRead = Math.min(len, input.remaining()); + input.get(b, off, toRead); + return toRead; + } + + @Override + public int available() { + return input.remaining(); + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java index d4546b7e3f..535f91d4ed 100644 --- a/jme3-core/src/main/java/com/jme3/util/BufferUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/BufferUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,35 +47,41 @@ import java.nio.DoubleBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; -import java.nio.LongBuffer; import java.nio.ShortBuffer; import java.util.concurrent.ConcurrentHashMap; /** * BufferUtils is a helper class for generating nio buffers from * jME data classes such as Vectors and ColorRGBA. - * + * * @author Joshua Slack * @version $Id: BufferUtils.java,v 1.16 2007/10/29 16:56:18 nca Exp $ */ public final class BufferUtils { /** - * The field should be final to support thread-safe. + * Should be final for thread safety. */ private static final BufferAllocator allocator = BufferAllocatorFactory.create(); private static boolean trackDirectMemory = false; - private static ReferenceQueue removeCollected = new ReferenceQueue(); - private static ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); + private static final ReferenceQueue removeCollected = new ReferenceQueue(); + private static final ConcurrentHashMap trackedBuffers = new ConcurrentHashMap(); static ClearReferences cleanupthread; + /** + * A private constructor to inhibit instantiation of this class. + */ + private BufferUtils() { + } + /** * Set it to true if you want to enable direct memory tracking for debugging * purpose. Default is false. To print direct memory usage use * BufferUtils.printCurrentDirectMemory(StringBuilder store); - * - * @param enabled + * + * @param enabled true to enable tracking, false to disable it + * (default=false) */ public static void setTrackDirectMemoryEnabled(boolean enabled) { trackDirectMemory = enabled; @@ -84,7 +90,7 @@ public static void setTrackDirectMemoryEnabled(boolean enabled) { /** * Creates a clone of the given buffer. The clone's capacity is equal to the * given buffer's limit. - * + * * @param buf * The buffer to clone * @return The cloned buffer @@ -141,9 +147,10 @@ private static void onBufferAllocated(Buffer buffer) { * Generate a new FloatBuffer using the given array of Vector3f objects. The * FloatBuffer will be 3 * data.length long and contain the vector data as * data[0].x, data[0].y, data[0].z, data[1].x... etc. - * + * * @param data * array of Vector3f objects to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(Vector3f... data) { if (data == null) { @@ -164,9 +171,10 @@ public static FloatBuffer createFloatBuffer(Vector3f... data) { /** * Generate a new FloatBuffer using the given array of Quaternion objects. * The FloatBuffer will be 4 * data.length long and contain the vector data. - * + * * @param data * array of Quaternion objects to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(Quaternion... data) { if (data == null) { @@ -190,6 +198,7 @@ public static FloatBuffer createFloatBuffer(Quaternion... data) { * * @param data * array of Vector4 objects to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(Vector4f... data) { if (data == null) { @@ -213,6 +222,7 @@ public static FloatBuffer createFloatBuffer(Vector4f... data) { * * @param data * array of ColorRGBA objects to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(ColorRGBA... data) { if (data == null) { @@ -232,9 +242,10 @@ public static FloatBuffer createFloatBuffer(ColorRGBA... data) { /** * Generate a new FloatBuffer using the given array of float primitives. - * + * * @param data * array of float primitives to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(float... data) { if (data == null) { @@ -250,7 +261,7 @@ public static FloatBuffer createFloatBuffer(float... data) { /** * Create a new FloatBuffer of an appropriate size to hold the specified * number of Vector3f object data. - * + * * @param vertices * number of vertices that need to be held by the newly created * buffer @@ -265,7 +276,7 @@ public static FloatBuffer createVector3Buffer(int vertices) { * Create a new FloatBuffer of an appropriate size to hold the specified * number of Vector3f object data only if the given buffer if not already * the right size. - * + * * @param buf * the buffer to first check and rewind * @param vertices @@ -285,7 +296,7 @@ public static FloatBuffer createVector3Buffer(FloatBuffer buf, int vertices) { /** * Sets the data contained in the given color into the FloatBuffer at the * specified index. - * + * * @param color * the data to insert * @param buf @@ -304,7 +315,7 @@ public static void setInBuffer(ColorRGBA color, FloatBuffer buf, int index) { /** * Sets the data contained in the given quaternion into the FloatBuffer at * the specified index. - * + * * @param quat * the {@link Quaternion} to insert * @param buf @@ -343,7 +354,7 @@ public static void setInBuffer(Vector4f vec, FloatBuffer buf, int index) { /** * Sets the data contained in the given Vector3F into the FloatBuffer at the * specified index. - * + * * @param vector * the data to insert * @param buf @@ -369,7 +380,7 @@ public static void setInBuffer(Vector3f vector, FloatBuffer buf, int index) { /** * Updates the values of the given vector from the specified buffer at the * index provided. - * + * * @param vector * the vector to set data on * @param buf @@ -387,7 +398,7 @@ public static void populateFromBuffer(Vector3f vector, FloatBuffer buf, int inde /** * Updates the values of the given vector from the specified buffer at the * index provided. - * + * * @param vector * the vector to set data on * @param buf @@ -405,7 +416,7 @@ public static void populateFromBuffer(Vector4f vector, FloatBuffer buf, int inde /** * Generates a Vector3f array from the given FloatBuffer. - * + * * @param buff * the FloatBuffer to read from * @return a newly generated array of Vector3f objects @@ -424,7 +435,7 @@ public static Vector3f[] getVector3Array(FloatBuffer buff) { * Copies a Vector3f from one position in the buffer to another. The index * values are in terms of vector number (eg, vector number 0 is positions * 0-2 in the FloatBuffer.) - * + * * @param buf * the buffer to copy from/to * @param fromPos @@ -438,7 +449,7 @@ public static void copyInternalVector3(FloatBuffer buf, int fromPos, int toPos) /** * Normalize a Vector3f in-buffer. - * + * * @param buf * the buffer to find the Vector3f within * @param index @@ -456,7 +467,7 @@ public static void normalizeVector3(FloatBuffer buf, int index) { /** * Add to a Vector3f in-buffer. - * + * * @param toAdd * the vector to add from * @param buf @@ -476,7 +487,7 @@ public static void addInBuffer(Vector3f toAdd, FloatBuffer buf, int index) { /** * Multiply and store a Vector3f in-buffer. - * + * * @param toMult * the vector to multiply against * @param buf @@ -497,7 +508,7 @@ public static void multInBuffer(Vector3f toMult, FloatBuffer buf, int index) { /** * Checks to see if the given Vector3f is equals to the data stored in the * buffer at the given data index. - * + * * @param check * the vector to check against - null will return false. * @param buf @@ -521,9 +532,10 @@ public static boolean equals(Vector3f check, FloatBuffer buf, int index) { * Generate a new FloatBuffer using the given array of Vector2f objects. The * FloatBuffer will be 2 * data.length long and contain the vector data as * data[0].x, data[0].y, data[1].x... etc. - * + * * @param data * array of Vector2f objects to place into a new FloatBuffer + * @return a new direct, flipped FloatBuffer, or null if data was null */ public static FloatBuffer createFloatBuffer(Vector2f... data) { if (data == null) { @@ -544,7 +556,7 @@ public static FloatBuffer createFloatBuffer(Vector2f... data) { /** * Create a new FloatBuffer of an appropriate size to hold the specified * number of Vector2f object data. - * + * * @param vertices * number of vertices that need to be held by the newly created * buffer @@ -559,7 +571,7 @@ public static FloatBuffer createVector2Buffer(int vertices) { * Create a new FloatBuffer of an appropriate size to hold the specified * number of Vector2f object data only if the given buffer if not already * the right size. - * + * * @param buf * the buffer to first check and rewind * @param vertices @@ -579,7 +591,7 @@ public static FloatBuffer createVector2Buffer(FloatBuffer buf, int vertices) { /** * Sets the data contained in the given Vector2F into the FloatBuffer at the * specified index. - * + * * @param vector * the data to insert * @param buf @@ -595,7 +607,7 @@ public static void setInBuffer(Vector2f vector, FloatBuffer buf, int index) { /** * Updates the values of the given vector from the specified buffer at the * index provided. - * + * * @param vector * the vector to set data on * @param buf @@ -611,7 +623,7 @@ public static void populateFromBuffer(Vector2f vector, FloatBuffer buf, int inde /** * Generates a Vector2f array from the given FloatBuffer. - * + * * @param buff * the FloatBuffer to read from * @return a newly generated array of Vector2f objects @@ -630,7 +642,7 @@ public static Vector2f[] getVector2Array(FloatBuffer buff) { * Copies a Vector2f from one position in the buffer to another. The index * values are in terms of vector number (eg, vector number 0 is positions * 0-1 in the FloatBuffer.) - * + * * @param buf * the buffer to copy from/to * @param fromPos @@ -644,7 +656,7 @@ public static void copyInternalVector2(FloatBuffer buf, int fromPos, int toPos) /** * Normalize a Vector2f in-buffer. - * + * * @param buf * the buffer to find the Vector2f within * @param index @@ -662,7 +674,7 @@ public static void normalizeVector2(FloatBuffer buf, int index) { /** * Add to a Vector2f in-buffer. - * + * * @param toAdd * the vector to add from * @param buf @@ -682,7 +694,7 @@ public static void addInBuffer(Vector2f toAdd, FloatBuffer buf, int index) { /** * Multiply and store a Vector2f in-buffer. - * + * * @param toMult * the vector to multiply against * @param buf @@ -703,7 +715,7 @@ public static void multInBuffer(Vector2f toMult, FloatBuffer buf, int index) { /** * Checks to see if the given Vector2f is equals to the data stored in the * buffer at the given data index. - * + * * @param check * the vector to check against - null will return false. * @param buf @@ -727,9 +739,10 @@ public static boolean equals(Vector2f check, FloatBuffer buf, int index) { * Generate a new IntBuffer using the given array of ints. The IntBuffer * will be data.length long and contain the int data as data[0], data[1]... * etc. - * + * * @param data * array of ints to place into a new IntBuffer + * @return a new direct, flipped IntBuffer, or null if data was null */ public static IntBuffer createIntBuffer(int... data) { if (data == null) { @@ -745,7 +758,7 @@ public static IntBuffer createIntBuffer(int... data) { /** * Create a new int[] array and populate it with the given IntBuffer's * contents. - * + * * @param buff * the IntBuffer to read from * @return a new int array populated from the IntBuffer @@ -765,7 +778,7 @@ public static int[] getIntArray(IntBuffer buff) { /** * Create a new float[] array and populate it with the given FloatBuffer's * contents. - * + * * @param buff * the FloatBuffer to read from * @return a new float array populated from the FloatBuffer @@ -785,7 +798,7 @@ public static float[] getFloatArray(FloatBuffer buff) { //// -- GENERAL DOUBLE ROUTINES -- //// /** * Create a new DoubleBuffer of the specified size. - * + * * @param size * required number of double to store. * @return the new DoubleBuffer @@ -800,7 +813,7 @@ public static DoubleBuffer createDoubleBuffer(int size) { /** * Create a new DoubleBuffer of an appropriate size to hold the specified * number of doubles only if the given buffer if not already the right size. - * + * * @param buf * the buffer to first check and rewind * @param size @@ -823,7 +836,7 @@ public static DoubleBuffer createDoubleBuffer(DoubleBuffer buf, int size) { * DoubleBuffer. The new DoubleBuffer is separate from the old one and * changes are not reflected across. If you want to reflect changes, * consider using Buffer.duplicate(). - * + * * @param buf * the DoubleBuffer to copy * @return the copy @@ -848,7 +861,7 @@ public static DoubleBuffer clone(DoubleBuffer buf) { //// -- GENERAL FLOAT ROUTINES -- //// /** * Create a new FloatBuffer of the specified size. - * + * * @param size * required number of floats to store. * @return the new FloatBuffer @@ -862,7 +875,7 @@ public static FloatBuffer createFloatBuffer(int size) { /** * Copies floats from one position in the buffer to another. - * + * * @param buf * the buffer to copy from/to * @param fromPos @@ -885,7 +898,7 @@ public static void copyInternal(FloatBuffer buf, int fromPos, int toPos, int len * FloatBuffer. The new FloatBuffer is separate from the old one and changes * are not reflected across. If you want to reflect changes, consider using * Buffer.duplicate(). - * + * * @param buf * the FloatBuffer to copy * @return the copy @@ -910,7 +923,7 @@ public static FloatBuffer clone(FloatBuffer buf) { //// -- GENERAL INT ROUTINES -- //// /** * Create a new IntBuffer of the specified size. - * + * * @param size * required number of ints to store. * @return the new IntBuffer @@ -925,7 +938,7 @@ public static IntBuffer createIntBuffer(int size) { /** * Create a new IntBuffer of an appropriate size to hold the specified * number of ints only if the given buffer if not already the right size. - * + * * @param buf * the buffer to first check and rewind * @param size @@ -948,7 +961,7 @@ public static IntBuffer createIntBuffer(IntBuffer buf, int size) { * The new IntBuffer is separate from the old one and changes are not * reflected across. If you want to reflect changes, consider using * Buffer.duplicate(). - * + * * @param buf * the IntBuffer to copy * @return the copy @@ -973,7 +986,7 @@ public static IntBuffer clone(IntBuffer buf) { //// -- GENERAL BYTE ROUTINES -- //// /** * Create a new ByteBuffer of the specified size. - * + * * @param size * required number of ints to store. * @return the new IntBuffer @@ -988,7 +1001,7 @@ public static ByteBuffer createByteBuffer(int size) { /** * Create a new ByteBuffer of an appropriate size to hold the specified * number of ints only if the given buffer if not already the right size. - * + * * @param buf * the buffer to first check and rewind * @param size @@ -1030,7 +1043,7 @@ public static ByteBuffer createByteBuffer(String data) { * The new ByteBuffer is separate from the old one and changes are not * reflected across. If you want to reflect changes, consider using * Buffer.duplicate(). - * + * * @param buf * the ByteBuffer to copy * @return the copy @@ -1055,7 +1068,7 @@ public static ByteBuffer clone(ByteBuffer buf) { //// -- GENERAL SHORT ROUTINES -- //// /** * Create a new ShortBuffer of the specified size. - * + * * @param size * required number of shorts to store. * @return the new ShortBuffer @@ -1070,7 +1083,7 @@ public static ShortBuffer createShortBuffer(int size) { /** * Create a new ShortBuffer of an appropriate size to hold the specified * number of shorts only if the given buffer if not already the right size. - * + * * @param buf * the buffer to first check and rewind * @param size @@ -1104,7 +1117,7 @@ public static ShortBuffer createShortBuffer(short... data) { * ShortBuffer. The new ShortBuffer is separate from the old one and changes * are not reflected across. If you want to reflect changes, consider using * Buffer.duplicate(). - * + * * @param buf * the ShortBuffer to copy * @return the copy @@ -1130,7 +1143,7 @@ public static ShortBuffer clone(ShortBuffer buf) { * Ensures there is at least the required number of entries * left after the current position of the buffer. If the buffer is too small * a larger one is created and the old one copied to the new buffer. - * + * * @param buffer * buffer that should be checked/copied (may be null) * @param required @@ -1269,7 +1282,9 @@ public static void printCurrentDirectMemory(StringBuilder store) { * and cleans the direct buffers. However, as this doesn't happen * immediately after discarding all references to a direct buffer, it's easy * to OutOfMemoryError yourself using direct buffers. - **/ + * + * @param toBeDestroyed the buffer to de-allocate (not null) + */ public static void destroyDirectBuffer(Buffer toBeDestroyed) { if (!isDirect(toBeDestroyed)) { return; @@ -1277,36 +1292,14 @@ public static void destroyDirectBuffer(Buffer toBeDestroyed) { allocator.destroyDirectBuffer(toBeDestroyed); } - /* - * FIXME when java 1.5 supprt is dropped - replace calls to this method with - * Buffer.isDirect - * - * Buffer.isDirect() is only java 6. Java 5 only have this method on Buffer - * subclasses : FloatBuffer, IntBuffer, ShortBuffer, - * ByteBuffer,DoubleBuffer, LongBuffer. CharBuffer has been excluded as we - * don't use it. - * + /** + * Test whether the specified buffer is direct. + * + * @param buf the buffer to test (not null, unaffected) + * @return true if direct, otherwise false */ private static boolean isDirect(Buffer buf) { - if (buf instanceof FloatBuffer) { - return ((FloatBuffer) buf).isDirect(); - } - if (buf instanceof IntBuffer) { - return ((IntBuffer) buf).isDirect(); - } - if (buf instanceof ShortBuffer) { - return ((ShortBuffer) buf).isDirect(); - } - if (buf instanceof ByteBuffer) { - return ((ByteBuffer) buf).isDirect(); - } - if (buf instanceof DoubleBuffer) { - return ((DoubleBuffer) buf).isDirect(); - } - if (buf instanceof LongBuffer) { - return ((LongBuffer) buf).isDirect(); - } - throw new UnsupportedOperationException(" BufferUtils.isDirect was called on " + buf.getClass().getName()); + return buf.isDirect(); } private static class BufferInfo extends PhantomReference { @@ -1340,5 +1333,4 @@ public void run() { } } } - } diff --git a/jme3-core/src/main/java/com/jme3/util/IntMap.java b/jme3-core/src/main/java/com/jme3/util/IntMap.java index b572fad224..198b380dd8 100644 --- a/jme3-core/src/main/java/com/jme3/util/IntMap.java +++ b/jme3-core/src/main/java/com/jme3/util/IntMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -80,8 +80,9 @@ public IntMap(int initialCapacity, float loadFactor) { } @Override + @SuppressWarnings("unchecked") public IntMap clone(){ - try{ + try { IntMap clone = (IntMap) super.clone(); Entry[] newTable = new Entry[table.length]; for (int i = table.length - 1; i >= 0; i--){ @@ -90,7 +91,7 @@ public IntMap clone(){ } clone.table = newTable; return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex){ } return null; } @@ -111,7 +112,7 @@ public Object jmeClone() { * Called internally by com.jme3.util.clone.Cloner. Do not call directly. */ @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.table = cloner.clone(table); } @@ -137,6 +138,7 @@ public boolean containsKey(int key) { return false; } + @SuppressWarnings("unchecked") public T get(int key) { int index = key & mask; for (Entry e = table[index]; e != null; e = e.next){ @@ -147,6 +149,7 @@ public T get(int key) { return null; } + @SuppressWarnings("unchecked") public T put(int key, T value) { int index = key & mask; // Check if key already exists. @@ -164,14 +167,14 @@ public T put(int key, T value) { int newCapacity = 2 * capacity; Entry[] newTable = new Entry[newCapacity]; Entry[] src = table; - int bucketmask = newCapacity - 1; + int bucketMask = newCapacity - 1; for (int j = 0; j < src.length; j++){ Entry e = src[j]; if (e != null){ src[j] = null; do{ Entry next = e.next; - index = e.key & bucketmask; + index = e.key & bucketMask; e.next = newTable[index]; newTable[index] = e; e = next; @@ -186,6 +189,7 @@ public T put(int key, T value) { return null; } + @SuppressWarnings("unchecked") public T remove(int key) { int index = key & mask; Entry prev = table[index]; @@ -213,12 +217,13 @@ public int size() { public void clear() { Entry[] table = this.table; - for (int index = table.length; --index >= 0;){ + for (int index = table.length; --index >= 0;) { table[index] = null; } size = 0; } + @Override public Iterator> iterator() { IntMapIterator it = new IntMapIterator(); it.beginUse(); @@ -251,15 +256,18 @@ public void beginUse(){ el = 0; } + @Override public boolean hasNext() { return el < size; } + @Override + @SuppressWarnings("unchecked") public Entry next() { if (el >= size) throw new NoSuchElementException("No more elements!"); - if (cur != null){ + if (cur != null) { Entry e = cur; cur = cur.next; el++; @@ -285,9 +293,9 @@ public Entry next() { return e; } + @Override public void remove() { } - } public static final class Entry implements Cloneable, JmeCloneable { @@ -316,12 +324,13 @@ public String toString(){ } @Override + @SuppressWarnings("unchecked") public Entry clone(){ - try{ + try { Entry clone = (Entry) super.clone(); clone.next = next != null ? next.clone() : null; return clone; - }catch (CloneNotSupportedException ex){ + } catch (CloneNotSupportedException ex) { } return null; } @@ -336,7 +345,7 @@ public Object jmeClone() { } @Override - public void cloneFields( Cloner cloner, Object original ) { + public void cloneFields(Cloner cloner, Object original) { this.value = cloner.clone(value); this.next = cloner.clone(next); } diff --git a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java index 0834665d4e..bd21d58eb4 100644 --- a/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java +++ b/jme3-core/src/main/java/com/jme3/util/JmeFormatter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,19 +40,19 @@ /** * More simple formatter than the default one used in Java logging. - * Example output:
                + * Example output:
                * INFO Display3D 12:00 PM: Display created. */ public class JmeFormatter extends Formatter { - private Date calendar = new Date(); - private String lineSeperator; - private MessageFormat format; - private Object args[] = new Object[1]; - private StringBuffer store = new StringBuffer(); + private final Date calendar = new Date(); + private final String lineSeparator; + private final MessageFormat format; + private final Object args[] = new Object[1]; + private final StringBuffer store = new StringBuffer(); public JmeFormatter(){ - lineSeperator = System.getProperty("line.separator"); + lineSeparator = System.getProperty("line.separator"); format = new MessageFormat("{0,time}"); } @@ -66,21 +66,21 @@ public String format(LogRecord record) { format.format(args, store, null); String clazz = null; - try{ + try { clazz = Class.forName(record.getSourceClassName()).getSimpleName(); - } catch (ClassNotFoundException ex){ + } catch (ClassNotFoundException ex) { } - + sb.append(record.getLevel().getLocalizedName()).append(" "); sb.append(clazz).append(" "); sb.append(store.toString()).append(" "); - sb.append(formatMessage(record)).append(lineSeperator); + sb.append(formatMessage(record)).append(lineSeparator); if (record.getThrown() != null) { try { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); - record.getThrown().printStackTrace(pw); + record.getThrown().printStackTrace(pw); pw.close(); sb.append(sw.toString()); } catch (Exception ex) { diff --git a/jme3-core/src/main/java/com/jme3/util/ListMap.java b/jme3-core/src/main/java/com/jme3/util/ListMap.java index 379fff7414..0295c0c950 100644 --- a/jme3-core/src/main/java/com/jme3/util/ListMap.java +++ b/jme3-core/src/main/java/com/jme3/util/ListMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ */ public final class ListMap extends AbstractMap implements Cloneable, Serializable { - private final static class ListMapEntry implements Map.Entry, Cloneable { + private static final class ListMapEntry implements Map.Entry, Cloneable { private final K key; private V value; @@ -53,14 +53,17 @@ public ListMapEntry(K key, V value){ this.value = value; } + @Override public K getKey() { return key; } + @Override public V getValue() { return value; } + @Override public V setValue(V v) { throw new UnsupportedOperationException(); } @@ -71,6 +74,7 @@ public ListMapEntry clone(){ } @Override + @SuppressWarnings("unchecked") public boolean equals(Object obj) { if (obj == null) { return false; @@ -100,18 +104,21 @@ public int hashCode() { // private final ArrayList> entries; + @SuppressWarnings("unchecked") public ListMap(){ entries = new ListMapEntry[4]; backingMap = new HashMap(4); // entries = new ArrayList>(); } + @SuppressWarnings("unchecked") public ListMap(int initialCapacity){ entries = new ListMapEntry[initialCapacity]; backingMap = new HashMap(initialCapacity); // entries = new ArrayList>(initialCapacity); } + @SuppressWarnings("unchecked") public ListMap(Map map){ entries = new ListMapEntry[map.size()]; backingMap = new HashMap(map.size()); @@ -155,7 +162,7 @@ private static boolean keyEq(Object keyA, Object keyB){ @Override public boolean containsKey(Object key) { - return backingMap.containsKey( (K) key); + return backingMap.containsKey(key); // if (key == null) // throw new IllegalArgumentException(); // @@ -169,7 +176,7 @@ public boolean containsKey(Object key) { @Override public boolean containsValue(Object value) { - return backingMap.containsValue( (V) value); + return backingMap.containsValue(value); // for (int i = 0; i < entries.size(); i++){ // if (valEq(entries.get(i).value, value)) // return true; @@ -179,7 +186,7 @@ public boolean containsValue(Object value) { @Override public V get(Object key) { - return backingMap.get( (K) key); + return backingMap.get(key); // if (key == null) // throw new IllegalArgumentException(); // @@ -192,6 +199,7 @@ public V get(Object key) { } @Override + @SuppressWarnings("unchecked") public V put(K key, V value) { if (backingMap.containsKey(key)){ // set the value on the entry @@ -234,7 +242,7 @@ public V put(K key, V value) { @Override public V remove(Object key) { - V element = backingMap.remove( (K) key); + V element = backingMap.remove(key); if (element != null){ // find removed element int size = size() + 1; // includes removed element @@ -295,7 +303,7 @@ public void clear() { @Override public ListMap clone(){ - ListMap clone = new ListMap(size()); + ListMap clone = new ListMap<>(size()); clone.putAll(this); return clone; } @@ -322,11 +330,12 @@ public Collection values() { // return values; } + @Override public Set> entrySet() { return backingMap.entrySet(); -// HashSet> entryset = new HashSet>(); -// entryset.addAll(entries); -// return entryset; +// HashSet> entrySet = new HashSet>(); +// entrySet.addAll(entries); +// return entrySet; } } diff --git a/jme3-core/src/main/java/com/jme3/util/ListSort.java b/jme3-core/src/main/java/com/jme3/util/ListSort.java index 517f2dd6b2..2ed5f9af76 100644 --- a/jme3-core/src/main/java/com/jme3/util/ListSort.java +++ b/jme3-core/src/main/java/com/jme3/util/ListSort.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,12 +39,12 @@ * It's adapted from Tim Peters's work on list sorting for Python. More details * here http://svn.python.org/projects/python/trunk/Objects/listsort.txt * - * here is the C code from which this class is based + * Here is the C code on which this class is based: * http://svn.python.org/projects/python/trunk/Objects/listobject.c * - * This class was also greatly inspired from java 7 TimSort by Josh Blosh with the - * difference that the temporary necessary memory space are allocated as the - * geometry list grows and reused all along the application execution. + * This class was also greatly inspired by the Java7 TimSort by Josh Blosh, with the + * difference that temporary memory is allocated as the + * geometry list grows and reused throughout the execution of the application. * * Usage : ListSort has to be instantiated and kept with the geometry list ( or * w/e it may have to sort) Then the allocate method has to be called to @@ -53,11 +53,11 @@ * list changes * * {@code - * Disclaimer : I was intrigued by the use of val >>> 1 in java 7 Timsort class - * instead of val / 2 (integer division). Micro benching revealed that val >>> 1 - * is twice faster than val / 2 in java 6 and has similar perf in java 7. The - * following code uses val >>> 1 when ever a value needs to be divided by 2 and - * rounded to its floor + * Disclaimer : I was intrigued by the use of val >>> 1 in the Java7 Timsort + * in place of val / 2 (integer division). Micro benching revealed that val >>> 1 + * is twice as fast as val / 2 in Java6 and has similar perf in Java7. The + * following code uses val >>> 1 whenever a value needs to be divided by 2 and + * rounded to its floor. * } * * @author Nehon @@ -65,8 +65,8 @@ public class ListSort { /** - * Threshold for binary sort vs merge. Original algorithm use 64, java7 - * TimSort uses 32 and I used 128, see this post for explanations : + * Threshold for binary sort vs merge. Original algorithm used 64, Java7 + * TimSort used 32, and I used 128. See this post for explanations: * https://hub.jmonkeyengine.org/t/i-got-that-sorted-out-huhuhu/24478 */ private static final int MIN_SIZE = 128; @@ -75,30 +75,30 @@ public class ListSort { private Comparator comparator; /** - * attribute temp vars for merging. This was used to unroll the merge_lo & + * Attribute temp vars for merging. This was used to unroll the merge_lo & * merge_hi function of original implementations that used massive labeled - * goto branching and was almost unreadable + * goto branching and was almost unreadable. */ int iterA, iterB, dest, lengthA, lengthB; /** - * Number of runs to merge + * Number of runs to merge. */ private int nbRuns = 0; - /* Try to used a kind of structure like in the original implementation. - * Ended up using 2 arrays as done in the java 7 Timsort. - * Original implementation use a struct, but instanciation of this inner + /* Tried to use a struct, as in the original implementation, but + * ended up using 2 arrays, like the Java7 Timsort. + * Original implementation used a struct, but instantiation of this inner * class + array was a convoluted pain. */ /** - * array of start indices in the original array for runs : run i starting - * index is at runIndices[i] + * Array of run start indices in the original array: starting + * index of run 'i' is at runIndices[i]. */ private int[] runsIndices = null; /** - * array of runs length in the original array : run i length is at - * runLength[i] + * Array of run lengths in the original array: length of run 'i' is at + * runLength[i]. */ private int[] runsLength = null; /** @@ -109,12 +109,12 @@ public class ListSort { /** * MIN_GALLOP set to 7 constant as described in listsort.txt. this magic * number indicates how many wins should trigger the switch from binary - * search to galloping mode + * search to galloping mode. */ private static final int MIN_GALLOP = 7; /** - * This variable allows to adjust when switching to galloping mode. lowered - * when the data are "naturally" structured, raised when data are random. + * Controls switching to galloping mode. It is lowered + * when the data are "naturally" ordered and raised when they are random. */ private int minGallop = MIN_GALLOP; @@ -129,21 +129,22 @@ public ListSort() { * at least once, but only if the length of the list to sort changed before * sorting * - * @param len + * @param len the size of the array to sort */ + @SuppressWarnings("unchecked") public final void allocateStack(int len) { length = len; /* * We allocate a temp array of half the size of the array to sort. * the original implementation had a 256 maximum size for this and made - * the temp array grow on demand + * the temp array grow on demand. * * Timsort consumes half the size of the original array to merge at WORST. - * But considering we use the same temp array over and over across frames - * There is a good chance we stumble upon the worst case scenario one - * moment or another. - * So we just always take half of the original array size. + * But considering we use the same temp array over and over across frames, + * there is a good chance we will stumble upon the worst-case scenario at one + * time or another. + * So we use half of the original array size. */ int tmpLen = len >>> 1; @@ -154,9 +155,9 @@ public final void allocateStack(int len) { } /* - * this part was taken from java 7 TimSort. + * This part was taken from the Java7 TimSort. * The original implementation use a stack of length 85, but this seems - * to boost up performance for mid sized arrays. + * to boost performance for mid-sized arrays. * I changed the numbers so they fit our MIN_SIZE parameter. * * Those numbers can be computed using this formula : @@ -199,9 +200,9 @@ public void sort(T[] array, Comparator comparator) { int remaining = high - low; /* - * If array's size is bellow min_size we perform a binary insertion sort + * If array's size is below min_size, we perform a binary insertion sort, * but first we check if some existing ordered pattern exists to reduce - * the size of data to be sorted + * the size of data to be sorted. */ if (remaining < MIN_SIZE) { int runLength = getRunLength(array, low, high, comparator); @@ -210,7 +211,7 @@ public void sort(T[] array, Comparator comparator) { } /* - * main iteration : compute minrun length, then iterate through the + * Main iteration: compute minimum run length, then iterate through the * array to find runs and merge them until they can be binary sorted * if their length < minLength */ @@ -218,8 +219,8 @@ public void sort(T[] array, Comparator comparator) { while (remaining != 0) { int runLength = getRunLength(array, low, high, comparator); - /* if runlength is bellow the threshold we binary sort the remaining - * elements + /* If the run length is below the threshold, binary sort the remaining + * elements. */ if (runLength < minLength) { int newLength = remaining <= minLength ? remaining : minLength; @@ -280,8 +281,8 @@ private int getRunLength(T[] array, int firstId, int lastId, runEnd++; } // the run's order is descending, it has to be reversed - // original algorithmm return a descending = 1 value and the - //reverse is done in the sort method. Looks good to have it here though + // original algorithm returned a descending = 1 value and the + // reverse was done in the sort method. Looks good to have it here though reverseArray(array, firstId, runEnd); } @@ -292,7 +293,7 @@ private int getRunLength(T[] array, int firstId, int lastId, } /** - * binarysort is the best method for sorting small arrays: it does few + * Binary sort is the best method for sorting small arrays: it does few * compares, but can do data movement quadratic in the number of elements. * [firstId, lastId] is a contiguous slice of a list, and is sorted via * binary insertion. This sort is stable. On entry, must have firstId <= @@ -302,8 +303,8 @@ private int getRunLength(T[] array, int firstId, int lastId, * @param array the array to sort * @param firstId the index of the first element to sort * @param lastId the index+ of the last element to sort - * @param start the index of the element to start sorting range - * [firstId,satrt]is assumed to be already sorted + * @param start the index of the element to start sorting range. + * [firstId,start] is assumed to be already sorted. * @param comparator the comparator */ private void binaryInsertionSort(T[] array, int firstId, int lastId, int start, @@ -343,8 +344,8 @@ private void binaryInsertionSort(T[] array, int firstId, int lastId, int start, */ int nbElems = start - left; /* - * grabbed from java7 TimSort, the switch is an optimization to - * arraycopy in case there are 1 or 2 elements only to copy + * Grabbed from the Java7 TimSort, this switch optimizes + * arraycopy() in case there are only 1 or 2 elements to copy. */ switch (nbElems) { case 2: @@ -513,9 +514,9 @@ private int gallopLeft(T key, T[] array, int idx, int length, int hint, while (offset < maxOffset && comparator.compare(key, array[idx + hint + offset]) > 0) { lastOffset = offset; offset = (offset << 1) + 1; - /* int overflow. - * Note : not sure if that can happen but it's here in both - * original and java 7 TimSort implementation + /* int overflow. + * Note: not sure if this can happen, but it's included in both the + * original and Java7 TimSort implementations. */ if (offset <= 0) { offset = maxOffset; @@ -525,7 +526,7 @@ private int gallopLeft(T key, T[] array, int idx, int length, int hint, offset = maxOffset; } - // Translate back to offsets relative to idx. + // Translate back into offsets relative to idx. lastOffset += hint; offset += hint; } else { @@ -536,9 +537,9 @@ private int gallopLeft(T key, T[] array, int idx, int length, int hint, while (offset < maxOffset && comparator.compare(key, array[idx + hint - offset]) <= 0) { lastOffset = offset; offset = (offset << 1) + 1; - /* int overflow. - * Note : not sure if that can happen but it's here in both - * original and java 7 TimSort implementation + /* int overflow. + * Note: not sure if this can happen, but it's included in both the + * original and Java7 TimSort implementations. */ if (offset <= 0) { offset = maxOffset; @@ -605,8 +606,8 @@ private int gallopRight(T key, T[] array, int idx, int length, lastOffset = offset; offset = (offset << 1) + 1; /* int overflow. - * Note : not sure if that can happen but it's here in both - * original and java 7 TimSort implementation + * Note: not sure if this can happen, but it's included in both + * the original and Java7 TimSort implementations. */ if (offset <= 0) { offset = maxOffset; @@ -669,9 +670,9 @@ private int gallopRight(T key, T[] array, int idx, int length, * lenA <= lenB. See listsort.txt for more info. * * @param idxA index of first element in run A - * @param lengthA length of run A + * @param lenA length of run A * @param idxB index of first element in run B - * @param lengthB length of run B + * @param lenB length of run B */ private void mergeLow(int idxA, int lenA, int idxB, int lenB) { @@ -821,9 +822,9 @@ public void innerMergeLow(Comparator comp, T[] arr, T[] tempArray) { * lenA >= lenB. See listsort.txt for more info. * * @param idxA index of first element in run A - * @param lengthA length of run A + * @param lenA length of run A * @param idxB index of first element in run B - * @param lengthB length of run B + * @param lenB length of run B */ private void mergeHigh(int idxA, int lenA, int idxB, int lenB) { @@ -992,14 +993,18 @@ public int getLength() { return length; } - /* + /** * test case + * + * @param argv ignored */ + @SuppressWarnings("unchecked") public static void main(String[] argv) { Integer[] arr = new Integer[]{5, 6, 2, 9, 10, 11, 12, 8, 3, 12, 3, 7, 12, 32, 458, 12, 5, 3, 78, 45, 12, 32, 58, 45, 65, 45, 98, 45, 65, 2, 3, 47, 21, 35}; ListSort ls = new ListSort(); ls.allocateStack(34); ls.sort(arr, new Comparator() { + @Override public int compare(Integer o1, Integer o2) { int x = o1 - o2; return (x == 0) ? 0 : (x > 0) ? 1 : -1; diff --git a/jme3-core/src/main/java/com/jme3/util/LittleEndien.java b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java index abff3acca8..5ecb31134a 100644 --- a/jme3-core/src/main/java/com/jme3/util/LittleEndien.java +++ b/jme3-core/src/main/java/com/jme3/util/LittleEndien.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,7 +35,7 @@ /** * LittleEndien is a class to read little-endian stored data - * via a InputStream. All functions work as defined in DataInput, but + * via an InputStream. All functions work as defined in DataInput, but * assume they come from a LittleEndien input stream. Currently used to read .ms3d and .3ds files. * @author Jack Lindamood */ @@ -52,6 +52,7 @@ public LittleEndien(InputStream in) { this.in = new BufferedInputStream(in); } + @Override public int read() throws IOException { return in.read(); } @@ -61,17 +62,24 @@ public int read(byte[] buf) throws IOException { return in.read(buf); } + /** + * Attempts(!) to read up to len bytes into buf at offset off. + */ @Override public int read(byte[] buf, int off, int len) throws IOException { return in.read(buf, off, len); } + @Override public int readUnsignedShort() throws IOException { return (in.read() & 0xff) | ((in.read() & 0xff) << 8); } /** * read an unsigned int as a long + * + * @return the value that was read + * @throws IOException if an I/O error occurs while reading */ public long readUInt() throws IOException { return ((in.read() & 0xff) @@ -80,26 +88,32 @@ public long readUInt() throws IOException { | (((long) (in.read() & 0xff)) << 24)); } + @Override public boolean readBoolean() throws IOException { return (in.read() != 0); } + @Override public byte readByte() throws IOException { return (byte) in.read(); } + @Override public int readUnsignedByte() throws IOException { return in.read(); } + @Override public short readShort() throws IOException { return (short) this.readUnsignedShort(); } + @Override public char readChar() throws IOException { return (char) this.readUnsignedShort(); } + @Override public int readInt() throws IOException { return ((in.read() & 0xff) | ((in.read() & 0xff) << 8) @@ -107,6 +121,7 @@ public int readInt() throws IOException { | ((in.read() & 0xff) << 24)); } + @Override public long readLong() throws IOException { return ((in.read() & 0xff) | ((long) (in.read() & 0xff) << 8) @@ -118,30 +133,47 @@ public long readLong() throws IOException { | ((long) (in.read() & 0xff) << 56)); } + @Override public float readFloat() throws IOException { return Float.intBitsToFloat(readInt()); } + @Override public double readDouble() throws IOException { return Double.longBitsToDouble(readLong()); } + @Override public void readFully(byte b[]) throws IOException { - in.read(b, 0, b.length); + readFully(b, 0, b.length); } + @Override public void readFully(byte b[], int off, int len) throws IOException { - in.read(b, off, len); + int totalRead = 0; + while (totalRead < len) { + int bytesRead = in.read(b, off + totalRead, len - totalRead); + if (bytesRead < 0) { + throw new EOFException("Reached end of stream before reading fully."); + } + totalRead += bytesRead; + } } + /** + * Attempts(!) to skip n bytes in the input stream. + */ + @Override public int skipBytes(int n) throws IOException { return (int) in.skip(n); } + @Override public String readLine() throws IOException { throw new IOException("Unsupported operation"); } + @Override public String readUTF() throws IOException { throw new IOException("Unsupported operation"); } diff --git a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java index b0f3565e44..62d4c6c5de 100644 --- a/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java +++ b/jme3-core/src/main/java/com/jme3/util/MaterialDebugAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -98,9 +98,8 @@ public class MaterialDebugAppState extends AbstractAppState { private RenderManager renderManager; private AssetManager assetManager; private InputManager inputManager; - private List bindings = new ArrayList(); - private Map> fileTriggers = new HashMap> (); - + private final List bindings = new ArrayList<>(); + private final Map> fileTriggers = new HashMap<>(); @Override public void initialize(AppStateManager stateManager, Application app) { @@ -119,20 +118,18 @@ public void initialize(AppStateManager stateManager, Application app) { * @param spat the spatial to reload */ public void registerBinding(Trigger trigger, final Spatial spat) { - if(spat instanceof Geometry){ - GeometryBinding binding = new GeometryBinding(trigger, (Geometry)spat); + if (spat instanceof Geometry) { + GeometryBinding binding = new GeometryBinding(trigger, (Geometry) spat); bindings.add(binding); if (isInitialized()) { bind(binding); } - }else if (spat instanceof Node){ - for (Spatial child : ((Node)spat).getChildren()) { + } else if (spat instanceof Node) { + for (Spatial child : ((Node) spat).getChildren()) { registerBinding(trigger, child); } } } - - /** * Will reload the filter's materials whenever the trigger is fired. @@ -146,7 +143,6 @@ public void registerBinding(Trigger trigger, final Filter filter) { bind(binding); } } - /** * Will reload the filter's materials whenever the shader file is changed @@ -160,7 +156,7 @@ public void registerBinding(String shaderName, final Filter filter) { } /** - * Will reload the spatials's materials whenever the shader file is changed + * Will reload the spatial's materials whenever the shader file is changed * on the hard drive * @param shaderName the shader name (relative path to the asset folder or * to a registered asset path) @@ -174,7 +170,7 @@ private void bind(final Binding binding) { if (binding.getTrigger() instanceof FileChangedTrigger) { FileChangedTrigger t = (FileChangedTrigger) binding.getTrigger(); List b = fileTriggers.get(t); - if(b == null){ + if (b == null) { t.init(); b = new ArrayList(); fileTriggers.put(t, b); @@ -183,9 +179,10 @@ private void bind(final Binding binding) { } else { final String actionName = binding.getActionName(); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (actionName.equals(name) && isPressed) { - //reloading the material + // reloading the material binding.reload(); } } @@ -196,42 +193,41 @@ public void onAction(String name, boolean isPressed, float tpf) { } public Material reloadMaterial(Material mat) { - //clear the entire cache, there might be more clever things to do, like clearing only the matdef, and the associated shaders. + // clear the entire cache, there might be more clever things to do, like + // clearing only the matdef, and the associated shaders. assetManager.clearCache(); - //creating a dummy mat with the mat def of the mat to reload + // creating a dummy mat with the mat def of the mat to reload // Force the reloading of the asset, otherwise the new shader code will not be applied. Material dummy = new Material(assetManager, mat.getMaterialDef().getAssetName()); for (MatParam matParam : mat.getParams()) { dummy.setParam(matParam.getName(), matParam.getVarType(), matParam.getValue()); } - - dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState()); - //creating a dummy geom and assigning the dummy material to it + dummy.getAdditionalRenderState().set(mat.getAdditionalRenderState()); + + // creating a dummy geom and assigning the dummy material to it Geometry dummyGeom = new Geometry("dummyGeom", new Box(1f, 1f, 1f)); dummyGeom.setMaterial(dummy); try { - //preloading the dummyGeom, this call will compile the shader again + // preloading the dummyGeom, this call will compile the shader again renderManager.preloadScene(dummyGeom); } catch (RendererException e) { - //compilation error, the shader code will be output to the console - //the following code will output the error - //System.err.println(e.getMessage()); + // compilation error, the shader code will be output to the console + // the following code will output the error Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, e.getMessage()); return null; } - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.INFO, "Material succesfully reloaded"); - //System.out.println("Material succesfully reloaded"); + Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.INFO, "Material successfully reloaded"); return dummy; } @Override public void update(float tpf) { - super.update(tpf); //To change body of generated methods, choose Tools | Templates. + super.update(tpf); for (Trigger trigger : fileTriggers.keySet()) { if (trigger instanceof FileChangedTrigger) { FileChangedTrigger t = (FileChangedTrigger) trigger; @@ -242,7 +238,7 @@ public void update(float tpf) { } } } - } + } } private interface Binding { @@ -262,23 +258,24 @@ private class GeometryBinding implements Binding { public GeometryBinding(Trigger trigger, Geometry geom) { this.trigger = trigger; this.geom = geom; - } + @Override public void reload() { Material reloadedMat = reloadMaterial(geom.getMaterial()); - //if the reload is successful, we re setup the material with its params and reassign it to the box + // if the reload is successful, we re setup the material with its params and + // reassign it to the box if (reloadedMat != null) { - // setupMaterial(reloadedMat); geom.setMaterial(reloadedMat); } } + @Override public String getActionName() { return geom.getName() + "Reload"; - } + @Override public Trigger getTrigger() { return trigger; } @@ -294,11 +291,13 @@ public FilterBinding(Trigger trigger, Filter filter) { this.filter = filter; } + @Override + @SuppressWarnings("unchecked") public void reload() { Field[] fields1 = filter.getClass().getDeclaredFields(); Field[] fields2 = filter.getClass().getSuperclass().getDeclaredFields(); - List fields = new ArrayList(); + List fields = new ArrayList<>(); fields.addAll(Arrays.asList(fields1)); fields.addAll(Arrays.asList(fields2)); Material m = new Material(); @@ -313,12 +312,11 @@ public void reload() { } else { field.set(filter, mat); } - } if (field.getType().isInstance(p)) { field.setAccessible(true); p = (Filter.Pass) field.get(filter); - if (p!= null && p.getPassMaterial() != null) { + if (p != null && p.getPassMaterial() != null) { Material mat = reloadMaterial(p.getPassMaterial()); if (mat == null) { return; @@ -329,7 +327,7 @@ public void reload() { } if (field.getName().equals("postRenderPasses")) { field.setAccessible(true); - List passes = new ArrayList(); + List passes = new ArrayList<>(); passes = (List) field.get(filter); if (passes != null) { for (Pass pass : passes) { @@ -343,18 +341,17 @@ public void reload() { } } } - } catch (IllegalArgumentException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { + } catch (IllegalArgumentException | IllegalAccessException ex) { Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); } - } + @Override public String getActionName() { return filter.getName() + "Reload"; } + @Override public Trigger getTrigger() { return trigger; } @@ -380,13 +377,10 @@ public void init() { file = new File(url.getFile()); fileLastM = file.lastModified(); - } catch (NoSuchFieldException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); - } catch (SecurityException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalArgumentException ex) { - Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); - } catch (IllegalAccessException ex) { + } catch (NoSuchFieldException + | SecurityException + | IllegalArgumentException + | IllegalAccessException ex) { Logger.getLogger(MaterialDebugAppState.class.getName()).log(Level.SEVERE, null, ex); } } @@ -400,10 +394,12 @@ public boolean shouldFire() { return false; } + @Override public String getName() { return fileName; } + @Override public int triggerHashCode() { return 0; } diff --git a/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java b/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java index 25c5294fce..a3e53acf39 100644 --- a/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/MemoryUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,12 @@ public class MemoryUtils { } } + /** + * A private constructor to inhibit instantiation of this class. + */ + private MemoryUtils() { + } + /** * * @return the direct memory used in byte. @@ -64,7 +70,8 @@ public static long getDirectMemoryUsage() { Long value = (Long)mbeans.getAttribute(directPool, "MemoryUsed"); return value == null ? -1 : value; } catch (JMException ex) { - Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘MemoryUsed’", ex); + Logger.getLogger(MemoryUtils.class.getName()) + .log(Level.SEVERE, "Error retrieving MemoryUsed", ex); return -1; } } @@ -78,7 +85,7 @@ public static long getDirectMemoryCount() { Long value = (Long)mbeans.getAttribute(directPool, "Count"); return value == null ? -1 : value; } catch (JMException ex) { - Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘Count’", ex); + Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving Count", ex); return -1; } } @@ -93,7 +100,8 @@ public static long getDirectMemoryTotalCapacity() { Long value = (Long)mbeans.getAttribute(directPool, "TotalCapacity"); return value == null ? -1 : value; } catch (JMException ex) { - Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving ‘TotalCapacity’", ex); + Logger.getLogger(MemoryUtils.class.getName()) + .log(Level.SEVERE, "Error retrieving TotalCapacity", ex); return -1; } } diff --git a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java index 7ea5b23f7b..7073dfab52 100644 --- a/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/MipMapGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,35 +43,35 @@ public class MipMapGenerator { private MipMapGenerator() { } - + public static Image scaleImage(Image inputImage, int outputWidth, int outputHeight) { int size = outputWidth * outputHeight * inputImage.getFormat().getBitsPerPixel() / 8; ByteBuffer buffer = BufferUtils.createByteBuffer(size); - Image outputImage = new Image(inputImage.getFormat(), - outputWidth, - outputHeight, - buffer, + Image outputImage = new Image(inputImage.getFormat(), + outputWidth, + outputHeight, + buffer, inputImage.getColorSpace()); - + ImageRaster input = ImageRaster.create(inputImage, 0, 0, false); ImageRaster output = ImageRaster.create(outputImage, 0, 0, false); - - float xRatio = ((float)(input.getWidth() - 1)) / output.getWidth(); - float yRatio = ((float)(input.getHeight() - 1)) / output.getHeight(); + + float xRatio = ((float) (input.getWidth() - 1)) / output.getWidth(); + float yRatio = ((float) (input.getHeight() - 1)) / output.getHeight(); ColorRGBA outputColor = new ColorRGBA(0, 0, 0, 0); ColorRGBA bottomLeft = new ColorRGBA(); ColorRGBA bottomRight = new ColorRGBA(); ColorRGBA topLeft = new ColorRGBA(); ColorRGBA topRight = new ColorRGBA(); - + for (int y = 0; y < outputHeight; y++) { for (int x = 0; x < outputWidth; x++) { float x2f = x * xRatio; float y2f = y * yRatio; - - int x2 = (int)x2f; - int y2 = (int)y2f; + + int x2 = (int) x2f; + int y2 = (int) y2f; input.getPixel(x2, y2, bottomLeft); input.getPixel(x2 + 1, y2, bottomRight); @@ -92,15 +92,15 @@ public static Image resizeToPowerOf2(Image original){ int potHeight = FastMath.nearestPowerOfTwo(original.getHeight()); return scaleImage(original, potWidth, potHeight); } - + public static void generateMipMaps(Image image){ int width = image.getWidth(); int height = image.getHeight(); Image current = image; - ArrayList output = new ArrayList(); + ArrayList output = new ArrayList<>(); int totalSize = 0; - + while (height >= 1 || width >= 1){ output.add(current.getData(0)); totalSize += current.getData(0).capacity(); diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObject.java b/jme3-core/src/main/java/com/jme3/util/NativeObject.java index 727250b70a..44ec3ad947 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObject.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,7 @@ */ package com.jme3.util; +import java.lang.ref.WeakReference; import java.nio.Buffer; /** @@ -53,7 +54,8 @@ public abstract class NativeObject implements Cloneable { OBJTYPE_AUDIOBUFFER = 6, OBJTYPE_AUDIOSTREAM = 7, OBJTYPE_FILTER = 8, - OBJTYPE_BO = 9; + OBJTYPE_BO = 9, + OBJTYPE_FENCE = 10; /** * The object manager to which this NativeObject is registered to. @@ -79,6 +81,8 @@ public abstract class NativeObject implements Cloneable { */ protected boolean updateNeeded = true; + private WeakReference weakRef; + /** * Creates a new GLObject. Should be * called by the subclasses. @@ -90,6 +94,8 @@ public NativeObject(){ /** * Protected constructor that doesn't allocate handle ref. * This is used in subclasses for the createDestructableClone(). + * + * @param id the desired ID */ protected NativeObject(int id){ this.id = id; @@ -137,6 +143,8 @@ public void clearUpdateNeeded(){ /** * Internal use only. Check if {@link #setUpdateNeeded()} was called before. + * + * @return true if an update is needed, otherwise false */ public boolean isUpdateNeeded(){ return updateNeeded; @@ -204,6 +212,8 @@ void deleteNativeBuffersInternal() { /** * Creates a shallow clone of this GL Object. The deleteObject method * should be functional for this object. + * + * @return a new instance */ public abstract NativeObject createDestructableClone(); @@ -227,4 +237,17 @@ public void dispose() { objectManager.enqueueUnusedObject(this); } } + + /** + * Acquire a weak reference to this NativeObject. + * + * @param the type + * @return a weak reference (possibly a pre-existing one) + */ + public WeakReference getWeakRef() { + if (weakRef == null) { + weakRef = new WeakReference<>(this); + } + return (WeakReference) weakRef; + } } diff --git a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java index cea15e9edf..fed89068f6 100644 --- a/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java +++ b/jme3-core/src/main/java/com/jme3/util/NativeObjectManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -69,22 +69,22 @@ public class NativeObjectManager { /** * Reference queue for {@link NativeObjectRef native object references}. */ - private ReferenceQueue refQueue = new ReferenceQueue(); + private ReferenceQueue refQueue = new ReferenceQueue<>(); /** * List of currently active GLObjects. */ - private HashMap refMap = new HashMap(); + private final HashMap refMap = new HashMap<>(); /** * List of real objects requested by user for deletion. */ - private ArrayDeque userDeletionQueue = new ArrayDeque(); + private final ArrayDeque userDeletionQueue = new ArrayDeque<>(); private static class NativeObjectRef extends PhantomReference { - private NativeObject objClone; - private WeakReference realObj; + final private NativeObject objClone; + final private WeakReference realObj; public NativeObjectRef(ReferenceQueue refQueue, NativeObject obj){ super(obj.handleRef, refQueue); @@ -98,6 +98,8 @@ public NativeObjectRef(ReferenceQueue refQueue, NativeObject obj){ /** * (Internal use only) Register a NativeObject with the manager. + * + * @param obj the object to register (not null) */ public void registerObject(NativeObject obj) { if (obj.getId() <= 0) { @@ -138,6 +140,7 @@ private void deleteNativeObject(Object rendererObject, NativeObject obj, NativeO } assert ref == null || ref == ref2; + ref2.clear(); int id = obj.getId(); @@ -196,10 +199,12 @@ public void deleteUnused(Object rendererObject){ /** * (Internal use only) Deletes all objects. * Must only be called when display is destroyed. + * + * @param rendererObject the renderer object */ public void deleteAllObjects(Object rendererObject){ deleteUnused(rendererObject); - ArrayList refMapCopy = new ArrayList(refMap.values()); + ArrayList refMapCopy = new ArrayList<>(refMap.values()); for (NativeObjectRef ref : refMapCopy) { deleteNativeObject(rendererObject, ref.objClone, ref, true, false); } @@ -232,9 +237,7 @@ public void resetObjects(){ } realObj.resetObject(); - if (logger.isLoggable(Level.FINEST)) { - logger.log(Level.FINEST, "Reset: {0}", realObj); - } + logger.log(Level.FINEST, "Reset: {0}", realObj); } refMap.clear(); refQueue = new ReferenceQueue(); diff --git a/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java b/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java index 4fa0ca1574..63edfecaa7 100644 --- a/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java +++ b/jme3-core/src/main/java/com/jme3/util/PlaceholderAssets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,6 +71,12 @@ public class PlaceholderAssets { (byte)0xFF, (byte)0xFF, (byte)0xFF, }; + /** + * A private constructor to inhibit instantiation of this class. + */ + private PlaceholderAssets() { + } + @Deprecated public static Image getPlaceholderImage(){ ByteBuffer tempData = BufferUtils.createByteBuffer(3 * 4 * 4); diff --git a/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java b/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java index 9762dc1584..860a6536be 100644 --- a/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java +++ b/jme3-core/src/main/java/com/jme3/util/PrimitiveAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,17 +39,26 @@ * on any jvm */ public final class PrimitiveAllocator implements BufferAllocator { - + /** + * De-allocate a direct buffer. + * + * @param toBeDestroyed ignored + */ @Override public void destroyDirectBuffer(Buffer toBeDestroyed) { - // no exception by intent, as this way naivly written java7/8 + // no exception by intent, as this way naively written java7/8 // applications won't crash on 9 assuming they can dispose buffers System.err.println("Warning destroyBuffer not supported"); } + /** + * Allocate a direct ByteBuffer of the specified size. + * + * @param size in bytes (≥0) + * @return a new direct buffer + */ @Override public ByteBuffer allocate(int size) { return ByteBuffer.allocateDirect(size); } - } diff --git a/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java index a55d7c9180..b71ad69910 100644 --- a/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java +++ b/jme3-core/src/main/java/com/jme3/util/ReflectionAllocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,8 +64,7 @@ public final class ReflectionAllocator implements BufferAllocator { Class clazz = bb.getClass(); try { freeMethod = clazz.getMethod("free"); - } catch (NoSuchMethodException ex) { - } catch (SecurityException ex) { + } catch (NoSuchMethodException | SecurityException ex) { } } @@ -74,81 +73,78 @@ private static Method loadMethod(String className, String methodName) { Method method = Class.forName(className).getMethod(methodName); method.setAccessible(true);// according to the Java documentation, by default, a reflected object is not accessible return method; - } catch (NoSuchMethodException ex) { - return null; // the method was not found - } catch (SecurityException ex) { - return null; // setAccessible not allowed by security policy - } catch (ClassNotFoundException ex) { - return null; // the direct buffer implementation was not found + } catch (NoSuchMethodException // the method was not found + | SecurityException // setAccessible not allowed by security policy + | ClassNotFoundException ex) { // the direct buffer implementation was not found + return null; } catch (Throwable t) { - if (t.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { - return null;// the class is in an unexported module - } else { - throw t; - } + if (t.getClass().getName().equals("java.lang.reflect.InaccessibleObjectException")) { + return null;// the class is in an unexported module + } else { + throw t; + } } } - @Override /** * This function explicitly calls the Cleaner method of a direct buffer. - * + * * @param toBeDestroyed * The direct buffer that will be "cleaned". Utilizes reflection. - * */ + @Override public void destroyDirectBuffer(Buffer toBeDestroyed) { try { if (freeMethod != null) { freeMethod.invoke(toBeDestroyed); } else { - //TODO load the methods only once, store them into a cache (only for Java >= 9) - Method localCleanerMethod; - if (cleanerMethod == null) { - localCleanerMethod = loadMethod(toBeDestroyed.getClass().getName(), "cleaner"); - } else { - localCleanerMethod = cleanerMethod; - } - if (localCleanerMethod == null) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, - "Buffer cannot be destroyed: {0}", toBeDestroyed); - } else { - Object cleaner = localCleanerMethod.invoke(toBeDestroyed); - if (cleaner != null) { - Method localCleanMethod; - if (cleanMethod == null) { - if (cleaner instanceof Runnable) { - // jdk.internal.ref.Cleaner implements Runnable in Java 9 - localCleanMethod = loadMethod(Runnable.class.getName(), "run"); - } else { - // sun.misc.Cleaner does not implement Runnable in Java < 9 - localCleanMethod = loadMethod(cleaner.getClass().getName(), "clean"); - } - } else { - localCleanMethod = cleanMethod; - } - if (localCleanMethod == null) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, - "Buffer cannot be destroyed: {0}", toBeDestroyed); - } else { - localCleanMethod.invoke(cleaner); - } - } else { - Method localViewedBufferMethod; - if (viewedBufferMethod == null) { - localViewedBufferMethod = loadMethod(toBeDestroyed.getClass().getName(), "viewedBuffer"); - } else { - localViewedBufferMethod = viewedBufferMethod; - } - if (localViewedBufferMethod == null) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, - "Buffer cannot be destroyed: {0}", toBeDestroyed); - } else { - // Try the alternate approach of getting the viewed - // buffer - // first - Object viewedBuffer = localViewedBufferMethod.invoke(toBeDestroyed); - if (viewedBuffer != null) { + //TODO load the methods only once, store them into a cache (only for Java >= 9) + Method localCleanerMethod; + if (cleanerMethod == null) { + localCleanerMethod = loadMethod(toBeDestroyed.getClass().getName(), "cleaner"); + } else { + localCleanerMethod = cleanerMethod; + } + if (localCleanerMethod == null) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, + "Buffer cannot be destroyed: {0}", toBeDestroyed); + } else { + Object cleaner = localCleanerMethod.invoke(toBeDestroyed); + if (cleaner != null) { + Method localCleanMethod; + if (cleanMethod == null) { + if (cleaner instanceof Runnable) { + // jdk.internal.ref.Cleaner implements Runnable in Java 9 + localCleanMethod = loadMethod(Runnable.class.getName(), "run"); + } else { + // sun.misc.Cleaner does not implement Runnable in Java < 9 + localCleanMethod = loadMethod(cleaner.getClass().getName(), "clean"); + } + } else { + localCleanMethod = cleanMethod; + } + if (localCleanMethod == null) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, + "Buffer cannot be destroyed: {0}", toBeDestroyed); + } else { + localCleanMethod.invoke(cleaner); + } + } else { + Method localViewedBufferMethod; + if (viewedBufferMethod == null) { + localViewedBufferMethod = loadMethod(toBeDestroyed.getClass().getName(), "viewedBuffer"); + } else { + localViewedBufferMethod = viewedBufferMethod; + } + if (localViewedBufferMethod == null) { + Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, + "Buffer cannot be destroyed: {0}", toBeDestroyed); + } else { + // Try the alternate approach of getting the viewed + // buffer + // first + Object viewedBuffer = localViewedBufferMethod.invoke(toBeDestroyed); + if (viewedBuffer != null) { if (viewedBuffer instanceof Buffer) { destroyDirectBuffer((Buffer) viewedBuffer); } @@ -157,18 +153,15 @@ public void destroyDirectBuffer(Buffer toBeDestroyed) { } else { Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "Buffer cannot be destroyed: {0}", toBeDestroyed); - } - } - } - } + } + } + } + } } - } catch (IllegalAccessException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (IllegalArgumentException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (InvocationTargetException ex) { - Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); - } catch (SecurityException ex) { + } catch (IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | SecurityException ex) { Logger.getLogger(BufferUtils.class.getName()).log(Level.SEVERE, "{0}", ex); } } diff --git a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java index 0085b20d3d..432f51ad18 100644 --- a/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java +++ b/jme3-core/src/main/java/com/jme3/util/SafeArrayList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ *
              • The ListIterators returned by this class only support the remove() * modification method. add() and set() are not supported on the iterator. * Even after ListIterator.remove() or Iterator.remove() is called, this change - * is not reflected in the iterator instance as it is still refering to its + * is not reflected in the iterator instance as it is still referring to its * original snapshot. * * @@ -80,10 +80,10 @@ public class SafeArrayList implements List, Cloneable { // make this publicly act like a read-only list. // SafeArrayList-specific methods could then be exposed // for the classes like Node and Spatial to use to manage - // the list. This was the callers couldn't remove a child + // the list. This was because the callers couldn't remove a child // without it being detached properly, for example. - private Class elementType; + private final Class elementType; private List buffer; private E[] backingArray; private int size = 0; @@ -103,24 +103,27 @@ public SafeArrayList(final Class elementType, final Collection c this.size = buffer.size(); } + @Override + @SuppressWarnings("unchecked") public SafeArrayList clone() { try { SafeArrayList clone = (SafeArrayList)super.clone(); // Clone whichever backing store is currently active - if( backingArray != null ) { + if (backingArray != null) { clone.backingArray = backingArray.clone(); } - if( buffer != null ) { + if (buffer != null) { clone.buffer = (List)((ArrayList)buffer).clone(); } return clone; - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new AssertionError(); } } + @SuppressWarnings("unchecked") protected final T[] createArray(Class type, int size) { return (T[])java.lang.reflect.Array.newInstance(type, size); } @@ -134,59 +137,68 @@ protected final E[] createArray(int size) { * is guaranteed not to change through further List manipulation. * Changes to this array may or may not be reflected in the list and * should be avoided. + * + * @return either the pre-existing array or a new one */ public final E[] getArray() { - if( backingArray != null ) + if (backingArray != null) return backingArray; - if( buffer == null ) { + if (buffer == null) { backingArray = createArray(0); } else { // Only keep the array or the buffer but never both at // the same time. 1) it saves space, 2) it keeps the rest // of the code safer. - backingArray = buffer.toArray( createArray(buffer.size()) ); + backingArray = buffer.toArray(createArray(buffer.size())); buffer = null; } return backingArray; } protected final List getBuffer() { - if( buffer != null ) + if (buffer != null) return buffer; - if( backingArray == null ) { + if (backingArray == null) { buffer = new ArrayList<>(); } else { // Only keep the array or the buffer but never both at // the same time. 1) it saves space, 2) it keeps the rest // of the code safer. - buffer = new ArrayList<>( Arrays.asList(backingArray) ); + buffer = new ArrayList<>(Arrays.asList(backingArray)); backingArray = null; } return buffer; } + @Override public final int size() { return size; } + @Override public final boolean isEmpty() { return size == 0; } + @Override public boolean contains(Object o) { return indexOf(o) >= 0; } + @Override public Iterator iterator() { return listIterator(); } + @Override public Object[] toArray() { return getArray(); } + @Override + @SuppressWarnings("unchecked") public T[] toArray(T[] a) { E[] array = getArray(); @@ -194,7 +206,7 @@ public T[] toArray(T[] a) { return (T[])Arrays.copyOf(array, array.length, a.getClass()); } - System.arraycopy( array, 0, a, 0, array.length ); + System.arraycopy(array, 0, a, 0, array.length); if (a.length > array.length) { a[array.length] = null; @@ -203,51 +215,60 @@ public T[] toArray(T[] a) { return a; } + @Override public boolean add(E e) { boolean result = getBuffer().add(e); size = getBuffer().size(); return result; } + @Override public boolean remove(Object o) { boolean result = getBuffer().remove(o); size = getBuffer().size(); return result; } + @Override public boolean containsAll(Collection c) { return Arrays.asList(getArray()).containsAll(c); } + @Override public boolean addAll(Collection c) { boolean result = getBuffer().addAll(c); size = getBuffer().size(); return result; } + @Override public boolean addAll(int index, Collection c) { boolean result = getBuffer().addAll(index, c); size = getBuffer().size(); return result; } + @Override public boolean removeAll(Collection c) { boolean result = getBuffer().removeAll(c); size = getBuffer().size(); return result; } + @Override public boolean retainAll(Collection c) { boolean result = getBuffer().retainAll(c); size = getBuffer().size(); return result; } + @Override public void clear() { getBuffer().clear(); size = 0; } + @Override public boolean equals(Object o) { if (o == this) { @@ -265,168 +286,186 @@ public boolean equals(Object o) { List other = (List)o; Iterator i1 = iterator(); Iterator i2 = other.iterator(); - while( i1.hasNext() && i2.hasNext() ) { + while (i1.hasNext() && i2.hasNext()) { Object o1 = i1.next(); Object o2 = i2.next(); - if( o1 == o2 ) + if (o1 == o2) continue; - if( o1 == null || !o1.equals(o2) ) + if (o1 == null || !o1.equals(o2)) return false; } return !(i1.hasNext() || i2.hasNext()); } + @Override public int hashCode() { // Exactly the hash code described in the List interface, basically E[] array = getArray(); int result = 1; - for( E e : array ) { + for (E e : array) { result = 31 * result + (e == null ? 0 : e.hashCode()); } return result; } + @Override public final E get(int index) { - if( backingArray != null ) + if (backingArray != null) return backingArray[index]; - if( buffer != null ) + if (buffer != null) return buffer.get(index); - throw new IndexOutOfBoundsException( "Index:" + index + ", Size:0" ); + throw new IndexOutOfBoundsException("Index:" + index + ", Size:0"); } + @Override public E set(int index, E element) { return getBuffer().set(index, element); } + @Override public void add(int index, E element) { getBuffer().add(index, element); size = getBuffer().size(); } + @Override public E remove(int index) { E result = getBuffer().remove(index); size = getBuffer().size(); return result; } + @Override public int indexOf(Object o) { E[] array = getArray(); - for( int i = 0; i < array.length; i++ ) { + for (int i = 0; i < array.length; i++) { E element = array[i]; - if( element == o ) { + if (element == o) { return i; } - if( element != null && element.equals(o) ) { + if (element != null && element.equals(o)) { return i; } } return -1; } + @Override public int lastIndexOf(Object o) { E[] array = getArray(); - for( int i = array.length - 1; i >= 0; i-- ) { + for (int i = array.length - 1; i >= 0; i--) { E element = array[i]; - if( element == o ) { + if (element == o) { return i; } - if( element != null && element.equals(o) ) { + if (element != null && element.equals(o)) { return i; } } return -1; } + @Override public ListIterator listIterator() { return new ArrayIterator(getArray(), 0); } + @Override public ListIterator listIterator(int index) { return new ArrayIterator(getArray(), index); } + @Override public List subList(int fromIndex, int toIndex) { - - // So far JME doesn't use subList that I can see so I'm nerfing it. + // So far, JME doesn't use subList that I can see, so I'm nerfing it. List raw = Arrays.asList(getArray()).subList(fromIndex, toIndex); return Collections.unmodifiableList(raw); } + @Override public String toString() { - E[] array = getArray(); - if( array.length == 0 ) { + if (array.length == 0) { return "[]"; } StringBuilder sb = new StringBuilder(); sb.append('['); - for( int i = 0; i < array.length; i++ ) { - if( i > 0 ) - sb.append( ", " ); + for (int i = 0; i < array.length; i++) { + if (i > 0) + sb.append(", "); E e = array[i]; - sb.append( e == this ? "(this Collection)" : e ); + sb.append(e == this ? "(this Collection)" : e); } sb.append(']'); return sb.toString(); } protected class ArrayIterator implements ListIterator { - private E[] array; + final private E[] array; private int next; private int lastReturned; - protected ArrayIterator( E[] array, int index ) { + protected ArrayIterator(E[] array, int index) { this.array = array; this.next = index; this.lastReturned = -1; } + @Override public boolean hasNext() { return next != array.length; } + @Override public E next() { - if( !hasNext() ) + if (!hasNext()) throw new NoSuchElementException(); lastReturned = next++; return array[lastReturned]; } + @Override public boolean hasPrevious() { return next != 0; } + @Override public E previous() { - if( !hasPrevious() ) + if (!hasPrevious()) throw new NoSuchElementException(); lastReturned = --next; return array[lastReturned]; } + @Override public int nextIndex() { return next; } + @Override public int previousIndex() { return next - 1; } + @Override public void remove() { - // This operation is not so easy to do but we will fake it. + // This operation is not so easy to do, but we will fake it. // The issue is that the backing list could be completely - // different than the one this iterator is a snapshot of. + // different from the one this iterator is a snapshot of. // We'll just remove(element) which in most cases will be // correct. If the list had earlier .equals() equivalent // elements then we'll remove one of those instead. Either // way, none of those changes are reflected in this iterator. - SafeArrayList.this.remove( array[lastReturned] ); + SafeArrayList.this.remove(array[lastReturned]); } + @Override public void set(E e) { throw new UnsupportedOperationException(); } + @Override public void add(E e) { throw new UnsupportedOperationException(); } diff --git a/jme3-core/src/main/java/com/jme3/util/SkyFactory.java b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java index 9ef2a37a3d..48794ad991 100644 --- a/jme3-core/src/main/java/com/jme3/util/SkyFactory.java +++ b/jme3-core/src/main/java/com/jme3/util/SkyFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,7 @@ import com.jme3.material.Material; import com.jme3.math.Vector3f; import com.jme3.renderer.queue.RenderQueue.Bucket; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Sphere; @@ -49,19 +50,18 @@ /** * SkyFactory is used to create jME {@link Spatial}s that can * be attached to the scene to display a sky image in the background. - * + * * @author Kirill Vainer */ public class SkyFactory { - /** - * The type of map fed to the shader + * The type of map fed to the shader */ - public enum EnvMapType{ + public enum EnvMapType { /** * The env map is a cube map see {@link TextureCubeMap} or 6 separate images that form a cube map - * The texture is either a {@link TextureCubeMap} or 6 {@link com.jme3.texture.Texture2D}. + * The texture is either a {@link TextureCubeMap} or 6 {@link com.jme3.texture.Texture2D}. * In the latter case, a TextureCubeMap is build from the 6 2d maps. */ CubeMap, @@ -72,14 +72,20 @@ public enum EnvMapType{ */ SphereMap, /** - * The env map is an Equirectangular map. A 2D textures with pixels - * arranged for equirectangular + * The env map is an Equirectangular map. A 2D textures with pixels + * arranged for equirectangular * projection mapping.. - * + * */ EquirectMap } - + + /** + * A private constructor to inhibit instantiation of this class. + */ + private SkyFactory() { + } + /** * Create a sky with radius=10 using the given cubemap or spheremap texture. * @@ -102,9 +108,10 @@ public enum EnvMapType{ * * @return a new spatial representing the sky, ready to be attached to the * scene graph - * @deprecated use {@link SkyFactory#createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType)} + * @deprecated use {@link SkyFactory#createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, + * com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType)} */ - @Deprecated + @Deprecated public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap) { return createSky(assetManager, texture, normalScale, sphereMap, 10); @@ -123,12 +130,13 @@ public static Spatial createSky(AssetManager assetManager, Texture texture, * transformation to the normal. * @param envMapType see {@link EnvMapType} * @return a new spatial representing the sky, ready to be attached to the - * scene graph + * scene graph */ public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, EnvMapType envMapType) { return createSky(assetManager, texture, normalScale, envMapType, 10); } + /** * Create a sky using the given cubemap or spheremap texture. * @@ -151,15 +159,17 @@ public static Spatial createSky(AssetManager assetManager, Texture texture, * frustum * @return a new spatial representing the sky, ready to be attached to the * scene graph - * @deprecated use {@link #createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType, float)} + * @deprecated use {@link #createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, + * com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType, float)} */ @Deprecated public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, boolean sphereMap, int sphereRadius) { - return createSky(assetManager, texture, normalScale, sphereMap?EnvMapType.SphereMap:EnvMapType.CubeMap, sphereRadius); + return createSky(assetManager, texture, normalScale, + sphereMap ? EnvMapType.SphereMap : EnvMapType.CubeMap, sphereRadius); } - - /** + + /** * Create a sky using the given cubemap or spheremap texture. * * @param assetManager from which to load materials @@ -172,9 +182,9 @@ public static Spatial createSky(AssetManager assetManager, Texture texture, * its radius must fall between the near and far planes of the camera's * frustum * @return a new spatial representing the sky, ready to be attached to the - * scene graph + * scene graph */ - public static Spatial createSky(AssetManager assetManager, Texture texture, + public static Spatial createSky(AssetManager assetManager, Texture texture, Vector3f normalScale, EnvMapType envMapType, float sphereRadius) { if (texture == null) { throw new IllegalArgumentException("texture cannot be null"); @@ -185,25 +195,34 @@ public static Spatial createSky(AssetManager assetManager, Texture texture, sky.setQueueBucket(Bucket.Sky); sky.setCullHint(Spatial.CullHint.Never); sky.setModelBound(new BoundingSphere(Float.POSITIVE_INFINITY, Vector3f.ZERO)); + sky.setShadowMode(ShadowMode.Off); - Material skyMat = new Material(assetManager, "Common/MatDefs/Misc/Sky.j3md"); - skyMat.setVector3("NormalScale", normalScale); - switch (envMapType){ - case CubeMap : + Material skyMat; + switch (envMapType) { + case CubeMap: // make sure it's a cubemap if (!(texture instanceof TextureCubeMap)) { Image img = texture.getImage(); texture = new TextureCubeMap(); texture.setImage(img); } + skyMat = new Material(assetManager, + "Common/MatDefs/Misc/Sky.j3md"); break; - case SphereMap : + case SphereMap: + skyMat = new Material(assetManager, + "Common/MatDefs/Misc/SkyNonCube.j3md"); skyMat.setBoolean("SphereMap", true); break; - case EquirectMap : + case EquirectMap: + skyMat = new Material(assetManager, + "Common/MatDefs/Misc/SkyNonCube.j3md"); skyMat.setBoolean("EquirectMap", true); break; + default: + throw new IllegalArgumentException("envMapType=" + envMapType); } + skyMat.setVector3("NormalScale", normalScale); texture.setMagFilter(Texture.MagFilter.Bilinear); texture.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); texture.setAnisotropicFilter(0); @@ -213,75 +232,78 @@ public static Spatial createSky(AssetManager assetManager, Texture texture, return sky; } - + /** - * Create a sky using the given cubemap or spheremap texture. - * - * @param assetManager from which to load materials - * @param texture to use * - * @param sphereMap determines how the texture is used:
                - *
                  - *
                • true: The texture is a Texture2D with the pixels arranged for - * sphere - * mapping.
                • - *
                • false: The texture is either a TextureCubeMap or Texture2D. If it is - * a Texture2D then the image is taken from it and is inserted into a - * TextureCubeMap
                • - *
                - * @return a new spatial representing the sky, ready to be attached to the - * scene graph - * @deprecated use {@link SkyFactory#createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType)} - */ + * Create a sky using the given cubemap or spheremap texture. + * + * @param assetManager from which to load materials + * @param texture to use + * @param sphereMap determines how the texture is used:
                + *
                  + *
                • true: The texture is a Texture2D with the pixels arranged for + * sphere + * mapping.
                • + *
                • false: The texture is either a TextureCubeMap or Texture2D. If it is + * a Texture2D then the image is taken from it and is inserted into a + * TextureCubeMap
                • + *
                + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + * @deprecated use {@link SkyFactory#createSky(com.jme3.asset.AssetManager, com.jme3.texture.Texture, + * com.jme3.math.Vector3f, com.jme3.util.SkyFactory.EnvMapType)} + */ @Deprecated public static Spatial createSky(AssetManager assetManager, Texture texture, boolean sphereMap) { - return createSky(assetManager, texture, Vector3f.UNIT_XYZ, sphereMap?EnvMapType.SphereMap:EnvMapType.CubeMap); + return createSky(assetManager, texture, Vector3f.UNIT_XYZ, + sphereMap ? EnvMapType.SphereMap : EnvMapType.CubeMap); } /** - * Create a sky using the given cubemap or spheremap texture. - * - * @param assetManager from which to load materials - * @param textureName the path to the texture asset to use - * @param sphereMap determines how the texture is used:
                - *
                  - *
                • true: The texture is a Texture2D with the pixels arranged for - * sphere - * mapping.
                • - *
                • false: The texture is either a TextureCubeMap or Texture2D. If it is - * a Texture2D then the image is taken from it and is inserted into a - * TextureCubeMap
                • - *
                - * @return a new spatial representing the sky, ready to be attached to the - * scene graph - * @deprecated use {@link #createSky(com.jme3.asset.AssetManager, java.lang.String, com.jme3.util.SkyFactory.EnvMapType)} - */ + * Create a sky using the given cubemap or spheremap texture. + * + * @param assetManager from which to load materials + * @param textureName the path to the texture asset to use + * @param sphereMap determines how the texture is used:
                + *
                  + *
                • true: The texture is a Texture2D with the pixels arranged for + * sphere + * mapping.
                • + *
                • false: The texture is either a TextureCubeMap or Texture2D. If it is + * a Texture2D then the image is taken from it and is inserted into a + * TextureCubeMap
                • + *
                + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + * @deprecated use {@link #createSky(com.jme3.asset.AssetManager, java.lang.String, + * com.jme3.util.SkyFactory.EnvMapType)} + */ @Deprecated public static Spatial createSky(AssetManager assetManager, String textureName, boolean sphereMap) { - return createSky(assetManager, textureName, sphereMap?EnvMapType.SphereMap:EnvMapType.CubeMap); + return createSky(assetManager, textureName, sphereMap ? EnvMapType.SphereMap : EnvMapType.CubeMap); } - + /** - * Create a sky using the given cubemap or spheremap texture. - * - * @param assetManager from which to load materials - * @param texture to use - * @param envMapType see {@link EnvMapType} - * @return a new spatial representing the sky, ready to be attached to the - * scene graph - */ + * Create a sky using the given cubemap or spheremap texture. + * + * @param assetManager from which to load materials + * @param texture to use + * @param envMapType see {@link EnvMapType} + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ public static Spatial createSky(AssetManager assetManager, Texture texture, EnvMapType envMapType) { return createSky(assetManager, texture, Vector3f.UNIT_XYZ, envMapType); } - + /** - * Create a sky using the given cubemap or spheremap texture. - * - * @param assetManager from which to load materials - * @param textureName the path to the texture asset to use - * @param envMapType see {@link EnvMapType} - * @return a new spatial representing the sky, ready to be attached to the - * scene graph - */ + * Create a sky using the given cubemap or spheremap texture. + * + * @param assetManager from which to load materials + * @param textureName the path to the texture asset to use + * @param envMapType see {@link EnvMapType} + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ public static Spatial createSky(AssetManager assetManager, String textureName, EnvMapType envMapType) { TextureKey key = new TextureKey(textureName, true); key.setGenerateMips(false); @@ -313,7 +335,7 @@ private static void checkImagesForCubeMap(Image... images) { Format fmt = images[0].getFormat(); int width = images[0].getWidth(); int height = images[0].getHeight(); - + ByteBuffer data = images[0].getData(0); int size = data != null ? data.capacity() : 0; @@ -325,11 +347,11 @@ private static void checkImagesForCubeMap(Image... images) { if (image.getFormat() != fmt) { throw new IllegalArgumentException("Images must have same format"); } - if (image.getWidth() != width || image.getHeight() != height) { + if (image.getWidth() != width || image.getHeight() != height) { throw new IllegalArgumentException("Images must have same resolution"); } ByteBuffer data2 = image.getData(0); - if (data2 != null){ + if (data2 != null) { if (data2.capacity() != size) { throw new IllegalArgumentException("Images must have same size"); } @@ -395,7 +417,8 @@ public static Spatial createSky(AssetManager assetManager, Texture west, checkImagesForCubeMap(westImg, eastImg, northImg, southImg, upImg, downImg); - Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), null, westImg.getColorSpace()); + Image cubeImage = new Image(westImg.getFormat(), westImg.getWidth(), westImg.getHeight(), + null, westImg.getColorSpace()); cubeImage.addData(westImg.getData(0)); cubeImage.addData(eastImg.getData(0)); @@ -403,25 +426,26 @@ public static Spatial createSky(AssetManager assetManager, Texture west, cubeImage.addData(upImg.getData(0)); cubeImage.addData(southImg.getData(0)); cubeImage.addData(northImg.getData(0)); - + TextureCubeMap cubeMap = new TextureCubeMap(cubeImage); return createSky(assetManager, cubeMap, normalScale, EnvMapType.CubeMap, sphereRadius); } /** - * Create a cube-mapped sky using six textures. - * - * @param assetManager from which to load materials - * @param west texture for the western face of the cube - * @param east texture for the eastern face of the cube - * @param north texture for the northern face of the cube - * @param south texture for the southern face of the cube - * @param up texture for the top face of the cube - * @param down texture for the bottom face of the cube * - * @return a new spatial representing the sky, ready to be attached to the - * scene graph - */ - public static Spatial createSky(AssetManager assetManager, Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) { + * Create a cube-mapped sky using six textures. + * + * @param assetManager from which to load materials + * @param west texture for the western face of the cube + * @param east texture for the eastern face of the cube + * @param north texture for the northern face of the cube + * @param south texture for the southern face of the cube + * @param up texture for the top face of the cube + * @param down texture for the bottom face of the cube + * @return a new spatial representing the sky, ready to be attached to the + * scene graph + */ + public static Spatial createSky(AssetManager assetManager, + Texture west, Texture east, Texture north, Texture south, Texture up, Texture down) { return createSky(assetManager, west, east, north, south, up, down, Vector3f.UNIT_XYZ); } } diff --git a/jme3-core/src/main/java/com/jme3/util/SortUtil.java b/jme3-core/src/main/java/com/jme3/util/SortUtil.java index 7628495f5c..39c8d71729 100644 --- a/jme3-core/src/main/java/com/jme3/util/SortUtil.java +++ b/jme3-core/src/main/java/com/jme3/util/SortUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,46 +40,14 @@ */ public class SortUtil { - /** - * The size at or below which we will use insertion sort because it's - * probably faster. - */ - private static final int INSERTION_SORT_THRESHOLD = 7; - - - /* - procedure optimizedGnomeSort(a[]) - pos := 1 - last := 0 - while pos < length(a) - if (a[pos] >= a[pos-1]) - if (last != 0) - pos := last - last := 0 - end if - pos := pos + 1 - else - swap a[pos] and a[pos-1] - if (pos > 1) - if (last == 0) - last := pos - end if - pos := pos - 1 - else - pos := pos + 1 - end if - end if - end while -end procedure - */ - + @SuppressWarnings("unchecked") public static void gsort(Object[] a, Comparator comp) { int pos = 1; int last = 0; int length = a.length; - + while (pos < length){ - if ( comp.compare(a[pos], a[pos-1]) >= 0 ){ + if (comp.compare(a[pos], a[pos-1]) >= 0){ if (last != 0){ pos = last; last = 0; @@ -89,18 +57,18 @@ public static void gsort(Object[] a, Comparator comp) { Object tmp = a[pos]; a[pos] = a[pos-1]; a[pos-1] = tmp; - + if (pos > 1){ if (last == 0){ last = pos; } pos --; - }else{ + } else { pos ++; } } } - + // int p = 0; // int l = a.length; // while (p < l) { @@ -118,7 +86,7 @@ public static void gsort(Object[] a, Comparator comp) { private static void test(Float[] original, Float[] sorted, Comparator ic) { long time, dt; - + time = System.nanoTime(); for (int i = 0; i < 1000000; i++) { System.arraycopy(original, 0, sorted, 0, original.length); @@ -155,6 +123,7 @@ private static void test(Float[] original, Float[] sorted, Comparator ic) public static void main(String[] args) { Comparator ic = new Comparator() { + @Override public int compare(Float o1, Float o2) { return (int) (o1 - o2); } @@ -171,6 +140,9 @@ public int compare(Float o1, Float o2) { /** * Quick sorts the supplied array using the specified comparator. + * + * @param a the array to sort (not null, modified) + * @param comp the Comparator to use (not null) */ public static void qsort(Object[] a, Comparator comp) { qsort(a, 0, a.length - 1, comp); @@ -179,8 +151,10 @@ public static void qsort(Object[] a, Comparator comp) { /** * Quick sorts the supplied array using the specified comparator. * + * @param a the array to sort (modified) * @param lo0 the index of the lowest element to include in the sort. * @param hi0 the index of the highest element to include in the sort. + * @param comp the Comparator to use (not null) */ @SuppressWarnings("unchecked") public static void qsort(Object[] a, int lo0, int hi0, Comparator comp) { @@ -240,6 +214,7 @@ public static void qsort(Object[] a, int lo0, int hi0, Comparator comp) { } } + @SuppressWarnings("unchecked") public static void qsort(int[] a, int lo0, int hi0, Comparator comp) { // bail out if we're already done if (hi0 <= lo0) { @@ -296,17 +271,21 @@ public static void qsort(int[] a, int lo0, int hi0, Comparator comp) { qsort(a, hi + 1, hi0, comp); } } - + /** * Merge sort + * + * @param src the source array (not null) + * @param dest the destination array (not null) + * @param comp the Comparator to use */ public static void msort(Object[] src, Object[] dest, Comparator comp){ msort(src, dest, 0, src.length - 1, comp); } - + /** * Merge sort - * + * * @param src Source array * @param dest Destination array * @param low Index of beginning element @@ -322,7 +301,8 @@ public static void msort(Object[] src, Object[] dest, int low, int high, merge(src, dest, low, center + 1, high, comp); } } - + + @SuppressWarnings("unchecked") private static void merge(Object[] src, Object[] dest, int low, int middle, int high, Comparator comp) { int leftEnd = middle - 1; diff --git a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java index 1ae3610962..f19aacd914 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentBinormalGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,76 +59,86 @@ import java.util.logging.Logger; /** - * + * @deprecated This is an outdated and non-standard method. Please use @{link MikktspaceTangentGenerator} + * instead. * @author Lex (Aleksey Nikiforov) - */ + */ +@Deprecated public class TangentBinormalGenerator { - + + private static final Logger log = Logger.getLogger(TangentBinormalGenerator.class.getName()); private static final float ZERO_TOLERANCE = 0.0000001f; - private static final Logger log = Logger.getLogger( - TangentBinormalGenerator.class.getName()); private static float toleranceDot; public static boolean debug = false; - + static { setToleranceAngle(45); } - private static class VertexInfo { + public final Vector3f position; public final Vector3f normal; public final Vector2f texCoord; - public final ArrayList indices = new ArrayList(); - + public final ArrayList indices = new ArrayList<>(); + public VertexInfo(Vector3f position, Vector3f normal, Vector2f texCoord) { this.position = position; this.normal = normal; this.texCoord = texCoord; } } - - /** Collects all the triangle data for one vertex. + + /** + * Collects all the triangle data for one vertex. */ private static class VertexData { - public final ArrayList triangles = new ArrayList(); - - public VertexData() { } + + public final ArrayList triangles = new ArrayList<>(); } - - /** Keeps track of tangent, binormal, and normal for one triangle. + + /** + * Keeps track of tangent, binormal, and normal for one triangle. */ public static class TriangleData { + public final Vector3f tangent; public final Vector3f binormal; - public final Vector3f normal; + public final Vector3f normal; public int[] index = new int[3]; public int triangleOffset; - + public TriangleData(Vector3f tangent, Vector3f binormal, Vector3f normal) { this.tangent = tangent; this.binormal = binormal; this.normal = normal; } + public void setIndex(int[] index) { for (int i = 0; i < index.length; i++) { this.index[i] = index[i]; } } } - + + /** + * A private constructor to inhibit instantiation of this class. + */ + private TangentBinormalGenerator() { + } + private static List initVertexData(int size) { - List vertices = new ArrayList(size); + List vertices = new ArrayList<>(size); for (int i = 0; i < size; i++) { vertices.add(new VertexData()); } return vertices; } - + public static void generate(Mesh mesh) { generate(mesh, true, false); } - + public static void generate(Spatial scene, boolean splitMirrored) { if (scene instanceof Node) { Node node = (Node) scene; @@ -138,21 +148,21 @@ public static void generate(Spatial scene, boolean splitMirrored) { } else { Geometry geom = (Geometry) scene; Mesh mesh = geom.getMesh(); - + // Check to ensure mesh has texcoords and normals before generating - if (mesh.getBuffer(Type.TexCoord) != null - && mesh.getBuffer(Type.Normal) != null){ - generate(geom.getMesh(),true, splitMirrored); + if (mesh.getBuffer(Type.TexCoord) != null + && mesh.getBuffer(Type.Normal) != null) { + generate(geom.getMesh(), true, splitMirrored); } } } - + public static void generate(Spatial scene) { generate(scene, false); } - + public static void generateParallel(Spatial scene, ExecutorService executor) { - final Set meshes = new HashSet(); + final Set meshes = new HashSet<>(); scene.breadthFirstTraversal(new SceneGraphVisitor() { @Override public void visit(Spatial spatial) { @@ -168,7 +178,7 @@ public void visit(Spatial spatial) { } } }); - List> futures = new ArrayList>(); + List> futures = new ArrayList<>(); for (final Mesh m : meshes) { futures.add(executor.submit(new Runnable() { @Override @@ -184,12 +194,9 @@ public void run() { log.log(Level.WARNING, "Error while computing tangents", exc); } } + } - } - - - - public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirrored) { + public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirrored) { int[] index = new int[3]; Vector3f[] v = new Vector3f[3]; Vector2f[] t = new Vector2f[3]; @@ -197,16 +204,16 @@ public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirr v[i] = new Vector3f(); t[i] = new Vector2f(); } - + if (mesh.getBuffer(Type.Normal) == null) { throw new IllegalArgumentException("The given mesh has no normal data!"); } - - List vertices; + + List vertices; switch (mesh.getMode()) { case Triangles: vertices = processTriangles(mesh, index, v, t, splitMirrored); - if(splitMirrored){ + if (splitMirrored) { splitVertices(mesh, vertices, splitMirrored); } break; @@ -217,21 +224,20 @@ public static void generate(Mesh mesh, boolean approxTangents, boolean splitMirr vertices = processTriangleFan(mesh, index, v, t); break; default: - throw new UnsupportedOperationException( - mesh.getMode() + " is not supported."); + throw new UnsupportedOperationException(mesh.getMode() + " is not supported."); } - - processTriangleData(mesh, vertices, approxTangents,splitMirrored); + + processTriangleData(mesh, vertices, approxTangents, splitMirrored); //if the mesh has a bind pose, we need to generate the bind pose for the tangent buffer TangentUtils.generateBindPoseTangentsIfNecessary(mesh); } - + public static void generate(Mesh mesh, boolean approxTangents) { generate(mesh, approxTangents, false); } - private static List processTriangles(Mesh mesh, + private static List processTriangles(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t, boolean splitMirrored) { IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); @@ -239,101 +245,103 @@ private static List processTriangles(Mesh mesh, throw new IllegalArgumentException("Can only generate tangents for " + "meshes with texture coordinates"); } - + FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + List vertices = initVertexData(vertexBuffer.limit() / 3); - + for (int i = 0; i < indexBuffer.size() / 3; i++) { for (int j = 0; j < 3; j++) { index[j] = indexBuffer.get(i * 3 + j); populateFromBuffer(v[j], vertexBuffer, index[j]); populateFromBuffer(t[j], textureBuffer, index[j]); } - + TriangleData triData = processTriangle(index, v, t); - if(splitMirrored){ + if (splitMirrored) { triData.setIndex(index); - triData.triangleOffset = i * 3 ; + triData.triangleOffset = i * 3; } vertices.get(index[0]).triangles.add(triData); vertices.get(index[1]).triangles.add(triData); vertices.get(index[2]).triangles.add(triData); } - + return vertices; } - - //Don't remove splitmirorred boolean. It's not used right now, but I intend to - //make this method also split vertices with rotated tangent space and I'll - //add another splitRotated boolean - private static List splitVertices(Mesh mesh, List vertexData, boolean splitMirorred) { + + // Don't remove the split mirrored boolean. It's not used right now, but I intend to + // make this method also split vertices with rotated tangent space, and I'll + // add another splitRotated boolean. + private static List splitVertices(Mesh mesh, List vertexData, boolean splitMirrored) { + int nbVertices = mesh.getBuffer(Type.Position).getNumElements(); - List newVertices = new ArrayList(); - Map indiceMap = new HashMap(); + List newVertices = new ArrayList<>(); + Map indexMap = new HashMap<>(); FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); for (int i = 0; i < vertexData.size(); i++) { ArrayList triangles = vertexData.get(i).triangles; Vector3f givenNormal = new Vector3f(); populateFromBuffer(givenNormal, normalBuffer, i); - - ArrayList trianglesUp = new ArrayList(); - ArrayList trianglesDown = new ArrayList(); + + ArrayList trianglesUp = new ArrayList<>(); + ArrayList trianglesDown = new ArrayList<>(); for (int j = 0; j < triangles.size(); j++) { TriangleData triangleData = triangles.get(j); - if(parity(givenNormal, triangleData.normal) > 0){ + if (parity(givenNormal, triangleData.normal) > 0) { trianglesUp.add(triangleData); - }else{ + } else { trianglesDown.add(triangleData); } } - + //if the vertex has triangles with opposite parity it has to be split - if(!trianglesUp.isEmpty() && !trianglesDown.isEmpty()){ + if (!trianglesUp.isEmpty() && !trianglesDown.isEmpty()) { log.log(Level.FINE, "Splitting vertex {0}", i); //assigning triangle with the same parity to the original vertex vertexData.get(i).triangles.clear(); vertexData.get(i).triangles.addAll(trianglesUp); - + //creating a new vertex VertexData newVert = new VertexData(); //assigning triangles with opposite parity to it newVert.triangles.addAll(trianglesDown); - + newVertices.add(newVert); //keep vertex index to fix the index buffers later - indiceMap.put(nbVertices, i); + indexMap.put(nbVertices, i); for (TriangleData tri : newVert.triangles) { for (int j = 0; j < tri.index.length; j++) { - if(tri.index[j] == i){ - tri.index[j] = nbVertices; + if (tri.index[j] == i) { + tri.index[j] = nbVertices; } } } nbVertices++; - - } - - + } } - if(!newVertices.isEmpty()){ - + if (!newVertices.isEmpty()) { + //we have new vertices, we need to update the mesh's buffers. for (Type type : VertexBuffer.Type.values()) { //skip tangent buffer as we're gonna overwrite it later - if(type == Type.Tangent || type == Type.BindPoseTangent) continue; + if (type == Type.Tangent || type == Type.BindPoseTangent) { + continue; + } VertexBuffer vb = mesh.getBuffer(type); //Some buffer (hardware skinning ones) can be there but not //initialized, they must be skipped. //They'll be initialized when Hardware Skinning is engaged - if(vb==null || vb.getNumComponents() == 0) continue; - - Buffer buffer = vb.getData(); - //IndexBuffer has special treatement, only swapping the vertex indices is needed - if(type == Type.Index){ - boolean isShortBuffer = vb.getFormat() == VertexBuffer.Format.UnsignedShort; + if (vb == null || vb.getNumComponents() == 0) { + continue; + } + + Buffer buffer = vb.getData(); + //IndexBuffer has special treatment, only swapping the vertex indices is needed + if (type == Type.Index) { + boolean isShortBuffer = vb.getFormat() == VertexBuffer.Format.UnsignedShort; for (VertexData vertex : newVertices) { for (TriangleData tri : vertex.triangles) { for (int i = 0; i < tri.index.length; i++) { @@ -346,36 +354,36 @@ private static List splitVertices(Mesh mesh, List vertex } } vb.setUpdateNeeded(); - }else{ + } else { //copy the buffer in a bigger one and append nex vertices to the end - Buffer newVerts = VertexBuffer.createBuffer(vb.getFormat(), vb.getNumComponents(), nbVertices); + Buffer newVerts = VertexBuffer.createBuffer(vb.getFormat(), vb.getNumComponents(), nbVertices); if (buffer != null) { buffer.rewind(); - bulkPut(vb.getFormat(), newVerts,buffer); - - int index = vertexData.size(); + bulkPut(vb.getFormat(), newVerts, buffer); + + int index = vertexData.size(); newVerts.position(vertexData.size() * vb.getNumComponents()); for (int j = 0; j < newVertices.size(); j++) { - int oldInd = indiceMap.get(index) ; - for (int i = 0; i < vb.getNumComponents(); i++) { - putValue(vb.getFormat(), newVerts, buffer, oldInd* vb.getNumComponents() + i); - } + int oldInd = indexMap.get(index); + for (int i = 0; i < vb.getNumComponents(); i++) { + putValue(vb.getFormat(), newVerts, buffer, oldInd * vb.getNumComponents() + i); + } index++; - } - vb.updateData(newVerts); + } + vb.updateData(newVerts); //destroy previous buffer as it's no longer needed destroyDirectBuffer(buffer); - } - } + } + } } vertexData.addAll(newVertices); - + mesh.updateCounts(); } return vertexData; } - + private static void bulkPut(VertexBuffer.Format format, Buffer buf1, Buffer buf2) { switch (format) { case Byte: @@ -402,11 +410,11 @@ private static void bulkPut(VertexBuffer.Format format, Buffer buf1, Buffer buf2 break; default: - throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } } - - private static void putValue(VertexBuffer.Format format, Buffer buf1, Buffer buf2,int index) { + + private static void putValue(VertexBuffer.Format format, Buffer buf1, Buffer buf2, int index) { switch (format) { case Byte: case Half: @@ -417,113 +425,115 @@ private static void putValue(VertexBuffer.Format format, Buffer buf1, Buffer buf case Short: case UnsignedShort: short s = ((ShortBuffer) buf2).get(index); - ((ShortBuffer) buf1).put(s); + ((ShortBuffer) buf1).put(s); break; case Int: case UnsignedInt: int i = ((IntBuffer) buf2).get(index); - ((IntBuffer) buf1).put(i); - break; + ((IntBuffer) buf1).put(i); + break; case Float: float f = ((FloatBuffer) buf2).get(index); - ((FloatBuffer) buf1).put(f); - break; + ((FloatBuffer) buf1).put(f); + break; case Double: double d = ((DoubleBuffer) buf2).get(index); - ((DoubleBuffer) buf1).put(d); - break; + ((DoubleBuffer) buf1).put(d); + break; default: - throw new UnsupportedOperationException("Unrecoginized buffer format: " + format); + throw new UnsupportedOperationException("Unrecognized buffer format: " + format); } } - + private static List processTriangleStrip(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + List vertices = initVertexData(vertexBuffer.limit() / 3); - + index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); - + populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); - + populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); - + for (int i = 2; i < indexBuffer.size(); i++) { index[2] = indexBuffer.get(i); BufferUtils.populateFromBuffer(v[2], vertexBuffer, index[2]); BufferUtils.populateFromBuffer(t[2], textureBuffer, index[2]); - + boolean isDegenerate = isDegenerateTriangle(v[0], v[1], v[2]); TriangleData triData = processTriangle(index, v, t); - + if (!isDegenerate) { vertices.get(index[0]).triangles.add(triData); vertices.get(index[1]).triangles.add(triData); vertices.get(index[2]).triangles.add(triData); } - + Vector3f vTemp = v[0]; v[0] = v[1]; v[1] = v[2]; v[2] = vTemp; - + Vector2f tTemp = t[0]; t[0] = t[1]; t[1] = t[2]; t[2] = tTemp; - + index[0] = index[1]; index[1] = index[2]; } - + return vertices; } - + private static List processTriangleFan(Mesh mesh, int[] index, Vector3f[] v, Vector2f[] t) { + IndexBuffer indexBuffer = mesh.getIndexBuffer(); FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); FloatBuffer textureBuffer = (FloatBuffer) mesh.getBuffer(Type.TexCoord).getData(); - + List vertices = initVertexData(vertexBuffer.limit() / 3); - + index[0] = indexBuffer.get(0); index[1] = indexBuffer.get(1); - + populateFromBuffer(v[0], vertexBuffer, index[0]); populateFromBuffer(v[1], vertexBuffer, index[1]); - + populateFromBuffer(t[0], textureBuffer, index[0]); populateFromBuffer(t[1], textureBuffer, index[1]); - + for (int i = 2; i < vertexBuffer.limit() / 3; i++) { index[2] = indexBuffer.get(i); populateFromBuffer(v[2], vertexBuffer, index[2]); populateFromBuffer(t[2], textureBuffer, index[2]); - + TriangleData triData = processTriangle(index, v, t); vertices.get(index[0]).triangles.add(triData); vertices.get(index[1]).triangles.add(triData); vertices.get(index[2]).triangles.add(triData); - + Vector3f vTemp = v[1]; v[1] = v[2]; v[2] = vTemp; - + Vector2f tTemp = t[1]; t[1] = t[2]; t[2] = tTemp; - + index[1] = index[2]; } - + return vertices; } @@ -531,27 +541,26 @@ private static List processTriangleFan(Mesh mesh, private static boolean isDegenerateTriangle(Vector3f a, Vector3f b, Vector3f c) { return (a.subtract(b).cross(c.subtract(b))).lengthSquared() == 0; } - - public static TriangleData processTriangle(int[] index, - Vector3f[] v, Vector2f[] t) { + + public static TriangleData processTriangle(int[] index, Vector3f[] v, Vector2f[] t) { TempVars tmp = TempVars.get(); try { Vector3f edge1 = tmp.vect1; Vector3f edge2 = tmp.vect2; Vector2f edge1uv = tmp.vect2d; Vector2f edge2uv = tmp.vect2d2; - + Vector3f tangent = tmp.vect3; Vector3f binormal = tmp.vect4; Vector3f normal = tmp.vect5; - + t[1].subtract(t[0], edge1uv); t[2].subtract(t[0], edge2uv); float det = edge1uv.x * edge2uv.y - edge1uv.y * edge2uv.x; - + boolean normalize = false; if (Math.abs(det) < ZERO_TOLERANCE) { - log.log(Level.WARNING, "Colinear uv coordinates for triangle " + log.log(Level.WARNING, "Collinear uv coordinates for triangle " + "[{0}, {1}, {2}]; tex0 = [{3}, {4}], " + "tex1 = [{5}, {6}], tex2 = [{7}, {8}]", new Object[]{index[0], index[1], index[2], @@ -559,22 +568,20 @@ public static TriangleData processTriangle(int[] index, det = 1; normalize = true; } - + v[1].subtract(v[0], edge1); v[2].subtract(v[0], edge2); - + tangent.set(edge1); tangent.normalizeLocal(); binormal.set(edge2); binormal.normalizeLocal(); - - if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) - < ZERO_TOLERANCE) { - log.log(Level.WARNING, "Vertices are on the same line " - + "for triangle [{0}, {1}, {2}].", + + if (Math.abs(Math.abs(tangent.dot(binormal)) - 1) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Vertices are on the same line for triangle [{0}, {1}, {2}].", new Object[]{index[0], index[1], index[2]}); } - + float factor = 1 / det; tangent.x = (edge2uv.y * edge1.x - edge1uv.y * edge2.x) * factor; tangent.y = (edge2uv.y * edge1.y - edge1uv.y * edge2.y) * factor; @@ -582,96 +589,92 @@ public static TriangleData processTriangle(int[] index, if (normalize) { tangent.normalizeLocal(); } - + binormal.x = (edge1uv.x * edge2.x - edge2uv.x * edge1.x) * factor; binormal.y = (edge1uv.x * edge2.y - edge2uv.x * edge1.y) * factor; binormal.z = (edge1uv.x * edge2.z - edge2uv.x * edge1.z) * factor; if (normalize) { binormal.normalizeLocal(); } - + tangent.cross(binormal, normal); normal.normalizeLocal(); + + return new TriangleData(tangent.clone(), binormal.clone(), normal.clone()); - return new TriangleData( - tangent.clone(), - binormal.clone(), - normal.clone()); } finally { tmp.release(); } } - + public static void setToleranceAngle(float angle) { if (angle < 0 || angle > 179) { - throw new IllegalArgumentException( - "The angle must be between 0 and 179 degrees."); + throw new IllegalArgumentException("The angle must be between 0 and 179 degrees."); } toleranceDot = FastMath.cos(angle * FastMath.DEG_TO_RAD); } - - + private static boolean approxEqual(Vector3f u, Vector3f v) { float tolerance = 1E-4f; - return (FastMath.abs(u.x - v.x) < tolerance) && - (FastMath.abs(u.y - v.y) < tolerance) && - (FastMath.abs(u.z - v.z) < tolerance); + return (FastMath.abs(u.x - v.x) < tolerance) + && (FastMath.abs(u.y - v.y) < tolerance) + && (FastMath.abs(u.z - v.z) < tolerance); } - + private static boolean approxEqual(Vector2f u, Vector2f v) { float tolerance = 1E-4f; - return (FastMath.abs(u.x - v.x) < tolerance) && - (FastMath.abs(u.y - v.y) < tolerance); + return (FastMath.abs(u.x - v.x) < tolerance) + && (FastMath.abs(u.y - v.y) < tolerance); } - + private static ArrayList linkVertices(Mesh mesh, boolean splitMirrored) { - ArrayList vertexMap = new ArrayList(); - + ArrayList vertexMap = new ArrayList<>(); + FloatBuffer vertexBuffer = mesh.getFloatBuffer(Type.Position); FloatBuffer normalBuffer = mesh.getFloatBuffer(Type.Normal); FloatBuffer texcoordBuffer = mesh.getFloatBuffer(Type.TexCoord); - + Vector3f position = new Vector3f(); Vector3f normal = new Vector3f(); Vector2f texCoord = new Vector2f(); - + final int size = vertexBuffer.limit() / 3; for (int i = 0; i < size; i++) { - + populateFromBuffer(position, vertexBuffer, i); populateFromBuffer(normal, normalBuffer, i); populateFromBuffer(texCoord, texcoordBuffer, i); - + boolean found = false; //Nehon 07/07/2013 //Removed this part, joining split vertices to compute tangent space makes no sense to me //separate vertices should have separate tangent space - if(!splitMirrored){ + if (!splitMirrored) { for (int j = 0; j < vertexMap.size(); j++) { VertexInfo vertexInfo = vertexMap.get(j); - if (approxEqual(vertexInfo.position, position) && - approxEqual(vertexInfo.normal, normal) && - approxEqual(vertexInfo.texCoord, texCoord)) - { + if (approxEqual(vertexInfo.position, position) + && approxEqual(vertexInfo.normal, normal) + && approxEqual(vertexInfo.texCoord, texCoord)) { vertexInfo.indices.add(i); found = true; - break; + break; } } } + if (!found) { VertexInfo vertexInfo = new VertexInfo(position.clone(), normal.clone(), texCoord.clone()); vertexInfo.indices.add(i); vertexMap.add(vertexInfo); } } - + return vertexMap; } - + private static void processTriangleData(Mesh mesh, List vertices, boolean approxTangent, boolean splitMirrored) { - ArrayList vertexMap = linkVertices(mesh,splitMirrored); + ArrayList vertexMap = linkVertices(mesh, splitMirrored); FloatBuffer tangents = BufferUtils.createFloatBuffer(vertices.size() * 4); @@ -713,9 +716,7 @@ private static void processTriangleData(Mesh mesh, List vertices, tangentUnit.set(triangleData.tangent); tangentUnit.normalizeLocal(); if (tangent.dot(tangentUnit) < toleranceDot) { - log.log(Level.WARNING, - "Angle between tangents exceeds tolerance " - + "for vertex {0}.", i); + log.log(Level.WARNING, "Angle between tangents exceeds tolerance for vertex {0}.", i); break; } @@ -723,16 +724,13 @@ private static void processTriangleData(Mesh mesh, List vertices, binormalUnit.set(triangleData.binormal); binormalUnit.normalizeLocal(); if (binormal.dot(binormalUnit) < toleranceDot) { - log.log(Level.WARNING, - "Angle between binormals exceeds tolerance " - + "for vertex {0}.", i); + log.log(Level.WARNING, "Angle between binormals exceeds tolerance for vertex {0}.", i); break; } } } } - // find average tangent tangent.set(0, 0, 0); binormal.set(0, 0, 0); @@ -749,16 +747,13 @@ private static void processTriangleData(Mesh mesh, List vertices, TriangleData triangleData = triangles.get(j); tangent.addLocal(triangleData.tangent); binormal.addLocal(triangleData.binormal); - } } - int blameVertex = vertexInfo.indices.get(0); if (tangent.length() < ZERO_TOLERANCE) { - log.log(Level.WARNING, - "Shared tangent is zero for vertex {0}.", blameVertex); + log.log(Level.WARNING, "Shared tangent is zero for vertex {0}.", blameVertex); // attempt to fix from binormal if (binormal.length() >= ZERO_TOLERANCE) { binormal.cross(givenNormal, tangent); @@ -773,17 +768,13 @@ private static void processTriangleData(Mesh mesh, List vertices, tangentUnit.set(tangent); tangentUnit.normalizeLocal(); - if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) - < ZERO_TOLERANCE) { - log.log(Level.WARNING, - "Normal and tangent are parallel for vertex {0}.", blameVertex); + if (Math.abs(Math.abs(tangentUnit.dot(givenNormal)) - 1) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Normal and tangent are parallel for vertex {0}.", blameVertex); } - if (!approxTangent) { if (binormal.length() < ZERO_TOLERANCE) { - log.log(Level.WARNING, - "Shared binormal is zero for vertex {0}.", blameVertex); + log.log(Level.WARNING, "Shared binormal is zero for vertex {0}.", blameVertex); // attempt to fix from tangent if (tangent.length() >= ZERO_TOLERANCE) { givenNormal.cross(tangent, binormal); @@ -798,16 +789,12 @@ private static void processTriangleData(Mesh mesh, List vertices, binormalUnit.set(binormal); binormalUnit.normalizeLocal(); - if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) - < ZERO_TOLERANCE) { - log.log(Level.WARNING, - "Normal and binormal are parallel for vertex {0}.", blameVertex); + if (Math.abs(Math.abs(binormalUnit.dot(givenNormal)) - 1) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Normal and binormal are parallel for vertex {0}.", blameVertex); } - if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) - < ZERO_TOLERANCE) { - log.log(Level.WARNING, - "Tangent and binormal are parallel for vertex {0}.", blameVertex); + if (Math.abs(Math.abs(binormalUnit.dot(tangentUnit)) - 1) < ZERO_TOLERANCE) { + log.log(Level.WARNING, "Tangent and binormal are parallel for vertex {0}.", blameVertex); } } @@ -839,10 +826,8 @@ private static void processTriangleData(Mesh mesh, List vertices, // If the model already had a tangent buffer, replace it with the regenerated one mesh.clearBuffer(Type.Tangent); mesh.setBuffer(Type.Tangent, 4, tangents); - - - - if(mesh.isAnimated()){ + + if (mesh.isAnimated()) { mesh.clearBuffer(Type.BindPoseNormal); mesh.clearBuffer(Type.BindPosePosition); mesh.clearBuffer(Type.BindPoseTangent); @@ -850,12 +835,12 @@ private static void processTriangleData(Mesh mesh, List vertices, } if (debug) { - writeColorBuffer( vertices, cols, mesh); + writeColorBuffer(vertices, cols, mesh); } mesh.updateBound(); mesh.updateCounts(); - } - + } + private static void writeColorBuffer(List vertices, ColorRGBA[] cols, Mesh mesh) { FloatBuffer colors = BufferUtils.createFloatBuffer(vertices.size() * 4); colors.rewind(); @@ -875,146 +860,23 @@ private static int parity(Vector3f n1, Vector3f n) { } else { return 1; } - } + /** + * @deprecated Use {@link TangentUtils#genTbnLines(com.jme3.scene.Mesh, float) } instead. + */ + @Deprecated public static Mesh genTbnLines(Mesh mesh, float scale) { - if (mesh.getBuffer(Type.Tangent) == null) { - return genNormalLines(mesh, scale); - } else { - return genTangentLines(mesh, scale); - } + return TangentUtils.genTbnLines(mesh, scale); } - + + /** + * @deprecated Use {@link TangentUtils#genNormalLines(com.jme3.scene.Mesh, float) } instead. + */ + @Deprecated public static Mesh genNormalLines(Mesh mesh, float scale) { - FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); - FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); - - ColorRGBA originColor = ColorRGBA.White; - ColorRGBA normalColor = ColorRGBA.Blue; - - Mesh lineMesh = new Mesh(); - lineMesh.setMode(Mesh.Mode.Lines); - - Vector3f origin = new Vector3f(); - Vector3f point = new Vector3f(); - - FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 2); - FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 2); - - for (int i = 0; i < vertexBuffer.limit() / 3; i++) { - populateFromBuffer(origin, vertexBuffer, i); - populateFromBuffer(point, normalBuffer, i); - - int index = i * 2; - - setInBuffer(origin, lineVertex, index); - setInBuffer(originColor, lineColor, index); - - point.multLocal(scale); - point.addLocal(origin); - setInBuffer(point, lineVertex, index + 1); - setInBuffer(normalColor, lineColor, index + 1); - } - - lineMesh.setBuffer(Type.Position, 3, lineVertex); - lineMesh.setBuffer(Type.Color, 4, lineColor); - - lineMesh.setStatic(); - //lineMesh.setInterleaved(); - return lineMesh; - } - - private static Mesh genTangentLines(Mesh mesh, float scale) { - FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); - FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); - FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData(); - - FloatBuffer binormalBuffer = null; - if (mesh.getBuffer(Type.Binormal) != null) { - binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData(); - } - - ColorRGBA originColor = ColorRGBA.White; - ColorRGBA tangentColor = ColorRGBA.Red; - ColorRGBA binormalColor = ColorRGBA.Green; - ColorRGBA normalColor = ColorRGBA.Blue; - - Mesh lineMesh = new Mesh(); - lineMesh.setMode(Mesh.Mode.Lines); - - Vector3f origin = new Vector3f(); - Vector3f point = new Vector3f(); - Vector3f tangent = new Vector3f(); - Vector3f normal = new Vector3f(); - - IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.limit() / 3 * 6); - FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 4); - FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 4); - - boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4; - float tangentW = 1; - - for (int i = 0; i < vertexBuffer.limit() / 3; i++) { - populateFromBuffer(origin, vertexBuffer, i); - populateFromBuffer(normal, normalBuffer, i); - - if (hasParity) { - tangent.x = tangentBuffer.get(i * 4); - tangent.y = tangentBuffer.get(i * 4 + 1); - tangent.z = tangentBuffer.get(i * 4 + 2); - tangentW = tangentBuffer.get(i * 4 + 3); - } else { - populateFromBuffer(tangent, tangentBuffer, i); - } - - int index = i * 4; - - int id = i * 6; - lineIndex.put(id, index); - lineIndex.put(id + 1, index + 1); - lineIndex.put(id + 2, index); - lineIndex.put(id + 3, index + 2); - lineIndex.put(id + 4, index); - lineIndex.put(id + 5, index + 3); - - setInBuffer(origin, lineVertex, index); - setInBuffer(originColor, lineColor, index); - - point.set(tangent); - point.multLocal(scale); - point.addLocal(origin); - setInBuffer(point, lineVertex, index + 1); - setInBuffer(tangentColor, lineColor, index + 1); - - // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w - - if (binormalBuffer == null) { - normal.cross(tangent, point); - point.multLocal(-tangentW); - point.normalizeLocal(); - } else { - populateFromBuffer(point, binormalBuffer, i); - } - - point.multLocal(scale); - point.addLocal(origin); - setInBuffer(point, lineVertex, index + 2); - setInBuffer(binormalColor, lineColor, index + 2); - - point.set(normal); - point.multLocal(scale); - point.addLocal(origin); - setInBuffer(point, lineVertex, index + 3); - setInBuffer(normalColor, lineColor, index + 3); - } - - lineMesh.setBuffer(Type.Index, 1, lineIndex); - lineMesh.setBuffer(Type.Position, 3, lineVertex); - lineMesh.setBuffer(Type.Color, 4, lineColor); - - lineMesh.setStatic(); - //lineMesh.setInterleaved(); - return lineMesh; + return TangentUtils.genNormalLines(mesh, scale); } + + } diff --git a/jme3-core/src/main/java/com/jme3/util/TangentUtils.java b/jme3-core/src/main/java/com/jme3/util/TangentUtils.java index ae227896a1..2014eaf003 100644 --- a/jme3-core/src/main/java/com/jme3/util/TangentUtils.java +++ b/jme3-core/src/main/java/com/jme3/util/TangentUtils.java @@ -1,12 +1,58 @@ +/* + * Copyright (c) 2016-2021 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 static com.jme3.util.BufferUtils.populateFromBuffer; +import static com.jme3.util.BufferUtils.setInBuffer; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; + +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Type; /** * Created by Nehon on 03/10/2016. */ public class TangentUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private TangentUtils() { + } + public static void generateBindPoseTangentsIfNecessary(Mesh mesh){ if (mesh.getBuffer(VertexBuffer.Type.BindPosePosition) != null) { @@ -26,4 +72,143 @@ public static void generateBindPoseTangentsIfNecessary(Mesh mesh){ } } } + + public static Mesh genTbnLines(Mesh mesh, float scale) { + if (mesh.getBuffer(Type.Tangent) == null) { + return genNormalLines(mesh, scale); + } else { + return genTangentLines(mesh, scale); + } + } + + public static Mesh genNormalLines(Mesh mesh, float scale) { + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + + ColorRGBA originColor = ColorRGBA.White; + ColorRGBA normalColor = ColorRGBA.Blue; + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + + Vector3f origin = new Vector3f(); + Vector3f point = new Vector3f(); + + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 2); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 2); + + for (int i = 0; i < vertexBuffer.limit() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(point, normalBuffer, i); + + int index = i * 2; + + setInBuffer(origin, lineVertex, index); + setInBuffer(originColor, lineColor, index); + + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 1); + setInBuffer(normalColor, lineColor, index + 1); + } + + lineMesh.setBuffer(Type.Position, 3, lineVertex); + lineMesh.setBuffer(Type.Color, 4, lineColor); + + lineMesh.setStatic(); + // lineMesh.setInterleaved(); + return lineMesh; + } + + public static Mesh genTangentLines(Mesh mesh, float scale) { + FloatBuffer vertexBuffer = (FloatBuffer) mesh.getBuffer(Type.Position).getData(); + FloatBuffer normalBuffer = (FloatBuffer) mesh.getBuffer(Type.Normal).getData(); + FloatBuffer tangentBuffer = (FloatBuffer) mesh.getBuffer(Type.Tangent).getData(); + + FloatBuffer binormalBuffer = null; + if (mesh.getBuffer(Type.Binormal) != null) { + binormalBuffer = (FloatBuffer) mesh.getBuffer(Type.Binormal).getData(); + } + + ColorRGBA originColor = ColorRGBA.White; + ColorRGBA tangentColor = ColorRGBA.Red; + ColorRGBA binormalColor = ColorRGBA.Green; + ColorRGBA normalColor = ColorRGBA.Blue; + + Mesh lineMesh = new Mesh(); + lineMesh.setMode(Mesh.Mode.Lines); + + Vector3f origin = new Vector3f(); + Vector3f point = new Vector3f(); + Vector3f tangent = new Vector3f(); + Vector3f normal = new Vector3f(); + + IntBuffer lineIndex = BufferUtils.createIntBuffer(vertexBuffer.limit() / 3 * 6); + FloatBuffer lineVertex = BufferUtils.createFloatBuffer(vertexBuffer.limit() * 4); + FloatBuffer lineColor = BufferUtils.createFloatBuffer(vertexBuffer.limit() / 3 * 4 * 4); + + boolean hasParity = mesh.getBuffer(Type.Tangent).getNumComponents() == 4; + float tangentW = 1; + + for (int i = 0; i < vertexBuffer.limit() / 3; i++) { + populateFromBuffer(origin, vertexBuffer, i); + populateFromBuffer(normal, normalBuffer, i); + + if (hasParity) { + tangent.x = tangentBuffer.get(i * 4); + tangent.y = tangentBuffer.get(i * 4 + 1); + tangent.z = tangentBuffer.get(i * 4 + 2); + tangentW = tangentBuffer.get(i * 4 + 3); + } else { + populateFromBuffer(tangent, tangentBuffer, i); + } + + int index = i * 4; + + int id = i * 6; + lineIndex.put(id, index); + lineIndex.put(id + 1, index + 1); + lineIndex.put(id + 2, index); + lineIndex.put(id + 3, index + 2); + lineIndex.put(id + 4, index); + lineIndex.put(id + 5, index + 3); + + setInBuffer(origin, lineVertex, index); + setInBuffer(originColor, lineColor, index); + + point.set(tangent); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 1); + setInBuffer(tangentColor, lineColor, index + 1); + + // wvBinormal = cross(wvNormal, wvTangent) * -inTangent.w + if (binormalBuffer == null) { + normal.cross(tangent, point); + point.multLocal(-tangentW); + point.normalizeLocal(); + } else { + populateFromBuffer(point, binormalBuffer, i); + } + + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 2); + setInBuffer(binormalColor, lineColor, index + 2); + + point.set(normal); + point.multLocal(scale); + point.addLocal(origin); + setInBuffer(point, lineVertex, index + 3); + setInBuffer(normalColor, lineColor, index + 3); + } + + lineMesh.setBuffer(Type.Index, 1, lineIndex); + lineMesh.setBuffer(Type.Position, 3, lineVertex); + lineMesh.setBuffer(Type.Color, 4, lineColor); + + lineMesh.setStatic(); + // lineMesh.setInterleaved(); + return lineMesh; + } } diff --git a/jme3-core/src/main/java/com/jme3/util/TempVars.java b/jme3-core/src/main/java/com/jme3/util/TempVars.java index 5c1a45f4c8..5cb2c30afa 100644 --- a/jme3-core/src/main/java/com/jme3/util/TempVars.java +++ b/jme3-core/src/main/java/com/jme3/util/TempVars.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ import com.jme3.collision.bih.BIHNode.BIHStackData; import com.jme3.math.*; import com.jme3.scene.Spatial; +import java.io.Closeable; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.ArrayList; @@ -45,9 +46,9 @@ * these temp variables with TempVars.get(), all retrieved TempVars * instances must be returned via TempVars.release(). * This returns an available instance of the TempVar class ensuring this - * particular instance is never used elsewhere in the mean time. + * particular instance is never used elsewhere in the meantime. */ -public class TempVars { +public class TempVars implements Closeable{ /** * Allow X instances of TempVars in a single thread. @@ -225,5 +226,10 @@ public void release() { */ public final CollisionResults collisionResults = new CollisionResults(); public final float[] bihSwapTmp = new float[9]; - public final ArrayList bihStack = new ArrayList(); + public final ArrayList bihStack = new ArrayList<>(); + + @Override + public void close(){ + release(); + } } diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java index 7718a0f4b3..7b85f77503 100644 --- a/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/BlockLanguageParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,73 +39,73 @@ import java.util.List; public class BlockLanguageParser { - + private Reader reader; - private ArrayList statementStack = new ArrayList(); + private final ArrayList statementStack = new ArrayList<>(); private Statement lastStatement; private int lineNumber = 1; - + private BlockLanguageParser(){ } - + private void reset(){ statementStack.clear(); statementStack.add(new Statement(0, "")); lastStatement = null; lineNumber = 1; } - - private void pushStatement(StringBuilder buffer){ + + private void pushStatement(StringBuilder buffer) { String content = buffer.toString().trim(); if (content.length() > 0){ // push last statement onto the list lastStatement = new Statement(lineNumber, content); - Statement parent = statementStack.get(statementStack.size()-1); + Statement parent = statementStack.get(statementStack.size() - 1); parent.addStatement(lastStatement); buffer.setLength(0); } } - - private void load(InputStream in) throws IOException{ + + private void load(InputStream in) throws IOException { reset(); - + reader = new InputStreamReader(in, "UTF-8"); - + StringBuilder buffer = new StringBuilder(); boolean insideComment = false; char lastChar = '\0'; - + while (true){ int ci = reader.read(); char c = (char) ci; if (c == '\r'){ continue; } - if (insideComment && c == '\n'){ + if (insideComment && c == '\n') { insideComment = false; - }else if (c == '/' && lastChar == '/'){ - buffer.deleteCharAt(buffer.length()-1); + }else if (c == '/' && lastChar == '/') { + buffer.deleteCharAt(buffer.length() - 1); insideComment = true; pushStatement(buffer); lastChar = '\0'; lineNumber++; }else if (!insideComment){ - if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';'){ + if (ci == -1 || c == '{' || c == '}' || c == '\n' || c == ';') { pushStatement(buffer); lastChar = '\0'; - if (c == '{'){ + if (c == '{') { // push last statement onto the stack statementStack.add(lastStatement); continue; - }else if (c == '}'){ + }else if (c == '}') { // pop statement from stack - statementStack.remove(statementStack.size()-1); + statementStack.remove(statementStack.size() - 1); continue; - }else if (c == '\n'){ + }else if (c == '\n') { lineNumber++; - }else if (ci == -1){ + }else if (ci == -1) { break; } }else{ @@ -115,7 +115,7 @@ private void load(InputStream in) throws IOException{ } } } - + public static List parse(InputStream in) throws IOException { BlockLanguageParser parser = new BlockLanguageParser(); parser.load(in); diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java b/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java index bf6bfcd3dd..0d040e486e 100644 --- a/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/Statement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ public class Statement { protected int lineNumber; protected String line; - protected List contents = new ArrayList(); + protected List contents = new ArrayList<>(); protected Statement(int lineNumber, String line) { this.lineNumber = lineNumber; diff --git a/jme3-core/src/main/java/com/jme3/util/blockparser/package-info.java b/jme3-core/src/main/java/com/jme3/util/blockparser/package-info.java new file mode 100644 index 0000000000..d68a82d55f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/blockparser/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * parse block-structured languages that use braces and semicolons + */ +package com.jme3.util.blockparser; diff --git a/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java index cb58730078..782b8e563b 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/CloneFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 jMonkeyEngine + * Copyright (c) 2016-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,31 +51,31 @@ public interface CloneFunction { * Performs a shallow clone of the specified object. This is similar * to the JmeCloneable.clone() method in semantics and is the first part * of a two part cloning process. Once the shallow clone is created, it - * is cached and CloneFunction.cloneFields() is called. In this way, + * is cached and CloneFunction.cloneFields() is called. In this way, * the CloneFunction interface can completely take over the JmeCloneable * style cloning for an object that doesn't otherwise implement that interface. * * @param cloner The cloner performing the cloning operation. * @param original The original object that needs to be cloned. + * @return a new instance */ - public T cloneObject( Cloner cloner, T original ); - - + public T cloneObject(Cloner cloner, T original); + + /** * Performs a deep clone of the specified clone's fields. This is similar * to the JmeCloneable.cloneFields() method in semantics and is the second part * of a two part cloning process. Once the shallow clone is created, it - * is cached and CloneFunction.cloneFields() is called. In this way, + * is cached and CloneFunction.cloneFields() is called. In this way, * the CloneFunction interface can completely take over the JmeCloneable * style cloning for an object that doesn't otherwise implement that interface. - * + * * @param cloner The cloner performing the cloning operation. * @param clone The clone previously returned from cloneObject(). * @param original The original object that was cloned. This is provided for - * the very special case where field cloning needs to refer to - * the original object. Mostly the necessary fields should already - * be on the clone. + * the very special case where field cloning needs to refer to + * the original object. Mostly the necessary fields should already + * be on the clone. */ - public void cloneFields( Cloner cloner, T clone, T original ); - + public void cloneFields(Cloner cloner, T clone, T original); } diff --git a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java index 1e10f33cfc..92981c2cb5 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/Cloner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 jMonkeyEngine + * Copyright (c) 2016-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -99,17 +99,17 @@ */ public class Cloner { - static Logger log = Logger.getLogger(Cloner.class.getName()); + private static final Logger log = Logger.getLogger(Cloner.class.getName()); /** * Keeps track of the objects that have been cloned so far. */ - private IdentityHashMap index = new IdentityHashMap(); + private final IdentityHashMap index = new IdentityHashMap<>(); /** * Custom functions for cloning objects. */ - private Map functions = new HashMap(); + private final Map functions = new HashMap<>(); /** * Cache the clone methods once for all cloners. @@ -134,8 +134,12 @@ public Cloner() { /** * Convenience utility function that creates a new Cloner, uses it to * deep clone the object, and then returns the result. + * + * @param the type of object to be cloned + * @param object the object to be cloned (may be null) + * @return a new instance, or a cached value, or null */ - public static T deepClone( T object ) { + public static T deepClone(T object) { return new Cloner().clone(object); } @@ -155,8 +159,12 @@ public static T deepClone( T object ) { * * Note: objects returned by this method may not have yet had their cloneField() * method called. + * + * @param the type of object to be cloned + * @param object the object to be cloned (may be null) + * @return a new instance, or a cached value, or null */ - public T clone( T object ) { + public T clone(T object) { return clone(object, true); } @@ -165,7 +173,7 @@ public T clone( T object ) { * isolating the 'bad' case into a method with suppressed warnings. */ @SuppressWarnings("unchecked") - private Class objectClass( T object ) { + private Class objectClass(T object) { // This should be 100% allowed without a cast but Java generics // is not that smart sometimes. // Wrapping it in a method at least isolates the warning suppression @@ -192,14 +200,20 @@ private Class objectClass( T object ) { * * Note: objects returned by this method may not have yet had their cloneField() * method called. + * + * @param the type of object to be cloned + * @param object the object to be cloned (may be null) + * @param useFunctions true→use custom clone functions, + * false→don't use + * @return a new instance, or a cached value, or null */ - public T clone( T object, boolean useFunctions ) { + public T clone(T object, boolean useFunctions) { - if( object == null ) { + if (object == null) { return null; } - if( log.isLoggable(Level.FINER) ) { + if (log.isLoggable(Level.FINER)) { log.finer("cloning:" + object.getClass() + "@" + System.identityHashCode(object)); } @@ -207,8 +221,8 @@ public T clone( T object, boolean useFunctions ) { // Check the index to see if we already have it Object clone = index.get(object); - if( clone != null || index.containsKey(object) ) { - if( log.isLoggable(Level.FINER) ) { + if (clone != null || index.containsKey(object)) { + if (log.isLoggable(Level.FINER)) { log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + " as cached:" + (clone == null ? "null" : (clone.getClass() + "@" + System.identityHashCode(clone)))); } @@ -217,7 +231,7 @@ public T clone( T object, boolean useFunctions ) { // See if there is a custom function... that trumps everything. CloneFunction f = getCloneFunction(type); - if( f != null ) { + if (f != null) { T result = f.cloneObject(this, object); // Store the object in the identity map so that any circular references @@ -227,8 +241,8 @@ public T clone( T object, boolean useFunctions ) { // Now call the function again to deep clone the fields f.cloneFields(this, result, object); - if( log.isLoggable(Level.FINER) ) { - if( result == null ) { + if (log.isLoggable(Level.FINER)) { + if (result == null) { log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + " as transformed:null"); } else { @@ -239,12 +253,12 @@ public T clone( T object, boolean useFunctions ) { return result; } - if( object.getClass().isArray() ) { + if (object.getClass().isArray()) { // Perform an array clone clone = arrayClone(object); // Array clone already indexes the clone - } else if( object instanceof JmeCloneable ) { + } else if (object instanceof JmeCloneable) { // Use the two-step cloning semantics clone = ((JmeCloneable)object).jmeClone(); @@ -252,13 +266,13 @@ public T clone( T object, boolean useFunctions ) { // are resolvable index.put(object, clone); - ((JmeCloneable)clone).cloneFields(this, object); - } else if( object instanceof Cloneable ) { + ((JmeCloneable) clone).cloneFields(this, object); + } else if (object instanceof Cloneable) { // Perform a regular Java shallow clone try { clone = javaClone(object); - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new IllegalArgumentException("Object is not cloneable, type:" + type, e); } @@ -269,7 +283,7 @@ public T clone( T object, boolean useFunctions ) { throw new IllegalArgumentException("Object is not cloneable, type:" + type); } - if( log.isLoggable(Level.FINER) ) { + if(log.isLoggable(Level.FINER)) { log.finer("cloned:" + object.getClass() + "@" + System.identityHashCode(object) + " as " + clone.getClass() + "@" + System.identityHashCode(clone)); } @@ -283,9 +297,14 @@ public T clone( T object, boolean useFunctions ) { * not super-classes or super-interfaces unless you know specifically that they are cloneable.

                *

                By default ListCloneFunction is registered for ArrayList, LinkedList, CopyOnWriteArrayList, * Vector, Stack, and JME's SafeArrayList.

                + * + * @param the type of object to be cloned + * @param type the type of object to be cloned + * @param function the function to set, or null to cancel any previous + * setting */ - public void setCloneFunction( Class type, CloneFunction function ) { - if( function == null ) { + public void setCloneFunction(Class type, CloneFunction function) { + if (function == null) { functions.remove(type); } else { functions.put(type, function); @@ -295,19 +314,23 @@ public void setCloneFunction( Class type, CloneFunction function ) { /** * Returns a previously registered clone function for the specified type or null * if there is no custom clone function for the type. + * + * @param the type of object to be cloned + * @param type the type of object to be cloned + * @return the registered function, or null if none */ @SuppressWarnings("unchecked") - public CloneFunction getCloneFunction( Class type ) { - CloneFunction result = (CloneFunction)functions.get(type); - if( result == null ) { + public CloneFunction getCloneFunction(Class type) { + CloneFunction result = functions.get(type); + if (result == null) { // Do a more exhaustive search - for( Map.Entry e : functions.entrySet() ) { - if( e.getKey().isAssignableFrom(type) ) { + for (Map.Entry e : functions.entrySet()) { + if (e.getKey().isAssignableFrom(type)) { result = e.getValue(); break; } } - if( result != null ) { + if (result != null) { // Cache it for later functions.put(type, result); } @@ -321,23 +344,30 @@ public CloneFunction getCloneFunction( Class type ) { * This can be used to stub out specific values from being cloned or to * force global shared instances to be used even if the object is cloneable * normally. + * + * @param the type of object to be detected and returned + * @param original the instance to be detected (alias created) + * @param clone the instance to be returned (alias created) */ - public void setClonedValue( T original, T clone ) { + public void setClonedValue(T original, T clone) { index.put(original, clone); } /** * Returns true if the specified object has already been cloned * by this cloner during this session. Cloned objects are cached - * for later use and it's sometimes convenient to know if some + * for later use, and it's sometimes convenient to know if some * objects have already been cloned. + * + * @param o the object to be tested + * @return true if the object has been cloned, otherwise false */ - public boolean isCloned( Object o ) { + public boolean isCloned(Object o) { return index.containsKey(o); } /** - * Clears the object index allowing the cloner to be reused for a brand new + * Clears the object index allowing the cloner to be reused for a brand-new * cloning operation. */ public void clearIndex() { @@ -352,17 +382,22 @@ public void clearIndex() { * *

                This method is provided as a convenient way for CloneFunctions to call * clone() and objects without necessarily knowing their real type.

                + * + * @param the type of object to be cloned + * @param object the object to be cloned (may be null) + * @return a new instance or null + * @throws CloneNotSupportedException if the object has no public clone method */ - public T javaClone( T object ) throws CloneNotSupportedException { - if( object == null ) { + public T javaClone(T object) throws CloneNotSupportedException { + if (object == null) { return null; } Method m = methodCache.get(object.getClass()); - if( m == null ) { + if (m == null) { try { // Lookup the method and cache it m = object.getClass().getMethod("clone"); - } catch( NoSuchMethodException e ) { + } catch (NoSuchMethodException e) { throw new CloneNotSupportedException("No public clone method found for:" + object.getClass()); } methodCache.put(object.getClass(), m); @@ -373,7 +408,7 @@ public T javaClone( T object ) throws CloneNotSupportedException { try { Class type = objectClass(object); return type.cast(m.invoke(object)); - } catch( IllegalAccessException | InvocationTargetException e ) { + } catch (IllegalAccessException | InvocationTargetException e) { throw new RuntimeException("Error cloning object of type:" + object.getClass(), e); } } @@ -382,9 +417,12 @@ public T javaClone( T object ) throws CloneNotSupportedException { * Clones a primitive array by coping it and clones an object * array by coping it and then running each of its values through * Cloner.clone(). + * + * @param the type of array to be cloned + * @param object the array to be cloned + * @return a new array */ - protected T arrayClone( T object ) { - + protected T arrayClone(T object) { // Java doesn't support the cloning of arrays through reflection unless // you open access to Object's protected clone array... which requires // elevated privileges. So we will do a work-around that is slightly less @@ -399,12 +437,12 @@ protected T arrayClone( T object ) { // Store the clone for later lookups index.put(object, clone); - if( elementType.isPrimitive() ) { + if (elementType.isPrimitive()) { // Then our job is a bit easier System.arraycopy(object, 0, clone, 0, size); } else { - // Else it's an object array so we'll clone it and its children - for( int i = 0; i < size; i++ ) { + // Else it's an object array, so we'll clone it and its children. + for (int i = 0; i < size; i++) { Object element = clone(Array.get(object, i)); Array.set(clone, i, element); } diff --git a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java index ac3dce6eec..bdf376f8d3 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/IdentityCloneFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 jMonkeyEngine + * Copyright (c) 2016-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,7 @@ /** - * A CloneFunction implementation that simply returns the + * A CloneFunction implementation that simply returns * the passed object without cloning it. This is useful for * forcing some object types (like Meshes) to be shared between * the original and cloned object graph. @@ -46,13 +46,15 @@ public class IdentityCloneFunction implements CloneFunction { /** * Returns the object directly. */ - public T cloneObject( Cloner cloner, T object ) { + @Override + public T cloneObject(Cloner cloner, T object) { return object; } - + /** * Does nothing. - */ - public void cloneFields( Cloner cloner, T clone, T object ) { + */ + @Override + public void cloneFields(Cloner cloner, T clone, T object) { } } diff --git a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java index 28234d7bc9..1a9c22418d 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/JmeCloneable.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2018 jMonkeyEngine + * Copyright (c) 2016-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,8 +40,8 @@ * their local dependencies in a way that will be equivalent to the * original object graph. In other words, if two objects in the graph * share the same target reference then the cloned version will share - * the cloned reference. - * + * the cloned reference. + * *

                For example, if an object wishes to deep clone one of its fields * then it will call cloner.clone(object) instead of object.clone(). * The cloner will keep track of any clones already created for 'object' @@ -74,26 +74,28 @@ public interface JmeCloneable extends Cloneable { * fields and instead get at the superclass protected clone() methods. * For example, through super.jmeClone() or another protected clone * method that some base class eventually calls super.clone() in.

                + * + * @return a new instance */ - public Object jmeClone(); + public Object jmeClone(); /** * Implemented to perform deep cloning for this object, resolving * local cloned references using the specified cloner. The object * can call cloner.clone(fieldValue) to deep clone any of its fields. - * + * *

                Note: during normal clone operations the original object * will not be needed as the clone has already had all of the fields * shallow copied.

                * - * @param cloner The cloner that is performing the cloning operation. The + * @param cloner The cloner that is performing the cloning operation. The * cloneFields method can call back into the cloner to make - * clones of its subordinate fields. + * clones of its subordinate fields. * @param original The original object from which this object was cloned. * This is provided for the very rare case that this object needs * to refer to its original for some reason. In general, all of * the relevant values should have been transferred during the - * shallow clone and this object need merely clone what it wants. + * shallow clone, and this object need only clone what it wants. */ - public void cloneFields( Cloner cloner, Object original ); + public void cloneFields(Cloner cloner, Object original); } diff --git a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java index a1f269d673..8c91b8d7b0 100644 --- a/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java +++ b/jme3-core/src/main/java/com/jme3/util/clone/ListCloneFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 jMonkeyEngine + * Copyright (c) 2016-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,21 +42,23 @@ */ public class ListCloneFunction implements CloneFunction { - public T cloneObject( Cloner cloner, T object ) { + @Override + public T cloneObject(Cloner cloner, T object) { try { - T clone = cloner.javaClone(object); + T clone = cloner.javaClone(object); return clone; - } catch( CloneNotSupportedException e ) { + } catch (CloneNotSupportedException e) { throw new IllegalArgumentException("Clone not supported for type:" + object.getClass(), e); } } - + /** * Clones the elements of the list. - */ + */ @SuppressWarnings("unchecked") - public void cloneFields( Cloner cloner, T clone, T object ) { - for( int i = 0; i < clone.size(); i++ ) { + @Override + public void cloneFields(Cloner cloner, T clone, T object) { + for (int i = 0; i < clone.size(); i++) { // Need to clone the clones... because T might // have done something special in its clone method that // we will have to adhere to. For example, clone may have nulled diff --git a/jme3-core/src/main/java/com/jme3/util/clone/package-info.java b/jme3-core/src/main/java/com/jme3/util/clone/package-info.java new file mode 100644 index 0000000000..62c7596595 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/clone/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * generate deep clones of objects + */ +package com.jme3.util.clone; diff --git a/jme3-core/src/main/java/com/jme3/util/functional/Function.java b/jme3-core/src/main/java/com/jme3/util/functional/Function.java new file mode 100644 index 0000000000..c4948072e3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/functional/Function.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.functional; + +public interface Function { + R eval(T t); +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java new file mode 100644 index 0000000000..63dec2f682 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgFunction.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.functional; + +public interface NoArgFunction { + R eval(); +} diff --git a/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java new file mode 100644 index 0000000000..42dd416908 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/functional/NoArgVoidFunction.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.functional; + +public interface NoArgVoidFunction { + void eval(); +} diff --git a/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java new file mode 100644 index 0000000000..ccafb2e5c8 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/functional/VoidFunction.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.functional; + +public interface VoidFunction { + void eval(T t); +} diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java index d45913b835..d050d4a389 100644 --- a/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/MikkTSpaceContext.java @@ -1,7 +1,33 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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.mikktspace; @@ -23,7 +49,7 @@ public interface MikkTSpaceContext { * Returns the number of vertices on face number iFace iFace is a number in * the range {0, 1, ..., getNumFaces()-1} * - * @param face + * @param face which face (≥0, <numFaces) * @return the count (≥0) */ public int getNumVerticesOfFace(int face); @@ -33,9 +59,9 @@ public interface MikkTSpaceContext { * number iVert. iVert is in the range {0,1,2} for triangles and {0,1,2,3} * for quads. * - * @param posOut - * @param face - * @param vert + * @param posOut storage for the results (modified) + * @param face which face (≥0, <numFaces) + * @param vert which vertex in the face (≥0, <numVertices) */ public void getPosition(float posOut[], int face, int vert); @@ -44,7 +70,7 @@ public interface MikkTSpaceContext { public void getTexCoord(float texOut[], int face, int vert); /** - * The call-backsetTSpaceBasic() is sufficient for basic normal mapping. + * The callback setTSpaceBasic() is sufficient for basic normal mapping. * This function is used to return the tangent and sign to the application. * tangent is a unit length vector. For normal maps it is sufficient to use * the following simplified version of the bitangent which is generated at @@ -54,13 +80,13 @@ public interface MikkTSpaceContext { * * Note that the results are returned unindexed. It is possible to generate * a new index list But averaging/overwriting tangent spaces by using an - * already existing index list WILL produce INCRORRECT results. DO NOT! use + * already existing index list WILL produce INCORRECT results. DO NOT! use * an already existing index list. * - * @param tangent - * @param sign - * @param face - * @param vert + * @param tangent the desired tangent vector (unaffected) + * @param sign the desired sign + * @param face which face (≥0, <numFaces) + * @param vert which vertex in the face (≥0, <numVertices) */ public void setTSpaceBasic(float tangent[], float sign, int face, int vert); @@ -81,16 +107,17 @@ public interface MikkTSpaceContext { * * Note that the results are returned unindexed. It is possible to generate * a new index list. But averaging/overwriting tangent spaces by using an - * already existing index list WILL produce INCRORRECT results. DO NOT! use + * already existing index list WILL produce INCORRECT results. DO NOT! use * an already existing index list. * - * @param tangent - * @param biTangent - * @param magS - * @param magT - * @param isOrientationPreserving - * @param face - * @param vert + * @param tangent the desired tangent vector (unaffected) + * @param biTangent the desired bitangent vector (unaffected) + * @param magS true magnitude of S + * @param magT true magnitude of T + * @param isOrientationPreserving true→preserves, false→doesn't + * preserve + * @param face which face (≥0, <numFaces) + * @param vert which vertex in the face (≥0, <numVertices) */ void setTSpace(float tangent[], float biTangent[], float magS, float magT, boolean isOrientationPreserving, int face, int vert); 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 607272a70c..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,7 +1,33 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * 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.util.mikktspace; @@ -18,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); @@ -89,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 48a13d6f4b..0f272bd080 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,13 +1,40 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * 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.util.mikktspace; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; import com.jme3.scene.*; +import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.*; import java.util.ArrayList; @@ -17,30 +44,34 @@ import java.util.logging.Logger; /** - * This tangent generator is Highly experimental. - * This is the Java translation of The mikktspace generator made by Morten S. Mikkelsen - * C Source code can be found here - * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c - * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h - * - * MikkTspace looks like the new standard of tangent generation in 3D softwares. - * Xnormal, Blender, Substance painter, and many more use it. - * - * Usage is : - * MikkTSpaceTangentGenerator.generate(spatial); + * Mikktspace is a common standard for tangent space used across many 3D software. * + * This is the Java translation of the mikktspace generator made by Morten S. Mikkelsen C Source code can be + * found here https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.c + * https://developer.blender.org/diffusion/B/browse/master/intern/mikktspace/mikktspace.h * + * Usage is : + * MikktspaceTangentGenerator.generate(spatial); + * * * @author Nehon */ public class MikktspaceTangentGenerator { - private final static int MARK_DEGENERATE = 1; - private final static int QUAD_ONE_DEGEN_TRI = 2; - private final static int GROUP_WITH_ANY = 4; - private final static int ORIENT_PRESERVING = 8; - private final static long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; + private static final int MARK_DEGENERATE = 1; + private static final int QUAD_ONE_DEGEN_TRI = 2; + private static final int GROUP_WITH_ANY = 4; + private static final int ORIENT_PRESERVING = 8; + private static final long INTERNAL_RND_SORT_SEED = 39871946 & 0xffffffffL; static final int CELLS = 2048; + + private final static Logger logger = Logger.getLogger(MikktspaceTangentGenerator.class.getName()); + + /** + * A private constructor to inhibit instantiation of this class. + */ + private MikktspaceTangentGenerator() { + } static int makeIndex(final int face, final int vert) { assert (vert >= 0 && vert < 4 && face >= 0); @@ -78,15 +109,62 @@ 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.getLogger(MikktspaceTangentGenerator.class.getName()).log(Level.SEVERE, "Failed to generate tangents for geometry " + g.getName()); + + } else if (s instanceof Geometry) { + Geometry g = (Geometry) s; + Mesh mesh = g.getMesh(); + boolean success = generateTangents(mesh); + if (!success) { + logger.log(Level.SEVERE, "Failed to generate tangents for geometry {0}", g.getName()); } - TangentUtils.generateBindPoseTangentsIfNecessary(g.getMesh()); } } + + public static void generate(Mesh mesh) { + boolean success = generateTangents(mesh); + if (!success) { + logger.log(Level.SEVERE, "Failed to generate tangents for mesh {0}", mesh); + } + } + + private static boolean generateTangents(Mesh mesh) { + Mesh.Mode mode = mesh.getMode(); + boolean hasTriangles; + + if (mesh.getBuffer(Type.TexCoord) == null || mesh.getBuffer(Type.Normal) == null) { + logger.log(Level.SEVERE, "Tangent generation requires both a normal and texCoord buffer"); + return false; + } + + switch (mode) { + case Points: + case Lines: + case LineStrip: + case LineLoop: + hasTriangles = false; // skip this mesh + logger.log(Level.SEVERE, "Tangent generation requires a mesh with Triangles", mode); + break; + + case Triangles: + case TriangleFan: + case TriangleStrip: + case Patch: + hasTriangles = true; + break; + + default: + logger.log(Level.SEVERE, "Tangent generation isn't implemented for mode={0}", mode); + return false; + } + + if (hasTriangles) { + MikkTSpaceImpl context = new MikkTSpaceImpl(mesh); + boolean results = genTangSpaceDefault(context); + TangentUtils.generateBindPoseTangentsIfNecessary(mesh); + return results; + } + return false; + } public static boolean genTangSpaceDefault(MikkTSpaceContext mikkTSpace) { return genTangSpace(mikkTSpace, 180.0f); @@ -105,7 +183,7 @@ public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float ang int iNrActiveGroups, index; final int iNrFaces = mikkTSpace.getNumFaces(); //boolean bRes = false; - final float fThresCos = (float) FastMath.cos((angularThreshold * (float) FastMath.PI) / 180.0f); + final float fThresCos = FastMath.cos((angularThreshold * FastMath.PI) / 180.0f); // count triangles on supported faces for (int f = 0; f < iNrFaces; f++) { @@ -219,7 +297,7 @@ public static boolean genTangSpace(MikkTSpaceContext mikkTSpace, final float ang float tang[] = {pTSpace.os.x, pTSpace.os.y, pTSpace.os.z}; float bitang[] = {pTSpace.ot.x, pTSpace.ot.y, pTSpace.ot.z}; mikkTSpace.setTSpace(tang, bitang, pTSpace.magS, pTSpace.magT, pTSpace.orient, f, i); - mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? 1.0f : (-1.0f), f, i); + mikkTSpace.setTSpaceBasic(tang, pTSpace.orient == true ? -1.0f : 1.0f, f, i); ++index; } } @@ -434,7 +512,7 @@ static void MergeVertsFast(int piTriList_in_and_out[], TmpVert pTmpVert[], final boolean bReadyLeftSwap = false, bReadyRightSwap = false; while ((!bReadyLeftSwap) && iL < iR) { assert (iL >= iL_in && iL <= iR_in); - bReadyLeftSwap = !(pTmpVert[iL].vert[channel] < fSep); + bReadyLeftSwap = pTmpVert[iL].vert[channel] >= fSep; if (!bReadyLeftSwap) { ++iL; } @@ -582,7 +660,7 @@ static int generateInitialVerticesIndexList(TriInfo pTriInfos[], int piTriList_o ++iDstTriIndex; // next } else { //Note, Nehon: we should never get there with JME, because we don't support quads... - //but I'm going to let it there incase someone needs it... Just know this code is not tested. + //but I'm going to let it there in case someone needs it... Just know this code is not tested. {//TODO remove those useless brackets... pTriInfos[iDstTriIndex + 1].orgFaceNumber = f; pTriInfos[iDstTriIndex + 1].tSpacesOffs = iTSpacesOffs; @@ -866,8 +944,8 @@ static int build4RuleGroups(TriInfo pTriInfos[], Group pGroups[], int piGroupTri assert (iNrActiveGroups < iNrMaxGroups); pTriInfos[f].assignedGroup[i] = new Group(); pGroups[iNrActiveGroups] = pTriInfos[f].assignedGroup[i]; - pTriInfos[f].assignedGroup[i].vertexRepresentitive = vert_index; - pTriInfos[f].assignedGroup[i].orientPreservering = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; + pTriInfos[f].assignedGroup[i].vertexRepresentative = vert_index; + pTriInfos[f].assignedGroup[i].orientationPreserving = (pTriInfos[f].flag & ORIENT_PRESERVING) != 0; pTriInfos[f].assignedGroup[i].nrFaces = 0; ++iNrActiveGroups; @@ -930,7 +1008,7 @@ static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final TriInfo pMyTriInfo = psTriInfos[iMyTriIndex]; // track down vertex - final int iVertRep = pGroup.vertexRepresentitive; + final int iVertRep = pGroup.vertexRepresentative; int index = 3 * iMyTriIndex; int i = -1; if (piTriListIn[index] == iVertRep) { @@ -956,12 +1034,12 @@ static boolean assignRecur(final int piTriListIn[], TriInfo psTriInfos[], final && pMyTriInfo.assignedGroup[1] == null && pMyTriInfo.assignedGroup[2] == null) { pMyTriInfo.flag &= (~ORIENT_PRESERVING); - pMyTriInfo.flag |= (pGroup.orientPreservering ? ORIENT_PRESERVING : 0); + pMyTriInfo.flag |= (pGroup.orientationPreserving ? ORIENT_PRESERVING : 0); } } { final boolean bOrient = (pMyTriInfo.flag & ORIENT_PRESERVING) != 0; - if (bOrient != pGroup.orientPreservering) { + if (bOrient != pGroup.orientationPreserving) { return false; } } @@ -1028,7 +1106,7 @@ static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], fin assert (index >= 0 && index < 3); iVertIndex = piTriListIn[f * 3 + index]; - assert (iVertIndex == pGroup.vertexRepresentitive); + assert (iVertIndex == pGroup.vertexRepresentative); // is normalized already n = getNormal(mikkTSpace, iVertIndex); @@ -1099,7 +1177,7 @@ static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], fin System.arraycopy(tmp_group.triMembers, 0, pIndices, 0, iMembers); //memcpy(pIndices, tmp_group.pTriMembers, iMembers*sizeof(int)); pSubGroupTspace[iUniqueSubGroups] - = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentitive); + = evalTspace(tmp_group.triMembers, iMembers, piTriListIn, pTriInfos, mikkTSpace, pGroup.vertexRepresentative); ++iUniqueSubGroups; } @@ -1109,16 +1187,16 @@ static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], fin final int iVert = pTriInfos[f].vertNum[index]; TSpace pTS_out = psTspace[iOffs + iVert]; assert (pTS_out.counter < 2); - assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientPreservering); + assert (((pTriInfos[f].flag & ORIENT_PRESERVING) != 0) == pGroup.orientationPreserving); if (pTS_out.counter == 1) { pTS_out.set(avgTSpace(pTS_out, pSubGroupTspace[l])); pTS_out.counter = 2; // update counter - pTS_out.orient = pGroup.orientPreservering; + pTS_out.orient = pGroup.orientationPreserving; } else { assert (pTS_out.counter == 0); pTS_out.set(pSubGroupTspace[l]); pTS_out.counter = 1; // update counter - pTS_out.orient = pGroup.orientPreservering; + pTS_out.orient = pGroup.orientationPreserving; } } } @@ -1130,7 +1208,7 @@ static boolean generateTSpaces(TSpace psTspace[], final TriInfo pTriInfos[], fin } static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriListIn[], final TriInfo pTriInfos[], - final MikkTSpaceContext mikkTSpace, final int iVertexRepresentitive) { + final MikkTSpaceContext mikkTSpace, final int iVertexRepresentative) { TSpace res = new TSpace(); float fAngleSum = 0; @@ -1141,11 +1219,11 @@ static TSpace evalTspace(int face_indices[], final int iFaces, final int piTriLi if ((pTriInfos[f].flag & GROUP_WITH_ANY) == 0) { int i = -1; - if (piTriListIn[3 * f + 0] == iVertexRepresentitive) { + if (piTriListIn[3 * f + 0] == iVertexRepresentative) { i = 0; - } else if (piTriListIn[3 * f + 1] == iVertexRepresentitive) { + } else if (piTriListIn[3 * f + 1] == iVertexRepresentative) { i = 1; - } else if (piTriListIn[3 * f + 2] == iVertexRepresentitive) { + } else if (piTriListIn[3 * f + 2] == iVertexRepresentative) { i = 2; } assert (i >= 0 && i < 3); @@ -1273,7 +1351,7 @@ static void buildNeighborsFast(TriInfo pTriInfos[], Edge[] pEdges, final int piT } } - // sort over all edges by i0, this is the pricy one. + // sort over all edges by i0, this is the pricey one. quickSortEdges(pEdges, 0, iNrTrianglesIn * 3 - 1, 0, uSeed); // sort channel 0 which is i0 // sub sort over i1, should be fast. @@ -1640,9 +1718,9 @@ private static class SubGroup { private static class Group { int nrFaces; - List faceIndices = new ArrayList(); - int vertexRepresentitive; - boolean orientPreservering; + List faceIndices = new ArrayList<>(); + int vertexRepresentative; + boolean orientationPreserving; } private static class TriInfo { diff --git a/jme3-core/src/main/java/com/jme3/util/mikktspace/package-info.java b/jme3-core/src/main/java/com/jme3/util/mikktspace/package-info.java new file mode 100644 index 0000000000..3bbf579595 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/mikktspace/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * generate tangents for models using the mikktspace algorithm + */ +package com.jme3.util.mikktspace; diff --git a/jme3-core/src/main/java/com/jme3/util/package-info.java b/jme3-core/src/main/java/com/jme3/util/package-info.java new file mode 100644 index 0000000000..d01b79a74e --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * utility classes and miscellaneous useful algorithms + */ +package com.jme3.util; diff --git a/jme3-core/src/main/java/com/jme3/util/res/DefaultResourceLoader.java b/jme3-core/src/main/java/com/jme3/util/res/DefaultResourceLoader.java new file mode 100644 index 0000000000..b3f4e7d47f --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/res/DefaultResourceLoader.java @@ -0,0 +1,72 @@ +/* + * 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.util.res; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +/** + * Default implementation of {@link ResourceLoader}. + * Loads from classpath. + */ +class DefaultResourceLoader implements ResourceLoader { + + DefaultResourceLoader() { + + } + + @Override + public InputStream getResourceAsStream(String path, Class parent) { + if (parent == null) { + return Thread.currentThread().getContextClassLoader().getResourceAsStream(path); + } else { + return parent.getResourceAsStream(path); + } + } + + @Override + public URL getResource(String path, Class parent) { + if (parent == null) { + return Thread.currentThread().getContextClassLoader().getResource(path); + } else { + return parent.getResource(path); + } + } + + @Override + public Enumeration getResources(String path) throws IOException { + return Thread.currentThread().getContextClassLoader().getResources(path); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java b/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java new file mode 100644 index 0000000000..ef036e4400 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/res/ResourceLoader.java @@ -0,0 +1,81 @@ +/* + * 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.util.res; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; + +public interface ResourceLoader { + /** + * Finds the resource with the given name relative to the given parent class + * or to the root if the parent is null. + * + * @param path + * The resource name + * @param parent + * Optional parent class + * @return The resource URL or null if not found + */ + public URL getResource(String path, Class parent); + + /** + * Finds the resource with the given name relative to the given parent class + * or to the root if the parent is null. + * + * @param path + * The resource name + * @param parent + * Optional parent class + * @return An input stream to the resource or null if not found + */ + public InputStream getResourceAsStream(String path, Class parent); + + + /** + * Finds all resources with the given name. + * + * + * @param path + * The resource name + * @return An enumeration of {@link java.net.URL URL} objects for + * the resource. If no resources could be found, the enumeration + * will be empty. + * + * @throws IOException + * If I/O errors occur + * + * @throws IOException + */ + public Enumeration getResources(String path) throws IOException; +} diff --git a/jme3-core/src/main/java/com/jme3/util/res/Resources.java b/jme3-core/src/main/java/com/jme3/util/res/Resources.java new file mode 100644 index 0000000000..75c882c396 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/res/Resources.java @@ -0,0 +1,182 @@ +/* + * 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.util.res; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Enumeration; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This class is used to load resources from the default location usually the + * classpath. + */ +public class Resources { + /** + * The property name to set the ResourceLoader to use. Should be set automatically + * by the JmeSystemDelegate. + * Note: changing this property after the first use of the ResourceLoader will have no effect. + */ + public static final String PROPERTY_RESOURCE_LOADER_IMPLEMENTATION = "com.jme3.ResourceLoaderImplementation"; + + private static final String DEFAULT_IMPL = "com.jme3.util.res.DefaultResourceLoader"; + private static final Logger LOGGER = Logger.getLogger(Resources.class.getName()); + private static ResourceLoader impl = null; + + @SuppressWarnings("unchecked") + private static Class findResourceLoaderClass(String className) { + Class clazz = null; + + try { + clazz = Class.forName(className); + } catch (final Throwable e) { + LOGGER.log(Level.WARNING, "Unable to access {0}", className); + } + + if (clazz != null && !ResourceLoader.class.isAssignableFrom(clazz)) { + LOGGER.log(Level.WARNING, "{0} does not implement {1}", new Object[] { className, ResourceLoader.class.getName() }); + clazz = null; + } + + return (Class) clazz; + } + + private static ResourceLoader getResourceLoader() { + if (impl != null) return impl; + Class clazz = null; + String userDefinedImpl = System.getProperty(PROPERTY_RESOURCE_LOADER_IMPLEMENTATION, null); + + if (userDefinedImpl != null) { + LOGGER.log(Level.FINE, "Loading user defined ResourceLoader implementation {0}", userDefinedImpl); + clazz = findResourceLoaderClass(userDefinedImpl); + } + + if (clazz == null) { + LOGGER.log(Level.FINE, "No usable user defined ResourceLoader implementation found, using default implementation {0}", DEFAULT_IMPL); + clazz = findResourceLoaderClass(DEFAULT_IMPL); + } + + if (clazz == null) { + throw new RuntimeException("No ResourceLoader implementation found"); + } + + try { + impl = (ResourceLoader) clazz.getDeclaredConstructor().newInstance(); + } catch (final Throwable e) { + throw new RuntimeException("Could not instantiate ResourceLoader class " + clazz.getName(), e); + } + + return impl; + } + + /** + * Sets the resource loader implementation to use. + * @param impl The resource loader implementation + */ + public static void setResourceLoader(ResourceLoader impl) { + Resources.impl = impl; + } + + /** + * Finds the resource with the given name. + * + * @param path + * The resource name + * @return The resource URL or null if not found + */ + public static URL getResource(String path) { + return getResourceLoader().getResource(path, null); + } + + /** + * Finds the resource with the given name relative to the given parent class + * or to the root if the parent is null. + * + * @param path + * The resource name + * @param parent + * Optional parent class + * @return The resource URL or null if not found + */ + public static URL getResource(String path, Class parent) { + return getResourceLoader().getResource(path, parent); + } + + /** + * Finds the resource with the given name. + * + * @param path + * The resource name + * @return An input stream to the resource or null if not found + */ + public static InputStream getResourceAsStream(String path) { + return getResourceLoader().getResourceAsStream(path, null); + } + + /** + * Finds the resource with the given name relative to the given parent class + * or to the root if the parent is null. + * + * @param path + * The resource name + * @param parent + * Optional parent class + * @return An input stream to the resource or null if not found + */ + public static InputStream getResourceAsStream(String path, Class parent) { + return getResourceLoader().getResourceAsStream(path, parent); + } + + /** + * Finds all resources with the given name. + * + * + * @param path + * The resource name + * + * + * @return An enumeration of {@link java.net.URL URL} objects for + * the resource. If no resources could be found, the enumeration + * will be empty. + * + * @throws IOException + * If I/O errors occur + * + * @throws IOException + */ + public static Enumeration getResources(String path) throws IOException { + return getResourceLoader().getResources(path); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/Struct.java b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java new file mode 100644 index 0000000000..8b49d00248 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/Struct.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct; + +/** + * Classes implementing this interface are considered struct-like constructs. + * Class fields using one of the com.jme3.util.struct.fields.* classes as type + * will be considered part of the struct and must be declared as final, + * everything else will be ignored. + * + * @author Riccardo Balbo + */ +public interface Struct { +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructField.java b/jme3-core/src/main/java/com/jme3/util/struct/StructField.java new file mode 100644 index 0000000000..1390f45261 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructField.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct; + +/** + * A field of a struct + * + * @author Riccardo Balbo + */ +public abstract class StructField { + + private int position; + protected T value; + protected boolean isUpdateNeeded = true; + private String name; + private int depth = 0; + private int group = 0; + + protected StructField(int position, String name, T value) { + this.position = position; + this.value = value; + this.name = name; + } + + /** + * Get depth of the field + * + * @return + */ + public int getDepth() { + return depth; + } + + /** + * Get the group to which this field belongs (eg. a parent struct) + * + * @return id of the group + */ + public int getGroup() { + return group; + } + + void setGroup(int group) { + this.group = group; + } + + void setDepth(int depth) { + this.depth = depth; + } + + void setPosition(int position) { + this.position = position; + } + + /** + * Get position inside the struct + * + * @return position inside the struct + */ + public int getPosition() { + return position; + } + + /** + * Get value of this field + * + * @return value + */ + public T getValue() { + return value; + } + + /** + * Check if field needs update + * + * @return + */ + public boolean isUpdateNeeded() { + return isUpdateNeeded; + } + + /** + * Clear update needed used internally + */ + public void clearUpdateNeeded() { + isUpdateNeeded = false; + } + + /** + * Get simple name of the field + * + * @return + */ + public String getName() { + String friendlyName; + if (name != null) friendlyName = name; + else friendlyName = value.getClass().getSimpleName(); + return friendlyName; + } + + @Override + public String toString() { + return "StructField[" + getName() + "] = " + value.toString(); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java new file mode 100644 index 0000000000..4529239865 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructStd140BufferObject.java @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct; + +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.List; + +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.shader.bufferobject.BufferObject; +import com.jme3.shader.bufferobject.layout.Std140Layout; + +/** + * A BufferObject containing a struct serialized with Std140 layout. + * + * @author Riccardo Balbo + */ +public class StructStd140BufferObject extends BufferObject { + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(StructStd140BufferObject.class.getName()); + private transient Class rootStruct; + private transient List> resolvedFields; + private final Std140Layout std140 = new Std140Layout(); + + /** + * Create an empty Struct buffer + * + * @param str + */ + public StructStd140BufferObject() { + } + + /** + * Internal only + */ + public StructStd140BufferObject(int id) { + super(id); + } + + /** + * Create a Struct buffer from a Struct + * @param str the source struct + */ + public StructStd140BufferObject(Struct str) { + this(); + update(str); + } + + private void loadLayout(Struct struct) { + ArrayList classFields = new ArrayList(); + resolvedFields = StructUtils.getFields(struct, classFields); + for (Field field : classFields) { + if (!Modifier.isFinal(field.getModifiers())) throw new RuntimeException("Can't load layout for " + struct + " every field must be final"); + } + rootStruct = struct.getClass(); + StructUtils.setStd140BufferLayout(resolvedFields, std140, this); + } + + /** + * Update data and layout (when needed) using a Struct class. + * + * @param struct + */ + public void update(Struct struct) { + boolean forceUpdate = false; + if (rootStruct != struct.getClass()) { + if (logger.isLoggable(java.util.logging.Level.FINE)) { + logger.log(java.util.logging.Level.FINE, "Change in layout {0} =/= {1} ", new Object[] { rootStruct, struct.getClass() }); + } + loadLayout(struct); + forceUpdate = true; + } + StructUtils.updateBufferData(resolvedFields, forceUpdate, std140, this); + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(rootStruct.getName(), "rootClass", null); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + try { + String rootClass = ic.readString("rootClass", null); + if (rootClass == null) throw new Exception("rootClass is undefined"); + Class rootStructClass = (Class) Class.forName(rootClass); + Struct rootStruct = rootStructClass.newInstance(); + loadLayout(rootStruct); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public StructStd140BufferObject clone() { + StructStd140BufferObject clone = (StructStd140BufferObject) super.clone(); + return clone; + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java b/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java new file mode 100644 index 0000000000..707788cd29 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/StructUtils.java @@ -0,0 +1,217 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; + +import com.jme3.shader.bufferobject.BufferObject; +import com.jme3.shader.bufferobject.BufferRegion; +import com.jme3.shader.bufferobject.layout.BufferLayout; +import com.jme3.shader.bufferobject.layout.Std140Layout; + +/** + * StructUtils + * + * @author Riccardo Balbo + */ +public class StructUtils { + private static final java.util.logging.Logger logger = java.util.logging.Logger.getLogger(StructUtils.class.getName()); + + private static final Comparator> fieldComparator = new Comparator>() { + @Override + public int compare(final StructField a, final StructField b) { + return a.getPosition() - b.getPosition(); + } + }; + + /** + * In-place sort a List of StructFields accordingly to their position + * + * @param fields + * list to sort + * @return the passed list + */ + public static List> sortFields(List> fields) { + fields.sort(fieldComparator); + return fields; + } + + /** + * Get sorted List of StructFields from a Struct object + * + * @param struct + * the struct object + * @return the sorted list + */ + public static List> getFields(Struct struct) { + return getFields(struct, 0, null); + } + + public static List> getFields(Struct struct, ArrayList classFields) { + return getFields(struct, 0, classFields); + } + + private static List> getFields(Struct struct, int depth, ArrayList classFields) {// , + // final + // List + // fieldList) + // { + ArrayList> structFields = new ArrayList>(); + + Class structClass = struct.getClass(); + try { + // for each class field + // Extract class fields into a StructField List + // (Note: class methods are iterated in undefined order) + Field[] fields = structClass.getDeclaredFields(); + for (Field field : fields) { + field.setAccessible(true); + + Object o = field.get(struct); + if (o instanceof StructField) { + if (classFields != null) classFields.add(field); + StructField so = (StructField) o; + structFields.add(so); + } + } + + // Sort by position + sortFields(structFields); + + ArrayList> expandedStructFields = new ArrayList>(); + + // Expand sub struct and arrays to flat list + for (int i = 0; i < structFields.size(); i++) { + StructField so = structFields.get(i); + if (so.getValue() instanceof Struct) { // substruct + List> subStruct = getFields((Struct) so.getValue(), depth + 1, classFields); + expandedStructFields.addAll(subStruct); + } else if (so.getValue().getClass().isArray() && Struct.class.isAssignableFrom(so.getValue().getClass().getComponentType())) { // array + // of + // substruct + + Struct[] subA = (Struct[]) so.getValue(); + for (int j = 0; j < subA.length; j++) { + Struct sub = subA[j]; + List> subStruct = getFields(sub, depth + 1, classFields); + expandedStructFields.addAll(subStruct); + } + + } else { + so.setDepth(depth); + so.setGroup(struct.hashCode()); + expandedStructFields.add(so); + } + } + structFields = expandedStructFields; + + // Recompute positions in flat list + int i = 0; + for (StructField so : structFields) { + so.setPosition(i); + i++; + } + + } catch (final Exception e) { + throw new RuntimeException(e); + } + assert structFields.size() != 0; + return structFields; + } + + public static BufferObject setStd140BufferLayout(List> fields, Std140Layout serializer, BufferObject out) {// , + // final + // List + // fieldList) + // { + + int pos = -1; + + List regions = new ArrayList(); + + for (int i = 0; i < fields.size(); i++) { + StructField f = fields.get(i); + Object v = f.getValue(); + + int basicAlignment = serializer.getBasicAlignment(v); + int length = serializer.estimateSize(v); + + int start = serializer.align(pos + 1, basicAlignment); + int end = start + length - 1; + + if ((i == fields.size() - 1) || f.getGroup()!= fields.get(i + 1).getGroup()){// > fields.get(i + 1).getDepth()) { + end = (serializer.align(end, 16)) - 1; + } + + BufferRegion r = new BufferRegion(start, end); + regions.add(r); + pos = end; + } + + out.setRegions(regions); + + return out; + } + + /** + * Update data using a List of StructFields The current layout will be + * maintained unless previously invalidated + * + * @param fields + * sorted list of struct fields + */ + public static void updateBufferData(List> fields, boolean forceUpdate, BufferLayout layout, BufferObject out) { + boolean updateNeeded = false; + for (StructField f : fields) { + if (forceUpdate || f.isUpdateNeeded()) { + + BufferRegion region = out.getRegion(f.getPosition()); + if (logger.isLoggable(java.util.logging.Level.FINER)) { + logger.log(java.util.logging.Level.FINER, "Serialize {0} in {1} ", new Object[] { f, region }); + } + layout.write(region.getData(), f.getValue()); + region.markDirty(); + f.clearUpdateNeeded(); + updateNeeded = true; + } else { + if (logger.isLoggable(java.util.logging.Level.FINER)) { + logger.log(java.util.logging.Level.FINER, "Already up to date. Skip {0} ", new Object[] { f }); + } + } + } + if (updateNeeded) out.setUpdateNeeded(false); + } + +} \ No newline at end of file diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanArrayField.java new file mode 100644 index 0000000000..59e124b160 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanArrayField.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class BooleanArrayField extends StructField { + + public BooleanArrayField(int position, String name, Boolean[] value) { + super(position, name, value); + initializeToZero(); + } + + public BooleanArrayField(int position, String name, int length) { + super(position, name, new Boolean[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = false; + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Boolean[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanField.java new file mode 100644 index 0000000000..558ac0945a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/BooleanField.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class BooleanField extends StructField { + + public BooleanField(int position, String name, Boolean value) { + super(position, name, value); + } + + /** + * Set value for this field and mark for update + * + * @param value + */ + public void setValue(Boolean value) { + isUpdateNeeded = true; + this.value = value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAArrayField.java new file mode 100644 index 0000000000..6bf444cc71 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.ColorRGBA; +import com.jme3.util.struct.StructField; + +public class ColorRGBAArrayField extends StructField { + + public ColorRGBAArrayField(int position, String name, ColorRGBA[] value) { + super(position, name, value); + initializeToZero(); + } + + public ColorRGBAArrayField(int position, String name, int length) { + super(position, name, new ColorRGBA[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new ColorRGBA(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public ColorRGBA[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAField.java new file mode 100644 index 0000000000..c640e6cbe5 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/ColorRGBAField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.ColorRGBA; +import com.jme3.util.struct.StructField; + +public class ColorRGBAField extends StructField { + + public ColorRGBAField(int position, String name, ColorRGBA value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public ColorRGBA getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatArrayField.java new file mode 100644 index 0000000000..3356a3b10b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatArrayField.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class FloatArrayField extends StructField { + + public FloatArrayField(int position, String name, Float[] value) { + super(position, name, value); + initializeToZero(); + } + + public FloatArrayField(int position, String name, int length) { + super(position, name, new Float[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = 0f; + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Float[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatField.java new file mode 100644 index 0000000000..e19aa4579b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/FloatField.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class FloatField extends StructField { + + public FloatField(int position, String name, Float value) { + super(position, name, value); + } + + /** + * Set value for this field and mark for update + * + * @param value + */ + public void setValue(Float value) { + isUpdateNeeded = true; + this.value = value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/IntArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/IntArrayField.java new file mode 100644 index 0000000000..7fa1433e97 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/IntArrayField.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class IntArrayField extends StructField { + + public IntArrayField(int position, String name, Integer[] value) { + super(position, name, value); + initializeToZero(); + } + + public IntArrayField(int position, String name, Integer length) { + super(position, name, new Integer[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = 0; + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Integer[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/IntField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/IntField.java new file mode 100644 index 0000000000..1ee7caf174 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/IntField.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.StructField; + +public class IntField extends StructField { + + public IntField(int position, String name, Integer value) { + super(position, name, value); + } + + /** + * Set value for this field and mark for update + * + * @param value + */ + public void setValue(Integer value) { + isUpdateNeeded = true; + this.value = value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fArrayField.java new file mode 100644 index 0000000000..04ccabadf9 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Matrix3f; +import com.jme3.util.struct.StructField; + +public class Matrix3fArrayField extends StructField { + + public Matrix3fArrayField(int position, String name, Matrix3f[] value) { + super(position, name, value); + initializeToZero(); + } + + public Matrix3fArrayField(int position, String name, int length) { + super(position, name, new Matrix3f[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Matrix3f(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Matrix3f[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fField.java new file mode 100644 index 0000000000..554969e5c4 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix3fField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Matrix3f; +import com.jme3.util.struct.StructField; + +public class Matrix3fField extends StructField { + + public Matrix3fField(int position, String name, Matrix3f value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Matrix3f getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fArrayField.java new file mode 100644 index 0000000000..ba116f569b --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Matrix4f; +import com.jme3.util.struct.StructField; + +public class Matrix4fArrayField extends StructField { + + public Matrix4fArrayField(int position, String name, Matrix4f[] value) { + super(position, name, value); + initializeToZero(); + } + + public Matrix4fArrayField(int position, String name, int length) { + super(position, name, new Matrix4f[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Matrix4f(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Matrix4f[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fField.java new file mode 100644 index 0000000000..bafeb546da --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Matrix4fField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Matrix4f; +import com.jme3.util.struct.StructField; + +public class Matrix4fField extends StructField { + + public Matrix4fField(int position, String name, Matrix4f value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Matrix4f getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionArrayField.java new file mode 100644 index 0000000000..343e0c85fe --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Quaternion; +import com.jme3.util.struct.StructField; + +public class QuaternionArrayField extends StructField { + + public QuaternionArrayField(int position, String name, Quaternion[] value) { + super(position, name, value); + initializeToZero(); + } + + public QuaternionArrayField(int position, String name, int length) { + super(position, name, new Quaternion[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Quaternion(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Quaternion[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionField.java new file mode 100644 index 0000000000..5214d5fc24 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/QuaternionField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Quaternion; +import com.jme3.util.struct.StructField; + +public class QuaternionField extends StructField { + + public QuaternionField(int position, String name, Quaternion value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Quaternion getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java new file mode 100644 index 0000000000..65b25628c1 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructArrayField.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import java.lang.reflect.Array; +import java.lang.reflect.Constructor; + +import com.jme3.util.struct.Struct; +import com.jme3.util.struct.StructField; + +public class SubStructArrayField extends StructField { + + @SuppressWarnings("unchecked") + public SubStructArrayField(int position, String name, T[] value) { + super(position, name, value); + initializeToZero((Class) value[0].getClass()); + } + + @SuppressWarnings("unchecked") + public SubStructArrayField(int position, String name, int length, Class structClass) { + super(position, name, (T[]) Array.newInstance(structClass, length)); + initializeToZero(structClass); + } + + private void initializeToZero(Class structClass) { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) try { + Constructor constructor = structClass.getDeclaredConstructor(); + constructor.setAccessible(true); + value[i] = constructor.newInstance(); + } catch (Exception e) { + throw new RuntimeException("Can't create new instance of " + structClass + " default constructor is missing? ",e); + } + } + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java new file mode 100644 index 0000000000..f9025985fd --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/SubStructField.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.util.struct.Struct; +import com.jme3.util.struct.StructField; + + +public class SubStructField extends StructField { + + public SubStructField(int position, String name, T value) { + super(position, name, value); + } + +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fArrayField.java new file mode 100644 index 0000000000..2870c8683c --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector2f; +import com.jme3.util.struct.StructField; + +public class Vector2fArrayField extends StructField { + + public Vector2fArrayField(int position, String name, Vector2f[] value) { + super(position, name, value); + initializeToZero(); + } + + public Vector2fArrayField(int position, String name, int length) { + super(position, name, new Vector2f[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Vector2f(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector2f[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fField.java new file mode 100644 index 0000000000..84bc884404 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector2fField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector2f; +import com.jme3.util.struct.StructField; + +public class Vector2fField extends StructField { + + public Vector2fField(int position, String name, Vector2f value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector2f getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fArrayField.java new file mode 100644 index 0000000000..934941314a --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector3f; +import com.jme3.util.struct.StructField; + +public class Vector3fArrayField extends StructField { + + public Vector3fArrayField(int position, String name, Vector3f[] value) { + super(position, name, value); + initializeToZero(); + } + + public Vector3fArrayField(int position, String name, int length) { + super(position, name, new Vector3f[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Vector3f(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector3f[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fField.java new file mode 100644 index 0000000000..72e75da5ad --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector3fField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector3f; +import com.jme3.util.struct.StructField; + +public class Vector3fField extends StructField { + + public Vector3fField(int position, String name, Vector3f value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector3f getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fArrayField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fArrayField.java new file mode 100644 index 0000000000..67ed7be7ca --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fArrayField.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector4f; +import com.jme3.util.struct.StructField; + +public class Vector4fArrayField extends StructField { + + public Vector4fArrayField(int position, String name, Vector4f[] value) { + super(position, name, value); + initializeToZero(); + } + + public Vector4fArrayField(int position, String name, int length) { + super(position, name, new Vector4f[length]); + initializeToZero(); + } + + private void initializeToZero() { + for (int i = 0; i < value.length; i++) { + if (value[i] == null) value[i] = new Vector4f(); + } + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector4f[] getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fField.java b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fField.java new file mode 100644 index 0000000000..f119deb2d3 --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/struct/fields/Vector4fField.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util.struct.fields; + +import com.jme3.math.Vector4f; +import com.jme3.util.struct.StructField; + +public class Vector4fField extends StructField { + + public Vector4fField(int position, String name, Vector4f value) { + super(position, name, value); + } + + /** + * Get value and mark field for update + * + * @return + */ + public Vector4f getValueForUpdate() { + isUpdateNeeded = true; + return value; + } +} diff --git a/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java b/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java index 17a8e2067d..c3c7bc0e88 100644 --- a/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java +++ b/jme3-core/src/main/java/com/jme3/util/xml/SAXUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,69 +41,74 @@ */ public final class SAXUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private SAXUtil() { + } + /** * Parses an integer from a string, if the string is null returns * def. - * + * * @param i The string to parse * @param def The default value if the string is null * @return the parsed value or def - * @throws SAXException + * @throws SAXException in case of a syntax error */ - public static int parseInt(String i, int def) throws SAXException{ + public static int parseInt(String i, int def) throws SAXException { if (i == null) return def; else{ try { return Integer.parseInt(i); } catch (NumberFormatException ex){ - throw new SAXException("Expected an integer, got '"+i+"'"); + throw new SAXException("Expected an integer, got '" + i + "'"); } } } - public static int parseInt(String i) throws SAXException{ + public static int parseInt(String i) throws SAXException { if (i == null) throw new SAXException("Expected an integer"); else{ try { return Integer.parseInt(i); } catch (NumberFormatException ex){ - throw new SAXException("Expected an integer, got '"+i+"'"); + throw new SAXException("Expected an integer, got '" + i + "'"); } } } - public static float parseFloat(String f, float def) throws SAXException{ + public static float parseFloat(String f, float def) throws SAXException { if (f == null) return def; else{ try { return Float.parseFloat(f); } catch (NumberFormatException ex){ - throw new SAXException("Expected a decimal, got '"+f+"'"); + throw new SAXException("Expected a decimal, got '" + f + "'"); } } } - public static float parseFloat(String f) throws SAXException{ + public static float parseFloat(String f) throws SAXException { if (f == null) throw new SAXException("Expected a decimal"); else{ try { return Float.parseFloat(f); } catch (NumberFormatException ex){ - throw new SAXException("Expected a decimal, got '"+f+"'"); + throw new SAXException("Expected a decimal, got '" + f + "'"); } } } - public static boolean parseBool(String bool, boolean def) throws SAXException{ + public static boolean parseBool(String bool, boolean def) throws SAXException { if (bool == null || bool.equals("")) return def; else - return Boolean.valueOf(bool); - //else + return Boolean.valueOf(bool); //else // throw new SAXException("Expected a boolean, got'"+bool+"'"); } @@ -115,25 +120,24 @@ public static String parseString(String str, String def){ return str; } - public static String parseString(String str) throws SAXException{ + public static String parseString(String str) throws SAXException { if (str == null) throw new SAXException("Expected a string"); else return str; } - public static Vector3f parseVector3(Attributes attribs) throws SAXException{ + public static Vector3f parseVector3(Attributes attribs) throws SAXException { float x = parseFloat(attribs.getValue("x")); float y = parseFloat(attribs.getValue("y")); float z = parseFloat(attribs.getValue("z")); return new Vector3f(x,y,z); } - public static ColorRGBA parseColor(Attributes attribs) throws SAXException{ + public static ColorRGBA parseColor(Attributes attribs) throws SAXException { float r = parseFloat(attribs.getValue("r")); float g = parseFloat(attribs.getValue("g")); float b = parseFloat(attribs.getValue("b")); return new ColorRGBA(r, g, b, 1f); } - } diff --git a/jme3-core/src/main/java/com/jme3/util/xml/package-info.java b/jme3-core/src/main/java/com/jme3/util/xml/package-info.java new file mode 100644 index 0000000000..87f88713be --- /dev/null +++ b/jme3-core/src/main/java/com/jme3/util/xml/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * utilities for parsing XML + */ +package com.jme3.util.xml; diff --git a/jme3-core/src/main/resources/10000.monkeyz b/jme3-core/src/main/resources/10000.monkeyz deleted file mode 100644 index bf6a5c0146..0000000000 --- a/jme3-core/src/main/resources/10000.monkeyz +++ /dev/null @@ -1,112 +0,0 @@ - -. ...... ...... . ........... ............................. .................................. ........................................................ -................ . ... .. .... ... .. . ........... ....... ... ....... ........... ....... ............ ........... ....... ............ ...... -................ .... . ... .. ....... ... ........................................................................................................................... -................ .... .................. ............................................. .................................. .................................. ............... -................ ...... . ... .. .... .. .... .............::................................................. .................................. ............... -......... .. . .. ... ..... ......... ... .... ..............ID=............... ............................... ......... ..... ......... ... ......... .. -....... .... .. .. . ..............................................ZDN.. ... .......:Z ... ............................................................................. -....... .... .. .. . ...............................................MOM, .,M$. ......NM ................................................................................. -.......... ........................................................... . ..=M8ZN. ..MNMD87:,MDM+?DM8 . .......................................................................... -....... ..... ... ........... ... ...N,...... .~ZMMMMNDZZOOON7$8MOZZ$7$$$$77$DMMMMMO............... ........................................................ -......... ......... .. ......................... ... ..:NM~.. ..=$NNO8DOO8DOOOZZZZZZZ$$Z$$$$$$777$NNI~~~:,,:::~+=?77 ... .................................. ............... -....... . ........ ... ..MDOMN8Z8MMM8O8DNDDDN8OOO8OZZZZZO8$$$$$$$7777I77ODNNNMD8$ONNO, ........................................................... -....... .. .. .... ... ....... ...........?M888OOOOOO8DDDDDDDDDO8DDOZZZO888ZZ$$$$$$$7777777777777OMO?:,, ... ............................. ... ............... -......... .. ... ..............:8+... ... .=MOND8DNNNNNNDDDDDDDDDDDD88DDDDDOZZZZ$$$$$$777777777777I78MD+~:. .......................................................... -......... .. ...=I=:. ........... .~N?..... =M8DNNDNNNNNNNDDDDDDDDDDDDDDDDDOZZZZZZ$$$$$$777777777777I7I7I$NNI........................................................... -....... .. .. .. . .,DM8~. .......=, DMO,..=M88NNNNNNNNNNNDDDDDDDDDDDDDDD8OOZZZZZZ$$$$$$$777777777777IIIIII?ZNMN+,......,......................... ... ............... -......... ... ..ZMNI .... :MI.DDMMMM8ONNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD88Z$Z$$$$$$$77777777777IIIIIIIIII+I7ZO8MM7:. ... ... .... .... ... ......... .. -......... .. ... ............... .IMMM+ . .ONM$M8NNNDDNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDD8888O$$$$$$$777777777777IIIIIIIIDODMM$=., .......................... ... ............... -....... .. ...=DMMN~.=DNND8DNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDOZZZZZZZZZZ$$$$$$$777777777777IIIIIIII?$ND+,.. ................................................ -....... .. .. .. ...=MMMMM8DNNNNNNNNNNNNNNNNNNNNNNDDDNDDDDDDDDDDD8ZZZZZZ$$$$$$$$$$77777777777IIIIIIIIIIII7M+ ......... ........... .... ... ........... .. - ......................... ... ..~=ZNNNNDNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD888Z$$$$$$$777777777777IIIII?+IIIII?DZ.. - ......... .. OMMMMDDNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8ZZZ$Z$$$$$$777777777777IIIII+=+?III?$N... .. . .. - ......... .. .. =DMNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD8OOOOZZ$$$$$$$777777777777IIIIIIIII++III?N~ .. . .. - ........... .. .. .. ~$MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDD888888OZZ$$$$$7777777777777IIIIIIIIII????DM:....,~: - ....... .. .. .+?7NNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD88OZ$Z$$$$$$$7777777777777IIIIIIIIIIIIIII?ONNNMM?. -................... ....... .,OMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDOZZZZZZ$$$$$$$777777777777I77IIII+++++?III?8MMD?~. ................. - ......... ..................?ZDMMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8888OOOOOOOOOZ$777777777777IIIIIIIIIIIIIIII?$88N8ZI.,=+7Z8DDND8DD8O$I?:... . -. ........................ ......?MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDD8888888888Z$$$$7777777777777IIIIIIIIIIIIIIII??7ZDDNDDZ$I????????++++?7OMMMNZ~.... -................ .... .. .=MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDD8888O$$$$$$$$$7777777777777IIIIIIIIIIIIIIII??+++======+II?+78MMN87=~,.....:?O~ .. -................ .... .. ..=MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDD8O$ZZZZZZ$$$$$$$$7777777777777IIIIIIIIIIIII+=========?II??ZDMMN+........... ... ... -... ............. .. .. .ZMMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDOZZZZZ$$$$$$$$777777777777777IIIIIIIIIII======+?II??7NNZ~...... - ........ ........ ........$MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDD88DD8Z$Z$$$$$$$$$777777777777777IIIIIIIII?==++?II??IDMZ:.. .. - ?NMMZ: . .. .........ZMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD888888Z$$$$$$$$$$777777777777777IIIIIII++?III???INN=. - .....NM8++OMD?,. .........+MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD8888888Z$$$$$$$$$777777777777777IIIIIIIIIIIIIII8M8... - ..IM~.,...:$MN=........+MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDD88D888888$$$$$$$$$777777777777777IIIII+++++?IIIMO7~:,:~,::, .. .. .. - ....N:.,.......7MD.. . .:MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDDDDDDD8888888O$$$$$$$777777777777777IIIIIIIIIIIIII??$DMMM8Z7?~+ . .. . - ...ZN,,,.......,,DM+. ,MMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDDDDOOZZZZZZZ$$ZOO8Z$$$$$7777777777777777IIIIIIIIIIIIIINMMNO+. . .. . ... . ::~ -....M7:,,,,,,,...,.IM8. NMNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNDDDDDDDDDDDDDDDDDDD8OZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIII+?IIII?OM~..:7M+.. .......,?ZD8M? . .. - .M=:,,,,,,,,...,,,8M7MNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNNND888OOOOOOOOOOO88DDDDDDDDDDDDDDDDDDD8OZZZZZZZ$$$$$$$$$$777777777777777IIIIIII?+?IIII?ZMD$+::,.,,~=I7$8D8O7IODI. - ~M~::::,,,,,,,,.,,,=MMNNNNNNNNNNNNNNNNNNNNNNNNNND888888DNNO8OOOOOOOOOOOOOOOOOO8DDDDDDDDOZZZZZZZZZZZZZ$$$$$$$$$$777777777777777IIIIIIIIIIIIIII????I777$I?I????I7DN$~.. . .. .. - . +D~:~+::::::,,,,,,,,:MMNNNNNNNNNNNNNNNNNNNNNNDNN88888888O8D8O8OOOOOOOOOOOOOOOOOZZODDDDDD8ZZZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIII??++=+++????????+??$NNO+,... - 7Z~::+=::::::::,,,,,:,8MNNNNNNNNNNNNNNNNNNNN8ON888888O88O8O88MMMMNMNN8OOOOOOOOOZZZZZO8DDDDOZZZZZZZZZZZ$$$$$$$$$$777777777777777IIIIIII?=++?I?I?I78NMMD?=,... . - ..7$=:::O?:::::::::,,,:::OMNNNNNNNNNNNNNNNNNN88ON8888OO8NMN8ND8D+++===~+IO8NNDZZZZZZZZZZZOD8DOZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIII++==+?I78NDD8OZZZ8DMMMO7$O$ -. I$=~:::O7:::::::::,,,:::ZMNNNNNNNNNNNNNNNNO88O8OO8DMOI????++$DNO:~:~~~~=====+IONDZZZZZZZZO8D8ZZZZZZZZZZ$$$$$$$$$$7777777777777777IIIIIIII??++=++?78Z,,. ..,,,... . -. I$+:~~:D87:::::::::,,,:::8NNNNNNNNNNNNNNN888888ON8I?I???+==++++Z$~~~~~~::::::~==~$MNZZZZZZZ8D8ZZZZZZZZZ$$$$$$$$$$777777777777777III?IIIIIIIIIII??=??DM=......... . - ?$+~~~:DIZ8::::::::::,:::,NNNNNNNNNNNNNNO8888ONDIIIII+=~=~~~~~~~~~~~~~:::::::::::::~?OM8ZZZZZ88ZZZZZZZZ$$$$$$$$$$$$77777777777777I++++IIIIII7$Z7I?????ZM7. . - .~Z+:~~:D7+ZM=~~~::::::::::~DNNNNNNNNNNN8OOO8OD$DIII??+?+++==~~~~~~~::::::::::::::::::::~I8ZZZZZ8ZZZZZZZ$$$$$$$$$$$$77777777777777III?++?IIII?MO8NMMD87??$MI. . - ,D+~~~:D7+++M==~~~~:::::::::MNNNNNNNNN8888ONNIIIIIIIIIIII????????++==~::::::,,,,,,,:::,,:.$DZZZZZZZZZZZZ$$$$$$$$$$$77777777777777IIIII?=+?IIIIM~....~7MM7+ZM... - .M?+~~:8I++++N+~~~~~~~:::::~ONNNNNNNNN88OODMIIIIIIIIIIIIII??????+++++++===~::::::,,..,,,,,,=N8DZZZZZZ$ZZZ$$$$$$$$$$77777777777777IIIIIII?=?II??M7. . .OM8?M,.. . - .M7+~~~O?++++=87~~~~~~~:::~:=MNNNNNNNNOOOOMIIIIIIIIIIIIIIIII???7O8O8NND8O$?~==~:::::,..,,,,,,MN7D$ZZD8$Z$$$$ZZ$$7$$77$777$$ODNMMMMDZIIIIII?+II??88,.. ...$MO8:. - .+8++~~I$++?~=+Z$:~~~~~~::~~:INNNNNNNN8OONIIIIIIIIIIIIIIIII?+=~~~~~~~~~:::~IZN8$+~::,,,..,,,,,DD~8O$ZNND$$$$$O8+7O88878MZ?+=~~~~~~~=8M$IIIII??IZN7NO. ... .7MD+.. - ..8??=~~D???===IMO~~~~~~~:~~~~NNNNNNDNDODOIIIIIIIIIIIIII?====~=~~~~~~~~:::,,,,::::~=?:,,,.,,,,.8I:7MZZ8=7NO$$$7ONZ,,,.~?OD7,.,,,,,~~~~+D8IIII?+IIN7DMD=.. . =M?. - ..?7?+~~D???=+=M77D==~~~~:~~~~NNNNNNOODO8DIIIIIIIIIIII======~~~~~:::::::::::,,,,,,:::=Z~,,.,,,,.D,,,MZDD~:+NN$$$$$8M~.,,.,,,.,,,,~~=~~=~=7M7?II??IO$. ~, ..OI - .M??==7???++$N??$8~=~~~~~~~~NNNNNNOOOOONIIIIIIIIIIIII+=====~~~~~~~~~~:::::::::,,,,:,:ID,,,.,,,.,,,.ZZDZ,::~8MO$7$$OM+.,,,,,,,.,,,,,,~==~~$MIIIIIIOD~... ?. . - .?Z??==???++MI??+O7~===~~~~~NNNNNDOOOOODIIIIIIIIIIIIIII?+++===~~~~~::::::~:::::::,,,,,~8=,,,,,,,,,,,NOM,,,,,,+DMDZ77OM7..,,,,,,,,.....,~==~MD+I?II$M:.. . - ..MZ?==+?I?+M?I?++M?===~=~~~DNNNNNOOOOOMOIIIIIIIIIIIIIIIIII???????++=~::::::~++~:::,.,,,78.,,,,,,,,,,88+,,,,,,,..,INDO$NZ,.,,,,,,,,....,=Z~NDNMNZ+?IM+... - .. ~D+?=+?I??MII??$7D========NNNNNNOOOOO8D7IIIIIIIIIIIIIIII?????????++++++=::::::==:::,,,:~M=,,,,,,,,,,7$,,,,,,,,,,,,,.=Z8MN:..,,,,,,.....,IOM,O~=MD7IM+.. ... . .. - . 7Z?+++??+M?I??M?N7====~~?NNNNNN8OOOODIIIIIIIIIIIIIIIIII???????++++++++=++=~::::~:::,,.,~MZ,,,,,,,,,.N+,,,,,,,,,,,,,,,,.~$D=............,ZM~.DI.~8N7M?. - ...M7?++?I?OI??+MI7M=====~ZNNNNNN8OOOONIIIIIIIIIIIIIIIIII??$888ZZZ$$Z8DD8$?====~:::::::,.,:?D7,,,,,,,,~$.,,,,,,,,,,,,,...,:.,,,,,,,,,,,....~8. 7: :DDMI. - ...:MI+++??IOI?7MII8++===?NNNNNNNDOOOONIIIIIIIIIIIIIIII?$D7:,.............,OMDI===~:::~::,.,:,,,.Z8,,,,,,,,,,,,,,,.,,,,,,,:~~~~~~~===:,,,,,,$8..,....+MM? .. - .O8?++???OIIZ78IZI+===8NNNNNNNDOOOO8$IIIIIIIIIIIII?Z8:.. .........,8NOOZZ$8N7==~::~:,,.,,,,,,,.,,,,,,,,,,,,,,,,,,:~~7OOI?~:.,~====+=,,,.II.... .,NM: . - ..MII+????7II7DI7Z====NNNNNNNNNOOOOONIIIIIIIIIIIIID:,. ........ .Z8OOOZZZZ$$$MO:=::~~,,.,,,,,,,,,,,,,,,,,,,,,,,~=:,...,:~=========++++~,,M=. ZM. - ........$NI+???IIIIOIDI$+++ZNNNNNNDOOOOOOODZIIIIIIIIIIII,... .... ...+8OZZ$ZI?I$Z$$7IN7~=:~=:,,,,,,,,,,,,,,,,,,,,,,,OZ,,,~==========++==++++++:7N... .?D - .. .....N7?????I7III$8?++ZONNNNNOOOOOOOOOONIIIIIIIIII?,. ..........?OZI?II~...+$ZZ$7I7N7=~~=:,,,,,,,,,,,,,,,,,,,,:+,,,:~~~~~?ODNDNDD$++++++++=~N. ,= - ..II?+??O?I77IID=+=OODDDOOOOOOO8DD$IIIII7IIIIIII..... ....ODZ7..,++~,~7ZOZZ$77I7N+==~:,,,,,,,,,,,,,,,,,,~,,.,~~~~O$OZ$$$7?.,:I+++++?=ZMZ. ... - .......=7????N?I777IZ++IOON8OOOOODN8II7?~~~~~~~~=+II?M:. ........ NDZ$7IZZOOOOO88OZZ$7II7M?==~,,,,,,,,,,,,,,,,,I:..:~~:ZI+8Z77+++I8..=+++=INMD... . - .......$I?+??D+?O777?++ZOOOOOOODNO77I=~~~~~~~~~~~~~=IID=..........?DDZ7Z$7?Z8DDD888OZ$$77I+M+==~:,,,,,,,,,,,,,,,...,~~=N~IOZI+7??:?O..=++OMMD~.... . - .. ..M??+??ZI+N77I+++OOOOOODM$77?~~~~~~~~::::::::~~~=8O,........D8D$?7?7+?ODDDDD88OZ$77II?M===:,,,,,,,,,,,,,....:~~IO.=OI,?I$$$.~I+.++?MD... .. . - .. ..NII++??N+MI7$++?OOOOOM877?~~~~~~~:::::~~:::::::~~+M+. ....,M887~=?...+$DNDDD8OOZ$7II?+N~==:,,,,,,,,,,,,,,:~~~:N..MZ$77ZOZ$=~?I.=+7M8 .. . - .. ..IOII+++?N?M$8+++IOOONM777=~~~~~:::~~~~~~~~~~~:::::::IN:.....N88$77+...=7ONNDDD8OZI?I?+=N+===:,,,,,,,,,,,,,,~~~O...DZIDO88OZ~I8I8++$MO. - .. ....N7II++++?NM7++++$OOON7777~~~~::~~~~~~~~~~~~~~~~::,,:::8N,.. ?88$++I=,+7ONDNNDD88ZIII?+?IN~~=~~,,,,,,,,,,,,,~~:$..,8O8DZ888$?IMD=++7M$. . . - .. .M7I?++++7?++++++ZONM7777I=~~~~~~~~~~~~~~~~~~~~~~:::,,:::ON:..NOZZ$ZZ$IDOOZN888O$7ZI7I?M.N~==~~~,,,,,,,,,,,::~:,...NZO88OOZ$$O$==++++I8=. -... ....M$II+++7++++++==Z8877777?=~~~~~~~~~~~~~~~~~~:::::::::,,:::IOD8N8OZ8OOO$$ZZZZZZ$$$7I$7N+.I===~~~~:,,,,,,,,,:~~:=II.Z8Z8Z$7ZN$~=====+++=ZI. -... ....ZD?I?+II++++==+=ZM777777?=~~~~~~~~~~~~~~~~~~:::::::::::,,::~+?ZNMNOOOOO8OOOZZZZ$$ZZ7N:..====~~~~~~,,,,,,,,:~~~~~::=77OO$+~========+++++ON,. - .. .. .:MIII++D=+====+78$777777I~~~~~~~~~~~~~~~~~:::::::::::::::,,::=++=?7ONMMMMMND8OODNNNMMMN$====~==OM=~O~::,,,:=887:~~~~~~~~~~==~:,,:~+++++~?N:. .. .. .. .. - ..DOIII+===~==?77M77777777~~~~~~~~~~~~~~~+DN8ZZ?7?,:::::::::,,::~++++++=====~:~====~~~~=====+78=,,,.,=,,,,,Z..,.++~~~~~::,,..,,,,,,,,,:=++IN. - .......,M7III+===+7777M77777777==~~~~~~~~~~:~M,,,......=$I:::::::::,:::~+++++==========~~~~:::::,,,,,:=8$:,,,~+.,,$M:,.,,,,,,,,,,,,,,,,,,,,,,~++?8 . ... - ..... 7M?III++I7777IM$7777777=~~~~~~~~~~~~DZ8...........O+:::::::::,:::~=+~:::::::::::::::::,:,,,,,:IZ?,,,,.8,....,,,,,,,,,,,,.............,:+=OI... - .. .. .IM7IIIII7777MNN7777777=~~~~~~~~~~~~$,,7O,. ...:O+::::::::,,::::~==~::::::::::::::,,,,,,,,,,,,,,,,~Z,,,,,,,,,,,,,,,,,,,,,,,,,,,,...:==N=.. -... .. .....=M8IIIIII8N,=M7777777?~~~~~~~~~~~:7,...:I .......,O=~::::::::,::::::~~::::::::::::,,,,,,,,,,,,,,,,:==.,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,,==D. -... .. .......OM$??ZM=...M7777777I~~~~~~~~~~~:8,.....7? .........~7,:::::::::,::::::::::::::::,,,,,,,,,,,,,,,,,,,:O:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,.,:~O.. - ..ZN8=., . =D7777777+~~~~~~~~~~~?~,.... +8+. ..Z7::::::::::,::::::::::,,,,,,,,,,,,,,,,,,,,,,,,~O.,,,,,,,,,,,,,,,,,,,,,,,,,,::,,,=Z... ... .. . -............ ...............M$I77777I~~~~~~~~~~~:7,... ,..O~.. ....8+,:::::::::,,:::::,,:,,,,,,,,,,,,,,,,,,,,,,,:D.,,,,,,,,,,,,,,,,,,,,,,,,:7~,,,:=N... . - .............NM8777777=~~~~~~~~~~~:$+,.......:8: . ,O+,:::::::::,,,,:,,,,,,,,,,,,,,,,,,,,,,,,,,:Z.,,,,,,,,,,,,,,,,,,,,,,=8?,,,,,,MZ .. ... .. ... .. -. .. ....................ODO8777777+~~~~~~~~~~~:?7,,..... .,Z,.I.......,87,:,:::::::::,,,,,,,,,,,,,,,,,,,,,,,,,,,,I,,,,,,,,,,,,,,,,,,,,,?MZ:,,,,,,NM:..... .................... - .. .. ... ..... ?Z.IO777777=~~~~~~~~~~~~:O=.........M8..... .$Z~:::::,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:?:,,,,,,,,,,,,,,,,,+7$N.,,,,,,:$M. ... ....... . - ............... .Z8I77777I~~~~~~~~~~~~:?Z........:.=O~... . ..:O+,,:,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,,:7:,,,,,,,,,,,,,,:O$~D:,,,,,,:,NM... . .. ... .. -... .. ... ................... +N777777I~~~~~~~~~~~:::Z?..........~O$..=...... ,7$~,,:,,,,,,,,,,,,,,,,,,,,,,,,,,,,:D.,,,,,,,,,,,,7Z~,D=,,,,,,:::DN,............. ............... -... .. ... .................. ~M$7777III?~~~~~~~~~:~:~~8=...........8N,...... . ~II:,,,,,,,,,,,,,,,,,,,,,,,,,,,,=?,,,,,,,,,,?Z~.,O+,,,,,,,,,=MD . ............ ............... - ....... ....................IMDN777IIIII+~~~~:~~:::::::D7,........?+7D,,. ... ... .?N?,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,=ZI,..=$=:,,,,,,,,,8M=. .... .. .. ..... .. -. .. .............. .DM~:87I7IIIIII+:~~~~~:::::::,+D~,.........:77.,......... .::87~:,,,,,,,,,,,,,,,,,,,,.:~+7?~...:$7::,,,,,,:,:?MN:. .... .. ..... .. ... .. -. .. ... .................. .:,..NZ8IIIIIIIII?~:~::::::::::::+Z7,...........:+=..?... ....,~I$ZOZ$777?III7$Z$I+:,.....:O$~,,,,,,,,,,,~NMZ. ....... ... ..... .. -. .. ................ ~,:OND$IIIIII??+~::::::::::::::~$Z?........ .~$?I ..................+ZI,,,,,,,,,,,,,~NM= . .... .. ... .. ... -....... ....................... ..=M=I8??IIIII???~:::::::::::::::,~OO7, .. ... . ?77.?. . . ...,:?Z . ?DO:,:,,,,,,,,,,:,=NM~ .... ......... ... ........... .. - .. .. ................ . .:Z.~MI7?I?I?????=::::::::::::::::::,?7OI...... ......,~?+O..~:=~:....,:$NZ~,,,:,,,,:::,::,.IMM? . ... ....... ... .. ... .. - .. .. ................ . ,. ,MMM$III???????=:::::::::::::::::::,,?7ZNO+??~.............,~~7$$7,,,:::::::::::::::,=MMM$. ... ....... ... .. ... .. -....... . .................. .:M.88?OII???????++~:::::::::::::::::::::::::,,~======?+=~::,,,,::::::::::::::::::+DMM$,.... .... .. ... .... ... - ........... ................ . 7..$8M+OMNOI???????+=~::::::::::::::::::::::::::::::::::::::::::::::::::::::,+DMM8: .. ....................... ... ............... - ................ ...8M....7MMMD7?+???++?=~::::::::::::::::::::::::::::::::::::::::::::::::7MMM8=. ... ... ... .... .. ... .. ... .. -. ................ ,M. .. :I8MMND$I++++++++=~~::::::::::::::::::::::::::::::::::::::~7NMNZ~. .. . . .... .. .. .. . -. .. .............. = .~7DMMMM8ZI+++++++?+++==~~::::::::::::::~:::~~==?$8NMMDI,... . ........... ..... .............. ........... .. - .. ... ... ...... .... ... .,=IMMMMMNNNOIII+==+?+++++++++=++??Z8DMMMMM87~. . . .. ... ... .... .. ... .. ... .. - .. ... ... ...... .... . ...,:+$77OMMMMMMMMMMMMMMMMMNDDO7=:. .. . .. ... ... .... .. ... .. ... .. -............ .................. . .. .. .....................,..... .......... ..... ...................................................... - .................................... . .. .. ....................................................................................................................... -... .. ... .. .. . .. .. . -. .. ... .................. .. .. .. . .. .. .. . .. .. . .. ... . ....... ... .. ... - ................ . .. .. . .. .. . .... .. .. . - .. .. .... .. .. .. . .. . ... .. .. .. . .. . ........................................................ -. ........................... . .. .. .. . .. .. .. ... ... ....... .. ... .. -. ........................... . .. .. .. . .. .. .. ... ... ....... .. ... .. diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag b/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag new file mode 100644 index 0000000000..e9fb4645f5 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.frag @@ -0,0 +1,116 @@ +/** +* This code is based on the following articles: +* https://learnopengl.com/PBR/IBL/Diffuse-irradiance +* https://learnopengl.com/PBR/IBL/Specular-IBL +* - Riccardo Balbo +*/ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/IBL/Math.glsl" + +in vec2 TexCoords; +in vec3 LocalPos; + +uniform samplerCube m_EnvMap; +uniform float m_Roughness; +uniform int m_FaceId; + +void brdfKernel(){ + float NdotV=TexCoords.x; + float m_Roughness=TexCoords.y; + + vec3 V; + V.x = sqrt(1.0 - NdotV*NdotV); + V.y = 0.0; + V.z = NdotV; + float A = 0.0; + float B = 0.0; + vec3 N = vec3(0.0, 0.0, 1.0); + const uint SAMPLE_COUNT = 1024u; + for(uint i = 0u; i < SAMPLE_COUNT; i++){ + vec4 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, m_Roughness, N); + vec3 L = normalize(2.0 * dot(V, H) * H - V); + float NdotL = max(L.z, 0.0); + float NdotH = max(H.z, 0.0); + float VdotH = max(dot(V, H), 0.0); + if(NdotL > 0.0){ + float G = GeometrySmith(N, V, L, m_Roughness*m_Roughness); + float G_Vis = (G * VdotH) / (NdotH * NdotV); + float Fc = pow(1.0 - VdotH, 5.0); + A += (1.0 - Fc) * G_Vis; + B += Fc * G_Vis; + } + } + A /= float(SAMPLE_COUNT); + B /= float(SAMPLE_COUNT); + outFragColor.rg=vec2(A, B); + outFragColor.ba=vec2(0); +} + +void irradianceKernel(){ + // the sample direction equals the hemisphere's orientation + vec3 N = normalize(LocalPos); + vec3 irradiance = vec3(0.0); + vec3 up = vec3(0.0, 1.0, 0.0); + vec3 right = cross(up, N); + up = cross(N, right); + float sampleDelta = 0.025; + float nrSamples = 0.0; + for(float phi = 0.0; phi < 2.0 * PI; phi += sampleDelta){ + for(float theta = 0.0; theta < 0.5 * PI; theta += sampleDelta){ + // spherical to cartesian (in tangent space) + vec3 tangentSample = vec3(sin(theta) * cos(phi), sin(theta) * sin(phi), cos(theta)); + // tangent space to world + vec3 sampleVec = tangentSample.x * right + tangentSample.y * up + tangentSample.z * N; + irradiance += texture(m_EnvMap, sampleVec).rgb * cos(theta) * sin(theta); + nrSamples++; + } + } + irradiance = PI * irradiance * (1.0 / float(nrSamples)); + outFragColor = vec4(irradiance, 1.0); +} + +void prefilteredEnvKernel(){ + vec3 N = normalize(LocalPos); + vec3 R = N; + vec3 V = R; + + float a2 = m_Roughness * m_Roughness; + + const uint SAMPLE_COUNT = 1024u; + float totalWeight = 0.0; + vec3 prefilteredColor = vec3(0.0); + for(uint i = 0u; i < SAMPLE_COUNT; ++i) { + vec4 Xi = Hammersley(i, SAMPLE_COUNT); + vec3 H = ImportanceSampleGGX(Xi, a2, N); + float VoH = max(dot(V, H), 0.0); + vec3 L = normalize(2.0 * VoH * H - V); + float NdotL = max(dot(N, L), 0.0); + if(NdotL > 0.0) { + vec3 sampleColor = texture(m_EnvMap, L).rgb; + + float luminance = dot(sampleColor, vec3(0.2126, 0.7152, 0.0722)); + if (luminance > 64.0) { // TODO use average? + sampleColor *= 64.0/luminance; + } + + // TODO: use mipmap + prefilteredColor += sampleColor * NdotL; + totalWeight += NdotL; + } + + } + + if(totalWeight > 0.001) prefilteredColor /= totalWeight; + outFragColor = vec4(prefilteredColor, 1.0); +} + +void main(){ + #if defined(SIBL) + prefilteredEnvKernel(); + #elif defined(IRRADIANCE) + irradianceKernel(); + #else + brdfKernel(); + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md b/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md new file mode 100644 index 0000000000..147c1bb8a4 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.j3md @@ -0,0 +1,39 @@ +MaterialDef IBLKernels { + + MaterialParameters { + Int BoundDrawBuffer + TextureCubeMap EnvMap -LINEAR + Float Roughness + Int FaceId : 0 + Boolean UseBRDF + Boolean UseIrradiance + Boolean UseSpecularIBL + } + + Technique { + + VertexShader GLSL300 GLSL150 : Common/IBL/IBLKernels.vert + FragmentShader GLSL300 GLSL150 : Common/IBL/IBLKernels.frag + + WorldParameters { + WorldMatrix + ViewMatrix + ProjectionMatrix + } + + RenderState { + DepthWrite Off + DepthTest Off + DepthFunc Equal + FaceCull Off + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + BRDF:UseBRDF + IRRADIANCE: UseIrradiance + SIBL: UseSpecularIBL + } + + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert b/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert new file mode 100644 index 0000000000..aff3d7eae6 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBL/IBLKernels.vert @@ -0,0 +1,31 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +/** +* This code is based on the following articles: +* https://learnopengl.com/PBR/IBL/Diffuse-irradiance +* https://learnopengl.com/PBR/IBL/Specular-IBL +* - Riccardo Balbo +*/ +in vec3 inPosition; +in vec2 inTexCoord; +in vec3 inNormal; + +out vec2 TexCoords; +out vec3 LocalPos; + +uniform mat4 g_ViewMatrix; +uniform mat4 g_WorldMatrix; +uniform mat4 g_ProjectionMatrix; + +void main() { + LocalPos = inPosition.xyz; + TexCoords = inTexCoord.xy; + #ifdef BRDF + vec2 pos = inPosition.xy * 2.0 - 1.0; + gl_Position = vec4(pos, 0.0, 1.0); + #else + mat4 rotView = mat4(mat3(g_ViewMatrix)); // remove translation from the view matrix + vec4 clipPos = g_ProjectionMatrix * rotView * vec4(LocalPos, 1.0); + gl_Position = clipPos.xyww; + #endif +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBL/Math.glsl b/jme3-core/src/main/resources/Common/IBL/Math.glsl new file mode 100644 index 0000000000..e7e57240bd --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBL/Math.glsl @@ -0,0 +1,95 @@ +/** +* This code is based on the following articles: +* https://learnopengl.com/PBR/IBL/Diffuse-irradiance +* https://learnopengl.com/PBR/IBL/Specular-IBL +* - Riccardo Balbo +*/ +const float PI = 3.14159265359; + +float RadicalInverse_VdC(uint bits) { + bits = (bits << 16u) | (bits >> 16u); + bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u); + bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u); + bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u); + bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u); + return float(bits) * 2.3283064365386963e-10; // / 0x100000000 +} + +vec4 Hammersley(uint i, uint N){ + vec4 store=vec4(0); + store.x = float(i) / float(N); + store.y = RadicalInverse_VdC(i); + + float phi = 2.0 * PI *store.x; + store.z = cos(phi); + store.w = sin(phi); + + return store; +} + +// float VanDerCorput(uint n, uint base){ +// float invBase = 1.0 / float(base); +// float denom = 1.0; +// float result = 0.0; + +// for(uint i = 0u; i < 32u; ++i) +// { +// if(n > 0u) +// { +// denom = mod(float(n), 2.0); +// result += denom * invBase; +// invBase = invBase / 2.0; +// n = uint(float(n) / 2.0); +// } +// } + +// return result; +// } + +// vec2 Hammersley(uint i, uint N){ +// return vec2(float(i)/float(N), VanDerCorput(i, 2u)); +// } + + +vec3 ImportanceSampleGGX(vec4 Xi, float a2, vec3 N){ + + float cosTheta = sqrt((1.0 - Xi.y) / (1.0 + (a2 - 1.0) * Xi.y)); + float sinTheta = sqrt(1.0 - cosTheta*cosTheta); + + // from spherical coordinates to cartesian coordinates + vec3 H; + H.x = Xi.z * sinTheta; + H.y = Xi.w * sinTheta; + H.z = cosTheta; + + // from tangent-space vector to world-space sample vector + vec3 up = abs(N.z) < 0.999 ? vec3(0.0, 0.0, 1.0) : vec3(1.0, 0.0, 0.0); + vec3 tangent = normalize(cross(up, N)); + vec3 bitangent = cross(N, tangent); + + vec3 sampleVec = tangent * H.x + bitangent * H.y + N * H.z; + return normalize(sampleVec); +} + + + + +float GeometrySchlickGGX(float NdotV, float roughness){ + float a = roughness; + float k = (a * a) / 2.0; + + float nom = NdotV; + float denom = NdotV * (1.0 - k) + k; + + return nom / denom; +} +// ---------------------------------------------------------------------------- +float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness){ + float NdotV = max(dot(N, V), 0.0); + float NdotL = max(dot(N, L), 0.0); + float ggx2 = GeometrySchlickGGX(NdotV, roughness); + float ggx1 = GeometrySchlickGGX(NdotL, roughness); + + return ggx1 * ggx2; +} + \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag new file mode 100644 index 0000000000..6e83dcfde8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.frag @@ -0,0 +1,191 @@ +/** + +* - Riccardo Balbo +*/ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/IBL/Math.glsl" + +// #define NUM_SH_COEFFICIENT 9 +#ifndef PI + #define PI 3.1415926535897932384626433832795 +#endif + +in vec2 TexCoords; +in vec3 LocalPos; + + +uniform samplerCube m_Texture; +#ifdef SH_COEF + uniform sampler2D m_ShCoef; +#endif +uniform vec2 m_Resolution; +uniform int m_FaceId; + +const float sqrtPi = sqrt(PI); +const float sqrt3Pi = sqrt(3. / PI); +const float sqrt5Pi = sqrt(5. / PI); +const float sqrt15Pi = sqrt(15. / PI); + +#ifdef REMAP_MAX_VALUE + uniform float m_RemapMaxValue; +#endif + + +vec3 getVectorFromCubemapFaceTexCoord(float x, float y, float mapSize, int face) { + float u; + float v; + + /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] + * (+ 0.5f is for texel center addressing) */ + u = (2.0 * (x + 0.5) / mapSize) - 1.0; + v = (2.0 * (y + 0.5) / mapSize) - 1.0; + + + // Warp texel centers in the proximity of the edges. + float a = pow(mapSize, 2.0) / pow(mapSize - 1., 3.0); + + u = a * pow(u, 3.) + u; + v = a * pow(v, 3.) + v; + //compute vector depending on the face + // Code from Nvtt : https://github.com/castano/nvidia-texture-tools/blob/master/src/nvtt/CubeSurface.cpp#L101 + vec3 o =vec3(0); + switch(face) { + case 0: + o= normalize(vec3(1., -v, -u)); + break; + case 1: + o= normalize(vec3(-1., -v, u)); + break; + case 2: + o= normalize(vec3(u, 1., v)); + break; + case 3: + o= normalize(vec3(u, -1., -v)); + break; + case 4: + o= normalize(vec3(u, -v, 1.)); + break; + case 5: + o= normalize(vec3(-u, -v, -1.)); + break; + } + + return o; +} + +float atan2(in float y, in float x) { + bool s = (abs(x) > abs(y)); + return mix(PI / 2.0 - atan(x, y), atan(y, x), s); +} + +float areaElement(float x, float y) { + return atan2(x * y, sqrt(x * x + y * y + 1.)); +} + +float getSolidAngleAndVector(float x, float y, float mapSize, int face, out vec3 store) { + /* transform from [0..res - 1] to [- (1 - 1 / res) .. (1 - 1 / res)] + (+ 0.5f is for texel center addressing) */ + float u = (2.0 * (x + 0.5) / mapSize) - 1.0; + float v = (2.0 * (y + 0.5) / mapSize) - 1.0; + + store = getVectorFromCubemapFaceTexCoord(x, y, mapSize, face); + + /* Solid angle weight approximation : + * U and V are the -1..1 texture coordinate on the current face. + * Get projected area for this texel */ + float x0, y0, x1, y1; + float invRes = 1.0 / mapSize; + x0 = u - invRes; + y0 = v - invRes; + x1 = u + invRes; + y1 = v + invRes; + + return areaElement(x0, y0) - areaElement(x0, y1) - areaElement(x1, y0) + areaElement(x1, y1); +} + +void evalShBasis(vec3 texelVect, int i, out float shDir) { + float xV = texelVect.x; + float yV = texelVect.y; + float zV = texelVect.z; + + float x2 = xV * xV; + float y2 = yV * yV; + float z2 = zV * zV; + + if(i==0) shDir = (1. / (2. * sqrtPi)); + else if(i==1) shDir = -(sqrt3Pi * yV) / 2.; + else if(i == 2) shDir = (sqrt3Pi * zV) / 2.; + else if(i == 3) shDir = -(sqrt3Pi * xV) / 2.; + else if(i == 4) shDir = (sqrt15Pi * xV * yV) / 2.; + else if(i == 5) shDir = -(sqrt15Pi * yV * zV) / 2.; + else if(i == 6) shDir = (sqrt5Pi * (-1. + 3. * z2)) / 4.; + else if(i == 7) shDir = -(sqrt15Pi * xV * zV) / 2.; + else shDir = sqrt15Pi * (x2 - y2) / 4.; +} + +vec3 pixelFaceToV(int faceId, float pixelX, float pixelY, float cubeMapSize) { + vec2 normalizedCoords = vec2((2.0 * pixelX + 1.0) / cubeMapSize, (2.0 * pixelY + 1.0) / cubeMapSize); + + vec3 direction; + if(faceId == 0) { + direction = vec3(1.0, -normalizedCoords.y, -normalizedCoords.x); + } else if(faceId == 1) { + direction = vec3(-1.0, -normalizedCoords.y, normalizedCoords.x); + } else if(faceId == 2) { + direction = vec3(normalizedCoords.x, 1.0, normalizedCoords.y); + } else if(faceId == 3) { + direction = vec3(normalizedCoords.x, -1.0, -normalizedCoords.y); + } else if(faceId == 4) { + direction = vec3(normalizedCoords.x, -normalizedCoords.y, 1.0); + } else if(faceId == 5) { + direction = vec3(-normalizedCoords.x, -normalizedCoords.y, -1.0); + } + + return normalize(direction); +} + +void sphKernel() { + int width = int(m_Resolution.x); + int height = int(m_Resolution.y); + vec3 texelVect=vec3(0); + float shDir=0.; + float weight=0.; + vec4 color=vec4(0); + + int i=int(gl_FragCoord.x); + + #ifdef SH_COEF + vec4 r=texelFetch(m_ShCoef, ivec2(i, 0), 0); + vec3 shCoef=r.rgb; + float weightAccum = r.a; + #else + vec3 shCoef=vec3(0.0); + float weightAccum = 0.0; + #endif + + for(int y = 0; y < height; y++) { + for(int x = 0; x < width; x++) { + weight = getSolidAngleAndVector(float(x), float(y), float(width), m_FaceId, texelVect); + evalShBasis(texelVect, i, shDir); + color = texture(m_Texture, texelVect); + shCoef.x = (shCoef.x + color.r * shDir * weight); + shCoef.y = (shCoef.y + color.g * shDir * weight); + shCoef.z = (shCoef.z + color.b * shDir * weight); + weightAccum += weight; + } + } + + + + #ifdef REMAP_MAX_VALUE + shCoef.xyz=shCoef.xyz*m_RemapMaxValue; + weightAccum=weightAccum*m_RemapMaxValue; + #endif + + outFragColor = vec4(shCoef.xyz,weightAccum); + +} + +void main() { + sphKernel(); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md new file mode 100644 index 0000000000..eaafd2e108 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.j3md @@ -0,0 +1,34 @@ +MaterialDef IBLSphH { + + MaterialParameters { + Int BoundDrawBuffer + TextureCubeMap Texture -LINEAR + Int FaceId : 0 + Texture2D ShCoef -LINEAR + Vector2 Resolution + Float RemapMaxValue + } + + Technique { + + VertexShader GLSL300 GLSL150 : Common/IBLSphH/IBLSphH.vert + FragmentShader GLSL300 GLSL150 : Common/IBLSphH/IBLSphH.frag + + WorldParameters { + } + + RenderState { + DepthWrite Off + DepthTest Off + DepthFunc Equal + FaceCull Off + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + REMAP_MAX_VALUE: RemapMaxValue + SH_COEF: ShCoef + } + + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert new file mode 100644 index 0000000000..f7a3c82655 --- /dev/null +++ b/jme3-core/src/main/resources/Common/IBLSphH/IBLSphH.vert @@ -0,0 +1,18 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + +/** +*- Riccardo Balbo +*/ +in vec3 inPosition; +in vec2 inTexCoord; + +out vec2 TexCoords; +out vec3 LocalPos; + + +void main() { + LocalPos = inPosition.xyz; + TexCoords = inTexCoord.xy; + vec2 pos = inPosition.xy * 2.0 - 1.0; + gl_Position = vec4(pos, 0.0, 1.0); +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md index 7c5fddeb6c..f7011c5f20 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/HGaussianBlur.j3md @@ -1,6 +1,7 @@ MaterialDef HGaussianBlur { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float Size @@ -8,10 +9,14 @@ MaterialDef HGaussianBlur { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Post/Post.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Blur/HGaussianBlur.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Blur/HGaussianBlur.frag WorldParameters { } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag index a0d27b7bbd..5860df0def 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.frag @@ -14,7 +14,7 @@ void main(void) //float samples[10] = float[](-0.08,-0.05,-0.03,-0.02,-0.01,0.01,0.02,0.03,0.05,0.08); // 0.5,0.5 is the center of the screen - // so substracting texCoord from it will result in + // so subtracting texCoord from it will result in // a vector pointing to the middle of the screen vec2 dir = 0.5 - texCoord; @@ -39,7 +39,7 @@ void main(void) // we have taken eleven samples sum *= 1.0/11.0; - // weighten the blur effect with the distance to the + // weight the blur effect by the distance to the // center of the screen ( further out is blurred more) float t = dist * m_SampleStrength; t = clamp( t ,0.0,1.0); //0 <= t <= 1 diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md index 977007f073..806bb55473 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/RadialBlur.j3md @@ -1,6 +1,7 @@ MaterialDef Radial Blur { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Color Color @@ -10,13 +11,14 @@ MaterialDef Radial Blur { } Technique { - VertexShader GLSL300 GLSL120 GLSL150: Common/MatDefs/Post/Post.vert - FragmentShader GLSL300 GLSL120 GLSL150: Common/MatDefs/Blur/RadialBlur.frag + VertexShader GLSL300 GLSL150 GLSL120 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL120 : Common/MatDefs/Blur/RadialBlur.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md b/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md index 68e5d15baf..8bcb4292f2 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Blur/VGaussianBlur.j3md @@ -1,6 +1,7 @@ MaterialDef VGaussianBlur { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float Size @@ -8,10 +9,14 @@ MaterialDef VGaussianBlur { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Post/Post.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Blur/VGaussianBlur.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Blur/VGaussianBlur.frag WorldParameters { } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md index 23fbff8261..b3da65be47 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Gui/Gui.j3md @@ -1,37 +1,26 @@ MaterialDef Default GUI { MaterialParameters { + Int BoundDrawBuffer Texture2D Texture Color Color (Color) Boolean VertexColor (UseVertexColor) } Technique { - VertexShader GLSL150: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL150: Common/MatDefs/Gui/Gui.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Gui/Gui.frag WorldParameters { WorldViewProjectionMatrix } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TEXTURE : Texture VERTEX_COLOR : VertexColor } } - Technique { - VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL100: Common/MatDefs/Gui/Gui.frag - - WorldParameters { - WorldViewProjectionMatrix - } - - Defines { - TEXTURE : Texture - VERTEX_COLOR : VertexColor - } - } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md b/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md index 0c4c6c889e..31358d224e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Hdr/LogLum.j3md @@ -1,6 +1,7 @@ MaterialDef Log Lum 2D { MaterialParameters { + Int BoundDrawBuffer Texture2D Texture Vector2 BlockSize Vector2 PixelSize @@ -12,14 +13,15 @@ MaterialDef Log Lum 2D { } Technique { - VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL100: Common/MatDefs/Hdr/LogLum.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Hdr/LogLum.frag WorldParameters { WorldViewProjectionMatrix } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TEXTURE ENCODE_LUM : EncodeLum DECODE_LUM : DecodeLum diff --git a/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md b/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md index 24fbd04ae6..b515639eda 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Hdr/ToneMap.j3md @@ -1,5 +1,6 @@ MaterialDef Tone Mapper { MaterialParameters { + Int BoundDrawBuffer Texture2D Texture Texture2D Lum Texture2D Lum2 @@ -9,14 +10,15 @@ MaterialDef Tone Mapper { Float Gamma } Technique { - VertexShader GLSL100: Common/MatDefs/Gui/Gui.vert - FragmentShader GLSL100: Common/MatDefs/Hdr/ToneMap.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Gui/Gui.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Hdr/ToneMap.frag WorldParameters { WorldViewProjectionMatrix } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TEXTURE } } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md index 6983bfb0a6..7586e854fb 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.j3md @@ -2,8 +2,9 @@ MaterialDef Phong Lighting Deferred { MaterialParameters { + Int BoundDrawBuffer - // Use more efficent algorithms to improve performance + // Use more efficient algorithms to improve performance Boolean LowQuality // Improve quality at the cost of performance @@ -35,8 +36,8 @@ MaterialDef Phong Lighting Deferred { Technique { LightMode MultiPass - VertexShader GLSL100: Common/MatDefs/Light/Deferred.vert - FragmentShader GLSL100: Common/MatDefs/Light/Deferred.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Deferred.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Deferred.frag WorldParameters { WorldViewProjectionMatrix @@ -46,6 +47,7 @@ MaterialDef Phong Lighting Deferred { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer ATTENUATION : Attenuation V_TANGENT : VTangent MINNAERT : Minnaert @@ -59,4 +61,4 @@ MaterialDef Phong Lighting Deferred { Technique { } -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert index 0743cc1a94..e247694a98 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Deferred.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + varying vec2 texCoord; attribute vec3 inPosition; @@ -7,4 +9,4 @@ void main(){ texCoord = inTexCoord; vec4 pos = vec4(inPosition, 1.0); gl_Position = vec4(sign(pos.xy-vec2(0.5)), 0.0, 1.0); -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert index f4ad199635..d72712854f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/GBuf.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_WorldMatrix; @@ -68,4 +70,4 @@ void main(){ #ifdef VERTEX_COLOR DiffuseSum *= inColor; #endif -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag index f12478950b..c20b19c552 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Glow.frag @@ -17,10 +17,16 @@ void main(){ #ifdef HAS_GLOWMAP + #ifdef HAS_GLOWCOLOR + vec4 color = m_GlowColor; + #else + vec4 color = vec4(1.0); + #endif + #if defined(NEED_TEXCOORD1) - gl_FragColor = texture2D(m_GlowMap, texCoord1); + gl_FragColor = texture2D(m_GlowMap, texCoord1) * color; #else - gl_FragColor = texture2D(m_GlowMap, texCoord); + gl_FragColor = texture2D(m_GlowMap, texCoord) * color; #endif #else #ifdef HAS_GLOWCOLOR diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag index 32f3535788..adf4268810 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.frag @@ -6,25 +6,9 @@ #import "Common/ShaderLib/Lighting.glsllib" #endif -// fog - jayfella #ifdef USE_FOG -#import "Common/ShaderLib/MaterialFog.glsllib" -varying float fog_distance; -uniform vec4 m_FogColor; - -#ifdef FOG_LINEAR -uniform vec2 m_LinearFog; -#endif - -#ifdef FOG_EXP -uniform float m_ExpFog; -#endif - -#ifdef FOG_EXPSQ -uniform float m_ExpSqFog; -#endif - -#endif // end fog + #import "Common/ShaderLib/MaterialFog.glsllib" +#endif varying vec2 texCoord; #ifdef SEPARATE_TEXCOORD @@ -92,6 +76,11 @@ uniform float m_AlphaDiscardThreshold; #endif #endif + +#ifndef NORMAL_TYPE + #define NORMAL_TYPE -1.0 +#endif + void main(){ vec2 newTexCoord; @@ -141,11 +130,8 @@ void main(){ // *********************** #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); - //Note the -2.0 and -1.0. We invert the green channel of the normal map, - //as it's complient 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,-2.0,2.0) - vec3(1.0,-1.0,1.0))); + // Note we invert directx style normal maps to opengl style + vec3 normal = normalize((normalHeight.xyz * vec3(2.0,NORMAL_TYPE * 2.0,2.0) - vec3(1.0,NORMAL_TYPE * 1.0,1.0))); #ifdef LATC normal.z = sqrt(1.0 - (normal.x * normal.x) - (normal.y * normal.y)); #endif @@ -229,21 +215,11 @@ void main(){ SpecularSum2.rgb * specularColor.rgb * vec3(light.y); #endif - // 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 #ifdef USE_FOG - #ifdef FOG_LINEAR - gl_FragColor = getFogLinear(gl_FragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fog_distance); - #endif - #ifdef FOG_EXP - gl_FragColor = getFogExp(gl_FragColor, m_FogColor, m_ExpFog, fog_distance); - #endif - #ifdef FOG_EXPSQ - gl_FragColor = getFogExpSquare(gl_FragColor, m_FogColor, m_ExpSqFog, fog_distance); - #endif - #endif // end fog - + gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor)); + #endif gl_FragColor.a = alpha; } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md index 4a5fa256ee..fd32933738 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.j3md @@ -1,6 +1,7 @@ MaterialDef Phong Lighting { MaterialParameters { + Int BoundDrawBuffer // Compute vertex lighting in the shader // For better performance @@ -66,6 +67,9 @@ MaterialDef Phong Lighting { // The glow color of the object Color GlowColor + //The type of normal map: -1.0 (DirectX = default), 1.0 (OpenGl) + Float NormalType + // Parameters for fresnel // X = bias // Y = scale @@ -133,8 +137,8 @@ MaterialDef Phong Lighting { Technique { LightMode SinglePass - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/SPLighting.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/SPLighting.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/SPLighting.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/SPLighting.frag WorldParameters { WorldViewProjectionMatrix @@ -146,7 +150,8 @@ MaterialDef Phong Lighting { ViewProjectionMatrix } - Defines { + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer VERTEX_COLOR : UseVertexColor VERTEX_LIGHTING : VertexLighting MATERIAL_COLORS : UseMaterialColors @@ -167,6 +172,7 @@ MaterialDef Phong Lighting { INSTANCING : UseInstancing NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + NORMAL_TYPE: NormalType // fog - jayfella USE_FOG : UseFog @@ -180,8 +186,8 @@ MaterialDef Phong Lighting { LightMode MultiPass - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/Lighting.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/Lighting.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Light/Lighting.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Lighting.frag WorldParameters { WorldViewProjectionMatrix @@ -194,6 +200,7 @@ MaterialDef Phong Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer VERTEX_COLOR : UseVertexColor VERTEX_LIGHTING : VertexLighting MATERIAL_COLORS : UseMaterialColors @@ -214,6 +221,7 @@ MaterialDef Phong Lighting { INSTANCING : UseInstancing NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + NORMAL_TYPE: NormalType // fog - jayfella USE_FOG : UseFog @@ -225,8 +233,8 @@ MaterialDef Phong Lighting { Technique PreShadow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -236,6 +244,7 @@ MaterialDef Phong Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -255,8 +264,8 @@ MaterialDef Phong Lighting { Technique PostShadow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -267,6 +276,7 @@ MaterialDef Phong Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge @@ -292,8 +302,8 @@ MaterialDef Phong Lighting { Technique PreNormalPass { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -304,6 +314,7 @@ MaterialDef Phong Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DIFFUSEMAP_ALPHA : DiffuseMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -315,8 +326,8 @@ MaterialDef Phong Lighting { Technique Glow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix @@ -325,6 +336,7 @@ MaterialDef Phong Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NEED_TEXCOORD1 HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert index 390c1f1a47..c1460cc9be 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/Lighting.vert @@ -10,8 +10,8 @@ // fog - jayfella #ifdef USE_FOG -varying float fog_distance; -uniform vec3 g_CameraPosition; + varying float fogDistance; + uniform vec3 g_CameraPosition; #endif uniform vec4 m_Ambient; @@ -186,6 +186,6 @@ void main(){ #endif #ifdef USE_FOG - fog_distance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz); + fogDistance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); #endif -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRGlow.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRGlow.frag new file mode 100644 index 0000000000..e5cc237ff8 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRGlow.frag @@ -0,0 +1,38 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#if defined(NEED_TEXCOORD1) + varying vec2 texCoord1; +#else + varying vec2 texCoord; +#endif + + +#ifdef HAS_EMISSIVEMAP + uniform sampler2D m_EmissiveMap; +#endif + +#ifdef HAS_EMISSIVECOLOR + uniform vec4 m_Emissive; +#endif + + +void main(){ + #ifdef HAS_EMISSIVEMAP + #ifdef HAS_EMISSIVECOLOR + vec4 color = m_Emissive; + #else + vec4 color = vec4(1.0); + #endif + + #if defined(NEED_TEXCOORD1) + gl_FragColor = texture2D(m_EmissiveMap, texCoord1) * color; + #else + gl_FragColor = texture2D(m_EmissiveMap, texCoord) * color; + #endif + #else + #ifdef HAS_EMISSIVECOLOR + gl_FragColor = m_Emissive; + #else + gl_FragColor = vec4(0.0); + #endif + #endif +} 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 4eaa6a88f5..c6630a0fa0 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.frag @@ -1,318 +1,70 @@ #import "Common/ShaderLib/GLSLCompat.glsllib" -#import "Common/ShaderLib/PBR.glsllib" -#import "Common/ShaderLib/Parallax.glsllib" -#import "Common/ShaderLib/Lighting.glsllib" -varying vec2 texCoord; -#ifdef SEPARATE_TEXCOORD - varying vec2 texCoord2; -#endif - -varying vec4 Color; - -uniform vec4 g_LightData[NB_LIGHTS]; -uniform vec3 g_CameraPosition; -uniform vec4 g_AmbientLightColor; - -uniform float m_Roughness; -uniform float m_Metallic; - -varying vec3 wPosition; - - -#if NB_PROBES >= 1 - 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; -#endif -#if NB_PROBES == 3 - uniform samplerCube g_PrefEnvMap3; - uniform vec3 g_ShCoeffs3[9]; - uniform mat4 g_LightProbeData3; -#endif - -#ifdef BASECOLORMAP - uniform sampler2D m_BaseColorMap; -#endif - -#ifdef USE_PACKED_MR - uniform sampler2D m_MetallicRoughnessMap; -#else - #ifdef METALLICMAP - uniform sampler2D m_MetallicMap; - #endif - #ifdef ROUGHNESSMAP - uniform sampler2D m_RoughnessMap; - #endif -#endif - -#ifdef EMISSIVE - uniform vec4 m_Emissive; -#endif -#ifdef EMISSIVEMAP - uniform sampler2D m_EmissiveMap; -#endif -#if defined(EMISSIVE) || defined(EMISSIVEMAP) - uniform float m_EmissivePower; - uniform float m_EmissiveIntensity; -#endif +// enable apis and import PBRLightingUtils +#define ENABLE_PBRLightingUtils_getWorldPosition 1 +//#define ENABLE_PBRLightingUtils_getLocalPosition 1 +#define ENABLE_PBRLightingUtils_getWorldNormal 1 +#define ENABLE_PBRLightingUtils_getWorldTangent 1 +#define ENABLE_PBRLightingUtils_getTexCoord 1 +#define ENABLE_PBRLightingUtils_readPBRSurface 1 +#define ENABLE_PBRLightingUtils_computeDirectLightContribution 1 +#define ENABLE_PBRLightingUtils_computeProbesContribution 1 -#ifdef SPECGLOSSPIPELINE +#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib" - uniform vec4 m_Specular; - uniform float m_Glossiness; - #ifdef USE_PACKED_SG - uniform sampler2D m_SpecularGlossinessMap; - #else - uniform sampler2D m_SpecularMap; - uniform sampler2D m_GlossinessMap; - #endif +#ifdef DEBUG_VALUES_MODE + uniform int m_DebugValuesMode; #endif -#ifdef PARALLAXMAP - uniform sampler2D m_ParallaxMap; -#endif -#if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) - uniform float m_ParallaxHeight; -#endif - -#ifdef LIGHTMAP - uniform sampler2D m_LightMap; -#endif - -#if defined(NORMALMAP) || defined(PARALLAXMAP) - uniform sampler2D m_NormalMap; - varying vec4 wTangent; -#endif -varying vec3 wNormal; +uniform vec4 g_LightData[NB_LIGHTS]; +uniform vec3 g_CameraPosition; -#ifdef DISCARD_ALPHA - uniform float m_AlphaDiscardThreshold; +#ifdef USE_FOG + #import "Common/ShaderLib/MaterialFog.glsllib" #endif void main(){ - vec2 newTexCoord; - vec3 viewDir = normalize(g_CameraPosition - wPosition); - - vec3 norm = normalize(wNormal); - #if defined(NORMALMAP) || defined(PARALLAXMAP) - vec3 tan = normalize(wTangent.xyz); - mat3 tbnMat = mat3(tan, wTangent.w * cross( (norm), (tan)), norm); - #endif - - #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) - vec3 vViewDir = viewDir * tbnMat; - #ifdef STEEP_PARALLAX - #ifdef NORMALMAP_PARALLAX - //parallax map is stored in the alpha channel of the normal map - newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); - #else - //parallax map is a texture - newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); - #endif - #else - #ifdef NORMALMAP_PARALLAX - //parallax map is stored in the alpha channel of the normal map - newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); - #else - //parallax map is a texture - newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); - #endif - #endif - #else - newTexCoord = texCoord; - #endif + vec3 wpos = PBRLightingUtils_getWorldPosition(); + vec3 worldViewDir = normalize(g_CameraPosition - wpos); - #ifdef BASECOLORMAP - vec4 albedo = texture2D(m_BaseColorMap, newTexCoord) * Color; - #else - vec4 albedo = Color; - #endif - - #ifdef USE_PACKED_MR - vec2 rm = texture2D(m_MetallicRoughnessMap, newTexCoord).gb; - float Roughness = rm.x * max(m_Roughness, 1e-4); - float Metallic = rm.y * max(m_Metallic, 0.0); - #else - #ifdef ROUGHNESSMAP - float Roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-4); - #else - float Roughness = max(m_Roughness, 1e-4); - #endif - #ifdef METALLICMAP - float Metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0); - #else - float Metallic = max(m_Metallic, 0.0); - #endif - #endif - - float alpha = albedo.a; - - #ifdef DISCARD_ALPHA - if(alpha < m_AlphaDiscardThreshold){ - discard; - } - #endif - - // *********************** - // Read from textures - // *********************** - #if defined(NORMALMAP) - vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); - //Note the -2.0 and -1.0. We invert the green channel of the normal map, - //as it's complient 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))); - normal = normalize(tbnMat * normal); - //normal = normalize(normal * inverse(tbnMat)); - #else - vec3 normal = norm; - #endif - - #ifdef SPECGLOSSPIPELINE - - #ifdef USE_PACKED_SG - vec4 specularColor = texture2D(m_SpecularGlossinessMap, newTexCoord); - float glossiness = specularColor.a * m_Glossiness; - specularColor *= m_Specular; - #else - #ifdef SPECULARMAP - vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); - #else - vec4 specularColor = vec4(1.0); - #endif - #ifdef GLOSSINESSMAP - float glossiness = texture2D(m_GlossinessMap, newTexCoord).r * m_Glossiness; - #else - float glossiness = m_Glossiness; - #endif - specularColor *= m_Specular; - #endif - vec4 diffuseColor = albedo;// * (1.0 - max(max(specularColor.r, specularColor.g), specularColor.b)); - Roughness = 1.0 - glossiness; - vec3 fZero = specularColor.xyz; - #else - 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); - vec3 ao = vec3(1.0); - - #ifdef LIGHTMAP - vec3 lightMapColor; - #ifdef SEPARATE_TEXCOORD - lightMapColor = texture2D(m_LightMap, texCoord2).rgb; - #else - lightMapColor = texture2D(m_LightMap, texCoord).rgb; - #endif - #ifdef AO_MAP - lightMapColor.gb = lightMapColor.rr; - ao = lightMapColor; - #else - gl_FragColor.rgb += diffuseColor.rgb * lightMapColor; - #endif - specularColor.rgb *= lightMapColor; - #endif - - - float ndotv = max( dot( normal, viewDir ),0.0); - for( int i = 0;i < NB_LIGHTS; i+=3){ - vec4 lightColor = g_LightData[i]; - vec4 lightData1 = g_LightData[i+1]; - vec4 lightDir; - vec3 lightVec; - lightComputeDir(wPosition, lightColor.w, lightData1, lightDir, lightVec); - - float fallOff = 1.0; - #if __VERSION__ >= 110 - // allow use of control flow - if(lightColor.w > 1.0){ - #endif - fallOff = computeSpotFalloff(g_LightData[i+2], lightVec); - #if __VERSION__ >= 110 - } - #endif - //point light attenuation - fallOff *= lightDir.w; - - lightDir.xyz = normalize(lightDir.xyz); - vec3 directDiffuse; - vec3 directSpecular; - - float hdotv = PBR_ComputeDirectLight(normal, lightDir.xyz, viewDir, - lightColor.rgb, fZero, Roughness, ndotv, - directDiffuse, directSpecular); + // Create a blank PBRSurface. + PBRSurface surface = PBRLightingUtils_createPBRSurface(worldViewDir); + + // Read surface data from standard PBR matParams. (note: matParams are declared in 'PBRLighting.j3md' and initialized as uniforms in 'PBRLightingUtils.glsllib') + PBRLightingUtils_readPBRSurface(surface); - vec3 directLighting = diffuseColor.rgb *directDiffuse + directSpecular; - - gl_FragColor.rgb += directLighting * fallOff; + //Calculate necessary variables from pbr surface prior to applying lighting. Ensure all texture/param reading and blending occurrs prior to this being called! + PBRLightingUtils_calculatePreLightingValues(surface); + + // Calculate direct lights + for(int i = 0;i < NB_LIGHTS; i+=3){ + vec4 lightData0 = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + vec4 lightData2 = g_LightData[i+2]; + PBRLightingUtils_computeDirectLightContribution( + lightData0, lightData1, lightData2, + surface + ); } - #if NB_PROBES >= 1 - vec3 color1 = vec3(0.0); - vec3 color2 = vec3(0.0); - vec3 color3 = vec3(0.0); - float weight1 = 1.0; - float weight2 = 0.0; - float weight3 = 0.0; - - float ndf = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1); - #if NB_PROBES >= 2 - float ndf2 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData2, g_ShCoeffs2, g_PrefEnvMap2, color2); - #endif - #if NB_PROBES == 3 - float ndf3 = renderProbe(viewDir, wPosition, normal, norm, Roughness, diffuseColor, specularColor, ndotv, ao, g_LightProbeData3, g_ShCoeffs3, g_PrefEnvMap3, color3); - #endif - - #if NB_PROBES >= 2 - float invNdf = max(1.0 - ndf,0.0); - float invNdf2 = max(1.0 - ndf2,0.0); - float sumNdf = ndf + ndf2; - float sumInvNdf = invNdf + invNdf2; - #if NB_PROBES == 3 - float invNdf3 = max(1.0 - ndf3,0.0); - sumNdf += ndf3; - sumInvNdf += invNdf3; - weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf); - #endif - weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf); - weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf); + // Calculate env probes + PBRLightingUtils_computeProbesContribution(surface); - float weightSum = weight1 + weight2 + weight3; - - weight1 /= weightSum; - weight2 /= weightSum; - weight3 /= weightSum; - #endif - - #ifdef USE_AMBIENT_LIGHT - color1.rgb *= g_AmbientLightColor.rgb; - color2.rgb *= g_AmbientLightColor.rgb; - color3.rgb *= g_AmbientLightColor.rgb; - #endif - 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 defined(EMISSIVE) || defined (EMISSIVEMAP) - #ifdef EMISSIVEMAP - vec4 emissive = texture2D(m_EmissiveMap, newTexCoord); - #else - vec4 emissive = m_Emissive; - #endif - gl_FragColor += emissive * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity; + // Put it all together + gl_FragColor.rgb = vec3(0.0); + gl_FragColor.rgb += surface.bakedLightContribution; + gl_FragColor.rgb += surface.directLightContribution; + gl_FragColor.rgb += surface.envLightContribution; + gl_FragColor.rgb += surface.emission; + gl_FragColor.a = surface.alpha; + + #ifdef USE_FOG + gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor)); #endif - gl_FragColor.a = alpha; - + + //outputs the final value of the selected layer as a color for debug purposes. + #ifdef DEBUG_VALUES_MODE + gl_FragColor = PBRLightingUtils_getColorOutputForDebugMode(m_DebugValuesMode, vec4(gl_FragColor.rgba), surface); + #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 40b4f9a6c7..488c7e565a 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.j3md @@ -1,11 +1,12 @@ MaterialDef PBR Lighting { MaterialParameters { + Int BoundDrawBuffer // Alpha threshold for fragment discarding Float AlphaDiscardThreshold (AlphaTestFallOff) - //metalness of the material + //metallicity of the material Float Metallic : 1.0 //Roughness of the material Float Roughness : 1.0 @@ -28,7 +29,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 +39,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 @@ -53,6 +56,13 @@ MaterialDef PBR Lighting { // Parallax/height map Texture2D ParallaxMap -LINEAR + // Specular-AA + Boolean UseSpecularAA : true + // screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing. + Float SpecularAASigma + // clamping threshold,Use the slider to set a maximum value for the offset that HDRP subtracts from the smoothness value to reduce artifacts. + Float SpecularAAKappa + //Set to true if parallax map is stored in the alpha channel of the normal map Boolean PackedNormalParallax @@ -68,11 +78,15 @@ 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 gray scale ao map, on ly the r channel will be read. + // the light map is a grayscale ao map, only the r channel will be read. Boolean LightMapAsAOMap - + Boolean AoPackedInMRMap //shadows Int FilterMode Boolean HardwareShadows @@ -118,13 +132,37 @@ MaterialDef PBR Lighting { Boolean UseVertexColor Boolean BackfaceShadows : false + + Boolean UseFog + Color FogColor + Vector2 LinearFog + Float ExpFog + Float ExpSqFog + + Texture2D SunLightExposureMap + Boolean UseVertexColorsAsSunIntensity + Float StaticSunIntensity + Boolean BrightenIndoorShadows //should be set true when shadows are enabled, in order to prevent areas with low SunExposure from being way too dark when shadows are cast + + // debug the final value of the selected layer as a color output + Int DebugValuesMode + // Layers: + // 0 - albedo (unshaded) + // 1 - normals + // 2 - roughness + // 3 - metallic + // 4 - ao + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals } Technique { LightMode SinglePassAndImageBased - VertexShader GLSL300 GLSL110 GLSL150: Common/MatDefs/Light/PBRLighting.vert - FragmentShader GLSL300 GLSL110 GLSL150: Common/MatDefs/Light/PBRLighting.frag + VertexShader GLSL300 GLSL150 GLSL110: Common/MatDefs/Light/PBRLighting.vert + FragmentShader GLSL300 GLSL150 GLSL110: Common/MatDefs/Light/PBRLighting.frag WorldParameters { WorldViewProjectionMatrix @@ -135,9 +173,11 @@ MaterialDef PBR Lighting { ViewMatrix } - Defines { + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer BASECOLORMAP : BaseColorMap NORMALMAP : NormalMap + NORMALSCALE : NormalScale METALLICMAP : MetallicMap ROUGHNESSMAP : RoughnessMap EMISSIVEMAP : EmissiveMap @@ -154,21 +194,35 @@ MaterialDef PBR Lighting { USE_PACKED_MR: MetallicRoughnessMap USE_PACKED_SG: SpecularGlossinessMap SPECULARMAP : SpecularMap + SPECULAR_AA : UseSpecularAA + SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma + SPECULAR_AA_THRESHOLD : SpecularAAKappa GLOSSINESSMAP : GlossinessMap NORMAL_TYPE: NormalType 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 + EXPOSUREMAP : SunLightExposureMap + USE_VERTEX_COLORS_AS_SUN_INTENSITY : UseVertexColorsAsSunIntensity + STATIC_SUN_INTENSITY : StaticSunIntensity + BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows + DEBUG_VALUES_MODE : DebugValuesMode + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog } } Technique PreShadow { - VertexShader GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadowPBR.frag WorldParameters { WorldViewProjectionMatrix @@ -178,6 +232,7 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones INSTANCING : UseInstancing @@ -196,9 +251,9 @@ MaterialDef PBR Lighting { } - Technique PostShadow{ - VertexShader GLSL310 GLSL300 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL310 GLSL300 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + Technique PostShadow { + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowPBR.frag WorldParameters { WorldViewProjectionMatrix @@ -208,12 +263,12 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge DISCARD_ALPHA : AlphaDiscardThreshold SHADOWMAP_SIZE : ShadowMapSize - SHADOWMAP_SIZE : ShadowMapSize FADE : FadeInfo PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 @@ -231,44 +286,10 @@ MaterialDef PBR Lighting { } } - Technique PostShadow{ - VertexShader GLSL100: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadow.frag - - WorldParameters { - WorldViewProjectionMatrix - WorldMatrix - ViewProjectionMatrix - ViewMatrix - } - - Defines { - HARDWARE_SHADOWS : HardwareShadows - FILTER_MODE : FilterMode - PCFEDGE : PCFEdge - DISCARD_ALPHA : AlphaDiscardThreshold - SHADOWMAP_SIZE : ShadowMapSize - FADE : FadeInfo - PSSM : Splits - POINTLIGHT : LightViewProjectionMatrix5 - NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - BACKFACE_SHADOWS: BackfaceShadows - NUM_MORPH_TARGETS: NumberOfMorphTargets - NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers - } - - ForcedRenderState { - Blend Modulate - DepthWrite Off - PolyOffset -0.1 0 - } - } - Technique PreNormalPass { - VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -279,6 +300,8 @@ MaterialDef PBR Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + BASECOLORMAP_ALPHA : BaseColorMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing NUM_MORPH_TARGETS: NumberOfMorphTargets @@ -289,8 +312,8 @@ MaterialDef PBR Lighting { Technique Glow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/PBRGlow.frag WorldParameters { WorldViewProjectionMatrix @@ -299,6 +322,9 @@ MaterialDef PBR Lighting { } Defines { + HAS_EMISSIVEMAP : EmissiveMap + HAS_EMISSIVECOLOR : Emissive + BOUND_DRAW_BUFFER: BoundDrawBuffer NEED_TEXCOORD1 NUM_BONES : NumberOfBones INSTANCING : UseInstancing diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert index b910a8d4b2..9e50b0fb75 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/PBRLighting.vert @@ -4,12 +4,13 @@ #import "Common/ShaderLib/MorphAnim.glsllib" uniform vec4 m_BaseColor; + uniform vec4 g_AmbientLightColor; varying vec2 texCoord; #ifdef SEPARATE_TEXCOORD - varying vec2 texCoord2; - attribute vec2 inTexCoord2; + varying vec2 texCoord2; + attribute vec2 inTexCoord2; #endif varying vec4 Color; @@ -18,25 +19,41 @@ attribute vec3 inPosition; attribute vec2 inTexCoord; attribute vec3 inNormal; -#ifdef VERTEX_COLOR - attribute vec4 inColor; +#if defined (VERTEX_COLOR) || defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) + attribute vec4 inColor; +#endif + +#if defined(USE_VERTEX_COLORS_AS_SUN_INTENSITY) + varying vec4 vertColors; #endif varying vec3 wNormal; varying vec3 wPosition; -#if defined(NORMALMAP) || defined(PARALLAXMAP) - attribute vec4 inTangent; - varying vec4 wTangent; + +varying vec3 lPosition; + +attribute vec4 inTangent; +varying vec4 wTangent; + +#ifdef USE_FOG + varying float fogDistance; + uniform vec3 g_CameraPosition; #endif -void main(){ +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); vec3 modelSpaceNorm = inNormal; - - #if ( defined(NORMALMAP) || defined(PARALLAXMAP)) && !defined(VERTEX_LIGHTING) - vec3 modelSpaceTan = inTangent.xyz; + vec3 modelSpaceTan = inTangent.xyz; + + lPosition = modelSpacePos.xyz; + + + #ifdef USE_VERTEX_COLORS_AS_SUN_INTENSITY + vertColors = inColor; #endif + #ifdef NUM_MORPH_TARGETS #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) Morph_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); @@ -47,9 +64,9 @@ void main(){ #ifdef NUM_BONES #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) - Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); + Skinning_Compute(modelSpacePos, modelSpaceNorm, modelSpaceTan); #else - Skinning_Compute(modelSpacePos, modelSpaceNorm); + Skinning_Compute(modelSpacePos, modelSpaceNorm); #endif #endif @@ -61,14 +78,17 @@ void main(){ wPosition = TransformWorld(modelSpacePos).xyz; wNormal = TransformWorldNormal(modelSpaceNorm); - - #if defined(NORMALMAP) || defined(PARALLAXMAP) - wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w); - #endif + + wTangent = vec4(TransformWorldNormal(modelSpaceTan),inTangent.w); Color = m_BaseColor; - #ifdef VERTEX_COLOR + #ifdef VERTEX_COLOR Color *= inColor; #endif + + #ifdef USE_FOG + fogDistance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); + #endif + } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag index 2b5a4481c4..028ab44e89 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.frag @@ -88,6 +88,10 @@ uniform float m_Shininess; #endif #endif +#ifndef NORMAL_TYPE + #define NORMAL_TYPE -1.0 +#endif + void main(){ #if !defined(VERTEX_LIGHTING) #if defined(NORMALMAP) @@ -152,11 +156,7 @@ void main(){ // *********************** #if defined(NORMALMAP) && !defined(VERTEX_LIGHTING) vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); - //Note the -2.0 and -1.0. We invert the green channel of the normal map, - //as it's complient 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,-2.0,2.0) - vec3(1.0,-1.0,1.0))); + vec3 normal = normalize((normalHeight.xyz * vec3(2.0, NORMAL_TYPE * 2.0 ,2.0) - vec3(1.0, NORMAL_TYPE * 1.0,1.0))); #elif !defined(VERTEX_LIGHTING) vec3 normal = normalize(vNormal); diff --git a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert index 71233819b3..b16554a6e4 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Light/SPLighting.vert @@ -195,6 +195,6 @@ void main(){ #endif #ifdef USE_FOG - fog_distance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz); + fog_distance = distance(g_CameraPosition, (TransformWorld(modelSpacePos)).xyz); #endif } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md index 497b7d0a7b..86b1d8e212 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.j3md @@ -2,20 +2,22 @@ Exception This material definition is deprecated. Please use Unshaded.j3md inste MaterialDef Colored Textured { MaterialParameters { + Int BoundDrawBuffer Texture2D ColorMap Color Color (Color) } Technique { - VertexShader GLSL100: Common/MatDefs/Misc/ColoredTextured.vert - FragmentShader GLSL100: Common/MatDefs/Misc/ColoredTextured.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ColoredTextured.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ColoredTextured.frag WorldParameters { WorldViewProjectionMatrix } + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } - Technique { - } - + } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert index 572d841917..1ff1602be4 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ColoredTextured.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + uniform mat4 g_WorldViewProjectionMatrix; attribute vec3 inPosition; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Dashed.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Dashed.j3md new file mode 100644 index 0000000000..8cc0939f11 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Dashed.j3md @@ -0,0 +1,38 @@ +MaterialDef Dashed { + MaterialParameters { + Float DashSize + Vector4 Color + } + Technique { + WorldParameters { + WorldViewProjectionMatrix + } + VertexShaderNodes { + ShaderNode CommonVert { + Definition : CommonVert : Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn + InputMappings { + worldViewProjectionMatrix = WorldParam.WorldViewProjectionMatrix + modelPosition = Global.position.xyz + texCoord1 = Attr.inTexCoord + vertColor = Attr.inColor + } + OutputMappings { + Global.position = projPosition + } + } + } + FragmentShaderNodes { + ShaderNode Dashed { + Definition : Dashed : Common/MatDefs/ShaderNodes/Common/DashedPattern.j3sn + InputMappings { + texCoord = CommonVert.texCoord1 + inColor = MatParam.Color + size = MatParam.DashSize + } + OutputMappings { + Global.color = outColor + } + } + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md index 504062ffaa..db49951cd7 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Particle.j3md @@ -1,6 +1,7 @@ MaterialDef Point Sprite { MaterialParameters { + Int BoundDrawBuffer Texture2D Texture Float Quadratic Boolean PointSprite @@ -24,8 +25,8 @@ MaterialDef Point Sprite { // Point sprite should be used if running on ES2, but crash // if on desktop (because its not supported by HW) - VertexShader GLSL100 GLSL100 GLSL150 : Common/MatDefs/Misc/Particle.vert - FragmentShader GLSL100 GLSL120 GLSL150 : Common/MatDefs/Misc/Particle.frag + VertexShader GLSL300 GLSL150 GLSL120 GLSL100: Common/MatDefs/Misc/Particle.vert + FragmentShader GLSL300 GLSL150 GLSL120 GLSL100: Common/MatDefs/Misc/Particle.frag WorldParameters { WorldViewProjectionMatrix @@ -41,6 +42,7 @@ MaterialDef Point Sprite { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer USE_TEXTURE : Texture POINT_SPRITE : PointSprite } @@ -48,8 +50,8 @@ MaterialDef Point Sprite { Technique PreShadow { - VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -59,6 +61,7 @@ MaterialDef Point Sprite { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer COLOR_MAP : Texture } @@ -74,8 +77,8 @@ MaterialDef Point Sprite { Technique SoftParticles{ - VertexShader GLSL100 GLSL150 : Common/MatDefs/Misc/SoftParticle.vert - FragmentShader GLSL120 GLSL150 : Common/MatDefs/Misc/SoftParticle.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/SoftParticle.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/SoftParticle.frag WorldParameters { WorldViewProjectionMatrix @@ -91,6 +94,7 @@ MaterialDef Point Sprite { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer USE_TEXTURE : Texture POINT_SPRITE : PointSprite RESOLVE_DEPTH_MS : NumSamplesDepth @@ -99,14 +103,15 @@ MaterialDef Point Sprite { Technique Glow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NEED_TEXCOORD1 HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md index a21ffb50b1..a067c49fb6 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/ShowNormals.j3md @@ -1,12 +1,13 @@ MaterialDef Debug Normals { MaterialParameters { + Int BoundDrawBuffer // For instancing Boolean UseInstancing } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/ShowNormals.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/ShowNormals.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.frag WorldParameters { WorldViewProjectionMatrix @@ -16,7 +17,8 @@ MaterialDef Debug Normals { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer INSTANCING : UseInstancing - } + } } } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md index f7887a5a16..e1b503472d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Sky.j3md @@ -1,13 +1,14 @@ MaterialDef Sky Plane { MaterialParameters { + Int BoundDrawBuffer TextureCubeMap Texture Boolean SphereMap - Boolean EquirectMap + Boolean EquirectMap Vector3 NormalScale } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Sky.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/Sky.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Sky.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Sky.frag WorldParameters { ViewMatrix @@ -16,8 +17,9 @@ MaterialDef Sky Plane { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer SPHERE_MAP : SphereMap - EQUIRECT_MAP : EquirectMap + EQUIRECT_MAP : EquirectMap } RenderState { diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md new file mode 100644 index 0000000000..d039af39f5 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SkyNonCube.j3md @@ -0,0 +1,30 @@ +MaterialDef Sky Plane { + MaterialParameters { + Int BoundDrawBuffer + Texture2D Texture + Boolean SphereMap + Boolean EquirectMap + Vector3 NormalScale + } + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Sky.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Sky.frag + + WorldParameters { + ViewMatrix + ProjectionMatrix + WorldMatrixInverse + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + SPHERE_MAP : SphereMap + EQUIRECT_MAP : EquirectMap + } + + RenderState { + DepthWrite Off + DepthFunc Equal + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag index 0277727f6d..48c2a2e33d 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/SoftParticle.frag @@ -4,7 +4,7 @@ uniform DEPTHTEXTURE m_DepthTexture; uniform float m_Softness; // Power used in the contrast function varying vec2 vPos; // Position of the pixel -varying vec2 projPos;// z and w valus in projection space +varying vec2 projPos;// z and w values in projection space #ifdef USE_TEXTURE uniform sampler2D m_Texture; @@ -42,7 +42,7 @@ void main(){ float depthv = fetchTextureSample(m_DepthTexture, vPos, 0).x * 2.0 - 1.0; // Scene depth depthv *= projPos.y; float particleDepth = projPos.x; - + float zdiff = depthv - particleDepth; if(zdiff <= 0.0){ discard; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag index e615d8f1e3..0846d22df8 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.frag @@ -12,6 +12,10 @@ uniform vec4 m_Color; uniform sampler2D m_ColorMap; uniform sampler2D m_LightMap; +#ifdef DESATURATION + uniform float m_DesaturationValue; +#endif + varying vec2 texCoord1; varying vec2 texCoord2; @@ -45,6 +49,11 @@ void main(){ discard; } #endif + + #ifdef DESATURATION + vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), color.rgb)); + color.rgb = vec3(mix(color.rgb, gray, m_DesaturationValue)); + #endif gl_FragColor = color; -} \ No newline at end of file +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md index 4c609dca8d..b5854c2a23 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Misc/Unshaded.j3md @@ -1,6 +1,7 @@ MaterialDef Unshaded { MaterialParameters { + Int BoundDrawBuffer Texture2D ColorMap Texture2D LightMap Color Color (Color) @@ -59,11 +60,14 @@ MaterialDef Unshaded { Float ShadowMapSize Boolean BackfaceShadows: true + + // 1.0 indicates 100% desaturation + Float DesaturationValue } Technique { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100 : Common/MatDefs/Misc/Unshaded.frag WorldParameters { WorldViewProjectionMatrix @@ -72,6 +76,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer INSTANCING : UseInstancing SEPARATE_TEXCOORD : SeparateTexCoord HAS_COLORMAP : ColorMap @@ -82,14 +87,15 @@ MaterialDef Unshaded { NUM_BONES : NumberOfBones DISCARD_ALPHA : AlphaDiscardThreshold NUM_MORPH_TARGETS: NumberOfMorphTargets - NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers + DESATURATION : DesaturationValue } } Technique PreNormalPass { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -100,6 +106,8 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + COLORMAP_ALPHA : ColorMap NUM_BONES : NumberOfBones INSTANCING : UseInstancing NUM_MORPH_TARGETS: NumberOfMorphTargets @@ -109,8 +117,8 @@ MaterialDef Unshaded { Technique PreShadow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -120,6 +128,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer COLOR_MAP : ColorMap DISCARD_ALPHA : AlphaDiscardThreshold NUM_BONES : NumberOfBones @@ -140,8 +149,8 @@ MaterialDef Unshaded { Technique PostShadow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -151,6 +160,7 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge @@ -161,8 +171,8 @@ MaterialDef Unshaded { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - BACKFACE_SHADOWS: BackfaceShadows + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } @@ -176,8 +186,8 @@ MaterialDef Unshaded { Technique Glow { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix @@ -186,11 +196,12 @@ MaterialDef Unshaded { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NEED_TEXCOORD1 HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing HAS_POINTSIZE : PointSize NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn index bfa07d93d7..d7f30695c2 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorMix.j3sn @@ -6,7 +6,7 @@ ShaderNodeDefinitions{ mixes two colors according to a mix factor @input color1 the first color to mix @input color2 the second color to mix - @input factor the mix factor (from 0.0 to 1.0) fpr more information see the gsls mix function + @input factor the mix factor (from 0.0 to 1.0) fpr more information see the glsl mix function @output outColor the mixed color } Input { diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorSplitter.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorSplitter.j3sn index 9f202e51c0..3e2f534f92 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorSplitter.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorSplitter.j3sn @@ -3,7 +3,7 @@ ShaderNodeDefinitions{ Type: Fragment Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/colorSplitter.frag Documentation{ - Retrives the individual color channels + Retrieves the individual color channels @input color the color @output red the red channel @output green the green channel diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorToGrey.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorToGrey.j3sn index c90591c473..200e34425b 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorToGrey.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/ColorToGrey.j3sn @@ -3,7 +3,7 @@ ShaderNodeDefinitions{ Type: Fragment Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/colorToGrey.frag Documentation{ - Retrives the individual color channels + Retrieves the individual color channels @input color the color @output greyness or lightness of the color } diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag index f1a29fd2cc..3029f1d5f3 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/Mat3Vec3Mult100.frag @@ -1,3 +1,3 @@ void main(){ - outVector3 = matrix3 * vector3; + outVector3 = matrix3 * vector3; } diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn index 034fcd1167..deb28a9524 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Basic/TextureFetch.j3sn @@ -4,7 +4,7 @@ ShaderNodeDefinitions{ Shader GLSL100: Common/MatDefs/ShaderNodes/Basic/texture.frag Shader GLSL150: Common/MatDefs/ShaderNodes/Basic/texture15.frag Documentation{ - Fetches a color value in the given texture acording to given texture coordinates + Fetches a color value in the given texture according to given texture coordinates @input textureMap the texture to read @input texCoord the texture coordinates @output outColor the fetched color diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn index 0bc6a85337..e52c89f931 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard.j3sn @@ -13,7 +13,7 @@ ShaderNodeDefinitions{ @input mat4 worldViewMatrix The worldView matrix @input mat4 projectionMatrix The projection matrix @input vec3 modelPosition the vertex position - @input float scale the scale of the billboard (defautl 1) + @input float scale the scale of the billboard (default 1) //@output @output vec4 projPosition The position in projection space diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag index d858807605..0eba5e6d08 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Billboard100.frag @@ -1,15 +1,15 @@ void main(){ - // First colunm. + // First column. worldViewMatrix[0][0] = scale; worldViewMatrix[0][1] = 0.0; worldViewMatrix[0][2] = 0.0; - - // Second colunm. + + // Second column. worldViewMatrix[1][0] = 0.0; worldViewMatrix[1][1] = scale; worldViewMatrix[1][2] = 0.0; - - // Thrid colunm. + + // Third column. worldViewMatrix[2][0] = 0.0; worldViewMatrix[2][1] = 0.0; worldViewMatrix[2][2] = scale; diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn index 9afc7c7635..bc9b98da13 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/CommonVert.j3sn @@ -4,7 +4,7 @@ ShaderNodesDefinitions { Shader GLSL100: Common/MatDefs/ShaderNodes/Common/commonVert.vert Documentation { This Node is responsible for computing vertex position in projection space. - It also can pass texture coordinates 1 & 2, and vertexColor to the frgment shader as varying (or inputs for glsl >=1.3) + It also can pass texture coordinates 1 & 2, and vertexColor to the fragment shader as varying (or inputs for glsl >=1.3) @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) @input worldViewProjectionMatrix the World View Projection Matrix transforms model space to projection space. @input texCoord1 The first texture coordinates of the vertex (usually assigned with Attr.inTexCoord) diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern.j3sn new file mode 100644 index 0000000000..317906f370 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern.j3sn @@ -0,0 +1,23 @@ +ShaderNodeDefinitions{ + ShaderNodeDefinition Dashed { + Type: Fragment + + Shader GLSL100: Common/MatDefs/ShaderNodes/Common/DashedPattern100.frag + + Documentation{ + Renders dashed lines + @input vec2 texCoord The texture coordinates + @input float size the size of the dashes + @input vec4 inColor the color of the fragment so far + @outColor vec4 color the output color + } + Input { + vec2 texCoord + vec4 inColor + float size + } + Output { + vec4 outColor + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern100.frag new file mode 100644 index 0000000000..94dc08b12f --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/DashedPattern100.frag @@ -0,0 +1,9 @@ +void main(){ + //@input vec2 texCoord The texture coordinates + //@input float size the size of the dashes + //@output vec4 color the output color + + //insert glsl code here + outColor = inColor; + outColor.a = step(1.0 - size, texCoord.x); +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag index 98a05d116e..5e044b6c0a 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/FixedScale100.frag @@ -1,8 +1,8 @@ void main(){ - vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0); - vec3 dir = worldPos.xyz - cameraPos; - float distance = dot(cameraDir, dir); - float m11 = projectionMatrix[1][1]; - float halfHeight = (viewport.w - viewport.y) * 0.5; - scale = ((distance/halfHeight) * spriteHeight)/m11; + vec4 worldPos = worldMatrix * vec4(0.0, 0.0, 0.0, 1.0); + vec3 dir = worldPos.xyz - cameraPos; + float distance = dot(cameraDir, dir); + float m11 = projectionMatrix[1][1]; + float halfHeight = (viewport.w - viewport.y) * 0.5; + scale = ((distance/halfHeight) * spriteHeight)/m11; } diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn index f3cb30127f..01feedcc61 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Common/Unshaded.j3sn @@ -3,12 +3,12 @@ ShaderNodeDefinitions{ Type: Fragment Shader GLSL100: Common/MatDefs/ShaderNodes/Common/unshaded.frag Documentation { - This Node is responsible for outputing the unshaded color of a fragment. + This Node is responsible for outputting the unshaded color of a fragment. It can support texture mapping, an arbitrary input color and a vertex color (all resulting colors will be multiplied) @input texCoord the texture coordinates to use for texture mapping - @input vertColor the vertex color (often comming from a varrying) - @input matColor the material color (often comming from a material parameter) + @input vertColor the vertex color (often coming from a varying) + @input matColor the material color (often coming from a material parameter) @input colorMap the texture to use for texture mapping @input color the color this node contribution will be multiplied to @output outColor the color of the pixel (usually assigned to Global.color) diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn index 3177743b51..d7c3629e8e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Environment/reflect.j3sn @@ -5,7 +5,7 @@ ShaderNodeDefinitions{ Shader GLSL100: Common/MatDefs/ShaderNodes/Environment/reflect100.vert Documentation{ - Computes the relfection vector necessary to do some environment mapping + Computes the reflection vector necessary to do some environment mapping @input vec3 position position in model space @input vec3 normal the normal of the vertex @input vec3 camPosition camera position in world space diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn index f6c549ff5f..8ec69d6164 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Fog/Fog.j3sn @@ -6,7 +6,7 @@ ShaderNodesDefinitions { This Node is responsible for computing the fog factor of a vertex in the vertex shader. It computes the fogFactor according to view space z (distance from cam to vertex) and a fogDensity parameter. This Node should be used with a FogOutput for the fragment shader to effectively output the fog color. - @input modelPostion the vertex position in model space + @input modelPosition the vertex position in model space @input modelViewMatrix the model view matrix responsible to transform a vertex position from model space to view space. @input fogDensity the fog density (usually assigned with a material parameter) @output fogFactor the fog factor of the vertex output as a varying diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn index 2b734b5c8d..24d402733f 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/HardwareSkinning/HardwareSkinning.j3sn @@ -10,9 +10,9 @@ ShaderNodesDefinitions { This shader node doesn't take Normals and Tangent into account for full support use FullGPUSkinning IMPORTANT NOTE : for this node to work properly, you must declare a Int NumberOfBones material parameter to which the number of bones will be passed. @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) - @input boneMatrices an array of matrice holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter + @input boneMatrices an array of matrices holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter @input boneWeight a vec4 holding the bone weights applied to this vertex (4 weights max). - @input boneIndex a vec4 holding the bone indices assignes to this vertex (4 bones max). + @input boneIndex a vec4 holding the bone indices assigned to this vertex (4 bones max). @output modModelPosition transformed position of the vertex in model space. } Input{ @@ -37,9 +37,9 @@ ShaderNodesDefinitions { @input modelPosition the vertex position in model space (usually assigned with Attr.inPosition or Global.position) @input modelNormal the vertex normal in model space (usually assigned with Attr.inNormal) @input modelTangent the vertex tangent in model space (usually assigned with Attr.inTangent) - @input boneMatrices an array of matrice holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter + @input boneMatrices an array of matrices holding the transforms of the bones assigned to this vertex. Its size is defined by the NumberOfBones material parameter @input boneWeight a vec4 holding the bone weights applied to this vertex (4 weights max). - @input boneIndex a vec4 holding the bone indices assignes to this vertex (4 bones max). + @input boneIndex a vec4 holding the bone indices assigned to this vertex (4 bones max). @output modModelPosition transformed position of the vertex in model space. @output modModelNormal transformed normal of the vertex in model space. @output modModelTangent transformed tangent of the vertex in model space. diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag index 4fa7babec2..6e34a4c9c6 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/Dashed100.frag @@ -1,8 +1,8 @@ void main(){ - startPos.xy = (startPos * 0.5 + 0.5).xy * resolution; - float len = distance(gl_FragCoord.xy,startPos.xy); - outColor = inColor; - float factor = float(int(len * 0.25)); + startPos.xy = (startPos * 0.5 + 0.5).xy * resolution; + float len = distance(gl_FragCoord.xy,startPos.xy); + outColor = inColor; + float factor = float(int(len * 0.25)); if(mod(factor, 2.0) > 0.0){ discard; } diff --git a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag index 0e4f1d5be5..1394457e6a 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag +++ b/jme3-core/src/main/resources/Common/MatDefs/ShaderNodes/Misc/PerspectiveDivide100.frag @@ -1,3 +1,3 @@ void main(){ - outVec = vec4(inVec.xyz / inVec.w,1.0); + outVec = vec4(inVec.xyz / inVec.w,1.0); } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md index 4ba3815446..71e215b62e 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/BasicPostShadow.j3md @@ -1,13 +1,14 @@ MaterialDef Basic Post Shadow { MaterialParameters { + Int BoundDrawBuffer Texture2D ShadowMap Matrix4 LightViewProjectionMatrix } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/BasicPostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/BasicPostShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/BasicPostShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/BasicPostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -15,6 +16,7 @@ MaterialDef Basic Post Shadow { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NO_SHADOW2DPROJ } diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md index ce588b60af..6aad6d4ac8 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.j3md @@ -1,6 +1,7 @@ MaterialDef Post Shadow { MaterialParameters { + Int BoundDrawBuffer Int FilterMode Boolean HardwareShadows @@ -31,12 +32,12 @@ MaterialDef Post Shadow { Float ShadowMapSize Boolean BackfaceShadows: false - + } Technique { - VertexShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL310 GLSL300 GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL310 GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -44,6 +45,7 @@ MaterialDef Post Shadow { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert index 15be15f8da..78a2996d23 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadow.vert @@ -1,6 +1,7 @@ #import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/Instancing.glsllib" #import "Common/ShaderLib/Skinning.glsllib" +#import "Common/ShaderLib/MorphAnim.glsllib" uniform mat4 m_LightViewProjectionMatrix0; uniform mat4 m_LightViewProjectionMatrix1; diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md index 05188796c6..e5739281f8 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowFilter.j3md @@ -1,6 +1,7 @@ MaterialDef Post Shadow { MaterialParameters { + Int BoundDrawBuffer Int FilterMode Boolean HardwareShadows @@ -42,14 +43,15 @@ MaterialDef Post Shadow { } Technique { - VertexShader GLSL310 GLSL150: Common/MatDefs/Shadow/PostShadowFilter.vert - FragmentShader GLSL310 GLSL150: Common/MatDefs/Shadow/PostShadowFilter15.frag + VertexShader GLSL310 GLSL300 GLSL150 : Common/MatDefs/Shadow/PostShadowFilter.vert + FragmentShader GLSL310 GLSL300 GLSL150 : Common/MatDefs/Shadow/PostShadowFilter15.frag WorldParameters { ResolutionInverse } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth HARDWARE_SHADOWS : HardwareShadows @@ -65,14 +67,15 @@ MaterialDef Post Shadow { } Technique { - VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert - FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag WorldParameters { ResolutionInverse } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HARDWARE_SHADOWS : HardwareShadows FILTER_MODE : FilterMode PCFEDGE : PCFEdge diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowPBR.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowPBR.frag new file mode 100644 index 0000000000..8c779e4f86 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PostShadowPBR.frag @@ -0,0 +1,89 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Shadows.glsllib" +#if defined(PSSM) || defined(FADE) +varying float shadowPosition; +#endif + +varying vec4 projCoord0; +varying vec4 projCoord1; +varying vec4 projCoord2; +varying vec4 projCoord3; +#ifndef BACKFACE_SHADOWS + varying float nDotL; +#endif + +#ifdef POINTLIGHT + varying vec4 projCoord4; + varying vec4 projCoord5; + uniform vec3 m_LightPos; + varying vec4 worldPos; +#else + #ifndef PSSM + varying float lightDot; + #endif +#endif + +#ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + uniform sampler2D m_ColorMap; + #else + uniform sampler2D m_BaseColorMap; + #endif + uniform float m_AlphaDiscardThreshold; + varying vec2 texCoord; +#endif + +#ifdef FADE +uniform vec2 m_FadeInfo; +#endif + +void main(){ + + #ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + float alpha = texture2D(m_ColorMap,texCoord).a; + #else + float alpha = texture2D(m_BaseColorMap,texCoord).a; + #endif + if(alpha<=m_AlphaDiscardThreshold){ + discard; + } + #endif + + #ifndef BACKFACE_SHADOWS + if(nDotL > 0.0){ + discard; + } + #endif + + + float shadow = 1.0; + + #ifdef POINTLIGHT + shadow = getPointLightShadows(worldPos, m_LightPos, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, + projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); + #else + #ifdef PSSM + shadow = getDirectionalLightShadows(m_Splits, shadowPosition, + m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, + projCoord0, projCoord1, projCoord2, projCoord3); + #else + //spotlight + if(lightDot < 0.0){ + gl_FragColor = vec4(1.0); + return; + } + shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); + #endif + #endif + + #ifdef FADE + shadow = max(0.0, mix(shadow, 1.0, max(0.0, (shadowPosition - m_FadeInfo.x) * m_FadeInfo.y))); + #endif + + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + gl_FragColor = vec4(shadow, shadow, shadow, 1.0); + +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md index a76b36310b..72f1097bfc 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.j3md @@ -1,7 +1,15 @@ MaterialDef Pre Shadow { + MaterialParameters { + Int BoundDrawBuffer + } + Technique { - VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } WorldParameters { WorldViewProjectionMatrix diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert index 7157eea306..99cfc85ae0 100644 --- a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadow.vert @@ -12,7 +12,7 @@ void main(){ vec4 modelSpacePos = vec4(inPosition, 1.0); #ifdef NUM_MORPH_TARGETS - Morph_Compute(modelSpacePos, modelSpaceNorm); + Morph_Compute(modelSpacePos); #endif #ifdef NUM_BONES diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadowPBR.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadowPBR.frag new file mode 100644 index 0000000000..cf7f916696 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/PreShadowPBR.frag @@ -0,0 +1,28 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +varying vec2 texCoord; + +#ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + uniform sampler2D m_ColorMap; + #else + uniform sampler2D m_BaseColorMap; + #endif + uniform float m_AlphaDiscardThreshold; +#endif + + +void main(){ + #ifdef DISCARD_ALPHA + #ifdef COLOR_MAP + if (texture2D(m_ColorMap, texCoord).a <= m_AlphaDiscardThreshold){ + discard; + } + #else + if (texture2D(m_BaseColorMap, texCoord).a <= m_AlphaDiscardThreshold){ + discard; + } + #endif + #endif + + gl_FragColor = vec4(1.0); +} diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/FitLightFrustums.comp b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/FitLightFrustums.comp new file mode 100644 index 0000000000..191da705dc --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/FitLightFrustums.comp @@ -0,0 +1,236 @@ +#version 430 + +/** + * Computes tight bounding boxes for each shadow cascade via min/max on lightspace locations of depth samples that fall within each cascade. + */ + +layout(local_size_x = 16, local_size_y = 16) in; + +layout(binding = 0) uniform sampler2D inputDepth; + +layout(std430, binding = 1) readonly buffer MinMaxBuffer { + uint gMin; + uint gMax; +}; + +layout(std430, binding = 2) buffer CascadeBounds { + uvec4 gBounds[4]; // xy = min XY, zw = max XY per cascade + uvec2 gZBounds[4]; // x = min Z, y = max Z per cascade + uint rMin; // Copy of global min for output + uint rMax; // Copy of global max for output + uvec2 rSplitStart[3]; // [split start, blend end] for up to 3 splits +}; + +layout(location = 3) uniform mat4 cameraToLightView; +layout(location = 4) uniform int splitCount; +layout(location = 5) uniform vec2 cameraFrustum; // (near, far) +layout(location = 6) uniform float blendZone; + +// Shared memory for workgroup reduction +// Each workgroup is 16x16 = 256 threads +shared vec4 sharedBounds[4][256]; // minX, minY, maxX, maxY per cascade +shared vec2 sharedZBounds[4][256]; // minZ, maxZ per cascade + +/** + * Computes the start position of cascade i using log/uniform blend. + */ +float computeCascadeSplitStart(int i, float near, float far) { + float idm = float(i) / float(splitCount + 1); + float logSplit = near * pow(far / near, idm); + float uniformSplit = near + (far - near) * idm; + return logSplit * 0.65 + uniformSplit * 0.35; +} + +/** + * Converts projection-space Z to view-space Z (distance from camera). + */ +float getProjectionToViewZ(float projZPos) { + float near = cameraFrustum.x; + float far = cameraFrustum.y; + float a = far / (far - near); + float b = far * near / (near - far); + return b / (projZPos - a); +} + +/** + * Converts view-space Z to projection-space Z. + */ +float getViewToProjectionZ(float viewZPos) { + float near = cameraFrustum.x; + float far = cameraFrustum.y; + float a = far / (far - near); + float b = far * near / (near - far); + return a + b / viewZPos; +} + +/** + * Encodes a float for atomic min/max operations. + */ +uint floatFlip(float f) { + uint u = floatBitsToUint(f); + // If negative (sign bit set): flip ALL bits (turns into small uint) + // If positive (sign bit clear): flip ONLY sign bit (makes it large uint) + return (u & 0x80000000u) != 0u ? ~u : u ^ 0x80000000u; +} + +/** + * Decodes a uint back to float (inverse of floatFlip). + */ +float uintFlip(uint u) { + return uintBitsToFloat((u & 0x80000000u) != 0u ? u ^ 0x80000000u : ~u); +} + +void main() { + // Compute cascade split depths from the global min/max + float minDepth = uintFlip(gMin); + float maxDepth = uintFlip(gMax); + float minDepthFrustum = getProjectionToViewZ(minDepth); + float maxDepthFrustum = getProjectionToViewZ(maxDepth); + + // Compute split boundaries + vec2 splitStart[3]; // [split start, blend end] for up to 3 splits + int lastSplitIndex = splitCount - 1; + float lastSplit = minDepth; + + for (int i = 0; i < lastSplitIndex; i++) { + float viewSplitStart = computeCascadeSplitStart(i + 1, minDepthFrustum, maxDepthFrustum); + float nextSplit = getViewToProjectionZ(viewSplitStart); + float splitBlendStart = nextSplit - (nextSplit - lastSplit) * blendZone; + lastSplit = nextSplit; + splitStart[i] = vec2(splitBlendStart, nextSplit); + } + + ivec2 gid = ivec2(gl_GlobalInvocationID.xy); + ivec2 lid = ivec2(gl_LocalInvocationID.xy); + uint tid = gl_LocalInvocationIndex; + ivec2 inputSize = textureSize(inputDepth, 0); + ivec2 baseCoord = gid * 2; + + // Initialize local bounds to infinity + const float INF = 1.0 / 0.0; + vec4 localBounds[4] = vec4[4]( + vec4(INF, INF, -INF, -INF), + vec4(INF, INF, -INF, -INF), + vec4(INF, INF, -INF, -INF), + vec4(INF, INF, -INF, -INF) + ); + vec2 localZBounds[4] = vec2[4]( + vec2(INF, -INF), + vec2(INF, -INF), + vec2(INF, -INF), + vec2(INF, -INF) + ); + + // Sample 2x2 pixel block + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + ivec2 coord = baseCoord + ivec2(x, y); + if (coord.x < inputSize.x && coord.y < inputSize.y) { + float depth = texelFetch(inputDepth, coord, 0).r; + // Skip background (depth == 1.0) + if (depth != 1.0) { + // Reconstruct clip-space position from depth + vec2 uv = (vec2(coord) + 0.5) / vec2(textureSize(inputDepth, 0)); + vec4 clipPos = vec4(uv * 2.0 - 1.0, depth * 2.0 - 1.0, 1.0); + + // Transform to light view space + vec4 lightSpacePos = cameraToLightView * clipPos; + lightSpacePos /= lightSpacePos.w; + + // Find which cascade this sample belongs to + int cascadeIndex = 0; + while (cascadeIndex < lastSplitIndex) { + if (depth < splitStart[cascadeIndex].x) { + break; + } + cascadeIndex += 1; + } + + // Update bounds for primary cascade + vec4 exB = localBounds[cascadeIndex]; + localBounds[cascadeIndex] = vec4( + min(exB.xy, lightSpacePos.xy), + max(exB.zw, lightSpacePos.xy) + ); + vec2 exD = localZBounds[cascadeIndex]; + localZBounds[cascadeIndex] = vec2( + min(exD.x, lightSpacePos.z), + max(exD.y, lightSpacePos.z) + ); + + // Handle blend zone - also include in previous cascade + if (cascadeIndex > 0) { + int prevCascade = cascadeIndex - 1; + vec2 split = splitStart[prevCascade]; + if (depth < split.y) { + exB = localBounds[prevCascade]; + localBounds[prevCascade] = vec4( + min(exB.xy, lightSpacePos.xy), + max(exB.zw, lightSpacePos.xy) + ); + exD = localZBounds[prevCascade]; + localZBounds[prevCascade] = vec2( + min(exD.x, lightSpacePos.z), + max(exD.y, lightSpacePos.z) + ); + } + } + } + } + } + } + + // Store local results to shared memory + for (int i = 0; i < splitCount; i++) { + sharedBounds[i][tid] = localBounds[i]; + sharedZBounds[i][tid] = localZBounds[i]; + } + barrier(); + + // Parallel reduction in shared memory + for (uint stride = 128; stride > 0; stride >>= 1) { + if (tid < stride) { + for (int i = 0; i < splitCount; i++) { + vec4 us = sharedBounds[i][tid]; + vec4 other = sharedBounds[i][tid + stride]; + sharedBounds[i][tid] = vec4( + min(us.x, other.x), + min(us.y, other.y), + max(us.z, other.z), + max(us.w, other.w) + ); + + vec2 usZ = sharedZBounds[i][tid]; + vec2 otherZ = sharedZBounds[i][tid + stride]; + sharedZBounds[i][tid] = vec2( + min(usZ.x, otherZ.x), + max(usZ.y, otherZ.y) + ); + } + } + barrier(); + } + + // Global reduction using atomics (first thread in workgroup) + if (lid.x == 0 && lid.y == 0) { + for (int i = 0; i < splitCount; i++) { + vec4 bounds = sharedBounds[i][0]; + atomicMin(gBounds[i].x, floatFlip(bounds.x)); + atomicMin(gBounds[i].y, floatFlip(bounds.y)); + atomicMax(gBounds[i].z, floatFlip(bounds.z)); + atomicMax(gBounds[i].w, floatFlip(bounds.w)); + + vec2 zBounds = sharedZBounds[i][0]; + atomicMin(gZBounds[i].x, floatFlip(zBounds.x)); + atomicMax(gZBounds[i].y, floatFlip(zBounds.y)); + } + } + // Second thread copies output data + else if (gid.x == 1 && gid.y == 0) { + rMin = gMin; + rMax = gMax; + for (int i = 0; i < splitCount - 1; i++) { + rSplitStart[i] = uvec2(floatFlip(splitStart[i].x), floatFlip(splitStart[i].y)); + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/ReduceDepth.comp b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/ReduceDepth.comp new file mode 100644 index 0000000000..c6424c43e1 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/ReduceDepth.comp @@ -0,0 +1,75 @@ +#version 430 + +/** + * Finds the global minimum/maximum values of a depth texture. + */ + +layout(local_size_x = 16, local_size_y = 16) in; + +layout(binding = 0) uniform sampler2D inputDepth; + +layout(std430, binding = 1) buffer MinMaxBuffer { + uint gMin; + uint gMax; +}; + +// Each workgroup thread handles a 2x2 region, so 16x16 threads cover 32x32 pixels +// Then we reduce 256 values down to 1 +shared vec2 sharedMinMax[256]; + +/** + * Encodes a float for atomic min/max operations. + * Positive floats become large uints, negative floats become small uints, + * preserving the ordering relationship. + */ +uint floatFlip(float f) { + uint u = floatBitsToUint(f); + // If negative (sign bit set): flip ALL bits (turns into small uint) + // If positive (sign bit clear): flip ONLY sign bit (makes it large uint) + return (u & 0x80000000u) != 0u ? ~u : u ^ 0x80000000u; +} + +void main() { + ivec2 gid = ivec2(gl_GlobalInvocationID.xy); + ivec2 lid = ivec2(gl_LocalInvocationID.xy); + uint tid = gl_LocalInvocationIndex; + ivec2 inputSize = textureSize(inputDepth, 0); + + // Each thread samples a 2x2 block + ivec2 baseCoord = gid * 2; + vec2 minMax = vec2(1.0 / 0.0, 0.0); // (infinity, 0) + + for (int y = 0; y < 2; y++) { + for (int x = 0; x < 2; x++) { + ivec2 coord = baseCoord + ivec2(x, y); + if (coord.x < inputSize.x && coord.y < inputSize.y) { + float depth = texelFetch(inputDepth, coord, 0).r; + // Discard depth == 1.0 (background/sky) + if (depth != 1.0) { + minMax.x = min(minMax.x, depth); + minMax.y = max(minMax.y, depth); + } + } + } + } + + sharedMinMax[tid] = minMax; + barrier(); + + // Parallel reduction in shared memory + for (uint stride = 128; stride > 0; stride >>= 1) { + if (tid < stride) { + vec2 us = sharedMinMax[tid]; + vec2 other = sharedMinMax[tid + stride]; + sharedMinMax[tid] = vec2(min(us.x, other.x), max(us.y, other.y)); + } + barrier(); + } + + // First thread in workgroup writes to global buffer using atomics + if (lid.x == 0 && lid.y == 0) { + vec2 finalMinMax = sharedMinMax[0]; + atomicMin(gMin, floatFlip(finalMinMax.x)); + atomicMax(gMax, floatFlip(finalMinMax.y)); + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.frag b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.frag new file mode 100644 index 0000000000..c8a2c64057 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.frag @@ -0,0 +1,85 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Shadows.glsllib" + +//Stripped version of the usual shadow fragment shader for SDSM; it intentionally leaves out some features. +uniform sampler2D m_Texture; +uniform sampler2D m_DepthTexture; +uniform mat4 m_ViewProjectionMatrixInverse; +uniform vec4 m_ViewProjectionMatrixRow2; + +varying vec2 texCoord; + +const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, +0.0, 0.5, 0.0, 0.0, +0.0, 0.0, 0.5, 0.0, +0.5, 0.5, 0.5, 1.0); + +uniform mat4 m_LightViewProjectionMatrices[4]; + +uniform vec2 g_ResolutionInverse; + +uniform vec3 m_LightDir; + +uniform vec2[3] m_Splits; + +vec3 getPosition(in float depth, in vec2 uv){ + vec4 pos = vec4(uv, depth, 1.0) * 2.0 - 1.0; + pos = m_ViewProjectionMatrixInverse * pos; + return pos.xyz / pos.w; +} + + +float determineShadow(int index, vec4 worldPos){ + vec4 projCoord = biasMat * m_LightViewProjectionMatrices[index] * worldPos; + if(index == 0){ + return GETSHADOW(m_ShadowMap0, projCoord); + } else if(index == 1){ + return GETSHADOW(m_ShadowMap1, projCoord); + } else if(index == 2){ + return GETSHADOW(m_ShadowMap2, projCoord); + } else { + return GETSHADOW(m_ShadowMap3, projCoord); + } +} + +void main() { + float depth = texture2D(m_DepthTexture,texCoord).r; + vec4 color = texture2D(m_Texture,texCoord); + + //Discard shadow computation on the sky + if(depth == 1.0){ + gl_FragColor = color; + return; + } + + vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); + + float shadow = 1.0; + + int primary = 0; + int secondary = -1; + float mixture = 0; + while(primary < 3){ + vec2 split = m_Splits[primary]; + if(depth < split.y){ + if(depth >= split.x){ + secondary = primary + 1; + mixture = (depth - split.x) / (split.y - split.x); + } + break; + } + primary += 1; + } + shadow = determineShadow(primary, worldPos); + if(secondary >= 0){ + float secondaryShadow = determineShadow(secondary, worldPos); + shadow = mix(shadow, secondaryShadow, mixture); + } + + shadow = shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); + + gl_FragColor = color * vec4(shadow, shadow, shadow, 1.0); +} + + + diff --git a/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.j3md b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.j3md new file mode 100644 index 0000000000..3d82b3f440 --- /dev/null +++ b/jme3-core/src/main/resources/Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.j3md @@ -0,0 +1,52 @@ +MaterialDef Post Shadow { + + MaterialParameters { + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + + Float ShadowIntensity + + // SDSM uses Vector2Array for splits: + // Each Vector2 contains (blendStart, blendEnd) for that cascade transition + Vector2Array Splits + + Vector2 FadeInfo + + Matrix4Array LightViewProjectionMatrices + + Vector3 LightDir + + Float PCFEdge + Float ShadowMapSize + + Matrix4 ViewProjectionMatrixInverse + Vector4 ViewProjectionMatrixRow2 + + Texture2D Texture + Texture2D DepthTexture + + Boolean BackfaceShadows //Not used. + Int NumSamples //Not used. + } + + Technique { + VertexShader GLSL310 GLSL300 GLSL150 : Common/MatDefs/Shadow/PostShadowFilter.vert + FragmentShader GLSL310 GLSL300 GLSL150 : Common/MatDefs/Shadow/Sdsm/SdsmPostShadow.frag + + WorldParameters { + ResolutionInverse + } + + Defines { + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + SHADOWMAP_SIZE : ShadowMapSize + } + } +} \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/OpenCL/Random.clh b/jme3-core/src/main/resources/Common/OpenCL/Random.clh index 9891441c83..cdae61d84f 100644 --- a/jme3-core/src/main/resources/Common/OpenCL/Random.clh +++ b/jme3-core/src/main/resources/Common/OpenCL/Random.clh @@ -141,7 +141,7 @@ inline float randFloat(__global ulong* seed) * * __kernel void TestRandom(__global ulong* seeds) { * // ... - * float2 f2 = randGausianf(seeds + get_global_id(0)); + * float2 f2 = randGaussianf(seeds + get_global_id(0)); * // --- * } * @@ -167,7 +167,7 @@ inline float2 randGaussianf(__global ulong* seed) { * * __kernel void TestRandom(__global ulong* seeds) { * // ... - * double2 f2 = randGausian(seeds + get_global_id(0)); + * double2 f2 = randGaussian(seeds + get_global_id(0)); * // --- * } * diff --git a/jme3-core/src/main/resources/Common/ShaderLib/BlinnPhongLighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/BlinnPhongLighting.glsllib index 76d881a9a8..ade210f79f 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/BlinnPhongLighting.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/BlinnPhongLighting.glsllib @@ -1,4 +1,4 @@ -/*Blinn Phong ligting*/ +/*Blinn Phong lighting*/ /* * Computes diffuse factor (Lambert) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib index 0a28362cca..065bb9b99d 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Fog.glsllib @@ -8,8 +8,8 @@ uniform vec3 m_FogColor; // x == density // y == factor -// z == ystart -// w == yend +// z == yStart +// w == yEnd uniform vec4 m_FogParams; varying vec3 fogCoord; diff --git a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib index 697db195d5..ba03d60a3b 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/GLSLCompat.glsllib @@ -1,3 +1,21 @@ +#ifdef GL_ES + #ifdef FRAGMENT_SHADER + precision highp float; + precision highp int; + #if __VERSION__ >= 130 + precision highp sampler2DArray; + #endif + precision highp sampler2DArray; + precision highp sampler2DShadow; + precision highp samplerCube; + precision highp sampler3D; + precision highp sampler2D; + #if __VERSION__ >= 310 + precision highp sampler2DMS; + #endif + #endif +#endif + #if defined GL_ES # define hfloat highp float # define hvec2 highp vec2 @@ -19,11 +37,25 @@ #endif #if __VERSION__ >= 130 -# ifdef GL_ES -out highp vec4 outFragColor; -# else -out vec4 outFragColor; + +#ifdef FRAGMENT_SHADER + #ifdef GL_ES + #ifdef BOUND_DRAW_BUFFER + #for i=0..15 ( #if $i<=BOUND_DRAW_BUFFER $0 #endif ) + #if BOUND_DRAW_BUFFER == $i + layout( location = $i ) out highp vec4 outFragColor; + #else + layout( location = $i ) out highp vec4 outNOP$i; + #endif + #endfor + #else + out highp vec4 outFragColor; + #endif + #else + out vec4 outFragColor; + #endif #endif + # define texture1D texture # define texture2D texture # define texture3D texture @@ -42,4 +74,42 @@ out vec4 outFragColor; # define isnan(val) !(val<0.0||val>0.0||val==0.0) #endif +#if __VERSION__ == 110 +mat3 mat3_sub(mat4 m) { + return mat3(m[0].xyz, m[1].xyz, m[2].xyz); +} +#else + #define mat3_sub mat3 +#endif + +#if __VERSION__ <= 140 +float determinant(mat2 m) { + return m[0][0] * m[1][1] - m[1][0] * m[0][1]; +} + +float determinant(mat3 m) { + return + m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) + - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]) + + m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); +} +#endif + +#if __VERSION__ <= 130 +mat2 inverse(mat2 m) { + return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / determinant(m); +} + +mat3 inverse(mat3 m) { + return mat3( + + (m[1][1] * m[2][2] - m[2][1] * m[1][2]), + - (m[1][0] * m[2][2] - m[2][0] * m[1][2]), + + (m[1][0] * m[2][1] - m[2][0] * m[1][1]), + - (m[0][1] * m[2][2] - m[2][1] * m[0][2]), + + (m[0][0] * m[2][2] - m[2][0] * m[0][2]), + - (m[0][0] * m[2][1] - m[2][0] * m[0][1]), + + (m[0][1] * m[1][2] - m[1][1] * m[0][2]), + - (m[0][0] * m[1][2] - m[1][0] * m[0][2]), + + (m[0][0] * m[1][1] - m[1][0] * m[0][1])) / determinant(m); +} +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib index 5db1423ef5..9688ad71ec 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Hdr.glsllib @@ -62,4 +62,30 @@ vec3 HDR_ToneMap(in vec3 color, in float lumAvg, in float a, in float white){ vec3 HDR_ToneMap2(in vec3 color, in float lumAvg, in float a, in float white){ float scale = a / (lumAvg + 0.001); return (vec3(scale) * color) / (color + vec3(1.0)); +} + +// Based on https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl +// Input color is non-negative and resides in the Linear Rec. 709 color space. +// Output color is also Linear Rec. 709, but in the [0, 1] range. +vec3 HDR_KHRToneMap(in vec3 color, in vec3 exposure, in vec3 gamma) { + color *= pow(vec3(2.0), exposure); + + const float startCompression = 0.8 - 0.04; + const float desaturation = 0.15; + + float x = min(color.r, min(color.g, color.b)); + float offset = x < 0.08 ? x - 6.25 * x * x : 0.04; + color -= offset; + + float peak = max(color.r, max(color.g, color.b)); + if (peak < startCompression) return color; + + const float d = 1. - startCompression; + float newPeak = 1. - d * d / (peak + d - startCompression); + color *= newPeak / peak; + + float g = 1. - 1. / (desaturation * (peak - newPeak) + 1.); + color = mix(color, newPeak * vec3(1, 1, 1), g); + color = pow(color, gamma); + return color; } \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib index 37c3a40cf2..bffb200084 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Instancing.glsllib @@ -106,4 +106,4 @@ vec3 TransformWorldNormal(vec3 normal) { } -#endif \ No newline at end of file +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib index fb8f40524f..50eb7de874 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Lighting.glsllib @@ -3,7 +3,7 @@ /* * Computes light direction -* lightType should be 0.0,1.0,2.0, repectively for Directional, point and spot lights. +* lightType should be 0.0,1.0,2.0, respectively for Directional, point and spot lights. * Outputs the light direction and the light half vector. */ void lightComputeDir(in vec3 worldPos, in float lightType, in vec4 position, out vec4 lightDir, out vec3 lightVec){ diff --git a/jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib index 912be9d4c6..473835b0ce 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/MaterialFog.glsllib @@ -1,25 +1,59 @@ +//author @jayfella +#ifndef __MATERIAL_FOG_UTIL__ + #define __MATERIAL_FOG_UTIL__ -vec4 getFogLinear(in vec4 diffuseColor, in vec4 fogColor, in float start, in float end, in float distance) { + vec4 getFogLinear(in vec4 diffuseColor, in vec4 fogColor, in float start, in float end, in float distance) { - float fogFactor = (end - distance) / (end - start); - fogFactor = clamp(fogFactor, 0.0, 1.0); + float fogFactor = (end - distance) / (end - start); + fogFactor = clamp(fogFactor, 0.0, 1.0); - return mix(fogColor, diffuseColor, fogFactor); -} + return mix(fogColor, diffuseColor, fogFactor); + } -vec4 getFogExp(in vec4 diffuseColor, in vec4 fogColor, in float fogDensity, in float distance) { + vec4 getFogExp(in vec4 diffuseColor, in vec4 fogColor, in float fogDensity, in float distance) { - float fogFactor = 1.0 / exp(distance * fogDensity); - fogFactor = clamp( fogFactor, 0.0, 1.0 ); + float fogFactor = 1.0 / exp(distance * fogDensity); + fogFactor = clamp( fogFactor, 0.0, 1.0 ); - return mix(fogColor, diffuseColor, fogFactor); -} + return mix(fogColor, diffuseColor, fogFactor); + } -vec4 getFogExpSquare(in vec4 diffuseColor, in vec4 fogColor, in float fogDensity, in float distance) { + vec4 getFogExpSquare(in vec4 diffuseColor, in vec4 fogColor, in float fogDensity, in float distance) { - float fogFactor = 1.0 / exp( (distance * fogDensity) * (distance * fogDensity)); - fogFactor = clamp( fogFactor, 0.0, 1.0 ); + float fogFactor = 1.0 / exp( (distance * fogDensity) * (distance * fogDensity)); + fogFactor = clamp( fogFactor, 0.0, 1.0 ); - vec4 finalColor = mix(fogColor, diffuseColor, fogFactor); - return finalColor; -} \ No newline at end of file + vec4 finalColor = mix(fogColor, diffuseColor, fogFactor); + return finalColor; + } + + #if defined(USE_FOG) + uniform vec4 m_FogColor; + varying float fogDistance; + + uniform vec2 m_LinearFog; + + #ifdef FOG_EXP + uniform float m_ExpFog; + #endif + + #ifdef FOG_EXPSQ + uniform float m_ExpSqFog; + #endif + + vec4 MaterialFog_calculateFogColor(in vec4 fragColor){ + #ifdef FOG_LINEAR + fragColor = getFogLinear(fragColor, m_FogColor, m_LinearFog.x, m_LinearFog.y, fogDistance); + #endif + #ifdef FOG_EXP + fragColor = getFogExp(fragColor, m_FogColor, m_ExpFog, fogDistance); + #endif + #ifdef FOG_EXPSQ + fragColor = getFogExpSquare(fragColor, m_FogColor, m_ExpSqFog, fogDistance); + #endif + + return fragColor; + } + + #endif +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib index 6f4cc9074f..d97132fbd2 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Math.glsllib @@ -1,4 +1,17 @@ +#ifndef __MATH_GLSLLIB__ +#define __MATH_GLSLLIB__ + /// Multiplies the vector by the quaternion, then returns the resultant vector. vec3 Math_QuaternionMult(in vec4 quat, in vec3 vec){ return vec + 2.0 * cross(quat.xyz, cross(quat.xyz, vec) + quat.w * vec); -} \ No newline at end of file +} + +void Math_lengthAndNormalize(in vec3 vec,out float outLength,out vec3 outNormal){ + float dotv=dot(vec,vec); + float invl=inversesqrt(dotv); + outNormal=vec*invl; + outLength=invl*dotv; +} + + +#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib index eea4100ba1..6108ddd1a3 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/MorphAnim.glsllib @@ -105,7 +105,7 @@ Note that it only handles morphing position, normals and tangents. void Morph_Compute_Pos_Norm(inout vec4 pos, inout vec3 norm){ #if (NUM_TARGETS_BUFFERS == 2) - // weight sum may be over 1.0. It's totallyvalid for position + // weight sum may be over 1.0. It's totally valid for position // but for normals. the weights needs to be normalized. float invWeightsSum = Get_Inverse_Weights_Sum(); #if (NUM_BUFFERS > 1) @@ -141,7 +141,7 @@ Note that it only handles morphing position, normals and tangents. void Morph_Compute_Pos_Norm_Tan(inout vec4 pos, inout vec3 norm, inout vec3 tan){ #if (NUM_TARGETS_BUFFERS == 3) - // weight sum may be over 1.0. It's totallyvalid for position + // weight sum may be over 1.0. It's totally valid for position // but for normals. the weights needs to be normalized. float invWeightsSum = Get_Inverse_Weights_Sum(); #if (NUM_BUFFERS > 2) diff --git a/jme3-core/src/main/resources/Common/ShaderLib/NoiseLib.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/NoiseLib.glsllib new file mode 100644 index 0000000000..db58f51bd4 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/NoiseLib.glsllib @@ -0,0 +1,112 @@ +#ifndef __NOISE_UTILS_MODULE__ + #define __NOISE_UTILS_MODULE__ + + //2d noise functions + float rand(float n){return fract(sin(n) * 43758.5453123);} + float rand(vec2 n) { + return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); + } + + float noise(vec2 n) { + const vec2 d = vec2(0.0, 1.0); + vec2 b = floor(n), f = smoothstep(vec2(0.0), vec2(1.0), fract(n)); + return mix(mix(rand(b), rand(b + d.yx), f.x), mix(rand(b + d.xy), rand(b + d.yy), f.x), f.y); + } + + + float prand(vec2 c){ + return fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453); + } + + float pnoise(vec2 p, float freqPct){ + //float unit = circ/freq; + float unit = freqPct; + + vec2 ij = floor(p/unit); + vec2 xy = mod(p,unit)/unit; + //xy = 3.*xy*xy-2.*xy*xy*xy; + xy = .5*(1.-cos(3.1415926535*xy)); + float a = prand((ij+vec2(0.,0.))); + float b = prand((ij+vec2(1.,0.))); + float c = prand((ij+vec2(0.,1.))); + float d = prand((ij+vec2(1.,1.))); + float x1 = mix(a, b, xy.x); + float x2 = mix(c, d, xy.x); + return mix(x1, x2, xy.y); + } + + float rand2D(in vec2 co){ + return fract(sin(dot(co.xy ,vec2(12.9898,78.233))) * 43758.5453); + } + + // - - - - + + //3d noise functions + float rand3D(in vec3 co){ + return fract(sin(dot(co.xyz ,vec3(12.9898,78.233,144.7272))) * 43758.5453); + } + + float simple_interpolate(in float a, in float b, in float x) + { + return a + smoothstep(0.0,1.0,x) * (b-a); + } + float interpolatedNoise3D(in float x, in float y, in float z) + { + float integer_x = x - fract(x); + float fractional_x = x - integer_x; + + float integer_y = y - fract(y); + float fractional_y = y - integer_y; + + float integer_z = z - fract(z); + float fractional_z = z - integer_z; + + float v1 = rand3D(vec3(integer_x, integer_y, integer_z)); + float v2 = rand3D(vec3(integer_x+1.0, integer_y, integer_z)); + float v3 = rand3D(vec3(integer_x, integer_y+1.0, integer_z)); + float v4 = rand3D(vec3(integer_x+1.0, integer_y +1.0, integer_z)); + + float v5 = rand3D(vec3(integer_x, integer_y, integer_z+1.0)); + float v6 = rand3D(vec3(integer_x+1.0, integer_y, integer_z+1.0)); + float v7 = rand3D(vec3(integer_x, integer_y+1.0, integer_z+1.0)); + float v8 = rand3D(vec3(integer_x+1.0, integer_y +1.0, integer_z+1.0)); + + float i1 = simple_interpolate(v1,v5, fractional_z); + float i2 = simple_interpolate(v2,v6, fractional_z); + float i3 = simple_interpolate(v3,v7, fractional_z); + float i4 = simple_interpolate(v4,v8, fractional_z); + + float ii1 = simple_interpolate(i1,i2,fractional_x); + float ii2 = simple_interpolate(i3,i4,fractional_x); + + return simple_interpolate(ii1 , ii2 , fractional_y); + } + + float Noise3D(in vec3 coord, in float wavelength) + { + return interpolatedNoise3D(coord.x/wavelength, coord.y/wavelength, coord.z/wavelength); + } + + + //used to reference the same float generated by noise for all shaders, so afflictionness appears to spread and splat naturally + float getStaticNoiseVar0(vec3 wPos, float afflictionVar){ + float noiseVar0 = Noise3D(wPos, 28); + float noiseVar1 = Noise3D(wPos, 3); + float noiseVar2 = Noise3D(wPos, 0.8); + + float noiseVar = ((noiseVar0 + noiseVar1) * 0.4) + (noiseVar2 * 0.2 * afflictionVar); + + + if(noiseVar > 0.7){ + noiseVar -= noiseVar2 * 0.07; + // noiseVar = min(noiseVar, 0.3); + + } + + noiseVar = min(noiseVar, 1.0); + noiseVar = max(noiseVar, 0.0); + + return noiseVar; + } + +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib index ee4edac8ee..f6254da82e 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/PBR.glsllib @@ -2,6 +2,25 @@ #ifndef PI #define PI 3.14159265358979323846264 #endif +#ifndef NB_PROBES + #define NB_PROBES 0 +#endif + +// BEGIN-@jhonkkk,Specular AA -------------------------------------------------------------- +// see:http://www.jp.square-enix.com/tech/library/pdf/ImprovedGeometricSpecularAA(slides).pdf +// https://docs.unity3d.com/Packages/com.unity.render-pipelines.high-definition@15.0/manual/Geometric-Specular-Anti-Aliasing.html +float Specular_Anti_Aliasing(in vec3 normal, in vec3 half_vector, + float alpha, in float sigma, in float kappa) +{ + + vec3 dndu = dFdx(normal); + vec3 dndv = dFdy(normal); + float variance = sigma*sigma*(dot(dndu, dndu) + dot(dndv, dndv)); + float kernel_roughness = min(kappa, variance); + return sqrt(alpha*alpha + kernel_roughness); +} +// END-@jhonkkk + //Specular fresnel computation vec3 F_Shlick(float vh, vec3 F0){ @@ -31,12 +50,11 @@ vec3 sphericalHarmonics( const in vec3 normal, const vec3 sph[9] ){ return max(result, vec3(0.0)); } - -float PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, - vec3 lightColor, vec3 fZero, float roughness, float ndotv, +// BEGIN-@jhonkkk,todo:Keeping the original PBR_ComputeDirectLight function signature is for backwards compatibility, but this adds an extra internal function call, theoretically here we should use a macro to define Inner_PBR_ComputeDirectLight, or best to calculate alpha externally and directly call the Inner_PBR_ComputeDirectLight function. +float Inner_PBR_ComputeDirectLight( + vec3 normal, vec3 halfVec, vec3 lightDir, vec3 viewDir, + vec3 lightColor, vec3 fZero, float alpha, float ndotv, out vec3 outDiffuse, out vec3 outSpecular){ - // Compute halfway vector. - vec3 halfVec = normalize(lightDir + viewDir); // Compute ndotl, ndoth, vdoth terms which are needed later. float ndotl = max( dot(normal, lightDir), 0.0); @@ -50,9 +68,7 @@ float PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, //cook-torrence, microfacet BRDF : http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf - float alpha = roughness * roughness; - - //D, GGX normaal Distribution function + //D, GGX normal Distribution function float alpha2 = alpha * alpha; float sum = ((ndoth * ndoth) * (alpha2 - 1.0) + 1.0); float denom = PI * sum * sum; @@ -61,7 +77,7 @@ float PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, // Compute Fresnel function via Schlick's approximation. vec3 fresnel = F_Shlick(hdotv, fZero); - //G Shchlick GGX Gometry shadowing term, k = alpha/2 + //G Schlick GGX Geometry shadowing term, k = alpha/2 float k = alpha * 0.5; /* @@ -73,7 +89,7 @@ float PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, float specular =(D* fresnel * G) /(4 * ndotv); */ - // UE4 way to optimise shlick GGX Gometry shadowing term + // UE4 way to optimise Schlick GGX Geometry shadowing term //http://graphicrants.blogspot.co.uk/2013/08/specular-brdf-reference.html float G_V = ndotv + sqrt( (ndotv - ndotv * k) * ndotv + k ); float G_L = ndotl + sqrt( (ndotl - ndotl * k) * ndotl + k ); @@ -86,6 +102,31 @@ float PBR_ComputeDirectLight(vec3 normal, vec3 lightDir, vec3 viewDir, return hdotv; } +float PBR_ComputeDirectLight( + vec3 normal, vec3 lightDir, vec3 viewDir, + vec3 lightColor, vec3 fZero, float roughness, float ndotv, + out vec3 outDiffuse, out vec3 outSpecular){ + // Compute halfway vector. + vec3 halfVec = normalize(lightDir + viewDir); + return Inner_PBR_ComputeDirectLight(normal, halfVec, lightDir, viewDir, + lightColor, fZero, roughness * roughness, ndotv, + outDiffuse, outSpecular); +} + +float PBR_ComputeDirectLightWithSpecularAA( + vec3 normal, vec3 lightDir, vec3 viewDir, + vec3 lightColor, vec3 fZero, float roughness, float sigma, float kappa, float ndotv, + out vec3 outDiffuse, out vec3 outSpecular){ + // Compute halfway vector. + vec3 halfVec = normalize(lightDir + viewDir); + // Specular-AA + float alpha = Specular_Anti_Aliasing(normal, halfVec, roughness * roughness, sigma, kappa); + return Inner_PBR_ComputeDirectLight(normal, halfVec, lightDir, viewDir, + lightColor, fZero, alpha, ndotv, + outDiffuse, outSpecular); +} +// END-@jhonkkk + vec3 integrateBRDFApprox( const in vec3 specular, float roughness, float NoV ){ const vec4 c0 = vec4( -1, -0.0275, -0.572, 0.022 ); const vec4 c1 = vec4( 1, 0.0425, 1.04, -0.04 ); @@ -143,8 +184,9 @@ float renderProbe(vec3 viewDir, vec3 worldPos, vec3 normal, vec3 norm, float Rou if(lightProbeData[0][3] != 0.0){ // oriented box probe - mat3 wToLocalRot = mat3(lightProbeData); - wToLocalRot = inverse(wToLocalRot); + // mat3_sub our compat wrapper for mat3(mat4) + mat3 wToLocalRot = inverse(mat3_sub(lightProbeData)); + vec3 scale = vec3(lightProbeData[0][3], lightProbeData[1][3], lightProbeData[2][3]); #if NB_PROBES >= 2 // probe blending @@ -175,7 +217,7 @@ float renderProbe(vec3 viewDir, vec3 worldPos, vec3 normal, vec3 norm, float Rou } else { // spherical probe - // paralax fix + // parallax fix rv = invRadius * direction + rv; #if NB_PROBES >= 2 diff --git a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib index eb3380ce1f..2910679a47 100644 --- a/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib +++ b/jme3-core/src/main/resources/Common/ShaderLib/Shadows.glsllib @@ -1,6 +1,6 @@ #if __VERSION__ >= 130 // Because gpu_shader5 is actually where those - // gather functions are declared to work on shadowmaps + // gather functions are declared to work on shadow maps // This "if" statement is useless as jme3 changes line ordering, so all extensions are tried to be loaded #ifdef GL_ES #extension GL_OES_gpu_shader5 : enable @@ -177,8 +177,7 @@ float Shadow_DoPCF(in SHADOWMAP tex,in vec4 projCoord){ if (border > 0.0) return 1.0; - float bound = KERNEL * 0.5 - 0.5; - bound *= PCFEDGE; + const float bound = (KERNEL * 0.5 - 0.5 ) * PCFEDGE; for (float y = -bound; y <= bound; y += PCFEDGE){ for (float x = -bound; x <= bound; x += PCFEDGE){ #if __VERSION__ < 130 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/main/resources/Common/ShaderLib/TangentUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/TangentUtils.glsllib new file mode 100644 index 0000000000..477371155c --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/TangentUtils.glsllib @@ -0,0 +1,14 @@ +#ifndef __TANGENT_UTILS_MODULE__ + #define __TANGENT_UTILS_MODULE__ + + //used for calculating tangents in-shader for axis-aligned quads/planes/terrains + //primarily used for PBR terrains, since jme terrains do not store pre-calculated tangents by default (thus the tbnMat cannot be used for PBR light calculation like it is in jme's stock PBR shader) + vec3 calculateTangentsAndApplyToNormals(in vec3 normalIn, in vec3 worldNorm){ + vec3 baseNorm = worldNorm.rgb + vec3(0.0, 0.0, 1.0); + normalIn *= vec3(-1.0, 1.0, 1.0); + normalIn = baseNorm.rgb*dot(baseNorm.rgb, normalIn.rgb)/baseNorm.z - normalIn.rgb; + normalIn = normalize(normalIn); + + return normalIn; + } +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/TriPlanarUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/TriPlanarUtils.glsllib new file mode 100644 index 0000000000..8134200ae3 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/TriPlanarUtils.glsllib @@ -0,0 +1,72 @@ +#ifndef __TRIPLANAR_UTILS_MODULE__ + #define __TRIPLANAR_UTILS_MODULE__ + + #ifndef NORMAL_TYPE + #define NORMAL_TYPE -1.0 + #endif + + vec3 triBlending; + + void TriPlanarUtils_calculateBlending(vec3 geometryNormal){ + triBlending = abs( geometryNormal ); + triBlending = (triBlending -0.2) * 0.7; + triBlending = normalize(max(triBlending, 0.00001)); // Force weights to sum to 1.0 (very important!) + float b = (triBlending.x + triBlending.y + triBlending.z); + triBlending /= vec3(b, b, b); + } + + // basic triplanar blend: + vec4 getTriPlanarBlend(in vec3 coords, in sampler2D map, in float scale) { + vec4 col1 = texture2D( map, coords.yz * scale); + vec4 col2 = texture2D( map, coords.xz * scale); + vec4 col3 = texture2D( map, coords.xy * scale); + // blend the results of the 3 planar projections. + vec4 tex = col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z; + + return tex; + } + + // basic triplanar blend for TextureArrays: + vec4 getTriPlanarBlendFromTexArray(in vec3 coords, 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 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z; + + return tex; + } + + // triplanar blend for Normal maps: + vec4 getTriPlanarNormalBlend(in vec3 coords, in sampler2D map, in float scale) { + vec4 col1 = texture2D( map, coords.yz * scale); + vec4 col2 = texture2D( map, coords.xz * scale); + vec4 col3 = texture2D( map, coords.xy * scale); + + col1.xyz = col1.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + col2.xyz = col2.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + col3.xyz = col3.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + + // blend the results of the 3 planar projections. + vec4 tex = normalize(col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z); + + return tex; + } + + // triplanar blend for Normal maps in a TextureArray: + vec4 getTriPlanarNormalBlendFromTexArray(in vec3 coords, 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 )); + + col1.xyz = col1.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + col2.xyz = col2.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + col3.xyz = col3.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0); + + // blend the results of the 3 planar projections. + vec4 tex = normalize(col1 * triBlending.x + col2 * triBlending.y + col3 * triBlending.z); + + return tex; + } + +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/Light.glsl b/jme3-core/src/main/resources/Common/ShaderLib/module/Light.glsl new file mode 100644 index 0000000000..1021e83772 --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/Light.glsl @@ -0,0 +1,34 @@ +#ifndef __LIGHT_MODULE__ +#define __LIGHT_MODULE__ + +/** +* Defines a light +*/ + + +#ifndef Light + #struct StdLight + vec4 color; + vec3 position; + float type; + + float invRadius; + float spotAngleCos; + vec3 spotDirection; + + bool ready; + + float NdotL; // cos angle between normal and light direction + float NdotH; // cos angle between normal and half vector + float LdotH; // cos angle between light direction and half vector + float HdotV; // cos angle between view direction and half vector + vec3 vector; + vec3 dir; + float fallOff; + #endstruct + #define Light StdLight +#endif + + + +#endif \ No newline at end of file diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/PBRSurface.glsl b/jme3-core/src/main/resources/Common/ShaderLib/module/PBRSurface.glsl new file mode 100644 index 0000000000..9ccc6125ce --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/PBRSurface.glsl @@ -0,0 +1,44 @@ +#ifndef __SURFACE_MODULE__ +#define __SURFACE_MODULE__ + +#ifndef PBRSurface + #struct StdPBRSurface + // from geometry + vec3 position; // position in w space + vec3 viewDir; // view dir in worldSpace + vec3 geometryNormal; // normals w/o normalmap + vec3 normal; // normals w/ normalmap + bool frontFacing; //gl_FrontFacing + float depth; + mat3 tbnMat; + bool hasTangents; + + // from texture/param reads + vec3 albedo; + float alpha; + float metallic; // metallic value at the surface + float roughness; + vec3 ao; + vec3 lightMapColor; + bool hasBasicLightMap; + float exposure; + vec3 emission; + + //from post param-read calculations + vec3 diffuseColor; + vec3 specularColor; + vec3 fZero; + + // computed + float NdotV; + + // from env + vec3 bakedLightContribution; // light from light map or other baked sources + vec3 directLightContribution; // light from direct light sources + vec3 envLightContribution; // light from environment + + float brightestNonGlobalLightStrength; + #endstruct + #define PBRSurface StdPBRSurface +#endif +#endif diff --git a/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib new file mode 100644 index 0000000000..bf565b61fe --- /dev/null +++ b/jme3-core/src/main/resources/Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib @@ -0,0 +1,714 @@ +#ifndef __PBR_LIGHT_UTILS_MODULE__ + #define __PBR_LIGHT_UTILS_MODULE__ + + #import "Common/ShaderLib/Math.glsllib" + #import "Common/ShaderLib/PBR.glsllib" + #import "Common/ShaderLib/Parallax.glsllib" + + #import "Common/ShaderLib/module/Light.glsl" + #import "Common/ShaderLib/module/PBRSurface.glsl" + + // enable all apis + // #define ENABLE_PBRLightingUtils_getWorldPosition 1 + // #define ENABLE_PBRLightingUtils_getLocalPosition 1 + // #define ENABLE_PBRLightingUtils_getWorldNormal 1 + // #define ENABLE_PBRLightingUtils_getWorldTangent 1 + // #define ENABLE_PBRLightingUtils_getTexCoord 1 + // #define ENABLE_PBRLightingUtils_newLight 1 + // #define ENABLE_PBRLightingUtils_computeLightInWorldSpace 1 + // #define ENABLE_PBRLightingUtils_readPBRSurface 1 + // #define ENABLE_PBRLightingUtils_computeDirectLight 1 + // #define ENABLE_PBRLightingUtils_computeDirectLightContribution 1 + // #define ENABLE_PBRLightingUtils_computeProbesContribution 1 + + #if defined(ENABLE_PBRLightingUtils_readPBRSurface)||defined(ENABLE_PBRLightingUtils_getWorldPosition) + varying vec3 wPosition; + #endif + + #if defined(ENABLE_PBRLightingUtils_readPBRSurface)||defined(ENABLE_PBRLightingUtils_getWorldNormal) + varying vec3 wNormal; + #endif + + #if (defined(ENABLE_PBRLightingUtils_readPBRSurface)&&(defined(NORMALMAP)||defined(PARALLAXMAP)))||defined(ENABLE_PBRLightingUtils_getWorldTangent) + varying vec4 wTangent; + #endif + + #if defined(ENABLE_PBRLightingUtils_readPBRSurface)||defined(ENABLE_PBRLightingUtils_getTexCoord) + varying vec2 texCoord; + #ifdef SEPARATE_TEXCOORD + varying vec2 texCoord2; + #endif + #endif + + #if defined(ENABLE_PBRLightingUtils_getLocalPosition) + varying vec3 lPosition; + #endif + + + + #ifdef ENABLE_PBRLightingUtils_readPBRSurface + varying vec4 Color; + + uniform float m_Roughness; + uniform float m_Metallic; + + + #ifdef BASECOLORMAP + uniform sampler2D m_BaseColorMap; + #endif + + #ifdef USE_PACKED_MR + uniform sampler2D m_MetallicRoughnessMap; + #else + #ifdef METALLICMAP + uniform sampler2D m_MetallicMap; + #endif + #ifdef ROUGHNESSMAP + uniform sampler2D m_RoughnessMap; + #endif + #endif + + #ifdef EMISSIVE + uniform vec4 m_Emissive; + #endif + #ifdef EMISSIVEMAP + uniform sampler2D m_EmissiveMap; + #endif + #if defined(EMISSIVE) || defined(EMISSIVEMAP) + uniform float m_EmissivePower; + uniform float m_EmissiveIntensity; + #endif + + + + #ifdef SPECGLOSSPIPELINE + uniform vec4 m_Specular; + uniform float m_Glossiness; + #ifdef USE_PACKED_SG + uniform sampler2D m_SpecularGlossinessMap; + #else + uniform sampler2D m_SpecularMap; + uniform sampler2D m_GlossinessMap; + #endif + #endif + + #ifdef PARALLAXMAP + uniform sampler2D m_ParallaxMap; + #endif + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) + uniform float m_ParallaxHeight; + #endif + + #ifdef LIGHTMAP + uniform sampler2D m_LightMap; + #endif + + #ifdef AO_STRENGTH + uniform float m_AoStrength; + #endif + + #if defined(NORMALMAP) || defined(PARALLAXMAP) + uniform sampler2D m_NormalMap; + #endif + #ifdef NORMALSCALE + uniform float m_NormalScale; + #endif + + #ifdef DISCARD_ALPHA + uniform float m_AlphaDiscardThreshold; + #endif + #endif + + #if defined(ENABLE_PBRLightingUtils_computeDirectLight) || defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) + + // Specular-AA + #ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE + uniform float m_SpecularAASigma; + #endif + #ifdef SPECULAR_AA_THRESHOLD + uniform float m_SpecularAAKappa; + #endif + + // DirectionalLight Exposure + #if defined(EXPOSUREMAP) + uniform sampler2D m_SunLightExposureMap; + #endif + #if defined(USE_VERTEX_COLORS_AS_SUN_EXPOSURE) + varying vec4 vertColors; + #endif + #ifdef STATIC_SUN_EXPOSURE + uniform float m_StaticSunIntensity; + #endif + + void PBRLightingUtils_readSunLightExposureParams(inout PBRSurface surface){ + + surface.exposure = 1.0; //default value + #ifdef EXPOSUREMAP + surface.exposure *= texture2D(m_SunLightExposureMap, newTexCoord).r; + #endif + #ifdef STATIC_SUN_EXPOSURE + surface.exposure *= m_StaticSunIntensity; //single float value to indicate percentage of sunlight hitting the model (only suitable for small models or models with equal sunlight exposure accross the entire model + #endif + #ifdef USE_VERTEX_COLORS_AS_SUN_EXPOSURE + surface.exposure *= vertColors.r; // use red channel of vertexColors for non-uniform sunlighting accross a single model + #endif + } + + #endif + + + + #ifdef ENABLE_PBRLightingUtils_computeProbesContribution + #if NB_PROBES >= 1 + 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; + #endif + #if NB_PROBES == 3 + uniform samplerCube g_PrefEnvMap3; + uniform vec3 g_ShCoeffs3[9]; + uniform mat4 g_LightProbeData3; + #endif + #endif + + #ifdef ENABLE_PBRLightingUtils_getWorldPosition + vec3 PBRLightingUtils_getWorldPosition(){ + return wPosition.xyz; + } + #endif + + #ifdef ENABLE_PBRLightingUtils_getWorldNormal + vec3 PBRLightingUtils_getWorldNormal(){ + return normalize(wNormal.xyz); + } + #endif + + #ifdef ENABLE_PBRLightingUtils_getWorldTangent + vec4 PBRLightingUtils_getWorldTangent(){ + return wTangent; + } + #endif + + #ifdef ENABLE_PBRLightingUtils_getTexCoord + vec2 PBRLightingUtils_getTexCoord(){ + return texCoord; + } + + #ifdef SEPARATE_TEXCOORD + vec2 PBRLightingUtils_getTexCoord2(){ + return texCoord2; + } + #endif + #endif + + + + #if defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_newLight) + Light PBRLightingUtils_newLight(vec4 color, vec3 position, float type, float invRadius, float spotAngleCos, vec3 spotDirection){ + Light l; + l.color = color; + l.position = position; + l.type = type; + l.invRadius = invRadius; + l.spotAngleCos = spotAngleCos; + l.spotDirection = spotDirection; + l.ready = false; + return l; + } + #endif + + + #if defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) || defined(ENABLE_PBRLightingUtils_computeLightInWorldSpace) + void PBRLightingUtils_computeLightInWorldSpace(vec3 worldPos,vec3 worldNormal, vec3 viewDir, inout Light light){ + if(light.ready) return; + + // lightComputeDir + float posLight = step(0.5, light.type); + light.vector = light.position.xyz * sign(posLight - 0.5) - (worldPos * posLight); //tempVec lightVec + + vec3 L; // lightDir + float dist; + Math_lengthAndNormalize(light.vector,dist,L); + + float invRange=light.invRadius; // position.w + const float light_threshold = 0.01; + + #ifdef SRGB + light.fallOff = (1.0 - invRange * dist) / (1.0 + invRange * dist * dist); // lightDir.w + light.fallOff = clamp(light.fallOff, 1.0 - posLight, 1.0); + #else + light.fallOff = clamp(1.0 - invRange * dist * posLight, 0.0, 1.0); + #endif + + // computeSpotFalloff + if(light.type>1.){ + vec3 spotdir = normalize(light.spotDirection); + float curAngleCos = dot(-L, spotdir); + float innerAngleCos = floor(light.spotAngleCos) * 0.001; + float outerAngleCos = fract(light.spotAngleCos); + float innerMinusOuter = innerAngleCos - outerAngleCos; + float falloff = clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0); + #ifdef SRGB + // Use quadratic falloff (notice the ^4) + falloff = pow(clamp((curAngleCos - outerAngleCos) / innerMinusOuter, 0.0, 1.0), 4.0); + #endif + light.fallOff*=falloff; + } + + + vec3 h=normalize(L+viewDir); + light.dir=L; + light.NdotL = max(dot(worldNormal, L), 0.0); + light.NdotH = max(dot(worldNormal, h), 0.0); + light.LdotH = max(dot(L, h), 0.0); + light.HdotV = max(dot(viewDir,h), 0.); + } + #endif + + PBRSurface PBRLightingUtils_createPBRSurface(in vec3 wViewDir){ + + PBRSurface surface; //creates a new PBRSurface + + surface.position = wPosition; + surface.viewDir = wViewDir; + surface.geometryNormal = normalize(wNormal); + + //set default values + surface.hasTangents = false; + surface.hasBasicLightMap = false; + surface.albedo = vec3(1.0); + surface.normal = surface.geometryNormal; + surface.emission = vec3(0.0); + surface.ao = vec3(1.0); + surface.lightMapColor = vec3(0.0); + surface.alpha = 1.0; + surface.roughness = 1.0; + surface.metallic = 0.0; + surface.alpha = 1.0; + surface.exposure = 1.0; + + return surface; + } + + #ifdef ENABLE_PBRLightingUtils_readPBRSurface + vec2 newTexCoord; + + void PBRLightingUtils_readPBRSurface(inout PBRSurface surface){ + + surface.bakedLightContribution = vec3(0.0); + surface.directLightContribution = vec3(0.0); + surface.envLightContribution = vec3(0.0); + + #ifdef ENABLE_PBRLightingUtils_getWorldTangent + vec3 tan = normalize(wTangent.xyz); + surface.tbnMat = mat3(tan, wTangent.w * cross( surface.geometryNormal, tan), surface.geometryNormal); + surface.hasTangents = true; + #endif + + + #if (defined(PARALLAXMAP) || (defined(NORMALMAP_PARALLAX) && defined(NORMALMAP))) + vec3 vViewDir = surface.viewDir * surface.tbnMat; + #ifdef STEEP_PARALLAX + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = steepParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = steepParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #else + #ifdef NORMALMAP_PARALLAX + //parallax map is stored in the alpha channel of the normal map + newTexCoord = classicParallaxOffset(m_NormalMap, vViewDir, texCoord, m_ParallaxHeight); + #else + //parallax map is a texture + newTexCoord = classicParallaxOffset(m_ParallaxMap, vViewDir, texCoord, m_ParallaxHeight); + #endif + #endif + #else + newTexCoord = texCoord; + #endif + + #ifdef BASECOLORMAP + vec4 baseColor = texture2D(m_BaseColorMap, newTexCoord) * Color; + #else + vec4 baseColor = Color; + #endif + + #ifdef DISCARD_ALPHA + if( baseColor.a < m_AlphaDiscardThreshold) discard; + #endif + + surface.albedo = baseColor.rgb; + surface.alpha = baseColor.a; + + //ao in r channel, roughness in green channel, metallic in blue channel! + vec3 aoRoughnessMetallicValue = vec3(1.0, 1.0, 0.0); + #ifdef USE_PACKED_MR + aoRoughnessMetallicValue = texture2D(m_MetallicRoughnessMap, newTexCoord).rgb; + surface.roughness = aoRoughnessMetallicValue.g * max(m_Roughness, 1e-4); + surface.metallic = aoRoughnessMetallicValue.b * max(m_Metallic, 0.0); + #else + #ifdef ROUGHNESSMAP + surface.roughness = texture2D(m_RoughnessMap, newTexCoord).r * max(m_Roughness, 1e-4); + #else + surface.roughness = max(m_Roughness, 1e-4); + #endif + #ifdef METALLICMAP + surface.metallic = texture2D(m_MetallicMap, newTexCoord).r * max(m_Metallic, 0.0); + #else + surface.metallic = max(m_Metallic, 0.0); + #endif + #endif + + + + #if defined(NORMALMAP) + vec4 normalHeight = texture2D(m_NormalMap, newTexCoord); + // Note we invert directx style normal maps to opengl style + #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 + surface.normal = normalize(surface.tbnMat * normal); + #else + surface.normal = surface.geometryNormal; + #endif + + //spec gloss tex reads: + + #ifdef SPECGLOSSPIPELINE + float glossiness = m_Glossiness; + #ifdef USE_PACKED_SG + vec4 specularColor = texture2D(m_SpecularGlossinessMap, newTexCoord); + glossiness *= specularColor.a; + #else + #ifdef SPECULARMAP + vec4 specularColor = texture2D(m_SpecularMap, newTexCoord); + #else + vec4 specularColor = vec4(1.0); + #endif + #ifdef GLOSSINESSMAP + glossiness *= texture2D(m_GlossinessMap, newTexCoord).r; + #endif + #endif + specularColor *= m_Specular; + surface.specularColor = specularColor.rgb; + surface.roughness = 1.0 - glossiness; + #endif + + vec3 ao = vec3(1.0); + #ifdef LIGHTMAP + vec3 lightMapColor; + #ifdef SEPARATE_TEXCOORD + lightMapColor = texture2D(m_LightMap, texCoord2).rgb; + #else + lightMapColor = texture2D(m_LightMap, texCoord).rgb; + #endif + #ifdef AO_MAP + ao = lightMapColor.rrr; + #else + surface.lightMapColor = lightMapColor; + surface.hasBasicLightMap = true; + #endif + #endif + + #if defined(AO_PACKED_IN_MR_MAP) && defined(USE_PACKED_MR) + ao = aoRoughnessMetallicValue.rrr; //note that this will override the AO value if it was previously read from a lightMap that is being used as AO_Map above. so don't try to use an AO map packed in metallic roughness while also using lightmap as ao map + #endif + + #ifdef AO_STRENGTH + ao = 1.0 + m_AoStrength * (ao - 1.0); + // sanity check + ao = clamp(ao, 0.0, 1.0); + #endif + surface.ao = ao; + + #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 + surface.emission = emissive.rgb * pow(emissive.a, m_EmissivePower) * m_EmissiveIntensity; + #else + surface.emission = vec3(0.0); + #endif + + PBRLightingUtils_readSunLightExposureParams(surface); + + surface.frontFacing = gl_FrontFacing; + surface.depth = gl_FragCoord.z; + + + // surface.alphaRoughness = clamp(surface.roughness * surface.roughness, minRoughness, 1.0); + surface.NdotV = clamp(abs(dot(!surface.frontFacing?-surface.normal:surface.normal, surface.viewDir)), 0.001, 1.0); + // surface.reflectedVec = normalize(reflect(-surface.viewDir, surface.normal)); + + surface.brightestNonGlobalLightStrength = 0.0; + } + #endif + + + #if defined(ENABLE_PBRLightingUtils_computeDirectLight) || defined(ENABLE_PBRLightingUtils_computeDirectLightContribution) + + + void PBRLightingUtils_calculatePreLightingValues(inout PBRSurface surface){ + + if(surface.hasBasicLightMap == true){ + surface.bakedLightContribution += surface.diffuseColor.rgb * surface.lightMapColor; + surface.specularColor.rgb *= surface.lightMapColor; + } + surface.specularColor.rgb *= surface.ao; + + #ifdef SPECGLOSSPIPELINE + surface.diffuseColor = surface.albedo;// * (1.0 - max(max(specularColor.r, specularColor.g), specularColor.b)); + surface.fZero = surface.specularColor.xyz; + #else + surface.specularColor = (0.04 - 0.04 * surface.metallic) + surface.albedo * surface.metallic; // 0.04 is the standard base specular reflectance for non-metallic surfaces in PBR. While values like 0.08 can be used for different implementations, 0.04 aligns with Khronos' PBR specification. + surface.diffuseColor = surface.albedo - surface.albedo * surface.metallic; + surface.fZero = mix(vec3(0.04), surface.albedo.rgb, surface.metallic); + #endif + } + + void PBRLightingUtils_computeDirectLight(in Light light, in PBRSurface surface, inout vec3 directDiffuse, inout vec3 directSpecular, out float hdotv){ + + #ifdef SPECULAR_AA + #ifdef SPECULAR_AA_SCREEN_SPACE_VARIANCE + float sigma = m_SpecularAASigma; + #else + float sigma = 1.0; + #endif + + #ifdef SPECULAR_AA_THRESHOLD + float kappa = m_SpecularAAKappa; + #else + float kappa = 0.18; + #endif + + hdotv = PBR_ComputeDirectLightWithSpecularAA( + surface.normal, + light.dir.xyz, + surface.viewDir, + light.color.rgb, + surface.fZero, + surface.roughness, + sigma, + kappa, + surface.NdotV, + directDiffuse, + directSpecular + ); + #else + hdotv = PBR_ComputeDirectLight( + surface.normal, + light.dir.xyz, + surface.viewDir, + light.color.rgb, + surface.fZero, + surface.roughness, + surface.NdotV, + directDiffuse, + directSpecular + ); + #endif + + } + #endif + + + #ifdef ENABLE_PBRLightingUtils_computeDirectLightContribution + void PBRLightingUtils_computeDirectLightContribution( + in vec4 lightData0, + in vec4 lightData1, + in vec4 lightData2, + inout PBRSurface surface + ){ + vec4 lightColor = vec4(lightData0.rgb,1.0); + float lightType = lightData0.w; + + vec3 lightPosition = lightData1.xyz; + float lightInvRadius = lightData1.w; + + vec3 spotDirection = lightData2.xyz; + float spotAngleCos = lightData2.w; + + Light light = PBRLightingUtils_newLight(lightColor, lightPosition, lightType, lightInvRadius, spotAngleCos, spotDirection); + PBRLightingUtils_computeLightInWorldSpace(surface.position, surface.normal, surface.viewDir, light); + + + vec3 directDiffuse; + vec3 directSpecular; + float hdotv; + PBRLightingUtils_computeDirectLight(light, surface, directDiffuse, directSpecular, hdotv); + + vec3 directLighting = surface.diffuseColor.rgb * directDiffuse + directSpecular; + + if(light.fallOff == 1.0){ + directLighting.rgb *= surface.exposure;// is used to scale down how intense just the sun is indoors, and so the ambientLighting can be scaled back up indoors based on nearest pointlight intensity (ambient and direct light are 1.0 fallOff) + } + else{ + surface.brightestNonGlobalLightStrength = max(light.fallOff, surface.brightestNonGlobalLightStrength); + } + + surface.directLightContribution.rgb += directLighting * light.fallOff; + } + #endif + + #ifdef ENABLE_PBRLightingUtils_computeProbesContribution + + #ifdef USE_AMBIENT_LIGHT + uniform vec4 g_AmbientLightColor; + #endif + + void PBRLightingUtils_computeProbesContribution(inout PBRSurface surface){ + + #ifdef BRIGHTEN_INDOOR_SHADOWS + float minVertLighting = 0.0833; //enable this when using shadows, in order to brighten indoor areas (which are naturally covered from the DL shadows) so that indoor areas are not way too dark when using IndoorLighting with shadows compared to when shadows are off + #else + float minVertLighting = 0.0533; + #endif + + float finalLightingScale = surface.exposure; + finalLightingScale = max(finalLightingScale, surface.brightestNonGlobalLightStrength); + finalLightingScale = max(finalLightingScale, minVertLighting); //essentially just the vertColors.r (aka indoor light exposure) multiplied by the time of day scale. + + #if NB_PROBES >= 1 + vec3 color1 = vec3(0.0); + vec3 color2 = vec3(0.0); + vec3 color3 = vec3(0.0); + float weight1 = 1.0; + float weight2 = 0.0; + float weight3 = 0.0; + + float ndf = renderProbe( + surface.viewDir, + surface.position, + surface.normal, + surface.geometryNormal, + surface.roughness, + vec4(surface.diffuseColor, 1.0), + vec4(surface.specularColor, 1.0), + surface.NdotV, + surface.ao, + g_LightProbeData, g_ShCoeffs, g_PrefEnvMap, color1); + #if NB_PROBES >= 2 + float ndf2 = renderProbe( + surface.viewDir, + surface.position, + surface.normal, + surface.geometryNormal, + surface.roughness, + vec4(surface.diffuseColor, 1.0), + vec4(surface.specularColor, 1.0), + surface.NdotV, + surface.ao, + g_LightProbeData2, + g_ShCoeffs2, + g_PrefEnvMap2, + color2); + #endif + #if NB_PROBES == 3 + float ndf3 = renderProbe( + surface.viewDir, + surface.position, + surface.normal, + surface.geometryNormal, + surface.roughness, + vec4(surface.diffuseColor, 1.0), + vec4(surface.specularColor, 1.0), + surface.NdotV, + surface.ao, + g_LightProbeData3, + g_ShCoeffs3, + g_PrefEnvMap3, + color3); + #endif + + #if NB_PROBES >= 2 + float invNdf = max(1.0 - ndf,0.0); + float invNdf2 = max(1.0 - ndf2,0.0); + float sumNdf = ndf + ndf2; + float sumInvNdf = invNdf + invNdf2; + #if NB_PROBES == 3 + float invNdf3 = max(1.0 - ndf3,0.0); + sumNdf += ndf3; + sumInvNdf += invNdf3; + weight3 = ((1.0 - (ndf3 / sumNdf)) / (NB_PROBES - 1)) * (invNdf3 / sumInvNdf); + #endif + + weight1 = ((1.0 - (ndf / sumNdf)) / (NB_PROBES - 1)) * (invNdf / sumInvNdf); + weight2 = ((1.0 - (ndf2 / sumNdf)) / (NB_PROBES - 1)) * (invNdf2 / sumInvNdf); + + float weightSum = weight1 + weight2 + weight3; + + weight1 /= weightSum; + weight2 /= weightSum; + weight3 /= weightSum; + #endif + + #ifdef USE_AMBIENT_LIGHT + color1.rgb *= g_AmbientLightColor.rgb; + color2.rgb *= g_AmbientLightColor.rgb; + color3.rgb *= g_AmbientLightColor.rgb; + #endif + + color1.rgb *= finalLightingScale; + color2.rgb *= finalLightingScale; + color3.rgb *= finalLightingScale; + + surface.envLightContribution.rgb += color1 * clamp(weight1,0.0,1.0) + color2 * clamp(weight2,0.0,1.0) + color3 * clamp(weight3,0.0,1.0); + + #endif + } + #endif + + + vec4 PBRLightingUtils_getColorOutputForDebugMode(in int debugValuesMode, in vec4 finalRenderColor, in PBRSurface surface){ + vec4 outputColorForLayer = finalRenderColor; + if(debugValuesMode == 0){ + outputColorForLayer.rgb = vec3(surface.albedo); + } + else if(debugValuesMode == 1){ + outputColorForLayer.rgb = vec3(surface.normal); + } + else if(debugValuesMode == 2){ + outputColorForLayer.rgb = vec3(surface.roughness); + } + else if(debugValuesMode == 3){ + outputColorForLayer.rgb = vec3(surface.metallic); + } + else if(debugValuesMode == 4){ + outputColorForLayer.rgb = surface.ao.rgb; + } + else if(debugValuesMode == 5){ + outputColorForLayer.rgb = vec3(surface.emission.rgb); + } + else if(debugValuesMode == 6){ + outputColorForLayer.rgb = vec3(surface.exposure); + } + else if(debugValuesMode == 7){ + outputColorForLayer.rgb = vec3(surface.alpha); + } + else if(debugValuesMode == 8){ + outputColorForLayer.rgb = vec3(surface.geometryNormal); + } + + if(debugValuesMode >= 0){ + gl_FragColor.a = 1.0; + } + + return outputColorForLayer; + } + +#endif + diff --git a/jme3-core/src/main/resources/Common/Textures/lightbulb32.png b/jme3-core/src/main/resources/Common/Textures/lightbulb32.png new file mode 100644 index 0000000000..17181a9b97 Binary files /dev/null and b/jme3-core/src/main/resources/Common/Textures/lightbulb32.png differ diff --git a/jme3-core/src/main/resources/com/jme3/asset/General.cfg b/jme3-core/src/main/resources/com/jme3/asset/General.cfg index 2b3b25d93f..a88f3a0244 100644 --- a/jme3-core/src/main/resources/com/jme3/asset/General.cfg +++ b/jme3-core/src/main/resources/com/jme3/asset/General.cfg @@ -20,8 +20,7 @@ LOADER com.jme3.scene.plugins.ogre.MeshLoader : meshxml, mesh.xml LOADER com.jme3.scene.plugins.ogre.SkeletonLoader : skeletonxml, skeleton.xml LOADER com.jme3.scene.plugins.ogre.MaterialLoader : material LOADER com.jme3.scene.plugins.ogre.SceneLoader : scene -LOADER com.jme3.scene.plugins.blender.BlenderModelLoader : blend -LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib +LOADER com.jme3.shader.plugins.GLSLLoader : vert, frag, geom, tsctrl, tseval, glsl, glsllib, comp LOADER com.jme3.scene.plugins.fbx.FbxLoader : fbx LOADER com.jme3.scene.plugins.gltf.GltfLoader : gltf LOADER com.jme3.scene.plugins.gltf.BinLoader : bin diff --git a/jme3-core/src/main/resources/joystick-mapping.properties b/jme3-core/src/main/resources/joystick-mapping.properties index 210a0ac544..810a27596a 100644 --- a/jme3-core/src/main/resources/joystick-mapping.properties +++ b/jme3-core/src/main/resources/joystick-mapping.properties @@ -1,6 +1,6 @@ # # Add compatibility entries for different joysticks -# to map button and axis arrangments when possible. +# to map button and axis arrangements when possible. # This is keyed off of the reported joystick name and # reported button or axis logical ID. The value half is # the new name as it will be reported through the Joystick @@ -249,7 +249,7 @@ DragonRise\ Inc.\ \ \ Generic\ \ \ USB\ \ Joystick.rx=z DragonRise\ Inc.\ \ \ Generic\ \ \ USB\ \ Joystick.rz=rz # from : Two dots controller as "GASIA CORP. PLAYSTATION(R)3 Controller" -# most of the button have a analog axis +# most of the button have an analog axis # two controllers are detected at the same time instead of one # some button mappings are missing (triangle, circle, cross) on linux GASIA\ CORP.\ PLAYSTATION(R)3\ Controller.8=6 diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java index 14e57790eb..2c9ede0d02 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ClasspathLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,8 @@ import com.jme3.asset.*; import com.jme3.system.JmeSystem; +import com.jme3.util.res.Resources; + import java.io.File; import java.io.IOException; import java.net.URISyntaxException; @@ -44,8 +46,7 @@ * The ClasspathLocator looks up an asset in the classpath. * * This locator is used by default in all jME3 projects (unless - * {@link AssetManager#unregisterLocator(java.lang.String, java.lang.Class) unregistered} - * ). + * {@link AssetManager#unregisterLocator(java.lang.String, java.lang.Class) unregistered}). * Unlike Java's default resource loading mechanism, the ClasspathLocator * enforces case-sensitivity on platforms which do not have it such as Windows. * Therefore, it is critical to provide a path matching the case of the file on @@ -62,6 +63,7 @@ public class ClasspathLocator implements AssetLocator { public ClasspathLocator(){ } + @Override public void setRootPath(String rootPath) { this.root = rootPath; if (root.equals("/")) @@ -75,6 +77,7 @@ else if (root.length() > 1){ } } + @Override public AssetInfo locate(AssetManager manager, AssetKey key) { URL url; String name = key.getName(); @@ -86,20 +89,10 @@ public AssetInfo locate(AssetManager manager, AssetKey key) { // name = root + name; // } - if (JmeSystem.isLowPermissions()){ - url = ClasspathLocator.class.getResource("/" + name); - }else{ - url = Thread.currentThread().getContextClassLoader().getResource(name); - } - - if (url == null) { - final List classLoaders = manager.getClassLoaders(); - for (final ClassLoader classLoader : classLoaders) { - url = classLoader.getResource(name); - if(url != null) { - break; - } - } + if (JmeSystem.isLowPermissions()) { + url = Resources.getResource("/" + name, ClasspathLocator.class); + } else { + url = Resources.getResource(name); } if (url == null) diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java index f46a975165..0e4143e046 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/FileLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,9 +44,11 @@ public class FileLocator implements AssetLocator { private File root; + @Override public void setRootPath(String rootPath) { - if (rootPath == null) - throw new NullPointerException(); + if (rootPath == null) { + throw new IllegalArgumentException("rootPath cannot be null"); + } try { root = new File(rootPath).getCanonicalFile(); @@ -60,7 +62,7 @@ public void setRootPath(String rootPath) { private static class AssetInfoFile extends AssetInfo { - private File file; + final private File file; public AssetInfoFile(AssetManager manager, AssetKey key, File file){ super(manager, key); @@ -79,6 +81,7 @@ public InputStream openStream() { } } + @Override public AssetInfo locate(AssetManager manager, AssetKey key) { String name = key.getName(); File file = new File(root, name); 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 7edda53e4a..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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -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(){ @@ -107,12 +106,12 @@ public String toString(){ } private static int get16(byte[] b, int off) { - return (b[off++] & 0xff) | + return (b[off++] & 0xff) | ((b[off] & 0xff) << 8); } private static int get32(byte[] b, int off) { - return (b[off++] & 0xff) | + return (b[off++] & 0xff) | ((b[off++] & 0xff) << 8) | ((b[off++] & 0xff) << 16) | ((b[off] & 0xff) << 24); @@ -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,12 +142,12 @@ 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 its an error + // If the result is not an underflow, it's an error // that cannot be handled. - // if the error is an underflow and its the end of input - // then the decoder expects more bytes but there are no more => error + // If the error is an underflow and it's the end of input, + // then the decoder expects more bytes, but there are no more => error. if (!result.isUnderflow() || !endOfInput){ result.throwException(); } @@ -221,7 +220,7 @@ private int readTableEntry(byte[] table, int offset) throws IOException{ String name = getUTF8String(table, offset + ZipEntry.CENHDR, nameLen); if (name.charAt(name.length()-1) == '/'){ - // ignore this entry, it is directory node + // Ignore this entry. It is a directory node // or it has no name (?) return newOffset; } @@ -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 offset directly into file data .. - // move the offset forward to skip the LOC header - entry.offset += ZipEntry.LOCHDR + nameLen + extraLen; - entries.put(entry.name, entry); return newOffset; @@ -246,26 +241,25 @@ private int readTableEntry(byte[] table, int offset) throws IOException{ private void fillByteArray(byte[] array, InputStream source) throws IOException{ int total = 0; int length = array.length; - while (total < length) { - int read = source.read(array, total, length - total); + while (total < length) { + int read = source.read(array, total, length - total); if (read < 0) throw new IOException("Failed to read entire array"); - total += read; - } + total += read; + } } 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; @@ -339,6 +347,7 @@ public InputStream openStream(String name) throws IOException{ return openStream(entry); } + @Override public void setRootPath(String path){ if (!rootPath.equals(path)){ rootPath = path; @@ -350,6 +359,7 @@ public void setRootPath(String path){ } } + @Override public AssetInfo locate(AssetManager manager, AssetKey key){ final ZipEntry2 entry = entries.get(key.getName()); if (entry == null) diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java index dde2c01656..b634e2abf1 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlAssetInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ */ public class UrlAssetInfo extends AssetInfo { - private URL url; + private final URL url; private InputStream in; public static UrlAssetInfo create(AssetManager assetManager, AssetKey key, URL url) throws IOException { diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java index 31cfe0f2cb..846356ac43 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/UrlLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,6 +57,7 @@ public class UrlLocator implements AssetLocator { private static final Logger logger = Logger.getLogger(UrlLocator.class.getName()); private URL root; + @Override public void setRootPath(String rootPath) { try { this.root = new URL(rootPath); @@ -65,6 +66,7 @@ public void setRootPath(String rootPath) { } } + @Override public AssetInfo locate(AssetManager manager, AssetKey key) { String name = key.getName(); try{ diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java index 22e7cc2442..26e48e9998 100644 --- a/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/ZipLocator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,6 +63,7 @@ public JarAssetInfo(AssetManager manager, AssetKey key, ZipEntry entry){ this.entry = entry; } + @Override public InputStream openStream(){ try{ return zipfile.getInputStream(entry); @@ -72,6 +73,7 @@ public InputStream openStream(){ } } + @Override public void setRootPath(String rootPath) { try{ zipfile = new ZipFile(new File(rootPath), ZipFile.OPEN_READ); @@ -80,6 +82,7 @@ public void setRootPath(String rootPath) { } } + @Override public AssetInfo locate(AssetManager manager, AssetKey key) { String name = key.getName(); if(name.startsWith("/"))name=name.substring(1); diff --git a/jme3-core/src/plugins/java/com/jme3/asset/plugins/package-info.java b/jme3-core/src/plugins/java/com/jme3/asset/plugins/package-info.java new file mode 100644 index 0000000000..bcb34d166d --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/asset/plugins/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * access loadable assets on the classpath, in a filesystem, or on the Web + */ +package com.jme3.asset.plugins; diff --git a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java index 997a8f4fb5..6e9efbbf73 100644 --- a/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/audio/plugins/WAVLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,7 @@ import com.jme3.audio.AudioKey; import com.jme3.audio.AudioStream; import com.jme3.audio.SeekableStream; +import com.jme3.export.binary.ByteUtils; import com.jme3.util.BufferUtils; import com.jme3.util.LittleEndien; import java.io.BufferedInputStream; @@ -47,187 +48,270 @@ import java.util.logging.Level; import java.util.logging.Logger; +/** + * An {@code AssetLoader} for loading WAV audio files. + * This loader supports PCM (Pulse Code Modulation) WAV files, + * both as in-memory {@link AudioBuffer}s and streaming {@link AudioStream}s. + * It handles 8-bit and 16-bit audio formats. + * + *

                The WAV file format consists of chunks. This loader specifically parses + * the 'RIFF', 'WAVE', 'fmt ', and 'data' chunks. + */ public class WAVLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(WAVLoader.class.getName()); - // all these are in big endian + // RIFF chunk identifiers (Big-Endian representation of ASCII characters) private static final int i_RIFF = 0x46464952; private static final int i_WAVE = 0x45564157; private static final int i_fmt = 0x20746D66; private static final int i_data = 0x61746164; - private boolean readStream = false; - - private AudioBuffer audioBuffer; - private AudioStream audioStream; - private AudioData audioData; + /** + * The number of bytes per second for the audio data, calculated from the WAV header. + * Used to determine the duration of the audio. + */ private int bytesPerSec; + /** + * The duration of the audio in seconds. + */ private float duration; - + /** + * The input stream for reading the WAV file data. + */ private ResettableInputStream in; - private int inOffset = 0; - + + /** + * A custom {@link InputStream} extension that handles little-endian byte + * reading and provides seek capabilities for streaming audio by reopening + * and skipping the input stream. + */ private static class ResettableInputStream extends LittleEndien implements SeekableStream { - private AssetInfo info; + private final AssetInfo info; private int resetOffset = 0; public ResettableInputStream(AssetInfo info, InputStream in) { super(in); this.info = info; } - - public void setResetOffset(int resetOffset) { - this.resetOffset = resetOffset; + + /** + * Sets the offset from the beginning of the file to reset the stream to. + * This is typically the start of the audio data chunk. + * + * @param offset The byte offset to reset to. + */ + public void setResetOffset(int offset) { + this.resetOffset = offset; } + @Override public void setTime(float time) { if (time != 0f) { throw new UnsupportedOperationException("Seeking WAV files not supported"); } InputStream newStream = info.openStream(); try { - newStream.skip(resetOffset); + ByteUtils.skipFully(newStream, resetOffset); this.in = new BufferedInputStream(newStream); } catch (IOException ex) { // Resource could have gotten lost, etc. try { newStream.close(); - } catch (IOException ex2) { + } catch (IOException ignored) { } throw new RuntimeException(ex); } } } - private void readFormatChunk(int size) throws IOException{ + /** + * Reads and parses the 'fmt ' (format) chunk of the WAV file. + * This chunk contains information about the audio format such as + * compression, channels, sample rate, bits per sample, etc. + * + * @param chunkSize The size of the 'fmt ' chunk in bytes. + * @param audioData The {@link AudioData} object to set the format information on. + * @throws IOException if the file is not a supported PCM WAV, or if format + * parameters are invalid. + */ + private void readFormatChunk(int chunkSize, AudioData audioData) throws IOException { // if other compressions are supported, size doesn't have to be 16 // if (size != 16) // logger.warning("Expected size of format chunk to be 16"); int compression = in.readShort(); - if (compression != 1){ + if (compression != 1) { // 1 = PCM (Pulse Code Modulation) throw new IOException("WAV Loader only supports PCM wave files"); } - int channels = in.readShort(); + int numChannels = in.readShort(); int sampleRate = in.readInt(); + bytesPerSec = in.readInt(); // Average bytes per second - bytesPerSec = in.readInt(); // used to calculate duration - - int bytesPerSample = in.readShort(); + int bytesPerSample = in.readShort(); // Bytes per sample block (channels * bytesPerSample) int bitsPerSample = in.readShort(); - int expectedBytesPerSec = (bitsPerSample * channels * sampleRate) / 8; - if (expectedBytesPerSec != bytesPerSec){ + int expectedBytesPerSec = (bitsPerSample * numChannels * sampleRate) / 8; + if (expectedBytesPerSec != bytesPerSec) { logger.log(Level.WARNING, "Expected {0} bytes per second, got {1}", new Object[]{expectedBytesPerSec, bytesPerSec}); } - + if (bitsPerSample != 8 && bitsPerSample != 16) throw new IOException("Only 8 and 16 bits per sample are supported!"); - if ( (bitsPerSample / 8) * channels != bytesPerSample) + if ((bitsPerSample / 8) * numChannels != bytesPerSample) throw new IOException("Invalid bytes per sample value"); if (bytesPerSample * sampleRate != bytesPerSec) throw new IOException("Invalid bytes per second value"); - audioData.setupFormat(channels, bitsPerSample, sampleRate); + audioData.setupFormat(numChannels, bitsPerSample, sampleRate); - int remaining = size - 16; - if (remaining > 0){ - in.skipBytes(remaining); + // Skip any extra parameters in the format chunk (e.g., for non-PCM formats) + int remainingChunkBytes = chunkSize - 16; + if (remainingChunkBytes > 0) { + ByteUtils.skipFully((InputStream)in, remainingChunkBytes); } } - private void readDataChunkForBuffer(int len) throws IOException { - ByteBuffer data = BufferUtils.createByteBuffer(len); - byte[] buf = new byte[512]; + /** + * Reads the 'data' chunk for an {@link AudioBuffer}. This involves loading + * the entire audio data into a {@link ByteBuffer} in memory. + * + * @param dataChunkSize The size of the 'data' chunk in bytes. + * @param audioBuffer The {@link AudioBuffer} to update with the loaded data. + * @throws IOException if an error occurs while reading the data. + */ + private void readDataChunkForBuffer(int dataChunkSize, AudioBuffer audioBuffer) throws IOException { + ByteBuffer data = BufferUtils.createByteBuffer(dataChunkSize); + byte[] buf = new byte[1024]; // Use a larger buffer for efficiency int read = 0; - while ( (read = in.read(buf)) > 0){ - data.put(buf, 0, Math.min(read, data.remaining()) ); + while ((read = in.read(buf)) > 0) { + data.put(buf, 0, Math.min(read, data.remaining())); } data.flip(); audioBuffer.updateData(data); in.close(); } - private void readDataChunkForStream(int offset, int len) throws IOException { - in.setResetOffset(offset); + /** + * Configures the {@link AudioStream} to stream data from the 'data' chunk. + * This involves setting the reset offset for seeking and passing the + * input stream and duration to the {@link AudioStream}. + * + * @param dataChunkOffset The byte offset from the start of the file where the 'data' chunk begins. + * @param dataChunkSize The size of the 'data' chunk in bytes. + * @param audioStream The {@link AudioStream} to configure. + */ + private void readDataChunkForStream(int dataChunkOffset, int dataChunkSize, AudioStream audioStream) { + in.setResetOffset(dataChunkOffset); audioStream.updateData(in, duration); } - private AudioData load(AssetInfo info, InputStream inputStream, boolean stream) throws IOException{ + /** + * Main loading logic for WAV files. This method parses the RIFF, WAVE, fmt, + * and data chunks to extract audio information and data. + * + * @param info The {@link AssetInfo} for the WAV file. + * @param inputStream The initial {@link InputStream} opened for the asset. + * @param stream A boolean indicating whether the audio should be loaded + * as a stream (true) or an in-memory buffer (false). + * @return The loaded {@link AudioData} (either {@link AudioBuffer} or {@link AudioStream}). + * @throws IOException if the file is not a valid WAV, or if any I/O error occurs. + */ + private AudioData load(AssetInfo info, InputStream inputStream, boolean stream) throws IOException { this.in = new ResettableInputStream(info, inputStream); - inOffset = 0; - - int sig = in.readInt(); - if (sig != i_RIFF) + int inOffset = 0; + + // Read RIFF chunk + int riffId = in.readInt(); + if (riffId != i_RIFF) { throw new IOException("File is not a WAVE file"); - - // skip size + } + + // Skip RIFF chunk size in.readInt(); - if (in.readInt() != i_WAVE) + + int waveId = in.readInt(); + if (waveId != i_WAVE) throw new IOException("WAVE File does not contain audio"); - inOffset += 4 * 3; - - readStream = stream; - if (readStream){ + inOffset += 4 * 3; // RIFF_ID + ChunkSize + WAVE_ID + + AudioData audioData; + AudioBuffer audioBuffer = null; + AudioStream audioStream = null; + + if (stream) { audioStream = new AudioStream(); audioData = audioStream; - }else{ + } else { audioBuffer = new AudioBuffer(); audioData = audioBuffer; } while (true) { - int type = in.readInt(); - int len = in.readInt(); - - inOffset += 4 * 2; + int chunkType = in.readInt(); + int chunkSize = in.readInt(); + + inOffset += 4 * 2; // ChunkType + ChunkSize - switch (type) { + switch (chunkType) { case i_fmt: - readFormatChunk(len); - inOffset += len; + readFormatChunk(chunkSize, audioData); + inOffset += chunkSize; break; case i_data: // Compute duration based on data chunk size - duration = (float)(len / bytesPerSec); + duration = (float) (chunkSize / bytesPerSec); - if (readStream) { - readDataChunkForStream(inOffset, len); + if (stream) { + readDataChunkForStream(inOffset, chunkSize, audioStream); } else { - readDataChunkForBuffer(len); + readDataChunkForBuffer(chunkSize, audioBuffer); } return audioData; default: - int skipped = in.skipBytes(len); - if (skipped <= 0) { + // Skip unknown chunks + int skippedBytes = in.skipBytes(chunkSize); + if (skippedBytes <= 0) { + logger.log(Level.WARNING, "Reached end of stream prematurely while skipping unknown chunk of size {0}. Asset: {1}", + new Object[]{chunkSize, info.getKey().getName()}); return null; } - inOffset += skipped; + inOffset += skippedBytes; break; } } } + @Override public Object load(AssetInfo info) throws IOException { - AudioData data; - InputStream inputStream = null; + InputStream is = null; try { - inputStream = info.openStream(); - data = load(info, inputStream, ((AudioKey)info.getKey()).isStream()); - if (data instanceof AudioStream){ - inputStream = null; + is = info.openStream(); + boolean streamAudio = ((AudioKey) info.getKey()).isStream(); + AudioData loadedData = load(info, is, streamAudio); + + // If it's an AudioStream, the internal inputStream is managed by the stream itself + // and should not be closed here. + if (loadedData instanceof AudioStream) { + // Prevent closing in finally block + is = null; } - return data; + return loadedData; } finally { - if (inputStream != null){ - inputStream.close(); + // Nullify/reset instance variables to ensure the loader instance is clean + // for the next load operation. + in = null; + bytesPerSec = 0; + duration = 0.0f; + + if (is != null) { + is.close(); } } } diff --git a/jme3-core/src/plugins/java/com/jme3/audio/plugins/package-info.java b/jme3-core/src/plugins/java/com/jme3/audio/plugins/package-info.java new file mode 100644 index 0000000000..c0265d589a --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/audio/plugins/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio formats supported across all platforms + */ +package com.jme3.audio.plugins; diff --git a/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java index 22f22bc7dc..ad3d1ef24e 100644 --- a/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java +++ b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/JmeCursor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -138,7 +138,8 @@ public void setImagesData(IntBuffer imagesData) { /** * Sets the cursor image delay for each frame of an animated cursor. If the * cursor has no animation and consist of only 1 image, null is expected. - * @param imagesDelay + * + * @param imagesDelay the desired delay amount for each frame */ public void setImagesDelay(IntBuffer imagesDelay) { this.imagesDelay = imagesDelay; diff --git a/jme3-core/src/plugins/java/com/jme3/cursors/plugins/package-info.java b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/package-info.java new file mode 100644 index 0000000000..4770e1fe29 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/cursors/plugins/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * cursor formats supported across all platforms + */ +package com.jme3.cursors.plugins; diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java index 3ad1b8b228..4837943c9a 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryExporter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,10 +34,17 @@ import com.jme3.asset.AssetManager; import com.jme3.export.FormatVersion; import com.jme3.export.JmeExporter; +import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; import com.jme3.export.SavableClassUtil; import com.jme3.math.FastMath; -import java.io.*; + +import java.io.BufferedOutputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; import java.util.ArrayList; import java.util.HashMap; import java.util.IdentityHashMap; @@ -45,155 +52,136 @@ import java.util.logging.Logger; /** - * Exports to the jME Binary Format. Format descriptor: (each numbered item - * denotes a series of bytes that follows sequentially one after the next.) - *

                - * 1. "number of classes" - four bytes - int value representing the number of - * entries in the class lookup table. - *

                - *

                - * CLASS TABLE: There will be X blocks each consisting of numbers 2 thru 9, - * where X = the number read in 1. - *

                - *

                - * 2. "class alias" - 1...X bytes, where X = ((int) FastMath.log(aliasCount, - * 256) + 1) - an alias used when writing object data to match an object to its - * appropriate object class type. - *

                - *

                - * 3. "full class name size" - four bytes - int value representing number of - * bytes to read in for next field. - *

                - *

                - * 4. "full class name" - 1...X bytes representing a String value, where X = the - * number read in 3. The String is the fully qualified class name of the Savable - * class, eg "com.jme.math.Vector3f" - *

                - *

                - * 5. "number of fields" - four bytes - int value representing number of blocks - * to read in next (numbers 6 - 9), where each block represents a field in this - * class. - *

                - *

                - * 6. "field alias" - 1 byte - the alias used when writing out fields in a - * class. Because it is a single byte, a single class can not save out more than - * a total of 256 fields. - *

                - *

                - * 7. "field type" - 1 byte - a value representing the type of data a field - * contains. This value is taken from the static fields of + * Exports savable objects in jMonkeyEngine's native binary format. + * + *

                Format description: (Each numbered item + * describes a series of bytes that follow sequentially, one after the other.) + * + *

                1. "signature" - 4 bytes - 0x4A4D4533 + * + *

                2. "version" - 4 bytes - 0x00000002 + * + *

                3. "number of classes" - 4 bytes - number of entries in the class table + * + *

                CLASS TABLE: X blocks, each consisting of items 4 thru 11, + * where X = the number of Savable classes from item 3 + * + *

                4. "class alias" - X bytes, where X = ((int) FastMath.log(aliasCount, + * 256) + 1) + * - a numeric ID used to refer to a Savable class when reading or writing + * + *

                5. "full class-name size" - 4 bytes - the number of bytes in item 6 + * + *

                6. "full class name" - X bytes of text, where X = the size from item 5 + * - the fully qualified class name of the Savable class, + * e.g. "com.jme.math.Vector3f" + * + *

                7. "number of fields" - 4 bytes + * - the number of saved fields in the Savable class + * + *

                8. "field alias" - 1 byte + * - a numeric ID used to refer to a saved field when reading or writing. + * Because field aliases are only a single byte, + * no Savable class can save more than 256 fields. + * + *

                9. "field type" - 1 byte + * - the type of data in the saved field. Values are defined in * com.jme.util.export.binary.BinaryClassField. - *

                - *

                - * 8. "field name size" - 4 bytes - int value representing the size of the next - * field. - *

                - *

                - * 9. "field name" - 1...X bytes representing a String value, where X = the - * number read in 8. The String is the full String value used when writing the - * current field. - *

                - *

                - * 10. "number of unique objects" - four bytes - int value representing the - * number of data entries in this file. - *

                - *

                - * DATA LOOKUP TABLE: There will be X blocks each consisting of numbers 11 and - * 12, where X = the number read in 10. - *

                - *

                - * 11. "data id" - four bytes - int value identifying a single unique object - * that was saved in this data file. - *

                - *

                - * 12. "data location" - four bytes - int value representing the offset in the - * object data portion of this file where the object identified in 11 is - * located. - *

                - *

                - * 13. "future use" - four bytes - hardcoded int value 1. - *

                - *

                - * 14. "root id" - four bytes - int value identifying the top level object. - *

                - *

                - * OBJECT DATA SECTION: There will be X blocks each consisting of numbers 15 - * thru 19, where X = the number of unique location values named in 12. - *

                - * 15. "class alias" - see 2. - *

                - *

                - * 16. "data length" - four bytes - int value representing the length in bytes - * of data stored in fields 17 and 18 for this object. - *

                - *

                - * FIELD ENTRY: There will be X blocks each consisting of numbers 18 and 19 - *

                - *

                - * 17. "field alias" - see 6. - *

                - *

                - * 18. "field data" - 1...X bytes representing the field data. The data length - * is dependent on the field type and contents. - *

                + * + *

                10. "field-name size" - 4 bytes - the number of bytes in item 11 + * + *

                11. "field name" - X bytes of text, where X = the size from item 10 + * - the tag specified when reading or writing the saved field + * + *

                12. "number of capsules" - 4 bytes + * - the number of capsules in this stream + * + *

                LOCATION TABLE: X blocks, each consisting of items 13 and 14, + * where X = the number of capsules from item 12 + * + *

                13. "data id" - 4 bytes + * - numeric ID of an object that was saved to this stream + * + *

                14. "data location" - 4 bytes + * - the offset in the capsule-data section where the savable object identified + * in item 13 is stored + * + *

                15. "future use" - 4 bytes - 0x00000001 + * + *

                16. "root id" - 4 bytes - numeric ID of the top-level savable object + * + * CAPSULE-DATA SECTION: X blocks, each consisting of items 17 + * thru 19, where X = the number of capsules from item 12 + * + *

                17. "class alias" - 4 bytes - see item 4 + * + *

                18. "capsule length" - 4 bytes - the length in bytes of item 19 + * + *

                19. "capsule data" - X bytes of data, where X = the number of bytes from + * item 18 * * @author Joshua Slack */ - public class BinaryExporter implements JmeExporter { - private static final Logger logger = Logger.getLogger(BinaryExporter.class - .getName()); + + private static final Logger logger = Logger.getLogger(BinaryExporter.class.getName()); protected int aliasCount = 1; protected int idCount = 1; - protected IdentityHashMap contentTable - = new IdentityHashMap(); - - protected HashMap locationTable - = new HashMap(); + private final IdentityHashMap contentTable = new IdentityHashMap<>(); + protected HashMap locationTable = new HashMap<>(); // key - class name, value = bco - private HashMap classes - = new HashMap(); - - private ArrayList contentKeys = new ArrayList(); + private final HashMap classes = new HashMap<>(); + private final ArrayList contentKeys = new ArrayList<>(); public static boolean debug = false; public static boolean useFastBufs = true; - + + /** + * Constructs a new {@code BinaryExporter}. + */ public BinaryExporter() { } + /** + * Returns a new instance of {@code BinaryExporter}. + * + * @return A new {@code BinaryExporter} instance. + */ public static BinaryExporter getInstance() { return new BinaryExporter(); } - + /** * Saves the object into memory then loads it from memory. - * * Used by tests to check if the persistence system is working. - * + * * @param The type of savable. * @param assetManager AssetManager to load assets from. * @param object The object to save and then load. - * @return A new instance that has been saved and loaded from the + * @return A new instance that has been saved and loaded from the * original object. */ + @SuppressWarnings("unchecked") public static T saveAndLoad(AssetManager assetManager, T object) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); try { BinaryExporter exporter = new BinaryExporter(); exporter.save(object, baos); + BinaryImporter importer = new BinaryImporter(); importer.setAssetManager(assetManager); return (T) importer.load(baos.toByteArray()); + } catch (IOException ex) { // Should never happen. throw new AssertionError(ex); } } + @Override public void save(Savable object, OutputStream os) throws IOException { // reset some vars aliasCount = 1; @@ -202,54 +190,50 @@ public void save(Savable object, OutputStream os) throws IOException { contentTable.clear(); locationTable.clear(); contentKeys.clear(); - + // write signature and version - os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE)); - os.write(ByteUtils.convertToBytes(FormatVersion.VERSION)); - + os.write(ByteUtils.convertToBytes(FormatVersion.SIGNATURE)); // 1. "signature" + os.write(ByteUtils.convertToBytes(FormatVersion.VERSION)); // 2. "version" + int id = processBinarySavable(object); // write out tag table int classTableSize = 0; int classNum = classes.keySet().size(); - int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all - // aliases a - // fixed width - - os.write(ByteUtils.convertToBytes(classNum)); + int aliasSize = ((int) FastMath.log(classNum, 256) + 1); // make all aliases a fixed width + + os.write(ByteUtils.convertToBytes(classNum)); // 3. "number of classes" for (String key : classes.keySet()) { BinaryClassObject bco = classes.get(key); // write alias - byte[] aliasBytes = fixClassAlias(bco.alias, - aliasSize); - os.write(aliasBytes); + byte[] aliasBytes = fixClassAlias(bco.alias, aliasSize); + os.write(aliasBytes); // 4. "class alias" classTableSize += aliasSize; - + // jME3 NEW: Write class hierarchy version numbers - os.write( bco.classHierarchyVersions.length ); - for (int version : bco.classHierarchyVersions){ + os.write(bco.classHierarchyVersions.length); + for (int version : bco.classHierarchyVersions) { os.write(ByteUtils.convertToBytes(version)); } classTableSize += 1 + bco.classHierarchyVersions.length * 4; - - // write classname size & classname + + // write class name size & class name byte[] classBytes = key.getBytes(); - os.write(ByteUtils.convertToBytes(classBytes.length)); - os.write(classBytes); + os.write(ByteUtils.convertToBytes(classBytes.length)); // 5. "full class-name size" + os.write(classBytes); // 6. "full class name" classTableSize += 4 + classBytes.length; - + // for each field, write alias, type, and name - os.write(ByteUtils.convertToBytes(bco.nameFields.size())); + os.write(ByteUtils.convertToBytes(bco.nameFields.size())); // 7. "number of fields" for (String fieldName : bco.nameFields.keySet()) { BinaryClassField bcf = bco.nameFields.get(fieldName); - os.write(bcf.alias); - os.write(bcf.type); + os.write(bcf.alias); // 8. "field alias" + os.write(bcf.type); // 9. "field type" - // write classname size & classname byte[] fNameBytes = fieldName.getBytes(); - os.write(ByteUtils.convertToBytes(fNameBytes.length)); - os.write(fNameBytes); + os.write(ByteUtils.convertToBytes(fNameBytes.length)); // 10. "field-name size" + os.write(fNameBytes); // 11. "field name" classTableSize += 2 + 4 + fNameBytes.length; } } @@ -258,14 +242,12 @@ public void save(Savable object, OutputStream os) throws IOException { // write out data to a separate stream int location = 0; // keep track of location for each piece - HashMap> alreadySaved = new HashMap>( - contentTable.size()); + HashMap> alreadySaved = new HashMap<>(contentTable.size()); for (Savable savable : contentKeys) { // look back at previous written data for matches String savableName = savable.getClass().getName(); BinaryIdContentPair pair = contentTable.get(savable); - ArrayList bucket = alreadySaved - .get(savableName + getChunk(pair)); + ArrayList bucket = alreadySaved.get(savableName + getChunk(pair)); int prevLoc = findPrevMatch(pair, bucket); if (prevLoc != -1) { locationTable.put(pair.getId(), prevLoc); @@ -279,62 +261,60 @@ public void save(Savable object, OutputStream os) throws IOException { } bucket.add(pair); byte[] aliasBytes = fixClassAlias(classes.get(savableName).alias, aliasSize); - out.write(aliasBytes); + out.write(aliasBytes); // 17. "class alias" location += aliasSize; BinaryOutputCapsule cap = contentTable.get(savable).getContent(); - out.write(ByteUtils.convertToBytes(cap.bytes.length)); + out.write(ByteUtils.convertToBytes(cap.bytes.length)); // 18. "capsule length" location += 4; // length of bytes - out.write(cap.bytes); + out.write(cap.bytes); // 19. "capsule data" location += cap.bytes.length; } // write out location table // tag/location int numLocations = locationTable.keySet().size(); - os.write(ByteUtils.convertToBytes(numLocations)); + os.write(ByteUtils.convertToBytes(numLocations)); // 12. "number of capsules" int locationTableSize = 0; for (Integer key : locationTable.keySet()) { - os.write(ByteUtils.convertToBytes(key)); - os.write(ByteUtils.convertToBytes(locationTable.get(key))); + os.write(ByteUtils.convertToBytes(key)); // 13. "data id" + os.write(ByteUtils.convertToBytes(locationTable.get(key))); // 14. "data location" locationTableSize += 8; } // write out number of root ids - hardcoded 1 for now - os.write(ByteUtils.convertToBytes(1)); + os.write(ByteUtils.convertToBytes(1)); // 15. "future use" // write out root id - os.write(ByteUtils.convertToBytes(id)); + os.write(ByteUtils.convertToBytes(id)); // 16. "root id" // append stream to the output stream out.writeTo(os); - - out = null; - os = null; - if (debug) { - logger.fine("Stats:"); - logger.log(Level.FINE, "classes: {0}", classNum); - logger.log(Level.FINE, "class table: {0} bytes", classTableSize); - logger.log(Level.FINE, "objects: {0}", numLocations); - logger.log(Level.FINE, "location table: {0} bytes", locationTableSize); - logger.log(Level.FINE, "data: {0} bytes", location); + logger.log(Level.INFO, "BinaryExporter Stats:" + + "\n * Classes: {0}" + + "\n * Class Table: {1} bytes" + + "\n * Objects: {2}" + + "\n * Location Table: {3} bytes" + + "\n * Data: {4} bytes", + new Object[] {classNum, classTableSize, numLocations, locationTableSize, location}); } } - protected String getChunk(BinaryIdContentPair pair) { + private String getChunk(BinaryIdContentPair pair) { return new String(pair.getContent().bytes, 0, Math.min(64, pair .getContent().bytes.length)); } - protected int findPrevMatch(BinaryIdContentPair oldPair, - ArrayList bucket) { - if (bucket == null) + private int findPrevMatch(BinaryIdContentPair oldPair, ArrayList bucket) { + if (bucket == null) { return -1; + } for (int x = bucket.size(); --x >= 0;) { BinaryIdContentPair pair = bucket.get(x); - if (pair.getContent().equals(oldPair.getContent())) + if (pair.getContent().equals(oldPair.getContent())) { return locationTable.get(pair.getId()); + } } return -1; } @@ -349,44 +329,44 @@ protected byte[] fixClassAlias(byte[] bytes, int width) { return bytes; } - public void save(Savable object, File f) throws IOException { + @Override + public void save(Savable object, File f, boolean createDirectories) throws IOException { File parentDirectory = f.getParentFile(); - if (parentDirectory != null && !parentDirectory.exists()) { + if (parentDirectory != null && !parentDirectory.exists() && createDirectories) { parentDirectory.mkdirs(); } - FileOutputStream fos = new FileOutputStream(f); - try { - save(object, fos); - } finally { - fos.close(); + try (FileOutputStream fos = new FileOutputStream(f); + BufferedOutputStream bos = new BufferedOutputStream(fos)) { + save(object, bos); } } - public BinaryOutputCapsule getCapsule(Savable object) { + @Override + public OutputCapsule getCapsule(Savable object) { return contentTable.get(object).getContent(); } - private BinaryClassObject createClassObject(Class clazz) throws IOException{ + private BinaryClassObject createClassObject(Class clazz) throws IOException { BinaryClassObject bco = new BinaryClassObject(); bco.alias = generateTag(); - bco.nameFields = new HashMap(); + bco.nameFields = new HashMap<>(); bco.classHierarchyVersions = SavableClassUtil.getSavableVersions(clazz); - + classes.put(clazz.getName(), bco); - + return bco; } - + public int processBinarySavable(Savable object) throws IOException { if (object == null) { return -1; } Class clazz = object.getClass(); - BinaryClassObject bco = classes.get(object.getClass().getName()); + BinaryClassObject bco = classes.get(clazz.getName()); // is this class been looked at before? in tagTable? if (bco == null) { - bco = createClassObject(object.getClass()); + bco = createClassObject(clazz); } // is object in contentTable? @@ -401,7 +381,6 @@ public int processBinarySavable(Savable object) throws IOException { object.write(this); newPair.getContent().finish(); return newPair.getId(); - } protected byte[] generateTag() { @@ -418,9 +397,9 @@ protected byte[] generateTag() { return bytes; } - protected BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) { + private BinaryIdContentPair generateIdContentPair(BinaryClassObject bco) { BinaryIdContentPair pair = new BinaryIdContentPair(idCount++, new BinaryOutputCapsule(this, bco)); return pair; } -} \ No newline at end of file +} diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java index 93f082337b..03c54823de 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryImporter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,17 +54,17 @@ public final class BinaryImporter implements JmeImporter { private AssetManager assetManager; //Key - alias, object - bco - private HashMap classes - = new HashMap(); + private final HashMap classes + = new HashMap<>(); //Key - id, object - the savable - private HashMap contentTable - = new HashMap(); + private final HashMap contentTable + = new HashMap<>(); //Key - savable, object - capsule - private IdentityHashMap capsuleTable - = new IdentityHashMap(); - //Key - id, opject - location in the file - private HashMap locationTable - = new HashMap(); + private final IdentityHashMap capsuleTable + = new IdentityHashMap<>(); + //Key - id, object - location in the file + private final HashMap locationTable + = new HashMap<>(); public static boolean debug = false; @@ -77,6 +77,7 @@ public final class BinaryImporter implements JmeImporter { public BinaryImporter() { } + @Override public int getFormatVersion(){ return formatVersion; } @@ -93,10 +94,12 @@ public void setAssetManager(AssetManager manager){ this.assetManager = manager; } + @Override public AssetManager getAssetManager(){ return assetManager; } + @Override public Object load(AssetInfo info){ // if (!(info.getKey() instanceof ModelKey)) // throw new IllegalArgumentException("Model assets must be loaded using a ModelKey"); @@ -326,12 +329,7 @@ public Savable readObject(int id) { int dataLength = ByteUtils.convertIntFromBytes(dataArray, loc); loc+=4; - Savable out = null; - if (assetManager != null) { - out = SavableClassUtil.fromName(bco.className, assetManager.getClassLoaders()); - } else { - out = SavableClassUtil.fromName(bco.className); - } + Savable out = SavableClassUtil.fromName(bco.className); BinaryInputCapsule cap = new BinaryInputCapsule(this, out, bco); cap.setContent(dataArray, loc, loc+dataLength); diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java index 1216887143..1903f3ecc1 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryInputCapsule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,11 +37,11 @@ import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.BitSet; import java.util.HashMap; @@ -257,11 +257,13 @@ public void setContent(byte[] content, int start, int limit) { } } + @Override public int getSavableVersion(Class desiredClass){ return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, cObj.classHierarchyVersions, importer.getFormatVersion()); } + @Override public BitSet readBitSet(String name, BitSet defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -269,6 +271,7 @@ public BitSet readBitSet(String name, BitSet defVal) throws IOException { return (BitSet) fieldData.get(field.alias); } + @Override public boolean readBoolean(String name, boolean defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -276,6 +279,7 @@ public boolean readBoolean(String name, boolean defVal) throws IOException { return ((Boolean) fieldData.get(field.alias)).booleanValue(); } + @Override public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -284,6 +288,7 @@ public boolean[] readBooleanArray(String name, boolean[] defVal) return (boolean[]) fieldData.get(field.alias); } + @Override public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -292,6 +297,7 @@ public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) return (boolean[][]) fieldData.get(field.alias); } + @Override public byte readByte(String name, byte defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -299,6 +305,7 @@ public byte readByte(String name, byte defVal) throws IOException { return ((Byte) fieldData.get(field.alias)).byteValue(); } + @Override public byte[] readByteArray(String name, byte[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -306,6 +313,7 @@ public byte[] readByteArray(String name, byte[] defVal) throws IOException { return (byte[]) fieldData.get(field.alias); } + @Override public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -314,6 +322,7 @@ public byte[][] readByteArray2D(String name, byte[][] defVal) return (byte[][]) fieldData.get(field.alias); } + @Override public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -323,6 +332,7 @@ public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) } @SuppressWarnings("unchecked") + @Override public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -331,6 +341,7 @@ public ArrayList readByteBufferArrayList(String name, return (ArrayList) fieldData.get(field.alias); } + @Override public double readDouble(String name, double defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -338,6 +349,7 @@ public double readDouble(String name, double defVal) throws IOException { return ((Double) fieldData.get(field.alias)).doubleValue(); } + @Override public double[] readDoubleArray(String name, double[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -346,6 +358,7 @@ public double[] readDoubleArray(String name, double[] defVal) return (double[]) fieldData.get(field.alias); } + @Override public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -354,6 +367,7 @@ public double[][] readDoubleArray2D(String name, double[][] defVal) return (double[][]) fieldData.get(field.alias); } + @Override public float readFloat(String name, float defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -361,6 +375,7 @@ public float readFloat(String name, float defVal) throws IOException { return ((Float) fieldData.get(field.alias)).floatValue(); } + @Override public float[] readFloatArray(String name, float[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -369,6 +384,7 @@ public float[] readFloatArray(String name, float[] defVal) return (float[]) fieldData.get(field.alias); } + @Override public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -377,6 +393,7 @@ public float[][] readFloatArray2D(String name, float[][] defVal) return (float[][]) fieldData.get(field.alias); } + @Override public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -386,6 +403,7 @@ public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) } @SuppressWarnings("unchecked") + @Override public ArrayList readFloatBufferArrayList(String name, ArrayList defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -394,6 +412,7 @@ public ArrayList readFloatBufferArrayList(String name, return (ArrayList) fieldData.get(field.alias); } + @Override public int readInt(String name, int defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -401,6 +420,7 @@ public int readInt(String name, int defVal) throws IOException { return ((Integer) fieldData.get(field.alias)).intValue(); } + @Override public int[] readIntArray(String name, int[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -408,6 +428,7 @@ public int[] readIntArray(String name, int[] defVal) throws IOException { return (int[]) fieldData.get(field.alias); } + @Override public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -416,6 +437,7 @@ public int[][] readIntArray2D(String name, int[][] defVal) return (int[][]) fieldData.get(field.alias); } + @Override public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -424,6 +446,7 @@ public IntBuffer readIntBuffer(String name, IntBuffer defVal) return (IntBuffer) fieldData.get(field.alias); } + @Override public long readLong(String name, long defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -431,6 +454,7 @@ public long readLong(String name, long defVal) throws IOException { return ((Long) fieldData.get(field.alias)).longValue(); } + @Override public long[] readLongArray(String name, long[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -438,6 +462,7 @@ public long[] readLongArray(String name, long[] defVal) throws IOException { return (long[]) fieldData.get(field.alias); } + @Override public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -446,6 +471,7 @@ public long[][] readLongArray2D(String name, long[][] defVal) return (long[][]) fieldData.get(field.alias); } + @Override public Savable readSavable(String name, Savable defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -461,6 +487,7 @@ else if (value instanceof ID) { return defVal; } + @Override public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -488,6 +515,7 @@ private Savable[] resolveIDs(Object[] values) { } } + @Override public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -533,7 +561,7 @@ private ArrayList savableArrayListFromArray(Savable[] savables) { if(savables == null) { return null; } - ArrayList arrayList = new ArrayList(savables.length); + ArrayList arrayList = new ArrayList<>(savables.length); for (int x = 0; x < savables.length; x++) { arrayList.add(savables[x]); } @@ -545,7 +573,7 @@ private Map savableMapFrom2DArray(Savable[][] savables) { if(savables == null) { return null; } - Map map = new HashMap(savables.length); + Map map = new HashMap<>(savables.length); for (int x = 0; x < savables.length; x++) { map.put(savables[x][0], savables[x][1]); } @@ -557,7 +585,7 @@ private Map stringSavableMapFromKV(String[] keys, Savable[] val return null; } - Map map = new HashMap(keys.length); + Map map = new HashMap<>(keys.length); for (int x = 0; x < keys.length; x++) map.put(keys[x], values[x]); @@ -569,13 +597,14 @@ private IntMap intSavableMapFromKV(int[] keys, Savable[] values) { return null; } - IntMap map = new IntMap(keys.length); + IntMap map = new IntMap<>(keys.length); for (int x = 0; x < keys.length; x++) map.put(keys[x], values[x]); return map; } + @Override public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -591,6 +620,7 @@ public ArrayList readSavableArrayList(String name, ArrayList defVal) return (ArrayList) value; } + @Override public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -613,6 +643,7 @@ public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) return (ArrayList[]) value; } + @Override public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -639,6 +670,7 @@ public ArrayList[][] readSavableArrayListArray2D(String name, } @SuppressWarnings("unchecked") + @Override public Map readSavableMap(String name, Map defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -655,6 +687,7 @@ public ArrayList[][] readSavableArrayListArray2D(String name, } @SuppressWarnings("unchecked") + @Override public Map readStringSavableMap(String name, Map defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -672,6 +705,7 @@ public ArrayList[][] readSavableArrayListArray2D(String name, } @SuppressWarnings("unchecked") + @Override public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -688,6 +722,7 @@ public IntMap readIntSavableMap(String name, IntMap) value; } + @Override public short readShort(String name, short defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -695,6 +730,7 @@ public short readShort(String name, short defVal) throws IOException { return ((Short) fieldData.get(field.alias)).shortValue(); } + @Override public short[] readShortArray(String name, short[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -703,6 +739,7 @@ public short[] readShortArray(String name, short[] defVal) return (short[]) fieldData.get(field.alias); } + @Override public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -711,6 +748,7 @@ public short[][] readShortArray2D(String name, short[][] defVal) return (short[][]) fieldData.get(field.alias); } + @Override public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -719,6 +757,7 @@ public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) return (ShortBuffer) fieldData.get(field.alias); } + @Override public String readString(String name, String defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); if (field == null || !fieldData.containsKey(field.alias)) @@ -726,6 +765,7 @@ public String readString(String name, String defVal) throws IOException { return (String) fieldData.get(field.alias); } + @Override public String[] readStringArray(String name, String[] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -734,6 +774,7 @@ public String[] readStringArray(String name, String[] defVal) return (String[]) fieldData.get(field.alias); } + @Override public String[][] readStringArray2D(String name, String[][] defVal) throws IOException { BinaryClassField field = cObj.nameFields.get(name); @@ -972,129 +1013,17 @@ protected boolean[][] readBooleanArray2D(byte[] content) throws IOException { return value; } - /* - * UTF-8 crash course: - * - * UTF-8 codepoints map to UTF-16 codepoints and vv, which is what Java uses for its Strings. - * (so a UTF-8 codepoint can contain all possible values for a Java char) - * - * A UTF-8 codepoint can be 1, 2 or 3 bytes long. How long a codepint is can be told by reading the first byte: - * b < 0x80, 1 byte - * (b & 0xC0) == 0xC0, 2 bytes - * (b & 0xE0) == 0xE0, 3 bytes - * - * However there is an additional restriction to UTF-8, to enable you to find the start of a UTF-8 codepoint, - * if you start reading at a random point in a UTF-8 byte stream. That's why UTF-8 requires for the second and third byte of - * a multibyte codepoint: - * (b & 0x80) == 0x80 (in other words, first bit must be 1) - */ - private final static int UTF8_START = 0; // next byte should be the start of a new - private final static int UTF8_2BYTE = 2; // next byte should be the second byte of a 2 byte codepoint - private final static int UTF8_3BYTE_1 = 3; // next byte should be the second byte of a 3 byte codepoint - private final static int UTF8_3BYTE_2 = 4; // next byte should be the third byte of a 3 byte codepoint - private final static int UTF8_ILLEGAL = 10; // not an UTF8 string - - // String protected String readString(byte[] content) throws IOException { int length = readInt(content); if (length == BinaryOutputCapsule.NULL_OBJECT) return null; - /* - * @see ISSUE 276 - * - * We'll transfer the bytes into a separate byte array. - * While we do that we'll take the opportunity to check if the byte data is valid UTF-8. - * - * If it is not UTF-8 it is most likely saved with the BinaryOutputCapsule bug, that saves Strings using their native - * encoding. Unfortunatly there is no way to know what encoding was used, so we'll parse using the most common one in - * that case; latin-1 aka ISO8859_1 - * - * Encoding of "low" ASCII codepoint (in plain speak: when no special characters are used) will usually look the same - * for UTF-8 and the other 1 byte codepoint encodings (espc true for numbers and regular letters of the alphabet). So these - * are valid UTF-8 and will give the same result (at most a few charakters will appear different, such as the euro sign). - * - * However, when "high" codepoints are used (any codepoint that over 0x7F, in other words where the first bit is a 1) it's - * a different matter and UTF-8 and the 1 byte encoding greatly will differ, as well as most 1 byte encodings relative to each - * other. - * - * It is impossible to detect which one-byte encoding is used. Since UTF8 and practically all 1-byte encodings share the most - * used characters (the "none-high" ones) parsing them will give the same result. However, not all byte sequences are legal in - * UTF-8 (see explantion above). If not UTF-8 encoded content is detected we therefore fall back on latin1. We also log a warning. - * - * By this method we detect all use of 1 byte encoding if they: - * - use a "high" codepoint after a "low" codepoint or a sequence of codepoints that is valid as UTF-8 bytes, that starts with 1000 - * - use a "low" codepoint after a "high" codepoint - * - use a "low" codepoint after "high" codepoint, after a "high" codepoint that starts with 1110 - * - * In practise this means that unless 2 or 3 "high" codepoints are used after each other in proper order, we'll detect the string - * was not originally UTF-8 encoded. - * - */ byte[] bytes = new byte[length]; - int utf8State = UTF8_START; - int b; for (int x = 0; x < length; x++) { bytes[x] = content[index++]; - b = (int) bytes[x] & 0xFF; // unsign our byte - - switch (utf8State) { - case UTF8_START: - if (b < 0x80) { - // good - } - else if ((b & 0xC0) == 0xC0) { - utf8State = UTF8_2BYTE; - } - else if ((b & 0xE0) == 0xE0) { - utf8State = UTF8_3BYTE_1; - } - else { - utf8State = UTF8_ILLEGAL; - } - break; - case UTF8_3BYTE_1: - case UTF8_3BYTE_2: - case UTF8_2BYTE: - if ((b & 0x80) == 0x80) - utf8State = utf8State == UTF8_3BYTE_1 ? UTF8_3BYTE_2 : UTF8_START; - else - utf8State = UTF8_ILLEGAL; - break; - } } - try { - // even though so far the parsing might have been a legal UTF-8 sequence, only if a codepoint is fully given is it correct UTF-8 - if (utf8State == UTF8_START) { - // Java misspells UTF-8 as UTF8 for official use in java.lang - return new String(bytes, "UTF8"); - } - else { - logger.log( - Level.WARNING, - "Your export has been saved with an incorrect encoding for its String fields which means it might not load correctly " + - "due to encoding issues. You should probably re-export your work. See ISSUE 276 in the jME issue tracker." - ); - // We use ISO8859_1 to be consistent across platforms. We could default to native encoding, but this would lead to inconsistent - // behaviour across platforms! - // Developers that have previously saved their exports using the old exporter (which uses native encoding), can temporarly - // remove the ""ISO8859_1" parameter, and change the above if statement to "if (false)". - // They should then import and re-export their models using the same environment they were originally created in. - return new String(bytes, "ISO8859_1"); - } - } catch (UnsupportedEncodingException uee) { - // as a last resort fall back to platform native. - // JavaDoc is vague about what happens when a decoding a String that contains un undecodable sequence - // it also doesn't specify which encodings have to be supported (though UTF-8 and ISO8859 have been in the SUN JRE since at least 1.1) - logger.log( - Level.SEVERE, - "Your export has been saved with an incorrect encoding or your version of Java is unable to decode the stored string. " + - "While your export may load correctly by falling back, using it on different platforms or java versions might lead to "+ - "very strange inconsitenties. You should probably re-export your work. See ISSUE 276 in the jME issue tracker." - ); - return new String(bytes); - } + return new String(bytes, StandardCharsets.UTF_8); } protected String[] readStringArray(byte[] content) throws IOException { @@ -1239,7 +1168,7 @@ protected ArrayList readFloatBufferArrayList(byte[] content) if (length == BinaryOutputCapsule.NULL_OBJECT) { return null; } - ArrayList rVal = new ArrayList(length); + ArrayList rVal = new ArrayList<>(length); for (int x = 0; x < length; x++) { rVal.add(readFloatBuffer(content)); } @@ -1254,7 +1183,7 @@ protected ArrayList readByteBufferArrayList(byte[] content) if (length == BinaryOutputCapsule.NULL_OBJECT) { return null; } - ArrayList rVal = new ArrayList(length); + ArrayList rVal = new ArrayList<>(length); for (int x = 0; x < length; x++) { rVal.add(readByteBuffer(content)); } @@ -1368,6 +1297,7 @@ static private class IntIDMap { public ID[] values; } + @Override public > T readEnum(String name, Class enumType, T defVal) throws IOException { String eVal = readString(name, defVal != null ? defVal.name() : null); if (eVal != null) { @@ -1376,4 +1306,4 @@ public > T readEnum(String name, Class enumType, T defVal) return null; } } -} \ No newline at end of file +} diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java index 380772eaf7..bc8c7e825c 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/BinaryOutputCapsule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; @@ -68,6 +69,7 @@ public BinaryOutputCapsule(BinaryExporter exporter, BinaryClassObject bco) { this.cObj = bco; } + @Override public void write(byte value, String name, byte defVal) throws IOException { if (value == defVal) return; @@ -75,6 +77,7 @@ public void write(byte value, String name, byte defVal) throws IOException { write(value); } + @Override public void write(byte[] value, String name, byte[] defVal) throws IOException { if (value == defVal) @@ -83,6 +86,7 @@ public void write(byte[] value, String name, byte[] defVal) write(value); } + @Override public void write(byte[][] value, String name, byte[][] defVal) throws IOException { if (value == defVal) @@ -91,6 +95,7 @@ public void write(byte[][] value, String name, byte[][] defVal) write(value); } + @Override public void write(int value, String name, int defVal) throws IOException { if (value == defVal) return; @@ -98,6 +103,7 @@ public void write(int value, String name, int defVal) throws IOException { write(value); } + @Override public void write(int[] value, String name, int[] defVal) throws IOException { if (value == defVal) @@ -106,6 +112,7 @@ public void write(int[] value, String name, int[] defVal) write(value); } + @Override public void write(int[][] value, String name, int[][] defVal) throws IOException { if (value == defVal) @@ -114,6 +121,7 @@ public void write(int[][] value, String name, int[][] defVal) write(value); } + @Override public void write(float value, String name, float defVal) throws IOException { if (value == defVal) @@ -122,6 +130,7 @@ public void write(float value, String name, float defVal) write(value); } + @Override public void write(float[] value, String name, float[] defVal) throws IOException { if (value == defVal) @@ -130,6 +139,7 @@ public void write(float[] value, String name, float[] defVal) write(value); } + @Override public void write(float[][] value, String name, float[][] defVal) throws IOException { if (value == defVal) @@ -138,6 +148,7 @@ public void write(float[][] value, String name, float[][] defVal) write(value); } + @Override public void write(double value, String name, double defVal) throws IOException { if (value == defVal) @@ -146,6 +157,7 @@ public void write(double value, String name, double defVal) write(value); } + @Override public void write(double[] value, String name, double[] defVal) throws IOException { if (value == defVal) @@ -154,6 +166,7 @@ public void write(double[] value, String name, double[] defVal) write(value); } + @Override public void write(double[][] value, String name, double[][] defVal) throws IOException { if (value == defVal) @@ -162,6 +175,7 @@ public void write(double[][] value, String name, double[][] defVal) write(value); } + @Override public void write(long value, String name, long defVal) throws IOException { if (value == defVal) return; @@ -169,6 +183,7 @@ public void write(long value, String name, long defVal) throws IOException { write(value); } + @Override public void write(long[] value, String name, long[] defVal) throws IOException { if (value == defVal) @@ -177,6 +192,7 @@ public void write(long[] value, String name, long[] defVal) write(value); } + @Override public void write(long[][] value, String name, long[][] defVal) throws IOException { if (value == defVal) @@ -185,6 +201,7 @@ public void write(long[][] value, String name, long[][] defVal) write(value); } + @Override public void write(short value, String name, short defVal) throws IOException { if (value == defVal) @@ -193,6 +210,7 @@ public void write(short value, String name, short defVal) write(value); } + @Override public void write(short[] value, String name, short[] defVal) throws IOException { if (value == defVal) @@ -201,6 +219,7 @@ public void write(short[] value, String name, short[] defVal) write(value); } + @Override public void write(short[][] value, String name, short[][] defVal) throws IOException { if (value == defVal) @@ -209,6 +228,7 @@ public void write(short[][] value, String name, short[][] defVal) write(value); } + @Override public void write(boolean value, String name, boolean defVal) throws IOException { if (value == defVal) @@ -217,6 +237,7 @@ public void write(boolean value, String name, boolean defVal) write(value); } + @Override public void write(boolean[] value, String name, boolean[] defVal) throws IOException { if (value == defVal) @@ -225,6 +246,7 @@ public void write(boolean[] value, String name, boolean[] defVal) write(value); } + @Override public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException { if (value == defVal) @@ -233,6 +255,7 @@ public void write(boolean[][] value, String name, boolean[][] defVal) write(value); } + @Override public void write(String value, String name, String defVal) throws IOException { if (value == null ? defVal == null : value.equals(defVal)) @@ -241,6 +264,7 @@ public void write(String value, String name, String defVal) write(value); } + @Override public void write(String[] value, String name, String[] defVal) throws IOException { if (value == defVal) @@ -249,6 +273,7 @@ public void write(String[] value, String name, String[] defVal) write(value); } + @Override public void write(String[][] value, String name, String[][] defVal) throws IOException { if (value == defVal) @@ -257,6 +282,7 @@ public void write(String[][] value, String name, String[][] defVal) write(value); } + @Override public void write(BitSet value, String name, BitSet defVal) throws IOException { if (value == defVal) @@ -265,6 +291,7 @@ public void write(BitSet value, String name, BitSet defVal) write(value); } + @Override public void write(Savable object, String name, Savable defVal) throws IOException { if (object == defVal) @@ -273,6 +300,7 @@ public void write(Savable object, String name, Savable defVal) write(object); } + @Override public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { if (objects == defVal) @@ -281,6 +309,7 @@ public void write(Savable[] objects, String name, Savable[] defVal) write(objects); } + @Override public void write(Savable[][] objects, String name, Savable[][] defVal) throws IOException { if (objects == defVal) @@ -289,6 +318,7 @@ public void write(Savable[][] objects, String name, Savable[][] defVal) write(objects); } + @Override public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { if (value == defVal) @@ -297,6 +327,7 @@ public void write(FloatBuffer value, String name, FloatBuffer defVal) write(value); } + @Override public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { if (value == defVal) @@ -305,6 +336,7 @@ public void write(IntBuffer value, String name, IntBuffer defVal) write(value); } + @Override public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { if (value == defVal) @@ -313,6 +345,7 @@ public void write(ByteBuffer value, String name, ByteBuffer defVal) write(value); } + @Override public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { if (value == defVal) @@ -321,6 +354,7 @@ public void write(ShortBuffer value, String name, ShortBuffer defVal) write(value); } + @Override public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { if (array == defVal) @@ -329,6 +363,7 @@ public void writeFloatBufferArrayList(ArrayList array, writeFloatBufferArrayList(array); } + @Override public void writeByteBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { if (array == defVal) @@ -337,6 +372,7 @@ public void writeByteBufferArrayList(ArrayList array, writeByteBufferArrayList(array); } + @Override public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { if (array == defVal) @@ -345,6 +381,7 @@ public void writeSavableArrayList(ArrayList array, String name, writeSavableArrayList(array); } + @Override public void writeSavableArrayListArray(ArrayList[] array, String name, ArrayList[] defVal) throws IOException { if (array == defVal) @@ -353,6 +390,7 @@ public void writeSavableArrayListArray(ArrayList[] array, String name, writeSavableArrayListArray(array); } + @Override public void writeSavableArrayListArray2D(ArrayList[][] array, String name, ArrayList[][] defVal) throws IOException { if (array == defVal) @@ -361,6 +399,7 @@ public void writeSavableArrayListArray2D(ArrayList[][] array, String name, writeSavableArrayListArray2D(array); } + @Override public void writeSavableMap(Map map, String name, Map defVal) throws IOException { @@ -370,6 +409,7 @@ public void writeSavableMap(Map map, writeSavableMap(map); } + @Override public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { @@ -379,6 +419,7 @@ public void writeStringSavableMap(Map map, writeStringSavableMap(map); } + @Override public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { @@ -644,8 +685,7 @@ protected void write(String value) throws IOException { write(NULL_OBJECT); return; } - // write our output as UTF-8. Java misspells UTF-8 as UTF8 for official use in java.lang - byte[] bytes = value.getBytes("UTF8"); + byte[] bytes = value.getBytes(StandardCharsets.UTF_8); write(bytes.length); baos.write(bytes); } @@ -931,6 +971,7 @@ protected void write(ShortBuffer value) throws IOException { value.rewind(); } + @Override public void write(Enum value, String name, Enum defVal) throws IOException { if (value == defVal) return; diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java b/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java index 882cb529fd..812d8a7731 100644 --- a/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/ByteUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,6 +32,8 @@ package com.jme3.export.binary; import java.io.ByteArrayOutputStream; +import java.io.DataInput; +import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -44,6 +46,83 @@ */ public class ByteUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private ByteUtils() { + } + + + public static void readFully(InputStream in, byte[] b) throws IOException { + int bytesRead = 0; + int read = 0; + while (bytesRead < b.length && (read = in.read(b, bytesRead, b.length - bytesRead)) != -1) { + bytesRead += read; + } + if (bytesRead < b.length) { + throw new IOException( + "End of stream reached prematurely after " + bytesRead + " of " + b.length + " bytes."); + } + } + + + public static void skipFully(InputStream in, long n) throws IOException { + skipFully(in, n, true); + } + + public static void skipFully(InputStream in, long n, boolean throwOnEOF) throws IOException { + while (n > 0) { + long skipped = in.skip(n); + if (skipped > 0 && skipped <= n) { // skipped some bytes + n -= skipped; + } else if (skipped == 0) { // skipped nothing + // distinguish between EOF and no bytes available + if (in.read() == -1) { + if (throwOnEOF) { + throw new EOFException(); + } else { + return; + } + } else { + // stream was just hangling + n--; + } + } else { + throw new IOException( + "Unable to skip exactly " + n + " bytes. Only " + skipped + " bytes were skipped."); + } + } + } + + public static void skipFully(DataInput in, int n) throws IOException { + skipFully(in, n, true); + } + + public static void skipFully(DataInput in, int n, boolean throwOnEOF) throws IOException { + while (n > 0) { + long skipped = in.skipBytes(n); + if (skipped > 0 && skipped <= n) { // skipped some bytes + n -= skipped; + } else if (skipped == 0) { // skipped nothing + // distinguish between EOF and no bytes available + try { + in.readByte(); + } catch (EOFException e) { + if (throwOnEOF) { + throw e; + } else { + return; + } + } + n--; + } else { + throw new IOException( + "Unable to skip exactly " + n + " bytes. Only " + skipped + " bytes were skipped."); + } + } + } + + /** * Takes an InputStream and returns the complete byte content of it * @@ -119,7 +198,7 @@ public static short readShort(InputStream inputStream) throws IOException { byte[] byteArray = new byte[2]; // Read in the next 2 bytes - inputStream.read(byteArray); + readFully(inputStream, byteArray); short number = convertShortFromBytes(byteArray); @@ -181,7 +260,7 @@ public static int readInt(InputStream inputStream) throws IOException { byte[] byteArray = new byte[4]; // Read in the next 4 bytes - inputStream.read(byteArray); + readFully(inputStream, byteArray); int number = convertIntFromBytes(byteArray); @@ -257,7 +336,7 @@ public static long readLong(InputStream inputStream) throws IOException { byte[] byteArray = new byte[8]; // Read in the next 8 bytes - inputStream.read(byteArray); + readFully(inputStream, byteArray); long number = convertLongFromBytes(byteArray); @@ -320,7 +399,7 @@ public static double readDouble(InputStream inputStream) throws IOException { byte[] byteArray = new byte[8]; // Read in the next 8 bytes - inputStream.read(byteArray); + readFully(inputStream, byteArray); double number = convertDoubleFromBytes(byteArray); @@ -376,7 +455,7 @@ public static float readFloat(InputStream inputStream) throws IOException { byte[] byteArray = new byte[4]; // Read in the next 4 bytes - inputStream.read(byteArray); + readFully(inputStream, byteArray); float number = convertFloatFromBytes(byteArray); @@ -434,7 +513,7 @@ public static boolean readBoolean(InputStream inputStream) throws IOException { byte[] byteArray = new byte[1]; // Read in the next byte - inputStream.read(byteArray); + readFully(inputStream, byteArray); return convertBooleanFromBytes(byteArray); } @@ -464,9 +543,7 @@ public static boolean convertBooleanFromBytes(byte[] byteArray, int offset) { * if bytes greater than the length of the store. */ public static byte[] readData(byte[] store, int bytes, InputStream is) throws IOException { - for (int i = 0; i < bytes; i++) { - store[i] = (byte)is.read(); - } + readFully(is, store); return store; } diff --git a/jme3-core/src/plugins/java/com/jme3/export/binary/package-info.java b/jme3-core/src/plugins/java/com/jme3/export/binary/package-info.java new file mode 100644 index 0000000000..4fb9aa8635 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/export/binary/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * J3O format (jMonkeyEngine's native binary format) + */ +package com.jme3.export.binary; diff --git a/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java b/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java index 07aa9813ee..b81253601c 100644 --- a/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/font/plugins/BitmapFontLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ public class BitmapFontLoader implements AssetLoader { private BitmapFont load(AssetManager assetManager, String folder, InputStream in) throws IOException{ MaterialDef spriteMat = - (MaterialDef) assetManager.loadAsset(new AssetKey("Common/MatDefs/Misc/Unshaded.j3md")); + assetManager.loadAsset(new AssetKey<>("Common/MatDefs/Misc/Unshaded.j3md")); BitmapCharacterSet charSet = new BitmapCharacterSet(); Material[] matPages = null; BitmapFont font = new BitmapFont(); @@ -162,6 +162,7 @@ private BitmapFont load(AssetManager assetManager, String folder, InputStream in return font; } + @Override public Object load(AssetInfo info) throws IOException { InputStream in = null; try { diff --git a/jme3-core/src/plugins/java/com/jme3/font/plugins/package-info.java b/jme3-core/src/plugins/java/com/jme3/font/plugins/package-info.java new file mode 100644 index 0000000000..0067f2b8db --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/font/plugins/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * font formats supported across all platforms + */ +package com.jme3.font.plugins; diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java index 2e1ff1aa27..63ca5d6a46 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ConditionParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ import java.util.regex.Pattern; /** - * An utility class that allows to parse a define condition in a glsl language + * A utility class that parses a define condition in a GLSL language * style. * * extractDefines is able to get a list of defines in an expression and update @@ -75,9 +75,9 @@ public static void main(String argv[]) { } /** - * parse a condition and returns the list of defines of this condition. - * additionally this methods updates the formattedExpression with uppercased - * defines names + * Parses a condition and returns the list of defines of this condition. + * Additionally, this method updates the formattedExpression with uppercased + * defines names. * * supported expression syntax example:

                * {@code "(LightMap && SeparateTexCoord) || !ColorMap"}

                @@ -89,7 +89,7 @@ public static void main(String argv[]) { * @return the list of defines */ public List extractDefines(String expression) { - List defines = new ArrayList(); + List defines = new ArrayList<>(); expression = expression.replaceAll("#ifdef", "").replaceAll("#if", "").replaceAll("defined", ""); Pattern pattern = Pattern.compile("(\\w+)"); formattedExpression = expression; 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 88674fdffd..9999f5d3ec 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-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,7 @@ import com.jme3.util.blockparser.BlockLanguageParser; import com.jme3.util.blockparser.Statement; import com.jme3.util.clone.Cloner; +import jme3tools.shader.Preprocessor; import java.io.IOException; import java.io.InputStream; import java.util.*; @@ -61,7 +62,6 @@ public class J3MLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(J3MLoader.class.getName()); - // private ErrorLogger errors; private ShaderNodeLoaderDelegate nodesLoaderDelegate; boolean isUseNodes = false; int langSize = 0; @@ -73,10 +73,10 @@ public class J3MLoader implements AssetLoader { private Material material; private TechniqueDef technique; private RenderState renderState; - private ArrayList presetDefines = new ArrayList(); + private final ArrayList presetDefines = new ArrayList<>(); - private List> shaderLanguages; - private EnumMap shaderNames; + private final List> shaderLanguages; + private final EnumMap shaderNames; private static final String whitespacePattern = "\\p{javaWhitespace}+"; @@ -85,7 +85,6 @@ public J3MLoader() { shaderNames = new EnumMap<>(Shader.ShaderType.class); } - // : private void readShaderStatement(String statement) throws IOException { String[] split = statement.split(":"); @@ -102,7 +101,6 @@ private void readShaderStatement(String statement) throws IOException { } } - private void readShaderDefinition(Shader.ShaderType shaderType, String name, String... languages) { shaderNames.put(shaderType, name); @@ -128,15 +126,15 @@ private void readLightMode(String statement) throws IOException{ LightMode lm = LightMode.valueOf(split[1]); technique.setLightMode(lm); } - - + + // LightMode private void readLightSpace(String statement) throws IOException{ String[] split = statement.split(whitespacePattern); if (split.length != 2){ throw new IOException("LightSpace statement syntax incorrect"); } - TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]); + TechniqueDef.LightSpace ls = TechniqueDef.LightSpace.valueOf(split[1]); technique.setLightSpace(ls); } @@ -151,7 +149,7 @@ private void readShadowMode(String statement) throws IOException{ } private List tokenizeTextureValue(final String value) { - final List matchList = new ArrayList(); + final List matchList = new ArrayList<>(); final Pattern regex = Pattern.compile("[^\\s\"']+|\"([^\"]*)\"|'([^']*)'"); final Matcher regexMatcher = regex.matcher(value.trim()); @@ -169,7 +167,7 @@ private List tokenizeTextureValue(final String value) { } private List parseTextureOptions(final List values) { - final List matchList = new ArrayList(); + final List matchList = new ArrayList<>(); if (values.isEmpty() || values.size() == 1) { return matchList; @@ -219,6 +217,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(); @@ -253,6 +252,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) { @@ -273,8 +274,6 @@ private Texture parseTextureType(final VarType type, final String value) { break; } - textureKey.setGenerateMips(true); - Texture texture; try { @@ -295,7 +294,7 @@ private Texture parseTextureType(final VarType type, final String value) { for (final TextureOptionValue textureOptionValue : textureOptionValues) { textureOptionValue.applyToTexture(texture); } - } + } return texture; } @@ -309,28 +308,28 @@ private Object readValue(final VarType type, final String value) throws IOExcept if (split.length != 1){ throw new IOException("Float value parameter must have 1 entry: " + value); } - return Float.parseFloat(split[0]); + return Float.parseFloat(split[0]); case Vector2: if (split.length != 2){ throw new IOException("Vector2 value parameter must have 2 entries: " + value); } return new Vector2f(Float.parseFloat(split[0]), - Float.parseFloat(split[1])); + Float.parseFloat(split[1])); case Vector3: if (split.length != 3){ throw new IOException("Vector3 value parameter must have 3 entries: " + value); } return new Vector3f(Float.parseFloat(split[0]), - Float.parseFloat(split[1]), - Float.parseFloat(split[2])); + Float.parseFloat(split[1]), + Float.parseFloat(split[2])); case Vector4: if (split.length != 4){ throw new IOException("Vector4 value parameter must have 4 entries: " + value); } return new ColorRGBA(Float.parseFloat(split[0]), - Float.parseFloat(split[1]), - Float.parseFloat(split[2]), - Float.parseFloat(split[3])); + Float.parseFloat(split[1]), + Float.parseFloat(split[2]), + Float.parseFloat(split[3])); case Int: if (split.length != 1){ throw new IOException("Int value parameter must have 1 entry: " + value); @@ -409,7 +408,7 @@ private void readParam(String statement) throws IOException{ } private void readValueParam(String statement) throws IOException{ - // Use limit=1 incase filename contains colons + // Use limit=1 in case the filename contains colons. String[] split = statement.split(":", 2); if (split.length != 2){ throw new IOException("Value parameter statement syntax incorrect"); @@ -476,7 +475,7 @@ private void readRenderStateStatement(Statement statement) throws IOException{ }else if (split[0].equals("BlendEquationAlpha")){ renderState.setBlendEquationAlpha(RenderState.BlendEquationAlpha.valueOf(split[1])); }else if (split[0].equals("AlphaTestFalloff")){ - // Ignore for backwards compatbility + // ignore for backwards compatibility }else if (split[0].equals("PolyOffset")){ float factor = Float.parseFloat(split[1]); float units = Float.parseFloat(split[2]); @@ -484,11 +483,11 @@ private void readRenderStateStatement(Statement statement) throws IOException{ }else if (split[0].equals("ColorWrite")){ renderState.setColorWrite(parseBoolean(split[1])); }else if (split[0].equals("PointSprite")){ - // Ignore for backwards compatbility + // ignore for backwards compatibility }else if (split[0].equals("DepthFunc")){ renderState.setDepthFunc(RenderState.TestFunction.valueOf(split[1])); }else if (split[0].equals("AlphaFunc")){ - renderState.setAlphaFunc(RenderState.TestFunction.valueOf(split[1])); + // ignore for backwards compatibility }else if (split[0].equals("LineWidth")){ renderState.setLineWidth(Float.parseFloat(split[1])); } else { @@ -534,12 +533,12 @@ private void readDefine(String statement) throws IOException{ MatParam param = materialDef.getMaterialParam(paramName); if (param == null) { logger.log(Level.WARNING, "In technique ''{0}'':\n" - + "Define ''{1}'' mapped to non-existent" - + " material parameter ''{2}'', ignoring.", + + "Define ''{1}'' mapped to non-existent" + + " material parameter ''{2}'', ignoring.", new Object[]{technique.getName(), defineName, paramName}); return; } - + VarType paramType = param.getVarType(); technique.addShaderParamDefine(paramName, paramType, defineName); }else{ @@ -551,7 +550,6 @@ private void readDefines(List defineList) throws IOException{ for (Statement statement : defineList){ readDefine(statement.getLine()); } - } private void readTechniqueStatement(Statement statement) throws IOException{ @@ -598,12 +596,23 @@ private void readTechniqueStatement(Statement statement) throws IOException{ } } - private void readTransparentStatement(String statement) throws IOException{ + private void readTransparentStatement(String statement) throws IOException { + boolean transparent = readBooleanStatement(statement, "Transparent"); + material.setTransparent(transparent); + } + + private void readReceivesShadowsStatement(String statement) throws IOException { + boolean receivesShadows = readBooleanStatement(statement, "ReceivesShadows"); + material.setReceivesShadows(receivesShadows); + } + + private boolean readBooleanStatement(String statement, String propertyName) throws IOException { String[] split = statement.split(whitespacePattern); - if (split.length != 2){ - throw new IOException("Transparent statement syntax incorrect"); + if (split.length != 2) { + throw new IOException(propertyName + " statement syntax incorrect"); } - material.setTransparent(parseBoolean(split[1])); + + return parseBoolean(split[1]); } private static String createShaderPrologue(List presetDefines) { @@ -663,7 +672,7 @@ private void readTechnique(Statement techStat) throws IOException{ if(isUseNodes){ //used for caching later, the shader here is not a file. - + // KIRILL 9/19/2015 // Not sure if this is needed anymore, since shader caching // is now done by TechniqueDef. @@ -720,9 +729,11 @@ private void loadFromRoot(List roots) throws IOException{ boolean extending = false; Statement materialStat = roots.get(0); String materialName = materialStat.getLine(); + if (materialName.startsWith("MaterialDef")){ materialName = materialName.substring("MaterialDef ".length()).trim(); extending = false; + }else if (materialName.startsWith("Material")){ materialName = materialName.substring("Material ".length()).trim(); extending = true; @@ -743,7 +754,7 @@ private void loadFromRoot(List roots) throws IOException{ String extendedMat = split[1].trim(); - MaterialDef def = (MaterialDef) assetManager.loadAsset(new AssetKey(extendedMat)); + MaterialDef def = assetManager.loadAsset(new AssetKey(extendedMat)); if (def == null) { throw new MatParseException("Extended material " + extendedMat + " cannot be found.", materialStat); } @@ -751,41 +762,44 @@ private void loadFromRoot(List roots) throws IOException{ material = new Material(def); material.setKey(key); material.setName(split[0].trim()); -// material.setAssetName(fileName); + }else if (split.length == 1){ if (extending){ throw new MatParseException("Expected ':', got '{'", materialStat); } materialDef = new MaterialDef(assetManager, materialName); - // NOTE: pass file name for defs so they can be loaded later + // NOTE: pass the filename for defs, so they can be loaded later materialDef.setAssetName(key.getName()); }else{ throw new MatParseException("Cannot use colon in material name/path", materialStat); } - for (Statement statement : materialStat.getContents()){ + for (Statement statement : materialStat.getContents()) { split = statement.getLine().split("[ \\{]"); String statType = split[0]; - if (extending){ - if (statType.equals("MaterialParameters")){ + if (extending) { + if (statType.equals("MaterialParameters")) { readExtendingMaterialParams(statement.getContents()); - }else if (statType.equals("AdditionalRenderState")){ + } else if (statType.equals("AdditionalRenderState")) { readAdditionalRenderState(statement.getContents()); - }else if (statType.equals("Transparent")){ + } else if (statType.equals("Transparent")) { readTransparentStatement(statement.getLine()); + } else if (statType.equals("ReceivesShadows")) { + readReceivesShadowsStatement(statement.getLine()); } - }else{ - if (statType.equals("Technique")){ + } else { + if (statType.equals("Technique")) { readTechnique(statement); - }else if (statType.equals("MaterialParameters")){ + } else if (statType.equals("MaterialParameters")) { readMaterialParams(statement.getContents()); - }else{ - throw new MatParseException("Expected material statement, got '"+statType+"'", statement); + } else { + throw new MatParseException("Expected material statement, got '" + statType + "'", statement); } } } } + @Override public Object load(AssetInfo info) throws IOException { this.assetManager = info.getManager(); @@ -794,9 +808,11 @@ public Object load(AssetInfo info) throws IOException { key = info.getKey(); if (key.getExtension().equals("j3m") && !(key instanceof MaterialKey)) { throw new IOException("Material instances must be loaded via MaterialKey"); + } else if (key.getExtension().equals("j3md") && key instanceof MaterialKey) { throw new IOException("Material definitions must be loaded via AssetKey"); } + in = Preprocessor.apply(in); loadFromRoot(BlockLanguageParser.parse(in)); } finally { if (in != null){ @@ -841,11 +857,11 @@ protected void initNodesLoader() { * the path to the texture in the .j3m file. *

                * Example: - *

                -     *     DiffuseMap: MinTrilinear MagBilinear WrapRepeat_S "some/path/to a/texture.png"
                -     *     
                - * This would apply a minification filter of "Trilinear", a magnification filter of "Bilinear" and set the wrap mode to "Repeat". *

                + *
                +     * DiffuseMap: MinTrilinear MagBilinear WrapRepeat_S "some/path/to a/texture.png"
                +     * 
                + * This would apply a minification filter of "Trilinear", a magnification filter of "Bilinear" and set the wrap mode to "Repeat". *

                * Note: If several filters of the same type are added, eg. MinTrilinear MinNearestLinearMipMap, the last one will win. *

                @@ -856,6 +872,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/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java index 05ef9fd544..dffb256609 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeDefinitionLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,13 +38,14 @@ import com.jme3.asset.ShaderNodeDefinitionKey; import com.jme3.util.blockparser.BlockLanguageParser; import com.jme3.util.blockparser.Statement; + import java.io.IOException; import java.io.InputStream; import java.util.List; /** * ShaderNodeDefinition file loader (.j3sn) - * + *

                * a j3sn file is a block style file like j3md or j3m. It must contain one * ShaderNodeDefinition{} block that contains several ShaderNodeDefinition{} * blocks @@ -53,16 +54,14 @@ */ public class ShaderNodeDefinitionLoader implements AssetLoader { - private ShaderNodeLoaderDelegate loaderDelegate; - @Override public Object load(AssetInfo assetInfo) throws IOException { - AssetKey k = assetInfo.getKey(); - if (!(k instanceof ShaderNodeDefinitionKey)) { + AssetKey assetKey = assetInfo.getKey(); + if (!(assetKey instanceof ShaderNodeDefinitionKey)) { throw new IOException("ShaderNodeDefinition file must be loaded via ShaderNodeDefinitionKey"); } - ShaderNodeDefinitionKey key = (ShaderNodeDefinitionKey) k; - loaderDelegate = new ShaderNodeLoaderDelegate(); + ShaderNodeDefinitionKey key = (ShaderNodeDefinitionKey) assetKey; + ShaderNodeLoaderDelegate loaderDelegate = new ShaderNodeLoaderDelegate(); InputStream in = assetInfo.openStream(); List roots = BlockLanguageParser.parse(in); @@ -73,13 +72,12 @@ public Object load(AssetInfo assetInfo) throws IOException { if (line.startsWith("Exception")) { throw new AssetLoadException(line.substring("Exception ".length())); } else { - throw new MatParseException("In multiroot shader node definition, expected first statement to be 'Exception'", exception); + throw new MatParseException("In multi-root shader node definition, expected first statement to be 'Exception'", exception); } } else if (roots.size() != 1) { throw new MatParseException("Too many roots in J3SN file", roots.get(0)); } return loaderDelegate.readNodesDefinitions(roots.get(0).getContents(), key); - } } diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java index 3e64d0d785..f44dba182e 100644 --- a/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/ShaderNodeLoaderDelegate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,9 +49,9 @@ * This class is here to be able to load shaderNodeDefinition from both the * J3MLoader and ShaderNodeDefinitionLoader. * - * Also it allows to load the ShaderNodes from a j3md file and build the + * It also allows loading shader nodes from a j3md file and building the * ShaderNodes list of each technique and the ShaderGenerationInfo needed to - * generate shaders + * generate shaders. * * @author Nehon */ @@ -75,12 +75,12 @@ public class ShaderNodeLoaderDelegate { protected Set varNames = new HashSet<>(); protected AssetManager assetManager; protected ConditionParser conditionParser = new ConditionParser(); - protected List nulledConditions = new ArrayList(); + protected List nulledConditions = new ArrayList<>(); protected class DeclaredVariable { ShaderNodeVariable var; - List nodes = new ArrayList(); + List nodes = new ArrayList<>(); public DeclaredVariable(ShaderNodeVariable var) { this.var = var; @@ -101,7 +101,7 @@ public final void addNode(ShaderNode c) { * @param statements the list statements to parse * @param key the ShaderNodeDefinitionKey * @return a list of ShaderNodesDefinition - * @throws IOException + * @throws IOException if an I/O error occurs */ public List readNodesDefinitions(List statements, ShaderNodeDefinitionKey key) throws IOException { @@ -132,13 +132,13 @@ public List readNodesDefinitions(List statement * ShaderNodesDefinition This method is used by the j3m loader. * * When loaded in a material, the definitions are not stored as a list, but - * they are stores in Shadernodes based on this definition. + * they are stored in shader nodes based on this definition. * * The map is here to map the definition to the nodes, and ovoid reloading * already loaded definitions * * @param statements the list of statements to parse - * @throws IOException + * @throws IOException if an I/O error occurs */ public void readNodesDefinitions(List statements) throws IOException { readNodesDefinitions(statements, new ShaderNodeDefinitionKey()); @@ -149,10 +149,10 @@ public void readNodesDefinitions(List statements) throws IOException * * @param statements the list of statements to parse * @param key the ShaderNodeDefinitionKey - * @throws IOException + * @throws IOException if an I/O error occurs */ protected void readShaderNodeDefinition(List statements, ShaderNodeDefinitionKey key) throws IOException { - boolean isLoadDoc = key instanceof ShaderNodeDefinitionKey && ((ShaderNodeDefinitionKey) key).isLoadDocumentation(); + boolean isLoadDoc = key instanceof ShaderNodeDefinitionKey && key.isLoadDocumentation(); for (Statement statement : statements) { try { String[] split = statement.getLine().split("[ \\{]"); @@ -209,7 +209,7 @@ protected void readShaderNodeDefinition(List statements, ShaderNodeDe * * @param statement the statement to parse * @return a ShaderNodeVariable extracted from the statement - * @throws IOException + * @throws IOException if an I/O error occurs */ protected ShaderNodeVariable readVariable(Statement statement) throws IOException { @@ -248,7 +248,7 @@ protected ShaderNodeVariable readVariable(Statement statement) throws IOExceptio * reads the VertexShaderNodes{} block * * @param statements the list of statements to parse - * @throws IOException + * @throws IOException if an I/O error occurs */ public void readVertexShaderNodes(List statements) throws IOException { attributes.clear(); @@ -259,7 +259,7 @@ public void readVertexShaderNodes(List statements) throws IOException * reads a list of ShaderNode{} blocks * * @param statements the list of statements to parse - * @throws IOException + * @throws IOException if an I/O error occurs */ protected void readShaderNode(List statements) throws IOException { @@ -315,6 +315,7 @@ protected void readShaderNode(List statements) throws IOException { * * * @param statement the statement to read. + * @param hasNameSpace indicate which vars have namespaces * @return the read mapping. * @throws MatParseException if the statement isn't valid. */ @@ -381,17 +382,17 @@ protected VariableMapping parseMapping(Statement statement, boolean[] hasNameSpa * reads the FragmentShaderNodes{} block * * @param statements the list of statements to parse - * @throws IOException + * @throws IOException if an I/O error occurs */ public void readFragmentShaderNodes(List statements) throws IOException { readNodes(statements); } /** - * Reads a Shader statement of this form : + * Reads a Shader statement of the form TYPE LANG : SOURCE * - * @param statement - * @throws IOException + * @param statement the shader statement (not null) + * @throws IOException if an I/O error occurs */ protected void readShaderStatement(Statement statement) throws IOException { String[] split = statement.getLine().split(":"); @@ -418,7 +419,7 @@ public void setTechniqueDef(TechniqueDef techniqueDef) { /** * sets the material def currently being loaded * - * @param materialDef + * @param materialDef (alias created) */ public void setMaterialDef(MaterialDef materialDef) { this.materialDef = materialDef; @@ -546,7 +547,9 @@ protected boolean updateRightFromUniforms(UniformBinding param, VariableMapping * @param param the mat param. * @param mapping the mapping. * @param map the map of uniforms to search into. + * @param statement the statement being read * @return true if the param was added to the map. + * @throws MatParseException in case of a syntax error */ public boolean updateRightFromUniforms(MatParam param, VariableMapping mapping, Map map, Statement statement) throws MatParseException { @@ -626,7 +629,8 @@ public void updateVarFromAttributes(ShaderNodeVariable right, VariableMapping ma /** * Adds a define to the technique def * - * @param paramName + * @param paramName the name of the material parameter + * @param paramType the type of the material parameter */ public void addDefine(String paramName, VarType paramType) { if (techniqueDef.getShaderParamDefine(paramName) == null) { @@ -850,7 +854,7 @@ public VariableMapping readOutputMapping(Statement statement) throws MatParseExc * Reads a list of ShaderNodes * * @param statements the list of statements to read - * @throws IOException + * @throws IOException if an I/O error occurs */ public void readNodes(List statements) throws IOException { if (techniqueDef.getShaderNodes() == null) { @@ -963,7 +967,7 @@ public void storeFragmentUniform(ShaderNodeVariable var) { /** * sets the assetManager * - * @param assetManager + * @param assetManager for loading assets (alias created) */ public void setAssetManager(AssetManager assetManager) { this.assetManager = assetManager; @@ -974,7 +978,7 @@ public void setAssetManager(AssetManager assetManager) { * * @param statement the statement being read * @return the definition - * @throws IOException + * @throws IOException if an I/O error occurs */ public ShaderNodeDefinition findDefinition(Statement statement) throws IOException { @@ -1047,8 +1051,8 @@ public void storeVaryings(ShaderNode node, ShaderNodeVariable variable) { declaredVar.addNode(shaderNode); - // if a variable is declared with the same name as an input and an output and is a varying, - // set it as a shader output so it's declared as a varying only once. + // If a variable is declared with the same name as an input and an output and is a varying, + // set it as a shader output, so it's declared as a varying only once. for (final VariableMapping variableMapping : node.getInputMapping()) { final ShaderNodeVariable leftVariable = variableMapping.getLeftVariable(); if (leftVariable.getName().equals(variable.getName())) { @@ -1100,7 +1104,7 @@ public void storeVariable(ShaderNodeVariable variable, List * * @param mapping the mapping * @param statement1 the statement being read - * @throws MatParseException + * @throws MatParseException in case of a syntax error */ protected void checkTypes(VariableMapping mapping, Statement statement1) throws MatParseException { if (!ShaderUtils.typesMatch(mapping)) { diff --git a/jme3-core/src/plugins/java/com/jme3/material/plugins/package-info.java b/jme3-core/src/plugins/java/com/jme3/material/plugins/package-info.java new file mode 100644 index 0000000000..094b5f4701 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/material/plugins/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * the J3M asset format + */ +package com.jme3.material.plugins; diff --git a/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java b/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java index cdc3655d6e..ac186c1606 100644 --- a/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/scene/plugins/MTLLoader.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 @@ -152,6 +152,7 @@ protected void createMaterial(){ material.setFloat("AlphaDiscardThreshold", 0.01f); } + material.setName(matName); matList.put(matName, material); } @@ -183,7 +184,7 @@ protected Texture loadTexture(String path){ logger.log(Level.WARNING, "Cannot locate {0} for material {1}", new Object[]{texKey, key}); texture = new Texture2D(PlaceholderAssets.getPlaceholderImage(assetManager)); texture.setWrap(WrapMode.Repeat); - texture.setKey(key); + texture.setKey(texKey); } return texture; } @@ -287,6 +288,7 @@ protected boolean readLine(){ } @SuppressWarnings("empty-statement") + @Override public Object load(AssetInfo info) throws IOException{ reset(); diff --git a/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java b/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java index 4d57567c48..cd2b4a29e8 100644 --- a/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/scene/plugins/OBJLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,18 +63,17 @@ public final class OBJLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(OBJLoader.class.getName()); - protected final ArrayList verts = new ArrayList(); - protected final ArrayList texCoords = new ArrayList(); - protected final ArrayList norms = new ArrayList(); - - protected final ArrayList faces = new ArrayList(); - protected final HashMap> matFaces = new HashMap>(); - + protected final ArrayList verts = new ArrayList<>(); + protected final ArrayList texCoords = new ArrayList<>(); + protected final ArrayList norms = new ArrayList<>(); + + private final ArrayList groups = new ArrayList<>(); + protected String currentMatName; protected String currentObjectName; - protected final HashMap vertIndexMap = new HashMap(100); - protected final IntMap indexVertMap = new IntMap(100); + protected final HashMap vertIndexMap = new HashMap<>(100); + protected final IntMap indexVertMap = new IntMap<>(100); protected int curIndex = 0; protected int objectIndex = 0; protected int geomIndex = 0; @@ -87,6 +86,16 @@ public final class OBJLoader implements AssetLoader { protected String objName; protected Node objNode; + private static class Group { + final private String name; + private final ArrayList faces = new ArrayList<>(); + private final HashMap> matFaces = new HashMap<>(); + + public Group(final String name) { + this.name = name; + } + } + protected static class Vertex { Vector3f v; @@ -164,8 +173,7 @@ public void reset(){ verts.clear(); texCoords.clear(); norms.clear(); - faces.clear(); - matFaces.clear(); + groups.clear(); vertIndexMap.clear(); indexVertMap.clear(); @@ -200,7 +208,7 @@ protected Face[] quadToTriangle(Face f){ Vertex v2 = f.verticies[2]; Vertex v3 = f.verticies[3]; - // find the pair of verticies that is closest to each over + // find the pair of vertices that is closest to each over // v0 and v2 // OR // v1 and v3 @@ -229,7 +237,7 @@ protected Face[] quadToTriangle(Face f){ return t; } - private ArrayList vertList = new ArrayList(); + final private ArrayList vertList = new ArrayList<>(); protected void readFace(){ Face f = new Face(); @@ -289,10 +297,17 @@ protected void readFace(){ f.verticies[i] = vertList.get(i); } - if (matList != null && matFaces.containsKey(currentMatName)){ - matFaces.get(currentMatName).add(f); + Group group = groups.get(groups.size() - 1); + + if (currentMatName != null && matList != null && matList.containsKey(currentMatName)){ + ArrayList matFaces = group.matFaces.get(currentMatName); + if (matFaces == null) { + matFaces = new ArrayList(); + group.matFaces.put(currentMatName, matFaces); + } + matFaces.add(f); }else{ - faces.add(f); // faces that belong to the default material + group.faces.add(f); // faces that belong to the default material } } @@ -331,19 +346,12 @@ protected void loadMtlLib(String name) throws IOException{ // NOTE: Cut off any relative/absolute paths name = new File(name).getName(); - AssetKey mtlKey = new AssetKey(key.getFolder() + name); + AssetKey mtlKey = new AssetKey<>(key.getFolder() + name); try { - matList = (MaterialList) assetManager.loadAsset(mtlKey); + matList = assetManager.loadAsset(mtlKey); } catch (AssetNotFoundException ex){ logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{name, key}); } - - if (matList != null){ - // create face lists for every material - for (String matName : matList.keySet()){ - matFaces.put(matName, new ArrayList()); - } - } } protected boolean nextStatement(){ @@ -387,8 +395,14 @@ protected boolean readLine() throws IOException{ // specify MTL lib to use for this OBJ file String mtllib = scan.nextLine().trim(); loadMtlLib(mtllib); - }else if (cmd.equals("s") || cmd.equals("g")){ + }else if (cmd.equals("s")) { + logger.log(Level.WARNING, "smoothing groups are not supported, statement ignored: {0}", cmd); + return nextStatement(); + }else if (cmd.equals("mg")) { + logger.log(Level.WARNING, "merge groups are not supported, statement ignored: {0}", cmd); return nextStatement(); + }else if (cmd.equals("g")) { + groups.add(new Group(scan.nextLine().trim())); }else{ // skip entire command until next line logger.log(Level.WARNING, "Unknown statement in OBJ! {0}", cmd); @@ -439,7 +453,7 @@ protected Mesh constructMesh(ArrayList faceList){ boolean hasTexCoord = false; boolean hasNormals = false; - ArrayList newFaces = new ArrayList(faceList.size()); + ArrayList newFaces = new ArrayList<>(faceList.size()); for (int i = 0; i < faceList.size(); i++){ Face f = faceList.get(i); @@ -476,7 +490,7 @@ protected Mesh constructMesh(ArrayList faceList){ IndexBuffer indexBuf = null; if (vertIndexMap.size() >= 65536){ - // too many verticies: use intbuffer instead of shortbuffer + // too many vertices: use IntBuffer instead of ShortBuffer IntBuffer ib = BufferUtils.createIntBuffer(newFaces.size() * 3); m.setBuffer(VertexBuffer.Type.Index, 3, ib); indexBuf = new IndexIntBuffer(ib); @@ -549,6 +563,7 @@ protected Mesh constructMesh(ArrayList faceList){ } @SuppressWarnings("empty-statement") + @Override public Object load(AssetInfo info) throws IOException{ reset(); @@ -564,11 +579,14 @@ public Object load(AssetInfo info) throws IOException{ } objNode = new Node(objName + "-objnode"); + + Group defaultGroupStub = new Group(null); + groups.add(defaultGroupStub); if (!(info.getKey() instanceof ModelKey)) throw new IllegalArgumentException("Model assets must be loaded using a ModelKey"); - InputStream in = null; + InputStream in = null; try { in = info.openStream(); @@ -582,25 +600,43 @@ public Object load(AssetInfo info) throws IOException{ } } - if (matFaces.size() > 0){ - for (Entry> entry : matFaces.entrySet()){ - ArrayList materialFaces = entry.getValue(); - if (materialFaces.size() > 0){ - Geometry geom = createGeometry(materialFaces, entry.getKey()); + for (Group group : groups) { + if (group == defaultGroupStub) { + materializeGroup(group, objNode); + } else { + Node groupNode = new Node(group.name); + materializeGroup(group, groupNode); + if (groupNode.getQuantity() == 1) { + Spatial geom = groupNode.getChild(0); + geom.setName(groupNode.getName()); objNode.attachChild(geom); + } else if (groupNode.getQuantity() > 1) { + objNode.attachChild(groupNode); } } - }else if (faces.size() > 0){ - // generate final geometry - Geometry geom = createGeometry(faces, null); - objNode.attachChild(geom); } if (objNode.getQuantity() == 1) // only 1 geometry, so no need to send node - return objNode.getChild(0); + return objNode.getChild(0); else return objNode; } - + + private void materializeGroup(Group group, Node container) throws IOException { + if (group.matFaces.size() > 0) { + for (Entry> entry : group.matFaces.entrySet()){ + ArrayList materialFaces = entry.getValue(); + if (materialFaces.size() > 0){ + Geometry geom = createGeometry(materialFaces, entry.getKey()); + container.attachChild(geom); + } + } + } else if (group.faces.size() > 0) { + // generate final geometry + Geometry geom = createGeometry(group.faces, null); + container.attachChild(geom); + } + } + } diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java index 3c4dd91300..63b8ba68c7 100644 --- a/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/GLSLLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,7 @@ import com.jme3.asset.*; import com.jme3.asset.cache.AssetCache; - +import jme3tools.shader.Preprocessor; import java.io.*; import java.util.*; @@ -43,7 +43,7 @@ public class GLSLLoader implements AssetLoader { private AssetManager assetManager; - private Map dependCache = new HashMap<>(); + private final Map dependCache = new HashMap<>(); /** * Used to load {@link ShaderDependencyNode}s. @@ -85,8 +85,9 @@ private ShaderDependencyNode loadNode(Reader reader, String nodeName) { } while ((ln = bufferedReader.readLine()) != null) { - if (ln.trim().startsWith("#import ")) { - ln = ln.trim().substring(8).trim(); + String tln = ln.trim(); + if (tln.startsWith("#import ")) { + ln = tln.substring(8).trim(); if (ln.startsWith("\"") && ln.endsWith("\"") && ln.length() > 3) { // import user code // remove quotes to get filename @@ -105,7 +106,7 @@ private ShaderDependencyNode loadNode(Reader reader, String nodeName) { node.addDependency(sb.length(), dependNode); } - } else if (ln.trim().startsWith("#extension ")) { + } else if (tln.startsWith("#extension ")) { sbExt.append(ln).append('\n'); } else { sb.append(ln).append('\n'); @@ -124,27 +125,6 @@ private ShaderDependencyNode loadNode(Reader reader, String nodeName) { return node; } - private ShaderDependencyNode nextIndependentNode() throws IOException { - Collection allNodes = dependCache.values(); - - if (allNodes.isEmpty()) { - return null; - } - - for (ShaderDependencyNode node : allNodes) { - if (node.getDependOnMe().isEmpty()) { - return node; - } - } - - // Circular dependency found.. - for (ShaderDependencyNode node : allNodes){ - System.out.println(node.getName()); - } - - throw new IOException("Circular dependency."); - } - private String resolveDependencies(ShaderDependencyNode node, Set alreadyInjectedSet, StringBuilder extensions, boolean injectDependencies) { if (alreadyInjectedSet.contains(node)) { return "// " + node.getName() + " was already injected at the top.\n"; @@ -167,7 +147,7 @@ private String resolveDependencies(ShaderDependencyNode node, Set injectIndices = node.getDependencyInjectIndices(); for (int i = resolvedShaderNodes.size() - 1; i >= 0; i--) { - // Must insert them backwards .. + // Must insert them backward sb.insert(injectIndices.get(i), resolvedShaderNodes.get(i)); } return sb.toString(); @@ -186,7 +166,9 @@ public Object load(AssetInfo info) throws IOException { // The input stream provided is for the vertex shader, // to retrieve the fragment shader, use the content manager this.assetManager = info.getManager(); - Reader reader = new InputStreamReader(info.openStream()); + InputStream in = info.openStream(); + in = Preprocessor.apply(in); + Reader reader = new InputStreamReader(in); boolean injectDependencies = true; if (info.getKey() instanceof ShaderAssetKey) { injectDependencies = ((ShaderAssetKey) info.getKey()).isInjectDependencies(); diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderAssetKey.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderAssetKey.java index e27d92539f..b19559dd44 100644 --- a/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderAssetKey.java +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderAssetKey.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 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.shader.plugins; import com.jme3.asset.AssetKey; diff --git a/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java index a4f371a9d2..de7b01c088 100644 --- a/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java +++ b/jme3-core/src/plugins/java/com/jme3/shader/plugins/ShaderDependencyNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,9 +41,9 @@ class ShaderDependencyNode { private String shaderSource; private String shaderName; - private final List dependencies = new ArrayList(); - private final List dependencyInjectIndices = new ArrayList(); - private final List dependOnMe = new ArrayList(); + private final List dependencies = new ArrayList<>(); + private final List dependencyInjectIndices = new ArrayList<>(); + private final List dependOnMe = new ArrayList<>(); public ShaderDependencyNode(String shaderName){ this.shaderName = shaderName; diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java index 359fa979e5..2dc5e8e0a1 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DDSLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 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.AssetLoader; import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; @@ -61,12 +62,10 @@ public class DDSLoader implements AssetLoader { private static final Logger logger = Logger.getLogger(DDSLoader.class.getName()); - private static final boolean forceRGBA = false; private static final int DDSD_MANDATORY = 0x1007; private static final int DDSD_MANDATORY_DX10 = 0x6; private static final int DDSD_MIPMAPCOUNT = 0x20000; private static final int DDSD_LINEARSIZE = 0x80000; - private static final int DDSD_DEPTH = 0x800000; private static final int DDPF_ALPHAPIXELS = 0x1; private static final int DDPF_FOURCC = 0x4; private static final int DDPF_RGB = 0x40; @@ -76,8 +75,6 @@ public class DDSLoader implements AssetLoader { private static final int DDPF_ALPHA = 0x2; // used by NVTextureTools to mark normal images. private static final int DDPF_NORMAL = 0x80000000; - private static final int SWIZZLE_xGxR = 0x78477852; - private static final int DDSCAPS_COMPLEX = 0x8; private static final int DDSCAPS_TEXTURE = 0x1000; private static final int DDSCAPS_MIPMAP = 0x400000; private static final int DDSCAPS2_CUBEMAP = 0x200; @@ -88,12 +85,14 @@ public class DDSLoader implements AssetLoader { private static final int PF_ATI1 = 0x31495441; private static final int PF_ATI2 = 0x32495441; // 0x41544932; private static final int PF_DX10 = 0x30315844; // a DX10 format - private static final int DX10DIM_BUFFER = 0x1, - DX10DIM_TEXTURE1D = 0x2, - DX10DIM_TEXTURE2D = 0x3, - DX10DIM_TEXTURE3D = 0x4; - private static final int DX10MISC_GENERATE_MIPS = 0x1, - DX10MISC_TEXTURECUBE = 0x4; + private static final int PF_BC4S = 0x53344342; // a DX9 file format for BC4 signed + private static final int PF_BC5S = 0x53354342; // a DX9 file format for BC5 signed + private static final int PF_ETC2_RGBA_CSN = 0x41435445; // ETC2 RGBA format notation from Compressonator + private static final int PF_ETC2_RGB_CSN = 0x32435445; // ETC2 RGB format notation from Compressonator + private static final int PF_ETC_RGB_CSN = 0x20435445; // ETC RGB format notation from Compressonator + private static final int PF_ETC2_RGBA1_CSN = 0x50435445; // ETC RGB + Alpha1 format notation from Compressonator + private static final int DX10DIM_TEXTURE3D = 0x4; + private static final int DX10MISC_TEXTURECUBE = 0x4; private static final double LOG2 = Math.log(2); private int width; private int height; @@ -107,7 +106,6 @@ public class DDSLoader implements AssetLoader { private boolean compressed; private boolean texture3D; private boolean grayscaleOrAlpha; - private boolean normal; private Format pixelFormat; private int bpp; private int[] sizes; @@ -117,27 +115,23 @@ public class DDSLoader implements AssetLoader { public DDSLoader() { } + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof TextureKey)) { throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); } - InputStream stream = null; - try { - stream = info.openStream(); + TextureKey textureKey = (TextureKey) info.getKey(); + try (InputStream stream = info.openStream()) { in = new LittleEndien(stream); loadHeader(); if (texture3D) { - ((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.ThreeDimensional); + textureKey.setTextureTypeHint(Texture.Type.ThreeDimensional); } else if (depth > 1) { - ((TextureKey) info.getKey()).setTextureTypeHint(Texture.Type.CubeMap); + textureKey.setTextureTypeHint(Texture.Type.CubeMap); } - ArrayList data = readData(((TextureKey) info.getKey()).isFlipY()); + ArrayList data = readData(textureKey.isFlipY()); return new Image(pixelFormat, width, height, depth, data, sizes, ColorSpace.sRGB); - } finally { - if (stream != null){ - stream.close(); - } } } @@ -150,14 +144,10 @@ public Image load(InputStream stream) throws IOException { private void loadDX10Header() throws IOException { int dxgiFormat = in.readInt(); - if (dxgiFormat == 0) { - pixelFormat = Format.ETC1; - bpp = 4; - } else { - throw new IOException("Unsupported DX10 format: " + dxgiFormat); - } + setPixelFormat(dxgiFormat); + compressed = true; - + int resDim = in.readInt(); if (resDim == DX10DIM_TEXTURE3D) { texture3D = true; @@ -171,7 +161,52 @@ private void loadDX10Header() throws IOException { } } - in.skipBytes(4); // skip reserved value + ByteUtils.skipFully(in, 4); // skip reserved value + } + + private void setPixelFormat(int dxgiFormat) throws IOException { + switch(dxgiFormat) { + case DXGIFormat.DXGI_FORMAT_UNKNOWN: + pixelFormat = Format.ETC1; + break; + case DXGIFormat.DXGI_FORMAT_BC1_UNORM: + pixelFormat = Format.DXT1; + break; + case DXGIFormat.DXGI_FORMAT_BC2_UNORM: + pixelFormat = Format.DXT3; + break; + case DXGIFormat.DXGI_FORMAT_BC3_UNORM: + pixelFormat = Format.DXT5; + break; + case DXGIFormat.DXGI_FORMAT_BC4_UNORM: + pixelFormat = Image.Format.RGTC1; + break; + case DXGIFormat.DXGI_FORMAT_BC4_SNORM: + pixelFormat = Format.SIGNED_RGTC1; + break; + case DXGIFormat.DXGI_FORMAT_BC5_UNORM: + pixelFormat = Image.Format.RGTC2; + break; + case DXGIFormat.DXGI_FORMAT_BC5_SNORM: + pixelFormat = Image.Format.SIGNED_RGTC2; + break; + case DXGIFormat.DXGI_FORMAT_BC6H_UF16: + pixelFormat = Format.BC6H_UF16; + break; + case DXGIFormat.DXGI_FORMAT_BC6H_SF16: + pixelFormat = Format.BC6H_SF16; + break; + case DXGIFormat.DXGI_FORMAT_BC7_UNORM: + pixelFormat = Format.BC7_UNORM; + break; + case DXGIFormat.DXGI_FORMAT_BC7_UNORM_SRGB: + pixelFormat = Format.BC7_UNORM_SRGB; + break; + default: + throw new IOException("Unsupported DX10 format: " + dxgiFormat); + } + + bpp = DXGIFormat.getBitsPerPixel(dxgiFormat); } /** @@ -193,13 +228,13 @@ private void loadHeader() throws IOException { pitchOrSize = in.readInt(); depth = in.readInt(); mipMapCount = in.readInt(); - in.skipBytes(44); + ByteUtils.skipFully(in, 44); pixelFormat = null; directx10 = false; readPixelFormat(); caps1 = in.readInt(); caps2 = in.readInt(); - in.skipBytes(12); + ByteUtils.skipFully(in, 12); texture3D = false; if (!directx10) { @@ -227,7 +262,7 @@ private void loadHeader() throws IOException { mipMapCount = expectedMipmaps; } else if (mipMapCount != expectedMipmaps) { // changed to warning- images often do not have the required amount, - // or specify that they have mipmaps but include only the top level.. + // or specify that they have mipmaps but include only the top level. logger.log(Level.WARNING, "Got {0} mipmaps, expected {1}", new Object[]{mipMapCount, expectedMipmaps}); } @@ -252,13 +287,13 @@ private void readPixelFormat() throws IOException { } int pfFlags = in.readInt(); - normal = is(pfFlags, DDPF_NORMAL); + is(pfFlags, DDPF_NORMAL); if (is(pfFlags, DDPF_FOURCC)) { compressed = true; int fourcc = in.readInt(); int swizzle = in.readInt(); - in.skipBytes(16); + ByteUtils.skipFully(in, 16); switch (fourcc) { case PF_DXT1: @@ -276,9 +311,6 @@ private void readPixelFormat() throws IOException { case PF_DXT5: bpp = 8; pixelFormat = Image.Format.DXT5; - if (swizzle == SWIZZLE_xGxR) { - normal = true; - } break; case PF_ATI1: bpp = 4; @@ -306,6 +338,30 @@ private void readPixelFormat() throws IOException { pixelFormat = Format.Luminance16F; grayscaleOrAlpha = true; break; + case PF_BC4S: + bpp = 4; + pixelFormat = Format.SIGNED_RGTC1; + break; + case PF_BC5S: + bpp = 8; + pixelFormat = Format.SIGNED_RGTC2; + break; + case PF_ETC_RGB_CSN: + bpp = 4; + pixelFormat = Format.ETC1; + break; + case PF_ETC2_RGB_CSN: + bpp = 4; + pixelFormat = Format.ETC1; + break; + case PF_ETC2_RGBA1_CSN: + bpp = 4; + pixelFormat = Format.ETC2_ALPHA1; + break; + case PF_ETC2_RGBA_CSN: + bpp = 8; + pixelFormat = Format.ETC2; + break; default: throw new IOException("Unknown fourcc: " + string(fourcc) + ", " + Integer.toHexString(fourcc)); } @@ -543,6 +599,7 @@ public ByteBuffer readRGB2D(boolean flip, int totalSize) throws IOException { /** * Reads a DXT compressed image from the InputStream * + * @param flip true→flip image along the Y axis, false→don't flip * @param totalSize Total size of the image in bytes, including mipmaps * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ * @throws java.io.IOException If an error occurred while reading from InputStream @@ -686,6 +743,7 @@ public ByteBuffer readRGB3D(boolean flip, int totalSize) throws IOException { /** * Reads a DXT compressed image from the InputStream * + * @param flip true→flip image along the Y axis, false→don't flip * @param totalSize Total size of the image in bytes, including mipmaps * @return ByteBuffer containing compressed DXT image in the format specified by pixelFormat_ * @throws java.io.IOException If an error occurred while reading from InputStream @@ -745,7 +803,7 @@ public ArrayList readData(boolean flip) throws IOException { totalSize += sizes[i]; } - ArrayList allMaps = new ArrayList(); + ArrayList allMaps = new ArrayList<>(); if (depth > 1 && !texture3D) { for (int i = 0; i < depth; i++) { if (compressed) { @@ -824,7 +882,7 @@ private static int byte2int(byte[] b) { } /** - * Converts a int representing a FourCC into a String + * Converts an int representing a FourCC into a String */ private static String string(int value) { StringBuilder buf = new StringBuilder(); diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXGIFormat.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXGIFormat.java new file mode 100644 index 0000000000..35ab614f09 --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXGIFormat.java @@ -0,0 +1,300 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture.plugins; + +/** + * Holds the constants for DXGI format defined in DDS file + * + * @author Toni Helenius + */ +final class DXGIFormat { + + static final int DXGI_FORMAT_UNKNOWN = 0x00; + static final int DXGI_FORMAT_R32G32B32A32_TYPELESS = 0x01; + static final int DXGI_FORMAT_R32G32B32A32_FLOAT = 0x02; + static final int DXGI_FORMAT_R32G32B32A32_UINT = 0x03; + static final int DXGI_FORMAT_R32G32B32A32_SINT = 0x04; + static final int DXGI_FORMAT_R32G32B32_TYPELESS = 0x05; + static final int DXGI_FORMAT_R32G32B32_FLOAT = 0x06; + static final int DXGI_FORMAT_R32G32B32_UINT = 0x07; + static final int DXGI_FORMAT_R32G32B32_SINT = 0x08; + static final int DXGI_FORMAT_R16G16B16A16_TYPELESS = 0x09; + static final int DXGI_FORMAT_R16G16B16A16_FLOAT = 0x0A; + static final int DXGI_FORMAT_R16G16B16A16_UNORM = 0x0B; + static final int DXGI_FORMAT_R16G16B16A16_UINT = 0x0C; + static final int DXGI_FORMAT_R16G16B16A16_SNORM = 0x0D; + static final int DXGI_FORMAT_R16G16B16A16_SINT = 0x0E; + static final int DXGI_FORMAT_R32G32_TYPELESS = 0x0F; + static final int DXGI_FORMAT_R32G32_FLOAT = 0x10; + static final int DXGI_FORMAT_R32G32_UINT = 0x11; + static final int DXGI_FORMAT_R32G32_SINT = 0x12; + static final int DXGI_FORMAT_R32G8X24_TYPELESS = 0x13; + static final int DXGI_FORMAT_D32_FLOAT_S8X24_UINT = 0x14; + static final int DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS = 0x15; + static final int DXGI_FORMAT_X32_TYPELESS_G8X24_UINT = 0x16; + static final int DXGI_FORMAT_R10G10B10A2_TYPELESS = 0x17; + static final int DXGI_FORMAT_R10G10B10A2_UNORM = 0x18; + static final int DXGI_FORMAT_R10G10B10A2_UINT = 0x19; + static final int DXGI_FORMAT_R11G11B10_FLOAT = 0x1A; + static final int DXGI_FORMAT_R8G8B8A8_TYPELESS = 0x1B; + static final int DXGI_FORMAT_R8G8B8A8_UNORM = 0x1C; + static final int DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 0x1D; + static final int DXGI_FORMAT_R8G8B8A8_UINT = 0x1E; + static final int DXGI_FORMAT_R8G8B8A8_SNORM = 0x1F; + static final int DXGI_FORMAT_R8G8B8A8_SINT = 0x20; + static final int DXGI_FORMAT_R16G16_TYPELESS = 0x21; + static final int DXGI_FORMAT_R16G16_FLOAT = 0x22; + static final int DXGI_FORMAT_R16G16_UNORM = 0x23; + static final int DXGI_FORMAT_R16G16_UINT = 0x24; + static final int DXGI_FORMAT_R16G16_SNORM = 0x25; + static final int DXGI_FORMAT_R16G16_SINT = 0x26; + static final int DXGI_FORMAT_R32_TYPELESS = 0x27; + static final int DXGI_FORMAT_D32_FLOAT = 0x28; + static final int DXGI_FORMAT_R32_FLOAT = 0x29; + static final int DXGI_FORMAT_R32_UINT = 0x2A; + static final int DXGI_FORMAT_R32_SINT = 0x2B; + static final int DXGI_FORMAT_R24G8_TYPELESS = 0x2C; + static final int DXGI_FORMAT_D24_UNORM_S8_UINT = 0x2D; + static final int DXGI_FORMAT_R24_UNORM_X8_TYPELESS = 0x2E; + static final int DXGI_FORMAT_X24_TYPELESS_G8_UINT = 0x2F; + static final int DXGI_FORMAT_R8G8_TYPELESS = 0x30; + static final int DXGI_FORMAT_R8G8_UNORM = 0x31; + static final int DXGI_FORMAT_R8G8_UINT = 0x32; + static final int DXGI_FORMAT_R8G8_SNORM = 0x33; + static final int DXGI_FORMAT_R8G8_SINT = 0x34; + static final int DXGI_FORMAT_R16_TYPELESS = 0x35; + static final int DXGI_FORMAT_R16_FLOAT = 0x36; + static final int DXGI_FORMAT_D16_UNORM = 0x37; + static final int DXGI_FORMAT_R16_UNORM = 0x38; + static final int DXGI_FORMAT_R16_UINT = 0x39; + static final int DXGI_FORMAT_R16_SNORM = 0x3A; + static final int DXGI_FORMAT_R16_SINT = 0x3B; + static final int DXGI_FORMAT_R8_TYPELESS = 0x3C; + static final int DXGI_FORMAT_R8_UNORM = 0x3D; + static final int DXGI_FORMAT_R8_UINT = 0x3E; + static final int DXGI_FORMAT_R8_SNORM = 0x3F; + static final int DXGI_FORMAT_R8_SINT = 0x40; + static final int DXGI_FORMAT_A8_UNORM = 0x41; + static final int DXGI_FORMAT_R1_UNORM = 0x42; + static final int DXGI_FORMAT_R9G9B9E5_SHAREDEXP = 0x43; + static final int DXGI_FORMAT_R8G8_B8G8_UNORM = 0x44; + static final int DXGI_FORMAT_G8R8_G8B8_UNORM = 0x45; + static final int DXGI_FORMAT_BC1_TYPELESS = 0x46; + static final int DXGI_FORMAT_BC1_UNORM = 0x47; + static final int DXGI_FORMAT_BC1_UNORM_SRGB = 0x48; + static final int DXGI_FORMAT_BC2_TYPELESS = 0x49; + static final int DXGI_FORMAT_BC2_UNORM = 0x4A; + static final int DXGI_FORMAT_BC2_UNORM_SRGB = 0x4B; + static final int DXGI_FORMAT_BC3_TYPELESS = 0x4C; + static final int DXGI_FORMAT_BC3_UNORM = 0x4D; + static final int DXGI_FORMAT_BC3_UNORM_SRGB = 0x4E; + static final int DXGI_FORMAT_BC4_TYPELESS = 0x4F; + static final int DXGI_FORMAT_BC4_UNORM = 0x50; + static final int DXGI_FORMAT_BC4_SNORM = 0x51; + static final int DXGI_FORMAT_BC5_TYPELESS = 0x52; + static final int DXGI_FORMAT_BC5_UNORM = 0x53; + static final int DXGI_FORMAT_BC5_SNORM = 0x54; + static final int DXGI_FORMAT_B5G6R5_UNORM = 0x55; + static final int DXGI_FORMAT_B5G5R5A1_UNORM = 0x56; + static final int DXGI_FORMAT_B8G8R8A8_UNORM = 0x57; + static final int DXGI_FORMAT_B8G8R8X8_UNORM = 0x58; + static final int DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM = 0x59; + static final int DXGI_FORMAT_B8G8R8A8_TYPELESS = 0x5A; + static final int DXGI_FORMAT_B8G8R8A8_UNORM_SRGB = 0x5B; + static final int DXGI_FORMAT_B8G8R8X8_TYPELESS = 0x5C; + static final int DXGI_FORMAT_B8G8R8X8_UNORM_SRGB = 0x5D; + static final int DXGI_FORMAT_BC6H_TYPELESS = 0x5E; + static final int DXGI_FORMAT_BC6H_UF16 = 0x5F; + static final int DXGI_FORMAT_BC6H_SF16 = 0x60; + static final int DXGI_FORMAT_BC7_TYPELESS = 0x61; + static final int DXGI_FORMAT_BC7_UNORM = 0x62; + static final int DXGI_FORMAT_BC7_UNORM_SRGB = 0x63; + static final int DXGI_FORMAT_AYUV = 0x64; + static final int DXGI_FORMAT_Y410 = 0x65; + static final int DXGI_FORMAT_Y416 = 0x66; + static final int DXGI_FORMAT_NV12 = 0x67; + static final int DXGI_FORMAT_P010 = 0x68; + static final int DXGI_FORMAT_P016 = 0x69; + static final int DXGI_FORMAT_420_OPAQUE = 0x6A; + static final int DXGI_FORMAT_YUY2 = 0x6B; + static final int DXGI_FORMAT_Y210 = 0x6C; + static final int DXGI_FORMAT_Y216 = 0x6D; + static final int DXGI_FORMAT_NV11 = 0x6E; + static final int DXGI_FORMAT_AI44 = 0x6F; + static final int DXGI_FORMAT_IA44 = 0x70; + static final int DXGI_FORMAT_P8 = 0x71; + static final int DXGI_FORMAT_A8P8 = 0x72; + static final int DXGI_FORMAT_B4G4R4A4_UNORM = 0x73; + static final int DXGI_FORMAT_P208 = 0x74; + static final int DXGI_FORMAT_V208 = 0x75; + static final int DXGI_FORMAT_V408 = 0x76; + static final int DXGI_FORMAT_SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 0x77; + static final int DXGI_FORMAT_SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 0x78; + static final int DXGI_FORMAT_FORCE_UINT = 0x79; + + static int getBitsPerPixel(int dxgiFormat) { + switch (dxgiFormat) { + case DXGI_FORMAT_R32G32B32A32_TYPELESS: + case DXGI_FORMAT_R32G32B32A32_FLOAT: + case DXGI_FORMAT_R32G32B32A32_UINT: + case DXGI_FORMAT_R32G32B32A32_SINT: + return 128; + + case DXGI_FORMAT_R32G32B32_TYPELESS: + case DXGI_FORMAT_R32G32B32_FLOAT: + case DXGI_FORMAT_R32G32B32_UINT: + case DXGI_FORMAT_R32G32B32_SINT: + return 96; + + case DXGI_FORMAT_R16G16B16A16_TYPELESS: + case DXGI_FORMAT_R16G16B16A16_FLOAT: + case DXGI_FORMAT_R16G16B16A16_UNORM: + case DXGI_FORMAT_R16G16B16A16_UINT: + case DXGI_FORMAT_R16G16B16A16_SNORM: + case DXGI_FORMAT_R16G16B16A16_SINT: + case DXGI_FORMAT_R32G32_TYPELESS: + case DXGI_FORMAT_R32G32_FLOAT: + case DXGI_FORMAT_R32G32_UINT: + case DXGI_FORMAT_R32G32_SINT: + case DXGI_FORMAT_R32G8X24_TYPELESS: + case DXGI_FORMAT_D32_FLOAT_S8X24_UINT: + case DXGI_FORMAT_R32_FLOAT_X8X24_TYPELESS: + case DXGI_FORMAT_X32_TYPELESS_G8X24_UINT: + return 64; + + case DXGI_FORMAT_R10G10B10A2_TYPELESS: + case DXGI_FORMAT_R10G10B10A2_UNORM: + case DXGI_FORMAT_R10G10B10A2_UINT: + case DXGI_FORMAT_R11G11B10_FLOAT: + case DXGI_FORMAT_R8G8B8A8_TYPELESS: + case DXGI_FORMAT_R8G8B8A8_UNORM: + case DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: + case DXGI_FORMAT_R8G8B8A8_UINT: + case DXGI_FORMAT_R8G8B8A8_SNORM: + case DXGI_FORMAT_R8G8B8A8_SINT: + case DXGI_FORMAT_R16G16_TYPELESS: + case DXGI_FORMAT_R16G16_FLOAT: + case DXGI_FORMAT_R16G16_UNORM: + case DXGI_FORMAT_R16G16_UINT: + case DXGI_FORMAT_R16G16_SNORM: + case DXGI_FORMAT_R16G16_SINT: + case DXGI_FORMAT_R32_TYPELESS: + case DXGI_FORMAT_D32_FLOAT: + case DXGI_FORMAT_R32_FLOAT: + case DXGI_FORMAT_R32_UINT: + case DXGI_FORMAT_R32_SINT: + case DXGI_FORMAT_R24G8_TYPELESS: + case DXGI_FORMAT_D24_UNORM_S8_UINT: + case DXGI_FORMAT_R24_UNORM_X8_TYPELESS: + case DXGI_FORMAT_X24_TYPELESS_G8_UINT: + case DXGI_FORMAT_R9G9B9E5_SHAREDEXP: + case DXGI_FORMAT_R8G8_B8G8_UNORM: + case DXGI_FORMAT_G8R8_G8B8_UNORM: + case DXGI_FORMAT_B8G8R8A8_UNORM: + case DXGI_FORMAT_B8G8R8X8_UNORM: + case DXGI_FORMAT_R10G10B10_XR_BIAS_A2_UNORM: + case DXGI_FORMAT_B8G8R8A8_TYPELESS: + case DXGI_FORMAT_B8G8R8A8_UNORM_SRGB: + case DXGI_FORMAT_B8G8R8X8_TYPELESS: + case DXGI_FORMAT_B8G8R8X8_UNORM_SRGB: + return 32; + + case DXGI_FORMAT_R8G8_TYPELESS: + case DXGI_FORMAT_R8G8_UNORM: + case DXGI_FORMAT_R8G8_UINT: + case DXGI_FORMAT_R8G8_SNORM: + case DXGI_FORMAT_R8G8_SINT: + case DXGI_FORMAT_R16_TYPELESS: + case DXGI_FORMAT_R16_FLOAT: + case DXGI_FORMAT_D16_UNORM: + case DXGI_FORMAT_R16_UNORM: + case DXGI_FORMAT_R16_UINT: + case DXGI_FORMAT_R16_SNORM: + case DXGI_FORMAT_R16_SINT: + case DXGI_FORMAT_B5G6R5_UNORM: + case DXGI_FORMAT_B5G5R5A1_UNORM: + case DXGI_FORMAT_B4G4R4A4_UNORM: + return 16; + + case DXGI_FORMAT_R8_TYPELESS: + case DXGI_FORMAT_R8_UNORM: + case DXGI_FORMAT_R8_UINT: + case DXGI_FORMAT_R8_SNORM: + case DXGI_FORMAT_R8_SINT: + case DXGI_FORMAT_A8_UNORM: + return 8; + + case DXGI_FORMAT_R1_UNORM: + return 1; + + case DXGI_FORMAT_BC1_TYPELESS: + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC1_UNORM_SRGB: + case DXGI_FORMAT_BC4_TYPELESS: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 4; + + case DXGI_FORMAT_BC2_TYPELESS: + case DXGI_FORMAT_BC2_UNORM: + case DXGI_FORMAT_BC2_UNORM_SRGB: + case DXGI_FORMAT_BC3_TYPELESS: + case DXGI_FORMAT_BC3_UNORM: + case DXGI_FORMAT_BC3_UNORM_SRGB: + case DXGI_FORMAT_BC5_TYPELESS: + case DXGI_FORMAT_BC5_UNORM: + case DXGI_FORMAT_BC5_SNORM: + case DXGI_FORMAT_BC6H_TYPELESS: + case DXGI_FORMAT_BC6H_UF16: + case DXGI_FORMAT_BC6H_SF16: + case DXGI_FORMAT_BC7_TYPELESS: + case DXGI_FORMAT_BC7_UNORM: + case DXGI_FORMAT_BC7_UNORM_SRGB: + return 8; + + default: + return 0; + } + } + + static int getBlockSize(int dxgiFormat) { + switch (dxgiFormat) { + case DXGI_FORMAT_BC1_UNORM: + case DXGI_FORMAT_BC4_UNORM: + case DXGI_FORMAT_BC4_SNORM: + return 8; + } + + return 16; + } + + } diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java index e460a75a63..10a02df4a3 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/DXTFlipper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ import com.jme3.util.BufferUtils; import java.nio.ByteBuffer; import java.nio.ByteOrder; +import java.util.logging.Logger; /** * DXTFlipper is a utility class used to flip along Y axis DXT compressed textures. @@ -43,6 +44,7 @@ * @author Kirill Vainer */ public class DXTFlipper { + private static final Logger logger = Logger.getLogger(DXTFlipper.class.getName()); private static final ByteBuffer bb = ByteBuffer.allocate(8); @@ -50,6 +52,12 @@ public class DXTFlipper { bb.order(ByteOrder.LITTLE_ENDIAN); } + /** + * A private constructor to inhibit instantiation of this class. + */ + private DXTFlipper() { + } + private static long readCode5(long data, int x, int y){ long shift = (4 * y + x) * 3; long mask = 0x7; @@ -196,10 +204,14 @@ private static void flipDXT1orDXTA3Block(byte[] block, int h){ } } - public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){ + public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format) { + if (format == Format.ETC1 || format == Format.ETC2 || format == Format.ETC2_ALPHA1) { + logger.warning("This is not a DXT format, but ETC. Use flipETC instead."); + return ETCFlipper.flipETC(img, w, h, format); + } int originalLimit = img.limit(); - int blocksX = (int) FastMath.ceil((float)w / 4f); - int blocksY = (int) FastMath.ceil((float)h / 4f); + int blocksX = (int) FastMath.ceil(w / 4f); + int blocksY = (int) FastMath.ceil(h / 4f); int type; switch (format){ @@ -214,13 +226,15 @@ public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){ type = 3; break; case RGTC2: + case SIGNED_RGTC2: type = 4; break; case RGTC1: + case SIGNED_RGTC1: type = 5; break; default: - throw new IllegalArgumentException(); + throw new IllegalArgumentException("No flip support for texture format " + format); } // DXT1 uses 8 bytes per block, @@ -235,7 +249,7 @@ public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){ byte[] colorBlock = new byte[8]; byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null; for (int x = 0; x < blocksX; x++){ - // prepeare for block reading + // prepare for block reading int blockByteOffset = x * bpb; img.position(blockByteOffset); img.limit(blockByteOffset + bpb); @@ -270,7 +284,7 @@ public static ByteBuffer flipDXT(ByteBuffer img, int w, int h, Format format){ byte[] alphaBlock = type != 1 && type != 5 ? new byte[8] : null; for (int y = 0; y < blocksY; y++){ for (int x = 0; x < blocksX; x++){ - // prepeare for block reading + // prepare for block reading int blockIdx = y * blocksX + x; int blockByteOffset = blockIdx * bpb; diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ETCFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ETCFlipper.java new file mode 100644 index 0000000000..0809a5f7bb --- /dev/null +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ETCFlipper.java @@ -0,0 +1,19 @@ +package com.jme3.texture.plugins; + +import java.nio.ByteBuffer; +import java.util.logging.Logger; + +import com.jme3.texture.Image.Format; + +/** + * A place-holder for future implementation of ETC texture flipping. + * +*/ +public class ETCFlipper { + private static final Logger logger = Logger.getLogger(ETCFlipper.class.getName()); + + public static ByteBuffer flipETC(ByteBuffer img, int w, int h, Format format) { + logger.warning("ETC texture flipping is not supported yet"); + return img; + } +} diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java index 50f7fe5c06..e93c3b038f 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/HDRLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 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.AssetLoader; import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; import com.jme3.math.FastMath; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; @@ -113,10 +114,6 @@ public static void convertRGBEtoFloat3(byte[] rgbe, float[] rgbf){ rgbf[2] = B * e; } - private short flip(int in){ - return (short) ((in << 8 & 0xFF00) | (in >> 8)); - } - private void writeRGBE(byte[] rgbe){ if (writeRGBE){ dataStore.put(rgbe); @@ -140,7 +137,7 @@ private String readString(InputStream is) throws IOException{ } private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ - // must deocde RLE data into temp buffer before converting to float + // must decode RLE data into temp buffer before converting to float if (rleTempBuffer == null){ rleTempBuffer = BufferUtils.createByteBuffer(width * 4); }else{ @@ -149,7 +146,7 @@ private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ rleTempBuffer = BufferUtils.createByteBuffer(width * 4); } - // read each component separately + // read each component separately for (int i = 0; i < 4; i++) { // read WIDTH bytes for the channel for (int j = 0; j < width;) { @@ -161,7 +158,7 @@ private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ rleTempBuffer.put( (j++) * 4 + i , (byte)val); //scanline[j++][i] = val; } - } else { // non-run + } else { // non-run while ((code--) != 0) { int val = in.read(); rleTempBuffer.put( (j++) * 4 + i, (byte)val); @@ -184,16 +181,14 @@ private boolean decodeScanlineRLE(InputStream in, int width) throws IOException{ return true; } - private boolean decodeScanlineUncompressed(InputStream in, int width) throws IOException{ + private void decodeScanlineUncompressed(InputStream in, int width) throws IOException{ byte[] rgbe = new byte[4]; for (int i = 0; i < width; i+=3){ - if (in.read(rgbe) < 1) - return false; - + ByteUtils.readFully(in, rgbe); writeRGBE(rgbe); } - return true; + } private void decodeScanline(InputStream in, int width) throws IOException{ @@ -204,7 +199,7 @@ private void decodeScanline(InputStream in, int width) throws IOException{ // check format byte[] data = new byte[4]; - in.read(data); + ByteUtils.readFully(in, data); if (data[0] != 0x02 || data[1] != 0x02 || (data[2] & 0x80) != 0){ // not RLE data decodeScanlineUncompressed(in, width-1); @@ -222,7 +217,6 @@ private void decodeScanline(InputStream in, int width) throws IOException{ public Image load(InputStream in, boolean flipY) throws IOException{ float gamma = -1f; float exposure = -1f; - float[] colorcorr = new float[]{ -1f, -1f, -1f }; int width = -1, height = -1; boolean verifiedFormat = false; @@ -313,6 +307,7 @@ public Image load(InputStream in, boolean flipY) throws IOException{ return new Image(pixelFormat, width, height, dataStore, ColorSpace.Linear); } + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof TextureKey)) throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java index e74a70783d..0d5f39d84b 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ImageFlipper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,12 @@ */ public class ImageFlipper { + /** + * A private constructor to inhibit instantiation of this class. + */ + private ImageFlipper() { + } + public static void flipImage(Image img, int index){ if (img.getFormat().isCompressed()) throw new UnsupportedOperationException("Flipping compressed " + diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java index a94641f7eb..d5c8df7b39 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/PFMLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 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.AssetLoader; import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.image.ColorSpace; @@ -99,7 +100,7 @@ private Image load(InputStream in, boolean needYFlip) throws IOException{ String scaleStr = readString(in); float scale = Float.parseFloat(scaleStr); ByteOrder order = scale < 0 ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN; - boolean needEndienFlip = order != ByteOrder.nativeOrder(); + boolean needEndianFlip = order != ByteOrder.nativeOrder(); // make sure all unnecessary stuff gets deleted from heap // before allocating large amount of memory @@ -115,14 +116,9 @@ private Image load(InputStream in, boolean needYFlip) throws IOException{ if (!needYFlip) imageData.position(scanLineBytes * y); - int read = 0; - int off = 0; - do { - read = in.read(scanline, off, scanline.length - off); - off += read; - } while (read > 0); + ByteUtils.readFully(in, scanline); - if (needEndienFlip){ + if (needEndianFlip){ flipScanline(scanline); } @@ -133,6 +129,7 @@ private Image load(InputStream in, boolean needYFlip) throws IOException{ return new Image(format, width, height, imageData, null, ColorSpace.Linear); } + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof TextureKey)) throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java index 4ecb60b8ff..6ef6272ea0 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/TGALoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 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.AssetLoader; import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; import com.jme3.math.FastMath; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; @@ -71,6 +72,7 @@ public final class TGALoader implements AssetLoader { // 11 - run-length encoded, black and white image public static final int TYPE_BLACKANDWHITE_RLE = 11; + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof TextureKey)) { throw new IllegalArgumentException("Texture assets must be loaded using a TextureKey"); @@ -101,7 +103,7 @@ public Object load(AssetInfo info) throws IOException { * Flip the image vertically * @return Image object that contains the * image, either as a RGB888 or RGBA8888 - * @throws java.io.IOException + * @throws java.io.IOException if an I/O error occurs */ public static Image load(InputStream in, boolean flip) throws IOException { boolean flipH = false; @@ -157,7 +159,7 @@ public static Image load(InputStream in, boolean flip) throws IOException { // Skip image ID if (idLength > 0) { - dis.skip(idLength); + ByteUtils.skipFully((InputStream) dis, idLength); } ColorMapEntry[] cMapEntries = null; @@ -167,7 +169,7 @@ public static Image load(InputStream in, boolean flip) throws IOException { int bitsPerColor = Math.min(cMapDepth / 3, 8); byte[] cMapData = new byte[bytesInColorMap]; - dis.read(cMapData); + ByteUtils.readFully((InputStream) dis, cMapData); // Only go to the trouble of constructing the color map // table if this is declared a color mapped image. @@ -298,7 +300,7 @@ public static Image load(InputStream in, boolean flip) throws IOException { // Get the number of pixels the next chunk covers (either packed or unpacked) int count = dis.readByte(); if ((count & 0x80) != 0) { - // Its an RLE packed block - use the following 1 pixel for the next pixels + // It's an RLE-packed block: use the following pixel for the next pixels. count &= 0x07f; j += count; blue = dis.readByte(); @@ -337,7 +339,7 @@ public static Image load(InputStream in, boolean flip) throws IOException { // Get the number of pixels the next chunk covers (either packed or unpacked) int count = dis.readByte(); if ((count & 0x80) != 0) { - // Its an RLE packed block - use the following 1 pixel for the next pixels + // It's an RLE-packed block: use the following pixel for the next pixels. count &= 0x07f; j += count; blue = dis.readByte(); @@ -374,7 +376,7 @@ public static Image load(InputStream in, boolean flip) throws IOException { // Get the number of pixels the next chunk covers (either packed or unpacked) int count = dis.readByte(); if ((count & 0x80) != 0) { - // Its an RLE packed block - use the following 1 pixel for the next pixels + // It's an RLE-packed block: use the following pixel for the next pixels. count &= 0x07f; j += count; data[1] = dis.readByte(); diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java index 2aaa709cd0..0b3d25dbc4 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 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.AssetLoader; import com.jme3.asset.TextureKey; +import com.jme3.export.binary.ByteUtils; import com.jme3.renderer.Caps; import com.jme3.renderer.opengl.GLImageFormat; import com.jme3.renderer.opengl.GLImageFormats; @@ -55,7 +56,7 @@ /** * * A KTX file loader - * KTX file format is an image container defined by the Kronos group + * KTX file format is an image container defined by the Khronos group * See specs here https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/ * * This loader doesn't support compressed files yet. @@ -95,7 +96,7 @@ private Image load(InputStream stream) { DataInput in = new DataInputStream(stream); try { - stream.read(fileId, 0, 12); + ByteUtils.readFully(stream, fileId); if (!checkFileIdentifier(fileId)) { throw new IllegalArgumentException("Unrecognized ktx file identifier : " + new String(fileId) + " should be " + new String(fileIdentifier)); } @@ -141,7 +142,7 @@ private Image load(InputStream stream) { pixelReader = new SrTuRoPixelReader(); } - //some of the values may be 0 we need them at least to be 1 + // Some of the values may be 0. We need them to be at least 1. pixelDepth = Math.max(1, pixelDepth); numberOfArrayElements = Math.max(1, numberOfArrayElements); numberOfFaces = Math.max(1, numberOfFaces); @@ -166,9 +167,9 @@ private Image load(InputStream stream) { int offset = 0; //iterate over data for (int mipLevel = 0; mipLevel < numberOfMipmapLevels; mipLevel++) { - //size of the image in bytes. - //this value is bogus in many example, when using mipmaps. - //instead we compute the theoretical size and display a warning when it does not match. + // Size of the image in bytes. + // This value is bogus in many examples when using mipmaps. + // We compute the theoretical size and display a warning if it does not match. int fileImageSize = in.readInt(); int width = Math.max(1, pixelWidth >> mipLevel); @@ -193,13 +194,13 @@ private Image load(InputStream stream) { } //cube padding if (numberOfFaces == 6 && numberOfArrayElements == 0) { - in.skipBytes(3 - ((nbPixelRead + 3) % 4)); + ByteUtils.skipFully(in, 3 - ((nbPixelRead + 3) % 4)); } } } //mip padding log.log(Level.FINE, "skipping {0}", (3 - ((imageSize + 3) % 4))); - in.skipBytes(3 - ((imageSize + 3) % 4)); + ByteUtils.skipFully(in, 3 - ((imageSize + 3) % 4)); offset+=imageSize; } //there are loaded mip maps we set the sizes @@ -259,7 +260,7 @@ private int computeBuffersSize(int numberOfMipmapLevels, int pixelWidth, int pix * @return */ private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFormat, int pixelWidth, int pixelHeight, int depth) { - ArrayList imageData = new ArrayList(nbSlices); + ArrayList imageData = new ArrayList<>(nbSlices); for (int i = 0; i < nbSlices; i++) { imageData.add(BufferUtils.createByteBuffer(byteBuffersSize)); } @@ -270,10 +271,11 @@ private Image createImage(int nbSlices, int byteBuffersSize, Image.Format imgFor /** * Parse the file metaData to select the PixelReader that suits the file * coordinates orientation - * @param bytesOfKeyValueData - * @param in - * @return - * @throws IOException + * + * @param bytesOfKeyValueData number of bytes to read + * @param in the input stream (not null) + * @return a new instance or null + * @throws IOException from the input stream */ private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws IOException { PixelReader pixelReader = null; @@ -304,7 +306,7 @@ private PixelReader parseMetaData(int bytesOfKeyValueData, DataInput in) throws //padding int padding = 3 - ((keyAndValueByteSize + 3) % 4); if (padding > 0) { - in.skipBytes(padding); + ByteUtils.skipFully(in, padding); } i += 4 + keyAndValueByteSize + padding; } diff --git a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java index fbbdc8972d..116ec05df2 100644 --- a/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java +++ b/jme3-core/src/plugins/java/com/jme3/texture/plugins/ktx/KTXWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,7 +70,8 @@ public class KTXWriter { /** * Creates a KTXWriter that will write files in the given path - * @param path + * + * @param path the desired filesystem path (alias created) */ public KTXWriter(String path) { filePath = path; @@ -116,7 +117,7 @@ public void write(Image image, Class textureType, String file out.writeInt(1); //glFormat out.writeInt(format.format); - //glInernalFormat + //glInternalFormat out.writeInt(format.internalFormat); //glBaseInternalFormat out.writeInt(format.format); @@ -228,9 +229,10 @@ public void write(Image image, Class textureType, String file /** * writes padding data to the output padding times. - * @param padding - * @param out - * @throws IOException + * + * @param padding the number of bytes to be written + * @param out the output stream + * @throws IOException from the output stream */ private void pad(int padding, DataOutput out) throws IOException { //padding diff --git a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java index 331784352d..4813747695 100644 --- a/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java +++ b/jme3-core/src/test/java/com/jme3/anim/AnimComposerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,6 +31,10 @@ */ package com.jme3.anim; +import com.jme3.anim.tween.action.Action; +import com.jme3.util.clone.Cloner; +import java.util.Set; +import java.util.TreeSet; import org.junit.Assert; import org.junit.Test; @@ -55,6 +59,36 @@ public void testGetAnimClipsNames() { Assert.assertEquals(0, composer.getAnimClipsNames().size()); } + @Test + public void testMakeLayer() { + AnimComposer composer = new AnimComposer(); + + final String layerName = "TestLayer"; + + composer.makeLayer(layerName, null); + + final Set layers = new TreeSet<>(); + layers.add("Default"); + layers.add(layerName); + + Assert.assertNotNull(composer.getLayer(layerName)); + Assert.assertEquals(layers, composer.getLayerNames()); + } + + @Test + public void testMakeAction() { + AnimComposer composer = new AnimComposer(); + + final String animName = "TestClip"; + + final AnimClip anim = new AnimClip(animName); + composer.addAnimClip(anim); + + final Action action = composer.makeAction(animName); + + Assert.assertNotNull(action); + } + @Test(expected = UnsupportedOperationException.class) public void testGetAnimClipsIsNotModifiable() { AnimComposer composer = new AnimComposer(); @@ -69,4 +103,26 @@ public void testGetAnimClipsNamesIsNotModifiable() { composer.getAnimClipsNames().add("test"); } + @Test + public void testHasDefaultLayer() { + AnimComposer composer = new AnimComposer(); + + AnimLayer defaultLayer = composer.getLayer("Default"); + Assert.assertNotNull(defaultLayer); + } + + @Test + /** + * https://github.com/jMonkeyEngine/jmonkeyengine/issues/2341 + * + */ + public void testMissingDefaultLayerIssue2341() { + AnimComposer composer = new AnimComposer(); + composer.removeLayer(AnimComposer.DEFAULT_LAYER); + + AnimComposer clone = (AnimComposer) composer.jmeClone(); + clone.cloneFields(new Cloner(), composer); + Assert.assertNotNull(clone.getLayer(AnimComposer.DEFAULT_LAYER)); + } + } diff --git a/jme3-core/src/test/java/com/jme3/anim/ArmatureMaskTest.java b/jme3-core/src/test/java/com/jme3/anim/ArmatureMaskTest.java new file mode 100644 index 0000000000..e68a56c95f --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/anim/ArmatureMaskTest.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2021 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; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Test constructors and modification methods of the ArmatureMask class. + */ +public class ArmatureMaskTest { + + final private Joint j0 = createJoint("j0", 0); // leaf + final private Joint j1 = createJoint("j1", 1); // leaf + final private Joint j2 = createJoint("j2", 2, j0, j1); + final private Joint j3 = createJoint("j3", 3, j2); // root + final private Joint j4 = createJoint("j4", 4); // leaf + final private Joint j5 = createJoint("j5", 5, j4); // root + final private Joint j6 = createJoint("j6", 6); // root and leaf + final private Joint[] jointList = {j0, j1, j2, j3, j4, j5, j6}; + final private Armature arm = new Armature(jointList); + + private Joint createJoint(String name, int id, Joint... children) { + Joint result = new Joint(name); + result.setId(id); + for (Joint child : children) { + result.addChild(child); + } + return result; + } + + /** + * Test various ways to instantiate a mask that affects all joints. + */ + @Test + public void testMaskAll() { + ArmatureMask[] maskArray = new ArmatureMask[5]; + maskArray[0] = new ArmatureMask(arm); + maskArray[1] = ArmatureMask.createMask(arm, + "j0", "j1", "j2", "j3", "j4", "j5", "j6"); + + maskArray[2] = ArmatureMask.createMask(arm, "j3"); + maskArray[2].addFromJoint(arm, "j5"); + maskArray[2].addFromJoint(arm, "j6"); + + maskArray[3] = ArmatureMask.createMask(arm, "j3") + .addAncestors(j4) + .addAncestors(j6); + + maskArray[4] = ArmatureMask.createMask(arm, "j3"); + maskArray[4].addBones(arm, "j4", "j5", "j6"); + + for (ArmatureMask testMask : maskArray) { + for (Joint testJoint : jointList) { + Assert.assertTrue(testMask.contains(testJoint)); + } + } + } + + /** + * Instantiate masks that affect no joints. + */ + @Test + public void testMaskNone() { + ArmatureMask[] maskArray = new ArmatureMask[4]; + maskArray[0] = new ArmatureMask(); + maskArray[1] = ArmatureMask.createMask(arm); + + maskArray[2] = ArmatureMask.createMask(arm, "j2") + .removeAncestors(j0) + .removeAncestors(j1); + + maskArray[3] = ArmatureMask.createMask(arm, "j0", "j1") + .removeJoints(arm, "j0", "j1"); + + for (ArmatureMask testMask : maskArray) { + for (Joint testJoint : jointList) { + Assert.assertFalse(testMask.contains(testJoint)); + } + } + } + + /** + * Instantiate masks that affect only j1 and j2. + */ + @Test + public void testMask12() { + ArmatureMask[] maskArray = new ArmatureMask[4]; + maskArray[0] = new ArmatureMask(); + maskArray[0].addBones(arm, "j1", "j2"); + + maskArray[1] = ArmatureMask.createMask(arm, "j3") + .removeJoints(arm, "j0", "j3"); + + maskArray[2] = new ArmatureMask() + .addAncestors(j1) + .removeAncestors(j3); + + ArmatureMask mask0 = ArmatureMask.createMask(arm, "j0"); + maskArray[3] = ArmatureMask.createMask(arm, "j2") + .remove(mask0); + + for (ArmatureMask testMask : maskArray) { + for (Joint testJoint : jointList) { + if (testJoint == j1 || testJoint == j2) { + Assert.assertTrue(testMask.contains(testJoint)); + } else { + Assert.assertFalse(testMask.contains(testJoint)); + } + } + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/anim/JointCloneTest.java b/jme3-core/src/test/java/com/jme3/anim/JointCloneTest.java new file mode 100644 index 0000000000..34d6a3a677 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/anim/JointCloneTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2021 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; + +import com.jme3.util.clone.Cloner; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cloning a Joint. + * + * @author Stephen Gold + */ +public class JointCloneTest { + + /** + * Make sure the initial transform gets cloned. This was issue 1469 at + * GitHub. + */ + @Test + public void testInitialTransform() { + Joint testJoint = new Joint("testJoint"); + Assert.assertTrue(testJoint.getInitialTransform().isIdentity()); + + Joint clone = Cloner.deepClone(testJoint); + clone.getInitialTransform().setScale(2f); + + Assert.assertTrue(testJoint.getInitialTransform().isIdentity()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/anim/tween/action/ClipActionTest.java b/jme3-core/src/test/java/com/jme3/anim/tween/action/ClipActionTest.java new file mode 100644 index 0000000000..cbcdc416ea --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/anim/tween/action/ClipActionTest.java @@ -0,0 +1,80 @@ +/* + * 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.anim.tween.action; + +import com.jme3.anim.AnimClip; +import org.junit.Test; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; + +/** + * Test for ClipAction. + * + * @author Saichand Chowdary + */ +public class ClipActionTest { + + /** + * Test to verify setTransitionLength on BlendableAction does not accept negative values. + */ + @Test + public void testSetTransitionLength_negativeInput_exceptionThrown() { + AnimClip animClip = new AnimClip("clip"); + ClipAction clipAction = new ClipAction(animClip); + IllegalArgumentException thrown = assertThrows(IllegalArgumentException.class, + () -> clipAction.setTransitionLength(-1)); + assertEquals("transitionLength must be greater than or equal to 0", thrown.getMessage()); + } + + /** + * Test to verify setTransitionLength on BlendableAction accepts zero. + */ + @Test + public void testSetTransitionLength_zeroInput_noExceptionThrown() { + AnimClip animClip = new AnimClip("clip"); + ClipAction clipAction = new ClipAction(animClip); + clipAction.setTransitionLength(0); + assertEquals(0, clipAction.getTransitionLength(), 0); + } + + /** + * Test to verify setTransitionLength on BlendableAction accepts positive values. + */ + @Test + public void testSetTransitionLength_positiveNumberInput_noExceptionThrown() { + AnimClip animClip = new AnimClip("clip"); + ClipAction clipAction = new ClipAction(animClip); + clipAction.setTransitionLength(1.23d); + assertEquals(1.23d, clipAction.getTransitionLength(), 0); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/asset/TestLocators.java b/jme3-core/src/test/java/com/jme3/asset/TestLocators.java new file mode 100644 index 0000000000..ae2e1996c0 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/asset/TestLocators.java @@ -0,0 +1,68 @@ +package com.jme3.asset; + +import com.jme3.asset.plugins.ClasspathLocator; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.audio.plugins.WAVLoader; +import com.jme3.system.JmeSystem; +import com.jme3.texture.plugins.AWTLoader; +import org.junit.Assert; +import org.junit.Test; + +public class TestLocators { + + @Test + public void testAbsoluteLocators() { + AssetManager am = JmeSystem.newAssetManager(TestLocators.class.getResource("/com/jme3/asset/Desktop.cfg")); + am.registerLocator("/", ClasspathLocator.class); + am.registerLoader(WAVLoader.class, "wav"); + am.registerLoader(AWTLoader.class, "jpg"); + + Assert.assertNotNull(am.loadAudio("Sound/Effects/Gun.wav")); + Assert.assertNotNull(am.loadTexture("Textures/Terrain/Pond/Pond.jpg")); + } + + /** + * Demonstrates loading a file from a custom {@link AssetLoader} + */ + @Test + public void testCustomLoader() { + AssetManager am = new DesktopAssetManager(true); + am.registerLocator("/", ClasspathLocator.class); + am.registerLoader(TextLoader.class, "fnt"); + String result = (String)am.loadAsset("Interface/Fonts/Console.fnt"); + Assert.assertTrue(result.startsWith("info face=\"Lucida Console\" size=11 bold=0 italic=0 charset=\"\" unicode=1" + + " stretchH=100 smooth=1 aa=1 padding=0,0,0,0 spacing=1,1 outline=0")); + } + + @Test + public void testManyLocators() { + AssetManager am = new DesktopAssetManager(true); + am.registerLocator( + "https://github.com/jMonkeyEngine/wiki/raw/master/docs/modules/tutorials/assets/images/beginner/", + UrlLocator.class); + + am.registerLocator("../jme3-examples/town.zip", ZipLocator.class); + am.registerLocator( + "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/wildhouse.zip", + HttpZipLocator.class); + am.registerLocator("/", ClasspathLocator.class); + + // Try loading from jme3-core resources using the ClasspathLocator. + Assert.assertNotNull("Failed to load from classpath", + am.locateAsset(new AssetKey<>("Interface/Fonts/Default.fnt"))); + + // Try loading from the "town.zip" file using the ZipLocator. + Assert.assertNotNull("Failed to load from town.zip file", + am.locateAsset(new ModelKey("casaamarela.jpg"))); + + // Try loading from the Google Code Archive website using the HttpZipLocator. + Assert.assertNotNull("Failed to load from wildhouse.zip on googleapis.com", + am.locateAsset(new ModelKey("glasstile2.png"))); + + // Try loading from the GitHub website using the UrlLocator. + Assert.assertNotNull("Failed to load from HTTP", + am.locateAsset(new TextureKey("beginner-physics.png"))); + } +} diff --git a/jme3-core/src/test/java/com/jme3/asset/TextLoader.java b/jme3-core/src/test/java/com/jme3/asset/TextLoader.java new file mode 100644 index 0000000000..4e65a3f7dc --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/asset/TextLoader.java @@ -0,0 +1,23 @@ +package com.jme3.asset; + +import java.util.Scanner; + +/** + * An example implementation of {@link AssetLoader} to load text + * files as strings. + */ +public class TextLoader implements AssetLoader { + @Override + public Object load(AssetInfo assetInfo) { + Scanner scan = new Scanner(assetInfo.openStream()); + StringBuilder sb = new StringBuilder(); + try { + while (scan.hasNextLine()) { + sb.append(scan.nextLine()).append('\n'); + } + } finally { + scan.close(); + } + return sb.toString(); + } +} diff --git a/jme3-core/src/test/java/com/jme3/audio/AudioFilterTest.java b/jme3-core/src/test/java/com/jme3/audio/AudioFilterTest.java new file mode 100644 index 0000000000..5ccf79d6c0 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/audio/AudioFilterTest.java @@ -0,0 +1,62 @@ +package com.jme3.audio; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the Filter class. + * + * @author capdevon + */ +public class AudioFilterTest { + + /** + * Tests serialization and de-serialization of a {@code LowPassFilter}. + */ + @Test + public void testSaveAndLoad_LowPassFilter() { + AssetManager assetManager = new DesktopAssetManager(true); + + LowPassFilter f = new LowPassFilter(.5f, .5f); + LowPassFilter copy = BinaryExporter.saveAndLoad(assetManager, f); + + float delta = 0.001f; + Assert.assertEquals(f.getVolume(), copy.getVolume(), delta); + Assert.assertEquals(f.getHighFreqVolume(), copy.getHighFreqVolume(), delta); + } + + /** + * Tests serialization and de-serialization of a {@code HighPassFilter}. + */ + @Test + public void testSaveAndLoad_HighPassFilter() { + AssetManager assetManager = new DesktopAssetManager(true); + + HighPassFilter f = new HighPassFilter(.5f, .5f); + HighPassFilter copy = BinaryExporter.saveAndLoad(assetManager, f); + + float delta = 0.001f; + Assert.assertEquals(f.getVolume(), copy.getVolume(), delta); + Assert.assertEquals(f.getLowFreqVolume(), copy.getLowFreqVolume(), delta); + } + + /** + * Tests serialization and de-serialization of a {@code BandPassFilter}. + */ + @Test + public void testSaveAndLoad_BandPassFilter() { + AssetManager assetManager = new DesktopAssetManager(true); + + BandPassFilter f = new BandPassFilter(.5f, .5f, .5f); + BandPassFilter copy = BinaryExporter.saveAndLoad(assetManager, f); + + float delta = 0.001f; + Assert.assertEquals(f.getVolume(), copy.getVolume(), delta); + Assert.assertEquals(f.getHighFreqVolume(), copy.getHighFreqVolume(), delta); + Assert.assertEquals(f.getLowFreqVolume(), copy.getLowFreqVolume(), delta); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/audio/AudioNodeTest.java b/jme3-core/src/test/java/com/jme3/audio/AudioNodeTest.java new file mode 100644 index 0000000000..e7e32012f8 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/audio/AudioNodeTest.java @@ -0,0 +1,48 @@ +package com.jme3.audio; + +import com.jme3.asset.AssetManager; +import com.jme3.math.Vector3f; +import com.jme3.system.JmeSystem; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the {@code AudioNode} class. + * + * @author capdevon + */ +public class AudioNodeTest { + + @Test + public void testAudioNodeClone() { + AssetManager assetManager = JmeSystem.newAssetManager(AudioNodeTest.class.getResource("/com/jme3/asset/Desktop.cfg")); + + AudioNode audio = new AudioNode(assetManager, + "Sound/Effects/Bang.wav", AudioData.DataType.Buffer); + audio.setDirection(new Vector3f(0, 1, 0)); + audio.setVelocity(new Vector3f(1, 1, 1)); + audio.setDryFilter(new LowPassFilter(1f, .1f)); + audio.setReverbFilter(new LowPassFilter(.5f, .5f)); + + AudioNode clone = audio.clone(); + + Assert.assertNotNull(clone.previousWorldTranslation); + Assert.assertNotSame(audio.previousWorldTranslation, clone.previousWorldTranslation); + Assert.assertEquals(audio.previousWorldTranslation, clone.previousWorldTranslation); + + Assert.assertNotNull(clone.getDirection()); + Assert.assertNotSame(audio.getDirection(), clone.getDirection()); + Assert.assertEquals(audio.getDirection(), clone.getDirection()); + + Assert.assertNotNull(clone.getVelocity()); + Assert.assertNotSame(audio.getVelocity(), clone.getVelocity()); + Assert.assertEquals(audio.getVelocity(), clone.getVelocity()); + + Assert.assertNotNull(clone.getDryFilter()); + Assert.assertNotSame(audio.getDryFilter(), clone.getDryFilter()); + + Assert.assertNotNull(clone.getReverbFilter()); + Assert.assertNotSame(audio.getReverbFilter(), clone.getReverbFilter()); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java new file mode 100644 index 0000000000..7a48e553b6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingBox.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for the BoundingBox class. + * + * @author Stephen Gold + */ +public class TestBoundingBox { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), -0f, 1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 0f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, -0f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bb1, bb1.clone()); + Assert.assertEquals(bb2, bb2.clone()); + Assert.assertEquals(bb3, bb3.clone()); + Assert.assertEquals(bb4, bb4.clone()); + Assert.assertEquals(bb5, bb5.clone()); + Assert.assertEquals(bb6, bb6.clone()); + + Assert.assertNotEquals(bb1, bb2); // because their extents differ + Assert.assertNotEquals(bb3, bb4); // because their centers differ + Assert.assertEquals(bb5, bb6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingBox bb1 = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1f, 2f); + BoundingBox bb2 + = new BoundingBox(new Vector3f(3f, 4f, 5f), 0f, 1.1f, 2f); + + BoundingBox bb3 = new BoundingBox(new Vector3f(3f, 4f, 2f), 9f, 8f, 7f); + BoundingBox bb4 + = new BoundingBox(new Vector3f(3f, 3.9f, 2f), 9f, 8f, 7f); + + BoundingBox bb5 = new BoundingBox(new Vector3f(4f, 5f, 6f), 9f, 8f, 7f); + BoundingBox bb6 = (BoundingBox) bb5.clone(); + bb6.setCheckPlane(1); + + Assert.assertFalse(bb1.isSimilar(bb2, 0.09999f)); + Assert.assertTrue(bb1.isSimilar(bb2, 0.10001f)); + + Assert.assertFalse(bb3.isSimilar(bb4, 0.09999f)); + Assert.assertTrue(bb3.isSimilar(bb4, 0.10001f)); + + Assert.assertTrue(bb5.isSimilar(bb6, 0f)); // check planes are ignored + } +} diff --git a/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java new file mode 100644 index 0000000000..4c0e760bc2 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/bounding/TestBoundingSphere.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.bounding; + +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cases for the BoundingSphere class. + * + * @author Stephen Gold + */ +public class TestBoundingSphere { + /** + * Verify that equals() behaves as expected. + */ + @Test + public void testEquals() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(-0f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 0f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, -0f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + // Clones are equal to their base instances: + Assert.assertEquals(bs1, bs1.clone()); + Assert.assertEquals(bs2, bs2.clone()); + Assert.assertEquals(bs3, bs3.clone()); + Assert.assertEquals(bs4, bs4.clone()); + Assert.assertEquals(bs5, bs5.clone()); + Assert.assertEquals(bs6, bs6.clone()); + + Assert.assertNotEquals(bs1, bs2); // because their radii differ + Assert.assertNotEquals(bs3, bs4); // because their centers differ + Assert.assertEquals(bs5, bs6); // because check planes are ignored + } + + /** + * Verify that isSimilar() behaves as expected. + */ + @Test + public void testIsSimilar() { + BoundingSphere bs1 = new BoundingSphere(0f, new Vector3f(3f, 4f, 5f)); + BoundingSphere bs2 = new BoundingSphere(0.1f, new Vector3f(3f, 4f, 5f)); + + BoundingSphere bs3 = new BoundingSphere(1f, new Vector3f(3f, 4f, 2f)); + BoundingSphere bs4 = new BoundingSphere(1f, new Vector3f(3f, 3.9f, 2f)); + + BoundingSphere bs5 = new BoundingSphere(2f, new Vector3f(4f, 5f, 6f)); + BoundingSphere bs6 = (BoundingSphere) bs5.clone(); + bs6.setCheckPlane(1); + + Assert.assertFalse(bs1.isSimilar(bs2, 0.09999f)); + Assert.assertTrue(bs1.isSimilar(bs2, 0.10001f)); + + Assert.assertFalse(bs3.isSimilar(bs4, 0.09999f)); + Assert.assertTrue(bs3.isSimilar(bs4, 0.10001f)); + + Assert.assertTrue(bs5.isSimilar(bs6, 0f)); // check planes are ignored + } + + /** + * Verify that an infinite bounding sphere can be merged with a very + * eccentric bounding box without producing NaNs. This was issue #1459 at + * GitHub. + */ + @Test + public void testIssue1459() { + Vector3f boxCenter = new Vector3f(-92f, 3.3194322e29f, 674.89886f); + BoundingBox boundingBox = new BoundingBox(boxCenter, + 1.0685959f, 3.3194322e29f, 2.705017f); + + Vector3f sphCenter = new Vector3f(0f, 0f, 0f); + float radius = Float.POSITIVE_INFINITY; + BoundingSphere boundingSphere = new BoundingSphere(radius, sphCenter); + + boundingSphere.mergeLocal(boundingBox); + + Vector3f copyCenter = new Vector3f(); + boundingSphere.getCenter(copyCenter); + Assert.assertTrue(Vector3f.isValidVector(copyCenter)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java b/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java new file mode 100644 index 0000000000..7fa2c71f58 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/cinematic/MotionPathTest.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025 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.cinematic; + +import com.jme3.math.Vector3f; +import com.jme3.util.clone.Cloner; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link MotionPath} class works. + * + * @author Stephen Gold + */ +public class MotionPathTest { + + /** + * Verifies that MotionPath cloning works. + */ + @Test + public void cloneMotionPath() { + MotionPath original = new MotionPath(); + original.setCycle(true); + original.addWayPoint(new Vector3f(20, 3, 0)); + original.addWayPoint(new Vector3f(0, 3, 20)); + original.addWayPoint(new Vector3f(-20, 3, 0)); + original.addWayPoint(new Vector3f(0, 3, -20)); + original.setCurveTension(0.83f); + + MotionPath clone = Cloner.deepClone(original); + + // Verify that the clone is non-null and distinct from the original: + Assert.assertNotNull(clone); + Assert.assertTrue(clone != original); + + // Compare the return values of various getters: + Assert.assertEquals( + clone.getCurveTension(), original.getCurveTension(), 0f); + Assert.assertEquals(clone.getLength(), original.getLength(), 0f); + Assert.assertEquals(clone.getNbWayPoints(), original.getNbWayPoints()); + Assert.assertEquals( + clone.getPathSplineType(), original.getPathSplineType()); + Assert.assertEquals(clone.getWayPoint(0), original.getWayPoint(0)); + Assert.assertEquals(clone.isCycle(), original.isCycle()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java b/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java index 506a1732da..033b673100 100644 --- a/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java +++ b/jme3-core/src/test/java/com/jme3/collision/BoundingCollisionTest.java @@ -125,7 +125,7 @@ public void testBoxRayCollision() { ray.setOrigin(new Vector3f(0, 0, -5)); checkCollision(box, ray, 2); - // XXX: is this right? the ray origin is on the box's side.. + // XXX: is this right? the ray origin is on the box's side. ray.setOrigin(new Vector3f(0, 0, 2f)); checkCollision(box, ray, 0); @@ -151,7 +151,7 @@ public void testBoxTriangleCollision() { box.setCenter(new Vector3f(-1f, 0, 0)); checkCollision(box, geom, 2); - // Move it slightly farther.. + // Move it slightly farther. box.setCenter(new Vector3f(-1f - FastMath.ZERO_TOLERANCE, 0, 0)); checkCollision(box, geom, 0); @@ -181,7 +181,7 @@ public void testSphereTriangleCollision() { sphere.setCenter(new Vector3f(-1f + FastMath.ZERO_TOLERANCE, 0, 0)); checkCollision(sphere, geom, 2); - // Move it slightly farther.. + // Move it slightly farther. sphere.setCenter(new Vector3f(-1f - FastMath.ZERO_TOLERANCE, 0, 0)); checkCollision(sphere, geom, 0); diff --git a/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java b/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java index e098c22ba7..0a4a04249d 100644 --- a/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java +++ b/jme3-core/src/test/java/com/jme3/collision/CollideIgnoreTransformTest.java @@ -99,13 +99,13 @@ public void testPhantomTriangles() { rootNode.updateLogicalState(0.01f); rootNode.updateGeometricState(); - /** + /* * ray in the -Z direction, starting from (0.5, 0.6, 10) */ Ray ray1 = new Ray(/* origin */new Vector3f(0.5f, 0.6f, 10f), /* direction */ new Vector3f(0f, 0f, -1f)); castRay(ray1, 1); - /** + /* * ray in the -Z direction, starting from (0.5, 3, 10) */ Ray ray0 = new Ray(/* origin */new Vector3f(0.5f, 3f, 10f), diff --git a/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java b/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java index 43d7b20f0e..971c136ae9 100644 --- a/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java +++ b/jme3-core/src/test/java/com/jme3/collision/CollisionUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,12 @@ */ final class CollisionUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private CollisionUtil() { + } + private static void checkCollisionBase(Collidable a, Collidable b, int expected) { // Test bounding volume methods if (a instanceof BoundingVolume && b instanceof BoundingVolume) { @@ -54,7 +60,7 @@ private static void checkCollisionBase(Collidable a, Collidable b, int expected) assert results.size() == numCollisions; assert numCollisions == expected; - // force the results to be sorted here.. + // Force the results to be sorted here. results.getClosestCollision(); if (results.size() > 0) { @@ -71,7 +77,7 @@ private static void checkCollisionBase(Collidable a, Collidable b, int expected) * * @param a First collidable * @param b Second collidable - * @param expect Number of expected results + * @param expected the expected number of results */ public static void checkCollision(Collidable a, Collidable b, int expected) { checkCollisionBase(a, b, expected); diff --git a/jme3-core/src/test/java/com/jme3/effect/influencers/ParticleInfluencerTest.java b/jme3-core/src/test/java/com/jme3/effect/influencers/ParticleInfluencerTest.java new file mode 100644 index 0000000000..1e98da6e57 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/effect/influencers/ParticleInfluencerTest.java @@ -0,0 +1,78 @@ +package com.jme3.effect.influencers; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.math.Vector3f; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the {@code ParticleInfluencer} class. + * + * @author capdevon + */ +public class ParticleInfluencerTest { + + /** + * Tests cloning, serialization and de-serialization of a {@code NewtonianParticleInfluencer}. + */ + @Test + public void testNewtonianParticleInfluencer() { + AssetManager assetManager = new DesktopAssetManager(true); + + NewtonianParticleInfluencer inf = new NewtonianParticleInfluencer(); + inf.setNormalVelocity(1); + inf.setSurfaceTangentFactor(0.5f); + inf.setSurfaceTangentRotation(2.5f); + inf.setInitialVelocity(new Vector3f(0, 1, 0)); + inf.setVelocityVariation(2f); + + NewtonianParticleInfluencer clone = (NewtonianParticleInfluencer) inf.clone(); + assertEquals(inf, clone); + Assert.assertNotSame(inf.temp, clone.temp); + + NewtonianParticleInfluencer copy = BinaryExporter.saveAndLoad(assetManager, inf); + assertEquals(inf, copy); + } + + private void assertEquals(NewtonianParticleInfluencer inf, NewtonianParticleInfluencer clone) { + Assert.assertEquals(inf.getNormalVelocity(), clone.getNormalVelocity(), 0.001f); + Assert.assertEquals(inf.getSurfaceTangentFactor(), clone.getSurfaceTangentFactor(), 0.001f); + Assert.assertEquals(inf.getSurfaceTangentRotation(), clone.getSurfaceTangentRotation(), 0.001f); + Assert.assertEquals(inf.getInitialVelocity(), clone.getInitialVelocity()); + Assert.assertEquals(inf.getVelocityVariation(), clone.getVelocityVariation(), 0.001f); + } + + /** + * Tests cloning, serialization and de-serialization of a {@code RadialParticleInfluencer}. + */ + @Test + public void testRadialParticleInfluencer() { + AssetManager assetManager = new DesktopAssetManager(true); + + RadialParticleInfluencer inf = new RadialParticleInfluencer(); + inf.setHorizontal(true); + inf.setOrigin(new Vector3f(0, 1, 0)); + inf.setRadialVelocity(2f); + inf.setInitialVelocity(new Vector3f(0, 1, 0)); + inf.setVelocityVariation(2f); + + RadialParticleInfluencer clone = (RadialParticleInfluencer) inf.clone(); + assertEquals(inf, clone); + Assert.assertNotSame(inf.temp, clone.temp); + Assert.assertNotSame(inf.getOrigin(), clone.getOrigin()); + + RadialParticleInfluencer copy = BinaryExporter.saveAndLoad(assetManager, inf); + assertEquals(inf, copy); + } + + private void assertEquals(RadialParticleInfluencer inf, RadialParticleInfluencer clone) { + Assert.assertEquals(inf.isHorizontal(), clone.isHorizontal()); + Assert.assertEquals(inf.getOrigin(), clone.getOrigin()); + Assert.assertEquals(inf.getRadialVelocity(), clone.getRadialVelocity(), 0.001f); + Assert.assertEquals(inf.getInitialVelocity(), clone.getInitialVelocity()); + Assert.assertEquals(inf.getVelocityVariation(), clone.getVelocityVariation(), 0.001f); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java b/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java index aab30ff17c..80c75f0b7b 100644 --- a/jme3-core/src/test/java/com/jme3/light/LightFilterTest.java +++ b/jme3-core/src/test/java/com/jme3/light/LightFilterTest.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 @@ -113,7 +113,7 @@ public void testPointFiltering() { pl.setPosition(new Vector3f(0, 0, 8f)); checkFilteredLights(0); - // And more close - now its an intersection. + // And closer - now they intersect. pl.setPosition(new Vector3f(0, 0, 8f + FastMath.ZERO_TOLERANCE)); checkFilteredLights(1); @@ -134,8 +134,9 @@ public void testPointFiltering() { checkFilteredLights(1); // Rotate the camera so it is up, light is outside frustum. - cam.lookAtDirection(Vector3f.UNIT_Y, Vector3f.UNIT_Y); + cam.lookAtDirection(Vector3f.UNIT_Y, Vector3f.UNIT_X); checkFilteredLights(0); + cam.lookAtDirection(Vector3f.UNIT_Z, Vector3f.UNIT_Y); // ================================== // Tests for bounding Sphere @@ -148,30 +149,30 @@ public void testPointFiltering() { checkFilteredLights(1); pl.setRadius(1f); - // Put the light at the very close to the geom, - // the very edge of the sphere touches the other bounding sphere + // Put the light very close to the geometry. + // The edge of the sphere touches the other bounding sphere. // Still not considered an intersection though. pl.setPosition(new Vector3f(0, 0, 0)); checkFilteredLights(0); - // And more close - now its an intersection. + // And closer - now they intersect. pl.setPosition(new Vector3f(0, 0, 0f + FastMath.ZERO_TOLERANCE)); checkFilteredLights(1); geom.setLocalTranslation(0, 0, 0); - // In this case its an intersection for pointLight v. box - // But not for pointLight v. sphere + // In this case, it's an intersection for PointLight versus box, + // but not for PointLight versus sphere. // Vector3f(0, 0.5f, 0.5f).normalize().mult(2) ~ >= (0.0, 1.4142135, 1.4142135) //pl.setPosition(new Vector3f(0, 0.5f, 0.5f).normalizeLocal().multLocal(2 + FastMath.ZERO_TOLERANCE)); pl.setPosition(new Vector3f(0f, 1.4142135f, 1.4142135f).multLocal(1+FastMath.ZERO_TOLERANCE)); checkFilteredLights(0); - // Make the distance a wee bit closer, now its an intersection + // Make the distance a wee bit closer. Now it intersects. //pl.setPosition(new Vector3f(0, 0.5f, 0.5f).normalizeLocal().multLocal(2 - FastMath.ZERO_TOLERANCE)); pl.setPosition(new Vector3f(0f, 1.4142135f, 1.4142135f).multLocal(1-FastMath.ZERO_TOLERANCE)); checkFilteredLights(1); - // it's a point light, also test for the other corner + // It's a point light; also test for the other corner. pl.setPosition(new Vector3f(0f, -1.4142135f, -1.4142135f).multLocal(1-FastMath.ZERO_TOLERANCE)); checkFilteredLights(0); @@ -236,7 +237,7 @@ public void testSpotFiltering() { sl.setSpotRange(0); checkFilteredLights(1); - //the geommetry is outside the infinite cone (cone direction going away from the geom) + //the geometry is outside the infinite cone (cone direction going away from the geom) sl.setPosition(Vector3f.UNIT_Z.mult(1+FastMath.ZERO_TOLERANCE)); checkFilteredLights(0); diff --git a/jme3-core/src/test/java/com/jme3/light/LightSortTest.java b/jme3-core/src/test/java/com/jme3/light/LightSortTest.java index 593cc9d3ec..1be8305bc8 100644 --- a/jme3-core/src/test/java/com/jme3/light/LightSortTest.java +++ b/jme3-core/src/test/java/com/jme3/light/LightSortTest.java @@ -57,9 +57,9 @@ public void testSimpleSort() { list.sort(true); assert list.get(0) instanceof AmbientLight; // Ambients always first - assert list.get(1) instanceof DirectionalLight; // .. then directionals + assert list.get(1) instanceof DirectionalLight; // ... then directionals assert list.get(2) instanceof SpotLight; // Spot is 0 units away from geom - assert list.get(3) instanceof PointLight; // .. and point is 1 unit away. + assert list.get(3) instanceof PointLight; // ... and point is 1 unit away. } @Test @@ -75,7 +75,7 @@ public void testSceneGraphSort() { n.addLight(spot); n.addLight(point); - // .. and some on the geometry. + // ... and some on the geometry. g.addLight(directional); g.addLight(ambient); diff --git a/jme3-core/src/test/java/com/jme3/material/MaterialMatParamTest.java b/jme3-core/src/test/java/com/jme3/material/MaterialMatParamTest.java index fc4503b1aa..9b572e8e93 100644 --- a/jme3-core/src/test/java/com/jme3/material/MaterialMatParamTest.java +++ b/jme3-core/src/test/java/com/jme3/material/MaterialMatParamTest.java @@ -449,7 +449,7 @@ public void setTexture(int unit, Texture texture) { MaterialMatParamTest.this.usedTextures[unit] = texture; } }; - private final RenderManager renderManager = new RenderManager(renderer); + private final RenderManager renderManager = TestUtil.createRenderManager(renderer); private boolean evaluated = false; private Shader usedShader = null; diff --git a/jme3-core/src/test/java/com/jme3/material/MaterialTest.java b/jme3-core/src/test/java/com/jme3/material/MaterialTest.java index 17aea47755..6edd3ed34a 100644 --- a/jme3-core/src/test/java/com/jme3/material/MaterialTest.java +++ b/jme3-core/src/test/java/com/jme3/material/MaterialTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,7 +50,7 @@ import static org.junit.Assert.*; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class MaterialTest { @@ -104,7 +104,7 @@ public void testSelectDefaultTechnique_GLSL120Cap_MultipleLangs() { material.selectTechnique("Default", renderManager); - checkRequiredCaps(Caps.GLSL100, Caps.GLSL120); + checkRequiredCaps(Caps.GLSL120); } @Test @@ -188,10 +188,6 @@ private void supportGlsl(int version) { } } - private void caps(Caps... caps) { - myCaps.addAll(Arrays.asList(caps)); - } - private void material(String path) { AssetManager assetManager = TestUtil.createAssetManager(); material = new Material(assetManager, path); diff --git a/jme3-core/src/test/java/com/jme3/material/RenderStateTest.java b/jme3-core/src/test/java/com/jme3/material/RenderStateTest.java new file mode 100644 index 0000000000..431ce9008c --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/material/RenderStateTest.java @@ -0,0 +1,222 @@ +/* + * Copyright (c) 2021 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; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cloning/saving/loading a RenderState. Related issues include #1718 and + * #1723. + * + * @author Stephen Gold sgold@sonic.net + */ +public class RenderStateTest { + // ************************************************************************* + // fields + + private static final AssetManager assetManager = new DesktopAssetManager(); + private static final boolean testSerialization = true; + private static final RenderState testObject = new RenderState(); + // ************************************************************************* + // new methods exposed + + @Test + public void testCloneRenderState() { + for (RenderState.BlendEquation equation : RenderState.BlendEquation.values()) { + testObject.setBlendEquation(equation); + test(); + } + + for (RenderState.BlendEquationAlpha eqAlpha : RenderState.BlendEquationAlpha.values()) { + testObject.setBlendEquationAlpha(eqAlpha); + test(); + } + + for (RenderState.BlendMode mode : RenderState.BlendMode.values()) { + testObject.setBlendMode(mode); + test(); + } + + testObject.setColorWrite(true); + test(); + testObject.setColorWrite(false); + test(); + + testCustomBlendFactors(); + + for (RenderState.TestFunction function : RenderState.TestFunction.values()) { + testObject.setDepthFunc(function); + test(); + } + + testObject.setDepthTest(true); + test(); + testObject.setDepthTest(false); + test(); + + testObject.setDepthWrite(true); + test(); + testObject.setDepthWrite(false); + test(); + + for (RenderState.FaceCullMode mode : RenderState.FaceCullMode.values()) { + testObject.setFaceCullMode(mode); + test(); + } + + testObject.setLineWidth(1f); + test(); + testObject.setLineWidth(9f); + test(); + + for (int factor = -1; factor <= 1; ++factor) { + for (int units = -1; units <= 1; ++units) { + testObject.setPolyOffset(factor, units); + test(); + } + } + + testStencils(); + + testObject.setWireframe(true); + test(); + testObject.setWireframe(false); + test(); + } + // ************************************************************************* + // private methods + + private static void test() { + /* + * Test a clone for equality. + */ + RenderState clone = testObject.clone(); + Assert.assertEquals(testObject, clone); + + if (testSerialization) { + /* + * Test a save-and-load copy for equality. + */ + RenderState copy = BinaryExporter.saveAndLoad(assetManager, testObject); + Assert.assertEquals(testObject, copy); + } + } + + /** + * Tests 242 of the 14,641 possile blend-factor combinations. + */ + private static void testCustomBlendFactors() { + final RenderState.BlendFunc dAlpha = RenderState.BlendFunc.Zero; + final RenderState.BlendFunc sAlpha = RenderState.BlendFunc.Dst_Color; + + for (RenderState.BlendFunc sourceRgb : RenderState.BlendFunc.values()) { + for (RenderState.BlendFunc destRgb : RenderState.BlendFunc.values()) { + testObject.setCustomBlendFactors(sourceRgb, destRgb, sAlpha, dAlpha); + test(); + } + } + + final RenderState.BlendFunc dRgb = RenderState.BlendFunc.One_Minus_Dst_Alpha; + final RenderState.BlendFunc sRgb = RenderState.BlendFunc.Src_Color; + + for (RenderState.BlendFunc sourceAlpha : RenderState.BlendFunc.values()) { + for (RenderState.BlendFunc destAlpha : RenderState.BlendFunc.values()) { + testObject.setCustomBlendFactors(sRgb, dRgb, sourceAlpha, destAlpha); + test(); + } + } + } + + /** + * Tests a subset of the possile stencil combinations. + */ + private static void testStencils() { + boolean enabled = true; + + final RenderState.StencilOperation frontSfo = RenderState.StencilOperation.Increment; + final RenderState.StencilOperation frontDfo = RenderState.StencilOperation.Invert; + final RenderState.StencilOperation frontDpo = RenderState.StencilOperation.Zero; + final RenderState.StencilOperation backSfo = RenderState.StencilOperation.Replace; + final RenderState.StencilOperation backDfo = RenderState.StencilOperation.DecrementWrap; + final RenderState.StencilOperation backDpo = RenderState.StencilOperation.Keep; + /* + * Vary the test functions (8th and 9th arguments). + */ + for (RenderState.TestFunction front : RenderState.TestFunction.values()) { + for (RenderState.TestFunction back : RenderState.TestFunction.values()) { + testObject.setStencil(enabled, + frontSfo, frontDfo, frontDpo, backSfo, backDfo, backDpo, + front, back); + test(); + } + } + + final RenderState.TestFunction front = RenderState.TestFunction.GreaterOrEqual; + final RenderState.TestFunction back = RenderState.TestFunction.NotEqual; + /* + * Vary the 2nd, 4th, and 7th arguments. + */ + for (RenderState.StencilOperation arg2 : RenderState.StencilOperation.values()) { + for (RenderState.StencilOperation arg4 : RenderState.StencilOperation.values()) { + for (RenderState.StencilOperation arg7 : RenderState.StencilOperation.values()) { + testObject.setStencil(enabled, + arg2, frontDfo, arg4, backSfo, backDfo, arg7, + front, back); + test(); + } + } + } + /* + * Vary the 3rd, 5th, and 6th arguments. + */ + for (RenderState.StencilOperation arg3 : RenderState.StencilOperation.values()) { + for (RenderState.StencilOperation arg5 : RenderState.StencilOperation.values()) { + for (RenderState.StencilOperation arg6 : RenderState.StencilOperation.values()) { + testObject.setStencil(enabled, + frontSfo, arg3, frontDpo, arg5, arg6, backDpo, + front, back); + test(); + } + } + } + + enabled = false; + testObject.setStencil(enabled, + frontSfo, frontDfo, frontDpo, backSfo, backDfo, backDpo, + front, back); + test(); + } +} 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 b60e82c642..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 @@ -17,7 +17,7 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; import static org.mockito.Matchers.any; import static org.mockito.Mockito.verify; @@ -46,6 +46,7 @@ public class J3MLoaderTest { private MaterialDef materialDef; @Before + @SuppressWarnings("unchecked") public void setUp() throws Exception { when(assetKey.getExtension()).thenReturn(".j3m"); when(assetInfo.getManager()).thenReturn(assetManager); @@ -86,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); @@ -110,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); @@ -134,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/material/plugins/LoadJ3mdTest.java b/jme3-core/src/test/java/com/jme3/material/plugins/LoadJ3mdTest.java index eb76c18d9c..2d35362a15 100644 --- a/jme3-core/src/test/java/com/jme3/material/plugins/LoadJ3mdTest.java +++ b/jme3-core/src/test/java/com/jme3/material/plugins/LoadJ3mdTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ import static org.junit.Assert.assertEquals; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.runners.MockitoJUnitRunner; +import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class LoadJ3mdTest { @@ -103,9 +103,6 @@ private void supportGlsl(int version) { break; } } - private void caps(Caps... caps) { - myCaps.addAll(Arrays.asList(caps)); - } private void material(String path) { AssetManager assetManager = TestUtil.createAssetManager(); diff --git a/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java b/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java new file mode 100644 index 0000000000..914ef61c14 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/ColorRGBATest.java @@ -0,0 +1,30 @@ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * @author capdevon + */ +public class ColorRGBATest { + + @Test + public void testIntColor() { + ColorRGBA color = new ColorRGBA(1.0f, 0.2f, 0.6f, 0.8f); + + int rgba = color.asIntRGBA(); + int abgr = color.asIntABGR(); + int argb = color.asIntARGB(); + + Assert.assertEquals(-13395508, rgba); + Assert.assertEquals(-862374913, abgr); + Assert.assertEquals(-855690343, argb); + + ColorRGBA copy = new ColorRGBA(); + + Assert.assertEquals(color, copy.fromIntRGBA(rgba)); + Assert.assertEquals(color, copy.fromIntABGR(abgr)); + Assert.assertEquals(color, copy.fromIntARGB(argb)); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java index e4aa1750a7..13f8ba6e8a 100644 --- a/jme3-core/src/test/java/com/jme3/math/FastMathTest.java +++ b/jme3-core/src/test/java/com/jme3/math/FastMathTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -158,6 +158,73 @@ public void testCartesianToSpherical() { assertEquals(10.022974f, retval.getX(), 0.0f); assertEquals(1.4358196f, retval.getY(), 0.01f); assertEquals(0.61709767f, retval.getZ(), 0.0f); + /* + * ensure that the transformation is reversible in Octant I + */ + final Vector3f out1 = FastMath.sphericalToCartesian(retval, null); + assertEquals(cartCoords.x, out1.x, 1e-5f); + assertEquals(cartCoords.y, out1.y, 1e-5f); + assertEquals(cartCoords.z, out1.z, 1e-5f); + /* + * test reversibility in the other 7 octants + */ + final Vector3f in2 = new Vector3f(-1.9f, +5.8f, +8.1f); + final Vector3f spherical2 = FastMath.cartesianToSpherical(in2, null); + final Vector3f out2 = FastMath.sphericalToCartesian(spherical2, null); + assertEquals(in2.x, out2.x, 1e-5f); + assertEquals(in2.y, out2.y, 1e-5f); + assertEquals(in2.z, out2.z, 1e-5f); + + final Vector3f in3 = new Vector3f(+1.7f, -3.8f, +8.6f); + final Vector3f spherical3 = FastMath.cartesianToSpherical(in3, null); + final Vector3f out3 = FastMath.sphericalToCartesian(spherical3, null); + assertEquals(in3.x, out3.x, 1e-5f); + assertEquals(in3.y, out3.y, 1e-5f); + assertEquals(in3.z, out3.z, 1e-5f); + + final Vector3f in4 = new Vector3f(-1.5f, -3.2f, +4.1f); + final Vector3f spherical4 = FastMath.cartesianToSpherical(in4, null); + final Vector3f out4 = FastMath.sphericalToCartesian(spherical4, null); + assertEquals(in4.x, out4.x, 1e-5f); + assertEquals(in4.y, out4.y, 1e-5f); + assertEquals(in4.z, out4.z, 1e-5f); + + final Vector3f in5 = new Vector3f(+3.5f, +7.2f, -4.3f); + final Vector3f spherical5 = FastMath.cartesianToSpherical(in5, null); + final Vector3f out5 = FastMath.sphericalToCartesian(spherical5, null); + assertEquals(in5.x, out5.x, 1e-5f); + assertEquals(in5.y, out5.y, 1e-5f); + assertEquals(in5.z, out5.z, 1e-5f); + + final Vector3f in6 = new Vector3f(-6.9f, +5.8f, -2.1f); + final Vector3f spherical6 = FastMath.cartesianToSpherical(in6, null); + final Vector3f out6 = FastMath.sphericalToCartesian(spherical6, null); + assertEquals(in6.x, out6.x, 1e-5f); + assertEquals(in6.y, out6.y, 1e-5f); + assertEquals(in6.z, out6.z, 1e-5f); + + final Vector3f in7 = new Vector3f(+1.1f, -3.0f, -8.6f); + final Vector3f spherical7 = FastMath.cartesianToSpherical(in7, null); + final Vector3f out7 = FastMath.sphericalToCartesian(spherical7, null); + assertEquals(in7.x, out7.x, 1e-5f); + assertEquals(in7.y, out7.y, 1e-5f); + assertEquals(in7.z, out7.z, 1e-5f); + + final Vector3f in8 = new Vector3f(-6.2f, -2.2f, -4.1f); + final Vector3f spherical8 = FastMath.cartesianToSpherical(in8, null); + final Vector3f out8 = FastMath.sphericalToCartesian(spherical8, null); + assertEquals(in8.x, out8.x, 1e-5f); + assertEquals(in8.y, out8.y, 1e-5f); + assertEquals(in8.z, out8.z, 1e-5f); + /* + * test reversibility on the origin + */ + final Vector3f in0 = new Vector3f(0f, 0f, 0f); + final Vector3f spherical0 = FastMath.cartesianToSpherical(in0, null); + final Vector3f out0 = FastMath.sphericalToCartesian(spherical0, null); + assertEquals(in0.x, out0.x, 1e-5f); + assertEquals(in0.y, out0.y, 1e-5f); + assertEquals(in0.z, out0.z, 1e-5f); } @Test @@ -178,6 +245,73 @@ public void testCartesianZToSpherical() { assertEquals(10.022974f, retval.getX(), 0.01f); assertEquals(0.61709767f, retval.getY(), 0.01f); assertEquals(1.4358196f, retval.getZ(), 0.01f); + /* + * ensure that the transformation is reversible in Octant I + */ + final Vector3f out1 = FastMath.sphericalToCartesianZ(retval, null); + assertEquals(cartCoords.x, out1.x, 1e-5f); + assertEquals(cartCoords.y, out1.y, 1e-5f); + assertEquals(cartCoords.z, out1.z, 1e-5f); + /* + * test reversibility in the other 7 octants + */ + final Vector3f in2 = new Vector3f(-1.9f, +5.8f, +8.1f); + final Vector3f spherical2 = FastMath.cartesianZToSpherical(in2, null); + final Vector3f out2 = FastMath.sphericalToCartesianZ(spherical2, null); + assertEquals(in2.x, out2.x, 1e-5f); + assertEquals(in2.y, out2.y, 1e-5f); + assertEquals(in2.z, out2.z, 1e-5f); + + final Vector3f in3 = new Vector3f(+1.7f, -3.8f, +8.6f); + final Vector3f spherical3 = FastMath.cartesianZToSpherical(in3, null); + final Vector3f out3 = FastMath.sphericalToCartesianZ(spherical3, null); + assertEquals(in3.x, out3.x, 1e-5f); + assertEquals(in3.y, out3.y, 1e-5f); + assertEquals(in3.z, out3.z, 1e-5f); + + final Vector3f in4 = new Vector3f(-1.5f, -3.2f, +4.1f); + final Vector3f spherical4 = FastMath.cartesianZToSpherical(in4, null); + final Vector3f out4 = FastMath.sphericalToCartesianZ(spherical4, null); + assertEquals(in4.x, out4.x, 1e-5f); + assertEquals(in4.y, out4.y, 1e-5f); + assertEquals(in4.z, out4.z, 1e-5f); + + final Vector3f in5 = new Vector3f(+3.5f, +7.2f, -4.3f); + final Vector3f spherical5 = FastMath.cartesianZToSpherical(in5, null); + final Vector3f out5 = FastMath.sphericalToCartesianZ(spherical5, null); + assertEquals(in5.x, out5.x, 1e-5f); + assertEquals(in5.y, out5.y, 1e-5f); + assertEquals(in5.z, out5.z, 1e-5f); + + final Vector3f in6 = new Vector3f(-6.9f, +5.8f, -2.1f); + final Vector3f spherical6 = FastMath.cartesianZToSpherical(in6, null); + final Vector3f out6 = FastMath.sphericalToCartesianZ(spherical6, null); + assertEquals(in6.x, out6.x, 1e-5f); + assertEquals(in6.y, out6.y, 1e-5f); + assertEquals(in6.z, out6.z, 1e-5f); + + final Vector3f in7 = new Vector3f(+1.1f, -3.0f, -8.6f); + final Vector3f spherical7 = FastMath.cartesianZToSpherical(in7, null); + final Vector3f out7 = FastMath.sphericalToCartesianZ(spherical7, null); + assertEquals(in7.x, out7.x, 1e-5f); + assertEquals(in7.y, out7.y, 1e-5f); + assertEquals(in7.z, out7.z, 1e-5f); + + final Vector3f in8 = new Vector3f(-6.2f, -2.2f, -4.1f); + final Vector3f spherical8 = FastMath.cartesianZToSpherical(in8, null); + final Vector3f out8 = FastMath.sphericalToCartesianZ(spherical8, null); + assertEquals(in8.x, out8.x, 1e-5f); + assertEquals(in8.y, out8.y, 1e-5f); + assertEquals(in8.z, out8.z, 1e-5f); + /* + * test reversibility on the origin + */ + final Vector3f in0 = new Vector3f(0f, 0f, 0f); + final Vector3f spherical0 = FastMath.cartesianZToSpherical(in0, null); + final Vector3f out0 = FastMath.sphericalToCartesianZ(spherical0, null); + assertEquals(in0.x, out0.x, 1e-5f); + assertEquals(in0.y, out0.y, 1e-5f); + assertEquals(in0.z, out0.z, 1e-5f); } @Test @@ -570,9 +704,9 @@ public void testInterpolateLinear8() { @Test public void testInterpolateLinear_float() { assertEquals(0.0f, FastMath.interpolateLinear(2.0f, 2.93874e-39f, 0.0f), 0.0f); - assertEquals(0.0f, FastMath.interpolateLinear(0.999999f, 1.4013e-45f, 0.0f), 0.0f); - assertEquals(-2.93874e-39f, FastMath.interpolateLinear(0.0f, -2.93874e-39f, -0.0f), 0.0f); - assertEquals(0.0f, FastMath.interpolateLinear(0.0f, 0.0f, 0.0f), 0.0f); + assertEquals(0.0f, FastMath.interpolateLinear(0.999999f, 1.4013e-45f, 0.0f), 0.0f); + assertEquals(-2.93874e-39f, FastMath.interpolateLinear(0.0f, -2.93874e-39f, -0.0f), 0.0f); + assertEquals(0.0f, FastMath.interpolateLinear(0.0f, 0.0f, 0.0f), 0.0f); } @Test @@ -639,7 +773,7 @@ public void testPointInsideTriangle6() { final Vector2f t0 = new Vector2f(-0.43f, 2.54f); final Vector2f t1 = new Vector2f(Float.NEGATIVE_INFINITY, 2.54f); final Vector2f t2 = new Vector2f(Float.NaN, Float.POSITIVE_INFINITY); - final Vector2f p = new Vector2f(-3.19f, -0.001f);; + final Vector2f p = new Vector2f(-3.19f, -0.001f); assertEquals(0, FastMath.pointInsideTriangle(t0, t1, t2, p)); } diff --git a/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java new file mode 100644 index 0000000000..a81074de70 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/QuaternionTest.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import junit.framework.TestCase; + +/** + * Verifies that the {@link Quaternion} class works correctly. + * + * @author Richard Tingle (aka Richtea) + */ +public class QuaternionTest extends TestCase{ + + /** + * Verify that the {@link Quaternion#isValidQuaternion(com.jme3.math.Quaternion)} method works correctly. Testing + * for NaNs and infinities (which are not "valid") + */ + public void testIsValidQuaternion(){ + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.NaN, 2.1f, 3.0f, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, Float.NaN, 3.0f, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, 2.1f, Float.NaN, 1.5f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(1f, 2.1f, 3.0f, Float.NaN))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.POSITIVE_INFINITY, 1.5f, 1.9f, 2.0f))); + assertFalse(Quaternion.isValidQuaternion(new Quaternion(Float.NEGATIVE_INFINITY, 2.5f, 8.2f, 3.0f))); + assertFalse(Quaternion.isValidQuaternion(null)); + + assertTrue(Quaternion.isValidQuaternion(new Quaternion())); + assertTrue(Quaternion.isValidQuaternion(new Quaternion(1.5f, -5.7f, 8.2f, 3.0f))); + } +} \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/math/SplineTest.java b/jme3-core/src/test/java/com/jme3/math/SplineTest.java new file mode 100644 index 0000000000..447bf1d7bb --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/SplineTest.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2025 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 com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.util.clone.Cloner; +import java.util.ArrayList; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +/** + * Verifies that the {@link Spline} class works correctly. + * + * @author Stephen Gold + */ +public class SplineTest { + // ************************************************************************* + // fields + + private static final AssetManager assetManager = new DesktopAssetManager(); + // ************************************************************************* + // tests + + /** + * Verifies that spline cloning works correctly. + */ + @Test + public void cloneSplines() { + // Clone a Bézier spline: + { + Spline test1 = createBezier(); + Spline copy1 = Cloner.deepClone(test1); + assertSplineEquals(test1, copy1); + } + + // Clone a NURB spline: + { + Spline test2 = createNurb(); + Spline copy2 = Cloner.deepClone(test2); + assertSplineEquals(test2, copy2); + } + + // Clone a Catmull-Rom spline: + { + Spline test3 = createCatmullRom(); + Spline copy3 = Cloner.deepClone(test3); + assertSplineEquals(test3, copy3); + } + + // Clone a linear spline: + { + Spline test4 = createLinear(); + Spline copy4 = Cloner.deepClone(test4); + assertSplineEquals(test4, copy4); + } + + // Clone a default spline: + { + Spline test5 = new Spline(); + Spline copy5 = Cloner.deepClone(test5); + assertSplineEquals(test5, copy5); + } + } + + /** + * Verifies that spline serialization/deserialization works correctly. + */ + @Test + public void saveAndLoadSplines() { + // Serialize and deserialize a Bezier spline: + { + Spline test1 = createBezier(); + Spline copy1 = BinaryExporter.saveAndLoad(assetManager, test1); + assertSplineEquals(test1, copy1); + } + + // Serialize and deserialize a NURB spline: + { + Spline test2 = createNurb(); + Spline copy2 = BinaryExporter.saveAndLoad(assetManager, test2); + assertSplineEquals(test2, copy2); + } + + // Serialize and deserialize a Catmull-Rom spline: + { + Spline test3 = createCatmullRom(); + Spline copy3 = BinaryExporter.saveAndLoad(assetManager, test3); + assertSplineEquals(test3, copy3); + } + + // Serialize and deserialize a linear spline: + { + Spline test4 = createLinear(); + Spline copy4 = BinaryExporter.saveAndLoad(assetManager, test4); + assertSplineEquals(test4, copy4); + } + + // Serialize and deserialize a default spline: + { + Spline test5 = new Spline(); + Spline copy5 = BinaryExporter.saveAndLoad(assetManager, test5); + assertSplineEquals(test5, copy5); + } + } + // ************************************************************************* + // private helper methods + + /** + * Verifies that the specified lists are equivalent but distinct. + * + * @param s1 the first list to compare (may be null, unaffected) + * @param s2 the 2nd list to compare (may be null, unaffected) + */ + private static void assertListEquals(List a1, List a2) { + if (a1 == null || a2 == null) { + // If either list is null, verify that both are null: + Assert.assertNull(a1); + Assert.assertNull(a2); + + } else { + // Verify that the lists are distinct and and of equal length: + Assert.assertTrue(a1 != a2); + Assert.assertEquals(a1.size(), a2.size()); + + for (int i = 0; i < a1.size(); ++i) { + Assert.assertEquals(a1.get(i), a2.get(i)); + } + } + } + + /** + * Verify that the specified splines are equivalent. + * + * @param s1 the first spline to compare (not null, unaffected) + * @param s2 the 2nd split to compare (not null, unaffected) + */ + private static void assertSplineEquals(Spline s1, Spline s2) { + Assert.assertEquals(s1.getType(), s2.getType()); + Assert.assertEquals(s1.isCycle(), s2.isCycle()); + + Assert.assertEquals( + s1.getBasisFunctionDegree(), s2.getBasisFunctionDegree()); + assertListEquals(s1.getControlPoints(), s2.getControlPoints()); + Assert.assertEquals(s1.getCurveTension(), s2.getCurveTension(), 0f); + assertListEquals(s1.getKnots(), s2.getKnots()); + + if (s1.getType() == Spline.SplineType.Nurb) { + // These methods throw NPEs on non-NURB splines. + Assert.assertEquals(s1.getMaxNurbKnot(), s2.getMaxNurbKnot(), 0f); + Assert.assertEquals(s1.getMinNurbKnot(), s2.getMinNurbKnot(), 0f); + } + + assertListEquals(s1.getSegmentsLength(), s2.getSegmentsLength()); + Assert.assertEquals( + s1.getTotalLength(), s2.getTotalLength(), 0f); + Assert.assertArrayEquals(s1.getWeights(), s2.getWeights(), 0f); + } + + /** + * Generates a simple cyclic Bézier spline for testing. + * + * @return a new Spline + */ + private static Spline createBezier() { + Vector3f[] controlPoints1 = { + new Vector3f(0f, 1f, 0f), new Vector3f(1f, 2f, 1f), + new Vector3f(1.5f, 1.5f, 1.5f), new Vector3f(2f, 0f, 1f) + }; + + Spline result = new Spline( + Spline.SplineType.Bezier, controlPoints1, 0.1f, true); + return result; + } + + /** + * Generates a simple acyclic Catmull-Rom spline for testing. + * + * @return a new Spline + */ + private static Spline createCatmullRom() { + List controlPoints3 = new ArrayList<>(6); + controlPoints3.add(new Vector3f(0f, 1f, 2f)); + controlPoints3.add(new Vector3f(3f, -1f, 4f)); + controlPoints3.add(new Vector3f(2f, 5f, 3f)); + controlPoints3.add(new Vector3f(3f, -2f, 3f)); + controlPoints3.add(new Vector3f(0.5f, 1f, 0.6f)); + controlPoints3.add(new Vector3f(-0.5f, 4f, 0.2f)); + + Spline result = new Spline( + Spline.SplineType.CatmullRom, controlPoints3, 0.01f, false); + return result; + } + + /** + * Generates a simple cyclic linear spline for testing. + * + * @return a new Spline + */ + private static Spline createLinear() { + List controlPoints4 = new ArrayList<>(3); + controlPoints4.add(new Vector3f(3f, -1f, 4f)); + controlPoints4.add(new Vector3f(2f, 0f, 3f)); + controlPoints4.add(new Vector3f(3f, -2f, 3f)); + + Spline result = new Spline( + Spline.SplineType.Linear, controlPoints4, 0f, true); + return result; + } + + /** + * Generates a simple NURB spline for testing. + * + * @return a new Spline + */ + private static Spline createNurb() { + List controlPoints2 = new ArrayList<>(5); + controlPoints2.add(new Vector4f(0f, 1f, 2f, 3f)); + controlPoints2.add(new Vector4f(3f, 1f, 4f, 0f)); + controlPoints2.add(new Vector4f(2f, 5f, 3f, 0f)); + controlPoints2.add(new Vector4f(3f, 2f, 3f, 1f)); + controlPoints2.add(new Vector4f(0.5f, 1f, 0.6f, 5f)); + + List nurbKnots = new ArrayList<>(6); + nurbKnots.add(0.2f); + nurbKnots.add(0.3f); + nurbKnots.add(0.4f); + nurbKnots.add(0.43f); + nurbKnots.add(0.51f); + nurbKnots.add(0.52f); + + Spline result = new Spline(controlPoints2, nurbKnots); + return result; + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/TestIssue1388.java b/jme3-core/src/test/java/com/jme3/math/TestIssue1388.java new file mode 100644 index 0000000000..23fe86168a --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TestIssue1388.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2020-2021 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; + +/** + * Verify the order in which Tait-Bryan angles are applied by the Quaternion + * class. This was issue #1388 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue1388 { + + @Test + public void testIssue1388() { + Vector3f in = new Vector3f(4f, 6f, 9f); // test vector, never modified + Vector3f saveIn = in.clone(); + /* + * Three arbitrary rotation angles between -PI/2 and +PI/2 + */ + final float xAngle = 1.23f; + final float yAngle = 0.765f; + final float zAngle = -0.456f; + float[] angles = new float[]{xAngle, yAngle, zAngle}; + float[] saveAngles = new float[]{xAngle, yAngle, zAngle}; + /* + * Part 1: verify that the extrinsic rotation order is x-z-y + * + * Apply extrinsic rotations to the "in" vector in x-z-y order. + */ + Quaternion qx = new Quaternion().fromAngleAxis(xAngle, Vector3f.UNIT_X); + Quaternion qy = new Quaternion().fromAngleAxis(yAngle, Vector3f.UNIT_Y); + Quaternion qz = new Quaternion().fromAngleAxis(zAngle, Vector3f.UNIT_Z); + Vector3f outXZY = qx.mult(in); + qz.mult(outXZY, outXZY); + qy.mult(outXZY, outXZY); + /* + * Construct a Quaternion using fromAngles(float, float, float), + * use it to rotate the "in" vector, and compare. + */ + Quaternion q1 = new Quaternion().fromAngles(xAngle, yAngle, zAngle); + Vector3f out1 = q1.mult(in); + assertEquals(outXZY, out1, 1e-5f); + /* + * Construct a Quaternion using fromAngles(float[]), + * use it to rotate the "in" vector, and compare. + */ + Quaternion q2 = new Quaternion().fromAngles(angles); + Vector3f out2 = q2.mult(in); + assertEquals(outXZY, out2, 1e-5f); + /* + * Construct a Quaternion using only the constructor, + * use it to rotate the "in" vector, and compare. + */ + Quaternion q3 = new Quaternion(angles); + Vector3f out3 = q3.mult(in); + assertEquals(outXZY, out3, 1e-5f); + /* + * Verify that fromAngles() reverses toAngles() for the chosen angles. + */ + float[] out4 = q1.toAngles(null); + assertEquals(angles, out4, 1e-5f); + float[] out5 = q2.toAngles(null); + assertEquals(angles, out5, 1e-5f); + float[] out6 = q3.toAngles(null); + assertEquals(angles, out6, 1e-5f); + /* + * Part 2: verify intrinsic rotation order + * + * Apply intrinsic rotations to the "in" vector in y-z'-x" order. + */ + Quaternion q4 = qy.mult(qz).mult(qx); + Vector3f out7 = q4.mult(in); + assertEquals(outXZY, out7, 1e-5f); + /* + * Verify that the values of "saveAngles" and "in" haven't changed. + */ + assertEquals(saveAngles, angles, 0f); + assertEquals(saveIn, in, 0f); + } + + private void assertEquals(float[] expected, float[] actual, + float tolerance) { + Assert.assertEquals(expected[0], actual[0], tolerance); + Assert.assertEquals(expected[1], actual[1], tolerance); + Assert.assertEquals(expected[2], actual[2], tolerance); + } + + private void assertEquals(Vector3f expected, Vector3f actual, + float tolerance) { + Assert.assertEquals(expected.x, actual.x, tolerance); + Assert.assertEquals(expected.y, actual.y, tolerance); + Assert.assertEquals(expected.z, actual.z, tolerance); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/TestIssue2023.java b/jme3-core/src/test/java/com/jme3/math/TestIssue2023.java new file mode 100644 index 0000000000..e50ab207d9 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TestIssue2023.java @@ -0,0 +1,84 @@ +/* + * 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.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Verify that getRotationColumn() returns correct values for non-normalized + * Quaternions. This was issue #2023 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue2023 { + + /** + * Test a couple non-normalized quaternions. + */ + @Test + public void testIssue2023() { + Quaternion test1 = new Quaternion(2f, 0.5f, 1f, -0.3f); + + Vector3f col0 = test1.getRotationColumn(0); + Assert.assertEquals(0.5318352f, col0.x, 1e-6f); + Assert.assertEquals(0.26217228f, col0.y, 1e-6f); + Assert.assertEquals(0.80524343f, col0.z, 1e-6f); + + Vector3f col1 = test1.getRotationColumn(1); + Assert.assertEquals(0.4868914f, col1.x, 1e-6f); + Assert.assertEquals(-0.8726592f, col1.y, 1e-6f); + Assert.assertEquals(-0.03745319f, col1.z, 1e-6f); + + Vector3f col2 = test1.getRotationColumn(2); + Assert.assertEquals(0.6928839f, col2.x, 1e-6f); + Assert.assertEquals(0.41198504f, col2.y, 1e-6f); + Assert.assertEquals(-0.5917603f, col2.z, 1e-6f); + + Quaternion test2 = new Quaternion(0f, -0.2f, 0f, 0.6f); + + col0 = test2.getRotationColumn(0); + Assert.assertEquals(0.8f, col0.x, 1e-6f); + Assert.assertEquals(0f, col0.y, 1e-6f); + Assert.assertEquals(0.6f, col0.z, 1e-6f); + + col1 = test2.getRotationColumn(1); + Assert.assertEquals(0f, col1.x, 1e-6f); + Assert.assertEquals(1f, col1.y, 1e-6f); + Assert.assertEquals(0f, col1.z, 1e-6f); + + col2 = test2.getRotationColumn(2); + Assert.assertEquals(-0.6f, col2.x, 1e-6f); + Assert.assertEquals(0f, col2.y, 1e-6f); + Assert.assertEquals(0.8f, col2.z, 1e-6f); + } +} diff --git a/jme3-core/src/test/java/com/jme3/math/TestToString.java b/jme3-core/src/test/java/com/jme3/math/TestToString.java new file mode 100644 index 0000000000..c30c1e45ed --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/math/TestToString.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.math; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Test various toString() methods using JUnit. See also + * {@link com.jme3.math.TestTransform}. + * + * @author Stephen Gold + */ +public class TestToString { + /** + * Test various {@code toString()} methods against their javadoc. + */ + @Test + public void testToString() { + // Test data that's never modified: + Line line = new Line( + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f)); + + LineSegment segment = new LineSegment( + new Vector3f(1f, 0f, 0f), new Vector3f(0f, 1f, 0f), 1f); + + Rectangle rectangle = new Rectangle( + new Vector3f(1f, 0f, 0f), + new Vector3f(2f, 0f, 0f), + new Vector3f(1f, 2f, 0f)); + + Triangle triangle = new Triangle( + new Vector3f(1f, 0f, 0f), + new Vector3f(0f, 1f, 0f), + new Vector3f(0f, 0f, 1f)); + + // Verify that the methods don't throw an exception: + String lineString = line.toString(); + String segmentString = segment.toString(); + String rectangleString = rectangle.toString(); + String triangleString = triangle.toString(); + + // Verify that the results match the javadoc: + Assert.assertEquals( + "Line [Origin: (1.0, 0.0, 0.0) Direction: (0.0, 1.0, 0.0)]", + lineString); + Assert.assertEquals( + "LineSegment [Origin: (1.0, 0.0, 0.0) Direction: (0.0, 1.0, 0.0) Extent: 1.0]", + segmentString); + Assert.assertEquals( + "Rectangle [A: (1.0, 0.0, 0.0) B: (2.0, 0.0, 0.0) C: (1.0, 2.0, 0.0)]", + rectangleString); + Assert.assertEquals( + "Triangle [V1: (1.0, 0.0, 0.0) V2: (0.0, 1.0, 0.0) V3: (0.0, 0.0, 1.0)]", + triangleString); + } +} 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/math/Vector3fTest.java b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java index 136f634745..c03719746e 100644 --- a/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java +++ b/jme3-core/src/test/java/com/jme3/math/Vector3fTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -235,12 +235,16 @@ public void testCrossLocal() { assertEquals(-0.0f, retval.z, 0.0f); } + /** + * Verify that distance() doesn't always overflow when distanceSquared > + * Float.MAX_VALUE . + */ @Test public void testDistance() { final Vector3f target = new Vector3f(3.86405e+18f, 3.02146e+23f, 0.171875f); final Vector3f v = new Vector3f(-2.0f, -1.61503e+19f, 0.171875f); - - assertEquals(Float.POSITIVE_INFINITY, target.distance(v), 0.0f); + + assertEquals(3.0216215e23f, target.distance(v), 0f); } @Test @@ -540,11 +544,21 @@ public void testIsValidVector() { @Test public void testLength() { - assertEquals(0.0f, new Vector3f(1.88079e-37f, 0.0f, 1.55077e-36f).length(), 0.0f); + /* + * avoid underflow when lengthSquared is < Float.MIN_VALUE + */ + assertEquals(1.5621336e-36f, + new Vector3f(1.88079e-37f, 0.0f, 1.55077e-36f).length(), 0f); + assertEquals(Float.NaN, new Vector3f(Float.NaN, 0.0f, 1.55077e-36f).length(), 0.0f); assertEquals(Float.POSITIVE_INFINITY, new Vector3f(Float.POSITIVE_INFINITY, 0.0f, 1.0f).length(), 0.0f); + assertEquals(4.0124f, new Vector3f(1.9f, 3.2f, 1.5f).length(), 0.001f); - assertEquals(Float.POSITIVE_INFINITY, new Vector3f(1.8e37f, 1.8e37f, 1.5e36f).length(), 0.0f); + /* + * avoid overflow when lengthSquared > Float.MAX_VALUE + */ + assertEquals(2.5499999e37f, + new Vector3f(1.8e37f, 1.8e37f, 1.5e36f).length(), 0.0f); } @Test diff --git a/jme3-core/src/test/java/com/jme3/renderer/Issue2333Test.java b/jme3-core/src/test/java/com/jme3/renderer/Issue2333Test.java new file mode 100644 index 0000000000..ca553a6137 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/renderer/Issue2333Test.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) 2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.renderer; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for "Camera Viewport Dimensions not Checked" (issue #2333 at + * GitHub). + * + * @author Stephen Gold sgold@sonic.net + */ +public class Issue2333Test { + + /** + * Tests some basic functionality of the viewport settings. + */ + @Test + public void testIssue2333() { + Camera c = new Camera(1, 1); + + // Verify some Camera defaults: + Assert.assertEquals(0f, c.getViewPortBottom(), 0f); + Assert.assertEquals(0f, c.getViewPortLeft(), 0f); + Assert.assertEquals(1f, c.getViewPortRight(), 0f); + Assert.assertEquals(1f, c.getViewPortTop(), 0f); + + // Try some valid settings: + new Camera(1, 1).setViewPort(0.5f, 0.7f, 0.1f, 0.3f); + new Camera(1, 1).setViewPortBottom(0.9f); + new Camera(1, 1).setViewPortLeft(0.99f); + new Camera(1, 1).setViewPortRight(0.01f); + new Camera(1, 1).setViewPortTop(0.1f); + } + + /** + * Verifies that setViewPort() with left = right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase01() { + new Camera(1, 1).setViewPort(0.5f, 0.5f, 0f, 1f); + } + + /** + * Verifies that setViewPort() with left > right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase02() { + new Camera(1, 1).setViewPort(0.7f, 0.5f, 0f, 1f); + } + + /** + * Verifies that setViewPortLeft() resulting in left = right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase03() { + new Camera(1, 1).setViewPortLeft(1f); + } + + /** + * Verifies that setViewPortLeft() resulting in left > right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase04() { + new Camera(1, 1).setViewPortLeft(1.1f); + } + + /** + * Verifies that setViewPortRight() resulting in left = right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase05() { + new Camera(1, 1).setViewPortRight(0f); + } + + /** + * Verifies that setViewPortRight() resulting in left > right throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase06() { + new Camera(1, 1).setViewPortRight(-0.1f); + } + + /** + * Verifies that setViewPort() with bottom = top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase07() { + new Camera(1, 1).setViewPort(0f, 1f, 0.5f, 0.5f); + } + + /** + * Verifies that setViewPort() with bottom > top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase08() { + new Camera(1, 1).setViewPort(0f, 1f, 0.7f, 0.6f); + } + + /** + * Verifies that setViewPortBottom() resulting in bottom = top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase09() { + new Camera(1, 1).setViewPortBottom(1f); + } + + /** + * Verifies that setViewPortBottom() resulting in bottom > top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase10() { + new Camera(1, 1).setViewPortBottom(2f); + } + + /** + * Verifies that setViewPortTop() resulting in bottom = top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase11() { + new Camera(1, 1).setViewPortTop(0f); + } + + /** + * Verifies that setViewPortTop() resulting in bottom > top throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase12() { + new Camera(1, 1).setViewPortTop(-1f); + } + + /** + * Verifies that setViewPort() with left = NaN throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase13() { + new Camera(1, 1).setViewPort(Float.NaN, 1f, 0f, 1f); + } + + /** + * Verifies that setViewPort() with right = NaN throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase14() { + new Camera(1, 1).setViewPort(0f, Float.NaN, 0f, 1f); + } + + /** + * Verifies that setViewPort() with bottom = NaN throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase15() { + new Camera(1, 1).setViewPort(0f, 1f, Float.NaN, 1f); + } + + /** + * Verifies that setViewPort() with top = NaN throws an + * IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase16() { + new Camera(1, 1).setViewPort(0f, 1f, 0f, Float.NaN); + } + + /** + * Verifies that setViewPortBottom(NaN) throws an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase17() { + new Camera(1, 1).setViewPortBottom(Float.NaN); + } + + /** + * Verifies that setViewPortLeft(NaN) throws an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase18() { + new Camera(1, 1).setViewPortLeft(Float.NaN); + } + + /** + * Verifies that setViewPortRight(NaN) throws an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase19() { + new Camera(1, 1).setViewPortRight(Float.NaN); + } + + /** + * Verifies that setViewPortTop(NaN) throws an IllegalArgumentException. + */ + @Test(expected = IllegalArgumentException.class) + public void iaeCase20() { + new Camera(1, 1).setViewPortTop(Float.NaN); + } +} diff --git a/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java b/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java index 72fb43580e..929281819e 100644 --- a/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java +++ b/jme3-core/src/test/java/com/jme3/renderer/OpaqueComparatorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,10 +56,10 @@ public class OpaqueComparatorTest { private final Mesh mesh = new Box(1,1,1); - private Camera cam = new Camera(1, 1); + final private Camera cam = new Camera(1, 1); private RenderManager renderManager; private AssetManager assetManager; - private OpaqueComparator comparator = new OpaqueComparator(); + final private OpaqueComparator comparator = new OpaqueComparator(); @Before public void setUp() { @@ -108,7 +108,7 @@ private void testSort(Material ... materials) { System.out.println(mat); } - Set alreadySeen = new HashSet(); + Set alreadySeen = new HashSet<>(); Material current = null; for (int i = 0; i < gl.size(); i++) { Material mat = gl.get(i).getMaterial(); @@ -252,8 +252,8 @@ public void testSortByShaderDefines() { lightingMatTCVColorLight.setBoolean("VertexLighting", true); lightingMatTCVColorLight.setBoolean("SeparateTexCoord", true); - testSort(lightingMat, lightingMatVColor, lightingMatVLight, - lightingMatVColorLight, lightingMatTC, lightingMatTCVColorLight); + testSort(lightingMatVColor, lightingMat, lightingMatVColorLight, + lightingMatVLight, lightingMatTC, lightingMatTCVColorLight); } @Test @@ -332,8 +332,8 @@ public void testSortByAll() { Material mat2000 = matBase2.clone(); mat2000.setName("2000"); - testSort(mat1100, mat1101, mat1102, mat1110, - mat1120, mat1121, mat1122, mat1140, - mat1200, mat1210, mat1220, mat2000); + testSort(mat1110, mat1100, mat1101, mat1102, + mat1140, mat1120, mat1121, mat1122, + mat1220, mat1210, mat1200, mat2000); } } diff --git a/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java b/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java index 183dece704..d78703e500 100644 --- a/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java +++ b/jme3-core/src/test/java/com/jme3/scene/MPOTestUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,15 +52,21 @@ public void visit(Spatial spatial) { } }; + /** + * A private constructor to inhibit instantiation of this class. + */ + private MPOTestUtils() { + } + private static void validateSubScene(Spatial scene) { scene.checkCulling(DUMMY_CAM); - Set actualOverrides = new HashSet(); + Set actualOverrides = new HashSet<>(); for (MatParamOverride override : scene.getWorldMatParamOverrides()) { actualOverrides.add(override); } - Set expectedOverrides = new HashSet(); + Set expectedOverrides = new HashSet<>(); Spatial current = scene; while (current != null) { for (MatParamOverride override : current.getLocalMatParamOverrides()) { @@ -102,13 +108,10 @@ private static int getRefreshFlags(Spatial scene) { Field refreshFlagsField = Spatial.class.getDeclaredField("refreshFlags"); refreshFlagsField.setAccessible(true); return (Integer) refreshFlagsField.get(scene); - } catch (NoSuchFieldException ex) { - throw new AssertionError(ex); - } catch (SecurityException ex) { - throw new AssertionError(ex); - } catch (IllegalArgumentException ex) { - throw new AssertionError(ex); - } catch (IllegalAccessException ex) { + } catch (NoSuchFieldException + | SecurityException + | IllegalArgumentException + | IllegalAccessException ex) { throw new AssertionError(ex); } } diff --git a/jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java b/jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java index 91f190e9dd..e7f1013f62 100644 --- a/jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/PhantomTrianglesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 jMonkeyEngine + * Copyright (c) 2017-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,10 +41,12 @@ import com.jme3.math.Ray; import com.jme3.math.Vector3f; import com.jme3.scene.shape.Quad; +import com.jme3.system.JmeSystem; +import com.jme3.system.MockJmeSystemDelegate; import org.junit.Test; /** - * Verify that collideWith() doesn't reports collisions with phantom triangles. + * Verify that collideWith() doesn't report collisions with phantom triangles. * This was issue #710 at GitHub. * * @author Stephen Gold @@ -114,6 +116,7 @@ void createWhiteLines() { @Test public void testPhantomTriangles() { + JmeSystem.setSystemDelegate(new MockJmeSystemDelegate()); assetManager = new DesktopAssetManager(); assetManager.registerLocator(null, ClasspathLocator.class); assetManager.registerLoader(J3MLoader.class, "j3m", "j3md"); 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..48c9147920 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/SpatialTest.java @@ -0,0 +1,154 @@ +/* + * 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.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +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()); + } + + @Test + public void testTransferToOtherNode(){ + Node nodeA = new Node("nodeA"); + Node nodeB = new Node("nodeB"); + Node testNode=new Node("testNode"); + nodeA.setLocalTranslation(-1,0,0); + nodeB.setLocalTranslation(1,0,0); + nodeB.rotate(0,90* FastMath.DEG_TO_RAD,0); + testNode.setLocalTranslation(1,0,0); + nodeA.attachChild(testNode); + Vector3f worldTranslation = testNode.getWorldTranslation().clone(); + Quaternion worldRotation = testNode.getWorldRotation().clone(); + + Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); + Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); + + nodeB.attachChild(testNode); + + Assert.assertFalse(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); + Assert.assertFalse(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); + + testNode.setLocalTranslation(nodeB.worldToLocal(worldTranslation,null)); + Assert.assertTrue(worldTranslation.isSimilar(testNode.getWorldTranslation(),1e-6f)); + + testNode.setLocalRotation(nodeB.worldToLocal(worldRotation,null)); + System.out.println(testNode.getWorldRotation()); + Assert.assertTrue(worldRotation.isSimilar(testNode.getWorldRotation(),1e-6f)); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/TestUserData.java b/jme3-core/src/test/java/com/jme3/scene/TestUserData.java index c228af8926..afd9d70e2b 100644 --- a/jme3-core/src/test/java/com/jme3/scene/TestUserData.java +++ b/jme3-core/src/test/java/com/jme3/scene/TestUserData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,36 +46,36 @@ public static void userDataTest(Spatial sp, Object v) { @Test public void testLong() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Long) (long) (Math.random() * Long.MAX_VALUE)); + userDataTest(sp, Math.random() * Long.MAX_VALUE); } @Test public void testInt() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Integer) (int) (Math.random() * Integer.MAX_VALUE)); + userDataTest(sp, Math.random() * Integer.MAX_VALUE); } @Test public void testShort() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Short) (short) (Math.random() * Short.MAX_VALUE)); + userDataTest(sp, Math.random() * Short.MAX_VALUE); } @Test public void testByte() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Byte) (byte) (Math.random() * Byte.MAX_VALUE)); + userDataTest(sp, Math.random() * Byte.MAX_VALUE); } @Test public void testDouble() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Double) (double) (Math.random() * Double.MAX_VALUE)); + userDataTest(sp, Math.random() * Double.MAX_VALUE); } @Test public void testFloat() throws Exception { Spatial sp = new Node("TestSpatial"); - userDataTest(sp, (Float) (float) (Math.random() * Float.MAX_VALUE)); + userDataTest(sp, Math.random() * Float.MAX_VALUE); } } diff --git a/jme3-core/src/test/java/com/jme3/scene/debug/TestCloneMesh.java b/jme3-core/src/test/java/com/jme3/scene/debug/TestCloneMesh.java new file mode 100644 index 0000000000..b2a9a7704b --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/debug/TestCloneMesh.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2021 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.debug; + +import com.jme3.animation.Bone; +import com.jme3.animation.Skeleton; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.math.Vector3f; +import com.jme3.util.clone.Cloner; +import java.util.HashMap; +import java.util.Map; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test cloning/saving/loading debug meshes of various types. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestCloneMesh { + // ************************************************************************* + // new methods exposed + + /** + * Test cloning/saving/loading an Arrow. + */ + @Test + public void testCloneArrow() { + Arrow arrow = new Arrow(new Vector3f(1f, 1f, 1f)); + + Arrow deepClone = Cloner.deepClone(arrow); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, arrow); + + AssetManager assetManager = new DesktopAssetManager(); + Arrow saveAndLoad = BinaryExporter.saveAndLoad(assetManager, arrow); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a Grid. + */ + @Test + public void testCloneGrid() { + Grid grid = new Grid(5, 5, 1f); + + Grid deepClone = Cloner.deepClone(grid); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, grid); + + AssetManager assetManager = new DesktopAssetManager(); + Grid saveAndLoad = BinaryExporter.saveAndLoad(assetManager, grid); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a SkeletonDebugger. + */ + @Test + public void testCloneSkeletonDebugger() { + Bone[] boneArray = new Bone[2]; + boneArray[0] = new Bone("rootBone"); + boneArray[1] = new Bone("leafBone"); + boneArray[0].addChild(boneArray[1]); + Skeleton skeleton = new Skeleton(boneArray); + skeleton.setBindingPose(); + SkeletonDebugger skeletonDebugger + = new SkeletonDebugger("sd", skeleton); + + SkeletonDebugger deepClone = Cloner.deepClone(skeletonDebugger); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, skeletonDebugger); + + AssetManager assetManager = new DesktopAssetManager(); + SkeletonDebugger saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, skeletonDebugger); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a SkeletonInterBoneWire. See JME issue #1705. + */ + @Test + public void testCloneSkeletonInterBoneWire() { + Bone[] boneArray = new Bone[2]; + boneArray[0] = new Bone("rootBone"); + boneArray[1] = new Bone("leafBone"); + boneArray[0].addChild(boneArray[1]); + Skeleton skeleton = new Skeleton(boneArray); + skeleton.setBindingPose(); + Map boneLengths = new HashMap<>(); + boneLengths.put(0, 2f); + boneLengths.put(1, 1f); + SkeletonInterBoneWire sibw + = new SkeletonInterBoneWire(skeleton, boneLengths); + + SkeletonInterBoneWire deepClone = Cloner.deepClone(sibw); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, sibw); + + AssetManager assetManager = new DesktopAssetManager(); + SkeletonInterBoneWire saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, sibw); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a SkeletonPoints. See JME issue #1705. + */ + @Test + public void testCloneSkeletonPoints() { + Bone[] boneArray = new Bone[2]; + boneArray[0] = new Bone("rootBone"); + boneArray[1] = new Bone("leafBone"); + boneArray[0].addChild(boneArray[1]); + Skeleton skeleton = new Skeleton(boneArray); + skeleton.setBindingPose(); + SkeletonPoints skeletonPoints = new SkeletonPoints(skeleton); + + SkeletonPoints deepClone = Cloner.deepClone(skeletonPoints); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, skeletonPoints); + + AssetManager assetManager = new DesktopAssetManager(); + SkeletonPoints saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, skeletonPoints); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a SkeletonWire. See JME issue #1705. + */ + @Test + public void testCloneSkeletonWire() { + Bone[] boneArray = new Bone[2]; + boneArray[0] = new Bone("rootBone"); + boneArray[1] = new Bone("leafBone"); + boneArray[0].addChild(boneArray[1]); + Skeleton skeleton = new Skeleton(boneArray); + SkeletonWire skeletonWire = new SkeletonWire(skeleton); + + SkeletonWire deepClone = Cloner.deepClone(skeletonWire); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, skeletonWire); + + AssetManager assetManager = new DesktopAssetManager(); + SkeletonWire saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, skeletonWire); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a WireBox. + */ + @Test + public void testCloneWireBox() { + WireBox box = new WireBox(0.5f, 0.5f, 0.5f); + + WireBox deepClone = Cloner.deepClone(box); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, box); + + AssetManager assetManager = new DesktopAssetManager(); + WireBox saveAndLoad = BinaryExporter.saveAndLoad(assetManager, box); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a WireSphere. + */ + @Test + public void testCloneWireSphere() { + WireSphere sphere = new WireSphere(1f); + + WireSphere deepClone = Cloner.deepClone(sphere); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, sphere); + + AssetManager assetManager = new DesktopAssetManager(); + WireSphere saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, sphere); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } + + /** + * Test cloning/saving/loading a WireFrustum. + */ + @Test + public void testIssue1462() { + Vector3f[] vertices = new Vector3f[8]; + for (int vertexIndex = 0; vertexIndex < 8; vertexIndex++) { + vertices[vertexIndex] = new Vector3f(); + } + WireFrustum wireFrustum = new WireFrustum(vertices); + WireFrustum deepClone = Cloner.deepClone(wireFrustum); + Assert.assertNotNull(deepClone); + Assert.assertNotEquals(deepClone, wireFrustum); + + AssetManager assetManager = new DesktopAssetManager(); + WireFrustum saveAndLoad + = BinaryExporter.saveAndLoad(assetManager, wireFrustum); + Assert.assertNotNull(saveAndLoad); + Assert.assertNotEquals(deepClone, saveAndLoad); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/mesh/MeshTest.java b/jme3-core/src/test/java/com/jme3/scene/mesh/MeshTest.java new file mode 100644 index 0000000000..27f0cda591 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/mesh/MeshTest.java @@ -0,0 +1,57 @@ +/* + * 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.mesh; + +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.jme3.scene.Mesh; + +/** + * Tests selected methods of the Mesh class. + * + * @author Melvyn Linke + */ +public class MeshTest { + + /** + * Tests getVertexCount() on a empty Mesh. + */ + @Test + public void testVertexCountOfEmptyMesh() { + final Mesh mesh = new Mesh(); + + assertEquals(-1, mesh.getVertexCount()); + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/mesh/VirtualIndexBufferTest.java b/jme3-core/src/test/java/com/jme3/scene/mesh/VirtualIndexBufferTest.java index 85759a525a..a81ed9f5aa 100644 --- a/jme3-core/src/test/java/com/jme3/scene/mesh/VirtualIndexBufferTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/mesh/VirtualIndexBufferTest.java @@ -28,7 +28,7 @@ public void testRemaining() { assertEquals(7, bufferPoints.remaining()); final VirtualIndexBuffer bufferLineLoop = new VirtualIndexBuffer(8, Mode.LineLoop); - assertEquals(15, bufferLineLoop.remaining()); + assertEquals(16, bufferLineLoop.remaining()); // see JME issue #1603 final VirtualIndexBuffer bufferLineStrip = new VirtualIndexBuffer(8, Mode.LineStrip); assertEquals(14, bufferLineStrip.remaining()); @@ -79,6 +79,23 @@ public void testGet() { ib.clear(); assertEquals(0, bufferPoints.remaining()); + /* + * Test with Mode.Lines and no leftover vertices. + */ + IntBuffer ib8 = IntBuffer.allocate(8); + final VirtualIndexBuffer vibLinesEven + = new VirtualIndexBuffer(8, Mode.Lines); + for (int i = 0; i < 8; i++) { + ib8.put(vibLinesEven.get()); + } + assertArrayEquals(new int[]{ + 0, 1, + 2, 3, + 4, 5, + 6, 7}, ib8.array()); + assertEquals(0, vibLinesEven.remaining()); + ib8.clear(); + final VirtualIndexBuffer bufferLines = new VirtualIndexBuffer(27, Mode.Lines); for (int i=0; i<27; i++) {ib.put(bufferLines.get());} assertArrayEquals(new int[]{ @@ -109,6 +126,22 @@ public void testGet() { assertEquals(0, bufferTriangles.remaining()); ib.clear(); + /* + * Test with Mode.LineStrip and no leftover vertices. + */ + final VirtualIndexBuffer vibLineStripEven + = new VirtualIndexBuffer(5, Mode.LineStrip); + for (int i = 0; i < 8; i++) { + ib8.put(vibLineStripEven.get()); + } + assertArrayEquals(new int[]{ + 0, 1, + 1, 2, + 2, 3, + 3, 4}, ib8.array()); + assertEquals(0, vibLineStripEven.remaining()); + ib8.clear(); + final VirtualIndexBuffer bufferLineStrip = new VirtualIndexBuffer(27, Mode.LineStrip); for (int i=0; i<27; i++) {ib.put(bufferLineStrip.get());} assertArrayEquals(new int[]{ @@ -124,6 +157,24 @@ public void testGet() { assertEquals(25, bufferLineStrip.remaining()); ib.clear(); + /* + * Test with Mode.LineLoop and no leftover vertices, + * to ensure that the loop wraps around to 0 properly. + * See JME issue #1603. + */ + final VirtualIndexBuffer vibLineLoopEven + = new VirtualIndexBuffer(4, Mode.LineLoop); + for (int i = 0; i < 8; i++) { + ib8.put(vibLineLoopEven.get()); + } + assertArrayEquals(new int[]{ + 0, 1, + 1, 2, + 2, 3, + 3, 0}, ib8.array()); + assertEquals(0, vibLineLoopEven.remaining()); + ib8.clear(); + final VirtualIndexBuffer bufferLineLoop = new VirtualIndexBuffer(27, Mode.LineLoop); for (int i=0; i<27; i++) {ib.put(bufferLineLoop.get());} assertArrayEquals(new int[]{ @@ -135,8 +186,8 @@ public void testGet() { 8, 8, 9, 9, 10, 10, 11, 11, 12, - 12, 13, 0}, ib.array()); - assertEquals(26, bufferLineLoop.remaining()); + 12, 13, 13}, ib.array()); + assertEquals(27, bufferLineLoop.remaining()); // see JME issue #1603 ib.clear(); final VirtualIndexBuffer bufferTriangleStrip = new VirtualIndexBuffer(27, Mode.TriangleStrip); @@ -183,7 +234,7 @@ public void testSize() { assertEquals(9, bufferTriangleFan.size()); final VirtualIndexBuffer bufferLineLoop = new VirtualIndexBuffer(8, Mode.LineLoop); - assertEquals(15, bufferLineLoop.size()); + assertEquals(16, bufferLineLoop.size()); // see JME issue #1603 final VirtualIndexBuffer bufferPoints = new VirtualIndexBuffer(8, Mode.Points); assertEquals(8, bufferPoints.size()); diff --git a/jme3-core/src/test/java/com/jme3/scene/plugins/OBJLoaderTest.java b/jme3-core/src/test/java/com/jme3/scene/plugins/OBJLoaderTest.java new file mode 100644 index 0000000000..8fadbedae5 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/plugins/OBJLoaderTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetLoader; +import com.jme3.asset.AssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.TestUtil; +import com.jme3.texture.Image; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class OBJLoaderTest { + private AssetManager assetManager; + + @Before + public void init() { + assetManager = TestUtil.createAssetManager(); + // texture loaders are outside of core, so creating stub + assetManager.registerLoader(PngLoaderStub.class, "png"); + + } + + @Test + public void testHappyPath() { + Node scene = (Node) assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj")); + String sceneAsString = toDiffFriendlyString("", scene); + System.out.println(sceneAsString); + String expectedText = "" + + // generated root name (as before named groups support) + "TwoChairs-objnode\n" + + // unnamed geometry with generated name (as before named groups support). + // actually it's partially smoothed, but this fact is ignored. + " TwoChairs-geom-0 (material: dot_purple)\n" + + // named group as Geometry + " Chair 2 (material: dot_purple)\n" + + // named group as Geometry + " Pillow 2 (material: dot_red)\n" + + // named group as node with two different Geometry instances, + // because two materials are used (as before named groups support) + " Podium\n" + + " TwoChairs-geom-3 (material: dot_red)\n" + + " TwoChairs-geom-4 (material: dot_blue)\n" + + // named group as Geometry + " Pillow 1 (material: dot_green)"; + assertEquals(expectedText, sceneAsString.trim()); + } + + private static String toDiffFriendlyString(String indent, Spatial spatial) { + if (spatial instanceof Geometry) { + return indent + spatial.getName() + " (material: "+((Geometry) spatial).getMaterial().getName()+")\n"; + } + if (spatial instanceof Node) { + StringBuilder s = new StringBuilder(); + s.append(indent).append(spatial.getName()).append("\n"); + Node node = (Node) spatial; + for (final Spatial child : node.getChildren()) { + s.append(toDiffFriendlyString(indent + " ", child)); + } + return s.toString(); + } + return indent + spatial + "\n"; + } + + public static class PngLoaderStub implements AssetLoader { + @Override + public Object load(final AssetInfo assetInfo) { + return new Image(); + } + } +} \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/scene/shape/ShapeBoundsTest.java b/jme3-core/src/test/java/com/jme3/scene/shape/ShapeBoundsTest.java index b87d618d8f..dc923f6f46 100644 --- a/jme3-core/src/test/java/com/jme3/scene/shape/ShapeBoundsTest.java +++ b/jme3-core/src/test/java/com/jme3/scene/shape/ShapeBoundsTest.java @@ -58,12 +58,12 @@ public void testBox() { @Test public void testCurve() { - Vector3f[] controlp = new Vector3f[4]; - controlp[0] = new Vector3f(0, 0, 0); - controlp[1] = new Vector3f(1, 1, 1); - controlp[2] = new Vector3f(2, 1, 1); - controlp[3] = new Vector3f(3, 2, 1); - Curve shape = new Curve(controlp, 32); + Vector3f[] controlPoints = new Vector3f[4]; + controlPoints[0] = new Vector3f(0, 0, 0); + controlPoints[1] = new Vector3f(1, 1, 1); + controlPoints[2] = new Vector3f(2, 1, 1); + controlPoints[3] = new Vector3f(3, 2, 1); + Curve shape = new Curve(controlPoints, 32); Geometry geometry = new Geometry("geom", shape); testBounds(geometry); } diff --git a/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenGeometryExtendedTest.java b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenGeometryExtendedTest.java new file mode 100644 index 0000000000..3b264a4177 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenGeometryExtendedTest.java @@ -0,0 +1,207 @@ +package com.jme3.scene.threadwarden; + +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Box; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Parameterized tests for SceneGraphThreadWarden class with Geometry objects. + * These tests verify that various scene graph mutations are properly checked for thread safety. + */ +@RunWith(Parameterized.class) +public class SceneGraphThreadWardenGeometryExtendedTest { + + private static ExecutorService executorService; + + private final String testName; + private final Consumer action; + + @SuppressWarnings({"ReassignedVariable", "AssertWithSideEffects"}) + @BeforeClass + public static void setupClass() { + // Make sure assertions are enabled + boolean assertsEnabled = false; + assert assertsEnabled = true; + if (!assertsEnabled) { + throw new RuntimeException("WARNING: Assertions are not enabled! Tests may not work correctly."); + } + } + + @Before + public void setup() { + executorService = newSingleThreadDaemonExecutor(); + } + + @After + public void tearDown() { + executorService.shutdown(); + SceneGraphThreadWarden.reset(); + } + + /** + * Constructor for the parameterized test. + * + * @param testName A descriptive name for the test + * @param action The action to perform on the spatial + */ + public SceneGraphThreadWardenGeometryExtendedTest(String testName, Consumer action) { + this.testName = testName; + this.action = action; + } + + /** + * Define the parameters for the test. + * Each parameter is a pair of (test name, action to perform on spatial). + */ + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + Material mockMaterial = Mockito.mock(Material.class); + Box box = new Box(1, 1, 1); + + return Arrays.asList(new Object[][] { + { + "setMaterial", + (Consumer) spatial -> spatial.setMaterial(mockMaterial) + }, + { + "setMesh", + (Consumer) spatial -> spatial.setMesh(box) + }, + { + "setLodLevel", + (Consumer) spatial -> { + // Need to set a mesh with LOD levels first + Mesh mesh = new Box(1, 1, 1); + mesh.setLodLevels(new com.jme3.scene.VertexBuffer[]{ + mesh.getBuffer(com.jme3.scene.VertexBuffer.Type.Index) + }); + spatial.setMesh(mesh); + spatial.setLodLevel(0); + } + }, + { + "removeFromParent", + (Consumer) Geometry::removeFromParent + } + }); + } + + /** + * Test that scene graph mutation is fine on the main thread when the object is attached to the root. + */ + @Test + public void testMutationOnMainThreadOnAttachedObject() { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a geometry and attach it to the root node + Geometry geometry = new Geometry("geometry", new Box(1, 1, 1)); + rootNode.attachChild(geometry); + + // This should work fine since we're on the main thread + action.accept(geometry); + } + + /** + * Test that scene graph mutation is fine on the main thread when the object is not attached to the root. + */ + @Test + public void testMutationOnMainThreadOnDetachedObject() { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a geometry but don't attach it to the root node + Geometry geometry = new Geometry("geometry", new Box(1, 1, 1)); + + // This should work fine since we're on the main thread + action.accept(geometry); + } + + /** + * Test that scene graph mutation is fine on a non-main thread when the object is not attached to the root. + */ + @Test + public void testMutationOnNonMainThreadOnDetachedObject() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a geometry but don't attach it to the root node + Geometry geometry = new Geometry("geometry", new Box(1, 1, 1)); + + Future future = executorService.submit(() -> { + // This should work fine since the geometry is not connected to the root node + action.accept(geometry); + return null; + }); + + // This should complete without exceptions + future.get(); + } + + /** + * Test that scene graph mutation is not allowed on a non-main thread when the object is attached to the root. + */ + @Test + public void testMutationOnNonMainThreadOnAttachedObject() throws InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a geometry and attach it to the root node + Geometry geometry = new Geometry("geometry", new Box(1, 1, 1)); + rootNode.attachChild(geometry); + + Future future = executorService.submit(() -> { + // This should fail because we're trying to modify a geometry that's connected to the scene graph + action.accept(geometry); + return null; + }); + + try { + future.get(); + fail("Expected an IllegalThreadSceneGraphMutation exception"); + } catch (ExecutionException e) { + // This is expected - verify it's the right exception type + assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(), + e.getCause() instanceof IllegalThreadSceneGraphMutation); + } + } + + /** + * Creates a single-threaded executor service with daemon threads. + */ + private static ExecutorService newSingleThreadDaemonExecutor() { + return Executors.newSingleThreadExecutor(daemonThreadFactory()); + } + + /** + * Creates a thread factory that produces daemon threads. + */ + private static ThreadFactory daemonThreadFactory() { + return r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + return t; + }; + } +} \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenNodeExtendedTest.java b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenNodeExtendedTest.java new file mode 100644 index 0000000000..7c36f07bcd --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenNodeExtendedTest.java @@ -0,0 +1,203 @@ +package com.jme3.scene.threadwarden; + +import com.jme3.material.Material; +import com.jme3.material.MatParamOverride; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.shader.VarType; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.mockito.Mockito; + +import java.util.Arrays; +import java.util.Collection; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; +import java.util.function.Consumer; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Parameterized tests for SceneGraphThreadWarden class. + * These tests verify that various scene graph mutations are properly checked for thread safety. + */ +@RunWith(Parameterized.class) +public class SceneGraphThreadWardenNodeExtendedTest { + + private static ExecutorService executorService; + + private final String testName; + private final Consumer action; + + @SuppressWarnings({"ReassignedVariable", "AssertWithSideEffects"}) + @BeforeClass + public static void setupClass() { + // Make sure assertions are enabled + boolean assertsEnabled = false; + assert assertsEnabled = true; + if (!assertsEnabled) { + throw new RuntimeException("WARNING: Assertions are not enabled! Tests may not work correctly."); + } + } + + @Before + public void setup() { + executorService = newSingleThreadDaemonExecutor(); + } + + @After + public void tearDown() { + executorService.shutdown(); + SceneGraphThreadWarden.reset(); + } + + /** + * Constructor for the parameterized test. + * + * @param testName A descriptive name for the test + * @param action The action to perform on the spatial + */ + public SceneGraphThreadWardenNodeExtendedTest(String testName, Consumer action) { + this.testName = testName; + this.action = action; + } + + /** + * Define the parameters for the test. + * Each parameter is a pair of (test name, action to perform on spatial). + */ + @Parameterized.Parameters(name = "{0}") + public static Collection data() { + Material mockMaterial = Mockito.mock(Material.class); + MatParamOverride override = new MatParamOverride(VarType.Float, "TestParam", 1.0f); + + return Arrays.asList(new Object[][] { + { + "setMaterial", + (Consumer) spatial -> spatial.setMaterial(mockMaterial) + }, + { + "setLodLevel", + (Consumer) spatial -> spatial.setLodLevel(1) + }, + { + "addMatParamOverride", + (Consumer) spatial -> spatial.addMatParamOverride(override) + }, + { + "removeMatParamOverride", + (Consumer) spatial -> spatial.removeMatParamOverride(override) + }, + { + "clearMatParamOverrides", + (Consumer) Spatial::clearMatParamOverrides + } + }); + } + + /** + * Test that scene graph mutation is fine on the main thread when the object is attached to the root. + */ + @Test + public void testMutationOnMainThreadOnAttachedObject() { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and attach it to the root node + Node child = new Node("child"); + rootNode.attachChild(child); + + // This should work fine since we're on the main thread + action.accept(child); + } + + /** + * Test that scene graph mutation is fine on the main thread when the object is not attached to the root. + */ + @Test + public void testMutationOnMainThreadOnDetachedObject() { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node but don't attach it to the root node + Node child = new Node("child"); + + // This should work fine since we're on the main thread + action.accept(child); + } + + /** + * Test that scene graph mutation is fine on a non-main thread when the object is not attached to the root. + */ + @Test + public void testMutationOnNonMainThreadOnDetachedObject() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node but don't attach it to the root node + Node child = new Node("child"); + + Future future = executorService.submit(() -> { + // This should work fine since the node is not connected to the root node + action.accept(child); + return null; + }); + + // This should complete without exceptions + future.get(); + } + + /** + * Test that scene graph mutation is not allowed on a non-main thread when the object is attached to the root. + */ + @Test + public void testMutationOnNonMainThreadOnAttachedObject() throws InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and attach it to the root node + Node child = new Node("child"); + rootNode.attachChild(child); + + Future future = executorService.submit(() -> { + // This should fail because we're trying to modify a node that's connected to the scene graph + action.accept(child); + return null; + }); + + try { + future.get(); + fail("Expected an IllegalThreadSceneGraphMutation exception"); + } catch (ExecutionException e) { + // This is expected - verify it's the right exception type + assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(), + e.getCause() instanceof IllegalThreadSceneGraphMutation); + } + } + + /** + * Creates a single-threaded executor service with daemon threads. + */ + private static ExecutorService newSingleThreadDaemonExecutor() { + return Executors.newSingleThreadExecutor(daemonThreadFactory()); + } + + /** + * Creates a thread factory that produces daemon threads. + */ + private static ThreadFactory daemonThreadFactory() { + return r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + return t; + }; + } +} diff --git a/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenTest.java b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenTest.java new file mode 100644 index 0000000000..7c92b2edd6 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/scene/threadwarden/SceneGraphThreadWardenTest.java @@ -0,0 +1,316 @@ +package com.jme3.scene.threadwarden; + +import com.jme3.scene.Node; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ThreadFactory; + +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** + * Tests for SceneGraphThreadWarden class. + * These tests verify that: + * - Normal node mutation is fine on the main thread + * - Node mutation on nodes not connected to the root node is fine even on a non main thread + * - Adding a node to the scene graph (indirectly) connected to the root node isn't fine on a non main thread + * - Adding a node currently attached to a root node to a different node isn't fine on a non main thread + */ +public class SceneGraphThreadWardenTest { + + private static ExecutorService executorService; + + @SuppressWarnings({"ReassignedVariable", "AssertWithSideEffects"}) + @BeforeClass + public static void setupClass() { + // Make sure assertions are enabled + boolean assertsEnabled = false; + assert assertsEnabled = true; + //noinspection ConstantValue + if (!assertsEnabled) { + throw new RuntimeException("WARNING: Assertions are not enabled! Tests may not work correctly."); + } + } + + @Before + public void setup() { + executorService = newSingleThreadDaemonExecutor(); + } + + @After + public void tearDown() { + executorService.shutdown(); + SceneGraphThreadWarden.reset(); + } + + /** + * Test that normal node mutation is fine on the main thread. + */ + @Test + public void testNormalNodeMutationOnMainThread() { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // This should work fine since we're on the main thread + Node child = new Node("child"); + rootNode.attachChild(child); + + // Add another level of children + Node grandchild = new Node("grandchild"); + child.attachChild(grandchild); + + // Detach should also work fine + child.detachChild(grandchild); + rootNode.detachChild(child); + } + + /** + * Test that node mutation on nodes not connected to the root node is fine even on a non main thread. + *

                + * This is a use case where a thread is preparing things for later attachment to the scene graph. + *

                + */ + @Test + public void testNodeMutationOnNonConnectedNodesOnNonMainThread() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + Future nonConnectedNodeFuture = executorService.submit(() -> { + // This should work fine since these nodes are not connected to the root node + Node parent = new Node("parent"); + Node child = new Node("child"); + parent.attachChild(child); + + // Add another level of children + Node grandchild = new Node("grandchild"); + child.attachChild(grandchild); + + return parent; + }); + + // Get the result to ensure the task completed without exceptions + Node nonConnectedNode = nonConnectedNodeFuture.get(); + + // Now we can attach it to the root node on the main thread + rootNode.attachChild(nonConnectedNode); + } + + /** + * Test that adding a node to the scene graph connected to the root node in a non main thread leads to an + * exception. + */ + @Test + public void testAddingNodeToSceneGraphOnNonMainThread() throws InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and attach it to the root node + Node child = new Node("child"); + rootNode.attachChild(child); + + Future illegalMutationFuture = executorService.submit(() -> { + // This should fail because we're trying to add a node to a node that's connected to the scene graph + Node grandchild = new Node("grandchild"); + child.attachChild(grandchild); + return null; + }); + + try { + illegalMutationFuture.get(); + fail("Expected an IllegalThreadSceneGraphMutation exception"); + } catch (ExecutionException e) { + // This is expected - verify it's the right exception type + assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(), + e.getCause() instanceof IllegalThreadSceneGraphMutation); + } + } + + /** + * Test that adding a node currently attached to a root node to a different node leads to an exception. + *

                + * This is testing an edge case where you think you'd working with non-connected nodes, but in reality + * one of your nodes is already attached to the scene graph (and you're attaching it to a different node which will + * detach it from the scene graph). + *

                + */ + @Test + public void testMovingNodeAttachedToRootOnNonMainThread() throws InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create two child nodes and attach them to the root node + Node child1 = new Node("child1"); + Node child2 = new Node("child2"); + + rootNode.attachChild(child2); + + Future illegalMutationFuture = executorService.submit(() -> { + // This should fail because we're trying to move a node that's connected to the root node + child1.attachChild(child2); // This implicitly detaches child2 from rootNode + return null; + }); + + try { + illegalMutationFuture.get(); + fail("Expected an IllegalThreadSceneGraphMutation exception"); + } catch (ExecutionException e) { + // This is expected - verify it's the right exception type + assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(), + e.getCause() instanceof IllegalThreadSceneGraphMutation); + } + } + + /** + * Test that detaching a node releases it from thread protection. + */ + @Test + public void testDetachmentReleasesProtection() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and attach it to the root node + Node child = new Node("child"); + rootNode.attachChild(child); + + // Now detach it from the root node + child.removeFromParent(); + + // Now we should be able to modify it on another thread + Future legalMutationFuture = executorService.submit(() -> { + Node grandchild = new Node("grandchild"); + child.attachChild(grandchild); + return null; + }); + + // This should complete without exceptions + legalMutationFuture.get(); + } + + /** + * Test that adding a child to the root node also restricts the grandchild. + * This test will add a grandchild to a child BEFORE adding the child to the root, + * then try (and fail) to make an illegal on-thread change to the grandchild. + */ + @Test + public void testAddingAChildToTheRootNodeAlsoRestrictsTheGrandChild() throws InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and a grandchild node + Node child = new Node("child"); + Node grandchild = new Node("grandchild"); + + // Attach the grandchild to the child BEFORE adding the child to the root + child.attachChild(grandchild); + + // Now attach the child to the root node + rootNode.attachChild(child); + + // Try to make an illegal on-thread change to the grandchild + Future illegalMutationFuture = executorService.submit(() -> { + // This should fail because the grandchild is now restricted + Node greatGrandchild = new Node("greatGrandchild"); + grandchild.attachChild(greatGrandchild); + return null; + }); + + try { + illegalMutationFuture.get(); + fail("Expected an IllegalThreadSceneGraphMutation exception"); + } catch (ExecutionException e) { + // This is expected - verify it's the right exception type + assertTrue("Expected IllegalThreadSceneGraphMutation, got: " + e.getCause().getClass().getName(), + e.getCause() instanceof IllegalThreadSceneGraphMutation); + } + } + + /** + * Test that removing a child from the root node also unrestricts the grandchild. + * This test will add a child with a grandchild to the root node, then remove the child + * and verify that the grandchild can be modified on a non-main thread. + */ + @Test + public void testRemovingAChildFromTheRootNodeAlsoUnrestrictsTheGrandChild() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and a grandchild node + Node child = new Node("child"); + Node grandchild = new Node("grandchild"); + + // Attach the grandchild to the child + child.attachChild(grandchild); + + // Attach the child to the root node + rootNode.attachChild(child); + + // Now remove the child from the root node + child.removeFromParent(); + + // Try to make a change to the grandchild on a non-main thread + Future legalMutationFuture = executorService.submit(() -> { + // This should succeed because the grandchild is no longer restricted + Node greatGrandchild = new Node("greatGrandchild"); + grandchild.attachChild(greatGrandchild); + return null; + }); + + // This should complete without exceptions + legalMutationFuture.get(); + } + + /** + * Test that an otherwise illegal scene graph mutation won't throw an exception + * if the checks have been disabled by calling disableChecks(). + */ + @Test + public void testDisableChecksAllowsIllegalMutation() throws ExecutionException, InterruptedException { + Node rootNode = new Node("root"); + SceneGraphThreadWarden.setup(rootNode); + + // Create a child node and attach it to the root node + Node child = new Node("child"); + rootNode.attachChild(child); + + // Disable the thread warden checks + SceneGraphThreadWarden.disableChecks(); + + // Try to make a change to the child on a non-main thread + // This would normally be illegal, but should succeed because checks are disabled + Future mutationFuture = executorService.submit(() -> { + Node grandchild = new Node("grandchild"); + child.attachChild(grandchild); + return null; + }); + + // This should complete without exceptions + mutationFuture.get(); + } + + + + /** + * Creates a single-threaded executor service with daemon threads. + */ + private static ExecutorService newSingleThreadDaemonExecutor() { + return Executors.newSingleThreadExecutor(daemonThreadFactory()); + } + + /** + * Creates a thread factory that produces daemon threads. + */ + private static ThreadFactory daemonThreadFactory() { + return r -> { + Thread t = Executors.defaultThreadFactory().newThread(r); + t.setDaemon(true); + return t; + }; + } +} diff --git a/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java index 4c2c02b934..ac3ada832b 100644 --- a/jme3-core/src/test/java/com/jme3/shader/DefineListTest.java +++ b/jme3-core/src/test/java/com/jme3/shader/DefineListTest.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 @@ -47,7 +47,6 @@ public class DefineListTest { private static final int BOOL_VAR = 0; private static final int INT_VAR = 1; private static final int FLOAT_VAR = 2; - private static final DefineList EMPTY = new DefineList(NUM_DEFINES); @Test public void testHashCollision() { @@ -92,7 +91,9 @@ private String generateSource(DefineList dl) { @Test public void testSourceInitial() { DefineList dl = new DefineList(NUM_DEFINES); - assert dl.hashCode() == 0; + for (int id = 0; id < NUM_DEFINES; ++id) { + assert !dl.isSet(id); + } assert generateSource(dl).equals(""); } @@ -101,19 +102,29 @@ public void testSourceBooleanDefine() { DefineList dl = new DefineList(NUM_DEFINES); dl.set(BOOL_VAR, true); - assert dl.hashCode() == 1; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isBoolVar = (id == BOOL_VAR); + assert dl.isSet(id) == isBoolVar; + } assert generateSource(dl).equals("#define BOOL_VAR 1\n"); dl.set(BOOL_VAR, false); - assert dl.hashCode() == 0; + for (int id = 0; id < NUM_DEFINES; ++id) { + assert !dl.isSet(id); + } assert generateSource(dl).equals(""); dl.set(BOOL_VAR, true); - assert dl.hashCode() == 1; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isBoolVar = (id == BOOL_VAR); + assert dl.isSet(id) == isBoolVar; + } assert generateSource(dl).equals("#define BOOL_VAR 1\n"); dl.unset(BOOL_VAR); - assert dl.hashCode() == 0; + for (int id = 0; id < NUM_DEFINES; ++id) { + assert !dl.isSet(id); + } assert generateSource(dl).equals(""); } @@ -121,26 +132,38 @@ public void testSourceBooleanDefine() { public void testSourceIntDefine() { DefineList dl = new DefineList(NUM_DEFINES); - int hashCodeWithInt = 1 << INT_VAR; - dl.set(INT_VAR, 123); - assert dl.hashCode() == hashCodeWithInt; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isIntVar = (id == INT_VAR); + assert dl.isSet(id) == isIntVar; + } assert generateSource(dl).equals("#define INT_VAR 123\n"); dl.set(INT_VAR, 0); - assert dl.hashCode() == hashCodeWithInt; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isIntVar = (id == INT_VAR); + assert dl.isSet(id) == isIntVar; + } assert generateSource(dl).equals("#define INT_VAR 0\n"); dl.set(INT_VAR, -99); - assert dl.hashCode() == hashCodeWithInt; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isIntVar = (id == INT_VAR); + assert dl.isSet(id) == isIntVar; + } assert generateSource(dl).equals("#define INT_VAR -99\n"); dl.set(INT_VAR, Integer.MAX_VALUE); - assert dl.hashCode() == hashCodeWithInt; + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isIntVar = (id == INT_VAR); + assert dl.isSet(id) == isIntVar; + } assert generateSource(dl).equals("#define INT_VAR 2147483647\n"); dl.unset(INT_VAR); - assert dl.hashCode() == 0; + for (int id = 0; id < NUM_DEFINES; ++id) { + assert !dl.isSet(id); + } assert generateSource(dl).equals(""); } @@ -149,11 +172,17 @@ public void testSourceFloatDefine() { DefineList dl = new DefineList(NUM_DEFINES); dl.set(FLOAT_VAR, 1f); - assert dl.hashCode() == (1 << FLOAT_VAR); + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isFloatVar = (id == FLOAT_VAR); + assert dl.isSet(id) == isFloatVar; + } assert generateSource(dl).equals("#define FLOAT_VAR 1.0\n"); dl.set(FLOAT_VAR, 0f); - assert dl.hashCode() == (1 << FLOAT_VAR); + for (int id = 0; id < NUM_DEFINES; ++id) { + boolean isFloatVar = (id == FLOAT_VAR); + assert dl.isSet(id) == isFloatVar; + } assert generateSource(dl).equals("#define FLOAT_VAR 0.0\n"); dl.set(FLOAT_VAR, -1f); @@ -192,49 +221,38 @@ public void testEqualsAndHashCode() { DefineList dl1 = new DefineList(NUM_DEFINES); DefineList dl2 = new DefineList(NUM_DEFINES); - assertEquals(0, dl1.hashCode()); - assertEquals(0, dl2.hashCode()); + assertEquals(dl1.hashCode(), dl2.hashCode()); assertEquals(dl1, dl2); dl1.set(BOOL_VAR, true); - assertEquals(1, dl1.hashCode()); - assertEquals(0, dl2.hashCode()); assertNotEquals(dl1, dl2); dl2.set(BOOL_VAR, true); - assertEquals(1, dl1.hashCode()); - assertEquals(1, dl2.hashCode()); + assertEquals(dl1.hashCode(), dl2.hashCode()); assertEquals(dl1, dl2); dl1.set(INT_VAR, 2); - assertEquals(1 | 2, dl1.hashCode()); - assertEquals(1, dl2.hashCode()); assertNotEquals(dl1, dl2); dl2.set(INT_VAR, 2); - assertEquals(1 | 2, dl1.hashCode()); - assertEquals(1 | 2, dl2.hashCode()); + assertEquals(dl1.hashCode(), dl2.hashCode()); assertEquals(dl1, dl2); dl1.set(BOOL_VAR, false); - assertEquals(2, dl1.hashCode()); - assertEquals(1 | 2, dl2.hashCode()); assertNotEquals(dl1, dl2); dl2.unset(BOOL_VAR); - assertEquals(2, dl1.hashCode()); - assertEquals(2, dl2.hashCode()); + assertEquals(dl1.hashCode(), dl2.hashCode()); assertEquals(dl1, dl2); // unset is the same as false dl1.unset(BOOL_VAR); - assertEquals(2, dl1.hashCode()); - assertEquals(2, dl2.hashCode()); + assertEquals(dl1.hashCode(), dl2.hashCode()); assertEquals(dl1, dl2); } diff --git a/jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java b/jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java new file mode 100644 index 0000000000..5ba4ecd2c7 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/shader/GLSLPreprocessorTest.java @@ -0,0 +1,126 @@ +/* + * 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.shader; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import java.io.BufferedReader; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetKey; +import com.jme3.system.TestUtil; + +import org.junit.Test; + +import jme3tools.shader.Preprocessor; + + +public class GLSLPreprocessorTest { + + String readAllAsString(InputStream is) throws Exception{ + String output = ""; + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + while (true) { + String l = reader.readLine(); + if (l == null) break; + if (output != "") output += "\n"; + output += l; + } + reader.close(); + return output; + } + + @Test + public void testFOR() throws Exception{ + String source = "#for i=0..2 (#ifdef IS_SET$i $0 #endif)\n" + + " uniform float m_Something$i;\n" + + "#endfor"; + String processedSource= readAllAsString(Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8")))); + + AssetInfo testData = TestUtil.createAssetManager().locateAsset(new AssetKey("GLSLPreprocessorTest.testFOR.validOutput")); + assertNotNull(testData); + String sourceCheck=readAllAsString(testData.openStream()); + assertEquals(sourceCheck, processedSource); + } + + @Test + public void testStruct() throws Exception { + String source = "// nothing\n#struct MyStruct \n" + " float x ;//nothing \n" + " float y;\n" + + "#endstruct\n//nothing"; + String processedSource = readAllAsString( + Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8")))); + System.out.println(processedSource); + AssetInfo testData = TestUtil.createAssetManager() + .locateAsset(new AssetKey("GLSLPreprocessorTest.testStruct.validOutput")); + assertNotNull(testData); + String sourceCheck = readAllAsString(testData.openStream()); + assertEquals(sourceCheck, processedSource); + } + + @Test + public void testStructExtends() throws Exception { + String source = "// nothing\n#struct BaseStruct \n" + " float x0;\n" + " float y0;\n" + + "#endstruct\n//nothing\n"; + source += "//nothing\n#struct MyStruct extends BaseStruct \n" + " float x;\n" + " float y;\n" + + "#endstruct\n//nothing\n"; + String processedSource = readAllAsString( + Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8")))); + System.out.println(processedSource); + AssetInfo testData = TestUtil.createAssetManager() + .locateAsset(new AssetKey("GLSLPreprocessorTest.testStructExtends.validOutput")); + assertNotNull(testData); + String sourceCheck = readAllAsString(testData.openStream()); + assertEquals(sourceCheck, processedSource); + } + + @Test + public void testStructExtendsMulti() throws Exception { + String source = "#struct BaseStruct \n" + " float x0;\n" + " float y0;\n" + "#endstruct\n"; + source += "#struct BaseStruct2 \n" + " float x1;\n" + " float y1;\n" + + "#endstruct\n//nothing\n"; + source += "#struct MyStruct extends BaseStruct, BaseStruct2\n" + " float x;\n" + " float y;\n" + + "#endstruct\n"; + String processedSource = readAllAsString( + Preprocessor.apply(new ByteArrayInputStream(source.getBytes("UTF-8")))); + System.out.println(processedSource); + AssetInfo testData = TestUtil.createAssetManager().locateAsset( + new AssetKey("GLSLPreprocessorTest.testStructExtendsMulti.validOutput")); + assertNotNull(testData); + String sourceCheck = readAllAsString(testData.openStream()); + assertEquals(sourceCheck, processedSource); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/shader/UniformTest.java b/jme3-core/src/test/java/com/jme3/shader/UniformTest.java new file mode 100644 index 0000000000..1c67633b4e --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/shader/UniformTest.java @@ -0,0 +1,324 @@ +/* + * Copyright (c) 2009-2021 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.shader; + +import com.jme3.math.*; +import org.junit.Test; + +import java.nio.FloatBuffer; +import java.nio.IntBuffer; +import java.util.Arrays; + +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; + +public class UniformTest { + + @Test + public void testSetValue_IntArray() { + Uniform uniform = new Uniform(); + + // Set value for the first time + int[] intArray1 = new int[] {1, 2, 4, 8}; + uniform.setValue(VarType.IntArray, intArray1); + + assertTrue(uniform.getValue() instanceof IntBuffer); + verifyIntBufferContent((IntBuffer) uniform.getValue(), intArray1); + + // Overriding the previous value + int[] intArray2 = new int[] {3, 5, 7, 11, 13}; + uniform.setValue(VarType.IntArray, intArray2); + + assertTrue(uniform.getValue() instanceof IntBuffer); + verifyIntBufferContent((IntBuffer) uniform.getValue(), intArray2); + } + + private void verifyIntBufferContent(IntBuffer intBuffer, int[] intArray) { + assertEquals(0, intBuffer.position()); + assertEquals(intArray.length, intBuffer.capacity()); + assertEquals(intArray.length, intBuffer.limit()); + + for (int i = 0; i < intArray.length; i++) { + assertEquals(intArray[i], intBuffer.get(i)); + } + } + + + @Test + public void testSetValue_FloatArray() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] floatArray1 = new float[] {1.1f, 2.2f, 4.4f, 8.8f}; + uniform.setValue(VarType.FloatArray, floatArray1); + + verifyFloatBufferContent(uniform.getMultiData(), floatArray1); + + // Overriding the previous value + float[] floatArray2 = new float[] {3.3f, 5.5f, 7.7f, 11.11f, 13.13f}; + uniform.setValue(VarType.FloatArray, floatArray2); + + verifyFloatBufferContent(uniform.getMultiData(), floatArray2); + } + + + @Test + public void testSetValue_Vector2Array() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] expectedData1 = new float[] { + 1.1f, 2.2f, + 3.3f, 4.4f + }; + Vector2f[] vector2Array1 = new Vector2f[] { + new Vector2f(expectedData1[0], expectedData1[1]), + new Vector2f(expectedData1[2], expectedData1[3]) + }; + uniform.setValue(VarType.Vector2Array, vector2Array1); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData1); + + // Overriding the previous value + float[] expectedData2 = new float[] { + 1.2f, 2.3f, + 3.4f, 4.5f, + 5.6f, 6.7f + }; + Vector2f[] vector2Array2 = new Vector2f[] { + new Vector2f(expectedData2[0], expectedData2[1]), + new Vector2f(expectedData2[2], expectedData2[3]), + new Vector2f(expectedData2[4], expectedData2[5]) + }; + uniform.setValue(VarType.Vector2Array, vector2Array2); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData2); + } + + + @Test + public void testSetValue_Vector3Array() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] expectedData1 = new float[] { + 1.1f, 2.2f, 3.3f, + 4.4f, 5.5f, 6.6f + }; + Vector3f[] vector3Array1 = new Vector3f[] { + new Vector3f(expectedData1[0], expectedData1[1], expectedData1[2]), + new Vector3f(expectedData1[3], expectedData1[4], expectedData1[5]) + }; + uniform.setValue(VarType.Vector3Array, vector3Array1); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData1); + + // Overriding the previous value + float[] expectedData2 = new float[] { + 1.2f, 2.3f, 3.4f, + 4.5f, 5.6f, 6.7f, + 7.8f, 8.9f, 9.1f + }; + Vector3f[] vector3Array2 = new Vector3f[] { + new Vector3f(expectedData2[0], expectedData2[1], expectedData2[2]), + new Vector3f(expectedData2[3], expectedData2[4], expectedData2[5]), + new Vector3f(expectedData2[6], expectedData2[7], expectedData2[8]) + }; + uniform.setValue(VarType.Vector3Array, vector3Array2); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData2); + } + + + @Test + public void testSetValue_Vector4Array() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] expectedData1 = new float[] { + 1.1f, 2.2f, 3.3f, 4.4f, + 5.5f, 6.6f, 7.7f, 8.8f + }; + Vector4f[] vector4Array1 = new Vector4f[] { + new Vector4f(expectedData1[0], expectedData1[1], expectedData1[2], expectedData1[3]), + new Vector4f(expectedData1[4], expectedData1[5], expectedData1[6], expectedData1[7]) + }; + uniform.setValue(VarType.Vector4Array, vector4Array1); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData1); + + // Overriding the previous value + float[] expectedData2 = new float[] { + 1.2f, 2.3f, 3.4f, 4.5f, + 5.6f, 6.7f, 7.8f, 8.9f, + 9.10f, 10.11f, 11.12f, 12.13f + }; + Vector4f[] vector4Array2 = new Vector4f[] { + new Vector4f(expectedData2[0], expectedData2[1], expectedData2[2], expectedData2[3]), + new Vector4f(expectedData2[4], expectedData2[5], expectedData2[6], expectedData2[7]), + new Vector4f(expectedData2[8], expectedData2[9], expectedData2[10], expectedData2[11]) + }; + uniform.setValue(VarType.Vector4Array, vector4Array2); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData2); + } + + + @Test + public void testSetValue_Matrix3Array() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] expectedData1 = new float[] { + 1.1f, 2.2f, 3.3f, + 4.4f, 5.5f, 6.6f, + 7.7f, 8.8f, 9.9f, + + 10.10f, 11.11f, 12.12f, + 13.13f, 14.14f, 15.15f, + 16.16f, 17.17f, 18.18f + }; + Matrix3f[] matrix3Array1 = new Matrix3f[] { + new Matrix3f( + expectedData1[0], expectedData1[3], expectedData1[6], + expectedData1[1], expectedData1[4], expectedData1[7], + expectedData1[2], expectedData1[5], expectedData1[8] + ), + new Matrix3f( + expectedData1[9], expectedData1[12], expectedData1[15], + expectedData1[10], expectedData1[13], expectedData1[16], + expectedData1[11], expectedData1[14], expectedData1[17] + ) + }; + uniform.setValue(VarType.Matrix3Array, matrix3Array1); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData1); + + // Overriding the previous value + float[] expectedData2 = new float[] { + 1.2f, 2.3f, 3.4f, + 4.5f, 5.6f, 6.7f, + 7.8f, 8.9f, 9.1f, + + 10.11f, 11.12f, 12.13f, + 13.14f, 14.15f, 15.16f, + 16.17f, 17.18f, 18.19f, + + 19.20f, 20.21f, 21.22f, + 22.23f, 23.24f, 24.25f, + 25.26f, 26.27f, 27.28f + }; + Matrix3f[] matrix3Array2 = new Matrix3f[] { + new Matrix3f( + expectedData2[0], expectedData2[3], expectedData2[6], + expectedData2[1], expectedData2[4], expectedData2[7], + expectedData2[2], expectedData2[5], expectedData2[8] + ), + new Matrix3f( + expectedData2[9], expectedData2[12], expectedData2[15], + expectedData2[10], expectedData2[13], expectedData2[16], + expectedData2[11], expectedData2[14], expectedData2[17] + ), + new Matrix3f( + expectedData2[18], expectedData2[21], expectedData2[24], + expectedData2[19], expectedData2[22], expectedData2[25], + expectedData2[20], expectedData2[23], expectedData2[26] + ) + }; + uniform.setValue(VarType.Matrix3Array, matrix3Array2); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData2); + } + + + @Test + public void testSetValue_Matrix4Array() { + Uniform uniform = new Uniform(); + + // Set value for the first time + float[] expectedData1 = new float[] { + 1.1f, 2.2f, 3.3f, 4.4f, + 5.5f, 6.6f, 7.7f, 8.8f, + 9.9f, 10.10f, 11.11f, 12.12f, + 13.13f, 14.14f, 15.15f, 16.16f, + + 17.17f, 18.18f, 19.19f, 20.20f, + 21.21f, 22.22f, 23.23f, 24.24f, + 25.25f, 26.26f, 27.27f, 28.28f, + 29.29f, 30.30f, 31.31f, 32.32f + }; + Matrix4f[] matrix4Array1 = new Matrix4f[] { + new Matrix4f(Arrays.copyOfRange(expectedData1, 0, 16)), + new Matrix4f(Arrays.copyOfRange(expectedData1, 16, 32)) + }; + uniform.setValue(VarType.Matrix4Array, matrix4Array1); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData1); + + // Overriding the previous value + float[] expectedData2 = new float[] { + 1.2f, 2.3f, 3.4f, 4.5f, + 5.6f, 6.7f, 7.8f, 8.9f, + 9.1f, 10.11f, 11.12f, 12.13f, + 13.14f, 14.15f, 15.16f, 16.17f, + + 17.18f, 18.19f, 19.20f, 20.21f, + 21.22f, 22.23f, 23.24f, 24.25f, + 25.26f, 26.27f, 27.28f, 28.29f, + 29.30f, 30.31f, 31.32f, 32.33f, + + 33.34f, 34.35f, 35.36f, 36.37f, + 37.38f, 38.39f, 39.40f, 40.41f, + 41.42f, 42.43f, 43.44f, 44.45f, + 45.46f, 46.47f, 47.48f, 48.49f + }; + Matrix4f[] matrix4Array2 = new Matrix4f[] { + new Matrix4f(Arrays.copyOfRange(expectedData2, 0, 16)), + new Matrix4f(Arrays.copyOfRange(expectedData2, 16, 32)), + new Matrix4f(Arrays.copyOfRange(expectedData2, 32, 48)) + }; + uniform.setValue(VarType.Matrix4Array, matrix4Array2); + + verifyFloatBufferContent(uniform.getMultiData(), expectedData2); + } + + private void verifyFloatBufferContent(FloatBuffer floatBuffer, float[] floatArray) { + assertEquals(0, floatBuffer.position()); + assertEquals(floatArray.length, floatBuffer.capacity()); + assertEquals(floatArray.length, floatBuffer.limit()); + + for (int i = 0; i < floatArray.length; i++) { + assertEquals(floatArray[i], floatBuffer.get(i), 0f); + } + } + +} diff --git a/jme3-core/src/test/java/com/jme3/shadow/FilterPostProcessingTest.java b/jme3-core/src/test/java/com/jme3/shadow/FilterPostProcessingTest.java new file mode 100644 index 0000000000..83ff564b0b --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/shadow/FilterPostProcessingTest.java @@ -0,0 +1,68 @@ +package com.jme3.shadow; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.Filter; +import com.jme3.post.FilterPostProcessor; +import org.junit.Assert; +import org.junit.Test; + +/** + * Automated tests for the {@code FilterPostProcessing} class. + * + * @author capdevon + */ +public class FilterPostProcessingTest { + + /** + * Tests serialization and de-serialization of a {@code FilterPostProcessing}. + */ + @Test + public void testSaveAndLoad() { + AssetManager assetManager = new DesktopAssetManager(true); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + fpp.addFilter(createDirectionalLightShadowFilter(assetManager)); + fpp.addFilter(createSpotLightShadowFilter(assetManager)); + fpp.addFilter(createPointLightShadowFilter(assetManager)); + + BinaryExporter.saveAndLoad(assetManager, fpp); + } + + private DirectionalLightShadowFilter createDirectionalLightShadowFilter(AssetManager assetManager) { + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -2, -3).normalizeLocal()); + light.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 1f)); + + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 2048, 1); + dlsf.setLight(light); + + return dlsf; + } + + private SpotLightShadowFilter createSpotLightShadowFilter(AssetManager assetManager) { + SpotLight light = new SpotLight(); + light.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 1f)); + + SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 2048); + slsf.setLight(light); + + return slsf; + } + + private PointLightShadowFilter createPointLightShadowFilter(AssetManager assetManager) { + PointLight light = new PointLight(); + light.setColor(new ColorRGBA(0.8f, 0.8f, 0.8f, 1f)); + + PointLightShadowFilter plsf = new PointLightShadowFilter(assetManager, 2048); + plsf.setLight(light); + + return plsf; + } +} 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..d26f961341 100644 --- a/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java +++ b/jme3-core/src/test/java/com/jme3/system/MockJmeSystemDelegate.java @@ -32,6 +32,8 @@ package com.jme3.system; import com.jme3.audio.AudioRenderer; +import com.jme3.util.res.Resources; + import java.io.IOException; import java.io.OutputStream; import java.net.URL; @@ -43,18 +45,9 @@ 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"); + return Resources.getResource("com/jme3/asset/General.cfg"); } @Override diff --git a/jme3-core/src/test/java/com/jme3/system/TestUtil.java b/jme3-core/src/test/java/com/jme3/system/TestUtil.java index 124b59ba74..b23d1846ee 100644 --- a/jme3-core/src/test/java/com/jme3/system/TestUtil.java +++ b/jme3-core/src/test/java/com/jme3/system/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,8 @@ import com.jme3.asset.AssetManager; import com.jme3.asset.DesktopAssetManager; import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; + import java.util.logging.Level; import java.util.logging.Logger; @@ -44,12 +46,24 @@ public class TestUtil { JmeSystem.setSystemDelegate(new MockJmeSystemDelegate()); } + /** + * A private constructor to inhibit instantiation of this class. + */ + private TestUtil() { + } + public static AssetManager createAssetManager() { Logger.getLogger(AssetConfig.class.getName()).setLevel(Level.OFF); return new DesktopAssetManager(true); } - public static RenderManager createRenderManager() { - return new RenderManager(new NullRenderer()); + public static RenderManager createRenderManager() { + return createRenderManager(new NullRenderer()); + } + + public static RenderManager createRenderManager(Renderer renderer) { + RenderManager rm = new RenderManager(renderer); + rm.setPassDrawBufferTargetIdToShaders(false); + return rm; } } diff --git a/jme3-core/src/test/java/com/jme3/test/PreventCoreIssueRegressions.java b/jme3-core/src/test/java/com/jme3/test/PreventCoreIssueRegressions.java new file mode 100644 index 0000000000..d754c40ed3 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/test/PreventCoreIssueRegressions.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019-2021 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.test; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.app.LegacyApplication; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppState; +import com.jme3.app.state.ScreenshotAppState; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.input.InputManager; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Node; +import com.jme3.system.JmeSystem; +import com.jme3.system.NullRenderer; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Field; +import java.util.List; + +/** + * The Test Suite to prevent regressions from previously fixed Core issues + * @author Stephen Gold <sgold@sonic.net> + */ +public class PreventCoreIssueRegressions { + + /** + * Test case for JME issue #1421: ScreenshotAppState never cleans up. + */ + @Test + public void testIssue1421() throws NoSuchFieldException, IllegalAccessException { + ScreenshotAppState screenshotAppState = new ScreenshotAppState("./", "screen_shot"); + + SimpleApplication app = new SimpleApplication(new AppState[]{}) { + // Dummy application because SimpleApp is abstract + @Override + public void simpleInitApp() { } + }; + + app.setAssetManager(new DesktopAssetManager(true)); + Field f1 = LegacyApplication.class.getDeclaredField("inputManager"); + f1.setAccessible(true); + f1.set(app, new InputManager(new DummyMouseInput(), new DummyKeyInput(), null, null)); + + Field f2 = LegacyApplication.class.getDeclaredField("renderManager"); + f2.setAccessible(true); + f2.set(app, new RenderManager(new NullRenderer())); + + app.getRenderManager().createPostView("null", new Camera(1, 1)); + + Assert.assertTrue(app.getStateManager().attach(screenshotAppState)); + app.getStateManager().update(0f); // Causes SAS#initialize to be called + + // Confirm that the SceneProcessor is attached. + List vps = app.getRenderManager().getPostViews(); + Assert.assertEquals(1, vps.size()); + Assert.assertEquals(1, vps.get(0).getProcessors().size()); + Assert.assertTrue(app.getInputManager().hasMapping("ScreenShot")); // Confirm that KEY_SYSRQ is mapped. + Assert.assertTrue(app.getStateManager().detach(screenshotAppState)); + + app.getStateManager().update(0f); // Causes SAS#cleanup to be called + + // Check whether the SceneProcessor is still attached. + Assert.assertEquals(0, vps.get(0).getProcessors().size()); + Assert.assertFalse(app.getInputManager().hasMapping("ScreenShot")); // Confirm that KEY_SYSRQ is unmapped. + } + + /** + * Test case for JME issue #1138: Elephant's legUp animation sets Joint translation to NaN. + */ + @Test + public void testIssue1138() { + AssetManager am = JmeSystem.newAssetManager(PreventCoreIssueRegressions.class.getResource("/com/jme3/asset/Desktop.cfg")); + Node cgModel = (Node)am.loadModel("Models/Elephant/Elephant.mesh.xml"); + cgModel.rotate(0f, -1f, 0f); + cgModel.scale(0.04f); + + AnimComposer composer = cgModel.getControl(AnimComposer.class); + composer.setCurrentAction("legUp"); + SkinningControl sControl = cgModel.getControl(SkinningControl.class); + + for (Joint joint : sControl.getArmature().getJointList()) { + Assert.assertTrue("Invalid translation for joint " + joint.getName(), + Vector3f.isValidVector(joint.getLocalTranslation())); + } + + cgModel.updateLogicalState(1.0f); + cgModel.updateGeometricState(); + + for (Joint joint : sControl.getArmature().getJointList()) { + Assert.assertTrue("Invalid translation for joint " + joint.getName(), + Vector3f.isValidVector(joint.getLocalTranslation())); + } + } +} diff --git a/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java b/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java new file mode 100644 index 0000000000..51d9e9e007 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/texture/TestIssue2250.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import org.junit.Test; + +/** + * Verify that setMultiSamples(1) can be applied to any Image. This was issue + * #2250 at GitHub. + * + * @author Stephen Gold + */ +public class TestIssue2250 { + + /** + * Test setMultiSamples() on an Image with a data buffer. + */ + @Test + public void testIssue2250WithData() { + int width = 8; + int height = 8; + int numBytes = 4 * width * height; + ByteBuffer data = BufferUtils.createByteBuffer(numBytes); + Image image1 = new Image( + Image.Format.RGBA8, width, height, data, ColorSpace.Linear); + + image1.setMultiSamples(1); + } + + /** + * Test setMultiSamples() on an Image with mip maps. + */ + @Test + public void testIssue2250WithMips() { + int width = 8; + int height = 8; + int depth = 1; + int[] mipMapSizes = {256, 64, 16, 4}; + + ArrayList data = new ArrayList<>(); + Image image2 = new Image(Image.Format.RGBA8, width, height, depth, data, + mipMapSizes, ColorSpace.Linear); + + image2.setMultiSamples(1); + } +} diff --git a/jme3-core/src/test/java/com/jme3/texture/TextureArrayTest.java b/jme3-core/src/test/java/com/jme3/texture/TextureArrayTest.java new file mode 100644 index 0000000000..bd861b9879 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/texture/TextureArrayTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2009-2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.texture; + +import static org.junit.Assert.*; + +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.texture.Texture.WrapAxis; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.image.ColorSpace; +import com.jme3.util.BufferUtils; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; +import org.junit.Test; + +public class TextureArrayTest { + + private static final AssetManager assetManager = new DesktopAssetManager(); + + @Test + public void testExportWrapMode() { + List images = new ArrayList<>(); + images.add(createImage()); + images.add(createImage()); + TextureArray tex3 = new TextureArray(images); + tex3.setWrap(WrapMode.Repeat); + TextureArray tex4 = BinaryExporter.saveAndLoad(assetManager, tex3); + + assertEquals(tex3.getWrap(WrapAxis.S), tex4.getWrap(WrapAxis.S)); + assertEquals(tex3.getWrap(WrapAxis.T), tex4.getWrap(WrapAxis.T)); + } + + private Image createImage() { + int width = 8; + int height = 8; + int numBytes = 4 * width * height; + ByteBuffer data = BufferUtils.createByteBuffer(numBytes); + return new Image(Image.Format.RGBA8, width, height, data, ColorSpace.Linear); + } + +} diff --git a/jme3-core/src/test/java/com/jme3/tools/LodGeneratorTest.java b/jme3-core/src/test/java/com/jme3/tools/LodGeneratorTest.java new file mode 100644 index 0000000000..cdf1e9fad5 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/tools/LodGeneratorTest.java @@ -0,0 +1,192 @@ +/* + * 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.tools; + +import static org.junit.Assert.assertArrayEquals; + +import org.junit.Test; + +import com.jme3.asset.AssetManager; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.TestUtil; + +import jme3tools.optimize.LodGenerator; + +/** + * Tests the result of the LodGenerator. + * + * @author Melvyn Linke + */ +public class LodGeneratorTest { + AssetManager assetManager = TestUtil.createAssetManager(); + + float[] REDUCTION_VALUES = { 0.5f, 0.55f, 0.6f, 0.65f, 0.7f, 0.75f, 0.80f }; + + /** + * Tests the construction of the LodGenerator. + */ + @Test + public void testInit() { + LodGenerator lod = new LodGenerator(sphere()); + assert true; + } + + /** + * Returns a List of the sizes of the VertexBuff. + */ + private int[] getBufferSizes(VertexBuffer[] buffers) { + int[] result = new int[buffers.length]; + + for (int i = 0; i < buffers.length; i++) { + result[i] = buffers[i].getNumElements(); + } + + return result; + } + + /** + * Tests the LodGenerator with proportional reduction on a sphere(see sphere()). + */ + @Test + public void testSphereReductionProportional() { + LodGenerator lod = new LodGenerator(sphere()); + VertexBuffer[] buffer = lod.computeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, + REDUCTION_VALUES); + + int[] expected = { 240, 120, 108, 96, 84, 72, 60, 48 }; + int[] actual = getBufferSizes(buffer); + + assertArrayEquals(expected, actual); + } + + /** + * Tests the LodGenerator with collapse cost reduction on a sphere(see sphere()). + */ + @Test + public void testSphereReductionCollapsCost() { + LodGenerator lod = new LodGenerator(sphere()); + VertexBuffer[] buffer = lod.computeLods(LodGenerator.TriangleReductionMethod.COLLAPSE_COST, + REDUCTION_VALUES); + + int[] expected = { 240, 6, 2, 1 }; + int[] actual = getBufferSizes(buffer); + assert buffer != null; + assertArrayEquals(expected, actual); + + } + + /** + * Returns the mesh of a node. + */ + private Mesh getMesh(Node node) { + Mesh m = null; + for (Spatial spatial : node.getChildren()) { + if (spatial instanceof Geometry) { + m = ((Geometry) spatial).getMesh(); + if (m.getVertexCount() == 5108) { + + } + + } + } + return m; + } + + /** + * Returns the Monkey mesh used in the TestLodGeneration stresstest. Note: Doesn't work durring gradle + * build. + */ + private Mesh monkey() { + Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + return getMesh(model); + } + + /** + * Returns a 12x12 Sphere mesh. + */ + private Mesh sphere() { + return new Sphere(12, 12, 1, false, false); + } + + /** + * Tests the LodGenerator with constnat reduction on a monkey(see monkey()). + */ + // @Test + public void testMonkeyReductionConstant() { + + LodGenerator lod = new LodGenerator(monkey()); + VertexBuffer[] buffer = lod.computeLods(LodGenerator.TriangleReductionMethod.CONSTANT, + REDUCTION_VALUES); + + int[] expected = { 5108 }; + int[] actual = getBufferSizes(buffer); + + assertArrayEquals(expected, actual); + } + + /** + * Tests the LodGenerator with proportional reduction on a sphere(see sphere()). + */ + // @Test + public void testMonkeyReductionProportional() { + + LodGenerator lod = new LodGenerator(monkey()); + VertexBuffer[] buffer = lod.computeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, + REDUCTION_VALUES); + + int[] expected = { 5108, 2553, 2298, 2043, 1787, 1531, 1276, 1021 }; + int[] actual = getBufferSizes(buffer); + + assertArrayEquals(expected, actual); + } + + /** + * Tests the LodGenerator with collapse cost reduction on a monkey(see monkey()). + */ + // @Test + public void testMonkeyReductionCollapsCost() { + LodGenerator lod = new LodGenerator(monkey()); + VertexBuffer[] buffer = lod.computeLods(LodGenerator.TriangleReductionMethod.COLLAPSE_COST, + REDUCTION_VALUES); + + int[] expected = { 5108, 16 }; + int[] actual = getBufferSizes(buffer); + + assertArrayEquals(expected, actual); + } +} \ No newline at end of file diff --git a/jme3-core/src/test/java/com/jme3/util/ListMapTest.java b/jme3-core/src/test/java/com/jme3/util/ListMapTest.java index 5f4cf3eee7..15430a8054 100644 --- a/jme3-core/src/test/java/com/jme3/util/ListMapTest.java +++ b/jme3-core/src/test/java/com/jme3/util/ListMapTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ public class ListMapTest { @Test public void testListMap() { - ListMap listMap = new ListMap(); + ListMap listMap = new ListMap<>(); listMap.put("bob", "hello"); assert "hello".equals(listMap.get("bob")); assert "hello".equals(listMap.remove("bob")); diff --git a/jme3-core/src/test/java/com/jme3/util/StructTest.java b/jme3-core/src/test/java/com/jme3/util/StructTest.java new file mode 100644 index 0000000000..6dace56b16 --- /dev/null +++ b/jme3-core/src/test/java/com/jme3/util/StructTest.java @@ -0,0 +1,218 @@ +package com.jme3.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.nio.ByteBuffer; + +import com.jme3.shader.bufferobject.BufferObject; +import com.jme3.shader.bufferobject.BufferRegion; +import com.jme3.shader.bufferobject.DirtyRegionsIterator; +import com.jme3.shader.bufferobject.layout.Std140Layout; +import com.jme3.util.struct.Struct; +import com.jme3.util.struct.StructField; +import com.jme3.util.struct.StructUtils; +import com.jme3.util.struct.fields.*; + +import org.junit.Test; + +public class StructTest { + static class SubStruct implements Struct { + public final IntField subIntField0 = new IntField(0, "subIntField0", 100); + public final FloatField subFloatField1 = new FloatField(1, "subFloatField1", 100f); + + } + + static class TestStruct implements Struct { + public final IntField intField0 = new IntField(0, "intField0", 100); + public final FloatField floatField1 = new FloatField(1, "floatField1", 100f); + public final FloatArrayField floatFieldArray2 = new FloatArrayField(2, "floatFieldArray2", new Float[] { 100f, 200f, 300f }); + public final SubStructField structField3 = new SubStructField(3, "structField3", new SubStruct()); + public final SubStructArrayField structArrayField5 = new SubStructArrayField(5, "structArrayField5", new SubStruct[] { new SubStruct(), new SubStruct() }); + public final BooleanField boolField6 = new BooleanField(6, "boolField6", true); + } + + @Test + public void testFieldsExtraction() { + TestStruct test = new TestStruct(); + java.util.List> fields = StructUtils.getFields(test); + String checkString = ""; + for (StructField f : fields) { + String s = f.getPosition() + " " + f.getName() + " " + f.getDepth() + "\n"; + checkString += s; + } + String expectedString = "0 intField0 0\n1 floatField1 0\n2 floatFieldArray2 0\n3 subIntField0 1\n4 subFloatField1 1\n5 subIntField0 1\n6 subFloatField1 1\n7 subIntField0 1\n8 subFloatField1 1\n9 boolField6 0\n"; + assertEquals(expectedString, checkString); + } + + @Test + public void testStd140Serialization() { + TestStruct test = new TestStruct(); + java.util.List> fields = StructUtils.getFields(test); + + Std140Layout layout = new Std140Layout(); + BufferObject bo = new BufferObject(); + StructUtils.setStd140BufferLayout(fields, layout, bo); + System.out.println(bo.getData().getInt()); + + StructUtils.updateBufferData(fields, false, layout, bo); + + ByteBuffer bbf = bo.getData(); + + String expectedData = "100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72 67 0 0 0 0 0 0 0 0 0 0 0 0 0 0 -106 67 0 0 0 0 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 100 0 0 0 0 0 -56 66 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 "; + String actualData = ""; + while (bbf.hasRemaining()) { + actualData += bbf.get() + " "; + } + assertEquals(expectedData, actualData); + } + + @Test + public void testStd140PartialUpdate() { + TestStruct test = new TestStruct(); + + Std140Layout layout = new Std140Layout(); + BufferObject bo = new BufferObject(); + + java.util.List> fields = StructUtils.getFields(test); + StructUtils.setStd140BufferLayout(fields, layout, bo); + int bolength = bo.getData().limit(); + assertEquals(128, bolength); + assertEquals(128, bo.getData().capacity()); + + int nUpdated; + + // Update full + System.out.println("Test Full Update"); + StructUtils.updateBufferData(fields, false, layout, bo); + DirtyRegionsIterator dirtyI = bo.getDirtyRegions(); + nUpdated = 0; + assertTrue(bo.isUpdateNeeded()); + while (true) { + BufferRegion region = dirtyI.next(); + if (region == null) break; + int start = region.getStart(); + int end = region.getEnd(); + System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength); + assertEquals(0, start); + assertEquals(127,end); + assertTrue(region.isFullBufferRegion()); + assertTrue(region.isDirty()); + region.clearDirty(); + assertFalse(region.isDirty()); + nUpdated++; + } + bo.clearUpdateNeeded(); + assertFalse(bo.isUpdateNeeded()); + assertEquals(1, nUpdated); + + + // Update nothing + System.out.println("Test No Update"); + fields = StructUtils.getFields(test); + StructUtils.updateBufferData(fields, false, layout, bo); + dirtyI = bo.getDirtyRegions(); + assertFalse(bo.isUpdateNeeded()); + nUpdated = 0; + while (true) { + BufferRegion region = dirtyI.next(); + if (region == null) break; + + assertFalse("Update not expected", true); + nUpdated++; + } + bo.clearUpdateNeeded(); + assertFalse(bo.isUpdateNeeded()); + assertEquals(0, nUpdated); + + // Update something + System.out.println("Test Partial Update"); + test.floatField1.setValue(2f); + StructUtils.updateBufferData(fields, false, layout, bo); + dirtyI = bo.getDirtyRegions(); + assertTrue(bo.isUpdateNeeded()); + nUpdated = 0; + while (true) { + BufferRegion region = dirtyI.next(); + if (region == null) break; + assertTrue(region.isDirty()); + + int start = region.getStart(); + int end = region.getEnd(); + System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength); + assertEquals(4, start); + assertEquals(7, end); + assertFalse(region.isFullBufferRegion()); + assertTrue(region.isDirty()); + region.clearDirty(); + nUpdated++; + } + bo.clearUpdateNeeded(); + assertFalse(bo.isUpdateNeeded()); + assertEquals(1, nUpdated); + + // Update something2 + System.out.println("Test Partial Update 2"); + test.floatField1.setValue(2f); + test.boolField6.setValue(true); + StructUtils.updateBufferData(fields, false, layout, bo); + dirtyI = bo.getDirtyRegions(); + assertTrue(bo.isUpdateNeeded()); + nUpdated = 0; + while (true) { + BufferRegion region = dirtyI.next(); + if (region == null) break; + assertTrue(region.isDirty()); + + int start = region.getStart(); + int end = region.getEnd(); + System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength); + if (nUpdated == 0) { + assertEquals(4, start); + assertEquals(7, end); + } else { + assertEquals(112, start); + assertEquals(127, end); + } + assertFalse(region.isFullBufferRegion()); + assertTrue(region.isDirty()); + region.clearDirty(); + nUpdated++; + } + bo.clearUpdateNeeded(); + assertFalse(bo.isUpdateNeeded()); + assertEquals(2, nUpdated); + + + // Update substruct + System.out.println("Test Partial Update Substruct"); + test.structField3.getValue().subIntField0.setValue(3); + StructUtils.updateBufferData(fields, false, layout, bo); + dirtyI = bo.getDirtyRegions(); + assertTrue(bo.isUpdateNeeded()); + nUpdated = 0; + while (true) { + BufferRegion region = dirtyI.next(); + if (region == null) break; + assertTrue(region.isDirty()); + + int start = region.getStart(); + int end = region.getEnd(); + System.out.println("Update from " + start + " to " + end + " in buffer of length " + bolength); + assertEquals(64, start); + assertEquals(67, end); + assertFalse(region.isFullBufferRegion()); + assertTrue(region.isDirty()); + region.clearDirty(); + nUpdated++; + } + bo.clearUpdateNeeded(); + assertFalse(bo.isUpdateNeeded()); + assertEquals(1, nUpdated); + + + + + } +} 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-core/src/test/resources/GLSLPreprocessorTest.testFOR.validOutput b/jme3-core/src/test/resources/GLSLPreprocessorTest.testFOR.validOutput new file mode 100644 index 0000000000..39984e37f1 --- /dev/null +++ b/jme3-core/src/test/resources/GLSLPreprocessorTest.testFOR.validOutput @@ -0,0 +1,8 @@ + +#ifdef IS_SET0 + uniform float m_Something0; + #endif + +#ifdef IS_SET1 + uniform float m_Something1; + #endif \ No newline at end of file diff --git a/jme3-core/src/test/resources/GLSLPreprocessorTest.testStruct.validOutput b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStruct.validOutput new file mode 100644 index 0000000000..92c188d798 --- /dev/null +++ b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStruct.validOutput @@ -0,0 +1,8 @@ +// nothing +#define STRUCT_MyStruct \ +float x ; \ +float y; +struct MyStruct { +STRUCT_MyStruct +}; +//nothing \ No newline at end of file diff --git a/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtends.validOutput b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtends.validOutput new file mode 100644 index 0000000000..a12371192f --- /dev/null +++ b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtends.validOutput @@ -0,0 +1,17 @@ +// nothing +#define STRUCT_BaseStruct \ +float x0; \ +float y0; +struct BaseStruct { +STRUCT_BaseStruct +}; +//nothing +//nothing +#define STRUCT_MyStruct \ +STRUCT_BaseStruct \ +float x; \ +float y; +struct MyStruct { +STRUCT_MyStruct +}; +//nothing \ No newline at end of file diff --git a/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtendsMulti.validOutput b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtendsMulti.validOutput new file mode 100644 index 0000000000..6cd2227924 --- /dev/null +++ b/jme3-core/src/test/resources/GLSLPreprocessorTest.testStructExtendsMulti.validOutput @@ -0,0 +1,21 @@ +#define STRUCT_BaseStruct \ +float x0; \ +float y0; +struct BaseStruct { +STRUCT_BaseStruct +}; +#define STRUCT_BaseStruct2 \ +float x1; \ +float y1; +struct BaseStruct2 { +STRUCT_BaseStruct2 +}; +//nothing +#define STRUCT_MyStruct \ +STRUCT_BaseStruct \ +STRUCT_BaseStruct2 \ +float x; \ +float y; +struct MyStruct { +STRUCT_MyStruct +}; \ No newline at end of file diff --git a/jme3-core/src/test/resources/bad-booleans1.j3md b/jme3-core/src/test/resources/bad-booleans1.j3md index a5a7ddf7cd..52ce37214c 100644 --- a/jme3-core/src/test/resources/bad-booleans1.j3md +++ b/jme3-core/src/test/resources/bad-booleans1.j3md @@ -62,8 +62,8 @@ MaterialDef bad-booleans1 { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.frag WorldParameters { WorldViewProjectionMatrix @@ -88,8 +88,8 @@ MaterialDef bad-booleans1 { Technique PreNormalPass { - VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -109,8 +109,8 @@ MaterialDef bad-booleans1 { Technique PreShadow { - VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -140,8 +140,8 @@ MaterialDef bad-booleans1 { Technique PostShadow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -161,8 +161,8 @@ MaterialDef bad-booleans1 { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - BACKFACE_SHADOWS: BackfaceShadows + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } @@ -176,8 +176,8 @@ MaterialDef bad-booleans1 { Technique Glow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix @@ -190,7 +190,7 @@ MaterialDef bad-booleans1 { HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing HAS_POINTSIZE : PointSize NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers diff --git a/jme3-core/src/test/resources/bad-booleans2.j3md b/jme3-core/src/test/resources/bad-booleans2.j3md index 0732d3cc71..683a6b7098 100644 --- a/jme3-core/src/test/resources/bad-booleans2.j3md +++ b/jme3-core/src/test/resources/bad-booleans2.j3md @@ -62,8 +62,8 @@ MaterialDef bad-booleans2 { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.frag WorldParameters { WorldViewProjectionMatrix @@ -88,8 +88,8 @@ MaterialDef bad-booleans2 { Technique PreNormalPass { - VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -109,8 +109,8 @@ MaterialDef bad-booleans2 { Technique PreShadow { - VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -140,8 +140,8 @@ MaterialDef bad-booleans2 { Technique PostShadow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -161,8 +161,8 @@ MaterialDef bad-booleans2 { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - BACKFACE_SHADOWS: BackfaceShadows + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } @@ -176,8 +176,8 @@ MaterialDef bad-booleans2 { Technique Glow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix @@ -190,7 +190,7 @@ MaterialDef bad-booleans2 { HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing HAS_POINTSIZE : PointSize NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers diff --git a/jme3-core/src/test/resources/bad-booleans3.j3md b/jme3-core/src/test/resources/bad-booleans3.j3md index 8fda309da9..2c94e003d4 100644 --- a/jme3-core/src/test/resources/bad-booleans3.j3md +++ b/jme3-core/src/test/resources/bad-booleans3.j3md @@ -62,8 +62,8 @@ MaterialDef bad-booleans3 { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.frag WorldParameters { WorldViewProjectionMatrix @@ -88,8 +88,8 @@ MaterialDef bad-booleans3 { Technique PreNormalPass { - VertexShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -109,8 +109,8 @@ MaterialDef bad-booleans3 { Technique PreShadow { - VertexShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 GLSL150 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -141,8 +141,8 @@ MaterialDef bad-booleans3 { Technique PostShadow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Shadow/PostShadow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -162,8 +162,8 @@ MaterialDef bad-booleans3 { PSSM : Splits POINTLIGHT : LightViewProjectionMatrix5 NUM_BONES : NumberOfBones - INSTANCING : UseInstancing - BACKFACE_SHADOWS: BackfaceShadows + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers } @@ -177,8 +177,8 @@ MaterialDef bad-booleans3 { Technique Glow { - VertexShader GLSL100 GLSL150: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Light/Glow.frag + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix @@ -191,7 +191,7 @@ MaterialDef bad-booleans3 { HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor NUM_BONES : NumberOfBones - INSTANCING : UseInstancing + INSTANCING : UseInstancing HAS_POINTSIZE : PointSize NUM_MORPH_TARGETS: NumberOfMorphTargets NUM_TARGETS_BUFFERS: NumberOfTargetsBuffers diff --git a/jme3-core/src/tools/java/jme3tools/converters/RGB565.java b/jme3-core/src/tools/java/jme3tools/converters/RGB565.java index 557ab06b1d..53f786cba0 100644 --- a/jme3-core/src/tools/java/jme3tools/converters/RGB565.java +++ b/jme3-core/src/tools/java/jme3tools/converters/RGB565.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,12 @@ */ public class RGB565 { + /** + * A private constructor to inhibit instantiation of this class. + */ + private RGB565() { + } + public static short ARGB8_to_RGB565(int argb){ int a = (argb & 0xFF000000) >> 24; int r = (argb & 0x00FF0000) >> 16; diff --git a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java index 9904b32836..98ccf9c35b 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/GeometryBatchFactory.java @@ -91,8 +91,8 @@ private static void doTransformTangents(FloatBuffer inBuf, int offset, int compo * Merges all geometries in the collection into * the output mesh. Creates a new material using the TextureAtlas. * - * @param geometries - * @param outMesh + * @param geometries the geometries to merge + * @param outMesh a Mesh to receive the geometries */ public static void mergeGeometries(Collection geometries, Mesh outMesh) { int[] compsForBuf = new int[VertexBuffer.Type.values().length]; @@ -158,8 +158,8 @@ public static void mergeGeometries(Collection geometries, Mesh outMesh outMesh.setMaxNumWeights(maxWeights); outMesh.setMode(mode); if (totalVerts >= 65536) { - // make sure we create an UnsignedInt buffer so - // we can fit all of the meshes + // Create an UnsignedInt buffer so + // we can fit all of the meshes. formatForBuf[Type.Index.ordinal()] = Format.UnsignedInt; } else { formatForBuf[Type.Index.ordinal()] = Format.UnsignedShort; @@ -309,11 +309,13 @@ public static List makeBatches(Collection geometries) { /** * Batches a collection of Geometries so that all with the same material get combined. * @param geometries The Geometries to combine + * @param useLods true→generate levels of detail, false→don't + * generate them * @return A List of newly created Geometries, each with a distinct material */ public static List makeBatches(Collection geometries, boolean useLods) { - ArrayList retVal = new ArrayList(); - HashMap> matToGeom = new HashMap>(); + ArrayList retVal = new ArrayList<>(); + HashMap> matToGeom = new HashMap<>(); for (Geometry geom : geometries) { List outList = matToGeom.get(geom.getMaterial()); @@ -384,7 +386,7 @@ public static Spatial optimize(Node scene) { * @return The newly created optimized geometries attached to a node */ public static Node optimize(Node scene, boolean useLods) { - ArrayList geoms = new ArrayList(); + ArrayList geoms = new ArrayList<>(); gatherGeoms(scene, geoms); @@ -447,7 +449,7 @@ public static void main(String[] args) { Geometry g1 = new Geometry("g1", mesh); - ArrayList geoms = new ArrayList(); + ArrayList geoms = new ArrayList<>(); geoms.add(g1); Mesh outMesh = new Mesh(); @@ -481,17 +483,19 @@ public static enum AlignOption { * Very experimental for now. */ public static void alignBuffers(Node n, AlignOption option) { - List geoms = new ArrayList(); + List geoms = new ArrayList<>(); gatherGeoms(n, geoms); //gather buffer types - Map types = new EnumMap(VertexBuffer.Type.class); - Map typesCount = new EnumMap(VertexBuffer.Type.class); + Map types = new EnumMap<>(VertexBuffer.Type.class); + Map typesCount = new EnumMap<>(VertexBuffer.Type.class); for (Geometry geom : geoms) { for (VertexBuffer buffer : geom.getMesh().getBufferList()) { if (types.get(buffer.getBufferType()) == null) { types.put(buffer.getBufferType(), buffer); - logger.log(Level.FINE, buffer.getBufferType().toString()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, buffer.getBufferType().toString()); + } } Integer count = typesCount.get(buffer.getBufferType()); if (count == null) { @@ -510,8 +514,9 @@ public static void alignBuffers(Node n, AlignOption option) { Integer count = typesCount.get(buffer.getBufferType()); if (count != null && count < geoms.size()) { geom.getMesh().clearBuffer(buffer.getBufferType()); - logger.log(Level.FINE, "removing {0} from {1}", new Object[]{buffer.getBufferType(), geom.getName()}); - + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "removing {0} from {1}", new Object[]{buffer.getBufferType(), geom.getName()}); + } } } } @@ -536,7 +541,9 @@ public static void alignBuffers(Node n, AlignOption option) { } vb.setupData(types.get(type).getUsage(), types.get(type).getNumComponents(), types.get(type).getFormat(), b); geom.getMesh().setBuffer(vb); - logger.log(Level.FINE, "geom {0} misses buffer {1}. Creating", new Object[]{geom.getName(), type}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "geom {0} misses buffer {1}. Creating", new Object[]{geom.getName(), type}); + } } } } diff --git a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java index 5b2ee6fd8b..648f85f070 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/LodGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2013 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -18,7 +18,7 @@ * without specific prior written permission. * * This class is the java implementation of - * the enhanced version of Ogre engine Lod generator, by Péter Szücs, originally + * the enhanced version of Ogre Engine LOD generator, by Péter Szücs, originally * based on Stan Melax "easy mesh simplification". The MIT licenced C++ source * code can be found here * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod @@ -70,24 +70,24 @@ import java.util.logging.Logger; /** - * This is an utility class that allows to generated the lod levels for an - * arbitrary mesh. It computes a collapse cost for each vertex and each edges. - * The higher the cost the most likely collapsing the edge or the vertex will + * This is a utility class that adds the ability to generate LOD levels + * for an arbitrary mesh. It computes a collapse cost for each vertex and each edge. + * The higher the cost the more likely collapsing the edge or the vertex will * produce artifacts on the mesh.

                This class is the java implementation of - * the enhanced version of Ogre engine Lod generator, by Péter Szücs, originally + * the enhanced version of Ogre engine LOD generator, by Péter Szücs, originally * based on Stan Melax "easy mesh simplification". The MIT licenced C++ source * code can be found here * https://github.com/worldforge/ember/tree/master/src/components/ogre/lod more - * informations can be found here http://www.melax.com/polychop + * information can be found here http://www.melax.com/polychop * http://sajty.elementfx.com/progressivemesh/GSoC2012.pdf

                * - *

                The algorithm sort vertices according to their collapse cost - * ascending. It collapse from the "cheapest" vertex to the more expensive.
                - * Usage :
                + *

                The algorithm sorts vertices according to their collapse cost in + * ascending order. It collapses from the "cheapest" vertex to the more expensive.
                + * Usage:
                *

                  *      LodGenerator lODGenerator = new LodGenerator(geometry);
                - *      lODGenerator.bakeLods(reductionMethod,reductionvalue);
                - * 
                redutionMethod type is VertexReductionMethod described here + * lODGenerator.bakeLods(reductionMethod,reductionValue); + * reductionMethod type is VertexReductionMethod described here * {@link TriangleReductionMethod} reduction value depends on the * reductionMethod

                * @@ -103,22 +103,15 @@ public class LodGenerator { private Vector3f tmpV2 = new Vector3f(); private boolean bestQuality = true; private int indexCount = 0; - private List collapseCostSet = new ArrayList(); + private List collapseCostSet = new ArrayList<>(); private float collapseCostLimit; private List triangleList; - private List vertexList = new ArrayList(); + private List vertexList = new ArrayList<>(); private float meshBoundingSphereRadius; - private Mesh mesh; + private final Mesh mesh; /** - * Describe the way triangles will be removed.
                PROPORTIONAL : - * Percentage of triangles to be removed from the mesh. Valid range is a - * number between 0.0 and 1.0
                CONSTANT : Triangle count to be removed - * from the mesh. Pass only integers or it will be rounded.
                - * COLLAPSE_COST : Reduces the vertices, until the cost is bigger then the - * given value. Collapse cost is equal to the amount of artifact the - * reduction causes. This generates the best Lod output, but the collapse - * cost depends on implementation. + * Enumerate criteria for removing triangles. */ public enum TriangleReductionMethod { @@ -129,17 +122,17 @@ public enum TriangleReductionMethod { */ PROPORTIONAL, /** - * Triangle count to be removed from the mesh. + * Number of triangles to be removed from the mesh. * - * Pass only integers or it will be rounded. + * Pass an integer or it will be rounded. */ CONSTANT, /** - * Reduces the vertices, until the cost is bigger then the given value. + * Collapses vertices until the cost exceeds the given value. * - * Collapse cost is equal to the amount of artifact the reduction - * causes. This generates the best Lod output, but the collapse cost - * depends on implementation. + * Collapse cost indicates how much inaccuracy the + * reduction causes. This generates the best LOD output, but the collapse + * cost is implementation-dependant. */ COLLAPSE_COST }; @@ -175,7 +168,7 @@ public int hashCode() { @Override public String toString() { - return "Edge{" + "collapsTo " + destination.index + '}'; + return "Edge{" + "collapseTo " + destination.index + '}'; } } @@ -183,8 +176,8 @@ private class Vertex { Vector3f position = new Vector3f(); float collapseCost = UNINITIALIZED_COLLAPSE_COST; - List edges = new ArrayList(); - Set triangles = new HashSet(); + List edges = new ArrayList<>(); + Set triangles = new HashSet<>(); Vertex collapseTo; boolean isSeam; int index;//index in the buffer for debugging @@ -242,7 +235,8 @@ public String toString() { /** * Comparator used to sort vertices according to their collapse cost */ - private Comparator collapseComparator = new Comparator() { + private final Comparator collapseComparator = new Comparator() { + @Override public int compare(Vertex o1, Vertex o2) { if (Float.compare(o1.collapseCost, o2.collapseCost) == 0) { return 0; @@ -255,9 +249,9 @@ public int compare(Vertex o1, Vertex o2) { }; /** - * Construct a LodGenerator for the given mesh + * Constructs an LodGenerator for the given Mesh. * - * @param mesh the mesh to consider to generate de Lods. + * @param mesh the mesh for which to generate LODs. */ public LodGenerator(Mesh mesh) { this.mesh = mesh; @@ -265,9 +259,9 @@ public LodGenerator(Mesh mesh) { } /** - * Construct a LodGenerator for the given geometry + * Constructs an LodGenerator for the given Geometry. * - * @param geom the geometry to consider to generate de Lods. + * @param geom the geometry for which to generate LODs. */ public LodGenerator(Geometry geom) { mesh = geom.getMesh(); @@ -278,7 +272,7 @@ private void build() { BoundingSphere bs = new BoundingSphere(); bs.computeFromPoints(mesh.getFloatBuffer(VertexBuffer.Type.Position)); meshBoundingSphereRadius = bs.getRadius(); - List vertexLookup = new ArrayList(); + List vertexLookup = new ArrayList<>(); initialize(); gatherVertexData(mesh, vertexLookup); @@ -352,7 +346,9 @@ private void gatherIndexData(Mesh mesh, List vertexLookup) { } if (tri.isMalformed()) { if (!tri.isRemoved) { - logger.log(Level.FINE, "malformed triangle found with ID:{0}\n{1} It will be excluded from Lod level calculations.", new Object[]{triangleList.indexOf(tri), tri.toString()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "malformed triangle found with ID:{0}\n{1} It will be excluded from LOD calculations.", new Object[]{triangleList.indexOf(tri), tri.toString()}); + } tri.isRemoved = true; indexCount -= 3; } @@ -373,25 +369,13 @@ private void computeCosts() { if (!vertex.edges.isEmpty()) { computeVertexCollapseCost(vertex); } else { - logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from Lod level calculations.", vertex); + logger.log(Level.FINE, "Found isolated vertex {0} It will be excluded from LOD calculations.", vertex); } } // assert (vertexList.size() == collapseCostSet.size()); // assert (checkCosts()); } - //Debug only - private boolean checkCosts() { - for (Vertex vertex : vertexList) { - boolean test = find(collapseCostSet, vertex); - if (!test) { - System.out.println("vertex " + vertex.index + " not present in collapse costs"); - return false; - } - } - return true; - } - private void computeVertexCollapseCost(Vertex vertex) { vertex.collapseCost = UNINITIALIZED_COLLAPSE_COST; @@ -467,7 +451,7 @@ float computeEdgeCollapseCost(Vertex src, Edge dstEdge) { // Collapsing ALONG a border // We can't use curvature to measure the effect on the model // Instead, see what effect it has on 'pulling' the other border edges - // The more colinear, the less effect it will have + // The more collinear, the less effect it will have // So measure the 'kinkiness' (for want of a better term) // Find the only triangle using this edge. @@ -534,12 +518,12 @@ float computeEdgeCollapseCost(Vertex src, Edge dstEdge) { int nbCollapsedTri = 0; /** - * Computes the lod and return a list of VertexBuffers that can then be used - * for lod (use Mesh.setLodLevels(VertexBuffer[]))
                + * Computes the LODs and returns an array of VertexBuffers that can + * be passed to Mesh.setLodLevels().
                * * This method must be fed with the reduction method * {@link TriangleReductionMethod} and a list of reduction values.
                for - * each value a lod will be generated.
                The resulting array will always + * each value a LOD will be generated.
                The resulting array will always * contain at index 0 the original index buffer of the mesh.

                * Important note : some meshes cannot be decimated, so the * result of this method can vary depending of the given mesh. Also the @@ -547,9 +531,9 @@ float computeEdgeCollapseCost(Vertex src, Edge dstEdge) { * meet the required reduction. * * @param reductionMethod the reduction method to use - * @param reductionValues the reduction value to use for each lod level. + * @param reductionValues the reduction value to use for each LOD level. * @return an array of VertexBuffers containing the different index buffers - * representing the lod levels. + * representing the LOD levels. */ public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float... reductionValues) { int tricount = triangleList.size(); @@ -592,27 +576,36 @@ public VertexBuffer[] computeLods(TriangleReductionMethod reductionMethod, float numBakedLods++; } } - if (numBakedLods <= lodCount) { - VertexBuffer[] bakedLods = new VertexBuffer[numBakedLods]; - System.arraycopy(lods, 0, bakedLods, 0, numBakedLods); - return bakedLods; - } else { - return lods; + + return cleanBuffer(lods, numBakedLods); + } + + private VertexBuffer[] cleanBuffer(VertexBuffer[] lods, int numBakedLods) { + int index = 0; + VertexBuffer[] result = new VertexBuffer[numBakedLods]; + + for (VertexBuffer lod : lods) { + if (lod != null) { + result[index] = lod; + index++; + } } + + return result; } /** - * Computes the lods and bake them into the mesh
                + * Computes the LODs and bakes them into the mesh.
                * * This method must be fed with the reduction method * {@link TriangleReductionMethod} and a list of reduction values.
                for - * each value a lod will be generated.

                Important note : + * each value a LOD will be generated.

                Important note: * some meshes cannot be decimated, so the result of this method can vary - * depending of the given mesh. Also the reduction values are indicative and - * the produces mesh will not always meet the required reduction. + * depending on the given mesh. Also, the reduction values are approximate, and + * the algorithm won't always achieve the specified reduction. * * @param reductionMethod the reduction method to use - * @param reductionValues the reduction value to use for each lod level. + * @param reductionValues the reduction value to use for each LOD level. */ public void bakeLods(TriangleReductionMethod reductionMethod, float... reductionValues) { mesh.setLodLevels(computeLods(reductionMethod, reductionValues)); @@ -622,7 +615,7 @@ private VertexBuffer makeLod(Mesh mesh) { VertexBuffer indexBuffer = mesh.getBuffer(VertexBuffer.Type.Index); boolean isShortBuffer = indexBuffer.getFormat() == VertexBuffer.Format.UnsignedShort; - // Create buffers. + // Create buffers. VertexBuffer lodBuffer = new VertexBuffer(VertexBuffer.Type.Index); int bufsize = indexCount == 0 ? 3 : indexCount; @@ -761,7 +754,9 @@ private void addTriangleToEdges(Triangle tri) { if (!tri.isRemoved) { tri.isRemoved = true; indexCount -= 3; - logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from Lod level calculations.", new Object[]{tri, duplicate}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "duplicate triangle found{0}{1} It will be excluded from LOD level calculations.", new Object[]{tri, duplicate}); + } } } } @@ -895,15 +890,15 @@ private boolean collapse(Vertex src) { // It may have vertexIDs and triangles from different submeshes(different vertex buffers), // so we need to connect them correctly based on deleted triangle's edge. // mCollapsedEdgeIDs will be used, when looking up the connections for replacement. - List tmpCollapsedEdges = new ArrayList(); + List tmpCollapsedEdges = new ArrayList<>(); for (Iterator it = src.triangles.iterator(); it.hasNext();) { Triangle triangle = it.next(); if (triangle.hasVertex(dest)) { // Remove a triangle // Tasks: // 1. Add it to the collapsed edges list. - // 2. Reduce index count for the Lods, which will not have this triangle. - // 3. Mark as removed, so it will not be added in upcoming Lod levels. + // 2. Reduce index count for the LODs, which will not have this triangle. + // 3. Mark as removed, so it will not be added in upcoming LOD levels. // 4. Remove references/pointers to this triangle. // 1. task @@ -978,9 +973,9 @@ private boolean collapse(Vertex src) { } else { // TODO: Find out why is this needed. assertOutdatedCollapseCost() fails on some - // rare situations without this. For example goblin.mesh fails. + // rare situations without this. For example goblin.mesh fails. //Treeset to have an ordered list with unique values - SortedSet updatable = new TreeSet(collapseComparator); + SortedSet updatable = new TreeSet<>(collapseComparator); for (Edge edge : src.edges) { updatable.add(edge.destination); @@ -997,48 +992,4 @@ private boolean collapse(Vertex src) { } return true; } - - private boolean assertValidMesh() { - // Allows to find bugs in collapsing. - for (Vertex vertex : collapseCostSet) { - assertValidVertex(vertex); - } - return true; - - } - - private boolean assertValidVertex(Vertex v) { - // Allows to find bugs in collapsing. - // System.out.println("Asserting " + v.index); - for (Triangle t : v.triangles) { - for (int i = 0; i < 3; i++) { - // System.out.println("check " + t.vertex[i].index); - - //assert (collapseCostSet.contains(t.vertex[i])); - assert (find(collapseCostSet, t.vertex[i])); - - assert (t.vertex[i].edges.contains(new Edge(t.vertex[i].collapseTo))); - for (int n = 0; n < 3; n++) { - if (i != n) { - - int id = t.vertex[i].edges.indexOf(new Edge(t.vertex[n])); - Edge ed = t.vertex[i].edges.get(id); - //assert (ed.collapseCost != UNINITIALIZED_COLLAPSE_COST); - } else { - assert (!t.vertex[i].edges.contains(new Edge(t.vertex[n]))); - } - } - } - } - return true; - } - - private boolean find(List set, Vertex v) { - for (Vertex vertex : set) { - if (v == vertex) { - return true; - } - } - return false; - } } diff --git a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java index 9ca7390149..9447b2d956 100644 --- a/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java +++ b/jme3-core/src/tools/java/jme3tools/optimize/TextureAtlas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -87,7 +87,7 @@ * *

                Also note that textures are not scaled and the atlas needs to be large enough to hold all textures. * All methods that allow adding textures return false if the texture could not be added due to the - * atlas being full. Furthermore secondary textures (normal, spcular maps etc.) have to be the same size + * atlas being full. Furthermore secondary textures (normal, specular maps etc.) have to be the same size * as the main (e.g. DiffuseMap) texture.

                * *

                Usage examples

                @@ -121,11 +121,11 @@ public class TextureAtlas { private static final Logger logger = Logger.getLogger(TextureAtlas.class.getName()); private Map images; - private int atlasWidth, atlasHeight; - private Format format = Format.ABGR8; - private Node root; - private Map locationMap; - private Map mapNameMap; + private final int atlasWidth, atlasHeight; + private final Format format = Format.ABGR8; + private final Node root; + private final Map locationMap; + private final Map mapNameMap; private String rootMapName; public TextureAtlas(int width, int height) { @@ -138,7 +138,8 @@ public TextureAtlas(int width, int height) { /** * Add a geometries DiffuseMap (or ColorMap), NormalMap and SpecularMap to the atlas. - * @param geometry + * + * @param geometry the Geometry to be added (not null) * @return false if the atlas is full. */ public boolean addGeometry(Geometry geometry) { @@ -348,6 +349,7 @@ private void drawImage(Image source, int x, int y, String mapName) { } } + @SuppressWarnings("unchecked") private Image convertImageToAwt(Image source) { //use awt dependent classes without actual dependency via reflection try { @@ -356,15 +358,15 @@ private Image convertImageToAwt(Image source) { return null; } Image newImage = new Image(format, source.getWidth(), source.getHeight(), BufferUtils.createByteBuffer(source.getWidth() * source.getHeight() * 4), null, ColorSpace.Linear); - clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.newInstance(), source, newImage); + clazz.getMethod("convert", Image.class, Image.class).invoke(clazz.getDeclaredConstructor().newInstance(), source, newImage); return newImage; - } catch (InstantiationException ex) { - } catch (IllegalAccessException ex) { - } catch (IllegalArgumentException ex) { - } catch (InvocationTargetException ex) { - } catch (NoSuchMethodException ex) { - } catch (SecurityException ex) { - } catch (ClassNotFoundException ex) { + } catch (InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException + | SecurityException + | ClassNotFoundException ex) { } return null; } @@ -393,7 +395,8 @@ private TextureAtlasTile getAtlasTile(String assetName) { /** * Creates a new atlas texture for the given map name. - * @param mapName + * + * @param mapName the desired name * @return the atlas texture */ public Texture getAtlasTexture(String mapName) { @@ -468,7 +471,7 @@ public boolean applyCoords(Geometry geom, int offset, Mesh outMesh) { * @return Null if the atlas cannot be created because not all textures fit. */ public static TextureAtlas createAtlas(Spatial root, int atlasSize) { - List geometries = new ArrayList(); + List geometries = new ArrayList<>(); GeometryBatchFactory.gatherGeoms(root, geometries); TextureAtlas atlas = new TextureAtlas(atlasSize, atlasSize); for (Geometry geometry : geometries) { @@ -484,12 +487,12 @@ public static TextureAtlas createAtlas(Spatial root, int atlasSize) { * Creates one geometry out of the given root spatial and merges all single * textures into one texture of the given size. * @param spat The root spatial of the scene to batch - * @param mgr An assetmanager that can be used to create the material. + * @param mgr An asset manager that can be used to create the material. * @param atlasSize A size for the atlas texture, it has to be large enough to hold all single textures. * @return A new geometry that uses the generated texture atlas and merges all meshes of the root spatial, null if the atlas cannot be created because not all textures fit. */ public static Geometry makeAtlasBatch(Spatial spat, AssetManager mgr, int atlasSize) { - List geometries = new ArrayList(); + List geometries = new ArrayList<>(); GeometryBatchFactory.gatherGeoms(spat, geometries); TextureAtlas atlas = createAtlas(spat, atlasSize); if (atlas == null) { @@ -619,8 +622,8 @@ public Node insert(Image image) { public class TextureAtlasTile { - private int x; - private int y; + final private int x; + final private int y; private int width; private int height; @@ -637,10 +640,10 @@ public TextureAtlasTile(int x, int y, int width, int height) { * @return The new texture coordinate inside the atlas. */ public Vector2f getLocation(Vector2f previousLocation) { - float x = (float) getX() / (float) atlasWidth; - float y = (float) getY() / (float) atlasHeight; - float w = (float) getWidth() / (float) atlasWidth; - float h = (float) getHeight() / (float) atlasHeight; + float x = getX() / (float) atlasWidth; + float y = getY() / (float) atlasHeight; + float w = getWidth() / (float) atlasWidth; + float h = getHeight() / (float) atlasHeight; Vector2f location = new Vector2f(x, y); float prevX = previousLocation.x; float prevY = previousLocation.y; diff --git a/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java b/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java index 4a116380ba..f01b3e9c1c 100644 --- a/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java +++ b/jme3-core/src/tools/java/jme3tools/savegame/SaveGame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,10 +55,16 @@ */ public class SaveGame { + /** + * A private constructor to inhibit instantiation of this class. + */ + private SaveGame() { + } + /** * Saves a savable in a system-dependent way. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @param data The Savable to save */ public static void saveGame(String gamePath, String dataName, Savable data) { @@ -68,7 +74,7 @@ public static void saveGame(String gamePath, String dataName, Savable data) { /** * Saves a savable in a system-dependent way. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @param data The Savable to save * @param storageType The specific type of folder to use to save the data */ @@ -121,7 +127,7 @@ public static void saveGame(String gamePath, String dataName, Savable data, JmeS /** * Loads a savable that has been saved on this system with saveGame() before. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @return The savable that was saved */ public static Savable loadGame(String gamePath, String dataName) { @@ -131,7 +137,7 @@ public static Savable loadGame(String gamePath, String dataName) { /** * Loads a savable that has been saved on this system with saveGame() before. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @param storageType The specific type of folder to use to save the data * @return The savable that was saved */ @@ -142,7 +148,7 @@ public static Savable loadGame(String gamePath, String dataName, JmeSystem.Stora /** * Loads a savable that has been saved on this system with saveGame() before. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures) * @return The savable that was saved or null if none was found */ @@ -153,7 +159,7 @@ public static Savable loadGame(String gamePath, String dataName, AssetManager ma /** * Loads a savable that has been saved on this system with saveGame() before. * @param gamePath A unique path for this game, e.g. com/mycompany/mygame - * @param dataName A unique name for this savegame, e.g. "save_001" + * @param dataName A unique name for this SaveGame, e.g. "save_001" * @param manager Link to an AssetManager if required for loading the data (e.g. models with textures) * @param storageType The specific type of folder to use to save the data * @return The savable that was saved or null if none was found diff --git a/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java b/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java new file mode 100644 index 0000000000..7c429e1e7b --- /dev/null +++ b/jme3-core/src/tools/java/jme3tools/shader/Preprocessor.java @@ -0,0 +1,214 @@ +/* + * 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 jme3tools.shader; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * GLSL Preprocessor + * + * @author Riccardo Balbo + */ +public class Preprocessor { + + public static InputStream apply(InputStream in) throws IOException { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + byte chunk[] = new byte[1024]; + int read; + while ((read = in.read(chunk)) != -1) { + bos.write(chunk, 0, read); + } + bos.close(); + in.close(); + + String code = bos.toString("UTF-8"); + + code = Preprocessor.forMacro(code); + code = Preprocessor.structMacro(code); + + return new ByteArrayInputStream(code.getBytes("UTF-8")); + } + + /** + * #for i=0..100 ( #ifdef ENABLE_INPUT_$i $0 #endif ) + * do something with $i + * #endfor + */ + private static final Pattern FOR_REGEX = Pattern.compile("([^=]+)=\\s*([0-9]+)\\s*\\.\\.\\s*([0-9]+)\\s*\\((.+)\\)"); + + public static String forMacro(String code) { + StringBuilder expandedCode = new StringBuilder(); + StringBuilder currentFor = null; + String forDec = null; + int skip = 0; + String codel[] = code.split("\n"); + boolean captured = false; + for (String l : codel) { + if (!captured) { + String ln = l.trim(); + if (ln.startsWith("#for")) { + if (skip == 0) { + forDec = ln; + currentFor = new StringBuilder(); + skip++; + continue; + } + skip++; + } else if (ln.startsWith("#endfor")) { + skip--; + if (skip == 0) { + forDec = forDec.substring("#for ".length()).trim(); + + Matcher matcher = FOR_REGEX.matcher(forDec); + if (matcher.matches()) { + String varN = "$" + matcher.group(1); + int start = Integer.parseInt(matcher.group(2)); + int end = Integer.parseInt(matcher.group(3)); + String inj = matcher.group(4); + if (inj.trim().isEmpty()) inj = "$0"; + String inCode = currentFor.toString(); + currentFor = null; + for (int i = start; i < end; i++) { + expandedCode.append("\n").append(inj.replace("$0", "\n" + inCode ).replace(varN, "" + i)).append("\n"); + } + captured = true; + continue; + } + } + } + } + if (currentFor != null) currentFor.append(l).append("\n"); + else expandedCode.append(l).append("\n"); + } + code = expandedCode.toString(); + if (captured) code = forMacro(code); + return code; + } + + /** + * + * #struct MyStruct extends BaseStruct, BaseStruct2 + * int i; + * int b; + * #endstruct + * + */ + // match #struct MyStruct extends BaseStruct, BaseStruct2 + // extends is optional + // private static final Pattern FOR_REGEX = Pattern + // .compile("([^=]+)=\\s*([0-9]+)\\s*\\.\\.\\s*([0-9]+)\\s*\\((.+)\\)"); + + private static final Pattern STRUCT_REGEX = Pattern + .compile("(\\w+)(?:\\s+extends\\s+(\\w+(?:,\\s*\\w+)*))?"); + + public static String structMacro(String code) { + StringBuilder expandedCode = new StringBuilder(); + StringBuilder currentStruct = null; + String structDec = null; + int skip = 0; + String[] codeLines = code.split("\n"); + boolean captured = false; + for (String line : codeLines) { + if (!captured) { + String trimmedLine = line.trim(); + if (trimmedLine.startsWith("#struct")) { + if (skip == 0) { + structDec = trimmedLine; + currentStruct = new StringBuilder(); + skip++; + continue; + } + skip++; + } else if (trimmedLine.startsWith("#endstruct")) { + skip--; + if (skip == 0) { + structDec = structDec.substring("#struct ".length()).trim(); + + Matcher matcher = STRUCT_REGEX.matcher(structDec); + if (matcher.matches()) { + String structName = matcher.group(1); + if (structName == null) structName = ""; + + String extendsStructs = matcher.group(2); + String extendedStructs[]; + if (extendsStructs != null) { + extendedStructs = extendsStructs.split(",\\s*"); + } else { + extendedStructs = new String[0]; + } + String structBody = currentStruct.toString(); + if (structBody == null) structBody = ""; + else { + // remove tail spaces + structBody = structBody.replaceAll("\\s+$", ""); + } + + currentStruct = null; + expandedCode.append("#define STRUCT_").append(structName).append(" \\\n"); + for (String extendedStruct : extendedStructs) { + expandedCode.append("STRUCT_").append(extendedStruct).append(" \\\n"); + } + String structBodyLines[] = structBody.split("\n"); + for (int i = 0; i < structBodyLines.length; i++) { + String structBodyLine = structBodyLines[i]; + structBodyLine = structBodyLine.trim(); + if (structBodyLine == "") continue; + // remove comments if any + int commentIndex = structBodyLine.indexOf("//"); + if (commentIndex >= 0) + structBodyLine = structBodyLine.substring(0, commentIndex); + expandedCode.append(structBodyLine); + if (i < structBodyLines.length - 1) expandedCode.append(" \\"); + expandedCode.append("\n"); + } + expandedCode.append("struct ").append(structName).append(" { \nSTRUCT_") + .append(structName).append("\n};\n"); + captured = true; + continue; + } + } + } + } + if (currentStruct != null) currentStruct.append(line).append("\n"); + else expandedCode.append(line).append("\n"); + } + code = expandedCode.toString(); + if (captured) code = structMacro(code); + return code; + } + +} \ No newline at end of file diff --git a/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java b/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java index 47a1324026..4ffd8734e8 100644 --- a/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java +++ b/jme3-core/src/tools/java/jme3tools/shader/ShaderDebug.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,17 +38,24 @@ public class ShaderDebug { /** - * Append the line numbers to the source code of a shader to output it + * A private constructor to inhibit instantiation of this class. + */ + private ShaderDebug() { + } + + /** + * Prepend line numbers to the source code of a shader, for output. + * * @param source the source - * @return the formated source code + * @return the formatted source code */ public static String formatShaderSource(String source) { String[] sourceLines = source.split("\n"); - int nblines = 0; + int lineNumber = 0; StringBuilder out = new StringBuilder(); for (String string : sourceLines) { - nblines++; - out.append(nblines).append("\t").append(string).append("\n"); + lineNumber++; + out.append(lineNumber).append("\t").append(string).append("\n"); } return out.toString(); } diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java index c5f314711f..7710b5ddb8 100644 --- a/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/CgcValidator.java @@ -33,14 +33,17 @@ private static String checkCgCompilerVersion(){ return null; } + @Override public String getName() { return "NVIDIA Cg Toolkit"; } + @Override public boolean isInstalled() { return getInstalledVersion() != null; } + @Override public String getInstalledVersion() { if (version == null){ version = checkCgCompilerVersion(); @@ -94,6 +97,7 @@ private static void executeCg(String sourceCode, String language, String defines } } + @Override public void validate(Shader shader, StringBuilder results) { for (ShaderSource source : shader.getSources()){ results.append("Checking: ").append(source.getName()); diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java index d06f26c88c..2064c5ced8 100644 --- a/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/GpuAnalyzerValidator.java @@ -37,14 +37,17 @@ private static String checkGpuAnalyzerVersion(){ return null; } + @Override public String getName() { return "AMD GPU Shader Analyzer"; } + @Override public boolean isInstalled() { return getInstalledVersion() != null; } + @Override public String getInstalledVersion() { if (version == null){ version = checkGpuAnalyzerVersion(); @@ -104,6 +107,7 @@ private static void executeAnalyzer(String sourceCode, String language, String d } } + @Override public void validate(Shader shader, StringBuilder results) { for (ShaderSource source : shader.getSources()){ results.append("Checking: ").append(source.getName()); diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java index ad4bf1dd18..0798593ced 100644 --- a/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/ShaderCheck.java @@ -20,7 +20,7 @@ public class ShaderCheck { private static final Logger logger = Logger.getLogger(ShaderCheck.class.getName()); private static AssetManager assetManager; - private static Validator[] validators = new Validator[]{ + final private static Validator[] validators = new Validator[]{ new CgcValidator(), // new GpuAnalyzerValidator() }; diff --git a/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java b/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java index a3574ca572..ac48eed779 100644 --- a/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java +++ b/jme3-core/src/tools/java/jme3tools/shadercheck/Validator.java @@ -9,11 +9,15 @@ public interface Validator { /** * Returns the name of the validation tool + * + * @return the name */ public String getName(); /** * Returns true if the tool is installed on the system, false otherwise. + * + * @return true if installed, otherwise false */ public boolean isInstalled(); diff --git a/jme3-desktop/build.gradle b/jme3-desktop/build.gradle index 60d0a831d8..bfc5138e77 100644 --- a/jme3-desktop/build.gradle +++ b/jme3-desktop/build.gradle @@ -1,7 +1,3 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') + api project(':jme3-core') } \ No newline at end of file diff --git a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java index 3765c1f809..eddf09b333 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java +++ b/jme3-desktop/src/main/java/com/jme3/app/AppletHarness.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,11 +34,14 @@ import com.jme3.system.AppSettings; import com.jme3.system.JmeCanvasContext; import com.jme3.system.JmeSystem; +import com.jme3.util.res.Resources; + import java.applet.Applet; import java.awt.Canvas; import java.awt.Graphics; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.util.HashMap; @@ -65,6 +68,7 @@ public static Applet getApplet(Application app){ return appToApplet.get(app); } + @SuppressWarnings("unchecked") private void createCanvas(){ AppSettings settings = new AppSettings(true); @@ -103,13 +107,14 @@ private void createCanvas(){ JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); - }catch (ClassNotFoundException ex){ - ex.printStackTrace(); - }catch (InstantiationException ex){ - ex.printStackTrace(); - }catch (IllegalAccessException ex){ + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException + | InstantiationException + | IllegalAccessException + | NoSuchMethodException + | IllegalArgumentException + | InvocationTargetException ex) { ex.printStackTrace(); } @@ -147,7 +152,7 @@ public void init(){ assetCfg = new URL(getParameter("AssetConfigURL")); } catch (MalformedURLException ex){ System.out.println(ex.getMessage()); - assetCfg = getClass().getResource("/com/jme3/asset/Desktop.cfg"); + assetCfg = Resources.getResource("/com/jme3/asset/Desktop.cfg",this.getClass()); } createCanvas(); @@ -170,6 +175,7 @@ public void stop(){ public void destroy(){ System.out.println("applet:destroyStart"); SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ removeAll(); System.out.println("applet:destroyRemoved"); diff --git a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java b/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java deleted file mode 100644 index 15e7aa76c2..0000000000 --- a/jme3-desktop/src/main/java/com/jme3/app/SettingsDialog.java +++ /dev/null @@ -1,904 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.app; - -import com.jme3.system.AppSettings; -import java.awt.*; -import java.awt.event.*; -import java.awt.image.BufferedImage; -import java.lang.reflect.Method; -import java.net.MalformedURLException; -import java.net.URL; -import java.text.MessageFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; -import java.util.ResourceBundle; -import java.util.logging.Level; -import java.util.logging.Logger; -import java.util.prefs.BackingStoreException; -import javax.swing.*; - -/** - * PropertiesDialog provides an interface to make use of the - * GameSettings class. The GameSettings object - * is still created by the client application, and passed during construction. - * - * @see AppSettings - * @author Mark Powell - * @author Eric Woroshow - * @author Joshua Slack - reworked for proper use of GL commands. - */ -public final class SettingsDialog extends JFrame { - - public static interface SelectionListener { - - public void onSelection(int selection); - } - private static final Logger logger = Logger.getLogger(SettingsDialog.class.getName()); - private static final long serialVersionUID = 1L; - 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"); - - // connection to properties file. - private final AppSettings source; - - // Title Image - private URL imageFile = null; - // Array of supported display modes - private DisplayMode[] modes = null; - private static final DisplayMode[] windowDefaults = new DisplayMode[] { - new DisplayMode(1024, 768, 24, 60), - new DisplayMode(1280, 720, 24, 60), - new DisplayMode(1280, 1024, 24, 60), - new DisplayMode(1440, 900, 24, 60), - new DisplayMode(1680, 1050, 24, 60), - }; - private DisplayMode[] windowModes = null; - - // UI components - private JCheckBox vsyncBox = null; - private JCheckBox gammaBox = null; - private JCheckBox fullscreenBox = null; - private JComboBox displayResCombo = null; - private JComboBox colorDepthCombo = null; - private JComboBox displayFreqCombo = null; - private JComboBox antialiasCombo = null; - private JLabel icon = null; - private int selection = 0; - private SelectionListener selectionListener = null; - - private int minWidth = 0; - private int minHeight = 0; - - /** - * Constructor for the PropertiesDialog. Creates a - * properties dialog initialized for the primary display. - * - * @param source - * the AppSettings object to use for working with - * the properties file. - * @param imageFile - * the image file to use as the title of the dialog; - * null will result in to image being displayed - * @throws NullPointerException - * if the source is null - */ - public SettingsDialog(AppSettings source, String imageFile, boolean loadSettings) { - this(source, getURL(imageFile), loadSettings); - } - - /** - * Constructor for the PropertiesDialog. Creates a - * properties dialog initialized for the primary display. - * - * @param source - * the GameSettings object to use for working with - * the properties file. - * @param imageFile - * the image file to use as the title of the dialog; - * null will result in to image being displayed - * @param loadSettings - * @throws JmeException - * if the source is null - */ - public SettingsDialog(AppSettings source, URL imageFile, boolean loadSettings) { - if (source == null) { - throw new NullPointerException("Settings source cannot be null"); - } - - this.source = source; - this.imageFile = imageFile; - - //setModal(true); - setAlwaysOnTop(true); - setResizable(false); - - AppSettings registrySettings = new AppSettings(true); - - String appTitle; - if(source.getTitle()!=null){ - appTitle = source.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); - } - - if (loadSettings) { - source.copyFrom(registrySettings); - } else if(!registrySettings.isEmpty()) { - source.mergeFrom(registrySettings); - } - - GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - - modes = device.getDisplayModes(); - 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) { - merged[mergedIndex] = modes[dmIndex++]; - } else if (modes[dmIndex].getWidth() < windowDefaults[wdIndex].getWidth()) { - merged[mergedIndex] = modes[dmIndex++]; - } else if (modes[dmIndex].getWidth() == windowDefaults[wdIndex].getWidth()) { - if (modes[dmIndex].getHeight() < windowDefaults[wdIndex].getHeight()) { - merged[mergedIndex] = modes[dmIndex++]; - } else if (modes[dmIndex].getHeight() == windowDefaults[wdIndex].getHeight()) { - merged[mergedIndex] = modes[dmIndex++]; - wdIndex++; - } else { - merged[mergedIndex] = windowDefaults[wdIndex++]; - } - } else { - merged[mergedIndex] = windowDefaults[wdIndex++]; - } - } - - if (merged.length == mergedIndex) { - windowModes = merged; - } else { - windowModes = Arrays.copyOfRange(merged, 0, mergedIndex); - } - - createUI(); - } - - public void setSelectionListener(SelectionListener sl) { - this.selectionListener = sl; - } - - public int getUserSelection() { - return selection; - } - - private void setUserSelection(int selection) { - this.selection = selection; - selectionListener.onSelection(selection); - } - - public int getMinWidth() { - return minWidth; - } - - public void setMinWidth(int minWidth) { - this.minWidth = minWidth; - } - - public int getMinHeight() { - return minHeight; - } - - public void setMinHeight(int minHeight) { - this.minHeight = minHeight; - } - - - - - /** - * setImage sets the background image of the dialog. - * - * @param image - * String representing the image file. - */ - public void setImage(String image) { - try { - URL file = new URL("file:" + image); - setImage(file); - } catch (MalformedURLException e) { - logger.log(Level.WARNING, "Couldn’t read from file '" + image + "'", e); - } - } - - /** - * setImage sets the background image of this dialog. - * - * @param image - * URL pointing to the image file. - */ - public void setImage(URL image) { - icon.setIcon(new ImageIcon(image)); - pack(); // Resize to accomodate the new image - setLocationRelativeTo(null); // put in center - } - - /** - * showDialog sets this dialog as visble, and brings it to - * the front. - */ - public void showDialog() { - setLocationRelativeTo(null); - 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() { - - @Override - public void windowClosing(WindowEvent e) { - setUserSelection(CANCEL_SELECTION); - dispose(); - } - }); - - if (source.getIcons() != null) { - safeSetIconImages( (List) 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 cancel = new JButton(resourceBundle.getString("button.cancel")); - - icon = new JLabel(imageFile != null ? new ImageIcon(imageFile) : null); - - KeyListener aListener = new KeyAdapter() { - - @Override - public void keyPressed(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - if (verifyAndSaveCurrentSelection()) { - setUserSelection(APPROVE_SELECTION); - dispose(); - } - } - else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - setUserSelection(CANCEL_SELECTION); - dispose(); - } - } - }; - - displayResCombo = setUpResolutionChooser(); - displayResCombo.addKeyListener(aListener); - colorDepthCombo = new JComboBox(); - colorDepthCombo.addKeyListener(aListener); - displayFreqCombo = new JComboBox(); - displayFreqCombo.addKeyListener(aListener); - antialiasCombo = new JComboBox(); - antialiasCombo.addKeyListener(aListener); - fullscreenBox = new JCheckBox(resourceBundle.getString("checkbox.fullscreen")); - fullscreenBox.setSelected(source.isFullscreen()); - fullscreenBox.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - updateResolutionChoices(); - } - }); - 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; - gbc.gridwidth = 2; - gbc.gridy = 1; - gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(fullscreenBox, gbc); - gbc = new GridBagConstraints(); - gbc.weightx = 0.5; - // gbc.insets = new Insets(4, 16, 0, 4); - gbc.gridx = 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.anchor = GridBagConstraints.WEST; - mainPanel.add(gammaBox, gbc); - - - gbc = new GridBagConstraints(); - gbc.insets = new Insets(4, 4, 4, 4); - gbc.gridx = 0; - gbc.gridy = 2; - gbc.anchor = GridBagConstraints.EAST; - gbc.weightx = 0.5; - mainPanel.add(new JLabel(resourceBundle.getString("label.resolutions")), gbc); - gbc = new GridBagConstraints(); - gbc.gridx = 1; - gbc.gridy = 2; - gbc.anchor = GridBagConstraints.WEST; - mainPanel.add(displayResCombo, gbc); - gbc = new GridBagConstraints(); - gbc.insets = new Insets(4, 16, 4, 4); - gbc.gridx = 2; - gbc.gridy = 2; - gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(new JLabel(resourceBundle.getString("label.colordepth")), gbc); - gbc = new GridBagConstraints(); - gbc.weightx = 0.5; - gbc.gridx = 3; - gbc.gridy = 2; - gbc.anchor = GridBagConstraints.WEST; - mainPanel.add(colorDepthCombo, gbc); - gbc = new GridBagConstraints(); - gbc.insets = new Insets(4, 4, 4, 4); - gbc.weightx = 0.5; - gbc.gridx = 0; - gbc.gridy = 3; - gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(new JLabel(resourceBundle.getString("label.refresh")), gbc); - gbc = new GridBagConstraints(); - gbc.gridx = 1; - gbc.gridy = 3; - gbc.anchor = GridBagConstraints.WEST; - mainPanel.add(displayFreqCombo, gbc); - gbc = new GridBagConstraints(); - gbc.insets = new Insets(4, 16, 4, 4); - gbc.gridx = 2; - gbc.gridy = 3; - gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(new JLabel(resourceBundle.getString("label.antialias")), gbc); - gbc = new GridBagConstraints(); - gbc.weightx = 0.5; - gbc.gridx = 3; - 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() { - - 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(); - System.gc(); - } - } - }); - - cancel.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - setUserSelection(CANCEL_SELECTION); - dispose(); - } - }); - - gbc = new GridBagConstraints(); - gbc.gridx = 0; - gbc.gridwidth = 2; - gbc.gridy = 4; - gbc.anchor = GridBagConstraints.EAST; - mainPanel.add(ok, gbc); - gbc = new GridBagConstraints(); - gbc.insets = new Insets(4, 16, 4, 4); - gbc.gridx = 2; - gbc.gridwidth = 2; - gbc.gridy = 4; - gbc.anchor = GridBagConstraints.WEST; - mainPanel.add(cancel, gbc); - - if (icon != null) { - gbc = new GridBagConstraints(); - gbc.gridwidth = 4; - mainPanel.add(icon, gbc); - } - - this.getContentPane().add(mainPanel); - - pack(); - - mainPanel.getRootPane().setDefaultButton(ok); - SwingUtilities.invokeLater(new Runnable() { - - 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. - updateResolutionChoices(); - if (source.getWidth() != 0 && source.getHeight() != 0) { - displayResCombo.setSelectedItem(source.getWidth() + " x " - + source.getHeight()); - } else { - 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 */ - 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. - Window owner = getOwner(); - if (owner != null) { - Method setIconImages = owner.getClass().getMethod("setIconImages", List.class); - setIconImages.invoke(owner, icons); - return; - } - - Method setIconImages = getClass().getMethod("setIconImages", List.class); - setIconImages.invoke(this, icons); - } catch (Exception e) { - logger.log(Level.WARNING, "Error setting icon images", e); - } - } - - /** - * verifyAndSaveCurrentSelection first verifies that the - * display mode is valid for this system, and then saves the current - * selection as a properties.cfg file. - * - * @return if the selection is valid - */ - private boolean verifyAndSaveCurrentSelection() { - String display = (String) displayResCombo.getSelectedItem(); - boolean fullscreen = fullscreenBox.isSelected(); - boolean vsync = vsyncBox.isSelected(); - boolean gamma = gammaBox.isSelected(); - - int width = Integer.parseInt(display.substring(0, display.indexOf(" x "))); - display = display.substring(display.indexOf(" x ") + 3); - int height = Integer.parseInt(display); - - String depthString = (String) colorDepthCombo.getSelectedItem(); - int depth = -1; - if (depthString.equals("???")) { - depth = 0; - } else { - depth = Integer.parseInt(depthString.substring(0, depthString.indexOf(' '))); - } - - String freqString = (String) displayFreqCombo.getSelectedItem(); - int freq = -1; - if (fullscreen) { - if (freqString.equals("???")) { - freq = 0; - } else { - freq = Integer.parseInt(freqString.substring(0, freqString.indexOf(' '))); - } - } - - String aaString = (String) antialiasCombo.getSelectedItem(); - int multisample = -1; - if (aaString.equals(resourceBundle.getString("antialias.disabled"))) { - multisample = 0; - } else { - multisample = Integer.parseInt(aaString.substring(0, aaString.indexOf('x'))); - } - - // FIXME: Does not work in Linux - /* - * if (!fullscreen) { //query the current bit depth of the desktop int - * curDepth = GraphicsEnvironment.getLocalGraphicsEnvironment() - * .getDefaultScreenDevice().getDisplayMode().getBitDepth(); if (depth > - * curDepth) { showError(this,"Cannot choose a higher bit depth in - * windowed " + "mode than your current desktop bit depth"); return - * false; } } - */ - - boolean valid = false; - - // test valid display mode when going full screen - if (!fullscreen) { - valid = true; - } else { - GraphicsDevice device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - valid = device.isFullScreenSupported(); - } - - if (valid) { - //use the GameSettings class to save it. - source.setWidth(width); - source.setHeight(height); - source.setBitsPerPixel(depth); - source.setFrequency(freq); - source.setFullscreen(fullscreen); - source.setVSync(vsync); - source.setGammaCorrection(gamma); - //source.setRenderer(renderer); - source.setSamples(multisample); - - String appTitle = source.getTitle(); - - try { - source.save(appTitle); - } catch (BackingStoreException ex) { - logger.log(Level.WARNING, - "Failed to save setting changes", ex); - } - } else { - showError( - this, - resourceBundle.getString("error.unsupportedmode")); - } - - return valid; - } - - /** - * setUpChooser retrieves all available display modes and - * places them in a JComboBox. The resolution specified by - * GameSettings is used as the default value. - * - * @return the combo box of display modes. - */ - private JComboBox setUpResolutionChooser() { - JComboBox resolutionBox = new JComboBox(); - - resolutionBox.addActionListener(new ActionListener() { - - public void actionPerformed(ActionEvent e) { - updateDisplayChoices(); - } - }); - - return resolutionBox; - } - - /** - * updateDisplayChoices updates the available color depth and - * display frequency options to match the currently selected resolution. - */ - private void updateDisplayChoices() { - if (!fullscreenBox.isSelected()) { - // don't run this function when changing windowed settings - return; - } - String resolution = (String) displayResCombo.getSelectedItem(); - String colorDepth = (String) colorDepthCombo.getSelectedItem(); - if (colorDepth == null) { - colorDepth = source.getBitsPerPixel() + " bpp"; - } - String displayFreq = (String) displayFreqCombo.getSelectedItem(); - if (displayFreq == null) { - displayFreq = source.getFrequency() + " Hz"; - } - - // grab available depths - String[] depths = getDepths(resolution, modes); - colorDepthCombo.setModel(new DefaultComboBoxModel(depths)); - colorDepthCombo.setSelectedItem(colorDepth); - // grab available frequencies - String[] freqs = getFrequencies(resolution, modes); - 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. - displayFreqCombo.setSelectedItem(getBestFrequency(resolution, modes)); - } - } - - /** - * updateResolutionChoices updates the available resolutions - * list to match the currently selected window mode (fullscreen or - * windowed). It then sets up a list of standard options (if windowed) or - * calls updateDisplayChoices (if fullscreen). - */ - private void updateResolutionChoices() { - if (!fullscreenBox.isSelected()) { - displayResCombo.setModel(new DefaultComboBoxModel( - getWindowedResolutions(windowModes))); - if (displayResCombo.getItemCount() > 0) { - 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")})); - displayFreqCombo.setEnabled(false); - } else { - displayResCombo.setModel(new DefaultComboBoxModel( - getResolutions(modes, Integer.MAX_VALUE, Integer.MAX_VALUE))); - if (displayResCombo.getItemCount() > 0) { - displayResCombo.setSelectedIndex(displayResCombo.getItemCount()-1); - } - displayFreqCombo.setEnabled(true); - updateDisplayChoices(); - } - } - - 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"}; - antialiasCombo.setModel(new DefaultComboBoxModel(choices)); - antialiasCombo.setSelectedItem(choices[Math.min(source.getSamples()/2,5)]); - } - - // - // Utility methods - // - /** - * Utility method for converting a String denoting a file into a URL. - * - * @return a URL pointing to the file or null - */ - private static URL getURL(String file) { - URL url = null; - try { - url = new URL("file:" + file); - } catch (MalformedURLException e) { - logger.log(Level.WARNING, "Invalid file name '" + file + "'", e); - } - return url; - } - - private static void showError(java.awt.Component parent, String 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. - */ - 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(); - if (width >= minWidth && height >= minHeight) { - if (height >= heightLimit) { - height = heightLimit; - } - if (width >= widthLimit) { - width = widthLimit; - } - - String res = width + " x " + height; - if (!resolutions.contains(res)) { - resolutions.add(res); - } - } - } - - String[] res = new String[resolutions.size()]; - resolutions.toArray(res); - return res; - } - - /** - * 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(); - } - if (maxWidth < modes[i].getWidth()) { - maxWidth = modes[i].getWidth(); - } - } - - return getResolutions(modes, maxHeight, maxWidth); - } - - /** - * 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++) { - // 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) { - continue; - } - - String res = modes[i].getWidth() + " x " + modes[i].getHeight(); - String depth = modes[i].getBitDepth() + " bpp"; - if (res.equals(resolution) && !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(); - depths.add("24 bpp"); - } - - String[] res = new String[depths.size()]; - depths.toArray(res); - return res; - } - - /** - * 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(); - String freq; - if (modes[i].getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { - freq = "???"; - } else { - freq = modes[i].getRefreshRate() + " Hz"; - } - - if (res.equals(resolution) && !freqs.contains(freq)) { - freqs.add(freq); - } - } - - String[] res = new String[freqs.size()]; - freqs.toArray(res); - return res; - } - - /** - * Chooses the closest frequency to 60 Hz. - * - * @param resolution - * @param modes - * @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(); - if (freq != DisplayMode.REFRESH_RATE_UNKNOWN && res.equals(resolution)) { - if (Math.abs(freq - desired) < - Math.abs(closest - desired)) { - closest = modes[i].getRefreshRate(); - } - } - } - - if (closest != Integer.MAX_VALUE) { - return closest + " Hz"; - } else { - return null; - } - } - - /** - * Utility class for sorting DisplayModes. Sorts by - * resolution, then bit depth, and then finally refresh rate. - */ - private class DisplayModeSorter implements Comparator { - - /** - * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object) - */ - public int compare(DisplayMode a, DisplayMode b) { - // Width - if (a.getWidth() != b.getWidth()) { - return (a.getWidth() > b.getWidth()) ? 1 : -1; - } - // Height - if (a.getHeight() != b.getHeight()) { - return (a.getHeight() > b.getHeight()) ? 1 : -1; - } - // Bit depth - if (a.getBitDepth() != b.getBitDepth()) { - return (a.getBitDepth() > b.getBitDepth()) ? 1 : -1; - } - // Refresh rate - if (a.getRefreshRate() != b.getRefreshRate()) { - return (a.getRefreshRate() > b.getRefreshRate()) ? 1 : -1; - } - // All fields are equal - return 0; - } - } -} diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java index 4a24f04958..db42f9050a 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java +++ b/jme3-desktop/src/main/java/com/jme3/app/state/AWTComponentAppState.java @@ -61,16 +61,16 @@ public void initialize(AppStateManager stateManager, Application app) { @Override public void stateAttached(final AppStateManager stateManager) { processor = new AWTFrameProcessor(); - processor.setTransferMode(transferMode); + processor.setTransferMode(transferMode); - AWTTaskExecutor.getInstance().addToExecute(new Runnable() { + AWTTaskExecutor.getInstance().addToExecute(new Runnable() { - @Override - public void run() { - processor.bind(component, stateManager.getApplication(), stateManager.getApplication().getViewPort()); - } - - }); + @Override + public void run() { + processor.bind(component, stateManager.getApplication(), stateManager.getApplication().getViewPort()); + } + + }); } @Override @@ -80,7 +80,7 @@ public void setEnabled(boolean enabled) { @Override public void update(float tpf) { - executor.execute(); + executor.execute(); super.update(tpf); } @@ -121,7 +121,7 @@ public void setComponent(Component component) { * @see #setTransferMode(com.jme3.system.AWTFrameProcessor.TransferMode) */ public AWTFrameProcessor.TransferMode getTransferMode(){ - return transferMode; + return transferMode; } /** @@ -130,6 +130,6 @@ public AWTFrameProcessor.TransferMode getTransferMode(){ * @see #getTransferMode() */ public void setTransferMode(AWTFrameProcessor.TransferMode mode) { - this.transferMode = mode; + this.transferMode = mode; } } diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java index b3d7514653..ae1d6b3b55 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java +++ b/jme3-desktop/src/main/java/com/jme3/app/state/MjpegFileWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,11 +34,16 @@ import java.awt.Graphics2D; import java.awt.Image; import java.awt.image.BufferedImage; +import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -52,17 +57,17 @@ * Released under BSD License * @author monceaux, normenhansen, entrusC */ -public class MjpegFileWriter { +public class MjpegFileWriter implements AutoCloseable { int width = 0; int height = 0; double framerate = 0; int numFrames = 0; File aviFile = null; - FileOutputStream aviOutput = null; - FileChannel aviChannel = null; + OutputStream aviOutput = null; long riffOffset = 0; long aviMovieOffset = 0; + long position = 0; AVIIndexList indexlist = null; public MjpegFileWriter(File aviFile, int width, int height, double framerate) throws Exception { @@ -75,33 +80,38 @@ public MjpegFileWriter(File aviFile, int width, int height, double framerate, in this.height = height; this.framerate = framerate; this.numFrames = numFrames; - aviOutput = new FileOutputStream(aviFile); - aviChannel = aviOutput.getChannel(); + FileOutputStream fos = new FileOutputStream(aviFile); + aviOutput = new BufferedOutputStream(fos); RIFFHeader rh = new RIFFHeader(); - aviOutput.write(rh.toBytes()); - aviOutput.write(new AVIMainHeader().toBytes()); - aviOutput.write(new AVIStreamList().toBytes()); - aviOutput.write(new AVIStreamHeader().toBytes()); - aviOutput.write(new AVIStreamFormat().toBytes()); - aviOutput.write(new AVIJunk().toBytes()); - aviMovieOffset = aviChannel.position(); - aviOutput.write(new AVIMovieList().toBytes()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(2048); + baos.write(rh.toBytes()); + baos.write(new AVIMainHeader().toBytes()); + baos.write(new AVIStreamList().toBytes()); + baos.write(new AVIStreamHeader().toBytes()); + baos.write(new AVIStreamFormat().toBytes()); + baos.write(new AVIJunk().toBytes()); + byte[] headerBytes = baos.toByteArray(); + aviOutput.write(headerBytes); + aviMovieOffset = headerBytes.length; + byte[] listBytes = new AVIMovieList().toBytes(); + aviOutput.write(listBytes); indexlist = new AVIIndexList(); + + position = headerBytes.length + listBytes.length; } public void addImage(Image image) throws Exception { addImage(image, 0.8f); } - + public void addImage(Image image, float quality) throws Exception { addImage(writeImageToBytes(image, quality)); } - public void addImage(byte[] imagedata) throws Exception { + public void addImage(byte[] imageData) throws Exception { byte[] fcc = new byte[]{'0', '0', 'd', 'b'}; - int useLength = imagedata.length; - long position = aviChannel.position(); + int useLength = imageData.length; int extra = (useLength + (int) position) % 4; if (extra > 0) { useLength = useLength + extra; @@ -109,39 +119,44 @@ public void addImage(byte[] imagedata) throws Exception { indexlist.addAVIIndex((int) position, useLength); - aviOutput.write(fcc); - aviOutput.write(intBytes(swapInt(useLength))); - aviOutput.write(imagedata); + ByteArrayOutputStream baos = new ByteArrayOutputStream(fcc.length + 4 + useLength); + baos.write(fcc); + baos.write(intBytes(swapInt(useLength))); + baos.write(imageData); if (extra > 0) { for (int i = 0; i < extra; i++) { - aviOutput.write(0); + baos.write(0); } } - imagedata = null; - + byte[] data = baos.toByteArray(); + aviOutput.write(data); + imageData = null; + numFrames++; //add a frame + position += data.length; } - public void finishAVI() throws Exception { + public void finishAVI() throws IOException { byte[] indexlistBytes = indexlist.toBytes(); aviOutput.write(indexlistBytes); aviOutput.close(); - int fileSize = (int)aviFile.length(); + int fileSize = (int) aviFile.length(); int listSize = (int) (fileSize - 8 - aviMovieOffset - indexlistBytes.length); - - RandomAccessFile raf = new RandomAccessFile(aviFile, "rw"); - + //add header and length by writing the headers again //with the now available information - raf.write(new RIFFHeader(fileSize).toBytes()); - raf.write(new AVIMainHeader().toBytes()); - raf.write(new AVIStreamList().toBytes()); - raf.write(new AVIStreamHeader().toBytes()); - raf.write(new AVIStreamFormat().toBytes()); - raf.write(new AVIJunk().toBytes()); - raf.write(new AVIMovieList(listSize).toBytes()); - - raf.close(); + try (SeekableByteChannel sbc = Files.newByteChannel(aviFile.toPath(), StandardOpenOption.WRITE); + ByteArrayOutputStream baos = new ByteArrayOutputStream()) { + baos.write(new RIFFHeader(fileSize).toBytes()); + baos.write(new AVIMainHeader().toBytes()); + baos.write(new AVIStreamList().toBytes()); + baos.write(new AVIStreamHeader().toBytes()); + baos.write(new AVIStreamFormat().toBytes()); + baos.write(new AVIJunk().toBytes()); + baos.write(new AVIMovieList(listSize).toBytes()); + + sbc.write(ByteBuffer.wrap(baos.toByteArray())); + } } // public void writeAVI(File file) throws Exception @@ -181,6 +196,11 @@ public static byte[] shortBytes(short i) { return b; } + @Override + public void close() throws Exception { + finishAVI(); + } + private class RIFFHeader { public byte[] fcc = new byte[]{'R', 'I', 'F', 'F'}; @@ -192,12 +212,12 @@ private class RIFFHeader { public RIFFHeader() { } - + public RIFFHeader(int fileSize) { this.fileSize = fileSize; } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(fileSize))); @@ -212,7 +232,7 @@ public byte[] toBytes() throws Exception { private class AVIMainHeader { /* - * + * * FOURCC fcc; DWORD cb; DWORD dwMicroSecPerFrame; DWORD * dwMaxBytesPerSec; DWORD dwPaddingGranularity; DWORD dwFlags; DWORD * dwTotalFrames; DWORD dwInitialFrames; DWORD dwStreams; DWORD @@ -256,7 +276,7 @@ public AVIMainHeader() { dwTotalFrames = numFrames; } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); @@ -288,7 +308,7 @@ private class AVIStreamList { public AVIStreamList() { } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(size))); @@ -341,7 +361,7 @@ public AVIStreamHeader() { dwLength = numFrames; } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); @@ -401,7 +421,7 @@ public AVIStreamFormat() { biSizeImage = width * height; } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); @@ -434,8 +454,8 @@ public AVIMovieList() { public AVIMovieList(int listSize) { this.listSize = listSize; } - - public byte[] toBytes() throws Exception { + + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(listSize))); @@ -449,7 +469,7 @@ private class AVIIndexList { public byte[] fcc = new byte[]{'i', 'd', 'x', '1'}; public int cb = 0; - public List ind = new ArrayList(); + public List ind = new ArrayList<>(); public AVIIndexList() { } @@ -463,14 +483,14 @@ public void addAVIIndex(int dwOffset, int dwSize) { ind.add(new AVIIndex(dwOffset, dwSize)); } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { cb = 16 * ind.size(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(cb))); for (int i = 0; i < ind.size(); i++) { - AVIIndex in = (AVIIndex) ind.get(i); + AVIIndex in = ind.get(i); baos.write(in.toBytes()); } @@ -490,7 +510,7 @@ public AVIIndex(int dwOffset, int dwSize) { this.dwSize = dwSize; } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(dwFlags))); @@ -511,7 +531,7 @@ public AVIJunk() { Arrays.fill(data, (byte) 0); } - public byte[] toBytes() throws Exception { + public byte[] toBytes() throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(fcc); baos.write(intBytes(swapInt(size))); @@ -532,16 +552,16 @@ public byte[] writeImageToBytes(Image image, float quality) throws Exception { } ByteArrayOutputStream baos = new ByteArrayOutputStream(); - ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next(); - ImageOutputStream imgOutStrm = ImageIO.createImageOutputStream(baos); - imgWrtr.setOutput(imgOutStrm); - - ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam(); - jpgWrtPrm.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); - jpgWrtPrm.setCompressionQuality(quality); - imgWrtr.write(null, new IIOImage(bi, null, null), jpgWrtPrm); - imgOutStrm.close(); - + ImageWriter imgWrtr = ImageIO.getImageWritersByFormatName("jpg").next(); + try (ImageOutputStream imgOutStrm = ImageIO.createImageOutputStream(baos)) { + imgWrtr.setOutput(imgOutStrm); + + ImageWriteParam jpgWrtPrm = imgWrtr.getDefaultWriteParam(); + jpgWrtPrm.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + jpgWrtPrm.setCompressionQuality(quality); + imgWrtr.write(null, new IIOImage(bi, null, null), jpgWrtPrm); + } + return baos.toByteArray(); } } diff --git a/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java index eb4abe4cdc..7e1d55b3e3 100644 --- a/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java +++ b/jme3-desktop/src/main/java/com/jme3/app/state/VideoRecorderAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ /** * A Video recording AppState that records the screen output into an AVI file with - * M-JPEG content. The file should be playable on any OS in any video player.
                + * M-JPEG content. The file should be playable on any OS in any video player.
                * The video recording starts when the state is attached and stops when it is detached * or the application is quit. You can set the fileName of the file to be written when the * state is detached, else the old file will be overwritten. If you specify no file @@ -70,6 +70,7 @@ public class VideoRecorderAppState extends AbstractAppState { private Application app; private ExecutorService executor = Executors.newCachedThreadPool(new ThreadFactory() { + @Override public Thread newThread(Runnable r) { Thread th = new Thread(r); th.setName("jME3 Video Processor"); @@ -131,6 +132,7 @@ public VideoRecorderAppState(File file, float quality) { * This constructor allows you to specify the output file of the video as well as the quality * @param file the video file * @param quality the quality of the jpegs in the video stream (0.0 smallest file - 1.0 largest file) + * @param framerate the frame rate of the resulting video, the application will be locked to this framerate */ public VideoRecorderAppState(File file, float quality, int framerate) { this.file = file; @@ -216,11 +218,10 @@ private class VideoProcessor implements SceneProcessor { private int width; private int height; private RenderManager renderManager; - private boolean isInitilized = false; + private boolean isInitialized = false; private LinkedBlockingQueue freeItems; - private LinkedBlockingQueue usedItems = new LinkedBlockingQueue(); + private LinkedBlockingQueue usedItems = new LinkedBlockingQueue<>(); private MjpegFileWriter writer; - private AppProfiler prof; public void addImage(Renderer renderer, FrameBuffer out) { if (freeItems == null) { @@ -233,6 +234,7 @@ public void addImage(Renderer renderer, FrameBuffer out) { renderer.readFrameBufferWithFormat(out, item.buffer, Image.Format.BGRA8); executor.submit(new Callable() { + @Override public Void call() throws Exception { Screenshots.convertScreenShot(item.buffer, item.image); item.data = writer.writeImageToBytes(item.image, quality); @@ -250,12 +252,13 @@ public Void call() throws Exception { } } + @Override public void initialize(RenderManager rm, ViewPort viewPort) { this.camera = viewPort.getCamera(); this.width = camera.getWidth(); this.height = camera.getHeight(); this.renderManager = rm; - this.isInitilized = true; + this.isInitialized = true; if (freeItems == null) { freeItems = new LinkedBlockingQueue(); for (int i = 0; i < numCpus; i++) { @@ -264,13 +267,16 @@ public void initialize(RenderManager rm, ViewPort viewPort) { } } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { - return this.isInitilized; + return this.isInitialized; } + @Override public void preFrame(float tpf) { if (null == writer) { try { @@ -281,13 +287,16 @@ public void preFrame(float tpf) { } } + @Override public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { addImage(renderManager.getRenderer(), out); } + @Override public void cleanup() { try { while (freeItems.size() < numCpus) { @@ -302,7 +311,7 @@ public void cleanup() { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } } @@ -317,22 +326,27 @@ public IsoTimer(float framerate) { this.ticks = 0; } + @Override public long getTime() { return (long) (this.ticks * (1.0f / this.framerate) * 1000f); } + @Override public long getResolution() { return 1000L; } + @Override public float getFrameRate() { return this.framerate; } + @Override public float getTimePerFrame() { - return (float) (1.0f / this.framerate); + return 1.0f / this.framerate; } + @Override public void update() { long time = System.currentTimeMillis(); long difference = time - lastTime; @@ -346,6 +360,7 @@ public void update() { this.ticks++; } + @Override public void reset() { this.ticks = 0; } diff --git a/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java b/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java index 037927ac61..7d530ad960 100644 --- a/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/cursors/plugins/CursorLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,8 +32,8 @@ package com.jme3.cursors.plugins; import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetKey; import com.jme3.asset.AssetLoader; +import com.jme3.export.binary.ByteUtils; import com.jme3.util.BufferUtils; import com.jme3.util.LittleEndien; import java.awt.geom.AffineTransform; @@ -67,17 +67,18 @@ public class CursorLoader implements AssetLoader { * @return A JmeCursor representation of the LWJGL's Cursor. * @throws IOException if the file is not found. */ + @Override public JmeCursor load(AssetInfo info) throws IOException { isIco = false; isAni = false; isCur = false; - isIco = ((AssetKey) info.getKey()).getExtension().equals("ico"); + isIco = info.getKey().getExtension().equals("ico"); if (!isIco) { - isCur = ((AssetKey) info.getKey()).getExtension().equals("cur"); + isCur = info.getKey().getExtension().equals("cur"); if (!isCur) { - isAni = ((AssetKey) info.getKey()).getExtension().equals("ani"); + isAni = info.getKey().getExtension().equals("ani"); } } if (!isAni && !isIco && !isCur) { @@ -127,13 +128,13 @@ private JmeCursor loadCursor(InputStream inStream) throws IOException { nextInt = getNext(leIn); while (nextInt >= 0) { if (nextInt == 0x68696e61) { -// System.out.println("we have 'anih' header"); - leIn.skipBytes(8); // internal struct length (always 36) +// System.out.println("we have 'anih' header"); + ByteUtils.skipFully((DataInput) leIn, 8); // internal struct length (always 36) numIcons = leIn.readInt(); steps = leIn.readInt(); // number of blits for ani cycles width = leIn.readInt(); height = leIn.readInt(); - leIn.skipBytes(8); + ByteUtils.skipFully((DataInput) leIn, 8); jiffy = leIn.readInt(); flag = leIn.readInt(); nextInt = leIn.readInt(); @@ -162,7 +163,8 @@ private JmeCursor loadCursor(InputStream inStream) throws IOException { nextInt = leIn.readInt(); if (nextInt == 0x4f464e49) { // Got an INFO, skip its length // this part consist of Author, title, etc - leIn.skipBytes(length - 4); + + ByteUtils.skipFully((DataInput) leIn, length - 4); // System.out.println(" Discarding INFO (skipped = " + skipped + ")"); nextInt = leIn.readInt(); } else if (nextInt == 0x6d617266) { // found a 'fram' for animation @@ -177,10 +179,10 @@ private JmeCursor loadCursor(InputStream inStream) throws IOException { if (i > 0) { // skip 'icon' header and length as they are // known already and won't change. - leIn.skipBytes(8); + ByteUtils.skipFully((DataInput) leIn, 8); } byte[] data = new byte[icoLength]; - ((InputStream) leIn).read(data, 0, icoLength); + ByteUtils.readFully((InputStream) leIn, data); // in case the header didn't have width or height // get it from first image. if (width == 0 || height == 0 && i == 1) { @@ -189,8 +191,8 @@ private JmeCursor loadCursor(InputStream inStream) throws IOException { } icons.add(data); } - // at this point we have the icons, rates (either - // through jiffy or rate array, the sequence (if + // At this point we have the icons, the rates (either + // through jiffy or rate array), the sequence (if // applicable) and the ani header info. // Put things together. ciDat.assembleCursor(icons, rate, animSeq, jiffy, steps, width, height); @@ -216,7 +218,7 @@ private JmeCursor loadCursor(InputStream inStream) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); byte[] buffer = new byte[16384]; int bytesRead; - while ((bytesRead = in.read(buffer)) >= 0) { + while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } icoimages = out.toByteArray(); @@ -266,7 +268,7 @@ private JmeCursor setJmeCursor(CursorLoader.CursorImageData cid) { return jmeCursor; } - private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { + private BufferedImage[] parseICOImage(byte[] icoImage) throws IOException { /* * Most of this is original code by Jeff Friesen at * http://www.informit.com/articles/article.aspx?p=1186882&seqNum=3 @@ -277,52 +279,52 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { int DE_LENGTH = 16; // directory entry length int BMIH_LENGTH = 40; // BITMAPINFOHEADER length - if (icoimage[2] != 1 && icoimage[2] != 2 || icoimage[3] != 0) { + if (icoImage[2] != 1 && icoImage[2] != 2 || icoImage[3] != 0) { throw new IllegalArgumentException("Bad data in ICO/CUR file. ImageType has to be either 1 or 2."); } - int numImages = ubyte(icoimage[5]); + int numImages = ubyte(icoImage[5]); numImages <<= 8; - numImages |= icoimage[4]; + numImages |= icoImage[4]; bi = new BufferedImage[numImages]; int[] colorCount = new int[numImages]; for (int i = 0; i < numImages; i++) { - int width = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH]); + int width = ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH]); - int height = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 1]); + int height = ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 1]); - colorCount[i] = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 2]); + colorCount[i] = ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 2]); - int bytesInRes = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 11]); + int bytesInRes = ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 11]); bytesInRes <<= 8; - bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 10]); + bytesInRes |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 10]); bytesInRes <<= 8; - bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 9]); + bytesInRes |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 9]); bytesInRes <<= 8; - bytesInRes |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 8]); + bytesInRes |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 8]); - int imageOffset = ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 15]); + int imageOffset = ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 15]); imageOffset <<= 8; - imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 14]); + imageOffset |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 14]); imageOffset <<= 8; - imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 13]); + imageOffset |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 13]); imageOffset <<= 8; - imageOffset |= ubyte(icoimage[FDE_OFFSET + i * DE_LENGTH + 12]); + imageOffset |= ubyte(icoImage[FDE_OFFSET + i * DE_LENGTH + 12]); - if (icoimage[imageOffset] == 40 - && icoimage[imageOffset + 1] == 0 - && icoimage[imageOffset + 2] == 0 - && icoimage[imageOffset + 3] == 0) { + if (icoImage[imageOffset] == 40 + && icoImage[imageOffset + 1] == 0 + && icoImage[imageOffset + 2] == 0 + && icoImage[imageOffset + 3] == 0) { // BITMAPINFOHEADER detected - int _width = ubyte(icoimage[imageOffset + 7]); + int _width = ubyte(icoImage[imageOffset + 7]); _width <<= 8; - _width |= ubyte(icoimage[imageOffset + 6]); + _width |= ubyte(icoImage[imageOffset + 6]); _width <<= 8; - _width |= ubyte(icoimage[imageOffset + 5]); + _width |= ubyte(icoImage[imageOffset + 5]); _width <<= 8; - _width |= ubyte(icoimage[imageOffset + 4]); + _width |= ubyte(icoImage[imageOffset + 4]); // If width is 0 (for 256 pixels or higher), _width contains // actual width. @@ -331,13 +333,13 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { width = _width; } - int _height = ubyte(icoimage[imageOffset + 11]); + int _height = ubyte(icoImage[imageOffset + 11]); _height <<= 8; - _height |= ubyte(icoimage[imageOffset + 10]); + _height |= ubyte(icoImage[imageOffset + 10]); _height <<= 8; - _height |= ubyte(icoimage[imageOffset + 9]); + _height |= ubyte(icoImage[imageOffset + 9]); _height <<= 8; - _height |= ubyte(icoimage[imageOffset + 8]); + _height |= ubyte(icoImage[imageOffset + 8]); // If height is 0 (for 256 pixels or higher), _height contains // actual height times 2. @@ -345,13 +347,13 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { if (height == 0) { height = _height >> 1; // Divide by 2. } - int planes = ubyte(icoimage[imageOffset + 13]); + int planes = ubyte(icoImage[imageOffset + 13]); planes <<= 8; - planes |= ubyte(icoimage[imageOffset + 12]); + planes |= ubyte(icoImage[imageOffset + 12]); - int bitCount = ubyte(icoimage[imageOffset + 15]); + int bitCount = ubyte(icoImage[imageOffset + 15]); bitCount <<= 8; - bitCount |= ubyte(icoimage[imageOffset + 14]); + bitCount |= ubyte(icoImage[imageOffset + 14]); // If colorCount [i] is 0, the number of colors is determined // from the planes and bitCount values. For example, the number @@ -392,7 +394,7 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { for (int col = 0; col < width; col++) { int index; - if ((ubyte(icoimage[xorImageOffset + row + if ((ubyte(icoImage[xorImageOffset + row * scanlineBytes + col / 8]) & masks[col % 8]) != 0) { index = 1; @@ -401,16 +403,16 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { } int rgb = 0; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 2])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 1])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index + rgb |= (ubyte(icoImage[colorTableOffset + index * 4])); - if ((ubyte(icoimage[andImageOffset + row + if ((ubyte(icoImage[andImageOffset + row * scanlineBytes + col / 8]) & masks[col % 8]) != 0) { bi[i].setRGB(col, height - 1 - row, rgb); @@ -433,26 +435,26 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { int index; if ((col & 1) == 0) // even { - index = ubyte(icoimage[xorImageOffset + row + index = ubyte(icoImage[xorImageOffset + row * scanlineBytes + col / 2]); index >>= 4; } else { - index = ubyte(icoimage[xorImageOffset + row + index = ubyte(icoImage[xorImageOffset + row * scanlineBytes + col / 2]) & 15; } int rgb = 0; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 2])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 1])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index + rgb |= (ubyte(icoImage[colorTableOffset + index * 4])); - if ((ubyte(icoimage[andImageOffset + row + if ((ubyte(icoImage[andImageOffset + row * calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0) { @@ -474,19 +476,19 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { int index; - index = ubyte(icoimage[xorImageOffset + row + index = ubyte(icoImage[xorImageOffset + row * scanlineBytes + col]); int rgb = 0; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 2])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4 + rgb |= (ubyte(icoImage[colorTableOffset + index * 4 + 1])); rgb <<= 8; - rgb |= (ubyte(icoimage[colorTableOffset + index * 4])); + rgb |= (ubyte(icoImage[colorTableOffset + index * 4])); - if ((ubyte(icoimage[andImageOffset + row + if ((ubyte(icoImage[andImageOffset + row * calcScanlineBytes(width, 1) + col / 8]) & masks[col % 8]) != 0) { @@ -502,34 +504,34 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { for (int row = 0; row < height; row++) { for (int col = 0; col < width; col++) { - int rgb = ubyte(icoimage[colorTableOffset + row + int rgb = ubyte(icoImage[colorTableOffset + row * scanlineBytes + col * 4 + 3]); rgb <<= 8; - rgb |= ubyte(icoimage[colorTableOffset + row + rgb |= ubyte(icoImage[colorTableOffset + row * scanlineBytes + col * 4 + 2]); rgb <<= 8; - rgb |= ubyte(icoimage[colorTableOffset + row + rgb |= ubyte(icoImage[colorTableOffset + row * scanlineBytes + col * 4 + 1]); rgb <<= 8; - rgb |= ubyte(icoimage[colorTableOffset + row + rgb |= ubyte(icoImage[colorTableOffset + row * scanlineBytes + col * 4]); bi[i].setRGB(col, height - 1 - row, rgb); } } } - } else if (ubyte(icoimage[imageOffset]) == 0x89 - && icoimage[imageOffset + 1] == 0x50 - && icoimage[imageOffset + 2] == 0x4e - && icoimage[imageOffset + 3] == 0x47 - && icoimage[imageOffset + 4] == 0x0d - && icoimage[imageOffset + 5] == 0x0a - && icoimage[imageOffset + 6] == 0x1a - && icoimage[imageOffset + 7] == 0x0a) { + } else if (ubyte(icoImage[imageOffset]) == 0x89 + && icoImage[imageOffset + 1] == 0x50 + && icoImage[imageOffset + 2] == 0x4e + && icoImage[imageOffset + 3] == 0x47 + && icoImage[imageOffset + 4] == 0x0d + && icoImage[imageOffset + 5] == 0x0a + && icoImage[imageOffset + 6] == 0x1a + && icoImage[imageOffset + 7] == 0x0a) { // PNG detected ByteArrayInputStream bais; - bais = new ByteArrayInputStream(icoimage, imageOffset, + bais = new ByteArrayInputStream(icoImage, imageOffset, bytesInRes); bi[i] = ImageIO.read(bais); } else { @@ -537,7 +539,7 @@ private BufferedImage[] parseICOImage(byte[] icoimage) throws IOException { + "expected"); } } - icoimage = null; // This array can now be garbage collected. + icoImage = null; // This array can now be garbage collected. return bi; } @@ -578,7 +580,7 @@ public CursorImageData() { // 1 - ICO // 2 - CUR IntBuffer singleCursor = null; - ArrayList cursors = new ArrayList(); + ArrayList cursors = new ArrayList<>(); int bwidth = 0; int bheight = 0; boolean multIcons = false; @@ -708,7 +710,7 @@ private void addFrame(byte[] imgData, int rate, int jiffy, int width, int height } void assembleCursor(ArrayList icons, int[] rate, int[] animSeq, int jiffy, int steps, int width, int height) throws IOException { - // Jiffy multiplicator for LWJGL's delay, which is in milisecond. + // Jiffy multiplier for LWJGL's delay, which is in milliseconds. final int MULT = 17; numImages = icons.size(); int frRate = 0; diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java index cceab1d6e3..be3c9f5de5 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -83,7 +83,7 @@ public AWTInput(final AWTContext context) { public void bind(final Component component) { this.component = component; - Objects.requireNonNull(this.component, "binded Component cannot be null"); + Objects.requireNonNull(this.component, "bound Component cannot be null"); } public void unbind() { diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java index 0280d8aedb..b4c5059a07 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTKeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,18 +31,17 @@ */ package com.jme3.input; +import com.jme3.input.event.KeyInputEvent; import java.awt.Component; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; +import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; -import com.jme3.input.KeyInput; -import com.jme3.input.event.KeyInputEvent; import com.jme3.system.AWTContext; - /** * The implementation of the {@link KeyInput} dedicated to AWT {@link Component component}. *

                @@ -166,11 +165,11 @@ public class AWTKeyInput extends AWTInput implements KeyInput, KeyListener{ KEY_CODE_TO_JME.put(KeyEvent.VK_META, KEY_RCONTROL); } - private final LinkedList keyInputEvents; + private final Deque keyInputEvents; public AWTKeyInput(AWTContext context) { super(context); - keyInputEvents = new LinkedList(); + keyInputEvents = new LinkedList<>(); } @Override @@ -234,4 +233,9 @@ public void keyReleased(KeyEvent e) { System.out.println("Key released "+e.getKeyChar()); onKeyEvent(e, false); } + + @Override + public String getKeyName(int key){ + throw new UnsupportedOperationException("getKeyName is not implemented in AWTKeyInput"); + } } diff --git a/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java index 72b75a5d38..4caef0ca12 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/AWTMouseInput.java @@ -31,22 +31,21 @@ */ package com.jme3.input; +import com.jme3.cursors.plugins.JmeCursor; +import com.jme3.input.event.MouseButtonEvent; +import com.jme3.input.event.MouseMotionEvent; import java.awt.Component; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; +import java.util.Deque; import java.util.HashMap; import java.util.LinkedList; import java.util.Map; -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.MouseInput; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; import com.jme3.system.AWTContext; - /** * The implementation of the {@link MouseInput} dedicated to AWT {@link Component component}. *

                @@ -70,9 +69,9 @@ public class AWTMouseInput extends AWTInput implements MouseInput, MouseListener */ private static final int WHEEL_SCALE = 10; - private final LinkedList mouseMotionEvents; + private final Deque mouseMotionEvents; - private final LinkedList mouseButtonEvents; + private final Deque mouseButtonEvents; private int mouseX; private int mouseY; @@ -80,8 +79,8 @@ public class AWTMouseInput extends AWTInput implements MouseInput, MouseListener public AWTMouseInput(AWTContext context) { super(context); - mouseMotionEvents = new LinkedList(); - mouseButtonEvents = new LinkedList(); + mouseMotionEvents = new LinkedList<>(); + mouseButtonEvents = new LinkedList<>(); } @Override diff --git a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java index b27e290359..1ce0aeb694 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtKeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,6 +39,7 @@ import java.awt.event.KeyListener; import java.util.ArrayList; import java.util.BitSet; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; @@ -53,17 +54,19 @@ public class AwtKeyInput implements KeyInput, KeyListener { private static final Logger logger = Logger.getLogger(AwtKeyInput.class.getName()); - private final ArrayList eventQueue = new ArrayList(); + private final List eventQueue = new ArrayList<>(); private RawInputListener listener; private Component component; - private BitSet keyStateSet = new BitSet(0xFF); + private final BitSet keyStateSet = new BitSet(0xFF); public AwtKeyInput(){ } + @Override public void initialize() { } + @Override public void destroy() { } @@ -79,6 +82,7 @@ public void setInputSource(Component comp){ } } + @Override public long getInputTimeNanos() { return System.nanoTime(); } @@ -87,6 +91,7 @@ public int getKeyCount() { return KeyEvent.KEY_LAST+1; } + @Override public void update() { synchronized (eventQueue){ // flush events to listener @@ -97,18 +102,22 @@ public void update() { } } + @Override public boolean isInitialized() { return true; } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public void keyTyped(KeyEvent evt) { // key code is zero for typed events } + @Override public void keyPressed(KeyEvent evt) { int code = convertAwtKey(evt.getKeyCode()); @@ -123,6 +132,7 @@ public void keyPressed(KeyEvent evt) { } } + @Override public void keyReleased(KeyEvent evt) { int code = convertAwtKey(evt.getKeyCode()); @@ -601,7 +611,7 @@ public static int convertAwtKey(int key) { case KeyEvent.VK_ALT: return KEY_LMENU; //Left vs. Right need to improve case KeyEvent.VK_META: - return KEY_RCONTROL; + return KEY_RCONTROL; case KeyEvent.VK_PRINTSCREEN: return KEY_PRTSCR; @@ -614,4 +624,9 @@ public static int convertAwtKey(int key) { return 0; } + @Override + public String getKeyName(int key){ + throw new UnsupportedOperationException("getKeyName not implemented for awt input"); + } + } diff --git a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java index f53a3b003e..d4076c87b8 100644 --- a/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java +++ b/jme3-desktop/src/main/java/com/jme3/input/awt/AwtMouseInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,10 +36,16 @@ import com.jme3.input.RawInputListener; import com.jme3.input.event.MouseButtonEvent; import com.jme3.input.event.MouseMotionEvent; -import java.awt.*; + +import java.awt.Component; +import java.awt.Cursor; +import java.awt.Point; +import java.awt.Robot; +import java.awt.Toolkit; import java.awt.event.*; import java.awt.image.BufferedImage; import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.SwingUtilities; @@ -64,8 +70,8 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe private Component component; - private final ArrayList eventQueue = new ArrayList(); - private final ArrayList eventQueueCopy = new ArrayList(); + private final List eventQueue = new ArrayList<>(); + private final List eventQueueCopy = new ArrayList<>(); private int lastEventX; private int lastEventY; @@ -79,6 +85,7 @@ public class AwtMouseInput implements MouseInput, MouseListener, MouseWheelListe private Point centerLocation; private Point centerLocationOnScreen; private Point lastKnownLocation; + private Point grabLocation; private boolean isRecentering; private boolean cursorMoved; private int eventsSinceRecenter; @@ -88,6 +95,7 @@ public AwtMouseInput() { centerLocation = new Point(); centerLocationOnScreen = new Point(); lastKnownLocation = new Point(); + grabLocation = new Point(); try { robot = new Robot(); @@ -111,6 +119,7 @@ public void setInputSource(Component comp) { lastEventY = 0; lastEventWheel = 0; location = new Point(); + grabLocation = new Point(); centerLocation = new Point(); centerLocationOnScreen = new Point(); lastKnownLocation = new Point(); @@ -122,46 +131,47 @@ public void setInputSource(Component comp) { component.addMouseWheelListener(this); } + @Override public void initialize() { } + @Override public void destroy() { } + @Override public boolean isInitialized() { return true; } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public long getInputTimeNanos() { return System.nanoTime(); } - + + @Override public void setCursorVisible(boolean visible) { -// if(JmeSystem.getPlatform() != Platform.MacOSX32 && -// JmeSystem.getPlatform() != Platform.MacOSX64 && -// JmeSystem.getPlatform() != Platform.MacOSX_PPC32 && -// JmeSystem.getPlatform() != Platform.MacOSX_PPC64){ if (this.visible != visible) { - lastKnownLocation.x = lastKnownLocation.y = 0; + grabLocation.x = lastKnownLocation.x; + grabLocation.y = lastKnownLocation.y; this.visible = visible; final boolean newVisible = visible; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - component.setCursor(newVisible ? null : getTransparentCursor()); - if (!newVisible) { + SwingUtilities.invokeLater(() -> { + component.setCursor(newVisible ? null : getTransparentCursor()); + if (!newVisible) { recenterMouse(component); - } } }); -// } } } + @Override public void update() { if (cursorMoved) { int newX = location.x; @@ -205,25 +215,28 @@ private Cursor getTransparentCursor() { return transparentCursor; } -// public void setHardwareCursor(URL file, int xHotspot, int yHotspot) { -// //Create the image from the provided url -// java.awt.Image cursorImage = new ImageIcon( file ).getImage( ); -// //Create a custom cursor with this image -// opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" ); -// //Use this cursor -// setCursorVisible( isCursorVisible ); -// } +// public void setHardwareCursor(URL file, int xHotspot, int yHotspot) { +// //Create the image from the provided url +// java.awt.Image cursorImage = new ImageIcon( file ).getImage( ); +// //Create a custom cursor with this image +// opaqueCursor = Toolkit.getDefaultToolkit().createCustomCursor( cursorImage , new Point( xHotspot , yHotspot ) , "custom cursor" ); +// //Use this cursor +// setCursorVisible( isCursorVisible ); +// } + @Override public int getButtonCount() { return 3; } + @Override public void mouseClicked(MouseEvent awtEvt) { // MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); // listener.onMouseButtonEvent(evt); } + @Override public void mousePressed(MouseEvent awtEvt) { // Must flip Y! int y = component.getHeight() - awtEvt.getY(); @@ -234,6 +247,7 @@ public void mousePressed(MouseEvent awtEvt) { } } + @Override public void mouseReleased(MouseEvent awtEvt) { int y = component.getHeight() - awtEvt.getY(); MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), y); @@ -243,33 +257,38 @@ public void mouseReleased(MouseEvent awtEvt) { } } + @Override public void mouseEntered(MouseEvent awtEvt) { if (!visible) { recenterMouse(awtEvt.getComponent()); } } + @Override public void mouseExited(MouseEvent awtEvt) { if (!visible) { recenterMouse(awtEvt.getComponent()); } } + @Override public void mouseWheelMoved(MouseWheelEvent awtEvt) { int dwheel = awtEvt.getUnitsToScroll(); wheelPos += dwheel * WHEEL_AMP; cursorMoved = true; } + @Override public void mouseDragged(MouseEvent awtEvt) { mouseMoved(awtEvt); } + @Override public void mouseMoved(MouseEvent awtEvt) { if (isRecentering) { // MHenze (cylab) Fix Issue 35: // As long as the MouseInput is in recentering mode, nothing is done until the mouse is entered in the component - // by the events generated by the robot. If this happens, the last known location is resetted. + // by the events generated by the robot. If this happens, the last known location is reset. if ((centerLocation.x == awtEvt.getX() && centerLocation.y == awtEvt.getY()) || eventsSinceRecenter++ == 5) { lastKnownLocation.x = awtEvt.getX(); lastKnownLocation.y = awtEvt.getY(); @@ -297,7 +316,11 @@ private void recenterMouse(final Component component) { if (robot != null) { eventsSinceRecenter = 0; isRecentering = true; - centerLocation.setLocation(component.getWidth() / 2, component.getHeight() / 2); + if (grabLocation.x == 0 && grabLocation.y == 0) { + centerLocation.setLocation(component.getWidth() / 2, component.getHeight() / 2); + } else { + centerLocation.setLocation(grabLocation.x, grabLocation.y); + } centerLocationOnScreen.setLocation(centerLocation); SwingUtilities.convertPointToScreen(centerLocationOnScreen, component); robot.mouseMove(centerLocationOnScreen.x, centerLocationOnScreen.y); @@ -321,6 +344,7 @@ private int getJMEButtonIndex(MouseEvent awtEvt) { return index; } + @Override public void setNativeCursor(JmeCursor cursor) { } } diff --git a/jme3-desktop/src/main/java/com/jme3/input/awt/package-info.java b/jme3-desktop/src/main/java/com/jme3/input/awt/package-info.java new file mode 100644 index 0000000000..1ff2f553f7 --- /dev/null +++ b/jme3-desktop/src/main/java/com/jme3/input/awt/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * user-input classes specific to the Abstract Window Toolkit (AWT) + */ +package com.jme3.input.awt; diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java index 99e7456754..bfb0e85e31 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTComponentRenderer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -216,7 +216,7 @@ public void init(Renderer renderer, boolean main) { /** * Get the graphics context of the given component. - * @param destination the AWT component used for rendering. + * @param destination the AWT component used for rendering (not null) * @return the graphics context of the given component. */ protected Graphics getGraphics(Component destination) { @@ -230,7 +230,7 @@ protected Graphics getGraphics(Component destination) { //throw new IllegalArgumentException("AWT component "+destination.getClass().getSimpleName()+" does not provide 2D graphics capabilities."); } } else { - throw new NullPointerException("Component cannot be null."); + throw new IllegalArgumentException("destination component cannot be null"); } } @@ -306,7 +306,7 @@ public void run() { } /** - * Write the current rendered frame to the component graphics contex. + * Write the current rendered frame to the component graphics context. */ protected void writeFrame() { 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..d1911e3482 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 @@ -31,18 +31,12 @@ */ package com.jme3.system; - import com.jme3.input.AWTKeyInput; import com.jme3.input.AWTMouseInput; import com.jme3.input.JoyInput; 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. @@ -54,181 +48,240 @@ */ public class AWTContext implements JmeContext { - /** - * The settings. - */ - protected final AppSettings settings; - - /** - * The key input. - */ - protected final AWTKeyInput keyInput; - - /** - * The mouse input. - */ - protected final AWTMouseInput mouseInput; - - /** - * The current width. - */ - private volatile int width; - - /** - * The current height. - */ - private volatile int height; - - /** - * The background context. - */ - protected JmeContext backgroundContext; - - public AWTContext() { - this.keyInput = new AWTKeyInput(this); - this.mouseInput = new AWTMouseInput(this); - this.settings = createSettings(); - this.backgroundContext = createBackgroundContext(); - this.height = 1; - this.width = 1; - } - - /** - * @return the current height. - */ - public int getHeight() { - return height; - } - - /** - * @param height the current height. - */ - public void setHeight(final int height) { - this.height = height; - } - - /** - * @return the current width. - */ - public int getWidth() { - return width; - } - - /** - * @param width the current width. - */ - public void setWidth(final int width) { - this.width = width; - } - - /** - * @return new settings. - */ - protected AppSettings createSettings() { - final AppSettings settings = new AppSettings(true); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - return settings; - } - - /** - * @return new context/ - */ - protected JmeContext createBackgroundContext() { - return JmeSystem.newContext(settings, Type.OffscreenSurface); - } - - @Override - public Type getType() { - return Type.OffscreenSurface; - } - - @Override - public void setSettings(AppSettings settings) { - this.settings.copyFrom(settings); - this.settings.setRenderer(AppSettings.LWJGL_OPENGL32); - this.backgroundContext.setSettings(settings); - } - - @Override - public void setSystemListener(final SystemListener listener) { - backgroundContext.setSystemListener(listener); - } - - @Override - public AppSettings getSettings() { - return settings; - } - - @Override - public Renderer getRenderer() { - return backgroundContext.getRenderer(); - } - - @Override - public Context getOpenCLContext() { - return null; - } - - @Override - public AWTMouseInput getMouseInput() { - return mouseInput; - } - - @Override - public AWTKeyInput getKeyInput() { - return keyInput; - } - - @Override - public JoyInput getJoyInput() { - return null; - } - - @Override - public TouchInput getTouchInput() { - return null; - } - - @Override - public Timer getTimer() { - return backgroundContext.getTimer(); - } - - @Override - public void setTitle(final String title) { - } - - @Override - public boolean isCreated() { - return backgroundContext != null && backgroundContext.isCreated(); - } - - @Override - public boolean isRenderable() { - return backgroundContext != null && backgroundContext.isRenderable(); - } - - @Override - public void setAutoFlushFrames(final boolean enabled) { - // TODO Auto-generated method stub - } - - @Override - public void create(final boolean waitFor) { + /** + * The settings. + */ + protected final AppSettings settings; + + /** + * The key input. + */ + protected final AWTKeyInput keyInput; + + /** + * The mouse input. + */ + protected final AWTMouseInput mouseInput; + + /** + * The current width. + */ + private volatile int width; + + /** + * The current height. + */ + private volatile int height; + + /** + * The background context. + */ + protected JmeContext backgroundContext; + + public AWTContext() { + this.keyInput = new AWTKeyInput(this); + this.mouseInput = new AWTMouseInput(this); + this.settings = createSettings(); + this.backgroundContext = createBackgroundContext(); + this.height = 1; + this.width = 1; + } + + /** + * @return the current height. + */ + public int getHeight() { + return height; + } + + /** + * @param height the current height. + */ + public void setHeight(final int height) { + this.height = height; + } + + /** + * @return the current width. + */ + public int getWidth() { + return width; + } + + /** + * @param width the current width. + */ + public void setWidth(final int width) { + this.width = width; + } + + /** + * @return new settings. + */ + protected AppSettings createSettings() { + final AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + return settings; + } + + /** + * @return new context/ + */ + protected JmeContext createBackgroundContext() { + return JmeSystem.newContext(settings, Type.OffscreenSurface); + } + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public void setSettings(AppSettings settings) { + this.settings.copyFrom(settings); + this.settings.setRenderer(AppSettings.LWJGL_OPENGL32); + 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); + } + + @Override + public AppSettings getSettings() { + return settings; + } + + @Override + public Renderer getRenderer() { + return backgroundContext.getRenderer(); + } + + @Override + public Context getOpenCLContext() { + return null; + } + + @Override + public AWTMouseInput getMouseInput() { + return mouseInput; + } + + @Override + public AWTKeyInput getKeyInput() { + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public Timer getTimer() { + return backgroundContext.getTimer(); + } + + @Override + public void setTitle(final String title) {} + + @Override + public boolean isCreated() { + return backgroundContext != null && backgroundContext.isCreated(); + } + + @Override + public boolean isRenderable() { + return backgroundContext != null && backgroundContext.isRenderable(); + } + + @Override + public void setAutoFlushFrames(final boolean enabled) { + // TODO Auto-generated method stub + } + + @Override + public void create(final boolean waitFor) { String render = System.getProperty("awt.background.render", AppSettings.LWJGL_OPENGL33); backgroundContext.getSettings().setRenderer(render); backgroundContext.create(waitFor); - } - - @Override - public void restart() { - } - - @Override - public void destroy(final boolean waitFor) { - if (backgroundContext == null) throw new IllegalStateException("Not created"); - // destroy wrapped context - backgroundContext.destroy(waitFor); -} - + } + + @Override + public void restart() {} + + @Override + public void destroy(final boolean waitFor) { + if (backgroundContext == null) throw new IllegalStateException("Not created"); + // destroy wrapped context + 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"); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java index bb254e450a..111e856973 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java +++ b/jme3-desktop/src/main/java/com/jme3/system/AWTFrameProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,596 +62,595 @@ */ public class AWTFrameProcessor implements SceneProcessor, PropertyChangeListener { - public enum TransferMode { - ALWAYS, - ON_CHANGES - } - - private Application application = null; - - /** - * The width listener. - */ - protected PropertyChangeListener widthListener; - - /** - * The height listener. - */ - protected PropertyChangeListener heightListener; - - /** - * The ration listener. - */ - protected PropertyChangeListener rationListener; - - /** - * The flag to decide when we should resize. - */ - private final AtomicInteger reshapeNeeded; - - /** - * The render manager. - */ - private RenderManager renderManager; - - /** - * The source view port. - */ - private ViewPort viewPort; - - /** - * The frame transfer. - */ - private AWTComponentRenderer frameTransfer; - - /** - * The transfer mode. - */ - private TransferMode transferMode; - - /** - * The destination of jMe frames. - */ - protected volatile Component destination; - - /** - * The flag is true if this processor is main. - */ - private volatile boolean main; - - private int askWidth; - private int askHeight; - - private boolean askFixAspect; - private boolean enabled; - - @Override - public void initialize(RenderManager rm, ViewPort vp) { - this.renderManager = rm; - } - - @Override - public void reshape(ViewPort vp, int w, int h) { - // TODO Auto-generated method stub - } - - @Override - public boolean isInitialized() { - return frameTransfer != null; - } - - @Override - public void preFrame(float tpf) { - // TODO Auto-generated method stub - - } - - @Override - public void postQueue(RenderQueue rq) { - // TODO Auto-generated method stub - - } - - @Override - public void postFrame(FrameBuffer out) { - if (!isEnabled()) { - return; - } - - final AWTComponentRenderer frameTransfer = getFrameTransfer(); - if (frameTransfer != null) { - frameTransfer.copyFrameBufferToImage(getRenderManager()); - } - - // for the next frame - if (hasDestination() && reshapeNeeded.get() > 0 && reshapeNeeded.decrementAndGet() >= 0) { - - if (frameTransfer != null) { - frameTransfer.dispose(); - } - - setFrameTransfer(reshapeInThread(askWidth, askHeight, askFixAspect)); - } - } - - @Override - public void cleanup() { - final AWTComponentRenderer frameTransfer = getFrameTransfer(); - - if (frameTransfer != null) { - frameTransfer.dispose(); - setFrameTransfer(null); - } - } - - @Override - public void setProfiler(AppProfiler profiler) { - // TODO Auto-generated method stub - - } - - @Override - public void propertyChange(PropertyChangeEvent evt) { - System.out.println("Property changed: "+evt.getPropertyName()+" "+evt.getOldValue()+" -> "+evt.getNewValue()); - } - - public AWTFrameProcessor() { - transferMode = TransferMode.ALWAYS; - askWidth = 1; - askHeight = 1; - main = true; - reshapeNeeded = new AtomicInteger(2); - } - - /** - * Notify about that the ratio was changed. - * - * @param newValue the new value of the ratio. - */ - protected void notifyChangedRatio(Boolean newValue) { - notifyComponentResized(destination.getWidth(), destination.getHeight(), newValue); - } - - /** - * Notify about that the height was changed. - * - * @param newValue the new value of the height. - */ - protected void notifyChangedHeight(Number newValue) { - notifyComponentResized(destination.getWidth(), newValue.intValue(), isPreserveRatio()); - } - - /** - * Notify about that the width was changed. - * - * @param newValue the new value of the width. - */ - protected void notifyChangedWidth(Number newValue) { - notifyComponentResized(newValue.intValue(), destination.getHeight(), isPreserveRatio()); - } - - /** - * Gets the application. - * - * @return the application. - */ - protected Application getApplication() { - return application; - } - - /** - * Sets the application. - * - * @param application the application. - */ - protected void setApplication(Application application) { - this.application = application; - } - - /** - * Gets the current destination. - * - * @return the current destination. - */ - protected Component getDestination() { - return destination; - } - - /** - * Sets the destination. - * - * @param destination the destination. - */ - protected void setDestination(Component destination) { - this.destination = destination; - } - - /** - * Checks of existing destination. - * @return true if destination is exists. - */ - protected boolean hasDestination() { - return destination != null; - } - - /** - * Checks of existing application. - * @return true if destination is exists. - */ - protected boolean hasApplication() { - return application != null; - } - - - /** - * Gets the frame transfer. - * @return the file transfer. - */ - protected AWTComponentRenderer getFrameTransfer() { - return frameTransfer; - } - - /** - * Sets the frame transfer. - * - * @param frameTransfer the file transfer. - */ - protected void setFrameTransfer(AWTComponentRenderer frameTransfer) { - this.frameTransfer = frameTransfer; - } - - /** - * Gets the view port. - * - * @return the view port. - */ - protected ViewPort getViewPort() { - return viewPort; - } - - /** - * Gets the render manager. - * - * @return the render manager. - */ - protected RenderManager getRenderManager() { - return renderManager; - } - - public boolean isMain() { - return main; - } - - public boolean isEnabled() { - return enabled; - } - - public void setEnabled(final boolean enabled) { - this.enabled = enabled; - } - - /** - * Handle resizing. - * - * @param newWidth the new width. - * @param newHeight the new height. - * @param fixAspect true if need to fix aspect. - */ - protected void notifyComponentResized(int newWidth, int newHeight, boolean fixAspect) { - - newWidth = Math.max(newWidth, 1); - newHeight = Math.max(newHeight, 1); - - if (askWidth == newWidth && askWidth == newHeight && askFixAspect == fixAspect) { - return; - } - - askWidth = newWidth; - askHeight = newHeight; - askFixAspect = fixAspect; - reshapeNeeded.set(2); - } - - public void reshape() { - reshapeNeeded.set(2); - } - - /** - * Is preserve ratio. - * - * @return is preserve ratio. - */ - protected boolean isPreserveRatio() { - return false; - } - - /** - * Gets destination width. - * - * @return the destination width. - */ - protected int getDestinationWidth() { - return getDestination().getWidth(); - } - - /** - * Gets destination height. - * - * @return the destination height. - */ - protected int getDestinationHeight() { - return getDestination().getHeight(); - } - - /** - * Bind this processor. - * - * @param destination the destination. - * @param application the application. - */ - public void bind(Component destination, Application application) { - final RenderManager renderManager = application.getRenderManager(); - - if (renderManager == null) { - throw new RuntimeException("No render manager available from the application."); - } - - List postViews = renderManager.getPostViews(); - if (postViews.isEmpty()) { - throw new RuntimeException("the list of a post view is empty."); - } - - bind(destination, application, postViews.get(postViews.size() - 1), true); - } - - /** - * Bind this processor. - * - * @param destination the destination. - * @param application the application. - * @param viewPort the view port. - */ - public void bind(Component destination, Application application, ViewPort viewPort) { - bind(destination, application, viewPort, true); - } - - /** - * Bind this processor. - * - * @param destination the destination. - * @param application the application. - * @param viewPort the view port. - * @param main true if this processor is main. - */ - public void bind(final Component destination, final Application application, ViewPort viewPort, boolean main) { - - if (hasApplication()) { - throw new RuntimeException("This process is already bonded."); - } - - setApplication(application); - setEnabled(true); - - this.main = main; - this.viewPort = viewPort; - this.viewPort.addProcessor(this); - - if (EventQueue.isDispatchThread()) { - bindDestination(application, destination); - } else { - EventQueue.invokeLater(new Runnable() { - - @Override - public void run() { - bindDestination(application, destination); - }}); - } - } - - /** - * Unbind this processor from its current destination. - */ - public void unbind() { - - if (viewPort != null) { - viewPort.removeProcessor(this); - viewPort = null; - } - - if (EventQueue.isDispatchThread()) { - unbindDestination(); - } else { - EventQueue.invokeLater(new Runnable() { - - @Override - public void run() { - unbindDestination(); - }}); - } - - } - - /** - * Bind this processor. - * - * @param application the application. - * @param destination the destination. - */ - protected void bindDestination(Application application, Component destination) { - - if (!EventQueue.isDispatchThread()) { - throw new RuntimeException("bind has to be done from the Event Dispatching thread."); - } - - if (isMain()) { - - if (application.getContext() != null) { - if (application.getContext() instanceof AWTContext) { - AWTContext context = (AWTContext) application.getContext(); - AWTMouseInput mouseInput = context.getMouseInput(); - mouseInput.bind(destination); - AWTKeyInput keyInput = context.getKeyInput(); - keyInput.bind(destination); - - setDestination(destination); - bindListeners(); - - notifyComponentResized(getDestinationWidth(), getDestinationHeight(), isPreserveRatio()); - - } else { - throw new IllegalArgumentException("Underlying application has to use AWTContext (actually using "+application.getContext().getClass().getSimpleName()+")"); - } - } else { - throw new IllegalArgumentException("Underlying application has to use a valid AWTContext (context is null)"); - } - } - } - - /** - * Unbind this processor from destination. - */ - protected void unbindDestination() { - - if (!EventQueue.isDispatchThread()) { - throw new RuntimeException("unbind has to be done from the Event Dispatching thread."); - } - - if (hasApplication() && isMain()) { - final AWTContext context = (AWTContext) getApplication().getContext(); - final AWTMouseInput mouseInput = context.getMouseInput(); - mouseInput.unbind(); - final AWTKeyInput keyInput = context.getKeyInput(); - keyInput.unbind(); - } - - setApplication(null); - - if (hasDestination()) { - unbindListeners(); - setDestination(null); - } - } - - - protected void bindListeners() { - Component destination = getDestination(); - destination.addPropertyChangeListener(this); - destination.addPropertyChangeListener(this); - } - - - protected void unbindListeners() { - Component destination = getDestination(); - destination.removePropertyChangeListener(this); - destination.removePropertyChangeListener(this); - } - - /** - * Reshape the current frame transfer for the new size. - * - * @param width the width. - * @param height the height. - * @param fixAspect true if need to fix aspect ration. - * @return the new frame transfer. - */ - protected AWTComponentRenderer reshapeInThread(final int width, final int height, final boolean fixAspect) { - - reshapeCurrentViewPort(width, height); - - ViewPort viewPort = getViewPort(); - RenderManager renderManager = getRenderManager(); - FrameBuffer frameBuffer = viewPort.getOutputFrameBuffer(); - - AWTComponentRenderer frameTransfer = createFrameTransfer(frameBuffer, width, height); - frameTransfer.init(renderManager.getRenderer(), isMain()); - - if (isMain()) { - AWTContext context = (AWTContext) getApplication().getContext(); - context.setHeight(height); - context.setWidth(width); - } - - return frameTransfer; - } - - /** - * Create a new frame transfer. - * - * @param frameBuffer the frame buffer. - * @param width the width. - * @param height the height. - * @return the new frame transfer. - */ - protected AWTComponentRenderer createFrameTransfer(FrameBuffer frameBuffer, int width, int height) { - return new AWTComponentRenderer(getDestination(), getTransferMode(), isMain() ? null : frameBuffer, width, height); - } - - /** - * Reshape the current view port. - * - * @param width the width. - * @param height the height. - */ - protected void reshapeCurrentViewPort(int width, int height) { - - ViewPort viewPort = getViewPort(); - Camera camera = viewPort.getCamera(); - int cameraAngle = getCameraAngle(); - float aspect = (float) camera.getWidth() / camera.getHeight(); - - if (isMain()) { - getRenderManager().notifyReshape(width, height); - camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); - return; - } - - camera.resize(width, height, true); - camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); - - final List processors = viewPort.getProcessors(); - - boolean found = false; - Iterator iter = processors.iterator(); - while(!found && iter.hasNext()) { - if (!(iter.next() instanceof AWTFrameProcessor)) { - found = true; - } - } - - if (found) { - - FrameBuffer frameBuffer = new FrameBuffer(width, height, 1); - frameBuffer.setDepthBuffer(Image.Format.Depth); - frameBuffer.setColorBuffer(Image.Format.RGBA8); - frameBuffer.setSrgb(true); - - viewPort.setOutputFrameBuffer(frameBuffer); - } - - for (final SceneProcessor sceneProcessor : processors) { - if (!sceneProcessor.isInitialized()) { - sceneProcessor.initialize(renderManager, viewPort); - } else { - sceneProcessor.reshape(viewPort, width, height); - } - } - } - - /** - * Gets camera angle. - * - * @return the camera angle. - */ - protected int getCameraAngle() { - final String angle = System.getProperty("awt.frame.transfer.camera.angle", "45"); - return Integer.parseInt(angle); - } - - public TransferMode getTransferMode() { - return transferMode; - } - - public void setTransferMode(TransferMode transferMode) { - this.transferMode = transferMode; - } + public enum TransferMode { + ALWAYS, + ON_CHANGES + } + + private Application application = null; + + /** + * The width listener. + */ + protected PropertyChangeListener widthListener; + + /** + * The height listener. + */ + protected PropertyChangeListener heightListener; + + /** + * The ration listener. + */ + protected PropertyChangeListener rationListener; + + /** + * The flag to decide when we should resize. + */ + private final AtomicInteger reshapeNeeded; + + /** + * The render manager. + */ + private RenderManager renderManager; + + /** + * The source view port. + */ + private ViewPort viewPort; + + /** + * The frame transfer. + */ + private AWTComponentRenderer frameTransfer; + + /** + * The transfer mode. + */ + private TransferMode transferMode; + + /** + * The destination of jMe frames. + */ + protected volatile Component destination; + + /** + * The flag is true if this processor is main. + */ + private volatile boolean main; + + private int askWidth; + private int askHeight; + + private boolean askFixAspect; + private boolean enabled; + + @Override + public void initialize(RenderManager rm, ViewPort vp) { + this.renderManager = rm; + } + + @Override + public void reshape(ViewPort vp, int w, int h) { + // TODO Auto-generated method stub + } + + @Override + public boolean isInitialized() { + return frameTransfer != null; + } + + @Override + public void preFrame(float tpf) { + // TODO Auto-generated method stub + + } + + @Override + public void postQueue(RenderQueue rq) { + // TODO Auto-generated method stub + + } + + @Override + public void postFrame(FrameBuffer out) { + if (!isEnabled()) { + return; + } + + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + if (frameTransfer != null) { + frameTransfer.copyFrameBufferToImage(getRenderManager()); + } + + // for the next frame + if (hasDestination() && reshapeNeeded.get() > 0 && reshapeNeeded.decrementAndGet() >= 0) { + + if (frameTransfer != null) { + frameTransfer.dispose(); + } + + setFrameTransfer(reshapeInThread(askWidth, askHeight, askFixAspect)); + } + } + + @Override + public void cleanup() { + final AWTComponentRenderer frameTransfer = getFrameTransfer(); + + if (frameTransfer != null) { + frameTransfer.dispose(); + setFrameTransfer(null); + } + } + + @Override + public void setProfiler(AppProfiler profiler) { + // not implemented + } + + @Override + public void propertyChange(PropertyChangeEvent evt) { + System.out.println("Property changed: "+evt.getPropertyName()+" "+evt.getOldValue()+" -> "+evt.getNewValue()); + } + + public AWTFrameProcessor() { + transferMode = TransferMode.ALWAYS; + askWidth = 1; + askHeight = 1; + main = true; + reshapeNeeded = new AtomicInteger(2); + } + + /** + * Notify about that the ratio was changed. + * + * @param newValue the new value of the ratio. + */ + protected void notifyChangedRatio(Boolean newValue) { + notifyComponentResized(destination.getWidth(), destination.getHeight(), newValue); + } + + /** + * Notify about that the height was changed. + * + * @param newValue the new value of the height. + */ + protected void notifyChangedHeight(Number newValue) { + notifyComponentResized(destination.getWidth(), newValue.intValue(), isPreserveRatio()); + } + + /** + * Notify about that the width was changed. + * + * @param newValue the new value of the width. + */ + protected void notifyChangedWidth(Number newValue) { + notifyComponentResized(newValue.intValue(), destination.getHeight(), isPreserveRatio()); + } + + /** + * Gets the application. + * + * @return the application. + */ + protected Application getApplication() { + return application; + } + + /** + * Sets the application. + * + * @param application the application. + */ + protected void setApplication(Application application) { + this.application = application; + } + + /** + * Gets the current destination. + * + * @return the current destination. + */ + protected Component getDestination() { + return destination; + } + + /** + * Sets the destination. + * + * @param destination the destination. + */ + protected void setDestination(Component destination) { + this.destination = destination; + } + + /** + * Checks of existing destination. + * @return true if destination is exists. + */ + protected boolean hasDestination() { + return destination != null; + } + + /** + * Checks of existing application. + * @return true if destination is exists. + */ + protected boolean hasApplication() { + return application != null; + } + + + /** + * Gets the frame transfer. + * @return the file transfer. + */ + protected AWTComponentRenderer getFrameTransfer() { + return frameTransfer; + } + + /** + * Sets the frame transfer. + * + * @param frameTransfer the file transfer. + */ + protected void setFrameTransfer(AWTComponentRenderer frameTransfer) { + this.frameTransfer = frameTransfer; + } + + /** + * Gets the view port. + * + * @return the view port. + */ + protected ViewPort getViewPort() { + return viewPort; + } + + /** + * Gets the render manager. + * + * @return the render manager. + */ + protected RenderManager getRenderManager() { + return renderManager; + } + + public boolean isMain() { + return main; + } + + public boolean isEnabled() { + return enabled; + } + + public void setEnabled(final boolean enabled) { + this.enabled = enabled; + } + + /** + * Handle resizing. + * + * @param newWidth the new width. + * @param newHeight the new height. + * @param fixAspect true to fix the aspect ratio. + */ + protected void notifyComponentResized(int newWidth, int newHeight, boolean fixAspect) { + + newWidth = Math.max(newWidth, 1); + newHeight = Math.max(newHeight, 1); + + if (askWidth == newWidth && askWidth == newHeight && askFixAspect == fixAspect) { + return; + } + + askWidth = newWidth; + askHeight = newHeight; + askFixAspect = fixAspect; + reshapeNeeded.set(2); + } + + public void reshape() { + reshapeNeeded.set(2); + } + + /** + * Is preserve ratio. + * + * @return is preserve ratio. + */ + protected boolean isPreserveRatio() { + return false; + } + + /** + * Gets destination width. + * + * @return the destination width. + */ + protected int getDestinationWidth() { + return getDestination().getWidth(); + } + + /** + * Gets destination height. + * + * @return the destination height. + */ + protected int getDestinationHeight() { + return getDestination().getHeight(); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + */ + public void bind(Component destination, Application application) { + final RenderManager renderManager = application.getRenderManager(); + + if (renderManager == null) { + throw new RuntimeException("No render manager available from the application."); + } + + List postViews = renderManager.getPostViews(); + if (postViews.isEmpty()) { + throw new RuntimeException("the list of a post view is empty."); + } + + bind(destination, application, postViews.get(postViews.size() - 1), true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param viewPort the view port. + */ + public void bind(Component destination, Application application, ViewPort viewPort) { + bind(destination, application, viewPort, true); + } + + /** + * Bind this processor. + * + * @param destination the destination. + * @param application the application. + * @param viewPort the view port. + * @param main true if this processor is main. + */ + public void bind(final Component destination, final Application application, ViewPort viewPort, boolean main) { + + if (hasApplication()) { + throw new RuntimeException("This process is already bonded."); + } + + setApplication(application); + setEnabled(true); + + this.main = main; + this.viewPort = viewPort; + this.viewPort.addProcessor(this); + + if (EventQueue.isDispatchThread()) { + bindDestination(application, destination); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + bindDestination(application, destination); + }}); + } + } + + /** + * Unbind this processor from its current destination. + */ + public void unbind() { + + if (viewPort != null) { + viewPort.removeProcessor(this); + viewPort = null; + } + + if (EventQueue.isDispatchThread()) { + unbindDestination(); + } else { + EventQueue.invokeLater(new Runnable() { + + @Override + public void run() { + unbindDestination(); + }}); + } + + } + + /** + * Bind this processor. + * + * @param application the application. + * @param destination the destination. + */ + protected void bindDestination(Application application, Component destination) { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("bind has to be done from the Event Dispatching thread."); + } + + if (isMain()) { + + if (application.getContext() != null) { + if (application.getContext() instanceof AWTContext) { + AWTContext context = (AWTContext) application.getContext(); + AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.bind(destination); + AWTKeyInput keyInput = context.getKeyInput(); + keyInput.bind(destination); + + setDestination(destination); + bindListeners(); + + notifyComponentResized(getDestinationWidth(), getDestinationHeight(), isPreserveRatio()); + + } else { + throw new IllegalArgumentException("Underlying application has to use AWTContext (actually using "+application.getContext().getClass().getSimpleName()+")"); + } + } else { + throw new IllegalArgumentException("Underlying application has to use a valid AWTContext (context is null)"); + } + } + } + + /** + * Unbind this processor from destination. + */ + protected void unbindDestination() { + + if (!EventQueue.isDispatchThread()) { + throw new RuntimeException("unbind has to be done from the Event Dispatching thread."); + } + + if (hasApplication() && isMain()) { + final AWTContext context = (AWTContext) getApplication().getContext(); + final AWTMouseInput mouseInput = context.getMouseInput(); + mouseInput.unbind(); + final AWTKeyInput keyInput = context.getKeyInput(); + keyInput.unbind(); + } + + setApplication(null); + + if (hasDestination()) { + unbindListeners(); + setDestination(null); + } + } + + + protected void bindListeners() { + Component destination = getDestination(); + destination.addPropertyChangeListener(this); + destination.addPropertyChangeListener(this); + } + + + protected void unbindListeners() { + Component destination = getDestination(); + destination.removePropertyChangeListener(this); + destination.removePropertyChangeListener(this); + } + + /** + * Reshape the current frame transfer for the new size. + * + * @param width the width. + * @param height the height. + * @param fixAspect true to fix the aspect ratio. + * @return the new frame transfer. + */ + protected AWTComponentRenderer reshapeInThread(final int width, final int height, final boolean fixAspect) { + + reshapeCurrentViewPort(width, height); + + ViewPort viewPort = getViewPort(); + RenderManager renderManager = getRenderManager(); + FrameBuffer frameBuffer = viewPort.getOutputFrameBuffer(); + + AWTComponentRenderer frameTransfer = createFrameTransfer(frameBuffer, width, height); + frameTransfer.init(renderManager.getRenderer(), isMain()); + + if (isMain()) { + AWTContext context = (AWTContext) getApplication().getContext(); + context.setHeight(height); + context.setWidth(width); + } + + return frameTransfer; + } + + /** + * Create a new frame transfer. + * + * @param frameBuffer the frame buffer. + * @param width the width. + * @param height the height. + * @return the new frame transfer. + */ + protected AWTComponentRenderer createFrameTransfer(FrameBuffer frameBuffer, int width, int height) { + return new AWTComponentRenderer(getDestination(), getTransferMode(), isMain() ? null : frameBuffer, width, height); + } + + /** + * Reshape the current view port. + * + * @param width the width. + * @param height the height. + */ + protected void reshapeCurrentViewPort(int width, int height) { + + ViewPort viewPort = getViewPort(); + Camera camera = viewPort.getCamera(); + int cameraAngle = getCameraAngle(); + float aspect = (float) camera.getWidth() / camera.getHeight(); + + if (isMain()) { + getRenderManager().notifyReshape(width, height); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + return; + } + + camera.resize(width, height, true); + camera.setFrustumPerspective(cameraAngle, aspect, 1f, 10000); + + final List processors = viewPort.getProcessors(); + + boolean found = false; + Iterator iter = processors.iterator(); + while(!found && iter.hasNext()) { + if (!(iter.next() instanceof AWTFrameProcessor)) { + found = true; + } + } + + if (found) { + + FrameBuffer frameBuffer = new FrameBuffer(width, height, 1); + frameBuffer.setDepthBuffer(Image.Format.Depth); + frameBuffer.setColorBuffer(Image.Format.RGBA8); + frameBuffer.setSrgb(true); + + viewPort.setOutputFrameBuffer(frameBuffer); + } + + for (final SceneProcessor sceneProcessor : processors) { + if (!sceneProcessor.isInitialized()) { + sceneProcessor.initialize(renderManager, viewPort); + } else { + sceneProcessor.reshape(viewPort, width, height); + } + } + } + + /** + * Gets camera angle. + * + * @return the camera angle. + */ + protected int getCameraAngle() { + final String angle = System.getProperty("awt.frame.transfer.camera.angle", "45"); + return Integer.parseInt(angle); + } + + public TransferMode getTransferMode() { + return transferMode; + } + + public void setTransferMode(TransferMode transferMode) { + this.transferMode = transferMode; + } } 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 82cba27a5f..0000000000 --- a/jme3-desktop/src/main/java/com/jme3/system/ErrorDialog.java +++ /dev/null @@ -1,65 +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 diplaying 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 - * @param title - */ - 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 proved message. - * @param message - */ - 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 d02b5d21cd..81c197a3c5 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,38 +31,35 @@ */ 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 com.jme3.util.res.Resources; + +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; import java.io.IOException; import java.io.OutputStream; +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; /** * @@ -70,16 +67,19 @@ */ public class JmeDesktopSystem extends JmeSystemDelegate { + public JmeDesktopSystem() { + } + @Override public URL getPlatformAssetConfigURL() { - return Thread.currentThread().getContextClassLoader().getResource("com/jme3/asset/Desktop.cfg"); + return Resources.getResource("com/jme3/asset/Desktop.cfg"); } 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); @@ -87,22 +87,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); @@ -115,100 +127,28 @@ 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() { - 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() { - - public void onSelection(int selection) { - synchronized (lock) { - done.set(true); - result.set(selection); - lock.notifyAll(); - } - } - }; - SwingUtilities.invokeLater(new Runnable() { - - 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 { - Class ctxClazz = null; + Class ctxClazz = null; switch (type) { case Canvas: - ctxClazz = (Class) Class.forName("com.jme3.system.lwjgl.LwjglCanvas"); + ctxClazz = Class.forName("com.jme3.system.lwjgl.LwjglCanvas"); break; case Display: - ctxClazz = (Class) Class.forName("com.jme3.system.lwjgl.LwjglDisplay"); + ctxClazz = Class.forName("com.jme3.system.lwjgl.LwjglDisplay"); break; case OffscreenSurface: - ctxClazz = (Class) Class.forName("com.jme3.system.lwjgl.LwjglOffscreenBuffer"); + ctxClazz = Class.forName("com.jme3.system.lwjgl.LwjglOffscreenBuffer"); break; default: throw new IllegalArgumentException("Unsupported context type " + type); } - return ctxClazz.newInstance(); - } catch (InstantiationException ex) { - logger.log(Level.SEVERE, "Failed to create context", ex); - } catch (IllegalAccessException ex) { + return (JmeContext) ctxClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } catch (ClassNotFoundException ex) { logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n" @@ -218,46 +158,47 @@ private JmeContext newContextLwjgl(AppSettings settings, JmeContext.Type type) { return null; } + @SuppressWarnings("unchecked") private JmeContext newContextJogl(AppSettings settings, JmeContext.Type type) { try { - Class ctxClazz = null; + Class ctxClazz = null; switch (type) { case Display: - ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglNewtDisplay"); + ctxClazz = Class.forName("com.jme3.system.jogl.JoglNewtDisplay"); break; case Canvas: - ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglNewtCanvas"); + ctxClazz = Class.forName("com.jme3.system.jogl.JoglNewtCanvas"); break; case OffscreenSurface: - ctxClazz = (Class) Class.forName("com.jme3.system.jogl.JoglOffscreenBuffer"); + ctxClazz = Class.forName("com.jme3.system.jogl.JoglOffscreenBuffer"); break; default: throw new IllegalArgumentException("Unsupported context type " + type); } - return ctxClazz.newInstance(); - } catch (InstantiationException ex) { - logger.log(Level.SEVERE, "Failed to create context", ex); - } catch (IllegalAccessException ex) { + return (JmeContext) ctxClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } catch (ClassNotFoundException ex) { logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!\n" - + "Make sure jme3_jogl is on the classpath.", ex); + + "Make sure jme3-jogl is on the classpath.", ex); } return null; } + @SuppressWarnings("unchecked") private JmeContext newContextCustom(AppSettings settings, JmeContext.Type type) { try { String className = settings.getRenderer().substring("CUSTOM".length()); - Class ctxClazz = null; - ctxClazz = (Class) Class.forName(className); - return ctxClazz.newInstance(); - } catch (InstantiationException ex) { - logger.log(Level.SEVERE, "Failed to create context", ex); - } catch (IllegalAccessException ex) { + Class ctxClazz = Class.forName(className); + return (JmeContext) ctxClazz.getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } catch (ClassNotFoundException ex) { logger.log(Level.SEVERE, "CRITICAL ERROR: Context class is missing!", ex); @@ -292,16 +233,17 @@ public JmeContext newContext(AppSettings settings, Type contextType) { return ctx; } + @SuppressWarnings("unchecked") private T newObject(String className) { try { Class clazz = (Class) Class.forName(className); - return clazz.newInstance(); + return clazz.getDeclaredConstructor().newInstance(); } catch (ClassNotFoundException ex) { - logger.log(Level.SEVERE, "CRITICAL ERROR: Audio implementation class is missing!\n" - + "Make sure jme3_lwjgl-oal or jm3_joal is on the classpath.", ex); - } catch (IllegalAccessException ex) { - logger.log(Level.SEVERE, "Failed to create context", ex); - } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "CRITICAL ERROR: Audio implementation class " + + className + " is missing!\n", ex); + } catch (InstantiationException | IllegalAccessException + | IllegalArgumentException | InvocationTargetException + | NoSuchMethodException | SecurityException ex) { logger.log(Level.SEVERE, "Failed to create context", ex); } @@ -345,7 +287,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 081cc68587..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 can be different than {@link #getPathInNativesJar() path in natives jar}, + * + * 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 same name as the filename in + * 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 1cba8bcf28..c0eb8613e6 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,12 +35,17 @@ 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; import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.util.res.Resources; + /** * Utility class to register, extract, and load native libraries. *
                @@ -50,19 +55,19 @@ * using {@link #loadNativeLibrary(java.lang.String, boolean) }. *
                * Example:
                - *

                + * 
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Windows32, "native/windows/mystuff.dll");
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Windows64, "native/windows/mystuff64.dll");
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Linux32,   "native/linux/libmystuff.so");
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.Linux64,   "native/linux/libmystuff64.so");
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.MacOSX32,  "native/macosx/libmystuff.jnilib");
                  * NativeLibraryLoader.registerNativeLibrary("mystuff", Platform.MacOSX64,  "native/macosx/libmystuff.jnilib");
                - * 
                + *
                *
                * This will register the library. Load it via:
                - *
                + * 
                  * NativeLibraryLoader.loadNativeLibrary("mystuff", true);
                - * 
                + *
                * It will load the right library automatically based on the platform. * * @author Kirill Vainer @@ -70,15 +75,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 +116,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,94 +136,15 @@ 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.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"); - - // 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() { } /** - * Determine if native bullet is on the classpath. + * Determines whether native Bullet is on the classpath. * - * Currently the context extracts the native bullet libraries, so - * this method is needed to determine if it is needed. - * Ideally, native bullet should be responsible for its own natives. + * Currently, the context extracts the native Bullet libraries, so + * this method is needed to determine if they are needed. + * Ideally, native Bullet would be responsible for its own natives. * * @return True native bullet is on the classpath, false otherwise. */ @@ -239,13 +177,14 @@ 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> * is computed automatically as the XOR of the classpath hash code * and the last modified date of this class. + *
                * * @return Path where natives will be extracted to. */ @@ -254,15 +193,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(); } @@ -291,19 +235,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; } @@ -336,10 +275,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 = Resources.getResource("com/jme3/system/NativeLibraryLoader.class"); + try { StringBuilder sb = new StringBuilder(url.toString()); if (sb.indexOf("jar:") == 0) { sb.delete(0, 4); @@ -356,7 +295,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 { @@ -368,7 +308,7 @@ private static int computeNativesHash() { } public static File[] getJarsWithNatives() { - HashSet jarFiles = new HashSet(); + HashSet jarFiles = new HashSet<>(); for (Map.Entry lib : nativeLibraryMap.entrySet()) { File jarFile = getJarForNativeLibrary(lib.getValue().getPlatform(), lib.getValue().getName()); if (jarFile != null) { @@ -389,19 +329,6 @@ public static void extractNativeLibraries(Platform platform, File targetDir) thr } } - private static String mapLibraryName_emulated(String name, Platform platform) { - switch (platform) { - case MacOSX32: - case MacOSX64: - return name + ".dylib"; - case Windows32: - case Windows64: - return name + ".dll"; - default: - return name + ".so"; - } - } - /** * Removes platform-specific portions of a library file name so * that it can be accepted by {@link System#loadLibrary(java.lang.String) }. @@ -446,9 +373,9 @@ public static File getJarForNativeLibrary(Platform platform, String name) { fileNameInJar = pathInJar; } - URL url = Thread.currentThread().getContextClassLoader().getResource(pathInJar); + URL url = Resources.getResource(pathInJar); if (url == null) { - url = Thread.currentThread().getContextClassLoader().getResource(fileNameInJar); + url = Resources.getResource(fileNameInJar); } if (url == null) { @@ -476,18 +403,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); - } - + URL url = Resources.getResource(pathInJar); if (url == null) { return; } @@ -496,33 +412,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()); } } @@ -549,9 +448,11 @@ public static void loadNativeLibrary(String name, boolean isRequired) { "The required native library '" + name + "'" + " is not available for your OS: " + platform); } else { - logger.log(Level.FINE, "The optional native library ''{0}''" + - " is not available for your OS: {1}", - new Object[]{name, platform}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "The optional native library ''{0}''" + + " is not available for your OS: {1}", + new Object[]{name, platform}); + } return; } } @@ -562,48 +463,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 = Resources.getResource(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 { - 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; } @@ -614,87 +486,81 @@ 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 extactionDirectory = getExtractionFolder(); + File extractionDirectory = getExtractionFolder(); URLConnection conn; - InputStream in; - + try { conn = url.openConnection(); - in = conn.getInputStream(); } catch (IOException ex) { - // Maybe put more detail here? Not sure.. + // Maybe put more detail here? Not sure. throw new UncheckedIOException("Failed to open file: '" + url + "'. Error: " + ex, ex); } - File targetFile = new File(extactionDirectory, 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(); - - // 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; + File targetFile = new File(extractionDirectory, loadedAsFileName); + 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()); + + 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); + library.getLoadFunction().accept(targetFile.getAbsolutePath()); + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Loaded native library {0}.", library.getName()); } - - in.close(); - in = null; - out.close(); - out = null; - - // 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()); } 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", - extactionDirectory.getAbsolutePath()); - } else if (name.equals("jinput")) { - System.setProperty("net.java.games.input.librarypath", - extactionDirectory.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); } - - 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/AwtPanel.java b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java index f77eb73363..a234775bdb 100644 --- a/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java +++ b/jme3-desktop/src/main/java/com/jme3/system/awt/AwtPanel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image.Format; import com.jme3.util.BufferUtils; import com.jme3.util.Screenshots; import java.awt.*; @@ -57,6 +56,8 @@ public class AwtPanel extends Canvas implements SceneProcessor { + private static final Logger logger = Logger.getLogger(AwtPanel.class.getName()); + private boolean attachAsMain = false; private BufferedImage img; @@ -66,21 +67,20 @@ public class AwtPanel extends Canvas implements SceneProcessor { private IntBuffer intBuf; private RenderManager rm; private PaintMode paintMode; - private ArrayList viewPorts = new ArrayList(); + private final java.util.List viewPorts = new ArrayList<>(); // Visibility/drawing vars private BufferStrategy strategy; private AffineTransformOp transformOp; - private AtomicBoolean hasNativePeer = new AtomicBoolean(false); - private AtomicBoolean showing = new AtomicBoolean(false); - private AtomicBoolean repaintRequest = new AtomicBoolean(false); + private final AtomicBoolean hasNativePeer = new AtomicBoolean(false); + private final AtomicBoolean showing = new AtomicBoolean(false); + private final AtomicBoolean repaintRequest = new AtomicBoolean(false); // Reshape vars private int newWidth = 1; private int newHeight = 1; - private AtomicBoolean reshapeNeeded = new AtomicBoolean(false); + private final AtomicBoolean reshapeNeeded = new AtomicBoolean(false); private final Object lock = new Object(); - private AppProfiler prof; public AwtPanel(PaintMode paintMode) { this(paintMode, false); @@ -181,7 +181,7 @@ public void drawFrameInThread() { BufferCapabilities.FlipContents.UNDEFINED) ); } catch (AWTException ex) { - ex.printStackTrace(); + logger.log(Level.WARNING, "Failed to create buffer strategy!", ex); } strategy = getBufferStrategy(); } @@ -191,7 +191,7 @@ public void drawFrameInThread() { do { Graphics2D g2d = (Graphics2D) strategy.getDrawGraphics(); if (g2d == null) { - Logger.getLogger(AwtPanel.class.getName()).log(Level.WARNING, "OGL: DrawGraphics was null."); + logger.log(Level.WARNING, "OGL: DrawGraphics was null."); return; } @@ -211,7 +211,7 @@ public boolean isActiveDrawing() { } public void attachTo(boolean overrideMainFramebuffer, ViewPort... vps) { - if (viewPorts.size() > 0) { + if (!viewPorts.isEmpty()) { for (ViewPort vp : viewPorts) { vp.setOutputFrameBuffer(null); } @@ -243,8 +243,8 @@ private void reshapeInThread(int width, int height) { } fb = new FrameBuffer(width, height, 1); - fb.setDepthBuffer(Format.Depth); - fb.setColorBuffer(Format.RGB8); + fb.setDepthTarget(FrameBuffer.FrameBufferTarget.newTarget(com.jme3.texture.Image.Format.Depth)); + fb.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(com.jme3.texture.Image.Format.RGB8)); fb.setSrgb(srgb); if (attachAsMain) { @@ -338,6 +338,6 @@ public void cleanup() { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } } 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 ee41ea569d..a25f25379e 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,13 +41,16 @@ 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; - protected ArrayList panels = new ArrayList(); + protected ArrayList panels = new ArrayList<>(); protected AwtPanel inputSource; protected AwtMouseInput mouseInput = new AwtMouseInput(); @@ -57,91 +60,124 @@ public class AwtPanelsContext implements JmeContext { private class AwtPanelsListener implements SystemListener { + @Override public void initialize() { initInThread(); } + @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) { + logger.severe("rescale is not supported."); } + @Override public void update() { updateInThread(); } + @Override public void requestClose(boolean esc) { // shouldn't happen throw new IllegalStateException(); } + @Override public void gainFocus() { // shouldn't happen throw new IllegalStateException(); } + @Override public void loseFocus() { // shouldn't happen throw new IllegalStateException(); } + @Override public void handleError(String errorMsg, Throwable t) { listener.handleError(errorMsg, t); } + @Override public void destroy() { destroyInThread(); } } - public void setInputSource(AwtPanel panel){ - if (!panels.contains(panel)) - throw new IllegalArgumentException(); + public void setInputSource(AwtPanel panel) { + if (!panels.contains(panel)) throw new IllegalArgumentException(); inputSource = panel; mouseInput.setInputSource(panel); keyInput.setInputSource(panel); } + @Override 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; } + @Override public AppSettings getSettings() { return settings; } + @Override public Renderer getRenderer() { return actualContext.getRenderer(); } + @Override public MouseInput getMouseInput() { return mouseInput; } + @Override public KeyInput getKeyInput() { return keyInput; } + @Override public JoyInput getJoyInput() { return null; } + @Override public TouchInput getTouchInput() { return null; } + @Override public Timer getTimer() { return actualContext.getTimer(); } + @Override public boolean isCreated() { return actualContext != null && actualContext.isCreated(); } + @Override public boolean isRenderable() { return actualContext != null && actualContext.isRenderable(); } @@ -150,42 +186,41 @@ public boolean isRenderable() { public Context getOpenCLContext() { return actualContext.getOpenCLContext(); } - - public AwtPanelsContext(){ - } - public AwtPanel createPanel(PaintMode paintMode){ + public AwtPanelsContext() {} + + public AwtPanel createPanel(PaintMode paintMode) { AwtPanel panel = new AwtPanel(paintMode); panels.add(panel); return panel; } - - public AwtPanel createPanel(PaintMode paintMode, boolean srgb){ + + public AwtPanel createPanel(PaintMode paintMode, boolean srgb) { AwtPanel panel = new AwtPanel(paintMode, srgb); panels.add(panel); return panel; } - private void initInThread(){ + private void initInThread() { listener.initialize(); } - private void updateInThread(){ + private void updateInThread() { // Check if throttle required boolean needThrottle = true; - for (AwtPanel panel : panels){ - if (panel.isActiveDrawing()){ + for (AwtPanel panel : panels) { + if (panel.isActiveDrawing()) { needThrottle = false; break; } } - if (lastThrottleState != needThrottle){ + if (lastThrottleState != needThrottle) { lastThrottleState = needThrottle; - if (lastThrottleState){ + if (lastThrottleState) { System.out.println("OGL: Throttling update loop."); - }else{ + } else { System.out.println("OGL: Ceased throttling update loop."); } } @@ -193,31 +228,32 @@ private void updateInThread(){ if (needThrottle) { try { Thread.sleep(100); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } listener.update(); - - for (AwtPanel panel : panels){ + + for (AwtPanel panel : panels) { panel.onFrameEnd(); } } - private void destroyInThread(){ + private void destroyInThread() { listener.destroy(); } + @Override public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); this.settings.setRenderer(AppSettings.LWJGL_OPENGL2); - if (actualContext != null){ + if (actualContext != null) { actualContext.setSettings(settings); } } + @Override public void create(boolean waitFor) { - if (actualContext != null){ + if (actualContext != null) { throw new IllegalStateException("Already created"); } @@ -226,24 +262,78 @@ public void create(boolean waitFor) { actualContext.create(waitFor); } + @Override public void destroy(boolean waitFor) { - if (actualContext == null) - throw new IllegalStateException("Not created"); + if (actualContext == null) throw new IllegalStateException("Not created"); // destroy parent context actualContext.destroy(waitFor); } + @Override public void setTitle(String title) { // not relevant, ignore } + @Override public void setAutoFlushFrames(boolean enabled) { // not relevant, ignore } + @Override 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(); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } } diff --git a/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java index 952b3a6fdf..926e7305af 100644 --- a/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java +++ b/jme3-desktop/src/main/java/com/jme3/texture/plugins/AWTLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ import java.awt.Transparency; import java.awt.color.ColorSpace; import java.awt.image.*; +import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; @@ -183,21 +184,17 @@ public Image load(InputStream in, boolean flipY) throws IOException{ return load(img, flipY); } + @Override public Object load(AssetInfo info) throws IOException { if (ImageIO.getImageReadersBySuffix(info.getKey().getExtension()) != null){ boolean flip = ((TextureKey) info.getKey()).isFlipY(); - InputStream in = null; - try { - in = info.openStream(); - Image img = load(in, flip); + try (InputStream in = info.openStream(); + BufferedInputStream bin = new BufferedInputStream(in)) { + Image img = load(bin, flip); if (img == null){ throw new AssetLoadException("The given image cannot be loaded " + info.getKey()); } return img; - } finally { - if (in != null){ - in.close(); - } } }else{ throw new AssetLoadException("The extension " + info.getKey().getExtension() + " is not supported"); diff --git a/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java b/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java index 99e7e5e10f..dff06416e1 100644 --- a/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java +++ b/jme3-desktop/src/main/java/com/jme3/util/Screenshots.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,12 @@ public final class Screenshots { + /** + * A private constructor to inhibit instantiation of this class. + */ + private Screenshots() { + } + public static void convertScreenShot2(IntBuffer bgraBuf, BufferedImage out){ WritableRaster wr = out.getRaster(); DataBufferInt db = (DataBufferInt) wr.getDataBuffer(); @@ -67,8 +73,8 @@ public static void convertScreenShot2(IntBuffer bgraBuf, BufferedImage out){ /** * Flips the image along the Y axis and converts BGRA to ABGR * - * @param bgraBuf - * @param out + * @param bgraBuf (not null, modified) + * @param out (not null) */ public static void convertScreenShot(ByteBuffer bgraBuf, BufferedImage out){ WritableRaster wr = out.getRaster(); @@ -86,7 +92,7 @@ public static void convertScreenShot(ByteBuffer bgraBuf, BufferedImage out){ // flip the components the way AWT likes them - // calcuate half of height such that all rows of the array are written to + // calculate half of height such that all rows of the array are written to // e.g. for odd heights, write 1 more scanline int heightdiv2ceil = height % 2 == 1 ? (height / 2) + 1 : height / 2; for (int y = 0; y < heightdiv2ceil; y++){ diff --git a/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java index 6c1c35ed29..82f03a410d 100644 --- a/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java +++ b/jme3-desktop/src/main/java/jme3tools/converters/ImageToAwt.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -136,7 +136,7 @@ public DecodeParams(int bpp, int rm, int rs, int im, int is){ final int m__xx = 0x0000ffff; final int s__xx = 0; - // note: compressed, depth, or floating point formats not included here.. + // Note: compressed, depth, and floating-point formats are not included here. params.put(Format.ABGR8, new DecodeParams(4, mx___, m___x, m__x_, m_x__, sx___, s___x, s__x_, s_x__, @@ -177,6 +177,12 @@ public DecodeParams(int bpp, int rm, int rs, int im, int is){ } + /** + * A private constructor to inhibit instantiation of this class. + */ + private ImageToAwt() { + } + private static int Ix(int x, int y, int w){ return y * w + x; } @@ -200,11 +206,14 @@ private static void writePixel(ByteBuffer buf, int idx, int pixel, int bpp){ } } - /** - * Convert an AWT image to jME image. + * Convert an AWT image to jME image. XXX not implemented yet! + * + * @param image the input image (not null, unaffected) + * @param format the data format + * @param buf the output buffer (not null, modified) */ - public static void convert(BufferedImage image, Format format, ByteBuffer buf){ + public static void convert(BufferedImage image, Format format, ByteBuffer buf) { DecodeParams p = params.get(format); if (p == null) throw new UnsupportedOperationException("Image format " + format + " is not supported"); @@ -297,8 +306,8 @@ public static void createData(Image image, boolean mipmaps){ * It is assumed that both images have buffers with the appropriate * number of elements and that both have the same dimensions. * - * @param input - * @param output + * @param input the input image (not null, unaffected) + * @param output the output image (not null, modified) */ public static void convert(Image input, Image output){ DecodeParams inParams = params.get(input.getFormat()); @@ -366,7 +375,7 @@ public static void convert(Image input, Image output){ } } - public static BufferedImage convert(Image image, boolean do16bit, boolean fullalpha, int mipLevel){ + public static BufferedImage convert(Image image, boolean do16bit, boolean fullAlpha, int mipLevel){ Format format = image.getFormat(); DecodeParams p = params.get(image.getFormat()); if (p == null) @@ -402,7 +411,7 @@ else if (p.rm != 0 && p.gm != 0 && p.bm != 0) out = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY); }else if ( (rgb && alpha) || (luminance && alpha) ){ if (do16bit){ - if (fullalpha){ + if (fullAlpha){ ColorModel model = AWTLoader.AWT_RGBA4444; WritableRaster raster = model.createCompatibleWritableRaster(width, width); out = new BufferedImage(model, raster, false, null); diff --git a/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java b/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java index cfcff0a947..24378647e6 100644 --- a/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java +++ b/jme3-desktop/src/main/java/jme3tools/converters/MipMapGenerator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,12 @@ public class MipMapGenerator { + /** + * A private constructor to inhibit instantiation of this class. + */ + private MipMapGenerator() { + } + private static BufferedImage scaleDown(BufferedImage sourceImage, int targetWidth, int targetHeight) { int sourceWidth = sourceImage.getWidth(); int sourceHeight = sourceImage.getHeight(); @@ -85,7 +91,7 @@ public static void generateMipMaps(Image image){ BufferedImage current = original; AWTLoader loader = new AWTLoader(); - ArrayList output = new ArrayList(); + ArrayList output = new ArrayList<>(); int totalSize = 0; Format format = null; diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java b/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java deleted file mode 100644 index 79a72f5712..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/Coordinate.java +++ /dev/null @@ -1,274 +0,0 @@ -/* - * Copyright (c) 2009-2018 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 jme3tools.navigation; - -import java.text.DecimalFormat; - -/** - * Coordinate class. Used to store a coordinate in [DD]D MM.M format. - * - * @author Benjamin Jakobus (based on JMarine by Benjamin Jakobus and Cormac Gebruers) - * @version 1.0 - * @since 1.0 - */ -public class Coordinate { - - /* the degree part of the position (+ N/E, -W/S) */ - private int deg; - - /* the decimals of a minute */ - private double minsDecMins; - - /* the coordinate as a decimal*/ - private double decCoordinate; - - /* whether this coordinate is a latitude or a longitude: : LAT==0, LONG==1 */ - private int coOrdinate; - - /* The minutes trailing decimal precision to use for positions */ - public static final int MINPRECISION = 4; - /* The degrees trailing decimal precision to use for positions */ - public static final int DEGPRECISION = 7; - - /* typeDefs for coOrdinates */ - public static final int LAT = 0; - public static final int LNG = 1; - - /* typeDefs for quadrant */ - public static final int E = 0; - public static final int S = 1; - public static final int W = 2; - public static final int N = 3; - - /** - * Constructor - * - * @param deg - * @param minsDecMins - * @param coOrdinate - * @param quad - * @throws InvalidPositionException - * @since 1.0 - */ - public Coordinate(int deg, float minsDecMins, int coOrdinate, - int quad) throws InvalidPositionException { - buildCoOrdinate(deg, minsDecMins, coOrdinate, quad); - if (verify()) { - } else { - throw new InvalidPositionException(); - } - } - - /** - * Constructor - * @param decCoordinate - * @param coOrdinate - * @throws InvalidPositionException - * @since 1.0 - */ - public Coordinate(double decCoordinate, int coOrdinate) throws InvalidPositionException { - DecimalFormat form = new DecimalFormat("#.#######"); - - this.decCoordinate = decCoordinate; - this.coOrdinate = coOrdinate; - if (verify()) { - deg = new Float(decCoordinate).intValue(); - if (deg < 0) { - minsDecMins = Double.parseDouble(form.format((Math.abs(decCoordinate) - Math.abs(deg)) * 60)); - } else { - minsDecMins = Double.parseDouble(form.format((decCoordinate - deg) * 60)); - } - } else { - throw new InvalidPositionException(); - } - } - - /** - * This constructor takes a coordinate in the ALRS formats i.e - * 38∞31.64'N for lat, and 28∞19.12'W for long - * Note: ALRS positions are occasionally written with the decimal minutes - * apostrophe in the 'wrong' place and with a non CP1252 compliant decimal character. - * This issue has to be corrected in the source database - * @param coOrdinate - * @throws InvalidPositionException - * @since 1.0 - */ - public Coordinate(String coOrdinate) throws InvalidPositionException { - //firstly split it into its component parts and dispose of the unneeded characters - String[] items = coOrdinate.split("°"); - int deg = Integer.valueOf(items[0]); - - items = items[1].split("'"); - float minsDecMins = Float.valueOf(items[0]); - char quad = items[1].charAt(0); - - switch (quad) { - case 'N': - buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.N); - break; - case 'S': - buildCoOrdinate(deg, minsDecMins, Coordinate.LAT, Coordinate.S); - break; - case 'E': - buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.E); - break; - case 'W': - buildCoOrdinate(deg, minsDecMins, Coordinate.LNG, Coordinate.W); - } - if (verify()) { - } else { - throw new InvalidPositionException(); - } - } - - /** - * Prints out a coordinate as a string - * @return the coordinate in decimal format - * @since 1.0 - */ - public String toStringDegMin() { - String str = ""; - String quad = ""; - StringUtil su = new StringUtil(); - switch (coOrdinate) { - case LAT: - if (decCoordinate >= 0) { - quad = "N"; - } else { - quad = "S"; - } - str = su.padNumZero(Math.abs(deg), 2); - str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad; - break; - case LNG: - if (decCoordinate >= 0) { - quad = "E"; - } else { - quad = "W"; - } - str = su.padNumZero(Math.abs(deg), 3); - str += "\u00b0" + su.padNumZero(Math.abs(minsDecMins), 2, MINPRECISION) + "'" + quad; - break; - } - return str; - } - - /** - * Prints out a coordinate as a string - * @return the coordinate in decimal format - * @since 1.0 - */ - public String toStringDec() { - StringUtil u = new StringUtil(); - switch (coOrdinate) { - case LAT: - return u.padNumZero(decCoordinate, 2, DEGPRECISION); - case LNG: - return u.padNumZero(decCoordinate, 3, DEGPRECISION); - } - return "error"; - } - - /** - * Returns the coordinate's decimal value - * @return float the decimal value of the coordinate - * @since 1.0 - */ - public double decVal() { - return decCoordinate; - } - - /** - * Determines whether a decimal position is valid - * @return result of validity test - * @since 1.0 - */ - private boolean verify() { - switch (coOrdinate) { - case LAT: - if (Math.abs(decCoordinate) > 90.0) { - return false; - } - break; - - case LNG: - if (Math.abs(decCoordinate) > 180) { - return false; - } - } - return true; - } - - /** - * Populate this object by parsing the arguments to the function - * Placed here to allow multiple constructors to use it - * @since 1.0 - */ - private void buildCoOrdinate(int deg, float minsDecMins, int coOrdinate, - int quad) { - NumUtil nu = new NumUtil(); - - switch (coOrdinate) { - case LAT: - switch (quad) { - case N: - this.deg = deg; - this.minsDecMins = minsDecMins; - this.coOrdinate = coOrdinate; - decCoordinate = nu.Round(this.deg + (float) this.minsDecMins / 60, Coordinate.MINPRECISION); - break; - - case S: - this.deg = -deg; - this.minsDecMins = minsDecMins; - this.coOrdinate = coOrdinate; - decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); - } - - case LNG: - switch (quad) { - case E: - this.deg = deg; - this.minsDecMins = minsDecMins; - this.coOrdinate = coOrdinate; - decCoordinate = nu.Round(this.deg + ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); - break; - - case W: - this.deg = -deg; - this.minsDecMins = minsDecMins; - this.coOrdinate = coOrdinate; - decCoordinate = nu.Round(this.deg - ((float) this.minsDecMins / 60), Coordinate.MINPRECISION); - } - } - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java b/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java deleted file mode 100644 index 5a5182ba2c..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/GCSailing.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -/** - * A utility class to package up a great circle sailing. - * - * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin - * Jakobus) - * - * @version 1.0 - * @since 1.0 - */ -public class GCSailing { - - private int[] courses; - private float[] distancesNM; - - public GCSailing(int[] pCourses, float[] pDistancesNM) { - courses = pCourses; - distancesNM = pDistancesNM; - } - - public int[] getCourses() { - return courses; - } - - public float[] getDistancesNM() { - return distancesNM; - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java b/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java deleted file mode 100644 index 77240ed406..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/InvalidPositionException.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -/** - * - * @author normenhansen - */ -public class InvalidPositionException extends Exception{ - -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java deleted file mode 100644 index f3094071a3..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel2D.java +++ /dev/null @@ -1,394 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -import java.awt.Point; -import java.text.DecimalFormat; - -/** - * A representation of the actual map in terms of lat/long and x,y co-ordinates. - * The Map class contains various helper methods such as methods for determining - * the pixel positions for lat/long co-ordinates and vice versa. - * - * @author Cormac Gebruers - * @author Benjamin Jakobus - * @version 1.0 - * @since 1.0 - */ -public class MapModel2D { - - /* The number of radians per degree */ - private final static double RADIANS_PER_DEGREE = 57.2957; - - /* The number of degrees per radian */ - private final static double DEGREES_PER_RADIAN = 0.0174532925; - - /* The map's width in longitude */ - public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360; - - /* The top right hand corner of the map */ - private Position centre; - - /* The x and y co-ordinates for the viewport's centre */ - private int xCentre; - private int yCentre; - - /* The width (in pixels) of the viewport holding the map */ - private int viewportWidth; - - /* The viewport height in pixels */ - private int viewportHeight; - - /* The number of minutes that one pixel represents */ - private double minutesPerPixel; - - /** - * Constructor - * @param viewportWidth the pixel width of the viewport (component) in which - * the map is displayed - * @since 1.0 - */ - public MapModel2D(int viewportWidth) { - try { - this.centre = new Position(0, 0); - } catch (InvalidPositionException e) { - e.printStackTrace(); - } - - this.viewportWidth = viewportWidth; - - // Calculate the number of minutes that one pixel represents along the longitude - calculateMinutesPerPixel(DEFAULT_MAP_WIDTH_LONGITUDE); - - // Calculate the viewport height based on its width and the number of degrees (85) - // in our map - viewportHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerPixel) * 2; -// viewportHeight = viewportWidth; // REMOVE!!! - // Determine the map's x,y centre - xCentre = viewportWidth / 2; - yCentre = viewportHeight / 2; - } - - /** - * Returns the height of the viewport in pixels - * @return the height of the viewport in pixels - * @since 0.1 - */ - public int getViewportPixelHeight() { - return viewportHeight; - } - - /** - * Calculates the number of minutes per pixels using a given - * map width in longitude - * @param mapWidthInLongitude - * @since 1.0 - */ - public void calculateMinutesPerPixel(double mapWidthInLongitude) { - minutesPerPixel = (mapWidthInLongitude * 60) / (double) viewportWidth; - } - - /** - * Returns the width of the viewport in pixels - * @return the width of the viewport in pixels - * @since 0.1 - */ - public int getViewportPixelWidth() { - return viewportWidth; - } - - public void setViewportWidth(int viewportWidth) { - this.viewportWidth = viewportWidth; - } - - public void setViewportHeight(int viewportHeight) { - this.viewportHeight = viewportHeight; - } - - public void setCentre(Position centre) { - this.centre = centre; - } - - /** - * Returns the number of minutes there are per pixel - * @return the number of minutes per pixel - * @since 1.0 - */ - public double getMinutesPerPixel() { - return minutesPerPixel; - } - - public double getMetersPerPixel() { - return 1853 * minutesPerPixel; - } - - public void setMinutesPerPixel(double minutesPerPixel) { - this.minutesPerPixel = minutesPerPixel; - } - - /** - * Converts a latitude/longitude position into a pixel co-ordinate - * @param position the position to convert - * @return {@code Point} a pixel co-ordinate - * @since 1.0 - */ - public Point toPixel(Position position) { - // Get the distance between position and the centre for calculating - // the position's longitude translation - double distance = NavCalculator.computeLongDiff(centre.getLongitude(), - position.getLongitude()); - - // Use the distance from the centre to calculate the pixel x co-ordinate - double distanceInPixels = (distance / minutesPerPixel); - - // Use the difference in meridional parts to calculate the pixel y co-ordinate - double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(), - position.getLatitude()); - - int x = 0; - int y = 0; - - if (centre.getLatitude() == position.getLatitude()) { - y = yCentre; - } - if (centre.getLongitude() == position.getLongitude()) { - x = xCentre; - } - - // Distinguish between northern and southern hemisphere for latitude calculations - if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is north. Position is north of centre - y = yCentre + (int) ((dmp) / minutesPerPixel); - } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is north. Position is south of centre - y = yCentre - (int) ((dmp) / minutesPerPixel); - } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is south. Position is north of centre - y = yCentre + (int) ((dmp) / minutesPerPixel); - } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is south. Position is south of centre - y = yCentre - (int) ((dmp) / minutesPerPixel); - } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is at the equator. Position is north of the equator - y = yCentre + (int) ((dmp) / minutesPerPixel); - } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is at the equator. Position is south of the equator - y = yCentre - (int) ((dmp) / minutesPerPixel); - } - - // Distinguish between western and eastern hemisphere for longitude calculations - if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is west. Position is west of centre - x = xCentre - (int) distanceInPixels; - } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is west. Position is south of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is east. Position is west of centre - x = xCentre - (int) distanceInPixels; - } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is east. Position is east of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is at the equator. Position is east of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is at the equator. Position is west of centre - x = xCentre - (int) distanceInPixels; - } - - // Distinguish between northern and souterhn hemisphere for longitude calculations - return new Point(x, y); - } - - /** - * Converts a pixel position into a mercator position - * @param p {@link Point} object that you wish to convert into - * longitude / latiude - * @return the converted {@code Position} object - * @since 1.0 - */ - public Position toPosition(Point p) { - double lat, lon; - Position pos = null; - try { - Point pixelCentre = toPixel(new Position(0, 0)); - - // Get the distance between position and the centre - double xDistance = distance(xCentre, p.getX()); - double yDistance = distance(pixelCentre.getY(), p.getY()); - double lonDistanceInDegrees = (xDistance * minutesPerPixel) / 60; - double mp = (yDistance * minutesPerPixel); - // If we are zoomed in past a certain point, then use linear search. - // Otherwise use binary search - if (getMinutesPerPixel() < 0.05) { - lat = findLat(mp, getCentre().getLatitude()); - if (lat == -1000) { - System.out.println("lat: " + lat); - } - } else { - lat = findLat(mp, 0.0, 85.0); - } - lon = (p.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees - : centre.getLongitude() + lonDistanceInDegrees); - - if (p.getY() > pixelCentre.getY()) { - lat = -1 * lat; - } - if (lat == -1000 || lon == -1000) { - return pos; - } - pos = new Position(lat, lon); - } catch (InvalidPositionException ipe) { - ipe.printStackTrace(); - } - return pos; - } - - /** - * Calculates distance between two points on the map in pixels - * @param a - * @param b - * @return distance the distance between a and b in pixels - * @since 1.0 - */ - private double distance(double a, double b) { - return Math.abs(a - b); - } - - /** - * Defines the centre of the map in pixels - * @param p Point object denoting the map's new centre - * @since 1.0 - */ - public void setCentre(Point p) { - try { - Position newCentre = toPosition(p); - if (newCentre != null) { - centre = newCentre; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Sets the map's xCentre - * @param xCentre - * @since 1.0 - */ - public void setXCentre(int xCentre) { - this.xCentre = xCentre; - } - - /** - * Sets the map's yCentre - * @param yCentre - * @since 1.0 - */ - public void setYCentre(int yCentre) { - this.yCentre = yCentre; - } - - /** - * Returns the pixel (x,y) centre of the map - * @return {@link Point} object marking the map's (x,y) centre - * @since 1.0 - */ - public Point getPixelCentre() { - return new Point(xCentre, yCentre); - } - - /** - * Returns the {@code Position} centre of the map - * @return {@code Position} object marking the map's (lat, long) centre - * @since 1.0 - */ - public Position getCentre() { - return centre; - } - - /** - * Uses binary search to find the latitude of a given MP. - * - * @param mp maridian part - * @param low - * @param high - * @return the latitude of the MP value - * @since 1.0 - */ - private double findLat(double mp, double low, double high) { - DecimalFormat form = new DecimalFormat("#.####"); - mp = Math.round(mp); - double midLat = (low + high) / 2.0; - // ctr is used to make sure that with some - // numbers which can't be represented exactly don't inifitely repeat - double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); - - while (low <= high) { - if (guessMP == mp) { - return midLat; - } else { - if (guessMP > mp) { - high = midLat - 0.0001; - } else { - low = midLat + 0.0001; - } - } - - midLat = Double.valueOf(form.format(((low + high) / 2.0))); - guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); - guessMP = Math.round(guessMP); - } - return -1000; - } - - /** - * Uses linear search to find the latitude of a given MP - * @param mp the meridian part for which to find the latitude - * @param previousLat the previous latitude. Used as a upper / lower bound - * @return the latitude of the MP value - */ - private double findLat(double mp, double previousLat) { - DecimalFormat form = new DecimalFormat("#.#####"); - mp = Double.parseDouble(form.format(mp)); - double guessMP; - for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) { - guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat); - guessMP = Double.parseDouble(form.format(guessMP)); - if (guessMP == mp || Math.abs(guessMP - mp) < 0.001) { - return lat; - } - } - return -1000; - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java b/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java deleted file mode 100644 index 633d88bfcd..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/MapModel3D.java +++ /dev/null @@ -1,416 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -import com.jme3.math.Vector3f; -import java.text.DecimalFormat; - - -/** - * A representation of the actual map in terms of lat/long and x,y,z co-ordinates. - * The Map class contains various helper methods such as methods for determining - * the world unit positions for lat/long coordinates and vice versa. This map projection - * does not handle screen/pixel coordinates. - * - * @author Benjamin Jakobus (thanks to Cormac Gebruers) - * @version 1.0 - * @since 1.0 - */ -public class MapModel3D { - - /* The number of radians per degree */ - private final static double RADIANS_PER_DEGREE = 57.2957; - - /* The number of degrees per radian */ - private final static double DEGREES_PER_RADIAN = 0.0174532925; - - /* The map's width in longitude */ - public final static int DEFAULT_MAP_WIDTH_LONGITUDE = 360; - - /* The top right hand corner of the map */ - private Position centre; - - /* The x and y co-ordinates for the viewport's centre */ - private int xCentre; - private int zCentre; - - /* The width (in world units (wu)) of the viewport holding the map */ - private int worldWidth; - - /* The viewport height in pixels */ - private int worldHeight; - - /* The number of minutes that one pixel represents */ - private double minutesPerWorldUnit; - - /** - * Constructor. - * - * @param worldWidth The world unit width the map's area - * @since 1.0 - */ - public MapModel3D(int worldWidth) { - try { - this.centre = new Position(0, 0); - } catch (InvalidPositionException e) { - e.printStackTrace(); - } - - this.worldWidth = worldWidth; - - // Calculate the number of minutes that one pixel represents along the longitude - calculateMinutesPerWorldUnit(DEFAULT_MAP_WIDTH_LONGITUDE); - - // Calculate the viewport height based on its width and the number of degrees (85) - // in our map - worldHeight = ((int) NavCalculator.computeDMPClarkeSpheroid(0, 85) / (int) minutesPerWorldUnit) * 2; - - // Determine the map's x,y centre - xCentre = 0; - zCentre = 0; -// xCentre = worldWidth / 2; -// zCentre = worldHeight / 2; - } - - /** - * Returns the height of the viewport in pixels. - * - * @return The height of the viewport in pixels. - * @since 1.0 - */ - public int getWorldHeight() { - return worldHeight; - } - - /** - * Calculates the number of minutes per pixels using a given - * map width in longitude. - * - * @param mapWidthInLongitude The map's with in degrees of longitude. - * @since 1.0 - */ - public void calculateMinutesPerWorldUnit(double mapWidthInLongitude) { - // Multiply mapWidthInLongitude by 60 to convert it to minutes. - minutesPerWorldUnit = (mapWidthInLongitude * 60) / (double) worldWidth; - } - - /** - * Returns the width of the viewport in pixels. - * - * @return The width of the viewport in pixels. - * @since 1.0 - */ - public int getWorldWidth() { - return worldWidth; - } - - /** - * Sets the world's desired width. - * - * @param viewportWidth The world's desired width in WU. - * @since 1.0 - */ - public void setWorldWidth(int viewportWidth) { - this.worldWidth = viewportWidth; - } - - /** - * Sets the world's desired height. - * - * @param viewportHeight The world's desired height in WU. - * @since 1.0 - */ - public void setWorldHeight(int viewportHeight) { - this.worldHeight = viewportHeight; - } - - /** - * Sets the map's centre. - * - * @param centre The Position denoting the map's - * desired centre. - * @since 1.0 - */ - public void setCentre(Position centre) { - this.centre = centre; - } - - /** - * Returns the number of minutes there are per WU. - * - * @return The number of minutes per WU. - * @since 1.0 - */ - public double getMinutesPerWu() { - return minutesPerWorldUnit; - } - - /** - * Returns the meters per WU. - * - * @return The meters per WU. - * @since 1.0 - */ - public double getMetersPerWu() { - return 1853 * minutesPerWorldUnit; - } - - /** - * Converts a latitude/longitude position into a WU coordinate. - * - * @param position The Position to convert. - * @return The Point a pixel coordinate. - * @since 1.0 - */ - public Vector3f toWorldUnit(Position position) { - // Get the difference between position and the centre for calculating - // the position's longitude translation - double distance = NavCalculator.computeLongDiff(centre.getLongitude(), - position.getLongitude()); - - // Use the difference from the centre to calculate the pixel x co-ordinate - double distanceInPixels = (distance / minutesPerWorldUnit); - - // Use the difference in meridional parts to calculate the pixel y co-ordinate - double dmp = NavCalculator.computeDMPClarkeSpheroid(centre.getLatitude(), - position.getLatitude()); - - int x = 0; - int z = 0; - - if (centre.getLatitude() == position.getLatitude()) { - z = zCentre; - } - if (centre.getLongitude() == position.getLongitude()) { - x = xCentre; - } - - // Distinguish between northern and southern hemisphere for latitude calculations - if (centre.getLatitude() > 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is north. Position is north of centre - z = zCentre - (int) ((dmp) / minutesPerWorldUnit); - } else if (centre.getLatitude() > 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is north. Position is south of centre - z = zCentre + (int) ((dmp) / minutesPerWorldUnit); - } else if (centre.getLatitude() < 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is south. Position is north of centre - z = zCentre - (int) ((dmp) / minutesPerWorldUnit); - } else if (centre.getLatitude() < 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is south. Position is south of centre - z = zCentre + (int) ((dmp) / minutesPerWorldUnit); - } else if (centre.getLatitude() == 0 && position.getLatitude() > centre.getLatitude()) { - // Centre is at the equator. Position is north of the equator - z = zCentre - (int) ((dmp) / minutesPerWorldUnit); - } else if (centre.getLatitude() == 0 && position.getLatitude() < centre.getLatitude()) { - // Centre is at the equator. Position is south of the equator - z = zCentre + (int) ((dmp) / minutesPerWorldUnit); - } - - // Distinguish between western and eastern hemisphere for longitude calculations - if (centre.getLongitude() < 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is west. Position is west of centre - x = xCentre - (int) distanceInPixels; - } else if (centre.getLongitude() < 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is west. Position is south of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() > 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is east. Position is west of centre - x = xCentre - (int) distanceInPixels; - } else if (centre.getLongitude() > 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is east. Position is east of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() == 0 && position.getLongitude() > centre.getLongitude()) { - // Centre is at the equator. Position is east of centre - x = xCentre + (int) distanceInPixels; - } else if (centre.getLongitude() == 0 && position.getLongitude() < centre.getLongitude()) { - // Centre is at the equator. Position is west of centre - x = xCentre - (int) distanceInPixels; - } - - // Distinguish between northern and southern hemisphere for longitude calculations - return new Vector3f(x, 0, z); - } - - /** - * Converts a world position into a Mercator position. - * - * @param posVec Vector containing the world unit - * coordinates that are to be converted into - * longitude / latitude coordinates. - * @return The resulting Position in degrees of - * latitude and longitude. - * @since 1.0 - */ - public Position toPosition(Vector3f posVec) { - double lat, lon; - Position pos = null; - try { - Vector3f worldCentre = toWorldUnit(new Position(0, 0)); - - // Get the difference between position and the centre - double xDistance = difference(xCentre, posVec.getX()); - double yDistance = difference(worldCentre.getZ(), posVec.getZ()); - double lonDistanceInDegrees = (xDistance * minutesPerWorldUnit) / 60; - double mp = (yDistance * minutesPerWorldUnit); - // If we are zoomed in past a certain point, then use linear search. - // Otherwise use binary search - if (getMinutesPerWu() < 0.05) { - lat = findLat(mp, getCentre().getLatitude()); - if (lat == -1000) { - System.out.println("lat: " + lat); - } - } else { - lat = findLat(mp, 0.0, 85.0); - } - lon = (posVec.getX() < xCentre ? centre.getLongitude() - lonDistanceInDegrees - : centre.getLongitude() + lonDistanceInDegrees); - - if (posVec.getZ() > worldCentre.getZ()) { - lat = -1 * lat; - } - if (lat == -1000 || lon == -1000) { - return pos; - } - pos = new Position(lat, lon); - } catch (InvalidPositionException ipe) { - ipe.printStackTrace(); - } - return pos; - } - - /** - * Calculates difference between two points on the map in WU. - * - * @param a - * @param b - * @return difference The difference between a and b in WU. - * @since 1.0 - */ - private double difference(double a, double b) { - return Math.abs(a - b); - } - - /** - * Defines the centre of the map in pixels. - * - * @param posVec Vector3f object denoting the map's new centre. - * @since 1.0 - */ - public void setCentre(Vector3f posVec) { - try { - Position newCentre = toPosition(posVec); - if (newCentre != null) { - centre = newCentre; - } - } catch (Exception e) { - e.printStackTrace(); - } - } - - /** - * Returns the WU (x,y,z) centre of the map. - * - * @return Vector3f object marking the map's (x,y) centre. - * @since 1.0 - */ - public Vector3f getCentreWu() { - return new Vector3f(xCentre, 0, zCentre); - } - - /** - * Returns the Position centre of the map. - * - * @return Position object marking the map's (lat, long) - * centre. - * @since 1.0 - */ - public Position getCentre() { - return centre; - } - - /** - * Uses binary search to find the latitude of a given MP. - * - * @param mp Maridian part whose latitude to determine. - * @param low Minimum latitude bounds. - * @param high Maximum latitude bounds. - * @return The latitude of the MP value - * @since 1.0 - */ - private double findLat(double mp, double low, double high) { - DecimalFormat form = new DecimalFormat("#.####"); - mp = Math.round(mp); - double midLat = (low + high) / 2.0; - // ctr is used to make sure that with some - // numbers which can't be represented exactly don't inifitely repeat - double guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); - - while (low <= high) { - if (guessMP == mp) { - return midLat; - } else { - if (guessMP > mp) { - high = midLat - 0.0001; - } else { - low = midLat + 0.0001; - } - } - - midLat = Double.valueOf(form.format(((low + high) / 2.0))); - guessMP = NavCalculator.computeDMPClarkeSpheroid(0, (float) midLat); - guessMP = Math.round(guessMP); - } - return -1000; - } - - /** - * Uses linear search to find the latitude of a given MP. - * - * @param mp The meridian part for which to find the latitude. - * @param previousLat The previous latitude. Used as a upper / lower bound. - * @return The latitude of the MP value. - * @since 1.0 - */ - private double findLat(double mp, double previousLat) { - DecimalFormat form = new DecimalFormat("#.#####"); - mp = Double.parseDouble(form.format(mp)); - double guessMP; - for (double lat = previousLat - 0.25; lat < previousLat + 1; lat += 0.00001) { - guessMP = NavCalculator.computeDMPClarkeSpheroid(0, lat); - guessMP = Double.parseDouble(form.format(guessMP)); - if (guessMP == mp || Math.abs(guessMP - mp) < 0.05) { - return lat; - } - } - return -1000; - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java b/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java deleted file mode 100644 index e67247ae68..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/NavCalculator.java +++ /dev/null @@ -1,617 +0,0 @@ -/* - * Copyright (c) 2009-2019 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 jme3tools.navigation; - - - -/** - * A utlity class for performing position calculations - * - * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin - * Jakobus) - * @version 1.0 - * @since 1.0 - */ -public class NavCalculator { - - private double distance; - private double trueCourse; - - /* The earth's radius in meters */ - public static final int WGS84_EARTH_RADIUS = 6378137; - private String strCourse; - - /* The sailing calculation type */ - public static final int MERCATOR = 0; - public static final int GC = 1; - - /* The degree precision to use for courses */ - public static final int RL_CRS_PRECISION = 1; - - /* The distance precision to use for distances */ - public static final int RL_DIST_PRECISION = 1; - public static final int METERS_PER_MINUTE = 1852; - - /** - * Constructor - * @param P1 - * @param P2 - * @param calcType - * @since 1.0 - */ - public NavCalculator(Position P1, Position P2, int calcType) { - switch (calcType) { - case MERCATOR: - mercatorSailing(P1, P2); - break; - case GC: - greatCircleSailing(P1, P2); - break; - } - } - - /** - * Constructor - * @since 1.0 - */ - public NavCalculator() { - } - - /** - * Determines a great circle track between two positions - * @param p1 origin position - * @param p2 destination position - */ - public GCSailing greatCircleSailing(Position p1, Position p2) { - return new GCSailing(new int[0], new float[0]); - } - - /** - * Determines a Rhumb Line course and distance between two points - * @param p1 origin position - * @param p2 destination position - */ - public RLSailing rhumbLineSailing(Position p1, Position p2) { - RLSailing rl = mercatorSailing(p1, p2); - return rl; - } - - /** - * Determines the rhumb line course and distance between two positions - * @param p1 origin position - * @param p2 destination position - */ - public RLSailing mercatorSailing(Position p1, Position p2) { - - double dLat = computeDLat(p1.getLatitude(), p2.getLatitude()); - //plane sailing... - if (dLat == 0) { - RLSailing rl = planeSailing(p1, p2); - return rl; - } - - double dLong = computeDLong(p1.getLongitude(), p2.getLongitude()); - double dmp = (float) computeDMPClarkeSpheroid(p1.getLatitude(), p2.getLatitude()); - - trueCourse = (float) Math.toDegrees(Math.atan(dLong / dmp)); - double degCrs = convertCourse((float) trueCourse, p1, p2); - distance = (float) Math.abs(dLat / Math.cos(Math.toRadians(trueCourse))); - - RLSailing rl = new RLSailing(degCrs, (float) distance); - trueCourse = rl.getCourse(); - strCourse = (dLat < 0 ? "S" : "N"); - strCourse += " " + trueCourse; - strCourse += " " + (dLong < 0 ? "W" : "E"); - return rl; - - } - - /** - * Calculate a plane sailing situation - i.e. where Lats are the same - * @param p1 - * @param p2 - * @return a new instance - * @since 1.0 - */ - public RLSailing planeSailing(Position p1, Position p2) { - double dLong = computeDLong(p1.getLongitude(), p2.getLongitude()); - - double sgnDLong = 0 - (dLong / Math.abs(dLong)); - if (Math.abs(dLong) > 180 * 60) { - dLong = (360 * 60 - Math.abs(dLong)) * sgnDLong; - } - - double redist = 0; - double recourse = 0; - if (p1.getLatitude() == 0) { - redist = Math.abs(dLong); - } else { - redist = Math.abs(dLong * (float) Math.cos(p1.getLatitude() * 2 * Math.PI / 360)); - } - recourse = (float) Math.asin(0 - sgnDLong); - recourse = recourse * 360 / 2 / (float) Math.PI; - - if (recourse < 0) { - recourse = recourse + 360; - } - return new RLSailing(recourse, redist); - } - - /** - * Converts a course from cardinal XddY to ddd notation - * @param tc - * @param p1 position one - * @param p2 position two - * @return angle (in degrees) - * @since 1.0 - */ - public static double convertCourse(float tc, Position p1, Position p2) { - - double dLat = p1.getLatitude() - p2.getLatitude(); - double dLong = p1.getLongitude() - p2.getLongitude(); - //NE - if (dLong >= 0 & dLat >= 0) { - return Math.abs(tc); - } - - //SE - if (dLong >= 0 & dLat < 0) { - return 180 - Math.abs(tc); - } - - //SW - if (dLong < 0 & dLat < 0) { - return 180 + Math.abs(tc); - } - - //NW - if (dLong < 0 & dLat >= 0) { - return 360 - Math.abs(tc); - } - return -1; - } - - /** - * Getter method for the distance between two points - * @return distance - * @since 1.0 - */ - public double getDistance() { - return distance; - } - - /** - * Getter method for the true course - * @return true course - * @since 1.0 - */ - public double getTrueCourse() { - return trueCourse; - } - - /** - * Getter method for the true course - * @return true course - * @since 1.0 - */ - public String getStrCourse() { - return strCourse; - } - - /** - * Computes the difference in meridional parts for two latitudes in minutes - * (based on Clark 1880 spheroid) - * @param lat1 - * @param lat2 - * @return difference in minutes - * @since 1.0 - */ - public static double computeDMPClarkeSpheroid(double lat1, double lat2) { - double absLat1 = Math.abs(lat1); - double absLat2 = Math.abs(lat2); - - double m1 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45 - + (absLat1 / 2)))) / Math.log(10)) - - 23.268932 * Math.sin(Math.toRadians(absLat1)) - - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat1)), 3) - - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat1)), 5)); - - double m2 = (7915.704468 * (Math.log(Math.tan(Math.toRadians(45 - + (absLat2 / 2)))) / Math.log(10)) - - 23.268932 * Math.sin(Math.toRadians(absLat2)) - - 0.052500 * Math.pow(Math.sin(Math.toRadians(absLat2)), 3) - - 0.000213 * Math.pow(Math.sin(Math.toRadians(absLat2)), 5)); - if ((lat1 <= 0 && lat2 <= 0) || (lat1 > 0 && lat2 > 0)) { - return Math.abs(m1 - m2); - } else { - return m1 + m2; - } - } - - /** - * Computes the difference in meridional parts for a perfect sphere between - * two degrees of latitude - * @param lat1 - * @param lat2 - * @return difference in meridional parts between lat1 and lat2 in minutes - * @since 1.0 - */ - public static float computeDMPWGS84Spheroid(float lat1, float lat2) { - float absLat1 = Math.abs(lat1); - float absLat2 = Math.abs(lat2); - - float m1 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat1 / 2)))) - - 23.01358 * Math.sin(absLat1 - 0.05135) * Math.pow(Math.sin(absLat1), 3)); - - float m2 = (float) (7915.7045 * Math.log10(Math.tan(Math.toRadians(45 + (absLat2 / 2)))) - - 23.01358 * Math.sin(absLat2 - 0.05135) * Math.pow(Math.sin(absLat2), 3)); - - if (lat1 <= 0 & lat2 <= 0 || lat1 > 0 & lat2 > 0) { - return Math.abs(m1 - m2); - } else { - return m1 + m2; - } - } - - /** - * Predicts the position of a target for a given time in the future - * @param time the number of seconds from now for which to predict the future - * position - * @param speed the miles per minute that the target is traveling - * @param currentLat the target's current latitude - * @param currentLong the target's current longitude - * @param course the target's current course in degrees - * @return the predicted future position - * @since 1.0 - */ - public static Position predictPosition(int time, double speed, - double currentLat, double currentLong, double course) { - Position futurePosition = null; - course = Math.toRadians(course); - double futureLong = currentLong + speed * time * Math.sin(course); - double futureLat = currentLat + speed * time * Math.cos(course); - try { - futurePosition = new Position(futureLat, futureLong); - } catch (InvalidPositionException ipe) { - ipe.printStackTrace(); - } - return futurePosition; - - } - - /** - * Computes the coordinate of position B relative to an offset given - * a distance and an angle. - * - * @param initialPos - * @param heading - * @param distance The distance, in meters, between the offset - * and point B. - * @return The position of point B that is located from - * given offset at given distance and angle. - * @since 1.0 - */ - public static Position computePosition(Position initialPos, double heading, - double distance) { - if (initialPos == null) { - return null; - } - double angle; - if (heading < 90) { - angle = heading; - } else if (heading > 90 && heading < 180) { - angle = 180 - heading; - } else if (heading > 180 && heading < 270) { - angle = heading - 180; - } else { - angle = 360 - heading; - } - - Position newPosition = null; - - // Convert meters into nautical miles - distance = distance * 0.000539956803; - angle = Math.toRadians(angle); - double initialLat = initialPos.getLatitude(); - double initialLong = initialPos.getLongitude(); - double dlat = distance * Math.cos(angle); - dlat = dlat / 60; - dlat = Math.abs(dlat); - double newLat = 0; - if ((heading > 270 && heading < 360) || (heading > 0 && heading < 90)) { - newLat = initialLat + dlat; - } else if (heading < 270 && heading > 90) { - newLat = initialLat - dlat; - } - double meanLat = (Math.abs(dlat) / 2.0) + newLat; - double dep = (Math.abs(dlat * 60)) * Math.tan(angle); - double dlong = dep * (1.0 / Math.cos(Math.toRadians(meanLat))); - dlong = dlong / 60; - dlong = Math.abs(dlong); - double newLong; - if (heading > 180 && heading < 360) { - newLong = initialLong - dlong; - } else { - newLong = initialLong + dlong; - } - - if (newLong < -180) { - double diff = Math.abs(newLong + 180); - newLong = 180 - diff; - } - - if (newLong > 180) { - double diff = Math.abs(newLong + 180); - newLong = (180 - diff) * -1; - } - - if (heading == 0 || heading == 360 || heading == 180) { - newLong = initialLong; - newLat = initialLat + dlat; - } else if (heading == 90 || heading == 270) { - newLat = initialLat; -// newLong = initialLong + dlong; THIS WAS THE ORIGINAL (IT WORKED) - newLong = initialLong - dlong; - } - try { - newPosition = new Position(newLat, - newLong); - } catch (InvalidPositionException ipe) { - ipe.printStackTrace(); - System.out.println(newLat + "," + newLong); - } - return newPosition; - } - - /** - * Computes the difference in Longitude between two positions and assigns the - * correct sign -westwards travel, + eastwards travel - * @param lng1 - * @param lng2 - * @return difference in longitude - * @since 1.0 - */ - public static double computeDLong(double lng1, double lng2) { - if (lng1 - lng2 == 0) { - return 0; - } - - // both easterly - if (lng1 >= 0 & lng2 >= 0) { - return -(lng1 - lng2) * 60; - } - //both westerly - if (lng1 < 0 & lng2 < 0) { - return -(lng1 - lng2) * 60; - } - - //opposite sides of Date line meridian - - //sum less than 180 - if (Math.abs(lng1) + Math.abs(lng2) < 180) { - if (lng1 < 0 & lng2 > 0) { - return -(Math.abs(lng1) + Math.abs(lng2)) * 60; - } else { - return Math.abs(lng1) + Math.abs(lng2) * 60; - } - } else { - //sum greater than 180 - if (lng1 < 0 & lng2 > 0) { - return -(360 - (Math.abs(lng1) + Math.abs(lng2))) * 60; - } else { - return (360 - (Math.abs(lng1) + Math.abs(lng2))) * 60; - } - } - } - - /** - * Computes the difference in Longitude between two positions and assigns the - * correct sign -westwards travel, + eastwards travel - * @param lng1 - * @param lng2 - * @return difference in longitude - * @since 1.0 - */ - public static double computeLongDiff(double lng1, double lng2) { - if (lng1 - lng2 == 0) { - return 0; - } - - // both easterly - if (lng1 >= 0 & lng2 >= 0) { - return Math.abs(-(lng1 - lng2) * 60); - } - //both westerly - if (lng1 < 0 & lng2 < 0) { - return Math.abs(-(lng1 - lng2) * 60); - } - - if (lng1 == 0) { - return Math.abs(lng2 * 60); - } - - if (lng2 == 0) { - return Math.abs(lng1 * 60); - } - - return (Math.abs(lng1) + Math.abs(lng2)) * 60; - } - - /** - * Compute the difference in latitude between two positions - * @param lat1 - * @param lat2 - * @return difference in latitude - * @since 1.0 - */ - public static double computeDLat(double lat1, double lat2) { - //same side of equator - - //plane sailing - if (lat1 - lat2 == 0) { - return 0; - } - - //both northerly - if (lat1 >= 0 & lat2 >= 0) { - return -(lat1 - lat2) * 60; - } - //both southerly - if (lat1 < 0 & lat2 < 0) { - return -(lat1 - lat2) * 60; - } - - //opposite sides of equator - if (lat1 >= 0) { - //heading south - return -(Math.abs(lat1) + Math.abs(lat2)); - } else { - //heading north - return (Math.abs(lat1) + Math.abs(lat2)); - } - } - - public static class Quadrant { - - private static final Quadrant FIRST = new Quadrant(1, 1); - private static final Quadrant SECOND = new Quadrant(-1, 1); - private static final Quadrant THIRD = new Quadrant(-1, -1); - private static final Quadrant FOURTH = new Quadrant(1, -1); - private final int lonMultiplier; - private final int latMultiplier; - - public Quadrant(final int xMultiplier, final int yMultiplier) { - this.lonMultiplier = xMultiplier; - this.latMultiplier = yMultiplier; - } - - static Quadrant getQuadrant(double degrees, boolean invert) { - if (invert) { - if (degrees >= 0 && degrees <= 90) { - return FOURTH; - } else if (degrees > 90 && degrees <= 180) { - return THIRD; - } else if (degrees > 180 && degrees <= 270) { - return SECOND; - } - return FIRST; - } else { - if (degrees >= 0 && degrees <= 90) { - return FIRST; - } else if (degrees > 90 && degrees <= 180) { - return SECOND; - } else if (degrees > 180 && degrees <= 270) { - return THIRD; - } - return FOURTH; - } - } - } - - /** - * Converts meters to degrees. - * - * @param meters The meters that you want to convert into degrees. - * @return The degree equivalent of the given meters. - * @since 1.0 - */ - public static double toDegrees(double meters) { - return (meters / METERS_PER_MINUTE) / 60; - } - - /** - * Computes the bearing between two points. - * - * @param p1 - * @param p2 - * @return bearing (in degrees) - * @since 1.0 - */ - public static int computeBearing(Position p1, Position p2) { - int bearing; - double dLon = computeDLong(p1.getLongitude(), p2.getLongitude()); - double y = Math.sin(dLon) * Math.cos(p2.getLatitude()); - double x = Math.cos(p1.getLatitude()) * Math.sin(p2.getLatitude()) - - Math.sin(p1.getLatitude()) * Math.cos(p2.getLatitude()) * Math.cos(dLon); - bearing = (int) Math.toDegrees(Math.atan2(y, x)); - return bearing; - } - - /** - * Computes the angle between two points. - * - * @param p1 - * @param p2 - * @return angle (in degrees) - */ - public static int computeAngle(Position p1, Position p2) { - // cos (adj / hyp) - double adj = Math.abs(p1.getLongitude() - p2.getLongitude()); - double opp = Math.abs(p1.getLatitude() - p2.getLatitude()); - return (int) Math.toDegrees(Math.atan(opp / adj)); - -// int angle = (int)Math.atan2(p2.getLatitude() - p1.getLatitude(), -// p2.getLongitude() - p1.getLongitude()); - //Actually it's ATan2(dy , dx) where dy = y2 - y1 and dx = x2 - x1, or ATan(dy / dx) - } - - public static int computeHeading(Position p1, Position p2) { - int angle = computeAngle(p1, p2); - // NE - if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() >= p1.getLatitude()) { - return angle; - } else if (p2.getLongitude() >= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) { - // SE - return 90 + angle; - } else if (p2.getLongitude() <= p1.getLongitude() && p2.getLatitude() <= p1.getLatitude()) { - // SW - return 270 - angle; - } else { - // NW - return 270 + angle; - } - } - - public static void main(String[] args) { - try { - int pos = NavCalculator.computeHeading(new Position(0, 0), new Position(10, -10)); -// System.out.println(pos.getLatitude() + "," + pos.getLongitude()); - System.out.println(pos); - } catch (Exception e) { - } - - - - - - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java b/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java deleted file mode 100644 index 4110f50963..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/NumUtil.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -/** - * Provides various helper methods for number conversions (such as degree to radian - * conversion, decimal degree to radians etc) - * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin - * Jakobus) - * @version 1.0 - * @since 1.0 - */ -public class NumUtil { - - /** - * Rounds a number - * @param Rval number to be rounded - * @param Rpl number of decimal places - * @return rounded number - * @since 0.1 - */ - public float Round(float Rval, int Rpl) { - float p = (float) Math.pow(10, Rpl); - Rval = Rval * p; - float tmp = Math.round(Rval); - return (float) tmp / p; - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/Position.java b/jme3-desktop/src/main/java/jme3tools/navigation/Position.java deleted file mode 100644 index 2de517facc..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/Position.java +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -/** - * This class represents the position of an entity in the world. - * - * @author Benjamin Jakobus (based on JMarine by Cormac Gebruers and Benjamin Jakobus) - * @version 1.0 - * @since 1.0 - */ -public class Position { - - /* the latitude (+ N/E) */ - private Coordinate lat; - - /* the longitude (-W/S) */ - private Coordinate lng; - - /* An optional time to associate with this position - for historical tracking */ - private String utcTimeStamp; - - /* Degree position */ - private double degree; - - /** - * A new position expressed in decimal format - * @param dblLat - * @param dblLng - * @since 1.0 - */ - public Position(double dblLat, double dblLng) throws InvalidPositionException { - lat = new Coordinate(dblLat, Coordinate.LAT); - lng = new Coordinate(dblLng, Coordinate.LNG); - } - - /** - * A new position expressed in decimal format and degrees - * @param dblLat - * @param dblLng - * @param degree - * @since 1.0 - */ -// public Position(double dblLat, double dblLng, double degree) throws InvalidPositionException { -// lat = new Coordinate(dblLat, Coordinate.LAT); -// lng = new Coordinate(dblLng, Coordinate.LNG); -// this.degree = degree; -// } - /** - * A new position expressed in DegMin format - * @param latDeg - * @param latMin - * @param lngDeg - * @param lngMin - * @since 1.0 - */ - public Position(int latDeg, float latMin, int latQuad, int lngDeg, - float lngMin, int lngQuad) throws InvalidPositionException { - lat = new Coordinate(latDeg, latMin, Coordinate.LAT, latQuad); - lng = new Coordinate(lngDeg, lngMin, Coordinate.LNG, lngQuad); - } - - /** - * A new position expressed in ALRS format - * @param lat - * @param lng - * @since 1.0 - */ - public Position(String lat, String lng) throws InvalidPositionException { - this.lat = new Coordinate(lat); - this.lng = new Coordinate(lng); - } - - /** - * A new position expressed in NMEA GPS message format: - * 4807.038,N,01131.000,E - * @param latNMEAGPS - * @param latQuad - * @param lngNMEAGPS - * @param lngQuad - * @param utcTimeStamp - * @since 12.0 - */ - public Position(String latNMEAGPS, String latQuad, String lngNMEAGPS, String lngQuad, String utcTimeStamp) { - int quad; - - //LAT - if (latQuad.compareTo("N") == 0) { - quad = Coordinate.N; - } else { - quad = Coordinate.S; - } - try { - this.lat = new Coordinate(Integer.valueOf(latNMEAGPS.substring(0, 2)), Float.valueOf(latNMEAGPS.substring(2)), Coordinate.LAT, quad); - } catch (InvalidPositionException e) { - e.printStackTrace(); - } - - //LNG - if (lngQuad.compareTo("E") == 0) { - quad = Coordinate.E; - } else { - quad = Coordinate.W; - } - try { - this.lng = new Coordinate(Integer.valueOf(lngNMEAGPS.substring(0, 3)), Float.valueOf(lngNMEAGPS.substring(3)), Coordinate.LNG, quad); - } catch (InvalidPositionException e) { - e.printStackTrace(); - } - - //TIMESTAMP - this.associateUTCTime(utcTimeStamp); - } - - /** - * Add a reference time for this position - useful for historical tracking - * @param data - * @since 1.0 - */ - public void associateUTCTime(String data) { - utcTimeStamp = data; - } - - /** - * Returns the UTC time stamp - * @return str the UTC timestamp - * @since 1.0 - */ - public String utcTimeStamp() { - return utcTimeStamp; - } - - /** - * Prints out position using decimal format - * @return the position in decimal format - */ - public String toStringDec() { - return lat.toStringDec() + " " + lng.toStringDec(); - } - - /** - * Return the position latitude in decimal format - * @return the latitude in decimal format - * @since 1.0 - */ - public double getLatitude() { - return lat.decVal(); - } - - /** - * Returns the degree of the entity - * @return degree - * @since 1.0 - */ -// public double getDegree() { -// return degree; -// } - /** - * Return the position longitude in decimal format - * @return the longitude in decimal format - * @since 1.0 - */ - public double getLongitude() { - return lng.decVal(); - } - - /** - * Prints out position using DegMin format - * @return the position in DegMin Format - * @since 1.0 - */ - public String toStringDegMin() { - String output = ""; - output += lat.toStringDegMin(); - output += " " + lng.toStringDegMin(); - return output; - } - - /** - * Prints out the position latitude - * @return the latitude as a string for display purposes - * @since 1.0 - */ - public String toStringDegMinLat() { - return lat.toStringDegMin(); - } - - /** - * Prints out the position longitude - * @return the longitude as a string for display purposes - * @since 1.0 - */ - public String toStringDegMinLng() { - return lng.toStringDegMin(); - } - - /** - * Prints out the position latitude - * @return the latitude as a string for display purposes - * @since 1.0 - */ - public String toStringDecLat() { - return lat.toStringDec(); - } - - /** - * Prints out the position longitude - * @return the longitude as a string for display purposes - * @since 1.0 - */ - public String toStringDecLng() { - return lng.toStringDec(); - } - - //TEST HARNESS - DO NOT DELETE! - public static void main(String[] argsc) { - - //NMEA GPS Position format: - Position p = new Position("4807.038", "N", "01131.000", "W", "123519"); - System.out.println(p.toStringDegMinLat()); - System.out.println(p.getLatitude()); - System.out.println(p.getLongitude()); - System.out.println(p.toStringDegMinLng()); - System.out.println(p.utcTimeStamp()); - - }//main -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java b/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java deleted file mode 100644 index c9d70b8e53..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/RLSailing.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -/** - * A utility class to package up a rhumb line sailing - * - * @author Benjamin Jakobus, based on JMarine (by Cormac Gebruers and Benjamin - * Jakobus) - * @version 1.0 - * @since 1.0 - */ -public class RLSailing { - - private double course; - private double distNM; - - public RLSailing(double pCourse, double pDistNM) { - course = pCourse; - distNM = pDistNM; - } - - public double getCourse() { - return course; - } - - public double getDistNM() { - return distNM; - } -} diff --git a/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java b/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java deleted file mode 100644 index 873e6087bb..0000000000 --- a/jme3-desktop/src/main/java/jme3tools/navigation/StringUtil.java +++ /dev/null @@ -1,287 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3tools.navigation; - -import java.util.regex.Pattern; - -/** - * A collection of String utilities. - * - * @author Benjamin Jakobus - * @version 1.0 - */ -public class StringUtil { - - /** - * Splits a newline (\n) delimited string into an array of strings - * - * @param str the string to split up - * @param delimiter the delimiter to use in splitting - * @return an array of String objects equivalent to str - */ - public String[] splitDelimitedStr(String str, String delimiter) { - Pattern pttn = Pattern.compile(delimiter); - return pttn.split(str); - } - - /** - * Right aligns a long number with spaces for printing - * - * @param num the number to be aligned - * @param totalLen the total length of the padded string - * @return the padded number - */ - public String padNum(long num, int totalLen) { - String numStr = Long.toString(num); - int len = totalLen - numStr.length(); - String pads = ""; - for (int i = 0; i < len; i++) { - pads += " "; - } - return pads + numStr; - } - - /** - * Right aligns a long number with zeros for printing - * - * @param num the number to be aligned - * @param totalLen the total length of the padded string - * @return the padded number - */ - public String padNumZero(long num, int totalLen) { - String numStr = Long.toString(num); - int len = totalLen - numStr.length(); - String pads = ""; - for (int i = 0; i < len; i++) { - pads += "0"; - } - return pads + numStr; - } - - /** - * Right aligns an integer number with spaces for printing - * - * @param num the number to be aligned - * @param totalLen the total length of the padded string - * @return the padded number - */ - public String padNum(int num, int totalLen) { - String numStr = Integer.toString(num); - int len = totalLen - numStr.length(); - String pads = ""; - for (int i = 0; i < len; i++) { - pads += " "; - } - return pads + numStr; - } - - /** - * Right aligns an integer number with zeros for printing - * - * @param num the number to be aligned - * @param totalLen the total length of the padded string - * @return the padded number - */ - public String padNumZero(int num, int totalLen) { - String numStr = Integer.toString(num); - int len = totalLen - numStr.length(); - String pads = ""; - for (int i = 0; i < len; i++) { - pads += "0"; - } - return pads + numStr; - } - - /** - * Right aligns a double number with spaces for printing - * - * @param num the number to be aligned - * @param wholeLen the total length of the padded string - * @return the padded number - */ - public String padNum(double num, int wholeLen, int decimalPlaces) { - String numStr = Double.toString(num); - int dpLoc = numStr.indexOf("."); - - int len = wholeLen - dpLoc; - String pads = ""; - for (int i = 0; i < len; i++) { - pads += " "; - } - - numStr = pads + numStr; - - dpLoc = numStr.indexOf("."); - - if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) { - return numStr; - } - return numStr.substring(0, dpLoc + 1 + decimalPlaces); - } - - /** - * Right aligns a double number with zeros for printing - * - * @param num the number to be aligned - * @param wholeLen the total length of the padded string - * @return the padded number - */ - public String padNumZero(double num, int wholeLen, int decimalPlaces) { - String numStr = Double.toString(num); - int dpLoc = numStr.indexOf("."); - - int len = wholeLen - dpLoc; - String pads = ""; - for (int i = 0; i < len; i++) { - pads += "0"; - } - - numStr = pads + numStr; - - dpLoc = numStr.indexOf("."); - - if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) { - return numStr; - } - return numStr.substring(0, dpLoc + 1 + decimalPlaces); - } - - /** - * Right aligns a float number with spaces for printing - * - * @param num the number to be aligned - * @param wholeLen the total length of the padded string - * @return the padded number - */ - public String padNum(float num, int wholeLen, int decimalPlaces) { - String numStr = Float.toString(num); - int dpLoc = numStr.indexOf("."); - - int len = wholeLen - dpLoc; - String pads = ""; - for (int i = 0; i < len; i++) { - pads += " "; - } - - numStr = pads + numStr; - - dpLoc = numStr.indexOf("."); - - if (dpLoc + 1 + decimalPlaces > numStr.substring(dpLoc).length()) { - return numStr; - } - return numStr.substring(0, dpLoc + 1 + decimalPlaces); - } - - /** - * Right aligns a float number with zeros for printing - * - * @param num the number to be aligned - * @param wholeLen the total length of the padded string - * @return the padded number - */ - public String padNumZero(float num, int wholeLen, int decimalPlaces) { - String numStr = Float.toString(num); - int dpLoc = numStr.indexOf("."); - - int len = wholeLen - dpLoc; - String pads = ""; - - if (numStr.charAt(0) == '-') { - len += 1; - for (int i = 0; i < len; i++) { - pads += "0"; - } - pads = "-" + pads; - numStr = pads + numStr.substring(1); - } else { - for (int i = 0; i < len; i++) { - pads += "0"; - } - numStr = pads + numStr; - } - - dpLoc = numStr.indexOf("."); - int length = numStr.substring(dpLoc).length(); - while (length < decimalPlaces) { - numStr += "0"; - } - return numStr; - - } - - /** - * Right aligns a {@link String} with zeros for printing - * - * @param input the String to be aligned - * @param wholeLen the total length of the padded string - * @return the padded number - */ - public String padStringRight(String input, int wholeLen) { - for (int i = input.length(); i < wholeLen; i++) { - input += " "; - } - return input; - } - - /** - * @param arr a boolean array to be represented as a string - * @return the array as a string - */ - public String boolArrToStr(boolean[] arr) { - String output = ""; - for (int i = 0; i < arr.length; i++) { - if (arr[i]) { - output += "1"; - } else { - output += "0"; - } - } - return output; - } - - /** - * Formats a double nicely for printing: THIS DOES NOT ROUND!!!! - * @param num the double to be turned into a pretty string - * @return the pretty string - */ - public String prettyNum(double num) { - String numStr = (new Double(num)).toString(); - - while (numStr.length() < 4) { - numStr += "0"; - } - - numStr = numStr.substring(0, numStr.indexOf(".") + 3); - return numStr; - } -} diff --git a/jme3-effects/build.gradle b/jme3-effects/build.gradle index 0a77d7d720..9dd715218e 100644 --- a/jme3-effects/build.gradle +++ b/jme3-effects/build.gradle @@ -1,7 +1,3 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') + api project(':jme3-core') } diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java index 1d4ad6021b..1ad0a967c8 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/BloomFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ * BloomFilter is used to make objects in the scene have a glow effect.
                * There are 2 mode : Scene and Objects.
                * Scene mode extracts the bright parts of the scene to make them glow
                - * Object mode make objects glow according to their material's glowMap or their GlowColor
                + * Object mode makes objects glow according to their material's glowMap or their GlowColor
                * See advanced:bloom_and_glow for more details * * @author Rémy Bouquet aka Nehon @@ -59,7 +59,7 @@ public class BloomFilter extends Filter { /** - * GlowMode specifies if the glow will be applied to the whole scene,or to objects that have aglow color or a glow map + * GlowMode specifies if the glow will be applied to the whole scene or to objects that have a glow color or a glow map */ public enum GlowMode { @@ -87,7 +87,7 @@ public enum GlowMode { private Pass preGlowPass; private Pass extractPass; private Pass horizontalBlur = new Pass(); - private Pass verticalalBlur = new Pass(); + private Pass verticalBlur = new Pass(); private Material extractMat; private Material vBlurMat; private Material hBlurMat; @@ -97,8 +97,8 @@ public enum GlowMode { private ViewPort viewPort; private AssetManager assetManager; - private int initalWidth; - private int initalHeight; + private int initialWidth; + private int initialHeight; /** * Creates a Bloom filter @@ -108,8 +108,9 @@ public BloomFilter() { } /** - * Creates the bloom filter with the specific glow mode - * @param glowMode + * Creates the bloom filter with the specified glow mode + * + * @param glowMode the desired mode (default=Scene) */ public BloomFilter(GlowMode glowMode) { this(); @@ -122,8 +123,8 @@ protected void initFilter(AssetManager manager, RenderManager renderManager, Vie this.viewPort = vp; this.assetManager = manager; - this.initalWidth = w; - this.initalHeight = h; + this.initialWidth = w; + this.initialHeight = h; screenWidth = (int) Math.max(1, (w / downSamplingFactor)); screenHeight = (int) Math.max(1, (h / downSamplingFactor)); @@ -174,7 +175,7 @@ public void beforeRender() { //configuring vertical blur pass vBlurMat = new Material(manager, "Common/MatDefs/Blur/VGaussianBlur.j3md"); - verticalalBlur = new Pass() { + verticalBlur = new Pass() { @Override public void beforeRender() { @@ -184,18 +185,18 @@ public void beforeRender() { } }; - verticalalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, vBlurMat); - postRenderPasses.add(verticalalBlur); + verticalBlur.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth, 1, vBlurMat); + postRenderPasses.add(verticalBlur); //final material material = new Material(manager, "Common/MatDefs/Post/BloomFinal.j3md"); - material.setTexture("BloomTex", verticalalBlur.getRenderedTexture()); + material.setTexture("BloomTex", verticalBlur.getRenderedTexture()); } protected void reInitFilter() { - initFilter(assetManager, renderManager, viewPort, initalWidth, initalHeight); + initFilter(assetManager, renderManager, viewPort, initialWidth, initialHeight); } @Override @@ -234,7 +235,8 @@ public float getBloomIntensity() { /** * intensity of the bloom effect default is 2.0 - * @param bloomIntensity + * + * @param bloomIntensity the desired intensity (default=2) */ public void setBloomIntensity(float bloomIntensity) { this.bloomIntensity = bloomIntensity; @@ -250,7 +252,8 @@ public float getBlurScale() { /** * sets The spread of the bloom default is 1.5f - * @param blurScale + * + * @param blurScale the desired scale (default=1.5) */ public void setBlurScale(float blurScale) { this.blurScale = blurScale; @@ -267,7 +270,8 @@ public float getExposureCutOff() { /** * Define the color threshold on which the bloom will be applied (0.0 to 1.0) - * @param exposureCutOff + * + * @param exposureCutOff the desired threshold (≥0, ≤1, default=0) */ public void setExposureCutOff(float exposureCutOff) { this.exposureCutOff = exposureCutOff; @@ -283,9 +287,10 @@ public float getExposurePower() { } /** - * defines how many time the bloom extracted color will be multiplied by itself. default id 5.0
                - * a high value will reduce rough edges in the bloom and somhow the range of the bloom area * - * @param exposurePower + * defines how many times the bloom extracted color will be multiplied by itself. default is 5.0
                + * a high value will reduce rough edges in the bloom and somehow the range of the bloom area + * + * @param exposurePower the desired exponent (default=5) */ public void setExposurePower(float exposurePower) { this.exposurePower = exposurePower; @@ -303,7 +308,8 @@ public float getDownSamplingFactor() { /** * Sets the downSampling factor : the size of the computed texture will be divided by this factor. default is 1 for no downsampling * A 2 value is a good way of widening the blur - * @param downSamplingFactor + * + * @param downSamplingFactor the desired factor (default=1) */ public void setDownSamplingFactor(float downSamplingFactor) { this.downSamplingFactor = downSamplingFactor; diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java index c1d5b0dfb6..ab04279633 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/CartoonEdgeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,11 +31,16 @@ */ package com.jme3.post.filters; +import java.io.IOException; + import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.post.Filter; -import com.jme3.post.Filter.Pass; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; @@ -109,8 +114,6 @@ protected void initFilter(AssetManager manager, RenderManager renderManager, Vie protected void cleanUpFilter(Renderer r) { normalPass.cleanup(r); } - - /** * Return the depth sensitivity
                @@ -124,7 +127,8 @@ public float getDepthSensitivity() { /** * sets the depth sensitivity
                * defines how much depth will influence edges, default is 10 - * @param depthSensitivity + * + * @param depthSensitivity the desired sensitivity (default=10) */ public void setDepthSensitivity(float depthSensitivity) { this.depthSensitivity = depthSensitivity; @@ -145,7 +149,8 @@ public float getDepthThreshold() { /** * sets the depth threshold
                * Defines at what threshold of difference of depth an edge is outlined default is 0.1f - * @param depthThreshold + * + * @param depthThreshold the desired threshold (default=0.1) */ public void setDepthThreshold(float depthThreshold) { this.depthThreshold = depthThreshold; @@ -165,8 +170,9 @@ public float getEdgeIntensity() { /** * sets the edge intensity
                - * Defineshow visible will be the outlined edges - * @param edgeIntensity + * Defines how visible the outlined edges will be + * + * @param edgeIntensity the desired intensity (default=1) */ public void setEdgeIntensity(float edgeIntensity) { this.edgeIntensity = edgeIntensity; @@ -184,15 +190,15 @@ public float getEdgeWidth() { } /** - * sets the witdh of the edge in pixels default is 1 - * @param edgeWidth + * sets the width of the edge in pixels default is 1 + * + * @param edgeWidth the desired width (in pixels, default=1) */ public void setEdgeWidth(float edgeWidth) { this.edgeWidth = edgeWidth; if (material != null) { material.setFloat("EdgeWidth", edgeWidth); } - } /** @@ -205,8 +211,9 @@ public float getNormalSensitivity() { } /** - * sets the normals sensitivity default is 1 - * @param normalSensitivity + * Sets the normals sensitivity. Default is 1. + * + * @param normalSensitivity the desired sensitivity (default=1) */ public void setNormalSensitivity(float normalSensitivity) { this.normalSensitivity = normalSensitivity; @@ -227,7 +234,8 @@ public float getNormalThreshold() { /** * sets the normal threshold default is 0.5 - * @param normalThreshold + * + * @param normalThreshold the desired threshold (default=0.5) */ public void setNormalThreshold(float normalThreshold) { this.normalThreshold = normalThreshold; @@ -246,7 +254,8 @@ public ColorRGBA getEdgeColor() { /** * Sets the edge color, default is black - * @param edgeColor + * + * @param edgeColor the desired color (alias created, default=(0,0,0,1)) */ public void setEdgeColor(ColorRGBA edgeColor) { this.edgeColor = edgeColor; @@ -254,4 +263,30 @@ public void setEdgeColor(ColorRGBA edgeColor) { material.setColor("EdgeColor", edgeColor); } } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(edgeWidth, "edgeWidth", 1.0f); + oc.write(edgeIntensity, "edgeIntensity", 1.0f); + oc.write(normalThreshold, "normalThreshold", 0.5f); + oc.write(depthThreshold, "depthThreshold", 0.1f); + oc.write(normalSensitivity, "normalSensitivity", 1.0f); + oc.write(depthSensitivity, "depthSensitivity", 10.0f); + oc.write(edgeColor, "edgeColor", ColorRGBA.Black); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + edgeWidth = ic.readFloat("edgeWidth", 1.0f); + edgeIntensity = ic.readFloat("edgeIntensity", 1.0f); + normalThreshold = ic.readFloat("normalThreshold", 0.5f); + depthThreshold = ic.readFloat("depthThreshold", 0.1f); + normalSensitivity = ic.readFloat("normalSensitivity", 1.0f); + depthSensitivity = ic.readFloat("depthSensitivity", 10.0f); + edgeColor = (ColorRGBA) ic.readSavable("edgeColor", ColorRGBA.Black.clone()); + } } diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java index 58f1c79411..87fd5cf02c 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ColorOverlayFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,8 @@ public ColorOverlayFilter() { /** * creates a colorOverlayFilter with the given color - * @param color + * + * @param color the desired color (default=(1,1,1,1), alias created) */ public ColorOverlayFilter(ColorRGBA color) { this(); @@ -82,8 +83,9 @@ public ColorRGBA getColor() { } /** - * sets the color - * @param color + * sets the color + * + * @param color the desired color (default=(1,1,1,1), alias created) */ public void setColor(final ColorRGBA color) { this.color = color; diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java index 401b31a6ff..bb37d58d8b 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ComposeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,8 +44,8 @@ import java.io.IOException; /** - * This filter compose a texture with the viewport texture. This is used to - * compose post processed texture from another viewport. + * This filter composes a texture with the viewport texture. This is used to + * compose post-processed texture from another viewport. * * the compositing is done using the alpha value of the viewportTexture : * mix(compositeTextureColor, viewPortColor, viewportColor.alpha); @@ -68,7 +68,7 @@ public ComposeFilter() { /** * creates a ComposeFilter with the given texture * - * @param compositeTexture + * @param compositeTexture the texture to use (alias created) */ public ComposeFilter(Texture2D compositeTexture) { this(); @@ -93,7 +93,7 @@ public Texture2D getCompositeTexture() { /** * sets the compositeTexture * - * @param compositeTexture + * @param compositeTexture the desired texture (alias created) */ public void setCompositeTexture(Texture2D compositeTexture) { this.compositeTexture = compositeTexture; diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/ContrastAdjustmentFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/ContrastAdjustmentFilter.java new file mode 100644 index 0000000000..258679cb25 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/ContrastAdjustmentFilter.java @@ -0,0 +1,434 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * A filter to adjust the colors of a rendered scene by normalizing each color channel to a specified range, + * applying a power law, and scaling the output. The alpha channel is unaffected. + * + * @author pavl_g. + */ +public class ContrastAdjustmentFilter extends Filter { + /** + * Power-law exponent for the red channel. + */ + private float redExponent = 1f; + /** + * Power-law exponent for the green channel. + */ + private float greenExponent = 1f; + /** + * Power-law exponent for the blue channel. + */ + private float blueExponent = 1f; + /** + * Lower limit of the input range for all color channels: + * the highest level that the filter normalizes to 0. + */ + private float lowerLimit = 0f; + /** + * Upper limit of the input range for all color channels: + * the level that the filter normalizes to 1 (before output scaling). + */ + private float upperLimit = 1f; + /** + * Output scale factor for the red channel. + */ + private float redScale = 1f; + /** + * Output scale factor for the green channel. + */ + private float greenScale = 1f; + /** + * Output scale factor for the blue channel. + */ + private float blueScale = 1f; + + /** + * Instantiates a contrast-adjustment filter with the default parameters: + * + *

                input range from 0 to 1 + *

                power-law exponent=1 for all color channels + *

                output scale factor=1 for all color channels + * + * Such a filter has no effect. + */ + public ContrastAdjustmentFilter() { + super("Contrast Adjustment"); + } + + /** + * Instantiates a contrast-adjustment filter with the default input range and output scaling. + * + * @param exponent the desired power-law exponent for all color channels + */ + public ContrastAdjustmentFilter(float exponent) { + this(); + this.redExponent = exponent; + this.greenExponent = exponent; + this.blueExponent = exponent; + } + + /** + * Sets the power-law exponent for each color channel. + * The default value for each exponent is 1, which produces a linear filter. + * + * @param redExponent the desired red-channel exponent + * @param greenExponent the desired green-channel exponent + * @param blueExponent the desired blue-channel exponent + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setExponents(float redExponent, float greenExponent, float blueExponent) { + setRedExponent(redExponent); + setGreenExponent(greenExponent); + setBlueExponent(blueExponent); + + return this; + } + + /** + * Sets the power-law exponent for the red channel. + * + * @param exponent the desired exponent (default=1 for linear) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setRedExponent(float exponent) { + this.redExponent = exponent; + if (material != null) { + material.setFloat("redChannelExponent", redExponent); + } + return this; + } + + /** + * Sets the power-law exponent for the green channel. + * + * @param exponent the desired exponent (default=1 for linear) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setGreenExponent(float exponent) { + this.greenExponent = exponent; + if (material != null) { + material.setFloat("greenChannelExponent", greenExponent); + } + return this; + } + + /** + * Sets the power-law exponent for the blue channel. + * + * @param exponent the desired exponent (default=1 for linear) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setBlueExponent(float exponent) { + this.blueExponent = exponent; + if (material != null) { + material.setFloat("blueChannelExponent", blueExponent); + } + return this; + } + + /** + * Retrieves the red-channel exponent. + * + * @return the power-law exponent (default=1 for linear) + */ + public float getRedExponent() { + return redExponent; + } + + /** + * Retrieves the green-channel exponent. + * + * @return the power-law exponent (default=1 for linear) + */ + public float getGreenExponent() { + return greenExponent; + } + + /** + * Retrieves the blue-channel exponent. + * + * @return the power-law exponent (default=1 for linear) + */ + public float getBlueExponent() { + return blueExponent; + } + + /** + * Sets the input range for each color channel. + * + *

                Before applying the power law, the input levels get normalized: + * lowerLimit and below normalize to 0 and upperLimit normalizes to 1. + * + * @param lowerLimit the desired lower limit (default=0) + * @param upperLimit the desired upper limit (default=1) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setInputRange(float lowerLimit, float upperLimit) { + setLowerLimit(lowerLimit); + setUpperLimit(upperLimit); + return this; + } + + /** + * Sets the upper limit of the input range. + * + * @param level the input level that should be normalized to 1 (default=1) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setUpperLimit(float level) { + this.upperLimit = level; + if (material != null) { + material.setFloat("upperLimit", upperLimit); + } + return this; + } + + /** + * Sets the lower limit of the input range. + * + * @param level the highest input level that should be normalized to 0 (default=0) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setLowerLimit(float level) { + this.lowerLimit = level; + if (material != null) { + material.setFloat("lowerLimit", lowerLimit); + } + return this; + } + + /** + * Returns the lower limit of the input range. + * + * @return the input level (default=0) + */ + public float getLowerLimit() { + return lowerLimit; + } + + /** + * Returns the upper limit of the input range. + * + * @return the input level (default=1) + */ + public float getUpperLimit() { + return upperLimit; + } + + /** + * Adjusts the output scaling for each color channel. + * The default for each scale factor is 1, which has no effect. + * + * @param redScale the red-channel scale factor + * @param greenScale the green-channel scale factor + * @param blueScale the blue-channel scale factor + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setScales(float redScale, float greenScale, float blueScale) { + setRedScale(redScale); + setGreenScale(greenScale); + setBlueScale(blueScale); + + return this; + } + + /** + * Sets the output scale factor for the red channel. + * + * @param factor the desired scale factor (default=1) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setRedScale(float factor) { + this.redScale = factor; + if (material != null) { + material.setFloat("redChannelScale", redScale); + } + return this; + } + + /** + * Sets the output scale factor for the green channel. + * + * @param factor the desired scale factor (default=1) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setGreenScale(float factor) { + this.greenScale = factor; + if (material != null) { + material.setFloat("greenChannelScale", greenScale); + } + return this; + } + + /** + * Sets the output scale factor for the blue channel. + * + * @param factor the desired scale factor (default=1) + * @return this filter instance, for chaining + */ + public ContrastAdjustmentFilter setBlueScale(float factor) { + this.blueScale = factor; + if (material != null) { + material.setFloat("blueChannelScale", blueScale); + } + return this; + } + + /** + * Retrieves the output scale factor for the red channel. + * + * @return the scale factor (default=1 for no effect) + */ + public float getRedScale() { + return redScale; + } + + /** + * Retrieves the output scale factor for the green channel. + * + * @return the scale factor (default=1 for no effect) + */ + public float getGreenScale() { + return greenScale; + } + + /** + * Retrieves the output scale factor for the blue channel. + * + * @return the scale factor (default=1 for no effect) + */ + public float getBlueScale() { + return blueScale; + } + + /** + * Initializes the Filter when it is added to a FilterPostProcessor. + * + * @param assetManager for loading assets (not null) + * @param renderManager unused + * @param viewPort unused + * @param width unused + * @param height unused + */ + @Override + protected void initFilter(AssetManager assetManager, RenderManager renderManager, + ViewPort viewPort, int width, int height) { + material = new Material(assetManager, "Common/MatDefs/Post/ContrastAdjustment.j3md"); + + //different channels exp for different transfer functions + setExponents(redExponent, greenExponent, blueExponent); + + //input range + setInputRange(lowerLimit, upperLimit); + + //final pass scales + setScales(redScale, greenScale, blueScale); + } + + /** + * Returns the Material used in this Filter. This method is invoked on every frame. + * + * @return the pre-existing instance, or null if the Filter hasn't been initialized + */ + @Override + protected Material getMaterial() { + return material; + } + + /** + * De-serializes this filter, for example when loading from a J3O file. + * + * @param im the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + final InputCapsule inputCapsule = im.getCapsule(this); + redExponent = inputCapsule.readFloat("redExponent", 1f); + greenExponent = inputCapsule.readFloat("greenExponent", 1f); + blueExponent = inputCapsule.readFloat("blueExponent", 1f); + lowerLimit = inputCapsule.readFloat("lowerLimit", 0f); + upperLimit = inputCapsule.readFloat("upperLimit", 1f); + redScale = inputCapsule.readFloat("redScale", 1f); + greenScale = inputCapsule.readFloat("greenScale", 1f); + blueScale = inputCapsule.readFloat("blueScale", 1f); + } + + /** + * Serializes this filter, for example when saving to a J3O file. + * + * @param ex the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + final OutputCapsule outputCapsule = ex.getCapsule(this); + outputCapsule.write(redExponent, "redExponent", 1f); + outputCapsule.write(greenExponent, "greenExponent", 1f); + outputCapsule.write(blueExponent, "blueExponent", 1f); + outputCapsule.write(lowerLimit, "lowerLimit", 0f); + outputCapsule.write(upperLimit, "upperLimit", 1f); + outputCapsule.write(redScale, "redScale", 1f); + outputCapsule.write(greenScale, "greenScale", 1f); + outputCapsule.write(blueScale, "blueScale", 1f); + } + + /** + * Represent this Filter as a String. + * + * @return a descriptive string of text (not null) + */ + @Override + public String toString() { + String result = String.format( + "input(%.3f, %.3f) exp(%.3f, %.3f, %.3f) scale(%.3f, %.3f, %.3f)", + lowerLimit, upperLimit, + redExponent, greenExponent, blueExponent, + redScale, greenScale, blueScale); + return result; + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java index 9e94513556..2acc5f9620 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/CrossHatchFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,11 +32,16 @@ package com.jme3.post.filters; import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.post.Filter; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; +import java.io.IOException; /** * A Post Processing filter that makes the screen look like it was drawn as @@ -44,7 +49,7 @@ * Try combining this with a cartoon edge filter to obtain manga style visuals. * * Based on an article from Geeks3D: - * http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/ + * http://www.geeks3d.com/20110219/shader-library-crosshatching-glsl-filter/ * * @author Roy Straver a.k.a. Baal Garnaal */ @@ -114,7 +119,8 @@ protected Material getMaterial() { /** * Sets color used to draw lines - * @param lineColor + * + * @param lineColor the desired color (alias created, default=(0,0,0,1)) */ public void setLineColor(ColorRGBA lineColor) { this.lineColor = lineColor; @@ -125,7 +131,8 @@ public void setLineColor(ColorRGBA lineColor) { /** * Sets color used as background - * @param paperColor + * + * @param paperColor the desired color (alias created, default=(1,1,1,1)) */ public void setPaperColor(ColorRGBA paperColor) { this.paperColor = paperColor; @@ -136,7 +143,8 @@ public void setPaperColor(ColorRGBA paperColor) { /** * Sets color influence of original image on lines drawn - * @param colorInfluenceLine + * + * @param colorInfluenceLine the desired factor (default=0.8) */ public void setColorInfluenceLine(float colorInfluenceLine) { this.colorInfluenceLine = colorInfluenceLine; @@ -147,7 +155,8 @@ public void setColorInfluenceLine(float colorInfluenceLine) { /** * Sets color influence of original image on non-line areas - * @param colorInfluencePaper + * + * @param colorInfluencePaper the desired factor (default=0.1) */ public void setColorInfluencePaper(float colorInfluencePaper) { this.colorInfluencePaper = colorInfluencePaper; @@ -159,7 +168,8 @@ public void setColorInfluencePaper(float colorInfluencePaper) { /** * Sets line/paper color ratio for areas with values less than luminance5, * really dark areas get no lines but a filled blob instead - * @param fillValue + * + * @param fillValue the desired ratio (default=0.9) */ public void setFillValue(float fillValue) { this.fillValue = fillValue; @@ -171,9 +181,9 @@ public void setFillValue(float fillValue) { /** * * Sets minimum luminance levels for lines drawn - * @param luminance1 Top-left to down right 1 + * @param luminance1 Top-left to bottom right 1 * @param luminance2 Top-right to bottom left 1 - * @param luminance3 Top-left to down right 2 + * @param luminance3 Top-left to bottom right 2 * @param luminance4 Top-right to bottom left 2 * @param luminance5 Blobs */ @@ -195,7 +205,8 @@ public void setLuminanceLevels(float luminance1, float luminance2, float luminan /** * Sets the thickness of lines drawn - * @param lineThickness + * + * @param lineThickness the desired thickness (in pixels, default=1) */ public void setLineThickness(float lineThickness) { this.lineThickness = lineThickness; @@ -208,7 +219,8 @@ public void setLineThickness(float lineThickness) { * Sets minimum distance between lines drawn * Primary lines are drawn at 2*lineDistance * Secondary lines are drawn at lineDistance - * @param lineDistance + * + * @param lineDistance the desired distance (in pixels, default=4) */ public void setLineDistance(float lineDistance) { this.lineDistance = lineDistance; @@ -235,6 +247,8 @@ public ColorRGBA getPaperColor() { /** * Returns current influence of image colors on lines + * + * @return the influence factor */ public float getColorInfluenceLine() { return colorInfluenceLine; @@ -242,6 +256,8 @@ public float getColorInfluenceLine() { /** * Returns current influence of image colors on paper background + * + * @return the influence factor */ public float getColorInfluencePaper() { return colorInfluencePaper; @@ -249,6 +265,8 @@ public float getColorInfluencePaper() { /** * Returns line/paper color ratio for blobs + * + * @return the ratio */ public float getFillValue() { return fillValue; @@ -256,6 +274,8 @@ public float getFillValue() { /** * Returns the thickness of the lines drawn + * + * @return the thickness (in pixels) */ public float getLineThickness() { return lineThickness; @@ -263,6 +283,8 @@ public float getLineThickness() { /** * Returns minimum distance between lines + * + * @return the distance (in pixels) */ public float getLineDistance() { return lineDistance; @@ -270,6 +292,8 @@ public float getLineDistance() { /** * Returns threshold for lines 1 + * + * @return the first luminance threshold */ public float getLuminance1() { return luminance1; @@ -277,6 +301,8 @@ public float getLuminance1() { /** * Returns threshold for lines 2 + * + * @return the 2nd luminance threshold */ public float getLuminance2() { return luminance2; @@ -284,6 +310,8 @@ public float getLuminance2() { /** * Returns threshold for lines 3 + * + * @return the 3rd luminance threshold */ public float getLuminance3() { return luminance3; @@ -291,6 +319,8 @@ public float getLuminance3() { /** * Returns threshold for lines 4 + * + * @return the 4th luminance threshold */ public float getLuminance4() { return luminance4; @@ -298,8 +328,64 @@ public float getLuminance4() { /** * Returns threshold for blobs + * + * @return the 5th luminance threshold */ public float getLuminance5() { return luminance5; } -} \ No newline at end of file + + /** + * Load properties when the filter is de-serialized, for example when + * loading from a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + this.colorInfluenceLine = capsule.readFloat("colorInfluenceLine", 0.8f); + this.colorInfluencePaper = capsule.readFloat("colorInfluencePaper", 0.1f); + this.fillValue = capsule.readFloat("fillValue", 0.9f); + this.lineColor = (ColorRGBA) capsule.readSavable( + "lineColor", new ColorRGBA(0f, 0f, 0f, 0f)); + this.lineDistance = capsule.readFloat("lineDistance", 4f); + this.lineThickness = capsule.readFloat("lineThickness", 1f); + this.luminance1 = capsule.readFloat("luminance1", 0.9f); + this.luminance2 = capsule.readFloat("luminance2", 0.7f); + this.luminance3 = capsule.readFloat("luminance3", 0.5f); + this.luminance4 = capsule.readFloat("luminance4", 0.3f); + this.luminance5 = capsule.readFloat("luminance5", 0f); + this.paperColor = (ColorRGBA) capsule.readSavable( + "paperColor", new ColorRGBA(1f, 1f, 1f, 1f)); + } + + /** + * Save properties when the filter is serialized, for example when saving to + * a J3O file. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(colorInfluenceLine, "colorInfluenceLine", 0.8f); + capsule.write(colorInfluencePaper, "colorInfluencePaper", 0.1f); + capsule.write(fillValue, "fillValue", 0.9f); + capsule.write(lineColor, "lineColor", new ColorRGBA(0f, 0f, 0f, 0f)); + capsule.write(lineDistance, "lineDistance", 4f); + capsule.write(lineThickness, "lineThickness", 1f); + capsule.write(luminance1, "luminance1", 0.9f); + capsule.write(luminance2, "luminance2", 0.7f); + capsule.write(luminance3, "luminance3", 0.5f); + capsule.write(luminance4, "luminance4", 0.3f); + capsule.write(luminance5, "luminance5", 0f); + capsule.write(paperColor, "paperColor", new ColorRGBA(1f, 1f, 1f, 1f)); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java index 31ca381237..6174332699 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/DepthOfFieldFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -98,6 +98,8 @@ protected void initFilter(AssetManager assets, RenderManager renderManager, /** * Sets the distance at which objects are purely in focus. + * + * @param f the desired distance (in world units, default=50) */ public void setFocusDistance(float f) { @@ -120,6 +122,8 @@ public float getFocusDistance() { * Sets the range to either side of focusDistance where the * objects go gradually out of focus. Less than focusDistance - focusRange * and greater than focusDistance + focusRange, objects are maximally "blurred". + * + * @param f the desired half extent (in world units, default=10) */ public void setFocusRange(float f) { this.focusRange = f; @@ -148,6 +152,8 @@ public float getFocusRange() { *%MINIFYHTMLc3d0cd9fab65de6875a381fd3f83e1b338%* * Where 'x' is the texel being modified. Setting blur scale higher * than 1 spaces the samples out. + * + * @param f the desired filter scale (default=1) */ public void setBlurScale(float f) { this.blurScale = f; @@ -178,6 +184,8 @@ public float getBlurScale() { * the near-camera blurring and should be set smaller than the default * or to 0 to disable completely. Sometimes that cut-off is desired if * mid-to-far field unfocusing is all that is desired.

                + * + * @param f the desired blur factor (default=0.2) */ public void setBlurThreshold( float f ) { this.blurThreshold = f; @@ -199,7 +207,9 @@ public float getBlurThreshold() { * mix the convolution filter. When this is on, the 'unfocus' value * is rendered as gray scale. This can be used to more easily visualize * where in your view the focus is centered and how steep the gradient/cutoff - * is, etc.. + * is, etcetera. + * + * @param b true to enable debugging, false to disable it (default=false) */ public void setDebugUnfocus( boolean b ) { this.debugUnfocus = b; @@ -217,7 +227,7 @@ public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule oc = ex.getCapsule(this); oc.write(blurScale, "blurScale", 1f); - oc.write(blurScale, "blurThreshold", 0.2f); + oc.write(blurThreshold, "blurThreshold", 0.2f); oc.write(focusDistance, "focusDistance", 50f); oc.write(focusRange, "focusRange", 10f); oc.write(debugUnfocus, "debugUnfocus", false); // strange to write this I guess @@ -233,4 +243,4 @@ public void read(JmeImporter im) throws IOException { focusRange = ic.readFloat("focusRange", 10f); debugUnfocus = ic.readBoolean("debugUnfocus", false); } -} +} \ No newline at end of file diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java index 37fec3bcda..ba82c75f53 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FXAAFilter.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 @@ -32,14 +32,19 @@ package com.jme3.post.filters; import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.post.Filter; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; +import java.io.IOException; /** - * http://www.geeks3d.com/20110405/fxaa-fast-approximate-anti-aliasing-demo-glsl-opengl-test-radeon-geforce/3/ - * http://developer.download.nvidia.com/assets/gamedev/files/sdk/11/FXAA_WhitePaper.pdf + * demo + * whitepaper * * @author Phate666 (adapted to jme3) * @@ -86,7 +91,7 @@ public void setSpanMax(float spanMax) { /** * set to 0.0f for higher quality * - * @param subPixelShift + * @param subPixelShift the desired shift (default=0.25) */ public void setSubPixelShift(float subPixelShift) { this.subPixelShift = subPixelShift; @@ -98,7 +103,7 @@ public void setSubPixelShift(float subPixelShift) { /** * set to 0.0f for higher quality * - * @param reduceMul + * @param reduceMul the desired value (default=0.125) */ public void setReduceMul(float reduceMul) { this.reduceMul = reduceMul; @@ -129,4 +134,40 @@ public float getSubPixelShift() { public float getVxOffset() { return vxOffset; } -} \ No newline at end of file + + /** + * Load properties when the filter is de-serialized, for example when + * loading from a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + this.reduceMul = capsule.readFloat("reduceMul", 0.125f); + this.spanMax = capsule.readFloat("spanMax", 8f); + this.subPixelShift = capsule.readFloat("subPixelShift", 0.25f); + this.vxOffset = capsule.readFloat("vxOffset", 0f); + } + + /** + * Save properties when the filter is serialized, for example when saving to + * a J3O file. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(reduceMul, "reduceMul", 0.125f); + capsule.write(spanMax, "spanMax", 8f); + capsule.write(subPixelShift, "subPixelShift", 0.25f); + capsule.write(vxOffset, "vxOffset", 0f); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java index 8864f77576..96679cc1f8 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FadeFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,7 +65,8 @@ public FadeFilter() { /** * Creates a FadeFilter with the given duration - * @param duration + * + * @param duration the desired duration (in seconds, default=1) */ public FadeFilter(float duration) { this(); @@ -109,7 +110,8 @@ public float getDuration() { /** * Sets the duration of the filter default is 1 second - * @param duration + * + * @param duration the desired duration (in seconds, default=1) */ public void setDuration(float duration) { this.duration = duration; @@ -153,8 +155,8 @@ public void read(JmeImporter im) throws IOException { } /** - * return the current value of the fading - * can be used to check if fade is complete (eg value=1) + * Returns the current fade value. + * Can be used to check whether fade is complete (e.g. value=1). * @return the fractional progress (≥0, ≤1) */ public float getValue() { @@ -162,9 +164,10 @@ public float getValue() { } /** - * sets the fade value - * can be used to force complete black or compete scene - * @param value + * Sets the fade value. + * Can be used to force all black or all scene. + * + * @param value the desired value (default=1) */ public void setValue(float value) { this.value = value; diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java index 089dec5571..bd8364256a 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/FogFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -103,7 +103,8 @@ public ColorRGBA getFogColor() { /** * Sets the color of the fog - * @param fogColor + * + * @param fogColor the desired color (alias created, default=(1,1,1,1)) */ public void setFogColor(ColorRGBA fogColor) { if (material != null) { @@ -122,7 +123,8 @@ public float getFogDensity() { /** * Sets the density of the fog, a high value gives a thick fog - * @param fogDensity + * + * @param fogDensity the desired density (default=0.7) */ public void setFogDensity(float fogDensity) { if (material != null) { @@ -140,8 +142,9 @@ public float getFogDistance() { } /** - * the distance of the fog. the higer the value the distant the fog looks - * @param fogDistance + * the distance of the fog. the higher the value the distant the fog looks + * + * @param fogDistance the desired distance (in world units, default=1000) */ public void setFogDistance(float fogDistance) { if (material != null) { diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java index 24e5217a4c..df29f9ed6c 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/GammaCorrectionFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ * * @author Phate666 * @version 1.0 initial version - * @deprecated use the Gama Correction setting instead. + * @deprecated use the Gamma Correction setting instead. */ @Deprecated public class GammaCorrectionFilter extends Filter { @@ -76,7 +76,7 @@ public float getGamma() { /** * set to 0.0 to disable gamma correction * - * @param gamma + * @param gamma the desired exponent (>0, default=2.2) */ public final void setGamma(float gamma) { if(gamma<=0){ diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java new file mode 100644 index 0000000000..a93add502d --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/KHRToneMapFilter.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import java.io.IOException; + +/** + * Tone-mapping filter that uses khronos neutral pbr tone mapping curve. + */ +public class KHRToneMapFilter extends Filter { + + private static final float DEFAULT_EXPOSURE = 0.0f; + private static final float DEFAULT_GAMMA = 1.0f; + + private final Vector3f exposure = new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE); + private final Vector3f gamma = new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA); + + /** + * Creates a tone-mapping filter with the default exposure and gamma. + */ + public KHRToneMapFilter() { + super("KHRToneMapFilter"); + } + + + @Override + protected boolean isRequiresDepthTexture() { + return false; + } + + @Override + protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + material = new Material(manager, "Common/MatDefs/Post/KHRToneMap.j3md"); + material.setVector3("Exposure", exposure); + material.setVector3("Gamma", gamma); + } + + @Override + protected Material getMaterial() { + return material; + } + + /** + * Set the exposure for the tone mapping. + * + * @param whitePoint The exposure vector. + */ + public void setExposure(Vector3f whitePoint) { + this.exposure.set(whitePoint); + } + + /** + * Get the exposure for the tone mapping. + * + * @return The exposure vector. + */ + public Vector3f getExposure() { + return exposure; + } + + + /** + * Set the gamma for the tone mapping. + * + * @param gamma The gamma vector. + */ + public void setGamma(Vector3f gamma) { + this.gamma.set(gamma); + } + + /** + * Get the gamma for the tone mapping. + * + * @return The gamma vector. + */ + public Vector3f getGamma() { + return gamma; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(exposure, "exposure", new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE)); + oc.write(gamma, "gamma", new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA)); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + exposure.set((Vector3f)ic.readSavable("exposure", new Vector3f(DEFAULT_EXPOSURE, DEFAULT_EXPOSURE, DEFAULT_EXPOSURE))); + gamma.set((Vector3f)ic.readSavable("gamma", new Vector3f(DEFAULT_GAMMA, DEFAULT_GAMMA, DEFAULT_GAMMA))); + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java index 845b1b8372..e09eab116a 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/LightScatteringFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,7 +60,7 @@ public class LightScatteringFilter extends Filter { private float blurStart = 0.02f; private float blurWidth = 0.9f; private float lightDensity = 1.4f; - private boolean adaptative = true; + private boolean adaptive = true; Vector3f viewLightPos = new Vector3f(); private boolean display = true; private float innerLightDensity; @@ -75,7 +75,9 @@ public LightScatteringFilter() { /** * Creates a lightScatteringFilter - * @param lightPosition + * + * @param lightPosition the desired location (in world coordinates, alias + * created) */ public LightScatteringFilter(Vector3f lightPosition) { this(); @@ -102,7 +104,7 @@ protected Material getMaterial() { protected void postQueue(RenderQueue queue) { getClipCoordinates(lightPosition, screenLightPos, viewPort.getCamera()); viewPort.getCamera().getViewMatrix().mult(lightPosition, viewLightPos); - if (adaptative) { + if (adaptive) { float densityX = 1f - FastMath.clamp(FastMath.abs(screenLightPos.x - 0.5f), 0, 1); float densityY = 1f - FastMath.clamp(FastMath.abs(screenLightPos.y - 0.5f), 0, 1); innerLightDensity = lightDensity * densityX * densityY; @@ -142,7 +144,9 @@ public float getBlurStart() { /** * sets the blur start
                * at which distance from the light source the effect starts default is 0.02 - * @param blurStart + * + * @param blurStart the desired start distance (in world units, + * default=0.02) */ public void setBlurStart(float blurStart) { this.blurStart = blurStart; @@ -159,7 +163,8 @@ public float getBlurWidth() { /** * sets the blur width default is 0.9 - * @param blurWidth + * + * @param blurWidth the desired width (default=0.9) */ public void setBlurWidth(float blurWidth) { this.blurWidth = blurWidth; @@ -177,7 +182,8 @@ public float getLightDensity() { /** * sets how much the effect is visible over the rendered scene default is 1.4 - * @param lightDensity + * + * @param lightDensity the desired density (default=1.4) */ public void setLightDensity(float lightDensity) { this.lightDensity = lightDensity; @@ -193,7 +199,9 @@ public Vector3f getLightPosition() { /** * sets the light position - * @param lightPosition + * + * @param lightPosition the desired location (in world coordinates, alias + * created) */ public void setLightPosition(Vector3f lightPosition) { this.lightPosition = lightPosition; @@ -210,7 +218,8 @@ public int getNbSamples() { /** * sets the number of samples for the radial blur default is 50 * the higher the value the higher the quality, but the slower the performance. - * @param nbSamples + * + * @param nbSamples the desired number of samples (default=50) */ public void setNbSamples(int nbSamples) { this.nbSamples = nbSamples; @@ -225,7 +234,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(blurStart, "blurStart", 0.02f); oc.write(blurWidth, "blurWidth", 0.9f); oc.write(lightDensity, "lightDensity", 1.4f); - oc.write(adaptative, "adaptative", true); + oc.write(adaptive, "adaptative", true); } @Override @@ -237,6 +246,6 @@ public void read(JmeImporter im) throws IOException { blurStart = ic.readFloat("blurStart", 0.02f); blurWidth = ic.readFloat("blurWidth", 0.9f); lightDensity = ic.readFloat("lightDensity", 1.4f); - adaptative = ic.readBoolean("adaptative", true); + adaptive = ic.readBoolean("adaptative", true); } } diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java index f3400c5537..1230c5b4b3 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/PosterizationFilter.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 @@ -32,10 +32,15 @@ package com.jme3.post.filters; import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; import com.jme3.material.Material; import com.jme3.post.Filter; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; +import java.io.IOException; /** * A Post Processing filter to change colors appear with sharp edges as if the @@ -45,7 +50,7 @@ * to give nice results. * * Based on an article from Geeks3D: - * http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/ + * http://www.geeks3d.com/20091027/shader-library-posterization-post-processing-effect-glsl/ * * @author Roy Straver a.k.a. Baal Garnaal */ @@ -64,7 +69,8 @@ public PosterizationFilter() { /** * Creates a posterization Filter with the given number of colors - * @param numColors + * + * @param numColors the desired number of colors (>0, default=8) */ public PosterizationFilter(int numColors) { this(); @@ -73,8 +79,9 @@ public PosterizationFilter(int numColors) { /** * Creates a posterization Filter with the given number of colors and gamma - * @param numColors - * @param gamma + * + * @param numColors the desired number of colors (>0, default=8) + * @param gamma the desired exponent (default=0.6) */ public PosterizationFilter(int numColors, float gamma) { this(numColors); @@ -96,6 +103,8 @@ protected Material getMaterial() { /** * Sets number of color levels used to draw the screen + * + * @param numColors the desired number of colors (>0, default=8) */ public void setNumColors(int numColors) { this.numColors = numColors; @@ -105,7 +114,9 @@ public void setNumColors(int numColors) { } /** - * Sets gamma level used to enhange visual quality + * Sets gamma level used to enhance visual quality + * + * @param gamma the desired exponent (default=0.6) */ public void setGamma(float gamma) { this.gamma = gamma; @@ -116,6 +127,8 @@ public void setGamma(float gamma) { /** * Sets current strength value, i.e. influence on final image + * + * @param strength the desired influence factor (default=1) */ public void setStrength(float strength) { this.strength = strength; @@ -126,6 +139,8 @@ public void setStrength(float strength) { /** * Returns number of color levels used + * + * @return the count (>0) */ public int getNumColors() { return numColors; @@ -133,6 +148,8 @@ public int getNumColors() { /** * Returns current gamma value + * + * @return the exponent */ public float getGamma() { return gamma; @@ -140,8 +157,44 @@ public float getGamma() { /** * Returns current strength value, i.e. influence on final image + * + * @return the influence factor */ public float getStrength() { return strength; } -} \ No newline at end of file + + /** + * Load properties when the filter is de-serialized, for example when + * loading from a J3O file. + * + * @param importer the importer to use (not null) + * @throws IOException from the importer + */ + @Override + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); + + this.gamma = capsule.readFloat("gamma", 0.6f); + this.numColors = capsule.readInt("numColors", 8); + this.strength = capsule.readFloat("strength", 1f); + } + + /** + * Save properties when the filter is serialized, for example when saving to + * a J3O file. + * + * @param exporter the exporter to use (not null) + * @throws IOException from the exporter + */ + @Override + public void write(JmeExporter exporter) throws IOException { + super.write(exporter); + OutputCapsule capsule = exporter.getCapsule(this); + + capsule.write(gamma, "gamma", 0.6f); + capsule.write(numColors, "numColors", 8); + capsule.write(strength, "strength", 1f); + } +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java index c79339848f..3c70e23d12 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/RadialBlurFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,7 +89,8 @@ public float getSampleDistance() { /** * sets the samples distances default is 1 - * @param sampleDist + * + * @param sampleDist the desired distance (default=1) */ public void setSampleDistance(float sampleDist) { this.sampleDist = sampleDist; @@ -107,7 +108,7 @@ public float getSampleDist() { /** * - * @param sampleDist + * @param sampleDist the desired distance (default=1) * @deprecated use {@link #setSampleDistance(float sampleDist)} */ @Deprecated @@ -125,7 +126,8 @@ public float getSampleStrength() { /** * sets the sample strength default is 2.2 - * @param sampleStrength + * + * @param sampleStrength the desired strength (default=2.2) */ public void setSampleStrength(float sampleStrength) { this.sampleStrength = sampleStrength; diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java new file mode 100644 index 0000000000..3ccbf03ff9 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/SoftBloomFilter.java @@ -0,0 +1,337 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.post.filters; + +import com.jme3.asset.AssetManager; +import com.jme3.export.InputCapsule; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.OutputCapsule; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.post.Filter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.Renderer; +import com.jme3.renderer.ViewPort; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import java.io.IOException; +import java.util.logging.Logger; +import java.util.logging.Level; +import java.util.LinkedList; + +/** + * Adds a glow effect to the scene. + *

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

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

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

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

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

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

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

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

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

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

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

                + * A message will be logged if the number of sampling passes is changed. + * + * @param w texture width + * @param h texture height + */ + private void capPassesToSize(int w, int h) { + int limit = Math.min(w, h); + for (int i = 0; i < numSamplingPasses; i++) { + limit = limit >> 1; + if (limit <= 2) { + numSamplingPasses = i; + logger.log(Level.INFO, "Number of sampling passes capped at {0} due to texture size.", i); + break; + } + } + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(numSamplingPasses, "numSamplingPasses", 5); + oc.write(glowFactor, "glowFactor", 0.05f); + oc.write(bilinearFiltering, "bilinearFiltering", true); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + numSamplingPasses = ic.readInt("numSamplingPasses", 5); + glowFactor = ic.readFloat("glowFactor", 0.05f); + bilinearFiltering = ic.readBoolean("bilinearFiltering", true); + } + +} diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java index 743c0abea9..1519dec4cd 100644 --- a/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/filters/TranslucentBucketFilter.java @@ -50,7 +50,7 @@ /** * A filter to handle translucent objects when rendering a scene with filters that uses depth like WaterFilter and SSAOFilter - * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostPorcessor + * just create a TranslucentBucketFilter and add it to the Filter list of a FilterPostProcessor * @author Nehon */ public final class TranslucentBucketFilter extends Filter { @@ -124,7 +124,7 @@ protected boolean isRequiresDepthTexture() { protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { renderManager.setCamera(viewPort.getCamera(), false); if (prevFilterBuffer != sceneBuffer) { - renderManager.getRenderer().copyFrameBuffer(prevFilterBuffer, sceneBuffer, false); + renderManager.getRenderer().copyFrameBuffer(prevFilterBuffer, sceneBuffer, true, false); } renderManager.getRenderer().setFrameBuffer(sceneBuffer); viewPort.getQueue().renderQueue(RenderQueue.Bucket.Translucent, renderManager, viewPort.getCamera()); @@ -172,12 +172,16 @@ private void makeSoftParticleEmitter(Spatial scene, boolean enabled) { emitter.getMaterial().setTexture("DepthTexture", processor.getDepthTexture()); emitter.setQueueBucket(RenderQueue.Bucket.Translucent); - logger.log(Level.FINE, "Made particle Emitter {0} soft.", emitter.getName()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Made particle Emitter {0} soft.", emitter.getName()); + } } else { emitter.getMaterial().clearParam("DepthTexture"); emitter.getMaterial().selectTechnique("Default", renderManager); // emitter.setQueueBucket(RenderQueue.Bucket.Transparent); - logger.log(Level.FINE, "Particle Emitter {0} is not soft anymore.", emitter.getName()); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Particle Emitter {0} is not soft anymore.", emitter.getName()); + } } } } diff --git a/jme3-effects/src/main/java/com/jme3/post/filters/package-info.java b/jme3-effects/src/main/java/com/jme3/post/filters/package-info.java new file mode 100644 index 0000000000..a02a20531c --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/filters/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * standard special-effects filters + */ +package com.jme3.post.filters; diff --git a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java index 611f9954f0..58a12570d0 100644 --- a/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java +++ b/jme3-effects/src/main/java/com/jme3/post/ssao/SSAOFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,6 @@ import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.post.Filter; -import com.jme3.post.Filter.Pass; import com.jme3.renderer.RenderManager; import com.jme3.renderer.Renderer; import com.jme3.renderer.ViewPort; @@ -53,7 +52,7 @@ /** * SSAO stands for screen space ambient occlusion - * It's a technique that fakes ambient lighting by computing shadows that near by objects would casts on each others + * It's a technique that fakes ambient lighting by computing shadows that nearby objects would cast on each other. * under the effect of an ambient light * more info on this in this blog post http://jmonkeyengine.org/2010/08/16/screen-space-ambient-occlusion-for-jmonkeyengine-3-0/ * @@ -62,23 +61,22 @@ public class SSAOFilter extends Filter { private Pass normalPass; - private Vector3f frustumCorner; - private Vector2f frustumNearFar; - private Vector2f[] samples = {new Vector2f(1.0f, 0.0f), new Vector2f(-1.0f, 0.0f), new Vector2f(0.0f, 1.0f), new Vector2f(0.0f, -1.0f)}; + private final Vector2f[] samples = { + new Vector2f(1.0f, 0.0f), + new Vector2f(-1.0f, 0.0f), + new Vector2f(0.0f, 1.0f), + new Vector2f(0.0f, -1.0f) + }; private float sampleRadius = 5.1f; private float intensity = 1.5f; private float scale = 0.2f; private float bias = 0.1f; + private boolean approximateNormals = false; private boolean useOnlyAo = false; private boolean useAo = true; private Material ssaoMat; - private Pass ssaoPass; -// private Material downSampleMat; -// private Pass downSamplePass; - private float downSampleFactor = 1f; private RenderManager renderManager; private ViewPort viewPort; - private boolean approximateNormals = false; /** * Create a Screen Space Ambient Occlusion Filter @@ -89,10 +87,11 @@ public SSAOFilter() { /** * Create a Screen Space Ambient Occlusion Filter + * * @param sampleRadius The radius of the area where random samples will be picked. default 5.1f - * @param intensity intensity of the resulting AO. default 1.2f - * @param scale distance between occluders and occludee. default 0.2f - * @param bias the width of the occlusion cone considered by the occludee. default 0.1f + * @param intensity intensity of the resulting AO. default 1.5f + * @param scale distance between occluders and occludee. default 0.2f + * @param bias the width of the occlusion cone considered by the occludee. default 0.1f */ public SSAOFilter(float sampleRadius, float intensity, float scale, float bias) { this(); @@ -126,38 +125,32 @@ protected Material getMaterial() { } @Override - protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { + protected void initFilter(AssetManager assetManager, RenderManager renderManager, ViewPort vp, int w, int h) { this.renderManager = renderManager; this.viewPort = vp; int screenWidth = w; int screenHeight = h; + float downSampleFactor = 1f; postRenderPasses = new ArrayList(); normalPass = new Pass(); normalPass.init(renderManager.getRenderer(), (int) (screenWidth / downSampleFactor), (int) (screenHeight / downSampleFactor), Format.RGBA8, Format.Depth); - - frustumNearFar = new Vector2f(); - + Vector2f frustumNearFar = new Vector2f(); float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar(); - float farX = farY * ((float) screenWidth / (float) screenHeight); - frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar()); + float farX = farY * (screenWidth / (float) screenHeight); + Vector3f frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar()); frustumNearFar.x = vp.getCamera().getFrustumNear(); frustumNearFar.y = vp.getCamera().getFrustumFar(); - - - - //ssao Pass - ssaoMat = new Material(manager, "Common/MatDefs/SSAO/ssao.j3md"); + ssaoMat = new Material(assetManager, "Common/MatDefs/SSAO/ssao.j3md"); ssaoMat.setTexture("Normals", normalPass.getRenderedTexture()); - Texture random = manager.loadTexture("Common/MatDefs/SSAO/Textures/random.png"); + Texture random = assetManager.loadTexture("Common/MatDefs/SSAO/Textures/random.png"); random.setWrap(Texture.WrapMode.Repeat); ssaoMat.setTexture("RandomMap", random); - ssaoPass = new Pass("SSAO pass") { - + Pass ssaoPass = new Pass("SSAO pass") { @Override public boolean requiresDepthAsTexture() { return true; @@ -168,18 +161,18 @@ public boolean requiresDepthAsTexture() { // ssaoPass.getRenderedTexture().setMinFilter(Texture.MinFilter.Trilinear); // ssaoPass.getRenderedTexture().setMagFilter(Texture.MagFilter.Bilinear); postRenderPasses.add(ssaoPass); - material = new Material(manager, "Common/MatDefs/SSAO/ssaoBlur.j3md"); + material = new Material(assetManager, "Common/MatDefs/SSAO/ssaoBlur.j3md"); material.setTexture("SSAOMap", ssaoPass.getRenderedTexture()); + material.setVector2("FrustumNearFar", frustumNearFar); + material.setBoolean("UseAo", useAo); + material.setBoolean("UseOnlyAo", useOnlyAo); ssaoMat.setVector3("FrustumCorner", frustumCorner); ssaoMat.setFloat("SampleRadius", sampleRadius); ssaoMat.setFloat("Intensity", intensity); ssaoMat.setFloat("Scale", scale); ssaoMat.setFloat("Bias", bias); - material.setBoolean("UseAo", useAo); - material.setBoolean("UseOnlyAo", useOnlyAo); ssaoMat.setVector2("FrustumNearFar", frustumNearFar); - material.setVector2("FrustumNearFar", frustumNearFar); ssaoMat.setParam("Samples", VarType.Vector2Array, samples); ssaoMat.setBoolean("ApproximateNormals", approximateNormals); @@ -189,7 +182,6 @@ public boolean requiresDepthAsTexture() { float blurScale = 2f; material.setFloat("XScale", blurScale * xScale); material.setFloat("YScale", blurScale * yScale); - } @Override @@ -198,17 +190,20 @@ protected void cleanUpFilter(Renderer r) { } /** - * Return the bias
                - * see {@link #setBias(float bias)} - * @return bias + * Returns the bias value used in the SSAO calculation. + * + * @return The bias value. + * @see #setBias(float) */ public float getBias() { return bias; } /** - * Sets the width of the occlusion cone considered by the occludee default is 0.1f - * @param bias + * Sets the width of the occlusion cone considered by the occludee. + * A higher bias means a wider cone, resulting in less self-occlusion. + * + * @param bias The desired bias value (default: 0.1f). */ public void setBias(float bias) { this.bias = bias; @@ -218,59 +213,65 @@ public void setBias(float bias) { } /** - * returns the ambient occlusion intensity - * @return intensity + * Returns the ambient occlusion intensity. + * + * @return The intensity value. */ public float getIntensity() { return intensity; } /** - * Sets the Ambient occlusion intensity default is 1.2f - * @param intensity + * Sets the ambient occlusion intensity. A higher intensity makes the ambient + * occlusion effect more pronounced. + * + * @param intensity The desired intensity (default: 1.5f). */ public void setIntensity(float intensity) { this.intensity = intensity; if (ssaoMat != null) { ssaoMat.setFloat("Intensity", intensity); } - } /** - * returns the sample radius
                - * see {link setSampleRadius(float sampleRadius)} - * @return the sample radius + * Returns the sample radius used in the SSAO calculation. + * + * @return The sample radius. + * @see #setSampleRadius(float) */ public float getSampleRadius() { return sampleRadius; } /** - * Sets the radius of the area where random samples will be picked default 5.1f - * @param sampleRadius + * Sets the radius of the area where random samples will be picked for SSAO. + * A larger radius considers more distant occluders. + * + * @param sampleRadius The desired radius (default: 5.1f). */ public void setSampleRadius(float sampleRadius) { this.sampleRadius = sampleRadius; if (ssaoMat != null) { ssaoMat.setFloat("SampleRadius", sampleRadius); } - } /** - * returns the scale
                - * see {@link #setScale(float scale)} - * @return scale + * Returns the scale value used in the SSAO calculation. + * + * @return The scale value. + * @see #setScale(float) */ public float getScale() { return scale; } /** - * - * Returns the distance between occluders and occludee. default 0.2f - * @param scale + * Sets the distance between occluders and occludee for SSAO. + * This essentially controls the "thickness" of the ambient occlusion. + * + * @param scale The desired distance (default: 0.2f). */ public void setScale(float scale) { this.scale = scale; @@ -280,37 +281,50 @@ public void setScale(float scale) { } /** - * debugging only , will be removed - * @return Whether or not + * Sets whether to use approximate normals for the SSAO calculation. + * If `true`, normals are derived from the depth buffer. If `false`, a separate + * normal pass is rendered. + * + * @param approximateNormals `true` to use approximate normals, `false` to use a normal pass. + */ + public void setApproximateNormals(boolean approximateNormals) { + this.approximateNormals = approximateNormals; + if (ssaoMat != null) { + ssaoMat.setBoolean("ApproximateNormals", approximateNormals); + } + } + + /** + * Checks if approximate normals are being used for SSAO calculation. + * + * @return `true` if approximate normals are used, `false` otherwise. + */ + public boolean isApproximateNormals() { + return approximateNormals; + } + + /** + * debugging only, will be removed + * @return true if using ambient occlusion */ public boolean isUseAo() { return useAo; } /** - * debugging only , will be removed + * debugging only, will be removed + * + * @param useAo true to enable, false to disable (default=true) */ public void setUseAo(boolean useAo) { this.useAo = useAo; if (material != null) { material.setBoolean("UseAo", useAo); } - - } - - public void setApproximateNormals(boolean approximateNormals) { - this.approximateNormals = approximateNormals; - if (ssaoMat != null) { - ssaoMat.setBoolean("ApproximateNormals", approximateNormals); - } - } - - public boolean isApproximateNormals() { - return approximateNormals; } /** - * debugging only , will be removed + * debugging only, will be removed * @return useOnlyAo */ public boolean isUseOnlyAo() { @@ -318,7 +332,9 @@ public boolean isUseOnlyAo() { } /** - * debugging only , will be removed + * debugging only, will be removed + * + * @param useOnlyAo true to enable, false to disable (default=false) */ public void setUseOnlyAo(boolean useOnlyAo) { this.useOnlyAo = useOnlyAo; @@ -335,6 +351,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(intensity, "intensity", 1.5f); oc.write(scale, "scale", 0.2f); oc.write(bias, "bias", 0.1f); + oc.write(approximateNormals, "approximateNormals", false); } @Override @@ -345,5 +362,6 @@ public void read(JmeImporter im) throws IOException { intensity = ic.readFloat("intensity", 1.5f); scale = ic.readFloat("scale", 0.2f); bias = ic.readFloat("bias", 0.1f); + approximateNormals = ic.readBoolean("approximateNormals", false); } } diff --git a/jme3-effects/src/main/java/com/jme3/post/ssao/package-info.java b/jme3-effects/src/main/java/com/jme3/post/ssao/package-info.java new file mode 100644 index 0000000000..9d7c4e84b5 --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/post/ssao/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * screen-space ambient occlusion (SSAO) effects + */ +package com.jme3.post.ssao; diff --git a/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java b/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java index f310f5313a..3cc2220921 100644 --- a/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java +++ b/jme3-effects/src/main/java/com/jme3/water/ReflectionProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,6 @@ public class ReflectionProcessor implements SceneProcessor { private Camera reflectionCam; private FrameBuffer reflectionBuffer; private Plane reflectionClipPlane; - private AppProfiler prof; /** * Creates a ReflectionProcessor @@ -65,28 +64,33 @@ public ReflectionProcessor(Camera reflectionCam, FrameBuffer reflectionBuffer, P this.reflectionClipPlane = reflectionClipPlane; } + @Override public void initialize(RenderManager rm, ViewPort vp) { this.rm = rm; this.vp = vp; } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { return rm != null; } + @Override public void preFrame(float tpf) { } + @Override public void postQueue(RenderQueue rq) { - //we need special treatement for the sky because it must not be clipped + //we need special treatment for the sky because it must not be clipped rm.getRenderer().setFrameBuffer(reflectionBuffer); reflectionCam.setProjectionMatrix(null); rm.setCamera(reflectionCam, false); rm.getRenderer().clearBuffers(true, true, true); - //Rendering the sky whithout clipping + //Rendering the sky without clipping rm.getRenderer().setDepthRange(1, 1); vp.getQueue().renderQueue(RenderQueue.Bucket.Sky, rm, reflectionCam, true); rm.getRenderer().setDepthRange(0, 1); @@ -96,15 +100,17 @@ public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { } + @Override public void cleanup() { } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } /** @@ -119,7 +125,8 @@ public FrameBuffer getReflectionBuffer() { /** * Internal use only
                * sets the frame buffer - * @param reflectionBuffer + * + * @param reflectionBuffer the FrameBuffer to use (alias created) */ public void setReflectionBuffer(FrameBuffer reflectionBuffer) { this.reflectionBuffer = reflectionBuffer; @@ -135,7 +142,8 @@ public Camera getReflectionCam() { /** * sets the reflection cam - * @param reflectionCam + * + * @param reflectionCam the Camera to use (alias created) */ public void setReflectionCam(Camera reflectionCam) { this.reflectionCam = reflectionCam; @@ -151,7 +159,8 @@ public Plane getReflectionClipPlane() { /** * Sets the reflection clip plane - * @param reflectionClipPlane + * + * @param reflectionClipPlane the Plane to use (alias created) */ public void setReflectionClipPlane(Plane reflectionClipPlane) { this.reflectionClipPlane = reflectionClipPlane; diff --git a/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java b/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java index 677cc6d20a..3bc5e7ca79 100644 --- a/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java +++ b/jme3-effects/src/main/java/com/jme3/water/SimpleWaterProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ import com.jme3.scene.Spatial; import com.jme3.scene.shape.Quad; import com.jme3.texture.*; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture.WrapMode; import com.jme3.ui.Picture; @@ -50,8 +51,8 @@ * * Simple Water renders a simple plane that use reflection and refraction to look like water. * It's pretty basic, but much faster than the WaterFilter - * It's useful if you aim for low specs hardware and still want a good looking water. - * Usage is : + * It's useful if you aim for low specs hardware and still want good-looking water. + * Usage is: * * SimpleWaterProcessor waterProcessor = new SimpleWaterProcessor(assetManager); * //setting the scene to use for reflection @@ -120,8 +121,6 @@ public class SimpleWaterProcessor implements SceneProcessor { private float distortionScale = 0.2f; private float distortionMix = 0.5f; private float texScale = 1f; - private AppProfiler prof; - /** * Creates a SimpleWaterProcessor @@ -142,6 +141,7 @@ public SimpleWaterProcessor(AssetManager manager) { } + @Override public void initialize(RenderManager rm, ViewPort vp) { this.rm = rm; this.vp = vp; @@ -164,15 +164,18 @@ public void initialize(RenderManager rm, ViewPort vp) { } } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { return rm != null; } float time = 0; float savedTpf = 0; + @Override public void preFrame(float tpf) { time = time + (tpf * speed); if (time > 1f) { @@ -182,6 +185,7 @@ public void preFrame(float tpf) { savedTpf = tpf; } + @Override public void postQueue(RenderQueue rq) { Camera sceneCam = rm.getCurrentCamera(); @@ -207,6 +211,7 @@ public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { if (debug) { displayMap(rm.getRenderer(), dispRefraction, 64); @@ -215,12 +220,13 @@ public void postFrame(FrameBuffer out) { } } + @Override public void cleanup() { } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } //debug only : displays maps @@ -277,8 +283,8 @@ protected void createPreViews() { // create offscreen framebuffer reflectionBuffer = new FrameBuffer(renderWidth, renderHeight, 1); //setup framebuffer to use texture - reflectionBuffer.setDepthBuffer(Format.Depth); - reflectionBuffer.setColorTexture(reflectionTexture); + reflectionBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); + reflectionBuffer.addColorTarget(FrameBufferTarget.newTarget(reflectionTexture)); //set viewport to render to offscreen framebuffer reflectionView.setOutputFrameBuffer(reflectionBuffer); @@ -293,9 +299,8 @@ protected void createPreViews() { // create offscreen framebuffer refractionBuffer = new FrameBuffer(renderWidth, renderHeight, 1); //setup framebuffer to use texture - refractionBuffer.setDepthBuffer(Format.Depth); - refractionBuffer.setColorTexture(refractionTexture); - refractionBuffer.setDepthTexture(depthTexture); + refractionBuffer.addColorTarget(FrameBufferTarget.newTarget(refractionTexture)); + refractionBuffer.setDepthTarget(FrameBufferTarget.newTarget(depthTexture)); //set viewport to render to offscreen framebuffer refractionView.setOutputFrameBuffer(refractionBuffer); refractionView.addProcessor(new RefractionProcessor()); @@ -319,7 +324,8 @@ public Material getMaterial() { /** * Sets the reflected scene, should not include the water quad! * Set before adding processor. - * @param spat + * + * @param spat the scene-graph subtree to be reflected (alias created) */ public void setReflectionScene(Spatial spat) { reflectionScene = spat; @@ -344,8 +350,9 @@ public int getRenderHeight() { /** * Set the reflection Texture render size, * set before adding the processor! - * @param width - * @param height + * + * @param width the desired width (in pixels, default=512) + * @param height the desired height (in pixels, default=512) */ public void setRenderSize(int width, int height) { renderWidth = width; @@ -362,7 +369,8 @@ public Plane getPlane() { /** * Set the water plane for this processor. - * @param plane + * + * @param plane the Plane to use (not null, unaffected) */ public void setPlane(Plane plane) { this.plane.setConstant(plane.getConstant()); @@ -390,7 +398,9 @@ private void updateClipPlanes() { /** * Set the light Position for the processor - * @param position + * + * @param position the desired location (in world coordinates, + * alias created) */ //TODO maybe we should provide a convenient method to compute position from direction public void setLightPosition(Vector3f position) { @@ -399,7 +409,8 @@ public void setLightPosition(Vector3f position) { /** * Set the color that will be added to the refraction texture. - * @param color + * + * @param color the desired color (alias created) */ public void setWaterColor(ColorRGBA color) { material.setColor("waterColor", color); @@ -407,8 +418,9 @@ public void setWaterColor(ColorRGBA color) { /** * Higher values make the refraction texture shine through earlier. - * Default is 4 - * @param depth + * Default is 1 + * + * @param depth the desired depth (default=1) */ public void setWaterDepth(float depth) { waterDepth = depth; @@ -432,8 +444,9 @@ public float getWaterTransparency() { } /** - * sets the water transparency default os 0.1f - * @param waterTransparency + * sets the water transparency default is 0.4f + * + * @param waterTransparency the desired transparency (default=0.4) */ public void setWaterTransparency(float waterTransparency) { this.waterTransparency = Math.max(0, waterTransparency); @@ -442,7 +455,8 @@ public void setWaterTransparency(float waterTransparency) { /** * Sets the speed of the wave animation, default = 0.05f. - * @param speed + * + * @param speed the desired animation speed (default=0.05) */ public void setWaveSpeed(float speed) { this.speed = speed; @@ -458,6 +472,8 @@ public float getWaveSpeed(){ /** * Sets the scale of distortion by the normal map, default = 0.2 + * + * @param value the desired scale factor (default=0.2) */ public void setDistortionScale(float value) { distortionScale = value; @@ -466,6 +482,8 @@ public void setDistortionScale(float value) { /** * Sets how the normal and dudv map are mixed to create the wave effect, default = 0.5 + * + * @param value the desired mix fraction (default=0.5) */ public void setDistortionMix(float value) { distortionMix = value; @@ -476,6 +494,8 @@ public void setDistortionMix(float value) { * Sets the scale of the normal/dudv texture, default = 1. * Note that the waves should be scaled by the texture coordinates of the quad to avoid animation artifacts, * use mesh.scaleTextureCoordinates(Vector2f) for that. + * + * @param value the desired scale factor (default=1) */ public void setTexScale(float value) { texScale = value; @@ -514,7 +534,7 @@ public float getTexScale() { /** - * returns true if the waterprocessor is in debug mode + * returns true if the water processor is in debug mode * @return true if in debug mode, otherwise false */ public boolean isDebug() { @@ -523,7 +543,8 @@ public boolean isDebug() { /** * set to true to display reflection and refraction textures in the GUI for debug purpose - * @param debug + * + * @param debug true to enable display, false to disable it (default=false) */ public void setDebug(boolean debug) { this.debug = debug; @@ -531,8 +552,9 @@ public void setDebug(boolean debug) { /** * Creates a quad with the water material applied to it. - * @param width - * @param height + * + * @param width the desired width (in mesh coordinates) + * @param height the desired height (in mesh coordinates) * @return a new Geometry */ public Geometry createWaterGeometry(float width, float height) { @@ -553,8 +575,9 @@ public float getReflectionClippingOffset() { /** * sets the reflection clipping plane offset - * set a nagetive value to lower the clipping plane for relection texture rendering. - * @param reflectionClippingOffset + * set a negative value to lower the clipping plane for reflection texture rendering. + * + * @param reflectionClippingOffset the desired offset (default=-5) */ public void setReflectionClippingOffset(float reflectionClippingOffset) { this.reflectionClippingOffset = reflectionClippingOffset; @@ -572,7 +595,8 @@ public float getRefractionClippingOffset() { /** * Sets the refraction clipping plane offset * set a positive value to raise the clipping plane for refraction texture rendering - * @param refractionClippingOffset + * + * @param refractionClippingOffset the desired offset (default=0.3) */ public void setRefractionClippingOffset(float refractionClippingOffset) { this.refractionClippingOffset = refractionClippingOffset; @@ -586,37 +610,43 @@ public class RefractionProcessor implements SceneProcessor { RenderManager rm; ViewPort vp; - private AppProfiler prof; + @Override public void initialize(RenderManager rm, ViewPort vp) { this.rm = rm; this.vp = vp; } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { return rm != null; } + @Override public void preFrame(float tpf) { refractionCam.setClipPlane(refractionClipPlane, Plane.Side.Negative);//,-1 } + @Override public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { } + @Override public void cleanup() { } @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } } } 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 a42a5d9479..0d84b23674 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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,8 +58,8 @@ import java.io.IOException; /** - * The WaterFilter is a 2D post process that simulate water. - * It renders water above and under water. + * The WaterFilter is a 2-D post process that simulates water. + * It renders water from both above and below the surface. * See the jMonkeyEngine wiki for more info https://jmonkeyengine.github.io/wiki/jme3/advanced/post-processor_water.html. * * @@ -170,7 +170,7 @@ protected void preFrame(float tpf) { WaterUtils.updateReflectionCam(reflectionCam, plane, sceneCam); - //if we're under water no need to compute reflection + // If we're underwater, we don't need to compute reflection. if (sceneCam.getLocation().y >= waterHeight) { boolean rtb = true; if (!renderManager.isHandleTranslucentBucket()) { @@ -212,7 +212,7 @@ private DirectionalLight findLight(Node node) { } /** - * @return true if need to try to use direction light from a scene. + * @return true to try using directional light from a scene */ protected boolean useDirectionLightFromScene() { return true; @@ -473,7 +473,8 @@ public float getWaterHeight() { /** * Sets the height of the water plane * default is 0.0 - * @param waterHeight + * + * @param waterHeight the desired height (default=0) */ public void setWaterHeight(float waterHeight) { this.waterHeight = waterHeight; @@ -515,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 @@ -524,11 +534,12 @@ public float getWaterTransparency() { } /** - * Sets how fast will colours fade out. You can also think about this - * values as how clear water is. Therefore use smaller values (eg. 0.05) - * to have crystal clear water and bigger to achieve "muddy" water. + * Sets how fast colours fade out. You can also think about this + * as how clear water is. Therefore, use smaller values (e.g. 0.05) + * for crystal-clear water and bigger values for "muddy" water. * default is 0.1f - * @param waterTransparency + * + * @param waterTransparency the desired muddiness (default=0.1) */ public void setWaterTransparency(float waterTransparency) { this.waterTransparency = waterTransparency; @@ -548,8 +559,9 @@ public float getNormalScale() { /** * Sets the normal scaling factors to apply to the normal map. * the higher the value the more small ripples will be visible on the waves. - * default is 1.0 - * @param normalScale + * default is 3 + * + * @param normalScale the scaling factor (default=3) */ public void setNormalScale(float normalScale) { this.normalScale = normalScale; @@ -570,10 +582,11 @@ public float getRefractionConstant() { * This is a constant related to the index of refraction (IOR) used to compute the fresnel term. * F = R0 + (1-R0)( 1 - N.V)^5 * where F is the fresnel term, R0 the constant, N the normal vector and V the view vector. - * It usually depend on the material you are looking through (here water). - * Default value is 0.3f + * It depends on the substance you are looking through (here water). + * Default value is 0.5 * In practice, the lowest the value and the less the reflection can be seen on water - * @param refractionConstant + * + * @param refractionConstant the desired R0 value (default=0.5) */ public void setRefractionConstant(float refractionConstant) { this.refractionConstant = refractionConstant; @@ -592,8 +605,9 @@ public float getMaxAmplitude() { /** * Sets the maximum waves amplitude - * default is 1.0 - * @param maxAmplitude + * default is 1.5 + * + * @param maxAmplitude the desired maximum amplitude (default=1.5) */ public void setMaxAmplitude(float maxAmplitude) { this.maxAmplitude = maxAmplitude; @@ -612,7 +626,9 @@ public Vector3f getLightDirection() { /** * Sets the light direction - * @param lightDirection + * + * @param lightDirection the direction vector to use (alias created, + * default=(0,-1,0)) */ public void setLightDirection(Vector3f lightDirection) { this.lightDirection = lightDirection; @@ -632,7 +648,8 @@ public ColorRGBA getLightColor() { /** * Sets the light color to use * default is white - * @param lightColor + * + * @param lightColor the color to use (alias created, default=(1,1,1,1)) */ public void setLightColor(ColorRGBA lightColor) { this.lightColor = lightColor; @@ -642,7 +659,7 @@ public void setLightColor(ColorRGBA lightColor) { } /** - * Return the shoreHardeness + * Return the shore hardness. * @return the hardness value */ public float getShoreHardness() { @@ -653,7 +670,8 @@ public float getShoreHardness() { * The smaller this value is, the softer the transition between * shore and water. If you want hard edges use very big value. * Default is 0.1f. - * @param shoreHardness + * + * @param shoreHardness the desired hardness (default=0.1) */ public void setShoreHardness(float shoreHardness) { this.shoreHardness = shoreHardness; @@ -673,7 +691,8 @@ public float getFoamHardness() { /** * Sets the foam hardness : How much the foam will blend with the shore to avoid hard edged water plane. * Default is 1.0 - * @param foamHardness + * + * @param foamHardness the desired hardness (default=1) */ public void setFoamHardness(float foamHardness) { this.foamHardness = foamHardness; @@ -693,8 +712,9 @@ public float getRefractionStrength() { /** * This value modifies current fresnel term. If you want to weaken * reflections use bigger value. If you want to emphasize them use - * value smaller then 0. Default is 0.0f. - * @param refractionStrength + * a value smaller than 0. Default is 0. + * + * @param refractionStrength the desired strength (default=0) */ public void setRefractionStrength(float refractionStrength) { this.refractionStrength = refractionStrength; @@ -704,7 +724,7 @@ public void setRefractionStrength(float refractionStrength) { } /** - * returns the scale factor of the waves height map + * Returns the scale factor of the waves' height map. * @return the scale factor */ public float getWaveScale() { @@ -712,10 +732,11 @@ public float getWaveScale() { } /** - * Sets the scale factor of the waves height map - * the smaller the value the bigger the waves - * default is 0.005f - * @param waveScale + * Sets the scale factor of the waves' height map. + * The smaller the value, the bigger the waves. + * Default is 0.005 . + * + * @param waveScale the desired scale factor (default=0.005) */ public void setWaveScale(float waveScale) { this.waveScale = waveScale; @@ -737,7 +758,8 @@ public Vector3f getFoamExistence() { * at what it is completely invisible. The third value is at * what height foam for waves appear (+ waterHeight). * default is (0.45, 4.35, 1.0); - * @param foamExistence + * + * @param foamExistence the desired parameters (alias created) */ public void setFoamExistence(Vector3f foamExistence) { this.foamExistence = foamExistence; @@ -756,7 +778,8 @@ public float getSunScale() { /** * Sets the scale of the sun for specular effect - * @param sunScale + * + * @param sunScale the desired scale factor (default=3) */ public void setSunScale(float sunScale) { this.sunScale = sunScale; @@ -780,7 +803,9 @@ public Vector3f getColorExtinction() { * the third is for blue * Play with those parameters to "trouble" the water * default is (5.0, 20.0, 30.0f); - * @param colorExtinction + * + * @param colorExtinction the desired depth for each color component (alias + * created, default=(5,20,30)) */ public void setColorExtinction(Vector3f colorExtinction) { this.colorExtinction = colorExtinction; @@ -814,7 +839,7 @@ public Texture2D getFoamTexture() { /** * Sets the height texture * - * @param heightTexture + * @param heightTexture the texture to use (alias created) */ public void setHeightTexture(Texture2D heightTexture) { this.heightTexture = heightTexture; @@ -866,7 +891,8 @@ public float getShininess() { /** * Sets the shininess factor of the water * default is 0.7f - * @param shininess + * + * @param shininess the desired factor (default=0.7) */ public void setShininess(float shininess) { this.shininess = shininess; @@ -885,7 +911,8 @@ public float getSpeed() { /** * Set the speed of the waves (0.0 is still) default is 1.0 - * @param speed + * + * @param speed the desired speedup factor (default=1) */ public void setSpeed(float speed) { this.speed = speed; @@ -903,8 +930,10 @@ public ColorRGBA getWaterColor() { /** * Sets the color of the water * see setDeepWaterColor for deep water color - * default is (0.0078f, 0.5176f, 0.5f,1.0f) (greenish blue) - * @param waterColor + * default is (0.0078f, 0.3176f, 0.5f,1.0f) (greenish blue) + * + * @param waterColor the color to use (alias created, + * default=(0.0078,0.3176,0.5,1)) */ public void setWaterColor(ColorRGBA waterColor) { this.waterColor = waterColor; @@ -925,7 +954,9 @@ public ColorRGBA getDeepWaterColor() { * sets the deep water color * see setWaterColor for general color * default is (0.0039f, 0.00196f, 0.145f,1.0f) (very dark blue) - * @param deepWaterColor + * + * @param deepWaterColor the color to use (alias created, + * default=(0.0039,0.00196,0.145,1)) */ public void setDeepWaterColor(ColorRGBA deepWaterColor) { this.deepWaterColor = deepWaterColor; @@ -946,7 +977,9 @@ public Vector2f getWindDirection() { * sets the wind direction * the direction where the waves move * default is (0.0f, -1.0f) - * @param windDirection + * + * @param windDirection the direction vector to use (alias created, + * default=(0,-1)) */ public void setWindDirection(Vector2f windDirection) { this.windDirection = windDirection; @@ -966,7 +999,9 @@ public int getReflectionMapSize() { /** * Sets the size of the reflection map * default is 512, the higher, the better quality, but the slower the effect. - * @param reflectionMapSize + * + * @param reflectionMapSize the desired size (in pixels per side, + * default=512) */ public void setReflectionMapSize(int reflectionMapSize) { this.reflectionMapSize = reflectionMapSize; @@ -991,7 +1026,8 @@ public boolean isUseFoam() { /** * set to true to use foam with water * default true - * @param useFoam + * + * @param useFoam true for foam, false for no foam (default=true) */ public void setUseFoam(boolean useFoam) { this.useFoam = useFoam; @@ -1009,7 +1045,7 @@ public void setUseFoam(boolean useFoam) { public void setCausticsTexture(Texture2D causticsTexture) { this.causticsTexture = causticsTexture; if (material != null) { - material.setTexture("causticsMap", causticsTexture); + material.setTexture("CausticsMap", causticsTexture); } } @@ -1032,7 +1068,9 @@ public boolean isUseCaustics() { /** * set to true if you want caustics to be rendered on the ground underwater, false otherwise - * @param useCaustics + * + * @param useCaustics true to enable rendering fo caustics, false to disable + * it (default=true) */ public void setUseCaustics(boolean useCaustics) { this.useCaustics = useCaustics; @@ -1067,7 +1105,9 @@ public boolean isUseRefraction() { /** * set to true to use refraction (default is true) - * @param useRefraction + * + * @param useRefraction true to enable refraction, false to disable it + * (default=true) */ public void setUseRefraction(boolean useRefraction) { this.useRefraction = useRefraction; @@ -1088,7 +1128,9 @@ public boolean isUseRipples() { /** * * Set to true to use ripples - * @param useRipples + * + * @param useRipples true to enable ripples, false to disable them + * (default=true) */ public void setUseRipples(boolean useRipples) { this.useRipples = useRipples; @@ -1107,8 +1149,10 @@ public boolean isUseSpecular() { } /** - * Set to true to use specular lightings on the water - * @param useSpecular + * Set to true to use specular lighting on the water + * + * @param useSpecular true to enable the specular effect, false to disable + * it (default=true) */ public void setUseSpecular(boolean useSpecular) { this.useSpecular = useSpecular; @@ -1127,7 +1171,8 @@ public float getFoamIntensity() { /** * sets the foam intensity default is 0.5f - * @param foamIntensity + * + * @param foamIntensity the desired intensity (default=0.5) */ public void setFoamIntensity(float foamIntensity) { this.foamIntensity = foamIntensity; @@ -1148,7 +1193,8 @@ public float getReflectionDisplace() { /** * Sets the reflection displace. define how troubled will look the reflection in the water. default is 30 - * @param reflectionDisplace + * + * @param reflectionDisplace the desired displacement (default=30) */ public void setReflectionDisplace(float reflectionDisplace) { this.reflectionDisplace = reflectionDisplace; @@ -1166,7 +1212,7 @@ public boolean isUnderWater() { } /** - * returns the distance of the fog when under water + * returns the distance of the fog when underwater * @return the distance */ public float getUnderWaterFogDistance() { @@ -1174,9 +1220,11 @@ public float getUnderWaterFogDistance() { } /** - * sets the distance of the fog when under water. - * default is 120 (120 world units) use a high value to raise the view range under water - * @param underWaterFogDistance + * Sets the distance of the fog when underwater. + * Default is 120 (120 world units). Use a high value to raise the view range underwater. + * + * @param underWaterFogDistance the desired distance (in world units, + * default=120) */ public void setUnderWaterFogDistance(float underWaterFogDistance) { this.underWaterFogDistance = underWaterFogDistance; @@ -1186,7 +1234,7 @@ public void setUnderWaterFogDistance(float underWaterFogDistance) { } /** - * get the intensity of caustics under water + * Gets the intensity of caustics underwater * @return the intensity value (≥0, ≤1) */ public float getCausticsIntensity() { @@ -1194,8 +1242,10 @@ public float getCausticsIntensity() { } /** - * sets the intensity of caustics under water. goes from 0 to 1, default is 0.5f - * @param causticsIntensity + * Sets the intensity of caustics underwater. Goes from 0 to 1, default is 0.5. + * + * @param causticsIntensity the desired intensity (≥0, ≤1, + * default=0.5) */ public void setCausticsIntensity(float causticsIntensity) { this.causticsIntensity = causticsIntensity; @@ -1214,7 +1264,7 @@ public Vector3f getCenter() { /** * Set the center of the effect. - * By default the water will extent to the entire scene. + * By default, the water will extend across the entire scene. * By setting a center and a radius you can restrain it to a portion of the scene. * @param center the center of the effect */ @@ -1236,7 +1286,7 @@ public float getRadius() { /** * Set the radius of the effect. - * By default the water will extent to the entire scene. + * By default, the water will extend across the entire scene. * By setting a center and a radius you can restrain it to a portion of the scene. * @param radius the radius of the effect */ diff --git a/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java b/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java index 0060d79350..041a26c7ac 100644 --- a/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java +++ b/jme3-effects/src/main/java/com/jme3/water/WaterUtils.java @@ -1,6 +1,33 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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.water; @@ -13,12 +40,17 @@ * * @author Nehon */ -public class WaterUtils { - +public class WaterUtils { + /** + * A private constructor to inhibit instantiation of this class. + */ + private WaterUtils() { + } + public static void updateReflectionCam(Camera reflectionCam, Plane plane, Camera sceneCam){ TempVars vars = TempVars.get(); - //Temp vects for reflection cam orientation calculation + // temporary vectors for reflection cam orientation calculation: Vector3f sceneTarget = vars.vect1; Vector3f reflectDirection = vars.vect2; Vector3f reflectUp = vars.vect3; diff --git a/jme3-effects/src/main/java/com/jme3/water/package-info.java b/jme3-effects/src/main/java/com/jme3/water/package-info.java new file mode 100644 index 0000000000..d714d8729a --- /dev/null +++ b/jme3-effects/src/main/java/com/jme3/water/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * water effects + */ +package com.jme3.water; diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md index d3e51dd573..4171380e6d 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomExtract.j3md @@ -1,6 +1,7 @@ MaterialDef Bloom { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float ExposurePow @@ -10,13 +11,14 @@ MaterialDef Bloom { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/bloomExtract.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/bloomExtract.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HAS_GLOWMAP : GlowMap DO_EXTRACT : Extract RESOLVE_MS : NumSamples diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md index 817764cab9..e356e1f07f 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/BloomFinal.j3md @@ -1,20 +1,22 @@ MaterialDef Bloom Final { MaterialParameters { - Int NumSamples + Int BoundDrawBuffer + Int NumSamples Texture2D Texture Texture2D BloomTex Float BloomIntensity } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/bloomFinal.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/bloomFinal.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples } } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag index 7ee2e51d4a..219459931b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.frag @@ -55,4 +55,4 @@ void main(){ color = mix (color,m_EdgeColor.rgb,edgeAmount); gl_FragColor = vec4(color, 1.0); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md index c7a740bcc0..59001feec2 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CartoonEdge.j3md @@ -1,6 +1,7 @@ MaterialDef Cartoon Edge { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -16,8 +17,8 @@ MaterialDef Cartoon Edge { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/CartoonEdge.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/CartoonEdge.frag WorldParameters { WorldViewMatrix @@ -25,6 +26,7 @@ MaterialDef Cartoon Edge { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md index 8f5e584df0..b24909cdb8 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Compose.j3md @@ -1,6 +1,7 @@ MaterialDef Default GUI { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -8,13 +9,14 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/Compose.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Compose.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.frag new file mode 100644 index 0000000000..363d078a1b --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.frag @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2009-2021 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. + */ + +/* + * Used by ContrastAdjustment.j3md to adjust the color channels. + * + * First, the input range is normalized to upper and lower limits. + * Then a power law is applied, using the exponent for each channel. + * Finally, the output value is scaled linearly, using the scaling factor for each channel. + * + */ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +//constant inputs from java source +uniform float m_redChannelExponent; +uniform float m_greenChannelExponent; +uniform float m_blueChannelExponent; + +//final scale values +uniform float m_redChannelScale; +uniform float m_greenChannelScale; +uniform float m_blueChannelScale; + +//input range +uniform float m_lowerLimit; +uniform float m_upperLimit; + +//container for the input from post.vert +uniform COLORTEXTURE m_Texture; + +//varying input from post.vert vertex shader +varying vec2 texCoord; + +void main() { + //get the color from a 2d sampler. + vec4 color = texture2D(m_Texture, texCoord); + + //apply the color transfer function. + + //1) adjust the channels input range + color.rgb = (color.rgb - vec3(m_lowerLimit)) / (vec3(m_upperLimit) - vec3(m_lowerLimit)); + + // avoid negative levels + color.r = max(color.r, 0.0); + color.g = max(color.g, 0.0); + color.b = max(color.b, 0.0); + + //2) apply transfer functions on different channels. + color.r = pow(color.r, m_redChannelExponent); + color.g = pow(color.g, m_greenChannelExponent); + color.b = pow(color.b, m_blueChannelExponent); + + //3) scale the output levels + color.r = color.r * m_redChannelScale; + color.b = color.b * m_blueChannelScale; + color.g = color.g * m_greenChannelScale; + + //4) process the textures colors. + gl_FragColor = color; +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md new file mode 100644 index 0000000000..7c2ee7bea7 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ContrastAdjustment.j3md @@ -0,0 +1,32 @@ +//Used by com.jme3.post.filters.ColorAdjustmentFilter.java +//supports both OGL and OGLES glsl + +MaterialDef ColorAdjustmentFilter { + + MaterialParameters { + Int BoundDrawBuffer + Int NumSamples + Texture2D Texture + Float redChannelExponent + Float greenChannelExponent + Float blueChannelExponent + Float lowerLimit + Float upperLimit + Float redChannelScale + Float greenChannelScale + Float blueChannelScale + } + + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/ContrastAdjustment.frag + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + NUM_SAMPLES : NumSamples + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag index fe52cfc502..99b31175e4 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.frag @@ -51,4 +51,4 @@ void main() { vec4 paperColor = mix(m_PaperColor, texVal, m_ColorInfluencePaper); gl_FragColor = mix(paperColor, lineColor, linePixel); -} \ No newline at end of file +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md index e5c168a630..3526dd3a3a 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/CrossHatch.j3md @@ -1,6 +1,7 @@ MaterialDef CrossHatch { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture; Vector4 LineColor; @@ -18,11 +19,15 @@ MaterialDef CrossHatch { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/CrossHatch.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/CrossHatch.frag WorldParameters { } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } } \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md index 908889951b..8a7d2f354b 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/DepthOfField.j3md @@ -1,6 +1,7 @@ MaterialDef Depth Of Field { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -14,14 +15,15 @@ MaterialDef Depth Of Field { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/DepthOfField.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/DepthOfField.frag WorldParameters { FrustumNearFar } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth BLUR_THRESHOLD : BlurThreshold diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag new file mode 100644 index 0000000000..2803a95d87 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.frag @@ -0,0 +1,60 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform vec2 m_TexelSize; +varying vec2 texCoord; + +void main() { + + // downsampling code: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom + + float x = m_TexelSize.x; + float y = m_TexelSize.y; + + // Take 13 samples around current texel + // a - b - c + // - j - k - + // d - e - f + // - l - m - + // g - h - i + // === ('e' is the current texel) === + vec3 a = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y + 2*y)).rgb; + vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + 2*y)).rgb; + vec3 c = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y + 2*y)).rgb; + + vec3 d = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y)).rgb; + vec3 e = getColor(m_Texture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y)).rgb; + + vec3 g = getColor(m_Texture, vec2(texCoord.x - 2*x, texCoord.y - 2*y)).rgb; + vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - 2*y)).rgb; + vec3 i = getColor(m_Texture, vec2(texCoord.x + 2*x, texCoord.y - 2*y)).rgb; + + vec3 j = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 k = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + vec3 l = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 m = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution: + // 0.5 + 0.125 + 0.125 + 0.125 + 0.125 = 1 + // a,b,d,e * 0.125 + // b,c,e,f * 0.125 + // d,e,g,h * 0.125 + // e,f,h,i * 0.125 + // j,k,l,m * 0.5 + // This shows 5 square areas that are being sampled. But some of them overlap, + // so to have an energy preserving downsample we need to make some adjustments. + // The weights are the distributed, so that the sum of j,k,l,m (e.g.) + // contribute 0.5 to the final color output. The code below is written + // to effectively yield this sum. We get: + // 0.125*5 + 0.03125*4 + 0.0625*4 = 1 + vec3 downsample = e*0.125; + downsample += (a+c+g+i)*0.03125; + downsample += (b+d+f+h)*0.0625; + downsample += (j+k+l+m)*0.125; + + gl_FragColor = vec4(downsample, 1.0); + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md new file mode 100644 index 0000000000..595a918eb3 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Downsample.j3md @@ -0,0 +1,22 @@ +MaterialDef Downsample { + + MaterialParameters { + Texture2D Texture + Vector2 TexelSize + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Downsample.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag index 4064855b2d..edd29d88c5 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.frag @@ -84,6 +84,6 @@ vec3 FxaaPixelShader( void main() { - - gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), 1.0); -} \ No newline at end of file + vec4 texVal = texture2D(m_Texture, texCoord); + gl_FragColor = vec4(FxaaPixelShader(posPos, m_Texture, g_ResolutionInverse), texVal.a); +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md index 2debc760c7..385a3a1666 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/FXAA.j3md @@ -1,5 +1,6 @@ MaterialDef FXAA { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float SubPixelShift @@ -8,10 +9,14 @@ MaterialDef FXAA { Float ReduceMul } Technique { - VertexShader GLSL300 GLSL100 GLSL150: Common/MatDefs/Post/FXAA.vert - FragmentShader GLSL300 GLSL100 GLSL150: Common/MatDefs/Post/FXAA.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/FXAA.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/FXAA.frag WorldParameters { ResolutionInverse } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md index e9a55ab8b2..cac57cf21f 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fade.j3md @@ -1,19 +1,21 @@ MaterialDef Fade { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Texture2D Texture Float Value } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/Fade.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Fade.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples } } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md index b1d28103ce..b6267326a0 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Fog.j3md @@ -1,6 +1,7 @@ MaterialDef Fade { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -11,13 +12,14 @@ MaterialDef Fade { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/Fog.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Fog.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag new file mode 100644 index 0000000000..ba09268efa --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.frag @@ -0,0 +1,43 @@ +#extension GL_ARB_texture_multisample : enable +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Hdr.glsllib" + + +uniform vec3 m_Exposure; +uniform vec3 m_Gamma; +varying vec2 texCoord; + +vec3 applyCurve(in vec3 x) { + return HDR_KHRToneMap(x, m_Exposure, m_Gamma); +} + + +#ifdef NUM_SAMPLES + +uniform sampler2DMS m_Texture; + +vec4 applyToneMap() { + ivec2 iTexC = ivec2(texCoord * vec2(textureSize(m_Texture))); + vec4 color = vec4(0.0); + for (int i = 0; i < NUM_SAMPLES; i++) { + vec4 hdrColor = texelFetch(m_Texture, iTexC, i); + vec3 ldrColor = applyCurve(hdrColor.rgb); + color += vec4(ldrColor, hdrColor.a); + } + return color / float(NUM_SAMPLES); +} + +#else + +uniform sampler2D m_Texture; + +vec4 applyToneMap() { + vec4 texVal = texture2D(m_Texture, texCoord); + return vec4(applyCurve(texVal.rgb) , texVal.a); +} + +#endif + +void main() { + gl_FragColor = applyToneMap(); +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md new file mode 100644 index 0000000000..90b81d075e --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/KHRToneMap.j3md @@ -0,0 +1,24 @@ +MaterialDef KHRToneMap { + + MaterialParameters { + Int BoundDrawBuffer + Int NumSamples + Int NumSamplesDepth + Texture2D Texture + Vector3 Gamma + Vector3 Exposure + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/KHRToneMap.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + NUM_SAMPLES : NumSamples + } + } +} \ No newline at end of file diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.j3md index 590dbdc051..d7929bd022 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/LightScattering.j3md @@ -1,5 +1,6 @@ MaterialDef Light Scattering { - MaterialParameters { + MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -14,13 +15,14 @@ MaterialDef Light Scattering { } Technique { - VertexShader GLSL300 GLSL150 GLSL120: Common/MatDefs/Post/Post.vert + VertexShader GLSL300 GLSL150 GLSL120: Common/MatDefs/Post/Post.vert FragmentShader GLSL300 GLSL150 GLSL120: Common/MatDefs/Post/LightScattering.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples RESOLVE_DEPTH_MS : NumSamplesDepth DISPLAY: Display diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Overlay.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Overlay.j3md index 6cdb458883..51eb543573 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Overlay.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Overlay.j3md @@ -1,6 +1,7 @@ MaterialDef Default GUI { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -8,13 +9,14 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/Overlay.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Overlay.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer RESOLVE_MS : NumSamples } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Post15.vert b/jme3-effects/src/main/resources/Common/MatDefs/Post/Post15.vert index 3bcd2ede01..975780ea29 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Post15.vert +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Post15.vert @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + in vec4 inPosition; in vec2 inTexCoord; diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.j3md index 635dc0f536..71ca681a48 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Posterization.j3md @@ -1,6 +1,7 @@ MaterialDef Posterization { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture; @@ -10,14 +11,15 @@ MaterialDef Posterization { } Technique { - VertexShader GLSL150 GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL150 GLSL100: Common/MatDefs/Post/Posterization.frag + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Post/Posterization.frag WorldParameters { } Defines { - RESOLVE_MS : NumSamples + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples } } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag new file mode 100644 index 0000000000..20fb792cc3 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.frag @@ -0,0 +1,15 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform sampler2D m_GlowMap; +uniform float m_GlowFactor; +varying vec2 texCoord; + +void main() { + + gl_FragColor = mix(getColor(m_Texture, texCoord), texture2D(m_GlowMap, texCoord), m_GlowFactor); + +} + diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md new file mode 100644 index 0000000000..d0597f9f9b --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/SoftBloomFinal.j3md @@ -0,0 +1,23 @@ +MaterialDef PBRBloomFinal { + + MaterialParameters { + Texture2D Texture + Texture2D GlowMap + Float GlowFactor : 0.05 + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/SoftBloomFinal.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md index 7c1eb6673d..2ca259838f 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/ToneMap.j3md @@ -1,6 +1,7 @@ MaterialDef Default GUI { MaterialParameters { + Int BoundDrawBuffer Int NumSamples Int NumSamplesDepth Texture2D Texture @@ -8,13 +9,14 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100 GLSL150: Common/MatDefs/Post/Post.vert - FragmentShader GLSL100 GLSL150: Common/MatDefs/Post/ToneMap.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/ToneMap.frag WorldParameters { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer NUM_SAMPLES : NumSamples } } diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag new file mode 100644 index 0000000000..61bf280061 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.frag @@ -0,0 +1,46 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/MultiSample.glsllib" + +uniform COLORTEXTURE m_Texture; +uniform vec2 m_TexelSize; +varying vec2 texCoord; + +void main() { + + // upsampling code: https://learnopengl.com/Guest-Articles/2022/Phys.-Based-Bloom + + // The filter kernel is applied with a radius, specified in texture + // coordinates, so that the radius will vary across mip resolutions. + float x = m_TexelSize.x; + float y = m_TexelSize.y; + + // Take 9 samples around current texel: + // a - b - c + // d - e - f + // g - h - i + // === ('e' is the current texel) === + vec3 a = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y + y)).rgb; + vec3 b = getColor(m_Texture, vec2(texCoord.x, texCoord.y + y)).rgb; + vec3 c = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y + y)).rgb; + + vec3 d = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y)).rgb; + vec3 e = getColor(m_Texture, vec2(texCoord.x, texCoord.y)).rgb; + vec3 f = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y)).rgb; + + vec3 g = getColor(m_Texture, vec2(texCoord.x - x, texCoord.y - y)).rgb; + vec3 h = getColor(m_Texture, vec2(texCoord.x, texCoord.y - y)).rgb; + vec3 i = getColor(m_Texture, vec2(texCoord.x + x, texCoord.y - y)).rgb; + + // Apply weighted distribution, by using a 3x3 tent filter: + // | 1 2 1 | + // 1/16 * | 2 4 2 | + // | 1 2 1 | + vec3 upsample = e*4.0; + upsample += (b+d+f+h)*2.0; + upsample += (a+c+g+i); + upsample /= 16.0; + + gl_FragColor = vec4(upsample, 1.0); + +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md new file mode 100644 index 0000000000..2ce2cc9976 --- /dev/null +++ b/jme3-effects/src/main/resources/Common/MatDefs/Post/Upsample.j3md @@ -0,0 +1,23 @@ +MaterialDef Upsample { + + MaterialParameters { + Texture2D Texture + Vector2 TexelSize + Float FilterRadius : 0.01 + Int BoundDrawBuffer + Int NumSamples + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Post.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Post/Upsample.frag + + WorldParameters { + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + RESOLVE_MS : NumSamples + } + } +} diff --git a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag index d21e605cf9..0b91b97787 100644 --- a/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag +++ b/jme3-effects/src/main/resources/Common/MatDefs/SSAO/normal.frag @@ -5,6 +5,17 @@ varying vec2 texCoord; #ifdef DIFFUSEMAP_ALPHA uniform sampler2D m_DiffuseMap; +#endif + +#ifdef COLORMAP_ALPHA + uniform sampler2D m_ColorMap; +#endif + +#ifdef BASECOLORMAP_ALPHA + uniform sampler2D m_BaseColorMap; +#endif + +#if defined DIFFUSEMAP_ALPHA || defined COLORMAP_ALPHA || defined BASECOLORMAP_ALPHA uniform float m_AlphaDiscardThreshold; #endif @@ -16,6 +27,16 @@ void main(void) discard; } #endif + #ifdef COLORMAP_ALPHA + if(texture2D(m_ColorMap,texCoord).a + configurations.compileClasspath.resolvedConfiguration.resolvedArtifacts.each { artifact -> copy { from artifact.file into '../dist/lib' @@ -81,36 +72,6 @@ task dist (dependsOn: ['build', ':jme3-jogl:jar', ':jme3-bullet:jar', ':jme3-and into '../dist' rename { "jMonkeyEngine3.jar" } } - // Copy JOGL packages, remove version - def config = project(':jme3-jogl').configurations.runtime.copyRecursive({ !(it instanceof ProjectDependency); }) - config.resolvedConfiguration.resolvedArtifacts.each {artifact -> - copy{ - from artifact.file - into '../dist/opt/jogl/lib' - if(artifact.classifier != null){ - rename { "${artifact.name}-${artifact.classifier}.${artifact.extension}" } - } else{ - rename { "${artifact.name}.${artifact.extension}" } - } - } - } - copy { - from project(':jme3-jogl').jar.archivePath - into '../dist/opt/jogl' - rename {project(':jme3-jogl').name+".jar"} - } - - // Copy bullet packages, remove version - copy { - from project(':jme3-bullet').jar.archivePath - into '../dist/opt/native-bullet' - rename {project(':jme3-bullet').name+".jar"} - } - copy { - from project(':jme3-bullet-native').jar.archivePath - into '../dist/opt/native-bullet' - rename {project(':jme3-bullet-native').name+".jar"} - } // Copy android packages, remove version copy { @@ -123,10 +84,5 @@ task dist (dependsOn: ['build', ':jme3-jogl:jar', ':jme3-bullet:jar', ':jme3-and into '../dist/opt/android' rename {project(':jme3-android-native').name+".jar"} } - copy { - from project(':jme3-bullet-native-android').jar.archivePath - into '../dist/opt/native-bullet' - rename {project(':jme3-bullet-native-android').name+".jar"} - } } -} \ No newline at end of file +} diff --git a/jme3-examples/gradle.properties b/jme3-examples/gradle.properties index 6f09931209..bfcfb949b5 100644 --- a/jme3-examples/gradle.properties +++ b/jme3-examples/gradle.properties @@ -1,5 +1,5 @@ -# When running this project we don't need javadoc to be built. -buildJavaDoc = false - # We want assertions, because this is the test project. assertions = true + +# Build javadoc per Github issue #1366 +buildJavaDoc = true diff --git a/jme3-examples/src/main/java/jme3test/TestChooser.java b/jme3-examples/src/main/java/jme3test/TestChooser.java index c47196035e..3799bd5832 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,26 +35,36 @@ import com.jme3.app.LegacyApplication; import com.jme3.app.SimpleApplication; import com.jme3.system.JmeContext; -import java.awt.*; +import java.awt.BorderLayout; +import java.awt.Dimension; +import java.awt.FlowLayout; +import java.awt.HeadlessException; +import java.awt.Toolkit; import java.awt.event.*; -import java.io.File; -import java.io.FileFilter; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.JarURLConnection; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLDecoder; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collection; -import java.util.Enumeration; -import java.util.Vector; -import java.util.jar.JarFile; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.SynchronousQueue; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.zip.ZipEntry; import javax.swing.*; import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; @@ -62,96 +72,102 @@ import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; - /** * Class with a main method that displays a dialog to choose any jME demo to be * started. */ -public class TestChooser extends JDialog { - private static final Logger logger = Logger.getLogger(TestChooser.class - .getName()); +public class TestChooser extends JFrame { + + private static final Logger logger = Logger.getLogger(TestChooser.class.getName()); private static final long serialVersionUID = 1L; /** * Only accessed from EDT */ - private java.util.List selectedClass = null; + private List> selectedClass = null; private boolean showSetting = true; + private ExecutorService executorService; + /** * Constructs a new TestChooser that is initially invisible. */ public TestChooser() throws HeadlessException { - super((JFrame) null, "TestChooser"); - /** This listener ends application when window is closed (x button on top right corner of test chooser). + super("TestChooser"); + /* This listener ends application when window is closed (x button on top right corner of test chooser). * @see issue#85 https://github.com/jMonkeyEngine/jmonkeyengine/issues/85 */ setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE); } + @Override + public void dispose() { + if (executorService != null) { + executorService.shutdownNow(); + } + + super.dispose(); + } + /** * @param classes * vector that receives the found classes * @return classes vector, list of all the classes in a given package (must * be found in classpath). */ - protected Vector find(String pckgname, boolean recursive, - Vector classes) { - URL url; - + private void find(String packageName, boolean recursive, Set> classes) { // Translate the package name into an absolute path - String name = pckgname; + String name = packageName; if (!name.startsWith("/")) { name = "/" + name; } name = name.replace('.', '/'); // Get a File object for the package - // URL url = UPBClassLoader.get().getResource(name); - url = this.getClass().getResource(name); - // URL url = ClassLoader.getSystemClassLoader().getResource(name); - pckgname = pckgname + "."; - - File directory; + packageName = packageName + "."; + URI uri; + FileSystem fileSystem = null; try { - directory = new File(URLDecoder.decode(url.getFile(), "UTF-8")); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException(e); // should never happen + uri = this.getClass().getResource(name).toURI(); + } catch (URISyntaxException e) { + throw new RuntimeException("Failed to load demo classes.", e); } - if (directory.exists()) { - logger.fine("Searching for Demo classes in \"" - + directory.getName() + "\"."); - addAllFilesInDirectory(directory, classes, pckgname, recursive); - } else { + // Special case if we are running from inside a JAR + if ("jar".equalsIgnoreCase(uri.getScheme())) { try { - // It does not work with the filesystem: we must - // be in the case of a package contained in a jar file. - logger.fine("Searching for Demo classes in \"" + url + "\"."); - URLConnection urlConnection = url.openConnection(); - if (urlConnection instanceof JarURLConnection) { - JarURLConnection conn = (JarURLConnection) urlConnection; - - JarFile jfile = conn.getJarFile(); - Enumeration e = jfile.entries(); - while (e.hasMoreElements()) { - ZipEntry entry = (ZipEntry) e.nextElement(); - Class result = load(entry.getName()); - if (result != null && !classes.contains(result)) { - classes.add(result); - } - } - } + fileSystem = FileSystems.newFileSystem(uri, Collections.emptyMap()); } catch (IOException e) { - logger.logp(Level.SEVERE, this.getClass().toString(), - "find(pckgname, recursive, classes)", "Exception", e); - } catch (Exception e) { - logger.logp(Level.SEVERE, this.getClass().toString(), - "find(pckgname, recursive, classes)", "Exception", e); + throw new RuntimeException("Failed to load demo classes from JAR.", e); + } + } + + try { + Path directory = Paths.get(uri); + logger.log( + Level.FINE, + "Searching for Demo classes in \"{0}\".", + directory.getFileName().toString() + ); + addAllFilesInDirectory(directory, classes, packageName, recursive); + } catch (Exception e) { + logger.logp( + Level.SEVERE, + this.getClass().toString(), + "find(pckgname, recursive, classes)", + "Exception", + e + ); + } finally { + if (fileSystem != null) { + try { + fileSystem.close(); + } catch (IOException e) { + logger.log(Level.WARNING, "Failed to close JAR.", e); + } } } - return classes; } /** @@ -163,36 +179,26 @@ protected Vector find(String pckgname, boolean recursive, * not contain a main method */ private Class load(String name) { - if (name.endsWith(".class") - && name.indexOf("Test") >= 0 - && name.indexOf('$') < 0) { - String classname = name.substring(0, name.length() - - ".class".length()); - - if (classname.startsWith("/")) { - classname = classname.substring(1); - } - classname = classname.replace('/', '.'); + String classname = name.substring(0, name.length() - ".class".length()); - try { - final Class cls = Class.forName(classname); - cls.getMethod("main", new Class[] { String[].class }); - if (!getClass().equals(cls)) { - return cls; - } - } catch (NoClassDefFoundError e) { - // class has unresolved dependencies - return null; - } catch (ClassNotFoundException e) { - // class not in classpath - return null; - } catch (NoSuchMethodException e) { - // class does not have a main method - return null; - } catch (UnsupportedClassVersionError e){ - // unsupported version - return null; + if (classname.startsWith("/")) { + classname = classname.substring(1); + } + classname = classname.replace('/', '.'); + + try { + final Class cls = Class.forName(classname); + cls.getMethod("main", new Class[] { String[].class }); + if (!getClass().equals(cls)) { + return cls; } + } catch ( + NoClassDefFoundError // class has unresolved dependencies + | ClassNotFoundException // class not in classpath + | NoSuchMethodException // class does not have a main method + | UnsupportedClassVersionError e + ) { // unsupported version + return null; } return null; } @@ -205,29 +211,39 @@ private Class load(String name) { * @param allClasses * add loaded classes to this collection * @param packageName - * current package name for the diven directory + * current package name for the given directory * @param recursive - * true to descent into subdirectories + * true to descend into subdirectories */ - private void addAllFilesInDirectory(File directory, - Collection allClasses, String packageName, boolean recursive) { + private void addAllFilesInDirectory( + final Path directory, + final Set> allClasses, + final String packageName, + final boolean recursive + ) { // Get the list of the files contained in the package - File[] files = directory.listFiles(getFileFilter()); - if (files != null) { - for (int i = 0; i < files.length; i++) { + try (DirectoryStream stream = Files.newDirectoryStream(directory, getFileFilter())) { + for (Path file : stream) { // we are only interested in .class files - if (files[i].isDirectory()) { + if (Files.isDirectory(file)) { if (recursive) { - addAllFilesInDirectory(files[i], allClasses, - packageName + files[i].getName() + ".", 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 + files[i].getName()); + Class result = load(packageName + file.getFileName()); if (result != null && !allClasses.contains(result)) { allClasses.add(result); } } } + } catch (IOException ex) { + logger.log(Level.SEVERE, "Could not search the folder!", ex); } } @@ -235,75 +251,90 @@ private void addAllFilesInDirectory(File directory, * @return FileFilter for searching class files (no inner classes, only * those with "Test" in the name) */ - private FileFilter getFileFilter() { - return new FileFilter() { - public boolean accept(File pathname) { - return (pathname.isDirectory() && !pathname.getName().startsWith(".")) - || (pathname.getName().endsWith(".class") - && (pathname.getName().indexOf("Test") >= 0) - && pathname.getName().indexOf('$') < 0); + private static DirectoryStream.Filter getFileFilter() { + return new DirectoryStream.Filter() { + @Override + public boolean accept(Path entry) throws IOException { + String fileName = entry.getFileName().toString(); + return ( + (fileName.endsWith(".class") && (fileName.contains("Test")) && !fileName.contains("$")) || + (!fileName.startsWith(".") && Files.isDirectory(entry)) + ); } }; } - private void startApp(final java.util.List appClass){ - if (appClass == null){ - JOptionPane.showMessageDialog(rootPane, - "Please select a test from the list", - "Error", - JOptionPane.ERROR_MESSAGE); + private void startApp(final List> appClass) { + if (appClass == null || appClass.isEmpty()) { + JOptionPane.showMessageDialog( + rootPane, + "Please select a test from the list", + "Error", + JOptionPane.ERROR_MESSAGE + ); return; } - new Thread(new Runnable(){ - public void run(){ - for (int i = 0; i < appClass.size(); i++) { - Class clazz = (Class)appClass.get(i); - try { - if (LegacyApplication.class.isAssignableFrom(clazz)) { - Object app = clazz.newInstance(); - if (app instanceof SimpleApplication) { - final Method settingMethod = clazz.getMethod("setShowSettings", boolean.class); - settingMethod.invoke(app, showSetting); - } - final Method mainMethod = clazz.getMethod("start"); - mainMethod.invoke(app); - Field contextField = LegacyApplication.class.getDeclaredField("context"); - contextField.setAccessible(true); - JmeContext context = null; - while (context == null) { - context = (JmeContext) contextField.get(app); - Thread.sleep(100); - } - while (!context.isCreated()) { - Thread.sleep(100); - } - while (context.isCreated()) { - Thread.sleep(100); - } - } else { - final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); - mainMethod.invoke(clazz, new Object[]{new String[0]}); - } - // wait for destroy - System.gc(); - } catch (IllegalAccessException ex) { - logger.log(Level.SEVERE, "Cannot access constructor: "+clazz.getName(), ex); - } catch (IllegalArgumentException ex) { - logger.log(Level.SEVERE, "main() had illegal argument: "+clazz.getName(), ex); - } catch (InvocationTargetException ex) { - logger.log(Level.SEVERE, "main() method had exception: "+clazz.getName(), ex); - } catch (InstantiationException ex) { - logger.log(Level.SEVERE, "Failed to create app: "+clazz.getName(), ex); - } catch (NoSuchMethodException ex){ - logger.log(Level.SEVERE, "Test class doesn't have main method: "+clazz.getName(), ex); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Cannot start test: "+clazz.getName(), ex); - ex.printStackTrace(); + executorService.submit(getAppRunner(appClass)); + } + + private Runnable getAppRunner(final List> appClass) { + return new Runnable() { + @Override + public void run() { + for (Class clazz : appClass) { + try { + if (LegacyApplication.class.isAssignableFrom(clazz)) { + Object app = clazz.getDeclaredConstructor().newInstance(); + if (app instanceof SimpleApplication) { + final Method settingMethod = clazz.getMethod( + "setShowSettings", + boolean.class + ); + settingMethod.invoke(app, showSetting); + } + final Method mainMethod = clazz.getMethod("start"); + mainMethod.invoke(app); + Field contextField = LegacyApplication.class.getDeclaredField("context"); + contextField.setAccessible(true); + JmeContext context = null; + while (context == null) { + context = (JmeContext) contextField.get(app); + Thread.sleep(100); + } + while (!context.isCreated()) { + Thread.sleep(100); + } + while (context.isCreated()) { + Thread.sleep(100); + } + } else { + final Method mainMethod = clazz.getMethod("main", (new String[0]).getClass()); + mainMethod.invoke(clazz, new Object[] { new String[0] }); } - } + // wait for destroy + System.gc(); + } catch (IllegalAccessException ex) { + logger.log(Level.SEVERE, "Cannot access constructor: " + clazz.getName(), ex); + } catch (IllegalArgumentException ex) { + logger.log(Level.SEVERE, "main() had illegal argument: " + clazz.getName(), ex); + } catch (InvocationTargetException ex) { + logger.log(Level.SEVERE, "main() method had exception: " + clazz.getName(), ex); + } catch (InstantiationException ex) { + logger.log(Level.SEVERE, "Failed to create app: " + clazz.getName(), ex); + } catch (NoSuchMethodException ex) { + logger.log( + Level.SEVERE, + "Test class doesn't have main method: " + clazz.getName(), + ex + ); + } catch (Exception ex) { + logger.log(Level.SEVERE, "Cannot start test: " + clazz.getName(), ex); + ex.printStackTrace(); + } } - }).start(); + } + }; } /** @@ -312,7 +343,7 @@ public void run(){ * @param classes * what Classes to show in the list box */ - private void setup(Vector classes) { + private void setup(Collection> classes) { final JPanel mainPanel = new JPanel(); mainPanel.setLayout(new BorderLayout()); getContentPane().setLayout(new BorderLayout()); @@ -321,8 +352,9 @@ private void setup(Vector classes) { final FilteredJList list = new FilteredJList(); list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); - DefaultListModel model = new DefaultListModel(); - for (Class c : classes) { + DefaultListModel> model = new DefaultListModel<>(); + model.ensureCapacity(classes.size()); + for (Class c : classes) { model.addElement(c); } list.setModel(model); @@ -330,29 +362,38 @@ private void setup(Vector classes) { mainPanel.add(createSearchPanel(list), BorderLayout.NORTH); mainPanel.add(new JScrollPane(list), BorderLayout.CENTER); - list.getSelectionModel().addListSelectionListener( + list + .getSelectionModel() + .addListSelectionListener( new ListSelectionListener() { + @Override public void valueChanged(ListSelectionEvent e) { selectedClass = list.getSelectedValuesList(); } - }); - list.addMouseListener(new MouseAdapter() { - public void mouseClicked(MouseEvent e) { - if (e.getClickCount() == 2 && selectedClass != null) { - startApp(selectedClass); + } + ); + list.addMouseListener( + new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2 && selectedClass != null) { + startApp(selectedClass); + } } } - }); - list.addKeyListener(new KeyAdapter() { - @Override - public void keyTyped(KeyEvent e) { - if (e.getKeyCode() == KeyEvent.VK_ENTER) { - startApp(selectedClass); - } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { - dispose(); + ); + list.addKeyListener( + new KeyAdapter() { + @Override + public void keyTyped(KeyEvent e) { + if (e.getKeyCode() == KeyEvent.VK_ENTER) { + startApp(selectedClass); + } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) { + dispose(); + } } } - }); + ); final JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); mainPanel.add(buttonPanel, BorderLayout.PAGE_END); @@ -361,36 +402,46 @@ public void keyTyped(KeyEvent e) { okButton.setMnemonic('O'); buttonPanel.add(okButton); getRootPane().setDefaultButton(okButton); - okButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - startApp(selectedClass); + okButton.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + startApp(selectedClass); + } } - }); + ); final JButton cancelButton = new JButton("Cancel"); cancelButton.setMnemonic('C'); buttonPanel.add(cancelButton); - cancelButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - dispose(); + cancelButton.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + dispose(); + } } - }); + ); pack(); center(); } - private class FilteredJList extends JList { + private class FilteredJList extends JList> { + private static final long serialVersionUID = 1L; private String filter; private ListModel originalModel; + @Override + @SuppressWarnings("unchecked") public void setModel(ListModel m) { originalModel = m; super.setModel(m); } + @SuppressWarnings("unchecked") private void update() { if (filter == null || filter.length() == 0) { super.setModel(originalModel); @@ -433,8 +484,10 @@ private void center() { if (frameSize.width > screenSize.width) { frameSize.width = screenSize.width; } - this.setLocation((screenSize.width - frameSize.width) / 2, - (screenSize.height - frameSize.height) / 2); + this.setLocation( + (screenSize.width - frameSize.width) / 2, + (screenSize.height - frameSize.height) / 2 + ); } /** @@ -446,56 +499,80 @@ private void center() { public static void main(final String[] args) { try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); - } catch (Exception e) { - } + } catch (Exception e) {} new TestChooser().start(args); } protected void start(String[] args) { - final Vector classes = new Vector(); + executorService = + new ThreadPoolExecutor( + 1, + Integer.MAX_VALUE, + 60L, + TimeUnit.SECONDS, + new SynchronousQueue<>(), + new ThreadFactory() { + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "AppStarter"); + } + } + ); + final Set> classes = new LinkedHashSet<>(); logger.fine("Composing Test list..."); addDisplayedClasses(classes); setup(classes); - Class cls; setVisible(true); } - protected void addDisplayedClasses(Vector classes) { + protected void addDisplayedClasses(Set> classes) { find("jme3test", true, classes); } private JPanel createSearchPanel(final FilteredJList classes) { JPanel search = new JPanel(); search.setLayout(new BorderLayout()); - search.add(new JLabel("Choose a Demo to start: Find: "), - BorderLayout.WEST); + search.add(new JLabel("Choose a Demo to start: Find: "), BorderLayout.WEST); final javax.swing.JTextField jtf = new javax.swing.JTextField(); - jtf.getDocument().addDocumentListener(new DocumentListener() { - public void removeUpdate(DocumentEvent e) { - classes.setFilter(jtf.getText()); - } + jtf + .getDocument() + .addDocumentListener( + new DocumentListener() { + @Override + public void removeUpdate(DocumentEvent e) { + classes.setFilter(jtf.getText()); + } - public void insertUpdate(DocumentEvent e) { - classes.setFilter(jtf.getText()); - } + @Override + public void insertUpdate(DocumentEvent e) { + classes.setFilter(jtf.getText()); + } - public void changedUpdate(DocumentEvent e) { - classes.setFilter(jtf.getText()); - } - }); - jtf.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - selectedClass = classes.getSelectedValuesList(); - startApp(selectedClass); + @Override + public void changedUpdate(DocumentEvent e) { + classes.setFilter(jtf.getText()); + } + } + ); + jtf.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + selectedClass = classes.getSelectedValuesList(); + startApp(selectedClass); + } } - }); + ); final JCheckBox showSettingCheck = new JCheckBox("Show Setting"); showSettingCheck.setSelected(true); - showSettingCheck.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - showSetting = showSettingCheck.isSelected(); + showSettingCheck.addActionListener( + new ActionListener() { + @Override + public void actionPerformed(ActionEvent e) { + showSetting = showSettingCheck.isSelected(); + } } - }); + ); jtf.setPreferredSize(new Dimension(100, 25)); search.add(jtf, BorderLayout.CENTER); search.add(showSettingCheck, BorderLayout.EAST); diff --git a/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java b/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java index 392792ac2c..fcad3b0f10 100644 --- a/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java +++ b/jme3-examples/src/main/java/jme3test/animation/TestCameraMotionPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -92,13 +92,14 @@ public void simpleInitApp() { rootNode.attachChild(camNode); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - final BitmapText wayPointsText = new BitmapText(guiFont, false); + final BitmapText wayPointsText = new BitmapText(guiFont); wayPointsText.setSize(guiFont.getCharSet().getRenderedSize()); guiNode.attachChild(wayPointsText); path.addListener(new MotionPathListener() { + @Override public void onWayPointReach(MotionEvent control, int wayPointIndex) { if (path.getNbWayPoints() == wayPointIndex + 1) { wayPointsText.setText(control.getSpatial().getName() + " Finish!!! "); @@ -156,6 +157,7 @@ private void initInputs() { inputManager.addMapping("play_stop", new KeyTrigger(KeyInput.KEY_SPACE)); ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("display_hidePath") && keyPressed) { if (active) { diff --git a/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java index 8332ffafe2..6bce59d798 100644 --- a/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java +++ b/jme3-examples/src/main/java/jme3test/animation/TestCinematic.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,12 +31,16 @@ */ package jme3test.animation; -import com.jme3.animation.*; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimFactory; +import com.jme3.animation.LoopMode; import com.jme3.app.SimpleApplication; import com.jme3.cinematic.*; import com.jme3.cinematic.events.*; import com.jme3.font.BitmapText; import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; @@ -50,18 +54,16 @@ import com.jme3.scene.*; import com.jme3.scene.shape.Box; import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.texture.image.ColorSpace; import de.lessvoid.nifty.Nifty; -//TODO rework this Test when the new animation system is done. public class TestCinematic extends SimpleApplication { private Spatial model; private Spatial teapot; - private MotionPath path; private MotionEvent cameraMotionEvent; private Cinematic cinematic; private ChaseCamera chaseCam; - private FilterPostProcessor fpp; private FadeFilter fade; private float time = 0; @@ -75,16 +77,21 @@ public static void main(String[] args) { @Override public void simpleInitApp() { //just some text + + ColorSpace colorSpace = renderer.isMainFrameBufferSrgb() + ? ColorSpace.sRGB : ColorSpace.Linear; NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(getAssetManager(), getInputManager(), getAudioRenderer(), - getGuiViewPort()); + guiViewPort, + colorSpace); + Nifty nifty; nifty = niftyDisplay.getNifty(); nifty.fromXmlWithoutStartScreen("Interface/Nifty/CinematicTest.xml"); getGuiViewPort().addProcessor(niftyDisplay); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - final BitmapText text = new BitmapText(guiFont, false); + final BitmapText text = new BitmapText(guiFont); text.setSize(guiFont.getCharSet().getRenderedSize()); text.setText("Press enter to play/pause cinematic"); text.setLocalTranslation((cam.getWidth() - text.getLineWidth()) / 2, cam.getHeight(), 0); @@ -99,26 +106,43 @@ public void simpleInitApp() { createCameraMotion(); //creating spatial animation for the teapot - AnimationFactory factory = new AnimationFactory(20, "teapotAnim"); + AnimFactory factory = new AnimFactory(20f, "teapotAnim", 30f); factory.addTimeTranslation(0, new Vector3f(10, 0, 10)); factory.addTimeTranslation(20, new Vector3f(10, 0, -10)); factory.addTimeScale(10, new Vector3f(4, 4, 4)); factory.addTimeScale(20, new Vector3f(1, 1, 1)); - factory.addTimeRotationAngles(20, 0, 4 * FastMath.TWO_PI, 0); - AnimControl control = new AnimControl(); - control.addAnim(factory.buildAnimation()); - teapot.addControl(control); + for (int iStep = 1; iStep <= 12; ++iStep) { + float animationTime = iStep * 20f / 12; // in seconds + float yRotationAngle = iStep * FastMath.TWO_PI / 3; // in radians + factory.addTimeRotation(animationTime, 0f, yRotationAngle, 0f); + } + AnimClip spatialAnimation = factory.buildAnimation(teapot); + AnimComposer teapotComposer = new AnimComposer(); + teapotComposer.addAnimClip(spatialAnimation); + teapot.addControl(teapotComposer); //fade in cinematic.addCinematicEvent(0, new FadeEvent(true)); // cinematic.activateCamera(0, "aroundCam"); - cinematic.addCinematicEvent(0, new AnimationEvent(teapot, "teapotAnim", LoopMode.DontLoop)); + + // 20-second spatial animation begins at t=0 seconds + AnimEvent teapotAnimEvent = new AnimEvent(teapotComposer, "teapotAnim", + AnimComposer.DEFAULT_LAYER); + cinematic.addCinematicEvent(0f, teapotAnimEvent); + cinematic.addCinematicEvent(0, cameraMotionEvent); cinematic.addCinematicEvent(0, new SoundEvent("Sound/Environment/Nature.ogg", LoopMode.Loop)); cinematic.addCinematicEvent(3f, new SoundEvent("Sound/Effects/kick.wav")); cinematic.addCinematicEvent(3, new SubtitleTrack(nifty, "start", 3, "jMonkey engine really kicks A...")); cinematic.addCinematicEvent(5.1f, new SoundEvent("Sound/Effects/Beep.ogg", 1)); - cinematic.addCinematicEvent(2, new AnimationEvent(model, "Walk", LoopMode.Loop)); + + // 1.24-second bone animation loop begins at t=2 seconds + AnimComposer otoComposer = model.getControl(AnimComposer.class); + AnimEvent walkEvent = new AnimEvent(otoComposer, "Walk", + AnimComposer.DEFAULT_LAYER); + walkEvent.setLoopMode(LoopMode.Loop); + cinematic.addCinematicEvent(2f, walkEvent); + cinematic.activateCamera(0, "topView"); // cinematic.activateCamera(10, "aroundCam"); @@ -148,15 +172,18 @@ public void simpleInitApp() { cinematic.addListener(new CinematicEventListener() { + @Override public void onPlay(CinematicEvent cinematic) { chaseCam.setEnabled(false); System.out.println("play"); } + @Override public void onPause(CinematicEvent cinematic) { System.out.println("pause"); } + @Override public void onStop(CinematicEvent cinematic) { chaseCam.setEnabled(true); fade.setValue(1); @@ -178,7 +205,7 @@ private void createCameraMotion() { camNode.lookAt(teapot.getLocalTranslation(), Vector3f.UNIT_Y); CameraNode camNode2 = cinematic.bindCamera("aroundCam", cam); - path = new MotionPath(); + MotionPath path = new MotionPath(); path.setCycle(true); path.addWayPoint(new Vector3f(20, 3, 0)); path.addWayPoint(new Vector3f(0, 3, 20)); @@ -194,7 +221,7 @@ private void createCameraMotion() { private void createScene() { - model = assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); model.center(); model.setShadowMode(ShadowMode.CastAndReceive); rootNode.attachChild(model); @@ -224,7 +251,7 @@ private void createScene() { light.setColor(ColorRGBA.White.mult(1.5f)); rootNode.addLight(light); - fpp = new FilterPostProcessor(assetManager); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); fade = new FadeFilter(); fpp.addFilter(fade); @@ -239,11 +266,12 @@ private void createScene() { } private void initInputs() { - inputManager.addMapping("togglePause", new KeyTrigger(keyInput.KEY_RETURN)); - inputManager.addMapping("navFwd", new KeyTrigger(keyInput.KEY_RIGHT)); - inputManager.addMapping("navBack", new KeyTrigger(keyInput.KEY_LEFT)); + inputManager.addMapping("togglePause", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addMapping("navFwd", new KeyTrigger(KeyInput.KEY_RIGHT)); + inputManager.addMapping("navBack", new KeyTrigger(KeyInput.KEY_LEFT)); ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("togglePause") && keyPressed) { if (cinematic.getPlayState() == PlayState.Playing) { diff --git a/jme3-examples/src/main/java/jme3test/animation/TestIssue1138.java b/jme3-examples/src/main/java/jme3test/animation/TestIssue1138.java deleted file mode 100644 index ac390aaf51..0000000000 --- a/jme3-examples/src/main/java/jme3test/animation/TestIssue1138.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (c) 2019 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.animation; - -import com.jme3.anim.AnimComposer; -import com.jme3.anim.Joint; -import com.jme3.anim.SkinningControl; -import com.jme3.app.SimpleApplication; -import com.jme3.light.AmbientLight; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; - -/** - * Test case for JME issue #1138: Elephant's legUp animation sets Joint - * translation to NaN. - *

                - * If successful, the animation cycle will complete without throwing an - * IllegalStateException. - * - * @author Stephen Gold - */ -public class TestIssue1138 extends SimpleApplication { - - SkinningControl sControl; - - public static void main(String... argv) { - new TestIssue1138().start(); - } - - @Override - public void simpleInitApp() { - Node cgModel = (Node) assetManager.loadModel( - "Models/Elephant/Elephant.mesh.xml"); - rootNode.attachChild(cgModel); - cgModel.rotate(0f, -1f, 0f); - cgModel.scale(0.04f); - - AnimComposer composer = cgModel.getControl(AnimComposer.class); - composer.setCurrentAction("legUp"); - sControl = cgModel.getControl(SkinningControl.class); - - AmbientLight light = new AmbientLight(); - rootNode.addLight(light); - } - - @Override - public void simpleUpdate(float tpf) { - for (Joint joint : sControl.getArmature().getJointList()) { - Vector3f translation = joint.getLocalTranslation(); - if (!Vector3f.isValidVector(translation)) { - String msg = "Invalid translation for joint " + joint.getName(); - throw new IllegalStateException(msg); - } - } - } -} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestIssue2076.java b/jme3-examples/src/main/java/jme3test/animation/TestIssue2076.java new file mode 100644 index 0000000000..80fcac11b6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/TestIssue2076.java @@ -0,0 +1,132 @@ +/* + * 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.animation; + +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; + +/** + * Test for JMonkeyEngine issue #2076: software skinning requires vertex + * normals. + * + *

                If the issue is resolved, 2 copies of the Jaime model will be rendered. + * + *

                If the issue is present, then the application will immediately crash, + * typically with a {@code NullPointerException}. + * + * @author Stephen Gold + */ +public class TestIssue2076 extends SimpleApplication { + /** + * Main entry point for the TestIssue2076 application. + * + * @param args array of command-line arguments (not null) + */ + public static void main(String... args) { + TestIssue2076 app = new TestIssue2076(); + app.start(); + } + + /** + * Initialize this application. + */ + @Override + public void simpleInitApp() { + AmbientLight ambientLight = new AmbientLight(); + ambientLight.setColor(new ColorRGBA(1f, 1f, 1f, 1f)); + rootNode.addLight(ambientLight); + /* + * The original Jaime model was chosen for testing because it includes + * tangent buffers (needed to trigger issue #2076) and uses the old + * animation system (so it can be easily used to test both systems). + */ + String assetPath = "Models/Jaime/Jaime.j3o"; + + testOldAnimationSystem(assetPath); + testNewAnimationSystem(assetPath); + } + + /** + * Attach the specified model (which uses the old animation system) to the + * scene graph, enable software skinning, and remove its vertex normals. + * + * @param assetPath the asset path of the test model (not null) + */ + private void testOldAnimationSystem(String assetPath) { + Node oldJaime = (Node) assetManager.loadModel(assetPath); + rootNode.attachChild(oldJaime); + oldJaime.setLocalTranslation(-1f, 0f, 0f); + + // enable software skinning: + SkeletonControl skeletonControl + = oldJaime.getControl(SkeletonControl.class); + skeletonControl.setHardwareSkinningPreferred(false); + + // remove its vertex normals: + Geometry oldGeometry = (Geometry) oldJaime.getChild(0); + Mesh oldMesh = oldGeometry.getMesh(); + oldMesh.clearBuffer(VertexBuffer.Type.Normal); + oldMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + } + + /** + * Attach the specified model, converted to the new animation system, to the + * scene graph, enable software skinning, and remove its vertex normals. + * + * @param assetPath the asset path of the test model (not null) + */ + private void testNewAnimationSystem(String assetPath) { + Node newJaime = (Node) assetManager.loadModel(assetPath); + AnimMigrationUtils.migrate(newJaime); + rootNode.attachChild(newJaime); + newJaime.setLocalTranslation(1f, 0f, 0f); + + // enable software skinning: + SkinningControl skinningControl + = newJaime.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(false); + + // remove its vertex normals: + Geometry newGeometry = (Geometry) newJaime.getChild(0); + Mesh newMesh = newGeometry.getMesh(); + newMesh.clearBuffer(VertexBuffer.Type.Normal); + newMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + } +} diff --git a/jme3-examples/src/main/java/jme3test/animation/TestJaime.java b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java index 330788d089..a841b16e40 100644 --- a/jme3-examples/src/main/java/jme3test/animation/TestJaime.java +++ b/jme3-examples/src/main/java/jme3test/animation/TestJaime.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,10 @@ */ package jme3test.animation; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimationFactory; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimFactory; +import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.animation.LoopMode; import com.jme3.app.DebugKeysAppState; import com.jme3.app.FlyCamAppState; @@ -42,7 +44,7 @@ import com.jme3.cinematic.Cinematic; import com.jme3.cinematic.MotionPath; import com.jme3.cinematic.PlayState; -import com.jme3.cinematic.events.AnimationEvent; +import com.jme3.cinematic.events.AnimEvent; import com.jme3.cinematic.events.MotionEvent; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -73,7 +75,7 @@ public class TestJaime extends SimpleApplication { - Cinematic cinematic; + private Cinematic cinematic; public static void main(String... argv){ TestJaime app = new TestJaime(); @@ -99,6 +101,7 @@ public void simpleInitApp() { public Node LoadModel() { Node jaime = (Node)assetManager.loadModel("Models/Jaime/Jaime.j3o"); + AnimMigrationUtils.migrate(jaime); jaime.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); rootNode.attachChild(jaime); return jaime; @@ -132,7 +135,7 @@ public void setupLights() { FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - SSAOFilter filter = new SSAOFilter(0.10997847f,0.440001f,0.39999998f,-0.008000026f);; + SSAOFilter filter = new SSAOFilter(0.10997847f,0.440001f,0.39999998f,-0.008000026f); fpp.addFilter(filter); fpp.addFilter(new FXAAFilter()); fpp.addFilter(new FXAAFilter()); @@ -149,23 +152,49 @@ public void setupCinematic(final Node jaime) { stateManager.attach(cinematic); jaime.move(0, 0, -3); - AnimationFactory af = new AnimationFactory(0.7f, "JumpForward"); + AnimFactory af = new AnimFactory(1f, "JumpForward", 30f); af.addTimeTranslation(0, new Vector3f(0, 0, -3)); af.addTimeTranslation(0.35f, new Vector3f(0, 1, -1.5f)); af.addTimeTranslation(0.7f, new Vector3f(0, 0, 0)); - jaime.getControl(AnimControl.class).addAnim(af.buildAnimation()); - - cinematic.enqueueCinematicEvent(new AnimationEvent(jaime, "Idle",3, LoopMode.DontLoop)); - float jumpStart = cinematic.enqueueCinematicEvent(new AnimationEvent(jaime, "JumpStart", LoopMode.DontLoop)); - cinematic.addCinematicEvent(jumpStart+0.2f, new AnimationEvent(jaime, "JumpForward", LoopMode.DontLoop,1)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "JumpEnd", LoopMode.DontLoop)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Punches", LoopMode.DontLoop)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "SideKick", LoopMode.DontLoop)); - float camStart = cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Taunt", LoopMode.DontLoop)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Idle",1, LoopMode.DontLoop)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Wave", LoopMode.DontLoop)); - cinematic.enqueueCinematicEvent( new AnimationEvent(jaime, "Idle", LoopMode.DontLoop)); - + AnimClip forwardClip = af.buildAnimation(jaime); + AnimComposer composer = jaime.getControl(AnimComposer.class); + composer.addAnimClip(forwardClip); + /* + * Add a clip that warps the model to its starting position. + */ + AnimFactory af2 = new AnimFactory(0.01f, "StartingPosition", 30f); + af2.addTimeTranslation(0f, new Vector3f(0f, 0f, -3f)); + AnimClip startClip = af2.buildAnimation(jaime); + composer.addAnimClip(startClip); + + composer.makeLayer("SpatialLayer", null); + String boneLayer = AnimComposer.DEFAULT_LAYER; + + cinematic.addCinematicEvent(0f, + new AnimEvent(composer, "StartingPosition", "SpatialLayer")); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "Idle", boneLayer)); + float jumpStart = cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "JumpStart", boneLayer)); + cinematic.addCinematicEvent(jumpStart + 0.2f, + new AnimEvent(composer, "JumpForward", "SpatialLayer")); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "JumpEnd", boneLayer)); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "Punches", boneLayer)); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "SideKick", boneLayer)); + + float camStart = cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "Taunt", boneLayer)); + AnimEvent idleOneSecond = new AnimEvent(composer, "Idle", boneLayer); + idleOneSecond.setInitialDuration(1f); + cinematic.enqueueCinematicEvent(idleOneSecond); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "Wave", boneLayer)); + cinematic.enqueueCinematicEvent( + new AnimEvent(composer, "Idle", boneLayer)); + CameraNode camNode = cinematic.bindCamera("cam", cam); camNode.setLocalTranslation(new Vector3f(1.1f, 1.2f, 2.9f)); camNode.lookAt(new Vector3f(0, 0.5f, 0), Vector3f.UNIT_Y); @@ -212,6 +241,7 @@ public void setupInput() { inputManager.addMapping("start", new KeyTrigger(KeyInput.KEY_PAUSE)); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if(name.equals("start") && isPressed){ if(cinematic.getPlayState() != PlayState.Playing){ diff --git a/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java b/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java index 02bc5335c9..7d218258bf 100644 --- a/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java +++ b/jme3-examples/src/main/java/jme3test/animation/TestMotionPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -85,13 +85,14 @@ public void simpleInitApp() { motionControl.setInitialDuration(10f); motionControl.setSpeed(2f); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - final BitmapText wayPointsText = new BitmapText(guiFont, false); + final BitmapText wayPointsText = new BitmapText(guiFont); wayPointsText.setSize(guiFont.getCharSet().getRenderedSize()); guiNode.attachChild(wayPointsText); path.addListener(new MotionPathListener() { + @Override public void onWayPointReach(MotionEvent control, int wayPointIndex) { if (path.getNbWayPoints() == wayPointIndex + 1) { wayPointsText.setText(control.getSpatial().getName() + "Finished!!! "); @@ -153,6 +154,7 @@ private void initInputs() { inputManager.addMapping("play_stop", new KeyTrigger(KeyInput.KEY_SPACE)); ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("display_hidePath") && keyPressed) { if (active) { diff --git a/jme3-examples/src/main/java/jme3test/animation/package-info.java b/jme3-examples/src/main/java/jme3test/animation/package-info.java new file mode 100644 index 0000000000..94a164c463 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/animation/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for animation and cinematics + */ +package jme3test.animation; diff --git a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java index b90d28310e..811c90ebb1 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java +++ b/jme3-examples/src/main/java/jme3test/app/TestBareBonesApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,7 +72,7 @@ public void update(){ float tpf = timer.getTimePerFrame(); boxGeom.rotate(tpf * 2, tpf * 4, tpf * 3); - // dont forget to update the scenes + // don't forget to update the scenes boxGeom.updateLogicalState(tpf); boxGeom.updateGeometricState(); diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java b/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java index 132fb44a38..055d5acd00 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java +++ b/jme3-examples/src/main/java/jme3test/app/TestCloneSpatial.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 jMonkeyEngine + * Copyright (c) 2016-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,7 +54,7 @@ public class TestCloneSpatial { public static void main( String... args ) throws Exception { - // Setup a test node with some children, controls, etc. + // Set up a test node with some children, controls, etc. Node root = new Node("rootNode"); // A root light @@ -117,7 +117,7 @@ public static void main( String... args ) throws Exception { /** * Debug dump to check structure and identity */ - public static void dump( String indent, Spatial s ) { + private static void dump( String indent, Spatial s ) { if( s instanceof Node ) { dump(indent, (Node)s); } else if( s instanceof Geometry ) { diff --git a/jme3-examples/src/main/java/jme3test/app/TestCloner.java b/jme3-examples/src/main/java/jme3test/app/TestCloner.java index 4ae105603b..7685333003 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestCloner.java +++ b/jme3-examples/src/main/java/jme3test/app/TestCloner.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 jMonkeyEngine + * Copyright (c) 2016-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -125,6 +125,7 @@ public RegularObject( int i ) { this.i = i; } + @Override public RegularObject clone() { try { return (RegularObject)super.clone(); @@ -133,6 +134,7 @@ public RegularObject clone() { } } + @Override public String toString() { return getClass().getSimpleName() + "@" + System.identityHashCode(this) + "[i=" + i + "]"; @@ -147,6 +149,7 @@ public RegularSubclass( int i, String name ) { this.name = name; } + @Override public String toString() { return getClass().getSimpleName() + "@" + System.identityHashCode(this) + "[i=" + i + ", name=" + name + "]"; @@ -163,6 +166,7 @@ public Parent( String name, int age ) { this.rsc = new RegularSubclass(age, name); } + @Override public Parent clone() { try { return (Parent)super.clone(); @@ -171,17 +175,20 @@ public Parent clone() { } } + @Override public Parent jmeClone() { // Ok to delegate to clone() in this case because no deep // cloning is done there. return clone(); } + @Override public void cloneFields( Cloner cloner, Object original ) { this.ro = cloner.clone(ro); this.rsc = cloner.clone(rsc); } + @Override public String toString() { return getClass().getSimpleName() + "@" + System.identityHashCode(this) + "[ro=" + ro + ", rsc=" + rsc + "]"; @@ -190,7 +197,7 @@ public String toString() { public static class GraphNode implements Cloneable, JmeCloneable { - private String name; + final private String name; private List links = new ArrayList<>(); public GraphNode( String name ) { @@ -230,6 +237,7 @@ public List getLinks() { return links; } + @Override public GraphNode jmeClone() { try { return (GraphNode)super.clone(); @@ -238,10 +246,12 @@ public GraphNode jmeClone() { } } + @Override public void cloneFields( Cloner cloner, Object original ) { this.links = cloner.clone(links); } + @Override public String toString() { return getClass().getSimpleName() + "@" + System.identityHashCode(this) + "[name=" + name + "]"; @@ -252,9 +262,9 @@ public static class ArrayHolder implements JmeCloneable { private int[] intArray; private int[][] intArray2D; - private Object[] objects; + final private Object[] objects; private RegularObject[] regularObjects; - private String[] strings; + final private String[] strings; public ArrayHolder( int... values ) { this.intArray = values; @@ -273,6 +283,7 @@ public ArrayHolder( int... values ) { } } + @Override public ArrayHolder jmeClone() { try { return (ArrayHolder)super.clone(); @@ -281,6 +292,7 @@ public ArrayHolder jmeClone() { } } + @Override public void cloneFields( Cloner cloner, Object original ) { intArray = cloner.clone(intArray); intArray2D = cloner.clone(intArray2D); @@ -294,6 +306,7 @@ public void cloneFields( Cloner cloner, Object original ) { //strings = cloner.clone(strings); } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("intArray=" + intArray); diff --git a/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java index 0baaef3d6d..6b20d772d4 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java +++ b/jme3-examples/src/main/java/jme3test/app/TestContextRestart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,25 +34,57 @@ import com.jme3.app.LegacyApplication; import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; +/** + * Tests the functionality of LegacyApplication.restart(). + *

                + * If successful, the test will wait for 3 seconds, change to fullscreen, wait 3 + * more seconds, change to a 500x400 window, wait 3 more seconds, and terminate. + *

                + * If successful, the reshape() method will be logged twice: once for the + * transition to fullscreen mode and again for the transition to windowed mode. + */ public class TestContextRestart { + final private static Logger logger + = Logger.getLogger(TestContextRestart.class.getName()); + public static void main(String[] args) throws InterruptedException{ + logger.setLevel(Level.INFO); AppSettings settings = new AppSettings(true); - final LegacyApplication app = new LegacyApplication(); + final LegacyApplication app = new LegacyApplication() { + @Override + public void reshape(int width, int height) { + super.reshape(width, height); + logger.log(Level.INFO, "reshape(width={0} height={1})", + new Object[]{width, height}); + } + }; app.setSettings(settings); app.start(); Thread.sleep(3000); - + /* + * Restart with a fullscreen graphics context. + */ settings.setFullscreen(true); settings.setResolution(-1, -1); app.setSettings(settings); app.restart(); Thread.sleep(3000); + /* + * Restart with a 500x400 windowed context. + */ + settings.setFullscreen(false); + settings.setResolution(500, 400); + app.setSettings(settings); + app.restart(); + Thread.sleep(3000); app.stop(); } diff --git a/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java b/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java index 88ce27732d..c790d860da 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java +++ b/jme3-examples/src/main/java/jme3test/app/TestEnqueueRunnable.java @@ -48,9 +48,11 @@ public Thread getThread(){ return thread; } + @Override public void run(){ while(running){ enqueue(new Runnable(){ //primary usage of this in real applications would use lambda expressions which are unavailable at java 6 + @Override public void run(){ material.setColor("Color", ColorRGBA.randomColor()); } diff --git a/jme3-examples/src/main/java/jme3test/app/TestIDList.java b/jme3-examples/src/main/java/jme3test/app/TestIDList.java index 37f3f56eaa..655c50f20f 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestIDList.java +++ b/jme3-examples/src/main/java/jme3test/app/TestIDList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,13 +39,13 @@ public class TestIDList { static class StateCol { - static Random rand = new Random(); + final private static Random rand = new Random(); - Map objs = new HashMap(); + final private Map objs = new HashMap<>(); public StateCol(){ // populate with free ids - List freeIds = new ArrayList(); + List freeIds = new ArrayList<>(); for (int i = 0; i < 16; i++){ freeIds.add(i); } @@ -74,11 +74,9 @@ public void print(){ } - static IDList list = new IDList(); - static int boundSlot = 0; - - static Object[] slots = new Object[16]; - static boolean[] enabledSlots = new boolean[16]; + final private static IDList list = new IDList(); + final private static Object[] slots = new Object[16]; + final private static boolean[] enabledSlots = new boolean[16]; static void enable(int slot){ System.out.println("Enabled SLOT["+slot+"]"); @@ -154,7 +152,7 @@ public static void main(String[] args){ for (int i = 0; i < states.length; i++) states[i] = new StateCol(); - // shuffle would be useful here.. + // Shuffle would be useful here. for (int i = 0; i < states.length; i++){ setState(states[i]); diff --git a/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java b/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java new file mode 100644 index 0000000000..5a66a24bf5 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/TestMonitorApp.java @@ -0,0 +1,244 @@ +/* + * 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.app; + +import com.jme3.app.SimpleApplication; +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.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.system.DisplayInfo; +import com.jme3.system.Displays; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Tests the capability to change which monitor the window will be created on. + * Also, shows that you can force JME to center the window. Also, it shows to to + * force JME to set the window to x,y coords. Center window and window position + * doesn't apply if in fullscreen. + * + * @author Kevin Bales + */ +public class TestMonitorApp extends SimpleApplication implements ActionListener { + + private BitmapText txt; + private BitmapText selectedMonitorTxt; + private BitmapText fullScreenTxt; + private int monitorSelected = 0; + private Displays monitors = null; + + public static void main(String[] args) { + TestMonitorApp app = new TestMonitorApp(); + AppSettings settings = new AppSettings(true); + settings.setResizable(false); + app.setShowSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL33); + settings.setDisplay(0); + settings.setResolution(800, 600); + + settings.setFullscreen(true); + + // Force JME to center the window, this only applies if it is + // not fullscreen. + settings.setCenterWindow(true); + + // If center window is not turned on, you can force JME to + // open the window at certain x,y coords. These are ignored + // if the screen is set to "fullscreen". + settings.setWindowXPosition(0); + settings.setWindowYPosition(0); + + try { + // Let's try and load the AppSetting parameters back into memory + InputStream out = new FileInputStream("TestMonitorApp.prefs"); + settings.load(out); + } catch (IOException e) { + System.out.println("failed to load settings, reverting to defaults"); + } + app.setSettings(settings); + + app.start(); + } + + @Override + public void simpleInitApp() { + flyCam.setDragToRotate(true); + int numMonitors = 1; + + // If monitor is define, Jme supports multiple monitors. Setup to keys + if (monitors == null) { + inputManager.addMapping("down", new KeyTrigger(KeyInput.KEY_DOWN)); + inputManager.addMapping("fullscreen", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addListener(this, "down", "fullscreen"); + } + + // Get the selected monitor + monitorSelected = settings.getDisplay(); + monitors = context.getDisplays(); + if (monitors != null) numMonitors = monitors.size(); + + // Let's define the labels for users to see what is going on with Multiple + // Monitor + String labelValue = ""; + labelValue = "There are " + numMonitors + " monitor(s) hooked up to this computer."; + txt = new BitmapText(loadGuiFont()); + txt.setText(labelValue); + txt.setLocalTranslation(0, settings.getHeight(), 0); + guiNode.attachChild(txt); + + txt = new BitmapText(loadGuiFont()); + if (!settings.isFullscreen()) txt.setText( + "Window is on Monitor N/A (fullscreen only feature)" + ); else txt.setText("Window is on Monitor " + settings.getDisplay()); + + txt.setLocalTranslation(0, settings.getHeight() - 40, 0); + guiNode.attachChild(txt); + + if (monitors != null) { + selectedMonitorTxt = new BitmapText(loadGuiFont()); + // Lets display information about selected monitor + String label = + "Selected Monitor " + + "Name: " + + monitors.get(settings.getDisplay()).getName() + + " " + + monitorSelected + + " Res: " + + monitors.get(settings.getDisplay()).getWidth() + + "," + + monitors.get(settings.getDisplay()).getHeight() + + " refresh: " + + monitors.get(settings.getDisplay()).getRate(); + selectedMonitorTxt.setText(label); + selectedMonitorTxt.setLocalTranslation(0, settings.getHeight() - 80, 0); + guiNode.attachChild(selectedMonitorTxt); + + // Let's loop through all the monitors and display on the screen + for (int i = 0; i < monitors.size(); i++) { + DisplayInfo monitor = monitors.get(i); + labelValue = + "Mon : " + + i + + " " + + monitor.getName() + + " " + + monitor.getWidth() + + "," + + monitor.getHeight() + + " refresh: " + + monitor.getRate(); + txt = new BitmapText(loadGuiFont()); + txt.setText(labelValue); + txt.setLocalTranslation(0, settings.getHeight() - 160 - (40 * i), 0); + guiNode.attachChild(txt); + } + } + + // Lets put a label up there for FullScreen/Window toggle + fullScreenTxt = new BitmapText(loadGuiFont()); + if (!settings.isFullscreen()) fullScreenTxt.setText("(f) Window Screen"); else fullScreenTxt.setText( + "(f) Fullscreen" + ); + + fullScreenTxt.setLocalTranslation(00, settings.getHeight() - 240, 0); + guiNode.attachChild(fullScreenTxt); + + BitmapText infoTxt = new BitmapText(loadGuiFont()); + infoTxt.setText("Restart is required to activate changes in settings."); + infoTxt.setLocalTranslation(0, settings.getHeight() - 300, 0); + guiNode.attachChild(infoTxt); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (monitors == null) return; + + if (name.equals("down") && isPressed) { + monitorSelected++; + if (monitorSelected >= monitors.size()) monitorSelected = 0; + saveSettings(); + } else if (name.equals("up") && isPressed) { + monitorSelected--; + if (monitorSelected < 0) monitorSelected = monitors.size() - 1; + saveSettings(); + } else if (name.equals("fullscreen") && isPressed) { + settings.setFullscreen(!settings.isFullscreen()); + saveSettings(); + } + } + + /** + * This function saves out the AppSettings into a file to be loaded back in + * on start of application. + */ + public void saveSettings() { + try { + settings.setDisplay(monitorSelected); + OutputStream out = new FileOutputStream("TestMonitorApp.prefs"); + settings.save(out); + + int monitorSelected = settings.getDisplay(); + String label = + "Selected Monitor " + + monitorSelected + + " " + + monitors.get(monitorSelected).getName() + + " Res: " + + monitors.get(monitorSelected).getWidth() + + "," + + monitors.get(monitorSelected).getHeight() + + "refresh: " + + monitors.get(monitorSelected).getRate(); + selectedMonitorTxt.setText(label); + if (!settings.isFullscreen()) fullScreenTxt.setText( + "(f) Window Screen" + ); else fullScreenTxt.setText("(f) Fullscreen"); + } catch (FileNotFoundException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (IOException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java b/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java index d2314b989d..da88af4ff1 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java +++ b/jme3-examples/src/main/java/jme3test/app/TestResizableApp.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,15 +56,19 @@ public static void main(String[] args){ app.start(); } + @Override public void reshape(int width, int height) { super.reshape(width, height); // Need to move text relative to app height - txt.setLocalTranslation(0, settings.getHeight(), 0); - txt.setText("Drag the corners of the application to resize it.\n" + - "Current Size: " + settings.getWidth() + "x" + settings.getHeight()); + if (txt != null) { + txt.setLocalTranslation(0, settings.getHeight(), 0); + txt.setText("Drag the corners of the application to resize it.\n" + + "Current Size: " + settings.getWidth() + "x" + settings.getHeight()); + } } + @Override public void simpleInitApp() { flyCam.setDragToRotate(true); @@ -75,7 +79,7 @@ public void simpleInitApp() { geom.setMaterial(mat); rootNode.attachChild(geom); - txt = new BitmapText(loadGuiFont(), false); + txt = new BitmapText(loadGuiFont()); txt.setText("Drag the corners of the application to resize it.\n" + "Current Size: " + settings.getWidth() + "x" + settings.getHeight()); txt.setLocalTranslation(0, settings.getHeight(), 0); diff --git a/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java b/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java index 8c8c5dddd8..b241d78087 100644 --- a/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java +++ b/jme3-examples/src/main/java/jme3test/app/TestUseAfterFree.java @@ -38,6 +38,13 @@ import com.jme3.texture.Texture; import com.jme3.util.BufferUtils; +/** + * Demonstrate what happens if you use a deleted texture with OpenGL. + * + * If assertions are enabled, an AssertionError is thrown NativeObjectManager. + * + * @author Kirill Vainer + */ public class TestUseAfterFree extends SimpleApplication { private float time = 0; @@ -70,7 +77,7 @@ public void simpleUpdate(float tpf) { time += tpf; if (time > 5) { - System.out.println("Assiging texture to deleted object!"); + System.out.println("Assigning texture to deleted object!"); deletedTex = assetManager.loadTexture("Interface/Logo/Monkey.png"); BufferUtils.destroyDirectBuffer(deletedTex.getImage().getData(0)); diff --git a/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java b/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java index 9274f999e9..463ada9307 100644 --- a/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java +++ b/jme3-examples/src/main/java/jme3test/app/state/RootNodeState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ public class RootNodeState extends AbstractAppState { - private Node rootNode = new Node("Root Node"); + final private Node rootNode = new Node("Root Node"); public Node getRootNode(){ return rootNode; diff --git a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java index a19e2add8a..dbc90d1200 100644 --- a/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java +++ b/jme3-examples/src/main/java/jme3test/app/state/TestAppStates.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,6 +37,8 @@ import com.jme3.scene.Spatial; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; +import com.jme3.texture.image.ColorSpace; +import jme3test.niftygui.StartScreenController; public class TestAppStates extends LegacyApplication { @@ -69,11 +71,16 @@ public void initialize(){ model.setMaterial(assetManager.loadMaterial("Interface/Logo/Logo.j3m")); state.getRootNode().attachChild(model); + ColorSpace colorSpace = renderer.isMainFrameBufferSrgb() + ? ColorSpace.sRGB : ColorSpace.Linear; NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, - inputManager, - audioRenderer, - guiViewPort); - niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start"); + inputManager, + audioRenderer, + guiViewPort, + colorSpace); + StartScreenController startScreen = new StartScreenController(this); + niftyDisplay.getNifty().fromXml("Interface/Nifty/HelloJme.xml", "start", + startScreen); guiViewPort.addProcessor(niftyDisplay); } diff --git a/jme3-examples/src/main/java/jme3test/app/state/package-info.java b/jme3-examples/src/main/java/jme3test/app/state/package-info.java new file mode 100644 index 0000000000..e16b33942e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/app/state/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for appstates + */ +package jme3test.app.state; diff --git a/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java b/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java deleted file mode 100644 index be67acb8e0..0000000000 --- a/jme3-examples/src/main/java/jme3test/asset/TestAbsoluteLocators.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.asset; - -import com.jme3.asset.AssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.audio.AudioData; -import com.jme3.audio.plugins.WAVLoader; -import com.jme3.system.JmeSystem; -import com.jme3.texture.Texture; -import com.jme3.texture.plugins.AWTLoader; - -public class TestAbsoluteLocators { - public static void main(String[] args){ - AssetManager am = JmeSystem.newAssetManager(); - - am.registerLoader(AWTLoader.class, "jpg"); - am.registerLoader(WAVLoader.class, "wav"); - - // register absolute locator - am.registerLocator("/", ClasspathLocator.class); - - // find a sound - AudioData audio = am.loadAudio("Sound/Effects/Gun.wav"); - - // find a texture - Texture tex = am.loadTexture("Textures/Terrain/Pond/Pond.jpg"); - - if (audio == null) - throw new RuntimeException("Cannot find audio!"); - else - System.out.println("Audio loaded from Sounds/Effects/Gun.wav"); - - if (tex == null) - throw new RuntimeException("Cannot find texture!"); - else - System.out.println("Texture loaded from Textures/Terrain/Pond/Pond.jpg"); - - System.out.println("Success!"); - } -} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java b/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java index 2f86e9abf8..d7cef54c90 100644 --- a/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java +++ b/jme3-examples/src/main/java/jme3test/asset/TestAssetCache.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,7 +59,7 @@ private static class DummyData implements CloneableSmartAsset { private byte[] data = new byte[10 * 1024]; @Override - public Object clone(){ + public DummyData clone(){ try { DummyData clone = (DummyData) super.clone(); clone.data = data.clone(); @@ -73,10 +73,12 @@ public byte[] getData(){ return data; } + @Override public AssetKey getKey() { return key; } + @Override public void setKey(AssetKey key) { this.key = key; } @@ -122,7 +124,7 @@ public String toString() { private static void runTest(boolean cloneAssets, boolean smartCache, boolean keepRefs, int limit) { counter = 0; - List refs = new ArrayList(limit); + List refs = new ArrayList<>(limit); AssetCache cache; AssetProcessor proc = null; @@ -155,7 +157,7 @@ private static void runTest(boolean cloneAssets, boolean smartCache, boolean kee // Create some data DummyData data = new DummyData(); - // Post process the data before placing it in the cache + // Postprocess the data before placing it in the cache. if (proc != null){ data = (DummyData) proc.postProcess(key, data); } @@ -200,7 +202,7 @@ private static void runTest(boolean cloneAssets, boolean smartCache, boolean kee // collections of the asset in the cache thus causing // an out of memory error. if (keepRefs){ - // Prevent the saved references from taking too much memory .. + // Prevent the saved references from taking too much memory. if (cloneAssets) { data.data = null; } diff --git a/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java b/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java deleted file mode 100644 index 81e3a10d3f..0000000000 --- a/jme3-examples/src/main/java/jme3test/asset/TestCustomLoader.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.asset; - -import com.jme3.asset.AssetLoader; -import com.jme3.asset.AssetManager; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.system.JmeSystem; - -/** - * Demonstrates loading a file from a custom {@link AssetLoader} - */ -public class TestCustomLoader { - public static void main(String[] args){ - AssetManager assetManager = JmeSystem.newAssetManager(); - assetManager.registerLocator("/", ClasspathLocator.class); - assetManager.registerLoader(TextLoader.class, "fnt"); - System.out.println(assetManager.loadAsset("Interface/Fonts/Console.fnt")); - } -} diff --git a/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java b/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java deleted file mode 100644 index bf0cc642e1..0000000000 --- a/jme3-examples/src/main/java/jme3test/asset/TestManyLocators.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.asset; - -import com.jme3.asset.*; -import com.jme3.asset.plugins.ClasspathLocator; -import com.jme3.asset.plugins.HttpZipLocator; -import com.jme3.asset.plugins.UrlLocator; -import com.jme3.asset.plugins.ZipLocator; -import com.jme3.system.JmeSystem; - -public class TestManyLocators { - public static void main(String[] args){ - AssetManager am = JmeSystem.newAssetManager(); - - am.registerLocator("http://wiki.jmonkeyengine.org/jme3/beginner", - UrlLocator.class); - - am.registerLocator("town.zip", ZipLocator.class); - am.registerLocator( - "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/wildhouse.zip", - HttpZipLocator.class); - - am.registerLocator("/", ClasspathLocator.class); - - - - // Try loading from Core-Data source package - AssetInfo a = am.locateAsset(new AssetKey("Interface/Fonts/Default.fnt")); - - // Try loading from town scene zip file - AssetInfo b = am.locateAsset(new ModelKey("casaamarela.jpg")); - - // Try loading from wildhouse online scene zip file - AssetInfo c = am.locateAsset(new ModelKey("glasstile2.png")); - - // Try loading directly from HTTP - AssetInfo d = am.locateAsset(new TextureKey("beginner-physics.png")); - - if (a == null) - System.out.println("Failed to load from classpath"); - else - System.out.println("Found classpath font: " + a.toString()); - - if (b == null) - System.out.println("Failed to load from town.zip file"); - else - System.out.println("Found zip image: " + b.toString()); - - if (c == null) - System.out.println("Failed to load from wildhouse.zip on googleapis.com"); - else - System.out.println("Found online zip image: " + c.toString()); - - if (d == null) - System.out.println("Failed to load from HTTP"); - else - System.out.println("Found HTTP showcase image: " + d.toString()); - } -} diff --git a/jme3-examples/src/main/java/jme3test/asset/TextLoader.java b/jme3-examples/src/main/java/jme3test/asset/TextLoader.java deleted file mode 100644 index 27cf33995f..0000000000 --- a/jme3-examples/src/main/java/jme3test/asset/TextLoader.java +++ /dev/null @@ -1,25 +0,0 @@ -package jme3test.asset; - -import com.jme3.asset.AssetInfo; -import com.jme3.asset.AssetLoader; -import java.io.IOException; -import java.util.Scanner; - -/** - * An example implementation of {@link AssetLoader} to load text - * files as strings. - */ -public class TextLoader implements AssetLoader { - public Object load(AssetInfo assetInfo) throws IOException { - Scanner scan = new Scanner(assetInfo.openStream()); - StringBuilder sb = new StringBuilder(); - try { - while (scan.hasNextLine()) { - sb.append(scan.nextLine()).append('\n'); - } - } finally { - scan.close(); - } - return sb.toString(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/asset/package-info.java b/jme3-examples/src/main/java/jme3test/asset/package-info.java new file mode 100644 index 0000000000..a79bade643 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/asset/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for asset loading + */ +package jme3test.asset; diff --git a/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java index 6d300525ef..2c5590ce63 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestAmbient.java @@ -1,30 +1,33 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * Copyright (c) 2009-2025 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. + * 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. + * 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. + * 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. + * 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.audio; @@ -36,52 +39,70 @@ import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Box; +import com.jme3.scene.Mesh; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.shape.Sphere; public class TestAmbient extends SimpleApplication { - private AudioNode nature, waves; + public static void main(String[] args) { + TestAmbient test = new TestAmbient(); + test.start(); + } + + private final float[] eax = { + 15, 38.0f, 0.300f, -1000, -3300, 0, + 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, + 0.00f, -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, + 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f + }; + + @Override + public void simpleInitApp() { + configureCamera(); + + Environment env = new Environment(eax); + audioRenderer.setEnvironment(env); + + AudioNode waves = new AudioNode(assetManager, + "Sound/Environment/Ocean Waves.ogg", DataType.Buffer); + waves.setPositional(true); + waves.setLooping(true); + waves.setReverbEnabled(true); + rootNode.attachChild(waves); + + AudioNode nature = new AudioNode(assetManager, + "Sound/Environment/Nature.ogg", DataType.Stream); + nature.setPositional(false); + nature.setLooping(true); + nature.setVolume(3); + rootNode.attachChild(nature); + + waves.play(); + nature.play(); - public static void main(String[] args) { - TestAmbient test = new TestAmbient(); - test.start(); - } + // just a blue sphere to mark the spot + Geometry marker = makeShape("Marker", new Sphere(16, 16, 1f), ColorRGBA.Blue); + waves.attachChild(marker); - @Override - public void simpleInitApp() { - float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0, - 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, - 0.00f, -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, - 0.250f, 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f}; - Environment env = new Environment(eax); - audioRenderer.setEnvironment(env); + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + } - waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", - DataType.Buffer); - waves.setPositional(true); - waves.setLocalTranslation(new Vector3f(0, 0,0)); - waves.setMaxDistance(100); - waves.setRefDistance(5); + private void configureCamera() { + flyCam.setMoveSpeed(25f); + flyCam.setDragToRotate(true); - nature = new AudioNode(assetManager, "Sound/Environment/Nature.ogg", - DataType.Stream); - nature.setPositional(false); - nature.setVolume(3); - - waves.playInstance(); - nature.play(); - - // just a blue box to mark the spot - Box box1 = new Box(.5f, .5f, .5f); - Geometry player = new Geometry("Player", box1); - Material mat1 = new Material(assetManager, - "Common/MatDefs/Misc/Unshaded.j3md"); - mat1.setColor("Color", ColorRGBA.Blue); - player.setMaterial(mat1); - rootNode.attachChild(player); - } + cam.setLocation(Vector3f.UNIT_XYZ.mult(5f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } - @Override - public void simpleUpdate(float tpf) { - } + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestAudioDeviceDisconnect.java b/jme3-examples/src/main/java/jme3test/audio/TestAudioDeviceDisconnect.java new file mode 100644 index 0000000000..5f74f2f0a9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestAudioDeviceDisconnect.java @@ -0,0 +1,63 @@ +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioNode; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.input.controls.Trigger; + +/** + * This test demonstrates that destroying and recreating the OpenAL Context + * upon device disconnection is not an optimal solution. + * + * As shown, AudioNode instances playing in a loop cease to play after a device disconnection + * and would require explicit restarting. + * + * This test serves solely to highlight this issue, + * which should be addressed with a more robust solution within + * the ALAudioRenderer class in a dedicated future pull request on Git. + */ +public class TestAudioDeviceDisconnect extends SimpleApplication implements ActionListener { + + public static void main(String[] args) { + TestAudioDeviceDisconnect test = new TestAudioDeviceDisconnect(); + test.start(); + } + + private AudioNode audioSource; + + @Override + public void simpleInitApp() { + audioSource = new AudioNode(assetManager, + "Sound/Environment/Ocean Waves.ogg", AudioData.DataType.Buffer); + audioSource.setName("Waves"); + audioSource.setLooping(true); + rootNode.attachChild(audioSource); + + audioSource.play(); + + registerInputMappings(); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; + + if (name.equals("play")) { + // re-play active sounds + audioSource.play(); + } + } + + private void registerInputMappings() { + addMapping("play", new KeyTrigger(KeyInput.KEY_SPACE)); + } + + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestAudioDirectional.java b/jme3-examples/src/main/java/jme3test/audio/TestAudioDirectional.java new file mode 100644 index 0000000000..a996431655 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestAudioDirectional.java @@ -0,0 +1,141 @@ +package jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioNode; +import com.jme3.environment.util.BoundingSphereDebug; +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.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.shape.Line; + +/** + * @author capdevon + */ +public class TestAudioDirectional extends SimpleApplication implements ActionListener { + + public static void main(String[] args) { + TestAudioDirectional app = new TestAudioDirectional(); + app.start(); + } + + private AudioNode audioSource; + private final Vector3f tempDirection = new Vector3f(); + private boolean rotationEnabled = true; + + @Override + public void simpleInitApp() { + configureCamera(); + + audioSource = new AudioNode(assetManager, + "Sound/Environment/Ocean Waves.ogg", AudioData.DataType.Buffer); + audioSource.setLooping(true); + audioSource.setPositional(true); + audioSource.setMaxDistance(100); + audioSource.setRefDistance(5); + audioSource.setDirectional(true); +// audioSource.setOuterGain(0.2f); // Volume outside the cone is 20% of the inner volume (Not Supported by jME) + audioSource.setInnerAngle(30); // 30-degree cone (15 degrees on each side of the direction) + audioSource.setOuterAngle(90); // 90-degree cone (45 degrees on each side of the direction) + audioSource.play(); + + // just a green sphere to mark the spot + Geometry sphere = BoundingSphereDebug.createDebugSphere(assetManager); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Green); + sphere.setMaterial(mat); + sphere.setLocalScale(0.5f); + audioSource.attachChild(sphere); + + float angleIn = audioSource.getInnerAngle() * FastMath.DEG_TO_RAD; + float angleOut = audioSource.getOuterAngle() * FastMath.DEG_TO_RAD; + Vector3f forwardDir = audioSource.getWorldRotation().mult(Vector3f.UNIT_Z); + + audioSource.attachChild(createFOV(angleIn, 20f)); + audioSource.attachChild(createFOV(angleOut, 20f)); + audioSource.attachChild(makeShape("ZAxis", new Arrow(forwardDir.mult(5)), ColorRGBA.Blue)); + rootNode.attachChild(audioSource); + + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + + registerInputMappings(); + } + + @Override + public void simpleUpdate(float tpf) { + if (rotationEnabled) { + // Example: Rotate the audio node + audioSource.rotate(0, tpf * 0.5f, 0); + audioSource.setDirection(audioSource.getWorldRotation().mult(Vector3f.UNIT_Z, tempDirection)); + } + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; + + if (name.equals("toggleDirectional")) { + boolean directional = !audioSource.isDirectional(); + audioSource.setDirectional(directional); + System.out.println("directional: " + directional); + + } else if (name.equals("toggleRotationEnabled")) { + rotationEnabled = !rotationEnabled; + System.out.println("rotationEnabled: " + rotationEnabled); + } + } + + private void registerInputMappings() { + addMapping("toggleDirectional", new KeyTrigger(KeyInput.KEY_SPACE)); + addMapping("toggleRotationEnabled", new KeyTrigger(KeyInput.KEY_P)); + } + + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); + } + + private void configureCamera() { + flyCam.setMoveSpeed(25f); + flyCam.setDragToRotate(true); + + cam.setLocation(new Vector3f(12, 5, 12)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } + + private Spatial createFOV(float angleRad, float extent) { + Vector3f origin = new Vector3f(); + Node node = new Node("Cone"); + Vector3f sx = dirFromAngle(angleRad/2).scaleAdd(extent, origin); + Vector3f dx = dirFromAngle(-angleRad/2).scaleAdd(extent, origin); + node.attachChild(makeShape("Line.SX", new Line(origin, sx), ColorRGBA.Red)); + node.attachChild(makeShape("Line.DX", new Line(origin, dx), ColorRGBA.Red)); + + return node; + } + + private Vector3f dirFromAngle(float angleRad) { + return new Vector3f(FastMath.sin(angleRad), 0, FastMath.cos(angleRad)); + } +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java b/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java index 1065e1cee9..1e1731d2b8 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestDoppler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,11 +35,16 @@ import com.jme3.app.SimpleApplication; import com.jme3.audio.AudioData; import com.jme3.audio.AudioNode; -import com.jme3.math.FastMath; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.debug.Grid; import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Torus; + +import java.util.Locale; /** * Test Doppler Effect @@ -49,23 +54,17 @@ public class TestDoppler extends SimpleApplication { private float pos = -5; private float vel = 5; private AudioNode ufoNode; + private BitmapText bmp; - public static void main(String[] args){ - TestDoppler test = new TestDoppler(); - test.start(); + public static void main(String[] args) { + TestDoppler app = new TestDoppler(); + app.start(); } @Override public void simpleInitApp() { - flyCam.setMoveSpeed(10); - - Torus torus = new Torus(10, 6, 1, 3); - Geometry g = new Geometry("Torus Geom", torus); - g.rotate(-FastMath.HALF_PI, 0, 0); - g.center(); - - g.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); -// rootNode.attachChild(g); + configureCamera(); + bmp = createLabelText(10, 20, ""); ufoNode = new AudioNode(assetManager, "Sound/Effects/Beep.ogg", AudioData.DataType.Buffer); ufoNode.setLooping(true); @@ -73,22 +72,50 @@ public void simpleInitApp() { ufoNode.setRefDistance(1); ufoNode.setMaxDistance(100000000); ufoNode.setVelocityFromTranslation(true); - ufoNode.play(); + rootNode.attachChild(ufoNode); - Geometry ball = new Geometry("Beeper", new Sphere(10, 10, 0.1f)); - ball.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + Geometry ball = makeShape("Beeper", new Sphere(10, 10, .5f), ColorRGBA.Red); ufoNode.attachChild(ball); - rootNode.attachChild(ufoNode); + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + + ufoNode.play(); } + private void configureCamera() { + flyCam.setMoveSpeed(15f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(12)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } @Override public void simpleUpdate(float tpf) { pos += tpf * vel; - if (pos < -10 || pos > 10) { + if (pos < -10f || pos > 10f) { vel *= -1; } - ufoNode.setLocalTranslation(new Vector3f(pos, 0, 0)); + ufoNode.setLocalTranslation(pos, 0f, 0f); + bmp.setText(String.format(Locale.ENGLISH, "Audio Position: (%.2f, %.1f, %.1f)", pos, 0f, 0f)); + } + + private BitmapText createLabelText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; + } + + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; } } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestIssue1761.java b/jme3-examples/src/main/java/jme3test/audio/TestIssue1761.java new file mode 100644 index 0000000000..134cb18ee9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/TestIssue1761.java @@ -0,0 +1,86 @@ +/* + * 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 jme3test.audio; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.audio.AudioData; +import com.jme3.audio.AudioNode; +import java.util.Random; + +/** + * Stress test to reproduce JME issue #1761 (AssertionError in ALAudioRenderer). + * + *

                After some network delay, a song will play, + * albeit slowly and in a broken fashion. + * If the issue is solved, the song will play all the way through. + * If the issue is present, an AssertionError will be thrown, usually within a + * second of the song starting. + */ +public class TestIssue1761 extends SimpleApplication { + + private AudioNode audioNode; + final private Random random = new Random(); + + /** + * Main entry point for the TestIssue1761 application. + * + * @param unused array of command-line arguments + */ + public static void main(String[] unused) { + TestIssue1761 test = new TestIssue1761(); + test.start(); + } + + @Override + public void simpleInitApp() { + assetManager.registerLocator( + "https://web.archive.org/web/20170625151521if_/http://www.vorbis.com/music/", + UrlLocator.class); + audioNode = new AudioNode(assetManager, "Lumme-Badloop.ogg", + AudioData.DataType.Stream); + audioNode.setPositional(false); + audioNode.play(); + } + + @Override + public void simpleUpdate(float tpf) { + /* + * Randomly pause and restart the audio. + */ + if (random.nextInt(2) == 0) { + audioNode.pause(); + } else { + audioNode.play(); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java b/jme3-examples/src/main/java/jme3test/audio/TestMusicPlayer.java index 50fe7456a6..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-2012 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; @@ -50,7 +53,18 @@ public class TestMusicPlayer extends javax.swing.JFrame { private AudioNode musicSource; private float musicLength = 0; private float curTime = 0; - private Listener listener = new Listener(); + 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(); @@ -90,6 +104,7 @@ private void initComponents() { setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE); addWindowListener(new java.awt.event.WindowAdapter() { + @Override public void windowClosing(java.awt.event.WindowEvent evt) { formWindowClosing(evt); } @@ -102,6 +117,7 @@ public void windowClosing(java.awt.event.WindowEvent evt) { sldVolume.setPaintTicks(true); sldVolume.setValue(100); sldVolume.addChangeListener(new javax.swing.event.ChangeListener() { + @Override public void stateChanged(javax.swing.event.ChangeEvent evt) { sldVolumeStateChanged(evt); } @@ -113,6 +129,7 @@ public void stateChanged(javax.swing.event.ChangeEvent evt) { btnStop.setText("[ ]"); btnStop.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent evt) { btnStopActionPerformed(evt); } @@ -121,6 +138,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { btnPlay.setText("II / >"); btnPlay.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent evt) { btnPlayActionPerformed(evt); } @@ -129,6 +147,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { btnFF.setText(">>"); btnFF.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent evt) { btnFFActionPerformed(evt); } @@ -137,6 +156,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { btnOpen.setText("Open ..."); btnOpen.addActionListener(new java.awt.event.ActionListener() { + @Override public void actionPerformed(java.awt.event.ActionEvent evt) { btnOpenActionPerformed(evt); } @@ -153,6 +173,7 @@ public void actionPerformed(java.awt.event.ActionEvent evt) { sldBar.setValue(0); sldBar.addChangeListener(new javax.swing.event.ChangeListener() { + @Override public void stateChanged(javax.swing.event.ChangeEvent evt) { sldBarStateChanged(evt); } @@ -199,6 +220,8 @@ public InputStream openStream() { } musicSource = new AudioNode(musicData, key); + // A positional AudioNode would prohibit stereo sound! + musicSource.setPositional(false); musicLength = musicData.getDuration(); updateTime(); } @@ -239,7 +262,7 @@ private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:even }//GEN-LAST:event_formWindowClosing private void sldVolumeStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldVolumeStateChanged - listener.setVolume( (float) sldVolume.getValue() / 100f); + listener.setVolume(sldVolume.getValue() / 100f); ar.setListener(listener); }//GEN-LAST:event_sldVolumeStateChanged @@ -251,24 +274,13 @@ private void btnStopActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST }//GEN-LAST:event_btnStopActionPerformed private void btnFFActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_btnFFActionPerformed - if (musicSource.getStatus() == Status.Playing){ + if (musicSource != null && musicSource.getStatus() == Status.Playing) { musicSource.setPitch(2); } }//GEN-LAST:event_btnFFActionPerformed private void sldBarStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST:event_sldBarStateChanged - if (musicSource != null && !sldBar.getValueIsAdjusting()){ - curTime = sldBar.getValue() / 100f; - if (curTime < 0) - curTime = 0; - - musicSource.setTimeOffset(curTime); -// if (musicSource.getStatus() == Status.Playing){ -// musicSource.stop(); -// musicSource.play(); -// } - updateTime(); - } + // do nothing: OGG/Vorbis supports seeking, but only for time = 0! }//GEN-LAST:event_sldBarStateChanged /** @@ -276,6 +288,7 @@ private void sldBarStateChanged(javax.swing.event.ChangeEvent evt) {//GEN-FIRST: */ public static void main(String args[]) { java.awt.EventQueue.invokeLater(new Runnable() { + @Override public void run() { new TestMusicPlayer().setVisible(true); } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestOgg.java b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java index 3e4099c660..55cd7bf452 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestOgg.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestOgg.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,34 +37,170 @@ import com.jme3.audio.AudioNode; import com.jme3.audio.AudioSource; import com.jme3.audio.LowPassFilter; +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.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.shape.Sphere; -public class TestOgg extends SimpleApplication { +/** + * + * @author capdevon + */ +public class TestOgg extends SimpleApplication implements ActionListener { + private final StringBuilder sb = new StringBuilder(); + private int frameCount = 0; + private BitmapText bmp; private AudioNode audioSource; + private float volume = 1.0f; + private float pitch = 1.0f; - public static void main(String[] args){ + /** + * ### Filters ### + * Changing a parameter value in the Filter Object after it has been attached to a Source will not + * affect the Source. To update the filter(s) used on a Source, an application must update the + * parameters of a Filter object and then re-attach it to the Source. + */ + private final LowPassFilter dryFilter = new LowPassFilter(1f, .1f); + + public static void main(String[] args) { TestOgg test = new TestOgg(); test.start(); } @Override - public void simpleInitApp(){ - System.out.println("Playing without filter"); + public void simpleInitApp() { + configureCamera(); + + bmp = createLabelText(10, 20, ""); + + // just a blue sphere to mark the spot + Geometry marker = makeShape("Marker", new Sphere(16, 16, 1f), ColorRGBA.Blue); + rootNode.attachChild(marker); + + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", DataType.Buffer); + audioSource.setName("Foot steps"); + audioSource.setLooping(true); + audioSource.setVolume(volume); + audioSource.setPitch(pitch); + audioSource.setMaxDistance(100); + audioSource.setRefDistance(5); audioSource.play(); + rootNode.attachChild(audioSource); + + registerInputMappings(); + } + + private void configureCamera() { + flyCam.setMoveSpeed(25f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(20f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + @Override + public void simpleUpdate(float tpf) { + frameCount++; + if (frameCount % 10 == 0) { + frameCount = 0; + + sb.append("Audio: ").append(audioSource.getName()).append("\n"); + sb.append(audioSource.getAudioData()).append("\n"); + sb.append("Looping: ").append(audioSource.isLooping()).append("\n"); + sb.append("Volume: ").append(String.format("%.2f", audioSource.getVolume())).append("\n"); + sb.append("Pitch: ").append(String.format("%.2f", audioSource.getPitch())).append("\n"); + sb.append("Positional: ").append(audioSource.isPositional()).append("\n"); + sb.append("MaxDistance: ").append(audioSource.getMaxDistance()).append("\n"); + sb.append("RefDistance: ").append(audioSource.getRefDistance()).append("\n"); + sb.append("Status: ").append(audioSource.getStatus()).append("\n"); + sb.append("SourceId: ").append(audioSource.getChannel()).append("\n"); + sb.append("DryFilter: ").append(audioSource.getDryFilter() != null).append("\n"); + sb.append("FilterId: ").append(dryFilter.getId()).append("\n"); + + bmp.setText(sb.toString()); + sb.setLength(0); + } } @Override - public void simpleUpdate(float tpf){ - if (audioSource.getStatus() != AudioSource.Status.Playing){ - audioRenderer.deleteAudioData(audioSource.getAudioData()); - - System.out.println("Playing with low pass filter"); - audioSource = new AudioNode(assetManager, "Sound/Effects/Foot steps.ogg", DataType.Buffer); - audioSource.setDryFilter(new LowPassFilter(1f, .1f)); - audioSource.setVolume(3); - audioSource.play(); + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; + + if (name.equals("togglePlayPause")) { + if (audioSource.getStatus() != AudioSource.Status.Playing) { + audioSource.play(); + } else { + audioSource.stop(); + } + } else if (name.equals("togglePositional")) { + boolean positional = audioSource.isPositional(); + audioSource.setPositional(!positional); + + } else if (name.equals("dryFilter")) { + boolean hasFilter = audioSource.getDryFilter() != null; + audioSource.setDryFilter(hasFilter ? null : dryFilter); + + } else if (name.equals("Volume+")) { + volume = FastMath.clamp(volume + 0.1f, 0, 5f); + audioSource.setVolume(volume); + + } else if (name.equals("Volume-")) { + volume = FastMath.clamp(volume - 0.1f, 0, 5f); + audioSource.setVolume(volume); + + } else if (name.equals("Pitch+")) { + pitch = FastMath.clamp(pitch + 0.1f, 0.5f, 2f); + audioSource.setPitch(pitch); + + } else if (name.equals("Pitch-")) { + pitch = FastMath.clamp(pitch - 0.1f, 0.5f, 2f); + audioSource.setPitch(pitch); } } + private void registerInputMappings() { + addMapping("togglePlayPause", new KeyTrigger(KeyInput.KEY_P)); + addMapping("togglePositional", new KeyTrigger(KeyInput.KEY_RETURN)); + addMapping("dryFilter", new KeyTrigger(KeyInput.KEY_SPACE)); + addMapping("Volume+", new KeyTrigger(KeyInput.KEY_I)); + addMapping("Volume-", new KeyTrigger(KeyInput.KEY_K)); + addMapping("Pitch+", new KeyTrigger(KeyInput.KEY_J)); + addMapping("Pitch-", new KeyTrigger(KeyInput.KEY_L)); + } + + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); + } + + private BitmapText createLabelText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; + } + + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestReverb.java b/jme3-examples/src/main/java/jme3test/audio/TestReverb.java index 8fd5d1b7f7..e21a870993 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestReverb.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestReverb.java @@ -1,30 +1,33 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * Copyright (c) 2009-2025 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. + * 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. + * 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. + * 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. + * 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.audio; @@ -32,50 +35,143 @@ import com.jme3.audio.AudioData; import com.jme3.audio.AudioNode; import com.jme3.audio.Environment; +import com.jme3.audio.LowPassFilter; +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.material.Material; +import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.shape.Sphere; + +/** + * @author capdevon + */ +public class TestReverb extends SimpleApplication implements ActionListener { -public class TestReverb extends SimpleApplication { - - private AudioNode audioSource; - private float time = 0; - private float nextTime = 1; - - public static void main(String[] args) { - TestReverb test = new TestReverb(); - test.start(); - } - - @Override - public void simpleInitApp() { - audioSource = new AudioNode(assetManager, "Sound/Effects/Bang.wav", - AudioData.DataType.Buffer); - - float[] eax = new float[]{15, 38.0f, 0.300f, -1000, -3300, 0, - 1.49f, 0.54f, 1.00f, -2560, 0.162f, 0.00f, 0.00f, 0.00f, - -229, 0.088f, 0.00f, 0.00f, 0.00f, 0.125f, 1.000f, 0.250f, - 0.000f, -5.0f, 5000.0f, 250.0f, 0.00f, 0x3f}; - audioRenderer.setEnvironment(new Environment(eax)); - Environment env = Environment.Cavern; - audioRenderer.setEnvironment(env); - } - - @Override - public void simpleUpdate(float tpf) { - time += tpf; - - if (time > nextTime) { - Vector3f v = new Vector3f(); - v.setX(FastMath.nextRandomFloat()); - v.setY(FastMath.nextRandomFloat()); - v.setZ(FastMath.nextRandomFloat()); - v.multLocal(40, 2, 40); - v.subtractLocal(20, 1, 20); - - audioSource.setLocalTranslation(v); - audioSource.playInstance(); - time = 0; - nextTime = FastMath.nextRandomFloat() * 2 + 0.5f; + public static void main(String[] args) { + TestReverb app = new TestReverb(); + app.start(); } - } + + private AudioNode audioSource; + private float time = 0; + private float nextTime = 1; + + /** + * ### Effects ### + * Changing a parameter value in the Effect Object after it has been attached to the Auxiliary Effect + * Slot will not affect the effect in the effect slot. To update the parameters of the effect in the effect + * slot, an application must update the parameters of an Effect object and then re-attach it to the + * Auxiliary Effect Slot. + */ + private int index = 0; + private final Environment[] environments = { + Environment.Cavern, + Environment.AcousticLab, + Environment.Closet, + Environment.Dungeon, + Environment.Garage + }; + + @Override + public void simpleInitApp() { + + configureCamera(); + + // Activate the Environment preset + audioRenderer.setEnvironment(environments[index]); + + // Activate 3D audio + audioSource = new AudioNode(assetManager, "Sound/Effects/Bang.wav", AudioData.DataType.Buffer); + audioSource.setLooping(false); + audioSource.setVolume(1.2f); + audioSource.setPositional(true); + audioSource.setMaxDistance(100); + audioSource.setRefDistance(5); + audioSource.setReverbEnabled(true); + audioSource.setReverbFilter(new LowPassFilter(1f, 1f)); + rootNode.attachChild(audioSource); + + Geometry marker = makeShape("Marker", new Sphere(16, 16, 1f), ColorRGBA.Red); + audioSource.attachChild(marker); + + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 4), ColorRGBA.Blue); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + + registerInputMappings(); + } + + private void configureCamera() { + flyCam.setMoveSpeed(50f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(50f)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + + if (time > nextTime) { + time = 0; + nextTime = FastMath.nextRandomFloat() * 2 + 0.5f; + + Vector3f position = getRandomPosition(); + audioSource.setLocalTranslation(position); + audioSource.playInstance(); + } + } + + private Vector3f getRandomPosition() { + float x = FastMath.nextRandomFloat(); + float y = FastMath.nextRandomFloat(); + float z = FastMath.nextRandomFloat(); + Vector3f vec = new Vector3f(x, y, z); + vec.multLocal(40, 2, 40); + vec.subtractLocal(20, 1, 20); + return vec; + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; + + if (name.equals("toggleReverbEnabled")) { + boolean reverbEnabled = !audioSource.isReverbEnabled(); + audioSource.setReverbEnabled(reverbEnabled); + System.out.println("reverbEnabled: " + reverbEnabled); + + } else if (name.equals("nextEnvironment")) { + index = (index + 1) % environments.length; + audioRenderer.setEnvironment(environments[index]); + System.out.println("Next Environment Index: " + index); + } + } + + private void registerInputMappings() { + addMapping("toggleReverbEnabled", new KeyTrigger(KeyInput.KEY_SPACE)); + addMapping("nextEnvironment", new KeyTrigger(KeyInput.KEY_N)); + } + + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); + } + } diff --git a/jme3-examples/src/main/java/jme3test/audio/TestWav.java b/jme3-examples/src/main/java/jme3test/audio/TestWav.java index 09fcfe7147..a24b2f24d5 100644 --- a/jme3-examples/src/main/java/jme3test/audio/TestWav.java +++ b/jme3-examples/src/main/java/jme3test/audio/TestWav.java @@ -1,62 +1,139 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. + * Copyright (c) 2009-2025 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. + * 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. + * 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. + * 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. + * 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.audio; import com.jme3.app.SimpleApplication; import com.jme3.audio.AudioData; +import com.jme3.audio.AudioKey; import com.jme3.audio.AudioNode; +/** + * @author capdevon + */ public class TestWav extends SimpleApplication { - private float time = 0; - private AudioNode audioSource; + private float time = 0; + private AudioNode audioSource; + + public static void main(String[] args) { + TestWav app = new TestWav(); + app.start(); + } + + @Override + public void simpleInitApp() { + testMaxNumChannels(); + testFakeAudio(); + testPlaySourceInstance(); + + audioSource = createAudioNode("Sound/Effects/Gun.wav", AudioData.DataType.Buffer); + audioSource.setName("Gun"); + audioSource.setPositional(true); + } + + @Override + public void simpleUpdate(float tpf) { + time += tpf; + if (time > 1f) { + audioSource.playInstance(); + time = 0; + } + } + + /** + * Creates an {@link AudioNode} for the specified audio file. + * This method demonstrates an alternative way to defer the creation + * of an AudioNode by explicitly creating and potentially pre-loading + * the {@link AudioData} and {@link AudioKey} before instantiating + * the AudioNode. This can be useful in scenarios where you want more + * control over the asset loading process or when the AudioData and + * AudioKey are already available. + * + * @param filepath The path to the audio file. + * @param type The desired {@link AudioData.DataType} for the audio. + * @return A new {@code AudioNode} configured with the loaded audio data. + */ + private AudioNode createAudioNode(String filepath, AudioData.DataType type) { + boolean stream = (type == AudioData.DataType.Stream); + boolean streamCache = true; + AudioKey audioKey = new AudioKey(filepath, stream, streamCache); + AudioData data = assetManager.loadAsset(audioKey); - public static void main(String[] args) { - TestWav test = new TestWav(); - test.start(); - } + AudioNode audio = new AudioNode(); + audio.setAudioData(data, audioKey); + return audio; + } - @Override - public void simpleUpdate(float tpf) { - time += tpf; - if (time > 1f) { - audioSource.playInstance(); - time = 0; + /** + * WARNING: No channel available to play instance of AudioNode[status=Stopped, vol=0.1] + */ + private void testMaxNumChannels() { + final int MAX_NUM_CHANNELS = 64; + for (int i = 0; i < MAX_NUM_CHANNELS + 1; i++) { + AudioNode audio = createAudioNode("Sound/Effects/Gun.wav", AudioData.DataType.Buffer); + audio.setVolume(0.1f); + audio.playInstance(); + } } - } + /** + * java.lang.UnsupportedOperationException: Cannot play instances of audio streams. Use play() instead. + * at com.jme3.audio.openal.ALAudioRenderer.playSourceInstance() + */ + private void testPlaySourceInstance() { + try { + AudioNode nature = new AudioNode(assetManager, + "Sound/Environment/Nature.ogg", AudioData.DataType.Stream); + audioRenderer.playSourceInstance(nature); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private void testFakeAudio() { + /** + * Tests AudioRenderer.playSource() with an + * AudioNode lacking AudioData to observe its handling (typically discard). + */ + AudioNode fakeAudio = new AudioNode() { + @Override + public String toString() { + // includes node name for easier identification in log messages. + return getName() + " (" + AudioNode.class.getSimpleName() + ")"; + } + }; + fakeAudio.setName("FakeAudio"); + audioRenderer.playSource(fakeAudio); + audioRenderer.playSourceInstance(fakeAudio); + } - @Override - public void simpleInitApp() { - audioSource = new AudioNode(assetManager, "Sound/Effects/Gun.wav", - AudioData.DataType.Buffer); - audioSource.setLooping(false); - } } diff --git a/jme3-examples/src/main/java/jme3test/audio/package-info.java b/jme3-examples/src/main/java/jme3test/audio/package-info.java new file mode 100644 index 0000000000..d6c39d933d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/audio/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for sound effects and music + */ +package jme3test.audio; diff --git a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java index 77e8d7092c..0faa1a6507 100644 --- a/jme3-examples/src/main/java/jme3test/awt/AppHarness.java +++ b/jme3-examples/src/main/java/jme3test/awt/AppHarness.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ import java.awt.Graphics; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import javax.swing.SwingUtilities; @@ -58,6 +59,7 @@ public class AppHarness extends Applet { private String appClass; private URL appCfg = null; + @SuppressWarnings("unchecked") private void createCanvas(){ AppSettings settings = new AppSettings(true); @@ -79,13 +81,15 @@ private void createCanvas(){ JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); - }catch (ClassNotFoundException ex){ - ex.printStackTrace(); - }catch (InstantiationException ex){ - ex.printStackTrace(); - }catch (IllegalAccessException ex){ + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + }catch (ClassNotFoundException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException + | SecurityException ex) { ex.printStackTrace(); } @@ -138,6 +142,7 @@ public void stop(){ public void destroy(){ System.out.println("applet:destroyStart"); SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ removeAll(); System.out.println("applet:destroyRemoved"); diff --git a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java index 8ac4b7f0a9..e9e473b852 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestApplet.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestApplet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,6 +40,7 @@ import java.applet.Applet; import java.awt.Canvas; import java.awt.Graphics; +import java.lang.reflect.InvocationTargetException; import java.util.concurrent.Callable; import javax.swing.SwingUtilities; @@ -53,22 +54,25 @@ public class TestApplet extends Applet { public TestApplet(){ } + @SuppressWarnings("unchecked") public static void createCanvas(String appClass){ AppSettings settings = new AppSettings(true); settings.setWidth(640); settings.setHeight(480); -// settings.setRenderer(AppSettings.JOGL); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); JmeSystem.setLowPermissions(true); try{ - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); - }catch (ClassNotFoundException ex){ - ex.printStackTrace(); - }catch (InstantiationException ex){ - ex.printStackTrace(); - }catch (IllegalAccessException ex){ + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + } catch (ClassNotFoundException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException + | SecurityException ex) { ex.printStackTrace(); } @@ -85,6 +89,7 @@ public static void startApp(){ app.startCanvas(); app.enqueue(new Callable(){ + @Override public Void call(){ if (app instanceof SimpleApplication){ SimpleApplication simpleApp = (SimpleApplication) app; @@ -133,6 +138,7 @@ public void stop(){ @Override public void destroy(){ SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ removeAll(); System.out.println("applet:destroyStart"); diff --git a/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java b/jme3-examples/src/main/java/jme3test/awt/TestAwtPanels.java index 55d74e1197..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); @@ -56,6 +96,7 @@ public static void main(String[] args){ app.start(); SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ /* * Sleep 2 seconds to ensure there's no race condition. diff --git a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java index be4e5e8f6f..5f97ba8c27 100644 --- a/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java +++ b/jme3-examples/src/main/java/jme3test/awt/TestCanvas.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,7 +29,6 @@ * 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.LegacyApplication; @@ -45,6 +44,7 @@ import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; +import java.lang.reflect.InvocationTargetException; import java.util.concurrent.Callable; import java.util.logging.ConsoleHandler; import java.util.logging.Handler; @@ -88,6 +88,7 @@ private static void createMenu(){ final JMenuItem itemRemoveCanvas = new JMenuItem("Remove Canvas"); menuTortureMethods.add(itemRemoveCanvas); itemRemoveCanvas.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { if (itemRemoveCanvas.getText().equals("Remove Canvas")){ currentPanel.remove(canvas); @@ -104,6 +105,7 @@ public void actionPerformed(ActionEvent e) { final JMenuItem itemHideCanvas = new JMenuItem("Hide Canvas"); menuTortureMethods.add(itemHideCanvas); itemHideCanvas.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { if (itemHideCanvas.getText().equals("Hide Canvas")){ canvas.setVisible(false); @@ -118,6 +120,7 @@ public void actionPerformed(ActionEvent e) { final JMenuItem itemSwitchTab = new JMenuItem("Switch to tab #2"); menuTortureMethods.add(itemSwitchTab); itemSwitchTab.addActionListener(new ActionListener(){ + @Override public void actionPerformed(ActionEvent e){ if (itemSwitchTab.getText().equals("Switch to tab #2")){ canvasPanel1.remove(canvas); @@ -136,6 +139,7 @@ public void actionPerformed(ActionEvent e){ JMenuItem itemSwitchLaf = new JMenuItem("Switch Look and Feel"); menuTortureMethods.add(itemSwitchLaf); itemSwitchLaf.addActionListener(new ActionListener(){ + @Override public void actionPerformed(ActionEvent e){ try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); @@ -150,6 +154,7 @@ public void actionPerformed(ActionEvent e){ JMenuItem itemSmallSize = new JMenuItem("Set size to (0, 0)"); menuTortureMethods.add(itemSmallSize); itemSmallSize.addActionListener(new ActionListener(){ + @Override public void actionPerformed(ActionEvent e){ Dimension preferred = frame.getPreferredSize(); frame.setPreferredSize(new Dimension(0, 0)); @@ -161,10 +166,11 @@ public void actionPerformed(ActionEvent e){ JMenuItem itemKillCanvas = new JMenuItem("Stop/Start Canvas"); menuTortureMethods.add(itemKillCanvas); itemKillCanvas.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent e) { currentPanel.remove(canvas); app.stop(true); - + createCanvas(appClass); currentPanel.add(canvas, BorderLayout.CENTER); frame.pack(); @@ -175,6 +181,7 @@ public void actionPerformed(ActionEvent e) { JMenuItem itemExit = new JMenuItem("Exit"); menuTortureMethods.add(itemExit); itemExit.addActionListener(new ActionListener() { + @Override public void actionPerformed(ActionEvent ae) { frame.dispose(); app.stop(); @@ -196,19 +203,22 @@ public void windowClosed(WindowEvent e) { createMenu(); } + @SuppressWarnings("unchecked") public static void createCanvas(String appClass){ AppSettings settings = new AppSettings(true); settings.setWidth(640); settings.setHeight(480); try{ - Class clazz = (Class) Class.forName(appClass); - app = clazz.newInstance(); - }catch (ClassNotFoundException ex){ - ex.printStackTrace(); - }catch (InstantiationException ex){ - ex.printStackTrace(); - }catch (IllegalAccessException ex){ + Class clazz = Class.forName(appClass); + app = (LegacyApplication) clazz.getDeclaredConstructor().newInstance(); + }catch (ClassNotFoundException + | InstantiationException + | IllegalAccessException + | IllegalArgumentException + | InvocationTargetException + | NoSuchMethodException + | SecurityException ex) { ex.printStackTrace(); } @@ -225,6 +235,7 @@ public static void createCanvas(String appClass){ public static void startApp(){ app.startCanvas(); app.enqueue(new Callable(){ + @Override public Void call(){ if (app instanceof SimpleApplication){ SimpleApplication simpleApp = (SimpleApplication) app; @@ -253,6 +264,7 @@ public static void main(String[] args){ } SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ JPopupMenu.setDefaultLightWeightPopupEnabled(false); diff --git a/jme3-examples/src/main/java/jme3test/awt/package-info.java b/jme3-examples/src/main/java/jme3test/awt/package-info.java new file mode 100644 index 0000000000..36025141ca --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/awt/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for interfacing to the Abstract Window + * Toolkit (AWT) + */ +package jme3test.awt; diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java index db7e7a85e6..b8037f4a19 100644 --- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,30 +41,31 @@ import com.jme3.math.Vector3f; import com.jme3.scene.BatchNode; import com.jme3.scene.Geometry; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.debug.WireFrustum; import com.jme3.scene.shape.Box; import com.jme3.system.NanoTimer; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; /** - * + * A test to demonstrate the usage and functionality of the {@link BatchNode} * @author Nehon */ public class TestBatchNode extends SimpleApplication { + private BatchNode batch; + private WireFrustum frustum; + private final Vector3f[] points; + private Geometry cube2; + private float time = 0; + private DirectionalLight dl; + private boolean done = false; public static void main(String[] args) { - TestBatchNode app = new TestBatchNode(); app.start(); } - BatchNode batch; - WireFrustum frustum; - Geometry frustumMdl; - private Vector3f[] points; - { + public TestBatchNode() { points = new Vector3f[8]; for (int i = 0; i < points.length; i++) { points[i] = new Vector3f(); @@ -76,33 +77,28 @@ public void simpleInitApp() { timer = new NanoTimer(); batch = new BatchNode("theBatchNode"); - - - /** + /* * A cube with a color "bleeding" through transparent texture. Uses - * Texture from jme3-test-data library! + * Texture from jme3-testdata library! */ - Box boxshape4 = new Box(1f, 1f, 1f); - cube = new Geometry("cube1", boxshape4); + Box boxShape4 = new Box(1f, 1f, 1f); + Geometry cube = new Geometry("cube1", boxShape4); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); cube.setMaterial(mat); -// Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); -// mat.setColor("Diffuse", ColorRGBA.Blue); -// mat.setBoolean("UseMaterialColors", true); - /** + //Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + //mat.setColor("Diffuse", ColorRGBA.Blue); + //mat.setBoolean("UseMaterialColors", true); + /* * A cube with a color "bleeding" through transparent texture. Uses - * Texture from jme3-test-data library! + * Texture from jme3-testdata library! */ Box box = new Box(1f, 1f, 1f); cube2 = new Geometry("cube2", box); cube2.setMaterial(mat); - TangentBinormalGenerator.generate(cube); - TangentBinormalGenerator.generate(cube2); + MikktspaceTangentGenerator.generate(cube); + MikktspaceTangentGenerator.generate(cube2); - - n = new Node("aNode"); - // n.attachChild(cube2); batch.attachChild(cube); // batch.attachChild(cube2); // batch.setMaterial(mat); @@ -111,10 +107,9 @@ public void simpleInitApp() { cube.setLocalTranslation(3, 0, 0); cube2.setLocalTranslation(0, 20, 0); - - updateBoindPoints(points); + updateBoundingPoints(points); frustum = new WireFrustum(points); - frustumMdl = new Geometry("f", frustum); + Geometry frustumMdl = new Geometry("f", frustum); frustumMdl.setCullHint(Spatial.CullHint.Never); frustumMdl.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); frustumMdl.getMaterial().getAdditionalRenderState().setWireframe(true); @@ -126,12 +121,6 @@ public void simpleInitApp() { rootNode.addLight(dl); flyCam.setMoveSpeed(10); } - Node n; - Geometry cube; - Geometry cube2; - float time = 0; - DirectionalLight dl; - boolean done = false; @Override public void simpleUpdate(float tpf) { @@ -140,7 +129,7 @@ public void simpleUpdate(float tpf) { batch.attachChild(cube2); batch.batch(); } - updateBoindPoints(points); + updateBoundingPoints(points); frustum.update(points); time += tpf; dl.setDirection(cam.getDirection()); @@ -148,12 +137,10 @@ public void simpleUpdate(float tpf) { cube2.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z)); cube2.setLocalScale(Math.max(FastMath.sin(time), 0.5f)); -// batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z)); - + // batch.setLocalRotation(new Quaternion().fromAngleAxis(time, Vector3f.UNIT_Z)); } -// - public void updateBoindPoints(Vector3f[] points) { + public void updateBoundingPoints(Vector3f[] points) { BoundingBox bb = (BoundingBox) batch.getWorldBound(); float xe = bb.getXExtent(); float ye = bb.getYExtent(); diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java index 9430aa368a..52a87492c4 100644 --- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeCluster.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,35 +63,30 @@ public static void main(String[] args) { app.setShowSettings(false); app.start(); } - private ActionListener al = new ActionListener() { + final private ActionListener al = new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("Start Game")) { // randomGenerator(); } } }; - protected Random rand = new Random(); - protected int maxCubes = 2000; - protected int startAt = 0; - protected static int xPositions = 0, yPositions = 0, zPositions = 0; - protected int returner = 0; - protected ArrayList xPosition = new ArrayList(); - protected ArrayList yPosition = new ArrayList(); - protected ArrayList zPosition = new ArrayList(); - protected int xLimitf = 60, xLimits = -60, yLimitf = 60, yLimits = -20, zLimitf = 60, zLimits = -60; - protected int circ = 8;//increases by 8 every time. - protected int dynamic = 4; - protected static AppSettings settingst; - protected boolean isTrue = true; - private int lineLength = 50; - protected BatchNode batchNode; - Material mat1; - Material mat2; - Material mat3; - Material mat4; - Node terrain; - //protected + final private Random rand = new Random(); + final private int maxCubes = 2000; + final private int startAt = 0; + final private ArrayList xPosition = new ArrayList<>(); + final private ArrayList yPosition = new ArrayList<>(); + final private ArrayList zPosition = new ArrayList<>(); + final private int yLimitf = 60, yLimits = -20; + private static AppSettings settingst; + final private int lineLength = 50; + private BatchNode batchNode; + private Material mat1; + private Material mat2; + private Material mat3; + private Material mat4; + private Node terrain; // protected Geometry player; @Override @@ -334,12 +329,7 @@ public int gety(int i) { public int getz(int i) { return zPosition.get(i); } - long nbFrames = 0; - long cullTime = 0; - float time = 0; - Vector3f lookAtPos = new Vector3f(0, 0, 0); - float xpos = 0; - Spatial box; + private float time = 0; @Override public void simpleUpdate(float tpf) { @@ -360,7 +350,7 @@ public void simpleUpdate(float tpf) { mult1 = -1.0f; mult2 = -1.0f; } - box = batchNode.getChild("Box" + random); + Spatial box = batchNode.getChild("Box" + random); if (box != null) { Vector3f v = box.getLocalTranslation(); box.setLocalTranslation(v.x + FastMath.sin(time * mult1) * 20, v.y + (FastMath.sin(time * mult1) * FastMath.cos(time * mult1) * 20), v.z + FastMath.cos(time * mult2) * 20); diff --git a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java index a13dc6168f..5f0e603fb5 100644 --- a/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java +++ b/jme3-examples/src/main/java/jme3test/batching/TestBatchNodeTower.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,24 +67,23 @@ */ public class TestBatchNodeTower extends SimpleApplication { - int bricksPerLayer = 8; - int brickLayers = 30; + final private int bricksPerLayer = 8; + final private int brickLayers = 30; - static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; - float radius = 3f; - float angle = 0; + final private static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; + final private float radius = 3f; + private float angle = 0; - Material mat; - Material mat2; - Material mat3; - DirectionalLightShadowFilter shadowRenderer; + private Material mat; + private Material mat2; + private Material mat3; private Sphere bullet; private Box brick; private SphereCollisionShape bulletCollisionShape; private BulletAppState bulletAppState; - BatchNode batchNode = new BatchNode("batch Node"); + final private BatchNode batchNode = new BatchNode("batch Node"); public static void main(String args[]) { TestBatchNodeTower f = new TestBatchNodeTower(); @@ -106,7 +105,6 @@ public void simpleInitApp() { brick = new Box(brickWidth, brickHeight, brickDepth); brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); - //bulletAppState.getPhysicsSpace().enableDebug(assetManager); initMaterial(); initTower(); initFloor(); @@ -121,9 +119,9 @@ public void simpleInitApp() { batchNode.batch(); batchNode.setShadowMode(ShadowMode.CastAndReceive); rootNode.attachChild(batchNode); - - - shadowRenderer = new DirectionalLightShadowFilter(assetManager, 1024, 2); + + DirectionalLightShadowFilter shadowRenderer + = new DirectionalLightShadowFilter(assetManager, 1024, 2); DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); shadowRenderer.setLight(dl); @@ -139,19 +137,20 @@ public void simpleInitApp() { private PhysicsSpace getPhysicsSpace() { return bulletAppState.getPhysicsSpace(); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("shoot") && !keyPressed) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.setLocalTranslation(cam.getLocation()); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(cam.getLocation()); RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); // RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); bulletNode.setLinearVelocity(cam.getDirection().mult(25)); - bulletg.addControl(bulletNode); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletNode); + rootNode.attachChild(bulletGeometry); getPhysicsSpace().add(bulletNode); } } @@ -225,7 +224,7 @@ public void initMaterial() { tex3.setWrap(WrapMode.Repeat); mat3.setTexture("ColorMap", tex3); } -int nbBrick =0; + public void addBrick(Vector3f ori) { Geometry reBoxg = new Geometry("brick", brick); reBoxg.setMaterial(mat); @@ -236,12 +235,11 @@ public void addBrick(Vector3f ori) { reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f); this.batchNode.attachChild(reBoxg); this.getPhysicsSpace().add(reBoxg); - nbBrick++; } protected void initCrossHairs() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center diff --git a/jme3-examples/src/main/java/jme3test/batching/package-info.java b/jme3-examples/src/main/java/jme3test/batching/package-info.java new file mode 100644 index 0000000000..714fe0ab6e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/batching/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for batching multiple geometries into a + * single Mesh + */ +package jme3test.batching; diff --git a/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java b/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java deleted file mode 100644 index 0ff740bfbc..0000000000 --- a/jme3-examples/src/main/java/jme3test/blender/TestBlenderLoader.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.blender; - -import com.jme3.app.SimpleApplication; -import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector3f; -import com.jme3.scene.Spatial; - -public class TestBlenderLoader extends SimpleApplication { - - public static void main(String[] args){ - TestBlenderLoader app = new TestBlenderLoader(); - app.start(); - } - - @Override - public void simpleInitApp() { - viewPort.setBackgroundColor(ColorRGBA.DarkGray); - - //load model with packed images - Spatial ogre = assetManager.loadModel("Blender/2.4x/Sinbad.blend"); - rootNode.attachChild(ogre); - - //load model with referenced images - Spatial track = assetManager.loadModel("Blender/2.4x/MountainValley_Track.blend"); - rootNode.attachChild(track); - - // sunset light - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.1f,-0.7f,1).normalizeLocal()); - dl.setColor(new ColorRGBA(0.44f, 0.30f, 0.20f, 1.0f)); - rootNode.addLight(dl); - - // skylight - dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.6f,-1,-0.6f).normalizeLocal()); - dl.setColor(new ColorRGBA(0.10f, 0.22f, 0.44f, 1.0f)); - rootNode.addLight(dl); - - // white ambient light - dl = new DirectionalLight(); - dl.setDirection(new Vector3f(1, -0.5f,-0.1f).normalizeLocal()); - dl.setColor(new ColorRGBA(0.80f, 0.70f, 0.80f, 1.0f)); - rootNode.addLight(dl); - } - - @Override - public void simpleUpdate(float tpf){ - } - -} diff --git a/jme3-examples/src/main/java/jme3test/bounding/package-info.java b/jme3-examples/src/main/java/jme3test/bounding/package-info.java new file mode 100644 index 0000000000..6e7428f082 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bounding/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for bounding volumes + */ +package jme3test.bounding; diff --git a/jme3-examples/src/main/java/jme3test/bullet/BombControl.java b/jme3-examples/src/main/java/jme3test/bullet/BombControl.java index 4d9af6a456..49f9a43982 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/BombControl.java +++ b/jme3-examples/src/main/java/jme3test/bullet/BombControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,12 +61,12 @@ public class BombControl extends RigidBodyControl implements PhysicsCollisionLis private float explosionRadius = 10; private PhysicsGhostObject ghostObject; - private Vector3f vector = new Vector3f(); - private Vector3f vector2 = new Vector3f(); + final private Vector3f vector = new Vector3f(); + final private Vector3f vector2 = new Vector3f(); private float forceFactor = 1; private ParticleEmitter effect; - private float fxTime = 0.5f; - private float maxTime = 4f; + final private float fxTime = 0.5f; + final private float maxTime = 4f; private float curTime = -1.0f; private float timer; @@ -81,6 +81,7 @@ public BombControl(AssetManager manager, CollisionShape shape, float mass) { prepareEffect(manager); } + @Override public void setPhysicsSpace(PhysicsSpace space) { super.setPhysicsSpace(space); if (space != null) { @@ -93,7 +94,7 @@ private void prepareEffect(AssetManager assetManager) { float COUNT_FACTOR_F = 1f; effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); effect.setSelectRandomImage(true); - effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); effect.setStartSize(1.3f); effect.setEndSize(2f); @@ -116,6 +117,7 @@ protected void createGhostObject() { ghostObject = new PhysicsGhostObject(new SphereCollisionShape(explosionRadius)); } + @Override public void collision(PhysicsCollisionEvent event) { if (space == null) { return; @@ -135,10 +137,12 @@ public void collision(PhysicsCollisionEvent event) { } } + @Override public void prePhysicsTick(PhysicsSpace space, float f) { space.removeCollisionListener(this); } + @Override public void physicsTick(PhysicsSpace space, float f) { //get all overlapping objects and apply impulse to them for (Iterator it = ghostObject.getOverlappingObjects().iterator(); it.hasNext();) { diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java index 901b3433c3..8f38323d26 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsHoverControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -84,7 +84,7 @@ public PhysicsHoverControl() { /** * Creates a new PhysicsNode with the supplied collision shape - * @param shape + * @param shape the desired collision shape */ public PhysicsHoverControl(CollisionShape shape) { super(shape); @@ -112,6 +112,7 @@ public void cloneFields( Cloner cloner, Object original ) { throw new UnsupportedOperationException("Not yet implemented."); } + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -122,10 +123,12 @@ public void setSpatial(Spatial spatial) { setPhysicsRotation(spatial.getWorldRotation().toRotationMatrix()); } + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; } + @Override public boolean isEnabled() { return enabled; } @@ -140,6 +143,7 @@ private void createWheels() { } } + @Override public void prePhysicsTick(PhysicsSpace space, float f) { Vector3f angVel = getAngularVelocity(); float rotationVelocity = angVel.getY(); @@ -183,18 +187,22 @@ public void prePhysicsTick(PhysicsSpace space, float f) { } } + @Override public void physicsTick(PhysicsSpace space, float f) { } + @Override public void update(float tpf) { if (enabled && spatial != null) { getMotionState().applyTransform(spatial); } } + @Override public void render(RenderManager rm, ViewPort vp) { } + @Override public void setPhysicsSpace(PhysicsSpace space) { createVehicle(space); if (space == null) { @@ -210,6 +218,7 @@ public void setPhysicsSpace(PhysicsSpace space) { this.space = space; } + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java b/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java index 4d8a5a92f9..59d02d64c8 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java +++ b/jme3-examples/src/main/java/jme3test/bullet/PhysicsTestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,6 +63,11 @@ * @author normenhansen */ public class PhysicsTestHelper { + /** + * A private constructor to inhibit instantiation of this class. + */ + private PhysicsTestHelper() { + } /** * creates a simple physics test world with a floor, an obstacle and some test boxes @@ -216,7 +221,7 @@ public static Node createPhysicsTestNode(AssetManager manager, CollisionShape sh } /** - * creates the necessary inputlistener and action to shoot balls from the camera + * creates the necessary input listener and action to shoot balls from the camera * * @param app the application that's running * @param rootNode where ball geometries should be added @@ -225,6 +230,7 @@ public static Node createPhysicsTestNode(AssetManager manager, CollisionShape sh public static void createBallShooter(final Application app, final Node rootNode, final PhysicsSpace space) { ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { Sphere bullet = new Sphere(32, 32, 0.4f, true, false); bullet.setTextureMode(TextureMode.Projected); @@ -234,15 +240,15 @@ public void onAction(String name, boolean keyPressed, float tpf) { Texture tex2 = app.getAssetManager().loadTexture(key2); mat2.setTexture("ColorMap", tex2); if (name.equals("shoot") && !keyPressed) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.setLocalTranslation(app.getCamera().getLocation()); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(app.getCamera().getLocation()); RigidBodyControl bulletControl = new RigidBodyControl(10); - bulletg.addControl(bulletControl); + bulletGeometry.addControl(bulletControl); bulletControl.setLinearVelocity(app.getCamera().getDirection().mult(25)); - bulletg.addControl(bulletControl); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletControl); + rootNode.attachChild(bulletGeometry); space.add(bulletControl); } } @@ -259,7 +265,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { * @param assetManager for loading assets * @param floorDimensions width/depth of the "floor" (X/Z) * @param position sets the floor's local translation - * @return + * @return a new Geometry */ public static Geometry createGImpactTestFloor(AssetManager assetManager, float floorDimensions, Vector3f position) { Geometry floor = createTestFloor(assetManager, floorDimensions, position, ColorRGBA.Red); @@ -276,7 +282,7 @@ public static Geometry createGImpactTestFloor(AssetManager assetManager, float f * @param assetManager for loading assets * @param floorDimensions width/depth of the "floor" (X/Z) * @param position sets the floor's local translation - * @return + * @return a new Geometry */ public static Geometry createMeshTestFloor(AssetManager assetManager, float floorDimensions, Vector3f position) { Geometry floor = createTestFloor(assetManager, floorDimensions, position, new ColorRGBA(0.5f, 0.5f, 0.9f, 1)); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachDriver.java index 1de5b01189..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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,14 +60,13 @@ public class TestAttachDriver extends SimpleApplication implements ActionListener { private VehicleControl vehicle; - private RigidBodyControl driver; private RigidBodyControl bridge; private SliderJoint slider; private final float accelerationForce = 1000.0f; private final float brakeForce = 100.0f; private float steeringValue = 0; private float accelerationValue = 0; - private Vector3f jumpForce = new Vector3f(0, 3000, 0); + final private Vector3f jumpForce = new Vector3f(0, 3000, 0); private BulletAppState bulletAppState; public static void main(String[] args) { @@ -202,7 +201,8 @@ private void buildPlayer() { //driver Node driverNode=new Node("driverNode"); driverNode.setLocalTranslation(0,2,0); - driver=new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,.5f,0.2f))); + RigidBodyControl driver + = new RigidBodyControl(new BoxCollisionShape(new Vector3f(0.2f,.5f,0.2f))); driverNode.addControl(driver); rootNode.attachChild(driverNode); @@ -244,17 +244,18 @@ public void simpleUpdate(float tpf) { cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Lefts")) { if (value) { steeringValue += .5f; } else { - steeringValue += -.5f; + steeringValue -= .5f; } vehicle.steer(steeringValue); } else if (binding.equals("Rights")) { if (value) { - steeringValue += -.5f; + steeringValue -= .5f; } else { steeringValue += .5f; } @@ -274,8 +275,11 @@ public void onAction(String binding, boolean value, float tpf) { } } else if (binding.equals("Space")) { if (value) { - getPhysicsSpace().remove(slider); - slider.destroy(); + if (slider != null) { + getPhysicsSpace().remove(slider); + slider.destroy(); + slider = null; + } vehicle.applyImpulse(jumpForce, Vector3f.ZERO); } } else if (binding.equals("Reset")) { @@ -287,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/bullet/TestAttachGhostObject.java b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java index 5fac3fc5f2..f722899363 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestAttachGhostObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ import com.jme3.scene.Node; /** - * Tests attaching ghost nodes to physicsnodes via the scenegraph + * Tests attaching ghost nodes to physics nodes via the scene graph * @author normenhansen */ public class TestAttachGhostObject extends SimpleApplication implements AnalogListener { @@ -55,8 +55,6 @@ public class TestAttachGhostObject extends SimpleApplication implements AnalogLi private HingeJoint joint; private GhostControl ghostControl; private Node collisionNode; - private Node hammerNode; - private Vector3f tempVec = new Vector3f(); private BulletAppState bulletAppState; public static void main(String[] args) { @@ -71,6 +69,7 @@ private void setupKeys() { inputManager.addListener(this, "Lefts", "Rights", "Space"); } + @Override public void onAnalog(String binding, float value, float tpf) { if (binding.equals("Lefts")) { joint.enableMotor(true, 1, .1f); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java index 4a86f8219d..25065c484b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBetterCharacter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,12 +63,11 @@ public class TestBetterCharacter extends SimpleApplication implements ActionList private BetterCharacterControl physicsCharacter; private Node characterNode; private CameraNode camNode; - boolean rotate = false; - private Vector3f walkDirection = new Vector3f(0, 0, 0); - private Vector3f viewDirection = new Vector3f(0, 0, 1); - boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + final private Vector3f walkDirection = new Vector3f(0, 0, 0); + final private Vector3f viewDirection = new Vector3f(0, 0, 1); + private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, leftRotate = false, rightRotate = false; - private Vector3f normalGravity = new Vector3f(0, -9.81f, 0); + final private Vector3f normalGravity = new Vector3f(0, -9.81f, 0); private Geometry planet; public static void main(String[] args) { @@ -105,8 +104,8 @@ public void prePhysicsTick(PhysicsSpace space, float tpf) { characterNode = new Node("character node"); characterNode.setLocalTranslation(new Vector3f(4, 5, 2)); - // Add a character control to the node so we can add other things and - // control the model rotation + // Add a character control to the node, so we can add other things and + // control the model rotation. physicsCharacter = new BetterCharacterControl(0.3f, 2.5f, 8f); characterNode.addControl(physicsCharacter); getPhysicsSpace().add(physicsCharacter); @@ -119,6 +118,8 @@ public void prePhysicsTick(PhysicsSpace space, float tpf) { // Add character node to the rootNode rootNode.attachChild(characterNode); + cam.setLocation(new Vector3f(10f, 6f, -5f)); + // Set forward camera node that follows the character, only used when // view is "locked" camNode = new CameraNode("CamNode", cam); @@ -198,6 +199,7 @@ private PhysicsSpace getPhysicsSpace() { return bulletAppState.getPhysicsSpace(); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Strafe Left")) { if (value) { diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java index ee6e2f9978..538ec8b31c 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBoneRagdoll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,18 +91,18 @@ public void onStandDone() { @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("boom") && !isPressed) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(matBullet); - bulletg.setLocalTranslation(cam.getLocation()); - bulletg.setLocalScale(bulletSize); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(matBullet); + bulletGeometry.setLocalTranslation(cam.getLocation()); + bulletGeometry.setLocalScale(bulletSize); bulletCollisionShape = new SphereCollisionShape(bulletSize); BombControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1f); bulletNode.setForceFactor(8f); bulletNode.setExplosionRadius(20f); bulletNode.setCcdMotionThreshold(0.001f); bulletNode.setLinearVelocity(cam.getDirection().mult(180f)); - bulletg.addControl(bulletNode); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletNode); + rootNode.attachChild(bulletGeometry); physicsSpace.add(bulletNode); } if (name.equals("bullet+") && isPressed) { @@ -204,7 +204,7 @@ public void collide(PhysicsLink bone, PhysicsCollisionObject object, private void initCrossHairs() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2f); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java index feffac8e6e..a607878185 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickTower.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,38 +31,6 @@ */ package jme3test.bullet; -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.bullet.BulletAppState; @@ -90,17 +58,17 @@ */ public class TestBrickTower extends SimpleApplication { - int bricksPerLayer = 8; - int brickLayers = 30; + final private int bricksPerLayer = 8; + final private int brickLayers = 30; - static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; - float radius = 3f; - float angle = 0; + final private static float brickWidth = .75f, brickHeight = .25f, brickDepth = .25f; + final private float radius = 3f; + private float angle = 0; - Material mat; - Material mat2; - Material mat3; + private Material mat; + private Material mat2; + private Material mat3; private Sphere bullet; private Box brick; private SphereCollisionShape bulletCollisionShape; @@ -124,7 +92,6 @@ public void simpleInitApp() { brick = new Box(brickWidth, brickHeight, brickDepth); brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); - //bulletAppState.getPhysicsSpace().enableDebug(assetManager); initMaterial(); initTower(); initFloor(); @@ -140,19 +107,20 @@ public void simpleInitApp() { private PhysicsSpace getPhysicsSpace() { return bulletAppState.getPhysicsSpace(); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("shoot") && !keyPressed) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.setLocalTranslation(cam.getLocation()); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(cam.getLocation()); RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); // RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); bulletNode.setLinearVelocity(cam.getDirection().mult(25)); - bulletg.addControl(bulletNode); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletNode); + rootNode.attachChild(bulletGeometry); getPhysicsSpace().add(bulletNode); } } @@ -227,20 +195,20 @@ public void initMaterial() { } public void addBrick(Vector3f ori) { - Geometry reBoxg = new Geometry("brick", brick); - reBoxg.setMaterial(mat); - reBoxg.setLocalTranslation(ori); - reBoxg.rotate(0f, (float)Math.toRadians(angle) , 0f ); - reBoxg.addControl(new RigidBodyControl(1.5f)); - reBoxg.setShadowMode(ShadowMode.CastAndReceive); - reBoxg.getControl(RigidBodyControl.class).setFriction(1.6f); - this.rootNode.attachChild(reBoxg); - this.getPhysicsSpace().add(reBoxg); + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(mat); + brickGeometry.setLocalTranslation(ori); + brickGeometry.rotate(0f, (float)Math.toRadians(angle) , 0f ); + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + brickGeometry.getControl(RigidBodyControl.class).setFriction(1.6f); + this.rootNode.attachChild(brickGeometry); + this.getPhysicsSpace().add(brickGeometry); } protected void initCrossHairs() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java b/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java index ce61e355e6..d0cbdb2bba 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestBrickWall.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,15 +61,14 @@ */ public class TestBrickWall extends SimpleApplication { - static float bLength = 0.48f; - static float bWidth = 0.24f; - static float bHeight = 0.12f; - Material mat; - Material mat2; - Material mat3; + final private static float bLength = 0.48f; + final private static float bWidth = 0.24f; + final private static float bHeight = 0.12f; + private Material mat; + private Material mat2; + private Material mat3; private static Sphere bullet; private static Box brick; - private static SphereCollisionShape bulletCollisionShape; private BulletAppState bulletAppState; @@ -87,7 +86,6 @@ public void simpleInitApp() { bullet = new Sphere(32, 32, 0.4f, true, false); bullet.setTextureMode(TextureMode.Projected); - bulletCollisionShape = new SphereCollisionShape(0.4f); brick = new Box(bLength, bHeight, bWidth); brick.scaleTextureCoordinates(new Vector2f(1f, .5f)); @@ -109,21 +107,22 @@ public void simpleInitApp() { private PhysicsSpace getPhysicsSpace() { return bulletAppState.getPhysicsSpace(); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("shoot") && !keyPressed) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.setLocalTranslation(cam.getLocation()); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(cam.getLocation()); SphereCollisionShape bulletCollisionShape = new SphereCollisionShape(0.4f); RigidBodyControl bulletNode = new BombControl(assetManager, bulletCollisionShape, 1); // RigidBodyControl bulletNode = new RigidBodyControl(bulletCollisionShape, 1); bulletNode.setLinearVelocity(cam.getDirection().mult(25)); - bulletg.addControl(bulletNode); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletNode); + rootNode.attachChild(bulletGeometry); getPhysicsSpace().add(bulletNode); } if (name.equals("gc") && !keyPressed) { @@ -133,14 +132,14 @@ public void onAction(String name, boolean keyPressed, float tpf) { }; public void initWall() { - float startpt = bLength / 4; + float startX = bLength / 4; float height = 0; for (int j = 0; j < 15; j++) { for (int i = 0; i < 4; i++) { - Vector3f vt = new Vector3f(i * bLength * 2 + startpt, bHeight + height, 0); + Vector3f vt = new Vector3f(i * bLength * 2 + startX, bHeight + height, 0); addBrick(vt); } - startpt = -startpt; + startX = -startX; height += 2 * bHeight; } } @@ -181,20 +180,20 @@ public void initMaterial() { public void addBrick(Vector3f ori) { - Geometry reBoxg = new Geometry("brick", brick); - reBoxg.setMaterial(mat); - reBoxg.setLocalTranslation(ori); + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(mat); + brickGeometry.setLocalTranslation(ori); //for geometry with sphere mesh the physics system automatically uses a sphere collision shape - reBoxg.addControl(new RigidBodyControl(1.5f)); - reBoxg.setShadowMode(ShadowMode.CastAndReceive); - reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f); - this.rootNode.attachChild(reBoxg); - this.getPhysicsSpace().add(reBoxg); + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + brickGeometry.getControl(RigidBodyControl.class).setFriction(0.6f); + this.rootNode.attachChild(brickGeometry); + this.getPhysicsSpace().add(brickGeometry); } protected void initCrossHairs() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java index 58cb77c650..3c5ff09282 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCcd.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -125,28 +125,29 @@ public void simpleRender(RenderManager rm) { //TODO: add render code } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("shoot") && !value) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat); - bulletg.setName("bullet"); - bulletg.setLocalTranslation(cam.getLocation()); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); - bulletg.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f); - bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); - rootNode.attachChild(bulletg); - getPhysicsSpace().add(bulletg); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat); + bulletGeometry.setName("bullet"); + bulletGeometry.setLocalTranslation(cam.getLocation()); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletGeometry.getControl(RigidBodyControl.class).setCcdMotionThreshold(0.1f); + bulletGeometry.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletGeometry); + getPhysicsSpace().add(bulletGeometry); } else if (binding.equals("shoot2") && !value) { - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(mat2); - bulletg.setName("bullet"); - bulletg.setLocalTranslation(cam.getLocation()); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.addControl(new RigidBodyControl(bulletCollisionShape, 1)); - bulletg.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); - rootNode.attachChild(bulletg); - getPhysicsSpace().add(bulletg); + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(mat2); + bulletGeometry.setName("bullet"); + bulletGeometry.setLocalTranslation(cam.getLocation()); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.addControl(new RigidBodyControl(bulletCollisionShape, 1)); + bulletGeometry.getControl(RigidBodyControl.class).setLinearVelocity(cam.getDirection().mult(40)); + rootNode.attachChild(bulletGeometry); + getPhysicsSpace().add(bulletGeometry); } } } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java index 3c9450f3d2..01714f5b3b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestCollisionListener.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.collision.PhysicsCollisionEvent; import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; import com.jme3.renderer.RenderManager; import com.jme3.scene.shape.Sphere; import com.jme3.scene.shape.Sphere.TextureMode; @@ -49,8 +48,6 @@ public class TestCollisionListener extends SimpleApplication implements PhysicsCollisionListener { private BulletAppState bulletAppState; - private Sphere bullet; - private SphereCollisionShape bulletCollisionShape; public static void main(String[] args) { TestCollisionListener app = new TestCollisionListener(); @@ -62,9 +59,8 @@ public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); bulletAppState.setDebugEnabled(true); - bullet = new Sphere(32, 32, 0.4f, true, false); + Sphere bullet = new Sphere(32, 32, 0.4f, true, false); bullet.setTextureMode(TextureMode.Projected); - bulletCollisionShape = new SphereCollisionShape(0.4f); PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); PhysicsTestHelper.createBallShooter(this, rootNode, bulletAppState.getPhysicsSpace()); @@ -87,6 +83,7 @@ public void simpleRender(RenderManager rm) { //TODO: add render code } + @Override public void collision(PhysicsCollisionEvent event) { if ("Box".equals(event.getNodeA().getName()) || "Box".equals(event.getNodeB().getName())) { if ("bullet".equals(event.getNodeA().getName()) || "bullet".equals(event.getNodeB().getName())) { diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java index 3803c4c5d4..ce21b1f15b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestFancyCar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.bullet.PhysicsSpace; import com.jme3.bullet.collision.shapes.CollisionShape; import com.jme3.bullet.control.VehicleControl; -import com.jme3.bullet.objects.VehicleWheel; import com.jme3.bullet.util.CollisionShapeFactory; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -55,15 +54,12 @@ public class TestFancyCar extends SimpleApplication implements ActionListener { private BulletAppState bulletAppState; private VehicleControl player; - private VehicleWheel fr, fl, br, bl; - private Node node_fr, node_fl, node_br, node_bl; - private float wheelRadius; private float steeringValue = 0; private float accelerationValue = 0; private Node carNode; public static void main(String[] args) { - TestFancyCar app = new TestFancyCar(); + TestFancyCar app = new TestFancyCar(); app.start(); } @@ -72,13 +68,11 @@ private void setupKeys() { inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_K)); inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_U)); inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_J)); - inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN)); inputManager.addListener(this, "Lefts"); inputManager.addListener(this, "Rights"); inputManager.addListener(this, "Ups"); inputManager.addListener(this, "Downs"); - inputManager.addListener(this, "Space"); inputManager.addListener(this, "Reset"); } @@ -86,47 +80,22 @@ private void setupKeys() { public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); -// bulletAppState.getPhysicsSpace().enableDebug(assetManager); cam.setFrustumFar(150f); flyCam.setMoveSpeed(10); setupKeys(); - PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, bulletAppState.getPhysicsSpace()); -// setupFloor(); + PhysicsTestHelper.createPhysicsTestWorld(rootNode, assetManager, getPhysicsSpace()); buildPlayer(); DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-0.5f, -1f, -0.3f).normalizeLocal()); rootNode.addLight(dl); - - dl = new DirectionalLight(); - dl.setDirection(new Vector3f(0.5f, -0.1f, 0.3f).normalizeLocal()); - // rootNode.addLight(dl); } private PhysicsSpace getPhysicsSpace() { return bulletAppState.getPhysicsSpace(); } -// public void setupFloor() { -// Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); -// mat.getTextureParam("DiffuseMap").getTextureValue().setWrap(WrapMode.Repeat); -//// mat.getTextureParam("NormalMap").getTextureValue().setWrap(WrapMode.Repeat); -//// mat.getTextureParam("ParallaxMap").getTextureValue().setWrap(WrapMode.Repeat); -// -// Box floor = new Box(Vector3f.ZERO, 140, 1f, 140); -// floor.scaleTextureCoordinates(new Vector2f(112.0f, 112.0f)); -// Geometry floorGeom = new Geometry("Floor", floor); -// floorGeom.setShadowMode(ShadowMode.Receive); -// floorGeom.setMaterial(mat); -// -// PhysicsNode tb = new PhysicsNode(floorGeom, new MeshCollisionShape(floorGeom.getMesh()), 0); -// tb.setLocalTranslation(new Vector3f(0f, -6, 0f)); -//// tb.attachDebugShape(assetManager); -// rootNode.attachChild(tb); -// getPhysicsSpace().add(tb); -// } - private Geometry findGeom(Spatial spatial, String name) { if (spatial instanceof Node) { Node node = (Node) spatial; @@ -151,34 +120,33 @@ private void buildPlayer() { float dampValue = 0.3f; final float mass = 400; - //Load model and get chassis Geometry - carNode = (Node)assetManager.loadModel("Models/Ferrari/Car.scene"); + // Load model and get chassis Geometry + carNode = (Node) assetManager.loadModel("Models/Ferrari/Car.scene"); carNode.setShadowMode(ShadowMode.Cast); - Geometry chasis = findGeom(carNode, "Car"); - BoundingBox box = (BoundingBox) chasis.getModelBound(); + Geometry chassis = findGeom(carNode, "Car"); - //Create a hull collision shape for the chassis - CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis); + // Create a hull collision shape for the chassis + CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chassis); - //Create a vehicle control + // Create a vehicle control player = new VehicleControl(carHull, mass); carNode.addControl(player); - //Setting default values for wheels + // Setting default values for wheels player.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness)); player.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness)); player.setSuspensionStiffness(stiffness); player.setMaxSuspensionForce(10000); - //Create four wheels and add them at their locations - //note that our fancy car actually goes backwards.. + // Create four wheels and add them at their locations. + // Note that our fancy car actually goes backward. Vector3f wheelDirection = new Vector3f(0, -1, 0); Vector3f wheelAxle = new Vector3f(-1, 0, 0); Geometry wheel_fr = findGeom(carNode, "WheelFrontRight"); wheel_fr.center(); - box = (BoundingBox) wheel_fr.getModelBound(); - wheelRadius = box.getYExtent(); + BoundingBox box = (BoundingBox) wheel_fr.getModelBound(); + float wheelRadius = box.getYExtent(); float back_wheel_h = (wheelRadius * 1.7f) - 1f; float front_wheel_h = (wheelRadius * 1.9f) - 1f; player.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0), @@ -209,22 +177,23 @@ private void buildPlayer() { getPhysicsSpace().add(player); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Lefts")) { if (value) { steeringValue += .5f; } else { - steeringValue += -.5f; + steeringValue -= .5f; } player.steer(steeringValue); } else if (binding.equals("Rights")) { if (value) { - steeringValue += -.5f; + steeringValue -= .5f; } else { steeringValue += .5f; } player.steer(steeringValue); - } //note that our fancy car actually goes backwards.. + } // Note that our fancy car actually goes backward. else if (binding.equals("Ups")) { if (value) { accelerationValue -= 800; @@ -232,7 +201,6 @@ else if (binding.equals("Ups")) { accelerationValue += 800; } player.accelerate(accelerationValue); - player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, "Car"))); } else if (binding.equals("Downs")) { if (value) { player.brake(40f); @@ -247,7 +215,6 @@ else if (binding.equals("Ups")) { player.setLinearVelocity(Vector3f.ZERO); player.setAngularVelocity(Vector3f.ZERO); player.resetSuspension(); - } else { } } } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java index 922ce973b2..3127ec4a68 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestHoveringTank.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,19 +40,16 @@ import com.jme3.bullet.collision.shapes.CollisionShape; import com.jme3.bullet.control.RigidBodyControl; import com.jme3.bullet.util.CollisionShapeFactory; -import com.jme3.font.BitmapText; import com.jme3.input.ChaseCamera; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.AnalogListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.*; import com.jme3.renderer.Camera; import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.shadow.DirectionalLightShadowRenderer; import com.jme3.shadow.EdgeFilteringMode; @@ -73,13 +70,6 @@ public class TestHoveringTank extends SimpleApplication implements AnalogListene private BulletAppState bulletAppState; private PhysicsHoverControl hoverControl; private Spatial spaceCraft; - TerrainQuad terrain; - Material matRock; - boolean wireframe = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; - Geometry collisionMarker; /** * initial location of the tank (in world/physics-space coordinates) */ @@ -119,7 +109,6 @@ public void simpleInitApp() { bulletAppState = new BulletAppState(); bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); stateManager.attach(bulletAppState); -// bulletAppState.getPhysicsSpace().enableDebug(assetManager); bulletAppState.getPhysicsSpace().setAccuracy(1f/30f); rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", EnvMapType.CubeMap)); @@ -200,9 +189,11 @@ public void makeMissile() { getPhysicsSpace().add(missile); } + @Override public void onAnalog(String binding, float value, float tpf) { } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Lefts")) { hoverControl.steer(value ? 50f : 0); @@ -259,7 +250,8 @@ public void simpleUpdate(float tpf) { } private void createTerrain() { - matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matRock = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); matRock.setBoolean("useTriPlanarMapping", false); matRock.setBoolean("WardIso", true); matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); @@ -293,8 +285,9 @@ private void createTerrain() { } catch (Exception e) { e.printStackTrace(); } - terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList<>(); cameras.add(getCamera()); TerrainLodControl control = new TerrainLodControl(terrain, cameras); terrain.addControl(control); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1004.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue1004.java deleted file mode 100644 index 0379fadab1..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1004.java +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.control.KinematicRagdollControl; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.VertexBuffer; -import java.nio.ByteBuffer; - -/** - * Test case for JME issue #1004: RagdollUtils can't handle 16-bit bone indices. - *

                - * If successful, no exception will be thrown. - */ -public class TestIssue1004 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue1004 app = new TestIssue1004(); - app.start(); - } - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - String sinbadPath = "Models/Sinbad/SinbadOldAnim.j3o"; - Node sinbad = (Node) assetManager.loadModel(sinbadPath); - - Geometry geometry = (Geometry) sinbad.getChild(0); - Mesh mesh = geometry.getMesh(); - VertexBuffer.Type bufferType = VertexBuffer.Type.BoneIndex; - VertexBuffer vertexBuffer = mesh.getBuffer(bufferType); - - // Remove the existing bone-index buffer. - mesh.getBufferList().remove(vertexBuffer); - mesh.getBuffers().remove(bufferType.ordinal()); - - // Copy the 8-bit bone indices to 16-bit indices. - ByteBuffer oldBuffer = (ByteBuffer) vertexBuffer.getDataReadOnly(); - int numComponents = oldBuffer.limit(); - oldBuffer.rewind(); - short[] shortArray = new short[numComponents]; - for (int index = 0; oldBuffer.hasRemaining(); ++index) { - shortArray[index] = oldBuffer.get(); - } - - // Add the 16-bit bone indices to the mesh. - mesh.setBuffer(bufferType, 4, shortArray); - - KinematicRagdollControl ragdoll = new KinematicRagdollControl(0.5f); - sinbad.addControl(ragdoll); - - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1029.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue1029.java deleted file mode 100644 index fa3546f61b..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1029.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) 2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.Application; -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.PhysicsCollisionEvent; -import com.jme3.bullet.collision.PhysicsCollisionListener; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsGhostObject; -import com.jme3.bullet.objects.PhysicsRigidBody; - -/** - * Test case for JME issue #1029: sphere-sphere collisions not reported. - *

                - * If successful, the app will terminate normally, without a RuntimeException. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestIssue1029 - extends SimpleApplication - implements PhysicsCollisionListener { - - private double elapsedSeconds = 0.0; - - public static void main(String[] arguments) { - Application application = new TestIssue1029(); - application.start(); - } - - @Override - public void simpleInitApp() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - bulletAppState.setDebugEnabled(true); - - PhysicsSpace physicsSpace = bulletAppState.getPhysicsSpace(); - physicsSpace.addCollisionListener(this); - - CollisionShape shape; - shape = new SphereCollisionShape(1f); - //shape = new BoxCollisionShape(new Vector3f(1f, 1f, 1f)); - - PhysicsRigidBody staticBody = new PhysicsRigidBody(shape, 0f); - physicsSpace.add(staticBody); - - PhysicsGhostObject ghost = new PhysicsGhostObject(shape); - physicsSpace.add(ghost); - } - - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - - elapsedSeconds += tpf; - if (elapsedSeconds > 1.0) { - throw new RuntimeException("No collisions reported!"); - } - } - - @Override - public void collision(PhysicsCollisionEvent event) { - Class aClass = event.getObjectA().getCollisionShape().getClass(); - String aShape = aClass.getSimpleName().replace("CollisionShape", ""); - Class bClass = event.getObjectB().getCollisionShape().getClass(); - String bShape = bClass.getSimpleName().replace("CollisionShape", ""); - - System.out.printf("%s-%s collision reported at t = %f sec%n", - aShape, bShape, elapsedSeconds); - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1120.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue1120.java index e4a14344ad..d374ae5645 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue1120.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIssue1120.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,18 +65,15 @@ */ public class TestIssue1120 extends SimpleApplication { - private static TestIssue1120 test; private BulletAppState bulletAppState; private final boolean physicsDebug = true; - protected BitmapFont font; - private final BitmapText[] testInfo = new BitmapText[2]; private BitmapText speedText; private final List testObjects = new ArrayList<>(); private static final boolean SKIP_SETTINGS = false;//Used for repeated runs of this test during dev private float bulletSpeed = 0.5f; public static void main(String[] args) { - test = new TestIssue1120(); + TestIssue1120 test = new TestIssue1120(); test.setSettings(new AppSettings(true)); test.settings.setFrameRate(60); if (SKIP_SETTINGS) { @@ -133,17 +130,18 @@ public void simpleInitApp() { }, "pause", "restart", "+", "-"); guiNode = getGuiNode(); - font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText[] testInfo = new BitmapText[2]; testInfo[0] = new BitmapText(font); testInfo[1] = new BitmapText(font); speedText = new BitmapText(font); float lineHeight = testInfo[0].getLineHeight(); testInfo[0].setText("Camera move: W/A/S/D/Q/Z +/-: Increase/Decrease Speed"); - testInfo[0].setLocalTranslation(5, test.settings.getHeight(), 0); + testInfo[0].setLocalTranslation(5, settings.getHeight(), 0); guiNode.attachChild(testInfo[0]); testInfo[1].setText("Left Click: Toggle pause Space: Restart test"); - testInfo[1].setLocalTranslation(5, test.settings.getHeight() - lineHeight, 0); + testInfo[1].setLocalTranslation(5, settings.getHeight() - lineHeight, 0); guiNode.attachChild(testInfo[1]); speedText.setLocalTranslation(202, lineHeight * 1, 0); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java new file mode 100644 index 0000000000..a9da19b01a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIssue2282.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.bullet; + +import com.jme3.app.SimpleApplication; +import com.jme3.bullet.PhysicsSpace; + +/** + * Test case for JME issue #2282: VerifyError while creating + * PhysicsSpace with AXIS_SWEEP_3 broadphase acceleration. + * + *

                If successful, the application will print "SUCCESS" and terminate without + * crashing. If unsuccessful, the application will terminate with a VerifyError + * and no stack trace. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestIssue2282 extends SimpleApplication { + + /** + * Main entry point for the TestIssue2282 application. + * + * @param args array of command-line arguments (unused) + */ + public static void main(String[] args) { + TestIssue2282 test = new TestIssue2282(); + test.start(); + } + + /** + * Initialize the TestIssue2282 application. + */ + @Override + public void simpleInitApp() { + new PhysicsSpace(PhysicsSpace.BroadphaseType.AXIS_SWEEP_3); + System.out.println("SUCCESS"); + stop(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue877.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue877.java index d17d3407e3..aecdc43a6a 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue877.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIssue877.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 jMonkeyEngine + * Copyright (c) 2018-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,13 +31,6 @@ */ package jme3test.bullet; -/** - * Test case for JME issue #877: multiple hinges. Based on code submitted by - * Daniel Martensson. - * - * If successful, all pendulums will swing at the same frequency, and all the - * free-falling objects will fall straight down. - */ import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.collision.shapes.BoxCollisionShape; @@ -48,15 +41,22 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Node; +/** + * Test case for JME issue #877: multiple hinges. Based on code submitted by + * Daniel Martensson. + * + * If successful, all pendulums will swing at the same frequency, and all the + * free-falling objects will fall straight down. + */ public class TestIssue877 extends SimpleApplication { - BulletAppState bulletAppState = new BulletAppState(); - int numPendulums = 6; - int numFalling = 6; - Node pivots[] = new Node[numPendulums]; - Node bobs[] = new Node[numPendulums]; - Node falling[] = new Node[numFalling]; - float timeToNextPrint = 1f; // in seconds + final private BulletAppState bulletAppState = new BulletAppState(); + final private int numPendulums = 6; + final private int numFalling = 6; + final private Node pivots[] = new Node[numPendulums]; + final private Node bobs[] = new Node[numPendulums]; + final private Node falling[] = new Node[numFalling]; + private float timeToNextPrint = 1f; // in seconds public static void main(String[] args) { TestIssue877 app = new TestIssue877(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue883.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue883.java index f4e794feab..f0e4aec889 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue883.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestIssue883.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 jMonkeyEngine + * Copyright (c) 2018-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,21 +31,21 @@ */ package jme3test.bullet; -/** - * Test case for JME issue #883: extra physicsTicks in ThreadingType.PARALLEL. - * - * If successful, physics time and frame time will advance at the same rate. - */ import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.PhysicsSpace; +/** + * Test case for JME issue #883: extra physicsTicks in ThreadingType.PARALLEL. + * + *

                If successful, physics time and frame time will advance at the same rate. + */ public class TestIssue883 extends SimpleApplication { - boolean firstPrint = true; - float timeToNextPrint = 1f; // in seconds - double frameTime; // in seconds - double physicsTime; // in seconds + private boolean firstPrint = true; + private float timeToNextPrint = 1f; // in seconds + private double frameTime; // in seconds + private double physicsTime; // in seconds public static void main(String[] args) { TestIssue883 app = new TestIssue883(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue889.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue889.java deleted file mode 100644 index 0704233282..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue889.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -/** - * Test case for JME issue #889: disabled physics control gets added to a - * physics space. - *

                - * If successful, no debug meshes will be visible. - */ -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.PhysicsSpace; -import com.jme3.bullet.collision.shapes.BoxCollisionShape; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.BetterCharacterControl; -import com.jme3.bullet.control.GhostControl; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.math.Vector3f; - -public class TestIssue889 extends SimpleApplication { - - public static void main(String[] args) { - TestIssue889 app = new TestIssue889(); - app.start(); - } - - @Override - public void simpleInitApp() { - flyCam.setEnabled(false); - - BulletAppState bulletAppState = new BulletAppState(); - bulletAppState.setDebugEnabled(true); - bulletAppState.setSpeed(0f); - stateManager.attach(bulletAppState); - PhysicsSpace space = bulletAppState.getPhysicsSpace(); - - float radius = 1f; - CollisionShape sphere = new SphereCollisionShape(radius); - CollisionShape box = new BoxCollisionShape(Vector3f.UNIT_XYZ); - - RigidBodyControl rbc = new RigidBodyControl(box); - rbc.setEnabled(false); - rbc.setPhysicsSpace(space); - rootNode.addControl(rbc); - - BetterCharacterControl bcc = new BetterCharacterControl(radius, 4f, 1f); - bcc.setEnabled(false); - bcc.setPhysicsSpace(space); - rootNode.addControl(bcc); - - GhostControl gc = new GhostControl(sphere); - gc.setEnabled(false); - gc.setPhysicsSpace(space); - rootNode.addControl(gc); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue894.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue894.java deleted file mode 100644 index 9fa4616764..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue894.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -/** - * Test case for JME issue #894: SliderJoint.setRestitutionOrthoLin() sets wrong - * joint parameter. The bug existed in Native Bullet only. - *

                - * If successful, no exception will be thrown during initialization. - */ -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.SliderJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -public class TestIssue894 extends SimpleApplication { - - public static void main(String[] args) { - TestIssue894 app = new TestIssue894(); - app.start(); - } - - @Override - public void simpleInitApp() { - float radius = 1f; - CollisionShape sphere = new SphereCollisionShape(radius); - - PhysicsRigidBody rb1 = new PhysicsRigidBody(sphere); - PhysicsRigidBody rb2 = new PhysicsRigidBody(sphere); - rb2.setPhysicsLocation(new Vector3f(4f, 0f, 0f)); - - SliderJoint joint = new SliderJoint(rb1, rb2, - new Vector3f(2f, 0f, 0f), - new Vector3f(-2f, 0f, 0f), true); - - joint.setLowerAngLimit(-0.01f); - joint.setLowerLinLimit(-0.02f); - joint.setUpperAngLimit(0.01f); - joint.setUpperLinLimit(0.02f); - - joint.setDampingDirAng(0.03f); - joint.setDampingDirLin(0.04f); - joint.setDampingLimAng(0.05f); - joint.setDampingLimLin(0.06f); - joint.setDampingOrthoAng(0.07f); - joint.setDampingOrthoLin(0.08f); - - joint.setRestitutionDirAng(0.09f); - joint.setRestitutionDirLin(0.10f); - joint.setRestitutionLimAng(0.11f); - joint.setRestitutionLimLin(0.12f); - joint.setRestitutionOrthoAng(0.13f); - joint.setRestitutionOrthoLin(0.14f); - - joint.setSoftnessDirAng(0.15f); - joint.setSoftnessDirLin(0.16f); - joint.setSoftnessLimAng(0.17f); - joint.setSoftnessLimLin(0.18f); - joint.setSoftnessOrthoAng(0.19f); - joint.setSoftnessOrthoLin(0.20f); - - joint.setMaxAngMotorForce(0.21f); - joint.setMaxLinMotorForce(0.22f); - - joint.setTargetAngMotorVelocity(0.23f); - joint.setTargetLinMotorVelocity(0.24f); - - RuntimeException e = new RuntimeException(); - - if (joint.getLowerAngLimit() != -0.01f) { - throw new RuntimeException(); - } - if (joint.getLowerLinLimit() != -0.02f) { - throw new RuntimeException(); - } - if (joint.getUpperAngLimit() != 0.01f) { - throw new RuntimeException(); - } - if (joint.getUpperLinLimit() != 0.02f) { - throw new RuntimeException(); - } - - if (joint.getDampingDirAng() != 0.03f) { - throw new RuntimeException(); - } - if (joint.getDampingDirLin() != 0.04f) { - throw new RuntimeException(); - } - if (joint.getDampingLimAng() != 0.05f) { - throw new RuntimeException(); - } - if (joint.getDampingLimLin() != 0.06f) { - throw new RuntimeException(); - } - if (joint.getDampingOrthoAng() != 0.07f) { - throw new RuntimeException(); - } - if (joint.getDampingOrthoLin() != 0.08f) { - throw new RuntimeException(); - } - - if (joint.getRestitutionDirAng() != 0.09f) { - throw new RuntimeException(); - } - if (joint.getRestitutionDirLin() != 0.10f) { - throw new RuntimeException(); - } - if (joint.getRestitutionLimAng() != 0.11f) { - throw new RuntimeException(); - } - if (joint.getRestitutionLimLin() != 0.12f) { - throw new RuntimeException(); - } - if (joint.getRestitutionOrthoAng() != 0.13f) { - throw new RuntimeException(); - } - if (joint.getRestitutionOrthoLin() != 0.14f) { - throw new RuntimeException(); - } - - if (joint.getSoftnessDirAng() != 0.15f) { - throw new RuntimeException(); - } - if (joint.getSoftnessDirLin() != 0.16f) { - throw new RuntimeException(); - } - if (joint.getSoftnessLimAng() != 0.17f) { - throw new RuntimeException(); - } - if (joint.getSoftnessLimLin() != 0.18f) { - throw new RuntimeException(); - } - if (joint.getSoftnessOrthoAng() != 0.19f) { - throw new RuntimeException(); - } - if (joint.getSoftnessOrthoLin() != 0.20f) { - throw new RuntimeException(); - } - - if (joint.getMaxAngMotorForce() != 0.21f) { - throw new RuntimeException(); - } - if (joint.getMaxLinMotorForce() != 0.22f) { - throw new RuntimeException(); - } - - if (joint.getTargetAngMotorVelocity() != 0.23f) { - throw new RuntimeException(); - } - if (joint.getTargetLinMotorVelocity() != 0.24f) { - throw new RuntimeException(); - } - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue911.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue911.java deleted file mode 100644 index 4698add65e..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue911.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.objects.PhysicsRigidBody; - -/** - * Test case for JME issue #911: PhysicsRigidBody sleeping threshold setters - * have unexpected side effects. The bug existed in Native Bullet only. - *

                - * If successful, no exception will be thrown. - */ -public class TestIssue911 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue911 app = new TestIssue911(); - app.start(); - } - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - CollisionShape capsule = new SphereCollisionShape(1f); - PhysicsRigidBody body = new PhysicsRigidBody(capsule, 1f); - assert body.getAngularSleepingThreshold() == 1f; - assert body.getLinearSleepingThreshold() == 0.8f; - - body.setAngularSleepingThreshold(0.03f); - - assert body.getAngularSleepingThreshold() == 0.03f; - float lst = body.getLinearSleepingThreshold(); - assert lst == 0.8f : lst; // fails, actual value is 1f - - body.setLinearSleepingThreshold(0.17f); - - float ast = body.getAngularSleepingThreshold(); - assert ast == 0.03f : ast; // fails, actual value is 1f - - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue918.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue918.java deleted file mode 100644 index ed5a726646..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue918.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.Point2PointJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * Test case for JME issue #918: Point2PointJoint.getImpulseClamp() and - * .getTau() return the damping value instead. The bug existed in Native Bullet - * only. - *

                - * If successful, no UnsatisfiedLinkError exception will be thrown. - */ -public class TestIssue918 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue918 app = new TestIssue918(); - app.start(); - } - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - CollisionShape capsule = new SphereCollisionShape(1f); - PhysicsRigidBody body1 = new PhysicsRigidBody(capsule, 1f); - PhysicsRigidBody body2 = new PhysicsRigidBody(capsule, 1f); - Vector3f pivot1 = new Vector3f(); - Vector3f pivot2 = new Vector3f(); - Point2PointJoint joint - = new Point2PointJoint(body1, body2, pivot1, pivot2); - - joint.setImpulseClamp(42f); - joint.setTau(99f); - - if (joint.getImpulseClamp() != 42f) { - throw new RuntimeException(); - } - if (joint.getTau() != 99f) { - throw new RuntimeException(); - } - - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue919.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue919.java deleted file mode 100644 index 3fb6578e0b..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue919.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.joints.SixDofJoint; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.math.Vector3f; - -/** - * Test case for JME issue #919: native implementation of - * TranslationalLimitMotor.getLimitSoftness() has wrong name. The bug existed in - * Native Bullet only. - *

                - * If successful, no UnsatisfiedLinkError exception will be thrown. - */ -public class TestIssue919 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue919 app = new TestIssue919(); - app.start(); - } - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - CollisionShape capsule = new SphereCollisionShape(1f); - PhysicsRigidBody body1 = new PhysicsRigidBody(capsule, 1f); - PhysicsRigidBody body2 = new PhysicsRigidBody(capsule, 1f); - Vector3f pivot1 = new Vector3f(); - Vector3f pivot2 = new Vector3f(); - SixDofJoint joint = new SixDofJoint(body1, body2, pivot1, pivot2, true); - - joint.getTranslationalLimitMotor().getLimitSoftness(); - - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue928.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue928.java deleted file mode 100644 index 886fb8259c..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue928.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.BulletAppState.ThreadingType; - -/** - * Test case for JME issue #928: crash after 64 attached and detached - * BulletAppStates with parallel threading. The bug existed in Native Bullet - * only. - *

                - * If successful, no crash will occur. - */ -public class TestIssue928 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue928 app = new TestIssue928(); - app.start(); - } - - int count = 0; - int frame = 0; - BulletAppState bulletAppState; - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - } - - @Override - public void simpleUpdate(float tpf) { - if (frame % 4 == 0) { - System.out.println(++count); - bulletAppState = new BulletAppState(); - bulletAppState.setThreadingType(ThreadingType.PARALLEL); - stateManager.attach(bulletAppState); - } else if (frame % 4 == 2) { - stateManager.detach(bulletAppState); - } - - frame++; - if (count == 70) { - System.exit(0); - } - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue931.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue931.java deleted file mode 100644 index 63d56dd53f..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue931.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.bullet.BulletAppState; -import com.jme3.bullet.control.KinematicRagdollControl; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; - -/** - * Test case for JME issue #931: RagdollUtils can miss model meshes or use the - * non-animated ones. - *

                - * If successful, no AssertionError will be thrown. - */ -public class TestIssue931 extends SimpleApplication { - // ************************************************************************* - // new methods exposed - - public static void main(String[] args) { - TestIssue931 app = new TestIssue931(); - app.start(); - } - // ************************************************************************* - // SimpleApplication methods - - @Override - public void simpleInitApp() { - BulletAppState bulletAppState = new BulletAppState(); - stateManager.attach(bulletAppState); - String sinbadPath = "Models/Sinbad/SinbadOldAnim.j3o"; - Node sinbad = (Node) assetManager.loadModel(sinbadPath); - - Node extender = new Node(); - for (Spatial child : sinbad.getChildren()) { - extender.attachChild(child); - } - sinbad.attachChild(extender); - - //Note: PhysicsRagdollControl is still a WIP, constructor will change - KinematicRagdollControl ragdoll = new KinematicRagdollControl(0.5f); - sinbad.addControl(ragdoll); - - stop(); - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestIssue970.java b/jme3-examples/src/main/java/jme3test/bullet/TestIssue970.java deleted file mode 100644 index c94d46c474..0000000000 --- a/jme3-examples/src/main/java/jme3test/bullet/TestIssue970.java +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright (c) 2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package jme3test.bullet; - -import com.jme3.app.SimpleApplication; -import com.jme3.asset.AssetNotFoundException; -import com.jme3.asset.ModelKey; -import com.jme3.asset.plugins.FileLocator; -import com.jme3.bullet.collision.shapes.CollisionShape; -import com.jme3.bullet.collision.shapes.SphereCollisionShape; -import com.jme3.bullet.control.RigidBodyControl; -import com.jme3.bullet.objects.PhysicsRigidBody; -import com.jme3.export.JmeExporter; -import com.jme3.export.binary.BinaryExporter; -import com.jme3.math.Matrix3f; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.control.Control; -import java.io.File; -import java.io.IOException; - -/** - * Test case for JME issue #970: RigidBodyControl doesn't read/write velocities. - * - * If successful, no AssertionError will be thrown. - * - * @author Stephen Gold sgold@sonic.net - */ -public class TestIssue970 extends SimpleApplication { - - private int fileIndex = 0; - - public static void main(String[] args) { - TestIssue970 app = new TestIssue970(); - app.start(); - } - - @Override - public void simpleInitApp() { - assetManager.registerLocator(".", FileLocator.class); - - CollisionShape shape = new SphereCollisionShape(1f); - RigidBodyControl rbc = new RigidBodyControl(shape, 1f); - setParameters(rbc); - verifyParameters(rbc); - RigidBodyControl rbcCopy = (RigidBodyControl) saveThenLoad(rbc); - verifyParameters(rbcCopy); - - stop(); - } - - /** - * Clone a body that implements Control by saving and then loading it. - * - * @param sgc the body/control to copy (not null, unaffected) - * @return a new body/control - */ - private PhysicsRigidBody saveThenLoad(PhysicsRigidBody body) { - Control sgc = (Control) body; - Node savedNode = new Node(); - /* - * Add the Control to the Node without altering its physics transform. - */ - Vector3f pl = body.getPhysicsLocation(null); - Matrix3f pr = body.getPhysicsRotationMatrix(null); - savedNode.addControl(sgc); - body.setPhysicsLocation(pl); - body.setPhysicsRotation(pr); - - String fileName = String.format("tmp%d.j3o", ++fileIndex); - File file = new File(fileName); - - JmeExporter exporter = BinaryExporter.getInstance(); - try { - exporter.save(savedNode, file); - } catch (IOException exception) { - assert false; - } - - ModelKey key = new ModelKey(fileName); - Spatial loadedNode = new Node(); - try { - loadedNode = assetManager.loadAsset(key); - } catch (AssetNotFoundException e) { - assert false; - } - file.delete(); - Control loadedSgc = loadedNode.getControl(0); - - return (PhysicsRigidBody) loadedSgc; - } - - private void setParameters(PhysicsRigidBody body) { - body.setAngularVelocity(new Vector3f(0.04f, 0.05f, 0.06f)); - body.setLinearVelocity(new Vector3f(0.26f, 0.27f, 0.28f)); - } - - private void verifyParameters(PhysicsRigidBody body) { - Vector3f w = body.getAngularVelocity(); - assert w.x == 0.04f : w; - assert w.y == 0.05f : w; - assert w.z == 0.06f : w; - - Vector3f v = body.getLinearVelocity(); - assert v.x == 0.26f : v; - assert v.y == 0.27f : v; - assert v.z == 0.28f : v; - } -} diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java index 84bf607a4c..f7c0a6811c 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestKinematicAddToPhysicsSpaceIssue.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ public static void main(String[] args) { TestKinematicAddToPhysicsSpaceIssue app = new TestKinematicAddToPhysicsSpaceIssue(); app.start(); } - BulletAppState bulletAppState; + private BulletAppState bulletAppState; @Override public void simpleInitApp() { @@ -82,7 +82,7 @@ public void simpleInitApp() { getPhysicsSpace().add(physicsSphere2); //making it kinematic physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false); - //Making it not kinematic again, it works properly, the rigidbody is affected by grvity. + //Making it not kinematic again, it works properly, the rigid body is affected by gravity. physicsSphere2.getControl(RigidBodyControl.class).setKinematic(false); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java index 4ba4e060be..a4db5ca759 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,7 @@ public class TestPhysicsCar extends SimpleApplication implements ActionListener private final float brakeForce = 100.0f; private float steeringValue = 0; private float accelerationValue = 0; - private Vector3f jumpForce = new Vector3f(0, 3000, 0); + final private Vector3f jumpForce = new Vector3f(0, 3000, 0); public static void main(String[] args) { TestPhysicsCar app = new TestPhysicsCar(); @@ -177,17 +177,18 @@ public void simpleUpdate(float tpf) { cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Lefts")) { if (value) { steeringValue += .5f; } else { - steeringValue += -.5f; + steeringValue -= .5f; } vehicle.steer(steeringValue); } else if (binding.equals("Rights")) { if (value) { - steeringValue += -.5f; + steeringValue -= .5f; } else { steeringValue += .5f; } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java index 3a75ad5120..f0f4d7807d 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsCharacter.java @@ -1,26 +1,33 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine All rights reserved.

                + * Copyright (c) 2009-2021 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 + * 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. + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package jme3test.bullet; @@ -49,12 +56,9 @@ public class TestPhysicsCharacter extends SimpleApplication implements ActionLis private BulletAppState bulletAppState; private CharacterControl physicsCharacter; - private Node characterNode; - private CameraNode camNode; - boolean rotate = false; - private Vector3f walkDirection = new Vector3f(0,0,0); - private Vector3f viewDirection = new Vector3f(0,0,0); - boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, + final private Vector3f walkDirection = new Vector3f(0,0,0); + final private Vector3f viewDirection = new Vector3f(0,0,0); + private boolean leftStrafe = false, rightStrafe = false, forward = false, backward = false, leftRotate = false, rightRotate = false; public static void main(String[] args) { @@ -104,7 +108,7 @@ public void simpleInitApp() { // Add a physics character to the world physicsCharacter = new CharacterControl(new CapsuleCollisionShape(0.5f, 1.8f), .1f); physicsCharacter.setPhysicsLocation(new Vector3f(0, 1, 0)); - characterNode = new Node("character node"); + Node characterNode = new Node("character node"); Spatial model = assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); model.scale(0.25f); characterNode.addControl(physicsCharacter); @@ -113,7 +117,7 @@ public void simpleInitApp() { characterNode.attachChild(model); // set forward camera node that follows the character - camNode = new CameraNode("CamNode", cam); + CameraNode camNode = new CameraNode("CamNode", cam); camNode.setControlDir(ControlDirection.SpatialToCamera); camNode.setLocalTranslation(new Vector3f(0, 1, -5)); camNode.lookAt(model.getLocalTranslation(), Vector3f.UNIT_Y); @@ -154,6 +158,7 @@ public void simpleUpdate(float tpf) { physicsCharacter.setViewDirection(viewDirection); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Strafe Left")) { if (value) { diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java index 029892ad96..12b18af865 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsHingeJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,6 +60,7 @@ private void setupKeys() { inputManager.addListener(this, "Left", "Right", "Swing"); } + @Override public void onAnalog(String binding, float value, float tpf) { if(binding.equals("Left")){ joint.enableMotor(true, 1, .1f); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java index 24665b42b7..9d3a77350e 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsRayCast.java @@ -15,11 +15,11 @@ /** * - * @author @wezrule + * @author wezrule */ public class TestPhysicsRayCast extends SimpleApplication { - private BulletAppState bulletAppState = new BulletAppState(); + final private BulletAppState bulletAppState = new BulletAppState(); public static void main(String[] args) { new TestPhysicsRayCast().start(); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java index 7d94e05709..13c5133524 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestPhysicsReadWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,7 +59,7 @@ */ public class TestPhysicsReadWrite extends SimpleApplication{ private BulletAppState bulletAppState; - private Node physicsRootNode; + public static void main(String[] args){ TestPhysicsReadWrite app = new TestPhysicsReadWrite(); app.start(); @@ -70,7 +70,7 @@ public void simpleInitApp() { bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); bulletAppState.setDebugEnabled(true); - physicsRootNode=new Node("PhysicsRootNode"); + Node physicsRootNode = new Node("PhysicsRootNode"); rootNode.attachChild(physicsRootNode); // Add a physics sphere to the world diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java b/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java index 01d0c9ade9..9146c8d70b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestQ3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,22 +55,23 @@ public class TestQ3 extends SimpleApplication implements ActionListener { private BulletAppState bulletAppState; - private Node gameLevel; private PhysicsCharacter player; - private Vector3f walkDirection = new Vector3f(); + final private Vector3f walkDirection = new Vector3f(); private static boolean useHttp = false; private boolean left=false,right=false,up=false,down=false; - public static void main(String[] args) { - File file = new File("quake3level.zip"); - if (!file.exists()) { - useHttp = true; - } + public static void main(String[] args) { TestQ3 app = new TestQ3(); app.start(); } + @Override public void simpleInitApp() { + File file = new File("quake3level.zip"); + if (!file.exists()) { + useHttp = true; + } + bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); flyCam.setMoveSpeed(100); @@ -99,7 +100,7 @@ public void simpleInitApp() { // create the geometry and attach it MaterialList matList = (MaterialList) assetManager.loadAsset("Scene.material"); OgreMeshKey key = new OgreMeshKey("main.meshxml", matList); - gameLevel = (Node) assetManager.loadAsset(key); + Node gameLevel = (Node) assetManager.loadAsset(key); gameLevel.setLocalScale(0.1f); // add a physics control, it will generate a MeshCollisionShape based on the gameLevel @@ -152,6 +153,7 @@ private void setupKeys() { inputManager.addListener(this,"Space"); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Lefts")) { diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java index 2752ce5a89..2c4d81dade 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagDoll.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,10 +49,10 @@ */ public class TestRagDoll extends SimpleApplication implements ActionListener { - private BulletAppState bulletAppState = new BulletAppState(); - private Node ragDoll = new Node(); + private BulletAppState bulletAppState; + final private Node ragDoll = new Node(); private Node shoulders; - private Vector3f upforce = new Vector3f(0, 200, 0); + final private Vector3f upForce = new Vector3f(0, 200, 0); private boolean applyForce = false; public static void main(String[] args) { @@ -131,6 +131,7 @@ private PhysicsJoint join(Node A, Node B, Vector3f connectionPoint) { return joint; } + @Override public void onAction(String string, boolean bln, float tpf) { if ("Pull ragdoll up".equals(string)) { if (bln) { @@ -145,7 +146,7 @@ public void onAction(String string, boolean bln, float tpf) { @Override public void simpleUpdate(float tpf) { if (applyForce) { - shoulders.getControl(RigidBodyControl.class).applyForce(upforce, Vector3f.ZERO); + shoulders.getControl(RigidBodyControl.class).applyForce(upForce, Vector3f.ZERO); } } } diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java index e84d3b9a3d..b457e3b15b 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestRagdollCharacter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +66,6 @@ public class TestRagdollCharacter private AnimComposer composer; private boolean forward = false, backward = false, leftRotate = false, rightRotate = false; - private DynamicAnimControl ragdoll; private Node model; private PhysicsSpace physicsSpace; @@ -173,7 +172,7 @@ public void simpleInitApp() { composer.actionSequence("SliceOnce", slice, Tweens.callMethod(this, "onSliceDone")); - ragdoll = new DynamicAnimControl(); + DynamicAnimControl ragdoll = new DynamicAnimControl(); setupSinbad(ragdoll); model.addControl(ragdoll); physicsSpace.add(ragdoll); @@ -206,15 +205,15 @@ private void initWall(float bLength, float bWidth, float bHeight) { for (int j = 0; j < 15; j++) { for (int i = 0; i < 4; i++) { Vector3f ori = new Vector3f(i * bLength * 2f + startpt, bHeight + height, -10f); - Geometry reBoxg = new Geometry("brick", brick); - reBoxg.setMaterial(mat2); - reBoxg.setLocalTranslation(ori); - //for geometry with sphere mesh the physics system automatically uses a sphere collision shape - reBoxg.addControl(new RigidBodyControl(1.5f)); - reBoxg.setShadowMode(ShadowMode.CastAndReceive); - reBoxg.getControl(RigidBodyControl.class).setFriction(0.6f); - this.rootNode.attachChild(reBoxg); - physicsSpace.add(reBoxg); + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(mat2); + brickGeometry.setLocalTranslation(ori); + // for geometry with sphere mesh the physics system automatically uses a sphere collision shape + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + brickGeometry.getControl(RigidBodyControl.class).setFriction(0.6f); + this.rootNode.attachChild(brickGeometry); + physicsSpace.add(brickGeometry); } startpt = -startpt; height += 2f * bHeight; diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java index 2086950c6a..5b51b50dad 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestSweepTest.java @@ -19,12 +19,10 @@ */ public class TestSweepTest extends SimpleApplication { - private BulletAppState bulletAppState = new BulletAppState(); - private CapsuleCollisionShape obstacleCollisionShape; + final private BulletAppState bulletAppState = new BulletAppState(); private CapsuleCollisionShape capsuleCollisionShape; private Node capsule; - private Node obstacle; - private float dist = .5f; + final private float dist = .5f; public static void main(String[] args) { new TestSweepTest().start(); @@ -32,7 +30,8 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - obstacleCollisionShape = new CapsuleCollisionShape(0.3f, 0.5f); + CapsuleCollisionShape obstacleCollisionShape + = new CapsuleCollisionShape(0.3f, 0.5f); capsuleCollisionShape = new CapsuleCollisionShape(1f, 1f); stateManager.attach(bulletAppState); @@ -44,7 +43,7 @@ public void simpleInitApp() { bulletAppState.getPhysicsSpace().add(capsule); rootNode.attachChild(capsule); - obstacle = new Node("obstacle"); + Node obstacle = new Node("obstacle"); obstacle.move(2, 0, 0); RigidBodyControl bodyControl = new RigidBodyControl(obstacleCollisionShape, 0); obstacle.addControl(bodyControl); diff --git a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java index 532052da07..61f08d25f2 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java +++ b/jme3-examples/src/main/java/jme3test/bullet/TestWalkingChar.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,7 +31,13 @@ */ package jme3test.bullet; -import com.jme3.animation.*; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.Armature; +import com.jme3.anim.ArmatureMask; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; import com.jme3.app.SimpleApplication; import com.jme3.bullet.BulletAppState; import com.jme3.bullet.PhysicsSpace; @@ -75,39 +81,34 @@ * A walking animated character followed by a 3rd person camera on a terrain with LOD. * @author normenhansen */ -public class TestWalkingChar extends SimpleApplication implements ActionListener, PhysicsCollisionListener, AnimEventListener { +public class TestWalkingChar extends SimpleApplication + implements ActionListener, PhysicsCollisionListener { private BulletAppState bulletAppState; //character - CharacterControl character; - Node model; + private CharacterControl character; + private Node model; //temp vectors - Vector3f walkDirection = new Vector3f(); - //terrain - TerrainQuad terrain; - RigidBodyControl terrainPhysicsNode; + final private Vector3f walkDirection = new Vector3f(); //Materials - Material matRock; - Material matBullet; + private Material matBullet; //animation - AnimChannel animationChannel; - AnimChannel shootingChannel; - AnimControl animationControl; - float airTime = 0; + private Action standAction; + private Action walkAction; + private AnimComposer composer; + private float airTime = 0; //camera - boolean left = false, right = false, up = false, down = false; - ChaseCamera chaseCam; + private boolean left = false, right = false, up = false, down = false; //bullet - Sphere bullet; - SphereCollisionShape bulletCollisionShape; + private Sphere bullet; + private SphereCollisionShape bulletCollisionShape; //explosion - ParticleEmitter effect; + private ParticleEmitter effect; //brick wall - Box brick; - float bLength = 0.8f; - float bWidth = 0.4f; - float bHeight = 0.4f; - FilterPostProcessor fpp; + private Box brick; + final private float bLength = 0.8f; + final private float bWidth = 0.4f; + final private float bHeight = 0.4f; public static void main(String[] args) { TestWalkingChar app = new TestWalkingChar(); @@ -178,13 +179,13 @@ private void createWall() { } private void addBrick(Vector3f ori) { - Geometry reBoxg = new Geometry("brick", brick); - reBoxg.setMaterial(matBullet); - reBoxg.setLocalTranslation(ori); - reBoxg.addControl(new RigidBodyControl(1.5f)); - reBoxg.setShadowMode(ShadowMode.CastAndReceive); - this.rootNode.attachChild(reBoxg); - this.getPhysicsSpace().add(reBoxg); + Geometry brickGeometry = new Geometry("brick", brick); + brickGeometry.setMaterial(matBullet); + brickGeometry.setLocalTranslation(ori); + brickGeometry.addControl(new RigidBodyControl(1.5f)); + brickGeometry.setShadowMode(ShadowMode.CastAndReceive); + this.rootNode.attachChild(brickGeometry); + this.getPhysicsSpace().add(brickGeometry); } private void prepareBullet() { @@ -202,7 +203,7 @@ private void prepareEffect() { float COUNT_FACTOR_F = 1f; effect = new ParticleEmitter("Flame", Type.Triangle, 32 * COUNT_FACTOR); effect.setSelectRandomImage(true); - effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + effect.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); effect.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); effect.setStartSize(1.3f); effect.setEndSize(2f); @@ -238,7 +239,7 @@ private void createSky() { } private void createTerrain() { - matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); matRock.setBoolean("useTriPlanarMapping", false); matRock.setBoolean("WardIso", true); matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); @@ -274,15 +275,17 @@ private void createTerrain() { e.printStackTrace(); } - terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList<>(); cameras.add(getCamera()); TerrainLodControl control = new TerrainLodControl(terrain, cameras); terrain.addControl(control); terrain.setMaterial(matRock); terrain.setLocalScale(new Vector3f(2, 2, 2)); - terrainPhysicsNode = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0); + RigidBodyControl terrainPhysicsNode + = new RigidBodyControl(CollisionShapeFactory.createMeshShape(terrain), 0); terrain.addControl(terrainPhysicsNode); rootNode.attachChild(terrain); getPhysicsSpace().add(terrainPhysicsNode); @@ -291,8 +294,7 @@ private void createTerrain() { private void createCharacter() { CapsuleCollisionShape capsule = new CapsuleCollisionShape(3f, 4f); character = new CharacterControl(capsule, 0.01f); - model = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); - //model.setLocalScale(0.5f); + model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); model.addControl(character); character.setPhysicsLocation(new Vector3f(-140, 40, -10)); rootNode.attachChild(model); @@ -301,17 +303,43 @@ private void createCharacter() { private void setupChaseCamera() { flyCam.setEnabled(false); - chaseCam = new ChaseCamera(cam, model, inputManager); + new ChaseCamera(cam, model, inputManager); } private void setupAnimationController() { - animationControl = model.getControl(AnimControl.class); - animationControl.addListener(this); - animationChannel = animationControl.createChannel(); - shootingChannel = animationControl.createChannel(); - shootingChannel.addBone(animationControl.getSkeleton().getBone("uparm.right")); - shootingChannel.addBone(animationControl.getSkeleton().getBone("arm.right")); - shootingChannel.addBone(animationControl.getSkeleton().getBone("hand.right")); + composer = model.getControl(AnimComposer.class); + standAction = composer.action("stand"); + walkAction = composer.action("Walk"); + /* + * Add a "shootOnce" animation action + * that performs the "Dodge" action one time only. + */ + Action dodgeAction = composer.action("Dodge"); + Tween doneTween = Tweens.callMethod(this, "onShootDone"); + composer.actionSequence("shootOnce", dodgeAction, doneTween); + /* + * Define a shooting animation layer + * that animates only the joints of the right arm. + */ + SkinningControl skinningControl + = model.getControl(SkinningControl.class); + Armature armature = skinningControl.getArmature(); + ArmatureMask shootingMask + = ArmatureMask.createMask(armature, "uparm.right"); + composer.makeLayer("shootingLayer", shootingMask); + /* + * Define a walking animation layer + * that animates all joints except those used for shooting. + */ + ArmatureMask walkingMask = new ArmatureMask(); + walkingMask.addBones(armature, "head", "spine", "spinehigh"); + walkingMask.addFromJoint(armature, "hip.left"); + walkingMask.addFromJoint(armature, "hip.right"); + walkingMask.addFromJoint(armature, "uparm.left"); + composer.makeLayer("walkingLayer", walkingMask); + + composer.setCurrentAction("stand", "shootingLayer"); + composer.setCurrentAction("stand", "walkingLayer"); } @Override @@ -338,23 +366,26 @@ public void simpleUpdate(float tpf) { } else { airTime = 0; } - if (walkDirection.length() == 0) { - if (!"stand".equals(animationChannel.getAnimationName())) { - animationChannel.setAnim("stand", 1f); + + Action action = composer.getCurrentAction("walkingLayer"); + if (walkDirection.length() == 0f) { + if (action != standAction) { + composer.setCurrentAction("stand", "walkingLayer"); } } else { character.setViewDirection(walkDirection); - if (airTime > .3f) { - if (!"stand".equals(animationChannel.getAnimationName())) { - animationChannel.setAnim("stand"); + if (airTime > 0.3f) { + if (action != standAction) { + composer.setCurrentAction("stand", "walkingLayer"); } - } else if (!"Walk".equals(animationChannel.getAnimationName())) { - animationChannel.setAnim("Walk", 0.7f); + } else if (action != walkAction) { + composer.setCurrentAction("Walk", "walkingLayer"); } } character.setWalkDirection(walkDirection); } + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("CharLeft")) { if (value) { @@ -388,20 +419,21 @@ public void onAction(String binding, boolean value, float tpf) { } private void bulletControl() { - shootingChannel.setAnim("Dodge", 0.1f); - shootingChannel.setLoopMode(LoopMode.DontLoop); - Geometry bulletg = new Geometry("bullet", bullet); - bulletg.setMaterial(matBullet); - bulletg.setShadowMode(ShadowMode.CastAndReceive); - bulletg.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5))); + composer.setCurrentAction("shootOnce", "shootingLayer"); + + Geometry bulletGeometry = new Geometry("bullet", bullet); + bulletGeometry.setMaterial(matBullet); + bulletGeometry.setShadowMode(ShadowMode.CastAndReceive); + bulletGeometry.setLocalTranslation(character.getPhysicsLocation().add(cam.getDirection().mult(5))); RigidBodyControl bulletControl = new BombControl(bulletCollisionShape, 1); bulletControl.setCcdMotionThreshold(0.1f); bulletControl.setLinearVelocity(cam.getDirection().mult(80)); - bulletg.addControl(bulletControl); - rootNode.attachChild(bulletg); + bulletGeometry.addControl(bulletControl); + rootNode.attachChild(bulletGeometry); getPhysicsSpace().add(bulletControl); } + @Override public void collision(PhysicsCollisionEvent event) { if (event.getObjectA() instanceof BombControl) { final Spatial node = event.getNodeA(); @@ -416,12 +448,13 @@ public void collision(PhysicsCollisionEvent event) { } } - public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { - if (channel == shootingChannel) { - channel.setAnim("stand"); - } - } - - public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + /** + * Callback to indicate that the "shootOnce" animation action has completed. + */ + void onShootDone() { + /* + * Play the "stand" animation action on the shooting layer. + */ + composer.setCurrentAction("stand", "shootingLayer"); } } diff --git a/jme3-examples/src/main/java/jme3test/bullet/package-info.java b/jme3-examples/src/main/java/jme3test/bullet/package-info.java new file mode 100644 index 0000000000..866ed54fa7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for Bullet physics + */ +package jme3test.bullet; diff --git a/jme3-examples/src/main/java/jme3test/bullet/shape/TestGimpactShape.java b/jme3-examples/src/main/java/jme3test/bullet/shape/TestGimpactShape.java index 484f0f1774..f564790411 100644 --- a/jme3-examples/src/main/java/jme3test/bullet/shape/TestGimpactShape.java +++ b/jme3-examples/src/main/java/jme3test/bullet/shape/TestGimpactShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -80,7 +80,7 @@ * eventually, generally a larger scales for this test. * *

              • - * Some shapes such as PQTorus & signpost never go inactive at larger scales for both Native and JBullet (test + * Some shapes such as PQTorus and signpost never go inactive at larger scales for both Native and JBullet (test * at 1.5 and 1.9 scale) *
              • * @@ -92,8 +92,6 @@ public class TestGimpactShape extends SimpleApplication { private static TestGimpactShape test; private BulletAppState bulletAppState; private int solverNumIterations = 10; - private BitmapFont font; - private final BitmapText[] testInfo = new BitmapText[2]; private BitmapText timeElapsedTxt; private BitmapText solverNumIterationsTxt; private BitmapText testScale; @@ -129,7 +127,8 @@ public void simpleInitApp() { //Setup test instructions guiNode = getGuiNode(); - font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText[] testInfo = new BitmapText[2]; testInfo[0] = new BitmapText(font); testInfo[1] = new BitmapText(font); timeElapsedTxt = new BitmapText(font); diff --git a/jme3-examples/src/main/java/jme3test/bullet/shape/package-info.java b/jme3-examples/src/main/java/jme3test/bullet/shape/package-info.java new file mode 100644 index 0000000000..c1adfcb1a4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/bullet/shape/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for specific Bullet-physics collision + * shapes + */ +package jme3test.bullet.shape; diff --git a/jme3-examples/src/main/java/jme3test/collision/Main.java b/jme3-examples/src/main/java/jme3test/collision/Main.java deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/jme3-examples/src/main/java/jme3test/collision/RayTrace.java b/jme3-examples/src/main/java/jme3test/collision/RayTrace.java index d7cc5a246e..6023b6f132 100644 --- a/jme3-examples/src/main/java/jme3test/collision/RayTrace.java +++ b/jme3-examples/src/main/java/jme3test/collision/RayTrace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,11 +46,10 @@ public class RayTrace { - private BufferedImage image; - private Camera cam; - private Spatial scene; - private CollisionResults results = new CollisionResults(); - private JFrame frame; + final private BufferedImage image; + final private Camera cam; + final private Spatial scene; + final private CollisionResults results = new CollisionResults(); private JLabel label; public RayTrace(Spatial scene, Camera cam, int width, int height){ @@ -60,7 +59,7 @@ public RayTrace(Spatial scene, Camera cam, int width, int height){ } public void show(){ - frame = new JFrame("HDR View"); + JFrame frame = new JFrame("HDR View"); label = new JLabel(new ImageIcon(image)); frame.getContentPane().add(label); frame.setLayout(new FlowLayout()); diff --git a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java index 9f58b7fd86..00f25e59ef 100644 --- a/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java +++ b/jme3-examples/src/main/java/jme3test/collision/TestMousePick.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,127 +29,177 @@ * 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.collision; import com.jme3.app.SimpleApplication; import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapText; 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.Quaternion; import com.jme3.math.Ray; import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue; import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.debug.Arrow; import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Sphere; +import com.jme3.scene.shape.Torus; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.EdgeFilteringMode; +/** + * The primary purpose of TestMousePick is to illustrate how to detect intersections + * between a ray (originating from the camera's cursor position) and 3D objects in the scene. + *

                + * When an intersection occurs, a visual marker (a red arrow) + * is placed at the collision point, and the name of the + * intersected object is displayed on the HUD. + * + * @author capdevon + */ public class TestMousePick extends SimpleApplication { public static void main(String[] args) { TestMousePick app = new TestMousePick(); app.start(); } - - Node shootables; - Geometry mark; + + private BitmapText hud; + private Node shootables; + private Geometry mark; @Override public void simpleInitApp() { - flyCam.setEnabled(false); - initMark(); // a red sphere to mark the hit + hud = createLabel(10, 10, "Text"); + configureCamera(); + initMark(); + setupScene(); + setupLights(); + } + + private void configureCamera() { + flyCam.setMoveSpeed(15f); + flyCam.setDragToRotate(true); - /** create four colored boxes and a floor to shoot at: */ + cam.setLocation(Vector3f.UNIT_XYZ.mult(6)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + private void setupScene() { + /* Create four colored boxes and a floor to shoot at: */ shootables = new Node("Shootables"); + shootables.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); rootNode.attachChild(shootables); - shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f)); - shootables.attachChild(makeCube("a tin can", 1f, -2f, 0f)); - shootables.attachChild(makeCube("the Sheriff", 0f, 1f, -2f)); - shootables.attachChild(makeCube("the Deputy", 1f, 0f, -4f)); - shootables.attachChild(makeFloor()); - shootables.attachChild(makeCharacter()); + + Geometry sphere = makeShape("Sphere", new Sphere(32, 32, 1f), ColorRGBA.randomColor()); + sphere.setLocalTranslation(-2f, 0f, 1f); + shootables.attachChild(sphere); + + Geometry box = makeShape("Box", new Box(1, 1, 1), ColorRGBA.randomColor()); + box.setLocalTranslation(1f, -2f, 0f); + shootables.attachChild(box); + + Geometry cylinder = makeShape("Cylinder", new Cylinder(16, 16, 1.0f, 1.0f, true), ColorRGBA.randomColor()); + cylinder.setLocalTranslation(0f, 1f, -2f); + cylinder.rotate(90 * FastMath.DEG_TO_RAD, 0, 0); + shootables.attachChild(cylinder); + + Geometry torus = makeShape("Torus", new Torus(16, 16, 0.15f, 0.5f), ColorRGBA.randomColor()); + torus.setLocalTranslation(1f, 0f, -4f); + shootables.attachChild(torus); + + // load a character from jme3-testdata + Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + golem.scale(0.5f); + golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); + shootables.attachChild(golem); + + Geometry floor = makeShape("Floor", new Box(15, .2f, 15), ColorRGBA.Gray); + floor.setLocalTranslation(0, -4, -5); + shootables.attachChild(floor); } + private void setupLights() { + // We must add a light to make the model visible + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); + rootNode.addLight(sun); + + // init shadows + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 2048, 3); + dlsf.setLight(sun); + dlsf.setLambda(0.55f); + dlsf.setShadowIntensity(0.8f); + dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + fpp.addFilter(dlsf); + viewPort.addProcessor(fpp); + } + + private final CollisionResults results = new CollisionResults(); + private final Quaternion tempQuat = new Quaternion(); + @Override public void simpleUpdate(float tpf){ - Vector3f origin = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.0f); - Vector3f direction = cam.getWorldCoordinates(inputManager.getCursorPosition(), 0.3f); - direction.subtractLocal(origin).normalizeLocal(); - Ray ray = new Ray(origin, direction); - CollisionResults results = new CollisionResults(); + Ray ray = cam.screenPointToRay(inputManager.getCursorPosition()); + results.clear(); shootables.collideWith(ray, results); -// System.out.println("----- Collisions? " + results.size() + "-----"); -// for (int i = 0; i < results.size(); i++) { -// // For each hit, we know distance, impact point, name of geometry. -// float dist = results.getCollision(i).getDistance(); -// Vector3f pt = results.getCollision(i).getWorldContactPoint(); -// String hit = results.getCollision(i).getGeometry().getName(); -// System.out.println("* Collision #" + i); -// System.out.println(" You shot " + hit + " at " + pt + ", " + dist + " wu away."); -// } + if (results.size() > 0) { CollisionResult closest = results.getClosestCollision(); - mark.setLocalTranslation(closest.getContactPoint()); + Vector3f point = closest.getContactPoint(); + Vector3f normal = closest.getContactNormal(); - Quaternion q = new Quaternion(); - q.lookAt(closest.getContactNormal(), Vector3f.UNIT_Y); - mark.setLocalRotation(q); + tempQuat.lookAt(normal, Vector3f.UNIT_Y); + mark.setLocalRotation(tempQuat); + mark.setLocalTranslation(point); rootNode.attachChild(mark); + hud.setText(closest.getGeometry().toString()); + } else { + hud.setText("No collision"); rootNode.detachChild(mark); } } - - /** A cube object for target practice */ - protected Geometry makeCube(String name, float x, float y, float z) { - Box box = new Box(1, 1, 1); - Geometry cube = new Geometry(name, box); - cube.setLocalTranslation(x, y, z); - Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat1.setColor("Color", ColorRGBA.randomColor()); - cube.setMaterial(mat1); - return cube; + + private BitmapText createLabel(int x, int y, String text) { + BitmapText bmp = guiFont.createLabel(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; } - /** A floor to show that the "shot" can go through several objects. */ - protected Geometry makeFloor() { - Box box = new Box(15, .2f, 15); - Geometry floor = new Geometry("the Floor", box); - floor.setLocalTranslation(0, -4, -5); - Material mat1 = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat1.setColor("Color", ColorRGBA.Gray); - floor.setMaterial(mat1); - return floor; + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, Materials.LIGHTING); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Diffuse", color); + geo.setMaterial(mat); + return geo; } - /** A red ball that marks the last spot that was "hit" by the "shot". */ - protected void initMark() { + /** + * A red arrow to mark the spot being picked. + */ + private void initMark() { Arrow arrow = new Arrow(Vector3f.UNIT_Z.mult(2f)); - - //Sphere sphere = new Sphere(30, 30, 0.2f); - mark = new Geometry("BOOM!", arrow); - //mark = new Geometry("BOOM!", sphere); - Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mark_mat.getAdditionalRenderState().setLineWidth(3); - mark_mat.setColor("Color", ColorRGBA.Red); - mark.setMaterial(mark_mat); + mark = new Geometry("Marker", arrow); + Material mat = new Material(assetManager, Materials.UNSHADED); + mat.setColor("Color", ColorRGBA.Red); + mark.setMaterial(mat); } - protected Spatial makeCharacter() { - // load a character from jme3test-test-data - Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - golem.scale(0.5f); - golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); - - // We must add a light to make the model visible - DirectionalLight sun = new DirectionalLight(); - sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); - golem.addLight(sun); - return golem; - } } diff --git a/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java b/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java index 06b07e87b8..e3c1ba136b 100644 --- a/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java +++ b/jme3-examples/src/main/java/jme3test/collision/TestRayCasting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,7 +56,7 @@ public void simpleInitApp() { // flyCam.setEnabled(false); // load material - Material mat = (Material) assetManager.loadMaterial("Interface/Logo/Logo.j3m"); + Material mat = assetManager.loadMaterial("Interface/Logo/Logo.j3m"); Mesh q = new Mesh(); q.setBuffer(Type.Position, 3, new float[] diff --git a/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java index e28d7c7685..6d20ea1fc2 100644 --- a/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java +++ b/jme3-examples/src/main/java/jme3test/collision/TestTriangleCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,9 +49,9 @@ public class TestTriangleCollision extends SimpleApplication { - Geometry geom1; + private Geometry geom1; - Spatial golem; + private Spatial golem; public static void main(String[] args) { TestTriangleCollision app = new TestTriangleCollision(); @@ -69,7 +69,7 @@ public void simpleInitApp() { geom1.setMaterial(m1); rootNode.attachChild(geom1); - // load a character from jme3test-test-data + // load a character from jme3-testdata golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); golem.scale(0.5f); golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); @@ -90,8 +90,9 @@ public void simpleInitApp() { "MoveRight", "MoveLeft", "MoveUp", "MoveDown" }); } - private AnalogListener analogListener = new AnalogListener() { + final private AnalogListener analogListener = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("MoveRight")) { geom1.move(2 * tpf, 0, 0); diff --git a/jme3-examples/src/main/java/jme3test/collision/package-info.java b/jme3-examples/src/main/java/jme3test/collision/package-info.java new file mode 100644 index 0000000000..10ff40e8f6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/collision/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for picking and non-physics collision + * detection + */ +package jme3test.collision; diff --git a/jme3-examples/src/main/java/jme3test/conversion/package-info.java b/jme3-examples/src/main/java/jme3test/conversion/package-info.java new file mode 100644 index 0000000000..3a49d4c92d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/conversion/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for converting textures + */ +package jme3test.conversion; diff --git a/jme3-examples/src/main/java/jme3test/effect/TestEverything.java b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java index 4d0da65c5a..bb5ab62a1a 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestEverything.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestEverything.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,13 +48,13 @@ import com.jme3.shadow.DirectionalLightShadowRenderer; import com.jme3.texture.Texture; import com.jme3.util.SkyFactory; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestEverything extends SimpleApplication { private DirectionalLightShadowRenderer dlsr; private ToneMapFilter toneMapFilter; - private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + final private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); public static void main(String[] args){ TestEverything app = new TestEverything(); @@ -122,7 +122,7 @@ public void setupLighting(){ public void setupFloor(){ Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); Box floor = new Box(50, 1f, 50); - TangentBinormalGenerator.generate(floor); + MikktspaceTangentGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); floorGeom.setMaterial(mat); diff --git a/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java b/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java index ff5100ffed..7f6a32bc81 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestExplosionEffect.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ public class TestExplosionEffect extends SimpleApplication { private float time = 0; private int state = 0; - private Node explosionEffect = new Node("explosionFX"); + final private Node explosionEffect = new Node("explosionFX"); private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris, shockwave; @@ -66,7 +66,7 @@ public static void main(String[] args){ private void createFlame(){ flame = new ParticleEmitter("Flame", EMITTER_TYPE, 32 * COUNT_FACTOR); flame.setSelectRandomImage(true); - flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (float) (1f / COUNT_FACTOR_F))); + flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, (1f / COUNT_FACTOR_F))); flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); flame.setStartSize(1.3f); flame.setEndSize(2f); @@ -89,7 +89,7 @@ private void createFlame(){ private void createFlash(){ flash = new ParticleEmitter("Flash", EMITTER_TYPE, 24 * COUNT_FACTOR); flash.setSelectRandomImage(true); - flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1f / COUNT_FACTOR_F))); + flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f / COUNT_FACTOR_F)); flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); flash.setStartSize(.1f); flash.setEndSize(3.0f); @@ -113,7 +113,7 @@ private void createFlash(){ private void createRoundSpark(){ roundspark = new ParticleEmitter("RoundSpark", EMITTER_TYPE, 20 * COUNT_FACTOR); roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 / COUNT_FACTOR_F))); - roundspark.setEndColor(new ColorRGBA(0, 0, 0, (float) (0.5f / COUNT_FACTOR_F))); + roundspark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f / COUNT_FACTOR_F)); roundspark.setStartSize(1.2f); roundspark.setEndSize(1.8f); roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 2f)); @@ -135,7 +135,7 @@ private void createRoundSpark(){ private void createSpark(){ spark = new ParticleEmitter("Spark", Type.Triangle, 30 * COUNT_FACTOR); - spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F))); + spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); spark.setStartSize(.5f); spark.setEndSize(.5f); @@ -156,7 +156,7 @@ private void createSpark(){ private void createSmokeTrail(){ smoketrail = new ParticleEmitter("SmokeTrail", Type.Triangle, 22 * COUNT_FACTOR); - smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, (float) (1.0f / COUNT_FACTOR_F))); + smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f / COUNT_FACTOR_F)); smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); smoketrail.setStartSize(.2f); smoketrail.setEndSize(1f); @@ -183,7 +183,7 @@ private void createDebris(){ debris.setSelectRandomImage(true); debris.setRandomAngle(true); debris.setRotateSpeed(FastMath.TWO_PI * 4); - debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, (float) (1.0f / COUNT_FACTOR_F))); + debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f / COUNT_FACTOR_F)); debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f)); debris.setStartSize(.2f); debris.setEndSize(.2f); @@ -208,7 +208,7 @@ private void createShockwave(){ shockwave = new ParticleEmitter("Shockwave", Type.Triangle, 1 * COUNT_FACTOR); // shockwave.setRandomAngle(true); shockwave.setFaceNormal(Vector3f.UNIT_Y); - shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, (float) (.8f / COUNT_FACTOR_F))); + shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f / COUNT_FACTOR_F)); shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f)); shockwave.setStartSize(0f); 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/effect/TestMovingParticle.java b/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java index b38eed97a2..7aee885075 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestMovingParticle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -74,6 +74,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if ("setNum".equals(name) && isPressed) { emit.setNumParticles(1000); diff --git a/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java b/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java index 1a3e89092b..8543eba0e1 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestPointSprite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -75,6 +75,7 @@ public void simpleInitApp() { rootNode.attachChild(emit); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if ("setNum".equals(name) && isPressed) { emit.setNumParticles(5000); diff --git a/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java b/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java index 84189e1e9f..1a50a02a6c 100644 --- a/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java +++ b/jme3-examples/src/main/java/jme3test/effect/TestSoftParticles.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,6 @@ public class TestSoftParticles extends SimpleApplication { private boolean softParticles = true; private FilterPostProcessor fpp; - private TranslucentBucketFilter tbf; private Node particleNode; public static void main(String[] args) { @@ -93,7 +92,7 @@ public void simpleInitApp() { geom2.setLocalScale(0.1f, 0.2f, 1); fpp = new FilterPostProcessor(assetManager); - tbf = new TranslucentBucketFilter(true); + TranslucentBucketFilter tbf = new TranslucentBucketFilter(true); fpp.addFilter(tbf); int samples = context.getSettings().getSamples(); if (samples > 0) { @@ -109,6 +108,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if(isPressed && name.equals("toggle")){ // tbf.setEnabled(!tbf.isEnabled()); @@ -125,6 +125,7 @@ public void onAction(String name, boolean isPressed, float tpf) { // emit again inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if(isPressed && name.equals("refire")) { //fpp.removeFilter(tbf); // <-- add back in to fix diff --git a/jme3-examples/src/main/java/jme3test/effect/package-info.java b/jme3-examples/src/main/java/jme3test/effect/package-info.java new file mode 100644 index 0000000000..09f2903c48 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/effect/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for special effects, including particles + */ +package jme3test.effect; diff --git a/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java b/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java index 0710a6843e..42e291da25 100644 --- a/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java +++ b/jme3-examples/src/main/java/jme3test/export/TestAssetLinkNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,6 @@ import com.jme3.export.binary.BinaryImporter; import com.jme3.light.DirectionalLight; import com.jme3.light.PointLight; -import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; @@ -56,9 +55,9 @@ public class TestAssetLinkNode extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; + private float angle; + private PointLight pl; + private Spatial lightMdl; public static void main(String[] args){ TestAssetLinkNode app = new TestAssetLinkNode(); @@ -78,7 +77,7 @@ public void simpleInitApp() { //export to byte array ByteArrayOutputStream bout=new ByteArrayOutputStream(); BinaryExporter.getInstance().save(loaderNode, bout); - //import from byte array, automatically loads the monkeyhead from file + //import from byte array, automatically loads the monkey head from file ByteArrayInputStream bin=new ByteArrayInputStream(bout.toByteArray()); BinaryImporter imp=BinaryImporter.getInstance(); imp.setAssetManager(assetManager); @@ -93,10 +92,10 @@ public void simpleInitApp() { rootNode.attachChild(loaderNode); lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); - lightMdl.setMaterial( (Material) assetManager.loadAsset(new MaterialKey("Common/Materials/RedColor.j3m"))); + lightMdl.setMaterial(assetManager.loadAsset(new MaterialKey("Common/Materials/RedColor.j3m"))); rootNode.attachChild(lightMdl); - // flourescent main light + // fluorescent main light pl = new PointLight(); pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); rootNode.addLight(pl); diff --git a/jme3-examples/src/main/java/jme3test/export/TestIssue2068.java b/jme3-examples/src/main/java/jme3test/export/TestIssue2068.java new file mode 100644 index 0000000000..bf0bd10412 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/export/TestIssue2068.java @@ -0,0 +1,124 @@ +/* + * 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.export; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.FileLocator; +import com.jme3.export.JmeExporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.export.xml.XMLImporter; +import com.jme3.scene.Spatial; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Test case for JME issue: #2068 exporting a Map to XML results in a + * DOMException. + * + *

                If the issue is unresolved, the application will exit prematurely with an + * uncaught exception. + * + *

                If the issue is resolved, the application will complete normally. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestIssue2068 extends SimpleApplication { + // ************************************************************************* + // constants and loggers + + /** + * message logger for this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue2068.class.getName()); + // ************************************************************************* + // new methods exposed + + /** + * Main entry point for the TestIssue2068 application. + * + * @param args array of command-line arguments (not null) + */ + public static void main(String[] args) { + TestIssue2068 app = new TestIssue2068(); + app.start(); + } + + /** + * Initialize the application. + */ + @Override + public void simpleInitApp() { + + ArrayList list = new ArrayList<>(); + list.add("list-value"); + rootNode.setUserData("list", list); + + Map map = new HashMap<>(); + map.put("map-key", "map-value"); + rootNode.setUserData("map", map); + + String[] array = new String[1]; + array[0] = "array-value"; + rootNode.setUserData("array", array); + + // export xml + String filename = "TestIssue2068.xml"; + File xmlFile = new File(filename); + JmeExporter exporter = XMLExporter.getInstance(); + try { + exporter.save(rootNode, xmlFile); + } catch (IOException exception) { + throw new IllegalStateException(exception); + } + + // import binary/xml + assetManager.registerLocator("", FileLocator.class); + assetManager.registerLoader(XMLImporter.class, "xml"); + Spatial model = assetManager.loadModel(filename); + //Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); + model.depthFirstTraversal((Spatial spatial) -> { + System.out.println("UserData for "+spatial); + for (String key : spatial.getUserDataKeys()) { + System.out.println(" "+key+": "+spatial.getUserData(key)); + } + }); + + stop(); + + } +} diff --git a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java index 57dba2fb54..2c1449f5d5 100644 --- a/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java +++ b/jme3-examples/src/main/java/jme3test/export/TestOgreConvert.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -29,53 +29,109 @@ * 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.export; import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; import com.jme3.app.SimpleApplication; import com.jme3.export.binary.BinaryExporter; -import com.jme3.export.binary.BinaryImporter; +import com.jme3.font.BitmapText; +import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; +import com.jme3.material.MatParamOverride; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import java.io.*; - +/** + * This class is a jMonkeyEngine 3 (jME3) test application designed to verify + * the import, export, and runtime behavior of 3D models, particularly those + * in or compatible with the Ogre3D format (.mesh.xml). + * It loads an Ogre model, saves and reloads it using jME3's binary exporter, + * plays an animation, and displays debugging information about its skinning + * and material parameters. + * + * @author capdevon + */ public class TestOgreConvert extends SimpleApplication { - public static void main(String[] args){ + public static void main(String[] args) { TestOgreConvert app = new TestOgreConvert(); + app.setPauseOnLostFocus(false); app.start(); } + private final StringBuilder sb = new StringBuilder(); + private int frameCount = 0; + private BitmapText bmp; + private Spatial spCopy; + private SkinningControl skinningControl; + @Override public void simpleInitApp() { - Spatial ogreModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + configureCamera(); + setupLights(); + + bmp = createLabelText(10, 20, ""); + + // Load the Ogre model (Oto.mesh.xml) from the assets + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + // Save the loaded model to jME3's binary format and then reload it. + // This tests the binary serialization/deserialization process. + spCopy = BinaryExporter.saveAndLoad(assetManager, model); + spCopy.setName("Oto-Copy"); + rootNode.attachChild(spCopy); + + AnimComposer animComposer = spCopy.getControl(AnimComposer.class); + animComposer.setCurrentAction("Walk"); + + // Get the SkinningControl from the model to inspect skinning properties + skinningControl = spCopy.getControl(SkinningControl.class); + } + + private void setupLights() { + AmbientLight al = new AmbientLight(); + rootNode.addLight(al); DirectionalLight dl = new DirectionalLight(); - dl.setColor(ColorRGBA.White); - dl.setDirection(new Vector3f(0,-1,-1).normalizeLocal()); + dl.setDirection(new Vector3f(0, -1, -1).normalizeLocal()); rootNode.addLight(dl); + } - try { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BinaryExporter exp = new BinaryExporter(); - exp.save(ogreModel, baos); + private void configureCamera() { + flyCam.setDragToRotate(true); + flyCam.setMoveSpeed(15f); - ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); - BinaryImporter imp = new BinaryImporter(); - imp.setAssetManager(assetManager); - Node ogreModelReloaded = (Node) imp.load(bais, null, null); + cam.setLocation(new Vector3f(0, 0, 20)); + } - AnimComposer composer = ogreModelReloaded.getControl(AnimComposer.class); - composer.setCurrentAction("Walk"); + @Override + public void simpleUpdate(float tpf) { + frameCount++; + if (frameCount == 10) { + frameCount = 0; - rootNode.attachChild(ogreModelReloaded); - } catch (IOException ex){ - ex.printStackTrace(); + sb.append("HW Skinning Preferred: ").append(skinningControl.isHardwareSkinningPreferred()).append("\n"); + sb.append("HW Skinning Enabled: ").append(skinningControl.isHardwareSkinningUsed()).append("\n"); + sb.append("Mesh Targets: ").append(skinningControl.getTargets().length).append("\n"); + + for (MatParamOverride mpo : spCopy.getLocalMatParamOverrides()) { + sb.append(mpo.getVarType()).append(" "); + sb.append(mpo.getName()).append(": "); + sb.append(mpo.getValue()).append("\n"); + } + + bmp.setText(sb.toString()); + sb.setLength(0); } } + + private BitmapText createLabelText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; + } } diff --git a/jme3-examples/src/main/java/jme3test/export/package-info.java b/jme3-examples/src/main/java/jme3test/export/package-info.java new file mode 100644 index 0000000000..a30a5e6ca0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/export/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for asset exporting + */ +package jme3test.export; diff --git a/jme3-examples/src/main/java/jme3test/games/CubeField.java b/jme3-examples/src/main/java/jme3test/games/CubeField.java index ec4f07f504..21d060ce0a 100644 --- a/jme3-examples/src/main/java/jme3test/games/CubeField.java +++ b/jme3-examples/src/main/java/jme3test/games/CubeField.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -65,7 +65,7 @@ public static void main(String[] args) { private BitmapFont defaultFont; private boolean START; - private int difficulty, Score, colorInt, highCap, lowCap,diffHelp; + private int difficulty, Score, colorInt, lowCap; private Node player; private Geometry fcube; private ArrayList cubeField; @@ -78,7 +78,7 @@ public static void main(String[] args) { private Material playerMaterial; private Material floorMaterial; - private float fpsRate = 1000f / 1f; + final private float fpsRate = 1000f / 1f; /** * Initializes game @@ -93,8 +93,8 @@ public void simpleInitApp() { Keys(); defaultFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - pressStart = new BitmapText(defaultFont, false); - fpsScoreText = new BitmapText(defaultFont, false); + pressStart = new BitmapText(defaultFont); + fpsScoreText = new BitmapText(defaultFont); loadText(fpsScoreText, "Current Score: 0", defaultFont, 0, 2, 0); loadText(pressStart, "PRESS ENTER", defaultFont, 0, 5, 0); @@ -113,8 +113,7 @@ private void gameReset(){ Score = 0; lowCap = 10; colorInt = 0; - highCap = 40; - difficulty = highCap; + difficulty = 40; for (Geometry cube : cubeField){ cube.removeFromParent(); @@ -134,7 +133,6 @@ private void gameReset(){ speed = lowCap / 400f; coreTime = 20.0f; coreTime2 = 10.0f; - diffHelp=lowCap; player.setLocalTranslation(0,0,0); } @@ -148,7 +146,7 @@ public void simpleUpdate(float tpf) { } /** * Forcefully takes over Camera adding functionality and placing it behind the character - * @param tpf Tickes Per Frame + * @param tpf Ticks Per Frame */ private void camTakeOver(float tpf) { cam.setLocation(player.getLocalTranslation().add(-8, 2, 0)); @@ -250,18 +248,17 @@ private void gameLost(){ * Core Game Logic */ private void gameLogic(float tpf){ - //Subtract difficulty level in accordance to speed every 10 seconds - if(timer.getTimeInSeconds()>=coreTime2){ - coreTime2=timer.getTimeInSeconds()+10; - if(difficulty<=lowCap){ - difficulty=lowCap; - } - else if(difficulty>lowCap){ - difficulty-=5; - diffHelp+=1; - } - } - + //Subtract difficulty level in accordance to speed every 10 seconds + if(timer.getTimeInSeconds()>=coreTime2){ + coreTime2=timer.getTimeInSeconds()+10; + if(difficulty<=lowCap){ + difficulty=lowCap; + } + else if(difficulty>lowCap){ + difficulty-=5; + } + } + if(speed<.1f){ speed+=.000001f*tpf*fpsRate; } @@ -277,8 +274,8 @@ else if(difficulty>lowCap){ requestClose(false); }else{ for (int i = 0; i < cubeField.size(); i++){ - - //better way to check collision + + //better way to check collision Geometry playerModel = (Geometry) player.getChild(0); Geometry cubeModel = cubeField.get(i); @@ -311,6 +308,7 @@ private void Keys() { inputManager.addListener(this, "START", "Left", "Right"); } + @Override public void onAnalog(String binding, float value, float tpf) { if (binding.equals("START") && !START){ START = true; @@ -329,73 +327,73 @@ public void onAnalog(String binding, float value, float tpf) { * Determines the colors of the player, floor, obstacle and background */ private void colorLogic() { - if (timer.getTimeInSeconds() >= coreTime){ + if (timer.getTimeInSeconds() >= coreTime){ - colorInt++; + colorInt++; coreTime = timer.getTimeInSeconds() + 20; - switch (colorInt){ - case 1: - obstacleColors.clear(); - solidBox = false; - obstacleColors.add(ColorRGBA.Green); - renderer.setBackgroundColor(ColorRGBA.Black); - playerMaterial.setColor("Color", ColorRGBA.White); - floorMaterial.setColor("Color", ColorRGBA.Black); - break; - case 2: - obstacleColors.set(0, ColorRGBA.Black); - solidBox = true; - renderer.setBackgroundColor(ColorRGBA.White); - playerMaterial.setColor("Color", ColorRGBA.Gray); + switch (colorInt){ + case 1: + obstacleColors.clear(); + solidBox = false; + obstacleColors.add(ColorRGBA.Green); + renderer.setBackgroundColor(ColorRGBA.Black); + playerMaterial.setColor("Color", ColorRGBA.White); + floorMaterial.setColor("Color", ColorRGBA.Black); + break; + case 2: + obstacleColors.set(0, ColorRGBA.Black); + solidBox = true; + renderer.setBackgroundColor(ColorRGBA.White); + playerMaterial.setColor("Color", ColorRGBA.Gray); floorMaterial.setColor("Color", ColorRGBA.LightGray); - break; - case 3: - obstacleColors.set(0, ColorRGBA.Pink); - break; - case 4: - obstacleColors.set(0, ColorRGBA.Cyan); - obstacleColors.add(ColorRGBA.Magenta); - renderer.setBackgroundColor(ColorRGBA.Gray); + break; + case 3: + obstacleColors.set(0, ColorRGBA.Pink); + break; + case 4: + obstacleColors.set(0, ColorRGBA.Cyan); + obstacleColors.add(ColorRGBA.Magenta); + renderer.setBackgroundColor(ColorRGBA.Gray); floorMaterial.setColor("Color", ColorRGBA.Gray); - playerMaterial.setColor("Color", ColorRGBA.White); - break; - case 5: - obstacleColors.remove(0); - renderer.setBackgroundColor(ColorRGBA.Pink); - solidBox = false; - playerMaterial.setColor("Color", ColorRGBA.White); - break; - case 6: - obstacleColors.set(0, ColorRGBA.White); - solidBox = true; - renderer.setBackgroundColor(ColorRGBA.Black); - playerMaterial.setColor("Color", ColorRGBA.Gray); + playerMaterial.setColor("Color", ColorRGBA.White); + break; + case 5: + obstacleColors.remove(0); + renderer.setBackgroundColor(ColorRGBA.Pink); + solidBox = false; + playerMaterial.setColor("Color", ColorRGBA.White); + break; + case 6: + obstacleColors.set(0, ColorRGBA.White); + solidBox = true; + renderer.setBackgroundColor(ColorRGBA.Black); + playerMaterial.setColor("Color", ColorRGBA.Gray); floorMaterial.setColor("Color", ColorRGBA.LightGray); - break; - case 7: - obstacleColors.set(0, ColorRGBA.Green); - renderer.setBackgroundColor(ColorRGBA.Gray); - playerMaterial.setColor("Color", ColorRGBA.Black); + break; + case 7: + obstacleColors.set(0, ColorRGBA.Green); + renderer.setBackgroundColor(ColorRGBA.Gray); + playerMaterial.setColor("Color", ColorRGBA.Black); floorMaterial.setColor("Color", ColorRGBA.Orange); - break; - case 8: - obstacleColors.set(0, ColorRGBA.Red); + break; + case 8: + obstacleColors.set(0, ColorRGBA.Red); floorMaterial.setColor("Color", ColorRGBA.Pink); - break; - case 9: - obstacleColors.set(0, ColorRGBA.Orange); - obstacleColors.add(ColorRGBA.Red); - obstacleColors.add(ColorRGBA.Yellow); - renderer.setBackgroundColor(ColorRGBA.White); - playerMaterial.setColor("Color", ColorRGBA.Red); - floorMaterial.setColor("Color", ColorRGBA.Gray); - colorInt=0; - break; - default: - break; - } + break; + case 9: + obstacleColors.set(0, ColorRGBA.Orange); + obstacleColors.add(ColorRGBA.Red); + obstacleColors.add(ColorRGBA.Yellow); + renderer.setBackgroundColor(ColorRGBA.White); + playerMaterial.setColor("Color", ColorRGBA.Red); + floorMaterial.setColor("Color", ColorRGBA.Gray); + colorInt=0; + break; + default: + break; + } } } /** diff --git a/jme3-examples/src/main/java/jme3test/games/RollingTheMonkey.java b/jme3-examples/src/main/java/jme3test/games/RollingTheMonkey.java index 47cd53daa4..fe47ad43c0 100644 --- a/jme3-examples/src/main/java/jme3test/games/RollingTheMonkey.java +++ b/jme3-examples/src/main/java/jme3test/games/RollingTheMonkey.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,7 +68,6 @@ */ public class RollingTheMonkey extends SimpleApplication implements ActionListener, PhysicsCollisionListener { - private static final String TITLE = "Rolling The Monkey"; private static final String MESSAGE = "Thanks for Playing!"; private static final String INFO_MESSAGE = "Collect all the spinning cubes!\nPress the 'R' key any time to reset!"; @@ -103,17 +102,12 @@ public static void main(String[] args) { private boolean keyBackward; private boolean keyLeft; private boolean keyRight; - - private PhysicsSpace space; - private RigidBodyControl player; private int score; private Node pickUps; - - BitmapText infoText; - BitmapText scoreText; - BitmapText messageText; + private BitmapText scoreText; + private BitmapText messageText; @Override public void simpleInitApp() { @@ -124,7 +118,7 @@ public void simpleInitApp() { // init physics BulletAppState bulletState = new BulletAppState(); stateManager.attach(bulletState); - space = bulletState.getPhysicsSpace(); + PhysicsSpace space = bulletState.getPhysicsSpace(); space.addCollisionListener(this); // create light @@ -226,8 +220,8 @@ public void simpleInitApp() { Quaternion rotation = new Quaternion(); Vector3f translation = new Vector3f(0.0f, PICKUP_SIZE * 1.5f, -PICKUP_RADIUS); int index = 0; - float ammount = FastMath.TWO_PI / PICKUP_COUNT; - for(float angle = 0; angle < FastMath.TWO_PI; angle += ammount) { + float amount = FastMath.TWO_PI / PICKUP_COUNT; + for(float angle = 0; angle < FastMath.TWO_PI; angle += amount) { Geometry pickUp = new Geometry("pickUp" + (index++), new Box(PICKUP_SIZE,PICKUP_SIZE, PICKUP_SIZE)); pickUp.setShadowMode(ShadowMode.CastAndReceive); pickUp.setMaterial(materialYellow); @@ -278,15 +272,15 @@ public void simpleInitApp() { , INPUT_MAPPING_LEFT, INPUT_MAPPING_RIGHT, INPUT_MAPPING_RESET); // init UI - infoText = new BitmapText(guiFont, false); + BitmapText infoText = new BitmapText(guiFont); infoText.setText(INFO_MESSAGE); guiNode.attachChild(infoText); - scoreText = new BitmapText(guiFont, false); + scoreText = new BitmapText(guiFont); scoreText.setText("Score: 0"); guiNode.attachChild(scoreText); - messageText = new BitmapText(guiFont, false); + messageText = new BitmapText(guiFont); messageText.setText(MESSAGE); messageText.setLocalScale(0.0f); guiNode.attachChild(messageText); @@ -327,7 +321,7 @@ public void simpleUpdate(float tpf) { if(keyRight) centralForce.addLocal(cam.getLeft().negate()); if(!Vector3f.ZERO.equals(centralForce)) { - centralForce.setY(0); // stop ball from pusing down or flying up + centralForce.setY(0); // stop ball from pushing down or flying up centralForce.normalizeLocal(); // normalize force centralForce.multLocal(PLAYER_FORCE); // scale vector to force diff --git a/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java b/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java index 080b3d00b3..04aca57e8f 100644 --- a/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java +++ b/jme3-examples/src/main/java/jme3test/games/WorldOfInception.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -142,7 +142,7 @@ private void setupKeys() { private void setupDisplay() { if (fpsText == null) { - fpsText = new BitmapText(guiFont, false); + fpsText = new BitmapText(guiFont); } fpsText.setLocalScale(0.7f, 0.7f, 0.7f); fpsText.setLocalTranslation(0, fpsText.getLineHeight(), 0); @@ -164,6 +164,7 @@ private void setupFog() { viewPort.addProcessor(fpp); } + @Override public void onAnalog(String name, float value, float tpf) { Vector3f left = rootNode.getLocalRotation().mult(Vector3f.UNIT_X.negate()); Vector3f forward = rootNode.getLocalRotation().mult(Vector3f.UNIT_Z.negate()); @@ -235,7 +236,7 @@ public void update(float tpf) { return; } //give to parent - logger.log(Level.INFO, "give to parent");; + logger.log(Level.INFO, "give to parent"); parent.takeOverChild(inParentPosition.add(playerPos.normalize())); application.getStateManager().attach(parent); currentReturnLevel = parent; @@ -243,7 +244,7 @@ public void update(float tpf) { } AppStateManager stateManager = application.getStateManager(); - // We create child positions based on the parent position hash so we + // We create child positions based on the parent position hash, so we // should in practice get the same galaxy w/o too many doubles // with each run with the same root vector. Vector3f[] vectors = getPositions(poiCount, inParentPosition.hashCode()); @@ -275,7 +276,7 @@ public void update(float tpf) { currentReturnLevel = this; return; } else if (currentActiveChild != null && currentActiveChild.getPositionInParent().equals(vector3f)) { - //TODO: doing this here causes problems when close to multiple pois + //TODO: doing this here causes problems when close to multiple POIs rootNode.getChild(i).setCullHint(Spatial.CullHint.Inherit); } } @@ -483,15 +484,15 @@ public InceptionLevel getCurrentLevel() { public String getCoordinates() { InceptionLevel cur = this; - StringBuilder strb = new StringBuilder(); - strb.insert(0, this.getPlayerPosition()); - strb.insert(0, this.getPositionInParent() + " / "); + StringBuilder stringBuilder = new StringBuilder(); + stringBuilder.insert(0, this.getPlayerPosition()); + stringBuilder.insert(0, this.getPositionInParent() + " / "); cur = cur.getParent(); while (cur != null) { - strb.insert(0, cur.getPositionInParent() + " / "); + stringBuilder.insert(0, cur.getPositionInParent() + " / "); cur = cur.getParent(); } - return strb.toString(); + return stringBuilder.toString(); } } @@ -514,7 +515,7 @@ public static Vector3f[] getPositions(int count, long seed) { * @param max * @return the mapped value */ - public static float mapValue(float x, float min, float max) { + private static float mapValue(float x, float min, float max) { return mapValue(x, 0, 1, min, max); } @@ -528,7 +529,7 @@ public static float mapValue(float x, float min, float max) { * @param max * @return the mapped value */ - public static float mapValue(float x, float inputMin, float inputMax, float min, float max) { + private static float mapValue(float x, float inputMin, float inputMax, float min, float max) { return (x - inputMin) * (max - min) / (inputMax - inputMin) + min; } } diff --git a/jme3-examples/src/main/java/jme3test/games/package-info.java b/jme3-examples/src/main/java/jme3test/games/package-info.java new file mode 100644 index 0000000000..37b6b362a4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/games/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * simple example games + */ +package jme3test.games; diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java index 11fc69abff..96bc5db0b6 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFont.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,6 @@ public class TestBitmapFont extends SimpleApplication { "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567 890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?"; private BitmapText txt; - private BitmapText txt2; private BitmapText txt3; public static void main(String[] args){ @@ -64,20 +63,20 @@ public void simpleInitApp() { inputManager.addRawInputListener(textListener); BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - txt = new BitmapText(fnt, false); + txt = new BitmapText(fnt); txt.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); txt.setSize(fnt.getPreferredSize() * 2f); txt.setText(txtB); txt.setLocalTranslation(0, txt.getHeight(), 0); guiNode.attachChild(txt); - txt2 = new BitmapText(fnt, false); + BitmapText txt2 = new BitmapText(fnt); txt2.setSize(fnt.getPreferredSize() * 1.2f); txt2.setText("Text without restriction. \nText without restriction. Text without restriction. Text without restriction"); txt2.setLocalTranslation(0, txt2.getHeight(), 0); guiNode.attachChild(txt2); - txt3 = new BitmapText(fnt, false); + txt3 = new BitmapText(fnt); txt3.setBox(new Rectangle(0, 0, settings.getWidth(), 0)); txt3.setText("Press Tab to toggle word-wrap. type text and enter to input text"); txt3.setLocalTranslation(0, settings.getHeight()/2, 0); @@ -94,8 +93,8 @@ public void onAction(String name, boolean isPressed, float tpf) { } }; - private RawInputListener textListener = new RawInputListener() { - private StringBuilder str = new StringBuilder(); + final private RawInputListener textListener = new RawInputListener() { + final private StringBuilder str = new StringBuilder(); @Override public void onMouseMotionEvent(MouseMotionEvent evt) { } diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java index d2ac0e8bec..6d2ad6f37b 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapFontLayout.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 20018 jMonkeyEngine + * Copyright (c) 2018-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -84,10 +84,10 @@ public class TestBitmapFontLayout extends SimpleApplication { public static final float ZOOM_SPEED = 0.1f; public static final float SCROLL_SPEED = 50; - private Node testRoot = new Node("test root"); - private Node scrollRoot = new Node("scroll root"); - private Vector3f scroll = new Vector3f(0, 0, 0); - private Vector3f zoom = new Vector3f(0, 0, 0); + final private Node testRoot = new Node("test root"); + final private Node scrollRoot = new Node("scroll root"); + final private Vector3f scroll = new Vector3f(0, 0, 0); + final private Vector3f zoom = new Vector3f(0, 0, 0); public static void main(String[] args){ TestBitmapFontLayout app = new TestBitmapFontLayout(); @@ -109,7 +109,7 @@ public static Font loadTtf( String resource ) { } } - protected Texture renderAwtFont( TestConfig test, int width, int height, BitmapFont bitmapFont ) { + private Texture renderAwtFont( TestConfig test, int width, int height, BitmapFont bitmapFont ) { BitmapCharacterSet charset = bitmapFont.getCharSet(); @@ -156,7 +156,7 @@ protected Texture renderAwtFont( TestConfig test, int width, int height, BitmapF return new Texture2D(jmeImage); } - protected Node createVisual( TestConfig test ) { + private Node createVisual( TestConfig test ) { Node result = new Node(test.name); // For reasons I have trouble articulating, I want the visual's 0,0,0 to be @@ -165,7 +165,7 @@ protected Node createVisual( TestConfig test ) { // JME BitmapText (currently) renders from what it thinks the top of the letter is // down. The actual bitmap text bounds may extend upwards... so we need to account // for that in any labeling we add above it. - // Thus we add and setup the main test text first. + // Thus we add and set up the main test text first. BitmapFont bitmapFont = assetManager.loadFont(test.jmeFont); BitmapCharacterSet charset = bitmapFont.getCharSet(); @@ -240,8 +240,8 @@ protected Node createVisual( TestConfig test ) { float y1 = bb.getCenter().y - bb.getYExtent(); float y2 = bb.getCenter().y + bb.getYExtent(); System.out.println("xy1:" + x1 + ", " + y1 + " xy2:" + x2 + ", " + y2); - int width = (int)Math.round(x2 - Math.min(0, x1)); - int height = (int)Math.round(y2 - Math.min(0, y1)); + int width = Math.round(x2 - Math.min(0, x1)); + int height = Math.round(y2 - Math.min(0, y1)); Texture awtText = renderAwtFont(test, width, height, bitmapFont); Quad quad = new Quad(width, height); @@ -390,7 +390,7 @@ protected void setupTestScene() { loadTtf("/jme3test/font/DroidSansMono.ttf").deriveFont(Font.BOLD | Font.ITALIC, 32f))); */ - // Setup the test root node so that y = 0 is the top of the screen + // Set up the test root node so that y = 0 is the top of the screen testRoot.setLocalTranslation(0, cam.getHeight(), 0); testRoot.attachChild(scrollRoot); guiNode.attachChild(testRoot); @@ -454,6 +454,7 @@ protected void setupUserInput() { ZOOM_IN, ZOOM_OUT); } + @Override public void simpleUpdate( float tpf ) { if( scroll.lengthSquared() != 0 ) { scrollRoot.move(scroll.mult(tpf)); @@ -465,6 +466,7 @@ public void simpleUpdate( float tpf ) { } private class KeyStateListener implements ActionListener { + @Override public void onAction(String name, boolean value, float tpf) { switch( name ) { case RESET_VIEW: diff --git a/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java b/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java index ce32e7a4d9..e895cfba75 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestBitmapText3D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ public class TestBitmapText3D extends SimpleApplication { - private String txtB = + final private String txtB = "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?"; public static void main(String[] args){ @@ -59,7 +59,7 @@ public void simpleInitApp() { rootNode.attachChild(g); BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText txt = new BitmapText(fnt, false); + BitmapText txt = new BitmapText(fnt); txt.setBox(new Rectangle(0, 0, 6, 3)); txt.setQueueBucket(Bucket.Transparent); txt.setSize( 0.5f ); diff --git a/jme3-examples/src/main/java/jme3test/gui/TestCursor.java b/jme3-examples/src/main/java/jme3test/gui/TestCursor.java index d2b10fc898..3ba14216d7 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestCursor.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestCursor.java @@ -14,7 +14,7 @@ */ public class TestCursor extends SimpleApplication { - private ArrayList cursors = new ArrayList(); + final private ArrayList cursors = new ArrayList<>(); private long sysTime; private int count = 0; @@ -68,7 +68,7 @@ public void simpleUpdate(float tpf) { } sysTime = currentTime; // 8 seconds have passed, - // tell jME3 to swith to a different cursor. + // tell jME3 to switch to a different cursor. inputManager.setMouseCursor(cursors.get(count)); } diff --git a/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java b/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java index 2e252aad33..dc61e9e2c5 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestOrtho.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { Picture p = new Picture("Picture"); p.move(0, 0, -1); // make it appear behind stats view diff --git a/jme3-examples/src/main/java/jme3test/gui/TestRtlBitmapText.java b/jme3-examples/src/main/java/jme3test/gui/TestRtlBitmapText.java index ed65c4a071..0b9ae4bab9 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestRtlBitmapText.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestRtlBitmapText.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,18 +32,17 @@ package jme3test.gui; import com.jme3.app.SimpleApplication; -import com.jme3.font.BitmapFont; -import com.jme3.font.BitmapText; -import com.jme3.font.LineWrapMode; -import com.jme3.font.Rectangle; +import com.jme3.app.StatsAppState; +import com.jme3.font.*; /** * Test case for JME issue #1158: BitmapText right to left line wrapping not work */ public class TestRtlBitmapText extends SimpleApplication { - // A right to left text. - private String text = ".text left to right test a is This"; + private String text = "This is a test right to left text."; + private BitmapFont fnt; + private BitmapText txt; public static void main(String[] args) { TestRtlBitmapText app = new TestRtlBitmapText(); @@ -52,14 +51,20 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); + float x = 400; + float y = 500; + getStateManager().detach(stateManager.getState(StatsAppState.class)); + fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); + fnt.setRightToLeft(true); + // A right to left BitmapText - BitmapText txt = new BitmapText(fnt, true); + txt = new BitmapText(fnt); txt.setBox(new Rectangle(0, 0, 150, 0)); txt.setLineWrapMode(LineWrapMode.Word); txt.setAlignment(BitmapFont.Align.Right); txt.setText(text); - txt.setLocalTranslation(cam.getWidth() / 2, cam.getHeight() / 2, 0); + + txt.setLocalTranslation(x, y, 0); guiNode.attachChild(txt); } } diff --git a/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java index 44bee2882f..e1baf0b5c7 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestSoftwareMouse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,6 +36,7 @@ import com.jme3.input.RawInputListener; import com.jme3.input.event.*; import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; import com.jme3.system.AppSettings; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; @@ -45,21 +46,24 @@ public class TestSoftwareMouse extends SimpleApplication { private Picture cursor; - private RawInputListener inputListener = new RawInputListener() { - - private float x = 0, y = 0; + final private RawInputListener inputListener = new RawInputListener() { + @Override public void beginInput() { } + @Override public void endInput() { } + @Override public void onJoyAxisEvent(JoyAxisEvent evt) { } + @Override public void onJoyButtonEvent(JoyButtonEvent evt) { } + @Override public void onMouseMotionEvent(MouseMotionEvent evt) { - x += evt.getDX(); - y += evt.getDY(); + float x = evt.getX(); + float y = evt.getY(); // Prevent mouse from leaving screen AppSettings settings = TestSoftwareMouse.this.settings; @@ -69,10 +73,13 @@ public void onMouseMotionEvent(MouseMotionEvent evt) { // adjust for hotspot cursor.setPosition(x, y - 64); } + @Override public void onMouseButtonEvent(MouseButtonEvent evt) { } + @Override public void onKeyEvent(KeyInputEvent evt) { } + @Override public void onTouchEvent(TouchEvent evt) { } }; @@ -99,6 +106,12 @@ public void simpleInitApp() { cursor.setWidth(64); cursor.setHeight(64); guiNode.attachChild(cursor); + /* + * Position the software cursor + * so that its upper-left corner is at the hotspot. + */ + Vector2f initialPosition = inputManager.getCursorPosition(); + cursor.setPosition(initialPosition.x, initialPosition.y - 64f); inputManager.addRawInputListener(inputListener); diff --git a/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java b/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java index 5a5683f683..6d9ed9768d 100644 --- a/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java +++ b/jme3-examples/src/main/java/jme3test/gui/TestZOrder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { Picture p = new Picture("Picture1"); p.move(0,0,-1); diff --git a/jme3-examples/src/main/java/jme3test/gui/package-info.java b/jme3-examples/src/main/java/jme3test/gui/package-info.java new file mode 100644 index 0000000000..33e0fc2cf7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/gui/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for native graphical user-interface + * (GUI) support + */ +package jme3test.gui; diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java index db9c700c87..5af191916d 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAnimation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,12 @@ package jme3test.helloworld; -import com.jme3.animation.*; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.tween.action.BlendSpace; +import com.jme3.anim.tween.action.LinearBlendSpace; import com.jme3.app.SimpleApplication; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -42,14 +47,13 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Node; -/** Sample 7 - how to load an OgreXML model and play an animation, - * using channels, a controller, and an AnimEventListener. */ -public class HelloAnimation extends SimpleApplication - implements AnimEventListener { +/** + * Sample 7 - Load an OgreXML model and play some of its animations. + */ +public class HelloAnimation extends SimpleApplication { - Node player; - private AnimChannel channel; - private AnimControl control; + private Action advance; + private AnimComposer control; public static void main(String[] args) { HelloAnimation app = new HelloAnimation(); @@ -61,55 +65,59 @@ public void simpleInitApp() { viewPort.setBackgroundColor(ColorRGBA.LightGray); initKeys(); - /** Add a light source so we can see the model */ + /* Add a light source so we can see the model */ DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-0.1f, -1f, -1).normalizeLocal()); rootNode.addLight(dl); - /** Load a model that contains animation */ - player = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + /* Load a model that contains animation */ + Node player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); player.setLocalScale(0.5f); rootNode.attachChild(player); - /** Create a controller and channels. */ - control = player.getControl(AnimControl.class); - control.addListener(this); - channel = control.createChannel(); - channel.setAnim("stand"); - } + /* Use the model's AnimComposer to play its "stand" animation clip. */ + control = player.getControl(AnimComposer.class); + control.setCurrentAction("stand"); + + /* Compose an animation action named "halt" + that transitions from "Walk" to "stand" in half a second. */ + BlendSpace quickBlend = new LinearBlendSpace(0f, 0.5f); + Action halt = control.actionBlended("halt", quickBlend, "stand", "Walk"); + halt.setLength(0.5); - /** Use this listener to trigger something after an animation is done. */ - public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { - if (animName.equals("Walk")) { - /** After "walk", reset to "stand". */ - channel.setAnim("stand", 0.50f); - channel.setLoopMode(LoopMode.DontLoop); - channel.setSpeed(1f); - } + /* Compose an animation action named "advance" + that walks for one cycle, then halts, then invokes onAdvanceDone(). */ + Action walk = control.action("Walk"); + Tween doneTween = Tweens.callMethod(this, "onAdvanceDone"); + advance = control.actionSequence("advance", walk, halt, doneTween); } - /** Use this listener to trigger something between two animations. */ - public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { - // unused + /** + * Callback to indicate that the "advance" animation action has completed. + */ + void onAdvanceDone() { + /* + * Play the "stand" animation action. + */ + control.setCurrentAction("stand"); } - /** Custom Keybindings: Mapping a named action to a key input. */ + /** + * Map the spacebar to the "Walk" input action, and add a listener to initiate + * the "advance" animation action each time it's pressed. + */ private void initKeys() { inputManager.addMapping("Walk", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addListener(actionListener, "Walk"); - } - /** Definining the named action that can be triggered by key inputs. */ - private ActionListener actionListener = new ActionListener() { - public void onAction(String name, boolean keyPressed, float tpf) { - if (name.equals("Walk") && !keyPressed) { - if (!channel.getAnimationName().equals("Walk")) { - /** Play the "walk" animation! */ - channel.setAnim("Walk", 0.50f); - channel.setLoopMode(LoopMode.Loop); + ActionListener handler = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (keyPressed && control.getCurrentAction() != advance) { + control.setCurrentAction("advance"); } } - } - }; + }; + inputManager.addListener(handler, "Walk"); + } } diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java index 4fb4738ace..d062ed8e98 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAssets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,13 +53,13 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - /** Load a teapot model (OBJ file from test-data) */ + /* Load a teapot model (OBJ file from jme3-testdata) */ Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); Material mat_default = new Material( assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); teapot.setMaterial(mat_default); rootNode.attachChild(teapot); - /** Create a wall (Box with material and texture from test-data) */ + /* Create a wall (Box with material and texture from jme3-testdata) */ Box box = new Box(2.5f, 2.5f, 1.0f); Spatial wall = new Geometry("Box", box ); Material mat_brick = new Material( assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); @@ -68,22 +68,22 @@ public void simpleInitApp() { wall.setLocalTranslation(2.0f,-2.5f,0.0f); rootNode.attachChild(wall); - /** Display a line of text (default font from test-data) */ + /* Display a line of text (default font from jme3-testdata) */ setDisplayStatView(false); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText helloText = new BitmapText(guiFont, false); + BitmapText helloText = new BitmapText(guiFont); helloText.setSize(guiFont.getCharSet().getRenderedSize()); helloText.setText("Hello World"); helloText.setLocalTranslation(300, helloText.getLineHeight(), 0); guiNode.attachChild(helloText); - /** Load a Ninja model (OgreXML + material + texture from test_data) */ + /* Load a Ninja model (OgreXML + material + texture from test_data) */ Spatial ninja = assetManager.loadModel("Models/Ninja/Ninja.mesh.xml"); ninja.scale(0.05f, 0.05f, 0.05f); ninja.rotate(0.0f, -3.0f, 0.0f); ninja.setLocalTranslation(0.0f, -5.0f, -2.0f); rootNode.attachChild(ninja); - /** You must add a light to make the model visible */ + /* You must add a light to make the model visible. */ DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal()); rootNode.addLight(sun); diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java index bc13e01e84..228fc53d83 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloAudio.java @@ -15,8 +15,6 @@ public class HelloAudio extends SimpleApplication { private AudioNode audio_gun; - private AudioNode audio_nature; - private Geometry player; public static void main(String[] args) { HelloAudio app = new HelloAudio(); @@ -27,15 +25,15 @@ public static void main(String[] args) { public void simpleInitApp() { flyCam.setMoveSpeed(40); - /** just a blue box floating in space */ + /* just a blue box floating in space */ Box box1 = new Box(1, 1, 1); - player = new Geometry("Player", box1); + Geometry player = new Geometry("Player", box1); Material mat1 = new Material(assetManager,"Common/MatDefs/Misc/Unshaded.j3md"); mat1.setColor("Color", ColorRGBA.Blue); player.setMaterial(mat1); rootNode.attachChild(player); - /** custom init methods, see below */ + /* custom init methods, see below */ initKeys(); initAudio(); } @@ -51,7 +49,7 @@ private void initAudio() { rootNode.attachChild(audio_gun); /* nature sound - keeps playing in a loop. */ - audio_nature = new AudioNode(assetManager, + AudioNode audio_nature = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Stream); audio_nature.setLooping(true); // activate continuous playing audio_nature.setPositional(true); @@ -67,7 +65,7 @@ private void initKeys() { } /** Defining the "Shoot" action: Play a gun sound. */ - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("Shoot") && !keyPressed) { @@ -76,7 +74,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { } }; - /** Move the listener with the a camera - for 3D audio. */ + /** Move the listener with the camera - for 3-D audio. */ @Override public void simpleUpdate(float tpf) { listener.setLocation(cam.getLocation()); diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java index 7d96f9744f..518e479b76 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,6 @@ import com.jme3.light.DirectionalLight; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; /** @@ -58,28 +57,25 @@ public class HelloCollision extends SimpleApplication implements ActionListener { - private Spatial sceneModel; - private BulletAppState bulletAppState; - private RigidBodyControl landscape; private CharacterControl player; - private Vector3f walkDirection = new Vector3f(); + final private Vector3f walkDirection = new Vector3f(); private boolean left = false, right = false, up = false, down = false; //Temporary vectors used on each frame. - //They here to avoid instanciating new vectors on each frame - private Vector3f camDir = new Vector3f(); - private Vector3f camLeft = new Vector3f(); + //They here to avoid instantiating new vectors on each frame + final private Vector3f camDir = new Vector3f(); + final private Vector3f camLeft = new Vector3f(); public static void main(String[] args) { HelloCollision app = new HelloCollision(); app.start(); } + @Override public void simpleInitApp() { - /** Set up Physics */ - bulletAppState = new BulletAppState(); + /* Set up physics */ + BulletAppState bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - //bulletAppState.getPhysicsSpace().enableDebug(assetManager); // We re-use the flyby camera for rotation, while positioning is handled by physics viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); @@ -91,20 +87,20 @@ public void simpleInitApp() { assetManager.registerLocator( "https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/town.zip", HttpZipLocator.class); - sceneModel = assetManager.loadModel("main.scene"); + Spatial sceneModel = assetManager.loadModel("main.scene"); sceneModel.setLocalScale(2f); // We set up collision detection for the scene by creating a // compound collision shape and a static RigidBodyControl with mass zero. CollisionShape sceneShape = - CollisionShapeFactory.createMeshShape((Node) sceneModel); - landscape = new RigidBodyControl(sceneShape, 0); + CollisionShapeFactory.createMeshShape(sceneModel); + RigidBodyControl landscape = new RigidBodyControl(sceneShape, 0); sceneModel.addControl(landscape); // We set up collision detection for the player by creating // a capsule collision shape and a CharacterControl. // The CharacterControl offers extra settings for - // size, stepheight, jumping, falling, and gravity. + // size, step height, jumping, falling, and gravity. // We also put the player in its starting position. CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1); player = new CharacterControl(capsuleShape, 0.05f); @@ -149,6 +145,7 @@ private void setUpKeys() { /** These are our custom actions triggered by key presses. * We do not walk yet, we just keep track of the direction the user pressed. */ + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Left")) { if (value) { left = true; } else { left = false; } diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java index c01bfb31c1..0796bd7ce8 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,15 +45,15 @@ import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; -/** Sample 5 - how to map keys and mousebuttons to actions */ +/** Sample 5 - how to map keys and mouse buttons to actions */ public class HelloInput extends SimpleApplication { public static void main(String[] args) { HelloInput app = new HelloInput(); app.start(); } - protected Geometry player; - Boolean isRunning=true; + private Geometry player; + private Boolean isRunning=true; @Override public void simpleInitApp() { @@ -68,19 +68,20 @@ public void simpleInitApp() { /** Custom Keybinding: Map named actions to inputs. */ private void initKeys() { - /** You can map one or several inputs to one named mapping. */ - inputManager.addMapping("Pause", new KeyTrigger(keyInput.KEY_P)); + /* You can map one or several inputs to one named mapping. */ + inputManager.addMapping("Pause", new KeyTrigger(KeyInput.KEY_P)); inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_J)); inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_K)); inputManager.addMapping("Rotate", new KeyTrigger(KeyInput.KEY_SPACE), // spacebar! new MouseButtonTrigger(MouseInput.BUTTON_LEFT) ); // left click! - /** Add the named mappings to the action listeners. */ + /* Add the named mappings to the action listeners. */ inputManager.addListener(actionListener,"Pause"); inputManager.addListener(analogListener,"Left", "Right", "Rotate"); } /** Use this listener for KeyDown/KeyUp events */ - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("Pause") && !keyPressed) { isRunning = !isRunning; @@ -89,7 +90,8 @@ public void onAction(String name, boolean keyPressed, float tpf) { }; /** Use this listener for continuous events */ - private AnalogListener analogListener = new AnalogListener() { + final private AnalogListener analogListener = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (isRunning) { if (name.equals("Rotate")) { diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java index f154a05cf8..5bdec52016 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloLoop.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,11 +48,11 @@ public static void main(String[] args){ app.start(); } - protected Geometry player; + private Geometry player; @Override public void simpleInitApp() { - /** this blue box is our player character */ + /* This blue box is our player character. */ Box b = new Box(1, 1, 1); player = new Geometry("blue cube", b); Material mat = new Material(assetManager, diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java index 9d93492063..a9e4b4f28a 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloMaterial.java @@ -43,7 +43,7 @@ import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; import com.jme3.texture.Texture; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; /** Sample 6 - how to give an object's surface a material and texture. * How to make objects transparent. How to make bumpy and shiny surfaces. */ @@ -57,7 +57,7 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - /** A simple textured cube -- in good MIP map quality. */ + /* A simple textured cube -- in good MIP map quality. */ Box cube1Mesh = new Box( 1f,1f,1f); Geometry cube1Geo = new Geometry("My Textured Box", cube1Mesh); cube1Geo.setLocalTranslation(new Vector3f(-3f,1.1f,0f)); @@ -67,7 +67,7 @@ public void simpleInitApp() { cube1Geo.setMaterial(cube1Mat); rootNode.attachChild(cube1Geo); - /** A translucent/transparent texture, similar to a window frame. */ + /* A translucent/transparent texture, similar to a window frame. */ Box cube2Mesh = new Box( 1f,1f,0.01f); Geometry cube2Geo = new Geometry("window frame", cube2Mesh); Material cube2Mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); @@ -77,11 +77,11 @@ public void simpleInitApp() { cube2Geo.setMaterial(cube2Mat); rootNode.attachChild(cube2Geo); - /** A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */ + /* A bumpy rock with a shiny light effect. To make bumpy objects you must create a NormalMap. */ Sphere sphereMesh = new Sphere(32,32, 2f); Geometry sphereGeo = new Geometry("Shiny rock", sphereMesh); sphereMesh.setTextureMode(Sphere.TextureMode.Projected); // better quality on spheres - TangentBinormalGenerator.generate(sphereMesh); // for lighting effect + MikktspaceTangentGenerator.generate(sphereMesh); // for lighting effect Material sphereMat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); sphereMat.setTexture("DiffuseMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond.jpg")); sphereMat.setTexture("NormalMap", assetManager.loadTexture("Textures/Terrain/Pond/Pond_normal.png")); @@ -95,7 +95,7 @@ public void simpleInitApp() { sphereGeo.rotate(1.6f, 0, 0); // Rotate it a bit rootNode.attachChild(sphereGeo); - /** Must add a light to make the lit object visible! */ + /* Must add a light to make the lit object visible! */ DirectionalLight sun = new DirectionalLight(); sun.setDirection(new Vector3f(1,0,-2).normalizeLocal()); sun.setColor(ColorRGBA.White); diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java index d70632f330..12bf60f950 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloNode.java @@ -53,7 +53,7 @@ public static void main(String[] args){ @Override public void simpleInitApp() { - /** create a blue box at coordinates (1,-1,1) */ + /* create a blue box at coordinates (1,-1,1) */ Box box1 = new Box(1,1,1); Geometry blue = new Geometry("Box", box1); blue.setLocalTranslation(new Vector3f(1,-1,1)); @@ -62,7 +62,7 @@ public void simpleInitApp() { mat1.setColor("Color", ColorRGBA.Blue); blue.setMaterial(mat1); - /** create a red box straight above the blue one at (1,3,1) */ + /* create a red box straight above the blue one at (1,3,1) */ Box box2 = new Box(1,1,1); Geometry red = new Geometry("Box", box2); red.setLocalTranslation(new Vector3f(1,3,1)); @@ -71,14 +71,14 @@ public void simpleInitApp() { mat2.setColor("Color", ColorRGBA.Red); red.setMaterial(mat2); - /** Create a pivot node at (0,0,0) and attach it to the root node */ + /* Create a pivot node at (0,0,0) and attach it to the root node */ Node pivot = new Node("pivot"); rootNode.attachChild(pivot); // put this node in the scene - /** Attach the two boxes to the *pivot* node. (And transitively to the root node.) */ + /* Attach the two boxes to the *pivot* node. (And transitively to the root node.) */ pivot.attachChild(blue); pivot.attachChild(red); - /** Rotate the pivot node: Note that both boxes have rotated! */ + /* Rotate the pivot node: Note that both boxes have rotated! */ pivot.rotate(.4f,.4f,0f); } } diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java index 0da38215da..ec566e3909 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloPhysics.java @@ -1,5 +1,5 @@ -/** - * Copyright (c) 2009-2018 jMonkeyEngine +/* + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ import com.jme3.texture.Texture.WrapMode; /** - * Example 12 - how to give objects physical properties so they bounce and fall. + * Example 12 - how to give objects physical properties, so they bounce and fall. * @author base code by double1984, updated by zathras */ public class HelloPhysics extends SimpleApplication { @@ -65,16 +65,13 @@ public static void main(String args[]) { private BulletAppState bulletAppState; /** Prepare Materials */ - Material wall_mat; - Material stone_mat; - Material floor_mat; + private Material wall_mat; + private Material stone_mat; + private Material floor_mat; - /** Prepare geometries and physical nodes for bricks and cannon balls. */ - private RigidBodyControl brick_phy; + /** Prepare geometries for bricks and cannonballs. */ private static final Box box; - private RigidBodyControl ball_phy; private static final Sphere sphere; - private RigidBodyControl floor_phy; private static final Box floor; /** dimensions used for bricks and wall */ @@ -83,27 +80,26 @@ public static void main(String args[]) { private static final float brickHeight = 0.12f; static { - /** Initialize the cannon ball geometry */ + /* Initialize the cannonball geometry */ sphere = new Sphere(32, 32, 0.4f, true, false); sphere.setTextureMode(TextureMode.Projected); - /** Initialize the brick geometry */ + /* Initialize the brick geometry */ box = new Box(brickLength, brickHeight, brickWidth); box.scaleTextureCoordinates(new Vector2f(1f, .5f)); - /** Initialize the floor geometry */ + /* Initialize the floor geometry */ floor = new Box(10f, 0.1f, 5f); floor.scaleTextureCoordinates(new Vector2f(3, 6)); } @Override public void simpleInitApp() { - /** Set up Physics Game */ + /* Set up Physics Game */ bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - //bulletAppState.getPhysicsSpace().enableDebug(assetManager); - /** Configure cam to look at scene */ + /* Configure cam to look at scene */ cam.setLocation(new Vector3f(0, 4f, 6f)); cam.lookAt(new Vector3f(2, 2, 0), Vector3f.UNIT_Y); - /** Initialize the scene, materials, inputs, and physics space */ + /* Initialize the scene, materials, inputs, and physics space */ initInputs(); initMaterials(); initWall(); @@ -119,10 +115,11 @@ private void initInputs() { } /** - * Every time the shoot action is triggered, a new cannon ball is produced. + * Every time the shoot action is triggered, a new cannonball is produced. * The ball is set up to fly from the camera position in the camera direction. */ - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("shoot") && !keyPressed) { makeCannonBall(); @@ -159,57 +156,57 @@ public void initFloor() { floor_geo.setLocalTranslation(0, -0.1f, 0); this.rootNode.attachChild(floor_geo); /* Make the floor physical with mass 0.0f! */ - floor_phy = new RigidBodyControl(0.0f); + RigidBodyControl floor_phy = new RigidBodyControl(0.0f); floor_geo.addControl(floor_phy); bulletAppState.getPhysicsSpace().add(floor_phy); } /** This loop builds a wall out of individual bricks. */ public void initWall() { - float startpt = brickLength / 4; + float startX = brickLength / 4; float height = 0; for (int j = 0; j < 15; j++) { for (int i = 0; i < 6; i++) { Vector3f vt = - new Vector3f(i * brickLength * 2 + startpt, brickHeight + height, 0); + new Vector3f(i * brickLength * 2 + startX, brickHeight + height, 0); makeBrick(vt); } - startpt = -startpt; + startX = -startX; height += 2 * brickHeight; } } - /** This method creates one individual physical brick. */ - public void makeBrick(Vector3f loc) { - /** Create a brick geometry and attach to scene graph. */ + /** Creates one physical brick. */ + private void makeBrick(Vector3f loc) { + /* Create a brick geometry and attach it to the scene graph. */ Geometry brick_geo = new Geometry("brick", box); brick_geo.setMaterial(wall_mat); rootNode.attachChild(brick_geo); - /** Position the brick geometry */ + /* Position the brick geometry. */ brick_geo.setLocalTranslation(loc); - /** Make brick physical with a mass > 0.0f. */ - brick_phy = new RigidBodyControl(2f); - /** Add physical brick to physics space. */ + /* Make brick physical with a mass > 0. */ + RigidBodyControl brick_phy = new RigidBodyControl(2f); + /* Add physical brick to physics space. */ brick_geo.addControl(brick_phy); bulletAppState.getPhysicsSpace().add(brick_phy); } - /** This method creates one individual physical cannon ball. - * By defaul, the ball is accelerated and flies + /** Creates one physical cannonball. + * By default, the ball is accelerated and flies * from the camera position in the camera direction.*/ public void makeCannonBall() { - /** Create a cannon ball geometry and attach to scene graph. */ + /* Create a cannonball geometry and attach to scene graph. */ Geometry ball_geo = new Geometry("cannon ball", sphere); ball_geo.setMaterial(stone_mat); rootNode.attachChild(ball_geo); - /** Position the cannon ball */ + /* Position the cannonball. */ ball_geo.setLocalTranslation(cam.getLocation()); - /** Make the ball physical with a mass > 0.0f */ - ball_phy = new RigidBodyControl(1f); - /** Add physical ball to physics space. */ + /* Make the ball physical with a mass > 0.0f */ + RigidBodyControl ball_phy = new RigidBodyControl(1f); + /* Add physical ball to physics space. */ ball_geo.addControl(ball_phy); bulletAppState.getPhysicsSpace().add(ball_phy); - /** Accelerate the physical ball to shoot it. */ + /* Accelerate the physical ball to shoot it. */ ball_phy.setLinearVelocity(cam.getDirection().mult(25)); } @@ -217,7 +214,7 @@ public void makeCannonBall() { protected void initCrossHairs() { setDisplayStatView(false); //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // fake crosshairs :) ch.setLocalTranslation( // center diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java index 4c74b90127..90e187a9a9 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloPicking.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -69,7 +69,7 @@ public void simpleInitApp() { initKeys(); // load custom key mappings initMark(); // a red sphere to mark the hit - /** create four colored boxes and a floor to shoot at: */ + /* Create four colored boxes and a floor to shoot at: */ shootables = new Node("Shootables"); rootNode.attachChild(shootables); shootables.attachChild(makeCube("a Dragon", -2f, 0f, 1f)); @@ -88,8 +88,9 @@ private void initKeys() { inputManager.addListener(actionListener, "Shoot"); } /** Defining the "Shoot" action: Determine what was hit and how to respond. */ - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("Shoot") && !keyPressed) { // 1. Reset results list. @@ -124,7 +125,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { }; /** A cube object for target practice */ - protected Geometry makeCube(String name, float x, float y, float z) { + private Geometry makeCube(String name, float x, float y, float z) { Box box = new Box(1, 1, 1); Geometry cube = new Geometry(name, box); cube.setLocalTranslation(x, y, z); @@ -135,7 +136,7 @@ protected Geometry makeCube(String name, float x, float y, float z) { } /** A floor to show that the "shot" can go through several objects. */ - protected Geometry makeFloor() { + private Geometry makeFloor() { Box box = new Box(15, .2f, 15); Geometry floor = new Geometry("the Floor", box); floor.setLocalTranslation(0, -4, -5); @@ -146,7 +147,7 @@ protected Geometry makeFloor() { } /** A red ball that marks the last spot that was "hit" by the "shot". */ - protected void initMark() { + private void initMark() { Sphere sphere = new Sphere(30, 30, 0.2f); mark = new Geometry("BOOM!", sphere); Material mark_mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); @@ -155,10 +156,10 @@ protected void initMark() { } /** A centred plus sign to help the player aim. */ - protected void initCrossHairs() { + private void initCrossHairs() { setDisplayStatView(false); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center @@ -166,8 +167,8 @@ protected void initCrossHairs() { guiNode.attachChild(ch); } - protected Spatial makeCharacter() { - // load a character from jme3test-test-data + private Spatial makeCharacter() { + // load a character from jme3-testdata Spatial golem = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); golem.scale(0.5f); golem.setLocalTranslation(-1.0f, -1.5f, -0.6f); diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java index 69b8caf84b..f6032faf36 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,9 +44,6 @@ public class HelloTerrain extends SimpleApplication { - private TerrainQuad terrain; - Material mat_terrain; - public static void main(String[] args) { HelloTerrain app = new HelloTerrain(); app.start(); @@ -56,42 +53,42 @@ public static void main(String[] args) { public void simpleInitApp() { flyCam.setMoveSpeed(50); - /** 1. Create terrain material and load four textures into it. */ - mat_terrain = new Material(assetManager, + /* 1. Create terrain material and load four textures into it. */ + Material mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md"); - /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ + /* 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ mat_terrain.setTexture("Alpha", assetManager.loadTexture( "Textures/Terrain/splat/alphamap.png")); - /** 1.2) Add GRASS texture into the red layer (Tex1). */ + /* 1.2) Add GRASS texture into the red layer (Tex1). */ Texture grass = assetManager.loadTexture( "Textures/Terrain/splat/grass.jpg"); grass.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex1", grass); mat_terrain.setFloat("Tex1Scale", 64f); - /** 1.3) Add DIRT texture into the green layer (Tex2) */ + /* 1.3) Add DIRT texture into the green layer (Tex2) */ Texture dirt = assetManager.loadTexture( "Textures/Terrain/splat/dirt.jpg"); dirt.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex2", dirt); mat_terrain.setFloat("Tex2Scale", 32f); - /** 1.4) Add ROAD texture into the blue layer (Tex3) */ + /* 1.4) Add ROAD texture into the blue layer (Tex3) */ Texture rock = assetManager.loadTexture( "Textures/Terrain/splat/road.jpg"); rock.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex3", rock); mat_terrain.setFloat("Tex3Scale", 128f); - /** 2.a Create a custom height map from an image */ + /* 2.a Create a custom height map from an image */ AbstractHeightMap heightmap = null; Texture heightMapImage = assetManager.loadTexture( "Textures/Terrain/splat/mountains512.png"); heightmap = new ImageBasedHeightMap(heightMapImage.getImage()); -/** 2.b Create a random height map */ +/* 2.b Create a random height map */ // HillHeightMap heightmap = null; // HillHeightMap.NORMALIZE_RANGE = 100; // try { @@ -102,7 +99,7 @@ public void simpleInitApp() { heightmap.load(); - /** 3. We have prepared material and heightmap. + /* 3. We have prepared material and heightmap. * Now we create the actual terrain: * 3.1) Create a TerrainQuad and name it "my terrain". * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65. @@ -111,15 +108,16 @@ public void simpleInitApp() { * 3.5) We supply the prepared heightmap itself. */ int patchSize = 65; - terrain = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap()); + TerrainQuad terrain + = new TerrainQuad("my terrain", patchSize, 513, heightmap.getHeightMap()); - /** 4. We give the terrain its material, position & scale it, and attach it. */ + /* 4. We give the terrain its material, position & scale it, and attach it. */ terrain.setMaterial(mat_terrain); terrain.setLocalTranslation(0, -100, 0); terrain.setLocalScale(2f, 1f, 2f); rootNode.attachChild(terrain); - /** 5. The LOD (level of detail) depends on were the camera is: */ + /* 5. The LOD (level of detail) depends on were the camera is: */ TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); control.setLodCalculator( new DistanceLodCalculator(patchSize, 2.7f) ); // patch size, and a multiplier terrain.addControl(control); diff --git a/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java index d7ea68f63d..739a93ff76 100644 --- a/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java +++ b/jme3-examples/src/main/java/jme3test/helloworld/HelloTerrainCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,16 +61,13 @@ public class HelloTerrainCollision extends SimpleApplication implements ActionListener { private BulletAppState bulletAppState; - private RigidBodyControl landscape; private CharacterControl player; - private Vector3f walkDirection = new Vector3f(); + final private Vector3f walkDirection = new Vector3f(); private boolean left = false, right = false, up = false, down = false; - private TerrainQuad terrain; - private Material mat_terrain; //Temporary vectors used on each frame. - //They here to avoid instanciating new vectors on each frame - private Vector3f camDir = new Vector3f(); - private Vector3f camLeft = new Vector3f(); + //They here to avoid instantiating new vectors on each frame + final private Vector3f camDir = new Vector3f(); + final private Vector3f camLeft = new Vector3f(); public static void main(String[] args) { HelloTerrainCollision app = new HelloTerrainCollision(); @@ -79,51 +76,50 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - /** Set up Physics */ + /* Set up physics */ bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - //bulletAppState.getPhysicsSpace().enableDebug(assetManager); flyCam.setMoveSpeed(100); setUpKeys(); - /** 1. Create terrain material and load four textures into it. */ - mat_terrain = new Material(assetManager, + /* 1. Create terrain material and load four textures into it. */ + Material mat_terrain = new Material(assetManager, "Common/MatDefs/Terrain/Terrain.j3md"); - /** 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ + /* 1.1) Add ALPHA map (for red-blue-green coded splat textures) */ mat_terrain.setTexture("Alpha", assetManager.loadTexture( "Textures/Terrain/splat/alphamap.png")); - /** 1.2) Add GRASS texture into the red layer (Tex1). */ + /* 1.2) Add GRASS texture into the red layer (Tex1). */ Texture grass = assetManager.loadTexture( "Textures/Terrain/splat/grass.jpg"); grass.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex1", grass); mat_terrain.setFloat("Tex1Scale", 64f); - /** 1.3) Add DIRT texture into the green layer (Tex2) */ + /* 1.3) Add DIRT texture into the green layer (Tex2) */ Texture dirt = assetManager.loadTexture( "Textures/Terrain/splat/dirt.jpg"); dirt.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex2", dirt); mat_terrain.setFloat("Tex2Scale", 32f); - /** 1.4) Add ROAD texture into the blue layer (Tex3) */ + /* 1.4) Add ROAD texture into the blue layer (Tex3) */ Texture rock = assetManager.loadTexture( "Textures/Terrain/splat/road.jpg"); rock.setWrap(WrapMode.Repeat); mat_terrain.setTexture("Tex3", rock); mat_terrain.setFloat("Tex3Scale", 128f); - /** 2. Create the height map */ + /* 2. Create the height map */ AbstractHeightMap heightmap = null; Texture heightMapImage = assetManager.loadTexture( "Textures/Terrain/splat/mountains512.png"); heightmap = new ImageBasedHeightMap(heightMapImage.getImage()); heightmap.load(); - /** 3. We have prepared material and heightmap. + /* 3. We have prepared material and heightmap. * Now we create the actual terrain: * 3.1) Create a TerrainQuad and name it "my terrain". * 3.2) A good value for terrain tiles is 64x64 -- so we supply 64+1=65. @@ -131,21 +127,22 @@ public void simpleInitApp() { * 3.4) As LOD step scale we supply Vector3f(1,1,1). * 3.5) We supply the prepared heightmap itself. */ - terrain = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap()); + TerrainQuad terrain + = new TerrainQuad("my terrain", 65, 513, heightmap.getHeightMap()); - /** 4. We give the terrain its material, position & scale it, and attach it. */ + /* 4. We give the terrain its material, position & scale it, and attach it. */ terrain.setMaterial(mat_terrain); terrain.setLocalTranslation(0, -100, 0); terrain.setLocalScale(2f, 1f, 2f); rootNode.attachChild(terrain); - /** 5. The LOD (level of detail) depends on were the camera is: */ - List cameras = new ArrayList(); + /* 5. The LOD (level of detail) depends on were the camera is: */ + List cameras = new ArrayList<>(); cameras.add(getCamera()); TerrainLodControl control = new TerrainLodControl(terrain, cameras); terrain.addControl(control); - /** 6. Add physics: */ + /* 6. Add physics: */ /* We set up collision detection for the scene by creating a static * RigidBodyControl with mass zero.*/ terrain.addControl(new RigidBodyControl(0)); @@ -153,7 +150,7 @@ public void simpleInitApp() { // We set up collision detection for the player by creating // a capsule collision shape and a CharacterControl. // The CharacterControl offers extra settings for - // size, stepheight, jumping, falling, and gravity. + // size, step height, jumping, falling, and gravity. // We also put the player in its starting position. CapsuleCollisionShape capsuleShape = new CapsuleCollisionShape(1.5f, 6f, 1); player = new CharacterControl(capsuleShape, 0.05f); @@ -185,6 +182,7 @@ private void setUpKeys() { /** These are our custom actions triggered by key presses. * We do not walk yet, we just keep track of the direction the user pressed. */ + @Override public void onAction(String binding, boolean value, float tpf) { if (binding.equals("Left")) { if (value) { left = true; } else { left = false; } diff --git a/jme3-examples/src/main/java/jme3test/helloworld/package-info.java b/jme3-examples/src/main/java/jme3test/helloworld/package-info.java new file mode 100644 index 0000000000..34b47f9c11 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/helloworld/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps used in the beginners tutorial + */ +package jme3test.helloworld; diff --git a/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java b/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java index c1ebfeb8f7..e4f4d8f263 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java +++ b/jme3-examples/src/main/java/jme3test/input/TestCameraNode.java @@ -1,46 +1,47 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine All rights reserved. - *

                + * Copyright (c) 2009-2021 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. - *

                + * 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. - *

                + * 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. + * 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.input; import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; import com.jme3.input.controls.*; import com.jme3.material.Material; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.CameraNode; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.control.CameraControl.ControlDirection; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.system.AppSettings; /** @@ -49,11 +50,9 @@ */ public class TestCameraNode extends SimpleApplication implements AnalogListener, ActionListener { - private Geometry teaGeom; private Node teaNode; - CameraNode camNode; - boolean rotate = false; - Vector3f direction = new Vector3f(); + private boolean rotate = false; + final private Vector3f direction = new Vector3f(); public static void main(String[] args) { TestCameraNode app = new TestCameraNode(); @@ -63,9 +62,11 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { // load a teapot model - teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + Geometry teaGeom + = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); teaGeom.setMaterial(mat); //create a node to attach the geometry and the camera node @@ -75,19 +76,20 @@ public void simpleInitApp() { // create a floor mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); - Geometry ground = new Geometry("ground", new Quad(50, 50)); - ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); - ground.setLocalTranslation(-25, -1, 25); + Geometry ground = new Geometry("ground", new RectangleMesh( + new Vector3f(-25, -1, 25), + new Vector3f(25, -1, 25), + new Vector3f(-25, -1, -25))); ground.setMaterial(mat); rootNode.attachChild(ground); //creating the camera Node - camNode = new CameraNode("CamNode", cam); - //Setting the direction to Spatial to camera, this means the camera will copy the movements of the Node + CameraNode camNode = new CameraNode("CamNode", cam); + // Set the direction to SpatialToCamera, which means the camera will copy the movements of the Node. camNode.setControlDir(ControlDirection.SpatialToCamera); //attaching the camNode to the teaNode teaNode.attachChild(camNode); - //setting the local translation of the cam node to move it away from the teanNode a bit + //setting the local translation of the cam node to move it away from the tea Node a bit camNode.setLocalTranslation(new Vector3f(-10, 0, 0)); //setting the camNode to look at the teaNode camNode.lookAt(teaNode.getLocalTranslation(), Vector3f.UNIT_Y); @@ -99,10 +101,10 @@ public void simpleInitApp() { } public void registerInput() { - inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W)); - inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S)); - inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D)); - inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A)); + inputManager.addMapping("moveForward", new KeyTrigger(KeyInput.KEY_UP), new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("moveBackward", new KeyTrigger(KeyInput.KEY_DOWN), new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("moveRight", new KeyTrigger(KeyInput.KEY_RIGHT), new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("moveLeft", new KeyTrigger(KeyInput.KEY_LEFT), new KeyTrigger(KeyInput.KEY_A)); inputManager.addMapping("toggleRotate", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); inputManager.addMapping("rotateRight", new MouseAxisTrigger(MouseInput.AXIS_X, true)); inputManager.addMapping("rotateLeft", new MouseAxisTrigger(MouseInput.AXIS_X, false)); @@ -110,6 +112,7 @@ public void registerInput() { inputManager.addListener(this, "rotateRight", "rotateLeft", "toggleRotate"); } + @Override public void onAnalog(String name, float value, float tpf) { //computing the normalized direction of the cam to move the teaNode direction.set(cam.getDirection()).normalizeLocal(); @@ -138,6 +141,7 @@ public void onAnalog(String name, float value, float tpf) { } + @Override public void onAction(String name, boolean keyPressed, float tpf) { //toggling rotation on or off if (name.equals("toggleRotate") && keyPressed) { diff --git a/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java b/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java index d933f96382..fc787458cf 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java +++ b/jme3-examples/src/main/java/jme3test/input/TestChaseCamera.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,28 +33,27 @@ import com.jme3.app.SimpleApplication; import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.AnalogListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.material.Material; -import com.jme3.math.FastMath; -import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; /** A 3rd-person chase camera orbits a target (teapot). * Follow the teapot with WASD keys, rotate by dragging the mouse. */ public class TestChaseCamera extends SimpleApplication implements AnalogListener, ActionListener { private Geometry teaGeom; - private ChaseCamera chaseCam; public static void main(String[] args) { TestChaseCamera app = new TestChaseCamera(); app.start(); } + @Override public void simpleInitApp() { // Load a teapot model teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); @@ -65,9 +64,10 @@ public void simpleInitApp() { // Load a floor model Material mat_ground = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat_ground.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); - Geometry ground = new Geometry("ground", new Quad(50, 50)); - ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); - ground.setLocalTranslation(-25, -1, 25); + Geometry ground = new Geometry("ground", new RectangleMesh( + new Vector3f(-25, -1, 25), + new Vector3f(25, -1, 25), + new Vector3f(-25, -1, -25))); ground.setMaterial(mat_ground); rootNode.attachChild(ground); @@ -75,7 +75,7 @@ public void simpleInitApp() { flyCam.setEnabled(false); // Enable a chase cam - chaseCam = new ChaseCamera(cam, teaGeom, inputManager); + ChaseCamera chaseCam = new ChaseCamera(cam, teaGeom, inputManager); //Uncomment this to invert the camera's vertical rotation Axis //chaseCam.setInvertVerticalAxis(true); @@ -94,11 +94,11 @@ public void simpleInitApp() { //chaseCam.setLookAtOffset(Vector3f.UNIT_Y.mult(3)); //Uncomment this to enable rotation when the middle mouse button is pressed (like Blender) - //WARNING : setting this trigger disable the rotation on right and left mouse button click + // WARNING: setting this trigger disables the rotation on right and left mouse button click //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE)); //Uncomment this to set multiple triggers to enable rotation of the cam - //Here spade bar and middle mouse button + // Here spacebar and middle mouse button //chaseCam.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE),new KeyTrigger(KeyInput.KEY_SPACE)); //registering inputs for target's movement @@ -107,15 +107,16 @@ public void simpleInitApp() { } public void registerInput() { - inputManager.addMapping("moveForward", new KeyTrigger(keyInput.KEY_UP), new KeyTrigger(keyInput.KEY_W)); - inputManager.addMapping("moveBackward", new KeyTrigger(keyInput.KEY_DOWN), new KeyTrigger(keyInput.KEY_S)); - inputManager.addMapping("moveRight", new KeyTrigger(keyInput.KEY_RIGHT), new KeyTrigger(keyInput.KEY_D)); - inputManager.addMapping("moveLeft", new KeyTrigger(keyInput.KEY_LEFT), new KeyTrigger(keyInput.KEY_A)); - inputManager.addMapping("displayPosition", new KeyTrigger(keyInput.KEY_P)); + inputManager.addMapping("moveForward", new KeyTrigger(KeyInput.KEY_UP), new KeyTrigger(KeyInput.KEY_W)); + inputManager.addMapping("moveBackward", new KeyTrigger(KeyInput.KEY_DOWN), new KeyTrigger(KeyInput.KEY_S)); + inputManager.addMapping("moveRight", new KeyTrigger(KeyInput.KEY_RIGHT), new KeyTrigger(KeyInput.KEY_D)); + inputManager.addMapping("moveLeft", new KeyTrigger(KeyInput.KEY_LEFT), new KeyTrigger(KeyInput.KEY_A)); + inputManager.addMapping("displayPosition", new KeyTrigger(KeyInput.KEY_P)); inputManager.addListener(this, "moveForward", "moveBackward", "moveRight", "moveLeft"); inputManager.addListener(this, "displayPosition"); } + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("moveForward")) { teaGeom.move(0, 0, -5 * tpf); @@ -133,6 +134,7 @@ public void onAnalog(String name, float value, float tpf) { } + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("displayPosition") && keyPressed) { teaGeom.move(10, 10, 10); diff --git a/jme3-examples/src/main/java/jme3test/input/TestChaseCameraAppState.java b/jme3-examples/src/main/java/jme3test/input/TestChaseCameraAppState.java index 00d6dd81a5..00dcae46aa 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestChaseCameraAppState.java +++ b/jme3-examples/src/main/java/jme3test/input/TestChaseCameraAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,6 +56,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { // Load a teapot model teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); @@ -87,7 +88,7 @@ public void simpleInitApp() { //chaseCamAS.setInvertHorizontalAxis(true); //Uncomment this to enable rotation when the middle mouse button is pressed (like Blender) - //WARNING : setting this trigger disable the rotation on right and left mouse button click + // WARNING: setting this trigger disables the rotation on right and left mouse button click //chaseCamAS.setToggleRotationTrigger(new MouseButtonTrigger(MouseInput.BUTTON_MIDDLE)); //Uncomment this to set multiple triggers to enable rotation of the cam @@ -109,6 +110,7 @@ public void registerInput() { inputManager.addListener(this, "displayPosition"); } + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("moveForward")) { teaGeom.move(0, 0, -5 * tpf); @@ -126,6 +128,7 @@ public void onAnalog(String name, float value, float tpf) { } + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("displayPosition") && keyPressed) { teaGeom.move(10, 10, 10); diff --git a/jme3-examples/src/main/java/jme3test/input/TestControls.java b/jme3-examples/src/main/java/jme3test/input/TestControls.java index 07568b2b59..9f3fa28559 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestControls.java +++ b/jme3-examples/src/main/java/jme3test/input/TestControls.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,12 +42,14 @@ public class TestControls extends SimpleApplication { - private ActionListener actionListener = new ActionListener(){ + final private ActionListener actionListener = new ActionListener(){ + @Override public void onAction(String name, boolean pressed, float tpf){ System.out.println(name + " = " + pressed); } }; - public AnalogListener analogListener = new AnalogListener() { + final private AnalogListener analogListener = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { System.out.println(name + " = " + value); } diff --git a/jme3-examples/src/main/java/jme3test/input/TestIssue1692.java b/jme3-examples/src/main/java/jme3test/input/TestIssue1692.java new file mode 100644 index 0000000000..cb63e3a70a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/TestIssue1692.java @@ -0,0 +1,134 @@ +/* + * Copyright (c) 2009-2021 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.input; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Quad; + +/** + * A test for issue https://github.com/jMonkeyEngine/jmonkeyengine/pull/1692. + * We are testing to see if disabling then re-enabling the chase camera keeps the correct flags + * set so that we can still rotate without dragging + */ +public class TestIssue1692 extends SimpleApplication implements ActionListener { + + private ChaseCamera chaseCam; + private BitmapText cameraStatus; + + public static void main(String[] args) { + TestIssue1692 app = new TestIssue1692(); + app.start(); + } + + @Override + public void simpleInitApp() { + // Load a teapot model, we will chase this with the camera + Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); + Material teapotMaterial = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); + teaGeom.setMaterial(teapotMaterial); + rootNode.attachChild(teaGeom); + + // Load a floor model + Material floorMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + floorMaterial.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); + Geometry ground = new Geometry("ground", new Quad(50, 50)); + ground.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + ground.setLocalTranslation(-25, -1, 25); + ground.setMaterial(floorMaterial); + rootNode.attachChild(ground); + + // Disable the default first-person cam! + flyCam.setEnabled(false); + + // Enable a chase cam + chaseCam = new ChaseCamera(cam, teaGeom, inputManager); + /* + * Explicitly set drag to rotate to false. + * We are testing to see if disabling then re-enabling the camera keeps the correct flags + * set so that we can still rotate without dragging. + */ + chaseCam.setDragToRotate(false); + + // Show instructions + int yTop = settings.getHeight(); + int size = guiFont.getCharSet().getRenderedSize(); + BitmapText hudText = new BitmapText(guiFont); + hudText.setSize(size); + hudText.setColor(ColorRGBA.Blue); + hudText.setText("This test is for issue 1692.\n" + + "We are testing to see if drag to rotate stays disabled" + + "after disabling and re-enabling the chase camera.\n" + + "For this test, use the SPACE key to disable and re-enable the camera."); + hudText.setLocalTranslation(0, yTop - (hudText.getLineHeight() * 3), 0); + guiNode.attachChild(hudText); + + // Show camera status + cameraStatus = new BitmapText(guiFont); + cameraStatus.setSize(size); + cameraStatus.setColor(ColorRGBA.Blue); + cameraStatus.setLocalTranslation(0, yTop - cameraStatus.getLineHeight(), 0); // position + guiNode.attachChild(cameraStatus); + + // Register inputs + registerInput(); + } + + @Override + public void simpleUpdate(float tpf) { + // Update chaseCam status + cameraStatus.setText("chaseCam " + (chaseCam.isEnabled() ? "enabled" : "disabled")); + } + + private void registerInput() { + inputManager.addMapping("toggleCamera", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "toggleCamera"); + } + + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("toggleCamera") && keyPressed) { + // Toggle chase camera + chaseCam.setEnabled(!chaseCam.isEnabled()); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java index fbc8ea1989..6ef8435d98 100644 --- a/jme3-examples/src/main/java/jme3test/input/TestJoystick.java +++ b/jme3-examples/src/main/java/jme3test/input/TestJoystick.java @@ -1,15 +1,13 @@ package jme3test.input; -import java.util.*; - import com.jme3.app.SimpleApplication; import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; import com.jme3.font.BitmapText; -import com.jme3.input.DefaultJoystickAxis; import com.jme3.input.Joystick; import com.jme3.input.JoystickAxis; import com.jme3.input.JoystickButton; +import com.jme3.input.MouseInput; import com.jme3.input.RawInputListener; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.MouseButtonTrigger; @@ -48,7 +46,9 @@ public class TestJoystick extends SimpleApplication { public static void main(String[] args){ TestJoystick app = new TestJoystick(); AppSettings settings = new AppSettings(true); + settings.setJoysticksMapper(AppSettings.JOYSTICKS_XBOX_MAPPER); settings.setUseJoysticks(true); + settings.setX11PlatformPreferred(true); app.setSettings(settings); app.start(); } @@ -87,7 +87,7 @@ public void simpleInitApp() { // add action listener for mouse click // to all easier custom mapping - inputManager.addMapping("mouseClick", new MouseButtonTrigger(mouseInput.BUTTON_LEFT)); + inputManager.addMapping("mouseClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); inputManager.addListener(new ActionListener() { @Override public void onAction(String name, boolean isPressed, float tpf) { @@ -157,14 +157,15 @@ protected void setViewedJoystick( Joystick stick ) { } } - + /** * Easier to watch for all button and axis events with a raw input listener. */ protected class JoystickEventListener implements RawInputListener { - private Map lastValues = new HashMap<>(); + final private Map lastValues = new HashMap<>(); + @Override public void onJoyAxisEvent(JoyAxisEvent evt) { Float last = lastValues.remove(evt.getAxis()); float value = evt.getValue(); @@ -185,36 +186,45 @@ public void onJoyAxisEvent(JoyAxisEvent evt) { gamepad.setAxisValue( evt.getAxis(), value ); if( value != 0 ) { lastValues.put(evt.getAxis(), value); + evt.getAxis().getJoystick().rumble(0.5f); } } + @Override public void onJoyButtonEvent(JoyButtonEvent evt) { setViewedJoystick( evt.getButton().getJoystick() ); gamepad.setButtonValue( evt.getButton(), evt.isPressed() ); + evt.getButton().getJoystick().rumble(1f); } + @Override public void beginInput() {} + @Override public void endInput() {} + @Override public void onMouseMotionEvent(MouseMotionEvent evt) {} + @Override public void onMouseButtonEvent(MouseButtonEvent evt) {} + @Override public void onKeyEvent(KeyInputEvent evt) {} + @Override public void onTouchEvent(TouchEvent evt) {} } protected class GamepadView extends Node { - float xAxis = 0; - float yAxis = 0; - float zAxis = 0; - float zRotation = 0; + private float xAxis = 0; + private float yAxis = 0; + private float zAxis = 0; + private float zRotation = 0; - float lastPovX = 0; - float lastPovY = 0; + private float lastPovX = 0; + private float lastPovY = 0; - Geometry leftStick; - Geometry rightStick; + final private Geometry leftStick; + final private Geometry rightStick; - Map buttons = new HashMap(); + final private Map buttons = new HashMap<>(); public GamepadView() { super( "gamepad" ); @@ -249,24 +259,24 @@ public GamepadView() { attachChild(rightStick); // A "standard" mapping... fits a majority of my game pads - addButton( JoystickButton.BUTTON_0, 371, 512 - 176, 42, 42 ); - addButton( JoystickButton.BUTTON_1, 407, 512 - 212, 42, 42 ); - addButton( JoystickButton.BUTTON_2, 371, 512 - 248, 42, 42 ); - addButton( JoystickButton.BUTTON_3, 334, 512 - 212, 42, 42 ); + addButton( JoystickButton.BUTTON_XBOX_Y, 371, 512 - 176, 42, 42 ); + addButton( JoystickButton.BUTTON_XBOX_B, 407, 512 - 212, 42, 42 ); + addButton( JoystickButton.BUTTON_XBOX_A, 371, 512 - 248, 42, 42 ); + addButton( JoystickButton.BUTTON_XBOX_X, 334, 512 - 212, 42, 42 ); // Front buttons Some of these have the top ones and the bottoms ones flipped. - addButton( JoystickButton.BUTTON_4, 67, 512 - 111, 95, 21 ); - addButton( JoystickButton.BUTTON_5, 348, 512 - 111, 95, 21 ); - addButton( JoystickButton.BUTTON_6, 67, 512 - 89, 95, 21 ); - addButton( JoystickButton.BUTTON_7, 348, 512 - 89, 95, 21 ); + addButton( JoystickButton.BUTTON_XBOX_LB, 67, 512 - 111, 95, 21 ); + addButton( JoystickButton.BUTTON_XBOX_RB, 348, 512 - 111, 95, 21 ); + addButton( JoystickButton.BUTTON_XBOX_LT, 67, 512 - 89, 95, 21 ); + addButton( JoystickButton.BUTTON_XBOX_RT, 348, 512 - 89, 95, 21 ); // Select and start buttons - addButton( JoystickButton.BUTTON_8, 206, 512 - 198, 48, 30 ); - addButton( JoystickButton.BUTTON_9, 262, 512 - 198, 48, 30 ); + addButton( JoystickButton.BUTTON_XBOX_BACK, 206, 512 - 198, 48, 30 ); + addButton( JoystickButton.BUTTON_XBOX_START, 262, 512 - 198, 48, 30 ); // Joystick push buttons - addButton( JoystickButton.BUTTON_10, 147, 512 - 300, 75, 70 ); - addButton( JoystickButton.BUTTON_11, 285, 512 - 300, 75, 70 ); + addButton( JoystickButton.BUTTON_XBOX_L3, 147, 512 - 300, 75, 70 ); + addButton( JoystickButton.BUTTON_XBOX_R3, 285, 512 - 300, 75, 70 ); // Fake button highlights for the POV axes // @@ -274,10 +284,14 @@ public GamepadView() { // -X +X // -Y // - addButton( "POV +Y", 96, 512 - 174, 40, 38 ); - addButton( "POV +X", 128, 512 - 208, 40, 38 ); - addButton( "POV -Y", 96, 512 - 239, 40, 38 ); - addButton( "POV -X", 65, 512 - 208, 40, 38 ); + // addButton( "POV +Y", 96, 512 - 174, 40, 38 ); + // addButton( "POV +X", 128, 512 - 208, 40, 38 ); + // addButton( "POV -Y", 96, 512 - 239, 40, 38 ); + // addButton( "POV -X", 65, 512 - 208, 40, 38 ); + addButton( JoystickButton.BUTTON_XBOX_DPAD_UP, 96, 512 - 174, 40, 38 ); + addButton( JoystickButton.BUTTON_XBOX_DPAD_RIGHT, 128, 512 - 208, 40, 38 ); + addButton( JoystickButton.BUTTON_XBOX_DPAD_DOWN, 96, 512 - 239, 40, 38 ); + addButton( JoystickButton.BUTTON_XBOX_DPAD_LEFT, 65, 512 - 208, 40, 38 ); resetPositions(); } @@ -289,22 +303,21 @@ private void addButton( String name, float x, float y, float width, float height } public void setAxisValue( JoystickAxis axis, float value ) { - - System.out.println( "Axis:" + axis.getName() + "(id:" + axis.getLogicalId() + ")=" + value ); - if( axis == axis.getJoystick().getXAxis() ) { + + if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X)){ setXAxis(value); - } else if( axis == axis.getJoystick().getYAxis() ) { + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y)){ setYAxis(-value); - } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_AXIS) ) { - // Note: in the above condition, we could check the axis name but + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X)) { + // Note: in the above condition, we could check the axis name, but // I have at least one joystick that reports 2 "Z Axis" axes. // In this particular case, the first one is the right one so // a name based lookup will find the proper one. It's a problem // because the erroneous axis sends a constant stream of values. setZAxis(value); - } else if( axis == axis.getJoystick().getAxis(JoystickAxis.Z_ROTATION) ) { + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y) ) { setZRotation(-value); - } else if( axis == axis.getJoystick().getAxis(JoystickAxis.LEFT_TRIGGER) ) { + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_LEFT_TRIGGER) ) { if( axis.getJoystick().getButton(JoystickButton.BUTTON_6) == null ) { // left/right triggers sometimes only show up as axes boolean pressed = value != 0; @@ -312,7 +325,7 @@ public void setAxisValue( JoystickAxis axis, float value ) { setButtonValue(JoystickButton.BUTTON_6, pressed); } } - } else if( axis == axis.getJoystick().getAxis(JoystickAxis.RIGHT_TRIGGER) ) { + } else if( axis == axis.getJoystick().getAxis(JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER) ) { if( axis.getJoystick().getButton(JoystickButton.BUTTON_7) == null ) { // left/right triggers sometimes only show up as axes boolean pressed = value != 0; @@ -414,8 +427,8 @@ private void resetPositions() { protected class ButtonView extends Node { private int state = 0; - private Material material; - private ColorRGBA hilite = new ColorRGBA( 0.0f, 0.75f, 0.75f, 0.5f ); + final private Material material; + final private ColorRGBA hilite = new ColorRGBA( 0.0f, 0.75f, 0.75f, 0.5f ); public ButtonView( String name, float x, float y, float width, float height ) { super( "Button:" + name ); @@ -459,11 +472,11 @@ public void up() { private void pickGamePad(Vector2f mouseLoc){ if (lastButton != null) { - CollisionResults cresults = pick(cam, mouseLoc, gamepad); - for (CollisionResult cr : cresults) { + CollisionResults results = pick(cam, mouseLoc, gamepad); + for (CollisionResult cr : results) { Node n = cr.getGeometry().getParent(); if (n != null && (n instanceof ButtonView)) { - String b = ((ButtonView) n).getName().substring("Button:".length()); + String b = n.getName().substring("Button:".length()); String name = lastButton.getJoystick().getName().replaceAll(" ", "\\\\ "); String id = lastButton.getLogicalId().replaceAll(" ", "\\\\ "); System.out.println(name + "." + id + "=" + b); diff --git a/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java index ee1a3c348a..2ad86bef36 100644 --- a/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMove.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,9 +39,9 @@ public class ComboMove { public static class ComboMoveState { - private String[] pressedMappings; - private String[] unpressedMappings; - private float timeElapsed; + final private String[] pressedMappings; + final private String[] unpressedMappings; + final private float timeElapsed; public ComboMoveState(String[] pressedMappings, String[] unpressedMappings, float timeElapsed) { this.pressedMappings = pressedMappings; @@ -63,8 +63,8 @@ public float getTimeElapsed() { } - private String moveName; - private List states = new ArrayList(); + final private String moveName; + final private List states = new ArrayList<>(); private boolean useFinalState = true; private float priority = 1; private float castTime = 0.8f; diff --git a/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java index 9b8e266ec9..1e1049ab89 100644 --- a/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/ComboMoveExecution.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,7 @@ public class ComboMoveExecution { private static final float TIME_LIMIT = 0.3f; - private ComboMove moveDef; + final private ComboMove moveDef; private int state; private float moveTime; private boolean finalState = false; diff --git a/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java b/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java index 3ca46dbd31..f5723434f0 100644 --- a/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/TestComboMoves.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ public class TestComboMoves extends SimpleApplication implements ActionListener { - private HashSet pressedMappings = new HashSet(); + final private HashSet pressedMappings = new HashSet<>(); private ComboMove fireball; private ComboMoveExecution fireballExec; @@ -168,6 +168,7 @@ public void simpleUpdate(float tpf){ } } + @Override public void onAction(String name, boolean isPressed, float tpf) { if (isPressed){ pressedMappings.add(name); @@ -176,7 +177,7 @@ public void onAction(String name, boolean isPressed, float tpf) { } // the pressed mappings was changed. update combo executions - List invokedMoves = new ArrayList(); + List invokedMoves = new ArrayList<>(); if (shurikenExec.updateState(pressedMappings, time)){ invokedMoves.add(shuriken); } @@ -194,7 +195,7 @@ public void onAction(String name, boolean isPressed, float tpf) { } if (invokedMoves.size() > 0){ - // choose move with highest priority + // Choose the move with the highest priority. float priority = 0; ComboMove toExec = null; for (ComboMove move : invokedMoves){ diff --git a/jme3-examples/src/main/java/jme3test/input/combomoves/package-info.java b/jme3-examples/src/main/java/jme3test/input/combomoves/package-info.java new file mode 100644 index 0000000000..287475f74e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/combomoves/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for "combos" (user-input sequences) + */ +package jme3test.input.combomoves; diff --git a/jme3-examples/src/main/java/jme3test/input/package-info.java b/jme3-examples/src/main/java/jme3test/input/package-info.java new file mode 100644 index 0000000000..4fa1b11446 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/input/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for user input + */ +package jme3test.input; diff --git a/jme3-examples/src/main/java/jme3test/light/DlsfError.java b/jme3-examples/src/main/java/jme3test/light/DlsfError.java deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java b/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java index 4c8ae5ff94..9181ca00b0 100644 --- a/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java +++ b/jme3-examples/src/main/java/jme3test/light/ShadowTestUIManager.java @@ -1,6 +1,33 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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; @@ -24,18 +51,18 @@ */ public class ShadowTestUIManager implements ActionListener { - private BitmapText shadowTypeText; - private BitmapText shadowCompareText; - private BitmapText shadowFilterText; - private BitmapText shadowIntensityText; + final private BitmapText shadowTypeText; + final private BitmapText shadowCompareText; + final private BitmapText shadowFilterText; + final private BitmapText shadowIntensityText; private final static String TYPE_TEXT = "(Space) Shadow type : "; private final static String COMPARE_TEXT = "(enter) Shadow compare "; private final static String FILTERING_TEXT = "(f) Edge filtering : "; private final static String INTENSITY_TEXT = "(t:up, g:down) Shadow intensity : "; private boolean hardwareShadows = true; - private AbstractShadowRenderer plsr; - private AbstractShadowFilter plsf; - private ViewPort viewPort; + final private AbstractShadowRenderer plsr; + final private AbstractShadowFilter plsf; + final private ViewPort viewPort; private int filteringIndex = 0; private int renderModeIndex = 0; @@ -80,6 +107,7 @@ public ShadowTestUIManager(AssetManager assetManager,AbstractShadowRenderer plsr } + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { renderModeIndex += 1; @@ -146,7 +174,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { } private BitmapText createText(BitmapFont guiFont) { - BitmapText t = new BitmapText(guiFont, false); + BitmapText t = new BitmapText(guiFont); t.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f); return t; } diff --git a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java index d96edf7063..0f2605839c 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java +++ b/jme3-examples/src/main/java/jme3test/light/TestConeVSFrustum.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,9 +47,9 @@ import com.jme3.math.*; import com.jme3.renderer.Camera; import com.jme3.scene.Geometry; -import com.jme3.scene.LightNode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; +import com.jme3.scene.control.LightControl; import com.jme3.scene.debug.Grid; import com.jme3.scene.debug.WireFrustum; import com.jme3.scene.shape.Box; @@ -108,8 +108,12 @@ public void simpleInitApp() { geom.getMaterial().setColor("Diffuse", ColorRGBA.White); geom.getMaterial().setColor("Ambient", ColorRGBA.DarkGray); geom.getMaterial().setBoolean("UseMaterialColors", true); - final LightNode ln = new LightNode("lb", spotLight); + + final Node ln = new Node("lb"); + LightControl lightControl = new LightControl(spotLight); + ln.addControl(lightControl); ln.attachChild(geom); + geom.setLocalTranslation(0, -spotLight.getSpotRange() / 2f, 0); geom.rotate(-FastMath.HALF_PI, 0, 0); rootNode.attachChild(ln); @@ -140,6 +144,7 @@ public void simpleInitApp() { flyCam.setEnabled(false); inputManager.addListener(new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { Spatial s = null; float mult = 1; @@ -184,6 +189,7 @@ public void onAnalog(String name, float value, float tpf) { }, "up", "down", "left", "right"); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("click")) { if (isPressed) { @@ -208,32 +214,28 @@ public void onAction(String name, boolean isPressed, float tpf) { } } }, "click", "middleClick", "shift"); - /** - * An unshaded textured cube. // * Uses texture from jme3-test-data - * library! + /* + * An unshaded textured cube. + * Uses texture from jme3-testdata library! */ Box boxMesh = new Box(1f, 1f, 1f); - boxGeo = new Geometry("A Textured Box", boxMesh); + Geometry boxGeo = new Geometry("A Textured Box", boxMesh); Material boxMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); Texture monkeyTex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); boxMat.setTexture("ColorMap", monkeyTex); boxGeo.setMaterial(boxMat); -// rootNode.attachChild(boxGeo); -// -//boxGeo2 = boxGeo.clone(); -//rootNode.attachChild(boxGeo2); System.err.println("light " + spotLight.getPosition()); } - Geometry boxGeo, boxGeo2; + private final static float MOVE_SPEED = 60; - Vector3f tmp = new Vector3f(); - Quaternion tmpQuat = new Quaternion(); - boolean moving, shift; - boolean panning; - Geometry geom; - SpotLight spotLight; - Camera frustumCam; + final private Vector3f tmp = new Vector3f(); + final private Quaternion tmpQuat = new Quaternion(); + private boolean moving, shift; + private boolean panning; + private Geometry geom; + private SpotLight spotLight; + private Camera frustumCam; @Override public void simpleUpdate(float tpf) { @@ -257,7 +259,5 @@ public void simpleUpdate(float tpf) { vars.release(); -// boxGeo.setLocalTranslation(spotLight.getPosition()); - // boxGeo.setLocalTranslation(projectedPoint); } } diff --git a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java index 3f3ac9127e..0c6f437305 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestDirectionalLightShadow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,13 +58,11 @@ import com.jme3.texture.Texture.WrapMode; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestDirectionalLightShadow extends SimpleApplication implements ActionListener, AnalogListener { public static final int SHADOWMAP_SIZE = 1024; - private Spatial[] obj; - private Material[] mat; private DirectionalLightShadowRenderer dlsr; private DirectionalLightShadowFilter dlsf; private Geometry ground; @@ -78,6 +76,7 @@ public static void main(String[] args) { } private float frustumSize = 100; + @Override public void onAnalog(String name, float value, float tpf) { if (cam.isParallelProjection()) { // Instead of moving closer/farther to object, we zoom in/out. @@ -93,11 +92,11 @@ public void onAnalog(String name, float value, float tpf) { } public void loadScene() { - obj = new Spatial[2]; + Spatial[] obj = new Spatial[2]; // Setup first view - mat = new Material[2]; + Material[] mat = new Material[2]; mat[0] = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); mat[1] = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); mat[1].setBoolean("UseMaterialColors", true); @@ -109,8 +108,8 @@ public void loadScene() { obj[0].setShadowMode(ShadowMode.CastAndReceive); obj[1] = new Geometry("cube", new Box(1.0f, 1.0f, 1.0f)); obj[1].setShadowMode(ShadowMode.CastAndReceive); - TangentBinormalGenerator.generate(obj[1]); - TangentBinormalGenerator.generate(obj[0]); + MikktspaceTangentGenerator.generate(obj[1]); + MikktspaceTangentGenerator.generate(obj[0]); Spatial t = obj[0].clone(false); t.setLocalScale(10f); @@ -160,7 +159,7 @@ public void loadScene() { rootNode.attachChild(sky); } - DirectionalLight l; + private DirectionalLight l; @Override public void simpleInitApp() { @@ -199,9 +198,6 @@ public void simpleInitApp() { } private void initInputs() { - - inputManager.addMapping("ThicknessUp", new KeyTrigger(KeyInput.KEY_Y)); - inputManager.addMapping("ThicknessDown", new KeyTrigger(KeyInput.KEY_H)); inputManager.addMapping("lambdaUp", new KeyTrigger(KeyInput.KEY_U)); inputManager.addMapping("lambdaDown", new KeyTrigger(KeyInput.KEY_J)); inputManager.addMapping("switchGroundMat", new KeyTrigger(KeyInput.KEY_M)); @@ -220,7 +216,7 @@ private void initInputs() { inputManager.addMapping("backShadows", new KeyTrigger(KeyInput.KEY_K)); - inputManager.addListener(this, "lambdaUp", "lambdaDown", "ThicknessUp", "ThicknessDown", + inputManager.addListener(this, "lambdaUp", "lambdaDown", "switchGroundMat", "debug", "up", "down", "right", "left", "fwd", "back", "pp", "stabilize", "distance", "ShadowUp", "ShadowDown", "backShadows"); ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, dlsr, dlsf, guiNode, inputManager, viewPort); @@ -229,14 +225,14 @@ private void initInputs() { inputManager.addMapping("Size+", new KeyTrigger(KeyInput.KEY_W)); inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S)); - shadowStabilizationText = new BitmapText(guiFont, false); + shadowStabilizationText = new BitmapText(guiFont); shadowStabilizationText.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f); shadowStabilizationText.setText("(b:on/off) Shadow stabilization : " + dlsr.isEnabledStabilization()); shadowStabilizationText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 100, 0); guiNode.attachChild(shadowStabilizationText); - shadowZfarText = new BitmapText(guiFont, false); + shadowZfarText = new BitmapText(guiFont); shadowZfarText.setSize(guiFont.getCharSet().getRenderedSize() * 0.75f); shadowZfarText.setText("(n:on/off) Shadow extend to 500 and fade to 50 : " + (dlsr.getShadowZExtend() > 0)); shadowZfarText.setLocalTranslation(10, viewPort.getCamera().getHeight() - 120, 0); @@ -245,6 +241,7 @@ private void initInputs() { private BitmapText shadowStabilizationText; private BitmapText shadowZfarText; + @Override public void onAction(String name, boolean keyPressed, float tpf) { @@ -331,14 +328,13 @@ public void onAction(String name, boolean keyPressed, float tpf) { } } - boolean up = false; - boolean down = false; - boolean left = false; - boolean right = false; - boolean fwd = false; - boolean back = false; - float time = 0; - float s = 1f; + private boolean up = false; + private boolean down = false; + private boolean left = false; + private boolean right = false; + private boolean fwd = false; + private boolean back = false; + final private float s = 1f; @Override public void simpleUpdate(float tpf) { diff --git a/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java b/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java index 4785b33c15..846acfce40 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java +++ b/jme3-examples/src/main/java/jme3test/light/TestEnvironmentMapping.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.light; import com.jme3.app.SimpleApplication; diff --git a/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java b/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java new file mode 100644 index 0000000000..8b2244399f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestGltfUnlit.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; + +/** + * Tests the GLTF scene containing an unlit material. If it works, it should use the + * Common/MatDefs/Misc/Unshaded.j3md material definition for those objects. + * + * @author Markil 3 + * @version 3.3.0-SNAPSHOT + */ +public class TestGltfUnlit extends SimpleApplication { + public static void main(String[] args) { + TestGltfUnlit testUnlit = new TestGltfUnlit(); + testUnlit.start(); + } + + @Override + public void simpleInitApp() { + ColorRGBA skyColor = new ColorRGBA(0.5f, 0.6f, 0.7f, 0.0f); + + flyCam.setMoveSpeed(20); + viewPort.setBackgroundColor(skyColor.mult(0.9f)); + + cam.setLocation(new Vector3f(0, 10, 20)); + rootNode.attachChild(getAssetManager().loadModel("jme3test/scenes/unlit.gltf")); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java b/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java new file mode 100644 index 0000000000..4b0699de88 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestIssue2209.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import java.util.logging.Logger; +import jme3test.bullet.TestIssue1125; + +/** + * Test case for JME issue #2209: AssertionError caused by shadow renderer. + * + *

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

                If successful, the Oto model will appear. If unsuccessful, the application + * with crash with an {@code AssertionError} in {@code GLRenderer}. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestIssue2209 extends SimpleApplication { + /** + * message logger for debugging this class + */ + final public static Logger logger + = Logger.getLogger(TestIssue1125.class.getName()); + + /** + * Main entry point for the TestIssue2209 application. + */ + public static void main(String[] args) { + new TestIssue2209().start(); + } + + /** + * Initializes this application, adding Oto, a light, and a shadow renderer. + */ + @Override + public void simpleInitApp() { + if (!areAssertionsEnabled()) { + throw new IllegalStateException( + "For a valid test, assertions must be enabled."); + } + + DirectionalLight dl = new DirectionalLight(); + rootNode.addLight(dl); + + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer(assetManager, 4_096, 3); + dlsr.setLight(dl); + viewPort.addProcessor(dlsr); + + Node player = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + player.setShadowMode(RenderQueue.ShadowMode.Cast); + rootNode.attachChild(player); + } + + /** + * Tests whether assertions are enabled. + * + * @return true if enabled, otherwise false + */ + private static boolean areAssertionsEnabled() { + boolean enabled = false; + assert enabled = true; // Note: intentional side effect. + + return enabled; + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightControl2Directional.java b/jme3-examples/src/main/java/jme3test/light/TestLightControl2Directional.java new file mode 100644 index 0000000000..84d0f651e9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightControl2Directional.java @@ -0,0 +1,169 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LightControl; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Dome; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TempVars; + +/** + * Similar to {@link TestLightControlDirectional}, except that the spatial is controlled by the light this + * time. + * + * @author Markil 3 + */ +public class TestLightControl2Directional extends SimpleApplication { + private final Vector3f rotAxis = new Vector3f(Vector3f.UNIT_X); + private final float[] angles = new float[3]; + + private Node lightNode; + private DirectionalLight direction; + + public static void main(String[] args) { + TestLightControl2Directional app = new TestLightControl2Directional(); + app.start(); + } + + public void setupLighting() { + Geometry lightMdl; + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2f)); + rootNode.addLight(al); + + direction = new DirectionalLight(); + direction.setColor(ColorRGBA.White.mult(10)); + rootNode.addLight(direction); + + lightMdl = new Geometry("Light", new Dome(Vector3f.ZERO, 2, 32, 5, false)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(0, 0, 0)); + lightMdl.setLocalRotation(new Quaternion().fromAngles(FastMath.PI / 2F, 0, 0)); + rootNode.attachChild(lightMdl); + + /* + * We need this Dome doesn't have a "floor." + */ + Geometry lightFloor = new Geometry("LightFloor", new Cylinder(2, 32, 5, .1F, true)); + lightFloor.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightFloor.getMaterial().setColor("Color", ColorRGBA.White); + + lightNode = new Node(); + lightNode.addControl(new LightControl(direction, LightControl.ControlDirection.LightToSpatial)); + lightNode.attachChild(lightMdl); + lightNode.attachChild(lightFloor); + + rootNode.attachChild(lightNode); + } + + public void setupDome() { + Geometry dome = new Geometry("Dome", new Sphere(16, 32, 30, false, true)); + dome.setMaterial(new Material(this.assetManager, "Common/MatDefs/Light/PBRLighting.j3md")); + dome.setLocalTranslation(new Vector3f(0, 0, 0)); + rootNode.attachChild(dome); + } + + @Override + public void simpleInitApp() { + this.cam.setLocation(new Vector3f(-50, 20, 50)); + this.cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupDome(); + } + + @Override + public void simpleUpdate(float tpf) { + final Vector3f INIT_DIR = Vector3f.UNIT_Z.negate(); + /* + * In Radians per second + */ + final float ROT_SPEED = FastMath.PI / 2; + /* + * 360 degree rotation + */ + final float FULL_ROT = FastMath.PI * 2; + + TempVars vars = TempVars.get(); + Vector3f lightDirection = vars.vect2, nodeDirection = vars.vect3; + float length; + + angles[0] += rotAxis.x * ROT_SPEED * tpf; + angles[1] += rotAxis.y * ROT_SPEED * tpf; + angles[2] += rotAxis.z * ROT_SPEED * tpf; + direction.setDirection(new Quaternion().fromAngles(angles).mult(INIT_DIR)); + super.simpleUpdate(tpf); + + /* + * Make sure they are equal. + */ + lightDirection.set(direction.getDirection()); + lightDirection.normalizeLocal(); + lightNode.getWorldRotation().mult(Vector3f.UNIT_Z, nodeDirection); + nodeDirection.negateLocal().normalizeLocal(); + length = lightDirection.subtract(nodeDirection, vars.vect4).lengthSquared(); + length = FastMath.abs(length); + if (length > .1F) { + System.err.printf("Rotation not equal: is %s, needs to be %s (%f)\n", nodeDirection, lightDirection, length); + } + + if (angles[0] >= FULL_ROT || angles[1] >= FULL_ROT || angles[2] >= FULL_ROT) { + direction.setDirection(INIT_DIR); + angles[0] = 0; + angles[1] = 0; + angles[2] = 0; + if (rotAxis.x > 0 && rotAxis.y == 0 && rotAxis.z == 0) { + rotAxis.set(0, 1, 0); + } else if (rotAxis.y > 0 && rotAxis.x == 0 && rotAxis.z == 0) { + rotAxis.set(0, 0, 1); + } else if (rotAxis.z > 0 && rotAxis.x == 0 && rotAxis.y == 0) { + rotAxis.set(FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1); + } else { + rotAxis.set(1, 0, 0); + } + } + + vars.release(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightControl2Spot.java b/jme3-examples/src/main/java/jme3test/light/TestLightControl2Spot.java new file mode 100644 index 0000000000..717a397b50 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightControl2Spot.java @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LightControl; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Dome; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TempVars; + +/** + * Similar to {@link TestLightControlSpot}, except that the spatial is controlled by the light this + * time. + * + * @author Markil 3 + */ +public class TestLightControl2Spot extends SimpleApplication { + private final Vector3f rotAxis = new Vector3f(Vector3f.UNIT_X); + private final float[] angles = new float[3]; + + private Node lightNode; + private SpotLight spot; + + public static void main(String[] args) { + TestLightControl2Spot app = new TestLightControl2Spot(); + app.start(); + } + + public void setupLighting() { + Geometry lightMdl; + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2f)); + rootNode.addLight(al); + + spot = new SpotLight(); + spot.setSpotRange(1000); + spot.setSpotInnerAngle(5 * FastMath.DEG_TO_RAD); + spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD); + spot.setColor(ColorRGBA.White.mult(10)); + rootNode.addLight(spot); + + lightMdl = new Geometry("Light", new Dome(Vector3f.ZERO, 2, 32, 5, false)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(0, 0, 0)); + lightMdl.setLocalRotation(new Quaternion().fromAngles(FastMath.PI / 2F, 0, 0)); + rootNode.attachChild(lightMdl); + + /* + * We need this Dome doesn't have a "floor." + */ + Geometry lightFloor = new Geometry("LightFloor", new Cylinder(2, 32, 5, .1F, true)); + lightFloor.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightFloor.getMaterial().setColor("Color", ColorRGBA.White); + + lightNode = new Node(); + lightNode.addControl(new LightControl(spot, LightControl.ControlDirection.LightToSpatial)); + lightNode.attachChild(lightMdl); + lightNode.attachChild(lightFloor); + + rootNode.attachChild(lightNode); + } + + public void setupDome() { + Geometry dome = new Geometry("Dome", new Sphere(16, 32, 30, false, true)); + dome.setMaterial(new Material(this.assetManager, "Common/MatDefs/Light/PBRLighting.j3md")); + dome.setLocalTranslation(new Vector3f(0, 0, 0)); + rootNode.attachChild(dome); + } + + @Override + public void simpleInitApp() { + this.cam.setLocation(new Vector3f(-50, 20, 50)); + this.cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupDome(); + } + + @Override + public void simpleUpdate(float tpf) { + final Vector3f INIT_DIR = Vector3f.UNIT_Z.negate(); + /* + * In Radians per second + */ + final float ROT_SPEED = FastMath.PI / 2; + /* + * 360 degree rotation + */ + final float FULL_ROT = FastMath.PI * 2; + + TempVars vars = TempVars.get(); + Vector3f lightPosition = vars.vect1, lightDirection = vars.vect2, nodeDirection = vars.vect3; + float length; + + angles[0] += rotAxis.x * ROT_SPEED * tpf; + angles[1] += rotAxis.y * ROT_SPEED * tpf; + angles[2] += rotAxis.z * ROT_SPEED * tpf; + spot.setDirection(new Quaternion().fromAngles(angles).mult(INIT_DIR)); + super.simpleUpdate(tpf); + + /* + * Make sure they are equal. + */ + lightPosition.set(spot.getPosition()); + lightPosition.subtractLocal(lightNode.getWorldTranslation()); + length = lightPosition.lengthSquared(); + if (length > 0.1F) { + System.err.printf("Translation not equal: is %s (%s), needs to be %s\n", lightNode.getWorldTranslation(), lightNode.getLocalTranslation(), spot.getPosition()); + } + lightDirection.set(spot.getDirection()); + lightDirection.normalizeLocal(); + lightNode.getWorldRotation().mult(Vector3f.UNIT_Z, nodeDirection); + nodeDirection.negateLocal().normalizeLocal(); + length = lightDirection.subtract(nodeDirection, vars.vect4).lengthSquared(); + length = FastMath.abs(length); + if (length > .1F) { + System.err.printf("Rotation not equal: is %s, needs to be %s (%f)\n", nodeDirection, lightDirection, length); + } + + if (angles[0] >= FULL_ROT || angles[1] >= FULL_ROT || angles[2] >= FULL_ROT) { + spot.setDirection(INIT_DIR); + angles[0] = 0; + angles[1] = 0; + angles[2] = 0; + if (rotAxis.x > 0 && rotAxis.y == 0 && rotAxis.z == 0) { + rotAxis.set(0, 1, 0); + } else if (rotAxis.y > 0 && rotAxis.x == 0 && rotAxis.z == 0) { + rotAxis.set(0, 0, 1); + } else if (rotAxis.z > 0 && rotAxis.x == 0 && rotAxis.y == 0) { + rotAxis.set(FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1); + } else { + rotAxis.set(1, 0, 0); + } + } + + vars.release(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightControlDirectional.java b/jme3-examples/src/main/java/jme3test/light/TestLightControlDirectional.java new file mode 100644 index 0000000000..5353403a4e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightControlDirectional.java @@ -0,0 +1,168 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LightControl; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Dome; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TempVars; + +/** + * Creates a directional light controlled by rotating a node. The light will shine on a surrounding sphere. + * The light will rotate on each axis, then on a random axis, then return to the X + * axis. + * + * @author Markil 3 + */ +public class TestLightControlDirectional extends SimpleApplication { + private final Vector3f rotAxis = new Vector3f(Vector3f.UNIT_X); + private final float[] angles = new float[3]; + + private Node lightNode; + private DirectionalLight direction; + + public static void main(String[] args) { + TestLightControlDirectional app = new TestLightControlDirectional(); + app.start(); + } + + public void setupLighting() { + Geometry lightMdl; + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2f)); + rootNode.addLight(al); + + direction = new DirectionalLight(); + direction.setColor(ColorRGBA.White.mult(10)); + rootNode.addLight(direction); + + lightMdl = new Geometry("Light", new Dome(Vector3f.ZERO, 2, 32, 5, false)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(0, 0, 0)); + lightMdl.setLocalRotation(new Quaternion().fromAngles(FastMath.PI / 2F, 0, 0)); + rootNode.attachChild(lightMdl); + + /* + * We need this Dome doesn't have a "floor." + */ + Geometry lightFloor = new Geometry("LightFloor", new Cylinder(2, 32, 5, .1F, true)); + lightFloor.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightFloor.getMaterial().setColor("Color", ColorRGBA.White); + + lightNode = new Node(); + lightNode.addControl(new LightControl(direction)); + lightNode.attachChild(lightMdl); + lightNode.attachChild(lightFloor); + rootNode.attachChild(lightNode); + } + + public void setupDome() { + Geometry dome = new Geometry("Dome", new Sphere(16, 32, 30, false, true)); + dome.setMaterial(new Material(this.assetManager, "Common/MatDefs/Light/PBRLighting.j3md")); + dome.setLocalTranslation(new Vector3f(0, 0, 0)); + rootNode.attachChild(dome); + } + + @Override + public void simpleInitApp() { + this.cam.setLocation(new Vector3f(-50, 20, 50)); + this.cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupDome(); + } + + @Override + public void simpleUpdate(float tpf) { + /* + * In Radians per second + */ + final float ROT_SPEED = FastMath.PI / 2; + /* + * 360 degree rotation + */ + final float FULL_ROT = FastMath.PI * 2; + + TempVars vars = TempVars.get(); + Vector3f lightDirection = vars.vect2, nodeDirection = vars.vect3; + float length; + + angles[0] += rotAxis.x * ROT_SPEED * tpf; + angles[1] += rotAxis.y * ROT_SPEED * tpf; + angles[2] += rotAxis.z * ROT_SPEED * tpf; + lightNode.setLocalRotation(new Quaternion().fromAngles(angles)); + super.simpleUpdate(tpf); + + /* + * Make sure they are equal. + */ + lightDirection.set(direction.getDirection()); + lightDirection.normalize(); + lightNode.getWorldRotation().mult(Vector3f.UNIT_Z, nodeDirection); + nodeDirection.negateLocal().normalizeLocal(); + length = lightDirection.subtract(nodeDirection, vars.vect4).lengthSquared(); + length = FastMath.abs(length); + if (length > .1F) { + System.err.printf("Rotation not equal: is %s, needs to be %s (%f)\n", nodeDirection, lightDirection, length); + } + + if (angles[0] >= FULL_ROT || angles[1] >= FULL_ROT || angles[2] >= FULL_ROT) { + lightNode.setLocalRotation(Quaternion.DIRECTION_Z); + angles[0] = 0; + angles[1] = 0; + angles[2] = 0; + if (rotAxis.x > 0 && rotAxis.y == 0 && rotAxis.z == 0) { + rotAxis.set(0, 1, 0); + } else if (rotAxis.y > 0 && rotAxis.x == 0 && rotAxis.z == 0) { + rotAxis.set(0, 0, 1); + } else if (rotAxis.z > 0 && rotAxis.x == 0 && rotAxis.y == 0) { + rotAxis.set(FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1); + } else { + rotAxis.set(1, 0, 0); + } + } + + vars.release(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightControlSpot.java b/jme3-examples/src/main/java/jme3test/light/TestLightControlSpot.java new file mode 100644 index 0000000000..2e08481cde --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestLightControlSpot.java @@ -0,0 +1,177 @@ +/* + * Copyright (c) 2009-2012 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.AmbientLight; +import com.jme3.light.SpotLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.control.LightControl; +import com.jme3.scene.shape.Cylinder; +import com.jme3.scene.shape.Dome; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.TempVars; + +/** + * Creates a spotlight controlled by rotating a node. The light will shine on a surrounding sphere. + * The light will rotate on each axis, then on a random axis, then return to the X + * axis. + * + * @author Markil 3 + */ +public class TestLightControlSpot extends SimpleApplication { + private final Vector3f rotAxis = new Vector3f(Vector3f.UNIT_X); + private final float[] angles = new float[3]; + + private Node lightNode; + private SpotLight spot; + + public static void main(String[] args) { + TestLightControlSpot app = new TestLightControlSpot(); + app.start(); + } + + public void setupLighting() { + Geometry lightMdl; + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(2f)); + rootNode.addLight(al); + + spot = new SpotLight(); + + spot.setSpotRange(1000); + spot.setSpotInnerAngle(5 * FastMath.DEG_TO_RAD); + spot.setSpotOuterAngle(10 * FastMath.DEG_TO_RAD); + spot.setColor(ColorRGBA.White.mult(10)); + rootNode.addLight(spot); + + lightMdl = new Geometry("Light", new Dome(Vector3f.ZERO, 2, 32, 5, false)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setLocalTranslation(new Vector3f(0, 0, 0)); + lightMdl.setLocalRotation(new Quaternion().fromAngles(FastMath.PI / 2F, 0, 0)); + rootNode.attachChild(lightMdl); + + /* + * We need this Dome doesn't have a "floor." + */ + Geometry lightFloor = new Geometry("LightFloor", new Cylinder(2, 32, 5, .1F, true)); + lightFloor.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightFloor.getMaterial().setColor("Color", ColorRGBA.White); + + lightNode = new Node(); + lightNode.addControl(new LightControl(spot)); + lightNode.attachChild(lightMdl); + lightNode.attachChild(lightFloor); + rootNode.attachChild(lightNode); + } + + public void setupDome() { + Geometry dome = new Geometry("Dome", new Sphere(16, 32, 30, false, true)); + dome.setMaterial(new Material(this.assetManager, "Common/MatDefs/Light/PBRLighting.j3md")); + dome.setLocalTranslation(new Vector3f(0, 0, 0)); + rootNode.attachChild(dome); + } + + @Override + public void simpleInitApp() { + this.cam.setLocation(new Vector3f(-50, 20, 50)); + this.cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + flyCam.setMoveSpeed(30); + + setupLighting(); + setupDome(); + } + + @Override + public void simpleUpdate(float tpf) { + /* + * In Radians per second + */ + final float ROT_SPEED = FastMath.PI / 2; + /* + * 360 degree rotation + */ + final float FULL_ROT = FastMath.PI * 2; + + TempVars vars = TempVars.get(); + Vector3f lightPosition = vars.vect1, lightDirection = vars.vect2, nodeDirection = vars.vect3; + float length; + + angles[0] += rotAxis.x * ROT_SPEED * tpf; + angles[1] += rotAxis.y * ROT_SPEED * tpf; + angles[2] += rotAxis.z * ROT_SPEED * tpf; + lightNode.setLocalRotation(new Quaternion().fromAngles(angles)); + super.simpleUpdate(tpf); + + /* + * Make sure they are equal. + */ + lightPosition.set(spot.getPosition()); + length = lightPosition.subtract(lightNode.getWorldTranslation(), vars.vect4).lengthSquared(); + if (length > 0.1F) { + System.err.printf("Translation not equal: is %s, needs to be %s\n", lightNode.getWorldTranslation(), spot.getPosition()); + } + lightDirection.set(spot.getDirection()); + lightDirection.normalizeLocal(); + lightNode.getWorldRotation().mult(Vector3f.UNIT_Z, nodeDirection); + nodeDirection.negateLocal().normalizeLocal(); + length = lightDirection.subtract(nodeDirection, vars.vect4).lengthSquared(); + length = FastMath.abs(length); + if (length > .1F) { + System.err.printf("Rotation not equal: is %s, needs to be %s (%f)\n", nodeDirection, lightDirection, length); + } + + if (angles[0] >= FULL_ROT || angles[1] >= FULL_ROT || angles[2] >= FULL_ROT) { + lightNode.setLocalRotation(Quaternion.DIRECTION_Z); + angles[0] = 0; + angles[1] = 0; + angles[2] = 0; + if (rotAxis.x > 0 && rotAxis.y == 0 && rotAxis.z == 0) { + rotAxis.set(0, 1, 0); + } else if (rotAxis.y > 0 && rotAxis.x == 0 && rotAxis.z == 0) { + rotAxis.set(0, 0, 1); + } else if (rotAxis.z > 0 && rotAxis.x == 0 && rotAxis.y == 0) { + rotAxis.set(FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1, FastMath.nextRandomFloat() % 1); + } else { + rotAxis.set(1, 0, 0); + } + } + + vars.release(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightNode.java b/jme3-examples/src/main/java/jme3test/light/TestLightNode.java deleted file mode 100644 index e2d337b83d..0000000000 --- a/jme3-examples/src/main/java/jme3test/light/TestLightNode.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.light; - -import com.jme3.app.SimpleApplication; -import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; -import com.jme3.scene.LightNode; -import com.jme3.scene.Node; -import com.jme3.scene.shape.Sphere; -import com.jme3.scene.shape.Torus; - -public class TestLightNode extends SimpleApplication { - - float angle; - Node movingNode; - - public static void main(String[] args){ - TestLightNode app = new TestLightNode(); - app.start(); - } - - @Override - public void simpleInitApp() { - Torus torus = new Torus(10, 6, 1, 3); -// Torus torus = new Torus(50, 30, 1, 3); - Geometry g = new Geometry("Torus Geom", torus); - g.rotate(-FastMath.HALF_PI, 0, 0); - g.center(); -// g.move(0, 1, 0); - - Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - mat.setFloat("Shininess", 32f); - mat.setBoolean("UseMaterialColors", true); - mat.setColor("Ambient", ColorRGBA.Black); - mat.setColor("Diffuse", ColorRGBA.White); - mat.setColor("Specular", ColorRGBA.White); -// mat.setBoolean("VertexLighting", true); -// mat.setBoolean("LowQuality", true); - g.setMaterial(mat); - - rootNode.attachChild(g); - - Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); - lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); - - movingNode=new Node("lightParentNode"); - movingNode.attachChild(lightMdl); - rootNode.attachChild(movingNode); - - PointLight pl = new PointLight(); - pl.setColor(ColorRGBA.Green); - pl.setRadius(4f); - rootNode.addLight(pl); - - LightNode lightNode=new LightNode("pointLight", pl); - movingNode.attachChild(lightNode); - - DirectionalLight dl = new DirectionalLight(); - dl.setColor(ColorRGBA.Red); - dl.setDirection(new Vector3f(0, 1, 0)); - rootNode.addLight(dl); - } - - @Override - public void simpleUpdate(float tpf){ -// cam.setLocation(new Vector3f(5.0347548f, 6.6481347f, 3.74853f)); -// cam.setRotation(new Quaternion(-0.19183293f, 0.80776674f, -0.37974006f, -0.40805697f)); - - angle += tpf; - angle %= FastMath.TWO_PI; - - movingNode.setLocalTranslation(new Vector3f(FastMath.cos(angle) * 3f, 2, FastMath.sin(angle) * 3f)); - } - -} diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java b/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java index f3133099b3..26b56e6610 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java +++ b/jme3-examples/src/main/java/jme3test/light/TestLightRadius.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,9 +45,9 @@ public class TestLightRadius extends SimpleApplication { - float pos, vel=1; - PointLight pl; - Geometry lightMdl; + private float pos, vel=1; + private PointLight pl; + private Geometry lightMdl; public static void main(String[] args){ TestLightRadius app = new TestLightRadius(); diff --git a/jme3-examples/src/main/java/jme3test/light/TestLightingFog.java b/jme3-examples/src/main/java/jme3test/light/TestLightingFog.java index adcb24a0cc..b142dfc417 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestLightingFog.java +++ b/jme3-examples/src/main/java/jme3test/light/TestLightingFog.java @@ -17,9 +17,9 @@ public class TestLightingFog extends SimpleApplication implements ActionListener { private Material material; - private Vector2f linear = new Vector2f(25, 120); - private float exp = 0.015f; - private float expsq = 0.02f; + final private Vector2f linear = new Vector2f(25, 120); + final private float exp = 0.015f; + final private float expsq = 0.02f; public static void main(String[] args) { TestLightingFog testLightingFog = new TestLightingFog(); diff --git a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java index 005634bcd1..ca9365cb88 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java +++ b/jme3-examples/src/main/java/jme3test/light/TestManyLightsSingle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,6 @@ import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.scene.Geometry; -import com.jme3.scene.LightNode; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.scene.control.AbstractControl; @@ -69,7 +68,7 @@ public static void main(String[] args) { /** * Switch mode with space bar at run time */ - TechniqueDef.LightMode lm = TechniqueDef.LightMode.SinglePass; + private TechniqueDef.LightMode lm = TechniqueDef.LightMode.SinglePass; @Override public void simpleInitApp() { @@ -105,27 +104,19 @@ public void simpleInitApp() { if (nb > 60) { n.removeLight(light); } else { - - LightNode ln = new LightNode("l", light); - n.attachChild(ln); - ln.setLocalTranslation(p.getPosition()); int rand = FastMath.nextRandomInt(0, 3); switch (rand) { case 0: light.setColor(ColorRGBA.Red); - // ln.addControl(new MoveControl(5f)); break; case 1: light.setColor(ColorRGBA.Yellow); - // ln.addControl(new MoveControl(5f)); break; case 2: light.setColor(ColorRGBA.Green); - //ln.addControl(new MoveControl(-5f)); break; case 3: light.setColor(ColorRGBA.Orange); - //ln.addControl(new MoveControl(-5f)); break; } } @@ -157,6 +148,7 @@ public void simpleInitApp() { stateManager.attach(debug); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("toggle") && isPressed) { if (lm == TechniqueDef.LightMode.SinglePass) { @@ -212,12 +204,12 @@ public void onAction(String name, boolean isPressed, float tpf) { rootNode.addLight(al); - /** + /* * Write text on the screen (HUD) */ guiNode.detachAllChildren(); guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - helloText = new BitmapText(guiFont, false); + helloText = new BitmapText(guiFont); helloText.setSize(guiFont.getCharSet().getRenderedSize()); helloText.setText("(Single pass) nb lights per batch : " + renderManager.getSinglePassLightBatchSize()); helloText.setLocalTranslation(300, helloText.getLineHeight(), 0); @@ -236,10 +228,7 @@ protected void reloadScene(Geometry g, Geometry boxGeo, Node cubeNodes) { } } - BitmapText helloText; - long time; - long nbFrames; - long startTime = 0; + private BitmapText helloText; @Override public void simpleUpdate(float tpf) { @@ -256,8 +245,8 @@ public void simpleUpdate(float tpf) { class MoveControl extends AbstractControl { - float direction; - Vector3f origPos = new Vector3f(); + final private float direction; + final private Vector3f origPos = new Vector3f(); public MoveControl(float direction) { this.direction = direction; @@ -268,7 +257,7 @@ public void setSpatial(Spatial spatial) { super.setSpatial(spatial); //To change body of generated methods, choose Tools | Templates. origPos.set(spatial.getLocalTranslation()); } - float time = 0; + private float time = 0; @Override protected void controlUpdate(float tpf) { @@ -280,4 +269,4 @@ protected void controlUpdate(float tpf) { protected void controlRender(RenderManager rm, ViewPort vp) { } } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java b/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java index 9e5db8948b..d98619a2b2 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java +++ b/jme3-examples/src/main/java/jme3test/light/TestObbVsBounds.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,16 +53,16 @@ public class TestObbVsBounds extends SimpleApplication { private Node ln; - private BoundingBox aabb = new BoundingBox(); - private BoundingSphere sphere = new BoundingSphere(10, new Vector3f(-30, 0, -60)); + final private BoundingBox aabb = new BoundingBox(); + final private BoundingSphere sphere = new BoundingSphere(10, new Vector3f(-30, 0, -60)); private final static float MOVE_SPEED = 60; - private Vector3f tmp = new Vector3f(); - private Quaternion tmpQuat = new Quaternion(); + final private Vector3f tmp = new Vector3f(); + final private Quaternion tmpQuat = new Quaternion(); private boolean moving, shift; private boolean panning; - private OrientedBoxProbeArea area = new OrientedBoxProbeArea(); + final private OrientedBoxProbeArea area = new OrientedBoxProbeArea(); private Camera frustumCam; private Geometry areaGeom; @@ -134,6 +134,7 @@ public void simpleInitApp() { flyCam.setEnabled(false); inputManager.addListener(new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { Spatial s = null; float mult = 1; @@ -178,6 +179,7 @@ public void onAnalog(String name, float value, float tpf) { }, "up", "down", "left", "right"); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("click")) { if (isPressed) { @@ -224,7 +226,7 @@ public void makeAreaGeom() { points[7].set(1, -1, -1); Mesh box = WireFrustum.makeFrustum(points); - areaGeom = new Geometry("light", (Mesh)box); + areaGeom = new Geometry("light", box); areaGeom.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md")); areaGeom.getMaterial().setColor("Color", ColorRGBA.White); } diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointDirectionalAndSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointDirectionalAndSpotLightShadows.java index 473ba8dfe8..332c0ae893 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPointDirectionalAndSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPointDirectionalAndSpotLightShadows.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,16 +60,10 @@ public static void main(String[] args) { TestPointDirectionalAndSpotLightShadows app = new TestPointDirectionalAndSpotLightShadows(); app.start(); } - Node lightNode; - PointLightShadowRenderer plsr; - PointLightShadowFilter plsf; - DirectionalLightShadowRenderer dlsr; - DirectionalLightShadowFilter dlsf; - SpotLightShadowRenderer slsr; - SpotLightShadowFilter slsf; - SpotLight spotLight; + private Node lightNode; + private SpotLight spotLight; - boolean useFilter = false; + final private boolean useFilter = false; @Override public void simpleInitApp() { @@ -98,15 +92,15 @@ public void simpleInitApp() { rootNode.attachChild(box); box.setLocalTranslation(-1f, 0.5f, -2); - ((PointLight) scene.getLocalLightList().get(0)).setColor(ColorRGBA.Red); - - plsr = new PointLightShadowRenderer(assetManager, SHADOWMAP_SIZE); + scene.getLocalLightList().get(0).setColor(ColorRGBA.Red); + + PointLightShadowRenderer plsr + = new PointLightShadowRenderer(assetManager, SHADOWMAP_SIZE); plsr.setLight((PointLight) scene.getLocalLightList().get(0)); plsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); - - - plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); + PointLightShadowFilter plsf + = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); plsf.setLight((PointLight) scene.getLocalLightList().get(0)); plsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); plsf.setEnabled(useFilter); @@ -116,11 +110,14 @@ public void simpleInitApp() { rootNode.addLight(directionalLight); directionalLight.setColor(ColorRGBA.Blue); directionalLight.setDirection(new Vector3f(-1f, -.2f, 0f)); - dlsr = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE*2, 4); + + DirectionalLightShadowRenderer dlsr + = new DirectionalLightShadowRenderer(assetManager, SHADOWMAP_SIZE*2, 4); dlsr.setLight(directionalLight); dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); - - dlsf = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE*2, 4); + + DirectionalLightShadowFilter dlsf + = new DirectionalLightShadowFilter(assetManager, SHADOWMAP_SIZE*2, 4); dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); dlsf.setLight(directionalLight); dlsf.setEnabled(useFilter); @@ -137,12 +134,14 @@ public void simpleInitApp() { sphereGeometry.setMaterial(assetManager.loadMaterial("Common/Materials/WhiteColor.j3m")); rootNode.attachChild(sphereGeometry); rootNode.addLight(spotLight); - - slsr = new SpotLightShadowRenderer(assetManager, SHADOWMAP_SIZE); + + SpotLightShadowRenderer slsr + = new SpotLightShadowRenderer(assetManager, SHADOWMAP_SIZE); slsr.setLight(spotLight); slsr.setEdgeFilteringMode(EdgeFilteringMode.PCF4); - - slsf = new SpotLightShadowFilter(assetManager, SHADOWMAP_SIZE); + + SpotLightShadowFilter slsf + = new SpotLightShadowFilter(assetManager, SHADOWMAP_SIZE); slsf.setLight(spotLight); slsf.setEdgeFilteringMode(EdgeFilteringMode.PCF4); slsf.setEnabled(useFilter); @@ -166,7 +165,7 @@ public void simpleInitApp() { } - float timeElapsed = 0.0f; + private float timeElapsed = 0.0f; @Override public void simpleUpdate(float tpf) { timeElapsed += tpf; diff --git a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java index 80079711d0..762e11df4a 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestPointLightShadows.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,10 +55,8 @@ public static void main(String[] args) { TestPointLightShadows app = new TestPointLightShadows(); app.start(); } - Node lightNode; - PointLightShadowRenderer plsr; - PointLightShadowFilter plsf; - AmbientLight al; + private PointLightShadowRenderer plsr; + private AmbientLight al; @Override public void simpleInitApp () { @@ -76,7 +74,7 @@ public void simpleInitApp () { scene.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); rootNode.attachChild(scene); rootNode.getChild("Cube").setShadowMode(RenderQueue.ShadowMode.Receive); - lightNode = (Node) rootNode.getChild("Lamp"); + Node lightNode = (Node) rootNode.getChild("Lamp"); Geometry lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); //Geometry lightMdl = new Geometry("Light", new Box(.1f,.1f,.1f)); lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); @@ -104,8 +102,8 @@ public void simpleInitApp () { plsr.displayDebug(); viewPort.addProcessor(plsr); - - plsf = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); + PointLightShadowFilter plsf + = new PointLightShadowFilter(assetManager, SHADOWMAP_SIZE); plsf.setLight((PointLight) scene.getLocalLightList().get(0)); plsf.setShadowZExtend(15); plsf.setShadowZFadeLength(5); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSdsmDirectionalLightShadow.java b/jme3-examples/src/main/java/jme3test/light/TestSdsmDirectionalLightShadow.java new file mode 100644 index 0000000000..3e70aba0f2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/TestSdsmDirectionalLightShadow.java @@ -0,0 +1,430 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.light; + + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.ZipLocator; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.post.Filter; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.shadow.SdsmDirectionalLightShadowFilter; +import com.jme3.util.SkyFactory; + +import java.io.File; + + +/** + * Test application for SDSM (Sample Distribution Shadow Mapping). + */ +public class TestSdsmDirectionalLightShadow extends SimpleApplication implements ActionListener { + + private static final int[] SHADOW_MAP_SIZES = {256, 512, 1024, 2048, 4096}; + private int shadowMapSizeIndex = 2; // Start at 1024 + private int numSplits = 2; + + private DirectionalLight sun; + private FilterPostProcessor fpp; + + private Filter activeFilter; + private SdsmDirectionalLightShadowFilter sdsmFilter; + private DirectionalLightShadowFilter traditionalFilter; + + private boolean useSdsm = true; + + // Light direction parameters (in radians) + private float lightElevation = 1.32f; + private float lightAzimuth = FastMath.QUARTER_PI; + + private BitmapText statusText; + + public static void main(String[] args) { + TestSdsmDirectionalLightShadow app = new TestSdsmDirectionalLightShadow(); + app.start(); + } + + @Override + public void simpleInitApp() { + setupCamera(); + buildScene(); + setupLighting(); + setupShadows(); + setupUI(); + setupInputs(); + } + + private void setupCamera() { + // Start at origin looking along +X + cam.setLocation(new Vector3f(0, 5f, 0)); + flyCam.setMoveSpeed(20); + flyCam.setDragToRotate(true); + inputManager.setCursorVisible(true); + //Note that for any specific scene, the actual frustum sizing matters a lot for non-SDSM results. + //Sometimes values that make the frustums match the usual scene depths will result in pretty good splits + //without SDSM! But then, the creator has to tune for that specific scene. + // If they just use a general frustum, results will be awful. + // Most users will probably not even know about this and want a frustum that shows things really far away and things closer than 1 meter to the camera. + //So what's fair to show off, really? + //(And if a user looks really closely at a shadow on a wall or something, SDSM is always going to win.) + cam.setFrustumPerspective(60f, cam.getAspect(), 0.01f, 500f); + } + + private void buildScene() { + // Add reference objects at origin for orientation + addReferenceObjects(); + + // Load Sponza scene from zip - need to extract to temp file since ZipLocator needs filesystem path + File f = new File("jme3-examples/sponza.zip"); + if(!f.exists()){ + System.out.println("Sponza demo not found. Note that SDSM is most effective with interior environments."); + } else { + assetManager.registerLocator(f.getAbsolutePath(), ZipLocator.class); + Spatial sponza = assetManager.loadModel("NewSponza_Main_glTF_003.gltf"); + sponza.setShadowMode(ShadowMode.CastAndReceive); + sponza.getLocalLightList().clear(); + + rootNode.attachChild(sponza); + + // Light probe for PBR materials + LightProbe probe = (LightProbe) assetManager.loadAsset("lightprobe.j3o"); + probe.getArea().setRadius(Float.POSITIVE_INFINITY); + probe.setPosition(new Vector3f(0f,0f,0f)); + rootNode.addLight(probe); + } + } + + private void addReferenceObjects() { + Material red = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + red.setBoolean("UseMaterialColors", true); + red.setColor("Diffuse", ColorRGBA.Red); + red.setColor("Ambient", ColorRGBA.Red.mult(0.3f)); + + Material green = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + green.setBoolean("UseMaterialColors", true); + green.setColor("Diffuse", ColorRGBA.Green); + green.setColor("Ambient", ColorRGBA.Green.mult(0.3f)); + + Material blue = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + blue.setBoolean("UseMaterialColors", true); + blue.setColor("Diffuse", ColorRGBA.Blue); + blue.setColor("Ambient", ColorRGBA.Blue.mult(0.3f)); + + Material white = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + white.setBoolean("UseMaterialColors", true); + white.setColor("Diffuse", ColorRGBA.White); + white.setColor("Ambient", ColorRGBA.White.mult(0.3f)); + + + Material brown = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + brown.setBoolean("UseMaterialColors", true); + brown.setColor("Diffuse", ColorRGBA.Brown); + brown.setColor("Ambient", ColorRGBA.Brown.mult(0.3f)); + + // Origin sphere (white) + Geometry origin = new Geometry("Origin", new Sphere(16, 16, 1f)); + origin.setMaterial(white); + origin.setLocalTranslation(0, 0, 0); + origin.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(origin); + + // X axis marker (red) at +10 + Geometry xMarker = new Geometry("X+", new Box(1f, 1f, 1f)); + xMarker.setMaterial(red); + xMarker.setLocalTranslation(10, 0, 0); + xMarker.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(xMarker); + + // Y axis marker (green) at +10 + Geometry yMarker = new Geometry("Y+", new Box(1f, 1f, 1f)); + yMarker.setMaterial(green); + yMarker.setLocalTranslation(0, 10, 0); + yMarker.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(yMarker); + + // Z axis marker (blue) at +10 + Geometry zMarker = new Geometry("Z+", new Box(1f, 1f, 1f)); + zMarker.setMaterial(blue); + zMarker.setLocalTranslation(0, 0, 10); + zMarker.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(zMarker); + + // Ground plane + Geometry ground = new Geometry("Ground", new Box(50f, 0.1f, 50f)); + ground.setMaterial(brown); + ground.setLocalTranslation(0, -1f, 0); + ground.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(ground); + } + + private void setupLighting() { + sun = new DirectionalLight(); + updateLightDirection(); + sun.setColor(ColorRGBA.White); + rootNode.addLight(sun); + + AmbientLight ambient = new AmbientLight(); + ambient.setColor(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f)); + rootNode.addLight(ambient); + + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + rootNode.attachChild(sky); + } + + /** + * Updates the light direction based on elevation and azimuth angles. + * Elevation: 0 = horizon, PI/2 = straight down (noon) + * Azimuth: rotation around the Y axis + */ + private void updateLightDirection() { + // Compute direction from spherical coordinates + // The light points DOWN toward the scene, so we negate Y + float cosElev = FastMath.cos(lightElevation); + float sinElev = FastMath.sin(lightElevation); + float cosAz = FastMath.cos(lightAzimuth); + float sinAz = FastMath.sin(lightAzimuth); + + // Direction vector (pointing from sun toward scene) + Vector3f dir = new Vector3f( + cosElev * sinAz, // X component + -sinElev, // Y component (negative = pointing down) + cosElev * cosAz // Z component + ); + sun.setDirection(dir.normalizeLocal()); + if(sdsmFilter != null) { sdsmFilter.setLight(sun); } + if(traditionalFilter != null) { traditionalFilter.setLight(sun); } + } + + private void setupShadows() { + fpp = new FilterPostProcessor(assetManager); + + setActiveFilter(useSdsm); + + viewPort.addProcessor(fpp); + } + + private void setActiveFilter(boolean isSdsm){ + if(activeFilter != null){ fpp.removeFilter(activeFilter); } + int shadowMapSize = SHADOW_MAP_SIZES[shadowMapSizeIndex]; + if(isSdsm){ + // SDSM shadow filter (requires OpenGL 4.3) + sdsmFilter = new SdsmDirectionalLightShadowFilter(assetManager, shadowMapSize, numSplits); + sdsmFilter.setLight(sun); + sdsmFilter.setShadowIntensity(0.7f); + sdsmFilter.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + activeFilter = sdsmFilter; + traditionalFilter = null; + } else { + // Traditional shadow filter for comparison + traditionalFilter = new DirectionalLightShadowFilter(assetManager, shadowMapSize, numSplits); + traditionalFilter.setLight(sun); + traditionalFilter.setLambda(0.55f); + traditionalFilter.setShadowIntensity(0.7f); + traditionalFilter.setEdgeFilteringMode(EdgeFilteringMode.PCF4); + this.activeFilter = traditionalFilter; + sdsmFilter = null; + } + fpp.addFilter(activeFilter); + } + + private void setupUI() { + statusText = new BitmapText(guiFont); + statusText.setSize(guiFont.getCharSet().getRenderedSize() * 0.8f); + statusText.setLocalTranslation(10, cam.getHeight() - 10, 0); + guiNode.attachChild(statusText); + updateStatusText(); + } + + private void updateStatusText() { + StringBuilder sb = new StringBuilder(); + sb.append("SDSM Shadow Test (Requires OpenGL 4.3)\n"); + sb.append("---------------------------------------\n"); + + if (useSdsm) { + sb.append("Mode: SDSM (Sample Distribution Shadow Mapping)\n"); + } else { + sb.append("Mode: Traditional (Lambda-based splits)\n"); + } + + sb.append(String.format("Shadow Map Size: %d | Splits: %d\n", + SHADOW_MAP_SIZES[shadowMapSizeIndex], numSplits)); + sb.append(String.format("Light: Elevation %.0f deg | Azimuth %.0f deg\n", + lightElevation * FastMath.RAD_TO_DEG, lightAzimuth * FastMath.RAD_TO_DEG)); + + sb.append("\n"); + sb.append("Controls:\n"); + sb.append(" T - Toggle SDSM / Traditional\n"); + sb.append(" 1-4 - Set number of splits\n"); + sb.append(" -/+ - Change shadow map size\n"); + sb.append(" Numpad 8/5 - Light elevation\n"); + sb.append(" Numpad 4/6 - Light rotation\n"); + sb.append(" X - Show shadow frustum debug\n"); + sb.append(" C - Restart display\n"); + + statusText.setText(sb.toString()); + } + + private void setupInputs() { + inputManager.addMapping("toggleMode", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("splits1", new KeyTrigger(KeyInput.KEY_1)); + inputManager.addMapping("splits2", new KeyTrigger(KeyInput.KEY_2)); + inputManager.addMapping("splits3", new KeyTrigger(KeyInput.KEY_3)); + inputManager.addMapping("splits4", new KeyTrigger(KeyInput.KEY_4)); + inputManager.addMapping("sizeUp", new KeyTrigger(KeyInput.KEY_EQUALS)); + inputManager.addMapping("sizeDown", new KeyTrigger(KeyInput.KEY_MINUS)); + inputManager.addMapping("debug", new KeyTrigger(KeyInput.KEY_X)); + inputManager.addMapping("restartDisplay", new KeyTrigger(KeyInput.KEY_C)); + + inputManager.addMapping("elevUp", new KeyTrigger(KeyInput.KEY_NUMPAD8)); + inputManager.addMapping("elevDown", new KeyTrigger(KeyInput.KEY_NUMPAD5)); + inputManager.addMapping("azimLeft", new KeyTrigger(KeyInput.KEY_NUMPAD4)); + inputManager.addMapping("azimRight", new KeyTrigger(KeyInput.KEY_NUMPAD6)); + + inputManager.addListener(this, + "toggleMode", "splits1", "splits2", "splits3", "splits4", + "sizeUp", "sizeDown", "debug", "restartDisplay", + "elevUp", "elevDown", "azimLeft", "azimRight"); + } + + private boolean elevUp, elevDown, azimLeft, azimRight; + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + // Track light direction key states + switch (name) { + case "elevUp": elevUp = isPressed; return; + case "elevDown": elevDown = isPressed; return; + case "azimLeft": azimLeft = isPressed; return; + case "azimRight": azimRight = isPressed; return; + default: break; + } + + // Other keys only on press + if (!isPressed) { + return; + } + + switch (name) { + case "toggleMode": + useSdsm = !useSdsm; + setActiveFilter(useSdsm); + updateStatusText(); + break; + + case "splits1": + case "splits2": + case "splits3": + case "splits4": + int newSplits = Integer.parseInt(name.substring(6)); + if (newSplits != numSplits) { + numSplits = newSplits; + setActiveFilter(useSdsm); + updateStatusText(); + } + break; + + case "sizeUp": + if (shadowMapSizeIndex < SHADOW_MAP_SIZES.length - 1) { + shadowMapSizeIndex++; + setActiveFilter(useSdsm); + updateStatusText(); + } + break; + + case "sizeDown": + if (shadowMapSizeIndex > 0) { + shadowMapSizeIndex--; + setActiveFilter(useSdsm); + updateStatusText(); + } + break; + + case "debug": + if (useSdsm) { + sdsmFilter.displayAllFrustums(); + } else { + traditionalFilter.displayFrustum(); + } + break; + case "restartDisplay": + (context).restart(); + default: + break; + } + } + + @Override + public void simpleUpdate(float tpf) { + boolean changed = false; + + // Adjust elevation (clamped between 5 degrees and 90 degrees) + if (elevUp) { + lightElevation = Math.min(FastMath.PI, lightElevation + tpf); + changed = true; + } + if (elevDown) { + lightElevation = Math.max(0f, lightElevation - tpf); + changed = true; + } + + // Adjust azimuth (wraps around) + if (azimLeft) { + lightAzimuth -= tpf; + changed = true; + } + if (azimRight) { + lightAzimuth += tpf; + changed = true; + } + + if (changed) { + updateLightDirection(); + updateStatusText(); + } + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java index a631f9eac8..600e60cc95 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java +++ b/jme3-examples/src/main/java/jme3test/light/TestShadowsPerf.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,6 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; @@ -45,25 +44,20 @@ import com.jme3.math.Vector3f; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; import com.jme3.shadow.DirectionalLightShadowRenderer; import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestShadowsPerf extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; - public static void main(String[] args) { TestShadowsPerf app = new TestShadowsPerf(); app.start(); } - Geometry sphere; - Material mat; + private Geometry sphere; + private Material mat; @Override public void simpleInitApp() { @@ -90,7 +84,7 @@ public void simpleInitApp() { Sphere sphMesh = new Sphere(32, 32, 1); sphMesh.setTextureMode(Sphere.TextureMode.Projected); sphMesh.updateGeometry(32, 32, 1, false, false); - TangentBinormalGenerator.generate(sphMesh); + MikktspaceTangentGenerator.generate(sphMesh); sphere = new Geometry("Rock Ball", sphMesh); sphere.setLocalTranslation(0, 5, 0); @@ -111,7 +105,7 @@ public void simpleInitApp() { rootNode.addLight(al); //rootNode.setShadowMode(ShadowMode.CastAndReceive); - createballs(); + createBalls(); final DirectionalLightShadowRenderer pssmRenderer = new DirectionalLightShadowRenderer(assetManager, 1024, 4); viewPort.addProcessor(pssmRenderer); @@ -130,22 +124,23 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("display") && isPressed) { //pssmRenderer.debugFrustrums(); System.out.println("tetetetet"); } if (name.equals("add") && isPressed) { - createballs(); + createBalls(); } } }, "display", "add"); inputManager.addMapping("display", new KeyTrigger(KeyInput.KEY_SPACE)); inputManager.addMapping("add", new KeyTrigger(KeyInput.KEY_RETURN)); } - int val = 0; + private int val = 0; - private void createballs() { + private void createBalls() { System.out.println((frames / time) + ";" + val); @@ -164,15 +159,15 @@ private void createballs() { time = 0; frames = 0; } - float time; - float frames = 0; + private float time; + private float frames = 0; @Override public void simpleUpdate(float tpf) { time += tpf; frames++; if (time > 1) { - createballs(); + createBalls(); } } } diff --git a/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java index 840aa2f6ef..dba24ad18d 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSimpleLighting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,13 +43,13 @@ import com.jme3.scene.Geometry; import com.jme3.scene.shape.Sphere; import com.jme3.util.MaterialDebugAppState; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestSimpleLighting extends SimpleApplication { - float angle; - PointLight pl; - Geometry lightMdl; + private float angle; + private PointLight pl; + private Geometry lightMdl; public static void main(String[] args){ TestSimpleLighting app = new TestSimpleLighting(); @@ -59,7 +59,7 @@ public static void main(String[] args){ @Override public void simpleInitApp() { Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); - TangentBinormalGenerator.generate(teapot.getMesh(), true); + MikktspaceTangentGenerator.generate(teapot.getMesh()); teapot.setLocalScale(2f); Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java index 0cd4512cd1..ed53b0de35 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,19 +43,19 @@ import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; import com.jme3.texture.Texture.WrapMode; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestSpotLight extends SimpleApplication { - private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); + final private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); public static void main(String[] args){ TestSpotLight app = new TestSpotLight(); app.start(); } - SpotLight spot; - Geometry lightMdl; + private SpotLight spot; + private Geometry lightMdl; public void setupLighting(){ AmbientLight al=new AmbientLight(); al.setColor(ColorRGBA.White.mult(0.02f)); @@ -101,7 +101,7 @@ public void setupFloor(){ Box floor = new Box(50, 1f, 50); - TangentBinormalGenerator.generate(floor); + MikktspaceTangentGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); floorGeom.setMaterial(mat); @@ -120,7 +120,7 @@ public void setupSignpost(){ signpost.setLocalTranslation(12, 3.5f, 30); signpost.setLocalScale(4); signpost.setShadowMode(ShadowMode.CastAndReceive); - TangentBinormalGenerator.generate(signpost); + MikktspaceTangentGenerator.generate(signpost); rootNode.attachChild(signpost); } @@ -137,7 +137,7 @@ public void simpleInitApp() { } - float angle; + private float angle; @Override public void simpleUpdate(float tpf) { diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java index 254bfb0284..11dd519ef3 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightShadows.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,18 +49,18 @@ import com.jme3.shadow.SpotLightShadowFilter; import com.jme3.shadow.SpotLightShadowRenderer; import com.jme3.texture.Texture.WrapMode; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestSpotLightShadows extends SimpleApplication { - private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); + final private Vector3f lightTarget = new Vector3f(12, 3.5f, 30); public static void main(String[] args) { TestSpotLightShadows app = new TestSpotLightShadows(); app.start(); } - SpotLight spot; - Geometry lightMdl; + private SpotLight spot; + private Geometry lightMdl; public void setupLighting() { AmbientLight al = new AmbientLight(); @@ -99,7 +99,7 @@ public void setupLighting() { final SpotLightShadowRenderer slsr = new SpotLightShadowRenderer(assetManager, 512); slsr.setLight(spot); - slsr.setShadowIntensity(0.5f); + slsr.setShadowIntensity(.7f); slsr.setShadowZExtend(100); slsr.setShadowZFadeLength(5); slsr.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); @@ -107,7 +107,7 @@ public void setupLighting() { SpotLightShadowFilter slsf = new SpotLightShadowFilter(assetManager, 512); slsf.setLight(spot); - slsf.setShadowIntensity(0.5f); + slsf.setShadowIntensity(.7f); slsf.setShadowZExtend(100); slsf.setShadowZFadeLength(5); slsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); @@ -120,6 +120,7 @@ public void setupLighting() { ShadowTestUIManager uiMan = new ShadowTestUIManager(assetManager, slsr, slsf, guiNode, inputManager, viewPort); inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("stop") && isPressed) { stop = !stop; @@ -148,7 +149,7 @@ public void setupFloor() { Box floor = new Box(50, 1f, 50); - TangentBinormalGenerator.generate(floor); + MikktspaceTangentGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); floorGeom.setMaterial(mat); @@ -165,7 +166,7 @@ public void setupSignpost() { signpost.setLocalTranslation(12, 3.5f, 30); signpost.setLocalScale(4); signpost.setShadowMode(ShadowMode.CastAndReceive); - TangentBinormalGenerator.generate(signpost); + MikktspaceTangentGenerator.generate(signpost); rootNode.attachChild(signpost); } @@ -181,8 +182,8 @@ public void simpleInitApp() { } - float angle; - boolean stop = true; + private float angle; + private boolean stop = true; @Override public void simpleUpdate(float tpf) { diff --git a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java index 43e7beba29..9fa5a0ea36 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java +++ b/jme3-examples/src/main/java/jme3test/light/TestSpotLightTerrain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,16 +33,13 @@ import com.jme3.app.SimpleApplication; import com.jme3.bounding.BoundingBox; -import com.jme3.font.BitmapText; import com.jme3.light.AmbientLight; -import com.jme3.light.PointLight; import com.jme3.light.SpotLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainQuad; @@ -60,20 +57,10 @@ */ public class TestSpotLightTerrain extends SimpleApplication { - private TerrainQuad terrain; - Material matTerrain; - Material matWire; - boolean wireframe = false; - boolean triPlanar = false; - boolean wardiso = false; - boolean minnaert = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; - SpotLight sl; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; + private SpotLight sl; public static void main(String[] args) { TestSpotLightTerrain app = new TestSpotLightTerrain(); @@ -115,7 +102,8 @@ public void simpleUpdate(float tpf) { private void makeTerrain() { // TERRAIN TEXTURE material - matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matTerrain = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); matTerrain.setBoolean("useTriPlanarMapping", false); matTerrain.setBoolean("WardIso", true); @@ -170,7 +158,8 @@ private void makeTerrain() { matTerrain.setTexture("NormalMap_4", normalMap2); // WIREFRAME material - matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material matWire = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); matWire.getAdditionalRenderState().setWireframe(true); matWire.setColor("Color", ColorRGBA.Green); @@ -188,7 +177,8 @@ private void makeTerrain() { e.printStackTrace(); } - terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); control.setLodCalculator( new DistanceLodCalculator(65, 2.7f) ); terrain.addControl(control); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java b/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java index eb0353962f..d173eeafd8 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentCube.java @@ -40,7 +40,7 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; /** * @@ -57,7 +57,7 @@ public static void main(String... args) { public void simpleInitApp() { Box aBox = new Box(1, 1, 1); Geometry aGeometry = new Geometry("Box", aBox); - TangentBinormalGenerator.generate(aBox); + MikktspaceTangentGenerator.generate(aBox); Material aMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); aMaterial.setTexture("DiffuseMap", @@ -74,7 +74,7 @@ public void simpleInitApp() { aGeometry.rotate(FastMath.QUARTER_PI, FastMath.QUARTER_PI, 0.0f); rootNode.attachChild(aGeometry); - /** + /* * Must add a light to make the lit object visible! */ PointLight aLight = new PointLight(); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java index 042a85705a..2337c0bca9 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,6 @@ import com.jme3.app.SimpleApplication; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; @@ -46,17 +45,15 @@ import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Sphere; import com.jme3.util.BufferUtils; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.TangentUtils; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + import java.nio.FloatBuffer; import java.nio.IntBuffer; public class TestTangentGen extends SimpleApplication { - float angle; - PointLight pl; - Geometry lightMdl; - public static void main(String[] args){ TestTangentGen app = new TestTangentGen(); app.start(); @@ -84,7 +81,7 @@ public void simpleInitApp() { } private void addMesh(String name, Mesh mesh, Vector3f translation) { - TangentBinormalGenerator.generate(mesh); + MikktspaceTangentGenerator.generate(mesh); Geometry testGeom = new Geometry(name, mesh); Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m"); @@ -94,7 +91,7 @@ private void addMesh(String name, Mesh mesh, Vector3f translation) { Geometry debug = new Geometry( "Debug " + name, - TangentBinormalGenerator.genTbnLines(mesh, 0.08f) + TangentUtils.genTbnLines(mesh, 0.08f) ); Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); debug.setMaterial(debugMat); @@ -129,9 +126,9 @@ private Mesh createTriangleStripMesh() { IntBuffer ib = BufferUtils.createIntBuffer(indexes.length); ib.put(indexes); strip.setBuffer(Type.Position, 3, vb); - strip.setBuffer(Type.Normal, 3, nb); - strip.setBuffer(Type.TexCoord, 2, tb); - strip.setBuffer(Type.Index, 3, ib); + strip.setBuffer(Type.Normal, 3, nb); + strip.setBuffer(Type.TexCoord, 2, tb); + strip.setBuffer(Type.Index, 3, ib); strip.updateBound(); return strip; } diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java deleted file mode 100644 index 21817e7c83..0000000000 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadModels.java +++ /dev/null @@ -1,135 +0,0 @@ -package jme3test.light; - -import com.jme3.app.SimpleApplication; -import com.jme3.font.BitmapText; -import com.jme3.input.KeyInput; -import com.jme3.input.controls.ActionListener; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Vector3f; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.*; -import com.jme3.scene.Spatial.CullHint; -import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; - -/** - * - * @author Kirusha - */ -public class TestTangentGenBadModels extends SimpleApplication { - - float angle; - PointLight pl; - Geometry lightMdl; - - public static void main(String[] args){ - TestTangentGenBadModels app = new TestTangentGenBadModels(); - app.start(); - } - - @Override - public void simpleInitApp() { -// assetManager.registerLocator("http://jme-glsl-shaders.googlecode.com/hg/assets/Models/LightBlow/", UrlLocator.class); -// assetManager.registerLocator("http://jmonkeyengine.googlecode.com/files/", UrlLocator.class); - - final Spatial badModel = assetManager.loadModel("Models/TangentBugs/test.blend"); -// badModel.setLocalScale(1f); - - Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png")); -// Material mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m"); - badModel.setMaterial(mat); - rootNode.attachChild(badModel); - - // TODO: For some reason blender loader fails to load this. - // need to check it -// Spatial model = assetManager.loadModel("test.blend"); -// rootNode.attachChild(model); - - final Node debugTangents = new Node("debug tangents"); - debugTangents.setCullHint(CullHint.Always); - rootNode.attachChild(debugTangents); - - final Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); - - badModel.depthFirstTraversal(new SceneGraphVisitorAdapter(){ - @Override - public void visit(Geometry g){ - Mesh m = g.getMesh(); - Material mat = g.getMaterial(); - -// if (mat.getParam("DiffuseMap") != null){ -// mat.setTexture("DiffuseMap", null); -// } - TangentBinormalGenerator.generate(m); - - Geometry debug = new Geometry( - "debug tangents geom", - TangentBinormalGenerator.genTbnLines(g.getMesh(), 0.2f) - ); - debug.setMaterial(debugMat); - debug.setCullHint(Spatial.CullHint.Never); - debug.setLocalTransform(g.getWorldTransform()); - debugTangents.attachChild(debug); - } - }); - - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.8f, -0.6f, -0.08f).normalizeLocal()); - dl.setColor(new ColorRGBA(1,1,1,1)); - rootNode.addLight(dl); - - lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); - lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); - lightMdl.getMesh().setStatic(); - rootNode.attachChild(lightMdl); - - pl = new PointLight(); - pl.setColor(ColorRGBA.White); -// rootNode.addLight(pl); - - - BitmapText info = new BitmapText(guiFont); - info.setText("Press SPACE to switch between lighting and tangent display"); - info.setQueueBucket(Bucket.Gui); - info.move(0, settings.getHeight() - info.getLineHeight(), 0); - rootNode.attachChild(info); - - inputManager.addMapping("space", new KeyTrigger(KeyInput.KEY_SPACE)); - inputManager.addListener(new ActionListener() { - - private boolean isLit = true; - - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) return; - Material mat; - if (isLit){ - mat = assetManager.loadMaterial("Textures/BumpMapTest/Tangent.j3m"); - debugTangents.setCullHint(CullHint.Inherit); - }else{ - mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - mat.setTexture("NormalMap", assetManager.loadTexture("Models/TangentBugs/test_normal.png")); - debugTangents.setCullHint(CullHint.Always); - } - isLit = !isLit; - badModel.setMaterial(mat); - } - }, "space"); - } - - @Override - public void simpleUpdate(float tpf){ - angle += tpf; - angle %= FastMath.TWO_PI; - - pl.setPosition(new Vector3f(FastMath.cos(angle) * 2f, 2f, FastMath.sin(angle) * 2f)); - lightMdl.setLocalTranslation(pl.getPosition()); - } - - -} diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java index 6b7a630e88..e791d30889 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentGenBadUV.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,13 +42,14 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.TangentUtils; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestTangentGenBadUV extends SimpleApplication { - float angle; - PointLight pl; - Geometry lightMdl; + private float angle; + private PointLight pl; + private Geometry lightMdl; public static void main(String[] args){ TestTangentGenBadUV app = new TestTangentGenBadUV(); @@ -60,7 +61,7 @@ public void simpleInitApp() { Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); if (teapot instanceof Geometry){ Geometry g = (Geometry) teapot; - TangentBinormalGenerator.generate(g.getMesh()); + MikktspaceTangentGenerator.generate(g.getMesh()); }else{ throw new RuntimeException(); } @@ -71,7 +72,7 @@ public void simpleInitApp() { Geometry debug = new Geometry( "Debug Teapot", - TangentBinormalGenerator.genTbnLines(((Geometry) teapot).getMesh(), 0.03f) + TangentUtils.genTbnLines(((Geometry) teapot).getMesh(), 0.03f) ); Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); debug.setMaterial(debugMat); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java index 9f8daf8e77..d3201cce99 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTangentSpace.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.light; import com.jme3.app.SimpleApplication; @@ -13,6 +44,7 @@ import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.TangentUtils; import com.jme3.util.mikktspace.MikktspaceTangentGenerator; /** @@ -27,7 +59,7 @@ public static void main(String[] args) { app.start(); } - private Node debugNode = new Node("debug"); + final private Node debugNode = new Node("debug"); @Override public void simpleInitApp() { @@ -89,7 +121,7 @@ private void initView() { private void createDebugTangents(Geometry geom) { Geometry debug = new Geometry( "Debug " + geom.getName(), - TangentBinormalGenerator.genTbnLines(geom.getMesh(), 0.8f) + TangentUtils.genTbnLines(geom.getMesh(), 0.8f) ); Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); debug.setMaterial(debugMat); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java index 23fe73c12e..16229161d9 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTransparentShadow.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,12 +45,12 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.scene.shape.Sphere; import com.jme3.shadow.CompareMode; import com.jme3.shadow.DirectionalLightShadowRenderer; import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestTransparentShadow extends SimpleApplication { @@ -59,21 +59,24 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { cam.setLocation(new Vector3f(5.700248f, 6.161693f, 5.1404157f)); cam.setRotation(new Quaternion(-0.09441641f, 0.8993388f, -0.24089815f, -0.35248178f)); viewPort.setBackgroundColor(ColorRGBA.DarkGray); - Quad q = new Quad(20, 20); - q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); - TangentBinormalGenerator.generate(q); - Geometry geom = new Geometry("floor", q); + RectangleMesh rm = new RectangleMesh( + new Vector3f(-10, 0, 10), + new Vector3f(10, 0, 10), + new Vector3f(-10, 0, -10) + ); + rm.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(10)); + MikktspaceTangentGenerator.generate(rm); + + Geometry geom = new Geometry("floor", rm); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); geom.setMaterial(mat); - - geom.rotate(-FastMath.HALF_PI, 0, 0); - geom.center(); geom.setShadowMode(ShadowMode.CastAndReceive); rootNode.attachChild(geom); @@ -93,7 +96,7 @@ public void simpleInitApp() { rootNode.attachChild(tree); - // Uses Texture from jme3-test-data library! + // Uses Texture from jme3-testdata library! ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); diff --git a/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java b/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java index 2d3ff4bccf..476c261ef6 100644 --- a/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/TestTwoSideLighting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,18 +44,18 @@ import com.jme3.scene.Geometry; import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; /** - * Checks two sided lighting capability. + * Checks two-sided lighting capability. * * @author Kirill Vainer */ public class TestTwoSideLighting extends SimpleApplication { - float angle; - PointLight pl; - Geometry lightMdl; + private float angle; + private PointLight pl; + private Geometry lightMdl; public static void main(String[] args){ TestTwoSideLighting app = new TestTwoSideLighting(); @@ -80,7 +80,7 @@ public void simpleInitApp() { quadGeom.setMaterial(mat1); // SimpleBump material requires tangents. - TangentBinormalGenerator.generate(quadGeom); + MikktspaceTangentGenerator.generate(quadGeom); rootNode.attachChild(quadGeom); Geometry teapot = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); diff --git a/jme3-examples/src/main/java/jme3test/light/package-info.java b/jme3-examples/src/main/java/jme3test/light/package-info.java new file mode 100644 index 0000000000..a896fc8170 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for lighting + */ +package jme3test.light; diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java b/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java index f0d100ee23..038fca3fe6 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/ConsoleProgressReporter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ public class ConsoleProgressReporter extends JobProgressAdapter{ private static final Logger logger = Logger.getLogger(ConsoleProgressReporter.class.getName()); - long time; + private long time; @Override public void start() { @@ -53,8 +53,10 @@ public void start() { } @Override - public void progress(double value) { - logger.log(Level.INFO, "Progress : {0}%", (value * 100)); + public void progress(double value) { + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Progress : {0}%", (value * 100)); + } } @Override @@ -65,7 +67,9 @@ public void step(String message) { @Override public void done(LightProbe result) { long end = System.currentTimeMillis(); - logger.log(Level.INFO, "Generation done in {0}", ((float)(end - time) / 1000f)); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Generation done in {0}", (end - time) / 1000f); + } } } diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java index 051202cdac..ff32877ad8 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/RefEnv.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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; @@ -9,7 +40,6 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.LightProbe; -import com.jme3.light.SphereProbeArea; import com.jme3.material.Material; import com.jme3.math.*; import com.jme3.scene.*; @@ -117,8 +147,9 @@ private void switchMat(Spatial s) { public void simpleUpdate(float tpf) { frame++; + EnvironmentCamera eCam = stateManager.getState(EnvironmentCamera.class); if (frame == 2) { - final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, EnvMapUtils.GenerationType.Fast, new JobProgressAdapter() { + final LightProbe probe = LightProbeFactory.makeProbe(eCam, rootNode, EnvMapUtils.GenerationType.Fast, new JobProgressAdapter() { @Override public void done(LightProbe result) { @@ -127,9 +158,13 @@ public void done(LightProbe result) { rootNode.getChild(0).setCullHint(Spatial.CullHint.Dynamic); } }); - ((SphereProbeArea) probe.getArea()).setRadius(100); + probe.getArea().setRadius(100); rootNode.addLight(probe); } + + if (eCam.isBusy()) { + System.out.println("EnvironmentCamera busy as of frame " + frame); + } } } diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1340.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1340.java new file mode 100644 index 0000000000..9a9c79d2eb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestIssue1340.java @@ -0,0 +1,92 @@ +package jme3test.light.pbr; + +import com.jme3.anim.SkinningControl; +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.UrlLocator; +import com.jme3.environment.EnvironmentCamera; +import com.jme3.environment.LightProbeFactory; +import com.jme3.environment.generation.JobProgressAdapter; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; + +/** + * This test case validates a shader compilation fix with a model using a PBR material in combination with a + * SkinningControl. When you run this application and don't see a RenderException, the test is successful. + * For a detailed explanation consult the GitHub issue: https://github.com/jMonkeyEngine/jmonkeyengine/issues/1340 + * -rvandoosselaer + */ +public class TestIssue1340 extends SimpleApplication { + + private Spatial model; + private int frame; + + public static void main(String[] args) { + TestIssue1340 testIssue1340 = new TestIssue1340(); + testIssue1340.setSettings(createSettings()); + testIssue1340.start(); + } + + private static AppSettings createSettings() { + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL32); + return settings; + } + + @Override + public void simpleInitApp() { + stateManager.attach(new EnvironmentCamera(32)); + + DirectionalLight light = new DirectionalLight(Vector3f.UNIT_Y.negate(), ColorRGBA.White); + rootNode.addLight(light); + + assetManager.registerLocator("https://github.com/KhronosGroup/glTF-Sample-Models/raw/master/2.0/RiggedFigure/", UrlLocator.class); + + model = assetManager.loadModel("/glTF-Embedded/RiggedFigure.gltf"); + SkinningControl skinningControl = getSkinningControl(model); + if (skinningControl == null || !skinningControl.isHardwareSkinningPreferred()) { + throw new IllegalArgumentException("This test case requires a model with a SkinningControl and with Hardware skinning preferred!"); + } + + viewPort.setBackgroundColor(ColorRGBA.LightGray); + } + + @Override + public void simpleUpdate(float tpf) { + frame++; + if (frame == 2) { + LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() { + @Override + public void done(LightProbe result) { + enqueue(() -> { + rootNode.attachChild(model); + rootNode.addLight(result); + }); + } + }); + } + } + + private SkinningControl getSkinningControl(Spatial model) { + SkinningControl control = model.getControl(SkinningControl.class); + if (control != null) { + return control; + } + + if (model instanceof Node) { + for (Spatial child : ((Node) model).getChildren()) { + SkinningControl skinningControl = getSkinningControl(child); + if (skinningControl != null) { + return skinningControl; + } + } + } + + return null; + } + +} 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/light/pbr/TestPBRLighting.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java index 9ebd1a8264..50fb4a482b 100644 --- a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRLighting.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ import com.jme3.app.SimpleApplication; import com.jme3.environment.EnvironmentCamera; import com.jme3.environment.LightProbeFactory; +import com.jme3.environment.FastLightProbeFactory; import com.jme3.environment.generation.JobProgressAdapter; import com.jme3.environment.util.EnvMapUtils; import com.jme3.environment.util.LightsDebugState; @@ -59,7 +60,8 @@ * @author nehon */ public class TestPBRLighting extends SimpleApplication { - + private static final boolean USE_ACCELERATED_BAKING=true; + private static final int RESOLUTION=256; public static void main(String[] args) { TestPBRLighting app = new TestPBRLighting(); app.start(); @@ -79,7 +81,7 @@ public void simpleInitApp() { assetManager.registerLoader(KTXLoader.class, "ktx"); viewPort.setBackgroundColor(ColorRGBA.White); - modelNode = (Node) new Node("modelNode"); + modelNode = new Node("modelNode"); model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); MikktspaceTangentGenerator.generate(model); modelNode.attachChild(model); @@ -111,7 +113,7 @@ public void simpleInitApp() { model.setMaterial(pbrMat); - final EnvironmentCamera envCam = new EnvironmentCamera(256, new Vector3f(0, 3f, 0)); + final EnvironmentCamera envCam = new EnvironmentCamera(RESOLUTION, new Vector3f(0, 3f, 0)); stateManager.attach(envCam); // EnvironmentManager envManager = new EnvironmentManager(); @@ -199,18 +201,23 @@ public void simpleUpdate(float tpf) { if (frame == 2) { modelNode.removeFromParent(); - final LightProbe probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() { + LightProbe probe; - @Override - public void done(LightProbe result) { - System.err.println("Done rendering env maps"); - tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); - } - }); - ((SphereProbeArea) probe.getArea()).setRadius(100); + if (USE_ACCELERATED_BAKING) { + probe = FastLightProbeFactory.makeProbe(renderManager, assetManager, RESOLUTION, Vector3f.ZERO, 1f, 1000f, rootNode); + } else { + probe = LightProbeFactory.makeProbe(stateManager.getState(EnvironmentCamera.class), rootNode, new JobProgressAdapter() { + + @Override + public void done(LightProbe result) { + System.err.println("Done rendering env maps"); + tex = EnvMapUtils.getCubeMapCrossDebugViewWithMipMaps(result.getPrefilteredEnvMap(), assetManager); + } + }); + } + probe.getArea().setRadius(100); rootNode.addLight(probe); //getStateManager().getState(EnvironmentManager.class).addEnvProbe(probe); - } if (frame > 10 && modelNode.getParent() == null) { rootNode.attachChild(modelNode); diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java new file mode 100644 index 0000000000..4070c2a401 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/TestPBRSimple.java @@ -0,0 +1,101 @@ +/* + * 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.light.pbr; + + +import com.jme3.app.SimpleApplication; +import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.input.ChaseCamera; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.util.SkyFactory; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +/** + * A simpler PBR example that uses EnvironmentProbeControl to bake the environment + */ +public class TestPBRSimple extends SimpleApplication { + private boolean REALTIME_BAKING = false; + + public static void main(String[] args) { + new TestPBRSimple().start(); + } + + @Override + public void simpleInitApp() { + + + Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); + MikktspaceTangentGenerator.generate(model); + + Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); + model.setMaterial(pbrMat); + rootNode.attachChild(model); + + ChaseCamera chaseCam = new ChaseCamera(cam, model, inputManager); + chaseCam.setDragToRotate(true); + chaseCam.setMinVerticalRotation(-FastMath.HALF_PI); + chaseCam.setMaxDistance(1000); + chaseCam.setSmoothMotion(true); + chaseCam.setRotationSensitivity(10); + chaseCam.setZoomSensitivity(5); + flyCam.setEnabled(false); + + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + rootNode.attachChild(sky); + + // Create baker control + EnvironmentProbeControl envProbe=new EnvironmentProbeControl(assetManager,256); + rootNode.addControl(envProbe); + + // Tag the sky, only the tagged spatials will be rendered in the env map + envProbe.tag(sky); + + + + } + + + float lastBake = 0; + @Override + public void simpleUpdate(float tpf) { + if (REALTIME_BAKING) { + lastBake += tpf; + if (lastBake > 1.4f) { + rootNode.getControl(EnvironmentProbeControl.class).rebake(); + lastBake = 0; + } + } + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/light/pbr/package-info.java b/jme3-examples/src/main/java/jme3test/light/pbr/package-info.java new file mode 100644 index 0000000000..399267e70a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/light/pbr/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for physically based rendering (PBR) + */ +package jme3test.light.pbr; diff --git a/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java index 49028ba7ed..645dd074f5 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java +++ b/jme3-examples/src/main/java/jme3test/material/TestBumpModel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,7 +35,6 @@ import com.jme3.app.SimpleApplication; import com.jme3.light.DirectionalLight; import com.jme3.light.PointLight; -import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Vector3f; @@ -43,13 +42,13 @@ import com.jme3.scene.Spatial; import com.jme3.scene.plugins.ogre.OgreMeshKey; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestBumpModel extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; + private float angle; + private PointLight pl; + private Spatial lightMdl; public static void main(String[] args){ TestBumpModel app = new TestBumpModel(); @@ -58,16 +57,16 @@ public static void main(String[] args){ @Override public void simpleInitApp() { - Spatial signpost = (Spatial) assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml")); - signpost.setMaterial( (Material) assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m")); - TangentBinormalGenerator.generate(signpost); + Spatial signpost = assetManager.loadAsset(new OgreMeshKey("Models/Sign Post/Sign Post.mesh.xml")); + signpost.setMaterial(assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m")); + MikktspaceTangentGenerator.generate(signpost); rootNode.attachChild(signpost); lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); - lightMdl.setMaterial( (Material) assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); rootNode.attachChild(lightMdl); - // flourescent main light + // fluorescent main light pl = new PointLight(); pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); rootNode.addLight(pl); diff --git a/jme3-examples/src/main/java/jme3test/material/TestGeometryShader.java b/jme3-examples/src/main/java/jme3test/material/TestGeometryShader.java index 04c8223f10..0199d70cc2 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestGeometryShader.java +++ b/jme3-examples/src/main/java/jme3test/material/TestGeometryShader.java @@ -8,6 +8,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; import com.jme3.util.BufferUtils; /** @@ -37,6 +38,9 @@ public void simpleInitApp() { public static void main(String[] args) { TestGeometryShader app = new TestGeometryShader(); + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL33); + app.setSettings(settings); app.start(); } } diff --git a/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java b/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java index 6124d9eddd..2d599ef2f5 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java +++ b/jme3-examples/src/main/java/jme3test/material/TestMatParamOverride.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ */ public class TestMatParamOverride extends SimpleApplication { - private Box box = new Box(1, 1, 1); + final private Box box = new Box(1, 1, 1); final MatParamOverride overrideYellow = new MatParamOverride(VarType.Vector4, "Color", ColorRGBA.Yellow); diff --git a/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java index c955b3467a..e8ca464d7e 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java +++ b/jme3-examples/src/main/java/jme3test/material/TestNormalMapping.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,13 +41,13 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestNormalMapping extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; + private float angle; + private PointLight pl; + private Spatial lightMdl; public static void main(String[] args){ TestNormalMapping app = new TestNormalMapping(); @@ -59,7 +59,7 @@ public void simpleInitApp() { Sphere sphMesh = new Sphere(32, 32, 1); sphMesh.setTextureMode(Sphere.TextureMode.Projected); sphMesh.updateGeometry(32, 32, 1, false, false); - TangentBinormalGenerator.generate(sphMesh); + MikktspaceTangentGenerator.generate(sphMesh); Geometry sphere = new Geometry("Rock Ball", sphMesh); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); diff --git a/jme3-examples/src/main/java/jme3test/material/TestNormalMappingConsistency.java b/jme3-examples/src/main/java/jme3test/material/TestNormalMappingConsistency.java new file mode 100644 index 0000000000..3595d7b34f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestNormalMappingConsistency.java @@ -0,0 +1,206 @@ +/* + * Copyright (c) 2009-2021 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.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapText; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.material.TechniqueDef.LightMode; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.plugins.gltf.GltfModelKey; +import com.jme3.system.AppSettings; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; + +/** + * This test cycles through a model exported in different formats and with different materials with tangents + * generated in different ways. The normal map should look correct in all cases. Refer to + * https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/NormalTangentMirrorTest for details on + * the correct result and debugging. + */ +public class TestNormalMappingConsistency extends SimpleApplication { + Node probeNode; + DirectionalLight light; + BitmapText materialTxt; + BitmapText modelTxt; + boolean flipTextures = false; + float t = -1; + int modelType = 0; + int materialType = 0; + Spatial loadedSpatial; + + final int maxModels = 4; + final int maxMaterials = 3; + + public static void main(String[] args) { + AppSettings sett = new AppSettings(true); + sett.setWidth(1024); + sett.setHeight(768); + TestNormalMappingConsistency app = new TestNormalMappingConsistency(); + app.setSettings(sett); + app.start(); + } + + @Override + public void simpleInitApp() { + setPauseOnLostFocus(false); + flyCam.setMoveSpeed(20); + viewPort.setBackgroundColor(new ColorRGBA().setAsSrgb(0.2f, 0.2f, 0.2f, 1.0f)); + probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); + rootNode.attachChild(probeNode); + + probeNode.addLight(new AmbientLight(ColorRGBA.Gray)); + light = new DirectionalLight(new Vector3f(-1, -1, -1), ColorRGBA.White); + rootNode.addLight(light); + + modelTxt = new BitmapText(guiFont); + modelTxt.setSize(guiFont.getCharSet().getRenderedSize()); + modelTxt.setLocalTranslation(0, 700, 0); + guiNode.attachChild(modelTxt); + + materialTxt = new BitmapText(guiFont); + materialTxt.setSize(guiFont.getCharSet().getRenderedSize()); + materialTxt.setLocalTranslation(300, 700, 0); + guiNode.attachChild(materialTxt); + } + + @Override + public void simpleUpdate(float tpf) { + if (t == -1 || t > 5) { + t = 0; + loadModel(new Vector3f(0, 0, 0), 3, modelType, materialType); + materialType++; + if (materialType >= maxMaterials) { + materialType = 0; + modelType++; + if (modelType >= maxModels) { + modelType = 0; + } + } + } + t += tpf; + + } + + private void loadModel(Vector3f offset, float scale, int modelType, int materialType) { + if (loadedSpatial != null) { + loadedSpatial.removeFromParent(); + } + + + if (modelType == 0) loadedSpatial = loadGltf(); + else if (modelType == 1) loadedSpatial = loadGltfGen(); + else if (modelType == 2) loadedSpatial = loadOgre(); + else if (modelType == 3) loadedSpatial = loadOgreGen(); + + loadedSpatial.scale(scale); + loadedSpatial.move(offset); + if (materialType == 0) loadedSpatial.setMaterial(createPBRLightingMat()); + else if (materialType == 1) loadedSpatial.setMaterial(createSPLightingMat()); + else if (materialType == 2) loadedSpatial.setMaterial(createLightingMat()); + probeNode.attachChild(loadedSpatial); + } + + private Spatial loadGltf() { + GltfModelKey k = new GltfModelKey("jme3test/normalmapCompare/NormalTangentMirrorTest.gltf"); + Spatial sp = assetManager.loadModel(k); + modelTxt.setText("GLTF"); + return sp; + } + + private Spatial loadGltfGen() { + GltfModelKey k = new GltfModelKey("jme3test/normalmapCompare/NormalTangentMirrorTest.gltf"); + Spatial sp = assetManager.loadModel(k); + MikktspaceTangentGenerator.generate(loadedSpatial); + modelTxt.setText("GLTF - regen tg"); + return sp; + } + + private Spatial loadOgre() { + GltfModelKey k = new GltfModelKey("jme3test/normalmapCompare/ogre/NormalTangentMirrorTest.scene"); + Spatial sp = assetManager.loadModel(k); + modelTxt.setText("OGRE"); + return sp; + } + + private Spatial loadOgreGen() { + GltfModelKey k = new GltfModelKey("jme3test/normalmapCompare/ogre/NormalTangentMirrorTest.scene"); + Spatial sp = assetManager.loadModel(k); + MikktspaceTangentGenerator.generate(loadedSpatial); + modelTxt.setText("OGRE - regen tg"); + return sp; + } + + private Material createPBRLightingMat() { + renderManager.setPreferredLightMode(LightMode.SinglePassAndImageBased); + Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setTexture("BaseColorMap", assetManager.loadTexture(new TextureKey( + "jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png", flipTextures))); + mat.setTexture("NormalMap", assetManager.loadTexture( + new TextureKey("jme3test/normalmapCompare/NormalTangentTest_Normal.png", flipTextures))); + mat.setFloat("NormalType", 1); + materialTxt.setText("PBR Lighting"); + return mat; + } + + private Material createSPLightingMat() { + renderManager.setPreferredLightMode(LightMode.SinglePass); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setTexture("DiffuseMap", assetManager.loadTexture(new TextureKey( + "jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png", flipTextures))); + mat.setTexture("NormalMap", assetManager.loadTexture( + new TextureKey("jme3test/normalmapCompare/NormalTangentTest_Normal.png", flipTextures))); + mat.setFloat("NormalType", 1); + materialTxt.setText("SP Lighting"); + return mat; + } + + private Material createLightingMat() { + renderManager.setPreferredLightMode(LightMode.MultiPass); + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setTexture("DiffuseMap", assetManager.loadTexture(new TextureKey( + "jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png", flipTextures))); + mat.setTexture("NormalMap", assetManager.loadTexture( + new TextureKey("jme3test/normalmapCompare/NormalTangentTest_Normal.png", flipTextures))); + materialTxt.setText("Lighting"); + mat.setFloat("NormalType", 1); + + return mat; + } + +} diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallax.java b/jme3-examples/src/main/java/jme3test/material/TestParallax.java index cf5263ed2e..3715806f6d 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallax.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallax.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,11 +41,10 @@ import com.jme3.math.*; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.util.SkyFactory; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestParallax extends SimpleApplication { @@ -59,40 +58,35 @@ public static void main(String[] args) { public void setupSkyBox() { rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap)); } - DirectionalLight dl; public void setupLighting() { - - dl = new DirectionalLight(); + DirectionalLight dl = new DirectionalLight(); dl.setDirection(lightDir); dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); rootNode.addLight(dl); } - Material mat; + private Material mat; public void setupFloor() { mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); - - Node floorGeom = new Node("floorGeom"); - Quad q = new Quad(100, 100); - q.scaleTextureCoordinates(new Vector2f(10, 10)); - Geometry g = new Geometry("geom", q); - g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); - floorGeom.attachChild(g); - - - TangentBinormalGenerator.generate(floorGeom); - floorGeom.setLocalTranslation(-50, 22, 60); - //floorGeom.setLocalScale(100); - - floorGeom.setMaterial(mat); + + RectangleMesh rm = new RectangleMesh( + new Vector3f(-50, 22, 60), + new Vector3f(50, 22, 60), + new Vector3f(-50, 22, -40)); + rm.scaleTextureCoordinates(new Vector2f(10, 10)); + + Geometry floorGeom = new Geometry("floorGeom", rm); + MikktspaceTangentGenerator.generate(floorGeom); + floorGeom.setMaterial(mat); + rootNode.attachChild(floorGeom); } public void setupSignpost() { Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); Material matSp = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); - TangentBinormalGenerator.generate(signpost); + MikktspaceTangentGenerator.generate(signpost); signpost.setMaterial(matSp); signpost.rotate(0, FastMath.HALF_PI, 0); signpost.setLocalTranslation(12, 23.5f, 30); @@ -117,13 +111,13 @@ public void simpleInitApp() { @Override public void onAnalog(String name, float value, float tpf) { if ("heightUP".equals(name)) { - parallaxHeigh += 0.01; - mat.setFloat("ParallaxHeight", parallaxHeigh); + parallaxHeight += 0.01; + mat.setFloat("ParallaxHeight", parallaxHeight); } if ("heightDown".equals(name)) { - parallaxHeigh -= 0.01; - parallaxHeigh = Math.max(parallaxHeigh, 0); - mat.setFloat("ParallaxHeight", parallaxHeigh); + parallaxHeight -= 0.01; + parallaxHeight = Math.max(parallaxHeight, 0); + mat.setFloat("ParallaxHeight", parallaxHeight); } } @@ -143,9 +137,8 @@ public void onAction(String name, boolean isPressed, float tpf) { }, "toggleSteep"); inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE)); } - float parallaxHeigh = 0.05f; - float time = 0; - boolean steep = false; + private float parallaxHeight = 0.05f; + private boolean steep = false; @Override public void simpleUpdate(float tpf) { diff --git a/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java index 2a97a7054a..038066c68b 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java +++ b/jme3-examples/src/main/java/jme3test/material/TestParallaxPBR.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,15 +41,14 @@ import com.jme3.math.*; import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; -import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.util.SkyFactory; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestParallaxPBR extends SimpleApplication { - private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + final private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); public static void main(String[] args) { TestParallaxPBR app = new TestParallaxPBR(); @@ -59,7 +58,7 @@ public static void main(String[] args) { public void setupSkyBox() { rootNode.attachChild(SkyFactory.createSky(assetManager, "Scenes/Beach/FullskiesSunset0068.dds", SkyFactory.EnvMapType.CubeMap)); } - DirectionalLight dl; + private DirectionalLight dl; public void setupLighting() { @@ -68,32 +67,30 @@ public void setupLighting() { dl.setColor(new ColorRGBA(.9f, .9f, .9f, 1)); rootNode.addLight(dl); } - Material mat; + private Material mat; public void setupFloor() { mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR.j3m"); //mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWallPBR2.j3m"); - - Node floorGeom = new Node("floorGeom"); - Quad q = new Quad(100, 100); - q.scaleTextureCoordinates(new Vector2f(10, 10)); - Geometry g = new Geometry("geom", q); - g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); - floorGeom.attachChild(g); - - - TangentBinormalGenerator.generate(floorGeom); - floorGeom.setLocalTranslation(-50, 22, 60); + + RectangleMesh rm = new RectangleMesh( + new Vector3f(-50, 22, 60), + new Vector3f(50, 22, 60), + new Vector3f(-50, 22, -40)); + rm.scaleTextureCoordinates(new Vector2f(10, 10)); + + Geometry floorGeom = new Geometry("floorGeom", rm); + MikktspaceTangentGenerator.generate(floorGeom); //floorGeom.setLocalScale(100); - floorGeom.setMaterial(mat); + floorGeom.setMaterial(mat); rootNode.attachChild(floorGeom); } public void setupSignpost() { Spatial signpost = assetManager.loadModel("Models/Sign Post/Sign Post.mesh.xml"); Material mat = assetManager.loadMaterial("Models/Sign Post/Sign Post.j3m"); - TangentBinormalGenerator.generate(signpost); + MikktspaceTangentGenerator.generate(signpost); signpost.setMaterial(mat); signpost.rotate(0, FastMath.HALF_PI, 0); signpost.setLocalTranslation(12, 23.5f, 30); @@ -116,15 +113,16 @@ public void simpleInitApp() { inputManager.addListener(new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if ("heightUP".equals(name)) { - parallaxHeigh += 0.01; - mat.setFloat("ParallaxHeight", parallaxHeigh); + parallaxHeight += 0.01; + mat.setFloat("ParallaxHeight", parallaxHeight); } if ("heightDown".equals(name)) { - parallaxHeigh -= 0.01; - parallaxHeigh = Math.max(parallaxHeigh, 0); - mat.setFloat("ParallaxHeight", parallaxHeigh); + parallaxHeight -= 0.01; + parallaxHeight = Math.max(parallaxHeight, 0); + mat.setFloat("ParallaxHeight", parallaxHeight); } } @@ -134,6 +132,7 @@ public void onAnalog(String name, float value, float tpf) { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (isPressed && "toggleSteep".equals(name)) { steep = !steep; @@ -143,9 +142,8 @@ public void onAction(String name, boolean isPressed, float tpf) { }, "toggleSteep"); inputManager.addMapping("toggleSteep", new KeyTrigger(KeyInput.KEY_SPACE)); } - float parallaxHeigh = 0.05f; - float time = 0; - boolean steep = false; + private float parallaxHeight = 0.05f; + private boolean steep = false; @Override public void simpleUpdate(float tpf) { diff --git a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java index b1683fcffb..592663efeb 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java +++ b/jme3-examples/src/main/java/jme3test/material/TestShaderNodes.java @@ -23,8 +23,8 @@ public static void main(String[] args) { public void simpleInitApp() { flyCam.setMoveSpeed(20); Logger.getLogger("com.jme3").setLevel(Level.WARNING); - Box boxshape1 = new Box(1f, 1f, 1f); - Geometry cube_tex = new Geometry("A Textured Box", boxshape1); + Box boxShape1 = new Box(1f, 1f, 1f); + Geometry cube_tex = new Geometry("A Textured Box", boxShape1); Texture tex = assetManager.loadTexture("Interface/Logo/Monkey.jpg"); Material mat = new Material(assetManager, "Common/MatDefs/Misc/UnshadedNodes.j3md"); diff --git a/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java index 5be1adf682..6fb1f5037f 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java +++ b/jme3-examples/src/main/java/jme3test/material/TestSimpleBumps.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,14 +42,14 @@ import com.jme3.scene.Spatial; import com.jme3.scene.shape.Quad; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; // phong cutoff for light to normal angle > 90? public class TestSimpleBumps extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; + private float angle; + private PointLight pl; + private Spatial lightMdl; public static void main(String[] args){ TestSimpleBumps app = new TestSimpleBumps(); @@ -63,7 +63,7 @@ public void simpleInitApp() { Geometry sphere = new Geometry("Rock Ball", quadMesh); Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m"); sphere.setMaterial(mat); - TangentBinormalGenerator.generate(sphere); + MikktspaceTangentGenerator.generate(sphere); rootNode.attachChild(sphere); lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); diff --git a/jme3-examples/src/main/java/jme3test/material/TestTessellationShader.java b/jme3-examples/src/main/java/jme3test/material/TestTessellationShader.java index dbf93811f6..a5af58bfc5 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestTessellationShader.java +++ b/jme3-examples/src/main/java/jme3test/material/TestTessellationShader.java @@ -9,6 +9,7 @@ import com.jme3.scene.Mesh; import com.jme3.scene.VertexBuffer; import com.jme3.scene.shape.Quad; +import com.jme3.system.AppSettings; import com.jme3.util.BufferUtils; import java.util.concurrent.Callable; @@ -17,8 +18,8 @@ * Created by michael on 28.02.15. */ public class TestTessellationShader extends SimpleApplication { - Material tessellationMaterial; - int tessFactor=5; + private Material tessellationMaterial; + private int tessFactor=5; @Override public void simpleInitApp() { tessellationMaterial = new Material(getAssetManager(), "Materials/Tess/SimpleTess.j3md"); @@ -63,6 +64,10 @@ public Boolean call() throws Exception { } public static void main(String[] args) { - new TestTessellationShader().start(); + TestTessellationShader app = new TestTessellationShader(); + AppSettings settings = new AppSettings(true); + settings.setRenderer(AppSettings.LWJGL_OPENGL40); + app.setSettings(settings); + app.start(); } } diff --git a/jme3-examples/src/main/java/jme3test/material/TestUBO.java b/jme3-examples/src/main/java/jme3test/material/TestUBO.java new file mode 100644 index 0000000000..e24a1a7204 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/TestUBO.java @@ -0,0 +1,149 @@ +package jme3test.material; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Matrix3f; +import com.jme3.math.Matrix4f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.util.struct.Struct; +import com.jme3.util.struct.StructStd140BufferObject; +import com.jme3.util.struct.fields.ColorRGBAField; +import com.jme3.util.struct.fields.FloatField; +import com.jme3.util.struct.fields.IntField; +import com.jme3.util.struct.fields.Matrix3fField; +import com.jme3.util.struct.fields.Matrix4fField; +import com.jme3.util.struct.fields.SubStructArrayField; +import com.jme3.util.struct.fields.SubStructField; +import com.jme3.util.struct.fields.Vector3fField; + +/** + * This is an example of UBO usage and an unit test for the Struct Buffer Object. + * If everything works as expected, a green square should appear, if not the square will be red. + * + * RenderDOC can be used to see the UBO on the gpu. + */ +public class TestUBO extends SimpleApplication { + + public final static class PointLight implements Struct { + public final Vector3fField position = new Vector3fField(0, "position", new Vector3f()); + public final FloatField radius = new FloatField(1, "radius", 1f); + public final ColorRGBAField color = new ColorRGBAField(2, "color", new ColorRGBA()); + + PointLight() { + } + + PointLight(Vector3f position, float radius, ColorRGBA color) { + this.position.getValueForUpdate().set(position); + this.color.getValueForUpdate().set(color); + this.radius.setValue(radius); + } + } + + public final static class DirectLight implements Struct { + public final Vector3fField direction = new Vector3fField(0, "direction", new Vector3f()); + public final ColorRGBAField color = new ColorRGBAField(1, "color", new ColorRGBA()); + + DirectLight() { + } + + DirectLight(Vector3f direction, ColorRGBA color) { + this.direction.getValueForUpdate().set(direction); + this.color.getValueForUpdate().set(color); + } + } + + public final static class Lights implements Struct { + public final IntField nDirectLights = new IntField(0, "nDirectLights", 1); + + public final SubStructField test1 = new SubStructField(1, "test1", new DirectLight( + new Vector3f(0,1,0), + ColorRGBA.Blue + )); + + public final IntField test2 = new IntField(2, "test2", 111); + + public final SubStructField test3 = new SubStructField(3, "test3", new PointLight( + new Vector3f(7,9,7), + 99f, + ColorRGBA.Red + )); + + public final IntField test4 = new IntField(4, "test4", 222); + + public final Matrix3fField test5 = new Matrix3fField(5,"test5",new Matrix3f(1,2,3,4,5,6,7,8,9)); + public final IntField test6 = new IntField(6, "test6", 333); + + public final Matrix4fField test7 = new Matrix4fField(7, "test7", new Matrix4f(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)); + public final IntField test8 = new IntField(8, "test8", 444); + + public final SubStructArrayField directLights = new SubStructArrayField(9, "directLights", new DirectLight[] { + new DirectLight(new Vector3f(0, 0, 1), ColorRGBA.Green), + }); + + public final IntField nPointLights = new IntField(10, "nPointLights", 2); + + public final SubStructArrayField pointLights = new SubStructArrayField(11, "pointLights", new PointLight[] { + new PointLight(new Vector3f(5, 9, 7), 9f, ColorRGBA.Red), + new PointLight(new Vector3f(5, 10, 7), 8f, ColorRGBA.Green), + }); + public final SubStructArrayField pointLights2 = new SubStructArrayField(12, "pointLights2", new PointLight[] { + new PointLight(new Vector3f(3, 9, 7), 91f, ColorRGBA.Green), + new PointLight(new Vector3f(3, 10, 7), 90f, ColorRGBA.Blue), + }); + + public final IntField test13 = new IntField(13, "test13", 555); + + } + + StructStd140BufferObject lightsBO; + StructStd140BufferObject lightsBO2; + Lights lights = new Lights(); + int n = 0; + + @Override + public void simpleInitApp() { + flyCam.setEnabled(false); + + lightsBO = new StructStd140BufferObject(lights); + lightsBO2 = lightsBO.clone(); + + Material mat = new Material(assetManager, "jme3test/ubo/TestUBO.j3md"); + mat.setUniformBufferObject("TestStruct1", lightsBO); + mat.setUniformBufferObject("TestStruct2", lightsBO2); + + Geometry geo = new Geometry("Test", new Box(1, 1, 1)); + geo.setMaterial(mat); + rootNode.attachChild(geo); + geo.setLocalTranslation(0, 0, 1); + + cam.lookAt(geo.getWorldTranslation(), Vector3f.UNIT_Y); + } + + @Override + public void update() { + super.update(); + n++; + if (n > 10) { + lights.test8.setValue(999999); + lights.test13.setValue(111); + lightsBO2.update(lights); + } + } + + + public static void main(String[] args) { + AppSettings sett = new AppSettings(true); + sett.putBoolean("GraphicsDebug", true); + sett.setRenderer(AppSettings.LWJGL_OPENGL32); + TestUBO app = new TestUBO(); + app.setSettings(sett); + app.setPauseOnLostFocus(false); + app.start(); + } + +} + diff --git a/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java b/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java index 826ef448d0..659a447a48 100644 --- a/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java +++ b/jme3-examples/src/main/java/jme3test/material/TestUnshadedModel.java @@ -8,7 +8,7 @@ import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.shape.Sphere; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestUnshadedModel extends SimpleApplication { @@ -22,7 +22,7 @@ public void simpleInitApp() { Sphere sphMesh = new Sphere(32, 32, 1); sphMesh.setTextureMode(Sphere.TextureMode.Projected); sphMesh.updateGeometry(32, 32, 1, false, false); - TangentBinormalGenerator.generate(sphMesh); + MikktspaceTangentGenerator.generate(sphMesh); Geometry sphere = new Geometry("Rock Ball", sphMesh); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); diff --git a/jme3-examples/src/main/java/jme3test/material/package-info.java b/jme3-examples/src/main/java/jme3test/material/package-info.java new file mode 100644 index 0000000000..a90750386a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/material/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for materials + */ +package jme3test.material; diff --git a/jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java b/jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java new file mode 100644 index 0000000000..ef264b95d1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/math/TestRandomPoints.java @@ -0,0 +1,84 @@ +package jme3test.math; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector2f; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.debug.WireSphere; +import com.jme3.scene.shape.Sphere; + +/** + * @author capdevon + */ +public class TestRandomPoints extends SimpleApplication { + + public static void main(String[] args) { + TestRandomPoints app = new TestRandomPoints(); + app.start(); + } + + private float radius = 5; + + @Override + public void simpleInitApp() { + configureCamera(); + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + + Geometry grid = makeShape("DebugGrid", new Grid(21, 21, 2), ColorRGBA.LightGray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + + Geometry bsphere = makeShape("BoundingSphere", new WireSphere(radius), ColorRGBA.Red); + rootNode.attachChild(bsphere); + + for (int i = 0; i < 100; i++) { + Vector2f v = FastMath.insideUnitCircle().multLocal(radius); + Arrow arrow = new Arrow(Vector3f.UNIT_Y.negate()); + Geometry geo = makeShape("Arrow." + i, arrow, ColorRGBA.Green); + geo.setLocalTranslation(new Vector3f(v.x, 0, v.y)); + rootNode.attachChild(geo); + } + + for (int i = 0; i < 100; i++) { + Vector3f v = FastMath.insideUnitSphere().multLocal(radius); + Geometry geo = makeShape("Sphere." + i, new Sphere(16, 16, 0.05f), ColorRGBA.Blue); + geo.setLocalTranslation(v); + rootNode.attachChild(geo); + } + + for (int i = 0; i < 100; i++) { + Vector3f v = FastMath.onUnitSphere().multLocal(radius); + Geometry geo = makeShape("Sphere." + i, new Sphere(16, 16, 0.06f), ColorRGBA.Cyan); + geo.setLocalTranslation(v); + rootNode.attachChild(geo); + } + + for (int i = 0; i < 100; i++) { + float value = FastMath.nextRandomFloat(-5, 5); + System.out.println(value); + } + } + + private void configureCamera() { + flyCam.setMoveSpeed(15f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(12)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + private Geometry makeShape(String name, Mesh mesh, ColorRGBA color) { + Geometry geo = new Geometry(name, mesh); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } + +} diff --git a/jme3-examples/src/main/java/jme3test/math/package-info.java b/jme3-examples/src/main/java/jme3test/math/package-info.java new file mode 100644 index 0000000000..0dd99ffacb --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/math/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for arithmetic + */ +package jme3test.math; diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java index 5c91859838..4ece681a65 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfLoading.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 @@ -35,6 +35,7 @@ import com.jme3.anim.SkinningControl; import com.jme3.app.*; import com.jme3.asset.plugins.FileLocator; +import com.jme3.asset.plugins.UrlLocator; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; @@ -50,19 +51,15 @@ public class TestGltfLoading extends SimpleApplication { - Node autoRotate = new Node("autoRotate"); - List assets = new ArrayList<>(); - Node probeNode; - float time = 0; - int assetIndex = 0; - boolean useAutoRotate = false; + final private Node autoRotate = new Node("autoRotate"); + final private List assets = new ArrayList<>(); + private Node probeNode; + private float time = 0; + private int assetIndex = 0; + private boolean useAutoRotate = false; private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; - int duration = 1; - boolean playAnim = true; - - Geometry g; - int morphIndex = 0; - + final private int duration = 1; + private boolean playAnim = true; public static void main(String[] args) { TestGltfLoading app = new TestGltfLoading(); @@ -74,7 +71,7 @@ public static void main(String[] args) { you can find them here : https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0 https://sketchfab.com/features/gltf - You have to copy them in Model/gltf folder in the test-data project. + You have to copy them in Model/gltf folder in the jme3-testdata project. */ @Override public void simpleInitApp() { @@ -85,6 +82,7 @@ public void simpleInitApp() { String folder = System.getProperty("user.home"); assetManager.registerLocator(folder, FileLocator.class); + assetManager.registerLocator("https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/refs/heads/main/", UrlLocator.class); // cam.setLocation(new Vector3f(4.0339394f, 2.645184f, 6.4627485f)); // cam.setRotation(new Quaternion(-0.013950467f, 0.98604023f, -0.119502485f, -0.11510504f)); @@ -116,13 +114,13 @@ public void simpleInitApp() { // rootNode.addLight(pl1); //loadModel("Models/gltf/polly/project_polly.gltf", new Vector3f(0, 0, 0), 0.5f); - //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.1f); + //loadModel("Models/gltf/zophrac/scene.gltf", new Vector3f(0, 0, 0), 0.01f); // loadModel("Models/gltf/scifigirl/scene.gltf", new Vector3f(0, -1, 0), 0.1f); //loadModel("Models/gltf/man/scene.gltf", new Vector3f(0, -1, 0), 0.1f); //loadModel("Models/gltf/torus/scene.gltf", new Vector3f(0, -1, 0), 0.1f); //loadModel("Models/gltf/morph/scene.gltf", new Vector3f(0, 0, 0), 0.2f); - //loadModel("Models/gltf/morphCube/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f); - // loadModel("Models/gltf/morph/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f); +// loadModel("Models/gltf/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 1f); +// loadModel("Models/gltf/SimpleMorph/glTF/SimpleMorph.gltf", new Vector3f(0, 0, 0), 0.1f); //loadModel("Models/gltf/nier/scene.gltf", new Vector3f(0, -1.5f, 0), 0.01f); //loadModel("Models/gltf/izzy/scene.gltf", new Vector3f(0, -1, 0), 0.01f); //loadModel("Models/gltf/darth/scene.gltf", new Vector3f(0, -1, 0), 0.01f); @@ -137,31 +135,33 @@ public void simpleInitApp() { //loadModel("Models/gltf/manta/scene.gltf", Vector3f.ZERO, 0.2f); //loadModel("Models/gltf/bone/scene.gltf", Vector3f.ZERO, 0.1f); // loadModel("Models/gltf/box/box.gltf", Vector3f.ZERO, 1); - loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, -1, 0), 1); -// loadModel("Models/gltf/damagedHelmet/damagedHelmet.gltf", Vector3f.ZERO, 1); + loadModel("Models/gltf/duck/Duck.gltf", new Vector3f(0, 1, 0), 1); +// loadModel("Models/gltf/DamagedHelmet/glTF/DamagedHelmet.gltf", Vector3f.ZERO, 1); // loadModel("Models/gltf/hornet/scene.gltf", new Vector3f(0, -0.5f, 0), 0.4f); //// loadModel("Models/gltf/adamHead/adamHead.gltf", Vector3f.ZERO, 0.6f); //loadModel("Models/gltf/busterDrone/busterDrone.gltf", new Vector3f(0, 0f, 0), 0.8f); -// loadModel("Models/gltf/animatedCube/AnimatedCube.gltf", Vector3f.ZERO, 0.5f); -// - //loadModel("Models/gltf/BoxAnimated/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); -// - //loadModel("Models/gltf/RiggedFigure/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); - //loadModel("Models/gltf/RiggedFigure/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f); - //loadModel("Models/gltf/CesiumMan/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f); - //loadModel("Models/gltf/BrainStem/BrainStem.gltf", new Vector3f(0, -1, 0), 1f); +// loadModel("Models/gltf/AnimatedCube/glTF/AnimatedCube.gltf", Vector3f.ZERO, 0.5f); +// loadModel("Models/gltf/BoxAnimated/glTF/BoxAnimated.gltf", new Vector3f(0, 0f, 0), 0.8f); +// loadModel("Models/gltf/RiggedSimple/glTF/RiggedSimple.gltf", new Vector3f(0, -0.3f, 0), 0.2f); +// loadModel("Models/gltf/RiggedFigure/glTF/RiggedFigure.gltf", new Vector3f(0, -1f, 0), 1f); +// loadModel("Models/gltf/CesiumMan/glTF/CesiumMan.gltf", new Vector3f(0, -1, 0), 1f); +// loadModel("Models/gltf/BrainStem/glTF/BrainStem.gltf", new Vector3f(0, -1, 0), 1f); //loadModel("Models/gltf/Jaime/Jaime.gltf", new Vector3f(0, -1, 0), 1f); // loadModel("Models/gltf/GiantWorm/GiantWorm.gltf", new Vector3f(0, -1, 0), 1f); //loadModel("Models/gltf/RiggedFigure/WalkingLady.gltf", new Vector3f(0, -0.f, 0), 1f); //loadModel("Models/gltf/Monster/Monster.gltf", Vector3f.ZERO, 0.03f); -// loadModel("Models/gltf/corset/Corset.gltf", new Vector3f(0, -1, 0), 20f); - // loadModel("Models/gltf/boxInter/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); +// loadModel("Models/gltf/Corset/glTF/Corset.gltf", new Vector3f(0, -1, 0), 20f); +// loadModel("Models/gltf/BoxInterleaved/glTF/BoxInterleaved.gltf", new Vector3f(0, 0, 0), 1f); + // From url locator - probeNode.attachChild(assets.get(0)); + // loadModel("Models/AnimatedColorsCube/glTF/AnimatedColorsCube.gltf", new Vector3f(0, 0f, 0), 0.1f); + // loadModel("Models/AntiqueCamera/glTF/AntiqueCamera.gltf", new Vector3f(0, 0, 0), 0.1f); + // loadModel("Models/AnimatedMorphCube/glTF/AnimatedMorphCube.gltf", new Vector3f(0, 0, 0), 0.1f); + // loadModel("Models/AnimatedMorphCube/glTF-Binary/AnimatedMorphCube.glb", new Vector3f(0, 0, 0), 0.1f); - // setMorphTarget(morphIndex); + probeNode.attachChild(assets.get(0)); ChaseCameraAppState chaseCam = new ChaseCameraAppState(); chaseCam.setTarget(probeNode); @@ -234,10 +234,16 @@ private T findControl(Spatial s, Class controlClass) { } private void loadModel(String path, Vector3f offset, float scale) { + loadModel(path, offset, new Vector3f(scale, scale, scale)); + } + private void loadModel(String path, Vector3f offset, Vector3f scale) { GltfModelKey k = new GltfModelKey(path); //k.setKeepSkeletonPose(true); + long t = System.currentTimeMillis(); Spatial s = assetManager.loadModel(k); - s.scale(scale); + System.out.println("Load time : " + (System.currentTimeMillis() - t) + " ms"); + + s.scale(scale.x, scale.y, scale.z); s.move(offset); assets.add(s); if (playAnim) { @@ -264,8 +270,8 @@ private void loadModel(String path, Vector3f offset, float scale) { } - Queue anims = new LinkedList<>(); - AnimComposer composer; + final private Queue anims = new LinkedList<>(); + private AnimComposer composer; private void playFirstAnim(Spatial s) { diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfNaming.java b/jme3-examples/src/main/java/jme3test/model/TestGltfNaming.java new file mode 100644 index 0000000000..d5c6ad0c21 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfNaming.java @@ -0,0 +1,95 @@ +/* + * 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.model; + +import com.jme3.app.*; +import com.jme3.scene.*; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.scene.plugins.gltf.GltfModelKey; + +public class TestGltfNaming extends SimpleApplication { + private final static String indentString = "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t"; + + public static void main(String[] args) { + TestGltfNaming app = new TestGltfNaming(); + app.start(); + } + + @Override + public void simpleInitApp() { + Node r1 = new Node("test1"); + Node r2 = new Node("test2"); + Node r3 = new Node("test3"); + + r1.attachChild(loadModel("jme3test/gltfnaming/single.gltf")); + r2.attachChild(loadModel("jme3test/gltfnaming/multi.gltf")); + r3.attachChild(loadModel("jme3test/gltfnaming/parent.gltf")); + + System.out.println(""); + System.out.println(""); + + System.out.println("Test 1: "); + dumpScene(r1, 0); + + System.out.println(""); + System.out.println(""); + + System.out.println("Test 2: "); + dumpScene(r2, 0); + + System.out.println(""); + System.out.println(""); + + System.out.println("Test 3: "); + dumpScene(r3, 0); + } + + private Spatial loadModel(String path) { + GltfModelKey k = new GltfModelKey(path); + Spatial s = assetManager.loadModel(k); + s.setCullHint(CullHint.Always); + return s; + } + + private void dumpScene(Spatial s, int indent) { + System.err.println(indentString.substring(0, indent) + s.getName() + " (" + + s.getClass().getSimpleName() + ") / " + s.getLocalTransform().getTranslation().toString() + + ", " + s.getLocalTransform().getRotation().toString() + ", " + + s.getLocalTransform().getScale().toString()); + if (s instanceof Node) { + Node n = (Node) s; + for (Spatial spatial : n.getChildren()) { + dumpScene(spatial, indent + 1); + } + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/TestGltfVertexColor.java b/jme3-examples/src/main/java/jme3test/model/TestGltfVertexColor.java new file mode 100644 index 0000000000..ed91d1357d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestGltfVertexColor.java @@ -0,0 +1,73 @@ +/* + * 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.model; + +import com.jme3.app.*; +import com.jme3.math.*; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.*; +import com.jme3.scene.plugins.gltf.GltfModelKey; + +public class TestGltfVertexColor extends SimpleApplication { + Node probeNode; + + public static void main(String[] args) { + TestGltfVertexColor app = new TestGltfVertexColor(); + app.start(); + } + + @Override + public void simpleInitApp() { + rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); + rootNode.attachChild(probeNode); + + cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f); + renderer.setDefaultAnisotropicFilter(Math.min(renderer.getLimits().get(Limits.TextureAnisotropy), 8)); + setPauseOnLostFocus(false); + + flyCam.setEnabled(false); + viewPort.setBackgroundColor(new ColorRGBA().setAsSrgb(0.2f, 0.2f, 0.2f, 1.0f)); + + loadModel("jme3test/gltfvertexcolor/VertexColorTest.glb", new Vector3f(0, -1, 0), 1); + } + + private void loadModel(String path, Vector3f offset, float scale) { + GltfModelKey k = new GltfModelKey(path); + Spatial s = assetManager.loadModel(k); + s.scale(scale); + s.move(offset); + probeNode.attachChild(s); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java b/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java index c75e221f0f..f97a71b3c0 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java +++ b/jme3-examples/src/main/java/jme3test/model/TestMonkeyHead.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,9 +44,9 @@ public class TestMonkeyHead extends SimpleApplication { - float angle; - PointLight pl; - Spatial lightMdl; + private float angle; + private PointLight pl; + private Spatial lightMdl; public static void main(String[] args){ TestMonkeyHead app = new TestMonkeyHead(); @@ -57,14 +57,14 @@ public static void main(String[] args){ public void simpleInitApp() { viewPort.setBackgroundColor(ColorRGBA.DarkGray); - Spatial bumpy = (Spatial) assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml"); + Spatial bumpy = assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml"); rootNode.attachChild(bumpy); lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); rootNode.attachChild(lightMdl); - // flourescent main light + // fluorescent main light pl = new PointLight(); pl.setColor(new ColorRGBA(0.88f, 0.92f, 0.95f, 1.0f)); rootNode.addLight(pl); diff --git a/jme3-examples/src/main/java/jme3test/model/TestObjGroupsLoading.java b/jme3-examples/src/main/java/jme3test/model/TestObjGroupsLoading.java new file mode 100644 index 0000000000..fb81e69382 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/TestObjGroupsLoading.java @@ -0,0 +1,119 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.ModelKey; +import com.jme3.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.light.AmbientLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +public class TestObjGroupsLoading extends SimpleApplication { + + public static void main(String[] args) { + TestObjGroupsLoading app = new TestObjGroupsLoading(); + app.start(); + } + + private BitmapText pointerDisplay; + + @Override + public void simpleInitApp() { + + // load scene with following structure: + // Chair 1 (just mesh without name) and named groups: Chair 2, Pillow 2, Podium + Spatial scene = assetManager.loadModel(new ModelKey("OBJLoaderTest/TwoChairs.obj")); + // add light to make it visible + scene.addLight(new AmbientLight(ColorRGBA.White)); + // attach scene to the root + rootNode.attachChild(scene); + + // configure camera for best scene viewing + cam.setLocation(new Vector3f(-3, 4, 3)); + cam.lookAtDirection(new Vector3f(0, -0.5f, -1), Vector3f.UNIT_Y); + flyCam.setMoveSpeed(10); + + // create display to indicate pointed geometry name + pointerDisplay = new BitmapText(guiFont); + pointerDisplay.setBox(new Rectangle(0, settings.getHeight(), settings.getWidth(), settings.getHeight()/2)); + pointerDisplay.setAlignment(BitmapFont.Align.Center); + pointerDisplay.setVerticalAlignment(BitmapFont.VAlign.Center); + guiNode.attachChild(pointerDisplay); + + initCrossHairs(); + } + + @Override + public void simpleUpdate(final float tpf) { + + // ray to the center of the screen from the camera + Ray ray = new Ray(cam.getLocation(), cam.getDirection()); + + // find object at the center of the screen + + final CollisionResults results = new CollisionResults(); + rootNode.collideWith(ray, results); + + CollisionResult result = results.getClosestCollision(); + if (result == null) { + pointerDisplay.setText(""); + } else { + // display pointed geometry and it's parents names + StringBuilder sb = new StringBuilder(); + for (Spatial node = result.getGeometry(); node != null; node = node.getParent()) { + if (sb.length() > 0) { + sb.append(" < "); + } + sb.append(node.getName()); + } + pointerDisplay.setText(sb); + } + } + + private void initCrossHairs() { + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText ch = new BitmapText(guiFont); + ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); + ch.setText("+"); // crosshairs + ch.setLocalTranslation( // center + settings.getWidth() / 2 - guiFont.getCharSet().getRenderedSize() / 3 * 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java b/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java index 2546fdd79c..2788f12891 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestObjLoading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { // create the geometry and attach it Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); diff --git a/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java b/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java index f8d1e85f12..c3bc78f479 100644 --- a/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java +++ b/jme3-examples/src/main/java/jme3test/model/TestOgreLoading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,18 +43,19 @@ public class TestOgreLoading extends SimpleApplication { - float angle1; - float angle2; - PointLight pl; - PointLight p2; - Spatial lightMdl; - Spatial lightMd2; + private float angle1; + private float angle2; + private PointLight pl; + private PointLight p2; + private Spatial lightMdl; + private Spatial lightMd2; public static void main(String[] args) { TestOgreLoading app = new TestOgreLoading(); app.start(); } + @Override public void simpleInitApp() { // PointLight pl = new PointLight(); // pl.setPosition(new Vector3f(10, 10, -10)); @@ -89,7 +90,7 @@ public void simpleInitApp() { // create the geometry and attach it - Spatial elephant = (Spatial) assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); + Spatial elephant = assetManager.loadModel("Models/Elephant/Elephant.mesh.xml"); float scale = 0.05f; elephant.scale(scale, scale, scale); rootNode.attachChild(elephant); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java b/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java index ba61891954..6e7b1ee82d 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/EraseTimer.java @@ -1,6 +1,33 @@ /* - * To change this template, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package jme3test.model.anim; @@ -38,27 +65,32 @@ public float getTimeInSeconds() { return getTime() * INVERSE_TIMER_RESOLUTION; } + @Override public long getTime() { //return System.currentTimeMillis() - startTime; return System.nanoTime() - startTime; } + @Override public long getResolution() { return TIMER_RESOLUTION; } + @Override public float getFrameRate() { return fps; } + @Override public float getTimePerFrame() { return tpf; } + @Override public void update() { tpf = (getTime() - previousTime) * (1.0f / TIMER_RESOLUTION); if (tpf >= 0.2) { - //the frame lasted more than 200ms we erase its time to 16ms. + // The frame lasted more than 200ms, so we erase its time to 16ms. tpf = 0.016666f; } else { fps = 1.0f / tpf; @@ -66,6 +98,7 @@ public void update() { previousTime = getTime(); } + @Override public void reset() { //startTime = System.currentTimeMillis(); startTime = System.nanoTime(); 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 fd6d9e3e1f..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,8 +1,39 @@ +/* + * Copyright (c) 2017-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 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; @@ -24,12 +55,12 @@ */ public class TestAnimMigration extends SimpleApplication { - ArmatureDebugAppState debugAppState; - AnimComposer composer; - LinkedList anims = new LinkedList<>(); - boolean playAnim = false; - BlendAction action; - float blendValue = 1f; + private ArmatureDebugAppState debugAppState; + private AnimComposer composer; + final private LinkedList anims = new LinkedList<>(); + private boolean playAnim = false; + private BlendAction action; + private float blendValue = 1f; public static void main(String... argv) { TestAnimMigration app = new TestAnimMigration(); @@ -126,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)); @@ -142,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) { @@ -186,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/model/anim/TestAnimMorphSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java index f89b6ee720..30306429da 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimMorphSerialization.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.model.anim; import com.jme3.anim.*; @@ -24,11 +55,11 @@ */ public class TestAnimMorphSerialization extends SimpleApplication { - ArmatureDebugAppState debugAppState; - AnimComposer composer; - Queue anims = new LinkedList<>(); - boolean playAnim = true; - File file; + private ArmatureDebugAppState debugAppState; + private AnimComposer composer; + final private Queue anims = new LinkedList<>(); + private boolean playAnim = true; + private File file; public static void main(String... argv) { TestAnimMorphSerialization app = new TestAnimMorphSerialization(); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java index 1d3551df25..512946bd01 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimSerialization.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.model.anim; import com.jme3.anim.AnimComposer; @@ -28,11 +59,11 @@ */ public class TestAnimSerialization extends SimpleApplication { - ArmatureDebugAppState debugAppState; - AnimComposer composer; - Queue anims = new LinkedList<>(); - boolean playAnim = true; - File file; + private ArmatureDebugAppState debugAppState; + private AnimComposer composer; + final private Queue anims = new LinkedList<>(); + private boolean playAnim = true; + private File file; public static void main(String... argv) { TestAnimSerialization app = new TestAnimSerialization(); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java index 2ddb251e5b..602b88d04a 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAnimationFactory.java @@ -1,7 +1,8 @@ package jme3test.model.anim; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimationFactory; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimFactory; import com.jme3.app.SimpleApplication; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; @@ -11,12 +12,12 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.shape.Box; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestAnimationFactory extends SimpleApplication { public static void main(String[] args) { - TestSpatialAnim app = new TestSpatialAnim(); + TestAnimationFactory app = new TestAnimationFactory(); app.start(); } @@ -40,17 +41,17 @@ public void simpleInitApp() { Box child = new Box(0.5f, 0.5f, 0.5f); Geometry childGeom = new Geometry("box", child); childGeom.setMaterial(assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m")); - Node childModel = new Node("childmodel"); + Node childModel = new Node("child model"); childModel.setLocalTranslation(2, 2, 2); childModel.attachChild(childGeom); model.attachChild(childModel); - TangentBinormalGenerator.generate(model); + MikktspaceTangentGenerator.generate(model); - //creating quite complex animation with the AnimationHelper - // animation of 6 seconds named "anim" and with 25 frames per second - AnimationFactory animationFactory = new AnimationFactory(6, "anim", 25); + // Construct a complex animation using AnimFactory: + // 6 seconds in duration, named "anim", running at 25 frames per second + AnimFactory animationFactory = new AnimFactory(6f, "anim", 25f); - //creating a translation keyFrame at time = 3 with a translation on the x axis of 5 WU + // Create a translation keyFrame at time = 3 with a translation on the X axis of 5 WU. animationFactory.addTimeTranslation(3, new Vector3f(5, 0, 0)); //resetting the translation to the start position at time = 6 animationFactory.addTimeTranslation(6, new Vector3f(0, 0, 0)); @@ -67,19 +68,23 @@ public void simpleInitApp() { animationFactory.addTimeRotation(0.5f,new Quaternion().fromAngleAxis(FastMath.QUARTER_PI, Vector3f.UNIT_Z)); //rotating back to initial rotation value at time = 1 animationFactory.addTimeRotation(1,Quaternion.IDENTITY); - //Creating a rotation keyFrame at time = 2. Note that i used the Euler angle version because the angle is higher than PI - //this should result in a complete revolution of the spatial around the x axis in 1 second (from 1 to 2) - animationFactory.addTimeRotationAngles(2, FastMath.TWO_PI,0, 0); - - - AnimControl control = new AnimControl(); - control.addAnim(animationFactory.buildAnimation()); - + /* + * Perform a 360-degree rotation around the X axis between t=1 and t=2. + */ + for (int i = 1; i <= 3; ++i) { + float rotTime = i / 3f; + float xAngle = FastMath.TWO_PI * rotTime; + animationFactory.addTimeRotation(1f + rotTime, xAngle, 0f, 0f); + } + + AnimClip clip = animationFactory.buildAnimation(model); + AnimComposer control = new AnimComposer(); + control.addAnimClip(clip); model.addControl(control); rootNode.attachChild(model); //run animation - control.createChannel().setAnim("anim"); + control.setCurrentAction("anim"); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java index f7a8393277..6c71144286 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestArmature.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.model.anim; import com.jme3.anim.*; @@ -11,7 +42,6 @@ import com.jme3.scene.*; import com.jme3.scene.debug.custom.ArmatureDebugAppState; import com.jme3.scene.shape.Cylinder; -import com.jme3.util.TangentBinormalGenerator; import java.nio.FloatBuffer; import java.nio.ShortBuffer; @@ -21,9 +51,6 @@ */ public class TestArmature extends SimpleApplication { - Joint j1; - Joint j2; - public static void main(String... argv) { TestArmature app = new TestArmature(); app.start(); @@ -38,8 +65,8 @@ public void simpleInitApp() { //create armature Joint root = new Joint("Root_Joint"); - j1 = new Joint("Joint_1"); - j2 = new Joint("Joint_2"); + Joint j1 = new Joint("Joint_1"); + Joint j2 = new Joint("Joint_2"); Joint j3 = new Joint("Joint_3"); root.addChild(j1); j1.addChild(j2); @@ -140,32 +167,6 @@ public void onAction(String name, boolean isPressed, float tpf) { }, "bind"); } - - private void displayNormals(Spatial s) { - final Node debugTangents = new Node("debug tangents"); - debugTangents.setCullHint(Spatial.CullHint.Never); - - rootNode.attachChild(debugTangents); - - final Material debugMat = assetManager.loadMaterial("Common/Materials/VertexColor.j3m"); - debugMat.getAdditionalRenderState().setLineWidth(2); - - s.depthFirstTraversal(new SceneGraphVisitorAdapter() { - @Override - public void visit(Geometry g) { - Mesh m = g.getMesh(); - Geometry debug = new Geometry( - "debug tangents geom", - TangentBinormalGenerator.genNormalLines(m, 0.1f) - ); - debug.setMaterial(debugMat); - debug.setCullHint(Spatial.CullHint.Never); - debug.setLocalTransform(g.getWorldTransform()); - debugTangents.attachChild(debug); - } - }); - } - private Mesh createMesh() { Cylinder c = new Cylinder(30, 16, 0.1f, 1, true); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java b/jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java index 89e60559d6..78d81f558c 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestAttachmentsNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,11 +31,12 @@ */ package jme3test.model.anim; -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.animation.AnimEventListener; -import com.jme3.animation.LoopMode; -import com.jme3.animation.SkeletonControl; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.util.AnimMigrationUtils; import com.jme3.app.SimpleApplication; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; @@ -56,16 +57,16 @@ * Derived from {@link jme3test.model.anim.TestOgreAnim}. */ public class TestAttachmentsNode extends SimpleApplication - implements AnimEventListener, ActionListener { + implements ActionListener { + + private Action punchesOnce; + private AnimComposer control; public static void main(String[] args) { TestAttachmentsNode app = new TestAttachmentsNode(); app.start(); } - private AnimChannel channel; - private AnimControl control; - @Override public void simpleInitApp() { flyCam.setMoveSpeed(10f); @@ -76,23 +77,40 @@ public void simpleInitApp() { dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); dl.setColor(ColorRGBA.White); rootNode.addLight(dl); - + /* + * Load the Jaime model and convert it + * from the old animation system to the new one. + */ Spatial model = assetManager.loadModel("Models/Jaime/Jaime.j3o"); - control = model.getControl(AnimControl.class); - SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + AnimMigrationUtils.migrate(model); + /* + * Play the "Idle" animation at half speed. + */ + control = model.getControl(AnimComposer.class); + control.setCurrentAction("Idle"); + control.setGlobalSpeed(0.5f); + /* + * Define a "PunchesOnce" action sequence to play the "Punches" + * animation for one cycle before returning to idle. + */ + Action punches = control.action("Punches"); + Tween doneTween + = Tweens.callMethod(control, "setCurrentAction", "Idle"); + punchesOnce = control.actionSequence("PunchesOnce", punches, doneTween); model.center(); model.setLocalScale(5f); - control.addListener(this); - channel = control.createChannel(); - channel.setAnim("Idle"); - Box box = new Box(0.3f, 0.02f, 0.02f); Geometry saber = new Geometry("saber", box); saber.move(0.4f, 0.05f, 0.01f); Material red = assetManager.loadMaterial("Common/Materials/RedColor.j3m"); saber.setMaterial(red); + /* + * Create an attachments node for Jaime's right hand, + * and attach the saber to that Node. + */ + SkinningControl skeletonControl = model.getControl(SkinningControl.class); Node n = skeletonControl.getAttachmentsNode("hand.R"); n.attachChild(saber); rootNode.attachChild(model); @@ -101,27 +119,11 @@ public void simpleInitApp() { inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE)); } - @Override - public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { - if (animName.equals("Punches")) { - channel.setAnim("Idle", 0.5f); - channel.setLoopMode(LoopMode.DontLoop); - channel.setSpeed(1f); - } - } - - @Override - public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { - } - @Override public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("Attack") && value) { - if (!channel.getAnimationName().equals("Punches")) { - channel.setAnim("Punches", 0.5f); - channel.setLoopMode(LoopMode.Cycle); - channel.setSpeed(0.5f); - } + if (value && binding.equals("Attack") + && control.getCurrentAction() != punchesOnce) { + control.setCurrentAction("PunchesOnce"); } } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java index 24d8b21e55..5a5934142c 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestBaseAnimSerialization.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2017-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.model.anim; import com.jme3.anim.*; @@ -25,11 +56,9 @@ */ public class TestBaseAnimSerialization extends SimpleApplication { - Joint j1; - Joint j2; - AnimComposer composer; - Armature armature; - File file; + private AnimComposer composer; + private Armature armature; + private File file; public static void main(String... argv) { TestBaseAnimSerialization app = new TestBaseAnimSerialization(); @@ -45,8 +74,8 @@ public void simpleInitApp() { //create armature Joint root = new Joint("Root_Joint"); - j1 = new Joint("Joint_1"); - j2 = new Joint("Joint_2"); + Joint j1 = new Joint("Joint_1"); + Joint j2 = new Joint("Joint_2"); Joint j3 = new Joint("Joint_3"); root.addChild(j1); j1.addChild(j2); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java deleted file mode 100644 index 0fea65ae99..0000000000 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderAnim.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.model.anim; - -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.app.SimpleApplication; -import com.jme3.asset.BlenderKey; -import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; - -public class TestBlenderAnim extends SimpleApplication { - - private AnimChannel channel; - private AnimControl control; - - public static void main(String[] args) { - TestBlenderAnim app = new TestBlenderAnim(); - app.start(); - } - - @Override - public void simpleInitApp() { - flyCam.setMoveSpeed(10f); - cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); - cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f)); - - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); - dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); - rootNode.addLight(dl); - - BlenderKey blenderKey = new BlenderKey("Blender/2.4x/BaseMesh_249.blend"); - - Spatial scene = (Spatial) assetManager.loadModel(blenderKey); - rootNode.attachChild(scene); - - Spatial model = this.findNode(rootNode, "BaseMesh_01"); - model.center(); - - control = model.getControl(AnimControl.class); - channel = control.createChannel(); - - channel.setAnim("run_01"); - } - - /** - * This method finds a node of a given name. - * @param rootNode the root node to search - * @param name the name of the searched node - * @return the found node or null - */ - private Spatial findNode(Node rootNode, String name) { - if (name.equals(rootNode.getName())) { - return rootNode; - } - return rootNode.getChild(name); - } -} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java deleted file mode 100644 index 68855176a0..0000000000 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestBlenderObjectAnim.java +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package jme3test.model.anim; - -import com.jme3.animation.AnimChannel; -import com.jme3.animation.AnimControl; -import com.jme3.app.SimpleApplication; -import com.jme3.asset.BlenderKey; -import com.jme3.light.DirectionalLight; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; - -public class TestBlenderObjectAnim extends SimpleApplication { - - private AnimChannel channel; - private AnimControl control; - - public static void main(String[] args) { - TestBlenderObjectAnim app = new TestBlenderObjectAnim(); - app.start(); - } - - @Override - public void simpleInitApp() { - flyCam.setMoveSpeed(10f); - cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); - cam.setRotation(new Quaternion(-0.060740203f, 0.93925786f, -0.2398315f, -0.2378785f)); - - DirectionalLight dl = new DirectionalLight(); - dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); - dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); - rootNode.addLight(dl); - - BlenderKey blenderKey = new BlenderKey("Blender/2.4x/animtest.blend"); - - Spatial scene = (Spatial) assetManager.loadModel(blenderKey); - rootNode.attachChild(scene); - - Spatial model = this.findNode(rootNode, "Cube"); - model.center(); - - control = model.getControl(AnimControl.class); - channel = control.createChannel(); - - channel.setAnim("Action"); - } - - /** - * This method finds a node of a given name. - * @param rootNode the root node to search - * @param name the name of the searched node - * @return the found node or null - */ - private Spatial findNode(Node rootNode, String name) { - if (name.equals(rootNode.getName())) { - return rootNode; - } - return rootNode.getChild(name); - } -} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java index 93403587ab..6c4b230f01 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestCustomAnim.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,13 +32,17 @@ package jme3test.model.anim; -import com.jme3.animation.Bone; -import com.jme3.animation.Skeleton; -import com.jme3.animation.SkeletonControl; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; + +import com.jme3.anim.Armature; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; import com.jme3.app.SimpleApplication; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; import com.jme3.math.Quaternion; +import com.jme3.math.Transform; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.Node; @@ -47,14 +51,12 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.shape.Box; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; public class TestCustomAnim extends SimpleApplication { - private Bone bone; - private Skeleton skeleton; - private Quaternion rotation = new Quaternion(); + private Joint bone; + private Armature armature; + final private Quaternion rotation = new Quaternion(); public static void main(String[] args) { TestCustomAnim app = new TestCustomAnim(); @@ -79,15 +81,15 @@ public void simpleInitApp() { weightsHW.setUsage(Usage.CpuOnly); box.setBuffer(weightsHW); box.setBuffer(indicesHW); - + // Setup bone weight buffer - FloatBuffer weights = FloatBuffer.allocate( box.getVertexCount() * 4 ); + FloatBuffer weights = FloatBuffer.allocate(box.getVertexCount() * 4); VertexBuffer weightsBuf = new VertexBuffer(Type.BoneWeight); weightsBuf.setupData(Usage.CpuOnly, 4, Format.Float, weights); box.setBuffer(weightsBuf); // Setup bone index buffer - ByteBuffer indices = ByteBuffer.allocate( box.getVertexCount() * 4 ); + ByteBuffer indices = ByteBuffer.allocate(box.getVertexCount() * 4); VertexBuffer indicesBuf = new VertexBuffer(Type.BoneIndex); indicesBuf.setupData(Usage.CpuOnly, 4, Format.UnsignedByte, indices); box.setBuffer(indicesBuf); @@ -96,24 +98,23 @@ public void simpleInitApp() { box.generateBindPose(); // Create skeleton - bone = new Bone("root"); - bone.setBindTransforms(Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ); - bone.setUserControl(true); - skeleton = new Skeleton(new Bone[]{ bone }); + bone = new Joint("root"); + bone.setLocalTransform(new Transform(Vector3f.ZERO, Quaternion.IDENTITY, Vector3f.UNIT_XYZ)); + armature = new Armature(new Joint[] { bone }); - // Assign all verticies to bone 0 with weight 1 - for (int i = 0; i < box.getVertexCount() * 4; i += 4){ + // Assign all vertices to bone 0 with weight 1 + for (int i = 0; i < box.getVertexCount() * 4; i += 4) { // assign vertex to bone index 0 - indices.array()[i+0] = 0; - indices.array()[i+1] = 0; - indices.array()[i+2] = 0; - indices.array()[i+3] = 0; + indices.array()[i + 0] = 0; + indices.array()[i + 1] = 0; + indices.array()[i + 2] = 0; + indices.array()[i + 3] = 0; // set weight to 1 only for first entry - weights.array()[i+0] = 1; - weights.array()[i+1] = 0; - weights.array()[i+2] = 0; - weights.array()[i+3] = 0; + weights.array()[i + 0] = 1; + weights.array()[i + 1] = 0; + weights.array()[i + 2] = 0; + weights.array()[i + 3] = 0; } // Maximum number of weights per bone is 1 @@ -126,14 +127,14 @@ public void simpleInitApp() { model.attachChild(geom); // Create skeleton control - SkeletonControl skeletonControl = new SkeletonControl(skeleton); - model.addControl(skeletonControl); + SkinningControl skinningControl = new SkinningControl(armature); + model.addControl(skinningControl); rootNode.attachChild(model); } @Override - public void simpleUpdate(float tpf){ + public void simpleUpdate(float tpf) { // Rotate around X axis Quaternion rotate = new Quaternion(); rotate.fromAngleAxis(tpf, Vector3f.UNIT_X); @@ -142,10 +143,10 @@ public void simpleUpdate(float tpf){ rotation.multLocal(rotate); // Set new rotation into bone - bone.setUserTransforms(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ); + bone.setLocalTransform(new Transform(Vector3f.ZERO, rotation, Vector3f.UNIT_XYZ)); // After changing skeleton transforms, must update world data - skeleton.updateWorldVectors(); + armature.update(); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestGltfMorph.java b/jme3-examples/src/main/java/jme3test/model/anim/TestGltfMorph.java new file mode 100644 index 0000000000..244aa66599 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestGltfMorph.java @@ -0,0 +1,145 @@ +/* + * 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.model.anim; + +import com.jme3.anim.AnimComposer; +import com.jme3.app.*; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.*; +import com.jme3.renderer.Limits; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.*; +import com.jme3.scene.plugins.gltf.GltfModelKey; +import com.jme3.scene.shape.Quad; +import com.jme3.shadow.DirectionalLightShadowRenderer; +import com.jme3.shadow.EdgeFilteringMode; + +public class TestGltfMorph extends SimpleApplication { + private Node probeNode; + private float t = -1; + private int n = 0; + + public static void main(String[] args) { + TestGltfMorph app = new TestGltfMorph(); + app.start(); + } + + @Override + public void simpleInitApp() { + rootNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + + probeNode = (Node) assetManager.loadModel("Scenes/defaultProbe.j3o"); + rootNode.attachChild(probeNode); + + cam.setFrustumPerspective(45f, (float) cam.getWidth() / cam.getHeight(), 0.1f, 100f); + renderer.setDefaultAnisotropicFilter(Math.min(renderer.getLimits().get(Limits.TextureAnisotropy), 8)); + setPauseOnLostFocus(false); + + flyCam.setMoveSpeed(5); + flyCam.setDragToRotate(true); + flyCam.setEnabled(true); + viewPort.setBackgroundColor(new ColorRGBA().setAsSrgb(0.2f, 0.2f, 0.2f, 1.0f)); + + setupFloor(); + + Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + + // To make shadows, sun + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(lightDir); + dl.setColor(ColorRGBA.White); + rootNode.addLight(dl); + + // Add ambient light + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.multLocal(0.4f)); + rootNode.addLight(al); + + final int SHADOWMAP_SIZE = 1024; + DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(getAssetManager(), SHADOWMAP_SIZE, 3); + dlsr.setLight(dl); + dlsr.setLambda(0.55f); + dlsr.setShadowIntensity(0.6f); + dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCF8); + getViewPort().addProcessor(dlsr); + + loadModel("jme3test/morph/MorphStressTest.glb", new Vector3f(0, -1, 0), 1); + } + + private void setupFloor() { + Material floorMaterial = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + floorMaterial.setColor("Diffuse", new ColorRGBA(.9f, .9f, .9f, .9f)); + + Node floorGeom = new Node("floorGeom"); + Quad q = new Quad(20, 20); + Geometry g = new Geometry("geom", q); + g.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); + g.setShadowMode(RenderQueue.ShadowMode.Receive); + floorGeom.attachChild(g); + + floorGeom.setMaterial(floorMaterial); + + floorGeom.move(-10f, -2f, 10f); + + rootNode.attachChild(floorGeom); + } + + private void loadModel(String path, Vector3f offset, float scale) { + GltfModelKey k = new GltfModelKey(path); + Spatial s = assetManager.loadModel(k); + s.scale(scale); + s.move(offset); + probeNode.attachChild(s); + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + if (t == -1 || t > 5) { + rootNode.depthFirstTraversal(sx -> { + AnimComposer composer = sx.getControl(AnimComposer.class); + if (composer != null) { + String anims[] = composer.getAnimClipsNames().toArray(new String[0]); + String anim = anims[n++ % anims.length]; + System.out.println("Play " + anim); + composer.setCurrentAction(anim); + } + }); + t = 0; + } else { + t += tpf; + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java index 6eca8a9a2a..bf882bf1a1 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinning.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,10 +50,10 @@ public class TestHWSkinning extends SimpleApplication implements ActionListener{ // private AnimComposer composer; - private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + final private String[] animNames = {"Dodge", "Walk", "pull", "push"}; private final static int SIZE = 40; private boolean hwSkinningEnable = true; - private List skControls = new ArrayList<>(); + final private List skControls = new ArrayList<>(); private BitmapText hwsText; public static void main(String[] args) { @@ -86,8 +86,8 @@ public void simpleInitApp() { Node model = (Node)models[(i + j) % 4]; Spatial s = model.getChild(0).clone(); model.attachChild(s); - float x = (float)(i - SIZE / 2) / 0.1f; - float z = (float)(j - SIZE / 2) / 0.1f; + float x = (i - SIZE / 2) / 0.1f; + float z = (j - SIZE / 2) / 0.1f; s.setLocalTranslation(x, 0, z); } } @@ -123,7 +123,7 @@ public void onAction(String name, boolean isPressed, float tpf) { private void makeHudText() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - hwsText = new BitmapText(guiFont, false); + hwsText = new BitmapText(guiFont); hwsText.setSize(guiFont.getCharSet().getRenderedSize()); hwsText.setText("HWS : "+ hwSkinningEnable); hwsText.setLocalTranslation(0, cam.getHeight(), 0); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java index f0fa27d8fe..d5c5cbd7e0 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestHWSkinningOld.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,12 +46,10 @@ public class TestHWSkinningOld extends SimpleApplication implements ActionListener { - private AnimChannel channel; - private AnimControl control; - private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + final private String[] animNames = {"Dodge", "Walk", "pull", "push"}; private final static int SIZE = 50; private boolean hwSkinningEnable = true; - private List skControls = new ArrayList(); + final private List skControls = new ArrayList<>(); private BitmapText hwsText; public static void main(String[] args) { @@ -75,12 +73,11 @@ public void simpleInitApp() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + Spatial model = assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); model.setLocalScale(0.1f); model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); - control = model.getControl(AnimControl.class); - - channel = control.createChannel(); + AnimControl control = model.getControl(AnimControl.class); + AnimChannel channel = control.createChannel(); channel.setAnim(animNames[(i + j) % 4]); SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); @@ -106,7 +103,7 @@ public void onAction(String name, boolean isPressed, float tpf) { private void makeHudText() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - hwsText = new BitmapText(guiFont, false); + hwsText = new BitmapText(guiFont); hwsText.setSize(guiFont.getCharSet().getRenderedSize()); hwsText.setText("HWS : " + hwSkinningEnable); hwsText.setLocalTranslation(0, cam.getHeight(), 0); diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestIssue1395.java b/jme3-examples/src/main/java/jme3test/model/anim/TestIssue1395.java new file mode 100644 index 0000000000..f0e8bd3a6b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestIssue1395.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +import com.jme3.anim.SkinningControl; +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +/** + * Test case for Issue 1395 (OGRE Importer should call saveInitialPose, to be + * consistent with GLTF) + * + * If the test succeeds, the humanoid Oto model will appear in a standing + * position. If the test fails, the model will appear rolled up into a tight + * bundle. + * + * @author sgold + */ +public class TestIssue1395 extends SimpleApplication { + + public static void main(String[] args) { + TestIssue1395 app = new TestIssue1395(); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(new ColorRGBA(1f, 1f, 1f, 1f)); + + flyCam.setMoveSpeed(10f); + cam.setLocation(new Vector3f(6.4013605f, 7.488437f, 12.843031f)); + cam.setRotation(new Quaternion(-0.06f, 0.939258f, -0.2399f, -0.2379f)); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + dl.setColor(new ColorRGBA(1f, 1f, 1f, 1f)); + rootNode.addLight(dl); + + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + rootNode.attachChild(model); + model.center(); + + SkinningControl skinningControl + = model.getControl(SkinningControl.class); + skinningControl.getArmature().applyInitialPose(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java index c32a8ae0a7..f0b2161528 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestMorph.java @@ -3,23 +3,26 @@ import com.jme3.anim.MorphControl; import com.jme3.app.ChaseCameraAppState; import com.jme3.app.SimpleApplication; +import com.jme3.export.binary.BinaryExporter; +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.AnalogListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; +import com.jme3.material.RenderState; import com.jme3.scene.Geometry; import com.jme3.scene.VertexBuffer; import com.jme3.scene.mesh.MorphTarget; import com.jme3.scene.shape.Box; import com.jme3.util.BufferUtils; - +import com.jme3.util.clone.Cloner; import java.nio.FloatBuffer; public class TestMorph extends SimpleApplication { - float[] weights = new float[2]; + final private float[] weights = new float[2]; public static void main(String... args) { TestMorph app = new TestMorph(); @@ -28,7 +31,16 @@ public static void main(String... args) { @Override public void simpleInitApp() { + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText text = new BitmapText(font); + text.setText("Press U-Y-I-J to vary weights. Drag LMB to orbit camera."); + text.setLocalTranslation(10f, cam.getHeight() - 10f, 0f); + guiNode.attachChild(text); + final Box box = new Box(1, 1, 1); + /* + * Add a morph target that increases X coordinates of the "right" face. + */ FloatBuffer buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); float[] d = new float[box.getVertexCount() * 3]; @@ -36,10 +48,10 @@ public void simpleInitApp() { d[i] = 0; } - d[12] = 1f; - d[15] = 1f; - d[18] = 1f; - d[21] = 1f; + d[12] = 3f; + d[15] = 3f; + d[18] = 3f; + d[21] = 3f; buffer.put(d); buffer.rewind(); @@ -47,18 +59,19 @@ public void simpleInitApp() { MorphTarget target = new MorphTarget(); target.setBuffer(VertexBuffer.Type.Position, buffer); box.addMorphTarget(target); - - + /* + * Add a morph target that increases Y coordinates of the "right" face. + */ buffer = BufferUtils.createVector3Buffer(box.getVertexCount()); for (int i = 0; i < d.length; i++) { d[i] = 0; } - d[13] = 1f; - d[16] = 1f; - d[19] = 1f; - d[22] = 1f; + d[13] = 3f; + d[16] = 3f; + d[19] = 3f; + d[22] = 3f; buffer.put(d); buffer.rewind(); @@ -70,13 +83,27 @@ public void simpleInitApp() { final Geometry g = new Geometry("box", box); Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); g.setMaterial(m); - m.setColor("Color", ColorRGBA.Red); + m.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); + m.setTexture("ColorMap", assetManager.loadTexture("Interface/Logo/Monkey.jpg")); m.setInt("NumberOfMorphTargets", 2); rootNode.attachChild(g); g.setMorphState(weights); g.addControl(new MorphControl()); + /* + * Attach a clone of the morphing box model, in order to test cloning. + */ + Geometry g2 = Cloner.deepClone(g); + g2.move(-4f, 0f, 0f); + rootNode.attachChild(g2); + /* + * Attach a saveAndLoad() copy of the morphing box model, + * in order to test serialization. + */ + Geometry g3 = BinaryExporter.saveAndLoad(assetManager, g); + g3.move(-4f, 4f, 0f); + rootNode.attachChild(g3); ChaseCameraAppState chase = new ChaseCameraAppState(); chase.setTarget(rootNode); @@ -103,6 +130,8 @@ public void onAnalog(String name, float value, float tpf) { if (name.equals("morphdown")) { weights[1] -= tpf; } + weights[0] = Math.max(0f, weights[0]); + weights[1] = Math.max(0f, weights[1]); g.setMorphState(weights); } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java index a7616f7b54..fbd9524d80 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreAnim.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,23 +32,31 @@ package jme3test.model.anim; -import com.jme3.animation.*; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.Tween; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.tween.action.BaseAction; +import com.jme3.anim.tween.action.LinearBlendSpace; import com.jme3.app.SimpleApplication; import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.math.*; -import com.jme3.scene.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; import com.jme3.scene.shape.Box; -//TODO rework this Test when the new animation system is done. -public class TestOgreAnim extends SimpleApplication - implements AnimEventListener, ActionListener { +public class TestOgreAnim extends SimpleApplication implements ActionListener { - private AnimChannel channel; - private AnimControl control; - private Geometry geom; + private AnimComposer animComposer; + private static Action currentAction; public static void main(String[] args) { TestOgreAnim app = new TestOgreAnim(); @@ -66,25 +74,28 @@ public void simpleInitApp() { dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); model.center(); - control = model.getControl(AnimControl.class); - control.addListener(this); - channel = control.createChannel(); - - for (String anim : control.getAnimationNames()) - System.out.println(anim); + animComposer = model.getControl(AnimComposer.class); + animComposer.actionBlended("Attack", new LinearBlendSpace(0f, 0.5f), "Dodge"); + for (AnimClip animClip : animComposer.getAnimClips()) { + Action action = animComposer.action(animClip.getName()); + if(!"stand".equals(animClip.getName())) { + action = new BaseAction(Tweens.sequence(action, Tweens.callMethod(this, "backToStand", animComposer))); + } + animComposer.addAction(animClip.getName(), action); + } + currentAction = animComposer.setCurrentAction("stand"); // Walk, pull, Dodge, stand, push - channel.setAnim("stand"); - geom = (Geometry)((Node)model).getChild(0); - SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); + SkinningControl skinningControl = model.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(false); - Box b = new Box(.25f,3f,.25f); + Box b = new Box(.25f, 3f, .25f); Geometry item = new Geometry("Item", b); item.move(0, 1.5f, 0); item.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); - Node n = skeletonControl.getAttachmentsNode("hand.right"); + Node n = skinningControl.getAttachmentsNode("hand.right"); n.attachChild(item); rootNode.attachChild(model); @@ -93,33 +104,18 @@ public void simpleInitApp() { inputManager.addMapping("Attack", new KeyTrigger(KeyInput.KEY_SPACE)); } - @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); -// geom.getMesh().createCollisionData(); - - } - - - public void onAnimCycleDone(AnimControl control, AnimChannel channel, String animName) { - if (animName.equals("Dodge")){ - channel.setAnim("stand", 0.50f); - channel.setLoopMode(LoopMode.DontLoop); - channel.setSpeed(1f); - } - } - - public void onAnimChange(AnimControl control, AnimChannel channel, String animName) { + public Tween backToStand(AnimComposer animComposer) { + currentAction = animComposer.setCurrentAction("stand"); + return currentAction; } - + + @Override public void onAction(String binding, boolean value, float tpf) { - if (binding.equals("Attack") && value){ - if (!channel.getAnimationName().equals("Dodge")){ - channel.setAnim("Dodge", 0.50f); - channel.setLoopMode(LoopMode.Cycle); - channel.setSpeed(0.10f); + if (binding.equals("Attack") && value) { + if (currentAction != null && !currentAction.equals(animComposer.getAction("Dodge"))) { + currentAction = animComposer.setCurrentAction("Dodge"); + currentAction.setSpeed(0.1f); } } } - } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java index e83fa7a6b6..aac2c1f970 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestOgreComplexAnim.java @@ -32,20 +32,26 @@ package jme3test.model.anim; -import com.jme3.animation.*; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.ArmatureMask; +import com.jme3.anim.Joint; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.action.Action; import com.jme3.app.SimpleApplication; import com.jme3.light.DirectionalLight; import com.jme3.material.Material; -import com.jme3.math.*; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; import com.jme3.scene.Node; -import com.jme3.scene.debug.SkeletonDebugger; +import com.jme3.scene.debug.custom.ArmatureDebugger; -//TODO rework this Test when the new animation system is done. public class TestOgreComplexAnim extends SimpleApplication { - private AnimControl control; + private SkinningControl skinningControl; + private float angle = 0; - private float scale = 1; private float rate = 1; public static void main(String[] args) { @@ -64,61 +70,57 @@ public void simpleInitApp() { dl.setColor(new ColorRGBA(1f, 1f, 1f, 1.0f)); rootNode.addLight(dl); - Node model = (Node) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); - - control = model.getControl(AnimControl.class); + Node model = (Node) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); - AnimChannel feet = control.createChannel(); - AnimChannel leftHand = control.createChannel(); - AnimChannel rightHand = control.createChannel(); + skinningControl = model.getControl(SkinningControl.class); + AnimComposer ac = model.getControl(AnimComposer.class); - // feet will dodge - feet.addFromRootBone("hip.right"); - feet.addFromRootBone("hip.left"); - feet.setAnim("Dodge"); - feet.setSpeed(2); - feet.setLoopMode(LoopMode.Cycle); + ArmatureMask feet = ArmatureMask.createMask(skinningControl.getArmature(), "hip.right", "hip.left"); + Action dodgeAction = ac.action("Dodge"); + dodgeAction.setMask(feet); + dodgeAction.setSpeed(2f); + Action walkAction = ac.action("Walk"); + walkAction.setMask(feet); + walkAction.setSpeed(0.25f); - // will blend over 15 seconds to stand - feet.setAnim("Walk", 15); - feet.setSpeed(0.25f); - feet.setLoopMode(LoopMode.Cycle); + ArmatureMask rightHand = ArmatureMask.createMask(skinningControl.getArmature(), "uparm.right"); + Action pullAction = ac.action("pull"); + pullAction.setMask(rightHand); + pullAction.setSpeed(0.5f); + Action standAction = ac.action("stand"); + standAction.setMask(rightHand); + standAction.setSpeed(0.5f); - // left hand will pull - leftHand.addFromRootBone("uparm.right"); - leftHand.setAnim("pull"); - leftHand.setSpeed(.5f); + ac.actionSequence("complexAction", + ac.actionSequence("feetAction", dodgeAction, walkAction), + ac.actionSequence("rightHandAction", pullAction, standAction)); - // will blend over 15 seconds to stand - leftHand.setAnim("stand", 15); + ac.setCurrentAction("complexAction"); - // right hand will push - rightHand.addBone("spinehigh"); - rightHand.addFromRootBone("uparm.left"); - rightHand.setAnim("push"); - - SkeletonDebugger skeletonDebug = new SkeletonDebugger("skeleton", control.getSkeleton()); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); mat.setColor("Color", ColorRGBA.Green); - mat.setFloat("PointSize", 7f); + mat.setFloat("PointSize", 7f); // Bug ? do not change size of debug points ? mat.getAdditionalRenderState().setDepthTest(false); - skeletonDebug.setMaterial(mat); - model.attachChild(skeletonDebug); + ArmatureDebugger armatureDebug = new ArmatureDebugger("armature", skinningControl.getArmature(), + skinningControl.getArmature().getJointList()); + armatureDebug.setMaterial(mat); + model.attachChild(armatureDebug); + rootNode.attachChild(model); } @Override - public void simpleUpdate(float tpf){ - Bone b = control.getSkeleton().getBone("spinehigh"); - Bone b2 = control.getSkeleton().getBone("uparm.left"); + public void simpleUpdate(float tpf) { + Joint j = skinningControl.getArmature().getJoint("spinehigh"); + Joint j2 = skinningControl.getArmature().getJoint("uparm.left"); angle += tpf * rate; - if (angle > FastMath.HALF_PI / 2f){ + if (angle > FastMath.HALF_PI / 2f) { angle = FastMath.HALF_PI / 2f; rate = -1; - }else if (angle < -FastMath.HALF_PI / 2f){ + } else if (angle < -FastMath.HALF_PI / 2f) { angle = -FastMath.HALF_PI / 2f; rate = 1; } @@ -126,13 +128,8 @@ public void simpleUpdate(float tpf){ Quaternion q = new Quaternion(); q.fromAngles(0, angle, 0); - b.setUserControl(true); - b.setUserTransforms(Vector3f.ZERO, q, Vector3f.UNIT_XYZ); - - b2.setUserControl(true); - b2.setUserTransforms(Vector3f.ZERO, Quaternion.IDENTITY, new Vector3f(1+angle,1+ angle, 1+angle)); - - + j.setLocalRotation(j.getInitialTransform().getRotation().mult(q)); + j2.setLocalScale(j.getInitialTransform().getScale().mult(new Vector3f(1 + angle, 1 + angle, 1 + angle))); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java new file mode 100644 index 0000000000..18e38ea10f --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSingleLayerInfluenceMask.java @@ -0,0 +1,201 @@ +/* + * Copyright (c) 2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.model.anim; + +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimLayer; +import com.jme3.anim.Armature; +import com.jme3.anim.ArmatureMask; +import com.jme3.anim.SingleLayerInfluenceMask; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.action.BlendableAction; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.SimpleApplication; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; + +/** + * Tests {@link SingleLayerInfluenceMask}. + * + * The test runs two simultaneous looping actions on separate layers. + *

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

                + * Press the spacebar to switch between using SingleLayerInfluenceMasks and + * masks provided by {@link ArmatureMask}. + * + * @author codex + */ +public class TestSingleLayerInfluenceMask extends SimpleApplication implements ActionListener { + + private Spatial model; + private AnimComposer animComposer; + private SkinningControl skinningControl; + private final String idleLayer = "idleLayer"; + private final String danceLayer = "danceLayer"; + private boolean useSingleLayerInfMask = true; + private BitmapText display; + + public static void main(String[] args) { + TestSingleLayerInfluenceMask app = new TestSingleLayerInfluenceMask(); + app.start(); + } + + @Override + public void simpleInitApp() { + + flyCam.setMoveSpeed(30f); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-0.1f, -0.7f, -1).normalizeLocal()); + rootNode.addLight(dl); + + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + display = new BitmapText(font); + display.setSize(font.getCharSet().getRenderedSize()); + display.setText(""); + display.setLocalTranslation(5, context.getSettings().getHeight() - 5, 0); + guiNode.attachChild(display); + + inputManager.addMapping("SWITCH_MASKS", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addListener(this, "SWITCH_MASKS"); + + setupModel(); + createAnimMasks(); + testSerialization(); + playAnimations(); + updateUI(); + } + + @Override + public void simpleUpdate(float tpf) { + cam.lookAt(model.getWorldTranslation(), Vector3f.UNIT_Y); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (name.equals("SWITCH_MASKS") && isPressed) { + useSingleLayerInfMask = !useSingleLayerInfMask; + animComposer.removeLayer(idleLayer); + animComposer.removeLayer(danceLayer); + + createAnimMasks(); + playAnimations(); + updateUI(); + } + } + + /** + * Sets up the model by loading it, migrating animations, and attaching it to + * the root node. + */ + private void setupModel() { + model = assetManager.loadModel("Models/Sinbad/SinbadOldAnim.j3o"); + // Migrate the model's animations to the new system + AnimMigrationUtils.migrate(model); + rootNode.attachChild(model); + + animComposer = model.getControl(AnimComposer.class); + skinningControl = model.getControl(SkinningControl.class); + + ((BlendableAction) animComposer.action("Dance")).setMaxTransitionWeight(.9f); + ((BlendableAction) animComposer.action("IdleTop")).setMaxTransitionWeight(.8f); + } + + /** + * Creates animation masks for the idle and dance layers. + */ + private void createAnimMasks() { + Armature armature = skinningControl.getArmature(); + ArmatureMask idleMask; + ArmatureMask danceMask; + + if (useSingleLayerInfMask) { + // Create single layer influence masks for idle and dance layers + idleMask = new SingleLayerInfluenceMask(idleLayer, animComposer, armature); + danceMask = new SingleLayerInfluenceMask(danceLayer, animComposer, armature); + + } else { + // Create default armature masks for idle and dance layers + idleMask = new ArmatureMask(armature); + danceMask = new ArmatureMask(armature); + } + + // Assign the masks to the respective animation layers + animComposer.makeLayer(idleLayer, idleMask); + animComposer.makeLayer(danceLayer, danceMask); + } + + /** + * Plays the "Dance" and "IdleTop" animations on their respective layers. + */ + private void playAnimations() { + animComposer.setCurrentAction("Dance", danceLayer); + animComposer.setCurrentAction("IdleTop", idleLayer); + } + + /** + * Tests the serialization of animation masks. + */ + private void testSerialization() { + AnimComposer aComposer = model.getControl(AnimComposer.class); + for (String layerName : aComposer.getLayerNames()) { + + System.out.println("layerName: " + layerName); + AnimLayer layer = aComposer.getLayer(layerName); + + if (layer.getMask() instanceof SingleLayerInfluenceMask) { + SingleLayerInfluenceMask mask = (SingleLayerInfluenceMask) layer.getMask(); + // Serialize and deserialize the mask + mask = BinaryExporter.saveAndLoad(assetManager, mask); + // Reassign the AnimComposer to the mask and remake the layer + mask.setAnimComposer(aComposer); + aComposer.makeLayer(layerName, mask); + } + } + } + + private void updateUI() { + display.setText("Using SingleLayerInfluenceMasks: " + useSingleLayerInfMask + "\nPress Spacebar to switch masks"); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java index bbc727af39..3761858166 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSkeletonControlRefresh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,13 +31,12 @@ */ package jme3test.model.anim; -/** - * - * @author Nehon - */ - - -import com.jme3.animation.*; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.SkinningControl; +import com.jme3.anim.tween.Tweens; +import com.jme3.anim.tween.action.Action; +import com.jme3.anim.tween.action.BaseAction; import com.jme3.app.SimpleApplication; import com.jme3.asset.TextureKey; import com.jme3.font.BitmapText; @@ -58,15 +57,14 @@ import java.util.ArrayList; import java.util.List; -//TODO rework this Test when the new animation system is done. +/** + * @author Nehon + */ public class TestSkeletonControlRefresh extends SimpleApplication implements ActionListener{ - - private AnimChannel channel; - private AnimControl control; - private String[] animNames = {"Dodge", "Walk", "pull", "push"}; + private final static int SIZE = 10; private boolean hwSkinningEnable = true; - private List skControls = new ArrayList(); + final private List skinningControls = new ArrayList<>(); private BitmapText hwsText; public static void main(String[] args) { @@ -92,22 +90,25 @@ public void simpleInitApp() { for (int i = 0; i < SIZE; i++) { for (int j = 0; j < SIZE; j++) { - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/OtoOldAnim.j3o"); + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); //setting a different material model.setMaterial(m.clone()); model.setLocalScale(0.1f); model.setLocalTranslation(i - SIZE / 2, 0, j - SIZE / 2); - control = model.getControl(AnimControl.class); - - channel = control.createChannel(); - channel.setAnim(animNames[(i + j) % 4]); - channel.setLoopMode(LoopMode.DontLoop); - SkeletonControl skeletonControl = model.getControl(SkeletonControl.class); - - //This is a workaround the issue. this call will make the SkeletonControl gather the targets again. - //skeletonControl.setSpatial(model); - skeletonControl.setHardwareSkinningPreferred(hwSkinningEnable); - skControls.add(skeletonControl); + + AnimComposer animComposer + = model.getControl(AnimComposer.class); + for (AnimClip animClip : animComposer.getAnimClips()) { + Action action = animComposer.action(animClip.getName()); + animComposer.addAction(animClip.getName(), new BaseAction( + Tweens.sequence(action, Tweens.callMethod(animComposer, "removeCurrentAction", AnimComposer.DEFAULT_LAYER)))); + } + animComposer.setCurrentAction(new ArrayList<>(animComposer.getAnimClips()).get((i + j) % 4).getName()); + + SkinningControl skinningControl = model.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(hwSkinningEnable); + skinningControls.add(skinningControl); + rootNode.attachChild(model); } } @@ -153,19 +154,19 @@ public void setupFloor() { public void onAction(String name, boolean isPressed, float tpf) { if(isPressed && name.equals("toggleHWS")){ hwSkinningEnable = !hwSkinningEnable; - for (SkeletonControl skControl : skControls) { - skControl.setHardwareSkinningPreferred(hwSkinningEnable); - hwsText.setText("HWS : "+ hwSkinningEnable); + for (SkinningControl sc : skinningControls) { + sc.setHardwareSkinningPreferred(hwSkinningEnable); } + hwsText.setText("HWS : "+ hwSkinningEnable); } } private void makeHudText() { guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - hwsText = new BitmapText(guiFont, false); + hwsText = new BitmapText(guiFont); hwsText.setSize(guiFont.getCharSet().getRenderedSize()); hwsText.setText("HWS : "+ hwSkinningEnable); hwsText.setLocalTranslation(0, cam.getHeight(), 0); guiNode.attachChild(hwsText); } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java b/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java index 94dfd03a44..6ca1611d19 100644 --- a/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java +++ b/jme3-examples/src/main/java/jme3test/model/anim/TestSpatialAnim.java @@ -1,8 +1,9 @@ package jme3test.model.anim; -import com.jme3.animation.AnimControl; -import com.jme3.animation.Animation; -import com.jme3.animation.SpatialTrack; +import com.jme3.anim.AnimClip; +import com.jme3.anim.AnimComposer; +import com.jme3.anim.AnimTrack; +import com.jme3.anim.TransformTrack; import com.jme3.app.SimpleApplication; import com.jme3.light.AmbientLight; import com.jme3.light.DirectionalLight; @@ -11,12 +12,11 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.shape.Box; -import java.util.HashMap; public class TestSpatialAnim extends SimpleApplication { public static void main(String[] args) { - TestSpatialAnim app = new TestSpatialAnim(); + TestSpatialAnim app = new TestSpatialAnim(); app.start(); } @@ -58,30 +58,28 @@ public void simpleInitApp() { Vector3f[] translations = new Vector3f[totalFrames]; Quaternion[] rotations = new Quaternion[totalFrames]; Vector3f[] scales = new Vector3f[totalFrames]; - for (int i = 0; i < totalFrames; ++i) { - times[i] = t; - t += dT; - translations[i] = new Vector3f(x, 0, 0); - x += dX; - rotations[i] = Quaternion.IDENTITY; - scales[i] = Vector3f.UNIT_XYZ; + for (int i = 0; i < totalFrames; ++i) { + times[i] = t; + t += dT; + translations[i] = new Vector3f(x, 0, 0); + x += dX; + rotations[i] = Quaternion.IDENTITY; + scales[i] = Vector3f.UNIT_XYZ; } - SpatialTrack spatialTrack = new SpatialTrack(times, translations, rotations, scales); - - //creating the animation - Animation spatialAnimation = new Animation("anim", animTime); - spatialAnimation.setTracks(new SpatialTrack[] { spatialTrack }); - - //create spatial animation control - AnimControl control = new AnimControl(); - HashMap animations = new HashMap(); - animations.put("anim", spatialAnimation); - control.setAnimations(animations); - model.addControl(control); + TransformTrack transformTrack = new TransformTrack(geom, times, translations, rotations, scales); + TransformTrack transformTrackChild = new TransformTrack(childGeom, times, translations, rotations, scales); + // creating the animation + AnimClip animClip = new AnimClip("anim"); + animClip.setTracks(new AnimTrack[] { transformTrack, transformTrackChild }); + + // create spatial animation control + AnimComposer animComposer = new AnimComposer(); + animComposer.addAnimClip(animClip); + model.addControl(animComposer); rootNode.attachChild(model); - - //run animation - control.createChannel().setAnim("anim"); + + // run animation + model.getControl(AnimComposer.class).setCurrentAction("anim"); } } diff --git a/jme3-examples/src/main/java/jme3test/model/anim/package-info.java b/jme3-examples/src/main/java/jme3test/model/anim/package-info.java new file mode 100644 index 0000000000..dc7e590f17 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/anim/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for animated 3-D models + */ +package jme3test.model.anim; diff --git a/jme3-examples/src/main/java/jme3test/model/package-info.java b/jme3-examples/src/main/java/jme3test/model/package-info.java new file mode 100644 index 0000000000..964c56fd87 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for 3-D models + */ +package jme3test.model; diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java b/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java index 8fa8a66f14..17c44a1430 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestBillboard.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,7 @@ */ public class TestBillboard extends SimpleApplication { + @Override public void simpleInitApp() { flyCam.setMoveSpeed(10); @@ -93,8 +94,8 @@ public void simpleInitApp() { // rootNode.attachChild(bb); // rootNode.attachChild(g2); } - Node n; - Node n2; + private Node n; + private Node n2; @Override public void simpleUpdate(float tpf) { super.simpleUpdate(tpf); diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java b/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java index e11b5264d4..5f205def3b 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestCustomMesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -127,7 +127,7 @@ public void simpleInitApp() { coloredMesh.setLocalTranslation(4, 0, 0); rootNode.attachChild(coloredMesh); -// /** Alternatively, you can show the mesh vertixes as points +// /** Alternatively, you can show the mesh vertices as points // * instead of coloring the faces. */ // cMesh.setMode(Mesh.Mode.Points); // cMesh.setPointSize(10f); diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java index fc4731615e..25bde0a797 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestDebugShapes.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,32 +50,31 @@ public static void main(String[] args){ app.start(); } - public Geometry putShape(Mesh shape, ColorRGBA color, float lineWidth){ + private Geometry putShape(Mesh shape, ColorRGBA color) { Geometry g = new Geometry("shape", shape); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.getAdditionalRenderState().setWireframe(true); - mat.getAdditionalRenderState().setLineWidth(lineWidth); mat.setColor("Color", color); g.setMaterial(mat); rootNode.attachChild(g); return g; } - public void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){ + private void putArrow(Vector3f pos, Vector3f dir, ColorRGBA color){ Arrow arrow = new Arrow(dir); - putShape(arrow, color, 4).setLocalTranslation(pos); + putShape(arrow, color).setLocalTranslation(pos); } - public void putBox(Vector3f pos, float size, ColorRGBA color){ - putShape(new WireBox(size, size, size), color, 1).setLocalTranslation(pos); + private void putBox(Vector3f pos, float size, ColorRGBA color) { + putShape(new WireBox(size, size, size), color).setLocalTranslation(pos); } - public void putGrid(Vector3f pos, ColorRGBA color){ - putShape(new Grid(6, 6, 0.2f), color, 1).center().move(pos); + private void putGrid(Vector3f pos, ColorRGBA color) { + putShape(new Grid(6, 6, 0.2f), color).center().move(pos); } - public void putSphere(Vector3f pos, ColorRGBA color){ - putShape(new WireSphere(1), color, 1).setLocalTranslation(pos); + private void putSphere(Vector3f pos, ColorRGBA color) { + putShape(new WireSphere(1), color).setLocalTranslation(pos); } @Override diff --git a/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java b/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java index 9512946d5a..69f5b8f4f6 100644 --- a/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java +++ b/jme3-examples/src/main/java/jme3test/model/shape/TestExpandingTorus.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,6 @@ public class TestExpandingTorus extends SimpleApplication { private float outerRadius = 1.5f; private float rate = 1; private Torus torus; - private Geometry geom; public static void main(String[] args) { TestExpandingTorus app = new TestExpandingTorus(); @@ -51,7 +50,7 @@ public static void main(String[] args) { @Override public void simpleInitApp() { torus = new Torus(30, 10, .5f, 1f); - geom = new Geometry("Torus", torus); + Geometry geom = new Geometry("Torus", torus); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); geom.setMaterial(mat); rootNode.attachChild(geom); diff --git a/jme3-examples/src/main/java/jme3test/model/shape/package-info.java b/jme3-examples/src/main/java/jme3test/model/shape/package-info.java new file mode 100644 index 0000000000..60502bdb70 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/model/shape/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for meshes + */ +package jme3test.model.shape; diff --git a/jme3-examples/src/main/java/jme3test/network/MovingAverage.java b/jme3-examples/src/main/java/jme3test/network/MovingAverage.java index 991b5e5f61..28addcb3d8 100644 --- a/jme3-examples/src/main/java/jme3test/network/MovingAverage.java +++ b/jme3-examples/src/main/java/jme3test/network/MovingAverage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,7 +34,7 @@ class MovingAverage { - private long[] samples; + final private long[] samples; private long sum; private int count, index; @@ -57,7 +57,7 @@ public long getAverage(){ if (count == 0) return 0; else - return (long) ((float) sum / (float) count); + return (long) (sum / (float) count); } } \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java index 2e8ecbfafc..7312122e67 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatClient.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 jMonkeyEngine + * Copyright (c) 2011-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -117,7 +117,7 @@ public static String getString(Component owner, String title, String message, St public static void main(String... args) throws Exception { - // Increate the logging level for networking... + // Increase the logging level for networking... System.out.println("Setting logging to max"); Logger networkLog = Logger.getLogger("com.jme3.network"); networkLog.setLevel(Level.FINEST); diff --git a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java index 8bdb740b99..31aca2ffe3 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestChatServer.java +++ b/jme3-examples/src/main/java/jme3test/network/TestChatServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 jMonkeyEngine + * Copyright (c) 2011-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -129,7 +129,7 @@ public static void initializeClasses() { public static void main(String... args) throws Exception { - // Increate the logging level for networking... + // Increase the logging level for networking... System.out.println("Setting logging to max"); Logger networkLog = Logger.getLogger("com.jme3.network"); networkLog.setLevel(Level.FINEST); @@ -163,8 +163,8 @@ public ChatHandler() { public void messageReceived(HostedConnection source, Message m) { if (m instanceof ChatMessage) { // Keep track of the name just in case we - // want to know it for some other reason later and it's - // a good example of session data + // want to know it for some other reason later, and it's + // a good example of session data. ChatMessage cm = (ChatMessage)m; source.setAttribute("name", cm.getName()); diff --git a/jme3-examples/src/main/java/jme3test/network/TestLatency.java b/jme3-examples/src/main/java/jme3test/network/TestLatency.java index c47ae05562..e449380ead 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestLatency.java +++ b/jme3-examples/src/main/java/jme3test/network/TestLatency.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,8 +54,8 @@ private static long getTime(){ @Serializable public static class TimestampMessage extends AbstractMessage { - long timeSent = 0; - long timeReceived = 0; + private long timeSent = 0; + private long timeReceived = 0; public TimestampMessage(){ setReliable(false); @@ -79,6 +79,7 @@ public static void main(String[] args) throws IOException, InterruptedException{ client.start(); client.addMessageListener(new MessageListener(){ + @Override public void messageReceived(Client source, Message m) { TimestampMessage timeMsg = (TimestampMessage) m; @@ -90,7 +91,7 @@ public void messageReceived(Client source, Message m) { long latency = (curTime - timeMsg.timeSent); System.out.println("Latency: " + (latency) + " ms"); //long timeOffset = ((timeMsg.timeSent + curTime) / 2) - timeMsg.timeReceived; - //System.out.println("Approximate timeoffset: "+ (timeOffset) + " ms"); + //System.out.println("Approximate timeOffset: "+ (timeOffset) + " ms"); average.add(latency); System.out.println("Average latency: " + average.getAverage()); @@ -103,6 +104,7 @@ public void messageReceived(Client source, Message m) { }, TimestampMessage.class); server.addMessageListener(new MessageListener(){ + @Override public void messageReceived(HostedConnection source, Message m) { TimestampMessage timeMsg = (TimestampMessage) m; TimestampMessage outMsg = new TimestampMessage(timeMsg.timeSent, getTime()); diff --git a/jme3-examples/src/main/java/jme3test/network/TestMessages.java b/jme3-examples/src/main/java/jme3test/network/TestMessages.java index 361f097ecd..b67476ae1d 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestMessages.java +++ b/jme3-examples/src/main/java/jme3test/network/TestMessages.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,7 @@ public static class PongMessage extends AbstractMessage { } private static class ServerPingResponder implements MessageListener { + @Override public void messageReceived(HostedConnection source, com.jme3.network.Message message) { if (message instanceof PingMessage){ System.out.println("Server: Received ping message!"); @@ -57,6 +58,7 @@ public void messageReceived(HostedConnection source, com.jme3.network.Message me } private static class ClientPingResponder implements MessageListener { + @Override public void messageReceived(Client source, com.jme3.network.Message message) { if (message instanceof PongMessage){ System.out.println("Client: Received pong message!"); diff --git a/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java b/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java index dab33c5976..13bf63c8e2 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java +++ b/jme3-examples/src/main/java/jme3test/network/TestNetworkStress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,11 +39,13 @@ public class TestNetworkStress implements ConnectionListener { + @Override public void connectionAdded(Server server, HostedConnection conn) { System.out.println("Client Connected: "+conn.getId()); //conn.close("goodbye"); } + @Override public void connectionRemoved(Server server, HostedConnection conn) { } diff --git a/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java b/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java index 4335bb3253..208af7b682 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java +++ b/jme3-examples/src/main/java/jme3test/network/TestRemoteCall.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,12 +66,14 @@ public static interface ServerAccess { } public static class ServerAccessImpl implements ServerAccess { + @Override public boolean attachChild(String model) { if (model == null) throw new RuntimeException("Cannot be null. .. etc"); final String finalModel = model; serverApp.enqueue(new Callable() { + @Override public Void call() throws Exception { Spatial spatial = serverApp.getAssetManager().loadModel(finalModel); serverApp.getRootNode().attachChild(spatial); diff --git a/jme3-examples/src/main/java/jme3test/network/TestSerialization.java b/jme3-examples/src/main/java/jme3test/network/TestSerialization.java index cbdeb6697c..0d1ba0a585 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestSerialization.java +++ b/jme3-examples/src/main/java/jme3test/network/TestSerialization.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,23 +71,23 @@ public enum Status { @Serializable public static class TestSerializationMessage extends AbstractMessage { - boolean z; - byte b; - char c; - short s; - int i; - float f; - long l; - double d; + private boolean z; + private byte b; + private char c; + private short s; + private int i; + private float f; + private long l; + private double d; - int[] ia; - List ls; - Map mp; + private int[] ia; + private List ls; + private Map mp; - Status status1; - Status status2; + private Status status1; + private Status status2; - Date date; + private Date date; public TestSerializationMessage(){ super(true); @@ -121,6 +121,7 @@ public TestSerializationMessage(boolean initIt){ } } + @Override public void messageReceived(HostedConnection source, Message m) { TestSerializationMessage cm = (TestSerializationMessage) m; System.out.println(cm.z); diff --git a/jme3-examples/src/main/java/jme3test/network/TestThroughput.java b/jme3-examples/src/main/java/jme3test/network/TestThroughput.java index c261ec0b9e..b27b17ef14 100644 --- a/jme3-examples/src/main/java/jme3test/network/TestThroughput.java +++ b/jme3-examples/src/main/java/jme3test/network/TestThroughput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011 jMonkeyEngine + * Copyright (c) 2011-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,8 +42,8 @@ public class TestThroughput implements MessageListener { //ex private static long counter = 0; private static long total = 0; // Change this flag to test UDP instead of TCP - private static boolean testReliable = false; - private boolean isOnServer; + final private static boolean testReliable = false; + final private boolean isOnServer; public TestThroughput(boolean isOnServer) { this.isOnServer = isOnServer; @@ -61,7 +61,7 @@ public TestMessage() { public void messageReceived(MessageConnection source, Message msg) { if (!isOnServer) { - // It's local to the client so we got it back + // It's local to the client, so we got it back. counter++; total++; long time = System.currentTimeMillis(); @@ -83,7 +83,7 @@ public void messageReceived(MessageConnection source, Message msg) { //System.out.println( "sending:" + msg + " back to client:" + source ); // The 'reliable' flag is transient and the server doesn't // (yet) reset this value for us. - ((com.jme3.network.Message) msg).setReliable(testReliable); + msg.setReliable(testReliable); source.send(msg); } } diff --git a/jme3-examples/src/main/java/jme3test/network/package-info.java b/jme3-examples/src/main/java/jme3test/network/package-info.java new file mode 100644 index 0000000000..087eb1bb41 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/network/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for network communication + */ +package jme3test.network; diff --git a/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java b/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java new file mode 100644 index 0000000000..3fba0f46a0 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/StartScreenController.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2009-2021 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.niftygui; + +import com.jme3.app.Application; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.screen.Screen; +import de.lessvoid.nifty.screen.ScreenController; + +/** + * A ScreenController for the "start" screen defined in + * "Interfaces/Nifty/HelloJme.xml", which is used in the TestAppStates and + * TestNiftyGui applications. + */ +public class StartScreenController implements ScreenController { + + final private Application application; + + /** + * Instantiate a ScreenController for the specified Application. + * + * @param app the Application + */ + public StartScreenController(Application app) { + this.application = app; + } + + /** + * Nifty invokes this method when the screen gets enabled for the first + * time. + * + * @param nifty (not null) + * @param screen (not null) + */ + @Override + public void bind(Nifty nifty, Screen screen) { + System.out.println("bind(" + screen.getScreenId() + ")"); + } + + /** + * Nifty invokes this method each time the screen starts up. + */ + @Override + public void onStartScreen() { + System.out.println("onStartScreen"); + } + + /** + * Nifty invokes this method each time the screen shuts down. + */ + @Override + public void onEndScreen() { + System.out.println("onEndScreen"); + } + + /** + * Stop the Application. Nifty invokes this method (via reflection) after + * the user clicks on the flashing orange panel. + */ + public void quit() { + application.stop(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestIssue1013.java b/jme3-examples/src/main/java/jme3test/niftygui/TestIssue1013.java new file mode 100644 index 0000000000..b40311f62c --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestIssue1013.java @@ -0,0 +1,127 @@ +/* + * Copyright (c) 2009-2020 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.niftygui; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.builder.LayerBuilder; +import de.lessvoid.nifty.builder.PanelBuilder; +import de.lessvoid.nifty.builder.ScreenBuilder; +import de.lessvoid.nifty.controls.button.builder.ButtonBuilder; +import de.lessvoid.nifty.screen.Screen; +import de.lessvoid.nifty.screen.ScreenController; + +public class TestIssue1013 extends SimpleApplication implements ScreenController { + + public static void main(String[] args) { + new TestIssue1013().start(); + } + + private NiftyJmeDisplay niftyDisplay; + + @Override + public void simpleInitApp() { + + // this box here always renders + Box b = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", b); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setTexture("ColorMap", assetManager.loadTexture("/com/jme3/app/Monkey.png")); + geom.setMaterial(mat); + rootNode.attachChild(geom); + + niftyDisplay = NiftyJmeDisplay.newNiftyJmeDisplay(assetManager, inputManager, audioRenderer, guiViewPort); + + Nifty nifty = niftyDisplay.getNifty(); + nifty.loadStyleFile("nifty-default-styles.xml"); + nifty.loadControlFile("nifty-default-controls.xml"); + + ScreenController ctrl = this; + + new ScreenBuilder("start") { + { + controller(ctrl); + layer(new LayerBuilder() { + { + childLayoutVertical(); + panel(new PanelBuilder() { + { + childLayoutCenter(); + width("100%"); + height("50%"); + backgroundColor("#ff0000"); + } + }); + control(new ButtonBuilder("RestartButton", "Restart Context") { + { + alignCenter(); + valignCenter(); + height("32px"); + width("480px"); + interactOnClick("restartContext()"); + } + }); + } + }); + } + }.build(nifty); + + guiViewPort.addProcessor(niftyDisplay); + nifty.gotoScreen("start"); + + flyCam.setDragToRotate(true); + } + + @Override + public void bind(Nifty nifty, Screen screen) { + } + + @Override + public void onStartScreen() { + } + + @Override + public void onEndScreen() { + } + + public void restartContext() { + // even without changing settings, stuff breaks! + restart(); + // ...and re-adding the processor doesn't help at all + guiViewPort.addProcessor(niftyDisplay); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestIssue99.java b/jme3-examples/src/main/java/jme3test/niftygui/TestIssue99.java index 44722d2b57..d63d988768 100644 --- a/jme3-examples/src/main/java/jme3test/niftygui/TestIssue99.java +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestIssue99.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 jMonkeyEngine + * Copyright (c) 2019-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ import com.jme3.app.SimpleApplication; import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.texture.image.ColorSpace; import de.lessvoid.nifty.Nifty; import de.lessvoid.nifty.screen.Screen; import de.lessvoid.nifty.screen.ScreenController; @@ -64,8 +65,10 @@ public void simpleInitApp() { /* * Start NiftyGUI without the batched renderer. */ + ColorSpace colorSpace = renderer.isMainFrameBufferSrgb() + ? ColorSpace.sRGB : ColorSpace.Linear; NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay( - assetManager, inputManager, audioRenderer, guiViewPort); + assetManager, inputManager, audioRenderer, guiViewPort, colorSpace); guiViewPort.addProcessor(niftyDisplay); /* * Load GUI controls, styles, and layout from XML assets. diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java index 19246e4f5e..f498c3b87d 100644 --- a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyExamples.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,25 +34,27 @@ import com.jme3.app.SimpleApplication; import com.jme3.niftygui.NiftyJmeDisplay; +import com.jme3.texture.image.ColorSpace; import de.lessvoid.nifty.Nifty; public class TestNiftyExamples extends SimpleApplication { - private Nifty nifty; - public static void main(String[] args){ TestNiftyExamples app = new TestNiftyExamples(); app.setPauseOnLostFocus(false); app.start(); } + @Override public void simpleInitApp() { + ColorSpace colorSpace = renderer.isMainFrameBufferSrgb() + ? ColorSpace.sRGB : ColorSpace.Linear; NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, - guiViewPort); - nifty = niftyDisplay.getNifty(); - + guiViewPort, + colorSpace); + Nifty nifty = niftyDisplay.getNifty(); nifty.fromXml("all/intro.xml", "start"); // attach the nifty display to the gui view port as a processor diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java index f02c2c2de2..873d90548a 100644 --- a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyGui.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,10 +38,8 @@ import com.jme3.scene.Geometry; import com.jme3.scene.shape.Box; import de.lessvoid.nifty.Nifty; -import de.lessvoid.nifty.screen.Screen; -import de.lessvoid.nifty.screen.ScreenController; -public class TestNiftyGui extends SimpleApplication implements ScreenController { +public class TestNiftyGui extends SimpleApplication { private Nifty nifty; @@ -66,7 +64,8 @@ public void simpleInitApp() { audioRenderer, guiViewPort); nifty = niftyDisplay.getNifty(); - nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", this); + StartScreenController startScreen = new StartScreenController(this); + nifty.fromXml("Interface/Nifty/HelloJme.xml", "start", startScreen); // attach the nifty display to the gui view port as a processor guiViewPort.addProcessor(niftyDisplay); @@ -76,24 +75,4 @@ public void simpleInitApp() { // flyCam.setDragToRotate(true); inputManager.setCursorVisible(true); } - - @Override - public void bind(Nifty nifty, Screen screen) { - System.out.println("bind( " + screen.getScreenId() + ")"); - } - - @Override - public void onStartScreen() { - System.out.println("onStartScreen"); - } - - @Override - public void onEndScreen() { - System.out.println("onEndScreen"); - } - - public void quit(){ - nifty.gotoScreen("end"); - } - } diff --git a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java index 9ec915f532..da428f5d5e 100644 --- a/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java +++ b/jme3-examples/src/main/java/jme3test/niftygui/TestNiftyToMesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2022 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,37 +44,42 @@ import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.image.ColorSpace; import de.lessvoid.nifty.Nifty; public class TestNiftyToMesh extends SimpleApplication{ - private Nifty nifty; - public static void main(String[] args){ TestNiftyToMesh app = new TestNiftyToMesh(); app.start(); } + @Override public void simpleInitApp() { ViewPort niftyView = renderManager.createPreView("NiftyView", new Camera(1024, 768)); niftyView.setClearFlags(true, true, true); + + ColorSpace colorSpace = renderer.isMainFrameBufferSrgb() + ? ColorSpace.sRGB : ColorSpace.Linear; NiftyJmeDisplay niftyDisplay = new NiftyJmeDisplay(assetManager, inputManager, audioRenderer, - niftyView); - nifty = niftyDisplay.getNifty(); + niftyView, + colorSpace); + Nifty nifty = niftyDisplay.getNifty(); nifty.fromXml("all/intro.xml", "start"); niftyView.addProcessor(niftyDisplay); Texture2D depthTex = new Texture2D(1024, 768, Format.Depth); FrameBuffer fb = new FrameBuffer(1024, 768, 1); - fb.setDepthTexture(depthTex); + fb.setDepthTarget(FrameBufferTarget.newTarget(depthTex)); Texture2D tex = new Texture2D(1024, 768, Format.RGBA8); tex.setMinFilter(MinFilter.Trilinear); tex.setMagFilter(MagFilter.Bilinear); - fb.setColorTexture(tex); + fb.addColorTarget(FrameBufferTarget.newTarget(tex)); niftyView.setClearFlags(true, true, true); niftyView.setOutputFrameBuffer(fb); diff --git a/jme3-examples/src/main/java/jme3test/niftygui/package-info.java b/jme3-examples/src/main/java/jme3test/niftygui/package-info.java new file mode 100644 index 0000000000..485741acf3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/niftygui/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for Nifty GUI + */ +package jme3test.niftygui; diff --git a/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java b/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java index e088027851..737547fca9 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java +++ b/jme3-examples/src/main/java/jme3test/opencl/HelloOpenCL.java @@ -58,7 +58,7 @@ public static void main(String[] args){ AppSettings settings = new AppSettings(true); settings.setOpenCLSupport(true); settings.setVSync(true); -// settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); app.setSettings(settings); app.start(); // start the game } @@ -308,4 +308,4 @@ private boolean testImages(Context clContext, CommandQueue clQueue) { } return true; } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestContextSwitching.java b/jme3-examples/src/main/java/jme3test/opencl/TestContextSwitching.java index a1d87899de..0ea9c0fe3d 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/TestContextSwitching.java +++ b/jme3-examples/src/main/java/jme3test/opencl/TestContextSwitching.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,22 +46,20 @@ import java.util.logging.Logger; /** - * + * * @author Sebastian Weiss */ public class TestContextSwitching extends SimpleApplication implements ScreenController { private static final Logger LOG = Logger.getLogger(TestContextSwitching.class.getName()); - private Nifty nifty; private Label infoLabel; private Button applyButton; - private ListBox platformListBox; private ListBox deviceListBox; private static String selectedPlatform; private static String selectedDevice; private Context clContext; - private static List availabePlatforms; + private static List availablePlatforms; private Buffer testBuffer; private boolean bufferCreated; @@ -79,7 +77,7 @@ public TestContextSwitching() { settings.setWidth(800); settings.setHeight(600); settings.setOpenCLPlatformChooser(CustomPlatformChooser.class); - //settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); setSettings(settings); setShowSettings(false); @@ -95,7 +93,7 @@ public void simpleInitApp() { inputManager, audioRenderer, guiViewPort); - nifty = niftyDisplay.getNifty(); + Nifty nifty = niftyDisplay.getNifty(); nifty.fromXml("jme3test/opencl/ContextSwitchingScreen.xml", "Screen", this); guiViewPort.addProcessor(niftyDisplay); inputManager.setCursorVisible(true); @@ -113,14 +111,15 @@ public void simpleUpdate(float tpf) { @SuppressWarnings("unchecked") public void bind(Nifty nifty, Screen screen) { applyButton = screen.findNiftyControl("ApplyButton", Button.class); - platformListBox = screen.findNiftyControl("PlatformListBox", ListBox.class); + ListBox platformListBox + = screen.findNiftyControl("PlatformListBox", ListBox.class); deviceListBox = screen.findNiftyControl("DeviceListBox", ListBox.class); infoLabel = screen.findNiftyControl("InfoLabel", Label.class); updateInfos(); platformListBox.clear(); - for (Platform p : availabePlatforms) { + for (Platform p : availablePlatforms) { platformListBox.addItem(p.getName()); } platformListBox.selectItem(selectedPlatform); @@ -159,14 +158,14 @@ private void updateInfos() { @NiftyEventSubscriber(id="ApplyButton") public void onButton(String id, ButtonClickedEvent event) { - LOG.log(Level.INFO, "Change context: platorm={0}, device={1}", new Object[]{selectedPlatform, selectedDevice}); + LOG.log(Level.INFO, "Change context: platform={0}, device={1}", new Object[]{selectedPlatform, selectedDevice}); restart(); } private void changePlatform(String platform) { selectedPlatform = platform; Platform p = null; - for (Platform p2 : availabePlatforms) { + for (Platform p2 : availablePlatforms) { if (p2.getName().equals(selectedPlatform)) { p = p2; break; @@ -213,7 +212,7 @@ public CustomPlatformChooser() {} @Override public List chooseDevices(List platforms) { - availabePlatforms = platforms; + availablePlatforms = platforms; Platform platform = null; for (Platform p : platforms) { @@ -252,3 +251,4 @@ public List chooseDevices(List platforms) } } + diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java b/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java index d0045ff203..3b3ec8fe7f 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java +++ b/jme3-examples/src/main/java/jme3test/opencl/TestMultipleApplications.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,17 +50,13 @@ public class TestMultipleApplications extends SimpleApplication { private static final Logger LOG = Logger.getLogger(TestMultipleApplications.class.getName()); private static final Object sync = new Object(); - private static Platform selectedPlatform; private static List availableDevices; private static int currentDeviceIndex; - private Context clContext; private CommandQueue clQueue; private Kernel kernel; private Buffer buffer; private boolean failed; - - private BitmapText infoText; private BitmapText statusText; /** @@ -71,9 +67,10 @@ public static void main(String[] args) { settings.setOpenCLSupport(true); settings.setVSync(true); settings.setOpenCLPlatformChooser(CustomPlatformChooser.class); - settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); for (int i=0; i<2; ++i) { new Thread() { + @Override public void run() { if (currentDeviceIndex == -1) { return; @@ -100,7 +97,6 @@ public List chooseDevices(List platforms) Platform platform = platforms.get(0); availableDevices = platform.getDevices(); - selectedPlatform = platform; Device device = platform.getDevices().get(currentDeviceIndex); currentDeviceIndex ++; @@ -116,7 +112,7 @@ public List chooseDevices(List platforms) @Override public void simpleInitApp() { - clContext = context.getOpenCLContext(); + Context clContext = context.getOpenCLContext(); if (clContext == null) { LOG.severe("No OpenCL context found"); stop(); @@ -145,12 +141,12 @@ public void simpleInitApp() { inputManager.setCursorVisible(true); BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); - infoText = new BitmapText(fnt, false); + BitmapText infoText = new BitmapText(fnt); //infoText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); infoText.setText("Device: "+clContext.getDevices()); infoText.setLocalTranslation(0, settings.getHeight(), 0); guiNode.attachChild(infoText); - statusText = new BitmapText(fnt, false); + statusText = new BitmapText(fnt); //statusText.setBox(new Rectangle(0, 0, settings.getWidth(), settings.getHeight())); statusText.setText("Running"); statusText.setLocalTranslation(0, settings.getHeight() - infoText.getHeight() - 2, 0); diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java b/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java index b057a15bb7..2a712314cb 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java +++ b/jme3-examples/src/main/java/jme3test/opencl/TestOpenCLLibraries.java @@ -59,7 +59,7 @@ public static void main(String[] args){ AppSettings settings = new AppSettings(true); settings.setOpenCLSupport(true); settings.setVSync(true); -// settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); app.setSettings(settings); app.start(); // start the game } @@ -398,4 +398,4 @@ private boolean testMatrix4f(Context clContext, CommandQueue clQueue) { } return true; } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestVertexBufferSharing.java b/jme3-examples/src/main/java/jme3test/opencl/TestVertexBufferSharing.java index 0e4314fbc9..0842705b47 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/TestVertexBufferSharing.java +++ b/jme3-examples/src/main/java/jme3test/opencl/TestVertexBufferSharing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -/** +/** * This test class tests the capability to read and modify an OpenGL vertex buffer. * It is also shown how to use the program binaries to implement a simple program * cache. @@ -72,7 +72,7 @@ public static void main(String[] args){ AppSettings settings = new AppSettings(true); settings.setOpenCLSupport(true); settings.setVSync(false); -// settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); app.setSettings(settings); app.start(); // start the game } @@ -155,7 +155,7 @@ private void initOpenCL1() { } LOG.info("create new program from sources"); } - program.register(); + program.register(); kernel = program.createKernel("ScaleKernel").register(); } private void initOpenCL2() { @@ -168,9 +168,9 @@ private void updateOpenCL(float tpf) { //advect time time += tpf; - //aquire resource + //acquire resource buffer.acquireBufferForSharingNoEvent(clQueue); - //no need to wait for the returned event, since the kernel implicitely waits for it (same command queue) + //no need to wait for the returned event, since the kernel implicitly waits for it (same command queue) //execute kernel float scale = (float) Math.pow(1.1, (1.0 - time%2) / 16.0); @@ -180,4 +180,4 @@ private void updateOpenCL(float tpf) { buffer.releaseBufferForSharingNoEvent(clQueue); } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/opencl/TestWriteToTexture.java b/jme3-examples/src/main/java/jme3test/opencl/TestWriteToTexture.java index 6feeab4b9a..aaf4cc4d06 100644 --- a/jme3-examples/src/main/java/jme3test/opencl/TestWriteToTexture.java +++ b/jme3-examples/src/main/java/jme3test/opencl/TestWriteToTexture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ * * In addition, this test shows how to use {@link ProgramCache}. * - * @author shaman + * @author shaman */ public class TestWriteToTexture extends SimpleApplication implements AnalogListener, ActionListener { private static final Logger LOG = Logger.getLogger(TestWriteToTexture.class.getName()); @@ -61,7 +61,6 @@ public class TestWriteToTexture extends SimpleApplication implements AnalogListe private int initCounter; private Context clContext; private CommandQueue clQueue; - private ProgramCache programCache; private Kernel kernel; private Vector2f C; private Image texCL; @@ -72,7 +71,7 @@ public static void main(String[] args){ AppSettings settings = new AppSettings(true); settings.setOpenCLSupport(true); settings.setVSync(false); -// settings.setRenderer(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE); + settings.setRenderer(AppSettings.LWJGL_OPENGL2); app.setSettings(settings); app.start(); // start the game } @@ -123,7 +122,7 @@ public void simpleUpdate(float tpf) { private void initOpenCL1() { clContext = context.getOpenCLContext(); clQueue = clContext.createQueue().register(); - programCache = new ProgramCache(clContext); + ProgramCache programCache = new ProgramCache(clContext); //create kernel String cacheID = getClass().getName()+".Julia"; Program program = programCache.loadFromCache(cacheID); @@ -142,9 +141,9 @@ private void initOpenCL2() { texCL = clContext.bindImage(tex, MemoryAccess.WRITE_ONLY).register(); } private void updateOpenCL(float tpf) { - //aquire resource + //acquire resource texCL.acquireImageForSharingNoEvent(clQueue); - //no need to wait for the returned event, since the kernel implicitely waits for it (same command queue) + //no need to wait for the returned event, since the kernel implicitly waits for it (same command queue) //execute kernel Kernel.WorkSize ws = new Kernel.WorkSize(settings.getWidth(), settings.getHeight()); @@ -177,4 +176,4 @@ public void onAction(String name, boolean isPressed, float tpf) { inputManager.setCursorVisible(!isPressed); } } -} \ No newline at end of file +} diff --git a/jme3-examples/src/main/java/jme3test/opencl/package-info.java b/jme3-examples/src/main/java/jme3test/opencl/package-info.java new file mode 100644 index 0000000000..86f63de163 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/opencl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for OpenCL support + */ +package jme3test.opencl; diff --git a/jme3-examples/src/main/java/jme3test/package-info.java b/jme3-examples/src/main/java/jme3test/package-info.java new file mode 100644 index 0000000000..b3bee48a92 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * launch example apps and non-automated tests + */ +package jme3test; diff --git a/jme3-examples/src/main/java/jme3test/post/BloomUI.java b/jme3-examples/src/main/java/jme3test/post/BloomUI.java index f4878c1976..f0aef3475c 100644 --- a/jme3-examples/src/main/java/jme3test/post/BloomUI.java +++ b/jme3-examples/src/main/java/jme3test/post/BloomUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ public BloomUI(InputManager inputManager, final BloomFilter filter) { System.out.println("-- blur Scale : press Y to increase, H to decrease"); System.out.println("-- exposure Power : press U to increase, J to decrease"); System.out.println("-- exposure CutOff : press I to increase, K to decrease"); - System.out.println("-- bloom Intensity : press O to increase, P to decrease"); + System.out.println("-- bloom Intensity : press O to increase, L to decrease"); System.out.println("-------------------------------------------------------"); inputManager.addMapping("blurScaleUp", new KeyTrigger(KeyInput.KEY_Y)); @@ -64,6 +64,7 @@ public BloomUI(InputManager inputManager, final BloomFilter filter) { AnalogListener anl = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("blurScaleUp")) { filter.setBlurScale(filter.getBlurScale() + 0.01f); diff --git a/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java index 4cf2527165..92cd1f36f4 100644 --- a/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java +++ b/jme3-examples/src/main/java/jme3test/post/LightScatteringUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ public LightScatteringUI(InputManager inputManager, LightScatteringFilter proc) System.out.println("-- Sample number : press Y to increase, H to decrease"); System.out.println("-- blur start : press U to increase, J to decrease"); System.out.println("-- blur width : press I to increase, K to decrease"); - System.out.println("-- Light density : press O to increase, P to decrease"); + System.out.println("-- Light density : press O to increase, L to decrease"); System.out.println("-- Toggle LS on/off : press space bar"); System.out.println("-------------------------------------------------------"); @@ -71,6 +71,7 @@ public LightScatteringUI(InputManager inputManager, LightScatteringFilter proc) ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("sampleUp")) { @@ -97,6 +98,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { AnalogListener anl = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("blurStartUp")) { diff --git a/jme3-examples/src/main/java/jme3test/post/SSAOUI.java b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java index c736b2598a..64372bf971 100644 --- a/jme3-examples/src/main/java/jme3test/post/SSAOUI.java +++ b/jme3-examples/src/main/java/jme3test/post/SSAOUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ */ public class SSAOUI { - SSAOFilter filter; + final private SSAOFilter filter; public SSAOUI(InputManager inputManager, SSAOFilter filter) { this.filter = filter; @@ -56,7 +56,7 @@ private void init(InputManager inputManager) { System.out.println("-- Sample Radius : press Y to increase, H to decrease"); System.out.println("-- AO Intensity : press U to increase, J to decrease"); System.out.println("-- AO scale : press I to increase, K to decrease"); - System.out.println("-- AO bias : press O to increase, P to decrease"); + System.out.println("-- AO bias : press O to increase, L to decrease"); System.out.println("-- Toggle AO on/off : press space bar"); System.out.println("-- Use only AO : press Num pad 0"); System.out.println("-- Output config declaration : press P"); @@ -77,6 +77,7 @@ private void init(InputManager inputManager) { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggleUseAO") && keyPressed) { @@ -102,6 +103,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { AnalogListener anl = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("sampleRadiusUp")) { filter.setSampleRadius(filter.getSampleRadius() + 0.01f); diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloom.java b/jme3-examples/src/main/java/jme3test/post/TestBloom.java index 9572568ea2..5608890801 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestBloom.java +++ b/jme3-examples/src/main/java/jme3test/post/TestBloom.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,20 +46,14 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; import com.jme3.scene.shape.Box; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; public class TestBloom extends SimpleApplication { - float angle; - Spatial lightMdl; - Spatial teapot; - Geometry frustumMdl; - WireFrustum frustum; - boolean active=true; - FilterPostProcessor fpp; + private boolean active=true; + private FilterPostProcessor fpp; public static void main(String[] args){ TestBloom app = new TestBloom(); @@ -81,8 +75,6 @@ public void simpleInitApp() { mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); - - Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); matSoil.setFloat("Shininess", 15f); @@ -92,8 +84,7 @@ public void simpleInitApp() { matSoil.setColor("Specular", ColorRGBA.Gray); - - teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); teapot.setLocalTranslation(0,0,10); teapot.setMaterial(mat); @@ -148,6 +139,7 @@ private void initInputs() { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { if(active){ diff --git a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java index b9f136726e..9e5a9aaed3 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java +++ b/jme3-examples/src/main/java/jme3test/post/TestBloomAlphaThreshold.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,142 +48,135 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; import com.jme3.scene.shape.Box; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; public class TestBloomAlphaThreshold extends SimpleApplication { - - float angle; - Spatial lightMdl; - Spatial teapot; - Geometry frustumMdl; - WireFrustum frustum; - boolean active = true; - FilterPostProcessor fpp; - - public static void main(String[] args) - { - TestBloomAlphaThreshold app = new TestBloomAlphaThreshold(); - app.start(); - } - - @Override - public void simpleInitApp() - { - // put the camera in a bad position - cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10)); - cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); - // cam.setFrustumFar(1000); - - Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - - mat.setFloat("Shininess", 15f); - mat.setBoolean("UseMaterialColors", true); - mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); - mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); - mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); - mat.setColor("GlowColor", ColorRGBA.Green); - - Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); - matSoil.setFloat("Shininess", 15f); - matSoil.setBoolean("UseMaterialColors", true); - matSoil.setColor("Ambient", ColorRGBA.Gray); - matSoil.setColor("Diffuse", ColorRGBA.Black); - matSoil.setColor("Specular", ColorRGBA.Gray); - - teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); - teapot.setLocalTranslation(0, 0, 10); - - teapot.setMaterial(mat); - teapot.setShadowMode(ShadowMode.CastAndReceive); - teapot.setLocalScale(10.0f); - rootNode.attachChild(teapot); - - Vector3f boxMin1 = new Vector3f(-800f, -23f, -150f); - Vector3f boxMax1 = new Vector3f(800f, 3f, 1250f); - Box boxMesh1 = new Box(boxMin1, boxMax1); - Geometry soil = new Geometry("soil", boxMesh1); - soil.setMaterial(matSoil); - soil.setShadowMode(ShadowMode.CastAndReceive); - rootNode.attachChild(soil); - - Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png")); - matBox.setFloat("AlphaDiscardThreshold", 0.5f); + private boolean active = true; + private FilterPostProcessor fpp; + + public static void main(String[] args) + { + TestBloomAlphaThreshold app = new TestBloomAlphaThreshold(); + app.start(); + } + + @Override + public void simpleInitApp() + { + // put the camera in a bad position + cam.setLocation(new Vector3f(-2.336393f, 11.91392f, -10)); + cam.setRotation(new Quaternion(0.23602544f, 0.11321983f, -0.027698677f, 0.96473104f)); + // cam.setFrustumFar(1000); + + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + + mat.setFloat("Shininess", 15f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); + mat.setColor("GlowColor", ColorRGBA.Green); + + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setFloat("Shininess", 15f); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Gray); + + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(0, 0, 10); + + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + teapot.setLocalScale(10.0f); + rootNode.attachChild(teapot); + + Vector3f boxMin1 = new Vector3f(-800f, -23f, -150f); + Vector3f boxMax1 = new Vector3f(800f, 3f, 1250f); + Box boxMesh1 = new Box(boxMin1, boxMax1); + Geometry soil = new Geometry("soil", boxMesh1); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + Material matBox = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + matBox.setTexture("ColorMap", assetManager.loadTexture("Textures/ColoredTex/Monkey.png")); + matBox.setFloat("AlphaDiscardThreshold", 0.5f); - Vector3f boxMin2 = new Vector3f(-5.5f, 8f, -4f); - Vector3f boxMax2 = new Vector3f(-1.5f, 12f, 0f); - Box boxMesh2 = new Box(boxMin2, boxMax2); - Geometry box = new Geometry("box", boxMesh2); - box.setMaterial(matBox); - box.setQueueBucket(RenderQueue.Bucket.Translucent); - // box.setShadowMode(ShadowMode.CastAndReceive); - rootNode.attachChild(box); - - DirectionalLight light = new DirectionalLight(); - light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); - light.setColor(ColorRGBA.White.mult(1.5f)); - rootNode.addLight(light); - - // load sky - Spatial sky = SkyFactory.createSky(assetManager, + Vector3f boxMin2 = new Vector3f(-5.5f, 8f, -4f); + Vector3f boxMax2 = new Vector3f(-1.5f, 12f, 0f); + Box boxMesh2 = new Box(boxMin2, boxMax2); + Geometry box = new Geometry("box", boxMesh2); + box.setMaterial(matBox); + box.setQueueBucket(RenderQueue.Bucket.Translucent); + // box.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(box); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", EnvMapType.CubeMap); - sky.setCullHint(Spatial.CullHint.Never); - rootNode.attachChild(sky); - - fpp = new FilterPostProcessor(assetManager); - int numSamples = getContext().getSettings().getSamples(); - if (numSamples > 0) - { - fpp.setNumSamples(numSamples); - } - - BloomFilter bloom = new BloomFilter(GlowMode.Objects); - bloom.setDownSamplingFactor(2); - bloom.setBlurScale(1.37f); - bloom.setExposurePower(3.30f); - bloom.setExposureCutOff(0.2f); - bloom.setBloomIntensity(2.45f); - BloomUI ui = new BloomUI(inputManager, bloom); - - viewPort.addProcessor(fpp); - fpp.addFilter(bloom); - initInputs(); - - } - - private void initInputs() - { - inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); - - ActionListener acl = new ActionListener() - { - - @Override - public void onAction(String name, boolean keyPressed, float tpf) - { - if (name.equals("toggle") && keyPressed) - { - if (active) - { - active = false; - viewPort.removeProcessor(fpp); - } - else - { - active = true; - viewPort.addProcessor(fpp); - } - } - } - }; - - inputManager.addListener(acl, "toggle"); - - } + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + + fpp = new FilterPostProcessor(assetManager); + int numSamples = getContext().getSettings().getSamples(); + if (numSamples > 0) + { + fpp.setNumSamples(numSamples); + } + + BloomFilter bloom = new BloomFilter(GlowMode.Objects); + bloom.setDownSamplingFactor(2); + bloom.setBlurScale(1.37f); + bloom.setExposurePower(3.30f); + bloom.setExposureCutOff(0.2f); + bloom.setBloomIntensity(2.45f); + BloomUI ui = new BloomUI(inputManager, bloom); + + viewPort.addProcessor(fpp); + fpp.addFilter(bloom); + initInputs(); + + } + + private void initInputs() + { + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + + ActionListener acl = new ActionListener() + { + + @Override + public void onAction(String name, boolean keyPressed, float tpf) + { + if (name.equals("toggle") && keyPressed) + { + if (active) + { + active = false; + viewPort.removeProcessor(fpp); + } + else + { + active = true; + viewPort.addProcessor(fpp); + } + } + } + }; + + inputManager.addListener(acl, "toggle"); + + } } diff --git a/jme3-examples/src/main/java/jme3test/post/TestContrastAdjustment.java b/jme3-examples/src/main/java/jme3test/post/TestContrastAdjustment.java new file mode 100644 index 0000000000..52c125cb3e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestContrastAdjustment.java @@ -0,0 +1,220 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.ContrastAdjustmentFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Sphere; +import com.jme3.texture.Texture; + +/** + * A {@link ContrastAdjustmentFilter} with user-controlled exponents, scales, and input range. + * + * @author pavl_g. + */ +public class TestContrastAdjustment extends SimpleApplication { + + /** + * Display filter status. + */ + private BitmapText statusText; + /** + * The filter being tested. + */ + private ContrastAdjustmentFilter contrastAdjustmentFilter; + + public static void main(String[] args) { + new TestContrastAdjustment().start(); + } + + @Override + public void simpleInitApp() { + /* + * Attach an unshaded globe to the scene. + */ + final Sphere globe = new Sphere(40, 40, 3.5f); + final Geometry earth = new Geometry("Earth", globe); + earth.rotate(-FastMath.HALF_PI, 0f, 0f); + final Material material = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + final Texture texture = assetManager.loadTexture("Textures/Sky/Earth/Earth.jpg"); + material.setTexture("ColorMap", texture); + earth.setMaterial(material); + rootNode.attachChild(earth); + + final FilterPostProcessor postProcessor = new FilterPostProcessor(assetManager); + int numSamples = settings.getSamples(); + if (numSamples > 0) { + postProcessor.setNumSamples(numSamples); + } + viewPort.addProcessor(postProcessor); + /* + * Add the filter to be tested. + */ + contrastAdjustmentFilter = new ContrastAdjustmentFilter(); + //adjusting some parameters + contrastAdjustmentFilter.setExponents(1.8f, 1.8f, 2.1f) + .setInputRange(0, 0.367f) + .setScales(0.25f, 0.25f, 1f); + postProcessor.addFilter(contrastAdjustmentFilter); + + setUpUserInterface(); + } + + /** + * Update the status text. + * + * @param tpf unused + */ + @Override + public void simpleUpdate(float tpf) { + String status = contrastAdjustmentFilter.toString(); + statusText.setText(status); + } + + private void setUpUserInterface() { + /* + * Attach a BitmapText to display the status of the ContrastAdjustmentFilter. + */ + statusText = new BitmapText(guiFont); + guiNode.attachChild(statusText); + statusText.setLocalTranslation(0f, cam.getHeight(), 0f); + /* + * Create listeners for user keypresses. + */ + ActionListener action = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("reset") && keyPressed) { + contrastAdjustmentFilter.setExponents(1f, 1f, 1f) + .setInputRange(0f, 1f) + .setScales(1f, 1f, 1f); + } + } + }; + AnalogListener analog = new AnalogListener() { + @Override + public void onAnalog(String name, float value, float tpf) { + float increment = name.endsWith("+") ? 0.3f * tpf : -0.3f * tpf; + + if (name.startsWith("lower")) { + float newValue = contrastAdjustmentFilter.getLowerLimit() + increment; + contrastAdjustmentFilter.setLowerLimit(newValue); + + } else if (name.startsWith("upper")) { + float newValue = contrastAdjustmentFilter.getUpperLimit() + increment; + contrastAdjustmentFilter.setUpperLimit(newValue); + + } else if (name.startsWith("re")) { + float newValue = contrastAdjustmentFilter.getRedExponent() + increment; + contrastAdjustmentFilter.setRedExponent(newValue); + + } else if (name.startsWith("ge")) { + float newValue = contrastAdjustmentFilter.getGreenExponent() + increment; + contrastAdjustmentFilter.setGreenExponent(newValue); + + } else if (name.startsWith("be")) { + float newValue = contrastAdjustmentFilter.getBlueExponent() + increment; + contrastAdjustmentFilter.setBlueExponent(newValue); + + } else if (name.startsWith("rs")) { + float newValue = contrastAdjustmentFilter.getRedScale() + increment; + contrastAdjustmentFilter.setRedScale(newValue); + + } else if (name.startsWith("gs")) { + float newValue = contrastAdjustmentFilter.getGreenScale() + increment; + contrastAdjustmentFilter.setGreenScale(newValue); + + } else if (name.startsWith("bs")) { + float newValue = contrastAdjustmentFilter.getBlueScale() + increment; + contrastAdjustmentFilter.setBlueScale(newValue); + } + } + }; + /* + * Add mappings and listeners for user keypresses. + */ + System.out.println("Press Enter to reset the filter to defaults."); + inputManager.addMapping("reset", new KeyTrigger(KeyInput.KEY_RETURN)); + inputManager.addListener(action, "reset"); + + System.out.println("lower limit: press R to increase, F to decrease"); + inputManager.addMapping("lower+", new KeyTrigger(KeyInput.KEY_R)); + inputManager.addMapping("lower-", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addListener(analog, "lower+", "lower-"); + + System.out.println("upper limit: press T to increase, G to decrease"); + inputManager.addMapping("upper+", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("upper-", new KeyTrigger(KeyInput.KEY_G)); + inputManager.addListener(analog, "upper+", "upper-"); + + System.out.println("red exponent: press Y to increase, H to decrease"); + inputManager.addMapping("re+", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("re-", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addListener(analog, "re+", "re-"); + + System.out.println("green exponent: press U to increase, J to decrease"); + inputManager.addMapping("ge+", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("ge-", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(analog, "ge+", "ge-"); + + System.out.println("blue exponent: press I to increase, K to decrease"); + inputManager.addMapping("be+", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("be-", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addListener(analog, "be+", "be-"); + + System.out.println("red scale: press O to increase, L to decrease"); + inputManager.addMapping("rs+", new KeyTrigger(KeyInput.KEY_O)); + inputManager.addMapping("rs-", new KeyTrigger(KeyInput.KEY_L)); + inputManager.addListener(analog, "rs+", "rs-"); + + System.out.println("green scale: press P to increase, ; to decrease"); + inputManager.addMapping("gs+", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("gs-", new KeyTrigger(KeyInput.KEY_SEMICOLON)); + inputManager.addListener(analog, "gs+", "gs-"); + + System.out.println("blue scale: press [ to increase, ' to decrease"); + inputManager.addMapping("bs+", new KeyTrigger(KeyInput.KEY_LBRACKET)); + inputManager.addMapping("bs-", new KeyTrigger(KeyInput.KEY_APOSTROPHE)); + inputManager.addListener(analog, "bs+", "bs-"); + + System.out.println(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java b/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java index 5cc3669aea..6883ddcabf 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java +++ b/jme3-examples/src/main/java/jme3test/post/TestCrossHatch.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,20 +46,14 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; import com.jme3.scene.shape.Box; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; public class TestCrossHatch extends SimpleApplication { - float angle; - Spatial lightMdl; - Spatial teapot; - Geometry frustumMdl; - WireFrustum frustum; - boolean active=true; - FilterPostProcessor fpp; + private boolean active=true; + private FilterPostProcessor fpp; public static void main(String[] args){ TestCrossHatch app = new TestCrossHatch(); @@ -92,8 +86,7 @@ public void simpleInitApp() { matSoil.setColor("Specular", ColorRGBA.Gray); - - teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); teapot.setLocalTranslation(0,0,10); teapot.setMaterial(mat); @@ -143,6 +136,7 @@ private void initInputs() { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { if(active){ diff --git a/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java b/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java index 6fee9e404b..3690fe9aa7 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java +++ b/jme3-examples/src/main/java/jme3test/post/TestDepthOfField.java @@ -1,8 +1,40 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package jme3test.post; import com.jme3.app.SimpleApplication; import com.jme3.collision.CollisionResult; import com.jme3.collision.CollisionResults; +import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.AnalogListener; import com.jme3.input.controls.KeyTrigger; @@ -31,11 +63,9 @@ */ public class TestDepthOfField extends SimpleApplication { - private FilterPostProcessor fpp; - private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); - TerrainQuad terrain; - Material matRock; - DepthOfFieldFilter dofFilter; + final private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + private TerrainQuad terrain; + private DepthOfFieldFilter dofFilter; public static void main(String[] args) { TestDepthOfField app = new TestDepthOfField(); @@ -71,9 +101,7 @@ public void simpleInitApp() { sky.setLocalScale(350); mainScene.attachChild(sky); - - - fpp = new FilterPostProcessor(assetManager); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); // fpp.setNumSamples(4); int numSamples = getContext().getSettings().getSamples(); if( numSamples > 0 ) { @@ -89,6 +117,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (isPressed) { if (name.equals("toggle")) { @@ -101,6 +130,7 @@ public void onAction(String name, boolean isPressed, float tpf) { }, "toggle"); inputManager.addListener(new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("blurScaleUp")) { dofFilter.setBlurScale(dofFilter.getBlurScale() + 0.01f); @@ -131,18 +161,19 @@ public void onAnalog(String name, float value, float tpf) { }, "blurScaleUp", "blurScaleDown", "focusRangeUp", "focusRangeDown", "focusDistanceUp", "focusDistanceDown"); - inputManager.addMapping("toggle", new KeyTrigger(keyInput.KEY_SPACE)); - inputManager.addMapping("blurScaleUp", new KeyTrigger(keyInput.KEY_U)); - inputManager.addMapping("blurScaleDown", new KeyTrigger(keyInput.KEY_J)); - inputManager.addMapping("focusRangeUp", new KeyTrigger(keyInput.KEY_I)); - inputManager.addMapping("focusRangeDown", new KeyTrigger(keyInput.KEY_K)); - inputManager.addMapping("focusDistanceUp", new KeyTrigger(keyInput.KEY_O)); - inputManager.addMapping("focusDistanceDown", new KeyTrigger(keyInput.KEY_L)); + inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("blurScaleUp", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("blurScaleDown", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addMapping("focusRangeUp", new KeyTrigger(KeyInput.KEY_I)); + inputManager.addMapping("focusRangeDown", new KeyTrigger(KeyInput.KEY_K)); + inputManager.addMapping("focusDistanceUp", new KeyTrigger(KeyInput.KEY_O)); + inputManager.addMapping("focusDistanceDown", new KeyTrigger(KeyInput.KEY_L)); } private void createTerrain(Node rootNode) { - matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matRock = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); matRock.setBoolean("useTriPlanarMapping", false); matRock.setBoolean("WardIso", true); matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); @@ -177,7 +208,7 @@ private void createTerrain(Node rootNode) { e.printStackTrace(); } terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); + List cameras = new ArrayList<>(); cameras.add(getCamera()); terrain.setMaterial(matRock); terrain.setLocalScale(new Vector3f(5, 5, 5)); diff --git a/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java b/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java index 47b74f472d..72a4c875f5 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java +++ b/jme3-examples/src/main/java/jme3test/post/TestFBOPassthrough.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.ui.Picture; /** @@ -53,7 +54,7 @@ */ public class TestFBOPassthrough extends SimpleApplication { - private Node fbNode = new Node("Framebuffer Node"); + final private Node fbNode = new Node("Framebuffer Node"); private FrameBuffer fb; public static void main(String[] args){ @@ -70,8 +71,8 @@ public void simpleInitApp() { fb = new FrameBuffer(w, h, 1); Texture2D fbTex = new Texture2D(w, h, Format.RGBA8); - fb.setDepthBuffer(Format.Depth); - fb.setColorTexture(fbTex); + fb.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); + fb.addColorTarget(FrameBufferTarget.newTarget(fbTex)); // setup framebuffer's scene Sphere sphMesh = new Sphere(20, 20, 1); diff --git a/jme3-examples/src/main/java/jme3test/post/TestFog.java b/jme3-examples/src/main/java/jme3test/post/TestFog.java index 3ecac03a0d..b7a913d79f 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestFog.java +++ b/jme3-examples/src/main/java/jme3test/post/TestFog.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,6 +66,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { this.flyCam.setMoveSpeed(50); Node mainScene=new Node(); @@ -113,6 +114,7 @@ private void initInputs() { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { if(enabled){ @@ -129,6 +131,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { AnalogListener anl=new AnalogListener() { + @Override public void onAnalog(String name, float isPressed, float tpf) { if(name.equals("DensityUp")){ fog.setFogDensity(fog.getFogDensity()+0.001f); @@ -191,7 +194,7 @@ private void createTerrain(Node rootNode) { e.printStackTrace(); } TerrainQuad terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); + List cameras = new ArrayList<>(); cameras.add(getCamera()); terrain.setMaterial(matRock); terrain.setLocalScale(new Vector3f(5, 5, 5)); diff --git a/jme3-examples/src/main/java/jme3test/post/TestIssue1798.java b/jme3-examples/src/main/java/jme3test/post/TestIssue1798.java new file mode 100644 index 0000000000..79605f29a8 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestIssue1798.java @@ -0,0 +1,135 @@ +/* + * 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 jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.CartoonEdgeFilter; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.system.AppSettings; +import com.jme3.texture.Texture; + +/** + * Test case for JME issue #1798: filtered scenes are squeezed by resizable + * windows. + *

                + * If successful, a cartoon monkey head will be shown, and resizing the window + * (using the system's window manager) will not change its shape. + *

                + * If unsuccessful, then making the window taller will make the head taller, and + * making the window wider will make the head wider. + *

                + * Based on the TestCartoonEdge application. + */ +public class TestIssue1798 extends SimpleApplication { + // ************************************************************************* + // fields + + private FilterPostProcessor fpp; + // ************************************************************************* + // new methods exposed + + public static void main(String[] args) { + AppSettings s = new AppSettings(true); + s.setResizable(true); + TestIssue1798 app = new TestIssue1798(); + app.setSettings(s); + app.start(); + } + // ************************************************************************* + // SimpleApplication methods + + /** + * Initialize this application. + */ + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.Gray); + flyCam.setDragToRotate(true); + setupLighting(); + setupModel(); + setupFilters(); + } + // ************************************************************************* + // private methods + + private void makeToonish(Spatial spatial) { + if (spatial instanceof Node) { + Node n = (Node) spatial; + for (Spatial child : n.getChildren()) { + makeToonish(child); + } + } else if (spatial instanceof Geometry) { + Geometry g = (Geometry) spatial; + Material m = g.getMaterial(); + if (m.getMaterialDef().getMaterialParam("UseMaterialColors") != null) { + Texture t = assetManager.loadTexture("Textures/ColorRamp/toon.png"); + m.setTexture("ColorRamp", t); + m.setBoolean("UseMaterialColors", true); + m.setColor("Specular", ColorRGBA.Black); + m.setColor("Diffuse", ColorRGBA.White); + m.setBoolean("VertexLighting", true); + } + } + } + + private void setupFilters() { + fpp = new FilterPostProcessor(assetManager); + int numSamples = getContext().getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + CartoonEdgeFilter toon = new CartoonEdgeFilter(); + toon.setEdgeColor(ColorRGBA.Yellow); + fpp.addFilter(toon); + viewPort.addProcessor(fpp); + } + + private void setupLighting() { + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, 1).normalizeLocal()); + dl.setColor(new ColorRGBA(2, 2, 2, 1)); + rootNode.addLight(dl); + } + + private void setupModel() { + Spatial model = assetManager.loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml"); + makeToonish(model); + rootNode.attachChild(model); + } +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java b/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java index c2940cab6b..83e0d464e8 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java +++ b/jme3-examples/src/main/java/jme3test/post/TestLightScattering.java @@ -45,7 +45,7 @@ import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.util.SkyFactory; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestLightScattering extends SimpleApplication { @@ -66,7 +66,7 @@ public void simpleInitApp() { flyCam.setMoveSpeed(10); Material mat = assetManager.loadMaterial("Textures/Terrain/Rocky/Rocky.j3m"); Spatial scene = assetManager.loadModel("Models/Terrain/Terrain.mesh.xml"); - TangentBinormalGenerator.generate(((Geometry)((Node)scene).getChild(0)).getMesh()); + MikktspaceTangentGenerator.generate(((Geometry) ((Node) scene).getChild(0)).getMesh()); scene.setMaterial(mat); scene.setShadowMode(ShadowMode.CastAndReceive); scene.setLocalScale(400); diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java b/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java index ccb073a334..a5422ac539 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiRenderTarget.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,208 +33,203 @@ package jme3test.post; import com.jme3.app.SimpleApplication; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; import com.jme3.post.SceneProcessor; import com.jme3.profile.AppProfiler; import com.jme3.renderer.RenderManager; import com.jme3.renderer.ViewPort; import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture2D; import com.jme3.ui.Picture; +/** + * Demonstrates rendering to multiple texture targets of a single FrameBuffer. + * + *

                The GUI viewport is tiled into 4 pictures, + * each displaying a different render of 5 colorful cubes in the main scene. + */ public class TestMultiRenderTarget extends SimpleApplication implements SceneProcessor { - private FrameBuffer fb; - private Texture2D diffuseData, normalData, specularData, depthData; - private Picture display1, display2, display3, display4; - - private Picture display; - private Material mat; - - private String techOrig; - private PointLight[] pls; - - public static void main(String[] args){ + /** + * Displays the merged RGB (normal color) output

                  + *
                • from "ExtractRGB.frag" location 3,
                • + *
                • in the lower-left quadrant of the window.
                + */ + private Picture display1; + /** + * Displays the red-channel output in color
                  + *
                • from "ExtractRGB.frag" location 0,
                • + *
                • in the upper-left quadrant of the window.
                + */ + private Picture display2; + /** + * Displays the green-channel output in monochrome
                  + *
                • from ExtractRGB.frag location 1,
                • + *
                • in the upper-right quadrant of the window.
                + */ + private Picture display3; + /** + * Displays the blue-channel output in monochrome
                  + *
                • from ExtractRGB.frag location 2,
                • + *
                • in the lower-right quadrant of the window.
                + */ + private Picture display4; + + private boolean initialized = false; + + /** + * The main entry point for the TestMultiRenderTarget application. + * + * @param args unused + */ + public static void main(String[] args) { TestMultiRenderTarget app = new TestMultiRenderTarget(); app.start(); } + /** + * Add 5 colorful cubes to the main scene. + */ + protected void buildScene() { + Geometry cube1 = buildCube(ColorRGBA.Red); + cube1.setLocalTranslation(-1f, 0f, 0f); + Geometry cube2 = buildCube(ColorRGBA.Green); + cube2.setLocalTranslation(0f, 0f, 0f); + Geometry cube3 = buildCube(ColorRGBA.Blue); + cube3.setLocalTranslation(1f, 0f, 0f); + + Geometry cube4 = buildCube(ColorRGBA.randomColor()); + cube4.setLocalTranslation(-0.5f, 1f, 0f); + Geometry cube5 = buildCube(ColorRGBA.randomColor()); + cube5.setLocalTranslation(0.5f, 1f, 0f); + + rootNode.attachChild(cube1); + rootNode.attachChild(cube2); + rootNode.attachChild(cube3); + rootNode.attachChild(cube4); + rootNode.attachChild(cube5); + } + + /** + * Create a cube with the specified color, + * using a custom unshaded material that outputs 4 textures:
                  + *
                • red channel only to location 0,
                • + *
                • green channel only to location 1,
                • + *
                • blue channel only to location 2,
                • + *
                • merged RGB to location 3.
                + * + * @param color the desired albedo color (alias created) + * @return a new Geometry with no parent + */ + private Geometry buildCube(ColorRGBA color) { + Geometry cube = new Geometry("Box", new Box(0.5f, 0.5f, 0.5f)); + Material mat = new Material(assetManager, "TestMRT/MatDefs/ExtractRGB.j3md"); + mat.setColor("Albedo", color); + cube.setMaterial(mat); + return cube; + } + @Override public void simpleInitApp() { viewPort.addProcessor(this); - -// flyCam.setEnabled(false); - cam.setLocation(new Vector3f(4.8037705f, 4.851632f, 10.789033f)); - cam.setRotation(new Quaternion(-0.05143692f, 0.9483723f, -0.21131563f, -0.230846f)); - - Node tank = (Node) assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); - - //tankMesh.getMaterial().setColor("Specular", ColorRGBA.Black); - rootNode.attachChild(tank); + buildScene(); display1 = new Picture("Picture"); display1.move(0, 0, -1); // make it appear behind stats view display2 = (Picture) display1.clone(); display3 = (Picture) display1.clone(); display4 = (Picture) display1.clone(); - display = (Picture) display1.clone(); - - ColorRGBA[] colors = new ColorRGBA[]{ - ColorRGBA.White, - ColorRGBA.Blue, - ColorRGBA.Cyan, - ColorRGBA.DarkGray, - ColorRGBA.Green, - ColorRGBA.Magenta, - ColorRGBA.Orange, - ColorRGBA.Pink, - ColorRGBA.Red, - ColorRGBA.Yellow - }; - - pls = new PointLight[3]; - for (int i = 0; i < pls.length; i++){ - PointLight pl = new PointLight(); - pl.setColor(colors[i % colors.length]); - pl.setRadius(5); - display.addLight(pl); - pls[i] = pl; - } } @Override - public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf);//To change body of generated methods, choose Tools | Templates. - for (int i = 0; i < 3; i++){ - PointLight pl = pls[i]; - float angle = (float)Math.PI * (i + (timer.getTimeInSeconds() % 6)/3); // 3s for full loop - pl.setPosition( new Vector3f(FastMath.cos(angle)*3f, 0, - FastMath.sin(angle)*3f)); - } + public void destroy() { + viewPort.removeProcessor(this); + super.destroy(); } + + // Scene Processor from now on + @Override public void initialize(RenderManager rm, ViewPort vp) { reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); viewPort.setOutputFrameBuffer(fb); guiViewPort.setClearFlags(true, true, true); - guiNode.attachChild(display); -// guiNode.attachChild(display1); + + guiNode.attachChild(display1); guiNode.attachChild(display2); -// guiNode.attachChild(display3); -// guiNode.attachChild(display4); + guiNode.attachChild(display3); + guiNode.attachChild(display4); guiNode.updateGeometricState(); } + @Override public void reshape(ViewPort vp, int w, int h) { - diffuseData = new Texture2D(w, h, Format.RGBA8); - normalData = new Texture2D(w, h, Format.RGBA8); - specularData = new Texture2D(w, h, Format.RGBA8); - depthData = new Texture2D(w, h, Format.Depth); + // You can use multiple channel formats as well. That's why red is using RGBA8 as an example. + Texture2D redTexture = new Texture2D(w, h, Format.RGBA8); // color texture + Texture2D greenTexture = new Texture2D(w, h, Format.Luminance8); // monochrome texture + Texture2D blueTexture = new Texture2D(w, h, Format.Luminance8); // monochrome texture + Texture2D rgbTexture = new Texture2D(w, h, Format.RGBA8); // color texture - mat = new Material(assetManager, "Common/MatDefs/Light/Deferred.j3md"); - mat.setTexture("DiffuseData", diffuseData); - mat.setTexture("SpecularData", specularData); - mat.setTexture("NormalData", normalData); - mat.setTexture("DepthData", depthData); - - display.setMaterial(mat); - display.setPosition(0, 0); - display.setWidth(w); - display.setHeight(h); - - display1.setTexture(assetManager, diffuseData, false); - display2.setTexture(assetManager, normalData, false); - display3.setTexture(assetManager, specularData, false); - display4.setTexture(assetManager, depthData, false); + fb = new FrameBuffer(w, h, 1); + fb.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(redTexture)); // location 0 + fb.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(greenTexture)); // location 1 + fb.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(blueTexture)); // location 2 + fb.addColorTarget(FrameBuffer.FrameBufferTarget.newTarget(rgbTexture)); // location 3 + fb.setMultiTarget(true); - display1.setPosition(0, 0); - display2.setPosition(w/2, 0); - display3.setPosition(0, h/2); - display4.setPosition(w/2, h/2); + display1.setTexture(assetManager, rgbTexture, false); + display2.setTexture(assetManager, redTexture, false); + display3.setTexture(assetManager, greenTexture, false); + display4.setTexture(assetManager, blueTexture, false); - display1.setWidth(w/2); - display1.setHeight(h/2); + display1.setPosition(0, 0); // lower-left quadrant + display1.setWidth(w / 2f); + display1.setHeight(h / 2f); - display2.setWidth(w/2); - display2.setHeight(h/2); + display2.setPosition(0, h / 2f); // upper-left quadrant + display2.setWidth(w / 2f); + display2.setHeight(h / 2f); - display3.setWidth(w/2); - display3.setHeight(h/2); + display3.setPosition(w / 2f, h / 2f); // upper-right quadrant + display3.setWidth(w / 2f); + display3.setHeight(h / 2f); - display4.setWidth(w/2); - display4.setHeight(h/2); + display4.setPosition(w / 2f, 0f); // lower-right quadrant + display4.setWidth(w / 2f); + display4.setHeight(h / 2f); guiNode.updateGeometricState(); - - fb = new FrameBuffer(w, h, 1); - fb.setDepthTexture(depthData); - fb.addColorTexture(diffuseData); - fb.addColorTexture(normalData); - fb.addColorTexture(specularData); - fb.setMultiTarget(true); - - /* - * Marks pixels in front of the far light boundary - Render back-faces of light volume - Depth test GREATER-EQUAL - Write to stencil on depth pass - Skipped for very small distant lights - */ - - /* - * Find amount of lit pixels inside the volume - Start pixel query - Render front faces of light volume - Depth test LESS-EQUAL - Don’t write anything – only EQUAL stencil test - */ - - /* - * Enable conditional rendering - Based on query results from previous stage - GPU skips rendering for invisible lights - */ - - /* - * Render front-faces of light volume - Depth test - LESS-EQUAL - Stencil test - EQUAL - Runs only on marked pixels inside light - */ + initialized = true; } + @Override public boolean isInitialized() { - return diffuseData != null; + return initialized; } + @Override public void preFrame(float tpf) { - Matrix4f inverseViewProj = cam.getViewProjectionMatrix().invert(); - mat.setMatrix4("ViewProjectionMatrixInverse", inverseViewProj); - techOrig = renderManager.getForcedTechnique(); - renderManager.setForcedTechnique("GBuf"); } + @Override public void postQueue(RenderQueue rq) { } + @Override public void postFrame(FrameBuffer out) { - renderManager.setForcedTechnique(techOrig); } + @Override public void cleanup() { + initialized = false; } @Override public void setProfiler(AppProfiler profiler) { - + // not implemented } - } diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java b/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java index adbc036c7f..fe0484ece5 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiViewsFilters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,7 @@ public static void main(String[] args) { } private boolean filterEnabled = true; + @Override public void simpleInitApp() { // create the geometry and attach it Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); @@ -149,7 +150,7 @@ public void simpleInitApp() { fpp.addFilter(rbf); - SSAOFilter f = new SSAOFilter(1.8899765f, 20.490374f, 0.4699998f, 0.1f);; + SSAOFilter f = new SSAOFilter(1.8899765f, 20.490374f, 0.4699998f, 0.1f); fpp4.addFilter(f); SSAOUI ui = new SSAOUI(inputManager, f); @@ -165,6 +166,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if (name.equals("press") && isPressed) { if (filterEnabled) { diff --git a/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java b/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java index f2aa6032e3..0d150bfe1a 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java +++ b/jme3-examples/src/main/java/jme3test/post/TestMultiplesFilters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,19 +54,19 @@ public class TestMultiplesFilters extends SimpleApplication { private static boolean useHttp = false; - public static void main(String[] args) { - File file = new File("wildhouse.zip"); - if (!file.exists()) { - useHttp = true; - } + public static void main(String[] args) { TestMultiplesFilters app = new TestMultiplesFilters(); app.start(); } - SSAOFilter ssaoFilter; - FilterPostProcessor fpp; - boolean en = true; + private SSAOFilter ssaoFilter; + @Override public void simpleInitApp() { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + this.flyCam.setMoveSpeed(10); cam.setLocation(new Vector3f(6.0344796f, 1.5054002f, 55.572033f)); cam.setRotation(new Quaternion(0.0016069f, 0.9810479f, -0.008143323f, 0.19358753f)); @@ -93,7 +93,7 @@ public void simpleInitApp() { sun.setColor(ColorRGBA.White.clone().multLocal(2)); scene.addLight(sun); - fpp = new FilterPostProcessor(assetManager); + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); // fpp.setNumSamples(4); ssaoFilter = new SSAOFilter(0.92f, 2.2f, 0.46f, 0.2f); final WaterFilter water=new WaterFilter(rootNode,new Vector3f(-0.4790551f, -0.39247334f, -0.7851566f)); @@ -117,6 +117,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if ("toggleSSAO".equals(name) && isPressed) { if (ssaoFilter.isEnabled()) { diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java index 7bd807073f..a46b14655c 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFilters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,13 +48,13 @@ import com.jme3.texture.Texture; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestPostFilters extends SimpleApplication implements ActionListener { private FilterPostProcessor fpp; - private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); - FadeFilter fade; + final private Vector3f lightDir = new Vector3f(-1, -1, .5f).normalizeLocal(); + private FadeFilter fade; public static void main(String[] args) { TestPostFilters app = new TestPostFilters(); @@ -111,7 +111,7 @@ public void setupLighting() { public void setupFloor() { Material mat = assetManager.loadMaterial("Textures/Terrain/BrickWall/BrickWall.j3m"); Box floor = new Box(50, 1f, 50); - TangentBinormalGenerator.generate(floor); + MikktspaceTangentGenerator.generate(floor); floor.scaleTextureCoordinates(new Vector2f(5, 5)); Geometry floorGeom = new Geometry("Floor", floor); floorGeom.setMaterial(mat); @@ -159,6 +159,7 @@ protected void initInput() { } + @Override public void onAction(String name, boolean value, float tpf) { if (name.equals("fadein") && value) { fade.fadeIn(); diff --git a/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java index 5a2353e38d..8661ee550e 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPostFiltersCompositing.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,8 @@ import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; +import com.jme3.texture.Image.Format; import com.jme3.util.SkyFactory; /** @@ -61,6 +63,7 @@ public static void main(String[] args) { } + @Override public void simpleInitApp() { this.flyCam.setMoveSpeed(10); cam.setLocation(new Vector3f(0.028406568f, 2.015769f, 7.386517f)); @@ -74,25 +77,26 @@ public void simpleInitApp() { fpp.addFilter(new ColorOverlayFilter(ColorRGBA.Blue)); viewPort.addProcessor(fpp); - //creating a frame buffer for the mainviewport + //creating a frame buffer for the main viewport FrameBuffer mainVPFrameBuffer = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); Texture2D mainVPTexture = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - mainVPFrameBuffer.addColorTexture(mainVPTexture); - mainVPFrameBuffer.setDepthBuffer(Image.Format.Depth); + mainVPFrameBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); + mainVPFrameBuffer.addColorTarget(FrameBufferTarget.newTarget(mainVPTexture)); + viewPort.setOutputFrameBuffer(mainVPFrameBuffer); - //creating the post processor for the gui viewport - final FilterPostProcessor guifpp = new FilterPostProcessor(assetManager); - guifpp.setFrameBufferFormat(Image.Format.RGBA8); - guifpp.addFilter(new ColorOverlayFilter(ColorRGBA.Red)); - //this will compose the main viewport texture with the guiviewport back buffer. - //Note that you can switch the order of the filters so that guiviewport filters are applied or not to the main viewport texture - guifpp.addFilter(new ComposeFilter(mainVPTexture)); + // Create the post processor for the GUI viewport. + final FilterPostProcessor guiFpp = new FilterPostProcessor(assetManager); + guiFpp.setFrameBufferFormat(Image.Format.RGBA8); + guiFpp.addFilter(new ColorOverlayFilter(ColorRGBA.Red)); + // This will compose the main viewport texture with the GUI-viewport back buffer. + // Note that you can switch the order of the filters so that GUI-viewport filters are applied or not to the main viewport texture + guiFpp.addFilter(new ComposeFilter(mainVPTexture)); - guiViewPort.addProcessor(guifpp); + guiViewPort.addProcessor(guiFpp); - //compositing is done by mixing texture depending on the alpha channel, - //it's important that the guiviewport clear color alpha value is set to 0 + // Compositing is done by mixing texture depending on the alpha channel, so + // it's important that the GUI-viewport clear-color alpha value is set to 0. guiViewPort.setBackgroundColor(ColorRGBA.BlackNoAlpha); guiViewPort.setClearColor(true); diff --git a/jme3-examples/src/main/java/jme3test/post/TestPosterization.java b/jme3-examples/src/main/java/jme3test/post/TestPosterization.java index c35236bc28..aebb415a07 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestPosterization.java +++ b/jme3-examples/src/main/java/jme3test/post/TestPosterization.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,8 +51,7 @@ public class TestPosterization extends SimpleApplication { - Spatial teapot; - PosterizationFilter pf; + private PosterizationFilter pf; public static void main(String[] args){ TestPosterization app = new TestPosterization(); @@ -79,7 +78,7 @@ public void simpleInitApp() { matSoil.setColor("Diffuse", ColorRGBA.Black); matSoil.setColor("Specular", ColorRGBA.Gray); - teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); teapot.setLocalTranslation(0,0,10); teapot.setMaterial(mat); @@ -121,6 +120,7 @@ private void initInputs() { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { pf.setEnabled(!pf.isEnabled()); diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java index b3473b7ec9..da00ccecd5 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToCubemap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,6 +47,7 @@ import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.TextureCubeMap; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; @@ -58,7 +59,6 @@ public class TestRenderToCubemap extends SimpleApplication { private Geometry offBox; private float angle = 0; - private ViewPort offView; public static void main(String[] args){ TestRenderToCubemap app = new TestRenderToCubemap(); @@ -68,7 +68,8 @@ public static void main(String[] args){ public Texture setupOffscreenView(){ Camera offCamera = new Camera(512, 512); - offView = renderManager.createPreView("Offscreen View", offCamera); + ViewPort offView + = renderManager.createPreView("Offscreen View", offCamera); offView.setClearFlags(true, true, true); offView.setBackgroundColor(ColorRGBA.DarkGray); @@ -86,15 +87,15 @@ public Texture setupOffscreenView(){ offTex.setMagFilter(Texture.MagFilter.Bilinear); //setup framebuffer to use texture - offBuffer.setDepthBuffer(Format.Depth); + offBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); offBuffer.setMultiTarget(true); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeX); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveX); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeY); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveY); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.NegativeZ); - offBuffer.addColorTexture(offTex, TextureCubeMap.Face.PositiveZ); - + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.NegativeX)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.PositiveX)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.NegativeY)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.PositiveY)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.NegativeZ)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex, TextureCubeMap.Face.PositiveZ)); + //set viewport to render to offscreen framebuffer offView.setOutputFrameBuffer(offBuffer); diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java index c46448efbe..1cfa50b9a2 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToMemory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,8 +49,8 @@ import com.jme3.system.AppSettings; import com.jme3.system.JmeContext.Type; import com.jme3.texture.FrameBuffer; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture2D; import com.jme3.util.BufferUtils; import com.jme3.util.Screenshots; import java.awt.Color; @@ -67,7 +67,7 @@ /** * This test renders a scene to an offscreen framebuffer, then copies - * the contents to a Swing JFrame. Note that some parts are done inefficently, + * the contents to a Swing JFrame. Note that some parts are done inefficiently, * this is done to make the code more readable. */ public class TestRenderToMemory extends SimpleApplication implements SceneProcessor { @@ -76,15 +76,11 @@ public class TestRenderToMemory extends SimpleApplication implements SceneProces private float angle = 0; private FrameBuffer offBuffer; - private ViewPort offView; - private Texture2D offTex; - private Camera offCamera; private ImageDisplay display; private static final int width = 800, height = 600; private final ByteBuffer cpuBuf = BufferUtils.createByteBuffer(width * height * 4); - private final byte[] cpuArray = new byte[width * height * 4]; private final BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); @@ -138,6 +134,7 @@ public static void main(String[] args){ public void createDisplayFrame(){ SwingUtilities.invokeLater(new Runnable(){ + @Override public void run(){ JFrame frame = new JFrame("Render Display"); display = new ImageDisplay(); @@ -145,6 +142,7 @@ public void run(){ frame.getContentPane().add(display); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.addWindowListener(new WindowAdapter(){ + @Override public void windowClosed(WindowEvent e){ stop(); } @@ -170,10 +168,11 @@ public void updateImageContents(){ } public void setupOffscreenView(){ - offCamera = new Camera(width, height); + Camera offCamera = new Camera(width, height); // create a pre-view. a view that is rendered before the main view - offView = renderManager.createPreView("Offscreen View", offCamera); + ViewPort offView + = renderManager.createPreView("Offscreen View", offCamera); offView.setBackgroundColor(ColorRGBA.DarkGray); offView.setClearFlags(true, true, true); @@ -194,9 +193,8 @@ public void setupOffscreenView(){ //setup framebuffer to use renderbuffer // this is faster for gpu -> cpu copies - offBuffer.setDepthBuffer(Format.Depth); - offBuffer.setColorBuffer(Format.RGBA8); -// offBuffer.setColorTexture(offTex); + offBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(Format.RGBA8)); //set viewport to render to offscreen framebuffer offView.setOutputFrameBuffer(offBuffer); @@ -229,19 +227,24 @@ public void simpleUpdate(float tpf){ offBox.updateGeometricState(); } + @Override public void initialize(RenderManager rm, ViewPort vp) { } + @Override public void reshape(ViewPort vp, int w, int h) { } + @Override public boolean isInitialized() { return true; } + @Override public void preFrame(float tpf) { } + @Override public void postQueue(RenderQueue rq) { } @@ -249,16 +252,18 @@ public void postQueue(RenderQueue rq) { * Update the CPU image's contents after the scene has * been rendered to the framebuffer. */ + @Override public void postFrame(FrameBuffer out) { updateImageContents(); } + @Override public void cleanup() { } @Override public void setProfiler(AppProfiler profiler) { - + // not implemented } diff --git a/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java b/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java index 037c959d36..88de57274d 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java +++ b/jme3-examples/src/main/java/jme3test/post/TestRenderToTexture.java @@ -49,6 +49,7 @@ import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; /** * This test renders a scene to a texture, then displays the texture on a cube. @@ -86,9 +87,9 @@ public Texture setupOffscreenView(){ offTex.setMagFilter(Texture.MagFilter.Bilinear); //setup framebuffer to use texture - offBuffer.setDepthBuffer(Format.Depth); - offBuffer.setColorTexture(offTex); - + offBuffer.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth)); + offBuffer.addColorTarget(FrameBufferTarget.newTarget(offTex)); + //set viewport to render to offscreen framebuffer offView.setOutputFrameBuffer(offBuffer); diff --git a/jme3-examples/src/main/java/jme3test/post/TestSSAO.java b/jme3-examples/src/main/java/jme3test/post/TestSSAO.java index 29ad9913b6..9275feb23d 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestSSAO.java +++ b/jme3-examples/src/main/java/jme3test/post/TestSSAO.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,8 +45,6 @@ public class TestSSAO extends SimpleApplication { - Geometry model; - public static void main(String[] args) { TestSSAO app = new TestSSAO(); app.start(); @@ -75,7 +73,8 @@ public void simpleInitApp() { rootNode.addLight(al); - model = (Geometry) assetManager.loadModel("Models/Sponza/Sponza.j3o"); + Geometry model + = (Geometry) assetManager.loadModel("Models/Sponza/Sponza.j3o"); model.getMesh().scaleTextureCoordinates(new Vector2f(2, 2)); model.setMaterial(mat); @@ -83,7 +82,7 @@ public void simpleInitApp() { rootNode.attachChild(model); FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - SSAOFilter ssaoFilter = new SSAOFilter(2.9299974f,32.920483f,5.8100376f,0.091000035f);; + SSAOFilter ssaoFilter = new SSAOFilter(2.9299974f,32.920483f,5.8100376f,0.091000035f); ssaoFilter.setApproximateNormals(true); fpp.addFilter(ssaoFilter); SSAOUI ui = new SSAOUI(inputManager, ssaoFilter); diff --git a/jme3-examples/src/main/java/jme3test/post/TestSSAO2.java b/jme3-examples/src/main/java/jme3test/post/TestSSAO2.java index 678484e089..34fe62e27c 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestSSAO2.java +++ b/jme3-examples/src/main/java/jme3test/post/TestSSAO2.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,8 +43,6 @@ public class TestSSAO2 extends SimpleApplication { - Geometry model; - public static void main(String[] args) { TestSSAO2 app = new TestSSAO2(); app.start(); @@ -78,7 +76,7 @@ public void simpleInitApp() { - // show normals as material + // A special Material to visualize mesh normals: //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); for (int f = 10; f > 3; f--) { for (int y = -f; y < f; y++) { diff --git a/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java b/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java new file mode 100644 index 0000000000..e277e8f943 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/TestSoftBloom.java @@ -0,0 +1,253 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package jme3test.post; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.DirectionalLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.SoftBloomFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import com.jme3.util.SkyFactory; +import com.jme3.util.SkyFactory.EnvMapType; + +/** + * Tests {@link SoftBloomFilter} with HDR. + *

                + * Note: the camera is pointed directly at the ground, which is completely + * black for some reason. + * + * @author codex + */ +public class TestSoftBloom extends SimpleApplication implements ActionListener, AnalogListener { + + private SoftBloomFilter bloom; + private BitmapText passes, factor, bilinear; + private BitmapText power, intensity; + private Material tankMat; + private float emissionPower = 50; + private float emissionIntensity = 50; + private final int maxPasses = 10; + private final float factorRate = 0.1f; + + public static void main(String[] args){ + TestSoftBloom app = new TestSoftBloom(); + app.start(); + } + + @Override + public void simpleInitApp() { + + cam.setLocation(new Vector3f(10, 10, 10)); + flyCam.setMoveSpeed(20); + + Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 15f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Diffuse", ColorRGBA.Yellow.mult(0.2f)); + mat.setColor("Specular", ColorRGBA.Yellow.mult(0.8f)); + + Material matSoil = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); + matSoil.setFloat("Shininess", 15f); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Gray); + matSoil.setColor("Diffuse", ColorRGBA.Gray); + matSoil.setColor("Specular", ColorRGBA.Gray); + + Spatial teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setLocalTranslation(0,0,10); + + teapot.setMaterial(mat); + teapot.setShadowMode(ShadowMode.CastAndReceive); + teapot.setLocalScale(10.0f); + rootNode.attachChild(teapot); + + Geometry soil = new Geometry("soil", new Box(800, 10, 700)); + soil.setLocalTranslation(0, -13, 550); + soil.setMaterial(matSoil); + soil.setShadowMode(ShadowMode.CastAndReceive); + rootNode.attachChild(soil); + + tankMat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + tankMat.setTexture("BaseColorMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_diffuse.jpg", !true))); + tankMat.setTexture("SpecularMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_specular.jpg", !true))); + tankMat.setTexture("NormalMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_normals.png", !true))); + tankMat.setTexture("EmissiveMap", assetManager.loadTexture(new TextureKey("Models/HoverTank/tank_glow_map.jpg", !true))); + tankMat.setFloat("EmissivePower", emissionPower); + tankMat.setFloat("EmissiveIntensity", 50); + tankMat.setFloat("Metallic", .5f); + Spatial tank = assetManager.loadModel("Models/HoverTank/Tank2.mesh.xml"); + tank.setLocalTranslation(-10, 5, -10); + tank.setMaterial(tankMat); + rootNode.attachChild(tank); + + DirectionalLight light=new DirectionalLight(); + light.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + light.setColor(ColorRGBA.White); + //rootNode.addLight(light); + + PointLight pl = new PointLight(); + pl.setPosition(new Vector3f(5, 5, 5)); + pl.setRadius(1000); + pl.setColor(ColorRGBA.White); + rootNode.addLight(pl); + + // load sky + Spatial sky = SkyFactory.createSky(assetManager, + "Textures/Sky/Bright/FullskiesBlueClear03.dds", + EnvMapType.CubeMap); + sky.setCullHint(Spatial.CullHint.Never); + rootNode.attachChild(sky); + EnvironmentProbeControl.tagGlobal(sky); + + rootNode.addControl(new EnvironmentProbeControl(assetManager, 256)); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + bloom = new SoftBloomFilter(); + fpp.addFilter(bloom); + viewPort.addProcessor(fpp); + + int textY = context.getSettings().getHeight()-5; + float xRow1 = 10, xRow2 = 250; + guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); + passes = createText("", xRow1, textY); + createText("[ R / F ]", xRow2, textY); + factor = createText("", xRow1, textY-25); + createText("[ T / G ]", xRow2, textY-25); + bilinear = createText("", xRow1, textY-25*2); + createText("[ space ]", xRow2, textY-25*2); + power = createText("", xRow1, textY-25*3); + createText("[ Y / H ]", xRow2, textY-25*3); + intensity = createText("", xRow1, textY-25*4); + createText("[ U / J ]", xRow2, textY-25*4); + updateHud(); + + inputManager.addMapping("incr-passes", new KeyTrigger(KeyInput.KEY_R)); + inputManager.addMapping("decr-passes", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addMapping("incr-factor", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("decr-factor", new KeyTrigger(KeyInput.KEY_G)); + inputManager.addMapping("toggle-bilinear", new KeyTrigger(KeyInput.KEY_SPACE)); + inputManager.addMapping("incr-power", new KeyTrigger(KeyInput.KEY_Y)); + inputManager.addMapping("decr-power", new KeyTrigger(KeyInput.KEY_H)); + inputManager.addMapping("incr-intensity", new KeyTrigger(KeyInput.KEY_U)); + inputManager.addMapping("decr-intensity", new KeyTrigger(KeyInput.KEY_J)); + inputManager.addListener(this, "incr-passes", "decr-passes", "incr-factor", "decr-factor", + "toggle-bilinear", "incr-power", "decr-power", "incr-intensity", "decr-intensity"); + + } + + @Override + public void simpleUpdate(float tpf) { + updateHud(); + } + + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (isPressed) { + if (name.equals("incr-passes")) { + bloom.setNumSamplingPasses(Math.min(bloom.getNumSamplingPasses()+1, maxPasses)); + } else if (name.equals("decr-passes")) { + bloom.setNumSamplingPasses(Math.max(bloom.getNumSamplingPasses()-1, 1)); + } else if (name.equals("toggle-bilinear")) { + bloom.setBilinearFiltering(!bloom.isBilinearFiltering()); + } + updateHud(); + } + } + + @Override + public void onAnalog(String name, float value, float tpf) { + if (name.equals("incr-factor")) { + bloom.setGlowFactor(bloom.getGlowFactor()+factorRate*tpf); + } else if (name.equals("decr-factor")) { + bloom.setGlowFactor(bloom.getGlowFactor()-factorRate*tpf); + } else if (name.equals("incr-power")) { + emissionPower += 10f*tpf; + updateTankMaterial(); + } else if (name.equals("decr-power")) { + emissionPower -= 10f*tpf; + updateTankMaterial(); + } else if (name.equals("incr-intensity")) { + emissionIntensity += 10f*tpf; + updateTankMaterial(); + } else if (name.equals("decr-intensity")) { + emissionIntensity -= 10f*tpf; + updateTankMaterial(); + } + updateHud(); + } + + private BitmapText createText(String string, float x, float y) { + BitmapText text = new BitmapText(guiFont); + text.setSize(guiFont.getCharSet().getRenderedSize()); + text.setLocalTranslation(x, y, 0); + text.setText(string); + guiNode.attachChild(text); + return text; + } + + private void updateHud() { + passes.setText("Passes = " + bloom.getNumSamplingPasses()); + factor.setText("Glow Factor = " + floatToString(bloom.getGlowFactor(), 5)); + bilinear.setText("Bilinear Filtering = " + bloom.isBilinearFiltering()); + power.setText("Emission Power = " + floatToString(emissionPower, 5)); + intensity.setText("Emission Intensity = " + floatToString(emissionIntensity, 5)); + } + + private String floatToString(float value, int length) { + String string = Float.toString(value); + return string.substring(0, Math.min(length, string.length())); + } + + private void updateTankMaterial() { + emissionPower = Math.max(emissionPower, 0); + emissionIntensity = Math.max(emissionIntensity, 0); + tankMat.setFloat("EmissivePower", emissionPower); + tankMat.setFloat("EmissiveIntensity", emissionIntensity); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java b/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java index 2d12958000..b886829650 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java +++ b/jme3-examples/src/main/java/jme3test/post/TestToneMapFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -99,6 +99,7 @@ private void initInputs() { ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { if (enabled) { @@ -116,6 +117,7 @@ public void onAction(String name, boolean keyPressed, float tpf) { AnalogListener anl = new AnalogListener() { + @Override public void onAnalog(String name, float isPressed, float tpf) { if (name.equals("WhitePointUp")) { whitePointLog += tpf * 1.0; diff --git a/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java b/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java index 46ba0ac49a..2e2c9874f7 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java +++ b/jme3-examples/src/main/java/jme3test/post/TestTransparentCartoonEdge.java @@ -12,7 +12,7 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.texture.Texture; public class TestTransparentCartoonEdge extends SimpleApplication { @@ -22,6 +22,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { renderManager.setAlphaToCoverage(true); cam.setLocation(new Vector3f(0.14914267f, 0.58147097f, 4.7686534f)); @@ -32,14 +33,14 @@ public void simpleInitApp() { viewPort.setBackgroundColor(ColorRGBA.DarkGray); - Quad q = new Quad(20, 20); - q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5)); - Geometry geom = new Geometry("floor", q); + RectangleMesh rm = new RectangleMesh( + new Vector3f(-10, 0, 10), + new Vector3f(10, 0, 10), + new Vector3f(-10, 0, -10)); + rm.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5)); + Geometry geom = new Geometry("floor", rm); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); geom.setMaterial(mat); - - geom.rotate(-FastMath.HALF_PI, 0, 0); - geom.center(); geom.setShadowMode(ShadowMode.Receive); rootNode.attachChild(geom); diff --git a/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java index 310cc2de1a..241796d444 100644 --- a/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java +++ b/jme3-examples/src/main/java/jme3test/post/TestTransparentSSAO.java @@ -11,8 +11,8 @@ import com.jme3.renderer.queue.RenderQueue.ShadowMode; import com.jme3.scene.Geometry; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.scene.shape.RectangleMesh; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; public class TestTransparentSSAO extends SimpleApplication { @@ -21,6 +21,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { renderManager.setAlphaToCoverage(true); cam.setLocation(new Vector3f(0.14914267f, 0.58147097f, 4.7686534f)); @@ -31,16 +32,17 @@ public void simpleInitApp() { viewPort.setBackgroundColor(ColorRGBA.DarkGray); - Quad q = new Quad(20, 20); - q.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5)); - Geometry geom = new Geometry("floor", q); + RectangleMesh rm = new RectangleMesh( + new Vector3f(-10, 0, 10), + new Vector3f(10, 0, 10), + new Vector3f(-10, 0, -10)); + rm.scaleTextureCoordinates(Vector2f.UNIT_XY.mult(5)); + Geometry geom = new Geometry("floor", rm); Material mat = assetManager.loadMaterial("Textures/Terrain/Pond/Pond.j3m"); geom.setMaterial(mat); - geom.rotate(-FastMath.HALF_PI, 0, 0); - geom.center(); geom.setShadowMode(ShadowMode.Receive); - TangentBinormalGenerator.generate(geom); + MikktspaceTangentGenerator.generate(geom); rootNode.attachChild(geom); // create the geometry and attach it diff --git a/jme3-examples/src/main/java/jme3test/post/package-info.java b/jme3-examples/src/main/java/jme3test/post/package-info.java new file mode 100644 index 0000000000..8b0450420a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/post/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for post-processing, including filters + */ +package jme3test.post; diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestAlphaToCoverage.java b/jme3-examples/src/main/java/jme3test/renderer/TestAlphaToCoverage.java index 1765e5478f..d5e02ae7a9 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestAlphaToCoverage.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestAlphaToCoverage.java @@ -25,12 +25,12 @@ public static void main(String[] args) { new TestAlphaToCoverage().start(); } - public GL gl = new LwjglGL(); - public GLExt glext = new LwjglGLExt(); - public GLFbo glfbo = new LwjglGLFboEXT(); - private GLRenderer glRenderer= new GLRenderer(gl,glext,glfbo); + final private GL gl = new LwjglGL(); + final private GLExt glext = new LwjglGLExt(); + final private GLFbo glfbo = new LwjglGLFboEXT(); + final private GLRenderer glRenderer= new GLRenderer(gl,glext,glfbo); - public EnumSet caps = glRenderer.getCaps(); + final private EnumSet caps = glRenderer.getCaps(); diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestAspectFov.java b/jme3-examples/src/main/java/jme3test/renderer/TestAspectFov.java new file mode 100644 index 0000000000..50a2377bef --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestAspectFov.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2009-2021 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.plugins.HttpZipLocator; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.AnalogListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Spatial; +import com.jme3.util.TempVars; + +/** + * Tests the setting of the FOV and aspect ratios. + * + * @author Markil 3 + */ +public class TestAspectFov extends SimpleApplication implements AnalogListener { + private static final String FOV_IN = "fovIn"; + private static final String FOV_OUT = "fovOut"; + private BitmapText header, fov; + + public static void main(String[] args) { + new TestAspectFov().start(); + } + + @Override + public void simpleInitApp() { + header = new BitmapText(this.guiFont); + header.setText("Adjust FOV with R/F or with mouse scroll"); + guiNode.attachChild(header); + fov = new BitmapText(this.guiFont); + guiNode.attachChild(fov); + + viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); + flyCam.setMoveSpeed(100); + + // We add light so we see the scene + AmbientLight al = new AmbientLight(); + al.setColor(ColorRGBA.White.mult(1.3f)); + rootNode.addLight(al); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(new Vector3f(2.8f, -2.8f, -2.8f).normalizeLocal()); + rootNode.addLight(dl); + + assetManager.registerLocator("https://storage.googleapis.com/google-code-archive-downloads/v2/code.google.com/jmonkeyengine/town.zip", + HttpZipLocator.class); + Spatial sceneModel = assetManager.loadModel("main.scene"); + sceneModel.setLocalScale(2f); + + rootNode.attachChild(sceneModel); + + inputManager.addMapping(FOV_IN, new KeyTrigger(KeyInput.KEY_R)); + inputManager.addMapping(FOV_OUT, new KeyTrigger(KeyInput.KEY_F)); + inputManager.addListener(this, FOV_IN, FOV_OUT); + } + + @Override + public void update() { + /* + * Updates the labels + */ + TempVars vars = TempVars.get(); + super.update(); + header.setLocalTranslation(0, cam.getHeight(), 0); + vars.vect1.set(header.getLocalTranslation()); + vars.vect1.subtractLocal(0, header.getLineHeight(), 0); + fov.setLocalTranslation(vars.vect1); + fov.setText("FOV: " + cam.getFov()); + vars.vect1.subtractLocal(0, fov.getLineHeight(), 0); + vars.release(); + } + + @Override + public void onAnalog(String name, float value, float tpf) { + final float CHANGE_VALUE = tpf * 10; + float newFov = cam.getFov(); + switch (name) { + case FOV_IN: + newFov -= CHANGE_VALUE; + break; + case FOV_OUT: + newFov += CHANGE_VALUE; + break; + } + if (newFov > 0 && newFov != cam.getFov()) { + cam.setFov(newFov); + } + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestBackgroundImage.java b/jme3-examples/src/main/java/jme3test/renderer/TestBackgroundImage.java new file mode 100644 index 0000000000..c8c004a9c3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestBackgroundImage.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2021 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.renderer; + +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.app.SimpleApplication; +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.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.texture.Texture; +import java.util.List; + +/** + * Demonstrates how to render a non-moving background image using a pre + * ViewPort. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestBackgroundImage extends SimpleApplication { + + private static ViewPort backgroundViewport; + + public static void main(String[] args) { + new TestBackgroundImage().start(); + } + + @Override + public void simpleInitApp() { + /* + * SimpleApplication creates 2 viewports: + * 1. the default viewport (rendered first, after clearing all buffers) + * 2. the GUI viewport (rendered last, without clearing any buffers) + * + * Create a 3rd ViewPort, named "background viewport", + * to be rendered BEFORE the default viewport. + */ + Camera backgroundCamera = guiViewPort.getCamera().clone(); + backgroundViewport = renderManager.createPreView( + "background viewport", backgroundCamera); + /* + * Don't clear the color buffer before drawing the main viewport. + * Clearing the color buffer would hide the background. + */ + boolean clearColorBuffer = false; + viewPort.setClearFlags(clearColorBuffer, true, true); + /* + * Create a quad to display the JMonkeyEngine logo, + * assign it to the Gui bucket, + * and attach it to the background viewport. + */ + Texture quadTexture + = assetManager.loadTexture("Interface/Logo/Monkey.png"); + Material quadMaterial = new Material(assetManager, Materials.UNSHADED); + quadMaterial.setTexture("ColorMap", quadTexture); + + float quadHeight = backgroundCamera.getHeight(); + float quadWidth = backgroundCamera.getWidth(); + Mesh quadMesh = new Quad(quadWidth, quadHeight); + + Spatial quadGeometry = new Geometry("quad geometry", quadMesh); + quadGeometry.setMaterial(quadMaterial); + quadGeometry.setQueueBucket(RenderQueue.Bucket.Gui); + backgroundViewport.attachScene(quadGeometry); + /* + * Add Jaime model and lighting to the default scene. + */ + loadModel(); + setupLights(); + /* + * Speed up camera motion for convenience. + */ + flyCam.setMoveSpeed(8f); + } + + @Override + public void simpleUpdate(float timePerFrame) { + /* + * Since SimpleApplication is unaware of the background viewport, + * the application must explicitly update its scenes. + */ + List scenes = backgroundViewport.getScenes(); + for (Spatial scene : scenes) { + scene.updateLogicalState(timePerFrame); + scene.updateGeometricState(); + } + } + + private void loadModel() { + Node jaime = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + AnimMigrationUtils.migrate(jaime); + jaime.scale(3f); + rootNode.attachChild(jaime); + } + + private void setupLights() { + AmbientLight ambient = new AmbientLight(); + ambient.setColor(new ColorRGBA(0.2f, 0.2f, 0.2f, 1f)); + rootNode.addLight(ambient); + + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(new Vector3f(0f, -1f, -1f).normalizeLocal()); + sun.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(sun); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java b/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java index 04eded427b..f979017b3c 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestBlendEquations.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ import com.jme3.scene.shape.Quad; /** - * This test demonstrates the usage of customized blend equations and factors on a material.
                + * This test demonstrates the usage of customized blend equations and factors on a material.
                * Customized blend equations and factors always requires {@link com.jme3.material.RenderState.BlendMode#Custom}. * * @author the_Minka @@ -61,6 +61,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { cam.setLocation(new Vector3f(0f, 0.5f, 3f)); viewPort.setBackgroundColor(ColorRGBA.LightGray); @@ -106,7 +107,7 @@ private void createLeftQuad() { } /** - * Adds a "transparent" quad to the scene, that limits the color values of the scene behind the object.
                + * Adds a "transparent" quad to the scene, that limits the color values of the scene behind the object.
                * This effect can be good seen on bright areas of the scene (e.g. areas with specular lighting effects). */ private void createRightQuad() { diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java b/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java new file mode 100644 index 0000000000..2244191459 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestContextRestart.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2009-2021 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.scene.Geometry; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +/** + * Tests whether gamma correction works after a context restart. This test + * generates a series of boxes, each one with a slightly different shade from + * the other. If the boxes look the same before and after the restart, that + * means that gamma correction is working properly. + *

                + * Note that for testing, it may be helpful to bypass the test chooser and run + * this class directly, since it can be easier to define your own settings + * beforehand. Of course, it should still work if all you need to test is the + * gamma correction, as long as you enable it in the settings dialog. + *

                + * + * @author Markil 3 + */ +public class TestContextRestart extends SimpleApplication +{ + public static final String INPUT_RESTART_CONTEXT = "SIMPLEAPP_Restart"; + + public static void main(String[] args) + { + TestContextRestart app = new TestContextRestart(); + AppSettings settings = new AppSettings(true); + settings.setGammaCorrection(true); +// settings.setRenderer(AppSettings.LWJGL_OPENGL32); + app.setSettings(settings); + app.start(); + } + + @Override + public void simpleInitApp() + { + for (int i = 0, l = 256; i < l; i += 8) + { + Geometry box = new Geometry("Box" + i, new Box(10, 200, 10)); + Material mat = new Material(this.assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", new ColorRGBA((float) i / 255F, 0, 0, 1)); + box.setMaterial(mat); + box.setLocalTranslation(-2.5F * (l / 2 - i), 0, -700); + box.addControl(new AbstractControl() + { + @Override + protected void controlUpdate(float tpf) + { + float[] angles = this.getSpatial() + .getLocalRotation() + .toAngles(new float[3]); + angles[0] = angles[0] + (FastMath.PI / 500F); + this.getSpatial() + .setLocalRotation(new Quaternion().fromAngles(angles)); + } + + @Override + protected void controlRender(RenderManager rm, ViewPort vp) + { + + } + }); + this.rootNode.attachChild(box); + } + + this.viewPort.setBackgroundColor(ColorRGBA.Yellow); + + this.flyCam.setEnabled(false); + this.inputManager.setCursorVisible(true); + + inputManager.addMapping(INPUT_RESTART_CONTEXT, new KeyTrigger( + KeyInput.KEY_TAB)); + this.inputManager.addListener(new ActionListener() + { + @Override + public void onAction(String name, boolean isPressed, float tpf) + { + if (name.equals(INPUT_RESTART_CONTEXT)) + { + restart(); + } + } + }, INPUT_RESTART_CONTEXT); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java index 9f4f4cef8d..20f0a55e89 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestDepthFuncChange.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { viewPort.setBackgroundColor(ColorRGBA.DarkGray); flyCam.setMoveSpeed(20); @@ -56,8 +57,8 @@ public void simpleInitApp() { //2 cubes, a blue and a red. the red cube is offset by 0.2 WU to the right //the red cube is put in the transparent bucket to be sure it's rendered after the blue one (but there is no transparency involved). //You should see a small part of the blue cube on the left and the whole red cube - Box boxshape1 = new Box(1f, 1f, 1f); - Geometry cube1 = new Geometry("box", boxshape1); + Box boxShape1 = new Box(1f, 1f, 1f); + Geometry cube1 = new Geometry("box", boxShape1); Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); mat.setColor("Color", ColorRGBA.Blue); @@ -72,8 +73,8 @@ public void simpleInitApp() { rootNode.attachChild(cube2); //Bottom of the screen - //here the 2 cubes are clonned and the depthFunc for the red cube's material is set to Less - //You should see the whole bleu cube and a small part of the red cube on the right + //here the 2 cubes are cloned and the depthFunc for the red cube's material is set to Less + //You should see the whole blue cube and a small part of the red cube on the right Geometry cube3 = cube1.clone(); Geometry cube4 = cube2.clone(true); cube4.getMaterial().getAdditionalRenderState().setDepthFunc(RenderState.TestFunction.Less); diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java b/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java index 77528893a2..0f333ac661 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestDepthStencil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,13 +48,14 @@ import com.jme3.texture.FrameBuffer; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture2D; +import com.jme3.texture.FrameBuffer.FrameBufferTarget; import com.jme3.ui.Picture; public class TestDepthStencil extends SimpleApplication { private boolean enableStencil = false; - private Node fbNode = new Node("Framebuffer Node"); + final private Node fbNode = new Node("Framebuffer Node"); private FrameBuffer fb; public static void main(String[] args){ @@ -71,8 +72,8 @@ public void simpleInitApp() { fb = new FrameBuffer(w, h, 1); Texture2D fbTex = new Texture2D(w, h, Format.RGB8); - fb.setDepthBuffer(Format.Depth24Stencil8); - fb.setColorTexture(fbTex); + fb.setDepthTarget(FrameBufferTarget.newTarget(Format.Depth24Stencil8)); + fb.addColorTarget(FrameBufferTarget.newTarget(fbTex)); // setup framebuffer's scene Sphere sphMesh = new Sphere(20, 20, 1); @@ -86,12 +87,12 @@ public void simpleInitApp() { @Override protected void controlUpdate(float tpf) { Material mat = sphere.getMaterial(); - mat.getAdditionalRenderState().setStencil(enableStencil, + mat.getAdditionalRenderState().setStencil(enableStencil, RenderState.StencilOperation.Keep, RenderState.StencilOperation.Keep, RenderState.StencilOperation.Keep, RenderState.StencilOperation.Keep, RenderState.StencilOperation.Keep, RenderState.StencilOperation.Keep, RenderState.TestFunction.Never, RenderState.TestFunction.Never //TestFunction.Always, TestFunction.Always - ); + ); } @Override @@ -110,6 +111,7 @@ protected void controlRender(RenderManager rm, ViewPort vp) { inputManager.addMapping("toggle", new KeyTrigger(KeyInput.KEY_SPACE)); ActionListener acl = new ActionListener() { + @Override public void onAction(String name, boolean keyPressed, float tpf) { if (name.equals("toggle") && keyPressed) { if (enableStencil) { diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestInconsistentCompareDetection.java b/jme3-examples/src/main/java/jme3test/renderer/TestInconsistentCompareDetection.java index c1f99b35dc..dbb2eaad8d 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestInconsistentCompareDetection.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestInconsistentCompareDetection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -92,6 +92,7 @@ public void simpleInitApp() { } Thread evilThread = new Thread(new Runnable() { + @Override public void run() { try { Thread.sleep(1000); diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestIssue2011.java b/jme3-examples/src/main/java/jme3test/renderer/TestIssue2011.java new file mode 100644 index 0000000000..037dd980a6 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestIssue2011.java @@ -0,0 +1,107 @@ +/* + * 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.system.AppSettings; + +/** + * Test for JMonkeyEngine issue #2011: context profiles are not defined for + * OpenGL v3.0/v3.1 + *

                + * If the issue is resolved, then pressing the "0" or "1" key shouldn't crash + * the app; it should close the app display and create a new display, mostly + * black with statistics displayed in the lower left. + *

                + * If the issue is not resolved, then pressing the "0" or "1" key should crash + * the app with multiple exceptions. + *

                + * Since the issue was specific to LWJGL v3, this test should be built with the + * jme3-lwjgl3 library, not jme3-lwjgl. + * + * @author Stephen Gold + */ +public class TestIssue2011 extends SimpleApplication { + /** + * Main entry point for the TestIssue2011 application. + * + * @param args array of command-line arguments (not null) + */ + public static void main(String[] args) { + TestIssue2011 app = new TestIssue2011(); + app.start(); + } + + /** + * Initialize this application. + */ + @Override + public void simpleInitApp() { + inputManager.addMapping("3.0", new KeyTrigger(KeyInput.KEY_0), + new KeyTrigger(KeyInput.KEY_NUMPAD0)); + inputManager.addMapping("3.1", new KeyTrigger(KeyInput.KEY_1), + new KeyTrigger(KeyInput.KEY_NUMPAD1)); + inputManager.addMapping("3.2", new KeyTrigger(KeyInput.KEY_2), + new KeyTrigger(KeyInput.KEY_NUMPAD2)); + + ActionListener listener = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("3.0") && keyPressed) { + setApi(AppSettings.LWJGL_OPENGL30); + } else if (name.equals("3.1") && keyPressed) { + setApi(AppSettings.LWJGL_OPENGL31); + } else if (name.equals("3.2") && keyPressed) { + setApi(AppSettings.LWJGL_OPENGL32); + } + } + }; + + inputManager.addListener(listener, "3.0", "3.1", "3.2"); + } + + /** + * Restart the app, specifying which OpenGL version to use. + * + * @param desiredApi the string to be passed to setRenderer() + */ + private void setApi(String desiredApi) { + System.out.println("desiredApi = " + desiredApi); + settings.setRenderer(desiredApi); + setSettings(settings); + + restart(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestIssue37.java b/jme3-examples/src/main/java/jme3test/renderer/TestIssue37.java new file mode 100644 index 0000000000..ce276e81d9 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestIssue37.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2021 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.renderer; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.material.MatParamOverride; +import com.jme3.material.Material; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Box; +import com.jme3.shader.VarType; +import com.jme3.texture.Texture; + +/** + * Test a Material with increasing numbers of texture parameters, to see what + * happens when the renderer's dynamic limit is exceeded. + * + * If successful, this test throws an IllegalStateException with a helpful + * diagnostic message. + */ +public class TestIssue37 extends SimpleApplication { + + /** + * Edit this field to change how parameters are assigned (which determines + * where the exception is caught): true to use mat param overrides, false to + * use ordinary mat params. + */ + final private boolean useOverrides = true; + + private int numTextures; + private Material manyTexturesMaterial; + private Texture testTexture; + + public static void main(String[] args) { + Application application = new TestIssue37(); + application.start(); + } + + @Override + public void simpleInitApp() { + /* + * Attach a test geometry to the scene. + */ + Mesh cubeMesh = new Box(1f, 1f, 1f); + Geometry cubeGeometry = new Geometry("Box", cubeMesh); + rootNode.attachChild(cubeGeometry); + /* + * Apply a test material (with no textures assigned) to the geometry. + */ + manyTexturesMaterial = new Material(assetManager, + "jme3test/materials/TestIssue37.j3md"); + manyTexturesMaterial.setName("manyTexturesMaterial"); + cubeGeometry.setMaterial(manyTexturesMaterial); + numTextures = 0; + /* + * Load the test texture. + */ + String texturePath = "Interface/Logo/Monkey.jpg"; + testTexture = assetManager.loadTexture(texturePath); + } + + /** + * During each update, define another texture parameter until the dynamic + * limit is reached. + * + * @param tpf ignored + */ + @Override + public void simpleUpdate(float tpf) { + String parameterName = "ColorMap" + numTextures; + if (useOverrides) { + MatParamOverride override = new MatParamOverride(VarType.Texture2D, + parameterName, testTexture); + rootNode.addMatParamOverride(override); + } else { + manyTexturesMaterial.setTexture(parameterName, testTexture); + } + ++numTextures; + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestIssue798.java b/jme3-examples/src/main/java/jme3test/renderer/TestIssue798.java new file mode 100644 index 0000000000..d350566d03 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestIssue798.java @@ -0,0 +1,156 @@ +/* + * 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; + +/** + * Test for JMonkeyEngine issue #798: OpenGLException on restart with changed + * display settings. + *

                + * If the issue is resolved, then pressing the "P", "T", or "Y" key shouldn't + * crash the app; it should close the app display and create a new display, + * mostly black with statistics displayed in the lower left. + *

                + * If the issue is not resolved, the expected failure mode depends on whether + * assertions are enabled. If they're enabled, the app will crash with an + * OpenGLException. If assertions aren't enabled, the new window will be + * entirely black, with no statistics visible. + *

                + * Since the issue was specific to LWJGL v2, this test should be built with the + * jme3-lwjgl library, not jme3-lwjgl3. + * + * @author Stephen Gold + */ +public class TestIssue798 extends SimpleApplication { + /** + * Main entry point for the TestIssue798 application. + * + * @param args array of command-line arguments (not null) + */ + public static void main(String[] args) { + TestIssue798 app = new TestIssue798(); + app.start(); + } + + /** + * Initialize this application. + */ + @Override + public void simpleInitApp() { + inputManager.addMapping("windowedMode", new KeyTrigger(KeyInput.KEY_F)); + inputManager.addMapping("moreSamples", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("toggleDepth", new KeyTrigger(KeyInput.KEY_T)); + inputManager.addMapping("toggleBpp", new KeyTrigger(KeyInput.KEY_Y)); + + ActionListener listener = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("moreSamples") && keyPressed) { + moreSamples(); + } else if (name.equals("toggleBpp") && keyPressed) { + toggleBpp(); + } else if (name.equals("toggleDepth") && keyPressed) { + toggleDepth(); + } else if (name.equals("windowedMode") && keyPressed) { + windowedMode(); + } + } + }; + + inputManager.addListener(listener, + "moreSamples", "toggleBpp", "toggleDepth", "windowedMode"); + } + + /** + * Restart the app, requesting 2 more MSAA samples per pixel. + */ + private void moreSamples() { + int numSamples = settings.getSamples(); + numSamples += 2; + System.out.println("numSamples = " + numSamples); + settings.setSamples(numSamples); + setSettings(settings); + + restart(); + } + + /** + * Restart the app, requesting a different number of bits per pixel in the + * RGB buffer. + */ + private void toggleBpp() { + int bpp = settings.getBitsPerPixel(); + bpp = (bpp == 24) ? 16 : 24; + System.out.println("BPP = " + bpp); + settings.setBitsPerPixel(bpp); + setSettings(settings); + + restart(); + } + + /** + * Restart the app, requesting a different number of bits per pixel in the + * depth buffer. + */ + private void toggleDepth() { + int depthBits = settings.getDepthBits(); + depthBits = (depthBits == 24) ? 16 : 24; + System.out.println("depthBits = " + depthBits); + settings.setDepthBits(depthBits); + setSettings(settings); + + restart(); + } + + /** + * If the app is in fullscreen mode, restart it in 640x480 windowed mode. + */ + private void windowedMode() { + boolean isFullscreen = settings.isFullscreen(); + if (!isFullscreen) { + System.out.println("Request ignored: already in windowed mode!"); + return; + } + + System.out.println("fullscreen = " + false); + settings.setFullscreen(false); + settings.setWidth(640); + settings.setHeight(480); + setSettings(settings); + + restart(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestIssue801.java b/jme3-examples/src/main/java/jme3test/renderer/TestIssue801.java new file mode 100644 index 0000000000..0f1893c43b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestIssue801.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2009-2020 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; + +public class TestIssue801 extends SimpleApplication { + + public static void main(String[] args) { + AppSettings initialSettings = new AppSettings(true); + initialSettings.setBitsPerPixel(24); + + TestIssue801 app = new TestIssue801(); + app.setSettings(initialSettings); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(new ColorRGBA(0.3f, 0.3f, 0.3f, 1f)); + + Box b = new Box(1, 1, 1); + Geometry geom = new Geometry("Box", b); + + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", ColorRGBA.Blue); + geom.setMaterial(mat); + + rootNode.attachChild(geom); + inputManager.addMapping("changeBpp", new KeyTrigger(KeyInput.KEY_P)); + ActionListener listener = new ActionListener() { + @Override + public void onAction(String name, boolean keyPressed, float tpf) { + if (name.equals("changeBpp") && keyPressed) { + goWindowed(); + } + } + }; + inputManager.addListener(listener, "changeBpp"); + } + + void goWindowed() { + AppSettings newSettings = new AppSettings(false); + newSettings.copyFrom(settings); + newSettings.setBitsPerPixel(16); + + setSettings(newSettings); + restart(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestLineWidth.java b/jme3-examples/src/main/java/jme3test/renderer/TestLineWidth.java new file mode 100644 index 0000000000..e73132d314 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/TestLineWidth.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2021 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.renderer; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.material.Material; +import com.jme3.material.Materials; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.shape.Line; +import com.jme3.system.AppSettings; + +/** + * Display the renderer's maximum line width. + * + * @author Stephen Gold sgold@sonic.net + */ +public class TestLineWidth extends SimpleApplication { + + public static void main(String... args) { + TestLineWidth app = new TestLineWidth(); + AppSettings set = new AppSettings(true); + set.setRenderer(AppSettings.LWJGL_OPENGL2); + app.setSettings(set); + app.start(); + } + + @Override + public void simpleInitApp() { + /* + * Generate a message to report (1) which renderer is selected + * and (2) the maximum line width. + */ + String rendererName = settings.getRenderer(); + float maxWidth = renderer.getMaxLineWidth(); + String message = String.format( + "using %s renderer%nmaximum line width = %.1f pixel%s", + rendererName, maxWidth, (maxWidth == 1f) ? "" : "s"); + /* + * Display the message, centered near the top of the display. + */ + BitmapFont font = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText text = new BitmapText(font); + text.setSize(font.getCharSet().getRenderedSize()); + text.setText(message); + float leftX = (cam.getWidth() - text.getLineWidth()) / 2; + float topY = cam.getHeight(); + text.setLocalTranslation(leftX, topY, 0f); + guiNode.attachChild(text); + /* + * Display a vertical green line on the left side of the display. + */ + float lineWidth = Math.min(maxWidth, leftX); + drawVerticalLine(lineWidth, leftX / 2, ColorRGBA.Green); + } + + private void drawVerticalLine(float lineWidth, float x, ColorRGBA color) { + Material material = new Material(assetManager, Materials.UNSHADED); + material.setColor("Color", color.clone()); + material.getAdditionalRenderState().setLineWidth(lineWidth); + + float viewportHeight = cam.getHeight(); + Vector3f startLocation = new Vector3f(x, 0.1f * viewportHeight, 0f); + Vector3f endLocation = new Vector3f(x, 0.9f * viewportHeight, 0f); + Mesh wireMesh = new Line(startLocation, endLocation); + Geometry wire = new Geometry("wire", wireMesh); + wire.setMaterial(material); + guiNode.attachChild(wire); + } +} diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java b/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java index b098eaddd4..ae8e51dca5 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestMultiViews.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,6 +47,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { // create the geometry and attach it Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java b/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java index 5fc7ab0990..990da80158 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestParallelProjection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { Geometry teaGeom = (Geometry) assetManager.loadModel("Models/Teapot/Teapot.obj"); @@ -70,6 +71,7 @@ public void simpleInitApp() { inputManager.addMapping("Size-", new KeyTrigger(KeyInput.KEY_S)); } + @Override public void onAnalog(String name, float value, float tpf) { // Instead of moving closer/farther to object, we zoom in/out. if (name.equals("Size-")) diff --git a/jme3-examples/src/main/java/jme3test/renderer/TestSplitScreen.java b/jme3-examples/src/main/java/jme3test/renderer/TestSplitScreen.java index fe6390aaa4..51f2f37071 100644 --- a/jme3-examples/src/main/java/jme3test/renderer/TestSplitScreen.java +++ b/jme3-examples/src/main/java/jme3test/renderer/TestSplitScreen.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2017 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,11 +50,10 @@ */ public class TestSplitScreen extends SimpleApplication implements ActionListener { - boolean splitScreen = false; - Box mesh = new Box(1f, 1f, 1f); - Camera leftCam, rightCam; - Node leftScene = new Node("left scene"); - ViewPort leftView, rightView; + private boolean splitScreen = false; + final private Box mesh = new Box(1f, 1f, 1f); + final private Node leftScene = new Node("left scene"); + private ViewPort leftView, rightView; @Override public void simpleInitApp() { @@ -66,7 +65,7 @@ public void simpleInitApp() { blueBox.setMaterial(blueMat); rootNode.attachChild(blueBox); - rightCam = cam.clone(); + Camera rightCam = cam.clone(); rightCam.setViewPort(0.5f, 1f, 0f, 1f); rightView = renderManager.createMainView("right", rightCam); @@ -80,7 +79,7 @@ public void simpleInitApp() { redBox.setMaterial(redMat); leftScene.attachChild(redBox); - leftCam = cam.clone(); + Camera leftCam = cam.clone(); leftCam.setViewPort(0f, 0.5f, 0f, 1f); leftView = renderManager.createMainView("left", leftCam); diff --git a/jme3-examples/src/main/java/jme3test/renderer/package-info.java b/jme3-examples/src/main/java/jme3test/renderer/package-info.java new file mode 100644 index 0000000000..aa907287a7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/renderer/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for rendering, including cameras + */ +package jme3test.renderer; diff --git a/jme3-examples/src/main/java/jme3test/scene/TestSceneIteration.java b/jme3-examples/src/main/java/jme3test/scene/TestSceneIteration.java new file mode 100644 index 0000000000..5404f29ff7 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/TestSceneIteration.java @@ -0,0 +1,127 @@ +/* + * 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.scene; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.SceneGraphIterator; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; + +/** + * Test suite for {@link SceneGraphIterator}. + *

                + * The test succeeds if the rootNode and all its children, + * except all spatials named "XXX", are printed on the console + * with indents precisely indicating each spatial's distance + * from the rootNode. + *

                + * The test fails if + *

                  + *
                • Not all expected children are printed on the console. + *
                • An XXX is printed on the console (indicating faulty {@code ignoreChildren}). + *
                • Indents do not accurately indicate distance from the rootNode. + *
                + * + * @author codex + */ +public class TestSceneIteration extends SimpleApplication { + + /** + * Launches the test application. + * + * @param args no argument required + */ + public static void main(String[] args) { + new TestSceneIteration().start(); + } + + @Override + public void simpleInitApp() { + + // setup scene graph + Node n1 = new Node("town"); + rootNode.attachChild(n1); + n1.attachChild(new Node("car")); + n1.attachChild(new Node("tree")); + Node n2 = new Node("house"); + n1.attachChild(n2); + n2.attachChild(new Node("chairs")); + n2.attachChild(new Node("tables")); + n2.attachChild(createGeometry("house-geometry")); + Node n3 = new Node("sky"); + rootNode.attachChild(n3); + n3.attachChild(new Node("airplane")); + Node ignore = new Node("cloud"); + n3.attachChild(ignore); + ignore.attachChild(new Node("XXX")); + ignore.attachChild(new Node("XXX")); + ignore.attachChild(new Node("XXX")); + n3.attachChild(new Node("bird")); + + // iterate + SceneGraphIterator iterator = new SceneGraphIterator(rootNode); + for (Spatial spatial : iterator) { + // create a hierarchy in the console + System.out.println(constructTabs(iterator.getDepth()) + spatial.getName()); + // see if the children of this spatial should be ignored + if (spatial == ignore) { + // ignore all children of this spatial + iterator.ignoreChildren(); + } + } + + // exit the application + stop(); + + } + + private Geometry createGeometry(String name) { + Geometry g = new Geometry(name, new Box(1, 1, 1)); + Material m = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + m.setColor("Color", ColorRGBA.Blue); + g.setMaterial(m); + return g; + } + + private String constructTabs(int n) { + StringBuilder render = new StringBuilder(); + for (; n > 0; n--) { + render.append(" | "); + } + return render.toString(); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java b/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java index 2567d94fa1..0e007de394 100644 --- a/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java +++ b/jme3-examples/src/main/java/jme3test/scene/TestSceneLoading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,8 +47,8 @@ public class TestSceneLoading extends SimpleApplication { - private Sphere sphereMesh = new Sphere(32, 32, 10, false, true); - private Geometry sphere = new Geometry("Sky", sphereMesh); + final private Sphere sphereMesh = new Sphere(32, 32, 10, false, true); + final private Geometry sphere = new Geometry("Sky", sphereMesh); private static boolean useHttp = false; public static void main(String[] args) { @@ -62,18 +62,20 @@ public void simpleUpdate(float tpf){ sphere.setLocalTranslation(cam.getLocation()); } + @Override public void simpleInitApp() { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + this.flyCam.setMoveSpeed(10); // load sky rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap)); - - File file = new File("wildhouse.zip"); - if (!file.exists()) { - useHttp = true; - } + // create the geometry and attach it // load the level from zip or http zip if (useHttp) { diff --git a/jme3-examples/src/main/java/jme3test/scene/TestSceneStress.java b/jme3-examples/src/main/java/jme3test/scene/TestSceneStress.java index 7d70e2b27a..81ff47e092 100644 --- a/jme3-examples/src/main/java/jme3test/scene/TestSceneStress.java +++ b/jme3-examples/src/main/java/jme3test/scene/TestSceneStress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,10 +59,10 @@ */ public class TestSceneStress extends SimpleApplication { - private static Box BOX = new Box(2f, 0.5f, 0.5f); + final private static Box BOX = new Box(2f, 0.5f, 0.5f); private Material mat; - private Random random = new Random(0); + final private Random random = new Random(0); private int totalNodes = 0; private int totalGeometry = 0; @@ -142,7 +142,7 @@ protected Spatial createOctSplit( String name, int size, int depth ) { } private class RotatorControl extends AbstractControl { - private float[] rotate; + final private float[] rotate; public RotatorControl( float... rotate ) { this.rotate = rotate; diff --git a/jme3-examples/src/main/java/jme3test/scene/TestUserData.java b/jme3-examples/src/main/java/jme3test/scene/TestUserData.java index 59cf6d3741..b482f1906e 100644 --- a/jme3-examples/src/main/java/jme3test/scene/TestUserData.java +++ b/jme3-examples/src/main/java/jme3test/scene/TestUserData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,6 +43,7 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { Node scene = (Node) assetManager.loadModel("Scenes/DotScene/DotScene.scene"); System.out.println("Scene: " + scene); diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java index 371340897b..8a3da3abdd 100644 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ public class TestInstanceNode extends SimpleApplication { private final Material[] materials = new Material[6]; private Node instancedNode; private float time = 0; - private boolean INSTANCING = true; + final private boolean INSTANCING = true; public static void main(String[] args){ TestInstanceNode app = new TestInstanceNode(); diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithLight.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithLight.java index 29f85301b1..087ce1b7c3 100644 --- a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithLight.java +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithLight.java @@ -18,8 +18,8 @@ public static void main(String[] args) { app.start(); } - Geometry box; - PointLight pointLight; + private Geometry box; + private PointLight pointLight; @Override public void simpleInitApp() { diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithPbr.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithPbr.java new file mode 100644 index 0000000000..0e2c8894f4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstanceNodeWithPbr.java @@ -0,0 +1,92 @@ +package jme3test.scene.instancing; + +import java.util.Locale; + +import com.jme3.app.SimpleApplication; +import com.jme3.font.BitmapText; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; + +/** + * This test specifically validates the corrected PBR rendering when combined + * with instancing, as addressed in issue #2435. + * + * It creates an InstancedNode + * with a PBR-materialized Box to ensure the fix in PBRLighting.vert correctly + * handles world position calculations for instanced geometry. + */ +public class TestInstanceNodeWithPbr extends SimpleApplication { + + public static void main(String[] args) { + TestInstanceNodeWithPbr app = new TestInstanceNodeWithPbr(); + app.start(); + } + + private BitmapText bmp; + private Geometry box; + private float pos = -5; + private float vel = 5; + + @Override + public void simpleInitApp() { + configureCamera(); + bmp = createLabelText(10, 20, ""); + + InstancedNode instancedNode = new InstancedNode("InstancedNode"); + rootNode.attachChild(instancedNode); + + Box mesh = new Box(0.5f, 0.5f, 0.5f); + box = new Geometry("Box", mesh); + Material pbrMaterial = createPbrMaterial(ColorRGBA.Red); + box.setMaterial(pbrMaterial); + + instancedNode.attachChild(box); + instancedNode.instance(); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -2, -3).normalizeLocal()); + rootNode.addLight(light); + } + + private Material createPbrMaterial(ColorRGBA color) { + Material mat = new Material(assetManager, "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setColor("BaseColor", color); + mat.setFloat("Roughness", 0.8f); + mat.setFloat("Metallic", 0.1f); + mat.setBoolean("UseInstancing", true); + return mat; + } + + private void configureCamera() { + flyCam.setMoveSpeed(15f); + flyCam.setDragToRotate(true); + + cam.setLocation(Vector3f.UNIT_XYZ.mult(12)); + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + + private BitmapText createLabelText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, settings.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + return bmp; + } + + @Override + public void simpleUpdate(float tpf) { + pos += tpf * vel; + if (pos < -10f || pos > 10f) { + vel *= -1; + } + box.setLocalTranslation(pos, 0f, 0f); + bmp.setText(String.format(Locale.ENGLISH, "BoxPosition: (%.2f, %.1f, %.1f)", pos, 0f, 0f)); + } + +} diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithPicking.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithPicking.java new file mode 100644 index 0000000000..054f4e6efd --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithPicking.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2009-2021 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.collision.CollisionResult; +import com.jme3.collision.CollisionResults; +import com.jme3.font.BitmapText; +import com.jme3.input.MouseInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.MouseButtonTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Ray; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.system.AppSettings; + + +/** + * A test case for using instancing with ray casting. + * + * Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode. + * + * @author duncanj + */ +public class TestInstancedNodeAttachDetachWithPicking extends SimpleApplication { + public static void main(String[] args) { + TestInstancedNodeAttachDetachWithPicking app = new TestInstancedNodeAttachDetachWithPicking(); + AppSettings settings = new AppSettings(true); + settings.setVSync(false); + app.setSettings(settings); + app.start(); + } + + private InstancedNode instancedNode; + + final private Vector3f[] locations = new Vector3f[10]; + final private Geometry[] spheres = new Geometry[10]; + final private Geometry[] boxes = new Geometry[10]; + + @Override + public void simpleInitApp() { + addPointLight(); + addAmbientLight(); + + Material material = createInstancedLightingMaterial(); + + instancedNode = new InstancedNode("theParentInstancedNode"); + rootNode.attachChild(instancedNode); + Sphere sphereMesh = new Sphere(16, 16, 1f); + Box boxMesh = new Box(0.7f, 0.7f, 0.7f); + // create 10 spheres & boxes, positioned along Z-axis successively further from the camera + for (int i = 0; i < 10; i++) { + Vector3f location = new Vector3f(0, -3, -(i*5)); + locations[i] = location; + + Geometry sphere = new Geometry("sphere", sphereMesh); + sphere.setMaterial(material); + sphere.setLocalTranslation(location); + instancedNode.attachChild(sphere); // initially just add the spheres to the InstancedNode + spheres[i] = sphere; + + Geometry box = new Geometry("box", boxMesh); + box.setMaterial(material); + box.setLocalTranslation(location); + boxes[i] = box; + } + instancedNode.instance(); + + flyCam.setMoveSpeed(30); + + + addCrossHairs(); + + // when you left-click, print the distance to the object to system.out + inputManager.addMapping("leftClick", new MouseButtonTrigger(MouseInput.BUTTON_LEFT)); + inputManager.addListener(new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if( isPressed ) { + CollisionResult result = pickFromCamera(); + if( result != null ) { + System.out.println("Picked = " + result.getGeometry() + ", Distance = "+result.getDistance()); + } + } + } + }, "leftClick"); + } + + @Override + public void simpleUpdate(float tpf) { + // Each frame, determine the distance to each sphere/box from the camera. + // If the object is > 25 units away, switch in the Box. If it's nearer, switch in the Sphere. + // Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc. + + + boolean modified = false; + for (int i = 0; i < 10; i++) { + Vector3f location = locations[i]; + float distance = location.distance(cam.getLocation()); + + if(distance > 25.0f && boxes[i].getParent() == null) { + modified = true; + instancedNode.attachChild(boxes[i]); + instancedNode.detachChild(spheres[i]); + } else if(distance <= 25.0f && spheres[i].getParent() == null) { + modified = true; + instancedNode.attachChild(spheres[i]); + instancedNode.detachChild(boxes[i]); + } + } + + if(modified) { + instancedNode.instance(); + } + } + + private Material createInstancedLightingMaterial() { + Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", true); + material.setBoolean("UseInstancing", true); + material.setColor("Ambient", ColorRGBA.Red); + material.setColor("Diffuse", ColorRGBA.Red); + material.setColor("Specular", ColorRGBA.Red); + material.setFloat("Shininess", 1.0f); + return material; + } + + private void addAmbientLight() { + AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.2f, 0.2f, 0.2f, 1.0f)); + rootNode.addLight(ambientLight); + } + + private void addPointLight() { + PointLight pointLight = new PointLight(); + pointLight.setColor(ColorRGBA.White); + pointLight.setRadius(100f); + pointLight.setPosition(new Vector3f(10f, 10f, 0)); + rootNode.addLight(pointLight); + } + + private void addCrossHairs() { + BitmapText ch = new BitmapText(guiFont); + ch.setSize(guiFont.getCharSet().getRenderedSize()+4); + ch.setText("+"); // crosshairs + ch.setColor(ColorRGBA.White); + ch.setLocalTranslation( // center + settings.getWidth() / 2 - ch.getLineWidth() / 2, + settings.getHeight() / 2 + ch.getLineHeight() / 2, 0); + guiNode.attachChild(ch); + } + + private CollisionResult pickFromCamera() { + CollisionResults results = new CollisionResults(); + Ray ray = new Ray(cam.getLocation(), cam.getDirection()); + instancedNode.collideWith(ray, results); + return results.getClosestCollision(); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithShadowFilter.java b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithShadowFilter.java new file mode 100644 index 0000000000..6d5b76da83 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/TestInstancedNodeAttachDetachWithShadowFilter.java @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2009-2021 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.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Sphere; +import com.jme3.shadow.DirectionalLightShadowFilter; +import com.jme3.shadow.EdgeFilteringMode; +import com.jme3.system.AppSettings; + +/** + * A test case for using instancing with shadow filter. + * + * Based on distance from camera, swap in/out more/less detailed geometry to/from an InstancedNode. + * + * @author duncanj + */ +public class TestInstancedNodeAttachDetachWithShadowFilter extends SimpleApplication { + public static void main(String[] args) { + TestInstancedNodeAttachDetachWithShadowFilter app = new TestInstancedNodeAttachDetachWithShadowFilter(); + AppSettings settings = new AppSettings(true); + settings.setVSync(false); + app.setSettings(settings); + app.start(); + } + + private FilterPostProcessor filterPostProcessor; + private InstancedNode instancedNode; + + final private Vector3f[] locations = new Vector3f[10]; + final private Geometry[] spheres = new Geometry[10]; + final private Geometry[] boxes = new Geometry[10]; + + @Override + public void simpleInitApp() { + filterPostProcessor = new FilterPostProcessor(assetManager); + getViewPort().addProcessor(filterPostProcessor); + + addDirectionalLight(); + addAmbientLight(); + + Material instancingMaterial = createLightingMaterial(true, ColorRGBA.LightGray); + + instancedNode = new InstancedNode("theParentInstancedNode"); + instancedNode.setShadowMode(RenderQueue.ShadowMode.CastAndReceive); + rootNode.attachChild(instancedNode); + + // create 10 spheres & boxes, along the z-axis, successively further from the camera + Mesh sphereMesh = new Sphere(32, 32, 1f); + Mesh boxMesh = new Box(0.7f, 0.7f, 0.7f); + for (int z = 0; z < 10; z++) { + Vector3f location = new Vector3f(0, -3, -(z * 4)); + locations[z] = location; + + Geometry sphere = new Geometry("sphere", sphereMesh); + sphere.setMaterial(instancingMaterial); + sphere.setLocalTranslation(location); + instancedNode.attachChild(sphere); // initially just add the spheres to the InstancedNode + spheres[z] = sphere; + + Geometry box = new Geometry("box", boxMesh); + box.setMaterial(instancingMaterial); + box.setLocalTranslation(location); + boxes[z] = box; + } + + instancedNode.instance(); + + + Geometry floor = new Geometry("floor", new Box(20, 0.1f, 40)); + floor.setMaterial(createLightingMaterial(false, ColorRGBA.Yellow)); + floor.setLocalTranslation(5, -5, 0); + floor.setShadowMode(RenderQueue.ShadowMode.Receive); + rootNode.attachChild(floor); + + flyCam.setMoveSpeed(30); + } + + @Override + public void simpleUpdate(float tpf) { + // Each frame, determine the distance to each sphere/box from the camera. + // If the object is > 25 units away, switch in the Box. If it's nearer, switch in the Sphere. + // Normally we wouldn't do this every frame, only when player has moved a sufficient distance, etc. + + boolean modified = false; + for (int i = 0; i < 10; i++) { + Vector3f location = locations[i]; + float distance = location.distance(cam.getLocation()); + + if(distance > 25.0f && boxes[i].getParent() == null) { + modified = true; + instancedNode.attachChild(boxes[i]); + instancedNode.detachChild(spheres[i]); + } else if(distance <= 25.0f && spheres[i].getParent() == null) { + modified = true; + instancedNode.attachChild(spheres[i]); + instancedNode.detachChild(boxes[i]); + } + } + + if(modified) { + instancedNode.instance(); + } + } + + private Material createLightingMaterial(boolean useInstancing, ColorRGBA color) { + Material material = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + material.setBoolean("UseMaterialColors", true); + material.setBoolean("UseInstancing", useInstancing); + material.setColor("Ambient", color); + material.setColor("Diffuse", color); + material.setColor("Specular", color); + material.setFloat("Shininess", 1.0f); + return material; + } + + private void addAmbientLight() { + AmbientLight ambientLight = new AmbientLight(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)); + rootNode.addLight(ambientLight); + } + + private void addDirectionalLight() { + DirectionalLight light = new DirectionalLight(); + + light.setColor(ColorRGBA.White); + light.setDirection(new Vector3f(-1, -1, -1)); + + DirectionalLightShadowFilter dlsf = new DirectionalLightShadowFilter(assetManager, 1024, 1); + dlsf.setLight(light); + dlsf.setEdgeFilteringMode(EdgeFilteringMode.PCFPOISSON); + filterPostProcessor.addFilter(dlsf); + + rootNode.addLight(light); + } +} \ No newline at end of file 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/scene/instancing/package-info.java b/jme3-examples/src/main/java/jme3test/scene/instancing/package-info.java new file mode 100644 index 0000000000..ddce65ca9a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/instancing/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for instancing + */ +package jme3test.scene.instancing; diff --git a/jme3-examples/src/main/java/jme3test/scene/package-info.java b/jme3-examples/src/main/java/jme3test/scene/package-info.java new file mode 100644 index 0000000000..9e19c9956a --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/scene/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for various scene-graph features + */ +package jme3test.scene; diff --git a/jme3-examples/src/main/java/jme3test/stencil/TestStencilOutline.java b/jme3-examples/src/main/java/jme3test/stencil/TestStencilOutline.java new file mode 100644 index 0000000000..406b15621e --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stencil/TestStencilOutline.java @@ -0,0 +1,151 @@ +package jme3test.stencil; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.AssetManager; +import com.jme3.material.Material; +import com.jme3.material.RenderState; +import com.jme3.math.ColorRGBA; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.renderer.RenderManager; +import com.jme3.renderer.ViewPort; +import com.jme3.renderer.queue.OpaqueComparator; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.control.AbstractControl; +import com.jme3.scene.shape.Box; +import com.jme3.system.AppSettings; +import com.jme3.texture.Image; + +import java.util.logging.Level; +import java.util.logging.Logger; + +public class TestStencilOutline extends SimpleApplication { + + class OutlineComparator extends OpaqueComparator { + @Override + public int compare(Geometry o1, Geometry o2) { + boolean ol1 = o1.getUserData("Outline") != null; + boolean ol2 = o2.getUserData("Outline") != null; + if (ol1 == ol2) { + return super.compare(o1, o2); + } else { + if (ol1) { + return 1; + } else { + return -1; + } + } + } + } + + class OutlineControl extends AbstractControl { + private Material outlineMaterial; + + public OutlineControl(AssetManager assetManager, ColorRGBA colorRGBA) { + outlineMaterial = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + outlineMaterial.setColor("Color", colorRGBA); + outlineMaterial.getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Front); + outlineMaterial.getAdditionalRenderState().setDepthFunc(RenderState.TestFunction.Always); + outlineMaterial.getAdditionalRenderState().setStencil(true, + RenderState.StencilOperation.Keep, //front triangle fails stencil test + RenderState.StencilOperation.Keep, //front triangle fails depth test + RenderState.StencilOperation.Keep, //front triangle passes depth test + RenderState.StencilOperation.Keep, //back triangle fails stencil test + RenderState.StencilOperation.Keep, //back triangle fails depth test + RenderState.StencilOperation.Keep, //back triangle passes depth test + RenderState.TestFunction.NotEqual, //front triangle stencil test function + RenderState.TestFunction.NotEqual); //back triangle stencil test function + outlineMaterial.getAdditionalRenderState().setFrontStencilReference(1); + outlineMaterial.getAdditionalRenderState().setBackStencilReference(1); + outlineMaterial.getAdditionalRenderState().setFrontStencilMask(0xFF); + outlineMaterial.getAdditionalRenderState().setBackStencilMask(0xFF); + } + + @Override + protected void controlUpdate(float v) { + + } + + @Override + protected void controlRender(RenderManager renderManager, ViewPort viewPort) { + if (spatial instanceof Geometry) { + Geometry geometry= (Geometry) spatial; + Geometry clone = geometry.clone(); + clone.scale(1.1f); + clone.setUserData("Outline", true); + clone.setMaterial(outlineMaterial); + clone.updateGeometricState(); + viewPort.getQueue().addToQueue(clone, RenderQueue.Bucket.Opaque); + } + } + } + + @Override + public void update() { + super.update(); + } + + @Override + public void simpleInitApp() { + flyCam.setMoveSpeed(24); + flyCam.setZoomSpeed(-5); + viewPort.getQueue().setGeometryComparator(RenderQueue.Bucket.Opaque, new OutlineComparator()); + viewPort.setClearFlags(true,true,true); + Material boxMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + boxMat.getAdditionalRenderState().setStencil(true, + RenderState.StencilOperation.Keep, //front triangle fails stencil test + RenderState.StencilOperation.Replace, //front triangle fails depth test + RenderState.StencilOperation.Replace, //front triangle passes depth test + RenderState.StencilOperation.Keep, //back triangle fails stencil test + RenderState.StencilOperation.Replace, //back triangle fails depth test + RenderState.StencilOperation.Replace, //back triangle passes depth test + RenderState.TestFunction.Always, //front triangle stencil test function + RenderState.TestFunction.Always); //back triangle stencil test function + boxMat.getAdditionalRenderState().setFrontStencilReference(1); + boxMat.getAdditionalRenderState().setBackStencilReference(1); + boxMat.getAdditionalRenderState().setFrontStencilMask(0xFF); + boxMat.getAdditionalRenderState().setBackStencilMask(0xFF); + boxMat.setTexture("ColorMap", assetManager.loadTexture("Common/Textures/MissingTexture.png")); + + Material floorMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + floorMat.setTexture("ColorMap", assetManager.loadTexture("Common/Textures/MissingTexture.png")); + + Geometry floor = new Geometry("Floor", new Box(10f, 0, 10f)); + floor.setMaterial(floorMat); + rootNode.attachChild(floor); + + Geometry box1 = new Geometry("Box1", new Box(0.5f, 0.5f, 0.5f)); + box1.setLocalTranslation(3, 1.5f, 0); + box1.setLocalScale(4); + box1.addControl(new OutlineControl(assetManager, ColorRGBA.Blue)); + box1.setMaterial(boxMat); + Geometry box2 = new Geometry("Box2", new Box(0.5f, 0.5f, 0.5f)); + box2.setLocalTranslation(-3, 1.5f, 0); + box2.setLocalScale(3); + box2.addControl(new OutlineControl(assetManager,ColorRGBA.Red)); + box2.setMaterial(boxMat); + + rootNode.attachChild(box1); + rootNode.attachChild(box2); + + //This is to make sure a depth stencil format is used in the TestChooser app. + FilterPostProcessor postProcessor=new FilterPostProcessor(assetManager); + postProcessor.setFrameBufferDepthFormat(Image.Format.Depth24Stencil8); + viewPort.addProcessor(postProcessor); + postProcessor.addFilter(new BloomFilter()); + } + + + public static void main(String[] args) { + Logger.getLogger("").setLevel(Level.FINEST); + TestStencilOutline app = new TestStencilOutline(); + AppSettings settings = new AppSettings(true); + settings.setGraphicsDebug(true); + settings.setDepthBits(24); + settings.setStencilBits(8); + app.setSettings(settings); + app.setShowSettings(false); + app.start(); + } +} diff --git a/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java b/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java index 08ef772a3b..717ed6d8f6 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestBatchLod.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,13 +43,12 @@ public class TestBatchLod extends SimpleApplication { - private boolean lod = false; - public static void main(String[] args) { TestBatchLod app = new TestBatchLod(); app.start(); } + @Override public void simpleInitApp() { // inputManager.registerKeyBinding("USELOD", KeyInput.KEY_L); @@ -65,7 +64,7 @@ public void simpleInitApp() { mat.setBoolean("VertexLighting", true); teapot.setMaterial(mat); - // show normals as material + // A special Material to visualize mesh normals: //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); flyCam.setMoveSpeed(5); for (int y = -5; y < 5; y++) { diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java b/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java index bd2bd485b4..24861464ad 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestLeakingGL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,6 +59,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { original = new Sphere(4, 4, 1); original.setStatic(); diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java index 5400e98d8e..6c7e3d965d 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestLodGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2025 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,8 +31,12 @@ */ package jme3test.stress; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledThreadPoolExecutor; + import com.jme3.anim.SkinningControl; -import com.jme3.animation.AnimChannel; import com.jme3.app.SimpleApplication; import com.jme3.bounding.BoundingBox; import com.jme3.font.BitmapText; @@ -40,197 +44,207 @@ 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.math.*; -import com.jme3.scene.*; -import jme3tools.optimize.LodGenerator; +import com.jme3.material.Material; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ScheduledThreadPoolExecutor; +import jme3tools.optimize.LodGenerator; -public class TestLodGeneration extends SimpleApplication { +public class TestLodGeneration extends SimpleApplication implements ActionListener { public static void main(String[] args) { TestLodGeneration app = new TestLodGeneration(); app.start(); } - boolean wireFrame = false; - float reductionvalue = 0.0f; + + private boolean wireframe = false; + // Current reduction value for LOD generation (0.0 to 1.0) + private float reductionValue = 0.0f; private int lodLevel = 0; - private Node model; private BitmapText hudText; - private List listGeoms = new ArrayList(); - private ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5); - private AnimChannel ch; + private final List listGeoms = new ArrayList<>(); + private final ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(5); + @Override public void simpleInitApp() { + // --- Lighting Setup --- DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); rootNode.addLight(dl); + AmbientLight al = new AmbientLight(); - al.setColor(ColorRGBA.White.mult(0.6f)); rootNode.addLight(al); - // model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); - model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); + // --- Model Loading and Setup --- + // model = (Node) assetManager.loadModel("Models/Sinbad/Sinbad.mesh.xml"); + Node model = (Node) assetManager.loadModel("Models/Jaime/Jaime.j3o"); BoundingBox b = ((BoundingBox) model.getWorldBound()); model.setLocalScale(1.2f / (b.getYExtent() * 2)); - // model.setLocalTranslation(0,-(b.getCenter().y - b.getYExtent())* model.getLocalScale().y, 0); + // model.setLocalTranslation(0,-(b.getCenter().y - b.getYExtent())* model.getLocalScale().y, 0); + + // Iterate through the model's children and collect all Geometry objects for (Spatial spatial : model.getChildren()) { if (spatial instanceof Geometry) { listGeoms.add((Geometry) spatial); } } - ChaseCamera chaseCam = new ChaseCamera(cam, inputManager); - model.addControl(chaseCam); + + // --- Camera Setup --- + ChaseCamera chaseCam = new ChaseCamera(cam, model, inputManager); chaseCam.setLookAtOffset(b.getCenter()); chaseCam.setDefaultDistance(5); chaseCam.setMinVerticalRotation(-FastMath.HALF_PI + 0.01f); chaseCam.setZoomSensitivity(0.5f); - - -// ch = model.getControl(AnimControl.class).createChannel(); -// ch.setAnim("Wave"); - SkinningControl c = model.getControl(SkinningControl.class); - if (c != null) { - c.setEnabled(false); + SkinningControl skControl = model.getControl(SkinningControl.class); + if (skControl != null) { + // Disable skinning control if found. This is an optimization for static LOD generation + // as skinning computation is not needed when generating LODs. + skControl.setEnabled(false); } - - reductionvalue = 0.80f; + // --- Initial LOD Generation --- + // Set initial reduction value and LOD level + reductionValue = 0.80f; lodLevel = 1; - for (final Geometry geometry : listGeoms) { - LodGenerator lodGenerator = new LodGenerator(geometry); - lodGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue); - geometry.setLodLevel(lodLevel); + // Generate LODs for each geometry in the model + for (final Geometry geom : listGeoms) { + LodGenerator lodGenerator = new LodGenerator(geom); + lodGenerator.bakeLods(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionValue); + geom.setLodLevel(lodLevel); } rootNode.attachChild(model); + // Disable the default fly camera as we are using a chase camera flyCam.setEnabled(false); - - - guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - hudText = new BitmapText(guiFont, false); - hudText.setSize(guiFont.getCharSet().getRenderedSize()); + // --- HUD Setup --- + hudText = new BitmapText(guiFont); hudText.setText(computeNbTri() + " tris"); - hudText.setLocalTranslation(cam.getWidth() / 2, hudText.getLineHeight(), 0); + hudText.setLocalTranslation(cam.getWidth() / 2f, hudText.getLineHeight(), 0); guiNode.attachChild(hudText); - inputManager.addListener(new ActionListener() { - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) { - if (name.equals("plus")) { -// lodLevel++; -// for (Geometry geometry : listGeoms) { -// if (geometry.getMesh().getNumLodLevels() <= lodLevel) { -// lodLevel = 0; -// } -// geometry.setLodLevel(lodLevel); -// } -// jaimeText.setText(computeNbTri() + " tris"); - - - - reductionvalue += 0.05f; - updateLod(); - - - - } - if (name.equals("minus")) { -// lodLevel--; -// for (Geometry geometry : listGeoms) { -// if (lodLevel < 0) { -// lodLevel = geometry.getMesh().getNumLodLevels() - 1; -// } -// geometry.setLodLevel(lodLevel); -// } -// jaimeText.setText(computeNbTri() + " tris"); - - - - reductionvalue -= 0.05f; - updateLod(); - - - } - if (name.equals("wireFrame")) { - wireFrame = !wireFrame; - for (Geometry geometry : listGeoms) { - geometry.getMaterial().getAdditionalRenderState().setWireframe(wireFrame); - } - } - - } - - } + // Register input mappings for user interaction + registerInputMappings(); + } - private void updateLod() { - reductionvalue = FastMath.clamp(reductionvalue, 0.0f, 1.0f); - makeLod(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionvalue, 1); + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; + + if (name.equals("plus")) { + reductionValue += 0.05f; + updateLod(); + + } else if (name.equals("minus")) { + reductionValue -= 0.05f; + updateLod(); + + } else if (name.equals("wireframe")) { + wireframe = !wireframe; + for (Geometry geom : listGeoms) { + Material mat = geom.getMaterial(); + mat.getAdditionalRenderState().setWireframe(wireframe); } - }, "plus", "minus", "wireFrame"); - - inputManager.addMapping("plus", new KeyTrigger(KeyInput.KEY_ADD)); - inputManager.addMapping("minus", new KeyTrigger(KeyInput.KEY_SUBTRACT)); - inputManager.addMapping("wireFrame", new KeyTrigger(KeyInput.KEY_SPACE)); - + } + } + private void registerInputMappings() { + addMapping("plus", new KeyTrigger(KeyInput.KEY_P)); + addMapping("minus", new KeyTrigger(KeyInput.KEY_L)); + addMapping("wireframe", new KeyTrigger(KeyInput.KEY_SPACE)); + } + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(this, mappingName); } @Override - public void simpleUpdate(float tpf) { - // model.rotate(0, tpf, 0); + public void destroy() { + super.destroy(); + exec.shutdown(); + } + + private void updateLod() { + // Clamp the reduction value between 0.0 and 1.0 to ensure it's within valid range + reductionValue = FastMath.clamp(reductionValue, 0.0f, 1.0f); + makeLod(LodGenerator.TriangleReductionMethod.PROPORTIONAL, reductionValue, 1); } + /** + * Computes the total number of triangles currently displayed by all geometries. + * @return The total number of triangles. + */ private int computeNbTri() { int nbTri = 0; - for (Geometry geometry : listGeoms) { - if (geometry.getMesh().getNumLodLevels() > 0) { - nbTri += geometry.getMesh().getLodLevel(lodLevel).getNumElements(); + for (Geometry geom : listGeoms) { + Mesh mesh = geom.getMesh(); + // Check if the mesh has LOD levels + if (mesh.getNumLodLevels() > 0) { + nbTri += mesh.getLodLevel(lodLevel).getNumElements(); } else { - nbTri += geometry.getMesh().getTriangleCount(); + nbTri += mesh.getTriangleCount(); } } return nbTri; } - @Override - public void destroy() { - super.destroy(); - exec.shutdown(); - } - - private void makeLod(final LodGenerator.TriangleReductionMethod method, final float value, final int ll) { + /** + * Generates and applies LOD levels to the geometries in a background thread. + * + * @param reductionMethod The triangle reduction method to use (e.g., PROPORTIONAL). + * @param reductionPercentage The percentage of triangles to reduce (0.0 to 1.0). + * @param targetLodLevel The index of the LOD level to set active after generation. + */ + private void makeLod(final LodGenerator.TriangleReductionMethod reductionMethod, + final float reductionPercentage, final int targetLodLevel) { + + // --- Asynchronous LOD Generation --- + // Execute the LOD generation process in the background thread pool. exec.execute(new Runnable() { + @Override public void run() { - for (final Geometry geometry : listGeoms) { - LodGenerator lODGenerator = new LodGenerator(geometry); - final VertexBuffer[] lods = lODGenerator.computeLods(method, value); + for (final Geometry geom : listGeoms) { + LodGenerator lodGenerator = new LodGenerator(geom); + final VertexBuffer[] lods = lodGenerator.computeLods(reductionMethod, reductionPercentage); + // --- JME Thread Synchronization --- + // Mesh modifications and scene graph updates must be done on the main thread. enqueue(new Callable() { + @Override public Void call() throws Exception { - geometry.getMesh().setLodLevels(lods); + geom.getMesh().setLodLevels(lods); + + // Reset lodLevel to 0 initially lodLevel = 0; - if (geometry.getMesh().getNumLodLevels() > ll) { - lodLevel = ll; + // If the generated LOD levels are more than the target, set to target LOD + if (geom.getMesh().getNumLodLevels() > targetLodLevel) { + lodLevel = targetLodLevel; } - geometry.setLodLevel(lodLevel); - hudText.setText(computeNbTri() + " tris"); + geom.setLodLevel(lodLevel); + + int nbTri = computeNbTri(); + hudText.setText(nbTri + " tris"); + + // Print debug information to the console + System.out.println(geom + " lodLevel: " + lodLevel + ", numLodLevels: " + geom.getMesh().getNumLodLevels() + + ", reductionValue: " + reductionValue + ", triangles: " + nbTri); return null; } }); } } }); - } } diff --git a/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java b/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java index 21b8e9d539..a02616680f 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestLodStress.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { DirectionalLight dl = new DirectionalLight(); dl.setDirection(new Vector3f(-1,-1,-1).normalizeLocal()); @@ -66,7 +67,7 @@ public void simpleInitApp() { mat.setBoolean("VertexLighting", true); teapot.setMaterial(mat); - // show normals as material + // A special Material to visualize mesh normals: //Material mat = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md"); for (int y = -10; y < 10; y++){ diff --git a/jme3-examples/src/main/java/jme3test/stress/TestParallelTangentGeneration.java b/jme3-examples/src/main/java/jme3test/stress/TestParallelTangentGeneration.java index 421589fa66..a8c463b459 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestParallelTangentGeneration.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestParallelTangentGeneration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ public class TestParallelTangentGeneration { - static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); + final private static ExecutorService executor = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); public static void main(String[] args) { diff --git a/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java b/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java index 0635c2b16a..34ab16f550 100644 --- a/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java +++ b/jme3-examples/src/main/java/jme3test/stress/TestShaderNodesStress.java @@ -82,7 +82,7 @@ public void appStep(AppStep step) { renderTime = System.nanoTime(); sum += renderTime - updateTime; System.err.println("render time : " + (renderTime - updateTime)); - System.err.println("Average render time : " + ((float)sum / (float)(nbFrames-150))); + System.err.println("Average render time : " + (sum / (float)(nbFrames-150))); } break; diff --git a/jme3-examples/src/main/java/jme3test/stress/package-info.java b/jme3-examples/src/main/java/jme3test/stress/package-info.java new file mode 100644 index 0000000000..ab72f67ad2 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/stress/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * non-automated stress tests + */ +package jme3test.stress; diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java new file mode 100644 index 0000000000..d4cd04e403 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainAdvancedTest.java @@ -0,0 +1,514 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package jme3test.terrain; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.shader.VarType; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.TextureArray; +import java.util.ArrayList; +import java.util.List; + +/** + * This test uses 'AdvancedPBRTerrain.j3md' to create a terrain Material with + * more textures than 'PBRTerrain.j3md' can handle. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. (Screenshots + * showing how this test-case should look will also be available soon so you can + * compare your results, and I will replace this comment with a link to their + * location as soon as they are posted.) + * + * Press 'p' to toggle tri-planar mode. Enabling tri-planar mode should prevent + * stretching of textures in steep areas of the terrain. + * + * Press 'n' to toggle between night and day. Pressing 'n' will cause the light + * to gradually fade darker/brighter until the min/max lighting levels are + * reached. At night the scene should be noticeably darker, and the marble and + * tiled-road texture should be noticeably glowing from the emissiveColors and + * the emissiveIntensity map that is packed into the alpha channel of the + * MetallicRoughness maps. + * + * The MetallicRoughness map stores: + *
                  + *
                • AmbientOcclusion in the Red channel
                • + *
                • Roughness in the Green channel
                • + *
                • Metallic in the Blue channel
                • + *
                • EmissiveIntensity in the Alpha channel
                • + *
                + * + * The shaders are still subject to the GLSL max limit of 16 textures, however + * each TextureArray counts as a single texture, and each TextureArray can store + * multiple images. For more information on texture arrays see: + * https://www.khronos.org/opengl/wiki/Array_Texture + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + *

                + * Notes: (as of 12 April 2021) + *

                  + *
                1. + * The results look better with anti-aliasing, especially from a distance. This + * may be due to the way that the terrain is generated from a heightmap, as + * these same textures do not have this issue in my other project. + *
                2. + *
                3. + * The number of images per texture array may still be limited by + * GL_MAX_ARRAY_TEXTURE_LAYERS, however this value should be high enough that + * users will likely run into issues with extremely low FPS from too many + * texture-reads long before you surpass the limit of texture-layers per + * textureArray. If this ever becomes an issue, a secondary set of + * Albedo/Normal/MetallicRoughness texture arrays could be added to the shader + * to store any textures that surpass the limit of the primary textureArrays. + *
                4. + *
                + * + * @author yaRnMcDonuts + */ +public class PBRTerrainAdvancedTest extends SimpleApplication { + + private TerrainQuad terrain; + private Material matTerrain; + private boolean triPlanar = false; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + private final ColorRGBA tilesEmissiveColor = new ColorRGBA(0.12f, 0.02f, 0.23f, 0.85f); //dim magenta emission + private final ColorRGBA marbleEmissiveColor = new ColorRGBA(0.0f, 0.0f, 1.0f, 1.0f); //fully saturated blue emission + + private AmbientLight ambientLight; + private DirectionalLight directionalLight; + private boolean isNight = false; + + private final float dayLightIntensity = 1.0f; + private final float nightLightIntensity = 0.03f; + + private BitmapText keybindingsText; + + private final float camMoveSpeed = 50f; + + public static void main(String[] args) { + PBRTerrainAdvancedTest app = new PBRTerrainAdvancedTest(); + app.start(); + } + + private final ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matTerrain.setBoolean("useTriPlanarMapping", true); + // Tri-planar textures don't use the mesh's texture coordinates but real world coordinates, + // so we need to convert these texture coordinate scales into real world scales so it looks + // the same when we switch to/from tri-planar mode. + matTerrain.setFloat("AlbedoMap_0_scale", (dirtScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_1_scale", (darkRockScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_2_scale", (snowScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_3_scale", (tileRoadScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_4_scale", (grassScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_5_scale", (marbleScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_6_scale", (gravelScale / terrainSize)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + } + } + if (name.equals("toggleNight") && !pressed) { + isNight = !isNight; + // Ambient and directional light are faded smoothly in update loop below. + } + + if(name.length() == 1 && !pressed){ + if(name.equals("-")){ + matTerrain.setInt("DebugValuesMode", -1); + }else{ + try{ + int debugValueMode = Integer.parseInt(name); + matTerrain.setInt("DebugValuesMode", debugValueMode); + } + catch(Exception e){ + + } + } + } + } + }; + + @Override + public void simpleInitApp() { + setupKeys(); + setUpTerrain(); + setUpTerrainMaterial(); // <- This method contains the important info about using 'AdvancedPBRTerrain.j3md' + setUpLights(); + setUpCamera(); + } + + private void setUpTerrainMaterial() { + // advanced PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + // this material also supports 'AlphaMap_2', so you can get up to 12 texture slots + + // load textures for texture arrays + // These MUST all have the same dimensions and format in order to be put into a texture array. + //ALBEDO MAPS + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + Texture tileRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + Texture normalMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Normal.png"); + Texture normalMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + + //PACKED METALLIC/ROUGHNESS / AMBIENT OCCLUSION / EMISSIVE INTENSITY MAPS + Texture metallicRoughnessAoEiMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png"); + + // put all images into lists to create texture arrays. + // + // The index of each image in its list will be + // sent to the material to tell the shader to choose that texture from + // the textureArray when setting up a texture slot's mat params. + // + List albedoImages = new ArrayList<>(); + List normalMapImages = new ArrayList<>(); + List metallicRoughnessAoEiMapImages = new ArrayList<>(); + + albedoImages.add(dirt.getImage()); //0 + albedoImages.add(darkRock.getImage()); //1 + albedoImages.add(snow.getImage()); //2 + albedoImages.add(tileRoad.getImage()); //3 + albedoImages.add(grass.getImage()); //4 + albedoImages.add(marble.getImage()); //5 + albedoImages.add(gravel.getImage()); //6 + + normalMapImages.add(normalMapDirt.getImage()); //0 + normalMapImages.add(normalMapDarkRock.getImage()); //1 + normalMapImages.add(normalMapSnow.getImage()); //2 + normalMapImages.add(normalMapRoad.getImage()); //3 + normalMapImages.add(normalMapGrass.getImage()); //4 + normalMapImages.add(normalMapMarble.getImage()); //5 + normalMapImages.add(normalMapGravel.getImage()); //6 + + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDirt.getImage()); //0 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDarkRock.getImage()); //1 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapSnow.getImage()); //2 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapRoad.getImage()); //3 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGrass.getImage()); //4 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapMarble.getImage()); //5 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGravel.getImage()); //6 + + //initiate texture arrays + TextureArray albedoTextureArray = new TextureArray(albedoImages); + TextureArray normalParallaxTextureArray = new TextureArray(normalMapImages); // parallax is not used currently + TextureArray metallicRoughnessAoEiTextureArray = new TextureArray(metallicRoughnessAoEiMapImages); + + //apply wrapMode to the whole texture array, rather than each individual texture in the array + setWrapAndMipMaps(albedoTextureArray); + setWrapAndMipMaps(normalParallaxTextureArray); + setWrapAndMipMaps(metallicRoughnessAoEiTextureArray); + + //assign texture array to materials + matTerrain.setParam("AlbedoTextureArray", VarType.TextureArray, albedoTextureArray); + matTerrain.setParam("NormalParallaxTextureArray", VarType.TextureArray, normalParallaxTextureArray); + matTerrain.setParam("MetallicRoughnessAoEiTextureArray", VarType.TextureArray, metallicRoughnessAoEiTextureArray); + + //set up texture slots: + matTerrain.setInt("AlbedoMap_0", 0); // dirt is index 0 in the albedo image list + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0.02f); + + matTerrain.setInt("AlbedoMap_1", 1); // darkRock is index 1 in the albedo image list + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 1); + matTerrain.setFloat("Metallic_1", 0.04f); + + matTerrain.setInt("AlbedoMap_2", 2); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.72f); + matTerrain.setFloat("Metallic_2", 0.12f); + + matTerrain.setInt("AlbedoMap_3", 3); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 1); + matTerrain.setFloat("Metallic_3", 0.04f); + + matTerrain.setInt("AlbedoMap_4", 4); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + matTerrain.setInt("AlbedoMap_5", 5); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 1); + matTerrain.setFloat("Metallic_5", 0.2f); + + matTerrain.setInt("AlbedoMap_6", 6); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 1); + matTerrain.setFloat("Metallic_6", 0.01f); + + // NORMAL MAPS + // int being passed to shader corresponds to the index of the texture's + // image in the List of images used to create its texture array + matTerrain.setInt("NormalMap_0", 0); + matTerrain.setInt("NormalMap_1", 1); + matTerrain.setInt("NormalMap_2", 2); + matTerrain.setInt("NormalMap_3", 3); + matTerrain.setInt("NormalMap_4", 4); + matTerrain.setInt("NormalMap_5", 5); + matTerrain.setInt("NormalMap_6", 6); + + //METALLIC/ROUGHNESS/AO/EI MAPS + matTerrain.setInt("MetallicRoughnessMap_0", 0); + matTerrain.setInt("MetallicRoughnessMap_1", 1); + matTerrain.setInt("MetallicRoughnessMap_2", 2); + matTerrain.setInt("MetallicRoughnessMap_3", 3); + matTerrain.setInt("MetallicRoughnessMap_4", 4); + matTerrain.setInt("MetallicRoughnessMap_5", 5); + matTerrain.setInt("MetallicRoughnessMap_6", 6); + + //EMISSIVE + matTerrain.setColor("EmissiveColor_5", marbleEmissiveColor); + matTerrain.setColor("EmissiveColor_3", tilesEmissiveColor); + //these two texture slots (marble & tiledRoad, indexed in each texture array at 5 and 3 respectively) both + // have packed MRAoEi maps with an emissiveTexture packed into the alpha channel + +// matTerrain.setColor("EmissiveColor_1", new ColorRGBA(0.08f, 0.01f, 0.1f, 0.4f)); +//this texture slot does not have a unique emissiveIntensityMap packed into its MRAoEi map, + // so setting an emissiveColor will apply equal intensity to every pixel + + terrain.setMaterial(matTerrain); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N)); + + inputManager.addMapping("0", new KeyTrigger(KeyInput.KEY_0)); // toggleDebugModeForAlbedo + inputManager.addMapping("1", new KeyTrigger(KeyInput.KEY_1)); // toggleDebugModeForNormalMap + inputManager.addMapping("2", new KeyTrigger(KeyInput.KEY_2)); // toggleDebugModeForRoughness + inputManager.addMapping("3", new KeyTrigger(KeyInput.KEY_3)); // toggleDebugModeForMetallic + inputManager.addMapping("4", new KeyTrigger(KeyInput.KEY_4)); // toggleDebugModeForAo + inputManager.addMapping("5", new KeyTrigger(KeyInput.KEY_5)); // toggleDebugModeForEmissive + inputManager.addMapping("6", new KeyTrigger(KeyInput.KEY_6)); // toggleDebugModeForExposure + inputManager.addMapping("7", new KeyTrigger(KeyInput.KEY_7)); // toggleDebugModeForAlpha + inputManager.addMapping("8", new KeyTrigger(KeyInput.KEY_8)); // toggleDebugModeForGeometryNormals + + inputManager.addMapping("-", new KeyTrigger(KeyInput.KEY_MINUS)); // - key will disable dbug mode + + inputManager.addListener(actionListener, "triPlanar"); + inputManager.addListener(actionListener, "toggleNight"); + + inputManager.addListener(actionListener, "0"); + inputManager.addListener(actionListener, "1"); + inputManager.addListener(actionListener, "2"); + inputManager.addListener(actionListener, "3"); + inputManager.addListener(actionListener, "4"); + inputManager.addListener(actionListener, "5"); + inputManager.addListener(actionListener, "6"); + inputManager.addListener(actionListener, "7"); + inputManager.addListener(actionListener, "8"); + inputManager.addListener(actionListener, "-"); + + keybindingsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + keybindingsText.setText("Press N to toggle day/night fade (takes a moment) \n" + + "Press P to toggle tri-planar mode\n\n" + + "Press - for Final Render (disable debug view)\n" + + "Press 0 for Albedo debug view\n" + + "Press 1 for Normal Map debug view\n" + + "Press 2 for Roughness debug view\n" + + "Press 3 for Metallic debug view\n" + + "Press 4 for Ambient Occlusion (ao) debug view\n" + + "Press 5 for Emissive debug view\n" + + "Press 6 for Exposure debug view\n" + + "Press 7 for Alpha debug view\n" + + "Press 8 for Geoemtry Normals debug view\n"); + + + getGuiNode().attachChild(keybindingsText); + keybindingsText.move(new Vector3f(200, 120, 0)); + keybindingsText.move(new Vector3f(5, cam.getHeight() * 0.995f, 0)); + } + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + //smoothly transition from day to night + float currentLightIntensity = ambientLight.getColor().getRed(); + float incrementPerFrame = tpf * 0.3f; + + if (isNight) { + if (ambientLight.getColor().getRed() > nightLightIntensity) { + currentLightIntensity -= incrementPerFrame; + if (currentLightIntensity < nightLightIntensity) { + currentLightIntensity = nightLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } else { + if (ambientLight.getColor().getRed() < dayLightIntensity) { + currentLightIntensity += incrementPerFrame; + if (currentLightIntensity > dayLightIntensity) { + currentLightIntensity = dayLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } + } + + private void setUpTerrain() { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); +//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + } + + private void setWrapAndMipMaps(Texture texture){ + texture.setWrap(WrapMode.Repeat); + texture.setMinFilter(MinFilter.Trilinear); + texture.setMagFilter(MagFilter.Bilinear); + } + + private void setUpLights() { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + rootNode.addLight(probe); + + directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(directionalLight); + + ambientLight = new AmbientLight(); + ambientLight.setColor(ColorRGBA.White); + rootNode.addLight(ambientLight); + } + + private void setUpCamera() { + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + + getFlyByCamera().setMoveSpeed(camMoveSpeed); + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java new file mode 100644 index 0000000000..1500257612 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/PBRTerrainTest.java @@ -0,0 +1,416 @@ +package jme3test.terrain; + +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.font.BitmapText; +import com.jme3.input.KeyInput; +import com.jme3.input.controls.ActionListener; +import com.jme3.input.controls.KeyTrigger; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; + +/** + * This test uses 'PBRTerrain.j3md' to create a terrain Material for PBR. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. (Screenshots + * showing how this test-case should look will also be available soon so you can + * compare your results, and I will replace this comment with a link to their + * location as soon as they are posted.) + * + * Press 'p' to toggle tri-planar mode. Enabling tri-planar mode should prevent + * stretching of textures in steep areas of the terrain. + * + * Press 'n' to toggle between night and day. Pressing 'n' will cause the light + * to gradually fade darker/brighter until the min/max lighting levels are + * reached. At night the scene should be noticeably darker. + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + *

                + * Notes: (as of 12 April 2021) + *

                  + *
                1. + * This shader is subject to the GLSL max limit of 16 textures, and users should + * consider using "AdvancedPBRTerrain.j3md" instead if they need additional + * texture slots. + *
                2. + *
                + * + * @author yaRnMcDonuts + */ +public class PBRTerrainTest extends SimpleApplication { + + private TerrainQuad terrain; + private Material matTerrain; + private boolean triPlanar = false; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + private AmbientLight ambientLight; + private DirectionalLight directionalLight; + private boolean isNight = false; + + private final float dayLightIntensity = 1.0f; + private final float nightLightIntensity = 0.03f; + + private BitmapText keybindingsText; + + private final float camMoveSpeed = 50f; + + public static void main(String[] args) { + PBRTerrainTest app = new PBRTerrainTest(); + app.start(); + } + + private final ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean pressed, float tpf) { + if (name.equals("triPlanar") && !pressed) { + triPlanar = !triPlanar; + if (triPlanar) { + matTerrain.setBoolean("useTriPlanarMapping", true); + // Tri-planar textures don't use the mesh's texture coordinates but real world coordinates, + // so we need to convert these texture coordinate scales into real world scales so it looks + // the same when we switch to/from tri-planar mode. + matTerrain.setFloat("AlbedoMap_0_scale", (dirtScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_1_scale", (darkRockScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_2_scale", (snowScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_3_scale", (tileRoadScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_4_scale", (grassScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_5_scale", (marbleScale / terrainSize)); + matTerrain.setFloat("AlbedoMap_6_scale", (gravelScale / terrainSize)); + } else { + matTerrain.setBoolean("useTriPlanarMapping", false); + + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + } + } + if (name.equals("toggleNight") && !pressed) { + isNight = !isNight; + // Ambient and directional light are faded smoothly in update loop below. + } + + if(name.length() == 1 && !pressed){ + if(name.equals("-")){ + matTerrain.setInt("DebugValuesMode", -1); + }else{ + try{ + int debugValueMode = Integer.parseInt(name); + matTerrain.setInt("DebugValuesMode", debugValueMode); + } + catch(Exception e){ + + } + } + + } + + } + }; + + @Override + public void simpleInitApp() { + setupKeys(); + setUpTerrain(); + setUpTerrainMaterial(); + setUpLights(); + setUpCamera(); + } + + private void setUpTerrainMaterial() { + // PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/PBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + // this material also supports 'AlphaMap_2', so you can get up to 12 diffuse textures + + // DIRT texture, Diffuse textures 0 to 3 use the first AlphaMap + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_0", dirt); + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0); + //matTerrain.setInt("AfflictionMode_0", 0); + + // DARK ROCK texture + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + darkRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_1", darkRock); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 0.92f); + matTerrain.setFloat("Metallic_1", 0.02f); + //matTerrain.setInt("AfflictionMode_1", 0); + + // SNOW texture + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + snow.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_2", snow); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.55f); + matTerrain.setFloat("Metallic_2", 0.12f); + + Texture tiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + tiles.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_3", tiles); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 0.87f); + matTerrain.setFloat("Metallic_3", 0.08f); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_4", grass); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + // MARBLE texture + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + marble.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_5", marble); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 0.06f); + matTerrain.setFloat("Metallic_5", 0.8f); + + // Gravel texture + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + gravel.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_6", gravel); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 0.9f); + matTerrain.setFloat("Metallic_6", 0.07f); + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + normalMapDirt.setWrap(WrapMode.Repeat); + + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + normalMapDarkRock.setWrap(WrapMode.Repeat); + + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + normalMapSnow.setWrap(WrapMode.Repeat); + + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + normalMapGravel.setWrap(WrapMode.Repeat); + + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + normalMapGrass.setWrap(WrapMode.Repeat); + +// Texture normalMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Normal.png"); +// normalMapMarble.setWrap(WrapMode.Repeat); + + Texture normalMapTiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + normalMapTiles.setWrap(WrapMode.Repeat); + + matTerrain.setTexture("NormalMap_0", normalMapDirt); + matTerrain.setTexture("NormalMap_1", normalMapDarkRock); + matTerrain.setTexture("NormalMap_2", normalMapSnow); + matTerrain.setTexture("NormalMap_3", normalMapTiles); + matTerrain.setTexture("NormalMap_4", normalMapGrass); +// matTerrain.setTexture("NormalMap_5", normalMapMarble); // Adding this texture would exceed the 16 texture limit. + matTerrain.setTexture("NormalMap_6", normalMapGravel); + + terrain.setMaterial(matTerrain); + } + + private void setupKeys() { + flyCam.setMoveSpeed(50); + inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); + inputManager.addMapping("toggleNight", new KeyTrigger(KeyInput.KEY_N)); + + inputManager.addMapping("0", new KeyTrigger(KeyInput.KEY_0)); // toggleDebugModeForAlbedo + inputManager.addMapping("1", new KeyTrigger(KeyInput.KEY_1)); // toggleDebugModeForNormalMap + inputManager.addMapping("2", new KeyTrigger(KeyInput.KEY_2)); // toggleDebugModeForRoughness + inputManager.addMapping("3", new KeyTrigger(KeyInput.KEY_3)); // toggleDebugModeForMetallic + inputManager.addMapping("4", new KeyTrigger(KeyInput.KEY_4)); // toggleDebugModeForAo + inputManager.addMapping("5", new KeyTrigger(KeyInput.KEY_5)); // toggleDebugModeForEmissive + inputManager.addMapping("6", new KeyTrigger(KeyInput.KEY_6)); // toggleDebugModeForExposure + inputManager.addMapping("7", new KeyTrigger(KeyInput.KEY_7)); // toggleDebugModeForAlpha + inputManager.addMapping("8", new KeyTrigger(KeyInput.KEY_8)); // toggleDebugModeForGeometryNormals + + inputManager.addMapping("-", new KeyTrigger(KeyInput.KEY_MINUS)); // - key will disable dbug mode + + inputManager.addListener(actionListener, "triPlanar"); + inputManager.addListener(actionListener, "toggleNight"); + + inputManager.addListener(actionListener, "0"); + inputManager.addListener(actionListener, "1"); + inputManager.addListener(actionListener, "2"); + inputManager.addListener(actionListener, "3"); + inputManager.addListener(actionListener, "4"); + inputManager.addListener(actionListener, "5"); + inputManager.addListener(actionListener, "6"); + inputManager.addListener(actionListener, "7"); + inputManager.addListener(actionListener, "8"); + inputManager.addListener(actionListener, "-"); + + keybindingsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + keybindingsText.setText("Press N to toggle day/night fade (takes a moment) \n" + + "Press P to toggle tri-planar mode\n\n" + + "Press - for Final Render (disable debug view)\n" + + "Press 0 for Albedo debug view\n" + + "Press 1 for Normal Map debug view\n" + + "Press 2 for Roughness debug view\n" + + "Press 3 for Metallic debug view\n" + + "Press 4 for Ambient Occlusion (ao) debug view\n" + + "Press 5 for Emissive debug view\n" + + "Press 6 for Exposure debug view\n" + + "Press 7 for Alpha debug view\n" + + "Press 8 for Geoemtry Normals debug view\n"); + + + getGuiNode().attachChild(keybindingsText); + keybindingsText.move(new Vector3f(5, cam.getHeight() * 0.995f, 0)); + } + + @Override + public void simpleUpdate(float tpf) { + super.simpleUpdate(tpf); + + //smoothly transition from day to night + float currentLightIntensity = ambientLight.getColor().getRed(); + float incrementPerFrame = tpf * 0.3f; + + if (isNight) { + if (ambientLight.getColor().getRed() > nightLightIntensity) { + currentLightIntensity -= incrementPerFrame; + if (currentLightIntensity < nightLightIntensity) { + currentLightIntensity = nightLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } else { + if (ambientLight.getColor().getRed() < dayLightIntensity) { + currentLightIntensity += incrementPerFrame; + if (currentLightIntensity > dayLightIntensity) { + currentLightIntensity = dayLightIntensity; + } + + ambientLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + directionalLight.getColor().set(currentLightIntensity, currentLightIntensity, currentLightIntensity, 1.0f); + } + } + } + + private void setUpTerrain() { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap = null; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + + } catch (Exception e) { + e.printStackTrace(); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); +//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + rootNode.attachChild(terrain); + } + + private void setUpLights() { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + rootNode.addLight(probe); + + directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + rootNode.addLight(directionalLight); + + ambientLight = new AmbientLight(); + ambientLight.setColor(ColorRGBA.White); + rootNode.addLight(ambientLight); + } + + private void setUpCamera() { + cam.setLocation(new Vector3f(0, 10, -10)); + cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + + getFlyByCamera().setMoveSpeed(camMoveSpeed); + } +} diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java index 0ab647d624..76d10a8220 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainFractalGridTest.java @@ -2,9 +2,9 @@ import com.jme3.app.SimpleApplication; import com.jme3.app.state.ScreenshotAppState; -import com.jme3.bullet.control.CharacterControl; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.terrain.geomipmap.TerrainGrid; import com.jme3.terrain.geomipmap.TerrainGridLodControl; @@ -24,22 +24,14 @@ public class TerrainFractalGridTest extends SimpleApplication { - private Material mat_terrain; - private TerrainGrid terrain; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; public static void main(final String[] args) { TerrainFractalGridTest app = new TerrainFractalGridTest(); app.start(); } - private CharacterControl player3; - private FractalSum base; - private PerturbFilter perturb; - private OptimizedErode therm; - private SmoothFilter smooth; - private IterativeFilter iterate; @Override public void simpleInitApp() { @@ -48,10 +40,11 @@ public void simpleInitApp() { this.stateManager.attach(state); // TERRAIN TEXTURE material - this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + Material mat_terrain = new Material(this.assetManager, + "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); // Parameters to material: - // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionXColorMap: X = 1..4 the texture that should be applied to state X // regionX: a Vector3f containing the following information: // regionX.x: the start height of the region // regionX.y: the end height of the region @@ -63,37 +56,37 @@ public void simpleInitApp() { // GRASS texture Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); grass.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region1ColorMap", grass); - this.mat_terrain.setVector3("region1", new Vector3f(15, 200, this.grassScale)); + mat_terrain.setTexture("region1ColorMap", grass); + mat_terrain.setVector3("region1", new Vector3f(15, 200, this.grassScale)); // DIRT texture Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); dirt.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region2ColorMap", dirt); - this.mat_terrain.setVector3("region2", new Vector3f(0, 20, this.dirtScale)); + mat_terrain.setTexture("region2ColorMap", dirt); + mat_terrain.setVector3("region2", new Vector3f(0, 20, this.dirtScale)); // ROCK texture Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); rock.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region3ColorMap", rock); - this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region3ColorMap", rock); + mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("region4ColorMap", rock); - this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region4ColorMap", rock); + mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("slopeColorMap", rock); - this.mat_terrain.setFloat("slopeTileFactor", 32); + mat_terrain.setTexture("slopeColorMap", rock); + mat_terrain.setFloat("slopeTileFactor", 32); - this.mat_terrain.setFloat("terrainSize", 513); + mat_terrain.setFloat("terrainSize", 513); - this.base = new FractalSum(); - this.base.setRoughness(0.7f); - this.base.setFrequency(1.0f); - this.base.setAmplitude(1.0f); - this.base.setLacunarity(2.12f); - this.base.setOctaves(8); - this.base.setScale(0.02125f); - this.base.addModulator(new NoiseModulator() { + FractalSum base = new FractalSum(); + base.setRoughness(0.7f); + base.setFrequency(1.0f); + base.setAmplitude(1.0f); + base.setLacunarity(2.12f); + base.setOctaves(8); + base.setScale(0.02125f); + base.addModulator(new NoiseModulator() { @Override public float value(float... in) { @@ -101,41 +94,41 @@ public float value(float... in) { } }); - FilteredBasis ground = new FilteredBasis(this.base); + FilteredBasis ground = new FilteredBasis(base); - this.perturb = new PerturbFilter(); - this.perturb.setMagnitude(0.119f); + PerturbFilter perturb = new PerturbFilter(); + perturb.setMagnitude(0.119f); - this.therm = new OptimizedErode(); - this.therm.setRadius(5); - this.therm.setTalus(0.011f); + OptimizedErode therm = new OptimizedErode(); + therm.setRadius(5); + therm.setTalus(0.011f); - this.smooth = new SmoothFilter(); - this.smooth.setRadius(1); - this.smooth.setEffect(0.7f); + SmoothFilter smooth = new SmoothFilter(); + smooth.setRadius(1); + smooth.setEffect(0.7f); - this.iterate = new IterativeFilter(); - this.iterate.addPreFilter(this.perturb); - this.iterate.addPostFilter(this.smooth); - this.iterate.setFilter(this.therm); - this.iterate.setIterations(1); + IterativeFilter iterate = new IterativeFilter(); + iterate.addPreFilter(perturb); + iterate.addPostFilter(smooth); + iterate.setFilter(therm); + iterate.setIterations(1); - ground.addPreFilter(this.iterate); + ground.addPreFilter(iterate); - this.terrain = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f)); + TerrainGrid terrain + = new TerrainGrid("terrain", 33, 129, new FractalTileLoader(ground, 256f)); + terrain.setMaterial(mat_terrain); + terrain.setLocalTranslation(0, 0, 0); + terrain.setLocalScale(2f, 1f, 2f); + this.rootNode.attachChild(terrain); - this.terrain.setMaterial(this.mat_terrain); - this.terrain.setLocalTranslation(0, 0, 0); - this.terrain.setLocalScale(2f, 1f, 2f); - this.rootNode.attachChild(this.terrain); - - TerrainLodControl control = new TerrainGridLodControl(this.terrain, this.getCamera()); + TerrainLodControl control + = new TerrainGridLodControl(terrain, this.getCamera()); control.setLodCalculator(new DistanceLodCalculator(33, 2.7f)); // patch size, and a multiplier - this.terrain.addControl(control); - - + terrain.addControl(control); this.getCamera().setLocation(new Vector3f(0, 300, 0)); + cam.setRotation(new Quaternion(0.51176f, -0.14f, 0.085f, 0.84336f)); this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java index cf5e9d75c1..a5f01976bd 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridAlphaMapTest.java @@ -16,6 +16,7 @@ import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; @@ -44,9 +45,9 @@ public class TerrainGridAlphaMapTest extends SimpleApplication { private TerrainGrid terrain; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; private boolean usePhysics = false; public static void main(final String[] args) { @@ -54,13 +55,6 @@ public static void main(final String[] args) { app.start(); } private CharacterControl player3; - private FractalSum base; - private PerturbFilter perturb; - private OptimizedErode therm; - private SmoothFilter smooth; - private IterativeFilter iterate; - private Material material; - private Material matWire; @Override public void simpleInitApp() { @@ -85,7 +79,8 @@ public void simpleInitApp() { this.stateManager.attach(state); // TERRAIN TEXTURE material - material = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material material = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); material.setBoolean("useTriPlanarMapping", false); //material.setBoolean("isTerrainGrid", true); material.setFloat("Shininess", 0.0f); @@ -109,18 +104,19 @@ public void simpleInitApp() { material.setFloat("DiffuseMap_2_scale", rockScale); // WIREFRAME material - matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material matWire = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); matWire.getAdditionalRenderState().setWireframe(true); matWire.setColor("Color", ColorRGBA.Green); - this.base = new FractalSum(); - this.base.setRoughness(0.7f); - this.base.setFrequency(1.0f); - this.base.setAmplitude(1.0f); - this.base.setLacunarity(2.12f); - this.base.setOctaves(8); - this.base.setScale(0.02125f); - this.base.addModulator(new NoiseModulator() { + FractalSum base = new FractalSum(); + base.setRoughness(0.7f); + base.setFrequency(1.0f); + base.setAmplitude(1.0f); + base.setLacunarity(2.12f); + base.setOctaves(8); + base.setScale(0.02125f); + base.addModulator(new NoiseModulator() { @Override public float value(float... in) { @@ -128,29 +124,29 @@ public float value(float... in) { } }); - FilteredBasis ground = new FilteredBasis(this.base); + FilteredBasis ground = new FilteredBasis(base); - this.perturb = new PerturbFilter(); - this.perturb.setMagnitude(0.119f); + PerturbFilter perturb = new PerturbFilter(); + perturb.setMagnitude(0.119f); - this.therm = new OptimizedErode(); - this.therm.setRadius(5); - this.therm.setTalus(0.011f); + OptimizedErode therm = new OptimizedErode(); + therm.setRadius(5); + therm.setTalus(0.011f); - this.smooth = new SmoothFilter(); - this.smooth.setRadius(1); - this.smooth.setEffect(0.7f); + SmoothFilter smooth = new SmoothFilter(); + smooth.setRadius(1); + smooth.setEffect(0.7f); - this.iterate = new IterativeFilter(); - this.iterate.addPreFilter(this.perturb); - this.iterate.addPostFilter(this.smooth); - this.iterate.setFilter(this.therm); - this.iterate.setIterations(1); + IterativeFilter iterate = new IterativeFilter(); + iterate.addPreFilter(perturb); + iterate.addPostFilter(smooth); + iterate.setFilter(therm); + iterate.setIterations(1); - ground.addPreFilter(this.iterate); + ground.addPreFilter(iterate); this.terrain = new TerrainGrid("terrain", 33, 257, new FractalTileLoader(ground, 256)); - this.terrain.setMaterial(this.material); + this.terrain.setMaterial(material); this.terrain.setLocalTranslation(0, 0, 0); this.terrain.setLocalScale(2f, 1f, 2f); @@ -165,6 +161,7 @@ public float value(float... in) { this.getCamera().setLocation(new Vector3f(0, 256, 0)); + cam.setRotation(new Quaternion(-0.1f, 0.89826f, -0.2695f, -0.3325f)); this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); @@ -182,9 +179,11 @@ public float value(float... in) { } terrain.addListener(new TerrainGridListener() { + @Override public void gridMoved(Vector3f newCenter) { } + @Override public void tileAttached(Vector3f cell, TerrainQuad quad) { Texture alpha = null; try { @@ -200,6 +199,7 @@ public void tileAttached(Vector3f cell, TerrainQuad quad) { updateMarkerElevations(); } + @Override public void tileDetached(Vector3f cell, TerrainQuad quad) { if (usePhysics) { if (quad.getControl(RigidBodyControl.class) != null) { @@ -218,7 +218,7 @@ public void tileDetached(Vector3f cell, TerrainQuad quad) { createMarkerPoints(1); } - Node markers; + private Node markers; private void createMarkerPoints(float count) { diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java index b20357ded7..d380201516 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridSerializationTest.java @@ -13,6 +13,7 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.terrain.geomipmap.TerrainGrid; import com.jme3.terrain.geomipmap.TerrainGridListener; @@ -25,7 +26,7 @@ public class TerrainGridSerializationTest extends SimpleApplication { private TerrainGrid terrain; - private boolean usePhysics = true; + final private boolean usePhysics = true; public static void main(final String[] args) { TerrainGridSerializationTest app = new TerrainGridSerializationTest(); @@ -37,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); } @@ -58,6 +61,7 @@ public void simpleInitApp() { stateManager.attach(bulletAppState); this.getCamera().setLocation(new Vector3f(0, 256, 0)); + cam.setRotation(new Quaternion(-0.0075f, 0.949784f, -0.312f, -0.0227f)); this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); @@ -74,9 +78,11 @@ public void simpleInitApp() { terrain.addListener(new TerrainGridListener() { + @Override public void gridMoved(Vector3f newCenter) { } + @Override public void tileAttached(Vector3f cell, TerrainQuad quad) { //workaround for bugged test j3o's while(quad.getControl(RigidBodyControl.class)!=null){ @@ -86,6 +92,7 @@ public void tileAttached(Vector3f cell, TerrainQuad quad) { bulletAppState.getPhysicsSpace().add(quad); } + @Override public void tileDetached(Vector3f cell, TerrainQuad quad) { if (quad.getControl(RigidBodyControl.class) != null) { bulletAppState.getPhysicsSpace().remove(quad); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTest.java index 1781b71060..762b32f2da 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTest.java @@ -14,6 +14,7 @@ import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.terrain.geomipmap.TerrainGrid; import com.jme3.terrain.geomipmap.TerrainGridListener; @@ -28,13 +29,11 @@ public class TerrainGridTest extends SimpleApplication { - private Material mat_terrain; private TerrainGrid terrain; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; - private boolean usePhysics = false; - private boolean physicsAdded = false; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; + final private boolean usePhysics = false; public static void main(final String[] args) { TerrainGridTest app = new TerrainGridTest(); @@ -51,10 +50,11 @@ public void simpleInitApp() { this.stateManager.attach(state); // TERRAIN TEXTURE material - this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + Material mat_terrain = new Material(this.assetManager, + "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); // Parameters to material: - // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionXColorMap: X = 1..4 the texture that should be applied to state X // regionX: a Vector3f containing the following information: // regionX.x: the start height of the region // regionX.y: the end height of the region @@ -66,31 +66,32 @@ public void simpleInitApp() { // GRASS texture Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); grass.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region1ColorMap", grass); - this.mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); + mat_terrain.setTexture("region1ColorMap", grass); + mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); // DIRT texture Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); dirt.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region2ColorMap", dirt); - this.mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); + mat_terrain.setTexture("region2ColorMap", dirt); + mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); // ROCK texture Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); rock.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region3ColorMap", rock); - this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region3ColorMap", rock); + mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("region4ColorMap", rock); - this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region4ColorMap", rock); + mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("slopeColorMap", rock); - this.mat_terrain.setFloat("slopeTileFactor", 32); + mat_terrain.setTexture("slopeColorMap", rock); + mat_terrain.setFloat("slopeTileFactor", 32); - this.mat_terrain.setFloat("terrainSize", 129); + mat_terrain.setFloat("terrainSize", 129); this.terrain = new TerrainGrid("terrain", 65, 257, new ImageTileLoader(assetManager, new Namer() { + @Override public String getName(int x, int y) { return "Scenes/TerrainMountains/terrain_" + x + "_" + y + ".png"; } @@ -109,7 +110,7 @@ public String getName(int x, int y) { stateManager.attach(bulletAppState); this.getCamera().setLocation(new Vector3f(0, 400, 0)); - this.getCamera().lookAt(new Vector3f(0,0,0), Vector3f.UNIT_Y); + cam.setRotation(new Quaternion(0.61573f, -0.0054f, 0.0042f, 0.78793f)); this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); @@ -130,9 +131,11 @@ public String getName(int x, int y) { terrain.addListener(new TerrainGridListener() { + @Override public void gridMoved(Vector3f newCenter) { } + @Override public void tileAttached(Vector3f cell, TerrainQuad quad) { while(quad.getControl(RigidBodyControl.class)!=null){ quad.removeControl(RigidBodyControl.class); @@ -141,6 +144,7 @@ public void tileAttached(Vector3f cell, TerrainQuad quad) { bulletAppState.getPhysicsSpace().add(quad); } + @Override public void tileDetached(Vector3f cell, TerrainQuad quad) { if (quad.getControl(RigidBodyControl.class) != null) { bulletAppState.getPhysicsSpace().remove(quad); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainGridTileLoaderTest.java index 3485e4dc84..5f9ca109fe 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; @@ -16,6 +14,7 @@ import com.jme3.input.controls.MouseButtonTrigger; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.terrain.Terrain; import com.jme3.terrain.geomipmap.TerrainGrid; @@ -27,17 +26,14 @@ 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 { - private Material mat_terrain; private TerrainGrid terrain; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; - private boolean usePhysics = true; - private boolean physicsAdded = false; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; + final private boolean usePhysics = true; public static void main(final String[] args) { TerrainGridTileLoaderTest app = new TerrainGridTileLoaderTest(); @@ -47,22 +43,21 @@ 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); // TERRAIN TEXTURE material - this.mat_terrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); + Material mat_terrain = new Material(assetManager, + "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); // Parameters to material: - // regionXColorMap: X = 1..4 the texture that should be appliad to state X + // regionXColorMap: X = 1..4 the texture that should be applied to state X // regionX: a Vector3f containing the following information: // regionX.x: the start height of the region // regionX.y: the end height of the region @@ -74,33 +69,33 @@ public void simpleInitApp() { // GRASS texture Texture grass = this.assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); grass.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region1ColorMap", grass); - this.mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); + mat_terrain.setTexture("region1ColorMap", grass); + mat_terrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); // DIRT texture Texture dirt = this.assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); dirt.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region2ColorMap", dirt); - this.mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); + mat_terrain.setTexture("region2ColorMap", dirt); + mat_terrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); // ROCK texture Texture rock = this.assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); rock.setWrap(WrapMode.Repeat); - this.mat_terrain.setTexture("region3ColorMap", rock); - this.mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region3ColorMap", rock); + mat_terrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("region4ColorMap", rock); - this.mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); + mat_terrain.setTexture("region4ColorMap", rock); + mat_terrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); - this.mat_terrain.setTexture("slopeColorMap", rock); - this.mat_terrain.setFloat("slopeTileFactor", 32); + mat_terrain.setTexture("slopeColorMap", rock); + mat_terrain.setFloat("slopeTileFactor", 32); - this.mat_terrain.setFloat("terrainSize", 129); + mat_terrain.setFloat("terrainSize", 129); //quad.getHeightMap(), terrain.getLocalScale()), 0 AssetTileLoader grid = new AssetTileLoader(assetManager, "testgrid", "TerrainGrid"); this.terrain = new TerrainGrid("terrain", 65, 257, grid); - this.terrain.setMaterial(this.mat_terrain); + this.terrain.setMaterial(mat_terrain); this.terrain.setLocalTranslation(0, 0, 0); this.terrain.setLocalScale(2f, 1f, 2f); // try { @@ -119,7 +114,8 @@ public void simpleInitApp() { final BulletAppState bulletAppState = new BulletAppState(); stateManager.attach(bulletAppState); - this.getCamera().setLocation(new Vector3f(0, 256, 0)); + cam.setLocation(new Vector3f(257f, 256f, -514f)); + cam.setRotation(new Quaternion(-0.0075f, 0.949784f, -0.312f, -0.0227f)); this.viewPort.setBackgroundColor(new ColorRGBA(0.7f, 0.8f, 1f, 1f)); @@ -136,9 +132,11 @@ public void simpleInitApp() { terrain.addListener(new TerrainGridListener() { + @Override public void gridMoved(Vector3f newCenter) { } + @Override public void tileAttached(Vector3f cell, TerrainQuad quad) { while(quad.getControl(RigidBodyControl.class)!=null){ quad.removeControl(RigidBodyControl.class); @@ -147,6 +145,7 @@ public void tileAttached(Vector3f cell, TerrainQuad quad) { bulletAppState.getPhysicsSpace().add(quad); } + @Override public void tileDetached(Vector3f cell, TerrainQuad quad) { if (quad.getControl(RigidBodyControl.class) != null) { bulletAppState.getPhysicsSpace().remove(quad); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java index 5da9d9d2bc..4f91ebcf0e 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,11 +37,10 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; @@ -55,9 +54,9 @@ * The base terrain class it uses is TerrainQuad, which is a quad tree of actual * meshes called TerrainPatches. * There are a couple options for the terrain in this test: - * The first is wireframe mode. Here you can see the underlying trianglestrip structure. + * The first is wireframe mode. Here you can see the underlying triangle strip structure. * You will notice some off lines; these are degenerate triangles and are part of the - * trianglestrip. They are only noticeable in wireframe mode. + * triangle strip. They are only noticeable in wireframe mode. * Second is Tri-Planar texture mode. Here the textures are rendered on all 3 axes and * then blended together to reduce distortion and stretching. * Third, which you have to modify the code to see, is Entropy LOD calculations. @@ -72,13 +71,10 @@ public class TerrainTest extends SimpleApplication { private TerrainQuad terrain; - Material matRock; - Material matWire; - boolean wireframe = false; - boolean triPlanar = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; + private Material matRock; + private Material matWire; + private boolean wireframe = false; + private boolean triPlanar = false; private float grassScale = 64; private float dirtScale = 16; private float rockScale = 128; @@ -150,9 +146,9 @@ public void simpleInitApp() { * Here we create the actual terrain. The tiles will be 65x65, and the total size of the * terrain will be 513x513. It uses the heightmap we created to generate the height values. */ - /** + /* * Optimal terrain patch size is 65 (64x64). - * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at + * The total size is up to you. At 1025, it ran fine for me (200+FPS), however at * size=2049, it got really slow. But that is a jump from 2 million to 8 million triangles... */ terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); @@ -169,14 +165,14 @@ public void simpleInitApp() { rootNode.addLight(light); cam.setLocation(new Vector3f(0, 10, -10)); - cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + cam.setRotation(new Quaternion(0.012f, 0.964311f, -0.261f, 0.043f)); } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setSize(guiFont.getCharSet().getRenderedSize()); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); - hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing"); + hintText.setText("Press T to toggle wireframe, P to toggle tri-planar texturing"); guiNode.attachChild(hintText); } @@ -187,12 +183,13 @@ private void setupKeys() { inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); inputManager.addListener(actionListener, "triPlanar"); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("wireframe") && !pressed) { wireframe = !wireframe; - if (!wireframe) { + if (wireframe) { terrain.setMaterial(matWire); } else { terrain.setMaterial(matRock); @@ -201,12 +198,12 @@ public void onAction(String name, boolean pressed, float tpf) { triPlanar = !triPlanar; if (triPlanar) { matRock.setBoolean("useTriPlanarMapping", true); - // planar textures don't use the mesh's texture coordinates but real world coordinates, - // so we need to convert these texture coordinate scales into real world scales so it looks - // the same when we switch to/from tri-planar mode - matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale)); - matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale)); - matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale)); + // Planar textures don't use the mesh's texture coordinates but real-world coordinates, + // so we need to convert these texture coordinate scales into real-world scales, so it looks + // the same when we switch to/from tri-planar mode. + matRock.setFloat("Tex1Scale", 1f / (512f / grassScale)); + matRock.setFloat("Tex2Scale", 1f / (512f / dirtScale)); + matRock.setFloat("Tex3Scale", 1f / (512f / rockScale)); } else { matRock.setBoolean("useTriPlanarMapping", false); matRock.setFloat("Tex1Scale", grassScale); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAdvanced.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAdvanced.java index afd83ac31f..c69eeac3fb 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAdvanced.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAdvanced.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,7 +39,6 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Vector3f; @@ -64,15 +63,10 @@ public class TerrainTestAdvanced extends SimpleApplication { private TerrainQuad terrain; - Material matTerrain; - Material matWire; - boolean wireframe = false; - boolean triPlanar = false; - boolean wardiso = false; - boolean minnaert = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; + private Material matTerrain; + private Material matWire; + private boolean wireframe = false; + private boolean triPlanar = false; private float dirtScale = 16; private float darkRockScale = 32; private float pinkRockScale = 32; @@ -200,9 +194,9 @@ public void simpleInitApp() { * Here we create the actual terrain. The tiles will be 65x65, and the total size of the * terrain will be 513x513. It uses the heightmap we created to generate the height values. */ - /** + /* * Optimal terrain patch size is 65 (64x64). - * The total size is up to you. At 1025 it ran fine for me (200+FPS), however at + * The total size is up to you. At 1025, it ran fine for me (200+FPS), however at * size=2049 it got really slow. But that is a jump from 2 million to 8 million triangles... */ terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap());//, new LodPerspectiveCalculatorFactory(getCamera(), 4)); // add this in to see it use entropy for LOD calculations @@ -231,10 +225,10 @@ public void simpleInitApp() { } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setSize(guiFont.getCharSet().getRenderedSize()); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); - hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing"); + hintText.setText("Press T to toggle wireframe, P to toggle tri-planar texturing"); guiNode.attachChild(hintText); } @@ -249,12 +243,13 @@ private void setupKeys() { inputManager.addMapping("DetachControl", new KeyTrigger(KeyInput.KEY_0)); inputManager.addListener(actionListener, "DetachControl"); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("wireframe") && !pressed) { wireframe = !wireframe; - if (!wireframe) { + if (wireframe) { terrain.setMaterial(matWire); } else { terrain.setMaterial(matTerrain); @@ -263,16 +258,16 @@ public void onAction(String name, boolean pressed, float tpf) { triPlanar = !triPlanar; if (triPlanar) { matTerrain.setBoolean("useTriPlanarMapping", true); - // planar textures don't use the mesh's texture coordinates but real world coordinates, - // so we need to convert these texture coordinate scales into real world scales so it looks - // the same when we switch to/from tr-planar mode (1024f is the alphamap size) - matTerrain.setFloat("DiffuseMap_0_scale", 1f / (float) (1024f / dirtScale)); - matTerrain.setFloat("DiffuseMap_1_scale", 1f / (float) (1024f / darkRockScale)); - matTerrain.setFloat("DiffuseMap_2_scale", 1f / (float) (1024f / pinkRockScale)); - matTerrain.setFloat("DiffuseMap_3_scale", 1f / (float) (1024f / riverRockScale)); - matTerrain.setFloat("DiffuseMap_4_scale", 1f / (float) (1024f / grassScale)); - matTerrain.setFloat("DiffuseMap_5_scale", 1f / (float) (1024f / brickScale)); - matTerrain.setFloat("DiffuseMap_6_scale", 1f / (float) (1024f / roadScale)); + // Planar textures don't use the mesh's texture coordinates but real-world coordinates, + // so we need to convert these texture coordinate scales into real-world scales, so it looks + // the same when we switch to/from tri-planar mode. (1024 is the alphamap size.) + matTerrain.setFloat("DiffuseMap_0_scale", 1f / (1024f / dirtScale)); + matTerrain.setFloat("DiffuseMap_1_scale", 1f / (1024f / darkRockScale)); + matTerrain.setFloat("DiffuseMap_2_scale", 1f / (1024f / pinkRockScale)); + matTerrain.setFloat("DiffuseMap_3_scale", 1f / (1024f / riverRockScale)); + matTerrain.setFloat("DiffuseMap_4_scale", 1f / (1024f / grassScale)); + matTerrain.setFloat("DiffuseMap_5_scale", 1f / (1024f / brickScale)); + matTerrain.setFloat("DiffuseMap_6_scale", 1f / (1024f / roadScale)); } else { matTerrain.setBoolean("useTriPlanarMapping", false); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAndroid.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAndroid.java index d5b74b2914..57be5d293a 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAndroid.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestAndroid.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,11 +37,10 @@ import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; -import com.jme3.scene.Geometry; import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainQuad; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; @@ -52,21 +51,18 @@ /** * Demonstrates how to use terrain on Android. - * The only difference is it uses a much smaller heightmap so it won't use up - * all of the android device's memory. + * The only difference is it uses a much smaller heightmap, so it won't use + * all the device's memory. * * @author bowens */ public class TerrainTestAndroid extends SimpleApplication { private TerrainQuad terrain; - Material matRock; - Material matWire; - boolean wireframe = false; - boolean triPlanar = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; + private Material matRock; + private Material matWire; + private boolean wireframe = false; + private boolean triPlanar = false; private float grassScale = 64; private float dirtScale = 16; private float rockScale = 128; @@ -150,14 +146,14 @@ public void simpleInitApp() { rootNode.addLight(light); cam.setLocation(new Vector3f(0, 10, -10)); - cam.lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + cam.setRotation(new Quaternion(0.01f, 0.964871f, -0.25966f, 0.0387f)); } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setSize(guiFont.getCharSet().getRenderedSize()); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); - hintText.setText("Hit T to switch to wireframe, P to switch to tri-planar texturing"); + hintText.setText("Press T to toggle wireframe, P to toggle tri-planar texturing"); guiNode.attachChild(hintText); } @@ -168,12 +164,13 @@ private void setupKeys() { inputManager.addMapping("triPlanar", new KeyTrigger(KeyInput.KEY_P)); inputManager.addListener(actionListener, "triPlanar"); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("wireframe") && !pressed) { wireframe = !wireframe; - if (!wireframe) { + if (wireframe) { terrain.setMaterial(matWire); } else { terrain.setMaterial(matRock); @@ -182,12 +179,12 @@ public void onAction(String name, boolean pressed, float tpf) { triPlanar = !triPlanar; if (triPlanar) { matRock.setBoolean("useTriPlanarMapping", true); - // planar textures don't use the mesh's texture coordinates but real world coordinates, - // so we need to convert these texture coordinate scales into real world scales so it looks - // the same when we switch to/from tr-planar mode - matRock.setFloat("Tex1Scale", 1f / (float) (512f / grassScale)); - matRock.setFloat("Tex2Scale", 1f / (float) (512f / dirtScale)); - matRock.setFloat("Tex3Scale", 1f / (float) (512f / rockScale)); + // Planar textures don't use the mesh's texture coordinates but real-world coordinates, + // so we need to convert these texture-coordinate scales into real-world scales so it looks + // the same when we switch to tri-planar mode. + matRock.setFloat("Tex1Scale", 1f / (512f / grassScale)); + matRock.setFloat("Tex2Scale", 1f / (512f / dirtScale)); + matRock.setFloat("Tex3Scale", 1f / (512f / rockScale)); } else { matRock.setBoolean("useTriPlanarMapping", false); matRock.setFloat("Tex1Scale", grassScale); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestCollision.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestCollision.java index faa67fd137..456e5cec29 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestCollision.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestCollision.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,7 +45,6 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.input.controls.MouseButtonTrigger; import com.jme3.light.DirectionalLight; -import com.jme3.light.PointLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; import com.jme3.math.Quaternion; @@ -53,7 +52,6 @@ import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; -import com.jme3.scene.Node; import com.jme3.scene.shape.Box; import com.jme3.scene.shape.Sphere; import com.jme3.terrain.geomipmap.TerrainLodControl; @@ -78,19 +76,12 @@ */ public class TerrainTestCollision extends SimpleApplication { - TerrainQuad terrain; - Node terrainPhysicsNode; - Material matRock; - Material matWire; - boolean wireframe = false; - protected BitmapText hintText; - PointLight pl; - Geometry lightMdl; - List collisionMarkers; - private BulletAppState bulletAppState; - Geometry collisionSphere; - Geometry collisionBox; - Geometry selectedCollisionObject; + private TerrainQuad terrain; + private Material matRock; + private Material matWire; + private boolean wireframe = false; + private List collisionMarkers; + private Geometry selectedCollisionObject; public static void main(String[] args) { TerrainTestCollision app = new TerrainTestCollision(); @@ -107,7 +98,7 @@ public void initialize() { @Override public void simpleInitApp() { collisionMarkers = new ArrayList<>(); - bulletAppState = new BulletAppState(); + BulletAppState bulletAppState = new BulletAppState(); bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL); stateManager.attach(bulletAppState); setupKeys(); @@ -149,7 +140,7 @@ public void simpleInitApp() { // if set to false, only the first collision is returned and collision is slightly faster. terrain.setSupportMultipleCollisions(true); - /** + /* * Create PhysicsRigidBodyControl for collision */ terrain.addControl(new RigidBodyControl(0)); @@ -171,7 +162,7 @@ public void simpleInitApp() { bulletAppState.getPhysicsSpace().add(sphere); } - collisionBox = new Geometry("collisionBox", new Box(2, 2, 2)); + Geometry collisionBox = new Geometry("collisionBox", new Box(2, 2, 2)); collisionBox.setModelBound(new BoundingBox()); collisionBox.setLocalTranslation(new Vector3f(20, 95, 30)); collisionBox.setMaterial(matWire); @@ -188,7 +179,7 @@ public void simpleInitApp() { } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setSize(guiFont.getCharSet().getRenderedSize()); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); hintText.setText("Press T to toggle wireframe"); @@ -197,7 +188,7 @@ public void loadHintText() { protected void initCrossHairs() { //guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt"); - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center @@ -246,8 +237,9 @@ private void createCollisionMarkers(int num) { } } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String binding, boolean keyPressed, float tpf) { if (binding.equals("wireframe") && !keyPressed) { wireframe = !wireframe; diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java index b68761593e..4a34906fd0 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestModifyHeight.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,27 +45,18 @@ import com.jme3.material.Material; import com.jme3.material.RenderState.BlendMode; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Ray; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.scene.Geometry; import com.jme3.scene.debug.Arrow; import com.jme3.scene.shape.Sphere; -import com.jme3.terrain.geomipmap.TerrainGrid; import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainQuad; -import com.jme3.terrain.geomipmap.grid.FractalTileLoader; import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.heightmap.AbstractHeightMap; import com.jme3.terrain.heightmap.ImageBasedHeightMap; -import com.jme3.terrain.noise.ShaderUtils; -import com.jme3.terrain.noise.basis.FilteredBasis; -import com.jme3.terrain.noise.filter.IterativeFilter; -import com.jme3.terrain.noise.filter.OptimizedErode; -import com.jme3.terrain.noise.filter.PerturbFilter; -import com.jme3.terrain.noise.filter.SmoothFilter; -import com.jme3.terrain.noise.fractal.FractalSum; -import com.jme3.terrain.noise.modulator.NoiseModulator; import com.jme3.texture.Texture; import com.jme3.texture.Texture.WrapMode; import java.util.ArrayList; @@ -78,16 +69,13 @@ public class TerrainTestModifyHeight extends SimpleApplication { private TerrainQuad terrain; - Material matTerrain; - Material matWire; - boolean wireframe = true; - boolean triPlanar = false; - boolean wardiso = false; - boolean minnaert = false; - protected BitmapText hintText; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; + private Material matTerrain; + private Material matWire; + private boolean wireframe = false; + private BitmapText hintText; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; private boolean raiseTerrain = false; private boolean lowerTerrain = false; @@ -152,11 +140,11 @@ public void simpleInitApp() { rootNode.addLight(ambLight); cam.setLocation(new Vector3f(0, 256, 0)); - cam.lookAtDirection(new Vector3f(0, -1f, 0).normalizeLocal(), Vector3f.UNIT_X); + cam.setRotation(new Quaternion(0.25966f, 0.690398f, -0.2952f, 0.60727f)); } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + hintText = new BitmapText(guiFont); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); hintText.setText("Hit 1 to raise terrain, hit 2 to lower terrain"); guiNode.attachChild(hintText); @@ -173,7 +161,7 @@ public void updateHintText(Vector3f target) { } protected void initCrossHairs() { - BitmapText ch = new BitmapText(guiFont, false); + BitmapText ch = new BitmapText(guiFont); ch.setSize(guiFont.getCharSet().getRenderedSize() * 2); ch.setText("+"); // crosshairs ch.setLocalTranslation( // center @@ -191,12 +179,13 @@ private void setupKeys() { inputManager.addMapping("Lower", new MouseButtonTrigger(MouseInput.BUTTON_RIGHT)); inputManager.addListener(actionListener, "Lower"); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("wireframe") && !pressed) { wireframe = !wireframe; - if (!wireframe) { + if (wireframe) { terrain.setMaterial(matWire); } else { terrain.setMaterial(matTerrain); @@ -218,8 +207,8 @@ private void adjustHeight(Vector3f loc, float radius, float height) { float xStepAmount = terrain.getLocalScale().x; float zStepAmount = terrain.getLocalScale().z; long start = System.currentTimeMillis(); - List locs = new ArrayList(); - List heights = new ArrayList(); + List locs = new ArrayList<>(); + List heights = new ArrayList<>(); for (int z = -radiusStepsZ; z < radiusStepsZ; z++) { for (int x = -radiusStepsX; x < radiusStepsX; x++) { @@ -326,95 +315,6 @@ private void createTerrain() { rootNode.attachChild(terrain); } - private void createTerrainGrid() { - - // TERRAIN TEXTURE material - matTerrain = new Material(this.assetManager, "Common/MatDefs/Terrain/HeightBasedTerrain.j3md"); - - // Parameters to material: - // regionXColorMap: X = 1..4 the texture that should be appliad to state X - // regionX: a Vector3f containing the following information: - // regionX.x: the start height of the region - // regionX.y: the end height of the region - // regionX.z: the texture scale for the region - // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) - // slopeColorMap: the texture to be used for cliffs, and steep mountain sites - // slopeTileFactor: the texture scale for slopes - // terrainSize: the total size of the terrain (used for scaling the texture) - // GRASS texture - Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); - grass.setWrap(WrapMode.Repeat); - matTerrain.setTexture("region1ColorMap", grass); - matTerrain.setVector3("region1", new Vector3f(88, 200, this.grassScale)); - - // DIRT texture - Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); - dirt.setWrap(WrapMode.Repeat); - matTerrain.setTexture("region2ColorMap", dirt); - matTerrain.setVector3("region2", new Vector3f(0, 90, this.dirtScale)); - - // ROCK texture - Texture rock = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); - rock.setWrap(WrapMode.Repeat); - matTerrain.setTexture("region3ColorMap", rock); - matTerrain.setVector3("region3", new Vector3f(198, 260, this.rockScale)); - - matTerrain.setTexture("region4ColorMap", rock); - matTerrain.setVector3("region4", new Vector3f(198, 260, this.rockScale)); - - matTerrain.setTexture("slopeColorMap", rock); - matTerrain.setFloat("slopeTileFactor", 32); - - matTerrain.setFloat("terrainSize", 513); - - FractalSum base = new FractalSum(); - base.setRoughness(0.7f); - base.setFrequency(1.0f); - base.setAmplitude(1.0f); - base.setLacunarity(2.12f); - base.setOctaves(8); - base.setScale(0.02125f); - base.addModulator(new NoiseModulator() { - @Override - public float value(float... in) { - return ShaderUtils.clamp(in[0] * 0.5f + 0.5f, 0, 1); - } - }); - - FilteredBasis ground = new FilteredBasis(base); - - PerturbFilter perturb = new PerturbFilter(); - perturb.setMagnitude(0.119f); - - OptimizedErode therm = new OptimizedErode(); - therm.setRadius(5); - therm.setTalus(0.011f); - - SmoothFilter smooth = new SmoothFilter(); - smooth.setRadius(1); - smooth.setEffect(0.7f); - - IterativeFilter iterate = new IterativeFilter(); - iterate.addPreFilter(perturb); - iterate.addPostFilter(smooth); - iterate.setFilter(therm); - iterate.setIterations(1); - - ground.addPreFilter(iterate); - - this.terrain = new TerrainGrid("terrain", 65, 257, new FractalTileLoader(ground, 256f)); - - - terrain.setMaterial(matTerrain); - terrain.setLocalTranslation(0, 0, 0); - terrain.setLocalScale(2f, 1f, 2f); - - rootNode.attachChild(this.terrain); - - TerrainLodControl control = new TerrainLodControl(this.terrain, getCamera()); - this.terrain.addControl(control); - } - private void createMarker() { // collision marker Sphere sphere = new Sphere(8, 8, 0.5f); diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java index 8610962985..06a4f95776 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestReadWrite.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ import com.jme3.light.DirectionalLight; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; import com.jme3.math.Vector3f; import com.jme3.scene.Node; import com.jme3.terrain.Terrain; @@ -64,12 +65,9 @@ public class TerrainTestReadWrite extends SimpleApplication { private Terrain terrain; - protected BitmapText hintText; - private float grassScale = 64; - private float dirtScale = 16; - private float rockScale = 128; - private Material matTerrain; - private Material matWire; + final private float grassScale = 64; + final private float dirtScale = 16; + final private float rockScale = 128; public static void main(String[] args) { TerrainTestReadWrite app = new TerrainTestReadWrite(); @@ -93,7 +91,8 @@ public void simpleInitApp() { } private void createMap() { - matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matTerrain = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); matTerrain.setBoolean("useTriPlanarMapping", false); matTerrain.setBoolean("WardIso", true); @@ -133,7 +132,8 @@ private void createMap() { matTerrain.setTexture("NormalMap_1", normalMap1); matTerrain.setTexture("NormalMap_2", normalMap2); - matWire = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + Material matWire = new Material(assetManager, + "Common/MatDefs/Misc/Unshaded.j3md"); matWire.getAdditionalRenderState().setWireframe(true); matWire.setColor("Color", ColorRGBA.Green); @@ -175,6 +175,7 @@ private void createMap() { private void createControls() { flyCam.setMoveSpeed(50); cam.setLocation(new Vector3f(0, 100, 0)); + cam.setRotation(new Quaternion(-0.1779f, 0.821934f, -0.39033f, -0.3747f)); inputManager.addMapping("save", new KeyTrigger(KeyInput.KEY_T)); inputManager.addListener(saveActionListener, "save"); @@ -187,14 +188,15 @@ private void createControls() { } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setSize(guiFont.getCharSet().getRenderedSize()); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); hintText.setText("Hit T to save, and Y to load"); guiNode.attachChild(hintText); } - private ActionListener saveActionListener = new ActionListener() { + final private ActionListener saveActionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("save") && !pressed) { @@ -265,16 +267,18 @@ private void loadTerrain() { } } } - private ActionListener loadActionListener = new ActionListener() { + final private ActionListener loadActionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("load") && !pressed) { loadTerrain(); } } }; - private ActionListener cloneActionListener = new ActionListener() { + final private ActionListener cloneActionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("clone") && !pressed) { @@ -285,45 +289,4 @@ public void onAction(String name, boolean pressed, float tpf) { } } }; - - // no junit tests, so this has to be hand-tested: - private static void testHeightmapBuilding() { - int s = 9; - int b = 3; - float[] hm = new float[s * s]; - for (int i = 0; i < s; i++) { - for (int j = 0; j < s; j++) { - hm[(i * s) + j] = i * j; - } - } - - for (int i = 0; i < s; i++) { - for (int j = 0; j < s; j++) { - System.out.print(hm[i * s + j] + " "); - } - System.out.println(""); - } - - TerrainQuad terrain = new TerrainQuad("terrain", b, s, hm); - float[] hm2 = terrain.getHeightMap(); - boolean failed = false; - for (int i = 0; i < s * s; i++) { - if (hm[i] != hm2[i]) { - failed = true; - } - } - - System.out.println(""); - if (failed) { - System.out.println("Terrain heightmap building FAILED!!!"); - for (int i = 0; i < s; i++) { - for (int j = 0; j < s; j++) { - System.out.print(hm2[i * s + j] + " "); - } - System.out.println(""); - } - } else { - System.out.println("Terrain heightmap building PASSED"); - } - } } diff --git a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java index 454846b8c5..e4d60bd915 100644 --- a/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java +++ b/jme3-examples/src/main/java/jme3test/terrain/TerrainTestTile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,14 +66,10 @@ public class TerrainTestTile extends SimpleApplication { private TiledTerrain terrain; - Material matTerrain; - Material matWire; - boolean wireframe = false; - boolean triPlanar = false; - boolean wardiso = false; - boolean minnaert = false; - protected BitmapText hintText; - private float grassScale = 256; + private Material matTerrain; + private Material matWire; + private boolean wireframe = false; + final private float grassScale = 256; public static void main(String[] args) { @@ -133,7 +129,7 @@ public void simpleInitApp() { } public void loadHintText() { - hintText = new BitmapText(guiFont, false); + BitmapText hintText = new BitmapText(guiFont); hintText.setLocalTranslation(0, getCamera().getHeight(), 0); hintText.setText("Press T to toggle wireframe"); guiNode.attachChild(hintText); @@ -145,8 +141,9 @@ private void setupKeys() { inputManager.addMapping("wireframe", new KeyTrigger(KeyInput.KEY_T)); inputManager.addListener(actionListener, "wireframe"); } - private ActionListener actionListener = new ActionListener() { + final private ActionListener actionListener = new ActionListener() { + @Override public void onAction(String name, boolean pressed, float tpf) { if (name.equals("wireframe") && !pressed) { wireframe = !wireframe; @@ -169,10 +166,10 @@ public void onAction(String name, boolean pressed, float tpf) { */ private class TiledTerrain extends Node implements Terrain, NeighbourFinder { - private TerrainQuad terrain1; - private TerrainQuad terrain2; - private TerrainQuad terrain3; - private TerrainQuad terrain4; + final private TerrainQuad terrain1; + final private TerrainQuad terrain2; + final private TerrainQuad terrain3; + final private TerrainQuad terrain4; TiledTerrain() { // TERRAIN TEXTURE material @@ -231,6 +228,7 @@ private class TiledTerrain extends Node implements Terrain, NeighbourFinder { * 1 3 * 2 4 */ + @Override public TerrainQuad getRightQuad(TerrainQuad center) { //System.out.println("lookup neighbour"); if (center == terrain1) @@ -245,6 +243,7 @@ public TerrainQuad getRightQuad(TerrainQuad center) { * 1 3 * 2 4 */ + @Override public TerrainQuad getLeftQuad(TerrainQuad center) { //System.out.println("lookup neighbour"); if (center == terrain3) @@ -259,6 +258,7 @@ public TerrainQuad getLeftQuad(TerrainQuad center) { * 1 3 * 2 4 */ + @Override public TerrainQuad getTopQuad(TerrainQuad center) { //System.out.println("lookup neighbour"); if (center == terrain2) @@ -273,6 +273,7 @@ public TerrainQuad getTopQuad(TerrainQuad center) { * 1 3 * 2 4 */ + @Override public TerrainQuad getDownQuad(TerrainQuad center) { //System.out.println("lookup neighbour"); if (center == terrain1) @@ -283,69 +284,84 @@ public TerrainQuad getDownQuad(TerrainQuad center) { return null; } + @Override public float getHeight(Vector2f xz) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public Vector3f getNormal(Vector2f xz) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public float getHeightmapHeight(Vector2f xz) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public void setHeight(Vector2f xzCoordinate, float height) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public void setHeight(List xz, List height) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public void adjustHeight(Vector2f xzCoordinate, float delta) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public void adjustHeight(List xz, List height) { // you will have to offset the coordinate for each terrain, to center on it throw new UnsupportedOperationException("Not supported yet."); } + @Override public float[] getHeightMap() { throw new UnsupportedOperationException("Not supported yet."); } + @Override public int getMaxLod() { throw new UnsupportedOperationException("Not supported yet."); } + @Override public void setLocked(boolean locked) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public void generateEntropy(ProgressMonitor monitor) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public Material getMaterial() { throw new UnsupportedOperationException("Not supported yet."); } + @Override public Material getMaterial(Vector3f worldLocation) { throw new UnsupportedOperationException("Not supported yet."); } + @Override public int getTerrainSize() { throw new UnsupportedOperationException("Not supported yet."); } + @Override public int getNumMajorSubdivisions() { throw new UnsupportedOperationException("Not supported yet."); } diff --git a/jme3-examples/src/main/java/jme3test/terrain/package-info.java b/jme3-examples/src/main/java/jme3test/terrain/package-info.java new file mode 100644 index 0000000000..6cb10c70d3 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/terrain/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for modeling terrain + */ +package jme3test.terrain; diff --git a/jme3-examples/src/main/java/jme3test/texture/TestAnisotropicFilter.java b/jme3-examples/src/main/java/jme3test/texture/TestAnisotropicFilter.java index b45e613326..16ba966618 100755 --- a/jme3-examples/src/main/java/jme3test/texture/TestAnisotropicFilter.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestAnisotropicFilter.java @@ -7,13 +7,12 @@ import com.jme3.input.controls.KeyTrigger; import com.jme3.material.Material; import com.jme3.math.ColorRGBA; -import com.jme3.math.FastMath; import com.jme3.math.Quaternion; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; import com.jme3.renderer.Limits; import com.jme3.scene.Geometry; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.texture.Image; import com.jme3.texture.Image.Format; import com.jme3.texture.Texture; @@ -35,10 +34,13 @@ public void simpleInitApp() { flyCam.setMoveSpeed(100); cam.setLocation(new Vector3f(197.02617f, 4.6769195f, -194.89545f)); cam.setRotation(new Quaternion(0.07921988f, 0.8992258f, -0.18292196f, 0.38943136f)); - Quad q = new Quad(1000, 1000); - q.scaleTextureCoordinates(new Vector2f(1000, 1000)); - Geometry geom = new Geometry("quad", q); - geom.rotate(-FastMath.HALF_PI, 0, 0); + + RectangleMesh rm = new RectangleMesh( + new Vector3f(-500, 0, 500), + new Vector3f(500, 0, 500), + new Vector3f(-500, 0, -500)); + rm.scaleTextureCoordinates(new Vector2f(1000, 1000)); + Geometry geom = new Geometry("rectangle", rm); geom.setMaterial(createCheckerBoardMaterial(assetManager)); rootNode.attachChild(geom); diff --git a/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java b/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java index 4f4b1229bd..5288d4ffe6 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestImageRaster.java @@ -17,7 +17,6 @@ import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture2D; -import com.jme3.texture.image.ColorSpace; import com.jme3.texture.image.ImageRaster; import com.jme3.util.BufferUtils; import java.nio.ByteBuffer; @@ -66,28 +65,6 @@ private void convertAndPutImage(Image image, float posX, float posY) { rootNode.attachChild(txt); } - private Image createTestImage() { - Image testImage = new Image(Format.BGR8, 4, 3, BufferUtils.createByteBuffer(4 * 4 * 3), null, ColorSpace.Linear); - - ImageRaster io = ImageRaster.create(testImage); - io.setPixel(0, 0, ColorRGBA.Black); - io.setPixel(1, 0, ColorRGBA.Gray); - io.setPixel(2, 0, ColorRGBA.White); - io.setPixel(3, 0, ColorRGBA.White.mult(4)); // HDR color - - io.setPixel(0, 1, ColorRGBA.Red); - io.setPixel(1, 1, ColorRGBA.Green); - io.setPixel(2, 1, ColorRGBA.Blue); - io.setPixel(3, 1, new ColorRGBA(0, 0, 0, 0)); - - io.setPixel(0, 2, ColorRGBA.Yellow); - io.setPixel(1, 2, ColorRGBA.Magenta); - io.setPixel(2, 2, ColorRGBA.Cyan); - io.setPixel(3, 2, new ColorRGBA(1, 1, 1, 0)); - - return testImage; - } - @Override public void simpleInitApp() { cam.setLocation(new Vector3f(16, 6, 36)); diff --git a/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java b/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java new file mode 100644 index 0000000000..52f698c99d --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/TestShaderImage.java @@ -0,0 +1,59 @@ +/* + * Click nbfs://nbhost/SystemFileSystem/Templates/Licenses/license-default.txt to change this license + * Click nbfs://nbhost/SystemFileSystem/Templates/Classes/Class.java to edit this template + */ +package jme3test.texture; + +import com.jme3.app.SimpleApplication; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.shape.Box; +import com.jme3.scene.shape.Quad; +import com.jme3.shader.VarType; +import com.jme3.texture.Image; +import com.jme3.texture.Texture2D; +import com.jme3.texture.TextureImage; + +/** + * + * @author codex + */ +public class TestShaderImage extends SimpleApplication { + + private int frame = 0; + + public static void main(String[] args) { + new TestShaderImage().start(); + } + + @Override + public void simpleInitApp() { + + Geometry box = new Geometry("Box", new Box(1, 1, 1)); + Material mat = new Material(assetManager, "Materials/ImageTest.j3md"); + box.setMaterial(mat); + rootNode.attachChild(box); + + int width = context.getFramebufferWidth(); + int height = context.getFramebufferHeight(); + Texture2D target = new Texture2D(width, height, Image.Format.RGBA8); + TextureImage targetImage = new TextureImage(target, TextureImage.Access.WriteOnly); + mat.setParam("TargetImage", VarType.Image2D, targetImage); + + Geometry pic = new Geometry("gui_pic", new Quad(200, 200)); + pic.setLocalTranslation(0, height - 200, 0); + Material picMat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); + picMat.setTexture("ColorMap", target); + pic.setMaterial(mat); + guiNode.attachChild(pic); + + } + @Override + public void simpleUpdate(float tpf) { + if (frame++ < 5) { + cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + } + } + +} diff --git a/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java b/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java index 7991bd067f..21c1b590b9 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestSkyLoading.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,6 +44,7 @@ public static void main(String[] args){ app.start(); } + @Override public void simpleInitApp() { Texture west = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_west.jpg"); Texture east = assetManager.loadTexture("Textures/Sky/Lagoon/lagoon_east.jpg"); diff --git a/jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java b/jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java index f57e71fd73..89c0718733 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestSkyRotation.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017 jMonkeyEngine + * Copyright (c) 2017-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -116,7 +116,7 @@ public void simpleInitApp() { * * @param name the name of the action * @param ongoing true→depress key, false→release key - * @param ignored + * @param ignored ignored */ @Override public void onAction(String name, boolean ongoing, float ignored) { diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java index 1e151bf741..e3f41b6fee 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestTexture3D.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,12 +66,12 @@ public void simpleInitApp() { flyCam.setMoveSpeed(10); //creating a sphere Sphere sphere = new Sphere(32, 32, 1); - //getting the boundingbox + // getting the bounding box sphere.updateBound(); BoundingBox bb = (BoundingBox) sphere.getBound(); Vector3f min = bb.getMin(null); float[] ext = new float[]{bb.getXExtent() * 2, bb.getYExtent() * 2, bb.getZExtent() * 2}; - //we need to change the UV coordinates (the sphere is assumet to be inside the 3D image box) + //we need to change the UV coordinates (the sphere is assumed to be inside the 3D image box) sphere.clearBuffer(Type.TexCoord); VertexBuffer vb = sphere.getBuffer(Type.Position); FloatBuffer fb = (FloatBuffer) vb.getData(); @@ -112,10 +112,10 @@ public void simpleInitApp() { } /** - * This method creates a RGB8 texture with the sizes of 10x10x10 pixels. + * This method creates an RGB8 texture with the sizes of 10x10x10 pixels. */ private Texture getTexture() throws IOException { - ArrayList data = new ArrayList(1); + ArrayList data = new ArrayList<>(1); ByteBuffer bb = BufferUtils.createByteBuffer(10 * 10 * 10 * 3);//all data must be inside one buffer for (int i = 0; i < 10; ++i) { for (int j = 0; j < 10 * 10; ++j) { diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java b/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java index 4e33cbc095..ada3f1fffe 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestTexture3DLoading.java @@ -68,7 +68,7 @@ public void simpleInitApp() { q.scaleTextureCoordinates(new Vector2f(rows, rows)); - //The image only have 8 pictures and we have 16 thumbs, the data will be interpolated by the GPU + // The image has only 8 pictures, and we have 16 thumbs, so the data will be interpolated by the GPU. material.setFloat("InvDepth", 1f / 16f); material.setInt("Rows", rows); material.setTexture("Texture", t); diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java b/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java index 1757c021bd..ba56b3ff2a 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestTextureArray.java @@ -33,7 +33,7 @@ public void simpleInitApp() Texture tex1 = assetManager.loadTexture( "Textures/Terrain/Pond/Pond.jpg"); Texture tex2 = assetManager.loadTexture("Textures/Terrain/Rock2/rock.jpg"); - List images = new ArrayList(); + List images = new ArrayList<>(); images.add(tex1.getImage()); images.add(tex2.getImage()); TextureArray tex3 = new TextureArray(images); @@ -76,7 +76,7 @@ public void simpleInitApp() } /** - * @param args + * @param args ignored */ public static void main(String[] args) { diff --git a/jme3-examples/src/main/java/jme3test/texture/TestTextureArrayCompressed.java b/jme3-examples/src/main/java/jme3test/texture/TestTextureArrayCompressed.java index 738f4b94c2..0af064f557 100644 --- a/jme3-examples/src/main/java/jme3test/texture/TestTextureArrayCompressed.java +++ b/jme3-examples/src/main/java/jme3test/texture/TestTextureArrayCompressed.java @@ -33,7 +33,7 @@ public void simpleInitApp() Texture tex1 = assetManager.loadTexture( "Textures/Terrain/Pond/Pond_dxt5.dds"); Texture tex2 = assetManager.loadTexture("Textures/Terrain/BrickWall/BrickWall_dxt5.dds"); - List images = new ArrayList(); + List images = new ArrayList<>(); images.add(tex1.getImage()); images.add(tex2.getImage()); TextureArray tex3 = new TextureArray(images); @@ -76,7 +76,7 @@ public void simpleInitApp() } /** - * @param args + * @param args ignored */ public static void main(String[] args) { diff --git a/jme3-examples/src/main/java/jme3test/texture/dds/TestLoadDds.java b/jme3-examples/src/main/java/jme3test/texture/dds/TestLoadDds.java new file mode 100644 index 0000000000..bcf4b4d8b1 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/dds/TestLoadDds.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2009-2021 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.texture.dds; + +import com.jme3.app.SimpleApplication; +import com.jme3.asset.TextureKey; +import com.jme3.math.ColorRGBA; +import com.jme3.renderer.RenderManager; +import com.jme3.texture.Texture2D; +import com.jme3.texture.plugins.DDSLoader; +import com.jme3.ui.Picture; + +/** + * Test various supported BC* textures in DDS file format + * + * @author Toni Helenius + */ +public class TestLoadDds extends SimpleApplication { + + public static void main(String[] args) { + TestLoadDds app = new TestLoadDds(); + //app.setShowSettings(false); + app.start(); + } + + @Override + public void simpleInitApp() { + viewPort.setBackgroundColor(ColorRGBA.DarkGray); + assetManager.registerLoader(DDSLoader.class, "dds"); + + loadTexture(0, "Textures/dds/Monkey_PNG_BC7_1.DDS", "BC7"); + loadTexture(1, "Textures/dds/Monkey_PNG_BC6H_3.DDS", "BC6"); + loadTexture(2, "Textures/dds/Monkey_PNG_BC6H_SF_2.DDS", "BC6_SF"); + loadTexture(3, "Textures/dds/Monkey_PNG_BC5_S_6.DDS", "BC5_S"); + loadTexture(4, "Textures/dds/Monkey_PNG_BC5_7.DDS", "BC5"); + loadTexture(5, "Textures/dds/Monkey_PNG_BC4_S_8.DDS", "BC4_S"); + loadTexture(6, "Textures/dds/Monkey_PNG_BC4_9.DDS", "BC4"); + loadTexture(7, "Textures/dds/Monkey_PNG_BC3_10.DDS", "BC3"); + loadTexture(8, "Textures/dds/Monkey_PNG_BC2_11.DDS", "BC2"); + loadTexture(9, "Textures/dds/Monkey_PNG_BC1_12.DDS", "BC1"); + + flyCam.setDragToRotate(true); + + } + + private void loadTexture(int index, String texture, String description) { + Texture2D t = (Texture2D)assetManager.loadTexture(new TextureKey(texture, false)); + Picture p = new Picture(description, true); + p.setTexture(assetManager, t, false); + p.setLocalTranslation((index % 4) * 200, Math.floorDiv(index, 4) * 200, 0); + p.setWidth(200); + p.setHeight(200); + guiNode.attachChild(p); + } + + + @Override + public void simpleUpdate(float tpf) { + //TODO: add update code + } + + @Override + public void simpleRender(RenderManager rm) { + //TODO: add render code + } +} diff --git a/jme3-examples/src/main/java/jme3test/texture/ktx/package-info.java b/jme3-examples/src/main/java/jme3test/texture/ktx/package-info.java new file mode 100644 index 0000000000..65f87771b4 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/ktx/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for textures in KTX (Khronos Texture) + * format + */ +package jme3test.texture.ktx; diff --git a/jme3-examples/src/main/java/jme3test/texture/package-info.java b/jme3-examples/src/main/java/jme3test/texture/package-info.java new file mode 100644 index 0000000000..64d258c788 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/texture/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for textures + */ +package jme3test.texture; diff --git a/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java b/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java index 8e90d181a5..2c54a5bdc9 100644 --- a/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java +++ b/jme3-examples/src/main/java/jme3test/tools/TestSaveGame.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,29 +49,30 @@ public static void main(String[] args) { public void simpleUpdate(float tpf) { } + @Override public void simpleInitApp() { - //node that is used to store player data + // Create a Node to store player data. Node myPlayer = new Node(); myPlayer.setName("PlayerNode"); myPlayer.setUserData("name", "Mario"); myPlayer.setUserData("health", 100.0f); myPlayer.setUserData("points", 0); - //the actual model would be attached to this node - Spatial model = (Spatial) assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + // Attach the model to the Node. + Spatial model = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); myPlayer.attachChild(model); - //before saving the game, the model should be detached so it's not saved along with the node + // Before saving the game, detach the model since it doesn't need to be saved. myPlayer.detachAllChildren(); SaveGame.saveGame("mycompany/mygame", "savegame_001", myPlayer); - //later the game is loaded again + // Later, the game is loaded again ... Node player = (Node) SaveGame.loadGame("mycompany/mygame", "savegame_001"); player.attachChild(model); rootNode.attachChild(player); - //and the data is available + // and the player data are available. System.out.println("Name: " + player.getUserData("name")); System.out.println("Health: " + player.getUserData("health")); System.out.println("Points: " + player.getUserData("points")); @@ -79,6 +80,6 @@ public void simpleInitApp() { AmbientLight al = new AmbientLight(); rootNode.addLight(al); - //note you can also implement your own classes that implement the Savable interface. + // Note that your custom classes can also implement the Savable interface. } } diff --git a/jme3-examples/src/main/java/jme3test/tools/package-info.java b/jme3-examples/src/main/java/jme3test/tools/package-info.java new file mode 100644 index 0000000000..db76f10763 --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/tools/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for miscellaneous tools + */ +package jme3test.tools; diff --git a/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java index 9e9caf9d0a..2784d59fa1 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java +++ b/jme3-examples/src/main/java/jme3test/water/TestMultiPostWater.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2021 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.water; import com.jme3.app.SimpleApplication; @@ -27,25 +58,19 @@ /** * test - * + * * @author normenhansen */ public class TestMultiPostWater extends SimpleApplication { - private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); - private WaterFilter water; - private TerrainQuad terrain; - private Material matRock; - private static float WATER_HEIGHT = 90; + final private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + final private static float WATER_HEIGHT = 90; public static void main(String[] args) { TestMultiPostWater app = new TestMultiPostWater(); AppSettings s = new AppSettings(true); s.setRenderer(AppSettings.LWJGL_OPENGL2); s.setAudioRenderer(AppSettings.LWJGL_OPENAL); -// -// s.setRenderer("JOGL"); -// s.setAudioRenderer("JOAL"); app.setSettings(s); app.start(); @@ -85,7 +110,7 @@ public void simpleInitApp() { FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - water = new WaterFilter(rootNode, lightDir); + WaterFilter water = new WaterFilter(rootNode, lightDir); water.setCenter(new Vector3f(9.628218f, -15.830074f, 199.23595f)); water.setRadius(260); water.setWaveScale(0.003f); @@ -136,7 +161,8 @@ public void simpleInitApp() { } private void createTerrain(Node rootNode) { - matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + Material matRock = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); matRock.setBoolean("useTriPlanarMapping", false); matRock.setBoolean("WardIso", true); matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); @@ -170,8 +196,9 @@ private void createTerrain(Node rootNode) { } catch (Exception e) { e.printStackTrace(); } - terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + List cameras = new ArrayList<>(); cameras.add(getCamera()); terrain.setMaterial(matRock); terrain.setLocalScale(new Vector3f(5, 5, 5)); diff --git a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java index 3d650c170b..7f6117bc0c 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestPostWater.java +++ b/jme3-examples/src/main/java/jme3test/water/TestPostWater.java @@ -1,18 +1,49 @@ +/* + * Copyright (c) 2009-2025 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.water; import com.jme3.app.SimpleApplication; import com.jme3.audio.AudioData.DataType; import com.jme3.audio.AudioNode; +import com.jme3.audio.Filter; import com.jme3.audio.LowPassFilter; -import com.jme3.effect.ParticleEmitter; -import com.jme3.effect.ParticleMesh; +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.RenderState.BlendMode; import com.jme3.math.ColorRGBA; import com.jme3.math.FastMath; import com.jme3.math.Quaternion; @@ -22,14 +53,12 @@ import com.jme3.post.filters.DepthOfFieldFilter; import com.jme3.post.filters.FXAAFilter; import com.jme3.post.filters.LightScatteringFilter; -import com.jme3.renderer.Camera; -import com.jme3.renderer.queue.RenderQueue.Bucket; import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Box; +import com.jme3.terrain.geomipmap.TerrainLodControl; import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; import com.jme3.terrain.heightmap.AbstractHeightMap; import com.jme3.terrain.heightmap.ImageBasedHeightMap; import com.jme3.texture.Texture; @@ -38,24 +67,14 @@ import com.jme3.util.SkyFactory; import com.jme3.util.SkyFactory.EnvMapType; import com.jme3.water.WaterFilter; -import java.util.ArrayList; -import java.util.List; /** - * test - * * @author normenhansen */ public class TestPostWater extends SimpleApplication { - private Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + private final Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); private WaterFilter water; - TerrainQuad terrain; - Material matRock; - AudioNode waves; - LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f); - LowPassFilter underWaterReverbFilter = new LowPassFilter(0.5f, 0.1f); - LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1); public static void main(String[] args) { TestPostWater app = new TestPostWater(); @@ -65,220 +84,154 @@ public static void main(String[] args) { @Override public void simpleInitApp() { - setDisplayFps(false); - setDisplayStatView(false); - Node mainScene = new Node("Main Scene"); rootNode.attachChild(mainScene); + configureCamera(); + createSky(mainScene); createTerrain(mainScene); + createLights(mainScene); + createWaterFilter(); + setupPostFilters(); + addAudioClip(); + setupUI(); + registerInputMappings(); + } + + private void configureCamera() { + flyCam.setMoveSpeed(50f); + cam.setLocation(new Vector3f(-370.31592f, 182.04016f, 196.81192f)); + cam.setRotation(new Quaternion(0.015302252f, 0.9304095f, -0.039101653f, 0.3641086f)); + cam.setFrustumFar(2000); + } + + private void createLights(Node mainScene) { DirectionalLight sun = new DirectionalLight(); sun.setDirection(lightDir); - sun.setColor(ColorRGBA.White.clone().multLocal(1f)); mainScene.addLight(sun); - + AmbientLight al = new AmbientLight(); al.setColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)); mainScene.addLight(al); - - flyCam.setMoveSpeed(50); + } - //cam.setLocation(new Vector3f(-700, 100, 300)); - //cam.setRotation(new Quaternion().fromAngleAxis(0.5f, Vector3f.UNIT_Z)); -// cam.setLocation(new Vector3f(-327.21957f, 61.6459f, 126.884346f)); -// cam.setRotation(new Quaternion(0.052168474f, 0.9443102f, -0.18395276f, 0.2678024f)); + private void createSky(Node mainScene) { + Spatial sky = SkyFactory.createSky(assetManager, + "Scenes/Beach/FullskiesSunset0068.dds", EnvMapType.CubeMap); + sky.setShadowMode(ShadowMode.Off); + mainScene.attachChild(sky); + } + private void setupUI() { + setText(0, 50, "1 - Set Foam Texture to Foam.jpg"); + setText(0, 80, "2 - Set Foam Texture to Foam2.jpg"); + setText(0, 110, "3 - Set Foam Texture to Foam3.jpg"); + setText(0, 140, "4 - Turn Dry Filter under water On/Off"); + setText(0, 240, "PgUp - Larger Reflection Map"); + setText(0, 270, "PgDn - Smaller Reflection Map"); + } - cam.setLocation(new Vector3f(-370.31592f, 182.04016f, 196.81192f)); - cam.setRotation(new Quaternion(0.015302252f, 0.9304095f, -0.039101653f, 0.3641086f)); + private void setText(int x, int y, String text) { + BitmapText bmp = new BitmapText(guiFont); + bmp.setText(text); + bmp.setLocalTranslation(x, cam.getHeight() - y, 0); + bmp.setColor(ColorRGBA.Red); + guiNode.attachChild(bmp); + } + private void registerInputMappings() { + addMapping("foam1", new KeyTrigger(KeyInput.KEY_1)); + addMapping("foam2", new KeyTrigger(KeyInput.KEY_2)); + addMapping("foam3", new KeyTrigger(KeyInput.KEY_3)); + addMapping("dryFilter", new KeyTrigger(KeyInput.KEY_4)); + addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP)); + addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN)); + } + private void addMapping(String mappingName, Trigger... triggers) { + inputManager.addMapping(mappingName, triggers); + inputManager.addListener(actionListener, mappingName); + } + private final ActionListener actionListener = new ActionListener() { + @Override + public void onAction(String name, boolean isPressed, float tpf) { + if (!isPressed) return; - Spatial sky = SkyFactory.createSky(assetManager, - "Scenes/Beach/FullskiesSunset0068.dds", EnvMapType.CubeMap); - sky.setLocalScale(350); + if (name.equals("foam1")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg")); - mainScene.attachChild(sky); - cam.setFrustumFar(4000); + } else if (name.equals("foam2")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); - //Water Filter - water = new WaterFilter(rootNode, lightDir); - water.setWaterColor(new ColorRGBA().setAsSrgb(0.0078f, 0.3176f, 0.5f, 1.0f)); - water.setDeepWaterColor(new ColorRGBA().setAsSrgb(0.0039f, 0.00196f, 0.145f, 1.0f)); - water.setUnderWaterFogDistance(80); - water.setWaterTransparency(0.12f); - water.setFoamIntensity(0.4f); - water.setFoamHardness(0.3f); - water.setFoamExistence(new Vector3f(0.8f, 8f, 1f)); - water.setReflectionDisplace(50); - water.setRefractionConstant(0.25f); - water.setColorExtinction(new Vector3f(30, 50, 70)); - water.setCausticsIntensity(0.4f); - water.setWaveScale(0.003f); - water.setMaxAmplitude(2f); - water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); - water.setRefractionStrength(0.2f); - water.setWaterHeight(initialWaterHeight); - - //Bloom Filter - BloomFilter bloom = new BloomFilter(); + } else if (name.equals("foam3")) { + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg")); + + } else if (name.equals("upRM")) { + water.setReflectionMapSize(Math.min(water.getReflectionMapSize() * 2, 4096)); + System.out.println("Reflection map size : " + water.getReflectionMapSize()); + + } else if (name.equals("downRM")) { + water.setReflectionMapSize(Math.max(water.getReflectionMapSize() / 2, 32)); + System.out.println("Reflection map size : " + water.getReflectionMapSize()); + + } else if (name.equals("dryFilter")) { + useDryFilter = !useDryFilter; + } + } + }; + + private void setupPostFilters() { + BloomFilter bloom = new BloomFilter(); bloom.setExposurePower(55); bloom.setBloomIntensity(1.0f); - - //Light Scattering Filter + LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300)); - lsf.setLightDensity(0.5f); - - //Depth of field Filter + lsf.setLightDensity(0.5f); + DepthOfFieldFilter dof = new DepthOfFieldFilter(); dof.setFocusDistance(0); dof.setFocusRange(100); - + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); - fpp.addFilter(water); fpp.addFilter(bloom); fpp.addFilter(dof); fpp.addFilter(lsf); fpp.addFilter(new FXAAFilter()); - -// fpp.addFilter(new TranslucentBucketFilter()); + int numSamples = getContext().getSettings().getSamples(); if (numSamples > 0) { fpp.setNumSamples(numSamples); } - - - uw = cam.getLocation().y < waterHeight; - - waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", - DataType.Buffer); - waves.setLooping(true); - waves.setReverbEnabled(true); - if (uw) { - waves.setDryFilter(new LowPassFilter(0.5f, 0.1f)); - } else { - waves.setDryFilter(aboveWaterAudioFilter); - } - audioRenderer.playSource(waves); - // viewPort.addProcessor(fpp); - - inputManager.addListener(new ActionListener() { - public void onAction(String name, boolean isPressed, float tpf) { - if (isPressed) { - if (name.equals("foam1")) { - water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam.jpg")); - } - if (name.equals("foam2")) { - water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); - } - if (name.equals("foam3")) { - water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam3.jpg")); - } - - if (name.equals("upRM")) { - water.setReflectionMapSize(Math.min(water.getReflectionMapSize() * 2, 4096)); - System.out.println("Reflection map size : " + water.getReflectionMapSize()); - } - if (name.equals("downRM")) { - water.setReflectionMapSize(Math.max(water.getReflectionMapSize() / 2, 32)); - System.out.println("Reflection map size : " + water.getReflectionMapSize()); - } - } - } - }, "foam1", "foam2", "foam3", "upRM", "downRM"); - inputManager.addMapping("foam1", new KeyTrigger(KeyInput.KEY_1)); - inputManager.addMapping("foam2", new KeyTrigger(KeyInput.KEY_2)); - inputManager.addMapping("foam3", new KeyTrigger(KeyInput.KEY_3)); - inputManager.addMapping("upRM", new KeyTrigger(KeyInput.KEY_PGUP)); - inputManager.addMapping("downRM", new KeyTrigger(KeyInput.KEY_PGDN)); -// createBox(); -// createFire(); } - Geometry box; - - private void createBox() { - //creating a transluscent box - box = new Geometry("box", new Box(50, 50, 50)); - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.setColor("Color", new ColorRGBA(1.0f, 0, 0, 0.3f)); - mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - //mat.getAdditionalRenderState().setDepthWrite(false); - //mat.getAdditionalRenderState().setDepthTest(false); - box.setMaterial(mat); - box.setQueueBucket(Bucket.Translucent); - - - //creating a post view port -// ViewPort post=renderManager.createPostView("transpPost", cam); -// post.setClearFlags(false, true, true); - - - box.setLocalTranslation(-600, 0, 300); - - //attaching the box to the post viewport - //Don't forget to updateGeometricState() the box in the simpleUpdate - // post.attachScene(box); - rootNode.attachChild(box); + private void createWaterFilter() { + //Water Filter + water = new WaterFilter(rootNode, lightDir); + water.setWaterColor(new ColorRGBA().setAsSrgb(0.0078f, 0.3176f, 0.5f, 1.0f)); + water.setDeepWaterColor(new ColorRGBA().setAsSrgb(0.0039f, 0.00196f, 0.145f, 1.0f)); + water.setUnderWaterFogDistance(80); + water.setWaterTransparency(0.12f); + water.setFoamIntensity(0.4f); + water.setFoamHardness(0.3f); + water.setFoamExistence(new Vector3f(0.8f, 8f, 1f)); + water.setReflectionDisplace(50); + water.setRefractionConstant(0.25f); + water.setColorExtinction(new Vector3f(30, 50, 70)); + water.setCausticsIntensity(0.4f); + water.setWaveScale(0.003f); + water.setMaxAmplitude(2f); + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); + water.setRefractionStrength(0.2f); + water.setWaterHeight(initialWaterHeight); } - private void createFire() { - /** - * Uses Texture from jme3-test-data library! - */ - ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30); - Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); - mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); - - fire.setMaterial(mat_red); - fire.setImagesX(2); - fire.setImagesY(2); // 2x2 texture animation - fire.setEndColor(new ColorRGBA(1f, 0f, 0f, 1f)); // red - fire.setStartColor(new ColorRGBA(1f, 1f, 0f, 0.5f)); // yellow - fire.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 2, 0)); - fire.setStartSize(10f); - fire.setEndSize(1f); - fire.setGravity(0, 0, 0); - fire.setLowLife(0.5f); - fire.setHighLife(1.5f); - fire.getParticleInfluencer().setVelocityVariation(0.3f); - fire.setLocalTranslation(-600, 50, 300); - - fire.setQueueBucket(Bucket.Translucent); - rootNode.attachChild(fire); - } + private void createTerrain(Node mainScene) { + Material matRock = createTerrainMaterial(); - private void createTerrain(Node rootNode) { - matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); - matRock.setBoolean("useTriPlanarMapping", false); - matRock.setBoolean("WardIso", true); - matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); - Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); - grass.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap", grass); - matRock.setFloat("DiffuseMap_0_scale", 64); - Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); - dirt.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap_1", dirt); - matRock.setFloat("DiffuseMap_1_scale", 16); - Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); - rock.setWrap(WrapMode.Repeat); - matRock.setTexture("DiffuseMap_2", rock); - matRock.setFloat("DiffuseMap_2_scale", 128); - Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); - normalMap0.setWrap(WrapMode.Repeat); - Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); - normalMap1.setWrap(WrapMode.Repeat); - Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); - normalMap2.setWrap(WrapMode.Repeat); - matRock.setTexture("NormalMap", normalMap0); - matRock.setTexture("NormalMap_1", normalMap1); - matRock.setTexture("NormalMap_2", normalMap2); - AbstractHeightMap heightmap = null; try { heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); @@ -286,42 +239,101 @@ private void createTerrain(Node rootNode) { } catch (Exception e) { e.printStackTrace(); } - terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); - List cameras = new ArrayList(); - cameras.add(getCamera()); + + int patchSize = 64; + int totalSize = 512; + TerrainQuad terrain = new TerrainQuad("terrain", patchSize + 1, totalSize + 1, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); terrain.setMaterial(matRock); - terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); - terrain.setLocked(false); // unlock it so we can edit the height + terrain.setLocalScale(new Vector3f(5, 5, 5)); terrain.setShadowMode(ShadowMode.Receive); - rootNode.attachChild(terrain); + mainScene.attachChild(terrain); + } + + private Material createTerrainMaterial() { + Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + + setTexture("Textures/Terrain/splat/grass.jpg", matRock, "DiffuseMap"); + setTexture("Textures/Terrain/splat/dirt.jpg", matRock, "DiffuseMap_1"); + setTexture("Textures/Terrain/splat/road.jpg", matRock, "DiffuseMap_2"); + matRock.setFloat("DiffuseMap_0_scale", 64); + matRock.setFloat("DiffuseMap_1_scale", 16); + matRock.setFloat("DiffuseMap_2_scale", 128); + setTexture("Textures/Terrain/splat/grass_normal.jpg", matRock, "NormalMap"); + setTexture("Textures/Terrain/splat/dirt_normal.png", matRock, "NormalMap_1"); + setTexture("Textures/Terrain/splat/road_normal.png", matRock, "NormalMap_2"); + + return matRock; } - //This part is to emulate tides, slightly varrying the height of the water plane + + private void setTexture(String texture, Material mat, String param) { + Texture tex = assetManager.loadTexture(texture); + tex.setWrap(WrapMode.Repeat); + mat.setTexture(param, tex); + } + + // This part is to emulate tides, slightly varying the height of the water plane private float time = 0.0f; private float waterHeight = 0.0f; - private float initialWaterHeight = 90f;//0.8f; - private boolean uw = false; + private final float initialWaterHeight = 90f; + private boolean underWater = false; + + private AudioNode waves; + private final LowPassFilter aboveWaterAudioFilter = new LowPassFilter(1, 1); + private final LowPassFilter underWaterAudioFilter = new LowPassFilter(0.5f, 0.1f); + private boolean useDryFilter = true; @Override public void simpleUpdate(float tpf) { - super.simpleUpdate(tpf); - // box.updateGeometricState(); time += tpf; waterHeight = (float) Math.cos(((time * 0.6f) % FastMath.TWO_PI)) * 1.5f; water.setWaterHeight(initialWaterHeight + waterHeight); - if (water.isUnderWater() && !uw) { + underWater = water.isUnderWater(); + updateAudio(); + } + + private void addAudioClip() { + underWater = cam.getLocation().y < waterHeight; - waves.setDryFilter(new LowPassFilter(0.5f, 0.1f)); - uw = true; + waves = new AudioNode(assetManager, "Sound/Environment/Ocean Waves.ogg", DataType.Buffer); + waves.setLooping(true); + updateAudio(); + waves.play(); + } + + /** + * Update the audio settings (dry filter and reverb) + * based on boolean fields ({@code underWater} and {@code useDryFilter}). + */ + private void updateAudio() { + Filter newDryFilter; + if (!useDryFilter) { + newDryFilter = null; + } else if (underWater) { + newDryFilter = underWaterAudioFilter; + } else { + newDryFilter = aboveWaterAudioFilter; + } + Filter oldDryFilter = waves.getDryFilter(); + if (oldDryFilter != newDryFilter) { + System.out.println("dry filter : " + newDryFilter); + waves.setDryFilter(newDryFilter); } - if (!water.isUnderWater() && uw) { - uw = false; - //waves.setReverbEnabled(false); - waves.setDryFilter(new LowPassFilter(1, 1f)); - //waves.setDryFilter(new LowPassFilter(1,1f)); + boolean newReverbEnabled = !underWater; + boolean oldReverbEnabled = waves.isReverbEnabled(); + if (oldReverbEnabled != newReverbEnabled) { + System.out.println("reverb enabled : " + newReverbEnabled); + waves.setReverbEnabled(newReverbEnabled); } } } diff --git a/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java b/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java index d5a6df5f22..3ac2c47ba0 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java +++ b/jme3-examples/src/main/java/jme3test/water/TestPostWaterLake.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,7 @@ import com.jme3.app.SimpleApplication; import com.jme3.asset.plugins.HttpZipLocator; import com.jme3.asset.plugins.ZipLocator; +import com.jme3.input.KeyInput; import com.jme3.input.controls.ActionListener; import com.jme3.input.controls.KeyTrigger; import com.jme3.light.DirectionalLight; @@ -49,16 +50,18 @@ public class TestPostWaterLake extends SimpleApplication { private static boolean useHttp = false; - public static void main(String[] args) { - File file = new File("wildhouse.zip"); - if (!file.exists()) { - useHttp = true; - } + public static void main(String[] args) { TestPostWaterLake app = new TestPostWaterLake(); app.start(); } + @Override public void simpleInitApp() { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + this.flyCam.setMoveSpeed(10); cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f)); // cam.setRotation(new Quaternion(0.03f, 0.9f, 0f, 0.4f)); @@ -67,12 +70,7 @@ public void simpleInitApp() { rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap)); - - File file = new File("wildhouse.zip"); - if (file.exists()) { - useHttp = false; - } // create the geometry and attach it // load the level from zip or http zip if (useHttp) { @@ -112,6 +110,7 @@ public void simpleInitApp() { inputManager.addListener(new ActionListener() { + @Override public void onAction(String name, boolean isPressed, float tpf) { if(isPressed){ if(water.isUseHQShoreline()){ @@ -123,6 +122,6 @@ public void onAction(String name, boolean isPressed, float tpf) { } }, "HQ"); - inputManager.addMapping("HQ", new KeyTrigger(keyInput.KEY_SPACE)); + inputManager.addMapping("HQ", new KeyTrigger(KeyInput.KEY_SPACE)); } } diff --git a/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java b/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java index 4e0d8d6fc5..c0f29cff8d 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java +++ b/jme3-examples/src/main/java/jme3test/water/TestSceneWater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; -import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.RectangleMesh; import com.jme3.scene.shape.Sphere; import com.jme3.util.SkyFactory; import com.jme3.water.SimpleWaterProcessor; @@ -51,7 +51,7 @@ public class TestSceneWater extends SimpleApplication { // set default for applets - private static boolean useHttp = true; + private static boolean useHttp = false; public static void main(String[] args) { @@ -59,7 +59,13 @@ public static void main(String[] args) { app.start(); } + @Override public void simpleInitApp() { + File file = new File("wildhouse.zip"); + if (!file.exists()) { + useHttp = true; + } + this.flyCam.setMoveSpeed(10); Node mainScene=new Node(); cam.setLocation(new Vector3f(-27.0f, 1.0f, 75.0f)); @@ -70,11 +76,7 @@ public void simpleInitApp() { "Textures/Sky/Bright/BrightSky.dds", SkyFactory.EnvMapType.CubeMap)); - - File file = new File("wildhouse.zip"); - if (file.exists()) { - useHttp = false; - } + // create the geometry and attach it // load the level from zip or http zip if (useHttp) { @@ -123,16 +125,17 @@ public void simpleInitApp() { //lower the speed of the waves if they are too fast // waterProcessor.setWaveSpeed(0.01f); - Quad quad = new Quad(400,400); + RectangleMesh rect = new RectangleMesh( + new Vector3f(-200, -20, 250), + new Vector3f(200, -20, 250), + new Vector3f(-200, -20, -150)); //the texture coordinates define the general size of the waves - quad.scaleTextureCoordinates(new Vector2f(6f,6f)); + rect.scaleTextureCoordinates(new Vector2f(6f, 6f)); - Geometry water=new Geometry("water", quad); + Geometry water = new Geometry("water", rect); water.setShadowMode(ShadowMode.Receive); - water.setLocalRotation(new Quaternion().fromAngleAxis(-FastMath.HALF_PI, Vector3f.UNIT_X)); water.setMaterial(waterProcessor.getMaterial()); - water.setLocalTranslation(-200, -20, 250); rootNode.attachChild(water); diff --git a/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java b/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java index c8ba06fed0..5bc6604a91 100644 --- a/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java +++ b/jme3-examples/src/main/java/jme3test/water/TestSimpleWater.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,13 +52,13 @@ */ public class TestSimpleWater extends SimpleApplication implements ActionListener { - Material mat; - Spatial waterPlane; - Geometry lightSphere; - SimpleWaterProcessor waterProcessor; - Node sceneNode; - boolean useWater = true; - private Vector3f lightPos = new Vector3f(33,12,-29); + private Material mat; + private Spatial waterPlane; + private Geometry lightSphere; + private SimpleWaterProcessor waterProcessor; + private Node sceneNode; + private boolean useWater = true; + final private Vector3f lightPos = new Vector3f(33,12,-29); public static void main(String[] args) { @@ -81,7 +81,7 @@ public void simpleInitApp() { //create water quad //waterPlane = waterProcessor.createWaterGeometry(100, 100); - waterPlane=(Spatial) assetManager.loadModel("Models/WaterTest/WaterTest.mesh.xml"); + waterPlane = assetManager.loadModel("Models/WaterTest/WaterTest.mesh.xml"); waterPlane.setMaterial(waterProcessor.getMaterial()); waterPlane.setLocalScale(40); waterPlane.setLocalTranslation(-5, 0, 5); @@ -142,6 +142,7 @@ public void simpleUpdate(float tpf) { waterProcessor.setLightPosition(lightPos); } + @Override public void onAction(String name, boolean value, float tpf) { if (name.equals("use_water") && value) { if (!useWater) { diff --git a/jme3-examples/src/main/java/jme3test/water/WaterUI.java b/jme3-examples/src/main/java/jme3test/water/WaterUI.java index c09732089c..f2713d885c 100644 --- a/jme3-examples/src/main/java/jme3test/water/WaterUI.java +++ b/jme3-examples/src/main/java/jme3test/water/WaterUI.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ public WaterUI(InputManager inputManager, SimpleWaterProcessor proc) { processor=proc; - System.out.println("----------------- SSAO UI Debugger --------------------"); + System.out.println("----------------- Water UI Debugger --------------------"); System.out.println("-- Water transparency : press Y to increase, H to decrease"); System.out.println("-- Water depth : press U to increase, J to decrease"); // System.out.println("-- AO scale : press I to increase, K to decrease"); @@ -92,6 +92,7 @@ public WaterUI(InputManager inputManager, SimpleWaterProcessor proc) { AnalogListener anl = new AnalogListener() { + @Override public void onAnalog(String name, float value, float tpf) { if (name.equals("transparencyUp")) { processor.setWaterTransparency(processor.getWaterTransparency()+0.001f); diff --git a/jme3-examples/src/main/java/jme3test/water/package-info.java b/jme3-examples/src/main/java/jme3test/water/package-info.java new file mode 100644 index 0000000000..8885d8452b --- /dev/null +++ b/jme3-examples/src/main/java/jme3test/water/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * example apps and non-automated tests for water effects + */ +package jme3test.water; 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-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.frag b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.frag new file mode 100644 index 0000000000..a887e2dc42 --- /dev/null +++ b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.frag @@ -0,0 +1,14 @@ +layout (location = 0) out vec4 Red; +layout (location = 1) out vec4 Green; +layout (location = 2) out vec4 Blue; +layout (location = 3) out vec4 Merged; +uniform vec4 m_Albedo; + +void main() { + Red.r = m_Albedo.r; + Green.r = m_Albedo.g; + Blue.r = m_Albedo.b; + + // This is cheating, typically you would use the red, green and blue as a texture and render/combine them with another shader. + Merged = m_Albedo; +} diff --git a/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.j3md b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.j3md new file mode 100644 index 0000000000..a107a2d6da --- /dev/null +++ b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.j3md @@ -0,0 +1,22 @@ +// Defines a custom unshaded material that outputs 4 textures: +// + red channel only to location 0, +// + green channel only to location 1, +// + blue channel only to location 2, and +// + merged RGB to location 3. +// +// This is used in the "TestMultiRenderTarget" example app. + +MaterialDef ExtractRGB { + MaterialParameters { + Color Albedo + } + + Technique { + VertexShader GLSL100 : TestMRT/MatDefs/ExtractRGB.vert + FragmentShader GLSL330 : TestMRT/MatDefs/ExtractRGB.frag + + WorldParameters { + WorldViewProjectionMatrix + } + } +} diff --git a/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.vert b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.vert new file mode 100644 index 0000000000..3ebecfeda3 --- /dev/null +++ b/jme3-examples/src/main/resources/TestMRT/MatDefs/ExtractRGB.vert @@ -0,0 +1,7 @@ +#import "Common/ShaderLib/Instancing.glsllib" + +in vec3 inPosition; + +void main() { + gl_Position = TransformWorldViewProjection(vec4(inPosition, 1.0)); +} diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.bin b/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.bin new file mode 100644 index 0000000000..ee6412805c Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.bin differ diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.gltf b/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.gltf new file mode 100644 index 0000000000..8d1d6f49c0 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/gltfnaming/multi.gltf @@ -0,0 +1,202 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.27", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Material.001", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.8000000715255737, + 0.005118918139487505, + 0.12912100553512573, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"CubeMesh", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + }, + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":7, + "material":1 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":20, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + 0 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":20, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":20, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":30, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":20, + "max":[ + 1, + 1, + 0 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":20, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":20, + "type":"VEC2" + }, + { + "bufferView":7, + "componentType":5123, + "count":30, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":240, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":240, + "byteOffset":240, + "target":34962 + }, + { + "buffer":0, + "byteLength":160, + "byteOffset":480, + "target":34962 + }, + { + "buffer":0, + "byteLength":60, + "byteOffset":640, + "target":34963 + }, + { + "buffer":0, + "byteLength":240, + "byteOffset":700, + "target":34962 + }, + { + "buffer":0, + "byteLength":240, + "byteOffset":940, + "target":34962 + }, + { + "buffer":0, + "byteLength":160, + "byteOffset":1180, + "target":34962 + }, + { + "buffer":0, + "byteLength":60, + "byteOffset":1340, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":1400, + "uri":"multi.bin" + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.bin b/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.bin new file mode 100644 index 0000000000..883d154413 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.bin differ diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.gltf b/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.gltf new file mode 100644 index 0000000000..c809c3a759 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/gltfnaming/parent.gltf @@ -0,0 +1,193 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.27", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 1 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube2", + "translation":[ + 1.2840238809585571, + -2.180809736251831, + -0.21893274784088135 + ] + }, + { + "children":[ + 0 + ], + "mesh":1, + "name":"CubeParent" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"CubeMesh2", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + }, + { + "name":"CubeMesh", + "primitives":[ + { + "attributes":{ + "POSITION":4, + "NORMAL":5, + "TEXCOORD_0":6 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + }, + { + "bufferView":4, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":5, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":24, + "type":"VEC2" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":576, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":840, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":1128, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":1416, + "target":34962 + } + ], + "buffers":[ + { + "byteLength":1608, + "uri":"parent.bin" + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/single.bin b/jme3-examples/src/main/resources/jme3test/gltfnaming/single.bin new file mode 100644 index 0000000000..c59221c4c9 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/gltfnaming/single.bin differ diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/single.gltf b/jme3-examples/src/main/resources/jme3test/gltfnaming/single.gltf new file mode 100644 index 0000000000..7ce125f480 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/gltfnaming/single.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.27", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"CubeMesh", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":576, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"single.bin" + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.bin b/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.bin new file mode 100644 index 0000000000..c59221c4c9 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.bin differ diff --git a/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.gltf b/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.gltf new file mode 100644 index 0000000000..fc7a539e3f --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/gltfnaming/untitled.gltf @@ -0,0 +1,121 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v3.6.27", + "version":"2.0" + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Cube" + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Material", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + } + ], + "meshes":[ + { + "name":"CubeMesh", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2 + }, + "indices":3, + "material":0 + } + ] + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":24, + "max":[ + 1, + 1, + 1 + ], + "min":[ + -1, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":24, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":24, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5123, + "count":36, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":288, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":288, + "byteOffset":288, + "target":34962 + }, + { + "buffer":0, + "byteLength":192, + "byteOffset":576, + "target":34962 + }, + { + "buffer":0, + "byteLength":72, + "byteOffset":768, + "target":34963 + } + ], + "buffers":[ + { + "byteLength":840, + "uri":"untitled.bin" + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/gltfvertexcolor/VertexColorTest.glb b/jme3-examples/src/main/resources/jme3test/gltfvertexcolor/VertexColorTest.glb new file mode 100644 index 0000000000..2bb5e14d57 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/gltfvertexcolor/VertexColorTest.glb differ diff --git a/jme3-examples/src/main/resources/jme3test/materials/TestIssue37.j3md b/jme3-examples/src/main/resources/jme3test/materials/TestIssue37.j3md new file mode 100644 index 0000000000..7827bbea96 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/materials/TestIssue37.j3md @@ -0,0 +1,41 @@ +MaterialDef TestIssue37 { + MaterialParameters { + // For instancing + Boolean UseInstancing + + Texture2D ColorMap0 + Texture2D ColorMap1 + Texture2D ColorMap2 + Texture2D ColorMap3 + Texture2D ColorMap4 + Texture2D ColorMap5 + Texture2D ColorMap6 + Texture2D ColorMap7 + Texture2D ColorMap8 + Texture2D ColorMap9 + Texture2D ColorMap10 + Texture2D ColorMap11 + Texture2D ColorMap12 + Texture2D ColorMap13 + Texture2D ColorMap14 + Texture2D ColorMap15 + Texture2D ColorMap16 + Texture2D ColorMap17 + } + + Technique { + VertexShader GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.vert + FragmentShader GLSL150 GLSL100: Common/MatDefs/Misc/ShowNormals.frag + + WorldParameters { + WorldViewProjectionMatrix + ViewProjectionMatrix + ViewMatrix + ProjectionMatrix + } + + Defines { + INSTANCING : UseInstancing + } + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/morph/MorphStressTest.glb b/jme3-examples/src/main/resources/jme3test/morph/MorphStressTest.glb new file mode 100644 index 0000000000..bb892cc648 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/morph/MorphStressTest.glb differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest.gltf b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest.gltf new file mode 100644 index 0000000000..fc3d35c8d0 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest.gltf @@ -0,0 +1,203 @@ +{ + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5123, + "count" : 15720, + "max" : [ + 2769 + ], + "min" : [ + 0 + ], + "type" : "SCALAR" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 2770, + "max" : [ + 1.4138822555541992, + 1.0467524528503418, + 0.08050008118152618 + ], + "min" : [ + -1.4246257543563843, + -1.2000000476837158, + -0.009999947622418404 + ], + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 2770, + "max" : [ + 0.9247413277626038, + 0.9247413277626038, + 1.0 + ], + "min" : [ + -0.9247413277626038, + -0.9247413277626038, + -1.0 + ], + "type" : "VEC3" + }, + { + "bufferView" : 3, + "componentType" : 5126, + "count" : 2770, + "max" : [ + 1.0, + 0.8718903064727783, + 0.9247627258300781, + 1.0 + ], + "min" : [ + -1.0, + -0.8827678561210632, + -0.9247735142707825, + -1.0 + ], + "type" : "VEC4" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 2770, + "max" : [ + 0.9821357727050781, + 0.9877346605062485 + ], + "min" : [ + 0.1472877860069275, + 0.03816509246826172 + ], + "type" : "VEC2" + } + ], + "asset" : { + "copyright" : "Copyright 2017-2018 Analytical Graphics, Inc., CC-BY 4.0 https://creativecommons.org/licenses/by/4.0/ - Mesh and textures by Ed Mackey.", + "generator" : "Khronos Blender glTF 2.0 exporter", + "version" : "2.0" + }, + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 31440, + "byteOffset" : 0, + "target" : 34963 + }, + { + "buffer" : 0, + "byteLength" : 33240, + "byteOffset" : 31440, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 33240, + "byteOffset" : 64680, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 44320, + "byteOffset" : 97920, + "target" : 34962 + }, + { + "buffer" : 0, + "byteLength" : 22160, + "byteOffset" : 142240, + "target" : 34962 + } + ], + "buffers" : [ + { + "byteLength" : 164400, + "uri" : "data:application/octet-stream;base64,AAABAAIAAAADAAEABAAFAAYABAAHAAUACAAJAAoACAALAAkADAANAA4ADAAPAA0AEAARABIAEAATABEAFAAVABYAFAAXABUAGAAZABoAGAAbABkAHAAdAB4AHAAfAB0AIAAhACIAIAAjACEAJAAlACYAJAAnACUAKAApACoAKAArACkAKwAsACkAKwAtACwALQAuACwALQAvAC4ALwAwAC4ALwAxADAAMQAyADAAMQAzADIAMwA0ADIAMwA1ADQANQA2ADQANQA3ADYAKgA4ACgAOQA3ADUAOQA6ADcAKAA4ADsAOwArACgAPAArADsAPAAtACsAPAA9AC0APQAvAC0APQA+AC8APgAxAC8APgA/ADEAPwAzADEAPwBAADMAQAA1ADMAQAA5ADUAQQA/AD4AQQBCAD8AQgBAAD8AQgBDAEAAQwA5AEAAQwBEADkARAA6ADkARABFADoAOwA4AEYARgA8ADsARwA8AEYARwA9ADwASAA9AEcASAA+AD0ASABBAD4ASQBHAEYASgBHAEkASgBIAEcASwBIAEoASwBBAEgATABBAEsATABCAEEATABNAEIATQBDAEIATQBOAEMATgBEAEMATgBPAEQATwBFAEQATwBQAEUARgA4AEkAUQBOAE0AUQBSAE4AUgBPAE4AUgBTAE8AUwBQAE8AUwBUAFAASQA4AFUAVQBKAEkAVgBKAFUAVgBLAEoAVwBLAFYAVwBMAEsAWABMAFcAWABNAEwAUQBNAFgAWQBWAFUAWgBWAFkAWgBXAFYAWwBXAFoAWwBYAFcAXABYAFsAXABRAFgAXQBRAFwAXQBSAFEAXgBSAF0AXgBTAFIAXgBfAFMAXwBUAFMAXwBgAFQAVQA4AFkAYQBeAF0AYgBeAGEAYgBfAF4AYgBjAF8AYwBgAF8AYwBkAGAAWQA4AGUAZQBaAFkAZgBaAGUAZgBbAFoAZwBbAGYAZwBcAFsAaABcAGcAaABdAFwAYQBdAGgAaQBnAGYAagBnAGkAagBoAGcAawBoAGoAawBhAGgAbABhAGsAbABiAGEAbQBiAGwAbQBjAGIAbgBjAG0AbgBkAGMAbwBkAG4AZQA4AHAAcABmAGUAaQBmAHAAcQBtAHIAcQBuAG0AcwBuAHEAbwBuAHMAcAA4AHQAdQBwAHQAdQBpAHAAdgBpAHUAdgBqAGkAdwBqAHYAdwBrAGoAeABrAHcAeABsAGsAcgBsAHgAcgBtAGwAeQB1AHoAeQB2AHUAewB2AHkAewB3AHYAfAB3AHsAfAB4AHcAfQB4AHwAfQByAHgAfgByAH0AcQByAH4AfwBxAH4AcwBxAH8AdAA4AIAAegB0AIAAegB1AHQAgQB9AIIAfgB9AIEAgwB+AIEAfwB+AIMAgAA4AIQAhQCAAIQAhQB6AIAAhgB6AIUAhgB5AHoAhwB5AIYAhwB7AHkAiAB7AIcAiAB8AHsAggB8AIgAfQB8AIIAiQCGAIoAiQCHAIYAiwCHAIkAiwCIAIcAjACIAIsAggCIAIwAjQCCAIwAgQCCAI0AjgCBAI0AgwCBAI4AhAA4AI8AkACEAI8AkACFAIQAigCFAJAAigCGAIUAkQCNAJIAjgCNAJEAjwA4AJMAlACPAJMAlACQAI8AlQCQAJQAlQCKAJAAlgCKAJUAiQCKAJYAlwCJAJYAiwCJAJcAmACLAJcAjACLAJgAkgCMAJgAjQCMAJIAmQCWAJoAlwCWAJkAmwCXAJkAmACXAJsAnACYAJsAkgCYAJwAnQCSAJwAkQCSAJ0AkwA4AJ4AnwCTAJ4AnwCUAJMAoACUAJ8AlQCUAKAAmgCVAKAAlgCVAJoAngA4AKEAogCeAKEAnwCeAKIAowCfAKIAoACfAKMApACgAKMAmgCgAKQApQCaAKQAmQCaAKUApgCZAKUAmwCZAKYApwCbAKYAnACbAKcAqACcAKcAnQCcAKgApQCpAKoApQCkAKkApgCqAKsApgClAKoApwCrAKwApwCmAKsAqACsAK0AqACnAKwAoQA4AK4ArwChAK4AogChAK8AowCvALAAowCiAK8ApACwAKkApACjALAArwCxALIArwCuALEAsACyALMAsACvALIAqQCzALQAqQCwALMAqgC0ALUAqgCpALQAqwC1ALYAqwCqALUArAC2ALcArACrALYArQC3ALgArQCsALcArgA4ALEAtgC5ALoAtgC1ALkAtwC6ALsAtwC2ALoAuAC7ALwAuAC3ALsAsQA4AL0AsgC9AL4AsQC9ALIAswC+AL8AswCyAL4AtAC/AMAAtACzAL8AtQDAALkAtQC0AMAAvgDBAMIAvQDBAL4AvwDCAMMAvgDCAL8AwADDAMQAwAC/AMMAuQDEAMUAuQDAAMQAugDFAMYAugC5AMUAuwDGAMcAuwC6AMYAvADHAMgAvAC7AMcAvQA4AMEAxgDJAMoAxgDFAMkAxwDKAMsAxwDGAMoAyADLAMwAyADHAMsAwQA4AM0AwgDNAM4AwQDNAMIAwwDOAM8AwgDOAMMAxADPANAAwwDPAMQAxQDQAMkAxQDEANAAzwDRANIAzgDRAM8A0ADSANMAzwDSANAAyQDTANQA0ADTAMkAygDUANUAygDJANQAywDVANYAywDKANUAzADWANcAzADLANYAzQA4ANgAzgDYANEAzQDYAM4A1gDZANoA1gDVANkA1wDaANsA1wDWANoA2AA4ANwA0QDcAN0A2ADcANEA0gDdAN4A0QDdANIA0wDeAN8A0gDeANMA1ADfAOAA0wDfANQA1QDgANkA1ADgANUA3wDhAOIA3gDhAN8A4ADiAOMA3wDiAOAA2QDjAOQA4ADjANkA2gDkAOUA2QDkANoA2wDlAOYA2wDaAOUA3AA4AOcA3QDnAOgA3ADnAN0A3gDoAOEA3QDoAN4A5gDpAOoA5QDpAOYA5wA4AOsA6ADrAOwA5wDrAOgA4QDsAO0A6ADsAOEA4gDtAO4A4QDtAOIA4wDuAO8A4gDuAOMA5ADvAPAA4wDvAOQA5QDwAOkA5ADwAOUA7QDxAO4A7QDyAPEA7gDzAO8A7gDxAPMA7wD0APAA7wDzAPQA8AD1AOkA8AD0APUA6QD2AOoA9QD2AOkA6wA4APcA6wD4AOwA6wD3APgA7ADyAO0A7AD4APIA9QD5APYA+gD5APUA9wA4APsA9wD8APgA9wD7APwA+AD9APIA+AD8AP0A8gD+APEA8gD9AP4A8QD/APMA8QD+AP8A8wAAAfQA8wD/AAAB9AD6APUAAAH6APQA/gABAf8A/gACAQEB/wADAQAB/wABAQMBAAEEAfoAAwEEAQAB+gAFAfkABAEFAfoA+wA4AAYB+wAHAfwA+wAGAQcB/AAIAf0A/AAHAQgB/QACAf4A/QAIAQIBBgE4AAkBBgEKAQcBBgEJAQoBBwELAQgBBwEKAQsBCAEMAQIBCAELAQwBAgENAQEBDAENAQIBAQEOAQMBDQEOAQEBAwEPAQQBDgEPAQMBBAEQAQUBDwEQAQQBDAERAQ0BEgERAQwBDQETAQ4BEQETAQ0BDgEUAQ8BEwEUAQ4BDwEVARABFAEVAQ8BCQE4ABYBCQEXAQoBCQEWARcBCgEYAQsBCgEXARgBCwESAQwBCwEYARIBEwEZARQBGgEZARMBFAEbARUBGQEbARQBFgE4ABwBFgEdARcBFgEcAR0BFwEeARgBHQEeARcBGAEfARIBHgEfARgBEgEgAREBHwEgARIBEQEaARMBIAEaAREBHQEhAR4BIgEhAR0BHgEjAR8BIQEjAR4BHwEkASABIwEkAR8BIAElARoBJAElASABGgEmARkBJQEmARoBGQEnARsBJgEnARkBHAE4ACgBHAEiAR0BKAEiARwBJQE0ACYBMgA0ACUBJgE2ACcBNAA2ACYBKAE4ACoAKAEpACIBKgApACgBIgEsACEBKQAsACIBIQEuACMBLAAuACEBIwEwACQBLgAwACMBJAEyACUBMAAyACQByADMACkBKgErASwBKgEtASsBcwB/AC4BNgA3AC8BOgBFAC8B1wDbACkBvADIACkBFQEbATABgwCOAC4BUABUAC8BJwE2ADABEAEVATAB5gDqACkBnQCoAC4BzADXACkBkQCdAC4BrQC4ACkBYABkAC8B9gD5ADAB2wDmACkBqACtAC4BGwEnATABbwBzAC4BVABgAC8BNwA6AC8B6gD2ADABuAC8ACkBZABvAC8BBQEQATABfwCDAC4BRQBQAC8B+QAFATABjgCRAC4BKQEuAa0AKQHqADABMAE2AC8BLwFvAC4BMQEyATMBMQE0ATIBNAE1ATIBNAE2ATUBNgE3ATUBNgE4ATcBOAE5ATcBOAE6ATkBOgE7ATkBOgE8ATsBPAE9ATsBPAE+AT0BPgE/AT0BPgFAAT8BMwFBATEBQgFAAT4BQgFDAUABMQFBAUQBRAE0ATEBRAFFATQBRQE2ATQBRQFGATYBRgE4ATYBRgFHATgBRwE6ATgBRwFIAToBSAE8AToBSAFJATwBSQE+ATwBSQFCAT4BSgFIAUcBSgFLAUgBSwFJAUgBSwFMAUkBTAFCAUkBTAFNAUIBTQFDAUIBTQFOAUMBRAFBAU8BTwFFAUQBUAFFAU8BUAFGAUUBUQFGAVABUQFHAUYBUQFKAUcBUgFQAU8BUwFQAVIBUwFRAVABVAFRAVMBVAFKAVEBVQFKAVQBVQFLAUoBVQFWAUsBVgFMAUsBVgFXAUwBVwFNAUwBVwFYAU0BWAFOAU0BWAFZAU4BTwFBAVIBWgFXAVYBWgFbAVcBWwFYAVcBWwFcAVgBXAFZAVgBXAFdAVkBUgFBAV4BXgFTAVIBXwFTAV4BXwFUAVMBYAFUAV8BYAFVAVQBYQFVAWABYQFWAVUBWgFWAWEBYgFfAV4BYwFfAWIBYwFgAV8BZAFgAWMBZAFhAWABZQFhAWQBZQFaAWEBZgFaAWUBZgFbAVoBZwFbAWYBZwFcAVsBZwFoAVwBaAFdAVwBaAFpAV0BXgFBAWIBagFnAWYBawFnAWoBawFoAWcBawFsAWgBbAFpAWgBbAFtAWkBYgFBAW4BbgFjAWIBbwFjAW4BbwFkAWMBcAFkAW8BcAFlAWQBcQFlAXABcQFmAWUBagFmAXEBcgFwAW8BcwFwAXIBcwFxAXABdAFxAXMBdAFqAXEBdQFqAXQBdQFrAWoBdgFrAXUBdgFsAWsBdwFsAXYBdwFtAWwBeAFtAXcBbgFBAXkBeQFvAW4BcgFvAXkBegF2AXsBegF3AXYBfAF3AXoBeAF3AXwBeQFBAX0BfgF5AX0BfgFyAXkBfwFyAX4BfwFzAXIBgAFzAX8BgAF0AXMBgQF0AYABgQF1AXQBewF1AYEBewF2AXUBggF+AYMBggF/AX4BhAF/AYIBhAGAAX8BhQGAAYQBhQGBAYABhgGBAYUBhgF7AYEBhwF7AYYBhwF6AXsBiAF6AYcBfAF6AYgBfQFBAYkBgwF9AYkBgwF+AX0BigGGAYsBhwGGAYoBjAGHAYoBiAGHAYwBiQFBAY0BjgGJAY0BjgGDAYkBjwGDAY4BjwGCAYMBkAGCAY8BkAGEAYIBkQGEAZABkQGFAYQBiwGFAZEBhgGFAYsBkgGPAZMBkgGQAY8BlAGQAZIBlAGRAZABlQGRAZQBiwGRAZUBlgGLAZUBigGLAZYBlwGKAZYBjAGKAZcBjQFBAZgBmQGNAZgBmQGOAY0BkwGOAZkBkwGPAY4BmgGWAZsBlwGWAZoBmAFBAZwBnQGYAZwBnQGZAZgBngGZAZ0BngGTAZkBnwGTAZ4BnwGSAZMBoAGSAZ8BlAGSAaABoQGUAaABlQGUAaEBmwGVAaEBlgGVAZsBogGfAaMBoAGfAaIBpAGgAaIBoQGgAaQBpQGhAaQBmwGhAaUBpgGbAaUBmgGbAaYBnAFBAacBqAGcAacBqAGdAZwBqQGdAagBngGdAakBowGeAakBnwGeAaMBpwFBAaoBqwGnAaoBqAGnAasBrAGoAasBqQGoAawBrQGpAawBowGpAa0BrgGjAa0BogGjAa4BrwGiAa4BpAGiAa8BsAGkAa8BpQGkAbABsQGlAbABpgGlAbEBrgGyAbMBrgGtAbIBrwGzAbQBrwGuAbMBsAG0AbUBsAGvAbQBsQG1AbYBsQGwAbUBqgFBAbcBqwG3AbgBqwGqAbcBrAG4AbkBrAGrAbgBrQG5AbIBrQGsAbkBuAG6AbsBuAG3AboBuQG7AbwBuQG4AbsBsgG8Ab0BsgG5AbwBswG9Ab4BswGyAb0BtAG+Ab8BtAGzAb4BtQG/AcABtQG0Ab8BtgHAAcEBtgG1AcABtwFBAboBvwHCAcMBvwG+AcIBwAHDAcQBwAG/AcMBwQHEAcUBwQHAAcQBugFBAcYBuwHGAccBugHGAbsBvAHHAcgBvAG7AccBvQHIAckBvQG8AcgBvgHJAcIBvgG9AckBxwHKAcsBxgHKAccByAHLAcwByAHHAcsByQHMAc0ByQHIAcwBwgHNAc4BwgHJAc0BwwHOAc8BwwHCAc4BxAHPAdABxAHDAc8BxQHQAdEBxQHEAdABxgFBAcoBzwHSAdMBzwHOAdIB0AHTAdQB0AHPAdMB0QHUAdUB0QHQAdQBygFBAdYBywHWAdcBygHWAcsBzAHXAdgBywHXAcwBzQHYAdkBzAHYAc0BzgHZAdIBzgHNAdkB2AHaAdsB1wHaAdgB2QHbAdwB2AHbAdkB0gHcAd0B2QHcAdIB0wHdAd4B0wHSAd0B1AHeAd8B1AHTAd4B1QHfAeAB1QHUAd8B1gFBAeEB1wHhAdoB1gHhAdcB3wHiAeMB3wHeAeIB4AHjAeQB4AHfAeMB4QFBAeUB2gHlAeYB4QHlAdoB2wHmAecB2gHmAdsB3AHnAegB2wHnAdwB3QHoAekB3AHoAd0B3gHpAeIB3gHdAekB6AHqAesB5wHqAegB6QHrAewB6AHrAekB4gHsAe0B6QHsAeIB4wHtAe4B4wHiAe0B5AHuAe8B5AHjAe4B5QFBAfAB5gHwAfEB5QHwAeYB5wHxAeoB5gHxAecB7wHyAfMB7wHuAfIB8AFBAfQB8QH0AfUB8AH0AfEB6gH1AfYB8QH1AeoB6wH2AfcB6gH2AesB7AH3AfgB6wH3AewB7QH4AfkB7AH4Ae0B7gH5AfIB7QH5Ae4B9gH6AfcB9gH7AfoB9wH8AfgB9wH6AfwB+AH9AfkB+AH8Af0B+QH+AfIB+QH9Af4B8gH/AfMB/gH/AfIB9AFBAQAC9AEBAvUB9AEAAgEC9QH7AfYB9QEBAvsB/gECAv8BAwICAv4BAAJBAQQCAAIFAgECAAIEAgUCAQIGAvsBAQIFAgYC+wEHAvoB+wEGAgcC+gEIAvwB+gEHAggC/AEJAv0B/AEIAgkC/QEDAv4BCQIDAv0BBwIKAggCBwILAgoCCAIMAgkCCgIMAggCCQINAgMCDAINAgkCAwIOAgICDQIOAgMCBAJBAQ8CBAIQAgUCBAIPAhACBQIRAgYCBQIQAhECBgILAgcCBgIRAgsCDwJBARICDwITAhACDwISAhMCEAIUAhECEAITAhQCEQIVAgsCEQIUAhUCCwIWAgoCCwIVAhYCCgIXAgwCFgIXAgoCDAIYAg0CFwIYAgwCDQIZAg4CGAIZAg0CFQIaAhYCGwIaAhUCFgIcAhcCGgIcAhYCFwIdAhgCHAIdAhcCGAIeAhkCHQIeAhgCEgJBAR8CEgIgAhMCEgIfAiACEwIhAhQCEwIgAiECFAIbAhUCFAIhAhsCHAIiAh0CIwIiAhwCHQIkAh4CIgIkAh0CHwJBASUCHwImAiACHwIlAiYCIAInAiECJgInAiACIQIoAhsCJwIoAiECGwIpAhoCKAIpAhsCGgIjAhwCKQIjAhoCJgIqAicCKwIqAiYCJwIsAigCKgIsAicCKAItAikCLAItAigCKQIuAiMCLQIuAikCIwIvAiICLgIvAiMCIgIwAiQCLwIwAiICJQJBATECJQIrAiYCMQIrAiUCLgI9AS8COwE9AS4CLwI/ATACPQE/AS8CMQJBATMBMQIyASsCMwEyATECKwI1ASoCMgE1ASsCKgI3ASwCNQE3ASoCLAI5AS0CNwE5ASwCLQI7AS4COQE7AS0C0QHVATICMwI0AjUCMwI2AjQCfAGIATcCPwFAATgCQwFOATgC4AHkATICxQHRATICHgIkAjkCjAGXATcCWQFdATgCMAI/ATkCGQIeAjkC7wHzATICpgGxATcC1QHgATICmgGmATcCtgHBATICaQFtATgC/wECAjkC5AHvATICsQG2ATcCJAIwAjkCeAF8ATcCXQFpATgCQAFDATgC8wH/ATkCwQHFATICbQF4ATgCDgIZAjkCiAGMATcCTgFZATgCAgIOAjkClwGaATcCMgI3ArYBMgLzATkCOQI/ATgCOAJ4ATcCOgI7AjwCOgI9AjsCPQI+AjsCPQI/Aj4CPgJAAkECPwJAAj4CQQJCAkMCQAJCAkECQwJEAkUCQgJEAkMCRAJGAkUCRAJHAkYCRgJIAkkCRwJIAkYCPAJKAjoCSwJIAkcCSwJMAkgCOgJKAk0CTQI9AjoCTQJOAj0CTgI/Aj0CTgJPAj8CTwJAAj8CTwJQAkACUAJCAkACUAJRAkICUQJEAkICUQJSAkQCUgJHAkQCUgJLAkcCUwJRAlACUwJUAlECVAJSAlECVAJVAlICVQJLAlICVQJWAksCVgJMAksCVgJXAkwCTQJKAlgCWAJOAk0CWQJOAlgCWQJPAk4CWQJaAk8CWgJQAk8CWgJTAlACWwJZAlgCXAJZAlsCXAJaAlkCXQJaAlwCXQJTAloCXgJTAl0CXgJUAlMCXgJfAlQCXwJVAlQCXwJgAlUCYAJWAlUCYAJhAlYCYQJXAlYCYQJiAlcCWAJKAlsCYwJgAl8CYwJkAmACZAJhAmACZAJlAmECZQJiAmECZQJmAmICWwJKAmcCZwJcAlsCaAJcAmcCaAJdAlwCaQJdAmgCaQJeAl0CagJeAmkCagJfAl4CYwJfAmoCawJoAmcCbAJoAmsCbAJpAmgCbQJpAmwCbQJqAmkCbgJqAm0CbgJjAmoCbwJjAm4CbwJkAmMCcAJkAm8CcAJlAmQCcAJxAmUCcQJmAmUCcQJyAmYCZwJKAmsCcwJwAm8CdAJwAnMCdAJxAnACdAJ1AnECdQJyAnECdQJ2AnICawJKAncCdwJsAmsCeAJsAncCeAJtAmwCeQJtAngCeQJuAm0CegJuAnkCegJvAm4CcwJvAnoCewJ5AngCfAJ5AnsCfAJ6AnkCfQJ6AnwCfQJzAnoCfgJzAn0CfgJ0AnMCfwJ0An4CfwJ1AnQCgAJ1An8CgAJ2AnUCgQJ2AoACdwJKAoICggJ4AncCewJ4AoICgwJ/AoQCgwKAAn8ChQKAAoMCgQKAAoUCggJKAoYChwKCAoYChwJ7AoICiAJ7AocCiAJ8AnsCiQJ8AogCiQJ9AnwCigJ9AokCigJ+An0ChAJ+AooChAJ/An4CiwKHAowCiwKIAocCjQKIAosCjQKJAogCjgKJAo0CjgKKAokCjwKKAo4CjwKEAooCkAKEAo8CgwKEApACkQKDApAChQKDApEChgJKApICjAKGApICjAKHAoYCkwKPApQCkAKPApMClQKQApMCkQKQApUCkgJKApYClwKSApYClwKMApICmAKMApcCmAKLAowCmQKLApgCmQKNAosCmgKNApkCmgKOAo0ClAKOApoCjwKOApQCmwKYApwCmwKZApgCnQKZApsCnQKaApkCngKaAp0ClAKaAp4CnwKUAp4CkwKUAp8CoAKTAp8ClQKTAqAClgJKAqECogKWAqECogKXApYCnAKXAqICnAKYApcCowKfAqQCoAKfAqMCoQJKAqUCpgKhAqUCpgKiAqECpwKiAqYCpwKcAqICqAKcAqcCmwKcAqgCqQKbAqgCnQKbAqkCqgKdAqkCngKdAqoCpAKeAqoCnwKeAqQCqwKoAqwCqQKoAqsCrQKpAqsCqgKpAq0CrgKqAq0CpAKqAq4CrwKkAq4CowKkAq8CpQJKArACsQKlArACsQKmAqUCsgKmArECpwKmArICrAKnArICqAKnAqwCsAJKArMCtAKwArMCtAKxArACtQKxArQCsgKxArUCtgKyArUCrAKyArYCtwKsArYCqwKsArcCuAKrArcCrQKrArgCuQKtArgCrgKtArkCugKuArkCrwKuAroCtwK7ArwCtwK2ArsCvQK3ArwCuAK3Ar0CuQK9Ar4CuQK4Ar0CugK+Ar8CugK5Ar4CswJKAsACwQKzAsACtAKzAsECtQLBAsICtQK0AsECtgLCArsCtgK1AsICwQLDAsQCwQLAAsMCwgLEAsUCwgLBAsQCuwLFAsYCuwLCAsUCvALGAscCvAK7AsYCvQLHAsgCvQK8AscCvgLIAskCvgK9AsgCvwLJAsoCvwK+AskCwAJKAsMCyALLAswCyALHAssCyQLMAs0CyQLIAswCygLNAs4CygLJAs0CwwJKAs8CxALPAtACwwLPAsQCxQLQAtECxQLEAtACxgLRAtICxgLFAtECxwLSAssCxwLGAtIC0ALTAtQCzwLTAtAC0QLUAtUC0ALUAtEC0gLVAtYC0gLRAtUCywLWAtcCywLSAtYCzALXAtgCzALLAtcCzQLYAtkCzQLMAtgCzgLZAtoCzgLNAtkCzwJKAtMC2ALbAtwC2ALXAtsC2QLcAt0C2QLYAtwC2gLdAt4C2gLZAt0C0wJKAt8C1ALfAuAC0wLfAtQC1QLgAuEC1ALgAtUC1gLhAuIC1QLhAtYC1wLiAtsC1wLWAuIC4QLjAuQC4ALjAuEC4gLkAuUC4QLkAuIC2wLlAuYC4gLlAtsC3ALmAucC3ALbAuYC3QLnAugC3QLcAucC3gLoAukC3gLdAugC3wJKAuoC4ALqAuMC3wLqAuAC6ALrAuwC6ALnAusC6QLsAu0C6QLoAuwC6gJKAu4C4wLuAu8C6gLuAuMC5ALvAvAC4wLvAuQC5QLwAvEC5ALwAuUC5gLxAvIC5QLxAuYC5wLyAusC5gLyAucC8QLzAvQC8ALzAvEC8gL0AvUC8QL0AvIC6wL1AvYC8gL1AusC7AL2AvcC6wL2AuwC7QL3AvgC7QLsAvcC7gJKAvkC7wL5AvoC7gL5Au8C8AL6AvMC7wL6AvAC+AL7AvwC9wL7AvgC+QJKAv0C+gL9Av4C+QL9AvoC8wL+Av8C+gL+AvMC9AL/AgAD8wL/AvQC9QIAAwED9AIAA/UC9gIBAwID9QIBA/YC9wICA/sC9gICA/cC/wIDAwAD/wIEAwMDAAMFAwEDAAMDAwUDAQMGAwIDAQMFAwYDAgMHA/sCAgMGAwcD+wIIA/wCBwMIA/sC/QJKAgkD/QIKA/4C/QIJAwoD/gIEA/8C/gIKAwQDBwMLAwgDDAMLAwcDCQNKAg0DCQMOAwoDCQMNAw4DCgMPAwQDCgMOAw8DBAMQAwMDBAMPAxADAwMRAwUDAwMQAxEDBQMSAwYDBQMRAxIDBgMMAwcDEgMMAwYDEAMTAxEDEAMUAxMDEQMVAxIDEQMTAxUDEgMWAwwDFQMWAxIDDAMXAwsDFgMXAwwDDQNKAhgDDQMZAw4DDQMYAxkDDgMaAw8DDgMZAxoDDwMUAxADDwMaAxQDGANKAhsDGAMcAxkDGAMbAxwDGQMdAxoDGQMcAx0DGgMeAxQDGgMdAx4DFAMfAxMDHgMfAxQDEwMgAxUDHwMgAxMDFQMhAxYDIAMhAxUDFgMiAxcDIQMiAxYDHgMjAx8DJAMjAx4DHwMlAyADIwMlAx8DIAMmAyEDJQMmAyADIQMnAyIDJgMnAyEDGwNKAigDGwMpAxwDGwMoAykDHAMqAx0DHAMpAyoDHQMkAx4DHQMqAyQDJQMrAyYDLAMrAyUDJgMtAycDKwMtAyYDKANKAi4DKAMvAykDKAMuAy8DKQMwAyoDKQMvAzADKgMxAyQDMAMxAyoDJAMyAyMDMQMyAyQDIwMsAyUDMgMsAyMDLwMzAzADNAMzAy8DMAM1AzEDMwM1AzADMQM2AzIDNQM2AzEDMgM3AywDNgM3AzIDLAM4AysDNwM4AywDKwM5Ay0DOAM5AysDLgNKAjoDLgM0Ay8DOgM0Ay4DNwNGAjgDRQJGAjcDOANJAjkDRgJJAjgDOgNKAjwCOgM7AjQDPAI7AjoDNAM+AjMDOwI+AjQDMwNBAjUDPgJBAjMDNQNDAjYDQQJDAjUDNgNFAjcDQwJFAjYD2gLeAjsDPAM9Az4DPAM/Az0DhQKRAkADSQJIAkEDTAJXAkED6QLtAjsDzgLaAjsDJwMtA0IDlQKgAkADYgJmAkEDOQNJAkIDIgMnA0ID+AL8AjsDrwK6AkAD3gLpAjsDowKvAkADvwLKAjsDcgJ2AkEDCAMLA0ID7QL4AjsDugK/AkADLQM5A0IDgQKFAkADZgJyAkEDSAJMAkED/AIIA0IDygLOAjsDdgKBAkEDFwMiA0IDkQKVAkADVwJiAkEDCwMXA0IDoAKjAkADOwNAA78COwP8AkIDQgNJAkEDQQOBAkADQwNEA0UDQwNGA0QDRgNHA0QDRgNIA0cDSANJA0cDSANKA0kDSgNLA0kDSgNMA0sDTANNA0sDTANOA00DTgNPA00DTgNQA08DUANRA08DUANSA1EDRQNTA0MDVANSA1ADVANVA1IDQwNTA1YDVgNGA0MDVwNGA1YDVwNIA0YDVwNYA0gDWANKA0gDWANZA0oDWQNMA0oDWQNaA0wDWgNOA0wDWgNbA04DWwNQA04DWwNUA1ADXANaA1kDXANdA1oDXQNbA1oDXQNeA1sDXgNUA1sDXgNfA1QDXwNVA1QDXwNgA1UDVgNTA2EDYQNXA1YDYgNXA2EDYgNYA1cDYwNYA2IDYwNZA1gDYwNcA1kDZANiA2EDZQNiA2QDZQNjA2IDZgNjA2UDZgNcA2MDZwNcA2YDZwNdA1wDZwNoA10DaANeA10DaANpA14DaQNfA14DaQNqA18DagNgA18DagNrA2ADYQNTA2QDbANpA2gDbANtA2kDbQNqA2kDbQNuA2oDbgNrA2oDbgNvA2sDZANTA3ADcANlA2QDcQNlA3ADcQNmA2UDcgNmA3EDcgNnA2YDcwNnA3IDcwNoA2cDbANoA3MDdANxA3ADdQNxA3QDdQNyA3EDdgNyA3UDdgNzA3IDdwNzA3YDdwNsA3MDeANsA3cDeANtA2wDeQNtA3gDeQNuA20DeQN6A24DegNvA24DegN7A28DcANTA3QDfAN5A3gDfQN5A3wDfQN6A3kDfQN+A3oDfgN7A3oDfgN/A3sDdANTA4ADgAN1A3QDgQN1A4ADgQN2A3UDggN2A4EDggN3A3YDgwN3A4IDgwN4A3cDfAN4A4MDhAOCA4EDhQOCA4QDhQODA4IDhgODA4UDhgN8A4MDhwN8A4YDhwN9A3wDiAN9A4cDiAN+A30DiQN+A4gDiQN/A34DigN/A4kDgANTA4sDiwOBA4ADhAOBA4sDjAOIA40DjAOJA4gDjgOJA4wDigOJA44DiwNTA48DkAOLA48DkAOEA4sDkQOEA5ADkQOFA4QDkgOFA5EDkgOGA4UDkwOGA5IDkwOHA4YDjQOHA5MDjQOIA4cDlAOQA5UDlAORA5ADlgORA5QDlgOSA5EDlwOSA5YDlwOTA5IDmAOTA5cDmAONA5MDmQONA5gDjAONA5kDmgOMA5kDjgOMA5oDjwNTA5sDlQOPA5sDlQOQA48DnAOYA50DmQOYA5wDngOZA5wDmgOZA54DmwNTA58DoAObA58DoAOVA5sDoQOVA6ADoQOUA5UDogOUA6EDogOWA5QDowOWA6IDowOXA5YDnQOXA6MDnQOYA5cDpAOhA6UDpAOiA6EDpgOiA6QDpgOjA6IDpwOjA6YDnQOjA6cDqAOdA6cDnAOdA6gDqQOcA6gDngOcA6kDnwNTA6oDqwOfA6oDqwOgA58DpQOgA6sDpQOhA6ADrAOoA60DqQOoA6wDqgNTA64DrwOqA64DrwOrA6oDsAOrA68DsAOlA6sDsQOlA7ADsQOkA6UDsgOkA7EDpgOkA7IDswOmA7IDpwOmA7MDrQOnA7MDqAOnA60DtAOxA7UDsgOxA7QDtgOyA7QDswOyA7YDtwOzA7YDrQOzA7cDuAOtA7cDrAOtA7gDrgNTA7kDugOuA7kDugOvA64DuwOvA7oDsAOvA7sDtQOwA7sDsQOwA7UDuQNTA7wDvQO5A7wDugO5A70DvgO6A70DuwO6A74DvwO7A74DtQO7A78DwAO1A78DtAO1A8ADwQO0A8ADtgO0A8EDwgO2A8EDtwO2A8IDwwO3A8IDuAO3A8MDwAPEA8UDwAO/A8QDwQPFA8YDwQPAA8UDwgPGA8cDwgPBA8YDwwPHA8gDwwPCA8cDvANTA8kDvQPJA8oDvQO8A8kDvgPKA8sDvgO9A8oDvwPLA8QDvwO+A8sDygPMA80DygPJA8wDywPNA84DywPKA80DxAPOA88DxAPLA84DxQPPA9ADxQPEA88DxgPQA9EDxgPFA9ADxwPRA9IDxwPGA9EDyAPSA9MDyAPHA9IDyQNTA8wD0QPUA9UD0QPQA9QD0gPVA9YD0gPRA9UD0wPWA9cD0wPSA9YDzANTA9gDzQPYA9kDzQPMA9gDzgPZA9oDzgPNA9kDzwPaA9sDzwPOA9oD0APbA9QD0APPA9sD2QPcA90D2APcA9kD2gPdA94D2QPdA9oD2wPeA98D2wPaA94D1APfA+AD1APbA98D1QPgA+ED1QPUA+AD1gPhA+ID1gPVA+ED1wPiA+MD1wPWA+ID2ANTA9wD4QPkA+UD4QPgA+QD4gPlA+YD4gPhA+UD4wPmA+cD4wPiA+YD3ANTA+gD3QPoA+kD3APoA90D3gPpA+oD3QPpA94D3wPqA+sD3gPqA98D4APrA+QD4APfA+sD6gPsA+0D6QPsA+oD6wPtA+4D6gPtA+sD5APuA+8D6wPuA+QD5QPvA/AD5QPkA+8D5gPwA/ED5gPlA/AD5wPxA/ID5wPmA/ED6ANTA/MD6QPzA+wD6APzA+kD8QP0A/UD8QPwA/QD8gP1A/YD8gPxA/UD8wNTA/cD7AP3A/gD8wP3A+wD7QP4A/kD7AP4A+0D7gP5A/oD7QP5A+4D7wP6A/sD7gP6A+8D8AP7A/QD7wP7A/AD+gP8A/0D+QP8A/oD+wP9A/4D+gP9A/sD9AP+A/8D+wP+A/QD9QP/AwAE9AP/A/UD9gMABAEE9gP1AwAE9wNTAwIE+AMCBAME9wMCBPgD+QMDBPwD+AMDBPkDAQQEBAUEAAQEBAEEAgRTAwYEAwQGBAcEAgQGBAME/AMHBAgEAwQHBPwD/QMIBAkE/AMIBP0D/gMJBAoE/QMJBP4D/wMKBAsE/gMKBP8DAAQLBAQE/wMLBAAECAQMBAkECAQNBAwECQQOBAoECQQMBA4ECgQPBAsECgQOBA8ECwQQBAQECwQPBBAEBAQRBAUEEAQRBAQEBgRTAxIEBgQTBAcEBgQSBBMEBwQNBAgEBwQTBA0EEAQUBBEEFQQUBBAEEgRTAxYEEgQXBBMEEgQWBBcEEwQYBA0EEwQXBBgEDQQZBAwEDQQYBBkEDAQaBA4EDAQZBBoEDgQbBA8EDgQaBBsEDwQVBBAEGwQVBA8EGQQcBBoEGQQdBBwEGgQeBBsEGgQcBB4EGwQfBBUEHgQfBBsEFQQgBBQEHwQgBBUEFgRTAyEEFgQiBBcEFgQhBCIEFwQjBBgEFwQiBCMEGAQdBBkEGAQjBB0EIQRTAyQEIQQlBCIEIQQkBCUEIgQmBCMEIgQlBCYEIwQnBB0EIwQmBCcEHQQoBBwEHQQnBCgEHAQpBB4EKAQpBBwEHgQqBB8EKQQqBB4EHwQrBCAEKgQrBB8EJwQsBCgELQQsBCcEKAQuBCkELAQuBCgEKQQvBCoELgQvBCkEKgQwBCsELwQwBCoEJARTAzEEJAQyBCUEJAQxBDIEJQQzBCYEJQQyBDMEJgQtBCcEMwQtBCYELgQ0BC8ENQQ0BC4ELwQ2BDAENAQ2BC8EMQRTAzcEMQQ4BDIEMQQ3BDgEMgQ5BDMEOAQ5BDIEMwQ6BC0EOQQ6BDMELQQ7BCwEOgQ7BC0ELAQ1BC4EOwQ1BCwEOAQ8BDkEPQQ8BDgEOQQ+BDoEPAQ+BDkEOgQ/BDsEPgQ/BDoEOwRABDUEPwRABDsENQRBBDQEQARBBDUENARCBDYEQQRCBDQENwRTA0MENwQ9BDgEQwQ9BDcEQARPA0EETQNPA0AEQQRRA0IETwNRA0EEQwRTA0UDQwREAz0ERQNEA0MEPQRHAzwERANHAz0EPARJAz4ERwNJAzwEPgRLAz8ESQNLAz4EPwRNA0AESwNNAz8E4wPnA0QERQRGBEcERQRIBEYEjgOaA0kEUQNSA0oEVQNgA0oE8gP2A0QE1wPjA0QEMAQ2BEsEngOpA0kEawNvA0oEQgRRA0sEKwQwBEsEAQQFBEQEuAPDA0kE5wPyA0QErAO4A0kEyAPTA0QEewN/A0oEEQQUBEsE9gMBBEQEwwPIA0kENgRCBEsEigOOA0kEbwN7A0oEUgNVA0oEBQQRBEsE0wPXA0QEfwOKA0oEIAQrBEsEmgOeA0kEYANrA0oEFAQgBEsEqQOsA0kERARJBMgDRAQFBEsESwRRA0oESgSKA0kETARNBE4ETARPBE0ETwRQBE0ETwRRBFAEUARSBFMEUQRSBFAEUgRUBFMEUgRVBFQEVQRWBFQEVQRXBFYEVwRYBFYEVwRZBFgEWQRaBFgEWQRbBFoETgRcBEwEXQRbBFkEXQReBFsETARcBF8EXwRPBEwEYARPBF8EYARRBE8EYARhBFEEYQRSBFEEYQRiBFIEYgRVBFIEYgRjBFUEYwRXBFUEYwRkBFcEZARZBFcEZARdBFkEZQRjBGIEZQRmBGMEZgRkBGMEZgRnBGQEZwRdBGQEZwRoBF0EaAReBF0EaARpBF4EXwRcBGoEagRgBF8EawRgBGoEawRhBGAEbARhBGsEbARiBGEEbARlBGIEbQRrBGoEbgRrBG0EbgRsBGsEbwRsBG4EbwRlBGwEcARlBG8EcARmBGUEcARxBGYEcQRnBGYEcQRyBGcEcgRoBGcEcgRzBGgEcwRpBGgEcwR0BGkEagRcBG0EdQRyBHEEdQR2BHIEdgRzBHIEdgR3BHMEdwR0BHMEdwR4BHQEbQRcBHkEeQRuBG0EegRuBHkEegRvBG4EewRvBHoEewRwBG8EfARwBHsEfARxBHAEdQRxBHwEfQR6BHkEfgR6BH0EfgR7BHoEfwR7BH4EfwR8BHsEgAR8BH8EgAR1BHwEgQR1BIAEgQR2BHUEggR2BIEEggR3BHYEggSDBHcEgwR4BHcEgwSEBHgEeQRcBH0EhQSCBIEEhgSCBIUEhgSDBIIEhwSDBIYEhwSEBIMEhwSIBIQEfQRcBIkEiQR+BH0EigR+BIkEigR/BH4EiwR/BIoEiwSABH8EjASABIsEjASBBIAEhQSBBIwEjQSLBIoEjgSLBI0EjgSMBIsEjwSMBI4EjwSFBIwEkASFBI8EkASGBIUEkQSGBJAEkQSHBIYEkgSHBJEEkgSIBIcEkgSTBIgEiQRcBJQElASKBIkEjQSKBJQElQSRBJYElQSSBJEElwSSBJUEkwSSBJcElARcBJgEmQSUBJgEmQSNBJQEmgSNBJkEmgSOBI0EmwSOBJoEmwSPBI4EnASPBJsEnASQBI8ElgSQBJwElgSRBJAEnQSZBJ4EnQSaBJkEnwSaBJ0EnwSbBJoEoASbBJ8EoAScBJsEoQScBKAEoQSWBJwEogSWBKEElQSWBKIEowSVBKIElwSVBKMEmARcBKQEngSYBKQEngSZBJgEpQShBKYEogShBKUEpwSiBKUEowSiBKcEpARcBKgEqQSkBKgEqQSeBKQEqgSeBKkEqgSdBJ4EqwSdBKoEqwSfBJ0ErASfBKsErASgBJ8EpgSgBKwEpgShBKAErQSqBK4ErQSrBKoErwSrBK0ErwSsBKsEsASsBK8EpgSsBLAEsQSmBLAEpQSmBLEEsgSlBLEEpwSlBLIEqARcBLMEtASoBLMEtASpBKgErgSpBLQErgSqBKkEtQSxBLYEsgSxBLUEswRcBLcEuASzBLcEuAS0BLMEuQS0BLgEuQSuBLQEugSuBLkEugStBK4EuwStBLoErwStBLsEvASvBLsEsASvBLwEtgSwBLwEsQSwBLYEvQS6BL4EuwS6BL0EvwS7BL0EvAS7BL8EwAS8BL8EtgS8BMAEwQS2BMAEtQS2BMEEtwRcBMIEwwS3BMIEwwS4BLcExAS4BMMEuQS4BMQEvgS5BMQEugS5BL4EwgRcBMUExgTCBMUEwwTCBMYExwTDBMYExATDBMcEyATEBMcEvgTEBMgEyQS+BMgEvQS+BMkEygS9BMkEvwS9BMoEywS/BMoEwAS/BMsEzATABMsEwQTABMwEzQTIBM4EyQTIBM0EygTNBM8EygTJBM0EywTPBNAEywTKBM8EzATQBNEEzATLBNAExQRcBNIExgTSBNMExgTFBNIExwTTBNQExwTGBNMEyATUBM4EyATHBNQE0wTVBNYE0wTSBNUE1ATWBNcE1ATTBNYEzgTXBNgEzgTUBNcEzQTYBNkEzQTOBNgEzwTZBNoEzwTNBNkE0ATaBNsE0ATPBNoE0QTbBNwE0QTQBNsE0gRcBNUE2gTdBN4E2gTZBN0E2wTeBN8E2wTaBN4E3ATfBOAE3ATbBN8E1QRcBOEE1gThBOIE1QThBNYE1wTiBOME1wTWBOIE2ATjBOQE2ATXBOME2QTkBN0E2QTYBOQE4gTlBOYE4QTlBOIE4wTmBOcE4gTmBOME5ATnBOgE5ATjBOcE3QToBOkE3QTkBOgE3gTpBOoE3gTdBOkE3wTqBOsE3wTeBOoE4ATrBOwE4ATfBOsE4QRcBOUE6gTtBO4E6gTpBO0E6wTuBO8E6wTqBO4E7ATvBPAE7ATrBO8E5QRcBPEE5gTxBPIE5QTxBOYE5wTyBPME5gTyBOcE6ATzBPQE5wTzBOgE6QT0BO0E6QToBPQE8wT1BPYE8gT1BPME9AT2BPcE8wT2BPQE7QT3BPgE9AT3BO0E7gT4BPkE7gTtBPgE7wT5BPoE7wTuBPkE8AT6BPsE8ATvBPoE8QRcBPwE8gT8BPUE8QT8BPIE+gT9BP4E+gT5BP0E+wT+BP8E+wT6BP4E/ARcBAAF9QQABQEF/AQABfUE9gQBBQIF9QQBBfYE9wQCBQMF9gQCBfcE+AQDBQQF9wQDBfgE+QQEBf0E+AQEBfkEAwUFBQYFAgUFBQMFBAUGBQcFAwUGBQQF/QQHBQgFBAUHBf0E/gQIBQkF/QQIBf4E/wQJBQoF/wT+BAkFAAVcBAsFAQULBQwFAAULBQEFAgUMBQUFAQUMBQIFCgUNBQ4FCQUNBQoFCwVcBA8FDAUPBRAFCwUPBQwFBQUQBREFDAUQBQUFBgURBRIFBQURBQYFBwUSBRMFBgUSBQcFCAUTBRQFBwUTBQgFCQUUBQ0FCAUUBQkFEQUVBRIFEQUWBRUFEgUXBRMFEgUVBRcFEwUYBRQFEwUXBRgFFAUZBQ0FFAUYBRkFDQUaBQ4FGQUaBQ0FDwVcBBsFDwUcBRAFDwUbBRwFEAUWBREFEAUcBRYFGQUdBRoFHgUdBRkFGwVcBB8FGwUgBRwFGwUfBSAFHAUhBRYFHAUgBSEFFgUiBRUFFgUhBSIFFQUjBRcFFQUiBSMFFwUkBRgFFwUjBSQFGAUeBRkFJAUeBRgFIgUlBSMFIgUmBSUFIwUnBSQFIwUlBScFJAUoBR4FJwUoBSQFHgUpBR0FKAUpBR4FHwVcBCoFHwUrBSAFHwUqBSsFIAUsBSEFIAUrBSwFIQUmBSIFIQUsBSYFKgVcBC0FKgUuBSsFKgUtBS4FKwUvBSwFKwUuBS8FLAUwBSYFLAUvBTAFJgUxBSUFJgUwBTEFJQUyBScFMQUyBSUFJwUzBSgFMgUzBScFKAU0BSkFMwU0BSgFMAU1BTEFNgU1BTAFMQU3BTIFNQU3BTEFMgU4BTMFNwU4BTIFMwU5BTQFOAU5BTMFLQVcBDoFLQU7BS4FLQU6BTsFLgU8BS8FLgU7BTwFLwU2BTAFLwU8BTYFNwU9BTgFPgU9BTcFOAU/BTkFPQU/BTgFOgVcBEAFOgVBBTsFOgVABUEFOwVCBTwFQQVCBTsFPAVDBTYFQgVDBTwFNgVEBTUFQwVEBTYFNQU+BTcFRAU+BTUFQQVFBUIFRgVFBUEFQgVHBUMFRQVHBUIFQwVIBUQFRwVIBUMFRAVJBT4FSAVJBUQFPgVKBT0FSQVKBT4FPQVLBT8FSgVLBT0FQAVcBEwFQAVGBUEFTAVGBUAFSQVYBEoFVgRYBEkFSgVaBEsFWARaBEoFTAVcBE4ETAVNBEYFTgRNBEwFRgVQBEUFTQRQBEYFRQVTBEcFUARTBEUFRwVUBEgFUwRUBEcFSAVWBEkFVARWBEgF7ATwBE0FTgVPBVAFTgVRBU8FlwSjBFIFWgRbBFMFXgRpBFMF+wT/BE0F4ATsBE0FOQU/BVQFpwSyBFIFdAR4BFMFSwVaBFQFNAU5BVQFCgUOBU0FwQTMBFIF8AT7BE0FtQTBBFIF0QTcBE0FhASIBFMFGgUdBVQF/wQKBU0FzATRBFIFPwVLBVQFkwSXBFIFeASEBFMFWwReBFMFDgUaBVQF3ATgBE0FiASTBFMFKQU0BVQFowSnBFIFaQR0BFMFHQUpBVQFsgS1BFIFTQVSBdEETQUOBVQFVAVaBFMFUwWTBFIFVQVWBVcFVQVYBVYFWAVZBVYFWAVaBVkFWgVbBVkFWgVcBVsFXAVdBVsFXAVeBV0FXgVfBV0FXgVgBV8FYAVhBV8FYAViBWEFYgVjBWEFYgVkBWMFVwVlBVUFZgVkBWIFZgVnBWQFVQVlBWgFaAVYBVUFaQVYBWgFaQVaBVgFaQVqBVoFagVcBVoFagVrBVwFawVeBVwFawVsBV4FbAVgBV4FbAVtBWAFbQViBWAFbQVmBWIFbgVsBWsFbgVvBWwFbwVtBWwFbwVwBW0FcAVmBW0FcAVxBWYFcQVnBWYFcQVyBWcFaAVlBXMFcwVpBWgFdAVpBXMFdAVqBWkFdAV1BWoFdQVrBWoFdQVuBWsFdgV0BXMFdwV0BXYFdwV1BXQFeAV1BXcFeAVuBXUFeAV5BW4FeQVvBW4FeQV6BW8FegVwBW8FegV7BXAFewVxBXAFewV8BXEFfAVyBXEFfAV9BXIFcwVlBXYFfgV7BXoFfgV/BXsFfwV8BXsFfwWABXwFgAV9BXwFgAWBBX0FdgVlBYIFggV3BXYFgwV3BYIFgwV4BXcFhAV4BYMFhAV5BXgFhQV5BYQFhQV6BXkFhQV+BXoFhgWDBYIFhwWDBYYFhwWEBYMFiAWEBYcFiAWFBYQFiQWFBYgFiQV+BYUFigV+BYkFigV/BX4FiwV/BYoFiwWABX8FiwWMBYAFjAWBBYAFjAWNBYEFggVlBYYFjgWLBYoFjwWLBY4FjwWMBYsFjwWQBYwFkAWNBYwFkAWRBY0FhgVlBZIFkgWHBYYFkwWHBZIFkwWIBYcFlAWIBZMFlAWJBYgFlQWJBZQFlQWKBYkFjgWKBZUFlgWUBZMFlwWUBZYFlwWVBZQFmAWVBZcFmAWOBZUFmQWOBZgFmQWPBY4FmgWPBZkFmgWQBY8FmwWQBZoFmwWRBZAFnAWRBZsFkgVlBZ0FnQWTBZIFlgWTBZ0FngWaBZ8FngWbBZoFoAWbBZ4FnAWbBaAFnQVlBaEFogWdBaEFogWWBZ0FowWWBaIFowWXBZYFpAWXBaMFpAWYBZcFpQWYBaQFpQWZBZgFnwWZBaUFnwWaBZkFpgWiBacFpgWjBaIFqAWjBaYFqAWkBaMFqQWkBagFqQWlBaQFqgWlBakFqgWfBaUFqwWfBaoFngWfBasFrAWeBasFoAWeBawFoQVlBa0FpwWhBa0FpwWiBaEFrgWqBa8FqwWqBa4FsAWrBa4FrAWrBbAFrQVlBbEFsgWtBbEFsgWnBa0FswWnBbIFswWmBacFtAWmBbMFtAWoBaYFtQWoBbQFtQWpBagFrwWpBbUFqgWpBa8FtgWzBbcFtgW0BbMFuAW0BbYFtQW0BbgFuQW1BbgFrwW1BbkFugWvBbkFrgWvBboFuwWuBboFsAWuBbsFsQVlBbwFvQWxBbwFvQWyBbEFtwWyBb0FtwWzBbIFvgW6Bb8FuwW6Bb4FvAVlBcAFwQW8BcAFwQW9BbwFwgW9BcEFwgW3Bb0FwwW3BcIFtgW3BcMFxAW2BcMFuAW2BcQFxQW4BcQFuQW4BcUFvwW5BcUFugW5Bb8FxgXDBccFxAXDBcYFyAXEBcYFxQXEBcgFyQXFBcgFvwXFBckFygW/BckFvgW/BcoFwAVlBcsFzAXABcsFzAXBBcAFzQXBBcwFwgXBBc0FxwXCBc0FwwXCBccFywVlBc4FzwXLBc4FzAXLBc8F0AXMBc8FzQXMBdAF0QXNBdAFxwXNBdEF0gXHBdEFxgXHBdIF0wXGBdIFyAXGBdMF1AXIBdMFyQXIBdQF1QXJBdQFygXJBdUF0gXWBdcF0gXRBdYF2AXSBdcF0wXSBdgF1AXYBdkF1AXTBdgF1QXZBdoF1QXUBdkFzgVlBdsFzwXbBdwFzwXOBdsF0AXcBd0F0AXPBdwF0QXdBdYF0QXQBd0F3AXeBd8F3AXbBd4F3QXfBeAF3QXcBd8F1gXgBeEF1gXdBeAF1wXhBeIF1wXWBeEF2AXiBeMF2AXXBeIF2QXjBeQF2QXYBeMF2gXkBeUF2gXZBeQF2wVlBd4F4wXmBecF4wXiBeYF5AXnBegF5AXjBecF5QXoBekF5QXkBegF3gVlBeoF3wXqBesF3gXqBd8F4AXrBewF4AXfBesF4QXsBe0F4QXgBewF4gXtBeYF4gXhBe0F6wXuBe8F6gXuBesF7AXvBfAF6wXvBewF7QXwBfEF7QXsBfAF5gXxBfIF5gXtBfEF5wXyBfMF5wXmBfIF6AXzBfQF6AXnBfMF6QX0BfUF6QXoBfQF6gVlBe4F8wX2BfcF8wXyBfYF9AX3BfgF9AXzBfcF9QX4BfkF9QX0BfgF7gVlBfoF7wX6BfsF7gX6Be8F8AX7BfwF7wX7BfAF8QX8Bf0F8AX8BfEF8gX9BfYF8gXxBf0F/AX+Bf8F+wX+BfwF/QX/BQAG/AX/Bf0F9gUABgEG/QUABvYF9wUBBgIG9wX2BQEG+AUCBgMG+AX3BQIG+QUDBgQG+QX4BQMG+gVlBQUG+wUFBv4F+gUFBvsFAwYGBgcGAwYCBgYGBAYHBggGBAYDBgcGBQZlBQkG/gUJBgoGBQYJBv4F/wUKBgsG/gUKBv8FAAYLBgwG/wULBgAGAQYMBg0GAAYMBgEGAgYNBgYGAQYNBgIGDAYOBg8GCwYOBgwGDQYPBhAGDAYPBg0GBgYQBhEGDQYQBgYGBwYRBhIGBgYRBgcGCAYSBhMGCAYHBhIGCQZlBRQGCgYUBhUGCQYUBgoGCwYVBg4GCgYVBgsGEwYWBhcGEgYWBhMGFAZlBRgGFQYYBhkGFAYYBhUGDgYZBhoGFQYZBg4GDwYaBhsGDgYaBg8GEAYbBhwGDwYbBhAGEQYcBh0GEAYcBhEGEgYdBhYGEQYdBhIGGgYeBhsGGgYfBh4GGwYgBhwGGwYeBiAGHAYhBh0GHAYgBiEGHQYiBhYGHQYhBiIGFgYjBhcGIgYjBhYGGAZlBSQGGAYlBhkGGAYkBiUGGQYfBhoGGQYlBh8GIgYmBiMGJwYmBiIGJAZlBSgGJAYpBiUGJAYoBikGJQYqBh8GJQYpBioGHwYrBh4GHwYqBisGHgYsBiAGHgYrBiwGIAYtBiEGIAYsBi0GIQYnBiIGIQYtBicGKwYuBiwGKwYvBi4GLAYwBi0GLgYwBiwGLQYxBicGMAYxBi0GJwYyBiYGMQYyBicGKAZlBTMGKAY0BikGKAYzBjQGKQY1BioGKQY0BjUGKgYvBisGKgY1Bi8GMwZlBTYGMwY3BjQGMwY2BjcGNAY4BjUGNAY3BjgGNQY5Bi8GNQY4BjkGLwY6Bi4GOQY6Bi8GLgY7BjAGOgY7Bi4GMAY8BjEGOwY8BjAGMQY9BjIGPAY9BjEGOQY+BjoGPwY+BjkGOgZABjsGPgZABjoGOwZBBjwGQAZBBjsGPAZCBj0GQQZCBjwGNgZlBUMGNgZEBjcGNgZDBkQGNwZFBjgGNwZEBkUGOAY/BjkGRQY/BjgGQAZGBkEGRwZGBkAGQQZIBkIGRgZIBkEGQwZlBUkGQwZKBkQGQwZJBkoGRAZLBkUGSgZLBkQGRQZMBj8GSwZMBkUGPwZNBj4GTAZNBj8GPgZHBkAGTQZHBj4GSgZOBksGTwZOBkoGSwZQBkwGTgZQBksGTAZRBk0GUAZRBkwGTQZSBkcGUQZSBk0GRwZTBkYGUgZTBkcGRgZUBkgGUwZUBkYGSQZlBVUGSQZPBkoGVQZPBkkGUgZhBVMGXwVhBVIGUwZjBVQGYQVjBVMGVQZlBVcFVQZWBU8GVwVWBVUGTwZZBU4GVgVZBU8GTgZbBVAGWQVbBU4GUAZdBVEGWwVdBVAGUQZfBVIGXQVfBVEG9QX5BVYGVwZYBlkGVwZaBlgGoAWsBVsGYwVkBVwGZwVyBVwGBAYIBlYG6QX1BVYGQgZIBl0GsAW7BVsGfQWBBVwGVAZjBV0GPQZCBl0GEwYXBlYGygXVBVsG+QUEBlYGvgXKBVsG2gXlBVYGjQWRBVwGIwYmBl0GCAYTBlYG1QXaBVsGSAZUBl0GnAWgBVsGgQWNBVwGZAVnBVwGFwYjBl0G5QXpBVYGkQWcBVwGMgY9Bl0GrAWwBVsGcgV9BVwGJgYyBl0GuwW+BVsGVgZbBtoFVgYXBl0GXQZjBVwGXAacBVsGXgZfBmAGXgZhBl8GYQZiBl8GYQZjBmIGYwZkBmIGYwZlBmQGZQZmBmQGZQZnBmYGZwZoBmYGZwZpBmgGaQZqBmgGaQZrBmoGawZsBmoGawZtBmwGYAZuBl4GbwZtBmsGbwZwBm0GXgZuBnEGcQZhBl4GcQZyBmEGcgZjBmEGcgZzBmMGcwZlBmMGcwZ0BmUGdAZnBmUGdAZ1BmcGdQZpBmcGdQZ2BmkGdgZrBmkGdgZvBmsGdwZ1BnQGdwZ4BnUGeAZ2BnUGeAZ5BnYGeQZvBnYGeQZ6Bm8GegZwBm8GegZ7BnAGcQZuBnwGfAZyBnEGfQZyBnwGfQZzBnIGfgZzBn0GfgZ0BnMGfgZ3BnQGfwZ9BnwGgAZ9Bn8GgAZ+Bn0GgQZ+BoAGgQZ3Bn4GggZ3BoEGggZ4BncGggaDBngGgwZ5BngGgwaEBnkGhAZ6BnkGhAaFBnoGhQZ7BnoGhQaGBnsGfAZuBn8GhwaEBoMGhwaIBoQGiAaFBoQGiAaJBoUGiQaGBoUGiQaKBoYGfwZuBosGiwaABn8GjAaABosGjAaBBoAGjQaBBowGjQaCBoEGjgaCBo0GjgaDBoIGhwaDBo4GjwaMBosGkAaMBo8GkAaNBowGkQaNBpAGkQaOBo0GkgaOBpEGkgaHBo4GkwaHBpIGkwaIBocGkwaUBogGlAaJBogGlAaVBokGlQaKBokGlQaWBooGiwZuBo8GlwaUBpMGmAaUBpcGmAaVBpQGmQaVBpgGmQaWBpUGmQaaBpYGjwZuBpsGmwaQBo8GnAaQBpsGnAaRBpAGnQaRBpwGnQaSBpEGngaSBp0GngaTBpIGlwaTBp4GnwadBpwGoAadBp8GoAaeBp0GoQaeBqAGoQaXBp4GogaXBqEGogaYBpcGowaYBqIGowaZBpgGpAaZBqMGpAaaBpkGpQaaBqQGmwZuBqYGpgacBpsGnwacBqYGpwajBqgGpwakBqMGqQakBqcGpQakBqkGpgZuBqoGqwamBqoGqwafBqYGrAafBqsGrAagBp8GrQagBqwGrQahBqAGrgahBq0GrgaiBqEGqAaiBq4GqAajBqIGrwarBrAGrwasBqsGsQasBq8GsQatBqwGsgatBrEGsgauBq0GswauBrIGswaoBq4GtAaoBrMGtAanBqgGtQanBrQGqQanBrUGqgZuBrYGsAaqBrYGsAarBqoGtwazBrgGtAazBrcGuQa0BrcGtQa0BrkGtgZuBroGuwa2BroGuwawBrYGvAawBrsGvAavBrAGvQavBrwGvQaxBq8GvgaxBr0GvgayBrEGuAayBr4GswayBrgGvwa8BsAGvwa9BrwGwQa9Br8GwQa+Br0Gwga+BsEGuAa+BsIGwwa4BsIGtwa4BsMGxAa3BsMGuQa3BsQGugZuBsUGxga6BsUGxga7BroGwAa7BsYGwAa8BrsGxwbDBsgGxAbDBscGxQZuBskGygbFBskGygbGBsUGywbGBsoGywbABsYGzAbABssGvwbABswGzQa/BswGwQa/Bs0GzgbBBs0GwgbBBs4GyAbCBs4GwwbCBsgGzwbMBtAGzQbMBs8G0QbNBs8GzgbNBtEG0gbOBtEGyAbOBtIG0wbIBtIGxwbIBtMGyQZuBtQG1QbJBtQG1QbKBskG1gbKBtUGywbKBtYG0AbLBtYGzAbLBtAG1AZuBtcG2AbUBtcG1QbUBtgG2QbVBtgG1gbVBtkG2gbWBtkG0AbWBtoG2wbQBtoGzwbQBtsG3AbPBtsG0QbPBtwG3QbRBtwG0gbRBt0G3gbSBt0G0wbSBt4G2wbfBuAG2wbaBt8G3AbgBuEG3AbbBuAG4gbcBuEG3QbcBuIG3gbiBuMG3gbdBuIG1wZuBuQG2AbkBuUG2AbXBuQG2QblBuYG2QbYBuUG2gbmBt8G2gbZBuYG5QbnBugG5QbkBucG5gboBukG5gblBugG3wbpBuoG3wbmBukG4AbqBusG4AbfBuoG4QbrBuwG4QbgBusG4gbsBu0G4gbhBuwG4wbtBu4G4wbiBu0G5AZuBucG7AbvBvAG7AbrBu8G7QbwBvEG7QbsBvAG7gbxBvIG7gbtBvEG5wZuBvMG6AbzBvQG5wbzBugG6Qb0BvUG6QboBvQG6gb1BvYG6gbpBvUG6wb2Bu8G6wbqBvYG9Ab3BvgG8wb3BvQG9Qb4BvkG9Qb0BvgG9gb5BvoG9gb1BvkG7wb6BvsG7wb2BvoG8Ab7BvwG8AbvBvsG8Qb8Bv0G8QbwBvwG8gb9Bv4G8gbxBv0G8wZuBvcG/Ab/BgAH/Ab7Bv8G/QYABwEH/Qb8BgAH/gYBBwIH/gb9BgEH9wZuBgMH+AYDBwQH9wYDB/gG+QYEBwUH+AYEB/kG+gYFBwYH+QYFB/oG+wYGB/8G+wb6BgYHBQcHBwgHBAcHBwUHBgcIBwkHBQcIBwYH/wYJBwoHBgcJB/8GAAcKBwsHAAf/BgoHAQcLBwwHAQcABwsHAgcMBw0HAgcBBwwHAwduBg4HBAcOBwcHAwcOBwQHDAcPBxAHDAcLBw8HDQcQBxEHDQcMBxAHDgduBhIHBwcSBxMHDgcSBwcHCAcTBxQHBwcTBwgHCQcUBxUHCAcUBwkHCgcVBxYHCQcVBwoHCwcWBw8HCwcKBxYHFQcXBxgHFAcXBxUHFgcYBxkHFQcYBxYHDwcZBxoHFgcZBw8HEAcaBxsHEAcPBxoHEQcbBxwHEQcQBxsHEgduBh0HEwcdBx4HEgcdBxMHFAceBxcHEwceBxQHHAcfByAHHAcbBx8HHQduBiEHHgchByIHHQchBx4HFwciByMHHgciBxcHGAcjByQHFwcjBxgHGQckByUHGAckBxkHGgclByYHGQclBxoHGwcmBx8HGgcmBxsHIwcnByQHIwcoBycHJAcpByUHJAcnBykHJQcqByYHJQcpByoHJgcrBx8HJgcqBysHHwcsByAHKwcsBx8HIQduBi0HIQcuByIHIQctBy4HIgcoByMHIgcuBygHKwcvBywHMAcvBysHLQduBjEHLQcyBy4HLQcxBzIHLgczBygHLgcyBzMHKAc0BycHKAczBzQHJwc1BykHJwc0BzUHKQc2ByoHKQc1BzYHKgcwBysHKgc2BzAHNAc3BzUHNAc4BzcHNQc5BzYHNQc3BzkHNgc6BzAHOQc6BzYHMAc7By8HOgc7BzAHMQduBjwHMQc9BzIHMQc8Bz0HMgc+BzMHMgc9Bz4HMwc4BzQHMwc+BzgHPAduBj8HPAdABz0HPAc/B0AHPQdBBz4HPQdAB0EHPgdCBzgHPgdBB0IHOAdDBzcHQgdDBzgHNwdEBzkHQwdEBzcHOQdFBzoHRAdFBzkHOgdGBzsHRQdGBzoHQgdHB0MHSAdHB0IHQwdJB0QHRwdJB0MHRAdKB0UHSQdKB0QHRQdLB0YHSgdLB0UHPwduBkwHPwdNB0AHPwdMB00HQAdOB0EHQAdNB04HQQdIB0IHTgdIB0EHSQdPB0oHUAdPB0kHSgdRB0sHTwdRB0oHTAduBlIHTAdTB00HTAdSB1MHTQdUB04HUwdUB00HTgdVB0gHVAdVB04HSAdWB0cHVQdWB0gHRwdQB0kHVgdQB0cHUwdXB1QHWAdXB1MHVAdZB1UHVwdZB1QHVQdaB1YHWQdaB1UHVgdbB1AHWgdbB1YHUAdcB08HWwdcB1AHTwddB1EHXAddB08HUgduBl4HUgdYB1MHXgdYB1IHWwdqBlwHaAZqBlsHXAdsBl0HagZsBlwHXgduBmAGXgdfBlgHYAZfBl4HWAdiBlcHXwZiBlgHVwdkBlkHYgZkBlcHWQdmBloHZAZmBlkHWgdoBlsHZgZoBloH/gYCB18HYAdhB2IHYAdjB2EHqQa1BmQHbAZtBmUHcAZ7BmUHDQcRB18H8gb+Bl8HSwdRB2YHuQbEBmQHhgaKBmUHXQdsBmYHRgdLB2YHHAcgB18H0wbeBmQHAgcNB18HxwbTBmQH4wbuBl8HlgaaBmUHLAcvB2YHEQccB18H3gbjBmQHUQddB2YHpQapBmQHigaWBmUHbQZwBmUHIAcsB2YH7gbyBl8HmgalBmUHOwdGB2YHtQa5BmQHewaGBmUHLwc7B2YHxAbHBmQHXwdkB+MGXwcgB2YHZgdsBmUHZQelBmQHZwdoB2kHZwdqB2gHaAdrB2wHagdrB2gHawdtB2wHawduB20HbQdvB3AHbgdvB20HbwdxB3AHbwdyB3EHcQdzB3QHcgdzB3EHdAd1B3YHcwd1B3QHaQd3B2cHeAd1B3MHeAd5B3UHZwd3B3oHegdqB2cHegd7B2oHewdrB2oHewd8B2sHfAduB2sHfAd9B24HfQdvB24HfQd+B28HfgdyB28Hfgd/B3IHfwdzB3IHfwd4B3MHgAd+B30HgAeBB34HgQd/B34HgQeCB38Hggd4B38HggeDB3gHgwd5B3gHgweEB3kHegd3B4UHhQd7B3oHhgd7B4UHhgd8B3sHhgeHB3wHhwd9B3wHhweAB30HiAeGB4UHiQeGB4gHiQeHB4YHigeHB4kHigeAB4cHiweAB4oHiweBB4AHiweMB4EHjAeCB4EHjAeNB4IHjQeDB4IHjQeOB4MHjgeEB4MHjgePB4QHhQd3B4gHkAeNB4wHkAeRB40HkQeOB40HkQeSB44HkgePB44HkgeTB48HiAd3B5QHlAeJB4gHlQeJB5QHlQeKB4kHlgeKB5UHlgeLB4oHlweLB5YHlweMB4sHkAeMB5cHmAeVB5QHmQeVB5gHmQeWB5UHmgeWB5kHmgeXB5YHmweXB5oHmweQB5cHnAeQB5sHnAeRB5AHnAedB5EHnQeSB5EHnQeeB5IHngeTB5IHngefB5MHlAd3B5gHoAedB5wHoQedB6AHoQeeB50HogeeB6EHogefB54HogejB58HmAd3B6QHpAeZB5gHpQeZB6QHpQeaB5kHpgeaB6UHpgebB5oHpwebB6YHpwecB5sHoAecB6cHqAemB6UHqQemB6gHqQenB6YHqgenB6kHqgegB6cHqwegB6oHqwehB6AHrAehB6sHrAeiB6EHrQeiB6wHrQejB6IHrgejB60HpAd3B68HrwelB6QHqAelB68HsAesB7EHsAetB6wHsgetB7AHrgetB7IHrwd3B7MHtAevB7MHtAeoB68HtQeoB7QHtQepB6gHtgepB7UHtgeqB6kHtweqB7YHtwerB6oHsQerB7cHsQesB6sHuAe0B7kHuAe1B7QHuge1B7gHuge2B7UHuwe2B7oHuwe3B7YHvAe3B7sHvAexB7cHvQexB7wHsAexB70HvgewB70HsgewB74Hswd3B78HuQezB78HuQe0B7MHwAe8B8EHvQe8B8AHwge9B8AHvge9B8IHvwd3B8MHxAe/B8MHxAe5B78HxQe5B8QHxQe4B7kHxge4B8UHxge6B7gHxwe6B8YHxwe7B7oHwQe7B8cHvAe7B8EHyAfFB8kHyAfGB8UHygfGB8gHygfHB8YHywfHB8oHwQfHB8sHzAfBB8sHwAfBB8wHzQfAB8wHwgfAB80Hwwd3B84HzwfDB84HzwfEB8MHyQfEB88HyQfFB8QH0AfMB9EHzQfMB9AHzgd3B9IH0wfOB9IH0wfPB84H1AfPB9MH1AfJB88H1QfJB9QHyAfJB9UH1gfIB9UHygfIB9YH1wfKB9YHywfKB9cH0QfLB9cHzAfLB9EH2AfVB9kH1gfVB9gH2gfWB9gH1wfWB9oH2wfXB9oH0QfXB9sH3AfRB9sH0AfRB9wH0gd3B90H3gfSB90H3gfTB9IH3wfTB94H1AfTB98H2QfUB98H1QfUB9kH3Qd3B+AH4QfdB+AH3gfdB+EH4gfeB+EH3wfeB+IH4wffB+IH2QffB+MH5AfZB+MH2AfZB+QH5QfYB+QH2gfYB+UH5gfaB+UH2wfaB+YH5wfbB+YH3AfbB+cH5AfoB+kH5AfjB+gH5QfpB+oH5QfkB+kH5gfqB+sH5gflB+oH5wfrB+wH5wfmB+sH4Ad3B+0H4QftB+4H4QfgB+0H4gfuB+8H4gfhB+4H4wfvB+gH4wfiB+8H7gfwB/EH7gftB/AH7wfxB/IH7wfuB/EH6AfyB/MH6AfvB/IH6QfzB/QH6QfoB/MH6gf0B/UH6gfpB/QH6wf1B/YH6wfqB/UH7Af2B/cH7AfrB/YH7Qd3B/AH9Qf4B/kH9Qf0B/gH9gf5B/oH9gf1B/kH9wf6B/sH9wf2B/oH8Ad3B/wH8Qf8B/0H8Af8B/EH8gf9B/4H8gfxB/0H8wf+B/8H8wfyB/4H9Af/B/gH9AfzB/8H/QcACAEI/AcACP0H/gcBCAII/QcBCP4H/wcCCAMI/wf+BwII+AcDCAQI+Af/BwMI+QcECAUI+Qf4BwQI+gcFCAYI+gf5BwUI+wcGCAcI+wf6BwYI/Ad3BwAIBQgICAkIBQgECAgIBggJCAoIBggFCAkIBwgKCAsIBwgGCAoIAAh3BwwIAQgMCA0IAAgMCAEIAggNCA4IAQgNCAIIAwgOCA8IAggOCAMIBAgPCAgIBAgDCA8IDggQCBEIDQgQCA4IDwgRCBIIDggRCA8ICAgSCBMICAgPCBIICQgTCBQICQgICBMICggUCBUICggJCBQICwgVCBYICwgKCBUIDAh3BxcIDQgXCBAIDAgXCA0IFQgYCBkIFQgUCBgIFggZCBoIFggVCBkIFwh3BxsIEAgbCBwIFwgbCBAIEQgcCB0IEAgcCBEIEggdCB4IEQgdCBIIEwgeCB8IEggeCBMIFAgfCBgIEwgfCBQIHgggCCEIHQggCB4IHwghCCIIHgghCB8IGAgiCCMIHwgiCBgIGQgjCCQIGQgYCCMIGggkCCUIGggZCCQIGwh3ByYIHAgmCCcIGwgmCBwIHQgnCCAIHAgnCB0IJQgoCCkIJAgoCCUIJgh3ByoIJwgqCCsIJggqCCcIIAgrCCwIJwgrCCAIIQgsCC0IIAgsCCEIIggtCC4IIQgtCCIIIwguCC8IIgguCCMIJAgvCCgIIwgvCCQILAgwCC0ILAgxCDAILQgyCC4ILQgwCDIILggzCC8ILggyCDMILwg0CCgILwgzCDQIKAg1CCkINAg1CCgIKgh3BzYIKgg3CCsIKgg2CDcIKwgxCCwIKwg3CDEINAg4CDUIOQg4CDQINgh3BzoINgg7CDcINgg6CDsINwg8CDEINwg7CDwIMQg9CDAIMQg8CD0IMAg+CDIIMAg9CD4IMgg/CDMIMgg+CD8IMwg5CDQIPwg5CDMIPQhACD4IPQhBCEAIPghCCD8IQAhCCD4IPwhDCDkIQghDCD8IOQhECDgIQwhECDkIOgh3B0UIOghGCDsIOghFCEYIOwhHCDwIOwhGCEcIPAhBCD0IPAhHCEEIRQh3B0gIRQhJCEYIRQhICEkIRghKCEcIRghJCEoIRwhLCEEIRwhKCEsIQQhMCEAISwhMCEEIQAhNCEIITAhNCEAIQghOCEMITQhOCEIIQwhPCEQITghPCEMISwhQCEwIUQhQCEsITAhSCE0IUAhSCEwITQhTCE4IUghTCE0ITghUCE8IUwhUCE4ISAh3B1UISAhWCEkISAhVCFYISQhXCEoISQhWCFcISghRCEsIVwhRCEoIUghYCFMIWQhYCFIIUwhaCFQIWAhaCFMIVQh3B1sIVQhcCFYIVQhbCFwIVghdCFcIXAhdCFYIVwheCFEIXQheCFcIUQhfCFAIXghfCFEIUAhZCFIIXwhZCFAIXAhgCF0IYQhgCFwIXQhiCF4IYAhiCF0IXghjCF8IYghjCF4IXwhkCFkIYwhkCF8IWQhlCFgIZAhlCFkIWAhmCFoIZQhmCFgIWwh3B2cIWwhhCFwIWwhnCGEIZAh0B2UIcQd0B2QIZQh2B2YIdAd2B2UIZwh3B2kHZwhoB2EIaQdoB2cIYQhsB2AIaAdsB2EIYAhtB2IIbAdtB2AIYghwB2MIbQdwB2IIYwhxB2QIcAdxB2MIBwgLCGgIaQhqCGsIaQhsCGoIsge+B20Idgd1B24IeQeEB24IFggaCGgI+wcHCGgIVAhaCG8IwgfNB20IjweTB24IZgh2B28ITwhUCG8IJQgpCGgI3AfnB20ICwgWCGgI0AfcB20I7Af3B2gInwejB24INQg4CG8IGgglCGgI5wfsB20IWghmCG8IrgeyB20IkwefB24IdQd5B24IKQg1CG8I9wf7B2gIoweuB24IRAhPCG8IvgfCB20IhAePB24IOAhECG8IzQfQB20IaAhtCOwHaAgpCG8Ibwh2B24IbgiuB20IcAhxCHIIcAhzCHEIcwh0CHEIcwh1CHQIdQh2CHQIdQh3CHYIdwh4CHYIdwh5CHgIeQh6CHgIeQh7CHoIegh8CH0Iewh8CHoIfAh+CH0IfAh/CH4IcgiACHAIgQh/CHwIgQiCCH8IcAiACIMIgwhzCHAIhAhzCIMIhAh1CHMIhAiFCHUIhQh3CHUIhQiGCHcIhgh5CHcIhgiHCHkIhwh7CHkIhwiICHsIiAh8CHsIiAiBCHwIiQiHCIYIiQiKCIcIigiICIcIigiLCIgIiwiBCIgIiwiMCIEIjAiCCIEIjAiNCIIIgwiACI4IjgiECIMIjwiECI4IjwiFCIQIkAiFCI8IkAiGCIUIkAiJCIYIkQiPCI4IkgiPCJEIkgiQCI8IkwiQCJIIkwiJCJAIlAiJCJMIlAiKCIkIlAiVCIoIlQiLCIoIlQiWCIsIlgiMCIsIlgiXCIwIlwiNCIwIlwiYCI0IjgiACJEImQiWCJUImQiaCJYImgiXCJYImgibCJcImwiYCJcImwicCJgIkQiACJ0InQiSCJEIngiSCJ0IngiTCJIInwiTCJ4InwiUCJMIoAiUCJ8IoAiVCJQImQiVCKAIoQieCJ0IogieCKEIogifCJ4IowifCKIIowigCJ8IpAigCKMIpAiZCKAIpQiZCKQIpQiaCJkIpgiaCKUIpgibCJoIpginCJsIpwicCJsIpwioCJwInQiACKEIqQimCKUIqgimCKkIqginCKYIqwinCKoIqwioCKcIqwisCKgIoQiACK0IrQiiCKEIrgiiCK0IrgijCKIIrwijCK4IrwikCKMIsAikCK8IsAilCKQIqQilCLAIsQivCK4IsgivCLEIsgiwCK8IswiwCLIIswipCLAItAipCLMItAiqCKkItQiqCLQItQirCKoItgirCLUItgisCKsItwisCLYIrQiACLgIuAiuCK0IsQiuCLgIuQi1CLoIuQi2CLUIuwi2CLkItwi2CLsIuAiACLwIvQi4CLwIvQixCLgIvgixCL0IvgiyCLEIvwiyCL4IvwizCLIIwAizCL8IwAi0CLMIugi0CMAIugi1CLQIwQi9CMIIwQi+CL0Iwwi+CMEIwwi/CL4IxAi/CMMIxAjACL8IxQjACMQIxQi6CMAIxgi6CMUIxgi5CLoIxwi5CMYIuwi5CMcIvAiACMgIwgi8CMgIwgi9CLwIyQjFCMoIxgjFCMkIywjGCMkIxwjGCMsIyAiACMwIzQjICMwIzQjCCMgIzgjCCM0IzgjBCMIIzwjBCM4IzwjDCMEI0AjDCM8I0AjECMMIygjECNAIxQjECMoI0QjOCNII0QjPCM4I0wjPCNEI0AjPCNMI1AjQCNMIygjQCNQI1QjKCNQIyQjKCNUI1gjJCNUIywjJCNYIzAiACNcI2AjMCNcI2AjNCMwI0gjNCNgI0gjOCM0I2QjVCNoI1gjVCNkI1wiACNsI3AjXCNsI3AjYCNcI3QjYCNwI3QjSCNgI3gjSCN0I3gjRCNII3wjRCN4I0wjRCN8I4AjTCN8I1AjTCOAI2gjUCOAI1QjUCNoI4QjeCOII3wjeCOEI4wjfCOEI4AjfCOMI5AjgCOMI2gjgCOQI5QjaCOQI2QjaCOUI2wiACOYI5wjbCOYI5wjcCNsI6AjcCOcI3QjcCOgI4gjdCOgI3gjdCOII5giACOkI6gjmCOkI5wjmCOoI6wjnCOoI6AjnCOsI7AjoCOsI4gjoCOwI7QjiCOwI4QjiCO0I7gjhCO0I4wjhCO4I7wjjCO4I5AjjCO8I8AjkCO8I5QjkCPAI7QjxCPII7QjsCPEI7gjyCPMI7gjtCPII7wjzCPQI7wjuCPMI8Aj0CPUI8AjvCPQI6QiACPYI6gj2CPcI6gjpCPYI6wj3CPgI6wjqCPcI7Aj4CPEI7AjrCPgI9wj5CPoI9wj2CPkI+Aj6CPsI+Aj3CPoI8Qj7CPwI8Qj4CPsI8gj8CP0I8gjxCPwI8wj9CP4I8wjyCP0I9Aj+CP8I9AjzCP4I9Qj/CAAJ9Qj0CP8I9giACPkI/ggBCQIJ/gj9CAEJ/wgCCQMJ/wj+CAIJAAkDCQQJAAn/CAMJ+QiACAUJ+ggFCQYJ+QgFCfoI+wgGCQcJ+wj6CAYJ/AgHCQgJ/Aj7CAcJ/QgICQEJ/Qj8CAgJBgkJCQoJBQkJCQYJBwkKCQsJBgkKCQcJCAkLCQwJCAkHCQsJAQkMCQ0JAQkICQwJAgkNCQ4JAgkBCQ0JAwkOCQ8JAwkCCQ4JBAkPCRAJBAkDCQ8JBQmACAkJDgkRCRIJDgkNCREJDwkSCRMJDwkOCRIJEAkTCRQJEAkPCRMJCQmACBUJCgkVCRYJCQkVCQoJCwkWCRcJCgkWCQsJDAkXCRgJCwkXCQwJDQkYCREJDQkMCRgJFwkZCRoJFgkZCRcJGAkaCRsJFwkaCRgJEQkbCRwJGAkbCREJEgkcCR0JEgkRCRwJEwkdCR4JEwkSCR0JFAkeCR8JFAkTCR4JFQmACCAJFgkgCRkJFQkgCRYJHgkhCSIJHgkdCSEJHwkiCSMJHwkeCSIJIAmACCQJGQkkCSUJIAkkCRkJGgklCSYJGQklCRoJGwkmCScJGgkmCRsJHAknCSgJGwknCRwJHQkoCSEJHQkcCSgJJwkpCSoJJgkpCScJKAkqCSsJJwkqCSgJIQkrCSwJKAkrCSEJIgksCS0JIQksCSIJIwktCS4JIwkiCS0JJAmACC8JJQkvCTAJJAkvCSUJJgkwCSkJJQkwCSYJLgkxCTIJLQkxCS4JLwmACDMJMAkzCTQJLwkzCTAJKQk0CTUJMAk0CSkJKgk1CTYJKQk1CSoJKwk2CTcJKgk2CSsJLAk3CTgJKwk3CSwJLQk4CTEJLAk4CS0JNQk5CTYJNQk6CTkJNgk7CTcJNgk5CTsJNwk8CTgJNwk7CTwJOAk9CTEJOAk8CT0JMQk+CTIJPQk+CTEJMwmACD8JMwlACTQJMwk/CUAJNAk6CTUJNAlACToJPQlBCT4JQglBCT0JPwmACEMJPwlECUAJPwlDCUQJQAlFCToJQAlECUUJOglGCTkJOglFCUYJOQlHCTsJOQlGCUcJOwlICTwJOwlHCUgJPAlCCT0JPAlICUIJRglJCUcJRglKCUkJRwlLCUgJSQlLCUcJSAlMCUIJSwlMCUgJQglNCUEJTAlNCUIJQwmACE4JQwlPCUQJQwlOCU8JRAlQCUUJRAlPCVAJRQlKCUYJRQlQCUoJTgmACFEJTglSCU8JTglRCVIJTwlTCVAJTwlSCVMJUAlUCUoJUAlTCVQJSglVCUkJVAlVCUoJSQlWCUsJVQlWCUkJSwlXCUwJVglXCUsJTAlYCU0JVwlYCUwJVAlZCVUJWglZCVQJVQlbCVYJWQlbCVUJVglcCVcJWwlcCVYJVwldCVgJXAldCVcJUQmACF4JUQlfCVIJUQleCV8JUglgCVMJUglfCWAJUwlaCVQJYAlaCVMJWwlhCVwJYglhCVsJXAljCV0JYQljCVwJXgmACGQJXgllCV8JXglkCWUJXwlmCWAJZQlmCV8JYAlnCVoJZglnCWAJWgloCVkJZwloCVoJWQliCVsJaAliCVkJZQlpCWYJaglpCWUJZglrCWcJaQlrCWYJZwlsCWgJawlsCWcJaAltCWIJbAltCWgJYgluCWEJbQluCWIJYQlvCWMJbglvCWEJZAmACHAJZAlqCWUJcAlqCWQJbQl9CG4Jegh9CG0Jbgl+CG8JfQh+CG4JcAmACHIIcAlxCGoJcghxCHAJagl0CGkJcQh0CGoJaQl2CGsJdAh2CGkJawl4CGwJdgh4CGsJbAl6CG0JeAh6CGwJEAkUCXEJcglzCXQJcgl1CXMJuwjHCHYJfgh/CHcJggiNCHcJHwkjCXEJBAkQCXEJXQljCXgJywjWCHYJmAicCHcJbwl+CHgJWAldCXgJLgkyCXEJ5QjwCHYJFAkfCXEJ2QjlCHYJ9QgACXEJqAisCHcJPglBCXgJIwkuCXEJ8Aj1CHYJYwlvCXgJtwi7CHYJnAioCHcJfwiCCHcJMgk+CXgJAAkECXEJrAi3CHcJTQlYCXgJxwjLCHYJjQiYCHcJQQlNCXgJ1gjZCHYJcQl2CfUIcQkyCXgJeAl+CHcJdwm3CHYJeQl6CXsJeQl8CXoJfAl9CXoJfAl+CX0Jfgl/CX0JfgmACX8JgAmBCX8JgAmCCYEJggmDCYEJggmECYMJhAmFCYMJhAmGCYUJhgmHCYUJhgmICYcJewmJCXkJigmICYYJigmLCYgJeQmJCYwJjAl8CXkJjQl8CYwJjQl+CXwJjQmOCX4JjgmACX4JjgmPCYAJjwmCCYAJjwmQCYIJkAmECYIJkAmRCYQJkQmGCYQJkQmKCYYJkgmQCY8JkgmTCZAJkwmRCZAJkwmUCZEJlAmKCZEJlAmVCYoJlQmLCYoJlQmWCYsJjAmJCZcJlwmNCYwJmAmNCZcJmAmOCY0JmQmOCZgJmQmPCY4JmQmSCY8JmgmYCZcJmwmYCZoJmwmZCZgJnAmZCZsJnAmSCZkJnQmSCZwJnQmTCZIJnQmeCZMJngmUCZMJngmfCZQJnwmVCZQJnwmgCZUJoAmWCZUJoAmhCZYJlwmJCZoJogmfCZ4JogmjCZ8JowmgCZ8JowmkCaAJpAmhCaAJpAmlCaEJmgmJCaYJpgmbCZoJpwmbCaYJpwmcCZsJqAmcCacJqAmdCZwJqQmdCagJqQmeCZ0JogmeCakJqgmnCaYJqwmnCaoJqwmoCacJrAmoCasJrAmpCagJrQmpCawJrQmiCakJrgmiCa0JrgmjCaIJrwmjCa4JrwmkCaMJrwmwCaQJsAmlCaQJsAmxCaUJpgmJCaoJsgmvCa4JswmvCbIJswmwCa8JtAmwCbMJtAmxCbAJtAm1CbEJqgmJCbYJtgmrCaoJtwmrCbYJtwmsCasJuAmsCbcJuAmtCawJuQmtCbgJuQmuCa0JsgmuCbkJugm4CbcJuwm4CboJuwm5CbgJvAm5CbsJvAmyCbkJvQmyCbwJvQmzCbIJvgmzCb0Jvgm0CbMJvwm0Cb4Jvwm1CbQJvwnACbUJtgmJCcEJwQm3CbYJugm3CcEJwgm+CcMJwgm/Cb4JxAm/CcIJwAm/CcQJwQmJCcUJxgnBCcUJxgm6CcEJxwm6CcYJxwm7CboJyAm7CccJyAm8CbsJyQm8CcgJyQm9CbwJwwm9CckJwwm+Cb0JygnGCcsJygnHCcYJzAnHCcoJzAnICccJzQnICcwJzQnJCcgJzgnJCc0JzgnDCckJzwnDCc4JzwnCCcMJ0AnCCc8JxAnCCdAJxQmJCdEJywnFCdEJywnGCcUJ0gnOCdMJzwnOCdIJ1AnPCdIJ0AnPCdQJ0QmJCdUJ1gnRCdUJ1gnLCdEJ1wnLCdYJ1wnKCcsJ2AnKCdcJ2AnMCcoJ2QnMCdgJ2QnNCcwJ0wnNCdkJ0wnOCc0J2gnXCdsJ2gnYCdcJ3AnYCdoJ2QnYCdwJ3QnZCdwJ0wnZCd0J3gnTCd0J0gnTCd4J3wnSCd4J1AnSCd8J1QmJCeAJ4QnVCeAJ4QnWCdUJ2wnWCeEJ2wnXCdYJ4gneCeMJ3wneCeIJ4AmJCeQJ5QngCeQJ5QnhCeAJ5gnhCeUJ5gnbCeEJ5wnbCeYJ2gnbCecJ6AnaCecJ3AnaCegJ6QncCegJ3QncCekJ4wndCekJ3gndCeMJ6gnnCesJ6AnnCeoJ7AnoCeoJ6QnoCewJ7QnpCewJ4wnpCe0J7gnjCe0J4gnjCe4J5AmJCe8J8AnkCe8J8AnlCeQJ8QnlCfAJ5gnlCfEJ6wnmCfEJ5wnmCesJ7wmJCfIJ8wnvCfIJ8AnvCfMJ9AnwCfMJ8QnwCfQJ9QnxCfQJ6wnxCfUJ9gnrCfUJ6gnrCfYJ9wnqCfYJ7AnqCfcJ+AnsCfcJ7QnsCfgJ+QntCfgJ7gntCfkJ9gn6CfsJ9gn1CfoJ9wn7CfwJ9wn2CfsJ+An8Cf0J+An3CfwJ+Qn9Cf4J+Qn4Cf0J8gmJCf8J8wn/CQAK8wnyCf8J9AkACgEK9AnzCQAK9QkBCvoJ9Qn0CQEKAAoCCgMKAAr/CQIKAQoDCgQKAQoACgMK+gkECgUK+gkBCgQK+wkFCgYK+wn6CQUK/AkGCgcK/An7CQYK/QkHCggK/Qn8CQcK/gkICgkK/gn9CQgK/wmJCQIKBwoKCgsKBwoGCgoKCAoLCgwKCAoHCgsKCQoMCg0KCQoICgwKAgqJCQ4KAwoOCg8KAgoOCgMKBAoPChAKBAoDCg8KBQoQChEKBQoEChAKBgoRCgoKBgoFChEKDwoSChMKDgoSCg8KEAoTChQKDwoTChAKEQoUChUKEQoQChQKCgoVChYKCgoRChUKCwoWChcKCwoKChYKDAoXChgKDAoLChcKDQoYChkKDQoMChgKDgqJCRIKFwoaChsKFwoWChoKGAobChwKGAoXChsKGQocCh0KGQoYChwKEgqJCR4KEwoeCh8KEgoeChMKFAofCiAKEwofChQKFQogCiEKFAogChUKFgohChoKFgoVCiEKIAoiCiMKHwoiCiAKIQojCiQKIAojCiEKGgokCiUKIQokChoKGwolCiYKGwoaCiUKHAomCicKHAobCiYKHQonCigKHQocCicKHgqJCSkKHwopCiIKHgopCh8KJwoqCisKJwomCioKKAorCiwKKAonCisKKQqJCS0KIgotCi4KKQotCiIKIwouCi8KIgouCiMKJAovCjAKIwovCiQKJQowCjEKJAowCiUKJgoxCioKJQoxCiYKMAoyCjMKLwoyCjAKMQozCjQKMAozCjEKKgo0CjUKMQo0CioKKwo1CjYKKgo1CisKLAo2CjcKLAorCjYKLQqJCTgKLgo4CjkKLQo4Ci4KLwo5CjIKLgo5Ci8KNwo6CjsKNgo6CjcKOAqJCTwKOQo8Cj0KOAo8CjkKMgo9Cj4KOQo9CjIKMwo+Cj8KMgo+CjMKNAo/CkAKMwo/CjQKNQpACkEKNApACjUKNgpBCjoKNQpBCjYKPgpCCj8KPgpDCkIKPwpECkAKPwpCCkQKQApFCkEKQApECkUKQQpGCjoKQQpFCkYKOgpHCjsKRgpHCjoKPAqJCUgKPApJCj0KPApICkkKPQpDCj4KPQpJCkMKRgpKCkcKSwpKCkYKSAqJCUwKSApNCkkKSApMCk0KSQpOCkMKSQpNCk4KQwpPCkIKQwpOCk8KQgpQCkQKQgpPClAKRApRCkUKRApQClEKRQpLCkYKRQpRCksKTwpSClAKTwpTClIKUApUClEKUgpUClAKUQpVCksKVApVClEKSwpWCkoKVQpWCksKTAqJCVcKTApYCk0KTApXClgKTQpZCk4KTQpYClkKTgpTCk8KTgpZClMKVwqJCVoKVwpbClgKVwpaClsKWApcClkKWApbClwKWQpdClMKWQpcCl0KUwpeClIKXQpeClMKUgpfClQKXgpfClIKVApgClUKXwpgClQKVQphClYKYAphClUKXQpiCl4KYwpiCl0KXgpkCl8KYgpkCl4KXwplCmAKZAplCl8KYApmCmEKZQpmCmAKWgqJCWcKWgpoClsKWgpnCmgKWwppClwKWwpoCmkKXApjCl0KaQpjClwKZApqCmUKawpqCmQKZQpsCmYKagpsCmUKZwqJCW0KZwpuCmgKZwptCm4KaApvCmkKbgpvCmgKaQpwCmMKbwpwCmkKYwpxCmIKcApxCmMKYgprCmQKcQprCmIKbgpyCm8KcwpyCm4Kbwp0CnAKcgp0Cm8KcAp1CnEKdAp1CnAKcQp2CmsKdQp2CnEKawp3CmoKdgp3CmsKagp4CmwKdwp4CmoKbQqJCXkKbQpzCm4KeQpzCm0KdgqFCXcKgwmFCXYKdwqHCXgKhQmHCXcKeQqJCXsJeQp6CXMKewl6CXkKcwp9CXIKegl9CXMKcgp/CXQKfQl/CXIKdAqBCXUKfwmBCXQKdQqDCXYKgQmDCXUKGQodCnoKewp8Cn0Kewp+CnwKxAnQCX8KhwmICYAKiwmWCYAKKAosCnoKDQoZCnoKZgpsCoEK1AnfCX8KoQmlCYAKeAqHCYEKYQpmCoEKNwo7CnoK7gn5CX8KHQooCnoK4gnuCX8K/gkJCnoKsQm1CYAKRwpKCoEKLAo3CnoK+Qn+CX8KbAp4CoEKwAnECX8KpQmxCYAKiAmLCYAKOwpHCoEKCQoNCnoKtQnACYAKVgphCoEK0AnUCX8KlgmhCYAKSgpWCoEK3wniCX8Kegp/Cv4Jego7CoEKgQqHCYAKgArACX8KggqDCoQKggqFCoMKhgqHCogKhgqJCocKigqLCowKigqNCosKjgqPCpAKjgqRCo8KkgqTCpQKkgqVCpMKlgqXCpgKlgqZCpcKmgqbCpwKmgqdCpsKngqfCqAKngqhCp8KogqjCqQKogqlCqMKpgqnCqgKpgqpCqcKqgqrCqwKqgqtCqsKrgqvCrAKrgqxCq8KsgqzCrQKsgq1CrMKtgq3CrgKtgq5CrcKugq7CrwKugq9CrsKvgq/CsAKvgrBCr8KwgrDCsQKwgrFCsMKxgrHCsgKxgrJCscKygrLCswKygrNCssKzgrPCtAKzgrRCs8KEJoQvpyZGT6BjkKyoObhvpyZGb6BjkIyEJoQvpyZGb6BjkIyoObhvpyZGT6BjkKyEJoQvs3MDD/0VzKz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" + } + ], + "images" : [ + { + "uri" : "data:image/png;base64,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aHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iCiAgICB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iCiAgIHhtcE1NOkRvY3VtZW50SUQ9ImdpbXA6ZG9jaWQ6Z2ltcDozNmNhMzZjYi0xOTlmLTQ5ZGQtOWVkNS1iMTYwNzZmNWRhNDYiCiAgIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6ODBlNjQ2ZGMtOTQ4MS00OGViLTliMGUtZWFmZTI4MGI3Njk4IgogICB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6Y2RmZTU4ZDItNTczYy00NWQxLWJmNzYtNjMxMDYwMWUwNjUxIgogICBHSU1QOkFQST0iMi4wIgogICBHSU1QOlBsYXRmb3JtPSJXaW5kb3dzIgogICBHSU1QOlRpbWVTdGFtcD0iMTUxOTk0Mjg1ODMyNTIyMCIKICAgR0lNUDpWZXJzaW9uPSIyLjkuNiIKICAgZGM6Rm9ybWF0PSJpbWFnZS9wbmciCiAgIHhtcDpDcmVhdG9yVG9vbD0iR0lNUCAyLjkvMi4xMCI+CiAgIDxpcHRjRXh0OkxvY2F0aW9uQ3JlYXRlZD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkxvY2F0aW9uQ3JlYXRlZD4KICAgPGlwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkxvY2F0aW9uU2hvd24+CiAgIDxpcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OkFydHdvcmtPck9iamVjdD4KICAgPGlwdGNFeHQ6UmVnaXN0cnlJZD4KICAgIDxyZGY6QmFnLz4KICAgPC9pcHRjRXh0OlJlZ2lzdHJ5SWQ+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InNhdmVkIgogICAgICBzdEV2dDpjaGFuZ2VkPSIvIgogICAgICBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjlmMWNhYWU2LWNjNmQtNGI5MC1hZThhLTY4NDQxYTFlYmJiZiIKICAgICAgc3RFdnQ6c29mdHdhcmVBZ2VudD0iR2ltcCAyLjkvMi4xMCAoV2luZG93cykiCiAgICAgIHN0RXZ0OndoZW49IjIwMTgtMDMtMDFUMTc6MjA6NTgiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogICA8cGx1czpJbWFnZVN1cHBsaWVyPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6SW1hZ2VTdXBwbGllcj4KICAgPHBsdXM6SW1hZ2VDcmVhdG9yPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6SW1hZ2VDcmVhdG9yPgogICA8cGx1czpDb3B5cmlnaHRPd25lcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkNvcHlyaWdodE93bmVyPgogICA8cGx1czpMaWNlbnNvcj4KICAgIDxyZGY6U2VxLz4KICAgPC9wbHVzOkxpY2Vuc29yPgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgIAo8P3hwYWNrZXQgZW5kPSJ3Ij8+I3jfEQAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB+IDARYUMpaWVIsAACAASURBVHja7H3ZcuO4tuwCSQ12d+/fOj+8X85X3TjdXbYkDrgPJYQTyUWNlERSmREVVWVLJAhizVP4Z2XRhJdCL0B4NYK2QBCPEsSzpov/0TsXX1wm356bfAlao/ix6Ek6o3jRoulfPO0KWv+vKF7yQxAvEUTvgnibIH4gTBmFtkAQBEEQBEEQBEEQBEEQBEEQBEEQBEEQ5g8l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" + }, + { + "uri" : "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAACAAAAAgACAIAAAA9xURnAAAACXBIWXMAAAsTAAALEwEAmpwYAAAgAElEQVR4nOzdZ3PjOPAu+m4ApJwnemZn99zPeu8XvVWnzn/TJO84iWJCnxcMYlSkJJB6fuXSyLLHFoOJRjcA8v/7/wkBtPGp3wAcEw43uADN0TitP244sjAQRxsrd97WHu9k6//qzlbv53jb4ciV0JG34YKx7Yqxvd/dTXhLt9s09y6zEz404+XeadLtJO/zcL/0oJtz6H01gp+/wY9w88x3812VzKnfALhKnD95AQAAAAAAAAAAAKAfCgAAgHoPAAAAAAAAAADsCFkll6EAAAAADsCkYgBYCT0KAIBzwIgKMxihBQAAw0EBAAAAAAAAAAAAAABgglAAAAAiwhgTADgMjOIDAAAAGD9MzgAAGC8UAAAAAGB36A0CnB2MGAA4LrSzAACwv2lEcNPYiuNDAQD6YUj4ucERBwAAAAAAAAAAmBAUAAAAAAAAdoXyOQAAHALaFwAAGAgKAABQgSgTALaHVYAAzgjiBACAc4WQDwAODlmpw0ABAFbCH94ZwkEHAAAYnf3abjT+AAAA44KGGwA2hwIAALQgDQAAW+oeEYZBYjAQNEquwJEAANjVLsPn0S8DgJHAtcpxKADAOog5zhOOOwAAAMCUoChbwq4A2BVWAQIAGCMUAACgB2oAALANdAgBJg5RAQAAAMA4jSmOQzLqAFAAgA3gb+9sZck8HH0A2AxqAHCmECkBAMCBuNfEIN4DgFNx7HI4JigAwGbcCzvgeHD04dDQh5gkHFYYCJqg41jT2uMwAADsDalzAAA4CRQAAGADmAoAAJtBzxZgghAAAACckHvjsRDvARyCY3/oWxj+nbt33Rs7FABgY/jzA5wDALAB9AkBAAAAhoSOGAAA7AEFANgGwg7AVAAA2AALagAwDDQ4GznobsIxmAZclAFgUBjwATAiiOYABQDYEmoAQJVYDycDAPRAtxDOyyQCpI6NGP9GAQA4ZfcAyb2GBsEeANDhrkyti55jl8CRQQEAtude5AEngwkBANAP3UKAcUP7DgDgFPTEAQBgJygAAMDeUAaAfSBDPD2VY8rNFwC2gIYFYBi4CldhbwAMB6M9ANyHiBoIBQDYEYYeQBvWBQKAHugcAozFMsRDaw4A4CD3euII807CsbMAztdhT8XKFQ/n/J5QAIBduRd5gCswIQAAWjAVALY1vmYEodHY4HABwEnsmzFHcwMAAFtCAQD2gMgDVkAZAOA8rezRlmUADBaD1dB6bGHonSVEjAMAAOAyx3riiOsA9ufS37RLHLvc9XH/PaIAAPsZyZ8inAzWBYLV0Fc4S1x5xCkA4Bw02ROD6ywAHB5qAABuOmhYd7SYEcHp/lAAgL1hoDdsAucJwDnYvueHMgC0jbitmMDACCaaxHYAALhsgHS5e1dq1AAA4CDcu9yNEQoAMBD8QcImMCEAALqgDAAltA8AcEBoaWBK3OuDowYAAOAmFABgOO7FH+AuTAgAQv8AmspLAk6Ns4Vm4cQqBwBh3UTgegrgqmFy5e5drFEDgDb3ztNzMZ31f3AO7Q0FABgU/iZhK5gQADAlw/X2UAk4T2gKTgwHAABgjNzrg6MGcGiOHXAAGAEUAGBo7sUfMAKYEAAAXVAJOB/TaQGOHAgd8nedIKbb5PfhcgAA0OBeHxyrOwJM2wkuOe5d6MYFBQA4ACRzYTeYEHBW0CGYmAMf0OpVAefO9OCqf3qnOgY7/N7Gf8EVYQXsHAC3DTlS3snUGKYCAGzoEH++7l0SdjGNrXABCgBwME6GIDAOqAQAQD8UA6YEl3nHHSSaG/YnYqIQbA4nCUybk+PwUAMYnGNHGM7RyU5CpBn3gAIAHBL+OGFPjWgRp9NkoB8wMac7oCgGjNH0r+Wji39WvtvBtubQ+wTrTTRgVwCMwfD5cffaINQAAI7PscvAjqaxFY5AAQAOzMlhCDBW1cgRJxUA1PVdFdDndAGu2Wft+DdFwJ89AJwzJ2sAhGszwCSc+Ori3vWNXHxHHVAAgKNw8k8Uxm2kkwMGCXvHsrF9EPtPjPMHFAuGn8rYr1V7GVHks8H73GtrTrIfMNwUAMbjIFcsJ5shlAH2595RdZeTfwSwBRy+YaEAAMeCqQBwUO1A8pgn25HD2A1/nZt/bgj54dTafxk4K4fi5lUHTua0J8QGGbUppwZwXWvDPgGHHaoGQC5e5lCiBRgvJ64oUw7gDggFADguV6MQmKDVceXmJ+F449POd37av77x7kzoM4ljirWDtoVm/Axt3dVy4SxxJ8nkyNuAcTrD08edv90pcDJThqkAu3HvSIKjDnSqHH9NRxgQCgBwCigDwMmdbbx5qqWTznaHw5it/vuY8EmN9nkYTuZcmrZ5h1tskPsbPm0TvjwBTNcByx6u9r7Ld4SL1ibcO4AApzOKMNsxKADA6eAvFuDkVoTbQ/15IqKfsPM+uLv9iRxnn6F1hVPC+dfg5jqBAOCew059cLUMQJgQADAGGP4/digAwEk5HIUAnLudawMI3s8EDvRO0OKdl8mNdVi/QWPb3qkdIlyZ+2DPwEgcfPkjh696KAOs4OpBAxeN/WzZ9P07czVz412shwIAOABlAHDP8c/HMQW7Y3qvAAAwJGd6WyOBFhMAtnSMGgC5eylHGaDN1WMFZwQn4QSgAADOcDsQgWlz4bzDbUhhTHBeAmwI+fIT2uxKNZ1DhCszwFQc4zbIbve+cXuAkquHCOAgtjvhpxPDHQMKAOAYtwMRmJKxnGWd7xOhMADAaBy0c3KKnk/v7xxLy3o0x2ytERkATMsxagA0gt73OVcCHD4s5+7c0s5H29iz2qvHhwIAOKls3nEBgEFN5oRqb8gZxsRwMjjbAM7eCLq+21ypDrI5yP67A/sHxulINQAaQRmAzmxpILcPxS6mt0UAmRGd2ygAgNvGEIvAKEz+JGps4JkExwAA4zCCfPm0bN8KDnyIkP0HgCEcrwZA4+h6n8OEALePALhu8PMHw/8nAwUAGIMxxCLgprM9a1APgEPByQQARORyUWPXy5S7W7QCrskAU3fUGgCNZi5+9d1N5kLo9i6Hc4Ts/5SgAADjgTIAbANnStUkQ2Q4AZw9ADs7XHb5dHnr5m92IYN+8ssUxv67BnsJxu/YNYDMeHrfE5gWMIbdDHAoe53/LgSfI4ECAIzNSIYkwAnh1FgNxQAAgNNwsIvi4Fvax96t2pj2B5pwgHNymhoAjaz3Pbpuzhh2KpwvnJ9rjWsXoQAAozWeIQlwTDgjtjK6KBlOCacIALS4MglguAvU7luAsf8AcEgnqwFkRlUJIIcXRB3J/gM4kgH+IsY0fOOUUACAkRtbIAKHg1NgTxOYPAsA4LoDdVFO2vM5fQ1g6HZrl0EmR2s70UhvBbsLpiW7Lp34vB7nOLz2+z3ybhzbDhvSJLd98jnn42zdqPfh6N48CgAwFeMMRGAoOPIDwrQA6IBTAWAornUZD/F+jrONB74ubbERyP4DDO3Eo93d5sTOGf84vM43PuCOHe2OATiSIf9GXIuunYQCAEzL+AMR2AGO9uFgWgAAwPDGm3Pf/JcfdGTGsdqk9RuB1D8AnIITUwEy0+qAT2IjYOnc0sIj2tgRvdXJQAEAJmpagQj0weE9GlQCzhqOOsC0DdE/7v4Zw5YBTnQt6t4IpP7dN4ldN4mNgENxYipAqfpW0EkD2JJTtYpDv5OD/Pzj7kF3DtbmUACAqcPSQNOFo3oSqAQAAAzAtUkAB+017ZMVcqaxWUaUuNkvADjDoakAVRiNBwWcAtAwgVNipJuAAgCcB4Qgk4MjeXKO9jdgcDjGAAfiWg3gOL981JcUOWJEOeod5QLsQDgn7obl6IbDdDk1Zn9AB92ow+6xqR6SgaAAAGcGIQjAoDAhAABgd07VAA63ENDYdbVwB1zrAg0qAOzE3TIAYYEgAJjIn/54twIFADhXWBpozHDcHOR0lwN2hiMKcGhOpcydejMu2OwaOFheC5dcANjbCGJyFAPOBg7v6BzukB3pZDhwKDvqUxoFADhvmBAwQjhWLsOEAACArQ3eV9nzZgC01/uZQhFhjzZsxX9dtVvQag4L+xPO3gjKABn0xwHOAP6+XYACAAARIfIYDRyfsRhNrwNWwPEDOBqnagB7//dR1gAOf8Xr+A31l8a30wDAbWMamoNpAZNzDodx2IBnlOHTBo69UQfbj2M/OigAANQh8nAYDsjooAwAALCpwRcn3L8GQLv/hNF0Yk/VRK27r0DVOPakOxB2ALSMLCZHlxzgFA7x1zaZv+AJbAgKAAD9MC0AYAgj63JABgcM4CRcG0u2RxnA3RrAaa9v2//29v9wdMcCgNvGNCGghC75OOFwAZ3wNBg6Bp3G+YwCAMAGMAbBAdjxY4cyAADARgavAdDJygAO1QBO3vwM+gYaP8yVneyCkx9oGA7jeB7MuCsBhKsewAic+M90oBh0ShcbFAAAtoQxCKeAnT0ZKAOMA44QwGm5dkuA8odktvlRJ6sBOHIdO8rbQFoMAHYzykoA4arnOqeOyaHjENembu5s2N/r1Dmws2lsRQkFAIBdIewA2BXKAAAAa7h2S4DGj8ps9gOP1Jt1qlE53Zs565EqTp0DAKMy1koAoVcO4BxX/hD3C0Bd2YrhoAAAMASEHQDbw8xuR+GoALjDweWA2j+w1P+TB64BuHmZcuxdnV0lwLH9P4gpbhO4rnrFGN8ZiF65A7Djz9wEToAJbEInFAAAhoZlWYeGXThhmAoAALDGgbL2h2hcV17Nhbf5naNoGEbxJs+wEgAAA5lOMYBwBTyS89zN7qwCdPJ7Lzl3Amy5R5x7/4NCAQDgwBB5AKyDqQAAAGuMqAzQ/xtP3i/d18jbquMf8+MZ+aEBcN+4iwGEyQHHcM77dewRziBvftR7gMb//tdCAQDguNrh0uQvMwAbwFQAV+AYALjscGv4HCsaGU0PeUQXwy3fau0kGsfBWGdEBwtgEhpXjvH9CWKI3tCwC4c1xkkA7p4DG+wRd9/8oFAAADi1QSKmM7liwdRhKgAAwHqHyNofcWikWzWAEbU6g77VvAyAIbEAsLdJ1QNwJdwe9lnGnYWAjm9Eb7VhvO98BygAAEzCnnGWw5c9h98aHARqAAAAmzrQ+P3Dz1Y8ard2jI3Ksd5z7UCMdIWgMR5fgEkbdz0AkwO2ce67p3Vyrw9vttllOwdLRy4ejOA06NojI3jbQ0MBAAA2iMvO8OoIp4MawMlgvwOM1KFHL66+OOz0G/ftnU7menXqDWkeiHHdMvjUew8A1ppUPaBhFNfJAxjddm8dcuxxmq75XVsmXtyfB+D42+s0xvc8CBQAAGADqBDAcaEGAACwo+MvZbDr9dr9bu2QHG7Vug+E+xMCHN6lANCnfVEZ8Z+yC2/9iFdplxuEXRzs8O0V3rRnFfAuP+28QqyNnfk+QQEAAIawovk886ss7Aq3BQYA2JfzSxm4n2Te1Mibq95MgbNHaOQ7HABKkyoJHN8OO2ubS7qDl/+tneJ8GjL/LvWftvHPPUINYESnx4je6uGgAAAAB4aJk7AHTAU4HuxogMlztVrv1qoz53oxXJUpcOsIne8xOjc4zmer80qD82EwK3dlx8535Mq/gpMnx7AF9OVP22Zsx0FrAO6fF5mxvM8jQAEAAE5qk9Z6FNfsU4Udo9g5+0ENACYI5/TOzuCidxpunJPHX74ItuNCJcCNc/UIzmZDATaCqsDgNr2QYy/v4VBlgMZLNOiv2YD7QVrzHWJRJBQAAMBxQsSIOVaYTAUFYAVcBKB0oJMB10n3bH6ocfSGskXv+FSVADQHAFCBqsAKaBzdcfAyQPUL9a+dYd773LZ3cygAAABMnatrPmwOkwBGCccMRmS3tfJxkrsBx2EHfPA8xNBwmM8MDjjsbJJVgZF02tzlQh78EGWA7h9Y/02Db/vJ92Sn9e/KhZPgpFAAAAA4Y+1Y2NVGETWAw8LOBQCAnXvHB50QgBbq/OCYw+BOfqthV/tYcGyDN5hrJgRw/nSqZ+BUt+sQUAAAANdNuLlykcMLMKMGAAAA4LTBJwSg4T8/OOZwHNUL1T5nnWMdJhiNYSsBq8oAg9YA3Dnhdxyv4M4GHB0KAAAwAud9oT4dF+71V4caAAAcA1odOGMDnP6DxA9o788SDjucRHmtWnsGIjoYL2eDuwEH4HWXAY62WN9RTGU7TgAFAAAYB2cb7LPgUtCAGgAAAMA47FwJQEt/lnDY4eTalQA3OkBwLna7KVX7h3ROBRDe93w+4Z/DYL/6jPNKKAAAwGi4lIU+SzgAU4UON8AO8IcDsLkNQwj8WZ0rHHlwDXo8kzS63G/ntXGTTeidCjCu7R/f+3UaCgAAMDIOr1F/HhwoA2ASAAAAwOEcKkWAxhvqcEYAwJE50JfdV/vK2bc57Y0VId5144+50w77u0ZYCBkECgAAMGKbN34wsFO3mqgBAAAAwFRNNciZ6nYBwLhMoAxQtXrhoFN33Lczorc6OhMtACCyGAT+8mCEdv7rx/kOAABL4+otAQA4ZqOYHN32baFhAhjOVEcTtpdMqEa1nZMA1oa9h94zx97zZxnnO1wAQDRwcjgEu9n8OoI97BIcjc1xO444ydvAUQOAU8HVB6bu1I08jI/gwngE2MmHsOHFDjv/DEzvIGdbpLjerLvRxjvwFs6LwwUAAAAAOLTpxbkAY4Q+EGwCV2wAAADYUiPhf9r8vysxrxtVkGNCAQAAAHZ16lYTkwAAYPTOrO8Beyln4AEAAADsbJuO/CCxKgLek6jGjCgAAADAiKEGAAAjhs4Q7AAtHwAAAGzpVIP33I12Tz2ccSgbRoUoAAAAAAAAHN0kuhwAAAAAAG0IdQ9nh6EgKAAAAMAeHCibYygkABxE3/UNVxw4uaO0fA608AAAAHAYB27mxxFCjC3W2Sf6QwEAAADgXCGPCQAAAAAAcE42z3vvkB4fVUZ9NDWA/TvuKAAAAMDoYRIAAAAAwKkgDAMAABpHLn1khmphUQAAAAAAAAAAAAAAgB2NNfvv6iSAYYvrrhYAMIQAAAC2gUkAAAAAAAAAAMfnZAp9Y+7VAAZPbrhaAACAzTl2nQJ3HShB7l5jCQAAADBSoxzQMMo3DXAi6DrBhk59ad38VMVJPaxDHHkUAABGDhda2NzUB8lPffsAAAAAAGC00HmHrRy6fzvQSL6JnNfOjGs80DFHAQBgzNy4PMGYIEcOJZwJAACjsjbuw3UdAMBd6LzDDpzvv0/qvHagBnC4o40CAMBonfrCBGPlfAyxj0lvHAAAnIvdorzsfw3YDjrQEQYAADhv6OKejYMeZxQAAAAAAAAATmyLVPvKDiISBQAAALC5nYv9ExwlcLqxD4cO3lAAAACASUHiAwAAxmKCPWcAAGjD5R7GZu05O9mTeqLzH1EAAAAAAAAAOJ5D9ytRCwcAAIDDmWKGvOLoNYAjhG0oAAAAwNQg8QEAAK6ZeFcZAAAAYBqmGLShAAAAAAAAcHQTnV8MVTjCAAAAMDEIb4Z1nMGLKAAAAMAEYRLAGtg7AACHgV4xAAAAjNq5BzNTHKaDAgAAAAAAAMBeJtdPBAAAAGg6l4AnGzB3+K092sA8FAAAAM7F6sZreiPCMQkAAAAO7Vy6wXAsCF0AACBzbv13F01oKgAKAAAAE7RDI1X+F0QSAAAAq52yM4h2GgBgdCaURoRD2Ln/fuSg4BzP4kP+8R7z8KEAAAAwEefYGAMAABwRmloAAAAYxCBBxW6z3lf/akQ7TcdaDuigUAAAABixQ7RBU1o5Z0rbAgAThPGAY4BDBAAAAINwsP+++Vs694io3MvtxRPGsGtQAAAAGBn3G5f2O0QWHgAAxsX91hYAAADch4iigStD6keZKCjffbkBO1UCjrztKAAAAIzDMeOGfVrizvd5whsMjDWqAACAU3C9l44mDQAAYAxcjyi2tNvm9P0vXvcNbc5FQNWkv3S96BgUAAAA3OVeq7HG2jd8klsVQRMOAABA3egaXAAAAHDNqcKJAQe9DbUJg+8Kd5cZ6Hwf7lUCUAAAGC0sHDxR4z2q264e6EqDDQBwQmjNTwc7HgAAAPZ08HDCgW7zVtt4tPjqhMsMbKH65k4aeqIAAADghOO1Bc40j0dbnAerAAEAQAl5fwAAANjTFMKJA2zDSXbLOCoBVHt/cvQ9hQIAAMApTSZu2HlNQNcbaQAAmIQpNLhoMgEAAE5qCuFEBa/8dMWLO3/bQY2mEkCtt3j43YcCAADACbjQOjoCNQAAOHdYBUiTeCSeiEfsC3vCniXLFDPFTBFTTBQzxbs0GGe+awEAwBVo7kcLx20t13bR+NYcPnw9AAUAAIAjca1RHNCem3aEGgDKDAAApyUzkpmIV/2w4gl7lj3LXsrGspeSSUkJsbAwC3OqKFGUaIoVxdkTplhRpPLyQMwUk31hZ1ZYBdgdYhUAAHcgnNiQsztqfGWAUt+b3mNfowAAAHBYzjaHTkGCHgCcg8v3EJIrSd9Y+yaxbyK5iMlYNikpYRZWQlx8KMlS/irL/mf5fFFkOa8EdD0hy5xoilX6MksfvfRRp0+K4lNv84GgmQQAADg8twLAA7T+w97R163d1WVSqYbNt6R1YFAAABgzzCJ0mKNHxuGm79AN86QafgA4IVxN1mKK7iS+S+2b2N5FcrMQk7JJWds89c9CLKyKRL4SYlEszDYrBhBbEiZhElU8YbLV58tigLLKvJ3Tb9ouvORllj756aNJH5UEp94PAAAAMBKO9t+PaIc9MJad5tpUgGO8k9bvQAEAAGBIY2kCBzTgJiOrdgzYxQAOmkRF33oU39noLk3uYrkL7VVI2pJJxaRsEjYp6yTP/qvaSH6Vv2iJhbh4rGX/FQlnNQKVzxMosv+i2CpKNCdGX2jvJqCPmmKdvFzEz3786KWPyj6Pf+cCAMAETKK5nxgckBVW75zR7bozzzagAAAAsK/RtXwuO/NWGQBgdNJLiu7S6C6JbmO5W8gsIZOKTsmkohPOsv86yR/ZUiXvT8Wof+ZK9p+yx6xAoJhYNVL/+WP+OlvNqaHEUGo40ZQaSrS+DGfvNCc6mc+SZz9+8uJHEz8xpafeXwAAAHBS6L8TEfFe+2Gk+/Ccsw0oAACMHAYRnM74dvzZtnWFc27vAQAGt3hnF5+j6C5MbkLyUjEp6ewxyR7zpL9OlM6exMXA/zzdzyTE1eaUi8s0M1liYk4VE5MoIkXEJHnqn5bFgLwAYD1ODaWGEsOpzuoBlGj/Ipzdafqs7cKLX2bhwyz46qWLk+ywPaD1AgAA2A/670DuLQd0NCgAAABsZ3xxw9ggTQ8A4LjgVhafo8WneXKzEC8RnZLJH0lnA//z1D/rWKnE6oRVoohpme/PsvpEUrar9UoAUd4a5E1C2TLk/4E55aw2oIkNsfXImqwGwKlHafZcL2cGXGj/Jrh87118uA6+XQRfjZ3q7YKHg5gHAGBgGMB3dNjfmW3X81nx/RPYpWeYc0ABAABgIxNo5A7hQLvlDNtjADh3I8kIhNfyeh8vPs3ju8D6ifiReDGZRHRCOiWdkI7z1L9KlE6IrRARMVtNxMVGcvHQyv4L1XYEL/P+tSdC9ZKAMCdKx0oLWcPWo9SwNXkZIK8BGEmMGN+Y5OrN6+uHm+DbRfBVix18JwEAAMCJjSGqGqXJ7NhzyzmgAAAA46dIPBJPxCP2hT1hz1KsKGaKmWLKH3da9ncyzRtkzq2ZBwAYSnxBT/fx4lMQvZlbP7ZeLH4sfkheJCYmldAy7x8TExMLMYuuJP3rBYBarr9zHgCRVK/ZPcUAkuoHs1V6oZSQaLaGU4+MYWsoMZT41sRifPG8Oy+5fuO/frief7sIvqnh9hMAAACspPP+OxX9d9JSdN4r/fftK/TovA+lb09ObA+fJDlwqnQECgAA4zeSMYN7kos8xS+eiCfi2eyRiw9lUvYsm5SUJSVsma2iRFOiKM4eyw+mWFG0DC9sfS3gae7OA7UzB9tZyNQDADgi8ej5Pnm9D6L3r6mXWC+2fiR+JF5EXkgmIhNSNt6fmIlFFAtXMv5crwFQ/ZHqzxvtytYFgOKDmESpmFWkDFNq2HiS+ErPxMwk8sX42iSeH9+8mb28v3797i9+uloGQHMIAABjI5dlz738sORJ0X9P2Vj2UjIpKWEiFqaUKdGUaIpVvf+uKOZl/z1iG9V+F/rve5rmDoQ6FAAAwGnJjaRvrH0T27tI/JSyLL/KbgworIRYhIWUWJbsvoJKCbHk6wFYZlEsxU0H8yeUfWNeG4h0+uSnT176qOwz2j6HoAYAAHBaVtPzx/Tp4yL8+GK9OPUT60XWi8SvpP5NyCyKWKzmLMXP7ex/NuS/8ry2/k/lubQKAFx5Xn1SrgLEHdn//IkIkxCJUgmriFREKhYdSVYGML4YT5vEn0U3b2cv76+fv/vRL0QCAABwSBMewMcU3Yl9k9o3sdxFYlIylk2addtZSdYVl+J51qNXLMySTeGr9NmZhanai7dZeUDJwqRPvn006aO281Nv8kjxLufgJE/b88k5oAAAAO5RFN1JfJfaN7G9C+UmFJOySVlZZqEyVlg+t1nckOX1iYWUkDBZJlFZJYCEyVafl/+P2Srvg6ZEJ6+z9HmWPnrpo06feLclg8B959PGA8DIOJYReLpPnz+G8w8v6Sy2Xpx6cZb9Jy8ss//MVokiUbWk/6rh/2UxgHpnAEj1NalcsvvuB9BfACg/RIhYUarMiyg/KwNYPRPti0ms8bQXzy7D27eXLz+unr974YtLRwIAAMBh1qPoziZ3qbyJ7F0kV6HorP+ej9grc/207ITnw/TyzjtlA/ianfdGPSD/r6mmj5oSHb/M0qdZmlUCntBwb2ST3XRuu/JM8gMoAACAK6xP0Z2N7pL0Lpa7UC4jMSnplEzKJiWTsLZl408sWT2AWVQxqp+yACJ7FCZS1TAiG1CQBRuqGnikilLDidYXIb3R9Ju2C9M8QJgAACAASURBVD9+niVPXvJo0icl4al3jauOEBmcSWMMAOCUxY08/L54+fCaXkSpF1s/Sr3Ymki8ULyQTMQmZJUyMYsWYs4a3I5R/50FAFq/9H9G+j6pFgA6VwFqvyKcNymsOFFeRGomHIvyrY5F+9b4VvvaJBdXi5u3l0/frx7+9PbdjwDbQMADAOOSXlJ0l0Z3ib2L7d1CZgkt++8Jm4SVpSL1n+X6s8475cWASue9VgBYPnKr864sU6op1ZwYfRHSW02JTud+/DxLnr3kUaePipJT75r9udckTLsqcA5pBxQAACbBsTGDW0muJLqz8V0S3UZyF4ofk7FiEtKpZKGDzh4T1kl1EZ/uvD9bIiG2JIqpCBdqj/ksw/IVSg2nhlJNieHUUKL1RejdzOleS+QlL7P4yY+fTPKk0tfR7uXJt2awIZwJALDO0zv73x/z1/fP6SwqR/1bE1mTDfwPWaWKmLIFf1jlFfcyv99e/ycf7L/iBgCdn7YvWCvuB1Ak+rlVBsjnB1jKbktMzNlsAI7Zi0j5oiJRM6tnomOrfas9o+PZZWj8Nw9/zpL6KsMngOs2AAC4JL6VLO+f3IZyG4qXkklFp2RSqXbeTcIqrQzgL0fyZb34avbfElOW9C8W+lXc34vP+++JyYbxUWL0Rei/mVOi7cJLnmfxs588mvhJNW71B51WB2QrXtzqx5YQ15wKCgAwtHMonMFAgnu7uA/j2yi5WZBJxVgyqehs4EBCJs3jBp2wjlknrJJyUX9VxApcW0S4XFWAmFPOlyKWLC2hSJQQUzOMIJsVAAynHqUmG01AqcluQGSuFhfvFac6ebmIn/zFj1nwzdWbBE7UIS4quFABgKNOXdH/+Sn974/n4HaeXoTJbFFk/0PJ1vrXSTbqP29Xl21spQbQnApAXU+oZ/2fxqeb1ADqj83lgKh4wkRCYok4LwaQZRJWEXsRqSgvAyhf9CxVM8Xy8XdrvDcPf10ssBwQAAAM69TN/Q6spuBzGn4Io9vQXkVk0my+vpiUTCK6Pm4v77+n2TR8qmT/iahv3T9WaRlJ5EGGsCKuFwMUpYatKYbxmXIYHyVGX2jvJri81xSZ5GUWP/vBNz98QP/9qFaf2q17Pbli8ikCFABgaNP+i4GBzN/axW/R4v41vQolDx2SPPtfFABYx0oXAYSKlUqJOBtYSERFYKArdwtspBJWrRrMnBKXiQphq9l6lUqAoexJtRIwi2Zv9MXH2cWH6/k3P/yJMAIAAKbDKvr2Jf71+1N4FSSzMJ2FqR9Yf2FNKDpkEzEptqpI9Lcf6/MAOlL/1U+p1WpTz6eNyHJdAaC1/k+2pkDRret4ZBJWIXsRcVYDiBWniq1i+/5ejH/78NfVy1nmDsaWmwIAgMNgerlPF5/DxccXO4uL8f5J9kg6FZ2o+rg9pWJSlot2n7OfIkz5GILi51Z/x7L/3jHhrza8z0jec7cepyZ/npT1AE2JoZn2rhYX78zF+4vg+1XwzY8e0aztaPMdt+dEgZNnE6ddA0ABAACOKriV+edocT9PbhbWi2UWixeTTvLUv05IJ6wTpRPSiVWxymbrE5HVtcQBtxcQKJ6XJQFu3y2weC615YOZRKmIVag0kzVsvbIMUB1WILHRJvFn4cX7i+D79fybH/1CGHEM026JAcBFZ3Z1D2f07cvi8ctj7EfxLExni9RfWH9uvYDMgkmx1UTZnX67Uv+iKLsTQDvvz61XshWBlnu4va83KwBkTTm3CgBC7TJAvgoQl8P/K2UAySINq/SCOLKUKLLFh7xj8U3yw7/59e8p+k1o/AAA4KRe3tvgc7i4f00vIuvH5EdikmzIf9l/Vzqx2Xx9Hat8xT1mm7WbZTxAlU/rz/P+u1S+2OrIN/vvVulQ6QUbnRcAPG/Zfy9WB5LY0yaZXS0uPlwG366Cb148ill9p2v999k7++/Z4jyYshNuHQoAAFPh/CzC8Fqe7+PFp3l8G1g/sV4kfix+RF6YFwBUPuqfVCJM2Xh/EZVV+iWPGoonxVCA4sd3RhIrFgumenagfCJKJaxipYmsYZuvC5QFE2J8MbEYX5tkdhFevbt8+XEVfPPiZ7d3veNN6IqVHgAAztDRG/SXG/n2Zf788TmehbEfpv7CZtl/E5CK2WpmFlJcXVSvpwZQ9PCr6f6eqQDLa/6uBYBaRb/dplNtRaAsfJAy+99YCyjbBGGyxrzaLPvPVrEkbG/Z+jo1/u3DX75Nd9jBAAAA4zN/Ky/34eJ+nl4tUj+2fkxeJH5EXkQ6EZWn/lnFVieKuIwThJjr7b4Q88D992WLr1SsVMTC+bpAtljdN8n674k13rVJLq8Wi/dXr98v59+8NBhwP43S4JHmgD8Q6YEDQQEAAA4uvqCn+3h+H8Rv5qkfWy+2fixeSH4kXkgmIh3lS/xn4YIokTJoWLGSAC0XBJKuAKLaZJSzAaQzgKB6JCFEojhlnSgdlDGE6JmYWVYDsJ5vvOTierF4d/ny42r+zUvmw+2y89Ga97niG4eNADCrAACAiB7ep99/e52/fYn9KPbDJFv2xwvEzEmlSlR2D51NVv7JO/zZAP81SwCtWP+n8crOSwBROcmvUgloZ/+rKwJZImKynp7b5SQAm5Aokj++JL558+OfWRS4XfUHAIBRcHgAX3Arzx+j4NM8uVmkXmy9WPxI/Ei8iLyQTEg6ZpWwSvOmfzlZv7MLX3lc9tQ7KwGb9987uvDMiTIxk3DqsTVifNL5AD5rfPF87SVXt8H8/dXLj8v5V2OjAffZWK09B/f/hp2dZELAhLMEKAAAwAElHj3fJ88fg+j9q/WS1I+z2wnmoYMXkQnJhIqIiDmLG7gzdKgMJMyHFhJRT9zQsQRQO26gfJUAXkYP3KoBZPkCJss6VHohOhIdkZ6JnomJrfaz2QBXN0Hw7vr5++Xrd5OGg+7BaevL+Uy1yQWAyZhK5+Dr5+TH5+fFzTz2w9gPE3+R+oH4gTVzJlLZsj9Uyf5XlwAqb9HXbq95RQGg+mKp9Wm2e7mdHdmyANC8J3ClDMCVYkA+M2D5oTlUxuo8+28Ttort50/im9tvX6/mx1lHeBLnGAAAjEh4Jc/38evHILqbl/13yQb+m7DI/keqzPtXOu+Vgf+VkEAqd/HrnP9XHcm3Rf+9ewxf3n/nlHWiVShqJjoW7Ws9syYW44vxb01yfTd/fXvz8uPi9ZuWM5zbN1wUc4R4CBmCoaAAADAhLg0isJqeP6ZPHxeLDy+pF6d+kqX+bT31zyxKVC1okJUFgHzyYDvvXx9LmPfuN5hCKP3RQ+tDq4i9SFQkKhI9szqfDSDG0ya5up3P310//7h8/q4lGXh/7s7ZpnLFuTqVzBoAgLNij75+Dn9+egovw9gPE28R+wvrB9nYf1WM+hdSvKwBZEn/PO/Pfdn/rFq/pgBAzYa7iYmkPr0vs3kBoBza3/Mh3FgIiOpRheJYa6tEUikrAfLhvfVN+s1c//qpt9rhAAAALosv6Olj/PwxiIpZ+6mXZf9DWg78DxUrFk3lXX+kM+/fjArKAXqVX7hn/31NF55JiKzWC+ZI1ExULNq3ZiY6mxDgvTHJzRv/9d318/eLl+9qyF25p9N1hPuG523+/YdzzDLAVLMRKAAAwPBe39pfXxav71/SWRY3xGk+8D8kk2f/ma2S6qoCqz+o9ZzqT6jZAEnfJ71TCKVzEkBrzKBWYbMMoH1rfGuSO5Ncv5nfvr359e/F638uhREHcPxi01RbYgCApgNX9OdX8vXz4uHjY7HszyL2FqkXiJmLjpTVzbz/cs0fVXbypVYDoP62m7o+pdYWbtLrrO6X8nk118/1xp1WtukixEyWiKW8DUD9Q5HV+iUVm5LVJJqsYvuGxTepb26/fUVPCgAA9uDMAL7Hz+njp3nw7jX1ktSLrR9Zk0/cFy9kE5IOmUmJrsz/U/0BQN84gM4nha3772sbeqb8dsFBUQaIRC3779okxotv3vgv767/++di4fiN/QAJgf0gbAWAgf26T//742X+5jX1Y+tH1outCa0XWROSF7EJmVMWZtJl7kBIsaxc/Kd2S4ASlw/L1xsNwvLbpfLVRjwhRFLMN+wcJJh9dTlOkMUqtWAvEs4qATPRvs0+jO+ZxL+8fvBvHr9ieGCPtcHVUdp2BBAAcIYij/79HPzMs/+L2F8k3iI1gfXmrBK2iriR+lckZT2g0jr3ttqdBQDKX+yexkf9r2Sk9dWy1eZKC06tsf/c3bJT464ATGS7f7lYT79oSVOxSqwWG4toEvMlJXn77dvBOlNonwAA4PCE6ccf8a/fn8LrwPr5qH9rIuuFYiLyQtbVWfuVR1HE1BsDCNXjAap33ivPtuu/V9r6ylq+Pf335eAAJqv1nDgSFRfD+Py8C29iz4+9i5uHv65ffk58DN+ATlUtGXUX/rTvHAUAABiMMP34Ev/3x3N4FSQXYTpb5NGDCSVb8EeniphEcxYxNGOIVQMHsi5+R9aglkqgylerEcPyW1vPi7hh9VpA0hhdWIQRJiCVlQFmomZWxaLjhC9u7qwxifHvHv7yxA6xc0elnaEBAAAXCNG/n8Kf759CL4z9RewHy+w/WxLNy1v+1gb+57cCrib9e5fs6xv3134sbVIWLregfKUYOSnt1D9VGvHym5sfxbQ/m2cxatmK/COLPYxeKLJabCJWkdVk2dKnT89x/Oa/qU/4g+NAvAQAxxf59OOP8PHLYzwLk4vQ+mHqRWJCa0IxEZuQlSVSLCofsUeqNv9Plg1l/Qm1YoDiiVD9Rap8dZP+e+Vx9VpA+d196v13TrV5IfbyMXxqZlVstZ+yvXsnxkuNf/Prn5OmSV1qDFYEZ6fK/pe//dD7adRlhj4oAABMi5zsYhz59ONL+Ov3x2gWJrMwnS2sv7D+wupQTMg6VqQou80vtVL/xZN69EBduYPyhoDV7ewbQti4aPcVAKgVOlArhugIICplgGw0QSRqZtWF4uxugeL9r9T4bx7+msWLXXfrno7QMK57reNI7HGKTrIlBgDocJgG/Z/7+Nv9Y2iiyAtjL4i9eWrm1gRMzLVb/rY/sq6+WrnUbyP132jKqTsXUNO5zdL6hmpan+pNM/W041T5zkb7roikyP73Fu01R9pYRakmqyyRp24u+dMnE8c3Ly+n7QgfxAQ3CQAAKl6u5ecfwfP9U+xH6cUinS1Sf2G9hehITMgqVXls0Nl/71vFl6pD94ontK7w39l5b7zSGsC3qv9edt4bN/thIlacsnkhzofxWXWRsCi2iq33/yTGv3v407cTvTOwszn9bfWdMbACCgAAMIDXa/nxJXj69BT7UZb9T/0gu5cg6ZBJsdXEfXFD7aaCRJRHD0zVMGLLkYPclTvprAe044b2bABLxM1VgKphhNgyjLCUKrKKbMpWsXz6bD3v7uffl8HTuJrUHfVtJBL3AAAn9+1N+u3T88ILIy+MzCLWQarnqZ4vb8nTtfgPlYv1VQr2Pev/EFF1ZgAVJfvOFnx1IZ+6vir1V6pJfybK7utbf5HbA/8rhLLsPxdlgDx04PK3FB9CRKQoUXrOlslTYpVY9e7mJfmso+gqitbvfwAAgKYTDeD77519+P319d1L7IfJLEz9ReoH1gvEm5NKFTFZTVy2/p1z91UzAGg298tPhbiylX3l/z0KAFJ9vdF5b43hEyFixQl7kXBsOWWyiqwiUSS//Z4a/83Dn7MomH7/fbctdGe/IMmwFScLADiAAKPy66398eX19d1LPAtjv8j+e4GYgDhRRepfpB09cDHwvzGCgISYpTeAqD+hVhvUzhS0P5V6nLF6HAFXZhH2TgUgYcUxey/MVpFVnCVU7IcP4pv0+7/Xzz/OepWAvHnePF5Aez447E+AM/br2n799PJ6EYQmjM0iNkGig1QFbBWxrqf7lx/carKLhHj2KXWVAVo1e6H+RryzVehrxxsJgla6v9k6lyWB1R/FJID8SVnDsM3fLsyUGh2QVWSycj9/vH2Kf9N//s/MDniNxeUaAAAO5tun5OHLS3D7GvtR4i9Sf2GL/juT5aL2Xw4CqGb/uREPVBp9kc7FfxoBAHU1/XsXAGpPqp33zkkAxaOwVqEyWfbfZpMAFNtP9+Kb2x9/X80f3cl1H1vflru2R5Az2JyTBQAA2MdxBxF8v09+fHkJbuexHyb+IvEX1svGDgTEokQXA/z7h//3Zg0644ZWyoCL57ULP9d2RP6vVL6nHkAwEwkL9YQRlTJAEUZIYxJAOaOQrNYv1rOKyzBC3rD1TPrdu3k45pKCR28J1553gzTPw7bxiBgAwF3DNejzmfzzMXi6eQ1NGJkwz/7rgIRbi/7XPooFf7pqAGtS/ys6/13Z/97WvN2IV9MEK2oAVL/G91UCatl/Lp707XsmURSzDjgvACiy6svbxzh+988/3oaHAwAA4CQSQ18/R//99hReLuJZGHsLO1ukfiBeYE2Q365vmffvXbl3s6a/pwBQTgZYtvitAkD+QrsAULTyTK2B/11j+Gj1GD5LRJpjra3yi/47WcXy/p31dfrNv376rvfZ4ds5Vr/UtST+mTh52gEFAADYUarp6+foZxY9+GHsh0mx7I+YQAmzFIsJ5E+acQNLvoxAuaCw1Fb/p5UZhGrc0DmIoHKBldo/9efVOYPUEz0UH9naANwVQEj2erYZ1tOviqwSu0wPkPg68c3d93/8NNlpj08CEu4AAEeWaPrr/eK/t0+hiUK9iIqx/yTCtdS/LnL9ist1/7mzw9/q/GdD5vMkfmcLTj2fFs9XtA0deX/qT/pTxzeUK/uVv3OZ4K+O/S+eLDMOXLnnwfLdGFqwKicBKEn593dPcfz2x48jpgkAAGAajjWAL7iUr58Xvz49xbMo9sO4WPbHeoHoRTFxvxj7X1+/N48Q8uZ+vwLAXv334rF36B7V++9cjvZrTwLgIoZRZJV+1WJTkSSfym/vWHyTfvNuf/w9/cTphiegm5WDA2UYppe4mP55DACHsLiQr5/Dh0+PefTgLSrZ/4USRZSN/c8GDyoSxY3ooZE16I0e1hUAms9XvNg/gqD2SuPF1od0BhBU3xDrqUAbm4pNs/UEWRTLly+pb958/ToL5262nscwvaYUAMBlf76Pfnwss/+LRAeJDoSTfJYeaVlm/Ku3/O1c5Lf9nOoNOm3WdneWAajrxXZav3yx0Z60avO17+GikC9CwsXzYp2frjJA2b7n/z8PDLJ3YFRASpFRYpV4TFb9/u4ljm8fH/de7g9t5PnBMQeAQ3u6s18/B8/vn2I/z/4n5cR9FXM+cb9xE6DqUr3N/nulQL669efik6H67+3HRs+dita/2n9vTwWoLfTHZD39qsVqsglJvqIvyx868c3dt39nyTnd7Gd0qQpkGDaBAgDAFB14EMHzrXz9PH98/5wN/M+y/2m26L+K2WoitSqbUEQP9aChnkQoP81mCDbvB0CtLVy7wY2d0goduBxN0IgkmLJMQfOjEUY0Ywgi0hxqY1OyWSShySqy9/fWN3dfv14+Px3ylgCTbgDRwAPAudi7Qf/7XfL1w1NowlCHUbH0v+VI2c6Wuru9rj/Pn/S34NSRCyi7/9mntUH9xVPpSQfkOf9Wrp+LJ3lyv+v/dpQKyqRANfXfzv6XT0rVyIGYxOOAlWKtyGOy6s0V//Fex9H1/AxuGwgAACPy80P67dPLy91r7Eexv0iK7L81AXOqbLnu34ouPPd04estPhdPBu6/l88b/fdycB61eusNqjI+wFbec+3DqIXSVsmy/67J/vY59c2bb/9ezA86hg/92/0gRbAWCgAAsJ3XS/n7y+vj26dq9JCaInroW0e4dgehzmmD7Q8qcgS1hAIvEwStkQTlJ7WMiTS/VH6ZKwGEZEMCOwOI8oeXr1gSIlLFf7GVX1cbCqEoVtpqKcIIsTHJu3ePWtk/05vX1+nkCLbaEjTPAABH8OPW/v3+OZgtFjoMzSLSQaIDqwJeTvOvttT1vH/H4j89s/e42kz3PFnO96834uXzFa3CslpQrwG0SwK1x8aXsh9V/ZLKcwfLSsCyJMBii1dU/ia4UbYXFutxQEqRUmwUWX5//Rx/0P/738v4jNf6AwCArR1yAN/Du/Tv35/m1/Ni6N4i8YLUC8QLmIilcyJgGRWolZ13osZaeZ1zAav99+Zmruu/S/WVcjEf7kr9V3vr0jGGL5/5J/kb5ubovYzmSGubFp13LVaT3H/4pfnNn39eheEuh2AappO5OFcoAADAFiJD/35ePN49h34Y+8Ey++8FzMSiRZrRAy+zCSuW/enJ/ncMGWBpBA7LnELdsu/PrZeK4QPSmS/oDiDq2QRVjyqoskWNSCJfUlBJZSqACN9x/Fn/+edVdE5zCavcqQG4804AAAb0dCl/vX99vZpnY/+z7H+qgnzd/+6CfZ7lF1JcW/ynYyrA8rY90td2txvx9qfU3Yh3K5rv5oudV/H269wMGvKvtwf+UyX7b/OtXuYg8pIAEzElvgpYK7KKtCJPfb57jGP9v7/6G29UHRokGCOctwCuerqWr59eXy/nkb+I/SDx8v67mECtigcanXfVs+BP7YPz15ePrVeqRf2WNf13KYYLVCfkNebut/vvfR+NX1f7UJwo/VL03K0mUSLv3z7FsfrzzwvbXTiA00O/fjUUAAAm6jCDCP75FP58/xh6UWQWsRckZp6N/VeyOnToX/mnOpqgkkGo3HCPK1vSmURoaLwoXV8qxxRUs//USv0XT7gyjkB6AoiutqbyRvMlBVPPKhGyLJY/vn2KY/3nnzNBMwUAAH12atBjTX+9D37dvIQ6Cs0i0otYZTf+JWqW6rmnvV6xClBfzX5V/b7y2Pmkqt3zb3xVup7UH7kszze+ufFzyn585/o/lotVgIrjUP6QvPXXFBErytYC0kyGv7x9jKJ3f/+HfhYAAJxS4NPXT/PHm5fIC/P+uzdPTUA6ypbtXdlz72n0y/570Xlv99zL4XpSqwHQuka/+E/NL0n9eVejv+yVc6P/Tq3+Oy/v9JN92PabYLJGvyhKlVglRJbFqk8fHuNY//OPt/2hWOcUCYG+6HKTgwTbciHlg8AUADb198f4+/1TaKLIFHcRNHNRizx6aC8mkDWrWWGAWWo3EWon/StxQ3UsIdO63AGtbI+qX2qMIKB6xJB9S6USwMX3dCf9yw9LpPKFCJiaFY7KZmq1UFrYMFlFVlGqfvvwGMfv/v136BjCheZlA/uU6FHeBwBY4ftN8vPmOcxX/smH/wulqmvxHy6W6RPmyry9asZf1VrnjqUAaN0TqjxZnQhofMOuF/tV/6+SKRBVtPjthYBskfPn5c0AuF4AECEik90MQGUFACWp+ngXPLzcLuId3zsAAJydoQfwpYr+vV88vH0OvSgyYWwWiQ5S/Sqc5Iv+t+OB6sq93FP475jKX5kLmHeHlz357FEqVQFafqlTexBAowxQz/tX5/Rz2bjzms47CWVr/fHyl9XekxCTNWquNLFhskyWKeXPH5/i+O2PH3qnY+KoKSX3kSVYAQUAANjIt7v06/3zwgtDE8bLuwgulBRjB3pnAHBRBihDhHYkQT2vUGUZQaq0TWtzB53aoQN1xBDlNy/z/uV/z+4TUI0eqDZUULh9K+AqzQvSiowiq8hTZPm3D89x/Obnz3HHEFMKGgAAxm6h6cftIlJJrOJIhbEOEhVYivsX/ylaZ6k2053r//R1/len/rdtxDdvVRodvb5P1/UHhVqpfyq2vbEQUHMGQPZoeE5KkVY2Nak2by9e7+8u/ufnlgV+9FkBAGAgf3+Iv398DE2YZf9jHaR6bjlRkt3sV9fjAa4+L1YC7BnD192LpyJCoOJ1agUAtGUTT+vG8NUfa+P2+tQn/Al1bhrnj2TUPO+8l/33jyaObx8fVf+vAHARCgAAsN7jVbZ0YBCaMDKL2CxSHaQ6YFHtRf+XwwfK6KE379/Z0FIRatAyYmCqxApZYaB8nWqvZ9oV/OU3MYkUP7D8KX33A8jHDhR5/wZF+euNFQOq72IZEmXv26hgWQOwfH2hvnzUcXz79HSOMQRK9AAAg/t+E/+6eo1UHKsoVmHMYZIV7Jupf64/72ypVdFAV79EAxUA6s/Lp2X6oPqNa1oL7soRrP3OBiXLlQEaj9keqNwTePkke8fWqEDYeMpLlZdo78Nt8PBiXsPNMx0AAADD+Pdt8u3+aWGi0ISRCWITpDqwHBYr/+iunvuK8r9q3wCAO2KAymNH/73/DsC0ov9erPtf6b/TBv331tC9rkkAyyd9hIg8rtQArLq7ev3tXsfx9Xw+UBPvfH94XKHMgBmGiSUrUAAAmK6BZhEGvvz9cf508xKaMNTZ2P95qgKyzMtpg7oWPYgq1gdQ7eihvwBAUqkB1GIIodqntdepeztrl2quv8TFCL5aoMDd0QNVw4iu6IE6AggpV0goJwQUOQKxXnbDQKPEslj15oq/fNRxfB0EQxywKbVRMF44DwEGtE2D/mrkx20QqThSUcxRwlHKIdnVpfqsQW833Ms5AXkDvVz8h/oLAH2p/3UFgHbDLZtv+Yrv3KA20PjVy7RF+aiIbLYWkBTZiMr/FCJRlBoVWuWnOkqV99af399dvH7f9W7AAAAAO3m4sf/ev8xny9F7iQpSNWeruXvZ3rL/vr7z3lzqttKFr43h65wKUB2V17ZN/52X37ZV/z3vvDfH8ImqvCmuvj0mYrE+l/13JZY/XD/H9/p//r6MsdbfShNLoO/MkZ2AAgAArJIo+vN9+N/b59BEoQ4jEyQ6SHUgJKp/+D9Rdc2f7kEE1HG/oK7U/5rcwSa9/eqL1BMcrPgJfWFEI/VPXCsDWBFVf3PL36LIelydB6A+3DzHH/X/+ecySXo2aDK2SuYcC0ITAJiMb9fx48U84nz4f8KhpYRlOd6/6w7AXCxz36zNc6P5ri3+Q/01ANqlANBhbYOxO1igUwAAIABJREFUYd5/KypfF7g1CaD+JBt8WM4DECbRFGrle8pPtZco8/Fq/nDpPW1Y3XepHdqwoQYAgCENMYDv5UL+/jB/uZpXb92X6nllGcDq8H9dhgf1ZQDXlAFomfdvNPRdef/uJ1WNdH/n61xUFRpJ/+o3d3bwubPz3jmGT4g5/2qt0q8oKcfwZTfz+3z3lMT6//w9jjI/b3xmDRIAdMZ5LoU5ZwoFAABY5a/30fePjwsThnpRTh4USjonD9bXDSyWC6jePqgnv7CMD3ht+qDzSetToeJHlZ9Xv60zs798LKYCUL5eUO2bSyqvBHB17ACVyYLWkkHNGMLngJXKywAe//bmKY71//w7jhhiQDtnaJC1BwBoePLkx002/D8uh/+L5XKwf6Ub37nyj2o9rvigrQsAy5Z5dSJgtVaCRPq/tKPsh5Tpfmpn/yu/WIoXRVHqUWiV7ykv0d6tF3y8u3wKZkO8JQAAgDUiQ3+9X/y6e16YMDSLSC9iHaRqTkJcLAMolaEAjfCgmO1XiQq67/pz6P77Vpn9sudevLi8Ww8XQ/eou/NORFIbw9dq4ssnoilipbL7/WQ3A/jy9jGK3/37feJp1W3jqr7vP2bnHYmCThM/UwHO3X4d4X/eJv9+eCrG/i9ind34N1Kd9/tdvtgZNDTyCMXrTLUYQtqhQyNc6IweujayZwqh5L91RQzR/pQr2fzsFgKyXNunFjRUnkglgGCqxCXLGCILINgoskos//72KY7f/vtzjyvzkRu6oTItAACw2mbX269X0fPFPMv+xypKOLSSsmjisjCv6sX4xpJ99Q9h4fZk/50LAO0FAdrPB9kR1aL+dormWhH33AdY8ifF/hRiVSwxzCRsKLIUpsr3lZdq79PF/OHa+/WqVv5aOGtIUgDAUP58H/549xiaKDJhpINYB4meC6dKtDSnAKralP1qMNAxaK94nRutf6P/vnn2f3X/nRuflx1zyTvm7QTv8hVuvCK1V7s671Qs9FepAXCzAMBEhvIBfGyYLItVf7x7juO7n790x+ZsaEJtwNqoq31c4ZhQAACAbj9v7N/vX4LZIsv+ZwFEqgIlumvln3o2oRk01IYW1lIJ0g4XNgkgdkscVAOFFan/FQXjahhRDCLonkJYH0SQlx4y5YBBMhwwq2IeANMsiyHe/BzVDYFRAgCAs+Nk3+W/mfy8mUecRBzHHCYcJhxy1jOXzva6/FKrk1+burdtAYAanxald6q3GDvXAEpliv8A5WhpbA5VigFMZfafVHGjAlUsBJQaijwOrfIS5V2bxafry1+vFwO/PQAAmKQ9GrQ/38f/vn8KTRSaRaiDSC8SPReOuXkfIC6fS3l338bggOaTaue9VQPo7rn3lQFo4y1spIs7++/VR+p6UjwX2aTzzrV6fyYLCPIuvOE5KbWcx3/B/+udjuPbp9dz7xPvlpQ5B+5sLAoAANDhdSZ/vX99vnwNdTZ5MIhUkKpgRfSQPZfmesH90UM+eq4IF7gngJDyq7RsVrqHEG6i0txw/ZXG0IBG9CDU11TJ8iZCVKQGyifLTEFjOiEXj0YFlM8lZNKKLviPdyaMbl4W040hWnFtx551p50EABiJr5fhi78oh/+nHIpYEt2z/k9/0n/TD+r5lJaPTPVXOp90ftqp2n402xJufGP3/9qUkOKOewBQZY+VWYMy+59XBQyFlnzLUaL9VMX3F68/b/2fzytL+2j1AABgD9/v0n/ePy+8RWjCSC8iHSRqnqqFss01e7tvBdQe0pdXu/u68H39d64v5lMmzw/Tf28GAo3+e/2/5xSRCFkunrdu+aMqP1uKXnw5D8B6PM8LAFqR5jdX/L/e6f8/vA5HcjM/ptquO0nS4dxqAI5AAQBg6nYaRPD9Nv519ZoNH4h0EKsg1UEx7H3FRyN90J39l45IgipjDKkjkqjN56tuT9+2bdCmCFcWBSr+izT+Y/Pn9O9LJSRcG0RQvttqn78VQ4j1ypsBaEWa31693N/NXhbb3wxgRK0o1g4CANjWyivnjwv747pY/Kcc/m/LSf1FW7wc4qe6l/eRosPP1XaZulpw6ioAUO3Tjj7/6ka8bwubVfqu/dIYCchbNTbNckT5zpdDEPIyANfiFkUsReOeLwTEYg2Flr2U/VR7Vzr8fBH+93JpR9RMAwDAeCRM32/DFz8Is1v3Ff13top4Rc89DwmKeQCqPpKv9ryYzFcNGKgVD1Cla0+VpnXn7H9dpauevx9Z1bJWFv5ta63/0yj5iyoy5eUvzB9ZUqPmywF8Rn24eXm8m/31sH1+1bHAYPOwbLdvOybUGNpQAACApseZ/LiZRzqOVBTpMFZBrAKhVImmztX/u8cV5o9loMDLb6NmuqEjbmikEqgnblhRAOjTbgt2eWVZM1iGDlSZAZBte3XYYDaUoJy8sIwhFKVeNg9AK6tNqr0PN68PL97j3MGW9Cg2bqvRrgPAoYyqTilEXy/DwCxijiOOYo4SDkWEqbxDT7MnX+b6qx9FS62EmJtfzbSfN9tuyVPmg7Tj0vNt7cOz+oDtsFhQ/p0ijc1pRTJ5TaW2EFA2CcCjMGUv0eb+4uXntf/1ZY81ggEA4ExsH4F8v00frl9ilUQqinQQ6SBRAWfNU3f/vWjru7vw3KoEdPbiqedJ47HzSdtWHTuuRwidA/iWr/f/1r7p+4rJFu17dkCWnXci0ZQwz0kxaW1TnWjz8TZ4eLkJovHEjsPZbZvRkT8+FAAAjmHFuDUHfb+NXmZBpOJYRTGHiVrYnqUDy5z+cvXA/imEtWGDjdsHLdf5qby+HDZYDyC4uiNXxxCdr1ZCqnqbs/xCM6vARFJ/kYtlBDPrRhDUywC0rATkMYSi2PBClJeqKFXejbf4eBs9zmf9W3diA5zM9dAWEQAAwM6+XaY/Ll8jtcz+p9nw/1pDvPk6P9R4pbZkX+urlVeKJ0ydLbgsm/B1TXneRnS2NisWAqoN/M/y91uWCtrf017/J39eiXyk/oSJOKsBpBz57CU6+nyx+BlcJ2nPbwMAANhJqOjHTRCqOFJRrKKEo4QXQpbri//wMgzoGRaw/KgO/C8au2ZXvdrcF6+v77+vbn/Xtc4rmkuu9yY7m/rlor7S6rxTebMibvXcK797+ag5JGVE+anyUuW9nb3e3138nx/emk2YnH3SAsgAHBkKAABnYJtBBA8zm91CMFtBuFhDoIgVGlMIJXtlZQCR3V2QGy925Bdaswg7H6lyAwDap8VZ8T8lz/a3wogVsqH8XA7/rz5WYohiQGFtISARIjEcWvY95aXKS7T5eP36cO3996rav6r/DYxNb2R2Aog/AGAEui6bKdO3y3BhoojjsuEuOrP1rr4U9fhGo1ztzNf6+Rt+UJEjoPxTyZq65lQArr37DWoA3S+1s/99zQlXfvqKskHPf6wtM9BRACh3mpDifCGgrK0vJgGw75GXspew+Xjx8vli9tcrOl8AADCkb9fJf9evkYpjFSfZIACK8qX/2+v/SBkbVLvw9UfpigSaC/au6L+30v279t+3+FZpNe0dUQRzrXeviCwxrRzAp0SIud15JybRHBqV9d/9RHsfb+YPL3db3MnvpJ3PfRL3A5p8H9yprUMMCgA132+iV29RDB8IE45EhEjnQwakHD5QJBRkXQDRTiu0+8/NT2lVAWBtyqDLiu/r/LnrfljPlVza0YMissUkymI9wY7FBIUpNRxa5Xs6SrV3bRb3d+F/r5ebvqmRao/dBACAbTx58ssPIo4jtRz+r0QXE//zBrq2dG93o1z9Bqp+yq0nrR5+Z+e/s/le3Yj3NcXtuZTt7H8+/L9Y87fxSK0aQPvXdb6YFQN6CgC1XaqKQYX5PjcSWvJTinz2Evbe+jEKAAAAsN7GA/jmhn7cBpGKY45jFWY3Acr67ELMUum2l7348h42tYasNZivN1Sgrk+p8inVX6f6xuyVeV7Xc+di6l+7w97ZhVckKwbwVfvvmTz1n43nU5TmNQAdJdq784KPt5e73MlvtPY6lnB0iEEBYOn7hf1581oEEFHCUbGGAFO+zk9j8Z966p83SCvUhhJQPcKg2utMPRmE6vPDNDqy/Hf5Lnq+s/XFyrstBwxKbSiBkOL8x+fLBTCpcrSgVX6qvER79xevD7ezH88bTwIYKST9AQD28OQlCxWnlKaUJBQlHJLt7NKvX/yn6DM3+vCbpACoaOKpq+1uJQKWmfYN2/Hqt2Xj77J2du3i/kUNQDq/rZ0daP53Xr556dn2cvfWbgNAJJoiQ6HhmaFEc3rrR9fm4jWp/xY0ggAAsKtvl/Hj5Wt1EIClpOi/V1f/K0oC1XiA60P+K19qLd7b1fpL9VNa2X/v7MhvSGi5hFBPo99uSVf23+uKkf5kmSt5/yI7UeyxLJJQlS68ULbcn/JT5fvKS7W5v5o/XHpPwWFyFEd3nM1gh0MhZ9/YblAAADi4sVw1hOjbdRh4YTWAyG8h2FgrsBz1X5s/WJ0NsHyUxoDBWnmAelIJVGQH+kKHnWOIzhUAmlmD5k/c7hBW44aeOwFkkwC4GUAwkUf5QkCJ8i60ub9ZPLxc2bGcQwAAcHRPJk4oTShNJEkpsZIs7/jHzMSVeQDVbjw1219pL3TTVwZYvlhZ56dz8d9Gw11vvmut2yateWsJoGZav3Pgf/l/s7faOcy/XUCofptQdUc1iwHL2EZYcf6u8g9NiaZEc2oovVGLO//mNcGtgAEAYAAvnvy4DSKVZHfvy6YAijA3Ou9lJNBYvLeV9+/+aA7vo65P6WD99zW6f9YW3Wfpef/VVXyLJ7wcwEekmIQpzfrvqfI85d2a4P728inY4E5+7nXwtz0q7lc5XC4tnAQKAADHs81S/Cf43V+v0oeb13wFYRWWtxCs3+A3z+xLNmi9Nn+wFjdURsyt+KCeT6kndOiLGzbcr6vX/933+HDzaeOxuZ4giWRxQ/FiNlow9MhPle8pP9Xx/ez14W729XFdsmASLRta6EnB4QQ4kHpj9azk2YvK7H8qSVG552WHv9pGr5qlR5XRfNRqoKsar7fb7h2acup6vW+Efjs7n+XduasG0LcE0IpGv/M7G3FL9Xmxk0UaLypJNCWGEsOp4fTOT/6ZowAAAADrbNJ/v4yfLuYRx9nQvYRDSylbTXl5XjX76b2L93Lr+YZdeKo/aTx2Pun8dJO90Nd/32iEX0Or504r7wRQbK80B/Dly/2xn+aT+M2niy3v5HdEeyU7DmmqHUfXNgoFAAAgIkqYvl0uFjqMVBxzFHOUciiWW6n/1ZFBRyWAqD5tsHZPIeqYEMB0mBhi9eIAjU+LdYSl9V86kw/VV5Y/UAlZztf8KQdIlrcE4MZyAdmLTMpQ6FG+ENBMR58uFw8v13G6bvtO5ORhxFTDBQCATTwa+6rDhNKUsuH/cdZ2N6ffreq9N18v/i9t80OoPnuAlo/L8XS0nMRfvFI0mX2NSfv11iSAPJyoNN/c+H6uf1v537sb8vL9cPXn53ujmt9v5vqru6to05kp0RRrSjSlmtJbE83ULLT1rYEzhlMAAHbz6MuP63k2dz8q7gBUrAG4uvPefIXbDVlv/71xlyCq9N/XdtsP1H/PX+Sy3W5ohQy1n1R7qQx+qOy5V3YaVZfwLcsATNbkY/i8VHnXZvHpKvrv9WLd1p3afqMfT54EgB2gAAAARNnw/+vXSCURR9m9g1IKObuFYHP+4LL5r/R1m2GEVCcBtMcb1lYMrD/vWECAuj6lrnanL0RofGnb4f+Vr0rrK6veQ/mey+iBajuBy+EDtSWDPQot+YnyEmU+zl7ub2Z/P+JyDQAATU9esf4PpakkKSVku/rt1Y/WYr4s7d47V5qz1k9YJtor3yONn9DxKEK8/O9U+e+0/DkdpOsb8sada0+y31KdClD+tlrqn5s/p/O31wcV5hmQxv5p7eHWzteUaE40JYbTW7W486+/L1wcGAgAACPy9TJ6nmW3/y2H/1uV99/LHnrP+LxaF17VGrW+4EG49W3Fk+7+e6MX337e9wrt139fNvHLGYJUean+W+qjBhrD/6kWLOX7qjkDgEiMhJZ8y36q/UR59xevP2/8Hy/9bT1qv3AKyCgBnI3+FHeo6dtlEKo44qgc/k+i6ksHVOKD/uUCu4YKEnUPRaSu540nVHmyYQzRtrI/v2lgUcYPxP3RRs/bW25Xke9YBlLF9MxyIGF2N+DIcphyPo7g88Xi5/wmjHu2DwEEAMBZKVqhgOnJROXw/5QSkZRJ9/TSK/+93VhL+5u54//WvtR4zq1Wr/ORVj7v1Gips5dqrXNXup/7v0pdTXZV30QBri/yU5sEwMs9WftQkmhJDCeGUp+SOz/5vvDXbTIAAECvh5n9fjUv5+4nHKYcFcP/uXMGQNkfl8o0tY54YM0HVf4XrQwAOp80nvfZtv/eXguo8zup/+c0fnUjtqk+b84AyL5qKLTkp+Sl2lxq8/li8fCKO/ltgZHVODwUAACAvl4mD5evtQCCImX12uiBiNZHCbX5/lnfmJr/kanesnY+dj5pPF9rkyUCOwOI9niBzkUGiKT6cl9gVP3oiCE0RYYij8KUveT/svfmXW7bSuNmFcBF6jVxHDv35p1zZub7f6l55yY3cbx12+5uiQDq9we4YCWpbqmbatVzFAUESZCibdaKgijfVD/er+v/bcpdfinDMAzzyrmR5nv5oEAr0pqUIdUZ/zBHRuOEkIL8COCfEpwOjo+g//9MCZ6T6YHfHyIffWc5EmbcAUnXf87YTDoIqNOCwHF/BOUDEo9LgJLULQWM+ko0Eiu9YDN3J72KYRiGORT5BL6/z7Z35UODTTd9f0tkkCSBwMh4j9ojHwCAcKagX7O3dQXMst+Toj/enP8UcvZ7+hSk6DDspS8OxzonkpexB5G+lDbh0SbwwabESkFVyebX1Y9PZ/VfP5a+6s/IQ2Rl4IksQdEL7oEDAAxz6twV8GF9vxXNFp3qgSRi1/+jcgSC2YKWSM/wKgJNfsNoe4SR3IGRnnycwHuhJu8huPlAjQgUCC8MUMDGQKmxqrDUoni/uv90V/xQr1wQ44tKype9+nPw+n8hwzwLi/l3dCNVA1q3MwAaTY1T/wcCKUwYiJ6+IA9CaMmDe1gKDISa0z98D6FyCg4Af2TrUBiX5a74dnt6b/4grFNLAYPfGE6MLzrE84OD3dDC8Lgihcf3myAgEkg7CQBUAfpKbC7Ls69bXM5fJIZhGOaI+Gdl/ln92IqmX/7X2u+OlJ+zet+I/Q6pfkvSfk8a7MlNyGwmyeXsj8z5m9ImJiRvrxUEvy5oBwl8rQ5gYwAaKw2lktv364dP9+eNyVxqCUw9rRyv3CVx/OT+mnMAgGEOy7KMu9Qr/qbUN9Vd02oPW4UbAw226f84o3RgUjkIO/PBg/5goEQ2Yv8dbwbtXA9EzoLgQcxJ/w/cCjOh6IfgoCJEHwKBQxgABah+EoDG8rq8v67Pf6goieDF/4Y9Vm9gGIZZOkt+vxE0oq3/o0gpUJqUASVIJur2dqf4DHsJEL0SQNgdEYsq9OVg5BGY+AZInz7SE9w6+p2YacS+gMkZAEFPqCegK9YJ/SOd55Z4/iioKdpCQHqF2+tSf92yFcYwDMM8hq9V8yC3W9w2YqNwo2BLREgC2vR/m7ondsn6z2gOHvEpkNoMvsEfbSe5n7THR3rc/qTQH4f8e471IlcXSiTwSdgUUJZQaSwVFD9V99fV+mO86s+L2+/M62X8Lxerngxz6tyWql0/EJXCjYINmCD9H2doDzA4EVzBGa406E8htAZ1pD045XUeoUPEjOgKk+kDKccBITj+gBCKr+L8QMx5B+JCQHYSQKWgkagl6quy+ROWPouQYRiGeR5uhPlebHS3/K8B5RT/zX78JXksYSM6ICY4PZZ33TBZCR4MPi7NAxdA35mpAmQbXi2g5PHJkdHZtlGReHzwn2oQRUh8BChpVEFagi5AX4stW2EMwzDMNJGXe4twW26t/d5Ao3CrcYMmYbwH0/5GdQNH9I/Y72FpX4i+k/qA2wjaOUak80j2Xi6Hz68FlLbfYxdBr8zYhwDhY/Es93azgI3GusCmQF1jc1Wpj7zqz2wCjYrZiTmPjlVPhjlpNo4CoUnZCsICvPT/OZV/Msek8+AG4UqBwgGR9jBHh8j1BIzMA8hNGBxLHuzKGuQuhN2V3EGCVRZdrcs+Q3c1YBSkhC0ZDKoAfVltV3L1oKd+KMMwDHMC3Er9IBpF3QrAdgGApN0edvZ4TgFfHEMsqqIRYlMfnAHBEXnJY+L2HAZBHLVjd3+8mTw3OfiwieEI7rDxA4f4TwHJCFKSmoJUgfoKtxfl+vt219/OMAzDnDq3hflWbBRoO/lPkyIDiIPx7hjyI0I8aDgCOmu/B05wyFjuc0R/rifYEbv++83x7L3YfofOzY8Y2u/tsM5F/R8SFjvqXR9DAh8BIRACCbvkDygJWqK+LBsB1ZKLAPWwRhLzlGjEM0cyZl6OAwAMc2L4Xu4bab4XD0MBAVBASBRWqM/69yecAoGFH28m9Q+Yp0M47Vnyyi22G2sP7i7MdEKsQ0SHxT059Sj+eN5/+2kVCFQF6kvxcFVdPNyL8IIMwzDMiUEIN7LRnf1vqDHUIIh0/Z9QWMSiHNxGVOcnJpb4EA2YHDwlxNObSULXfNdOOv3dkeP8/TgtAAI5junxwXl6STGczhOU1EhSBamC9LnY/CTMd57VxzAMw+zIbam3olHdCkAGmtb57BmVSWMz/PgL3o4bquDs6tvgn5X8dhtO+zH2eyz6k6PEMQD3erHgHrH6Xds8tNPBqQXkTp209nuBqgB9WWyuqrOvbrx/afZ77inm2fHwl2RpD/tAzP+ZHABgmJPmtlRbVMquH0jK0FBAgMYK98cKga8uoNMeYubuATs5DmLtwZc75G9mme84wJTq4J/VDubPJxxuhJwbI/83JrSH+GMfvoBGQiNBS9Al6KtKfbjnWYQheDLSnWGY5yY5N2wB3Er6JjeKdJsDCApoZNZdKFzsINh/pRuQdwEkj4x2YTBmRojvYEu6ghgiCd43ECAoAeQeAKl27nLJfMN+b/bjTKdon5sAJUlJ0gXZKkDNfzgAwDAMw0ziqCIEdvkf06b/g9KkkGKPf9ADGU3AkezoHJwQjP1sQojEHMyw4qP2XG0qtt+TjaTlHqTueRcd9qF7IQQCb8kfAPAWQHIFfb8SgPfwBTR2BkCB+gy3VxWv+sMcil2NEv6LyDCniwa4LRsF2hYQMKA0KfHUxYIQwM4hgMwxvVYRD2Ub8LQFAOLOOMsvOQNg5ymEiWtRvMs7Pak9jHyk9RfYJALUV8W2wEotw/00Qv+DGYZhmENwh+Yet238Hpz4fWfAI+AQHU9IS1cE90cEPTkhi5nNyNqnZP9T5Lh7wEh6flKUB8cEw2J01WTAgAAQvTHdu8LOvzAkOljZD4ACtCRVUCNBSdJnqCSCZknJMAzz4uyeBP1S3Ej6Vm4UaU1aWemfmnMGAJDN54N0Oz0O+gdAdCJkNscbLuNC/3H2O0TSH/L2e854B2dAjNpZo94a7xKVzeG7LJul+11Tf/+P5B/EQnke5e4RV1n2X0SGYQ7JjSRbQFB3BQRz+YNWe4h1CBwOgEzh4CT9mP2m20i6IcAfzc21nxRP7gGOgKdYjYjSBMYcB5BSKUb2ur+LRiZYBP2tDgG6AH2Jm6vq7PPGqQK0BPaqNMc+FYZhmJckDv4ugAbIABERGUNgjOjWhxmEuL1ra7QnxU1PbLHHewNyx2DmOznayK7wct2+wCmQiwGkB8kcFgjxZH9wbugF8MdEAsTUMxekkUgQCaISdckBgJOH//wZ5mhYxj/XG6nv5baf/KdJkUnb6f55sY0JUSMQ0PFQuXOTm5BozLXfg71Dbv7skH9ggLvp/jlxD8GRjqB3f3ss/UNDHsF4CXxic16sfigcfgrDPJnH/VXiAADDPCuLSC/obuJWqge5VaQVKGULCJoR7SGnFkBKNMKYaJxV/8dTFxLRAgL/fvy94Q8ODsim9XkPaEyHgF5/QG+0/v/kFCai7le49QH7s2KFzPcXgCpIFagk6hU2V5VuAwBLVSDcP7ml3iPDMMwuLO9d1kDr/DdIRIaI0Cu4B6MSvO8MviEzQqAb5C6RAt3/Ybxj7NzscG7EPeMLoMlJAPFouSuOuBvcwwLFKfAIWAjJIBECFWhKpIcX1AoXoZIyDMMwM+jt96JRZDrvf2NgqN+bk0GRBE9a8RCN4OLW/4GoERvyycMgU7b3cfZ7bK3nYgDgiezkPWB3JMXX6kcLrHjwj/HM+bboH+gC9AVurirzQy276B+rBEfFo+0SDgAwzL45nrfnjXSXD1Ka1GjRf0tSvYDUMZA6BQB63/1MHcLt75/uXMdBdHBuGmDsHYi1iuTp0fgUdA7noqNAdI860EECfwGCnQFASpIqQBWor0UDUI78ZIZhGObV04AxRASGDBEQgCEQCL7wAWgFCjpt+6FOsHrn5OR4AGY2Y/sfgdwQfnKER+hM6IvgXF5/0k0fn5i7DXJUjigGQATWq4K+OyDQAfpnTwiAiITUxgAK0uWRqIsMwzDMEvgu6Fuxbe13UgYUkUGQefs9MOTBN7TdRs7Y73bR+OkQGey2Ec/4T27GTNrvkzMAkgED8EW/25cw3u0INJT+yyXtQbApSRWkClASdYH6umj+u/xVf47Hi/U8LC/5p+UpN8YBAIY5Ub4J+la26f+2/g8RpVYQGlcjAnnf7oomGPa4RfAhEpkQjQyJ/mHIpIwKOim1NzeRMGXkp3UI90I5D0IywOAONfNjJDWSlERdgL4Sm6tyfdsch3zGBctOhmGY46UBQ5Z+BgAAtGvwRCLYfxF71vgQhh4aMwRM7FBIHpPcjBvj41hiaZ6IAeSr81PmlOSmezMJraBzbVB8X77KgkGUH8kgEAIJopJMiWxtMwynJ4cMAAAgAElEQVTDMHO5leZ78WDn7tv6P8n0f0yL18BbDc4m+Htjkh6AuJE358fs97hnjv2eNLGD7+BI73Kpn5qeZNDn7fm3hJ2+FDg0ENoZAE2bwAf6Cre1XG1U4pJHB2stkxzU+/HEwTkAwDAvxMu6RQlupPkhN7orIGioSSkQI2/4pBoB0VnOYRifDtG57mafNeAf4IXogxGS9+mM53VOTuoPejBSC7rDOicABv3D7bqKSKxDBDccfuwswoKUBH2Gm6tC324X+vZmnYBhGOYZsAEAA3YNgH5yukdgBAf94O3C9r+cBE+rBLkezPf0F5kvx90DUl77rEKVE7WY0Qeyg3ge/zbMEBwTP6WEVwWBEA0SIZFoAwAMwzAMM4sbqRRoBVqTtd8VGIit8tzqfd0wI8Z7JPe7yj/9lDhKeL1zwzqbof0+LvQfZ7/H3+6RwQjDLTl1e4NLgD+g+3HHjHUkBEBhZ/CTLlBfis11aT6ohS3jF8NpCcvm6VrjQl1IDMMcGlv/R4HW1OoQqUUCAdLaQyzk4kbkLKC0dOwbvVfA9/snBx+mFu5Cf3xSrsOoDgF+Ix5t/lUg9TCHhxM8cARsvf9tEoG5Fuo/S3t7s7rAMAzzXKihBBDZiQCjNsGIBPde3H6tnuQ7Pe5MDzVBeLfzz0X/5NjCx5TAHfEFzDGmEBPn2m1MDRLpP8M4xi4CjEBIUKKB5dcEYBiGYRbABuFWbvv6vXYBAEGBEPGM60gYBV77QB9ISS5K7sVuCM9aD7IK3MEdK3rOLMPgniEluCft9+AYiEz4uCd5cNqKj90jgQnfV/GVoCtU10J9gGq3n/4ivKhRv9+0iFeWZLGXn7MwFxLDMM/CvYBbue3T/zUoIo3kFhC05FwG7V7HC4/OZuzizykf/abbiL+7BibPSm7G5Cb7I4BdOxF2zCAY9/v3PTnVpD8m+ZC9HgQlbCVB0hL1ldicFau7I5lFONO/wjwT/OfBMMdP05cAamcAmG5PTqbEjMjTZI+7a1zgtlLM1xAAPLN//Orj5N5ic95uM+V4z8hcPXrE+xSBEAyCEURIxDMAGIZhmJncSPNNPijQipQilVn+Nxb9CTPTT6Qbt/3jocAZBPL2ey/w92K/B2Y12MR9zBrarmkfnZglNN67H+jOAMhpIOFHQCONtd9VAfpKbKWotEmdvTQ4se8J5vLCFTsOADDMKXIjzXf50M4fpMYMBQR7MHLlt/2Rnxqcw3IqAgAMUwhTBwSN5LD9nPtdVQf3sPFZhOBsBuY9plSH7tIUjxwPHgyb1iGcNRLaKZZWRWuXASBVkL7EzbXQdwtIGzx53YBhGOYF2AIp0GQXAQb7f3jyKzkpyicP3uHE1G7M7AgkqQ+N+/Ft77iPPun9H3cKkDPsHBI+FASDQHYpYAFU0lF4AhiGYU6AxTs9b6TaorLp/yZaACAzXz/pxAenJxkGCA7PmfBJh0A0fsJ+n/+gJ+33pKHtnp7UFtAbdSCevg/RVXb4tMsAkC5IX+HmWp5/Nkv5SzZxH4v/53Bq7CuuwAEAhjkAi39j/hC6Ad2WAAJloEnV/5nUIXZQIAgAKbl3OCvz2HJXgcxTDjqT+X25WYRxYd/kYRC1k3cTnxVrKnMeMoItI+gkEZwLvbi6AYv/az+H+a4dhmGYl6IBUl0JICIiNI96/7bi9bEv711PCrIFHcZkKcZHdP3j6fkzDx4/YL8ywfpRyK4BgMQBAIZhGGYuP4TSoBW10/cNKQSRNyED3L1J4z0+MvCShyZ8vxFP+JsaP3l7LhP2eyrxPzg+sLgh1U7eRtZLEFyI/HTJZM3kbhkAVZCusTlH/fmIHLCvwrR/HexREz2ev38M85p4aRdjWzrAGEJjyBjSSMIKLb/6PEDWKe/ujKMC7mFJdSR5Co5+R4N7Q46oO5YoHYB6T0QiQx/nTgLIXXo8g8Bm948oH5ECAVqQFmQEkTi2ugHsVWcYhtkjTTcDwAARGDLkCKKk2J10DbhHPoKkzf+Ia42L8kmLfVza5A7b9axYN5jpfwGwywDYD1FJRiAYFpAMwzBLYMEeT23X/jFkkIwxBjUABAl88yYBxHa32xNKc2duenxMTtbn7Pd4/b7H2O9D29kVWe5JiR+L+/hCrr6RUAAcR797ivswQ/sdySAcn/0OsOh/EZO8yLM+xEX3OyYHABjmMCz7dWkXDzRExrQFBDpROa439OqFJZbuIwoEAGC06g8COLPqwzGTDgWnkZhROELSaz/iC/C1B8JuCgM6F+4GSZYfGMsg2GEWYX8VtB8iBCpxiWmDj84jZRiGYebT2LV/of0AmVaSpu3wWJjmzPXkuSNO7URnJAdyonzWaKPgHD1gatDYHbAD+Vh+7AvodxACCesLIFMibI7NIcDsC/6TZ5ij4aX/uXZr/9h5f0TG+OItIeuirnHffV5aoncu2q/Wlo0t97z3H4L5BHOI7fegnbTiOzz7HUI/AEU9WeMduicQOP0n1CQb7B+m/S3Sfp/gGa37l/5HNrCcO9k7HABgmINB7cIxcf+L080AIELrQ4Bebrkv+fwLH0ele15KJDwUvTc/d1buQhDf8AzGpXvOEYCdlz/p1p9zrfhC2OsdM+4fEbD3/lsFAhFoAX+XGIZhmGemARrS/9sZAHMd9PGuvKCfP9pOxzyaJznrn5105APBiC6iX5AukTYcOWcYhlkIS01m6mb+GTJkwBAGnui+kUsmi63vnPEeubPTXvvAoZ8ka7/vyLgB7lnxGPSE9ntiBAybufR/cB5O/MsTwQAn5G8QFj0DYJF/65n9a70cAGCYQ+KGnN2eF0UBKDCt78BY9/+YjyAqaZc4pvtfUntInpXUAzDznb6xGT09SUUhN6yX/u+s+DepfCSvmDy93xWraNkxEQySQSJBVIApEJoF/HU6BMfl42EYhnlm2ig+9B+DJOyuHZ30aSf1AZgU2Ye49Jw4/RxpM8jxXGrH7PtBBGqXAgZTkikfPxrDMAxzAAL7fRk2SePM4LdpfIChiT0VvEia2LHxHmxOCutJ+33c+z/Tfp90zfebI+5+d5AR4vHbkXfRAdpH59rvRzQDYKe/WIxl76+KQ7x7OADAMIdnGXpDT69AEBEhERlohVHCH50aADMyflK6T2kDWdESX2JXx0Es73OiPcVE7r/12pN7OKaPD6MLqVsPggHDc0MkpC6DgHSJ0Ez97BcgoyKwT59hGGZfdCWADAEBGGc6WEKCn4bVNm7wj6X+zWG+ZIuPDHp6778AKkBXbFgzDMMskIXZLQ2QIk1g1/CziXzu/oTZnpIuO7nm07v6KYdtY9p+n3P1kRHm2O+xOI5nyqeO8XH8+9jN1PcK/Tl7Jx0mrXfD2u/tDH7QBYJa2F+tWRxeVdnvUznGZ/w8cACAYU6OBmhIHjT9FMI9vtTHFYi8qjH4wyfTB2ZeLj5yXIdw+/P6RJRHMMM1kHP3e0eP7kS7glBbQ5BMyRX3GYZhThWMDVJmAcz5w6BMm2EYhmGSNEDG1v8HImMAiBAwXKTXbQyfqErgpGs+JsgqcI5P2+9Pz95zj6TUOXPC+SPGPmTK+IB7SvdjXc9A0qjPhgRc+70gc6wBADiheQAL+fM50G1wAIBhTo6uhiBRuxKAAXyEL3mmdB85d+aJ4zrKroJoXFfI7U1qDzupHUHPzAxE9DeoX0rIFg5eqBxe6n0xDMO8DioSSIggsLX8RbeH/E/bNfuVfLyvb8pvjuyaS+ahzBoqnBCAAgAJhAHRoGyO9YEzDMMwz0fT1++1MQA0mWo0uYT0EYM6sKxHstofETlI8hT7fdy4Hr4xfXpyhD3ThQ0QrP2ORhAJaBP4HljuRyzE5/4UjmUGAwcAGObkaAAUaLv4rzEGkPyZbbHUH1cFBnKrEKaIDpw4M6ejjByfe3MmdYgZqkA40XJm+uX4JXL5An3b6UTTLiVEJNsZAAzDMMzJURIioDB2fXjhLsAzx4VPAOjZyfmj9sZkaZxDxB4mpeRMMdof9pQFAOw4RAIJBAESoEK5xFJ+DMMwzMLYtqX/bAKfIfMIoRlk70G0Ge8aDRtg3J8b/ED2+zgIQNGBOyXwBY1HRA4QbAkgMl0C344DLIojyRJhF8kIHABgmJNj29X/MdDNIrTx6bSMn1YFMn7/pF87CdpRvM09JBT07YTknzFOLOYnXflua7Zekh/H70QkEkACjI0BVEuWbkeiH5woB098YRjmsJSEAhANIggARBQEOnUgzfnXPveFnSx3F0rag779n/zmelbBlJ55QEYQYhsAAMEzABiGYZhJhvq9dhKArQAEsIvF7Uro+bn8+TGThXnGT8nauXH70ab0yDE4XpJ3R3kcuywSfxBtCSC7BsDyZvAv6FaWwRJM5IPeAwcAGObkaIIMguyC9pPCeyfpvq+eRxwcKwcjSQTjB4/czK6XGM9xSPZSt3igXUdoCRKKYRiGeW5Kaov/CEJhhQNA3t3vVgTq2+P5+H2nS+qASH+IDpqf6b+rVUxua9agicfzCEnqJizkTg+etnOrAk1bAggbEDwDgGEYhplEdSWADBCZXo4nkvAPwMikgTlnzUgETPfPN8ld43qnRKdYFwqulbwNjK6IwSjYdfb2uyDChSfwzeEA8YtjKZ7zPBc99P1zAIBhTo6hhqCdjW7In8I3nT6QYfKsRw2LME+BmNQtZjvxMwfmLzBfz9jpyCiDAAwgYTcJoCQzb6gXYln5DQzDMK+HEqEwUlA7CQDdJYFTTmdn1+R7+XHv7qSXf3KomXEImBKdFDV2OuyJZ1HqMzKaICEMIAE2IJYtyBmGYZiXxwwzAIz9gJm0fJMzA3Ku/H3b/rlktvkjtHvHPf4OiQn/k/GDsIcA83H9ObeU6EQwiNRX8S3BAMjRSywdNvGPGg4AMMzJ0fTLB9kYABkAiQCj+sEIcf2fHc6dPiUtZB+RiTCZ6T/z9Pnj7HqJkE6+9n8uhGT6JIKlBwAYhmGYw1AiliSRELvVgGHOqjCh0da66TE5CxCdo+YNN3n5Lttg/MTc5LncYXN2TT4c54DwEXX/f6q92yZskq0G3AUAnjgoc7wceRYowzDPRwPQgOlm8BMQ9dLDMRURhrZHd0y/4xHybMdTZr3g9uJGfqq5PXvkOFow0wFBSEZ0k/hLOv53/14jAItK/3/xP5tnuAEOADDMydEpEH0GAbi6wlPe5zPO5YBxlilh2s8ipE6BWFYAIHHz/k86nILGMAxzUpSIhXX9E6IR2KaszYsBjO1zHPQTg7nvd//EDkwfPDLO/HulTDs+Znxvqid9BnVTBOevA5yYE0CABIJQEKDhAADDMAwzgwZIgXYS+AyYIEvM8voM7Scm8M0fedaRM6z14dMd3Lr+xYvb78tL3X8dnoF9/YrneRocAGCY00J3NQSdGQA7TSEcOYwZIau+ECCmnnBKRmOb/m8VCDAlQvM6JCfDMAwzmxKhJCEAhY0BUO9Hnl+OJunBT+6NT4SpFH70v+Nhx68+ziyffmbQ8cjBTpV8Ru4kfxoiEZJBuw4wrwDMMAzDTNK0JYBMu4ZfG5OekCBTAmbvAuhoJNqo2rGvGfzgpFfaGfxGgC0BdARMaGbLiyXA8af/Pw8cAGCY04JsGtse3nDLe+sfJY9QMsa9EgtjkfoBwzDMUVPYdYBb7z8i4WgJoGRUoJ1V5ufs5/3ywwyDXKjgEX78oMzQnNMpsxk0Ykd/fGQ8Wu4WqLvX8HgCSs2VSIppAiAiSYCEaLgEEMMwDDOPVox5U/3YvjoKkuWYGIBjcmeMcVzp/8ABAIY5NQqAtnAwtJ+pF07CZdD1J9P6mDm4ngg303D4s0imLhIKAmFAEKBC0Rz8PplXCtdjYpgjp0SBBhEQSaARgBD/q0bnO+73oW66euyXz53neu2DMEDuG0JFguYrEv7NpH5vPjwwcpjvrE+c0adZjscY2o/zk1zJ3h5MiIaEMUgCNfIMAIZhGGaakqAAIQgRhGO/T6jyU8b53o33o/EGTD2WfQ3e/hlZ+70t/YfLCvw/7s+sVxmfct39wnbtTDgAwDAnx5A2CALb7LPhnfmUl/mMc49GM9g3WamEQ2MyEgMg2pUDaXkKBMMwDPNslCiE9f7bcL5p3es4o4hN5+jPueZtwvu4sN5BmjsO8cn6P7MtOG8ZvUCHyQUGRnL/szn70fGz7pC8X+30oyBAQkGECuTLBwBOViljGIY5HkrAkiRaE76z31Py7vV5QQ/3A+cPNZ6fFxw5fLoCQL3xLgiwQRa6S+Rl/+U859U5AMAwJ0dJtmpwn0Fg8+iS09hnEdmP8w3KpDvgEdecc/oTFYic8X/AN3ZQfAEAiZwAwLGUDmD/AsMwzL4pUSChoHgZgADKfPyEfUq55jHtxAaATPWe+en/IzGASXJSONk/IrIzHn8cYiddXaXuSAyuFYRb0hfqfp6dH9CuAWBQ8DQ+hmEYZg4lQmH6BL5g0ThXEkHSMu1CBU9J2j6EObeXMQ9niU+MPNfZQcIm8C2i9N8yrPJXk/6/l+s+881zAIBhTo6SBAIK01cBcuXQRNpgike4+3dlpn9/dumARx2Tv0DuxFwS4hwo/rNo0wb7KYRLEOAMwzDMS1Bg6wiw3n80SGB8FwAFFXZ8+jBA7KOHzmWdzHrLRd9HpTDFe4NzYVzQO270oLtroNuT+w5GSGk7YbTDOdGLB2TiB/4fQf9pn7IV4ohksBELmAHAMAzDLB4JUJIQhMII0c4AGDEqZ0T9x6Lyh+OJ8f6Zu3IHTPeMZkNm4yujt0SEgugV2u+PDii9lLN+hAXe0uHgAADDnBxltwCAMMJWEqT0evTjEg4jKT7uCNhLz66X6PtHeIQCMQfKtB89IBAKMnbxQPHyGQQ7sYx0A4ZhmFdDhUPuvyCBJIl0t9N1duc+sWx1vfCTb+3AZZ90LiQdDSNy3BWOk3WBIiFLuSPjUyg6PefNT57rfqc/USGmLgwghF0DwCAo4BkADMMwzCyGEr7tJACIJdfB7K2cPT5+wTmHBbpEvGu8J9g16aNPnhgE7+fcRmzaez3odNrSfwaXPoP/EX95HnfK3nkp9/1ervv8N88BAIY5OSpEYRAJ2/UDUXRpgzE5Cee97TH9/g9OnKMfuJsZmTLxmoxdEsmDnKTI8CJzFIicKkCJ8cdudeYz7zpJtOWDWwXiyHzq8yXcSwlyhmGYY+EcxRnW97QpqJBUSCoNbT1fcyuJnBeq52D3IwFDhr7bgGEtAApGeVwJoOQpMEOFGNlLmUZSmidPbDcJCPvH4v1ex92P0D2srr8/0vsRfQygP5IMFhpKDYWCQoP8LkoNzInCeg7DMDtR2eK9jv0OAJHzeuLV4leXnZ9IN5kQMDUUjRzc756w3z12MIITcn/GT528h2TYINmJRG36v0K5nX/jB+IRbvs9DbZM7/8LiuMXuTQHABjm5CgRC5KiLR0gHGd1Lgbudo695DORgCS7Cp/YNzEyyC6OA4o7552YurlMyGTkEpR57OQfAADdDAAhCFCj2B5bAIBhGIbZF5cSr0R1Q9IGAAQVaDAjU7yetl4wIbTCK+n6d+RsKLtyrn/IBu+pHzOOFgxHTC07DBDdSkJoYs77n5CqCZEdyvZofAruwXm2lHv47ZFGFJoKTYUGqUjeSLbCGIZhmFmU0C4ijyQQBBJGEnGmOzEQ0yOWdeCXD45JjpMb/EXs95zpPUlO2XiEw5ba7D0hCFChWEQC3x5jAAT0KO3t2DneX8SqJ8OcHCVi0c8iNO0aAI8q4JY09due0QGTSsCINjB/9sAkj1AU3P5sDGD2gPMViNAxQYiEwq4D3LysAvEIvWGPqgazF/CYlReGYQCuRflflIUTAzCkUjGAvM/a98vjIMGTZr87AozKcVdDGAkwxOPMJymOqQs2JA/Ouf6TT2yIWkQ6gHfY6FxFbzSDhYZCgVQgv5f1rVxoHQCGYRhmaZQo0KBoF/5BAAGQnEUWC323f453vieZqr8Xc+4p9vsc49qV4PEp85WNR1pKrvJAAg119jssZu2fGX8C8/+QRlTGg9qaTx/8BU3hl7o0BwAY5uQoEUuSNv2/KyOY0xXmE/r0MzJj3KfQj+N8U7ALInm0q3iKN8m/KYq+RwYkyF4+oaw8Vm8iACIQZNAgGkAFkmsHMwzDnDLXolhh+YBSUlGYoqHCUAPgp6IPGfFtv5eqhY5HgHrJC76DPmak/g8k5PhYDCAYZA45R0AgtZOb8SkjUt6NBBBCW/9nGKT9cir+Iw0HkNdJgBoK+1Ekb0X9XSzDC8AwDMMsnhJRdAl8wgjEXpiOuPv7QPVeHPehse80/G+alPvx5vh1k+cE9nt4sL/p9c9Y7Dd37vDAo5uJPwAAZNqZGwbwda/988we7Rf03R81HABgmJOjQChICLLrBwrMlgAaVylGDHsIZWLbA7NnEcZXnNQh4sHjqyd7ukY4r3/q+PzgGHbmFIhwlO7EjJ4hkAjtOsANLGMKocNeVEuGYRhmJlcVXj2svsN9uwyAKRXdh0I8UZfG3ZuWrc5KNnMmAfRtt5H0CATXAl9t6ElmHY70dO2hIgL533EwIJazCf0nFMo0dVbyaRMBkBGlhlJTOwPgVpbRL2IYhmGYNP0M/n4SgCNE05Z7JhUv553PnJHYS85kwZ2m6T+n/T5psMcPjTAe329Q4oHnHCb2YLL2u+nsd5W53Rfg5E33cdfPIU7c4wiPhgMADHNylAAltLn/SIhGAMZFfntb3kltaz/Z+YB+FbhgxJF4QKCFxN9JklrFTEaODLwGrh8k5zsASjyl+FrJBgXntk+Q+l0dCGTQQLsO8FHOADh5PYNhGGaPSIRrUX5AWaCU7VLABZGeZ5p6Yt2p+dMfHEQCnF73/2k/QiYG4F1ixOkwLs1jG75rkNtD4d7hV0PUzl0okNfgy/rxE72PwaL1/lNxL6sbKUcvzTAMwzADJUK7hp9dDThlv1uvfF46JU1sSgl09xSYytyf4+ifvMQcJrzzkdyPxx+0BAyPmWG8pxZeaElPmyRrvxMIQlsCaEGl/wiyFRuWz1G74F/w0sABAIY5TUrs1gCwZQStoxsA/EI4eYnQRwKSlj9MCZOkHuCLIAoOzqkpk9eKL53c9DWG7HTCpHIwcq1xBWX0Un4vEZEQNoPACGxAvKzwYBiGYV6cq7KoNsUDyAIKSYUwhYbcMgBBoLp1FDiiCjsfemDI+j5/z8nenZVoILQ5AXO0BVegJWV6UuJlfQFd3CJw+idd/10Y3lbvIfdBOQfjyDgTH/tMh/o/IL+V9Y08TpubYRiGeQlKxBJEm71nZ/C39rvvvE5NRKN0FaAR47pnxLMfefm9yj/jl9hVAo5bvbH9Hgtrd1fsp0/6IKLG8MCTtxA/eSBCa78ToVlYAGBvPOLP88kXfEGO3QPDAQCGOUVKFO38QRAIAk0vm2das9iZ1vMzCPp+2DFzEPIH27Hmix3KbEb91HfGL3lyj8vXEKRUm/zvub4DBABAsh9EIlxa/R+GYRjm+bkqxaVY3eGDxMLGAPR4zZ+0fOllLsSTAHzTF1K7snKcwhgAdA6C5AiJy6RIiXJ0+yn6djfdHsr8OvL3UldkoO2M5/x1vzDeRQBksNRUaCoUFQrkbVEtyIB8dtOdYRiG2ZUSgwQ+kRFbI1DeeI9lcXwu9LIWwxFy35BqzLlccN3kZtp+92Va3thPPC5KHZ8cIalTpe4euwX8jCCxyADAqA6wQAVhL+rTC+pgL67+cQCAYU4Rq0CI9iMESQLjlJ3pXAPYTi6MXv2BAhF4DRKz4DD4/5TXwDlsjpriXmvOvMKk9uA5C5ybSHoNgtFySkB8VjyaD0IidwOJQBgShlo1YokKxCjxY93/4MyuID87hjluaglXovoEsgApoZBUCiqITNbNPdD3o3PAiBwPffQYHpaYATDlC0heCFJyYlyUB8V/oIvNBwI3boCzmZPg8bkQHZxsDx8jCw2lrf6/EQXX/2EYhmF2QgIUKEW7BoAQJFqD0TEbCQhx6Ili0klzO5bFngjGsDNpwieZb7/vKvTdNo1+B40RXWhkcHfMYI5g/wtSiwAhEUkjhF3AjxP4ns6Lm60vfgNPhwMADHOKnAlRgOhLBgsqNG0S9nA2kXA8MX/Sy0u+7G/PGtYPoHiQ5ODJC02+mccFvLMZLicYNMILRXcTHzycgkO//6GUVkGkRWWotNUDNIg7fGn3weQfMsMwDHN4rqpCbqREKaGQYKsAbaA12rMu6U7OWhsVhlR9gig26Njtvbjue8A9PhsD6OYBgN8Yxpo/CcBfTdhVF/oa/QDpb68xeEZ6We8+k0CCo7+r146C/szHpv9bCf69qG/l0kP4LOEPyivwIDAM8/ycYyFJSioKKgQVwhQEJmVI5uitePLlNUT2tUvSHnfbvf4wP/1/TrreyAGBYe5oAjhyWN+efETZq7hHesEVgsT8PyItyrb6H8kNFne4SOl/JCJ/X6LzBUXwEqQ/BwAY5hS5FvICV/ewLboYgDYPAODbvTkB6cq2nHR3VQr3RHD6k2oHxY6D5ODoDRJfK3nb4WbvNeg7PfcBebv6hj+vMPmURrSHkbPszxgePg7fYLDQWGiSCuQPWd8s3n3AMAzDPANXlbjEegObNgZAhaGHGV5pHNoUTgLonfK+iAwEV06UD+P0330tO28vxePMx12nKGmlx99Bo/vJ5G+2T4TALZFEVvEg/9wwT8K1/+0gBERoq/9LBYUCeVtW22OwtBmGYZhFcS2KEmVBUpKUVEoqlSPuczXoMmb7TOMdnF0wKvfn2O+9i37kWrlLe5uewe7uolRn23CHch8L+G2I2hAdNvdjsNBUKig0yG+v2H6neX+YT7vCi4/zxHvY1094IhwAYJhT5EzClai/gCxISlMIUyLJwI7tJGtOe4DO1oVezEcSPXjRTWcQzNYhIBI1O71UKdWe6zhIlf4PHpHbGV+FMmfFWkj3Z4HYLRgqAMkAACAASURBVB5YKJC3ov4u2H/AMAzDwHmBl0X9tZEFFBIKiYUyXWUAMADYiVHjOf0TRm8vcC3BPIC+E/x+xPCU5IkQSfN4tB0D+eT2EHhuhdhih9RPBv+B5HaNHBb0GIjyMY0oNBUKCkVSgbwt2PhiGIZhduZaiktc3cOmoKKgoqHCRpmdQwIpFveTPy1vRCK7BJMDgrMoEt85+909PXetERJmNY2J/v7gZDseNr5KMLNw5IYTSoIRXfjfLv8jq2bZ5nv8B5nrfGZ2/Yty6HGOGtZBGeZEuRZFAaKAop0EYAoNTdK4zZcRCHzxc+z5oN87hVo/ArZT6j3twVUykqfPhFLtlPaA7jEjOkRSlCRVjZzHITwxiLsQEEFpusUDFckbWe7ykxfGlB7BsplhGGYnrqpS3ksJUmIhoBBUGtr2rn8CwsD7T4FgslLPEUnDUr2WpPEfbAa+gOR3MGBSJZhDWpRnNj2PAA3zG0JbPV7aF4Ccx5Kpi4h923SDeDEADaUGGwOQ34v6tnzpCn4MwzDMEbKScCWqzyD7GfzCWIESGJWxqEJ/V2xfw5RZHUt/pNY5Pnj5kbxN59L9LMDJC+UuHW+mpD+S3xMfDNHjAv9Z5Qf3j4ynXARFAgyVtvqfBtmAvJELdr1OmecvyEI8A0+8jYX8CuAAAMOcLNeFPMP6AbZ9EoGmbUpj6AkkQ6BSUEaoB6+7mRkE1E0SDLWHqVSF8XmLyZ6MgKe4P5D94G+6SkB4WORxgPh0/0LeR8tCQ2kLCDzIainrB87WFZYj85iQXKouwzDHw1Ul17LaqE0BRQmVhroxvUAfmQQAXpt68zWYBwD518TIjIGk9z9+40zOF4TMDUzK8eR3Ts4mO9vHMlFXwYsKGKdhAIwWlYJKQdnV/6nvXmkBAIZhGObQXIuyQCnbQkB9vD8t0VKSK+2dB4BJcd9bsr4rP5DvsdkeDJ50EezLfnfr91LqyLQNjlGPf3ysJMQ3FulUQBpt9f8u/C+WYb/vzmyL/yCXXsJQT7yNRZnaHABgmBPlqsQrUd/CnURpkwgUQXL9+si+dRSIPjOOYgHf44UN0OuMdYJAh3D73ZAARKe7PSPEgt9txJtxf9DOKQHRKej0j2QRJv4IwFBbQECT/FbUt3KpMXqGYRjm2bmq8Odqfbd9UFhpqDU0BrbGbALXPwH6UwHAl+luw5KIEEZ2YNKnn5TFycl8SW1hRJTPEuJD7f72O2nD54YNpHDck+w3UYMAQWPdYN1gvRXlRpZfq2OewMcwDMO8KFelON/UD7Cx0/clFYY2Gfs9KcCpTdy3wpySxvucSQDjBjv1a/Whtzd3lZ3s97idNMYhEtbgtAm8AgAZ4x3aKQUEhOlSS0RICH3gYXj+BqSBoivhK79x+H93Jv9avMhQxw4HABjmdLkW5V/YVQ2mUprSgAoVCErK1H5vOrmPADBtyad89+Sci5EOQZC8RMpTMOIWz2kGUQP7ntitkFILhs2k6uCcS65LghILCVA4lD3GdKqDIqlA3BaVyf/O54a8p06jfwa7H/cCsH7AMMzRgQDv1/WXh1oppbDSWBmot21WoAHAVlij6SzzTjz1XgOMJR0C0MQ7Ebv/tWvkhlF8Zxqfe04gzSHyC4xDmU2a+u4a/Y9tJz1khDgG/fETGz5t5R+7F9v0f4VVg3WD1VaUWyw/rs7/Xh9rAiDDMAzz4lyWeIXVDcoCi4IKaQpFGImqyB/dfnq7O2lZw4w5fzCc5VroCIPl3trvnjLgndiyX/s9NtKTR8b6Q9KidzYpHCGcFDgsO+x1GlHqNoFPqmXX/6He//EU83yv1v1+7fEnjvayp++d5f5FZJhXwMKra1xVst6UDzYGQIWgwlAzNs/d1R68zUCBGJkEMAh7JzvPOTFUF6g72L1uLoNgzsOechyQv5nQIYL2UOww7UFwhsJwBOjViJEKA1r0BQSLDZZLqf8zmyX/E2AYhnkdvF2LX+/OH7bbfhKAhq02D05qv+li9sYRr6EF6xesm2I4xNMEEjagmy7QTudzhDg615pjQ7qpCUOeI7XXdwR3H3d3A/Ddwb0Qz8jf5Fy9Ya9xzvUr/7RhANBYK6gbrLZY3Rf1h/XKLDP4vVe7nWEYhjkcV0Upt+2qP539HiXw+R9fxrmzAHPGeywSgl2+wd5KVT8GAG7nyCV2td/jdlz5JzbkQ/s99QkOGNqYOAziAV11wmCpobDV/+6WU793ipw68Jxqwn5dB+yICOAAAMOcLleVuML6B9xLkBJsFaDI6MVB7KG3Cwc5h4BeCaAR0Z6s3pPTEnzdIq2pgP9iz8mskc5cw5X97mbQhuFZpXu6BnoPdtAkghxDt9wwEvQzAGwBwbK+kQubQPjSjgOW6wzDMADwfl1/flgppTRWGioDNdEWqF8DwPX+myiW3wssAyCgM+hjMtP7UgeG7oDkeal5BokIQtLk7v/vSOesFwCSVv2oIyBp+ccf0317YQAla4V1g1Ujqq0oP56df1gvTHwzLwErLQzDPIWrslhjtcGNtJP4TUHQAPmiCv03TSsZexuzm3bvJd5B14bUiyo24UcM9tzeeBzIm5F5ue+1xyV+3Mi9g4O9rv0e7A3sd7D2e38MIQFgn72nQH4r6lvBYfZZLE1EPvF+FvJz3NvgAADDnC4FwlVR/dPIAm0VIKtDOOVrB3kGY+Zx5P1Hp52hUy8IMqpDd1hfqZDijAN3KHD2jpBUHcApCADOTwPvZyI4PxxSDyTpXOjaBDCU/QmecGZAIoNFp0BIBfK2qLavWn9YiJg8LRY+U4lhmHn8dIbv7s7utg9KlH0MQNF9J1C6SAD2MQDh+AsMgGjlUV/x1o7bvh8m/f7Y2cm5d8qIR8A9ZuQSlN8kv2fEC5BTacLouzczgAyAre0T7bJPj7oG2pkBqLFuRL0V1RbLu7L+sK4yP4phGIZh5nJV4ZWov8NdX8V3EPSh/e6KqqBSEETe/6T8TU7rT9rv/gF9UaDBft/7DACCXk1J2+/+MV1nZtofpDdzcwEpqQw49nuXvadI3pavYfkf8n0uhxh/aWO+7OlPJHd1DgAwzElzVRXlvZQgC5QSCkGlpodIY7CuARkJP1eTgHklgOL+pDvA+U4sMwD5CwWXm+NEoO4r1ht830EiFhL+tmBXRqXY4aNFr0AUCsRtuayX9qQqkJV8h1YiGIZhTo9359XnhzOllBaVhtpAY7xJAOQUAkqavjYMYCcBtFMBOmYYMvlD0G0m1hbqj5q8CiU2u7I/NExcyInyeRLZWxvA+P1ukoRxEv/bBhEhGCVXbfo/VltRfjo//7Ti9H+GYRjmqUiEy7KUWylR2kkAggoiHcky45vqnvjrxOWIEx+643ObwQK/5A2SWSbQPd3/WXPy+Z5ov4P/NPIrAHllA8E5ODmCgXAc0rJL/yf5IMqbhdnvWabM84n9T7DuZ6iYixhz+Uz+6iP5u8gwzGG4qsSFWN3bWYRYSCi0O4WwW86O2ir8EIXBoWuMKxAjPXOmEObmDwaaRA+5LUz1+5tBI6U9JBwHbjuhOuRP9D+t58I4D7zXPNr6P3b+4PdidVMssoCg85SfIPoZhmGYJ3G5xner9ffNvZaVEk2pK0O1MneOL8ApBDQUprO+/pwVDVF7knz9n/CY3OYISRcA5O9/juye8wmNfEJCCnYZAGNQKKwb0S7/+71a/312fOn/LM0ZhmGWyVVVVD+KDUiJhWir+CroPdo2Vo2d99+v3xu0o1n74/PwgiPHLfed7PcRBWDcfo83Rwz53FUCW36GStCVAELo1gRCZwEAZ/nf72V9K49JnI5L/73rBjspl8857BNHONDvevoVFxkA4HIEDPNcrCRcVtWXrbSzCAUUgqTv5XeXDRSR/IPWDH6MAtHuxcRcwtwpnfaAfcng8askIWewER3C1x7mzh9MPpZRrYIIwHQZBE4YAMig1FT2CsRtWd0dVQYhv8gZhmGemV8vy8/3Z0opJSutaw1bA1siDW0gvw8D+MKdeinvBwPaBL9O/s5/rYfyfKZ8nwml2pT4RujXN56w7TtjfljXd8j3N/5h7SbSUPefwGDX1qJN/99itcXy4+XZ1+qYjH+GYRhmyVyV4rJY3auHoosBoPHlVzvZbmwSQNdO+vRH8Ofie1P6Rr3/2Nv7cBj7PakDJH5ypv4POA8QogOiTxT+RxsGACIsNZSqWwH4tqjULr/z0NBiovuHcxQ8feQnjvDMPpCdLrfIAADDMM/IVVlKtLMIy8KWDDb3nUhDACQgTCgQ5Eh3V6ZaZhrznnLgeBmmkggIpgoNBUQ341XedxvJb3f9n7YxtSogjO6NP17+YOtEkCs1KBDiWAoILkexYBiGOTXOavz1bH27udei0qIxojZ6q8yPzu/fO/2he1WLrsedBNAFA0h0PQHjb/phBYEZuE6BeYd7RyeFOPiyG3yBCwkpnKvzG0jqweZ3K/8MEtygVFgr0a79e7tafzg7DtnNMAzDHAW1hMuy+vQgJRYFlAXUBrbGbCNBj1G+GqTaAH1CXkhQ/Mftdwzz6al+7oLDwWhpbYGGmn5OH7n73ca0/d4NONN4h9G9bvX/sDCgKs4UlHYBvwaLpdXvdclpclMa3lMt/fka34sM/sQRDvrrnn6t5f51ZBjmebiqxFW5alSjujUDDWyJ+twBGtIHMGkkQ9sYbPKRCr8xc0oAjRzc98OMd2CkRniNnCbhNUYT//0PwtDOeRZwcCj0yYO2k1AqrBust1g1WHwr1rflIuv/WHxdYNYff6Q+PEKGPaeIZRiGOQreXpVffpx9UEqJSotaiy2ZrSHdLwbQvX3dV7AbBnCDAYZAYLgewKPJCfFdaA15bzvViB0cI4Z98DGZbwr8/uhGAtAQGC3WrewW5VYUHy/PvpXHEBN/ukHPMAzDPBfXVVGLsoFSd/b7lratb5oM9mEA7Kz49FJ2fY58HInPletxe2IjfbIE0EHtd4r6Zwp955mgs0mZE3Go/DOoBEgAZESloLL2+xbLm2p9Wx7B/P1dVYD+j/Nx1zoQp+YWeNzv5QAAwzwrCzSvLkt8tzr79nCvsNRYa2gMbRtz5xQK6PIIxlYO7GXkePqAS6xA5NIAc2oEZFWHMd/CiOMg+vZmDoL/q5Mju0pDrH8MnzaQkCofbPUJVdQKqwarRlRbLP9Zr2+PwonAHCNceY9hXhGrEn65XH95uFOy1KLSoja4Nfp7W/mnlZsIYKhL+uuKBghHchkA4YT2e4cCAmFKrI/aj7kkwsfji293WoDn5oCM/e9sYiCgO4lMfg86M/17ee1+iIwoFdaNqLai2mJ5sz7/55xNLYZhGGbPvF/LT/cXW9VoqBTWBrYGamM2juVOBAZD4x2cdh8YAF82j/vlY5thfBrfLvZ7lhHjHZzfBQBuSmJS+k99CKaz/QL1oO00AEbLuqsBWG6x+LBePSw4f++J9H+u8w8+HPsa/4njPI89/ZSrsFbKMAy8X5Wf63OlVJ9EYMiWDO5lm80ZNN30wKQm4TZ6RgR8oAdQtGt8UmF8ltPfdo946scb3XfaiQ/RQ3B/OO0wS8DzOAw6hBGFTSG03v+b1dnfXEOAYRiGmcevV8WXHxcflNKy1KoyuCLURj8AgBMAAL8RTwKwJYDa9H+aa+aRI/px6NszvtSmvif5HQtuZ9Nf98if1O/a9n2hf/fTi29DCArXDdYNVo0ot6L8eLG+Kzhyzww8j2uAYZhXDyK8X9Vf7mslVAWVhlpDs6UtUF+5F52oP46KQnBeTnNygrCztEem7+cm8ecu4cpKgoS+Qak2JTb9uQ7kVf6JSVjlSSs+su4jHQDIyLrp0/9F+ens4u/1sblbO8VttsrnP9b5p+2VfcnWJ47zPCL+iVc5tr+RDMMcgHUF71brrw93SimFtcbGwLah706iH7UKBHZykVxROjQIDdpCAe3Ladd31IwagmHDPSZ30ZGepA6R9R3MWBoIAPqMQncVQUd7cPIN3VUEbQ0BAKPwzKb/WwXiw3r9Y/lOhBcS+QzDMExAKeHt5ermR6WEMlKTMWhMY4zR23wAANPrAAN0HoSRaewjAoB86RzFBh7JTCGebCQ3TdQOvoOi//4HjZIXjVxv5Wor660ov5yd/3PJdhbDMAxzEN6ei1/vzx+arRalxspAZajW9OBM2e+r+LYNJPB93NB+YydJ51XxdUT4eMAgM10g7IHMIJTf3M1+Twl98gchx37PFO8d7HcTTuJHAiAFtbLFe0W5kdWH9apZavmfV2O179Hn/jzu+yfy9JtkxZRhGACAd+fFp/vzD0opVWqoNNSGtoaUoz3Y+X3easC+NxwA3AX3YEYSwegkABuzDzrTAz6ihmDcdpSAdvJgrEZMev9H9YbE3mjtXyIjKoVD+sDXs/NXm/7/arQPhmGYhfH2Ut78uFBagTagDQgDaBQYIg0A3VQ56/QHQrSF/nHw/veTAHrXv80DSJmz5LaSLv786z4W4MnyQt6uvtoARUckLX+nERb8CeRyb8zH8YCuQZ7fv130T54pud6K1bZYbWR9V60+Xq43xzXxnyUywzDMUfF+XX2+XymlFCoNjYbGmG2miq9xxG0v0QQBIYDj95/pYMx59vduv1NmMxb6buXe4btP2+/y2hMf6mcJDHl70SxAsBMB47V/2/q9WtRKVO0UQCw/nZ//vT4uJSBk+UrB073hexxqjzdz0PE5AMAwh+VYamtXBby3kwBkpXWjoTaw3Zqtpz14cwkH1cHzFHRpg52UfQQzkwUov3eEZAwg0iGobVMi5d9tT3wcncNTIGgouDwoEASEYAiMCqoHnr3m6oE9j/i7svd/WUfxT5VhGGYOUsD/vFk1zeUnTaAJtEFhQBilfiC5KwEAQGcaA6RkOngzACiYDUCdbpAkth+jCEHrd0DvkBwU7E65AMJvv5ENz/sefyceQK0Qj+v/mKHmr1xvxXpTrDey3hTVf3+++uuno5fcyzf+GYZhTpmfzsW7u7P75kGJYTVgbe4dueYa772d7krGVtwTCCuSd3/tT07fT/bMt9/HYwDO5qAhECU8/sHmLBM+pSQEMwIJgAhRYaWwbkS9xeq+qD+sV+aoJOjRSXz2/j8ODgAwDNPy9kp+ur/4b6OUqLRojK4NbA012CYOdGEARBj816ILlbvlAmxCASCOrAj0CPYSTJmjQ+Q8CBPaw1Dex0sk9HMH0N8k03sW7K4g/f/zxRFWD2SOkWOJVTIMM5uzFf7PmzOl9I0hMAYMgTGIRpkfQMY/Fr3/Uz8VwHX3h20MXf+Rc9/rz+2FfGfMiPcfHBkNCde/0/CL+eaT/Vu1p88KDD8EmmSp5Xor1zb3f1PUf7+5/s+bVzpvj2EYhlkSv15Unx/WSiktKo21gS3RlsAAIfrGe2CBZir+0b4tgpHivfOZK/p9vz/4vxrCh+BZ6PH0fceQR7/TLfCLBsBorJXosvdE+eni/MN6idV/dvXyLzMqsF+D9aS8/8ABAIZhegoB787rLw+VUo0WlcZKQ92YrSPwEABhyBwUvkCNdAiy9QTmM6UZUHzYI5SJSIdA8G57mAno9s/THiBZ/MdRIDwnQr932NWm/4u2euA/58utHsgwDMMsnOtL8Xtz0TSajOnnAaAxyty1R1A3FQD7zV6g9/V/UmGAULp3dveYoz8ZGHA3YXQyQXIz5/2HhOt/sOHBkbwp1z/aJX97R4mX8t9X/iGUWqyVXG/laiNXm6L+9NPVf35ZGRbcDMMwzOG5WuO71dn3zYOSlRaNjQGodhJAb7z3bXJEfGDC93L/0QKsj/T78jphqe/Zfs9Y7n0jbcJ7qQAj2XthxX/PhCcygKiwtd+3WN6V9Yezasdf9xw8zpu/81mHDBrs3dV+at5/4AAAwzAuby/l5+/n22arRaVFrXFroDZmY7WHLusfAQy13oJxHQKe9tZ6SqbAyLmRAkHg3H/fT+ApE/O0h1A5iBQIIBjmTzgzB8GgU/zHVg/8fHTVAx8h8h2VjWEYhtk7b9/Iprn8/5QCTXY1YNAGwBizQYChFlD7IrbSU3Rt1/vvhwEIaZgE0E8FiL3/5DgFety9EO2aZNz7D768hkEKt+I+KbX9NgWiPFH5hxB0sVbFuilWW7naFNXt5fkfv6wfjjf7/5B2O8MwDHMI3l6Vn+/PlFJaVrqdwb+lYd0+r+gfAvi1/gJZ2X8/URqMWOKzdkXiaNJ+3zkAkLHQE75+6moA+pUA24O1XHXFe6utKD9enH9aHU0WwBGJ/QV6/w/KgW6PAwAMwwwgwtur+stDrZRSoipFbbBpaOu4/sFm0GFbSRD8KkCRJoF9JCD5Gst5BLyb2l2BQO8eEqR9B44vOlAU+kZGgfAnBqI3CcB0eoM9LFFDoDsGNNZNmz5Q3RfVP5dHUD3wiPQGhmGY0+S3d+W2uf7zkyFjwBjQBo1pjCHTdB6B9kVOADjU9096//uawrF9GwsE1/sfRALcg+dLEkptJsMAI0I8a/z77VZA9wZ/7wIgMIhGyXMl10quN3L1UNTfz87+fHtxu2aRyKRZuK+BYZgj5bzGt+fr282dFqUSlRa10duGfjiV+qzNbnPmu5Q+ipf8MQDCM94h9+ZKSu24c379HzcnIHfJfn0C8nuGs3pDPpXD5/egvytc/rc/LFAP2mP6Er6Ewpb+b0S1FeX3ev3PxRLT/+eQU8Ve3Ng/hPTcy5iHE+uHG5kDAAzzfByFUfjLhfjy/fxhu9Wy1LrSWBmsjN4AgJs+4PwaHJsE0PrVk0xKkx3FDXX3g+T02DhEMJbn7nf8/nYrUCZmBABGiga6nYkiwsPBWtaNTR8Q1VYUny4ullk9cCGwIc0wDDMTRPjXu1o1Vx++ErTrARgwRhlDNPgFXDqhib5Ad+v/9JGA5CQAx9dPwai5GAAk7yQ6INiMvP9RWb9RCW4GCY6u658SS/46HyXWWq4buW6K1aaoH6rV379efrxkqc0wDMM8N79elV++n9sEPjuP35gtGe2n/7viPj8JYMx4T/I0/3AfIPB6KOwcDo0jAcnvrALQzt1PFABMGfJEUeL/8CEgLVeD/Y7lp4uzr9VRuHx246ViAAey90/W+w8cAGCYZ+AphWxehLeX1ZcfK6WaNokAGwWajIoDAARo1wkc1gROf+OMxQCSHoF4c8bjpGgjPIOiNsFE9UDwNYNg09Ue+qSAOBIwFBMINAkjpMKVwspW/78rV/9cHmv6wK4c17+O18zRvaoYhplNVcFv79ZNoz/bEkBdGECpfkHg2MNOnexO1P9Jef9HagGNbMKeSwARpCS1244/g51Pg+ufUga/JjBGVlqulVxvi9W2qDdF/eHXqz9/fhVWla9zvZTNzzAMw8xnVcLbq/XNw52WSovKiJqwUfTDWbqvjY2j91LPTALwJH4sBcYlg7t3tgxJmurdF4YHpUT/LPs96Awn//Vr/9BQsLffm9AKjCyVqJWobPr/t9X6n8vjLQI4wTPrAwt3ry/89kZ4FaoqwzB75adz8cvF2d32QYnKCAVCozCN6XwENKgRQ4IgAICwq/76YthQ6z4whNgWFrBL9TxSiOwqfRIKhZ/v7zZ2UCAICFtVyuSSCFo/glv5Z1hLkADahQQJQYmzRq63cmUViE8X559PJ/2fHQwMwzCH5/wcf3t33jT6myEwBLqdB6DVD4BeHvYiTPTb/lSAZP2fXqz7YQCkblXhkRhAf+1JSRCHKNyGv4l9fh8E4nssBpB2+ncftAH7Qsu1KtZNudqW9UNRffzl6s+3pxKzZxiGYRbI28vis50EIGvSGoRGNMr86KbiuXP3AdBKYcK2FlAcAwACg4CA1njPXTaQ7HF/cnOS5PUm7fd4c2gPi/b1xX8olv7ON3adFKXuYZe9J9aNWG/FqhFlI4qPl2ffyuO2aefHdh4/ytSph2Nfgx+v9x84AMAwz8PRiYK3V9XN3RnpLk9QG0Cj9A9/ImH3TeAXAvK0hyGJgMDxHQTE2kMsOh4nTKYViCgekHP9AwRL/vq1/nMLBzlrCUZTCMkAGFVeWO//Rq42RfVtdbb89P99Oe0fJ+eeQToyDMO8Pn76STTNhfqzXRAYjAEiQWjUXTcPIHrDtqFu0YUBnEiANw+ghwC7ztD7H8cAXK8EeVcd5iRFr3x0O1Pe/1ZAQySp/Y9TArhP/O+KI4emvu00stTyTMl1U6xs8Z+v15d/vVs1cvafwfLZl4BnTgdWyxjmpakKeHu5/n7/QNqANKANCAPGaP0AAGEhoEHytjZ7Pw+eANCdAUBDuyup+wwSIn6nUNR2CwHlIgH25/iiP1qrz290bRoW/gk/ZABBiXUjzrZyvSnqTVF/Obv45+KYnKsjPpc5Zx3ifg4Ke/8tx/R3lGGYZ+Nqjb+/uVBK2wAASoPGoDBK3SUCAG1DQGs/J+cPutpDwIgoCVwGwfHJEydfnpRpJL/dRtBuP9EMwTAGQKlSwlalIDC6PFNyvZXrjVxtivquXv/x5uKGVxF8dth6ZRjmFPj112K7vfrvX5qMQUNIqEloElrdg1EJLzmRk++PAKJzDeQ/BJ2A7lWFXAwAurdvFPJPv5RpOGNozRHfKTk+LPrXC+6MqW8n+5drXaxVuVblqilW27L+dn7+1/uzHzWLbIZhGOaF+e0nuW2u/v9/NGiDXQwAjTF6G1juNlDfESTwuca7nQoAjtx3RkkL9PHAv9tw2cF+R69nRPTHDc/1j4PoT8cDRpYCUuVFI8+2crUpVpuivl2f/fHm7P5VVP+fdPHvNwbwPAb48s38Z7tDDgAwDJPm3bVstpf/2yiyMwC0ATSAxpgNAviFgMDRIvrVAqMZAF2bUHjTB7zsv/FQ9EzHwQg57QEc/QCyegMGvoOkxkDdtEHTqRemKxPkZhRqAGNkbb3/vQLx55urv6+PLJPw0XqA58N5DSoTwzDMEfDbb5VS1/98IEEoARtATUKRMHRPeps/j9pSAGHuWoYTYgAAIABJREFUf+5DfgPCb7Refleag3OAc930zUAXaYCUyAZHKOfsf1vNr5PXlLbzCQwiqfLc9N7/atVU1f1q/fe7i5uL11ivzxHKLJ8ZhmGOhd/fVM32+r9fiAzZNWsQTQOGSDsCt2sgAKC3kh+hHwZw6v6FkjkWDpP2++SRSSjVju33vCHv2u+e8R56/K397iwAMGQAtLMB0AAYVZypYr0VrfF+X6//fHPx+RUpA3NiAPA03eDZ/N37vdAhbvuZgxMcAGAYJsu/35ZNc/3HZ9MGAIxBYxpjyDShGd+XDgCYnAHQzSXsP5bRJALyO0OBM0eNoHCT3H5ySgokVAd7E5lKAqkwgFP2J5dRaESp7CqCcrUp6k1R/f3L9R9vjnztIHYVHDV4DDkSDMM8DSnhX7+tBPz88Z+bBlCSaEgIEBqEJmH0fSfIOtGGVg4KbB3uws/3j1z/2M8DwFBbsH5/+65pD4hd/7nXkN8/FPmBrPjGfq/zQX8zkNd9tL6t/KMJhSrOTdl7/+umqn9cnX94d/HxlyML2DMvBYtWhmGeASnh91/qRl1+NJ3xrgkEKfUdyDgmWpuG14ndoHhvKoeP0JkOSCkT3t0M7Hd7zeAs2NF+p+6r68RA9EOvAHROfPCUmXHjHYyzKoBxZvM7CQFkjKy1XDdyvS1s8Z/qv28u//rpmNyq+zLW+z+bxwVzDs3er/UKvP/AAQCGYUZAhH+/rZvm6oMm6tcMRKPIUOvx9+cBDAinemAmDOAxJzXA6aH4gJwMCt+r6HU6GoPjQehmJbiaRNAe1R7amEGibmDfSUJa7aHpcv8//3z1x5va7EUgHwOPFnhsRTMMwzyRqob/+b/qqvz5nw+3DyAECAlC2VpAJLS+QzeuT25LEBCCAEDM5f6T6/d3G+QEBqDrB6c9+YKPfAHp7yA2EHj8++V8hgoAQ7qfY+cjGCMLKs9MZV3/a1XV26r6/vPl3+/Pbq5fT7pfAg7nMwzDHCGrFf7+5qxp9FfdGe/GIBplvndJeG4yXe+Vb4U1jhnvbg6fpRcVOZlBvvTf2X7HRGcc/ofO+gZ0dYCE/T5mvCed/s5HG1louVa2cq+sN7L+8Mv1H2+WvnTfJHMcMeOnT4/4vLD3PwcHABiGGaOq4N9v102jvxgCY/MIDArTqB8ABgfDPga7QkCZMAAiEWKfKgg5t75bPSDejK8+/i6NfQfgKAfhdxsJ6LMFbV4AppcPIutB6IoJjNQNJAAt1kqum2K9KVYbWd1cXvzxZv1w5Nn/82EnPsMwzMuCAt7/u6yKnz78/f3HNxSIEoQCoUloEFrdgdHgrnsP1FUKtt9IINAL6lMUDOjldTwVADLp/+PkhPhoGCD6OIv3hN9D8B6NkbUp1qZc67bsT91U9c27q7/er+7P2DvOMAzDLJHLS/y9OVeN+m6IjIF2NoDWJljMDxyb2oryCeN9iPEjtnkAnv3umupJox72ZL/nrHingV0jYb9HYYC4eG/0MSi0OFNy3RSrraw3Rf3l58s/3qz0K00GeGkf/iM5Fk/9SzlDOADAMMwE5+f477fnSmkyxhYTBGMAjdI/YrngiIpevchUEqTuDKd6AGa1h5wOARk1IiZ4zfaTErPe/+7bVRfA2Zw5eTDxUeW59f5vZb2R9fezs/++Pb895oV/n5omsOsQDMMwzD74+Z2syssPf8mbr98UCAFCAQoQgoShOzLKOZbaN3UrPwXgEAzo6gKBFwYgSEUCoFs3CB154AcDAgnv3UN/TNcmR173x2AiAECe678r/oNBDKD96OKMyrWu1qZqvf/bdf3lt+u/fqvUidhPjp7E8plhGOaIePNGNs3l/yrdWu5dIV+jNwAQmdWuD3vSeG9TAPpmJ9QJvLWF+71BeKDfBUBxXd+A0H73O0dj/0Hln6gQELnrAAdr9bX6gO5n8AOSLs61XDfFaiNXm6K+vTz/85ez+2PO/n9lwv1ALvXX5P0HDgAwDDOHn34STXOhGtUGALr1ALR5aIPrAJFMtSv9jtcCwkD/oK6kQEoaxToEpJwEEwkF7qLDgRqRqfxjHRih0tB7/EOHgqM9BBkEhAbBKLnW1vvfLhy0+vDu8uPlK00e2DdHJNoZhmGOgvOfxb+r86qQnz7eKkSBaCMBGoRp7khvOukmwm9yvf8E7YSAXlLnc/8pnj4YOQ3C+QHDjq4ZuwAcmU7DZiem48T/wOxv+wlBlRemWJtqrauVquqmru8vzr786/Lv9ydmOr0y9wDDMMzJ8P592TRXf3zUdkFgOxVAaWOo8QPwAN7LPvb+t8Y7RaX/aLDZAy9/IDySMQAA6Nfky9rvTiR6OMf2kPczElF/SJvwuSpAmQ8aAKOlzd5bNcVqU9Tf12d//Xp+84rmAial/bGoAMflH3hZt8OJabEMwzyWX38tmubqPx80GUNdKgGQIb0dDgreZ9j/L9Ik4jUDex8BkrNJTrIhpFSKWDCNv1QpajtTAYZoRqdDRMkCgxqBSdXBehMSeoPt17JqS/8Xq21Rb4r6w69Xf/58Qq9idrUvnaTPjWGYV019jr/93+uykB8/3GxRCBQKhUChQRgQpB+ANLZGtyuIPe8/AHU1/WxZgMDvH4UBRgIAWWIhDqHgdjcRBglOcZHf4ZuAEA0AkZC6XJvyzFQrXa1VXTdVfffT+affz7/8zEv+MgzDMEfD+/d101z//eWrzd4DY8BopQwYPZi0nrOdurl9Qw4fThjvCIBdIIAi+z12/dvrzbffKbXZdYbrAPe/KPb7j9jvplMSPPvdlgQk0kaulFwrud4Wq21RP1T1h3eX/1ydhEqw8BjA4WzWV+n9Bw4AMAwzn/fvq6a5/vsjQbceABAZg0Y/pN9mrffcZg24awqNfsjVEgIvAzib4KsRMCqekqpD1EiUEch/EssAmHT1f+oWDipWduGgplhty3pTVB9/ufrz7TFPHXSY1A+mBd7UEC8uMhmGYV4rsoR3/09VVD9//uvbHaAAOw8ANQjCkvQW1KaTvwKI/AkBaIP3RHYSAPh5gjAWABje7I4ACBIH4w2ESJSnAgDkR+6HfP/u26n/Qyh1UZGsTVnraq3rla7rpq6/vb389PvZ94slm8CHZOHW/zHA2gvDMC9CWcL796umufzcV/E1hATa3INpnAMHm52of+XHkwDAPyWw3z3LHT0rHgAQPXGyJ/udwNcBpkz4rP0ezd3v4gG6WDv2++qhqD78ev3nLyfkR32kFnBI5eHQUvUQ4y9EEzihv7gMwzwRKW0eweWnL7YEEAlCRUKDMOoeSEdS1tEVUAAYqxxgqEb4CsQwo5DAcxxMZhDMea9Sqp33HYSfoL9fNSjKGugqBvZrCeriTBdrVaxVudqWq21Rf7m+/Ov9Sp1E9sBSZB7DMAwzwpvfi6K++vKH/H6DAoVClFiQrEyzIaxIbUBvOunsfrshgdjvH/SAI7gzkwCyMoOc/48I8ZGPn/5PBGBICChWpqhMUZuiNmVlqlrVtaqrr/+6/vT7alM//pG+BmhGoWaGYRhmeZyd4fv3Z02jvhlDhsCQJlQkjLo3+iEjbTtDHgEAgXI5fP3B6ET9obPl2xhAtMIfdMf0p08S2+/J76GRL97bfggJKQwARMV/QJfnqlircq3KVVPWm6L69Mv1f9+9kuy9gBGP/XIyAY7R9X+4YR8BBwAYhtmB1crqEPqbIUEoSAgQmoQmodU9kAEI33AEgEidawCHNQAorUC0swiH0gGO6x+tGoK+GHpEBgF4GgOltAckt4LwoC5YlQKdzd71Hy4f1Bb/IQRTXOiy0x6quinrbxfnf78/u6sXIkz3Q0452IvMW47gZBiGecVcvZVldSn/I799/CZRGFmabWVERWJjREWqIrUBs/Vn97vfscff+fTL/1ooEBr+JIDwve+7ANDtJ38O39wAACFSsYKyBuv9LytdVqYsdVVuz9Zf/3316ffS8Bo9DMMwzNFyfS2a5lIpDQYkYQNCUGu/G3XnyND+I5xcu8H73xX3S0YCKJPDB4RW8B/Ofnc6O/u9W/sHPAUAe+lPg9lOUeqetd+F1OVaW+9/tWqqVVNWX68v/3q3al5R9t58z/7LxgCexw/w6r3/wAEAhmF25fIS//WvC4l4e/Pd6hB2EoBNJQCjEvY29dZzMiUwUiDCWYTdiYQA0C03FKcNTr5dJ9IH0O1Jef+dX0RdfkEid2BI/CdDQurizLSJA+umqpuq/n59/vf7i5vXuPBvoBzsLPBeQrlYlFRmGIZ5cdZX+P7/PS9XxY+Pd9u7ByMbkqWRpWm2JEqDFekNqE0n8f1aQKGDABzXf78LMrn/I5MAIglO4ItpiFwYvcEfBgAIDAKYooaihrKmsjJFZcpKV5UpS10W9z+f3/569vW3V2TlP5HlpP8xDMMwO/L2rTT6+sMHcf8dJYiGhBpy+O6ATPiCp/5/1CXwjdjv6OTnBbP9MEoX6NmP/e7cJzg6AAy5eqH93hfsjVL3OvvdyJL+D3vv2eY2rqVrLzBJVbbLbbe7e+/eM+ecef//35qZ3bkdKkki8X6gAiUmRGIBfO7LV1mBBEEQwIqAiq73f7Orqs8fH3756e7pLgVZOJq0N7nar21Wjfu31hwWM9L9XYibnwEBAACANt99l1XF21+K/K8/v2SU5ZQd5DmV4PnqZ4Gv6DoIMrraIPjm34gCQYMKRD9LsJ9cQEOfDOkNA2972kPHidB5MaBANHkpy7P3f7uvtvtN9fn7h19+ukt4N2Fucg5oM5B4CwBYF+WWfvr/Ns8fyq+/3X374/Hw/NrsijYMILOdPFTNaUcgaupry38wtD8YFaDrF4Ov+1K7/0IpYN8R5Y0sNnWxoXJDZSXLzTHrvyybqty9v//66e7Lp3K/ddOSySBP+RcAAACi48efiqp8/+u/i69fvmUk8qscvidq6lNyW1eGZtd/u+7+EVk/YLnTSODf0n6/Tt07fdh5MfjvehvAASu+routLO/q8u5Q3e3btfub6s8f3v/yj83LNn0pOOu0950PsLwN6umKPI1pBAAAACbcvxX/8Z/3VZH//tvnTIiMRE7icF5OWD+ftYfzC0lCSEkkJGVCyKlUAkkzCoQQnTn1Rga1P0VIRO28ezP3yvNR55WD19pD54WgKe3h/EOCYjgAUBd3TXnXlHd1u2yw2uyr6s9/vP/lp9XvJqwPTwkKAABpc/chu/uwffdb9fW3129/fqtfdnJXNnkp96XMS7k/7Qh0eCVZ0+j+ADTyCV2/uH59FPSKAQCaNPg7G/flVX1O/K8qednzp9g/3D9+uvv6Q/l6n76FDwAAYG189zHbFG9/+fcxhy8jUZM45fA9yaufBW6RV7/xI26N96tIgLwx2G9d/9dBZFv7/fS393bUfm9OOQET9rusy/vWfj9Ux4X7u7vtHz89/PsfVY01gSfa5narKgUx9v1dlK3vAgEAAIAh5YZ+/j+bTfHh11+/vojsILJMZDVldbuisH46/iTAkeskAjm8FEDcOgtoUIG43jW4p0BczbgjCsRFtxhPHJA0qj10cwekvE0fELIu3jTlXVPdHartodruq+r1/u6Pfz788lM6uwn7iv/7zisAAACgw90P2d33d29/rb799vL452NTFM2ulEUp80ruS5lVMquo3lF9oGbfCeFfR/RFf00AHV9cEKc/529lR4z3AgDiWoKfk/2vt/2RJIXIZVbJvKBiI9vE/9b7X5VNWe7fbl9+vP/yqXp5B/EzCQQ0AADEzN2D+Fd1XxXZb799zoSoRZbRyX7fPzX1y7XZmxFJSVm7rb+U7Q4xE9sB0WTufzfwf2OhL2O/d6z4y0/3HV9IIY7e/+qcvVc9v3vz+z/f/vZDyl7TvmBXFPXnh2SsFwT0knu9NFvvPyEAAACwQWT06T/Ksnz/27/zbyQOJDLKMpFllGWUNYcnkvXJJj+rApe/J30iu3INXP38780L6ikQ1BM6fRE29vaoK8iL0iFvNYmzE0Heag/i6u05laBpRCaL+6a8b8ptfcodeH7/5o9/vv39EzIHTOAsRAEAYBVkdP+PfPvpzZtfNk+/vjz+/Sh3RVPs5a5sip3cl3Q4iPpA9V7UB2oOojlQc7iOBNz4CGj0xVVgoOf0774e+A3Ai8iWJEVeyqygvKjzgvKCsoLKSrbe/6qsq/Jwv3394f7bj5un93Bsz9OqbogBaAEdBgDAinJL//x/26rIf/v1ywuJjLLD2XinrD48C2qI6GT8no333nZAoi/iadJyH1z5N/jJvP1ORPKyjn/Cfu/v2Xv1V4pGUNNkhTwu3L+rq+2h2uw2m6cPb3/755u/v0sld4+IdJz76qK+F8wZLYiJQFyt958QAAAA2PP+p7yo3v3+P/nnv7/WIjuITJAQlDVtDKDu/EigvFEdJFFGot0dqJtNQJelADc/GCj6mYNDQufycX8SlqejJFHXfUCSpBDXCsRl7+DrZYPdv+KyFKDJC1ncNeV9XW3rzfaw2e6r6tvHd3/8fP/5fVLag18W9C8wF9IAAMCHrKA3/yq2n97e/bJ9/vX5+fNjU5RyV8iipENN9eEUBjj9bfbUjwQQdVYG0PHthbEMwX4koCvNL7JbkhB50fr9m7wUeUF5IfOC8pyKQhZF6/0/bKvXH948/7T59hHSGQAAwIoQGX36P2VRfff7v4vHr4+ZEAfKMiFqygRl8vBMTX2Swp0tgC4/5idJCJL9pQAnE/4i4s/2uzil59OQmTdtv8vOUbf2O5GkYfv95PqXvfR/0flENk2+adqde9vc/81mX1Vff3j47ee7xzfrjXgbm+Ocjes1u/5bEAAAADjgzYesqN5U/53/+duXjERG4iCyWmQNZZJ2vR8JvP4rRUefGFtISJ14AF3Lo6EVAJcJeESBkHRRGrp/r9IJb/5duf4lSXHSG4ikzLKm2Mp801TburqrN9t6s9lX1Zef3v/+8/YJGwqbEoUoTZB+ZA0AAIiIKN/Q2/9TbD+93f6yff716fXLM+0PVNd0qOlwoEMt6gMdumGATjDg1vifeNFlPBJwlOZSZjllpcwLysomL0RRtK5/ygtZ5Ke/uSzyZlO+fnr78tPm2w85ktlNwCIAAACIn+/+kRfVuz//O//y+WsmsoMQ7Q8DNFTI+lUef91H9kz4TJIUV9v5kor9LmZXAJjY7zfxgEHjvZfAd0rpk3nR5PeyqFrXf73ZHjab3Wbz+Z/vf/t5s6ssGpc9XUk+JtVTkva+7dpY7GYEAAAAbti8ET/+111e5X/++3MmskxkB8qkKJrs9fgjgfUrNXVv/aAc9P7Lq58Vou6LU4bAmaEAwIURBeLyuq9G3PyjmcWDreu/qGSxaYpNU1at9rDfbv7++eH3nzf70rRNAQAAAJYU9+Ldf5XbTw9Pv9wdvuz2317ly54ONR1qqkciAcfdgRpBDUkpZENSkpSnXwy6dg1chSE7Tv82kVC0yQaCRCaFaLIiy9uU/0LkhSgKygsqcnn8m1ORyyJv3mz27zavH6vnn/IGe/IZIc8pmMl4BQAAYK28/ZgV1Zviv/O/f/ucUbsOIGuyUu4rKSp5eJX168ntPpjJ17XiqWvLiwHLXdF+77tSJ+z3CRN+3HgnKfNcFhuZb5qyOtvv9Wbz8ubu75/f/fZzKVco4zzdcmiFYQHXfCzef0IAAADgkLykH/9fVVQf/vqfry9C5CJv8rLJS7nfNVl1DgOI40K8vgIhJAkhrxUIcVYjqKNekJoOMev9p1vVQXY1Bur8kOCQ9iBEU2yp2MiiaspNU1RNWdVl2VTVy9u7v//18PvPmGNNkZ19ngAAALCkfCfev6vkoTp8ud9/rvdf9oevr/XT7hQGqOlwEJfdgWqq95mUQkrRNO2LTDaXv9SI9ttWTxBEIju5+49/hRBXrn8hSGQiy2RxdP2Lot3tJz+m/FeFfLfZv6t2D+X+Idu/XaFNDwAAAAyzfSt+/K+7osr++t8vmRBNljdZ1WS7JqvkyX6X9StdfrpPiu5eQG3qnjx6/I85fKKbzEcKAQAyst9PJvyV/S479vuQFS+ypthSuZHFyfVfVk1Z1pvq+f2bv/719q8f1pggkN4igMXcCHH5K+CcAgA45uPPRVE9/P2/5fNf35qiaPKyycomq2T7d1/J+pXqV5Ktg1dcLy3sZRCcfjZQTsUABnWIifSBEe//beK/vP0NgPP+P4Io39TlhopKlpu6rJqiaqqqLsumLJ6+e/P3P++/rFJ7iIu4BDYAAPBEFFR+zMqPGVFZf76rv9SHz4f919fDt9fjmoBDLQ6HNipw9PU3He9/c3T6i8uygKbdzH/I7y9kljWiJSMhRCaEyESRi6KgIm9T/uVdKd9t6odq/1DsHrJ6G7qN0iNerwAAAIAORUU//temqD58/uXx9evTMYEvL5t92Q0DUL0bT/+/3gJI9j5RjQGo2+/juf+XHYGGU/eobBP/q6as6qpqyrIui8fv3/71891jWj/56wQH0n5ZhQGu/wkQAAAAuOfhU37/9u3X3zbffn9+/vLUFIXc7dulADIrm3ZRYRsGOO8CJOWQojCkUkwFAMStdDkuWry8GXpxVBdEPwBwlUpw8v7nG1lsqNzIspLl5pj1X5Z1Ve7e33/9/u7LD+V+Hb4Gr9JcwrkAAACxkb8X+fui+s+iedrWn+v686H5sjt8e21e93SoRd31+EvRSNHuAtSctwOSJBtqJMnTCoA2DJBlJEQjhDx6/IUQGR1fCMqEaF3/91XzsGnel4f3xe6dkDB0PHARzhDTc8ToHQAArJOP/yrePDx8/e3u2x+Pu28vza5owwCyDQOISopXWb9Ss7+EAW4d/RNWPJ0+v3lBt/a7vPpPM4HvZin/+af7iIpNXWxkuaHylL1XlnVZNlX58uHN10/bL5+KemXb9t7I8AmRHoW0X1jgRirfoRcDALxQbOnDf5bvPhZff9t++/359etTU5Ry1yYU7OS+kofzaoBzNsFpqaCkYe+/OC8tpNMxLacXYmjXGDmkN4jOJ3JMabhJJWiavGqzBqioqNo0repQVU1Z7B/uHj/df/lUvr7hLx8BAAAAv2T3lN3n5T9zudvUX97Un+v6806+HGjfiHZNQOvlbyRJOr1oTi/kMQicCRJCCtFkgo5u/3Y1gBCZEPnJ719mzbuqeV807/P9O0hhAAAAQJvNO7F5V737WH79bfftj8f904vclc2ulHkl8/Jkv7dhgLqb/i86q/aJqL+XL7VHy6EAgEv7/Wr/HymlINkUG2r/lRuqqm7q3v67+6+f7r7+UO43jlsyPdjGAII44iP1/hMCAAAArxRvxIc31ZuP5eOv22+/P+0eX2RRNLtSFqVslYn9cUWhaA7UnH8GcOTf5ecBaPjF8O/1DCkQV8kFJy3hZhHAaf2gzARlRX30/lfypD00VVmX5eHt9vmH+68/VM9wOrjj8nzQqKEYXIYLAACaiIqKT1nxKSNZyheivaR9+7ehvTy9aP/VdGjoUNO+IdlIEpRnTZmLIhNlJoqcyozKTJZCtn8rIUshS5KFaHrWO6THEkBMAwBAWmy+E5vvNm8/lN9+e3384/HwUjTFXu5KWeyaXSWzio47Au2pOdzk7YmbAIAc2wKoIzns7Peh/X8kkaQsl1lZF8fsPVlWR+9/VTZluX+4e/rx7suncncPGXZhWqS37W7YXh60Bbj+DUAAAADgnepBVA+b+++r519ev/3xtH96aXaFPC8I2Fei3lN9EPWBmoOoDyQPYnhFocIWQAOobgF0kz4gRS7yUuaFzArKC8qL1vUvy6qpyqYqD3eblx/ffPuhesJ2gU5ZRrLGLr8BACAyBIk7oruzKL8WnTWdAgN0DAxkRKWgUsiSmtbRr2M9nid5GPd+QQwAAACSY/sp2366e/PL5unXl29/PjbFrtkVMi+bopT7Su4rUR+oPojmaMKTrEcX8RNp2u9y6LXCFkBCUtZa7mWdF5QVVJSyrGRVUZu6V5WHt9uXH++/fqpe1pq6Zym0w8r8sPZ7At4DBAAAAAux+SA2H7bb36vnX14f/3isX3btpkCyqKg+0KE+qhH1geq9aA7UxgOIrnYH6m4T2F9FKKgnkmRnqj69ElMBAJmVIi9kXlBe1HlJrfe/yGVeUFnKqmqqot5uXn548/zj5tv3cP0DAAAA1uREOdH24imQjmwtRAKcc2v/IwYAAAApcvdTtv10f/fL5unXl6c/vzVFIXdFU5RUVFTXdDjc2O+iOVBz6FjulwDAaf8eBfu9v//PgPFO3Z17ZV62GXtNfszbO2bvFa39XjZVebjfvvx4//Tj5uk9JNaRi/QWvU8mzyIDsW+hKnDwvHOogzpjtUUAAACwKNtP2fb7u+2v1dMvr89/fGuKgg4HOtR0aHWImg5nNaJNKNgf0wqoIaKZXYDkzYcj6QPd9YPnPQnzgrLW7182eSHOSkOeU17Idpfhomg25eunt88/bR5/yLVSEVPFudV/K67gVgAAAOCCrnyBYAEAAABmETnd/5xvP73Z/rp5/vX55a9HuSvPxvs5h+8SDGhOyXzNoS2gZ6H37feuTB6y329CAkKSlJRlMitbE57yoslLURSU5zIvqChknlP7tizqu+rlhzcvP20ePyJ1zxm+bXQ+Dnc+NZlAsZIIAAAAFkfQ3U/59vv7p183L7+9HL6+1s+7kxpRH2MAh/oqDFAfRL2n5iCkJNm0fy/h5+P/fa3iWmkQdDlFZEJkJDIpBGVFk5ciL7K8aPJCFJ2U/yKXRU55Lotc3lW7t5uXH7fPP+YN5k4/RCFfAQAAxA6WBTgG0foeUGkAAMmQVfT2P4q77989/bp9/eP18PWled0ff7anrsXJeL9eE3BcECBkQ1LSxX7v/OrvmP0u6TpXj0hkRIJETkJIkbXpetkpb09ckvZaK75N3cua+83u3eblp83Tj/lSTRU96vJcW5WaLJqh0GRYpRt0awgnFgAgDKKgNz/nb/5+naY6AAAgAElEQVTxZv/lfv+l3n/ZH768Hh5fO2GA87KAA500CSFlJqWQTdZIIWVGjZAyk42QUrR/SVKrZAhBIiPK2heifSuEOL4WjchES6s35IUsClF08v1b1eHt5vBus3sodw/5/kFI5A0sTyq/GhQf+B1gAEDqIBIAAAAAqJDf0bv/W779udx/ud9/rvdf94evr/XTaxsJuErjO5zs96bOpBTN0WA//ZWCmuxsv8tGtL/fKwSJnEiIrG/CizZ1T7bmfF6IopB5IYpctCn/p6Q9KvLm3ebwsNm9K3bv8/1a9/q3Qdf4NlOlmFuZnKtnXDcEAAAAQcmo/E6U3xVERfNtW39u9l/2hy+7/bdXuTtchwFqqm8UCCmaVnW4aA/H/AJqTmn+R6WBsqPfX4qsaXWI7BgMEHku8lwUnXz/qpDvNs1DtXso9w/Z/i30hilc+ec5S1kAAABpg0iAFgOiH4sAAABgBYiSqu+z6vuMZFl/uas/10f7/fFFHGo6NOcNfkV9EE0zaLyfEviOVvxxcYA4+v07f49+/0YIkR2t+6P93qbuHXf7yeSmlO829ftq91DsHvL6LnQzRcKY6DYT6fPmfAyqAlunhH3FEAAAAHAheyuyt3n5r1y+bOvPTf35UH/ZHb69Ni/7kw5xVh3kUWNozisKJcmGmtOL8wqA7FZvaEMCZ+WBMiGyTLQp/9uyebdp3lf1Q7F7yOpt6BZZEzPyLAZdAQAAQAIgEmAOhDUAAKwHQfl7kb8vKiqax239+e3Jfn+Ru1MMoJGnBD4pZHO24klKajpbA7UvOsZ76/1vzhl7mSDRsd/zTBS5zHN5X8mHTfNQHd7nu4esKUO3SYxAcJ9I2PXfggAAAIAdYkvFNit+quhQ1V/e1J8P9ed9/fWVdkc1QlzFAC4vLsGARp60ByEz0ab8d/WG4+tMiCKXZd48bJr3ZfM+3z9kEvOiPpYmP1tZCwAAYLXgR4NNQAwAAADWR/ZGZG/y8udcvm7qz2/qL4f687752u7u24izhX7+233RviZ5TPnPhBRCikxesveOm/he7Pdt2TxUrf2+exCQO1ooCmpf8pylnsDTHeG8VnB0AQAYU1D+UeQfS6Ky+XJHe0l7ol1De0n7099DTfuGDg3tL+GBdgWAFKLOhChyUeaiyESZZWUuy6wphSwzWYrzv+ZBwM4Phapgc6cr8BTwAAAA2IJlARqwtO0BAAAsgNhQ8WNW/FhRXTXf7mkvaSdpL0/2e3Oy3E9WfF2fIgFEQsiMmiwTRS6KjMqcyqz91xyN95MJX1F9vUkvxM4sBrv9rESeM3QOeKoSAgAAgDjIHsRJAF3/Du+BaE/H2MBFt5CyFFRmohRUiqYkWQpZkmJ2P+x8A3xtFAjCgt8BBuBmasOIWDfQEM6sxC8AAADAhJyy92JYUOypY7mf/tWSSkFlRm1+3tl+z5WuBunsCS+ynocCwVOj91orBAAAAJFTEBVEd60Maf/mRCRdzJ7QJPzBU+ICAMAV/dkfUTFARNAQpuFh2wMAAOBISVQS0VV4wInxTpDORkwL7fREOk9FfoFaIQAAAADzYIMgFdSVA0Pxlp72AQAAIHKgIQwDkQ0AACAciAQoodY67kV6ICVhta7/FgQAAABADygTE8ymDwSHQx0AAHEwNp1hEQAYARrCFSuOAWCGAAAAJiBO74SoRTpbobxwxRAAAAAAQ2DnD9I2i+i8dlw6mhsAAABv4G44AqkNAACADbDfW/rCWUVcRyfS2fr9KVDdEAAAAABboElcIYkEa3EL9ECyMwAAWLB2JSE6hwEAAIDUWbtoNsWlSPemHjC3XANWDwEAAABwBjL+lgCuBADAAmCeAa5JT0lQFcgQ3AAAAFiy6kjA9T0rymq2Ip25358Y1BABAAAA8MKqlQmuBBe6AIBEwMoYYIfvYAC77snWYQAAAACkGKQ3QD0GQE5ayVo3YKftjMCknggAAACAXxAJAAAAAMAYzp0OTOxMAAAAIEbWYL/bx+VDRfajU3L4VBgBAAAAWIg1aBILgURCAAAAKZKyqrAa2c3H1F8IrIgCACRKykJ5BC1Z7WApgML14pUw3GqOAAAAACzNCjUJDnATwDEB2x4AAJZlbNKd1hy4T9WriQEAAABIiQTtd3d34rZxuGsyavC8CwQAAAAgGNhqEAAAAADq8DQpNUAMAAAAQLSswX43FtS6kQBpeT2WcNbTEAAAAAAWJJhW4JW0FAUAAABgLUCCAwAAiJ/Y7fcJaWwpqDk7wf3B/64RAAAAAF6sIa0AAABYozL5YmssAIxBDAAAAEAqxB4JCE/kWkEsBgECAAAAwBcoE1PoKAqxSGUAAABgFURu7QMAAAA3BMzkU7F2L4JXoXKQ0irE5WRAAAAAACJgYWXCVpJBX0gPJDsDAABwS4raAkQlAAAASmJZf4pS2hkxinsEAAAAIDISUCYAAAAAAAAAAIDkYWu/M3LxM6rKDDG6/lsQAAAAgIjxsUdQvCJtjPTuCAAAAEiBeAx+AAAAwJ647HdI6S6xexUQAAAAgBRgm1bgF6gkS4JdgAAAADgHohwAAMD6cGW/a9tnmhdbTkoz1gfSMIIRAADAP+0slsacAWJgsK+pCFN0UgAAAAAsDWObHwAAAPBNQPsdEnialDwkCAAA4A0x8jalKQTEw9L9jo0qgQEHAAAAcIeN2gAAAABwgI8Zu85FAHza3xUIAADgh4lpC9toAOAQTlpC+mD6AgAA4In4BTokJAAAgCSJX0SrkrAoRwAAAA/MTo1wogEAAAAAgJhx7wtYj4MBAAAAiIqFRHQgTWAN/jkEAAAAACTLGgQ5AAAAAAAAAAAww5BvXd3lnl6YflXuAgQAAAAA+CE9BQEQFjABAADwCZQHAAAAgCtpSOl1mrMIAADgmgSmQwDiIg01BAAAAAAEsQ4ACMfN5LNONyFIFycC1ruU9nMBjGYEAAAIBLJoAfAMRpgvMH0BAADwCmIAAICFGZxz2g+h94LU0ZW67ZjgL6gxdrtwDQDAuQAAAAkAAx4AAABIEYj3G2C8AhAxmNEA0MejrW9aNGTxBFwDAAAAAABgC+L0AAAAvIIcAgAAE6D3gjTwtq9OKHGNcakFAgAAAADip+cmgDYAAAAAxA1iAACABcA8A4CdyD2b3i4H03WFYN3bgwAAAAAAn8B6TxUkQwEAwFpZTrBDiwAAAAAiwSYSAMvSNwgAAAAAAAAAMMKNBQPrBICFQQwAABAcJL4AoAOGC0MQAAAAAJAaUDgWYtoWgr8GxM5gHxae1jm7ING5j1szAzwRAAAAAHgHGQBOQQAAAACAZ5aR3NAPgoB8KJAk05NJ+y3Dnn+uNsO6jYOZG8zDXsRHNeY8AGUAAADih72wBVYgAAAAAAAAAFaJjZXDeWugxZxxpleJ1byMtd4uCdYGcEsAAAAAawPS3x0IAAAAAEgKVl64VYC8PxAEbsbAdH2WHyM+BqaLArk9Nz3UWyDu+xwl8G3BC8AY6AIAAAAAZxAAAAAA4B8Y7WkDux90wWDvM9EmzMeOu+qtq18Mttu6msAPUCcAAAAAA8T5T2xA9DsCAQAAAAAAWMN2V3SgBdTr5fG3lZBlZM7pcFbtWSoXjbeX3txdbDcSW30BAAAAAMARBAAAAAAswgKhe0kS/omwYCkANzAioqP7yOxHk/GQ9JH476pMs3IYjoXujTCs3jWMKsgvExByDwAAQBrwk7FExLZakYEAAADeGJyhYCIAANIGSwGWAUqwV8aad+GOfa7Gwtd156ln1E9nbypsXc/VY9RkF9hVCo4AhiADAAAAAGAMAgAA+GHMLIFrDABvYGAxAnOdJfBtLYBBI4cKDNhEApb3yp0uF1kvZrJrP79IAJuKXIMYAAAAAKADxObKYRwAQN8EkaLSdSPq3nZeg6kbhWdwloj6iSIw19dGqPxl/mAgLMCSjbxYYMAstLZYDCBS1/8E/XZb7N7kspcbgvtzhFLBDOH/cUCbACFx0sOZdWLBrD4sSE+yxPvzv10g9JUZG9aMAwAALEmQqUTlosFFsrLXYPRuDG4h+F17Qreb2bQDpCNgxQojARiDbom0PfvVdjIEGK6w6VQm0melysIb94cLA8TxHOEOWBnTT5vVpJgO/IdYXA9+8ZD8WC1cFRUlrqYS/qODiEQc1QTqGI9IBABA/KQ9ny3pMtbHTYK/Vp0Z6h/qz0il8q7688S1Eh0y8vxfojeYCDdPh+GIVgF9zJI1N6DDIaAVBlD3ONiF7df1bBcLBiwbBljXQ7QmUjmWJG5/Qz0dkh/Szm9wgW39fFzCMmlPuQQ3Z3lltku42vTvphxOY01c/ZcK67P03Q4vBAAAS1Y2ql3iI9lwSEeZeUSzF1WplWXNLU935dZ3EsJxOyLCairrE9tgCg65fOiQrkBLGmDvr1L3IygeqeuYSG/bHyIyuJ25vfsdzGf+wwBRPkToFWAIhgulvIDO74O4Qkk6NbzqL0GW6RuXoNXVx66iZXdYxgOWHZuYCdLA93yDAAAIBKaoxXCtwcw/uumrOIkN6B5pwGzhin3YyfbBXvcgDqepADADOiQr8Dh8Y7xTFgNH1xK9w5uf13GpI256ZxkafsIAcY9vxADACIvttuId9PBQMJCwUxh4/3XvhclifScr6bXMXksb2bWwXnIOYC1UWVdOm4WnFgQAgE8SGpkacN7Z306DUXqext7/BdYEuMU4R8BJAr4nr/1cAiNbWHUNAGIltoGfLGaRABVHl3NnmDyW6rZAw29b1GrjvbMrWP5jXyo9IkcGcDqDPi2PAHBIlDEAdGZuOA8D2PdLzdOF1ilMEvJ00bKytcxeY2/++IlLjHLMJPwIO2IQAADuSHt+cXt3E6UtMCUYaDCChL2D3tj77zVPYRazTH+zeIBlMMB5JMDToIaVDkBwMAajQFdeh4gBuOlKDqs0vpNegF5vJExVAwNOC08BaBdghAhiAPF23U7N3Tdyp0QWLcRnNYA/69hfTp6rdhODL+euOOt+8R0GYNGDUyG29uQwZ7QgAABMiWrIqcLhpgbr4GPOUNaF/dr2Tlz/C+8FpLXIw2y/QptIgNswAIdBMQcfmQoAR2IYxSzwtLWaPVoeh2UdXQ7abJHa3iYxLDwoHEnn4QIikdQLsXhrQAMB5vAfudc1DNDbZ6ML158u1KKuwgCLyWvpYh2/+jG6R04yUHMb2zxgGCA2YR1bfXnBVjdAAAAok94EENEd3VTVbfx8vDRx82bJmYzzjkAGCwYVD745i0MYIEbhH2OdAXAFOv80y7SPj1i+usdhVl7bH9CpkRWe5fhoDT1tozeNP9kEqQfAHFwWAbAdqr2KsWiuWfohiqF6e2n1gF1K67oq3n9Xdrd1gxg+KXWZ7jYMwDwGYH0t1soFs8rFMWEiAABm4DSoHJDM7biNB4yoLy5bSzf9f/aOmEyxWq52g0gAkzAAM/kKALgCw7MPzzbp1spYiimGAfw7Jph7/zWq52MbvYlrIQbgGzQFYAjDPjlUJSYGlhVDonZ8ZzjrazFvssU8+6bt4H5kqJjAs2JCUY7wjwFYE1t9F4X56B8EAQDQI6UhntK9THC+TRuHAp89Fp1sCmR5itd9e3Rd8xzCAIyFf4yiFwBbuI7H5Yi9BSyDASpOh+lj7BYBcG5+87o530Zv7Cqcmy8N0MiACdz6YapO/zEmRa2blWCutgPygSSavTV7z77+vV+qpLio0QCVTH8nMYAVsIxyZAI2/dMHAQBARDwHtBHJ3IgZNpEAobBC0G2ag1dXvmU9zVRC3QUBS24KxC0GAKUKAF1WNWRWeLO6YkvlLMsYwOSVLQvxhLN1CV57oCcJCMHaBa0Beiw6XbHqfmvz+w8yKTRtgwHGfcvgRJ3jvXdD3co4Mf+1bHPfMQC2ssZ1rdjeqD+SnCQRAIgD551PXP0XOWnchVv0vQmCeJjxExVQrJvzWzBI8Pe0FECrcFcn8iZ4hwXAO4mN3MRuxyFmIXy7RH6Dwjk/QMdbFyIGEDv+WwNKCLiF1QCE37+Pgqjlm++sxQIb+6h1JkO//+x1nRjmQZLkopXUS+6YqIq7xlzJ3IgAQGAC9DOhdF1Go3qMCKoYGrUwgF4qX6gggasdDO3roLghoOKRtOBSAHsBGa2+AkBkxDvQ4q05H3RD+DaCW+fc0We7ZObjeEnugchLADxEsBh8etpITVbi3lJlTtRq21sMRGG/YCusvf/uXf/9Ciga5sZtASEyhJvts1yh+YxWPhMiALAQ4fuZ5tAcrHD44R2+BhEyrlW4b04Oawh8o5Xg7ykGYHaK8VkAgGWIa3jGVdvo0AoDzB5sEwPoXMGkfP947IlehSYWASwDGgT4hk8Hg+tflznhpTd/cDKE56vts6rC/yXofAnLGECSMmLBO7LZoskhbEYedxAAcAy7nud0/N3c3XJjO71JeWGuXQO2Zjwn5ebCklViEgMww/JCnJQkht0QABPYjKkpoqhkeuiGAXzEAAQJf7OttUaBjgnm4aS6gKRg0q/GqwFVeR6FGACpP2oeZrL3jjl+j0LhGMdghmdGgBGAPqAMAgCGMJjYJ1lkAHhf+4Nh7BShrpE41114KEPcWc0GgkuAxgH84d9F+ddwJaiHAYxjANMXVznRZvcDMjwXPXQYSMA+HtoEiu3a4TDKJuuwXBe1TC0KjoUYGi5NtyitU7ia1RwGBABgGgQAZmA5u44Tbt51HAxIWIB0b00uIcJF/41XJ0LAMMOSeErtXyYGkIRrIN6+A9YO59HHuW5AUYKbie+hz2/1B38xAKNz0VuBHkkoP4AFHDpSKNe/83vvFxhKxZ8UQxHtBRSke3IYE+DIOh8GRLwahf3UFHs7J+JF4vQYzk2qXSlOd+GA2dsRc4dZ9M6piysqJR4SCdfGTbgHXAE5DdYG2w7PtmJRoNt6lsLA3hGvFgMYuK0FYgCkevpCfRZDIzGgeAB7gnehuQo4tjiWv9/zFZe3nQKat44uvXyUIviAAOAIRLwCDlYAwKsVEt5dXGPLPN43oofDe7kpam6wuRf5ZprB4FmWV5w9PZDGpt7mmg9TmRUvAgAgDtiONbYViwWzBrSPDKs4yi3Et20CgeVeCgqno+cCc6D/ABvCdp4lXf8chkmQSIAri9KrZTpSuPa6PesKOMg4dFKZuPBa4ehaAywLtgCKk6gG9syCgKjuZYoFbmTIa2B+WXupzKEEf6WNX8R5OVe15m+aMqghAs8gAkIPk1HYViwunDSjTTBAJS4+Vuz4ufM/F+Q1gaB7Oik4OLwS9UhhIKn5opGgNFMGWBG8Xf/kqk/ynDcWTvMKFQOwu+7Cj041xZPJXDlRXZ59HpgB5WcOBABiI+YOfatvx3wvtyx7L6J7RUsDe8HdhL0vAlC/hD63T9jpE7etNUQdAKzgOR551ipGPLWkQeK8zVKA3udi/CvVMs0Omy6hxT7pwfjSIFWgOwF1eHv/HZg+/MdCpDEAf1zXcDTP0kaaT+QKqBSuWAcbFs6/tDlmGfjUJCAQ7pMgABAJCXViKYiSuqHlbmZmCQXLnQTmP9QKFSwbA1iyl1pumbAq0EqAKWwFG9uKgT4GMt3YwhfjjnWHMQByMWs7SXrQvRZIHrgJgAoBO8kCif8RDYEYYwCLLALw+AwncgW0CiEPz069Kqw6ub/KsLrNsEC4j4MAAG8S67id23Gx+pYH/u9B9Qo2wtXG1HeS4uc8BkA+F1F6e+iLarYQjQC4gudQ4lmr2FmsVbUE2ezB4yJVGAt3dYnlSLZdLX9scbuLIFgh0IXANKG6h9p1rabAGHv+GmIAmiXPG63u8vy0cwX6J7vaW8nVwTbfqh8DABsQAGBJevPIyB1B657AsGWMhauPGICWZ9+y2MHDWozVGpMjXJwCAIgFngOcZ62AAbphAJ0YgJg9y20MgDwE5qe7+pgKAUAXfWsEKxHXQqref0yD6iwfA7DxyJuhpjxMSWHdMABpnjV6ebuz7L3/uiD9fzHgZxwBAQBmJNlNJ28q7qUAfurtoFQzSztgDEDX12+QGjl+vFKD+1A7HF5oNWh0agh+4A+2XYttxYAN6iJPOQYgRj7XK1CrbiqlTV5kqdOWgnn1VgUUBtAnSJdQvqihUzqBfr7wIoDTFRdtObV7FIpHWh8jnC4juDrLKwv495kMKCbV4AYk+xAIAPAg1a6po8RE2Qau6+24EQzCABHFAMgu6aD9YNpRYol+CcgpA4A7bGUV24otg9fJ3EdRxle33ARvbM8fmxiA4jHnI0lP2q28a4OFiDsjCbgG3n/QxT7q4DpuIbqvfMYA5hcLdo9jYsrOdnX7AxSPsTk+bLFpEKuT0SMIAIQj7b5o5PpMu0mm8XjvBl7yiGIAZK5wCPLZ7mbe/zWPAQWYaJVgpXAenpzr5g/FGWFQ+sSLehigd8yMGW8ZbjdYmaeY4Zgkyd5Y5KzcGgEt6Xn/0avtUM21ny3FkT1++zy9xQBuFwtS4DDAfEceP0IqHKN8GTbefwA0QQBgcdYw+E3vcbVa9xJ37dYy9xoDGLy0SpVo7pjesV4wKt1KU1rnsAFgMTgPMc5184S9YWmc6stHTdG35G/NeLNwu8OlADQv2Zk0dpSg7YyZG+bIRUic5ceOzhXh/V9+BLpsPxcxgOH6eIgBWF2oxfphqTa+wnHdzL/Reilej4/3P7HR7QM+qjsPEABYipV0O+vbjG+E2tV46Zt1l3owZbdbxgCmSxi76M0xI4exdv1H1vUBSB3+Q5J/DZ3j0PKPfccPRYE4tnOxmZxVt/wNduebSDxMjJTvLQnis0aAI+D9B9coxc69Xl5dMrqLAQhytPj+prpqrafUZw06thh4KUcOcHlpeP+DA5neAQEAz6yqqzm62fWM0DC3qbsUwGw7ICcxALIIA9B1+3r97SaLoqV1CeYYXzTQ+ETOHViOKIQQk0oaWXcmzJY8uJ5MsdgFGnP6EsbtNikQxfQBBov55q6ofdjIKUx6txdSvreEWI81As4k5v1HB7ZmoAntYwC6Jcym5OsWrrj2TsUJMFnOeNHjX8qb9y6YvSiR9OT6NzsleMlJApl+AgEAb6yqh7m+2chGqH51w9+dbujeYE+AsRMHP9c6uPvVdN3OB/Zb3EZ7c/H8nCX+h+9MACREFAMqYCVnL30deXXGRFFjX2mZkV7VDpWSLdttNklQN1o//dXstzf10HQQHCufZNQ3ihkGtERmjYDYgPefN6NNGCgGoPpILaTzgObQO2a4nNnDFAoYqoEdytqXkqrCx/uP0W0GZDoRIQDgnhX2Kj+3nPAIZXRf6vrHrCOAvC0FmC6fpjSPqaYOnsbupAKMOpNfknQEAV7EMpoWqeftiBu6qFIm2nBxlrXpfS6H3P2DLvWJSvtYCmBWmlm7zabP+4gBkLKjQflexMCrVGTAYpNMLLMZf3rWSBo9EQyw8KiB998M/yNQqfEWjwEIreONDPypgEeLYiRA5WB/nVS93N6Ro0oNH9e/15LXQMIeRmUQAHDHOjuTz7uOaYQq15XdHcWyFIAU9A/R+Z+ZiWaywyAAYAHiGo/eIu4Gl9PIsLc3lScuL3uFy84VBy3S6TDA9M2oqyb2D0s/EjDjIzCLAUxXQCvZcLooewcEZ+KaasCZmKwRYEoy3n/0VQv0Gm/BGIAwuKKOga9qOKuXSSPal/MeaplmMfKlbboevP+cWb1MRwDABavtQ6u98UEUZhO+DeYqj88gkXBsKcBEfSa/FVf/TZazCI5T/rvYFMi3LwKwFNGNAncVnp8UNa81nz1vbCoPniXVDjALAzC0DRRk9G3ivIEsNjhLuXoqB6vmXZ6JKBjArUcBAM7A+2/MhBj1gZ9izZttkRiAuHljujRwpnzdjEDFgztnqbazj+WYpzqoH6j6qwCml2BX+KpgqOcvCAIAdqy46yxz7ykNzwhuRCuPz8A772SJwM23pwNmHE83+PcayLFLuyKC/uSMiJw8IA5iHD7WdVYdRxYXci+yzbz/N99OhAHMYgChVBMt17lBPN74K8UDbg6mq+NNWjSKYAAflxwwJiVrBIQldu+/4lV8TM765cQxaidFp55w17/Ebflazn3lg7WXVrhFt0BxOUmjpeH6j4sVi3UEAIxYa3e5gBYYZHwqiabBfC8FcBgeOH9pkIngKSRwKta7IyJgf3J46WhGBUiFeLucac215yJHYYbhYuxz5ei6hP7mP9N16svoieqytQ2uZaLhqgvjr2hO9E8fMHK8g5ZmGAzg2X+AGZJIcOlZwCVLjtN4vf9D5c9GhC+vFhk5YWZcn3c3ekfazunhU+aT5wx2+1G/hFcMLjx0iurjhfc/Rtjq+Z5BAECTVfaSW5ZtBO9jU1V/US5tgfYxEPy65TuJAZA7X3/Hyz+jD42VMI27pxaB9z+qecyqPdcq2sEtUXcDzcobDhmnTeRm5Klk92uVNrEUoP/hxOfEYG4Ran5zH8n+KksBSOMBCecqzeDzXZKoJxwwRvBRD6wJGcLx1HmW9TyqN+CV/LSf5CfywLRy1X1gHwMYKsFKvk+cQjpBd4scu8sllhl1No948typZg4ReAMu8bffFGMQAFBmZT1jmMQaQStzUKvY6+PdNFu/FE9p7N3CZwtUyQc08/WPZRCoVCxQJmAErn8AVkUCQ0btFqwmHz+t5MVXJiffKp4+EQbQ8vXP3qFnd6Gq3zxIDEDxGF3hbsaS8YDgc07wCqQNYgCJsdjT9BTI91f/65KNZ01/2QBCq07dg3kO4WuJqeGgN9gZyXgBgdpZt5Wf1ZR06+AK5QIHmnmxdE+wACuT7AgAKLCmDjFFYu1goDeot4DzeUSxNJsU+LECgywFICIxmdahfqdLBQPg/QeAEWmMl7m7cDDtsG2o/r3JyW91Sx4MA0wsBeDUUOLmTagYANkuBRhoVH9hgImr2idvgvXAbDYA5sD7r1Cs/WR8GTFmmfL23v+xArkN5FP76HcJHFwAACAASURBVNVLd8md6Ym35w8+F83CTM+xw+iKl84L13+SrEmyIwAwyWr6wTxBm8L9kJz2I4xdTCsMcKq0g5obFOHQhF58KcCta2O2WJo7Zrholy4Gv97/IMEk5+caEXKxNoiUZAT3+I24GRf+G8qXOi07fy0LGQsDaAp68wP0GXaa+4sBkIulAEOFTDXMAmGA/uUSIJkbYYnsvkJTA0Wi9f47nIDdjpiL998yr5/hsgCzGInyiaNBd7KIBJzOZdKEo1jXTyzwMzDcGzFpdPN9owUBgBFW8Ow1SKw1brz/ffE/Pf61XAMT5ahjrDc5XBDgainAyLfCf6bh6ClnTFvJozaQ2NADwDfJDJmhG3E51TBvqOn0f+cXEj05qx4VWDYGMFqSopQ0Tva3jAFcH6baHguHAWKH+aAGgBXLjJeIvP9+XP+29CPH5wyAG/vdMq/fMhhg7Ljvl2QTAyDNJXc6p0+d6i3BzgpXw0RcvYTVnzgrCPAjADBE6k9dj8RaY3ra7tr8E+575alB6BzsESfyyslSgNO3ozkITsIApHDk9LlmJTjBa28J3hUB8ES6fdv9PLRgW/lawOe2UaTaUoAxrWCpGMB8GbOy3l8MgFTVA+2tGxymMgDgCg66PWCO/yR9HwUymmjHvP9jR6qk8WldV70QV97/83/GBY6cqxd3J9UKDBc75jzxiudx0f04arUczJP6UgAEAK6J5EkrzjsO7iaSBlFF9t4ONqWKGqE1NXCwE1xl0lksBRCT314dZO9oIG0lZqqEG+TgSwuCdw8VoqgkWCHp9UxB5M9W4t9cE+n/Prz/3ZL7YYC++B4U6P6tBQ3rPUgMQOWA9i68pTeuGv7jOnIG+h0H3R6YscCD07+E0tzm08vJa3Kd8P7b2O9mdVhkpDu7SE9WmpSsYETrFRvLbKlQT2cxgFjaZJ2kK+IRADjB9QHbzC/TLtZ5OLWJgzHY9/6rX3Ji7T+p1YzJJOLEftZcCjCVHWAZBlCphu7BKnB4lAawqrZCZXiZIoAnrHq1CyTWAAVkMAygGAOY+Hz6KwUcP7dAMQBx8wphABAJo92NiW4P4gfe/ysmcsjGvP/dA27sPvt2616xrw/4wN7HPG2G65ZDx9IGP44e/ZuxfT5JNV+6JLoUAAEAImL3XH3L4AkppvBFnAx6/1ViAKSQSjDy1e0HNnaCWxvDVRhA0c632VVAOcygxIgSYwwvdRmAtZGQnFpiMomiuYKk/w9eUXQkb1/KT6wOdB0D0D5JUXT6iwHQpPtGvSj9q6yUKIY2AOtBc0jC+z+PVhX7K/nIXQP6aSzHIvJchtscOHH+E0WnmcS6Pxg+H8jr6EguDIAAAKPHGWQiHV7cxqZN3DCxTlD99LFMwMHDJopiEgMgl8kFNx/MHubs2/6F1e/I+bIA5lh2nsTmBJAASfTJ2+nH000FbSsN2cVqNpbXMQBSDgNMpwtoPgvDR2cp32djAKSxFGDqFiz9+GuT44MkMRNGDBYBxMNxnvD9vHyUn4b3X/FK/fhxNw/AzH7vv40CO1E+4NuxjygMlx6JFHb99DWeT1wdD/SJcQIZYd0BAB6PkMmEeYkE8GgWZ0ykExqU053pdVYDXB3AKgZADsIASr/pZ7kUgNTqaRMJ0DorLiIc1Kk+CuCACPtzn4EenqL335bB1XtBrj4dBlBfCqAjx60enUqqvmWmv8IBSrfgIR1hLUQ9uiNhvlshBgDO6PeE+Q7mzfvPccqc9v4bF9g3DzmNWR9h8qmcPKMCR8uc+C5sD1vkEc/oL5y6GXBAEmGAFQcAQj85jkKXiEQaHfvEmPffuPVvVgOQkYXPzVSwsJyFVgmWXn6teprdlE4wgOkQ7sOqswFgSfz9edGpI6Lmmo7WB5xwx8IAKksBHK0DMCR0DEDoBu8RBtAiotGdPNwUezCEIM+/sqMPvP/T3GaY2djv6mv1lsVHmHy+TP1IgElTqZ+j9WQZPLUutw+HWfWAe9jMHmasNQAQejk8U3rrAaPt2ETks6Gn9wSY/lzl2+lLm504i71uoVKCvZdfS2WxyXRYw8oARXwuPQZAg5h7zswsEvOtqWI5kYadh/thAJWlABYxgIV6hLcYgFA4RvtyioVQ6N6yAGuYMQBwivdZgbm23DPz2TFRLfsaj8Xp+x/yxIl8HCy2ZbzwJZonikcwDsPgIvBORLPHNasMAAR6Tkxl7ZmhZom2Y480t2X6f7+osWRAmvtc5dvpq3uKAdB8+zhYqGi/t4D6tboHqx8/drpxCaGIcvQCkBQBUvy8FuuD2fR/JhPvbBhgMCpAQ89iUsFy9ujsXQZG8vq2/loxAEIYYI6IhnbkaPQgLALgj9cHpF/4omkBwTvn7FjqyxF5/ZUP+33iw0XQu6a9na54mhz+GFwhbt+lqnGAKc5PPZ6hsr4AQIhnE/t0EHEYoIuPx6CyI1BcMQCaUS+cLVS0XwqgddjN8VqnjJVgU8gyOOkh0Y98kAoRdsWQM0SEzTWMK1+wwwLlScyNJQHYLQVY+tHZh+SFgr9Ayzh2ZUknGQZIZminB2IAQJmA3v8oZkRnm/8MwiwMoMekfHRTfXH9f+geY2+7u0Fh4WbopgLhiGUCWV0AYPFHEs0soNAyMam1E7mEC4YBZFdwTscAyKhxvU40Kjl906eTo6UAiuWoHGZ5ylgh9uX4IJrhCkCCMLdSeDGd/j94gCKD7eBk8ldZCmARA3CJYkjeRQxg5j50YwDEKAwwfWvLjfe4hjYAK4Fztj4D7//8DU1Uy1+NQ4cBzC+ySGxbDLw64eHSKq0xdoyvltB/QogBrJ2+dcCPNQUAln0GMQ1+5ZaJI7IVRIegyWRAUmg7m6UA0yUbc61emFzBid9B8RiyCAPonjVRjn1R9jjsDP761STBmxCwg7vguaDde53fWjxtNY9b13//AB9hgBux0pcOY7a0GP6SC3OCWDhZ/Kd5UQ00L631IJwvMlG6DGBITNlSK8Pfc9EveWqKSM77P3/hfsKZ1/T//tUHhTL/gdwTasvZf2PZDG6vYlSOYWfh/7hBdJz7IrPetZoAwILtHpnrykhrYdaNOwy2/mKP5GYpgOi11bSz3saV7zkMcKuKaZ7uIM1f3XQ3c+voRwJG/RJhlwXwHZyBYD1hgaSITPovjHrr2Fv+6kPeRxigvxSAeubphPHsSY57DsYL9ULUDzM4WKU08r5/gpeUAAiyxTF8dtA6wCSr8/7bsEylQy8FsMKD1Wl402oL5Xy36FR7LLWwI8qxBvzBbFnAagIAixDfaDftgkyVW838Al+cNQZ5fCf6KoWPpQDkS1kRNG8zzxfhyvugWA3jCjtcVrnwsgDkEQMQCPPxjWE74e43a1aDFnAbBuiWNrYucETQi/6JS2IqqcXcAYbXurmAzzCA7+Rg27pHN64BWAlsF92y8f7P5JCFTf/vX2ipMIB77c9RJMCXtBFLyzHnioPWpePzCrpA0ZGzahgEA9YRAPDfuFEOcrtmYRoD6BPq2XQ0BimHYgA03oKW2obTZ3Nr2JOFU8aVf183DKB4sJMTZwt0WOZg4cyJqKoAqBGlAhALSzauE4utHwaYiAGQNzXAGP0YwEAdfcQADI5XKdBmgaP2pYyqD6EZI9HYSCuCiaQOUg0m965NwMaKdDVAS9iV6EMEbLlQYYA1xAAMbnDwlFgGlntummOphlhBAMBzU8Y6tl00Cy/9lkn6/+B1BUnZyaPvfhvVUoAjxjJN3b/vPMefw4KAmzLP2Kyr8AejsQ0AU9gpANENWw7p//3TXS0FoI44G1wWQApqQLdiNrg1Rk+ljdYrhhiAuPpvifGs3b+iG9EJYdsdeNlIq8efQaTDVKfytpKAnaLShU/6/w2D49edcb3Q4k/95DOfq1BCEnA1QHo4b0ZEBY4s1RArCAB4A5MI8dFvxx4Gk4ckiQRJIlp+KYDN6dO2PZk2r8OlAIqlGRTr6kT1wm/oX2vJkcZiVAPAGgfzQaqWlj2y81cdVy3gKQwwtxRgqvpLLhlWlqozFWEcA5jylTAJA6Q0nAEA03gb78HtYK0lVlcHB6/6mBEd12qAM2M3MnmI2wsGZ+Gs/MQWASx8L2OXY9iv/DLd7kbNkXoAwE8fiX4wO20WLjGAPrL3IiCWSwHoeLrhpb1mvpj50x36BXSr4S4M4FG0Mx1UdiR5U2B9cBApKTCd6Be8ld2GAVSWApDaJOlwWcAYCrJNKMbyPcUAyPDRzLfZgmGA4YtAUCYDXwMJuMBV+n/ybtcxJqZZsyQAH0yEAUybeqH0f83Tju9ctDnnPrjwUoA0YgCsbgHLBa4wejZJBwDg/R/EQ7MEVnH9PRKFaLkekohICqJ+GEDF+DdOOvD6hMyEm3O/gEEYwGaLiegngnHWK0UBmIfp0Gc+bLVazWZmdk6QMACp3c4CkYARxPm/UDEA/VP0GmkRQT9wEeYDGYAY4TysfHr/w6srxun/muf2ynJXVLcErUX8USEGXvVQaMZYGmNJv3zsMYAoKh9234ToSDoA4JooBsAMPtcY8hpplun/YzfjzhcgxUnXWSYMoH+WxrFmzeIjwV9LzNo8zVTDAF4DRQBEjssRH9GIcG5L9+GW/n+DvQF346zv6kz2ngUfkYDxWxZqh+kdo36Y0Sm2CZI+O+TxJiKaEFaAswfOzjoCjnCV/u8Qbt7/6UaaTf93eElXM3nqYYAZ5mIDcbXBknZ8vDGASKtN4zWPq5d6It0AgNPHG2/vT5/BZ+PJ+39zgLUa0Zp8om8eqBjzZkaF76UApL+Q37ln32ApgNbxrs4FAKjDwI3Cd6B7bZl+4U4yr8dKYJX+37+Evfug7cmi5zWY8Cz0P58of+x4Rwv1BioS1rSdu7qbruFV1ouIvQMAAHPchmw7sJhPzNL/pfa5nVLUDrCX4xMSdq4OzrUV+wIdVGn6R4zOsOiXV0D4TpBky0zcVGgrczkSDQDA+z+I534dwD/j49mo34OrMMDgUoDTt1NVMl4K4NtbpBsDIA/pgf6q0b9OMmGA9Yg+ADRJYHybMLEYzmGLME//7xIkDDDx+cQlnHD9oEevP9sf/C0CmDzLsUxzuyBA3L7j3PGBOQyi18AxTtL/0/b+T+ND6GvZ7z6WAnS/0qpPUJxUU7UQs8xFzywjfKMT8XHV1gmzamwyJBoAcERSXX+RbutPy9Uo1ib936D2LtSIy1KAwTo4DwMsEAMgP0sBtEr2uiLB7bkcWDiJGABdwrlRoh7Z5vhrbbfp/8vjJwwgiUS3ZPswACsWjwF4bCfLSMBIzZLJJQC3IAYQmoDDarXef92EugsL2O+uhPjEpYciAelNA97vyP4CCk8ZMYAbYqnnkqi7qfiDAMAwqfX7Bfvjclru4EOyeXLG9Xa1FIDswgC6MYCJ0oyKvMUgB1/ds89vKYDtuWGJRWQBsDjcB3SoqLv61Dp9mP3C/1DTl0MPQrc056sBLDk96JkLqvSHBWMACzXP2NNROXL8QO5zTuqg/ZPC+VzASmHm6v2fqspE+r/TkKrqiV7DACoHhMOyUizvqYdaLWf6gqOhFYV8519Dtmh5pwKSYgDAukXR71Ng+afoLgwg+tsCdA6IbClAjDEAzVNuj40uDOBbCsWhIYIYWDyPMqJx7JJQYzbG5na1mUD7sp8EwCMMoHQdh6atXQwgpMxxce0ofARADywCWCV+0/+T6FGXmzBOAnBSA3s5Pv04JAlK5JElzKjwVX9wEN5AAbfdRHdeSTEAYEGaY3ZxYbOElssn/X+wHLueJCVR+3M6gxa+86UAK4kB0IJLAexPXwyWyijcH4ADHjshy3G3EIPpfpYzbVicLgWQg9I/aBhAOJyRPU/uLqsalFg0CACAHt6ma0bThRy/S8UVgeo4tN99LwVQPEYHm5JWkf6vj21HUIgDMRqqPTjXDYyh+9QQALiQZo8PND379SrPPqrgz9KFGnHcEYjGwwCulgJEHQMgRksB3J+eAKlqiCB14hi4PsYX0v/NcBUGkCTFyI8B8FgNMEXQUIFwXofQJHQr0eCxwbEIIAF0nqDfwct28x8dhGUSgNsBtcyOQIrH8Cbmus/jUfIONlykoxdECwIARAmPu6DTsy9Fd+xp2egQ/nwornYEoiF1weFSAN+6iL8YgMHB5LMyTq64GGlrcCBJFvGh8ByvUTLRlGmk/9/gUPR3S1MR/X6Ghnuvup91fuLmTSpjOKFbAWD1+Nn8h9cUoSL0Db5dDIQB5oiwytp4lby3hY9ldSwOkyEIfJNcAEB/TkJf90ccyS6+k9/JxY5A3TCA+o5ArLYD4hMDMDuekgsDLDA4Ixj/AAAj1Gaz2zlgNoSfBq5WAZ7DAIoZABwcCm7tZrXSBu44Icd5QreyeuKwi4ADPI5Zzt7/CXoV5ZX+P1g4+zCA8YzCfB6aqF40HX6SGbE+qNoB4IjkAgCapDygeEztjnVdH+n/C+A2DKC1I5DddkCOH1/UMQCzU/olEI9eymN+AMAEzz4UDgM0GMvPDKk2t6elAHT9jLT0AT44dWmP3mtCjvOEboU1aOTU8LFFTKjThwqJuMfKybdMcBUGEN3/p47hj6dqqhfb14A8wUjsLnLPXG4W+GfVAYCUOzonKeJMqMX+wFxIEimNdgTisx0QYgDEIAywzPzAaRYCqeHNWIpdzvBCcSsAhtl/rnAV/pdEYnI1QHQxAHUmxe7MXTKy4AEgorTGZiQsPwcsc0XHV3GSdCwHShosTYx/pcSSg8jFkr62nGhiACM1Gf7YonFcxdQ8jbhFNwJSP60Fug0wZaUBgMSGTP92+EiQJbBJ/1+4pZbZEYj/dkBa6MYAyGfMwOyUsXIoxGQE7z8AwBIL14CDcqJjgdUAg8oA2U7FhmerSEkX0X2l6qUSA0jlPviC5gUecZ3+77K7Lq6xi5va8x977nYEkmIyBkDLPg4Pq1KUkF4W5/joR3wlr9NIANN7BH5YYwAg6i6uVHkxelgop5wDZ/Ls5j8R4XtHoDEFwm47IGcs4HP3GjMwqI/bq1teC4A08DBBRSZPmI/oCQPPPv2fB3qiwJHoFzel3RiBY1mclr2F32PSuCG+FrweqdzH6uGTYQMiYmHvv8F0o2iqx6sAOIzlT8wAC8wPRuW7qtQlj2ECo0b2ZFL7k7xuSsaaAKDJ6gIAkQ4NV9XulrOw8mkjzm4zBcYuoF2u/ikOWSAMMLYUgJTvXU6mKiyJ7+19DI6nyym2InyBMEB0GSUAzAI3ShAcpv8bT3psnrv25G1t8MnzHguKSwGoc9i5DtFx3W7adwDfOQDABuVJZ2CmcTrlLpf772favN38Z7X2+/RSAJ3+ptce4ex6vbB9H7U2X6+0D7W1AIiNdQUAohsRJhU2UlCWkQTufTXRPdE+rlICB3cEmmhx9TCADxebmXDmFgMwO2W6NPKTuhAh61XggBbuJij0N5esozXPvS/IUgA6i36hsBTg6uReZWYuZlLDeUxnecMRn4RQSeImOLJ0qyJ6DbQQRM576WLGv/rbiLCfiyVJGnfH667aVyTctOPgyspRAef2dEySV//mo7k14Ii1BACi69kLV9jVGnGVC2lfwlP6PytcpQT2lwJMKxCK6gViAG5PmS2wxb7YIHoebFoQIbHLkOUwk8j9Jf826f+c6MYAKEgY4LwaQEv6X4roVWns2+AIIsuNg2My4kdJ4iYASBMvYzMK779ydrbW8ZNFhMaJm3l6RyC3YQCLciyr4PGh3RR9vVKQv6z0Vckobh4EYhUBgLj6v4ONRKyv7lW2OnMmy94LLfgoEC0OfQEGYYDJ1pBEgk+y0hpiAOdiW6JbHgvAkvCZnRJgujF15qKppDZLGD/uYGGAdjXAmPQntUZjry67SSFkf5uzJHETjAjTmJBcKyHqp2w516Sa/t/Frf0+/HXnQiPfz/eyqHP/TS8mpLNeFp/YxY5AYIT0AwARdXs+VfW9IEBD6R1rFD6N5RbnYYCbHYFopOlVIj/ObRVjWbqeGMC58C5jF+JgY3CoQx+Y2clj94hTlSdhmE7/P3+SUKP3e59JGMC6QY6/D9y9tqICEBCdG79a6LDURQEAIOTMGUX6vwrd9VvpzcDLhAG4SXAFAlfZ4ap6n/l+HgcEFB7QI/EAQEQd3kFVPUyx/gxG9+sAtGAuQd3uDEDKXoDZp8JH+VhbDODmQgCAG/jMTrEz1pJm81t/85/VoCfJ3S4FoKHVAN3y1QYLn1Eluq8QA0jhDrgQshn5DLAkWbxtPfWlWL3/0zl8sN9HuPyw38B3nav0vvHXNsYlc3hcR1k5qAithEl1YW2NASjtAEBEHZp5VbUXBKiFW81lFfP2coWrlMAJL0D/AajEAAZP5E9KMQCexNgrQErAmeKKwdVjiicS0eBzkJ0XNvMky0c80fX0xIKP8P+g1I5KlHup5srlNQDrIPAot5y8hPf6T5dvsF5/+BTLn2+JC2vhksZSAD51vH0gFssCYlUcYq038EKyAYBYOrmzei4yy84bjLbL4IeuZ3NA7DjfEYiG3DoKfoFbZYOD8sHJQX8pdbUiNnh/AMCIMOOVwxQ6jUG7rG/zny7TMQAKGAYY2xSIlPph8K46cHVXcjZyeR159VmABgSccd8/O/OpSuHmkWIf6f8R4XtHIBVTvX+KUDvSBcw13CNONwiyYQlpDo0BnEgzABBL946lnjeMagP6+W8mEsjJ0u/QBEsJHAsDKCwFGIgBUOjG9C3MICwBiIvg3kpwRo68XiuhwgCXpQA0ogCQ61Hj7nF7H82Q8iAskFmp4Hwikc47Rsf/q1eTq7P1kZNv1WEwUkLa7wPfacYAVszMg9N5UhFrDRFXHbgkwQBALB07lnqOca7/lW05hsGMk3T6vwznCyAa+X3gwRgAzT3Z4LqG7419OK0z4Av0TcAHnUlpbSPVI7M7AJCL9H/eU41K1zMR/a6WAtxcXjkMEErOz6w3dTV6YxbZMdc9PGg6wBZPndOmWA1B0LuMsL88DwLa74oxgMlSFpLlvDW1cVw8Ke5cKw1p3ysYI7UAQCz92HE9g060koiEeRX0hFEsD1iB9sZDqhE3Bv+Y/T+7ZvA2FrQ4iAEkistWDB6pAkuCx70w/YGK9P9JgmUR3hTY3xGoe8D1N8auHzMWHcEQ2SAgEFipwuqxuvg5gRkzcOwc9YPjIVgsfy5db2Y6OX29wllH9Qko7AvkQ2VYTg2BwrN6kgoAxNKZY6mnKidBQqayRFUIOXEosBR3IdUIlTDAbAyge+65hvOVUDhGEcQAAsJyTIG1s0Ljhiey82IFU6J6vwu5CvBc4ETgn26/mr81d89XqQ3XI2QnQTOYgUYDDnHbndrUOscFhsNN+j8bja4rCvXEstcdgRRNdf+weVDWJLwgAHrDukknABBLN46lnqpcT/PORM70ZgKpNOJNc4VUI26qMpYYqJ41sPwzCifMRi+7BvmajqIHkmNuqop+dDIJckyn/499YgCHm1VA67EEDgPclDYRCaBjAunwrTkdS2Ge8xrkNWALk8kcDLLso3EfSyCXt6AXCU40/b9vv4cR4mamuv4igGTmJxM5D90AJEciAQDZfcV4ivIygTC739GlAJYT6Gom38A7A9yUOb4UIHoWWARgfBYAwAkpTVmRcNXeTtbtrYBQYYCrHYFoQoG7fOt7SOkV7lbCRiuvo614MNBcgCdevP9ei5g7wE36P3tCxvJHTPX5JfsTQX0fKGytw5ShhxX3LkBLXwzwIoUAQCy9N5Z6ajAuNKatyMHjVQ+2aUd+nqDBGw+sRshrIT2UXyDF5bKMYLgRkPFZUcCuBwDQg20MgG3FtED6/xBmz9ZE9DsM/yuGAfQVvMkaXL0L/5ATltfgBJ4wcIv7HmU9FV5VyfXEOu9Zvnkt+3XSJLxsuIWV/W4SA5j/2oTbbMKZg64rw5zkdgRK6FaAHtEHAGLpu7HUUwMFmWEoWTy5D7gy1koh1Qg55ws4hQHYqWQ8rXeetbKE3bOfIw1/KzAAj94T08Ia6f9GBF4FqBMGYDGqnIvXJOU14A+XEQUcof80XU08mMDCEiyW3+1yijn+UtWW15ifzOaxfuV5kpKGkNK9AB3iDgDE0mljqacPHCu062tKRimB/QcpSfbXHsbFMosAbE4EANjTm8EwHH0wIA2ki7aOUMrYKD8hVwF2C6SLE2H4FMsnwy39/wzkdbrgwa6KiB63k6oOFOJnYj1O/r3rie4R3WMiehLKTEvAYLH8G0+6SgzAlS3vqrP5zLV3IN7FpXun2K9B+sQdAIiCNKcGnSlecbX46GI6Vy3IyLi8ZdaKZpESOPIgj/sI82neZQVymgN8Fj6PGwBFksyvDHhT6un/lkT71CwfThRhAPPtgJjLztgs+9jqC4ZIUkgBXUz7gPsZQLfEseMta8Z4UMzGACh4GEBtyT47W554b7njrW4Q5WABIg4AjA4PTvqT9zEc5E6NLurgsax7RgzsC5h0BEgi6v+cYEC05CcWAeiy4CN23GacpAMIALcOwK0+rpCdF2lMeuFYfhUg0dBmgN1K9PMAdDvyRN6oAcnIVuAZdBPgHF+Z+05KCKpguMzhi5lQ9vvNUoDZX++TRGJSls8Iek+djXkYgGfF1EngFoA+sQYAouirUVRySZRMRE/pA+xRtJ+DqRHd0s4F9sIAV9qeI1Xgppi4OwIELQBhSdXnvjBD6f9e2jXyh+Wqu7FYBXj5buCKVgoeTyCv0wIPEyRJsI49cWFPywFZ4tF+X3wpwDEGQPxUL7bimG3FABgnygBAFAONfyUHazg/4duJBPs0MUO4SbIh1BsnmBrRvXy3BuLyweUW7HSIsfP6Fx89zv8iAJNGjV1XiGEoATAFYgCWKG7+g/R/pzDaEWjguOMfs4HlYDh6EqxRyeuoKgtGgHhaHzbDNuCQv+2q04qBMWkNBz2x7GdHIElE42GASzLfyKZ/wR4Iy6UAgkhC9ILYiC8AoDTEomuIyQAAIABJREFUoD9doz4vTRwZcm84TKxDBFMjbso8FyuOLwc0Qp2eoxUFoRh7R7y6QgLzKqQDaKeyZLoBz3uB5X/C7fMJHwagqfvh2RmtiFdegw54hmskwfnoivlEKI+lzyBcFBILuh0t2JI+tTDAdAwgMDwlstNaLXOLDFsRLEZkAYBYOiuTerqtxtCab8NyRk+8voZYU/p/i4G0DalG3FT35OsfuAufKwrdSErTRQAAgChhssyZp4k1wUiW34C1jxnSG8E2A+yWM9Jvo+vR80DcgyVJcAiBUYynFldzkqF1P3F5h7NlPAPBIAZAPMIAYqjeEzGA8PMTJLJb0J7rI6YAQCydM3g9fVVgSAaMfOOU4A3KnvApgb0wwPAmgwoeN+MNBAbuxrNIsyo7RnEbjyI+Q3jtFbAhjc6w2F1Mz1puN/9J4Lmc8PR8gm0GOBcGCDOq1O5uLOERJAyeL/CE+66lNnXaX3fWga83h9+sEiNHakDqBI7ln9/SsJMH6wA0YFglAEaIJgAQy5gKW89QV9eNBCwqTRjKrUmMGyekGtHXFiVJcfvZ+auRL6yIT/LGVePYxhEAqvA0bxgyOF9FNIkFxV8vC7yZwEKZILZM125+R8FI5HUk1VwUNAhgi1nnVDprcsqbL0FqrNfvf+VGGvCWKX0Wtd+dLgWQRCQHlgKMxQAm7nSFCvXlaUAAg0iIJgAQBQFHvfdLK6ckmKgLgwdItbNSxEZ8hlQjbjz7E0sBqHdw5+MoVAdJLiR9LLpCFI8EAGPCzjtOru77FiZnKtE9AOn/i8NkM4FLyZr90Wvn1d2WIfYYAEiBWFRhsDiWk5DW6dPdUHSPQ/q/NYFj+SNLAWRXwZuelBaetSCRATAljgCAyQBfXHkKnn3PBFfp3etMH3BFSDXiZuiN7zB4vCifZ6Tcai4HHTSYOby0EKuOBwBzxkZg/3MnYzXRsel71uEWBuAwyxpUADI5JfAoAVsMOqdNf9Y4d/rQ2fT/FScBWEq94EJ8cCnA5aY6tzd8p5Jo8IcAOeFXKDhSIKCHAK9EEADAAJhgocbRn8i1Z395dbxYcfp/i734DKlGdAvsfj54SzpLC0EAUn0Y6GegCxYBjJWp8tXYa7A4y68CJLrdU/hc4OhOgP0SOM3Ho00Cozwq4n5WrIZEnPjuAAt3sIW8/70TZ7qhj1aIuefHa79fLQXoGebU2w4o3hgAACuHewDAajZbcPpZXsvkr9deNb/umkPdUwaB8AmrRvQWg4z90NDAkYyf3lXbOHEHcPYpsH0MADiH87yjiMNbUJ6UDAX9fHEJslgX05PkPpYC0KXMqVWAPvFyTc7yGnTAUwKcWbJ/6l1LK/3/9Pa8S4yb9H8QIpZ/tRTgnM/Xt81jjgEs0TehJAD2cA8ARAG8/7Zc3w82/2lxKDtDqhHXzn15Xt4xGAawiAEM1DeiccJTXYh/EM3AUz8FAQnYJVxd2r4clbkI6f8xEHIzwN7nw+H/7jGuBx8meABAGphNzP4E8u3UKoc+dFN0fASz3/3vCKQbAyB/zxPaJgCmsA4AOBja/tX/9L3/Fg2o3vy3KYSY1j0QUo3oaAHHl4OdoxctILUutFh/8XghnjEAANZGAl5D41tQnII6hyH9X5eF+1fIzQD7m/vNCfU4Bh+ENXsSeT5xjAegjW7/XM77b5z+f/4kkbHHi7Cx/Jv4vUYMgHgtBUDfPIOmWDl8AwDomoNE2Sw6+gTSB7r4kJoh1YjrMMBwDICuDru5+MThap9a4X30sXIrpDKIZuCjmQI+hOoVDq/rYAmV2pFI/4+BkJsBdj9py54MA7jNGcQEDwCwJeg8wi333+/FUpmvnXeZsLH8G5tdNQZwqoD6rwEp12yBMyxgZc4D0INpAMDlqPEpthce3QEmk4UlMdL/e3iKAVDYMEA3BkAjdzjk+FetArqQJamo4ErARQSS5DwNTndvrdnyJmAvO5/bz7qrGYahppywewpfPjz7Dmi0IUyaaKSqBkXN3zRj855x1RZi5bcPgOEQUE7XE52359kVu/8vQMBYviSizlIAjRjAqQKSiIQL5Ye5998REOXAH0wDAFGQvvffBceFY6NfE3W1ByeXXI0TwZKQKYGdhYE0sRTgclqvGipHalfOV8EacBD4KxxBiAGAGxJYBNAts0X0PjErB8RM2M0E6FSeOL/qXoiujhn6eOwaM19igl8Jqc1S6LuWeGtA455mEXN3f7yDM9vpHUkAPfyN3YCx/GMY4CYGcKrTvNSWdmGAdbj+AfANxwCA+7HqYQ6G99+GiWViqd2qC7zq/yFTAhWXAtyeb3V1M6au6dxlzyEGwIwlmgRmNrghvS7hdBQh/d+GsJ0r8CrATmGXRrgpXCWpcOzc8UNmi1pSFwIApIz1LG/l/XcrY+TwW+FwDlyZDmBPwFj++VcBLoLVIAxAOpEAuP4BcAe7AACGax/fbTJafm9Wdiydkf7PhmBqxKnAy1KA7iV4EGBSCuhZ4NTyS5OewxdYEqRL8OyH1zPSlfffHob365/gz5ljGOBy3O1LMdhYmpWZWEwI6yMB0nyIwWcKEANWnX/8ZNH7tt8ZsfnPGL7HbkAhTuKyfH8iDEDTLSA7h/WPM60nOiMA07ALAPjC6Ry85Mzi6VpmxWq7Z9Uvg/T/SRbQ/0P6AmhKhwDLgQYH4AY4X2g0+2/4LYiKkJsBnt92Cp46xeqavQLti8AiAACADrpmsdXBitqL1UIDR6SrZaVsvwsiOW7Cdz5X6obnZSXGVTI9ce2g4dYHrwAAeuACOGlkB4na8up1P8XAhHQViMUI5gvoFDimQ/hl5BaCTUrLexYwfAjeXsADVv2wNxGNbttiBp87XRw+zznkZoDnTzo1GT6FTXMBhsCEBOvEU+7/4AHdPX/a10j/Z0IoIX4u53ZTILoIda2kvsFKuVgBCAA4wigA4H0YOzId4k3/1ytNra2mGnXkel3HrktbLnW7cEnTN/APA9CoDrEwSvfkb0ZYMgaQ+vDRAE4mcEOQLsGhHw7NP7eb/6zZAhNEWyI59K8JXTcjAu4pfPmkU/bA8ZPfBgCLAMACcBAHYEGcLRQYmzFZzVqp922+9rvbHwagy+/52YcBbq9iVUe/DNTNUXWhXwBPMAoALIH1HByp999rte0Fm5v0f+CagGrE0mGAoWqz6JIQ/kGAsQ1uWGEMYNr7P36MHlENtGZDzVbKu4buG7qr6a4WmwNJItkudBdCkrj+S5Iub2vRPBbyMZOPWfN0LJPbZMPhhwForlm4NRoIDhQlcIPjLnE96ZgV7ryXqhZofOFxiS/OGQAYe/wIFctvixr4bQC6jCCIbwCCwyUAAPHhjwXadmA2n7jqTfq/k/qtQ5gEkZohUwKXCQOw9f63LBADWMfw0YNdoikIzapiALNzDqMp0hcyp+ZO1neyuWuabU13Nd0daHsQ2z1ljcikyBuRNYJIkBCSBFHvNQkp2hdZGwaoM7nPm+eqeSrlYyEf8+Yxk4+C9qHv9hoOYYDZpQCYnkHLCmYjYEHAycLo0or9eYFuL0beCjl8gINrJMryfTCgEG/tVnEq8yLKr416WsvDB4AdLAIAi6puFnNwjOn/3NTifpwA6f/8CewL8BoGYO79XwDoXxPAyQS6rKQ/jMyAa0j/rzd0+L6u7+rmrm62tdzuaXsQRU15Q5kUWUN5I7KGsibLG5E1WdbZ60eK/utuDEA0QjR5Vmf522dR56LOqM7kS1k/l81jWT/lzWPePAr5yKVdlt8MkGjg94G5bweEhXpgAVYifYAaS0w5YxL/7P1H+j97Qtnv7Y5AN2GAvv3ORYi7Ja5BEVdtgSNYBACWxmdA3gnhvf+Wk3F3keDIVy79/qmJjikCmgAB1YhjOf3FgzZZBCMV4ygK/fkX4hk7wXwssLpBWBbugWPDTCocowWzYfXyQR4+7g8fX+vvnqmoKWsokyJvKGsoa1rXP2WNyOosa0TeyKwRWS2z5viEjm1ytHZbp39GlBEJKY4vmkw0OdWZaHJR51Rnos7FfVacggF0XBxQ1I/l4XPx+kdGddBGIaKwqwA7hXEPA4BAcNTZADBFKkj8IN7/S/p/+9eJ939NE3dk9rsjIS7p6ocBbu33+MMAEEAgRsIHADByPBGsYccv3J/ckf5vQFiHZEA1YmrxYPcSs63j0PW/WO/14f+OVNtaHsQAwJm0NwIa9/7fTrapCO7DHT1/OBw+7usPz/X9qyxrUR4orymv20z/o/c/q4/5/ll9dv3LrCZx9NCLk+s/IyGIhDy9OMUABJGQJ9d/k19e1PkxKlDnos7yN8+izkSdN6/l7s/73R/V6+9F8xK0jYKvAjwVNjEIAk/SWAQAFgCqSMyEz+3TZLivObw8OvOyhIrlt0UJOWS/d97HuC/QQPPEoAnEUEfgl8ABgJBOap0JJrr0/+UbVqp486/T/914/yMSFAkRMiWwlzVAN73A6EIRiEO3LgYMHC2iTlABbgkVAyDP11Xx/rsi9FCSGb181zx/2B8+vtbvn2V5kEUty4Mo91QcRLGnk9P/kvWf1VK0f2uZNULUUsiz33/8n7y8bgMATS5kLppcNIU4BQBEnYumjQHk1OTikMmqLLev9z8UL5/vd39sX38vDl8Ct1rwMACWAoAbItDcQDL4F/2z/XmhDr9M+v/6CB7CCyjESVyWAlBfXs+a9lGAQQEiIfwKgGAEn4adMHQXDHP/578FOnDovIF9Ade6g43lb16j5bs00gzDwmHgARfYjqRQPcHfdRWX7sWf/r9/I58/1C8fXg/fvTR3O1kcZFnLYi/Lgyj2VOxFsRfFnrJaZLUQjWjDAKKWohEn7wcRSZkJef0wBh+NvPwniISos/yQtYEBeYoBdP+18YBDIQ+l3Je0L99U+/vvHvc/bp//unv9vdz9kflsnnlCiX78OLAiMY9OAPzgdF7QG2IpTUlIQkqCYPb7qRxBV77+gdX8MUQCbtsjRtEbY52BC0IGAML3OjWpHFf6v4PaGk20tz8POF4q0v9TgmEYgK4+Gy/Eqgrh5i/7GABGjQ3IMgUtKcUAJqYUHxNdiHZrCnr50Dx/t3/57qV+eG7z/WVxaIqDLPaiPFCxF8VOFHvK9yLfieyQEYnjLj4kSQiZE4mTI/oSCbgw31bntElJJAVRlu1FtjvuEXSKAWRNTnUpDxWVVRsDkPuyKPfbd8+HHzYvf909/Vm9/p7LveMm0sJE9LveEYhGuhIm6ZUQ3opcnpS8yWtCpa+ySv/v+mSR/u8QPiM4oBCXNBkG6FbrOhJwe1g4FvP+I/EP+GDFKwBShFvu/0BQ1yFMhIAZgig/eQOMdCk+OgSFUCOIrn8fmPRdMfFi03p8Ok3UsBp+IBQJxADm1u2lkf7/+H3z+M+X5w+PzWYvy/ro9y/3sjhQm/Wf70SxF63rX5AgQTInEqfEfTrt4kPjf+nWcTLQUFcBgO4LQfK4OCAnISU1FRUVHSoqSllWdFoNkFf76v7l3afi+cf75z+3z3/kh6eQM5GeLHK9IxBNDoWlwwAw0wEA3ljS+++9BlHrzxlRtlb73akQnw8DdK81lOrXr9oyyNE3AMRBsAAAl/EyNw1HlP7Pzfs/iMv0/6iQFdV3Ut5JuqvFXUN3ByqaYxagJJJCSBKnvySp+1ZIol3WPObyUchHIZvQNzNCQDXiUtQyKkDw3mvWenxUzgRAlimgoDEAsr50EO//ss2129DXf+4e//G4e/ssq31THmSxl8VBFnuZnzb8yXci34vskFFGx419jv9kGwyYcf33g89jzSSv21FefyJP58pMHLJiR1lBeSWLig6VLCoqS7kv5b6UZfFus3/78dvrj/fPf26//rvaP7tqMG0CrwKcGwo97wFIhOBaWDBYuQ/Xhlnjx/LI+oOq88lq0/+bLTVbKe8bumvEXU13B8rkmP3et+LlSy6/ZfIxa55C38k4AWP5ko6/D0ydUgeGy9zlpMIQsx+FcuBVtCRwC8CUMAEAXl1uXDBH5P33cen5iXKy0oPrB53BXJfKqb6TzVbWd03Tuvu3tbjb02YvcklZI/Lm4maQoutvOL1oPxHH142gOm+eK/lUNo+FfMzlt7x5FPQa+k57BE4J9O2WZTV5qbc18/GiBscMS4QBQEDb3vjS7AaSFz5/qr/98/n5+291tW82u6Z6leX+6PrPT4n/+T4TRCSELCSJk7v/9O+4AkAnAECkGQA4v7h5LYRosuJZZK+UVzKvqChlcVwTIPel3BdFub9797R99/bz/94//hbytwGYhwG6l8JsnQDrmMCAU3h74W9ziq3XNA+eMtUAPV//1esbkWUD46dARLKkZivrO9ncNc1dTXe12B5ou6fNQWQN5TLLGnG038XJVO/b7yfjnUg0Ge3z5qlqnkr5WMjHvHnM5KOgoJv49QkoxFvr7rwUgBQE+lBZRoeIm/8nz4LgAamALYCIiLtaoIIr5aB/wHDDGE2Caaf/1/d0+K5u7pp6Wzd3B7k9iO2eipoyKfKGskZkDeWNyJosayhvZPdnEy6Ra3FuHEGiba6MSNQZNXl+/yK+y6jORJ3J17J+Lpunsnks6se8eRTyURCPxQHBfQG+3LIM++1s00U+s8UBwgArJ2wMoEWlAuozWOTp/y/38u+fdk//+La/ezlU+2bzKqtdU71Q+SrzvWz9/llDJITMpGz9/tmV6//yj9T+dlELAJxzKWXf+y9PEU8pSIrsJateqK5kVlFeyUNJRSWLUpZlk1f5D3V1v9u8efv5f6p6Z9945piIfutOdfP7wLNdDJEAAEBE6M6RJnPqoPe/n/5vUHIMHB5k/dDUd3WzrevtgY72eyOyhjLZWu7H1L2sabImEx0xcmmljv1+CgZkkkSTU53lb55Fnbf2e/NSNs9l/VQ2j3l9Wty/9D2PEMp+7+8IdC7Vb9PIm/9Xw+puGFwRIADAtMu5iLfbXHzJ07WOHzCoNM9PO/1fZvT6oTl8vz98fK0fnuno65d0dvqf/P4ia0RWZ1kj84ayWoqmI+UuAYBu+n8mTz6JJhd1Tk0u6qzVIbK7vHg4BgPokDfPZfN01Cd2f+Y1A2UioBpxenNdslW51iV4JfzTBggDRIbjBSXB0wgc3oycfGvMIu0jBf31Y/3lp6fXD4+HaldXu6Zqc/9fZPlC5QvlO9Fm+jc5dVbfUT8GIMXJGO25+6WBP+Tk4j8nQdz6/c8vJFFzDgCcX2diL8pXykvKq3ZNgDxUTVZnWZNnzabaV2/eff6f7fNfIZcCkK4kX3wpwOWsXi1AFDBXx5YguLhZM/qNL43OMkAlsW+gFoOntZNq93Vy6f91RbsP9eH7/eHDS/P2hbLjAn3KmqvUvaxuXf+UNTKrRdYM2u+dpL2jIZ8RZSSozo4mfJ2LOhNNnt1n52AA7fPmuarbZL6nfPd73jBY2b98LP92RyDqNHCvYgAAS5YOALDW2yLVqHSqPd/+Q0XJayVA5aTh49NK/9+9la8f6sPH18OHl2a7o/JAxYHy5qQ01JQdtQeR1SJrslZvyGqZnQMA1DaVkNRarxkdVw5m8kqHoCYXTS6aQjQ51blosqMy0eSizqjO8/uMvstEnYk623++3/25ff293P8VvkMHVCM6Hw3VSaksq5qANYIwwGqJVIUYQtVHwIynd/LPH18ff/p62Ozqanc4u/6rV1m8UPkiSGbtr/u2dnpnu/+BAACJVjSf5EkbDbhE63svuoz7VGR3PcVYDODs/b99nYla5E8ie6W8lNldI+pMHJqszrL6ww/1dnv/9y9vP/9PEfy3gvQkebgwAHWunMrwBWDtxCCvLkzUVvFGzO/3+swB491hGh+bGfb1vXz9eDh8fK2/e2qqw8V+P2X6U+v3z9pV+3XH9V/LrKGT/S6OYZJTvj+J7BQDyOi0F1DTZu/llxfX+Xz5m+eyzkWd0SHb/fVm98dm93tx+Ba+sUIJ8bao7iqLywG9CwIADMAWQNecDPiIVAf1qtrclOzOxTrcmsjxpw80BT1/aF4/7A4fX+t3z7KoZXnUHkS5p/xwSRk4pfxTVov/n70v3XLbVtatAkip1R7bSWxn55z3f6u77ro7cezEc3dLnIC6P8ABI0VKlESq8bmXDJIgCIAgakQBJTFBKJEJQoFYu6o7igfyhCEgBFIGgNoG0P2J5lcwZQ+AivNVuX71ePPuJv+6KT6v8s/s4qGBLusS6Fw7quSIiP2I+qSniSuwASzT/V8k8O1t9f23x/zVVqyKKi3EqpBpTquM0oySDHmFhAhco7FNAht/f48lAMxf0NaY9RsAmuz+Q/L9qoQ0lP7YpKlbCoBACJLxjEAwrJQBgDMhUHImNzfFze3Lbx/X+c8Lj8VDzP/jbvDANQPA4NE34aLBS+HquZurb+BQXAGtOT+m6LQZjsATaf/RyjnDlo9EdQPZncjviuouk89ySipTfhdKeAe0/fY6ER4FobE2UOMYbIcCJEBgKDk2IjwoEV5oIny3PoBBlSSrcvMmyd9vii+bObjxXZCIt+PPawmAq6DXF8Pyv+WII3FWA8AyxtvZmaoj9fJneMrY+/vc/4/ERaf5/CVld1X2Ohd3O7kuKRE195CUkJaYVJAUyCvl+48oGFPqfokoCBCVrz8BEgNqGhNsEbU/AIAgGZOMFQiExJnGTzRrAhr/giqFMqU05aty/Wwnf1vtvt/mX1b550RmJ++iflzWJTAi4tyI/OkTxKL1Mvqqf+f8bHH/Wn57m93/ei9WZbUqRFqIVS7SjNIc0gySDAFRKp88VtvZ0SO21zsAG1I8KKf/Xr1///v22gDMX9LPYKP3JwAC0mMBoXlSMiwxqQiFYEKiYMoMwMSv78Tm5tnXf579+MgP79aJcCkNgk7344KAiIiIyXAYlR91FwHgoFlwKu2/hc7irR8egwvOpwi71zK7q/K7XLzeKr89U34vMSmBV8gkoGBMAgqVJpQIjcKfkIghObyADVN+R4FYMQYMqJHcPZ58IDhUKaUplClflZuX2+rdevftNv+8Kj4zEifuol5cloh35DhaAiIiJkJcAeABLVqA92ES7f+4XjH1CKhLu0uDWMHuTmR3Zf56J54rlwFBSUVJSUmlWAdISuQFJiWyEplgtdee+g9BxRyolf4mN7WnT1yHQUIAZBVCyTggAdMsASgTqlJIV1CmVK2oTGFVpjc5/ZJk7253X2+yz0l1f8nBfXGXwIiICyDyp7PEOJPkcCyUhXD7Ygnu/1/eVZ//c58/31VpKVZ5lRYizWUT8R+ZQGIISMCw9v1nTvAfa90d2OnWYN81ZqD2X4GcdM+v4rfIXgoAriUAERBBcv7IoZJQCRQSK8YEY/IFkzc3xWbz4uunVbG9/HA8hPRPTfejGWDpWKYMERHRh/N4Ae690+O6pwugy/z2ylvK7sTudVG83snbQvntybSipIRGfkdeQFIiL5GXiIJhR+IJECkBAKIuDOAx8jsAIKsYKxGAEepmAJAJVCtIUypXUKZUpklarm8z8Wuafb/dfVlnXxK5m7R3RuKyRNwgx9EScAyW+S1HTIvzGQAWNt7OJcCfgfCf2vcf3UPfosKFug/cvxcPv2+LVzuZVsplQCaV0v5D0qj+k0KxDgwAAJGYwSugpfS3tAnhdpHxX8eCIbVBhBEIQMUoLBAIJQe+gmQFyYpqTiJVv89W5e3rx/K3ze7bzfZLmn+95D6B52cjwHEJjIi4AKJK6YlgcS+6R/s/Y+7t3/fV5z9+ZM921SqvVoVIc5FmMskhzSjJGDCUvFP62wnT8d9nA6hN+OpQ+QDW0ETS9qT3dXfe/SqP6/sPmn5FT7TqfvLtCSz1CnMseFJxEBIrgUL9cSbeJtVm8/zrp9sfXy68M7DCOEp+GvP/AWaAoZlPZVEcihl/qRPgult3CBZqab4spuq0weUcPG733nhAyR4tv69Eg84tc/m+SOD+P+X27bZ4kVFaUdoK7538jkkJvJXfsXbXo5bu60RfUXxHnzFIfneJeyPCo0AuGCcEQpECX0GSQrKidAWVsgQkfJWmN8XzN0n+9nb39Wb7NSkvGtxvHFmemojbKv4AOb8yS8ClOYuIK0RcAWCD9NR1zByjoLf6uPmm6zydAi4Hu+f08J/88d1DdZPTqqxZh7QkXlLNN9Sqf4YSENGO3g91olP3u78AMKRnXN9AHz9BhECMZ8hz4ivFSVCygnRFZUplCmWapOXNi+2L39bZt832y+rhn0vGB7gsGxGxRFwJG2S1IQ7Ia8VSuAhdQR2+dCBO1gOf3pef//iZbXblKqvWO7HKZJILtdkvSiQOwAgQXe0/2TYAJedTbQwA6qwC0P0a6n69VXv9AbUL9g7AYB6Sna7Jvan9r5cYyubpCIBIMuUPEioOlQQhGksAfyM26yJNX37+OAuG/xDz/7gbPDjSDLA4c15ERMQcMYol6M18+Izou9N13QM9+M/S2O6HN/Lh92z7271clzItW72/+kPl758UwEsGhNgs0z+V/O6aAWyhHoEQBUu2yBkpN75qRUlKab0agNI0WZW3rx9fvL3Zfd1sP6923y9sBrgsETeIcljff2WWgAmwtG854kQ4kzyw1PF2Yul9sm4J1/OQRxxRLZdRUO5uy3IfkBy+v6se32/zV4/VuqRVIdc5JbXqH3i9ZhB5CYAIjIgjoaY+MBmIrvYBBmI/gu4DDktR/3GWs1VGYgV8BXxFyhiQplSuqEyStFxvstu79fr5ix9/rar8qO46EhdnIyIiLozIol4x5m8D6NH+H4+Ttf3v9+XnP35km125zqvVTqx2It3KJIMkR2BIHMh1+df1/qbBvpbzLdrtJdk9iRB0Rb9Ow0g7qWv5rTTZ50lX/RtmgITlkFQShGjMAAkKDvT2P0D08sunWdgAYLFmgJl/yleMpUqRp0YclAvHYQN7Wu2/dV4X5JGm+PbOOESLNfx8Xzy+fyye78SqkOuCVoUS3mt0epx6AAAgAElEQVT5XbnusYqBiuavluzr2v+9kvtY+d01AIBG0/U0IUjGM8ZyYivgKfAVJUp+V558SZKWm+e729e3P/9+9vNDQpebGedAxO35b58l4KlPlpGORjQ4hzCw7PE2V+5qpr3aMg3auWW5Dzy8kj/f59u399W6EKtCrgpa5bTKKc2JK+6hQCQGCJIjIKClR9DSCB6mwV1CuB8Ni4BeBgIaJ0FbTcCwYkkJLAee1kGBEuVTkFKZcl6l/1uubl/8+HuzvWh8gDmwERERl0e0BJwd4wyQh2HOkke/OmCuhPuv98XnP37kN1mRZtUqE+lWJFuZbIEJlNxw+cfW5T+o/a+Dw6HPeE+mLqCl6Z1jIOx7tToT2TpEaP1rrQlAk77bxB204D8SABGbdGPDQJBJ8sChklgJrARIJECCd78DyVdf/738tsAtLkX6Lbo//AOdq0Bw5ZjrPBSxTEz1GS9uOgh8SLbA7pw8Cmfsoh+/ivv3u90vD9WqFKuC1oVcZZTmkBSt/M4UsZecgGFH9BHI9OGbUn5XdH+g/I6tGx+yDNgKxAr4iqq0ld9lseJMrDf56tnLnx/W+cMlR+HFibiffAeI+pyZ8YiIc2Iu3kAzgX9GOg2NPwNHe4ZHePmG7sxUSoSzzNZVCl/flQ/vHosXu2pViFVBq1yucqoZiAyYihLIQI/2Q7Y2Iag7aH8N3UGgjWQeoKUpCLkSuP6DkqHApEKWA1OrAVKoVpKvOROCyVe/yfVt8ePZ8x8fUllN1JUH4eJsRETEXBAtAdeHGSoLzMkTA+cPxwnaKxH+el98+c/3fJ0XaVaudiJVvv9bAMEkB2CA2Pj7s4ZAM1R6f8LmquPrZ9BrxxLQo/dXJz2NbYTNTuhs6bhOvcxfcol481dfYo1CAQEkEWjei7KuGRFjGU8qDrIiQEIkBIL3vyPRy2+fZ2QDgMNI/wnM/wNVAzP8jhWilvyJYrYjMsKLYe+Lwpmn/9IpcEiLmVayW/r2tnh8/1DeZiItxSqX64LSjFY5JRkkGTKBwNDw92eGx57119H6Y+R3TfU/SH7XN/tBAGKsYliCsgSo1fxVKtiNZIIx8eY/Yv3s+Y+/b+8/XpimX5yI+2WmgCT1RM0AC/mWI86DkxsArmS8RQZrCJqXXdNJ6tLzx8838tvb3e63h2pV1NxD7fifKe6BIUPiSrJ2VgsGDACW7qDftaLvU9G3CoQBBgCbhwAgBIk8YywDtiK+IlYJFAwFZ4IzcbMqVs9e/vhwk/248Bu7OBsRETEjREvANWE+YoczZ3q0//Pj3gSDv97nn3//nq+LYtVo/5Ot5I8AxIg3Sn9v2J+GTJNLuCFMuOsE1oeWpl9Le7rLEjrJoW+WAcBW+qNrA9Bt/F0gICMKUHuGQcX4Iyb1ogIkwFv8/XckevH9y7xsAKB11qBxN5H5H5qX2p08xgYwv0/mChA7NWK+WJByIPAhoXa+NVKrxAQtO33nEMK3t+LH22129yhWRbUqRKoW7meN/F4wFRXQ1vjbkjsaibb2Ft0/TH53ab2bsBiANi0RJUt2yHJgKbE1Q6EMAJxJzuTNulg9e/7zw7rcHdeVR2OcOD4hEXcYMfsl+c7Ohx+PiDg/4gqADntmoTmReU9Vneqdm2kOPe/4epy424sb+Pq2+Pn2oXiWiVUhVkWVNjF/0gySDJhgxDVPfwaIFt8AgPqyAJN7APAfmm2rk01/2f1G4V+TgUDp4SGIABCBAIixArGQIBhWklWKjWBM/vKb3Nzcfvv07MeHy08L4xiD07AREREzQrQEnAxH2xBH4uK8RE9rJ+yIqdtYcfjrff7v++/FKi/SrEyzKt0q338EQsk6r3/grdIfAa2wP+0yf3PPHmxk/NZI0KS7xgRU/4M0xqARKj2hHzYkGy1Fv9cAIJsWWWYA0FvEQCDfgloEIAEkvCD4z3skevHj6yXj/vVghCVgEtIPjaoLtTP7XurFP+Ingqj9H4Q4HMfisj026ulm5gO/iFatPwAdtZv957d9QV/f5g9v76t1oRbuizSvw/6kGSUZQ0K1LrDlAQz53XUL8BN9ba24I78PEt7bRI/8LgERgNAyABA2DgESeQ5YMhSSVQwriYIxwVGs0nKzefHt4+bx84XJ+miaHM0A58Hsv+WIM+O0mr5rG2/TzRPX0TNeCbimmWO4jaHPOAG+/ya+/bZ7fPMg0lKsimqVizSXSvWfZsDzzvEfGHa8gtflH7FhI7SYwuBhF7wGAGNMuAPEZR2aBBHUvoUEKFteodERtIc6MyETviOsJIqagWBCMMGZ2KyLzebF14/r4vHyBHEO3gQREfNCtARcAS4lcwSmR09FZsajlAn89T779+2PIs2LNCvSXZUo3/8tAmHt8s8Img0ArB2A0ZT2Q38EDSlvEvWZjiqYQeNGaVTat64nsNZA63r/hohTrQvQabqu/VeZVd3sbYHb6jKoVnxbEaKsYwG9fAbwDohe/Pw2UxuAwlBLwMnMAAuyAczse42IuE54PrTeiWAWH2a4El73//5bRuCU86NI4Nvb6ttvj/nLrfL6r7X/aUZpBmkOvGSAQKb2Pxj2h+kMgEYJbN2Gp3l7hPf2pO+3pv4qzC824jxhUHgnBhKTh1p+b4R3xuTrX+RmXXy9ff7971SUR/Xt8biUGWBQWL+AGWA+1Hx6zGIaipgXLu/qOxOM+DqufJ44CKHum/GkIxn88z/F13c/y03ecA+5SBT3kEOaIUpGvAn4Y20h6FclkKY4AABnv6BeA4AN8h0GXQnQVBw42n+DmUBAAMmx5IkgqITyI6jDAcnf3lebzfMvn25/zmC3wJl4E0REzA7RErB0nPMN9qgA3DzHE+5JW1Sk8Nf77N9fvxdpXqR5ke7KpPX9B5SMgCFwbQWA8v3XSLaxv19o4T+0iZaItzSd6ngIIdo9hJS3xMmbsMi3pfTHho4rMI2ysya4kGUG6J6KUKVsi4kyAAASvHyG//MW/0vP77/P2gagMMgSgBOMW2o1Ytgc7s1/7DMjgpixABERcRxa42/gopv58AeNuVSTvf4bL438hv753+zHrz/VZr9VLb9n7cJ9hqgWBZLFCfgkd20hINSyfL2zDgTMADCAMriHvb/GOr+g8K4OEZCxnCdCQiWxYqxqffjW62Jz8+Lrp8320uF84TD5/QTCu//rccwAfmvBNWLGX3bE+XBCA8A1j7B5cv2XqhXZJHH+7v+Cw6f/yb/8/r28Udr/XP3JRDn+l4wQiANqXoTGXoK61wCYzAQYic6PwJcg786B9bX2R+PFHAMA6ZesPy8DYQQLZiCRP3IQEoSASqIQKCq2fsnkzU1xu3nx5dOqzA/t5elwfjYCfN4EERFzRLQEHI0pJowjcNI3OLBhc+XYSg7/fb/7/MuPfJUXq6xMd2Xr+0/QhPu3tf9G0P82YcXu83sCgpkwf+sXpL+nvapi1PTKoI01bHYFRI1et4euDQBqvT8SqJUBJG1LQO2FIO0qAHEoGdsyjpgASkSCVwQk8U/57P7nAmwACnssARPZAEAzAyxCKTDXb/coXGWjTohFjNRZYZ6CfAi9BoO+u8KwyZieeWZOADp2N/TP/+6+//azWufVKhdp7fhfy+9MMGJEiKixAd2KQEQj0Zr5EWonfI0T6OR3n94/KL83A6sbYK7SHzT53RXhQ8K7YeZnIHjyIEHUIjwKwYRA+ctbsdmUXz89+/opoRlMo+Pk99HSvgdDIwL5Lsx2Hj2cu5nBGIiYIeIKgENxuUliyLd8hu/doGuXqsRBKBP49Ef+5ffv5SovV1m13sk0F0kmE+X4D4war4FG6d+ZATpOAhEtfYFPfUDQ6zvQw9CRmQwYAOpfr7JgPwOh/jjLeVLxxgDAUQgUCROrP8rN5sXHP293MwgHBPNgIyIujgvraucMvV/ioF0iprUEDPhOPHR8ZpL/p1/Kf9/8zFXc/2RXJtsq2cpki4ToqP61+D+MALHbsIeFNf7a+UbgbzQCbWM0m32onf39RjpBQiehl6lTc4usE4Da0UdRcHcdAHXFOHViUKZsixxYAigBVnj3AkDCn/L5/cPC5gs/eZ+OMLhmgFAHLUuLGHHliMMRAM7PIh7W7WPvOu7l2rdS4HCSvX9PhscN/fM/ux+//ihXebnOxCoTaSaTXCbK8b+O2duyAR317zz5FK13ZXYn3cnvPgNAUH53hXedXrlSfJjW28K71KqnAv1RwncAlYRKNGaAiq05ypt1fnPz6uOfa1Ed0d3TYZzgNgMzwJy/ghGI0nJEAKcyACxryB1e24MmiWV1zl7YhJGMkxOUOymKFD7+J/v6/keR5uU6q1Y7kW5lupNJBrxihA3TYCr9PSGAkEjXEQQMAEZLehIW3IFlafwtfsL1IGhZB2ziCPsZCJVmIBtXgkpCJWpLgHz9hojw43832W4u1HAObERExNwxrSo54sw40pZzQSZj0vH25bn8981DwYsiyfN0WyW7KtkK3mj/KeT4r7n/2xv2dIfoEm5yCbeXlPekvWjtBq7qX9f4679WHsupn2mE3l0H4B8ACMCwYAwYR0yQEaCEu5cAEv8rnz1slzdTtDXufCQmhW4G6FmuSY3V6CK4MoFC4SobFTE7LE7PN7zCewzSJgHrMxwfhNP06sMtffpj+/OXn8UqL1eZSHci3Yp0R0kGTDLi9t4/nkTz10UFhDHy+xC6HzatGL86ufeK8KCRddR+QeNeJAAwrHjywEFIrARWHGpPvt/eEdHrj/9dS3dN4CUwWhw/mRnA/+ZMoSloLVgQIh2NCOMkBoCnNeRmxUCcsjJBq6l5ZjIG4gTIV/D379m3d9+LVV6ssirNVBxhmWwBgclGcYAO60C29r/+Q51j8KZB6zkzQdoZo3PJoD8A9cLE7tRADwJF+gibqwRSRf8H1OspAZSCAhOWQdK5EnCQSPDm7gcRfPzvJs/mMtBnwkZEXASznFpmjGgJGIZxlsVzwq3WQMFzHzzEek5dsEvhn192D+tdkRQFz0uWVclW8EckRZFd7b+z3V8dEmff3r8Nya41AjV99P6C2fsDPyo0v0NF++2EAbLMAJrGv90rWPkAkqH9x9YGYNWx0ZFzLJAjSkAJmCKT8OblfVXx/5NtxDzUBAeg1tGfpvBWv0+RATgL5jQJLRCzkkmfDg7r9gPuGnLLPu2/DoOwTeL+f5rhd/+MPv7n8ecv97X2f6W0/1uZbBkwlByAESIGV+175fexBoAmMU5+by+Ffn0GgHprHxWMlmppHTWxvTNmSASZ8EeCikMlQQisGBESvnv7neju439Xc4gFpHAp+d1dCgChoarp/uNsGnGtiCGApsDCDYUHVB+b2yzaWP/O0n0gW8OH97tv734Uq7xIle//TiZbmTwiIZLS+xvh/nVVQqtEIN1zsHMY9HIP3u7xNc/oMffqwR4EpkmAEFA21TYZiMbBEEEkyQMHIUAgERAiwa93P9U6gGIG+wG0mAMbERGxGERLwNVgQlluWrFw0qH18a74+vK+4EXB85JnFc8q3IFkCAzQivzDLZJN9W49uszfxO4L6f1bMt3J9j1mgHGtbeiGrtPXEwCdkE8YyFBr+btXxgCIgLCLCCS13/bZZFWUQ44cUAKTgAmQZL++/PmwTf/+skyJoOkQtE9M/IRaGQOeN0/zjpsRERFxcpzYBkBWynvXsLnPVlmPL+HM+Pmc/v798f7NfZFmRev7n2yJ7VAyAA6ITtgf/96/HbmnkNg+wACgY5D87nXjM8T2hu63Tnu1FF+v4CcGiEC65G5J8cix4EklQTCQSIgESPD+7XeSrz/+uRrZ5afFIfL7CXz4+j4+zZSzSOI+yw85Yj6Ynt1f3JCbrMLDJomT98/IuUpXLR88x6FW1gQT5Qnm2t0N/fVu9/3tzyKttf9VupWp2kiwcRloQwf6GIiGabCcCKA3bf16E3uh8w17GAhb42+eQcVDdMyjh4EAkAiS8y0SIWHNQ0j87e4HSfj456YsRnT7GTBa5j8NGxERsRhES0CE9+XPiXv79EL8e3df8CLnecGzkmUlZgCExAG4FqlPXwqAAZlf3/HP+wf7aPde7X/Pt6R3q6vZR8eW72bW07W/vxbZrz0pQzYAl7tLMGccUCIlXIpEJvy3u8f73cvlBQLyDdrRyoXBj0I7dWHM6ZOdBtfXogtgNuNzAVhuX435VLommncZwX/m6v7/44X8+/fH+9f3tfdeq/3nOySub/zjCu8N3Wfk5wHAl9YToJ05oJHkkCNXctfFdleE1+h+vSbAktzbhXsIgGopgFL9138Sf3/7g+j1p7/SwdU+E8bJ71PQ9REbA2jXljdJRDoasQ/L9PeZLfomkrljXK3dyWXGwX+2G/rr3e77rz+U9r9Ms0pF/lHaf2IEDQ/R8A1oJBBA3zKo1SxAgJMA07MAIMg6oOecQuf172r/LTOAci4MMxCdvz/T8kgAsJcTanskJmyHCbYMBBK8e/MDCP/+86YqD3kLp8YITuI0bERExMIQLQEmjjYOLgYnIdbTjaKHNX16s92uspwVZZKXPC9ZJrFE2S7F82v/tY1/3T0A7AgABICenQDBOQTzvNVUcydA/Qq1p3TNSj3KmnX9Lrph2AT/Qwwu4G9V/+Bo/3UbgH57neaYEePEUuKJTPirm+3bu/XD9ibwoPlh3+g90edMjbLshM94qoh9GbFsHKYpvLR+sdP+z+8L/P5Sfnj/8PD6IV/lZborVzuR7kSyJZYxaYcBREeE1+R3NxIg+NME7RkEsMm9m7bfHWnd6JXf9cO23wMiPFnGANm5JKJmBujEfECQKdtiAihR/QHBf377TvT6nw+zswEoXFx+32MGWJYNYH5fccQMMbEBYHGj7iQVXtI8UWNvfb0E0L1rgnZP3XWPt/TXu+33Nz/zVV6usiLdVU3cf+aPI+w6/jNLOe77g149gts2s52egWgRxB42oosVGHYiYF4ewl1OqFcrxS3jgGm9DgAI3t99B3n34a+1qMa+h/NhKCcRzQAREQohDWbEFYN6Dy8HAvjwMv/+7KF2/2dZyXaC7ZAcMt16/wFqO/e4MX9bY4BlyG9F/X7aPZiOQ6gbEfpC8Gqk3D4J2mYA+nmXmrcJiR4bgPN0AgRKWE48lTxZ8UTy5O3zh4e79J9vPFzV2WDYcJ2CyA+txvkZ/9l8stPgyppzYSxQDr0Ynk5fKYGlOZogWq+Fqbvx6yv54d3Dw6uHIs2KNCuTnUiU73/OiFGj8Td9+NAR4ffK7zb1b3d812K+Dpff0bmGZsKU4rtY/92OfQ6Vt2h9yxugtY6/aYlM2ZYl2G4YhAT/+e0H0et//5614++Z5fehEYEWNEVEOhoxDLOeCBaMPmPi6R8+Zqo6pI7m/KKs1+i7dHHcP6O/3m2/3/1UKwfLZFc2vv9Muo7/rg2g/XN9B7yswxCtgdXl7hsgX4YQG6HzCv02gJaBAC2MAGDHQIDWBPUfpWyLDDEBkIqHwN/ffAf5+q8PaylGvIiLYBAnEc0AERE62m/hSY5nnB0Rmx6KZBs4vs3TjZYPr6p/7+4LXuasCf3PMiDEbm8en6hP6Ar/HtptKwJgpAFgL/kOAc1Pi5yT+qXQMPSeZ43x3rsCQBIwp9K1WwADkWJGPCGeyCQRKX/3anu/fb7LZ/zxjx+rU5kBvCMgYhJc/awb8VRwmK5Qu+tUavlAuZ0IP7OP8Mtr8eHdw8OLh2btfu29RyxH6S4B9Arv/ZK7TfqxTrR6f3fKP1h+9yb88jtp+wEEtP+kPUs3A3RgIBnbYufDh0j4x68/QL7699MCVH9D5fephfceGwDNX8yf2SccMWdMOQvEgWfjgmaA05krm9fsFj/BAyet88Mt/fn+8fur+9p3IN0p9wHiW1QrB1HnGLgZQ7BhHSyHQZ2fsHcQatII2nmrVT2chPd8j8oANcf/XhsAelT/AASkXAUJanan4SFMwpuyR8aBJchULCAJv//yneTdXx9WfU6Nc8J+TmIKtV9oe8CIafEUVLSzwFO1BFz3ADuJnD/dCPm+oY+vHvMkz3le8LxkWckyCRWTvHH30zf4dTX+Ie2/rggAk7KDfYlMIk56I7WmHjDdh3o+WIxtNgi8PqbF9GsOqbYBIEjVAw1XSC0bgMASyAhTtQ5AcH538/jubvV/P67Htes8uOLPcjyuqTOuqS0zwoK8Vi+OafvqaBvA6dA+wXb/n5MTAAB8vhMf3t4/PH8sVnmR7sp0V3Kl/S+YVMK7qfo3Fgg2BB3DZgBqtwSANkEqKKBfft+r+nfPh+V30iV3r/xuqFoQQFHtxnhf7+dHgGip/lv7AkC9DoAhJoASkAAk/PErgnz5778LsAEo7JHfsefaUFhmgB7VnbtoYEaIdDRiDBYzBZwCZ/pYtLlkoZ9nj9K6ppnqYE7NEww+/ZJ9e6lr/7dVsiW+Y8RNjsHyIDC1BvtXDupKBGgMA2CehN5e7IFO3Hw2AMNbEB0eQrvbHwWoyVYTwDZ/9y4RIMUtckSpXAkACf745RvIuz//Xg1uyCwQ5CQmGrq1LQXGvOGIiJnjqVoCrg9+9fFsCHfJ4MPL3f3tNudFwfKCZSXPBO6QeEOXrRBAjBAbY4Bf+99ebfcArCU4g7KDnSDYrwUgGP5J9OQjACRsRMua+o7/1BiBRGJaQABoDAMqJlJ7mjQbAABQAhlhInkieSIT/u7Zw/2r9MsP5nnIBXH0KMXjyoiT34kwm+knIuLSOIENoF88N0T4CR4zDR7X9M+b7f2zx3yVlWlWpLtKRf5hRR35h3yO/8SaSICNl4BHfndcAazEpPK7pvgJye96sfr51lGPjEPTkw9rSwDq2mu9fu06AJYgk4ArQII/fkWSLz9/WUKsPw1BVf9U8rtjBvC/6csZVvt4mEhHI0ZiMgNAHHt7cP4p4xRPDOsO5sZAfHxT/nv3s+BFnmZFuq2SXcW3ku80R0Lv4sFWWcDQzzGEVP8u0+BlI9x2hprdwxmAjxB4jQHuX+sswLQzjS4g+BkLtQ4AJWACTAIQ/PHrd5B3f36a6Z5C/TA4iaknr2gGiLhOWHNSxOJwCkZtupHw4VX5+eV9rkL/86ximcAMiJmL8xjqRNne4zd02EO4PYoA7BrWowiYpvFOEV4qP0R97YYAgqYTms0AyFD9K0sAhwqwswE8S/P3z3cP22d5eXzjpsB0g/ZgG8DA13w2Nj8KXBGDEBcBDMccFgFMXo22zNCZ+QX/IYBPv+Tfnz/kSVEkWZFsq2Rb8S2wson8E9r7V48EuFdy76i/FvDHS+5D8nvPe9ovv6Nx1crjyu9eyb09lER2oD/obpAp2zKO3bbAt/A/vwLIl58Xsd+PibaZp5LfNTPADG0Afszp+41YCp70CoBzQ1sKcFIcLOSESqtB9klDbp7TBPT1Vv5z95jzskiKkqs4wo+SZWbkH42HaPQLBIi4V3fANB8BBGjDBezV/o/SHRg66rANwP0FgHbLQQSgeuFfDWazFMQ0M0CoXoQkU+VHIJEliBLZCv/z6/eyfPPp6/J4CAU1bk80cqMZIOKacdXLAqaloTOBf8e/2bTzyzP58eVDzouc5QXLS5ZVbCdBMNm4/2Ngmb8i3J1twDESgBvKzzIAaIe6778xfXvp+OjRH+IGAMYZ2OoYwWRF/mUAEpABWZaAtoGK3DMEAmSNd2EdCEiyVC0C+PX2/v7l6v99ubR1/wSD88hP+xpnu0tiNtPP9WJuuqo54+psAJZs3qPYPvoxk+Hj6+rfu/uclwXPC56VfFfxLWHJlPYfHb+9boEgEjqk3z1sgvfWbAPqnTFQft/b4KD8jmDL76hdrU+S+85syb2O30ukBfpjlv8/No59DESzDgBQAkrAW/zjV5bnL++3S50dTiu/U9AGMLsJNRLRiIMwjQFgicPvYnU+lxmgftZUD1JGUeckalePwnQdknP455fsYbNV3EPBsortKsyY5LXqn2wPAuwUCm3Mn4FOBNDwE9Ad+vmGw7QGOhsRsgG4t5hXCXzxf/STst0/kAw9kVV440cgQYUUpBR/u9veb59vs3kRxKEwR/Up5oS6P5fZPRER+3HVloCrgV/7P0G5k5X077P8YbXLeVGH/sdMYG6s9A9u82v9GVdrId8fyg98aWjmbN0Y4E3sb792uZ8MOFeN9+Ul9OoWZocABiQCc0Ng1RsSu/4hAKatBiAAVIGACBNiieD8/fOH++zVt8fLBQKajVwxll07NWbTMcfiahoSETEhEJr93Y4ux/+NkZmY03f444b++eUxS/IiycskL3lWsUxCia3YbjgBtOxBQ9rGrQAA3R4wnfbfyjlEbAfNNqDfXocAojr6v7OTn73mjyuduFtFBpXy4UMVC0jiiw17+2b9sLtZymZ+Nk4sv7dLAfzf4kzk+oW+u4gZIK4AuBDOZgY4bpIaot+foBGT9sPfr4svL+9zXhS8KHle8axiWc00+IP+MzIcBr3cgzduoEdxAACdQ4HuSxhsrbfxPj7An1CPo2ZPIfeW9lBXE7irCGWjI+gT+BFkongIicS55MnrzePbu9X//XuWewb2wxnV6D89wXOiDSDi+jHGf3n+CElpV4XZtPDrWn672ZVYlVgULCvZrmI7rIP/oC9Sn1LuM/QQ6IF/sIeOB1UA+yi4h5BYBIC0TKjdR824G/j9kFbnFnrwH2i1/xonA41+qXb/ByAEYlASZBITyRLJ+YvV7vfn6/vsthLD6jIhTjwsD/60lz+rzQizmXueACL/ORwzWQTgu3fUxOVq/1tx3kveDsd03VUx+Pgm+6k2AeJ5wbKKZRXbIXEk5rABvHXdczgBjxSvBfWFs8nvtSeBX37HehGCIb/7ZPwOrLEEEIFEAFP7z4yl/E5lGFQrtmUcgaPkXHL+28v7h23y6evSNIG+z+BU8nvPrr+XnVcjBY04DhN89nEQHo7zmAEOnaRadsE4dEninEbAv8/lv28elP1RbGwAACAASURBVPa/4FnJsoplkiTrFgm2TIMWNxCw2TvI4RuaEMMd99AtGPQyEF63wcG6A/u87l4b4CEM872XaVAcRqP0x0b1TwDIXBuAs2FgWxHVMJmwHbF6w0Ap+NuXDw/b5POP5QQCGmDQmtOgjohYGuKygDnhVMF/pnu5X2/LbZKXWJZYVlhUmBORudEfYifb67F9Wkrtlf9bkh0wAJB5iGCrA7rEYC2A3bGjObCW9eruDNoFmrPGQ73Bf/R2NYsANBsAACWYEaaEqcREsOTt7f3989V/f5xROzA/uju3CWx+PXQIrqMVS0K0AVwKk9oABmLQgr/5Rf//8Kr8/PpnvQqw9d6TtuofLd//kPAODEjX++ucAARFeI/e3yu/97wYV4jsxHbqov87uZpsms2gOd9uBYx6ON82FpAjvDdNMdU4xKBKWEY8IZ7KJFmL5Le73cIW8e+T308xooMf4qXm1Tl9thELxbGc/UIH4byqfQYzwMGTlKP91y/hJLWeruHblD6+3j6ud7X2n2cl30kslO9As/cvWsxEyyg4DISxAqCx5FsLBr02AO+v29QhLdfJmWsDsLJ5MziZ6yMGQFqkYLC0BgTMy0QiAJLoeAiebFL+9m53v3ueFwMadFkM/vKnZyOiABbxBHFdywKWiFMF/5kO9wl93WQlViVWJZYVlAIKlEwT7F33f0vvb9FrMHT9++P/hIT/Hgrujmbv+Hbnfdf9P0QbVHTgJguMokkGW6J5RyrijuizATASqdoNmCWSccH43U3+931Syf5nTYHZj9I4e02F2b/qiKeNyXn1c9oAyJ8dvVfn5ATw5Vb+c/eQ86JgecHzku0qlkkQmveeN/LPfuG9FlsB0OAEwEn3/FpNHSu8h65akrt9I7rltJ58bdB/Y/m+tgLAWIfQJRLIlAPfiieSJ3fr5SziHzZcW6bqTDizaB/JZ8REWNrCnyvGqSeRkeWHtAZoXZ3TZPThVfHtxUPBi5znJcsrlgnMULoaBEf77+EbPEsIASCsQYATMBCHQece/KYCAkKP0l8/BOw4Cb2EWlmQYEYsIa7WASS/bB7u71b//bQ6WaOmwMixem42IiLiurG0ZQHTWwHPj1ADZiX531QPq12t/cdCQEEECEiEaJJpw5vPT6MH/kHgEHy//Yl+hLT/Lix1gJ6tbxl673M7pb8eCMjeDECzAXDIU0wkphWuUhSv1rvXN5vPW7bnaUfivJ/Z8KeNetlnmNUWPx1dRROWiuiDMhxzswGAVxns5Ak8BM08EBbwL4Usgb9f7R5ulPdeXvKs4pnauq9x3WON3bpdBBAS23sYA+il+z2k3+rX4dR/iAOfm7n/pILXdY/5ogC1ZdV+BAlmxFK1CEAK/u75w8Or2S/iv6j8vud9n2dqndk3G7F0HGUAiKNxYpx6KcCQ8smT5YQK7Oka+/dL8c/r+4IXOSsKlqm9g4gAiKHuSGjEFEYABhjmG8hkIOzgP2dgICwggIoLYGkKXCcCcM6oe9szDEACgsk3aLUl1mwL3O5xXOsIEAgBU8iIpZKnKpjg++ePD9vk2/2JNQWH4Yipqof/Gl5CREREh7gs4DwIOANOgOnKzRl83eQFVCXU2n8BBUhLbvcF/+n961bsdbRb+yPQ9OBgEveeX6vlY3tBFxP3uv832cyAAcG8NpUyK69aR7rqHzTtv2UDKDmUCZYJVjesvNuUn7cncw+csRQRZ6aIa0O0AVwQR3b+gKnSWzxqtxtUYU5OAB9eFV9f3ee8yFkT+h+b4D/Ykv52G792EZsWA9CS4hvhnRSh3xP856R0X8P+MaBLnF26eYmouf+DFsJXXW9keWoW8RsPhpbKMxAJZlI58HF+m/J3r2a8iP+y8vvAt31S9d2M2aSI5eIprgC41Kc09LlTsGh9s96+eoQU1V2C7MTF8XNNf798zNI870L/7wSWzNj711UiMI1R6Pf9VyxFxyjUXAWAw0nAAAbCPRyJYM8PonfNsxWjAAEPAi2qIIKrLOBQpZC1iwCer3bvX67vt5fYM7AfR4/S43mIGlH6ioiwMG9jwGTf/vnRo/2fU5M+r8X3m8fG/b+soCSQWvT/lviyRpE93N9fQafdBrE2iHhr5vb/ehPewxD2KfrrseZaCOr61Yfhd2dK+2YTutZpPUCMkJxYQMip4FgmWCasTLB6vc6epavHcuqPc06DsB9DWn6GqWs5HRbEFTRh8Yhc6ECcoqNOJt2HSnW1/+pwgpZN1zn/PBefXt7nvCx4Xrv/s13HBhAzlfumCO/f+4eZiwX9DMAAyd1L9Ee03JOV9IsE9a4AQzhNPQ+rt/ELOfABMxfxG1QegaWQASQqFpBQi/hfr/7fP/NbxH9RmjF23eXEZgBf2xcskkTMDIcbAOIQPCEux6LtFW2x4STm4z4gEf5+lf189pjzvGB5weq9g7DbO6hlGgwugYy18KGEX7OgsRcwnoFoCAv5L07xdVlkIkQ1+lcRNgwEtXUinY1IMKOWh+D8t9v7n6/Tv76kx9d+Gkw3SR1MdO0xHqWviIgQQvNhxFiczvcfpnw7EuDLOlfa/xLLCgqBBUnEjjobhJjIOlknWnJMdpggnf5a6eHk2yXiexklHa5Q2O/+Tz2ZBxAQcupptbSLBdQEFGbNXfVfAqWAMsEqxeolz+42zx7L6byFLio8DHx4nIEmR5QZIxaGGdsAxj1RJwnUnZwDHlf04dV2u86U8F6yrGKZwJL59/41SL/JJHgv+eX3MAPgkk4wE+poAvl930DYK8IPEd7bpQDt8v3O0p9gJlFbxP/s8f7FnBbxTzREj9eYj/5ejxFkZvNhRlw9nuIKgGUgbEg8fjoLoWeyqokh7cl2EXx4VX16cZ+zonH/31VsBxKNlYMti0DeZYNmunM2PICBsH69CbDX9Y9eHjKKiwwPGQJtFaHr+29qDbDVFEDtR0AixZwwlSyVCZci+f3543326ufjpcfInIlotAFERPRj3ssCZo29NHpOc+Pntfy22bbR/ysoJJVYb/rn0GWH5vok/PqSL+aPcxeCfVedsH7dQyvdD0ub356xqHnoV8tJYeKBTfmke621jJs66XIvaGn/VedwaBYBYJVidbcu/oJksmVwi8KQ13yGKWpp3WZj6fW/KkQWdDjmagMYAgT7w5tMhJ+u/h9e5d9u6637muX7GUolpJuO/+1mv2jRr45JQJWtT3j3UnwrAdoZX4PHy++96oCumL39amQgBigJAD3Cu2bmVyS+Hg3aIn6qUswIE2oW8f/+fB6L+KemFmENSO9dxw/ySPYiZowDDQBxVJ8JRzAKY6c89zk2odKKm4/7/y6Bf55t86TIeVGwrMCswkyCYMTNlYOd7wABYstJeLQMrRmgu+p4E0CYk7B+vYnhXXB6VrFbRQhO/RmA7BpueAsqbwJikKeYECYSE8mTl6vd78/XD9uNvOAccZpHH8BDBN9c1G9GRAzEpT+Ww4SHy+AMFvpJS/+8KjJeFFiVUFZQCiy0eH0dgUaDWEOvc19TP+rJ0GTTYvqFKXivImBcd+jUfKAt387WmDKcpQDUs86ga51pCXD/atUAA5FQmUATBSjZ3W1uvu6O8w1czFc07qVGAr4Xy3nzTwZXbQNYwHg7ff/X+t427Tt/cXy5lf8+eyx4mbO8ZHmJWcV2SiYlal3+bTc+ZyGgs5R/CPW3095fbwIGv7yxrznEbHrO1+VSJ7w7loC6T+pYQGQ78AFQAhkp+Z0lUi3if5n++e1yi/hnMzJbXO80GfHUEVcAzB5nYdTcJ9hnyKCZ0z/vUHzZVD/XuxLLbvEgy1Dyhhvo+AZ01Aqa0OtVNAxkIKxE89uZj0O6g70dMWy5/5j7w8V5Vg42d2ATSRBMDwKm8xCSJZJxmfB3t/f3L9MPPy4xt8yJexg6xofUOfIgERFweWPAfOET9XuyzQHfU/q23pVQVVBWWKrtf5nU3f+Dvv9jCLT915DFHvLtVQH0yv9ebzEy/tPucrX/A9z/bfuBXQPn2Xr9vRyLZUcxlgJwKhIoE6wSrDasuFtXX3eHBgiezaibTUWeCmKHzxdXbQOYEifqKGWtR/83EtIEDwSChyWYbNO+KeX3cseLgpVFs/evhAqlHu3HCu/jZQy8eYazB+D53S+/9/UCAjjjxh1GZK8kOHCoqVtcNz60LAGu8I4gUsgJU4mpZFwkye/PtvfZyx+7S0wNp6QWYz+oHvf/OGtGXAcOUdItmqVbZOVPzKi5ZduSrhVQeDadWCF8vc0LrEqsKhVDADNoQweixhZQwxwYuwiaHEPrNTBi/WBIg6B6aYDiIPhqfezCIWf8sG8jaLZDZIDSZB3qcEAEiGh5EDAgYiASyAhTtQ5AJPz3Z9v73Yv74oyEcjZj8iTYq+2JiHhqcNWqJ8OR0vjJcTbt/6Rd/XlVPqZZHf8HigoLkC6pbeP5Kto2gBCjfqY5SdoZdG+3EtavN2Gm+/rW0gK0Z7zaf/CdMRPWszwvRc9Gxl7HXbH2n9W3HEpOZUJlilWK4k2SfUhWu6qnmT7M6bMZWJd+dq0n8+kwp14cgYVW+wkh2gAG4nRr8U/wCryK/i74z2w+yx8pfdvsmgCAZYV5hd3WfdiE8EVg3bpAWzb3ufzvl9+9nIP526nmewhCz5vrea+2YaDZAfjQkDNdVWvhHeu6Kd9/rX9QX77PAAiJGOSp2smPJZIlL9fb35+vfu42Zx0msxmTFuLsGHHFiCsAFgJzefdAfcSQbP0T3HByNwLTzamf1+L7TRNBGMoKCgkVEq+XBxrBBPrXBjZLC5UY3OQJhxqAHu6h0VPoTe0XKt0z/Zp9g1dAj/pgNBCg8yDwB0Zoe8/wIFCJBHOCVNbrAJJX6+3dZnNfnGUh4SxZh5PzDWfUfkZEzBpPeVmA1vaTN33SBzxy+rKutf8lFBUUEvT4Pz0E1z2pJ8Ah8VYbMEzgIPzrTXgPvbDma68ZAHoPyXevWbbnKjltUX+ExqFifpQmouV5IKkXAZQJVq/47m79bFfxAY31Vmx52Pten9pkMwoLf/lPBtEGMBCn6yhnRp+gtAZonZyTE8CXjfIAKCsoKywEFI0jGtPk91Y8b4P36jK7729//J8h1L8/0dMj3tc5bvQ4VL//duxy1BK6tYIf60X8pBYd2OsAEswIEomJRC4Y/2Wzvdusj434NxCzpBMTRP+PiJg9RhsAZvm1PhlMzYIMoWZAJm2c0wj4elvkrCyxVFsICig79/+G7GmxAplJDl02AiwuIRxoGPwJBJ2H0CwB7Xk90YOePN6AAM0JT7bhoL6mQRt10eYeVELtJiQxrXCVYnp3U3y4Tys5sgpjMafReBlES0BERIv9ztEHAuc22Xjl/MH5L47Pa/Ej3XbR/6EgIiSLcA8R4JVqADBwKXAvOJlh36+WQP3kEOiZ24X/raZAT1i/EKD4rQYftKKcB9UV7Qo0e1W/y/5DQLUCIKEqwTJlqzdp8QE2g5o7s8EGB7n/T5XzSMyvL/djiXV+uog2gIE4aUcdX3jr49/AolsT1H265u84fNtkavm+sgEIKEC6Qnp/8B/7/L79gUIiPLjye0Bm39sFbgZ3bR943jd1GUZ2s86OeCR3XyAgwxLAQCSYSUwFW1VYbVjx+uaIiH8DMVcigWZX2lfPWJOIiJNinAFgrh/sUFy2/jhJBUZyCT0P3Tu7IYzXMgyp0ET4upLfbnZdAAE9gjBis4MQGqoEDHAP9fo4nWQOYR0MXoEAkfQWHsNDWBi0nBCDmYcNmnYhIeocg8k6kGxcBRkgNTsLEQBjIDiWHFXUYPFqtbu72fy7PZkTwYwno8uwCNO6EUVEXAGudXHA5HR5L6Z+xiOrKpAV1LH7BLjxf3pC+kJAkrfSYN+Fbh4vvXYzmGUa/T+wa9xFAN6E9QvaIRizPDlldlnqbGiU35Yz9I9BlVCRQplClWB1x3evVzff+yP7zZgu78UxLFqEwpLf/xNGtAEMxKltANCVP05j4Gj/PZfm9HF+XlU/1kp+Lxr3f+UBwABZ48VvruD3x/ZhjTV8OGkDf6KT30PsgZvei37tv9fk794IYQFPWySgMmCodVqPYePAR+p2lkAhsax3/WHi9U12k6yysRH/hmNOQzEi4mkihgBaIBpvt+E+TQe6RWosBc6QgViX2yQvaw1CKbAAsmP4aBGEsSGBh6wcDHgXgpnw/noT3kMvLKofch8wGAi0M7v5vQ9q7/NySI4TATWWgHodAHIoEiwTLBNWrVn5elP+u10PaONIzGkQzg7RDBAR4cXRxoBxAvmJcFgNLl9vAxXAlpeCpCAhSEiqJAkG+va/iux6iTU4afeMftjC3Amgj4hbJHu4FsCV2N1L+hzdr/23Lul3+eqgqf61U3ohejnGn+k42dJ04lAmVKZUplDdsvyXtPxeBBwDZzbGdExetbMR2Bl3qgfLqm2EgWgDGIhTd9RYHr756kLi5WSVna7VFcKXTV6q9X9YVlAILIgQuzg/vkiAnVXASujud3v/wEn3/HoTw7ujf6yEroZXCXjYAGu5QGvG0AV2sIk7WYv4CQA6+R2r18nu7ubZ3w9jIv4NxIWIxFB1WdObcS6MuHqMMABE3m5GGMkiDMxoSLfknD9+BEw3p94n9G3TRhAulfs/StNfoE/ExbAloLWwuLeAVlqbpyGuqLdwIA+h+d07F8wCwccW+BwHyJuzveCuLnQHk95SN6HrCJjuVMih5FAmWKZYpljdrbJn6eqxnO6tX3QOGm5vuzyiGSAiogcHmsQvisAEdI66T/2MR6QtKwUJQVKSkCSAhoTubWvjld7Bm82h4N5selFe2q3LhS6t9MIrurfnqRHULTf/HhsAODO7QezdSz4+Ac2Hun/dZgA1TacigTKlMsXVmyT/L64KdyjOWDw4gHD3j/dFzBbnx4yHQMQwRBvAQEzaUej9dvby8OY9VkY0s6GT/7L4vBLfN9ui2f5XYClJINkeAI7IaZ90ov3AABbCTYO2+r/9dQ+tNABguFeHyO/6+ebXVAgE3v+e1QCNmN/2mzRbrbvudZ3DoV7Bn2KVMHG3Kf5+GBbxbyDmNAIPw0xmR/+Mcd4SIq4AcQXAgoE2sTiuNLNk6+QEz5l07vy8qu5XTfwfLAQWJAFBXyTIbIWCu0KwzVCTaotRAOeMj3VoE53jvPVrNX5gR1i8hdcXIMRG1IfozwZQ2y28bASZCyrBaKnRpazhNOqtmVTU4CYKUPWCZ3ebZ4/lRJNMpFdjEcW5iIghGLw44DJ8c/iRg77v+c2cj1xmrJQkZb0CQIDsj96roIv9YF51s/mAnmyNCR/M0kBLaIddZ46dW9v8usbfTbi/Vh6tBDLKdcoHt2Szk107mPsn1SKABKqExIaVG05FhfbTrgiRZh6AqxsFTxXRd2Qf0Gd3PdWzaNCXFawITVfHaeX3dbN7X+O9BxKb2LONA5+1Xt8S0jtRFEzRHgKErFeEJ9BOWr9W44+U33u8+/vyDxhuVLMxqi1dd0HXatJD+yrGp+5MBESolANfgmWK1eske7W++ZFP8eIvTR6W5L0XEXEuDNXNXfr7nQBX0AQPJmLXDPrmaP+7B80DOYMv69b9vxCKgSCLLUDbZcBxMETjEoT1DmBeddN7f8FX2kC4Tn9oXvUqDsDJ4y/TrJ+Wk6zGgtkzTQeavZpQIaiOApRidbcqPmAijxw8Mxh7S2UgrlGcm8FwiLhezGpxQO9YH1S147+WE/TAIxOCpECptP+SqrDE3lbCG4gPzJxu2rQrUCgbmGfqhGYb8GbwHnphmZhc93zoJeXeFQChV+vN2bbRcPrr+Wv7jZFgINXfDZY3jH60zMLsMZZw732X55wPltDBy6hkxDhE35EhmEcvuVWoz1A36c/qK+1274OyajYAYMRDSwCduL6Oo4B9o4J70khrbvJg5gEwzoOZ8B72oIfWw3ii3/MUk18icBprdULddQjYxgJKqKgaB75nLL/bVD/ydHBLeztgEUDrf+/FiIgrwSADwIK+36cGnNwZwXnZOEnZk86dn1fix822gKqEsoKywpJIInFv8J+eMD5OTgh7IPpuRzDPgPnbJEhjM8LcmgNyMhicBAFi8MVrnATpZ3rgNTO4rQv91QwEgtACAVV3ye71zc3X3aFbAS9q6pkvf3CNZoCIiHPAsQfgeaalfc9Y9Nf8yKt6AwBQBgDBqI42awap6yfB4Ej7YGZz4SoIwNQC6Dm7M4Te2LDBl+Bc8BPxOk0tizBqBUCoAp5HaKv99KK8XFDNL7X8DQOBJBhJBpIRbbgAOpSgLxyL/uhOgUUxaBFjMA/t9twxrJf28gw9GfrvDcqTuu//zJwAPq/KXZJ30f+hAMkIWDhCb1uJAVJ5kAEwM6N+F4R/p5Xf0ZfotQFQ++DQUgBXuuvs/WYsoLZkj+SuDuvVflimWCWsepPmH3haiEAT92Ie5GGp3nsRESdGDAF0VpxCd4CtDQAOnMNajkEvAJ1LM4EE+LzO2+iByn2gWT9osg7khv2BLl1byMFZPwgOjfReAnPdALi/hvOg0YdD3pNF0dszIXrvdR/ov9EqwTgkaupvxwLq3DEIdfeBus8TKBIqU6hKrG5Y8WZdft2N3wp4TkNuTnU5AlGii4g4EqdeHzBsrhn62JlJ/gpbhC0rBchG+1+B9NNoD1lqrmpCuSPVG27+qEn7eqssTkcvzfdLYNbFLbAHlvt/e0bXy+sJdMi3lTDLscekQ+7J4gpU37bxI62et9qF2Lj/M5Ic5IYJgKO9As+Cad3/I/G0cCV8UUQI0XFkCC7XS+4zXYF9bm/vPqEvq6zAqt0BuN69r4v/00rorKm9T4o3AtX2S+4+7kJzkHeWAli/YMrvA3u0R363EhZC6wDccrz1afIrom/YObBlIeoNk52oyEgyoSJpHPhe8+3rm80/j+O3Al4ibUDrf+/FiIjrwX4DwBI/5KeL8byIzjF4BaEJJr5J584va/lttS2xqpr4PxJKZ/1g403QnHQs4e3JjhUwIwy0FTd4C80/0dE++H+9Ce+hF17HfDA1At5fIzMaJbiwmBUEve3U8Q02O1VHaezUCgjIoEzUH1YpVm+SfJOsd9WAtlqNXhSWwR9EiS4i4mi0s+H+2XQIZjvdTT5READAI5M77uwAHBbafWv4LKG9SXRSPTrPbUtzBT0rYUX+CWX2HnrhEl80KbiecO91ib5VlAnyWv3dhKkRaE66iyARBCPBSHAVBYgJxOYhy0fovYaynQ0z7+CZVy9iMkSmcQhO6V7jpQ1D5iuE2TkBaLv31cF/avrT8QCtDQD6GQPo3NQsou/NrF+yEu4hOIduR+ztl8MWAYDv0CrE+3TrPBIAUl1O20tGd5G9DzAAclJb/pQJViusfknLf2CMAWBmhGFaJ4CIiGvCHgPAzL7lw3E1DfHC5g8G8yKtMH3A1Uvh86rIeFG7D0Cp1g/6gv9Yh+A7BO2wyxAOGQTGLegUZewD3MUNcMIaDIdN9bFOI0Lr2ad5RrqsA3l5CC+8xgaLP7DZBS+nxUktAigTFK/47s362V/VMB5iZoMN5lijoxGXAkREHAeb5uo48ZQx9Nud28zV1OcRZQGVAH0H4DYTDmhfPwVvUxgoLaQO0EsIH+pPGA2LFiv/fdTPNCGm3N/udvSUVqfQPl+nSeMc+gevt9pMWwSwgeqGwe7gsADnwoTDP1JLC3ObWiJOjsg07sVxXdQ/KQ8p+CQC+6QvXd+9Twnvavm+Y3W2A/n65HE9AU4eb0vMzAh2Oab8rkntepnDe0TP6VsEQCaJR1NyVydbam9oACy40n2jIhghtjdb/kDJqUioSqFKmLhLsher9X0xrNXLJAxDOM6IiOtDDAF0boyVvQ4sNuwfZkC7xyvy4vF1nXTuLAAeeFmBaBcPCihQcgA0lhC2jgMGCdf4gy46UHdJk5/tP+cSNE8BsxC9wVqUoeaMySIO6RrrRbpshFfX73ISdWmagqB7AAYYiLr+aDj4G4PGE2RJRQEqBRQJVCmWCaZvkuIv2Axt6Jww1n1gMYjiXETEcTgRKd/70OU9yeymR2ZsAEDGCoD22QZp9sn2CMY05r1dO0TP7WbabbBD0JUq3ZN5b2fpXYAaHfcm0LEE6Hnax3W6Ad9AtBiG4AqAAfseIYJoQgDRDVUbTjtxDfQDnURPngiFWXJqEadHXAqwF5frItQq0NGtmX2rj4weeFGBqKCsoKigaHfvQ2QdPSKdZFvUv5YxneA2sI+KKWiL+PeF8DXl97EUv36GmT9I7gHQ9ufz2P4h8EYtyb2tP2ndaNN9rRsd+Z3KBIoEyhSq5yx7kz67L/YF/ZvZSFMYW6k4t0U8KUQDwPXAr4/wciRkn/NSuRnOhltOW1aq0AGCKgFlpzuwVwsCQEvCvTxEnfAp913YbITDLrg8hDcBLWM2rHux+c9S/UOAV/CyDnt5CL1Kjo2B2pJ9f75FmggVpzKhIqV1iuKO7+5WN99CTgSzZB2uH8sU5+JgiYjYj5l8J041KoRHrCRISVKiMgBIhKRXaNfhEmIwc/ZTGSsnmlncWMBusYcpAnTCDZod3U2EbidfOlQBOzM6Jeyrt6LjdYqBZFTbAFZQbZiEUWEBzo5xCxxGnn/KmMm8EnExLJNvPCt8XdQ/v0/43CnfzNRv+ZGLAlX0PyFACKiAGgt0HU5W0R1XtGwr1BySVx7X84TvNRMO0e+R38f2iCV06+J5mwj9WjmHPL3Ob+wtZHSjYwYgPcQiAyAEUlGAUipTKFNcvUmKPzEVoeE7V5IwznvPifloZ4iIuDpEA8BVIchkOGf7JzUM3HVIhSbFI5M5KwXJLnywsX9guyetq5L20n4wz4B7Y4CN0JtnFdiyE64eYbTuAA2hHfwrAGpLRg/rMJCHqDOgkblV/fs8COw/FVKQQPEQUCZQplTd8vyXpPxWrPwPnCuu1v1fR1wKEBFxKM4h1ZuPW8yTHWETPgAAIABJREFUAv3yiLRjhSAhUGgUvHuw82QM16aHjoNGlfoLdMl0/yPATg/vK9LHC5pEtk2Efq272gf3uAT2lN9mc5kcDzuEIBhJptYBgNygmLMBYBLCfVmqOG/OKOLJw5qEIlyMt5QczE4g2He6Z+aARy6U8K5C/xFVpgLaL7wDgO8QtPNgXrWAYO/eF5bcQW0L6AbvHS281wUY+X0UuXOwC9kArNvdp7tPMVUEdn9aDgR1z1O97hARRCO/VylUd3z3S7r5p2DBxl0BAoJwnN4irhh9BoBr+rpnhZNqDXqEQj2D94xLDCeoyqRoGAgh2vDBhAhI2K0cNJa0GwvcoJZm7fWDYOYB85L3jJcR0X5tf0Of4mAQ2szDVwB4r1r3gnsG/ZmRCLDrRtMMYIRaajYUIgRADkVS7yZUJSSf8QrANABcxfxyDfxBdOmKiDgUJ6Xm1oOG4rJTa+/TH5ncsVJAHQKIbBM++IR/78kegVwJ/O0poyi/PI9gFruXCdLK79rrzW9dRlNcH+L+r5vmzXtdpY99wlu+VxGgl2H3PwPJahuA5CQ3MPsdAAbD+84iMfTiKli2iEkRucd+nL5/ELovc8rnTF1nCbBllQApQEhlA5CSKUMytuIkGKSnI9qtyAkASAi6S5+zx56XhQDzMCC5g7467nTyu1dsd3/1W+zSTA4n9BSl1Q9477XWF7TPcCo5lSmVCVQrrG65ANAMALOnBGOdABY3gZ1N7oi4YgQNANc0tq6pLQPh6nph7Bw3y157rBkIKZvwwagYiNoLvk6iRuF8SgRwzlsZ2qJ0c4KeR7/RUkn0cA89OosQBngQdAn08RbuvVbhIe6hLrbhL7HhtFzSg9Qs5Gx7DAEYVRyk0hrcsGqFUJDZphljCXWcFEtYCvDkXkpEBACM+jSP/0gOngcGPPoRax9ACUKikFQ1j+z+wquxh9TVf2/DFXjFZuyt+ZF03OXF0CTr7iXjt1vOT/2GfB395feB7EUYNXuDtfZfMKAbEAmDSvpLuCxGSf7eNzd7MngZROIbEcQTWxAwWvs2uH8O0es52n+EOX6uj1jH721XAGjxe1u6rKTIJm0GmG0zOOH7QcvmIiTCu4J86GTHP5jF7sUQ+T30a+W0SzMf3+sgWHdjwAwACNR47zV/HErebPzDQG6YtB91RdDfsed8RMSVwm8AuLoPfHY4hMwf9JThebw08LTPHolHhC0vDff/oPOgW5vuUu9iwJ4GoNNbTgLBPH+YysDN6bIRekIbSl2Qgb08BJhX9VqR8yC0+AP9j6xVF93yCokkFBtxg9WGU1FeFUm9qsZAdOaKiDgE56HmZ8LBn/+wLtihkMr9X20AAALJG0xGpyZ6Qq9lDzkOwSL0XrrsZYKOJOUuHe8hxN7bQ2sFvDf2ZOtVBHSZW8peg4FgJBhJRnJD5Q2jBzmw7UvCHJo0w8lkhlWKmCOemCVgFBBMG25PtuFlOlmViHssTiK/044VavGfBCGpsuL39v611fISfZty+e713q5pABCcgD9eoj+qa0J0HxuvOuykeDsWEJgJ/dFeim9lMwrRhHS9Mp6/NhIAklAb/3CQN6wCWC+FDIx1/7fvxMDViIjrgscAsJBvfPGYg9YgOMddvGY+PKLcsaJ1HzA8CPQ/QkBXK92AEBEMwu/nHtzDNg+YNxqFa2cGcg+hl+C6+IXYCP0XzPNgMRxtac1Tdat3kE1pGutTHHRRgPTGIBC2IYMZyTWUGyZ/zDhwsI5ZDv9zYa5LAZ70S4mYN05KzUd8jhf5SMY8lAhIAjAACYBEHd0ICe0W3KshOusrB7X/mmo38QRCj+vB2InSpdHgjJ0ATfdojjT67nkFXiLePZ38+wB7u1fpBWrtPyO5hmoD9DA/OnGk+//s2jMDjJtRejRUEU8K0RLQg32dM5Cd0LX/U3bzaV7ZIxMViEZ4r4hE8zBLouzos1aRVrQHjUihJsz62YbGcz8kwmsJO/JPIJsB+4yuC3BOdyQcDeLuUnwv7faWaT3LK/IHJPeOc0C7PEAkrIV3kIzkDas2DHbXE/mvg3/Q+C9ERFwVbANAZNvOiZNqDYY83UqH5KKjip4EBADwiKIEIUBKkir+D8j2eabA75eH6wxE7sY+/UoHK4OV7vn1JryH3icqDKHu/TwEhNkI94l7nQjs22v2wTbGAAOpfAY5SE604XLOOwe2ONx94GoQGaCIiJE4ETU/61c49mHjG0xEQEr7D8RGTzRoJ73k1UrvYwz83IL3MPSUSRAaQRopp541BL03emwM/Tdq/BIAAtSbAJPkJFMStyCNoMDLQYjLjeTOxcHa/8hBRNQIKS0joK9z9szR5O9LhNNwIUdCye8qfq/mwIfEfO5Griyp69W9krWX4rtE3Li3VXs7lgarcCsBw8axV353XfR6PPm85Nst1j3pEd7bQizPSCdasi6/CyTBSTKgG6g2nHZiAR/wJPK78R0toNEREYfAMADMkHBEnAihOW12DIRWmUcmhBY9QJJAI5S/Qku/AwxEmLTjnhvBPGMlWt+Evkc46SFoqXh7GLYBGKsIrTwQYBpqNC6Bek5vOT7uwVdtbFcAgNoGQACkI9secTnMaSnArOakiAgvhihlxxY4Auf8SA59FhEAERERI2UG2AeDxAyYk/pYm95srma451FB9YsJt5tcLQCMHzgHDzT3RvRtdGTdgjU1pzYKEK1pdjsAHDz8Z0Pl5oXD+pO02+bEQUTMAPbcc5laXBB9E7dzAV3bNNWFuMVOhmlfStOAvI7fK1vhnSRYkWPDNAicq7qY780QOmnl14oaJr87e+TshSVQg0n0UXPC18eAR/RGM8eAZ1kPwv4BaK6DRNRWAKyo2rBlOPANQf8bdDUjQ++MiFgUOgPAtWpYZt4uVy97todahw4Fmqj0Y6BVpkTYslKSlCgbBoIAGfpi0OsFNAcDGQhve0K6A/O81zO+J7//TKjEfr28F6FsoUHn5VSaewkBw4+rr5rmAaIucDDIDQpEQz6cIaL7v4HoyBcRMQYT2gDO/dkNfN5xzSNSNoDmiYjU92SEjoj3qOYxdMHNhuZhIJf3eJzYqJ109V57xXid8g6/q8UeGwMBOBGB2/zhbiEAQiAgmmyQT4VRhLtnJEUojH6/gX6MHEREEFGz1g/yS24h1MRyJjBr8shox0pBUtYbAIhG7+1K7t6TOlw1/d7R0y/pK9X6Xu3/kfJ7iCj3/IKXfAciFvb6CBJpXBRCpzNpb7E7X20AwEhwtQ0AF/M3AIwd/qNnnThlRVwRkvnQi6eMCbUGQ54VOpxgQptqTnS64xFpy+odgDsGonaIcBmFHu4BGk7J3QrYexjKoydCv96E99ALV33gI+19PISe01ug56EEgJ7CLfcEp4c7PUX7HyJIpf3nJG+ournSMIJXjksL8ZFIRSwIk1DzoyST0+Hop9QhgAgGhJDZK297MuxzOg7yA1oV+2s1vFj9aqhQV8Oz11rgPQOmft/jPGrePmKQYlNozRcQAEEgBMUCELX/e3Gw9j/k3hGXAkTsgSWjzB4HU/mxNw7RbU+GE8rvIsdSgBAkhJLf/XvIh07qGmpoRHhLFY6+v1Dh6Jzx5pxQfvfaAPRDhHqXgx6K7xHhTQ8Jw4HPKK0L8mM92lb9NxdkbQMAyUhuYO6i+yTeeyO+gJALZUTEQuDZBDjiIjiPDSBExDQZ79II1OGRyYyVKgSQBCGpAtmj9wffyX4FPZj3enkIDNzYHGqrA03u5BipM8RGOIScXMG+n+dwQc0F6ykta+IyEGD1lb4ag7U7BwLdQDnzMILR/b8PlxDi5zAhRUSMxTHU/JCP7AwGhyk+RYLa/b+2AshOjTx4dpnD7HuALmBKjJyJPYPxIHakViaQWgQwogInxzUR7ot37OTaf73YRbyCiEtiaZaAy8KQMC8+d0BYfkchSNYr+GsHvh59vVeQhwEKfReuCN/VVNOb6/J7v/b/APm9dY7rVe6T93yPCO8rJPiIfp4UzYO605Tqn5HgQBsQCYNqdpH/Dsf0E4zjFkHTPyMiYjJEA8CMcFIbQA8txUCGCZ4xCr2NVwyE0v4LrBQDgU7YmX01OoCB6LnL6Tnynt/7FPek17jc7z7gZkZT7BqyDsB4Cu55ivd21IMkIkkGah8huSJxTWEEnyLOK8TPQZyJiDgMB1DzmQoL032HtfJf2QHUmnRPmyfjR0J12Ff0KR49kGQPLevgDIffq7iBdgXATAfrHhyjyLl6HPOhDwztGJcCRAzF9VoCppL0vRLmBMUdhnCTCNsdgOsV/CQFItcld1QEBl19fSfae1T2Q6vuzezcZavge+7tf3TPEj1Xs69L667k7hXhrefSsEeAoy3R1SYeRQqSYM1OfjdU3jB68K/buDzO7f4/7EYkgH7/i4iIyyEaAOaFHtXsMQUOOn9BfduARz+iYiCkRCFJEAkE7rsxROH0rXv2UHGflOLyJcbV1t+/V8LpMT+Eco7SGljcQ6jk/u4OPkVjv0IMhH4LAiADgSQYCU6Sg9zQfMMIXpMX4WlxAjNA1PVHXB+GU/OjPqaTuv9P+mU2KwCaKEB2CCC/QSCiF1NpkwZwRQxr939CGrsV4slwGOGeS+1ngwPH0Ph+jDaAiHGIi0d8sPtjxvL7I9IW1Q7A9R4AQKhsyKbQGA5cB+ATM8E5Y6uwnUu6CcEq01t+n9S/r6pk5uvRy4cK8Waz+Ursu0W/t+dxru6ilt8ZSU6SkbyBasPo4Vq+wwmbsbcou9+HrsSIiDgtogFgjjjSDDBkPrLSE0w7hxUxrJE5QoaVJCFRCBIEQov/o5T7IarvVtGi/eD0R085e2Tk+ozB2Hif4r0x9ESvDcBKNIfUnxPcHtfiFLmMgvuUEA/R9q3Re9gEEFR/sw0jGLX/ozGdVBa1/xFXjL1C3oURqsEJPksCbQ8AApDLjST/BNG8KgIgWFYkgBDn15//6RCmI7X/A93/rcfFTz9iBK5rQcCRZltbhD+pE0APhj13h5RhKUBp/yuCCqQus+tio3sSfTO3N+GK2D0Ce7jBtmHCm39If/WQEa9cb0TxdcwG3sOekgMqgr4b6z/qYiF12wAkJDYDNm66CC7l/j/8lqHf+95MPmtPRMRhiAaA+UKfXPbO3IcV28lFF5lIBj+UCIiIJBAAMCKbIrpcgnvSQkj77xYbQviS1zYfuHeAP1RQ597vL9F7r14Hr4OAy0bshb/3GIn67xrDCD51HC3Kk/HflUh3ERE6TjioT0G4T8YMNCsAtHUAnZh7pi8/TjAHAgGk0lHMaA+AaasR0vHMpLEnwuGta3VWh98dETES+3jOs32txyvxD7s9JLIeVZWxGFN1Jb+DJEIi1kM9/PVA+1KPGaCrnaZAH6L41X498vsxXe4Vq71X954fOGR6hPegfcWZjREAGQllA1Ar+G9BLFdnOPlXc8w4OBwnX1Ac8YSw1I95IK6GcZ/qi56edTisrPEeQ1T7DDbGcfQ+OGTzb8/rvzCsP/Za/t2SQ3n6H9SDITyE+xsqwVtI/3m3Dl4rS3em5SeQ6kUAnOQNlTcwu1WE0f3/WBxkBrBV/0cUFRERcQjcD+2UPJNaAUD1fyd80HE4hWbSO82douQDMgx6ChECQwAVAmhJrHUPzwe9J62rC2rycBzcqJrLPK5TIrWPOBBX4TIydmLxt/X8E9NY+Z3qP2DNsj9sqawrRdZwGmtFnWsPh4yAfvvBvicfC6/8PkQhvFfw19G/0LSbsH3N80v09QqAegU/bWiOK/jHDv8DGIDj87d3neNj7R8IERENrtwAEKEjSAPPyUAc9CzlNVj/k3X8QNQk9QNEdsfcPTjvfgbCQLcpLoZuGaKGQeg8J2w6EqhQiNXoJ0PuXa7q31u9eiihcUaFERRdGMH5GQAipsFYab5nDEbFQETEXkxLuE/PBigjfrcHABEM8PwfNQ1cYs6YxGBwNiaM9j2u1z2z4bfmELzpWMXZsKt6tlO/pDOr8o7R/h9zu1va5QdTxEKxfEvAwIml363sqMcPx2Hyu1oBAEASiCkeYG+dVCCaAxsd0HGPgiNVHyW/95zWnfb0XzfP8DP7zQz7Zl1DfldmgBuoVgjFAi3h084Nx5R2JhtACF7NTcRTRTQAPBUECdfxs9HAGeSIB1ETOrimWuwQBaFF8OhwD4JgmXtuCZJ/78lRa/2GY8QtNCjKkJ4/3E21DWd2bnTR/X9iDJPHBnkORjNARMTp0H5Z55qUW0N+sxZg9KOPngyIbEfCQwqZTmtygP/+/jOkNlgMFHfAJQAArNU3AACEF1/AcRjhPlKJdmHp/XoRSX3EsViyJaDHlzvUGnSznhTHyO+tyV9VGlvzMZLJhqAhmNd5wMxgnrTcGtu/fnjJwr67PES15+UM0c4fjFAJw/UG6OsuX+8RgHpPS5bfWww04Iy6fSzmxUVEk8ATRjQAPAnslYImK9qLo2c7pS5QKWKkYtGGco7QyI/D8d02SvwcYvM/oAL7S/Dp/UOOCXam4DNrBgJnRPkGIxLEQ+CVx8i4MrSc+AIiIixM6H97LjQrADSNQF9e71kyd623NSe9+n3HB6B1aO+bYoZMQP169b1nrEsHOFiT+TfwFs9JVyPTzdqIAEBYRwFaCry6otCZIaUtp+l9OLgVp2t+JPURE+CMloATzQZjFdgnfBJMIb9TE8IXGuEdQ88+UkOr3zJkvt97pl9C76/A3o7rz9NetbKNvUsX3vGwMdts/DMv+X2sE8DFFUDLwBDzY8RVIBoArhw99A3hLKLMFI+oGQhlg5YB4j4UUxlx3cNhfgQjajKcVFs0fji7MDEsWtuweUjUBn6YEUmZFTdztTi+l6N/YESEjuO/qUsoMhGAAYJEJERCBN3Q7OqvyffnVdn7tNY29moXPeaEMU9xJ6lp+5cCaQ+0SpCTqA9azb7Wkt5iVSaJwFp+7GIYK/kfcLX/xqVzDjPU/uvlR1IfMQGuXpO1FPkdNKs/ErBxxRKAN1KgSYMxfOlSOIUkPhnxQf9Rx4Bh9x/WDnyjAgIsBBcZJ4thIa5+Cn3auCIDwCRT63Xh5A3aK1BPBAJo9w4EtQhAe/YoYn92zuBETzshBQkZzA/0hai3a0AgnJsHwRBc3axwYRw+AKJuICJiKlxiIr4B2FDCCTlxRpwRhwFhgHpJttcksFeVb5xBz/mD2YS93erV4/ff5XfVP6gy6owey8C1u9i31D2CqPZeAgBAIBqwe8MMcAAzM7DYxXEyLWar/dcftITBFbEcWGN3ycOrrvupNR7Tfe0SlBMfEAG1+wCPrpCb4XRv8ZiVAcuF95U3rgKEhDOS36P7/7nR9njsi2vBvA0AZ55sxj5u3p+Bt3Zncv+fumSCxuNM/YZDALX55/1yYHAFFy1pAkDtPVB7EMzJADCfmjwdTNDnS/i2IyJOiCXPXDfAGTEmGSOGxBDqoPLO3/9n7z2b3bahRe21UFik3ZO4JDkz974z9///qRM7duzttoskEljvBzaQACiqkxKe2aZBCiyiSKwKwIGRt77WR+/19xhHaB/Vt2urzpCuBj30Z/GTpZZRt8K6Cx18rn5cPwSVPThIA7FTyvHhln8QFNMlhPsDB2Syma17u9jhwnNnyjmACzO+mEjGO0nNfpnUT3tKY791XuysYPETFib8WTHcC3N+bP+0hUjAuXD0AMA5tR/93+Wk78YxvP++L3iAn7jM/YdKlWh3IVwbrN+ETc3G0ASuAwFY1YXwaIrfOjZNHwjszt4ahuAbCASmyQw4A8aIsaoTAIHqtA1U9zl3//ly+fs88u2cd9uJ79u3vzPBRi2RN8XeVfT5+gfGAHr6GdTL0r9vRT+c/QaIkBEwTYwAFTE17kBsf7rmXi58796a49hG40//75x0xE9Z4Cywn+xNnrljem1b3d4PxGHs99qDPKXZY86RjXMWEIhhOQcA4Uh+vb2k/5/W+z/91M4QCZg8uwUAxv38nvjqfKc//KuyvrE70K052B0vrdJ65sB1PQB6WLffphbH1hbKwB3H/Y4NgKiINyERAKKe/BcKjIPgGwhcIBNvP1NiAhivOwEAI1IAMKQHgEGnE4DDj+9pG9b2G0Dr4PaOBK2phte2RE6Xuq8wfN961f7rOVEJNtvbOyLUrhpzowahUGhiCvgLymdkngs+LMMf/wMN/jNppuX9N099yb9a4NjsFhI4EPu8BF+w+zAUAYDSfoeeIYDcjODeT56dciI1AFQhnOmwrzB/YD1BSE8TMXV7cnr03PB9vD9rvf/76bxtZ8UdEoLWHABAB51KNngW9wgBVgEbQoJR9ADYNH0gsDsHaSGC2hG4KKavraUck1w+G50ANKwGuLk7H/Wk8Psahc52c9Xy+BO4tpt+fzs8ANZ5h/v02x8huao53fr9+MIMfSEW7Hr/qTD9NeeauAKukL0w+XqKVne44D6O939aSXwTulSbIOoDp+R0+XyO8+z9TT64/V4OAUS2RNqBAfd+j7b8EdwCB/wZqqvf/BQI9TCNNAbrffPvsEv6f2ADnFpwYMSMew6AS2PnvIMjef87HN6wICDDdKWek46v5TmQ3nBka26H0yEAYjFqEwGOQ4UIHJXDPqzBNxC4BCbtwKtIGCYgGGDRCQCpTiS3c9idue1Oj789jI9Xl6JyfCE7hFBbyD3p/+4d2zV78Pn02x5/6lSqZWYnBjDwXJ6zeG9vA5ZL0ig0cQ1MIVfEnnEahkPI/d8L42l4Qm5OYFwUEVv7HdnrY7pn7/9xs/fKM1Spe40t77oW55Yt2Pkga1uaIU3ReBrOBtd192YkIAAgIBKNYgK/4UkAncIWHFrWTCuBYAOCST4RpqHHXy7O5sHzXh1v5J8eK/swlJoDNFZqkYm3USu/V52gky1oeg2GGylDlIxhtHIGd88c3Pl6WjshMSh6EVKlUJyQTRWIwDQIvoFAYPRIhJQEJ8ag7AFgDDgznFLUYl8MwL1jW3Ho7GiL+CFhhuHi3rnq3G47650ZfK37Rk0dn/ffecbudXYmYCAAzcr0f01MIXtG3nuEgzBccB8z428qNvzWFzm2bxfkfGAC+F6bHZ/d/b6NR7TfgUw5T60h9CrWxmtdnmt3PN4K8/svbU1KWs9BNm2K/Ld72GGqL7XpWXocAr1XVYLVvM2gzyV97yj+oosnhAFGTwgATBBXi+2Sp91d9vMmHt37D60AQDUEkHX2wf0B+036g2KfqF+9WLtl/SdbUOtEnYvbXBWqHjsNxLDw/o/NnnQSZNYeOd4vHhSOwLkyiXZzGCnW8wAzphlC7U0m158Z5q83+jz44G8F7O0uBYDAL/d8CkN/u9MvxzsOemPZHQjILDjDAHYcpScM0KnvuO2tGABxTVwhV8QXLHpip5kAYCM2lQPm03N8DnreM2o8AIKcD0yXzQMDLa/27pyimSuFaj2HHwAMSARrS1kCQGpC/t7zGDh1AOfu3kDCut03st/9ddx1u1Lb1RGw52p6HP1ObcF9HEIjRHI0T4mHgY9tf2Jo8P4flSCtR0wIAJwDCJ6ogKe8K0e3JxAAAUEDEiIhALPno1krm3YTXs6916ojQwIMTsfE2osZUtP56UY/3q6/NAFQnZKHfo3jWJyZJRxwcGolNRAI9JBwzjLGoO4EwDSotjlqlzt+fzO/zylnyWoGfC77zu4EgGiUPadwS+0BGsha77/1YVVqJys6ZKnn096zuLd0NpJGrsoYAHth8nlNvsn+GW75b2rzO9NOg55QMOb7EBwLgfGwa08gz84HebxPYb8DlcY7lh5lW0CDKw7tEP1VzU2z99Z6+bezHJzX5qzmqzBQIq+vMKB/QOsQ69QVQgBAAAaggRD09G2riV/+ZAnSepSEAMDk8b1Th3rXTmETpIQJySdijDgSY8SKEWWo6q5O3X7rHWodYq1hb+1nf4odQ35NXgD6P7LP5Kengn0p1RZ07rsmbIBWYfhlGDeU6pMTQ9AADE/bA2DT9IHA7pzm5w7aRuCcGLMfbnNShhFwDqU0R+JAqvqQTOHlkhaliKfSTQ+G7wUHvPmdCg7vf/ujcosr3mAfrR+fFHZup7aS4QyN1Kv1xo7+05QrcUyuvexD1TeZEIiQ6cL7T0wRe2ZCwRgxvf8Dc196lOezeee2/iKTuAPTd0kFAm7Ow/sPACkxDmWHvyLqryF3SRw3hlXbMt6rgYT6jff2MWB4jNcn8Z2Rg90hY1mBnU/7T+q00/dxbRqLeYBPOwnwpvb71td6zC95TprGeoJhPjLOOQBwCa/WwFdp6m9cyjAFUSoQxJH4Wi+2acEOi/b3uObbiX5uKWh7EGATHaKfIaLdpUCQo1StOm/gkDCDw1PQ51nAcgxB0kD8/F/JQM2Jf+ugbQQC4yNlLAHxq5gGADjTTLdFSRnLJ6eUqeWpLVvJMu47nnprS9mFry2Ru7mJvuPbBweXUmHTI80rvz82W6x4PNQStnPdYFVol1saQu1VqXQku3KtRJUTACjgCtgzP7bVsKkcGeL9XysWLsF86GFC3z3EAAJjYL8txjk90imwiATXxbh/hf2eFR/ZhvFWr3N3p76su0a++07YfwkHst9dpyHnjuYWl8XtOIXLOdB3kcZq0WcD0DXswujo9/4P+dnO6b0bKcEwHw3nHAA4e7bIbJooDCABwYlxKscOLnoRetzQ/fjMe/Ds3lN569u80b7rv5T/cBspEH2OiUFX6rgMIsYIkDQSA014Kh1i4GnP7MUJBG0jMG1Gb3RtSsIhQcmglublPMCW0d79M/r5oSGwenoA2H5/aNcpJhPqHKFZ9k4yXO/buWwPCL0Sti4UkYmOy75VqH33LqntdP23D+6V+HVsoBVs0Kwa/4fYCsQzjnECANykjd+vNBhzqGC0F7ZfgpAPnBNn9iSniAnIKoGPMWCqlGO+P5N6Y0c6wzB7vMfet6v5Kg88iJO1Nnj/Xv27b3EB5V8V9TfvefcnIGRqaNpaAAAgAElEQVQakACJgDQSO9mDudE3D97/CRBk9ggIAYCpcipr51TMiDNARoxrzogjNJ0A2tLYGQYwdQhzo1O6+7C0jcYY9y17zjKk/etRF3yGvbFE+yADxahT+QCPftb5qPOHRfKgBq6IP6PMhl1BYOqMy/WwkcYeCIyEcb1F+4EBJJzzahoABNbMOk8EtaO/GACodQdaksXY2NcDAJtqzca2/O0cYa0ch/bZob3FpvtFyDG8HkGnkrsC9LpIwFXHMO8R6n4VaH6KdVZku+MFEgBoEpqEIqaAvzJ55ADApm/A2vT/EcqBA73lWx92oq1OcCkETsteYoHn9wAnDFIS1ZQ/dQ/+nlvlFPSdCmtjAHZ70K5MPf0Nhtjva3+ojoOixxK37XdXMl3ldOg9Y4/xbh/V1hlafxq4rox3heyZ8d6zn5gdM2LP772bAEFmn5QQAJgk/e/LWb5NaTlnYJkzyIj1DiPYdUMb29d7512SF1x6RrNX/Vk7FNGvQ4AxqJ6tuNjX0RXtvtGI6oNbO3bK7mvyFOwhCDq33eGV0Cg0cQ1cIdPEnvE0CsRApfwsX5yTMEYPQlA1AtNijG/RfkgZZ4QMGCfBSXKSmixp7hgCqI5pkzGj/JAeAPZGXxjAWBL5D+50LqzFbZO3BCiaq2RVszWczgHtOkaBPDqSe7QlAioy/4rR/7ki9sLl6xGb0OGCG6uCudFZczhjTu0/HFP/ykM8c4HAgdix0TjXRzcFzgF5NQ0AUhFFbmRNeyQ6J0WHvMaatvrngUfig1mtcqPb3vyOc8BxAW37vj6XfaIe490sOw3naovbfofqXtmXtfYUtsLQ1Hd9Z9KMaxAKuT5p57+NXij7i2z/Tg058bm+scck2OYnIgQAJsam78jZvFMpZ3EuXqqphBA40KbZ5HYkwFno2ctZzdYe+nUIcH3qEjXU+2m3nr30Hd+nB5ClN9g7lni+XqPPFRWK0QMUckU8Y3zMGQRn86YE+giqRiBwaq45T1i0zFcChACZU6R1ZogPXYlR3x+2Cx2nfI/fHzw6AHiEuFNPgHYZ1jUoXnFMRt+HKrhhO/3B49avdzDDIXad1o7YfNr/VzpKCHk1AzAvZgDu/aZjYSq5/4dj6n78XQhCPjBFzviJTYEXg/9U0wAwAu2qWMsyp9Av6/ic+x467UFZ2TM7YF3B6Rlw7tJvvHdWvG59/0YwbWpzS0dkrzuFb0fybAdCoZFr4gpP0Pmvvtwh+H7+ge9Uk3exKc69zvhNPhxBbB+daWjzgYJL7seUMExA1nMAMGIKYK0R21YmWuIcrS2Otrw4RmvdUAWwJ2Whm60AAOvUCO+hesv9Usu3LwBupEB0vA9D/4i4LqYQRPbK5DOe4PG8ZEv4JEzghgdVIzByJvAWbc+DZPc8fYGFJJmRFCQVMQANhFU+N0Hh2sae2YAJsOzIjwA+UVvJWmckgFwFe9mp4zjL4K/eI2ftVQKAOjXSNVtv4Rfot+d9ZUPoY7W9udUaUAOQBqkq778C9syPF8Ifbvnb6f/OaoG1nFOrE4R84CRs1wngvB/UlDFeef+ZZgy4Au202XuhPnscoHsXyd7otPp9qXt2Zc+J+i7YXFk/9B81QXr7CB2R3X9Gtzrh38ueFYDK2D8wReyFHbXz33ZsfIG01V7DDlsy+ps2LoLYPiIhADAZtvD+n9NL1AwjCJxpzoxhBP1fkzwfupUAX+2qArSVgP45AKxzEbQ/Mt0QPWdsb0Hfp+RZdmoaSoBXGWh2NC/dUmW6qlZdMlQcKtMHgCviLycKAAxhpJc1QabkQQiqRmCcTOkt2gaG8CCiT0xIEAIkB8kpUrRweAS6t8L8FI0xbUyvS78Hpu3Bb8nlSoKjIc3JVhU6B4F1jYhLlHcKaGxpzwCMdoUBxrxVtr3/RgVyRRGIAEjxSIFUIBSwBZPPbIwzAHewf4wxt/GHeNe3O+ZZtjq9Wn0gcBA2igFcwvOZckxIPpcD+RbTAKx6JaO9vWNlgxWVt3fvWOLQFvTQPia4ttRGsG3X26fo/yJk1XVa8faqvd3Wi6rt7n1t5aHz57gGQqGLwD9yRewZT+AtHPgSbZb+TwPq7JGBSmLAJNjmRyEEAKbBJef+16RYzgNcdCSs5qTvl2c1tYnr1ADA007bG201wjYxPDpEt9w5RT9k1PVJd/Bst2W8fZdsv4B9KJ/20Pq0SFQkgGL0gGL6IEXsmclh33SfnKVNG9gnQdUIjIrLaLMeIn6zSFa4kiQykjlJpV9dkqUzHBBC6bYvV6uefB25XIPtUkeg234EquIKh+gB4LfwqV51LusKHRm9Rhb7qzm2o5WSqZnMQeYgM5QZyO8y/SmO1FAOt/w76f9rvP++43q+1nbJvFPkjL+mrXMHAofGFkU91c6ehGECghf2OxX2O4AhyrFPMHXeYJ/x3nMvmyO4uv536/ScBbtnIXDt465DRtlrYvvq+Ax2sj5t9jKup/9JdCgJmglVTOBHTOHJJvAbzvr8V1pX4aB0dM9TMwGJH2zzAxMCAGNn4MM/raSn7Ug45xnjUEwixBlxAuVpx2x5ViT4k9GoVBvqLVjvWx+mJ4PAOEi5pX/V3ncgTtFel51qhL0KDh2iNQpQK3N/3amdF9n9IxCKmhjA8RWI4U6EAx5919NMiQmoFD6CqhEIHJG5xHue/MAXgVKSzEEqisypgJuh7atRgEy/P9QflXn6YHlrnR4YO5DfKYCx6vT+26LcPriJ3Si6bXX/slOoyz1/ZbWy/wLa8QPfjhrqMACSgiiHKEOZgVgx8SWKXF/wZNjef2cdgAGSaWTG+S5MWAofDDqHHzYwPXrCABf1QEqEFEWZvQecaW5Jq4JmtZ00Z1Y2LGvTxY3U3c91sKJ/n1nAlrh3pu6RS3MYgu/bdeW+cQKnIV9+n9agw9j5HzzGu9mV3zTP7W5/5kGomfsH+CuLjt/5b5/2u3WsU759Z6RsHINgmx+MEAAYNVs/82f5spTDCAJjxBkx1IxAAUBHsFk2oflpJxO/bcwTeO6cpVi49QOX9kD2Sc1jQvdiHZ86NxbdCXvcB+Zqx+NArc+dHxmegvbAhW5dwaXokGbl+D+a2BJPM4PQ/tnavO7/wafPOfgdgqoROC3n8BYN5bdIflzIFYoMpSCZk9TUTAWMpT8a3QPUlH7/WjyZHvwhyZd22KDj9G/XdAT+zXPBgDMO9QW0l0YBwfj6vlN0JDJVX9Pa2PrT1qomYDlEOUYZyAzlt3j2NTpSCH/rl2Bt0t/Qcx9LBIzkdR/JZRyUINsDpyI8dQCQMM6A8aYHQC1NXDLLIe7BMrShVaD6aFB91PEDQPs49gHbS2pqYrf9cCoAYH3q3FiPBeRTCaz6DhPeDG84rfJOofmso7L44gfa6MF/qgn81oJWwbFq/Rpj+SZBJg2Hwo3aPyEAMF6GP+19bd8ZUQ4jWCcRENe0MpwFrT/qjvkDbTHZn57fwRn8X6tDmB+B61w1a+0vcpSp33EAVsGlQ3R1L7DKYFVoqrm0h6amRqFJaGIK2QuXz+yoD+ZAm3aDa9qjldyvPU6Qs/IgnLFaNubf6Sxv+EaM+dc5AHcJu3tJX7OlwHImAEWsNQ9tKT19f+AycW0PPnRsXuzWdMYA7CUMVhh6cIlyh9RutjR5f82EB+C/IWBt6Vk1/7rj/yge197/FfKvUbI6SgR/uODupHp02w97yOWNLmKazdEWTchFtTqT/WEDgWmTMs4BWdGDHzjTTJc9+J1SrMb8qD9Jv8BnXDtlt3koMwe/3z8wPOpv1yEAIDLDC077vVMA182x75h7Rzv93/ln9gkgZIX3XwPTxJ65yAd8zz2ykVTyesBcRxld+3/G9uZ+CTdq35x5AMBpC06Crb3/Z0zKMGGCK6wyCBiQOUOfQ4y1ZVtPEgE4WxcjTt9j//d4DcBVAOOp7P/1nMqQs+BbgkOHqNQB69xObcPWSwb9EUA5gxCVMwCver/qqDlcIxJE2miZyk8zUQlnM9zzd5acze84GMHgQUZfFlygFCgFSUVS0RIIKwdvIbAMwUSmlAFLTjnz96G9sZO/79yxoyH07FisrH0obdPdWRjiC1grfIfUqdSkanglADJjAISoIMpB5igzFL/i2Zd47EMAt9jF+18d4WybmstmKoI9EDgnUo6SRDGBHyOGwNEY8c8roRoJXlvx5BLctnEN5XYyt3dM+I55bq/C/ux3cpX7LXfXjqY61FQgz47kqmBfZ/dPA9fm+L3sqK7C/ZgCrqOMt9kPYmkg4UbtjzMPAEyUXbz/Z/xeCISEC5YxBowD5yAUcSBdOQsQWt7/Alu2OX30sC6hz5T9Th2CrDpGTeoc3Kes+DCqIbS/XbNKjmwClw5B9j2x71inAK6a9Q7m0IS1B6FWIJgC9syO6j7YjwKx0bF2YeIi7Qh36GSM5Kc551u8If234uS/1HZc6u97n4qrl2SVrzKUAqOcpNKLbvze7fQnaImeUuYav39XoBsS2pbgAGDO/WvSvwUBoBp9uF3RoVD0uADA+HbVEjtfGaBzZ7x/3prd9IiuMlDMAaAVxjlW4/+AeIyTZ36MV2u44O5L/9/d+19fzaTaky1akUtteCb32wYC0yZhGDPBcsaAcxCchNYLr2BymO1glJ1mOFRlk852apd9aXztT8net3PwfoxqtarQI/f7jPH6/x5fh71js+q6yd1rRSDNeJG6p4itQLzw8Y7f607/d/0yE2jwR2Jvjp8gv/dBCACMjvBU93DDpQAuQWYkBUU5Sa2XTgPYP/5PR9hDTw8AA1ONsHUIpzqyNklhyE9N3VXqbG80BrS2VIWOKuA7kUvbcPQf7Ky2VIrqa5Nmopg+SAHPgD/z0eUPrr/7RzaOg0gbLT7j4qDnCmyB8+6N+bW67J/7OsJ7mfxcvQgUHIQAqUgSdUYGKGL8GgAAWK+hW2N77W3QGNjXFtw9QtxSG5yncp+fXGWPC4D6jXn7yF4x7fGtQHV7ux8pFuVQjP8jnmXyJZbOLzNG9vtCHVIon/zVP/kFnJagcAUCR2PO8YbHP4BLEBlJTlFOAsghffx/WImzHuO906o5je41ch+rjdh3/LWNh92+9tnv1rJTB9q3AqyN4NmRYM0oQNZPgFBm7wFTwF/ZUSfwGyiVOhkA5kbnIabU1JuPW8BHuEs7EwIA42Kjh/mi0v8LHgS/ZsmKMgFSgBQkV7TwC7aO6gCWJgFtLwB4pY8jBcBWHcyPPKkETR3/uczTdt36nXKP9lB/WeiW0ecL8O3S1PGpEdWEjU3Npv8gsVcmj5M/WF/3aI6y+Umn9g6f5D6djOHK/6YHDBwO500+7YsWfveKu1R+fBUrFAJljhEnmVcjAyDoYjggQl3Z/GbSOrQLGpBVYop6fl6jlbXd+k56wgBmHd/ZerZQe4tTfHeksL3a3UhAiGRMnlyL5qqA7VUqZv0lRAIghVGOxeA/coXiW5p+k2NM//cdYs/XOhGhHFqULQg+hEDgaDyI6F8UkqQAIUDmJJXXfjffyo4hT8aL6zPe21Y2Qe3Kx1Z9XwzA/hSs1mJ4i9tjvNcFewlOG7x2crfT/83K9o7mEZx7tf/ImAGY2AuXLxNqIl0/y4Quv2EiiseJCXdpB84/ADAkE2wMbPoMX6D3HwCuJd6L5OfqRVKZRMCMhEGjT5xv/kBsi0C3PU8A7VF9O24/577YrmbrFmBGEbB1QB+dS4JN+g/aGkBVbmYPNjYa1drjKvQ7HTp/5QAChEyBVCBKBYLJZzzS4zncibCHoxyCSZmkk2haD4L9zbfIBwqckC1+wb2fMQBwn/A7OVtmK8lkBpKjVGqBoAGQyvB8EQbQVbS+ljWsLZvAM4bPJpAz/b+g58hrT2rLXGfBWKLPgDfLtlXf2PDOOtj91AzbayBNoJWIcogylBmKJYu+JlHvVzsZ2CmE92sTwt2qCT6EQOAI/Cb5LU+XeSap7MSvdDcA4BoLyJm913HWF9je+bU9AHyGfPv4BP5z+Wg3sUiuj9ba71a5bwjfteXOMU2HSaMJKBYpkjkJRUzRUScA2Mh+d6T/u/afcNs+KW/AyQh3aVvOPwAwCXb3/l8Ov4noI5MrFAKEIJlTlOsXv0ls//mC/1CVoapQU3uvOzqEne/v007M7APfWXqg9v9DtAe7JhmXYnoE6hpmToGvYP+5BhDgzQACGYqfYqQeBDdjsIyDSTo5xvDYBHZhyC84NGIbGErE4S6Jv74KgUKgUBhpjJVaVFH84k8bS2p7/+syFKsIG3VXr7sL+Jz4zmT/jsIw5Icnz6pTZJMx/g/4jfl+L4BDOhNo7H7aqqOZyCHKMcpQZiC+pbMv8TG6/w+3/J3Gf73/QcTm6MXxpg1PaKg6BB9CIHBoEgEPIvm2epYgBEgOkpPU3amAh2TvbRqhh2HZez3bwWokhjeiZFR32ub2slMA47s7j9+qSc2YP+ana9WDagYgFuUYFV0Al0z+FGP0E56/979m9OrHKAh3aXPG+GJfGnt5aC/nyb9P2N1rusClBJmBFCRVmbXXGYy+NQiAlVMAXZFP4FAgOre1OxCQrSs48wfRmCPX9hf4fjpbmhE0rRzVBfAuLd9BZ/CfdioBYruyV2loVA1rhIHizoOCKAeZo8xAPonkS3ykdma4E2Gn/Y/D6OXZqO5WIHAMwkN/AO4TMRNxlq8iFmvMNeQEOZEC6sYACDSW4rSW+NCSU7UhuOsv5XMudLz/G52GXOXhjgCfLDZ0Hqwlu23bV2Ws/P51NWwSABVECmRWjf/zmMZ6k294UNAlEn2W/57Ztzg+YUMS2jAfo9e5AoFp8yDFBxatcFV0AlAUrShryymn958Mm9osF9QqgYnZjd+28X2+fnuLMwwAxhYnbvu9XfaJfk+hNPjtbnzFtdhKQl3oUx46vhGNIocoL3QAEI/R7Gt0pAkAtrbfz9n7XxAk0xDM1zQwgIsIAGxqpR2N7R5Ud/N3MUgGDyL+ikKgECQ5FUkEtQ5Ruwx6VAdyyXiwFQgi897aZr/TRzBQe6iDAr3PpkOyrXUcOFapOB816oLRp6H6I/OARI6oiRlW6XzUeBA0i8oUQpAZiq9R+lNM4QkdYRsxYqk/wrsVCASmyG2Cb+bzxWqpUWnMCXOCPNO/sMn6r9O+EciZ/l/JOwLqnQCgn7orQHkE2rvyaHsBqkIz5o+57NjtYEne9p93fIBOHd36K2di1Bp5DlFWZv+JH8nsa8r3+vXdbHqLW78vebZfDEEW75HgQwgEDsd9yu5f0me2ECgkyRwk06zd269ffpkVCmwzvICMzXYKv22kr7XfqXU0gvVN73r7vWdpFYjAMT5SLdbBWAWrAhgmfMdyb9VUvPD+1/Z7pMbUGqKxbLa4focxXfU+CJJpICN2m4yNiwgAjJN9ef8vkPtEzBfxMl8JJoSSOUVaL30KhCHt0CURoV0ocMcDoDWGj6062PjCAL4zWh+RayMQGDrIYB3C/LI9W+xdfB/Zg/9oAJ3z2vsvX0X0JZauO7N/BprB7jco2NCbEO5WIBDYI39eyV+LuVJKK6VZrjEnyCqxXnoHCDQ2BmBHHhWRAHNZYonsBjIkPXrqtHHqAJtiax0EAJ0AvKvgNOzX/lkzAANBORBQPQSQJtAIOpeznCU5RsXYfY9psjh88t9wwe3IfTmmKDoLwzLI7iEEZ0sgcAgQ4EFGn5bFiH9SkMwhUnrh8WsXu/js0yLJn9qH72nhWk58bFnuA73/Ts8AgM+u99vvRmGt5Q7QOO47Rx9ouXcrt8MAdSoAETKFTfbej2Q2tu77HS7F+19zFkrIwQnyexghAHAa9uj9v8CH/DbGe578wleJ9ShAgtz+aKfT3yxDI90LjaLZDgid+2v2NFybPtCfRNA5tE/82SLfWVinQyAYG+08wepTY4wgl0IGlvbQvecapar6D65QfEuO139wCNPz/gd5HwgEzp00wvfz2fNiqfOqEwBmGeVkzADcBANQl50DyJoHuFy2YgDGlkbGu/C1tgN9/c597R19QhxMIesqOCz55q8c/Mcc1t9y/WOd79/+QwLQOU8yTFYsWfJ4xaJfUfp1NkYD4ZTycHzieCPlZcyazggZ368dCEyeu5TfvCYrXGUoRGm/vzqFWmVmdq14ao3+V/zvcs3bb2+rS98W2XtD7feq6ei3331WvFVAs2arK79Rk4xOhF2z3TW1ssNhojBpxv9B8TU6RgYAWLfJh53+79zzQhrtIM3XEOT3Osao3x+CvXfk3pqtH8jwJJvcJ9HHBS+TCEDmFOUeHcLpwna5DOxsAiB3iplPM3DqE/4uhBs8kuQqb6JDDBgZgACwrxp4tmtTk1Ci7j8oVii+REea/nf7t3sk7UIPIxNj479hgUBgcry95b8WV/+bZ0rFOs+LMECmnmrXP9YxgDIkwCofgTP9v9tQYRlF2Ay0CgDgGhqoR8fseCVsaU7VSj13n1OO90jh1rj/nrH7asdBJ0iggbRmImdpxpIVi1csWojo4838KTq44Blu+Tv6/lO3zlQIMnQq1Lp7IBDYC3OJ9zL5vnoRKAUWo/hGVE0FbOSWoUfwQbsALuHbcb87TfLOluHZe3u3351Lo0BgfH2vbU5kxkVs/37z50ngg7L7PsoMxC95vNn7htOjANgVzpOROQRGTZDfvYzu9T5v9v4cXuyDfZ/yu6fZMs8ECo5SgFT0CtSYvqW138yJ11EdmnxAW8A78ga6G+t5ffvTB3zag73L2t4AO+sQg/6GVcY607D+0wBaM26M/i+/p7Mv8TFGEB6I42WZiiE+GpE/lRsWCAQmx7ub+NdirnKleaxVMRlApvWqlf7fFExRXi4JGLoGAvKzfduKrWJP09j+iMj6qFY8nIIb2qK2s1on9+m2Va8BiBwb7T8iJMWSjCUrlqx4vOTRp5ubD7cjsg5GJLut5+WEMnGjUwfZvTWj0b8CgXPgPpEfX+SqMN5RKpIZZYadXsp3Krr6tWb9aUlGI2Ru4okHOGzwfsHtDAPYp4Ot7Pd+K74stHMCtrPc26Z61yVSjP5HBKR5pEDmIIvxf76lR5q9b6Bg6lzKhXr/C4JA2ohwuzyMSMU/NCfvBLDLE+jrl36xxBzu4vhxyQWTAmWOklNUTQWsAZAAsUwV7HgKnNa1S2PoPi7UvuMI0JlliNrLzkd2GYzfcK3S0Fnt0yFKpQGd2kOzAzaVexQL1wjC1N1SHERBlENUdB7MUHxNkuwoz+iW73UwiAOBQGA0XKX4/mr2vFxopbTKtcoJshXlZTi/HgsIwRCdzBgLCKqlEQMY0lP/sAyU4z5HgL+MlSBuuU6MJXYlOHbGCEINoBVLM5bW3v8fV1cfbuIjiMdNLf/mpyLLHbCXC7IYs46w0bWN+YtMguBDCAT2xX3K7mT6mi8ligwlB6kcUwG3uvq5JCDUy+rdNBz0dpOHliVO4LLch3QCME/nPFnPFrfZ3l1i68v2GuldxQCtQmOnE3TN+XJVl9l7WMzeFx9t9r7hmApA36fTYXu5vJtAujh9IMhvFxcUADgh4cE7BPepSJ/iFa4KBYKDNKYCpkqZ0ARojO3j0yE62YKeJH1yNJv2wEH+g/QEBnz4fAcDdAjqfOVC2eloBmDV6fmzgwHN+D+EmIPMUeYoM5Q/4/Q46f+bOhE22208jECATe6eBQKBafHmXvx6vf6QK82V5jmpnHSeq2fDNVD7BWqYIcRrUa6N2MAQyHAf7Lep9QnxuuxcOgvNKgEhkZX4T60YABVmfz3lr/VHpJnMeZLxZMXiJY9eZPLhZvYUn1rYVDhz/8dycVsRxOhEqb2AgUBgFzjCXRL9t+DFKEA5Slba77UIs7v6kSHlySU0YU3jWljATZ1dkkLXjAJUtRLb2e8EAC773f1n5fD1Df5TdQ0sxkhotmsmFZR994v0/+PM3red/W43wpfYLI/AJzAlgvy2ECfPiz8mJ/myuz9vIf3fyX3C7uLkOXstZgJQGGmIiLIq67/WHuqRf929CC0doucx6fPgV5EAX50tvP/QvrDOalf1oca/D+3v6HQiDPyorWdga5rBevABQK0wVk0GgXhMkxc+1of0cpq8QCAQmA6MwZv75NcyLTsBsFxjRpBpnUEZy4ciO85I+qv9AhqdMQD3WECEfWpUKcqpHO4PXZ8OZAcvwFAZ7fL+l4XW4D+15U+gETQh5OXgP3GR/v/5/urT9Ygi91DdaPTvdmw9YwSG90YqTNB39khwIwQCu3OfiqtfyQpXRQxAYZzRqorum6l7vskA+kbx7cWXmTc8/X8jTxK5yv2iH9qzHDsNdoC+Ck6toBi8l8ykgTI5AElBlKPMMcpQLFE8pkeavW8ILQXANfjP1q2xrdVNjBGoIhMj3DEDAWtDmefFMWMA4TE7NHdp9PlZSCYVxsWcgSvVjALUiQFgSy4OSCJwPyjokD+bTQm49lObDXWIdufBanyAarX+wx7VwaU9QOn9x5ZPoUxFVKyZPuhZxF/TY/QfHHgTz+RNPKnouhABEQgETsvtHN/ezF+WS8UjLXJSOWGeUd7E8qlpBwkQEYxkf1c/ANRACIjlxiJWbjSmhou/v5H1fdrjFST/qtv4bwtrMCQyQB2GbySyLalra78U0GiGAUjXI/8A6JzPM5aueDn4z7eb6w83sf/r743hgjvk+u1IENyHILgRAoFduI7wLkl+rV4ilAoTDTlhlqtXy3g3YwA9o/hCq6nztXrdl7YeCMhXe0fvv3016+x3tHQAMsS9uR2hvX29OY/Unf6n+EgDz+vxe+F4s/cNv4979P6jeWK0PjIIovM8CcK7ohkC6KLCAEdgXw9YSP/v4fdUfE2vlFKa5RpzDRlhlquFS3sw8wjAEIqtJAKsCn2s+7yRVXtTIDpnJSgbsaJgzoNEAODx+HdWW4pFdTTL4+8IBnQUiGIQ4SgHmaHMUa5QfEvT73KsD2lo4zYn3LNAIHA03tzLp5erT3f1ju8AACAASURBVEpppTTPtcqJ5Sp/ces+ZfPU8f4bMYBiFCACQyvo7I/to1ErExDRqNMTKljbTLocAU1PfzCsevAIbmgLbuegfNWy6atX/Clj5B8NoBWPM5ZkhfefRc9J+u9duhjdwL8Aven/F8jw2xBu2OEIboRAYBf+mEffXlOVK42KWE4qJ8hJ5x3jnUpTGtDRid804TUAW/NKrmkQD5cgaot+RwDAGKPfZbB3tncDAz6DvbOxs9QApLis++5nyB/TI83eN4TWhWzn/a/2clR2/trtvqVjJ8ihLajdcpdNdw6AaTzxu3GE73jQ5+riH9qGWMCfV+nzcqnzXKPSmBMUOoSyYwAEuupR2EkiaPwFtVe9YPNbvfbhclawz+M8CHkKzuWaAIBn3EB7aqCmjA7VQQNqQswxyVicg8xQLHj0dXaM/oMD3+J+BSKwlnDPAoHAMZEC/rhPn16XOlea55rnoBWi1mpZVTHb9brsjwGUmgBawwGZ9pPp929Xce+yke3l8v7bsrsTD/D7AlwB+1I0U7eXXtv7D5pAA2N55f0vB/95uPoyH9ewv7bsHkuHgOqX31Q47i5MgzgeD8GNEAhszUPK3s+vFqulZkpjXtjvK/pVdWvzJfB1/P6FyGNbtcc1PSl6/dl77m5/Ls3Aa78bbodyuW78H6eF7thim/nd2YCQNBMZJjlGOcoMxc949jUdZfr/FnP/9Lj+B+wFALjbU3U4qLMShNAWXPx9c0wCHGIAOx55tEc7S36/Yj8X8+VqpVjZD4Agz+iXpxdhkSPg1CHqZXvIYI/Z2Wo6uomAPQpEh/6ON+RfNS64kzNIVaHe4lxtjf+j21qC7VOoy1b6P2kl5hlLM5aseLxC+T2ZPabH8CMM4dy8/xcvsQKBwCXwcMOeXq6W2UqriLRGTagp16D1EsDTmCN0vf9UKwC1SDKCAWg4mbs2e7FK7al9OqGC+jr6G2WfHO86AtrLlsgmBCSf37/l/QcoBv8hIF0N/qOwitbX4jvHq1Jqi3jJ5de764+3o0z+7/TZtz86GFPXF6Z+/VMhKGWBwHa8u5Y/F9efc62V0pgTZgRZTv2d+DU6hvDthPwrhjSC7o58Q7xEPXW626kZuLAt9ztj/jT2uy8GAIX9vm7uX7tToKPvPgBkmJaTALEoY/IxTV7EwduzgbKp0c9c3n/vVdKAOgOpD3WAWxIE9Om5bOHtCABAiAHscMxDc8HPqpf319GvxVwVwwVUOoTWy7b2AMbS9P77EgZ9LmxXg3Got2VYAIDM1VahrR8AmBkB3mEEujkF1GQNOJSJYhiBFUtWLF7y6ClO/71O9IHuh+deHGaHAEC4bYFA4ES8eYieF9ePilAD01AscwKjH0BNk6mFLYFeu/7rSQWNYEDTupkOfY+SRZ1Pq3Lhn24OZVRwxPerlWYs28rmbwXyoRK+pRDHAf39G0lNtYxuJf4DlVtyMct5kvF4JeIVj36l80+/pdkx0v42tvxN73/QfoeL4yC4j0lxt8PzGQhsRCyLTvwLrXLN4rIfAOVEypgB2IwBgNGDv2O2F3K0CA/0YIn4oW3l8MQ+53GpUyhDAo3HH4yl04Q3qlFfZ/3e/v2tMEAu0pwlWWW/P86uPl2NMQ9gqPd/j67/9rnokJGAPXDZjuyduOBb5w4AwLrM5PNgjzGAS31+RkEa4/v57HlRDhdQzBm4orzK/oOWDoHVRnJ6//tjABs1Fc7eghspELBWh3DpDWAIfrNsr9ref9dHSNCdO6gaaoDxvMr9X/J4yaOPN1dfDz+MwHAnQmAXzrv9DwQCYyaJ4H/+mIGmbz8INaCmIgyQayK1AgBvG4917j8znAjoGhGo/gMrDGAvzTpVudtKkrPYXqkDBlYYwO0IoOqy18QADCNfWSJbAyhAnYt5LmaZSDOerHi0ENHnh/n3dOzScoMEwOOwud0Y5Ol5c8GehEBgS367YT8XV8tVprnSKifMCfNM/wJyJPBhOR9PYZP2Ge/D3sSDvrJu491QGrpLbCkALR2g9ulbzv0eK16XHf0dc/9qQK1YlPNkxZIlj5c8fonTjzfpSzTG9H/7o/6D7v07oPn7HeIEgRNyqb+pNwBQcPZdAfYS5zjQY3N5T+P2vLnnvxZX/5tnWkWa5RoKHeKpnT6AABoI2+pCHVEHAA24LgbQNemrX6l8kggAy1mLnLrFxo9aJ6Lt8/tXSzLVCEOBQGcAwNYbtGOVOtuNYQTYzEz//+/m5sM4hxHY7g3v7BVeyEAgEDgiV3P8nz/moOk7QdUPgFCT0qD1CsDTShMAYNkVoPQjFB6BjuufVVK1HQYgp/ef2gqjXfbhdgR4pTkCdUYBdqf/V4n/SO0uehqtgD2BQtQEpMRciVkmZplIVjJZyvjL/c2/D2sMgX2xqeXfUnmOy5DTHvnShp/uvK22MXOpnoSxEJ78KfLuNvq1mH9RSimlVa4hI8g0Le0EPiJzJj9jWcQD0GG8Ny8jgdfjj4Zb3vf2bmW8l+frerJd9ntZvWOnQ3u0XocaYIxp3FoSUKUJNNupnMxPE7CcJRlLV6yYBEj+e3P1+eoo3QA3whr8p/v7HNj172aEDb3n0XZWDDgYfAPPhvV6/9nHAGCH73jkp+XCHs7NeHsb/1rMVa4UL/oBFAMBrdDQHgDAuIv2EEBQdfRq1Ahsehdi+xcwWwtjfD/Y7wtD7eK6AIBTgfB6EHq8/3UvQjuLsKxQdh7kRfp/9Gs2+3CT5Icf/H+4E2GzHdbWND894at4LCl19s1+IBAYP1fX+D96Dhq+a0AqhwPKin4AOuvWpvq/QhTVif92PwAwwgPQDgPYjSwZeqKzK8AQyFVuxHc1JoAtxHvFd2s0P+efKgS6lnMl0kzMVjJdRckqir8+3H74I6YxaZZO7//o0v+PTvD+T4jL8yQEAtuTxvjuuunEr1lOKs8oJ9IAiK2BgMAQ6J05/DRQfwIfeU14MrYQeXSATSGraF6trx+AJfoJPKK/a7yjFQNwaQVlGEDJeTn0P4+XLHq8vf5wE+32fQcx3H533v1ReP87Zx9PWx8Ez45c2A08UuLP+NnIkjvCE3JJD+F+uJrh25vZ83KhiyQCnpPOV5SXlj8VagRUsf4qU3/dEECEGgHBMpE3+YHqRmWL1qXHZVAXugoEOTsBFH9ll8COAuGKAWBVpm76P4EmJgrv/5LFSxYvRPzv7fzH6IcRcGI6GoYaz2MT/PsmOBECgcBIuLplf9McNP0gQCrDAKhJ6Rc7BkBARdieALDr/e8ZAqi2+TtlW3ybBRgmBsiz6pbg2BTqjQPH/3G4/gE0IBS5/7lIVzJdyWQl4y+/3/7zNl0ew+pvfedNdzgP738QqRfFuWuIgcA+eXPPn16qTvzFQL4qz+kJSutbGy8TAgAgs0bxhSYSAIwAsOzTX4XX+3L7BwrxjV7oIULfXHUUyFAGOvY7WmY71RuxsdYd4/eiVjzJWZpV4/88pbMPN+lyNO5A9JRbHN31700U3sprvIs+EHSJA3JJMYBBb/wldAKo6QSIez49zjUEhvPmXjy9XH/Mc80rHQKzXL0Ulnz1Xndi/rAmBuBr8uuQANZlMvwF1RIr8V3TSk3v/6mpXbFedXoNzCVV9QixHQOw5/6tZxNqlAaq8gp96f9UThxUpv/LL/fXH26PoT4MbIgGpv+jVWGzlM5g5AUCgcDhub5jf9EVEP0kKs1YolxDTs+kcygFLQDUbXch5ljVAx+rgQKcrn9XDABdMYCWrG+dDwBcwsDjBTAnAS6PDNYQwJ2yJbuxZf/XNj+RxiLrHzWCJgQlZkrMcjHLSu9/9OXN3T9vk9WxRuwbLrjL9P+Tmhxjs3eGX8/YrvzCuSRnQiCwE2/u41+Lmc6V5op4rlVGOtN6ZcqEjpTHtXP4tSS+ua/hD2gUButlLUxEM/9vqP3u8/4PtN+rcmfwXmorAHX/vz7jvashaOCV/R4vebTk8tP9/HF2+M77m4gndO3gs+tP38aGhv6cuBjHzlCf3UXFAGou4AE4KziDN/fx02KmldJKUTEQkMpJZ930AQAoG210KRDalSroGQuoR3twfNpWJpq3qrOv9bY1k9BbjgPH0lAaqL3a8SB0NYlGUcAqfxArTaIePTAXaZU+EC959OPq6sNtPK32wXb9OyoArKtV1TivlmJaP2UgELgEbu4Z0TVo+FV0AiAqegMo/UI6B4C2l7yS11SUSydCW5pDy+PfiQE0MwGAsSSrYNLTdlKrTOZG8hR8AQACIAKNjTvAyPgz/0gTYuH9V1FaeP+XUfz17d0/7+JsNBl/Nbb3f7Tp/6YTaSQEwT1CLsaZEAjsxNUM39zNn5dLZXTiz1UGVI/8U0JmvH+N8c46Irw8QJMI2GnEqZXA1433b2e/k1VuWfF1P79uvn95eb4/r/FeSH+09IHChM+LwX94XNjvX+5uPtwcIxFgoHhq/cye7e76p2UkDf3IdJIJcwF3cnwWwMVz7o/cYbm9Yn/czl+Wy2ImAM1y4kppIsr9OWXcEwPoeArqLSY92kOngl3oHMeHrT3UhWExgDUKhGv8n3rogKbbYKlVKCbzJvc/eo2STw+zp/gYj+0WCoT308G2cv2b9XFGoiI4EQKBwDi5fWBEV/CBfuliLKAiEgBKPwMpINOt3w4DAIMy3o/WlAD+GEDX0e8LA9T0S3byFDbw/ldz8zTyGq1CY/CzyvsvZ5lMynH/39398y7Ojzjb38aCe5Mte+cQEnCXYw7cNwjuMXNGGmIgcCiKTvz/Vp34tcqBKZW/AOh2A9dOySeGDuO9m8NHgNiI9Q7DX9D92+9kxACqyDK5ugO2jPd6wB9yjP5vDQOI5WrOy6n7MhEvefRzNv/3PlHHyP7fgL5fYpze/5rQ0J8T5/5rbhAAuMxOAEfmrB+2I/HmXj69Xv1XdAIQGjXlHPLsGUh59kB/R0IouxCiGQmoMXz6jnn0elz/w4PFTu0BOgpEs0RbaYCO3uBZ9aQQ1joEaQBNiKX2wOMiAPD5/vrT9RF9CevYr/ff3PFyYgCBQCAwTu5+40TX8A89lT0AivA06vyV9LIUZ91IAAMAQCJghaxvHAHkjAGAVSZHMMDMKSBTshu1io1klFuFngAANGIaTb9/Ka+ROlKbDDufAJRGpvm8yP3Po3Qlk1UUf/nz7sO7aGwGv8lpB/8JBA5E0BADgX6KTvzPrzOtFCkFXDNNuYY8fwHQbhu7lBel2Y6N8a4r+c46L57VFaB3FF9H3t5G0QK7bOsAtfEODunv/XOm7rm8/1DOBKCrqftWPF7yeCHjz7/NfiTjyt6rjXRH+v+pvf+DvKADno7D6jhB0uyRs76Zm/UACDGAg3K+j9lRiSS8uU9fFytQmmliGlADcsjzZ9C6qtW62QRQ+QXsHgBYzQTgHD4YjBaiR3ton6opd6+kXdO36vH+l46GgQqEaxQgn/e/6gSQ83nG0owlKxEvefTt5vrf+yPNIbh7y4O7HeUSYgCheQ8EAiPn/ndOdAP/S88EoAkJFDANXIPQ+QJJGWKu7gFQRAXqjbYoJ88qtJfGatNcuoQDOVecAYDqCptCu9yMGuwY0rcuEGikapxfxrWYKTlTMs1lmslkGcdf/rz78D5y5Cocko0tf2OLXWfoQbf9jgOv9miCcmzXE9iFfo0/EAjcXrHf7+aL1Qo0YWG8a0ANSj1XA/r78HXf11WY3/lX47PfTAMf2mXwX4/PfncqAAQAhpTvN+G1q9zy/mPbcifQCEozrgrjvcre++/+5uNRpu7bGKej/9Te/w04pCtgR1kfVIWNmb5jx8coX/6L5EwfsNPwcMPy/OYfTc9UaA/ENCEjpV+AtLsJRGgUCLIUiJbGwKr6AODMHTCpt4OhOnTUjrX4vP/QVhSqVTQdBwPCAFh1JySP9x81ISg+z8QsF8lKxCsePSfpp4d0cZRZBIc7EXw77OX9Ou8YQNAMAoHAJHj4g5O++fQPIAFHppArFAqFBqHzV1JZW8y1BgICICq7/fmy/s1VsJYdP96QxBif/Q/gkOAu+78IX5P2ef/rMiGRiLVIlEiUnOVRkkXJMkm+/Hn38c9jzfm7IWbe3xo28oiPQxZvLViD9/8smbKSGAgcnL/+kFrdffjvW+H6ZxqYhkwX9rvZjx+tIjPc/VYaXwVBOYcAAlRzAJhefjuBz3xlN7XfbeO9LBgj/hvLMk+ta7y3+/+ZxruhDFDbbG/G71XEuOKzTMwyka54vBLR96v5p9/Glb2HbaWq5xCh/QwclTHpk3tk4wBA6ARwCM7uuTo9bx446Jv//QS1DoEacgZ5/oykHTuUfvxO+kBVbnUkrLsW2qmC5bFa2gN1tlebEL3vUvOaWdpDy1B2Ls1+AMZfoU9gawigyu9Plt+/USMINAAoMc9FWioQIlmK6PPD1eP1KIcS8Hn/99FynXcMIBAIBCbBb28Fwc3jZ/768yUHrow/DQudLwA0ABiGNKtC44VwJ0PQ2wEAbCz0vn4AMKy57w0AlM79ttO/dE5Uln8z328RBmh7/7HqBMCElokWiZaxknEeJXkUL9L0y1+3n96fwPu/qcjtuafbDA20oSwOpk3gOJypPyEQ2AOI8PfbCPTdxy/fK+OdUFNOkOfP7n2attvK26NqFQ3hTgjNrAA93n9qD+5XlXuMd9jafieAWl2BRhNoJD41uX1VDICIrLl/OhP4KUJRe/8zmS5l/BIlnx9mz9GIWiAzew8726fo/T+tHyB4IQ7B2d3VbXoAhBjAfjmvJ2pEvPldgL75309UZMVhMRwQI5W/QBkDsLP1qXAH4GYdCcHwJrh0iOZcRhNC5mvUvpLSg9/d1N7Lr0M4kgXaYQDHTACeYX9AE0MtZnmlPayiZBnFn3+/+/TbkRwKw9MH+jYes82aoJwITXogEJgWv78Vs/Tm66fo+5efijHFeNEVQKNQICh/JZ27RgHqeP/JL9aPEgBoAvbQBOZbUXyNLWHd9v6DBiJCIJGSjLVMlIhVFCsZ5VG0mqX//XXz3ym8/wNpsid6hNDW8mmwLB5+huPIyoFnCYJ7uoQwQCDghDH4610M+u7fL9+RCKkay5eRyl+w76UxY/mdSIC5Edpyv141BYZvS6/xDjva770mfDPxT4/xXoz5Uxrymovafl/JdCWTRZJ8/OPmy92Rpu4bLqEKX3/X+2/XmQoe3SOI7AkzQd9OD2EIoBNzRs/SGHnzRpC++eczYTEWEAFSMafQM4BjLCACQCDAQkWoEv89HQktx0G1irWWAEaDYRbAFXvwQa7yVt7/zl/TG6AYO9hKH0BNiJrPlJhlYraS6UrGKxl/enP3z9tYjWjqXwPjbh3I+z8oAjopORE0kkAgMEVmN5jM0lkqHj8/LZ5eFXLFuMaiHwDX+UKrV6Ta6d/j/a9GBKrzBAkOHwAwV6lUP4y/zsS/bZufKmufQEgtEpJl4r+K4lxGKpIvt/Pvf8y+vjuNnr+pWPHexx3l0wBZvP0Z1h38oLI1CO4zYFKqYiBwJDiHP9/HoG///UpF4jsSME05A6VeALrO4ka+IwAgks9yNzd2TXhEw1pv7HfTZrff1/5meCP73WvCU0sZ0EZXgGaOXyvxXxOTlf2ermSyksnrLP345vbfP8bl+kNXpn+J06K/VILEPz1nJLC3bAVCJ4C9cC5P0ah5+06Cvv3nM2E53H05K4BSL/VYQHVOfqlAUPGOozUiEFjzAVCZVoCGPkFm1oBZNlsOZxigg1N1AEtvAJfqYJZtDwIB2CMJdHoOakKmxEzJWV7lDiyj+PO7u3/eRvpYY/8MbGfsFMLT5P4HAoFA4IgwAb/9LdP09vFT9PPxl2JcMc6QaxQahQau1QK0AmCVCV2H9q1IAKLVzw+aZSlKigREQ3b3zKtbSyaqCtU+tcnr8Pu7J++pzX4NQEWWHyErEv8pqof9iVQUZWn884+rb2/i1/lp1MzhgrtPdlfbx6MqH0ebCDrLRXFGLoVAYG8IAe//TEDffXr8XiSkAZXJfLqIATQUoX2gUnpWOXyNfO9Y7nWfADKHBiIibMUGwFOA6vg9DLTfe0x47VIDWsY7Nia8NXgvk4X9nsmi7378Op99fHvz6ffjef83sN8tQb/NoH9j44QtexAqh+Nc7u32DUGIAezIps/PWTxvp+Htn5L07YfPVPQlLEYEYsV4gmUMoHL9lzSr2K9AtJz+nZwCai/BSiWAwe8QeQo+pz901QV3DEBbowc2CoRmvMgdyOUsk8lKJss4+fz+9p+3UY+74yR4PQgHa6HOqRNAaMYDgcDUmf3Govk8TeW3z7+WL4wxrlnZG0BngvIFqYURm+9k/1XlZpSA2vXfjgEUSwJX4+5q7m2/f70k8MhxV7S+ZfaXIluLGERCMqEo1lGcy1hFUR7JxcPVtzfp9z/G2UfPRTeXs9m+r+P3yOLDScDtjrypUhg4AzqGQSAQAAAp4d2fCdHtf48/sPL+owalSanXupoRRGfVFkLHcL6uPzI1AXAJ/Y7rfyP73RkGGOL979cHXCMBGj34NZeKz5VMC/t9GSWv89m/764//zZK73+7AC7v/1TbxjH5AYLOsDfOQmDv1BaEGMDWbPTYTPwZGwXv/oqI7j5++gZUDARUDgekspdqqkAwhGszajABq3oFmlkDrhhAdxVcagQAmu+Nayqh5veu3QRg7EKu5VrtofpDgtasv0YB69EDlWZCi5mSM1XlDiyS9L8/b/95e9ShhLdoXjo378SMSfY7GcNNCgQCgd0RCfz2f6N4fvf948vT92eFvAoDCEJOTEK+ArWq3Pe11LYHAjIluKuA0LTs3TYUrbWOux86grtSAjoD/jhjAGUnAGJcixmImKJER7GOYhVFuYyyefL09urxrVwlpxQ8m1r+Ox1lBzY9w3jE5XiuJLBHRq8wBgLHJorh3Z8paPrvkWrLvQgGaPVaSEYj6x8MK57qEYGgnO+XVYK4x4QnowBtK/5w9vswEx61MQ1A24THZgIA4pGq7Pdi5J+Xq/mn99f/PUwjJ2Az7/9wWTiCtnUXwR2E/uiYuMAe10BgF8LAB2bKz9X4wDIG8OnT92IIoEqHQMqL8QFafn+geiaAYuZAwK7G0IkHEEA9EBD4FQisZgEyEwrakHPFV6jn9YVGdUCX6lB+L2fiAJnDCGomitx/JdMsKmcN+vLX7Yc3Y5xI0J3sf3hRGcKfgUAgMDau3vBofhV/kD8+/8oZK4cDYgKyFbEM1IrKMIByJQCSNQeAFQBAe3oAP9Qp1VP+Gks0xHd38J8iNl8PAawBheaSRFwk/pe5/5FUUfTy+9XPd+nPh2MNz7cP6vhIa8tRGKf4HnJV47zywF4wzYNAIAAAcQJv/0qJ4OvjjyICXkQCNKDOF0bHOKoNdsDKoi9jA+YQvuBK5jPHAjIzANpW/H7sd2PZtd/rsQF99rvbhCcqJ/7VPNJiltfZezJ+vp5//vP6y/1Rvf/DkwCKr9v0Axjo/R8cJOiqYD1VD8TE3cSBPqb84+4aAAhesEMw2cdp1CCDd3/FRHefP30v0gcYoQKuQeh8ofOFkTgAALVgNiYPRAQqxhG2IwHQ1if8CkR1OcOumqyyrUlYuQMtJwJQOVCgmThgKxBlJ4Am9z+a5VGykslrmn75+wSzBm3QsFRV3cGA0zJi8TCq+xQIBAJ7IZrjH/8viefi58eXl5/PnHEtJImIsgzyjPgK8qwMA+gMWgKdDCEO7gK1I/pDsSz/VggfPDa/LisgEpeaS+ISRERcUhTrONZRpCK5vJ49v59/fyPzaKf7thc2tfzBeRP3LpzagniLw2+6yxFOEThXRqw2BgInIEnx7V8pEX17/AmaULfsd1AZADjCAEUn/uIjZK6heg1x3xX95rITmNvUfncWPCY8tb8IGOF/w4RH03KvOgEonmgxy8VMyTSPklUUP91c/ffX1de7keb+dzMAhrj1B7v+zU+7cZlLaF4v5GuenMne5z049UIMYCPWPifTfJCmAePw7q+Y9O2XT4SEHFgOXIHQIDRwnS+QlOE9rzsEVAkFxFp6Q3csIGopE26P/6YKBAxIIrACAGUeQb2qq9wBr/efkBBI8USLRMlUyTSXaRbFr7PZf3/ffB6r9x/t8tZm9+Yv3qSbvuleeSAQCKzl5k8Rza9/fpTP/z2pZUYiJ5GXMYCsCAOsSK1AZdW4QMwlxHsKsLkc98luMOx80/LXyKXmEoVELklExAUJSULqSFIc5VH08vb66X38dDuxxP8+TeiQwmnqgm/q1x8YyLZqaSBwnqQzfPvXjAh+PAIDVMgVcoVCgdDwSvmi6rcHhgytEviQVV39GAK2ZwOyTXhYZ8UPbIa3SuBr2e8EhVbQyv2narTeZvBeQKb4XIlYyZmKCu9/8nxz9fnvq29H1w02td99ysBa3WB489gNAxy5eSXj2dz2ALtfQuBQTFNa78evN2lH2DEJ3v+TwwW8+ztBvP/2388MmUCeI1fA61QC0qt2+L1UIKjK/adCgfB1AugoEGSINgS3pDNfHqfEI6NeEzFfp0C0+gGsyf0nJnKRaJFomSgZ51GSxfHrbP7f39dfju7934z6rsAmzZBdc82vsAOTjQ8HAoHAdEluMbpK5w/R6+Pq9dtr/rKkTNIqB5FRXnQIMHoDqBWQxo40J1OyVwVTlFNv415/SB2pDY28xo4EJwJCROIRcKmERCFBSOKShCQptBAkBEmxvJ0/v5/9fCf0aNL7NjMEnKl8rkPsRYQe0/zeOg9h74cNTJppOhYCgYMwm+Pbv2YM8cfjzyIAkKNQwDVwBYLyVyBrON9mICAszHkq5wPoMeGhbb9jtXBF/etG2f2Wduz3wQGArv3uN+FJaxGTKLL3YiXjPEqzKH66vfr899WPsWYGYCXuG59Iv3N/W9d/Z6/WYYJtHtgjU3ucxu3aOy+C938kCAl//Z84Te8fP5XjAxipBFwDp3yBrVEFa2UC4oT+EQAAIABJREFUXRMG9gYAzJQBAuNH9vzaDguvrTEQGFoC9CkQlepgjf/TFAg0IiqZgkyUiJVMlIxUFOcyer2a/ffXzeMfJ3AtbJ0+sLdDr1HpTso+Lin4EQKBwCXAOMzf8PmbdPWYvD5mr4+vy1+vkEnKcspyyjLIqhhAnoFagcqQFGjtF+vYFuXQ1yh3U87IEt/QjAJcTvArgUdaSCgS/6UkIUkILSQJQZKTENlturhLnv+Qy5sRiajhgrt29Pcb+ftix6MGcRk4LVNzLAQCh2J+hfH/maWp+Pb5afnCODIFXGHRlZ+7hvMtykXqntt4r6P+6LXfwRD6WxjvYMh68yOf/d6e1cCRt1eZ8IxrOSMRF6l7SsZ5FOVR9HR3/d/f85+n8P5vYL8P9P47EwX2RWhbA3tkUo/T3gIAoRPAjkznmTkTHt6KWXr99V/57ctPxngZBij6AYDQ+SvpHAAaAUy2978qY0eNsHIHmoIRDxiKrTEUx/AoEN1ZBAlAAxbbDdUBNYAmHisZk0xUqT1EeRSpKHq+nT++nX0/hfd/Myz9alDNjY6/Lr9z0IHHJBVCQx0IBC6N6AGjh2j+JJdfZq+Py8X3F1pllEnIIsoyystIAKgctar/QCsgVTWahncAa6dAjaeJR/Ck/wNAMcEvIuOAjBjXXCCPoMr6RynrfH8SXM/i1X26uIte73me7vn+HJ/O/bJn/9ud3Q+5xREOtEsQ3JdM8euPRosMBE6GiODt/0Sz9O7xU/Tr+5NiXDGuUGisRvTNX7HsCkBYmOrkMd4BAVlLsjvsd6cVP5Bd7PcqDIDmEL7NsD9aJCQTkomWcS5jJWMVyTyKfj1cff1z9nSK5IBNs/ecu6Fnu3vfDXHY7Ecxz3cR30H0T4kxeXv62WcPgBAD6KH/eZjI03JuJDf4fpamqXj8/LxAxhhnjGvGNfIqleAVW+n/HgWCfF0BoCqDR5MYgkeBIKcCYScOFFpFt/8gIVNiTjIhGWuZqCjKZawimc2SH39cfXsTLWaneSo3Sh+A+j76dtuXAyC8ooFAIDBxxBWKK5m+l6sv6fLrcvHtNV+sIMsoyymLIMtRK1QKVRUDqAtUFHIgDYC9I/+0PyKwnP4cGCfkyBhUrn/FGDIOXKCQIARKWfn9BUUiv0uz+/j1Xi7uRiqKhgvuWnZvtP+mVpU7HXOUNy94/wMDGesjHAgcm+vfWTKbf/tXfvvv16oy3hk29jupZWUC+413qOcGsE148JjtWxvvRqFlv/f8aSMq0CTwaRQgS/tdyVhFsYqiXMrVzezbH/Nvb2Qut7qnx4Qa493Rm/IArv/+iwkNa2BvTMRrtOchgEIMwMnoH4PLhQl4+B+ZzG++fZQ/H3+VqQSMayY0Co2csgWQAiqy+2tFoa1AVKvG2IIwTJNYi0+BKJzfBADulH/XFgJC0IBa84RkTDKhKNEyLrL+cylff7/69kf64/exJ/53b6Kz0dlvS3Qu+kFonwOBwIXDYkj+4smfs/RLsvyyWj0usucFrXLIc1QalUatQKmiUMYAVNkhoAgDICkgqnzZxlQ/VR9/NEU2ssLdT8iJcWSsXmpkULj+GUPGgHOQAmWZ9a/msbpPVg/R6z1X8bHv0vGgQdJ1iBDeu9hfg3VBh5CwQWoHaibiWwgEDo6c4Zv/L05S8e3T8/PPZ8UYwyKHT2gUGjip0n7vN94ry73I9lubxgeHsd892XvNdk1AiFQn/lNUeP+jYtif5zfX397ET6cb9H+zJABjtVs+sPff7agcoF5sLYhp2PEPR1AhTsDovUb7nwMgxAA6rH0Axv2EXASzBxbN58kH+f3zryVjxXBAGrlinFCUMwTqvHyhW8pEpy8hVhXaCgTa+gRUW4wXxvEoONP/q2UpVTzD/tRlNGIAjCsxK1QHkomKIhXFeSSzq/TX2/m3N9HqpF6GDZoOW7Pa8kCbnHGXd3UEwiC0zIFAIFCCEP3Boj+S7Ee8+jJbfV2o5xWtclQamjCARlUuizAA1AMEESAAI6ozCcstphJARXwAkTWuf2IMGNOcASs6ATBgDHlVkFxHUt2n+UO8ehCjGuXfxxbp/8232kQsOaXogeTaccRlEMqBLRiBOhkIjIKb9zyeX//4KL/9V+fwibIrfy7KqX1IYZ/xzgARgIjAkcOHnWQ+aJW3t9/Ngj8MgEYAgEe5SEAmFMU6ilUUKxmpSC7u5j/fzL69EXrsyXsAlQ5g+/pPk/tvMspWNWgIE2aUT1TNQSYBDjGA4Yz42bgsRAy//d8ont/9+PDy9OOZMa4YZ4wTk8RWpDIowgBq1U4iaGcTUFtvqAvUUSA8P3t3VAGX0uBY9mcNEBEhEDFGPCYRg0x0FFMU6zjOZaQi+fLm+sfb5Nf9yRIHwPg+a3F4ELY70J6YSnM3iYsMBAKBIyNvUd5G6V+Retb6WatnpV8y9ZLp11XRIQDKpWo6B2jduP6pdPozakL65j9AJMaBMeIMGNOMAWdlyj9jwBkyBM4gFjqW6jbKH6LVA9Pj78W/CfvSdcfslw/p/4GjEboCBAIF8Q3+MU+jufzx8en16YUzphhXyIlL4Bnlq9J+pyqNr2u8kyNv72T2ezsGQJqAgAvFExIJRQnJmOJYRZGKoiyJX95efXsbv16duCXYyH6vff3di7aOEtq3wLQZsZw+SAAApuMUOzSj/NEDXq7e8Gh+FX2Ifn36mTGmGCceEc8gX1GjRhQJBdqhQ/gUCBiiQPgy23YLACAAl5pHwCUJSSIqcgd0HKkoWt7Mnt/Nvr2V6lAtwaHwJgsctN05eTg3NCiBQCBwAFgCLGHwGwMQoGJ6Jv2s6Vnr51y9ZPo1o1XWdA5Quoj5M8OapVr81K5/KKVG6fqv0/w5ghSYCEgEpUIlXKdMpUynqKc21M9wkVsH77fZ+Ygc7aLWnmiUtycwIkbsXggEjgdyuPtbxPObHx+iX19+McY545RFlGXEs1YYoJvGZ3YFAMOiB5fl7nvV9mi/GwEAhsQjKux3LkmWqXs6knkULR7mT+9m395MIe2/wuz51+1PMQbv/wFs/PJrbXXYvSgAQYs4PSf3HbmYmttvUqz9ucf3PAQgmuMf/y+O5/c/P74sf75QlussL9QIyDLiK8rL3gCkVkCqrUPUyQL+AADWOYI22C7W/gRTV4Dushjhp+5FWA34Q0yUrn8hqfT+SxKColjHMo+j57fXT++T53GMMLBd+sA2h9iRUbbjawkaQCAQCAyFA94gv+EAHEDSIqVnomdFz0o/5/SS0TLDsn8dGJK6XTZ6ACjOMJGQCEwEzDgU7v4EVYrEACYpVTagI7LR+ek42FpWBiEbOBXTVEsDgT2T3rNoPovm4te/T9mLoCynVU55RllGWQZ8RUUOX74ivcIqja8a9qeYuMcXCYBe+x2tNWoUAK/rH1z2ezFar9QiQi7b9rvUcaSjKJsnz+/mP99Fy3QU7/0W9vsYvf+9BPke2InxCekDBgAuvBPAyH7owGbc/Cni6+vXr+nr42Lx44UySVkOWUZZTNkK8iKbIIN8RWqFOrPS//0BgFLDcGJsX6M6GMv2BAAECFxqLlFE2KgOgoQkKUgIHYnl3fzp/eznW0EnHvVnBzzef2cnzAsn3IRAIBDYGkwAE4TfBIAAHdMz0ZKQoP4DTWgK4dKop1I4M8QZgxR1ir6BfcZnHQxis/T/rXc+Fsf0/of0/8AeCV0BAgEA4BE8/J8ovb17+ZotHherp9fCfqcsgyKTL8+Ir5o0Pq0sU73Hfu95wza134uUwXb6PzLNY+QRColm3p6QJISWgiLx8vv18/v01+8Ts94RAH1D/1O35tmwixAPCsC5MTIhfdgeABcbAxjy+47mGQi4ia8xvpbXb+Xia/ryuFx8f9HLjFZGb4AqBgDVLMFlQoFvJoBuNsFABmUQEBAyqbnEyvUPbb9/oT3k18niLnl6Hy1PPWKgycbpAy7vv29Ypf03QVu7aibq4wkEAoFAAQO8RrxuLNnL1HJhI8Ft1B6tDBzV7ziqiwlMhaBjBgIAkN6z9D7Of0UvX2dlGt9KQpZXkQDDflcryFdICkjj2h78Q18vXG+5mz0AgIpxfpRtvwtBUmghSPLV7ez1IXl6L/MxjRO4sagyBv8Zo/d/f23oGIT4GK4h0GI0QvrgQwBdYAxgHL9sYD/wFOZ/i9l7sfqSvn5dvX57yV+WVYeAViQAtar+ctQKtKqO4QkA9LwYrWeoThModAVoKRDIADkhLzL9UUgQEQoBstYeuJZCp1F2ly7uotd7ns0n+YR6L9rv/a93vLQmyOSSv3sgEAhMhdHYBftn/On/R87UG9m3D5wPI8syDAROhrjGm2t59U4uv6Svj6vXby96uaJM0iqHLKK8st9Fhqq23xWQQp33DQQEW9vv7UgAckIOTGguQdSufwF1f/3C+z+Pl3fJ4j56vedqTK5/2CQJAJ1VJyIIj3+ZE7kxga0Yh5A+xhwAF+WAGx4dDkwI5BC/ZfHbZP4YL79ki8fX5a9XMPoVQp6jVqA0alVpErmpT7S7GZZH9Z6P7BUq/QPIgXFCjowB4xoZMg6MIZcgJQoJUoKRMqBu09V9srgXr/dsnI/dBo2DM/3/JG/TSVw1m5/xchreQCAQCByN7dL/N9v5KGx/LescQVszptsTmCRnHE0MBDaCJZD+LZI/xfxLsvqyev32mj0vWva7qsx2c6kVUt6EBLax3zuJ/wyQAStMeIaMa8YQGTBepPyjLO33wninSOb36eo+fr0Ty9tpv81N5MRM/x+DLX8wWl9uwy8WFICL4NRC+kiTAF9IDOCcGq+AE/mA8iFKnuTqy2z1dbn49lz0BsBcodKgFJoxgFqNUAq0Qqo6BwBAKfwIaiuyfEPqF6VIGajd/RyQEePIyqVmnJABqwIAgoOUUIz2I4WeRfl9mt1Hr/c8T497jw6A0+8P0FIm+ne/hPanwwV+5UAgEJgup7YIDsIwr8lpOImU7D9pENyBvTCOLMPjEV6cQA/IIH7D4jdJ8i1efslWXxerXy9FbwBUGpVCrY1IQG3IVzEArZAUaFWHtLF64tBtvyMwBsgBORV2umHCE2PEGLAik48VvfYr+52rq0Q9JMt7+XrPfZMGjYGNBu8dVG3T0xyldZtuwzLdK78UTiqkjxQAgAvwwQ3/BS9HITtXxBWKK5m8l8mXdPl1mT2+0ioDpVHpalkqEK1C1SGAASARArCyZxwVBQbEynlyyuAAMlYk+xcaA1R6A/FGdQDGkDPgDKQgKfK7VD3Eqwe5uPv/2bvX7jaRLUDDuwok24mTdKfTl9Nn1vz/XzWzZqbP6e70LYljS0JAzQd0QQgQlwKq4H1WliPLsoSwYFftXVV48EFrdU4ovp9pTyizTNUAAKbjRWCZwfB/K1th/a24sW8wH0srAwD1Vt+q1bfr5OfV7s9X0V/b/aeN7JOLnnuSSpb9P93ICgBJokyqRLQ5XCtAm8NF/7SIOvbf5dh/NzoQfZH0F62TU9JfawmON8JAVmF6t0q+fYjf3+3eB5FLV+nrqcnw/469e1fPbiOvKAi/TfQxHq8AIPOtAbT6q7l3pkJH+k7u/x3c//xq/89D8pykL0nyHKcv+3QTyWFCQKrSU6vi2JhIyxsQSkSMOR4g2bqBkrUYjA4k0IfUf77REGjRKms9pPdh8u1D/H4VvXdulcAqXYYPtLpeUO7BSs3z5FNlUW8WAOCUQ7e/QfZ//JrHhNl/QjPG52qiDJhG8Fq9eh3e/+sx/vIqfU6T5zh9idOXfbqNsmLAqR6QH8anjSkdvSdy1X8XyQb+Hwf76yQ4DdpT6tyLV7IK04dV/P4+fr/avdcmmGR/tGZ3+H/r573+FWfObsV30GbDLDYPjDM7BI2M3g4etQAgc6wBkP1fOpWtCxSKhJLemRdjvpr0OTm0JzZRutur3OQAlaY6l/rPnsMcRw2InEYPiIgYpU6p/zTQ6tR0uFup+9A8hOl9kDwE6YNO7lXyZs6fr2IzouY8cvNaQz7up4naEACAMTk+CaBzfHHhTdkKjiz9D+84fmIBRqbXsv6g5YMWCSUW82zS5zR9TtLnOHnZp5v9cYGgbCpAqlKjs+y/OeRXcyP+LzrvInIasZdorbQ6DN0LtNyv1H2Y3ofpofOu0weVvJ7hoXm69m/T4f89R84PsAsJyhjPuKWssQsAMq8awAxP2OhDi3pU6lFp0aGsZCfpszHPiXlO0ufYvOzNJpIkFSms+X+V+j/doZXRKl2F6n4lD6F6COQhSO51+qDTB5WN9Pf0Q9h/+ED5G2/yvI6NFwAAwH3+Lv5jeUBcp2drO24BsIvGL1AuFPVOBe+CQAKRtdmIeU7Nc5p+jc3Lof9+yPuf+++503ax/26M1kmgZR2q+1AeQnUfmldBeq+TB5U+qGxxf0+PxF6L91b9tH8IdKDC6cjwf3hsrCA9QQFA5lIDaPvXmfq8hNHdib5Tkk0OMHfm2ZhnI3ujjChjxEh2Q6XZeoFGjMjxR8aIMSZdaXkVqAeVPuj0Xoye+h1NpNHw/7bnFAfaCkOYwakVAJZsHtGp+VsY9P06slI/oRmOmMfpBRiOehD1oOWDDiSURMyzSb+mKhVlsi68yPF2Zf/9LpBXWj3o9EGl94s7/6vL5H7V8P/TA+xocGpr/lIdkgoXpsv+L+3DNkPDlwGmKQCI/zUAsv9oRx0mB+TvMzOpebdmf/h/5x55n3032q5v/Cpen1QBAM5qN/y/5S8PEU59WaiHwI2RMRUAaCoQ9VYFb8/L85vc1z5m33+v76qXzxTsaaJ96lb237tPFUoNGacnKwCIzzUAjiw4xcc2xE2n7P/t4f/urRsIAEBn3oem9nHZ4lseqHNxeNr2W8niP3CQ9ycZACPqMnrvugzQ4RlH12q73Mn+Y4ZOHxGr0XrKAoD4WQPosP9pYAEnvQ75gUbfzeIQ9e5cCgDwQtvh/92Cav9ZecPp/OSEZjiLqQDAhObSAb2gTl8vR+/Zmbs/KCXic/af4f9zZjVaT1wAkOMbcfAkUIrsP9zkSxui7fCB0lWAiuso9efL7gMALIN3canygn5twnSHPs4IPYhhpxQAk6IMAKBet8V7q84qA55tqltOg18QiAV7MTRLEwKmLwBkvJgKQNsIGNtwi/9YMvgmNDvvOLAnAAA2OVIDaBdfbESjJn2c0aJez1UGh3hawDpHzjbAoszsuPN4+L+I9LlK8NTZf4b/L0v+Y9T+7+5KAUDcrgF0PqA4EjEa99sQrYf/12f/7XJ/91Vz9swJAFiCyvjZLz5NHt0GmuA/+fsCrjEVABif+x3Q1tf+PU3iL/tl5Wb86xPcpx6rR/Z/0doXAxwqAIirNQCy/4A7KhcZWCp2AwDM1eSpgQ7D/0s3eMo30umFe8ZWQjM85V0ZgGMNmJyqzuy7cDJpcpbo8pipU/8HLuxiuOD6o1b22XCrACCOXRKAowl+mTxTUMPy8H9HzhFDc/bPCQCYu7bX/p2NQbP/C2m/wGsudyiAmXH5cGsdsGqH/zd5uvyucCpcdsj+j3GNImc/OnBB2UfQuQJAxoWpAD2PJg5GoK3SS/6Ol/13uf1VYfLzJABgUN6EJivB2oF3W7nVNjaMqA1feDcVAIBdzQcBqOv5f2UzAh09n6jb77RV9n+8axQ5ukPhNEcLADJpDaD/ocTBiKk40Hcu0fZYtr4+oJdd7lt/SC/fFADAB9aH/99un0zdgrESVQnNmJOpD0pgEWZyoFUvA3j6ab0hhv+3Tu7fVPEOR47+ZP/RjbsFAJliOSArxxEHI6blaRuiyfD/8sdMp922WP2ruLQbAAAD8iCs9x/+b45fJ3qrI2T/CdzwEVMBgAWyPPy/2dOZKRKAN11sTNmpcPytJfuPzpwuAGTGOQtwEAEDaXrwHh9XN/zfqeYAAACjGDkx3m74v7m6p/ppyx8wXHC3tUpv7+eh/QKvnT7AdJmBIXhQ6S9zzv7XDP+vnxlQ/nA7bg//bzXh/urBY0f2BqsVAfU8KABkhlsRyO6p1scTN+bHnTZE6zUEaq792/UU0OL33NlxbqwtCABAqbowVR2lboTZ0UPwaIsMAPPg1IQAjjtgCP37751PEU4d1A5l/9Xor4iZ8qYAIMNMBSD7DzilvCUBAMCyjVahbhd+2y/+U/JGprjw16hjDO29FuACdwbMALPh12F1vfhPk+V8x9Rz+H9N9r/kmUufqud7zz0nrQjY4lMBIGOrDGD99OrR+RpL4EIbosXwgdPivz2fy9rvuWt+7wgA4Ajr1/6tepUOqwPbYuuKfw2fjaiNWXJqKgAwDx713xv9wmmB347bMpm22f/rO9XVL1rYEqA3/woAmc5lAO/OPsD8XTcOLC3+M6gBrwBc/WAn9wQAYAwupAYu9Bjrd5E9HOtdWY+hZP+xcJQBgAU6D//P9+JdSvdbi8712f/cyjyFC6WYkge2QOMBA/G1AJBpVQYY9EzkwmkOKJg2U9B99UB7EY/YCQBAQ31b1O2D7rDZw+Pz9moM0MQHbpmkDEAjH7PkRf+9wLXFfxrpP96uLPsvFX9Bx3cGlsPvAkAmf4CZivuBZXJutOCl/OI/FhMK/X5vagz/BwBUcCisD1Ctt/vWhg6aDP8H8pgNAHit+SCAwvD/wk8n07jq33TV4Zrh/xXZ//wjORnCQXMoAORNcphxbAMFTTu9NwcLuNp7HnD9HyuvCACYqYFqAO2G/w8z1q9/JWC0WEn2HyhF5gvoyaFKf5nCtX9FbjQJXJwb1GSbbi393+QKhi7/HbFMcysAjI+jGo4bP/Z0WPyn4RY2fy9jdLw5+AEASzJa3CsN4lVTfstZ2daKJyG5D9QbugzAMQjY1Xnx3pLD3O3js+nw/5r7y676W/WLJAzgFAoAAJzRZ/h/rqvhdqujFrkGAMAt1ruU7aJM9Vi/Qfu6joTCJpvhyKYC0yL5BXTj07Ez3WD/a0Ms/lN64d+GLyfDV0OBVigA9MKRDC+M2YZoMXzg5ur/Fc9ffPzlZXdMn7fq5CFNHgEAMLlBF/9xUVmTgOw/0ArJL6AbN/vvhdX/HRr+33xn9d6tNxf/Kf0VToNwAQWA7jiGAZsaD/+viqDurPx7YcTmCABgISx2JklYN8SOArqxWwbgSATGV378unQ0jjP8v/M7phoKF1AA6IhDF34Zp+zcevh/vxfKnmeahof1vcniPwCA0blw7V/7+sfoq2dovuAvgFLkv4BWnOq/S27gf/lPe29Jd83z8je3sln2v08ZgHMgJkQBoAsOWqCzi8OnKoo2C6pmin72UMP/+78WAGBJpu9GlkWp6bdqAGT/AVt6lgE4ygCLWl/7t74MMNHx2XN1vkZbbSP7n//1+bWU4AUKAK1xrMJTQ/fJW8RCly4W5AT2AgCgvZ6Rve/w/7m63Kdk/wHruqXAOMqwNO7U1K/z/vUTAkbTYituXtqn5QV++3DnL4tFoQAALMjkkaZ8+L8jpto1LP4DAPCOs1HKajR39l0CM9CqY8LBCNjVYfi/i8dhg5NI260eYvGf0lehBoCRUQBoh0MUKNVt+H/5PQ6PtTu8ZsMTQY/zhYONKwCAgzp3IHsN/6/95dn0aZvHYqI20E3DqQAcYlgsF0LqabB/3ZZUHKWDbn+LxX/aDP+Xy/uHyP7nn3Dyvy+WgwJACxyZmIEhYvA0wwdU2ZN4d5TebIsAADAR74Kqxcv/kv0HRpM/iFTF/cBiudJ/d0m7YXk1z3D1PIX7h8v+51/R2f2MmaEA0BTHJGCBzw35cS7/6/MeAgBMoEPXsV2saT9Rz/feLLEYmApHH+AUVZoCb3OgWm8StK7QN375qc4/TAXAOCgANMKhiDmxG4P7DB9QzX+/82tY/12L+44zCwDAhiGW0u4Zo7ysAajWrRKSlQCAobnQf3ckpp83/tYG1bzNyRf/KeVlwwleoQBwGwchYIf1EJpfBci7A5XFfwAA7usRmcbuyvZ+MbL/AADIMaKWD/9vz1Z7oHn2/+zqkY0W/6l6/MCYCoBBUQAAlsh+DK7VYqR/2wBr5W00eJIR1v8hjwAA6KxhZO87/L9lrPJoOJvxZUMBAMszcv+9/qEdtqTn9g9doS8+vv2MQIs8ajvBLxQAbuDAw1yNFleKr9J19UAn2NplnFkAALbZj+w2wvRI7Y1+r9HhjXrXhAEALFyrQQDnuNrgUkBNYn239kDJqzVf/KfZ8P+a7P+EVwUgYQDrKADU4ZADanQZPpD7HY+OL8vD/1n8BwAwhZGH/+d/z+Wgb6T19hG1AQAjGzWYXsY5W6/baomb8lBrK/tfxYHsf/7VXW4+wTsUACpxpGH2+rQhxrmE4BgmufzvJfIIAAAr+mcHKkf89eNsDYCx/wAAX8yj/16f2u4TZFv8btXwfxubYZGzzSf4iAIAgIE5HlRvGWH4PwAAtlT1FduFs+tH947aA45l49I7AADYYoYaDXD5Iu3Vhvuqq/vW/NSppf+rMBUAtlAAKMfRhYWwthJfmbrV/x3RfArhYBzcKwCAZRpn3J8Lzez6NEHTXwQAYFyj9t+v76x+osHje/Psf8ufnp7fkcV/SjnSfILXKACU4LjCogwbSyqCpxNHWauNaPLg9sP/HWxbAAB8dx3ZJx/+f/1k1loCIy7fT9QGAMzVIZyOMvy/tVbZ//qGgSeL/1xjKgB6ogAAoB07w/9Ln8WxYGt5c4jVAICx5GsA7QL3wNn/wrOOGRu7XU4QAACntB3A51gnu71+2X9/F/8pxVQAdEYBoIhjCQtkPYpUJhHcMf7w/ysu7x4AgO+8GCnWdyOb/aaVgEvUBgA4Yrj+e93T3gqEg+Sme6z80+QB2Uu4vPjPNS8aeHAQBYALHEJYrIbRukVENBW3XTjQGmzBeZNZ/AcA4K0+y/62+P1+Ti9it4Vwe9sbvx5RGwDgo+7xq9VvqsPjLdcA2mb/Ve0DKob/+5X9P2EqANqiAABjOiPpAAAgAElEQVTAsttxyPnQ6vwGAgAwgKnjX7tKQPWDGo74a2jqvQIAQFGT/G/bNQCvn3DKFHPbKN9t6X+fk+hMBUArFADOOGywcDfbEMsZ/t/uwQz/BwD4bOTV/5sovLKtHAcAALMx4BjwHmHVwlZ1KPA363HXLP3vb0OCqQBoiALAAQcMUM/OtX+bP9FAWi3+Mwx/2xYAgPmZdvGfhuxvC8P/AQCz1n34f6PcedkTHR/TKyVd8Zt1r38z+1/6nLPI/meYCoAmKACIcJwAR3aqx87Gz7bZ/2GG/wMA4BxnY3eVUcKrd3sFALAog/bfq565+epD7batQ+q/yePntfR/DaYCoB4FAI4Q4EJp2GgxfKB68R8RURNGVzey/7NpXgAAZsDBxX/GQOsfADAXffvvlzd6NQBU8ddbpKTLHtfhij5VvzKnpf9rMBUANZZeAODAAK7NcB7AJIc62X8AAIbQJ6yz+A8AYF669d8rBwGUvkDzJ72qAVy8XOV2tH6pRr+ryu6f0eI/pSgDoNSiCwAcD0CVfBui4/D/6wdMFV2bHer2h//XPD8AAFNb6PD/ZtgHAACPdOi/n9gZ/l94uqunKtyhKlbmafcq1c9fY97Z/xPKAChYbgGAwwCo12ocgbvX/h0o+2/pdQEAQDujDP8HAMAvpmT8faWbw//rA+btXMGtgNsrPdAw+18x/H9RuDAAThZaAOAAAJqwmLSf5qDrkP238sws/gMAcBvD/2uwDwAAPuoVv5ounz+1hksGLXLxn1JMBUBmiQUAPveAXcUkQudY2nzEQsNna2CEEQHLaVsAADCs4Yf/E7UBAPOW7797lh9rk/2/tsDs/8loZYDiWk/DvyIaWlYBgE8eMI2RA2zn6/s1/MWWw/8BAHDKEof/k/0HAKBK+/g3wdoyuder296qqwvQT29yTeZ+TzvaK6KDBRUA+LQBQ7i5eqC0Ovr6TwIYOvvf8tVJJQAAnOJx9p/WPAAA/fgaSxtm/6sfs+Th/9esTAhotSe5GsHkllIA4HMGjGTChEKb47z7Ro3zKgAAII/FfwAA6OccD22s/zNeSrdqUH/FI0se1vwZliS/N5r8KXvuPa5GMK35FwD4bAHDaTL8v8uTtn3Clsd5TYOg1ws1XJEQAICJVMaxGQctsv8AAOSZ2m8b/uj488Fzbjay/6bhMyzYaDuHqQBTmXkBgE8V4KXs0L0Zgjod4d2z/wAAzIN3PWAiNQAA/QwUSwfM57Yatn8r+w93MBVgEnMuAPBhAgZVMvy/LLR2PxIHOIZ7Zf8Z/g8AmKUZBy2G/wMAkHeMeRZ724PUAKoS+i0fzNL/zmIqwMjmWQDgMwQMrelR5lKYHXDsP9l/AIDzfG0hD7zdRG0AwOwNvQag5WRuq2H7Ndl/lv53G1MBxjTDAgAfHWA8t4b/u6Nv9r/mwZx0AADOK1z3zxvDX/sXAIClGLINYCuZa9r308vfFs0ATzAVYBxzKwDwoQFGMMi1f4c0YPa/yWsBAICRsfgPAAAiUhYSh0uddS4DmA6/Vpv9Z/EfjzAVYATzKQDwQQFGZWq/dcmwV/1l8R8AgPOWOPy/Ab92BgAAvVyHvZuBUHUMlqdfqonkxSdun/2v3DSy/35iKsCgZlIA4CMCjKbF4TZ1sLUzE5DFfwAAMzZ1sK408OI/zr5vAAAsup6+3yTA2krFtrqKbwu1FwkgxPuLqQDD8b4AwMcCmJ6TAXbw7H/DVwQAYFK+Dv8HAAD91HVnHWkVdEjq1Wf/c0/oyFtEW5QBhuB3AYBPAzCyDqv/jz+Ny9olgOp/hcV/AAAYCMP/AQCYt26xnuz/YrAikF2+FgD4EADjK8/+OxZXR8r+N3lRAADc4VGsYul/AAB6K50C2DzGHtKvXS8DUKdzoK/N/gvZ/9lhKoBFXhYA+NsDuFZzCaDWbv4KpyEAgA/8i1c9t9i/NwwAwOhaJcgt1gB6hGlza4I+Sf+5ogxghX8FAP7kwCT6DP8feuqWzdR/Eyz+AwDwgX+r/w+f/fdlTwAA0IeVNoDNjny/J7r5JkzFbcwGKwL15FMBgL80gIKGEwDbYfEfAMCc+BKrhm/r+7InAABwSxaju8XR3vHd3HoSlv5fCKYC9OFNAYA/MDChDtf+LbBbrb2xIYNeOZCTEQAADroVoMkIAAAWomr4f4d+/UVHvnkZwF6vuVX2H0vAVIBu/CgA8KcFXNSyJ23lNH37NUfM/pNKAAC4zpdYxdL/AADYZakNUOzIjxVzTYOXM/TQF4mpAB14UADgLwpMq//w/5Nup+mmrzx07oC2BQDAHz41oVn6HwAAS3xqAFRokvoXsv+Lx1SAVpwuAPCHBNzVbzmgPFVxfwsMGwQA4Jr7XeFRQrD7uwEAACtuX/u3U1AcM9PaNPs/+IbAA0wFaM7dAgB/P8AFFof/V+n13P3PFA2fgcEFAADYZaW5T58BAIDhjVADOPeym7wSPXQcMRWgCUcLAPzlABdUZv8bRlc1ZBweM2tA2wIA4B3Hw9VYcdzx3QAAgC31w/9tXZDPer6uuLFNgjs9dFxiKsBNLhYA+IMB82G9BmDxBEH2HwAwV46HK7L/AAAMZODgZ3G0dcmWtg/uxHqcMBWghnMFAP5UQH+F46hbROw7/N8u66eGTtl/AAA80CpSDzpdr/TlAABATmlsbBucGwVYSxG//2jr8g1pmP2nLYFqTAWo4lYBgL8Q0FPpQWThKrt99MksDHRS6Jr9Z3ABAMB15vy/c03rcafxEbUBAI6riWYdu9EjBr/TSzUP73Vb1yn7T6xHKRebwVNzpQDAHwbo7+ZxlD2gSYy0PPy/+QvLwKeDHk9O2wIA4DpT/K5R3BthEoDd4E72HwDgv4b9d2kQ1KZNqfWNuc22nuw/WqEGUOBEAYA/CTCmVtl4+y88oVYbMPnWAgDQSllon74GMMUifmQEAABzUt+Fr7/2r+u6Zv+Bm1gOKG/6AgB/CWASNaMJKof/e61f9n9mOwMAsByT1QAmauUTsgEAszTZSL7h9Mj+z2o/YEhMBchMXADgbwBY1O28VmhG1D2DvzGW7D8AYMFGrQFMupQfIRsAMG+V/febIdC1GNk8+9/sTqAKUwFk2gLAwnc94JTi8TibiNr2RMOJCQAwRy1qAKdfaGWEAEqMBgDMSJ+Byd6HxFbZf+/fLZyw8KkA0xQAlrzHgUFZKGzOJvUvdrL/c9ofAIAla9ftKTzU1P50BCz9DwCYHWsZyQYh0KHsZ7/sP+EenTl0FIxuggLAYvc1MJruJ7WaWOpXmO28FtIlv940AAD1ug8UcL4FT8gGAPjIQkbSrxBI9h+TWuxyQGMXABa4i4FJtGtGzCyKkv0HAKCaf6OfWPofADBfgwzgc1DjN0n2H0PzrzHc23gFgKXtWWByjc5oMwuh9k40M9sxAADk+TT6yY+tBACguy7pyPZd1imTnr2z/4BdPjWGbRipALCcHQo4ZVlVzeVeQQkAgC7m0fOhZg8AmIHBJ/Gr6UJm2+x/yx8BnS0naTZ4AWAh+xFwVl3ffjYhtM+JhsV/AADLdop6LrbbWfwHALAYTQvzPYLf2OnObql/Fv/BiOYxIOamAQsAs993gEfKw/yEQwBs6XmiIfsPAMCRc/0fsv8AgOWZyZDkNu+hPvsPjMC5ZrBtgxQAZry/AH/1bUa4Vi3of6LhVAUAwJV8tJ8yVJL9BwAs1VA1ACVihi8wtHz2m9l/Ij5GM5PyWxnLBYC57iZgHmZyLrPyHiqehLYFAAAnk60ORPYfALBsQ41HHrQG0P5Jyf7DNXOdCmCtADC/XQPMUvFc1mpc/+STAGydaMj+AwDmZ8gwPeq0APoVALAwSomhM1ZmkFzkEDWATs/Fyj9w1kyGz+ZYKADMbI8AS+Dfuczi5nr2zgEAcMuwxYAGz0iOCABmxpjpB5u5zH7/3WINoOtTNPlz85HAhGY2FaBXAWA2ewFAUyO3y6yfZaqfkLYFAABt2SwGNPt94jUAzBKn9xas9MqPNQDpFsH7Rf3i5rP4D1w1mzJAlwLADN42gO5GqAEMdJYh+w8AwGC6XzCA3gUAAOM7xt9sBkarX+mspOtN9h/Om0EZoEUBwOv3CaBSh4T+QDWAQc8ynMIAALPnxgIKLaYFtIzODrw5AABmR42U3ySOw2v+Laadc7sA4O97AzCg7NRgJYAPfZa59fy0QgAAGEghyKqSWx2fCgCAhRgtLzdcGaAyiDP8H17xdypASQHAx7cBoBVrh/npiTrMIRgH2X8AwKK4MQmgirmMyw2bAw6/IQAARjdwXLSb4qzbWLL/8JOPUwFC77YYgIvcPJW4uVUAAAzKwRpARUR2bTMBAHDNVJ3a7pf2ufz1SvTW4TPvpgJ0uQgwgFmZa+e7wZl4rm8dALB0LtQAPOoSAQCACq0qAU1bH4wMwCx4VAagAAAsjhfnpr7I/gMAFs7i1XpavSIAALDEqdBqrU1B9h/z4sWKQBQAAMxLs/MubQsAwCJ0vlpPk+cEAACDKY+3vndlaUVgjtyfCkABAMCMuHy6BQBgQoRIAAAwrerWiO91DUDcngpAAQBYFmdPRn21eWO0LQAAAAAAGA/ZfyyAs1MBKAAA8J+DJ1cAAAAAADqZWx+X7D+WxMGpABQAAPjMtXMqAAAAAABD8DRZTrcdy+NaDYACALB4ysNmRL/zqI/vGAAAAACwBE7lDfuqfTN0zDFjTi0HRAEAgFcsnTupAQAAAAAAMCCy/1g8R6YCUAAAlsWRU09rA2w0NQAAAAAAAOzzMu8ADMKFqQAUAAA4nAsf+ATp7PsGAAAAACzQHDLnDd4DPXEszbTjcSkAAItTXnt0Khc+4knRqfcNAAAAAFgssv/AjE1YA6AAACxUyXln2lz4dC0dagAAAAAAAPQ1hwoGMKCplgOiAAAs1ynrfT71jJwLd6ZxQA0AAAAAAIDumnXw6XoD408FoAAA4LISoC7vss6ZpH8BNQAAAAAAwFRc7Ss3Q/YfaGPkGgAFAABn52CsTl8KP2jJ7yYMAAAAAACoRfYfaG/M5YAoAACoVFkPmCkmAQAAAAAAxudrd9vX7QZcMc5UAAoAAJoqJMcdD/T5rW2+qdQAAACYp9LWAFEfAIDO2iQFCLlAlRFqABQAAHR0Hb/dKQmYim/d2UIAADCeqhbA0Jc+AgCgAS87qmT/AXuGrgFQAABgjSMlgZq2RZMV1pgEAADAslAGAACgFS9LFoDTBr0kAAUAAAPqtg6PrVeseQzNFQAASvQJkL4n0CkDAACm4FnntP3mElqB5gZKWFEAADCSEYoBzRsWY15sHQAAF1mPgvkndK2v37wvRRkAAIAqZP+B4Q1RA6AAAGACQxQDOjQsrjeD1gkAYLbGrHufXsudyNqqL0WzAACAAgbQAWOxXgOgAABgYlau0Nu/h37jGQobR0YAAOA+FzrqTmXS2/alnNp4AMAcuRCrb+u6lYRQoDO7NQAKAABc0W1awOBNitJNcXBgIwBg4VxOIbiTSe/Ql3Jn4wEAC6emiEdk/4GJWKwBUAAA4KJCW6HqlDdN9r/wANo1AIAxuZzo90K3vhQRHwCwQGT/gUnZqgFQAADggWlaD1wtEAAwmiWk9X3PoRPxAQBWuR78yf4DDrBSA6AAAAA2kBQAAHTgQNffysV4PNOnI+V7GQMA4LVxwtCC2gSAB/rXACgAAIA9lAEAAM2N27u+GZ1KH2BzG/vHR4vX4OlZA7CyDQAAOKhf7Cc8AkPoWQOgAABgeH3OUhM2HzqfXxkbCAC4aZTsv5U8eWbKsYDXr22lEtCzI0W4BwD00D0EDReAegd7AiMwnD5NVwoAAGwYLitgcaxfB9QAAADWDZ9KHyIEZc85dhng5uv1DLg93xVTAeCJms84n18AB2T/Aed1zlFRAADQ3iSDAKfqY9eeX9XlA0t+RiMIADCWEWJO95FHHTau4Sv1D7hMBcBMNflc02IFvGQ39LDiP+CPbu1WCgAAGnCnQTBJH7vi/KoqvjWFe+lRAQBOhgmpY4aaLr2O4bL/+cdTAwCORj6AAEzASvnOXrOEcwjgMgoAAKq5k/efXFleoCpXUGyJMbAKAJCxHVitxZaW64P0zJbf1nn9PemxU/rXAPq8OmBJnxWt+PwC/ul86FoN5Jw9gDF1aLRSAABwhbx/qZan2JIyAM0iAFgyp7L/zTemYrW7FlFx5PDXJ+D2v9AB4R6T6nma4fMLjMZmo6BVBXqAzj7nDWB8bWsAFAAA5JD6r3d1ir15zr1YF4ixgQAAGzpGkv5RvkMgG2Hxn9JnmHY5ICHcY2y2WvHUAABfTdSX54wBeIECAACv8v6Tty+6jg08ZwPoVwHAAlkKtV0CiPUoP2ggcySL2awGUPoQY2UDgDY8assDyAy+mB6AuWt1GqEAACzb0hodAwzKa3XOPbw+SQEAWJRJsv+DhnglYhpEwGmD3cCXBKj64XnyH1MBMIqlNecBuIMQB0yreT6KAgCwSJ52FDq3L1TFt5bWCG47fEOJiBLTZwMAAAvTNGKMFuJvFrMnWfyn9DknuiQAVX+MwLWDBsBycKIAPEIBAFgYT1P/nTVZod/G2MAOKQJFUgAA0ECjQDGD+D7cWxh4KsDNFyfcYyAzOO4BeIqwBjiiYUOVAgCwGL53EYYbSNgnL2BjKoCIGBpQADBX/eLvjfgwbXCvyWs7GNemnQpAuIdtvjftAfh7GQCiGeAdCgDAAnjarMgbYRmBnnmBrjWAw4srkgIAMEfDZf9dDu6OLP5T+irDXxm47vUJ97Bk6COGWSvAOHysAXByAFzT5ExCAQCYr9H60g2N2VLo9t6nGxsoIoqxgQCAnMqA4HiqwPFA1n85oOOTdC/599kAwPlzAIBW/KoBEL4AT1EAAOZohEFBnX+rQ5Oh7a/0efs2lgnu04ZjbCAAzEePeFQeCjzKELQy/vuadCpAtiIQ4R7dzPU0ACyZLzUAAhfgLwoAwLy4mfq/fobmbYdJWhm9lwOiBgAAS2c3++9kYqBkozyKX5T84SEnzwQALHC/BkDIAlx28xxCAQCYC/dT/4Vna9KCmHAR4d7LARnVLykgNLIAYInGy/6XPm2r0GNqv+2zGaMh3MMfjicHAfTkfg0AgL8oAACeG6GN4FEzxHqVQnrlBYw6P02XF2dsIAB4qms8Kp71hwjBuavWl7xI49jnUevgBhvhvnsNQAj3aGQ+RxyAas7WAAhTgO8oAAB+Gq1dMNwL3Rxz50grg7GBAIBRDJ79P166tup1Vf6RN0PPDIb/5/UL90ZE9bkqQPanIdyjgjsHCoCh5S427wqiEzADFAAA34zZFpiw3THh4j+lz9zvUoGMDQSABel0xh8w+1+R9y/dhnY1gPxv+qNyxH+/cG+MSI9wL1wVABWcygMCGIcjZQCCEuCR+ilEFAAArywk+++gSdcHEKYCAIAv+kdPq6n/geLGxTZ2fo2JWhpZ76g8298/3DMVAFbRHgeWbNoyALEImBMKAIA/lpP9d2r4f+FVJkwKCFMBAGCezqd2e5ey7xYumq4+PItgNNxUgD7hXpgKgCOy/wCk6po9o7wigNmgAAB4gux/jfF3Ttc2EUkBAJiznov/WAlng436z71Cjm/D/68NMRWA5YDQnzOHCABXjFAJIPIAc0UBAPDBcrL/vugxNjDrz7M+AABA7Gb/h0/9H5irG74pTHSojOpcGRhLxecOcFn+CLXVfeeoB+aNAgDgPCez/0ONPvCo3cFUAABAXq/x3n1ferSAYCfuuzfaoG45oPIfNEK4RzfuHSIAHFUIEW3PHkQYYCEoAABuc6n5X9o4sFkJ6Nb6mHYXMRUAANCVhfO3svQ8zV/N/+H/mdKrHQwxFYBwj7Zcav4D8AyxAlis+vYDBQDAYSM3/ytermEbwtQ9R4Pn9bepwqUCAQDtz+QWFv8ZceD/gZXsv9vZTa4MjGm5fXwAAAAvUQAAICJ9s/+TcaST5MD6AMLYQADwUbfz/7gD/3OvuRRDXRmYqQCotaijDAAAjIYCAOAqB5b+79DBLJ1Q3+jX/OHm+gDC2EAAmErn4f9ds/9jnOwvX8Pa4j8uJTjrGy1MBcBi8fkCAGBmKAAATpq6e9yn3d+oBmAqbrcy3V7iUoEAgIMxs//DD/yv3KjZZf8bGmgqAOEe1zw8PgAAgB8oAACLd9XbaNGjLPzuMvqipwqHm1MBWB8A9fIfLj4mgE+mWPYn98rS9+WdzG42GbVAuMcInDw+AACAH242JCgAAO6ZdPGfph3J0o3s0BP2cPh/HlMB4JHrz1S/dSwAjDj8f5Tsf2Wif/FnCsI9BuVGqxYAAMwWBQDAMZP2AHpl/zu8jLdd1sKAQcYGwnH1i1zzGQHG5mT2v/bFpe8WzCLBSbjHEFw7OPgoAQAwPxQAgAW77HA0au7376NY6VW41lXiUoFwWPfVLQDU6zb839Xs/43h//PN/je6dtER4R4AAABOadKKpAAAuGS6HrLFPmPTNzHHbqqzlwoUxgYuldtpN2BZupyGxx/7f/linENKDRTuhakAy8MhBgAARkABAFiqXIdjpJV/8q/k/0DCqgGDbq4PIIwNXCQ3jhVgprodYK1+a8Tsf1VEm/3w/86YCoD+HDw4+OwAADBLFAAAZ0zUCSD7bx2XCoQLfDhWAG+NsPjPJNn/hS3+c9JqFaA8Z2f+Ee4BzAaLVQJAjYbNRgoAwCIdzxCW21Km+tQzx1Zbfb7AzakArA+wEJ7k3ABUmOSqv9YX/1nGmYhwj24cPD74vMBNfDIBoD8KAIAbpugEtGhLWdy8BQwkPGF9AEzCtwMF8M3Qw/+7Zf+rnv/WEw24+M9iMPMPbRGpAQBAf81bFBQAgOVRw3Tnmwz/X1L2/4RLBWJM3h4oALqqP+wblpyXuvjPSedVgE6YCoCG3Dw4+IwAADBjFAAAB4zbD7A2qHCo1/NJw3wBUwEwDjdzCsCsODX8v+HTVsea8uH/y8v+20K4BwAAwDhaNQ8pAADLYgbqk9cM/889prtZpBKcvVSgMDZwFmZxlABzNG32v4ncS3Im6Y+Zf6jh5iHG5wIAAL+0bVFQAACmNlY/oGPL3tbw/8Vn/zNurg8gjA3034yOEsBh3Yb/W2fpgD88TSH7v+Dh//1XATphKgBKeXtwAAAAv1EAACY1cvZ/5OH/y8j+t80XcKlAWOf8UQIs1CCL/9jN/hcsOPs/BGdn/hHugYJ+tTkAAEbVoTFIAQCYOdqybnJzKgDrAywW/V7gtkFT202y/0NsAIv/DIxwjxNnDzE+CMJOAADMHQUAYDpD9gPGa8Uue/h/H6wPACtmfZQAzuh2pLl6fF4v/nP+dtlR2+IqQCfM/IM4fHDwEQAAwC/dGhUUAIBZqWvEj9nzWFj2v0++gEsFog9/jhJgcVqcQaca/p9/erL/A2MqwJJxcAAAACs6NyooAAATsdcVmLLXVjX8H20wFQDd2D36WAUIqDT58P9bT2U6vKDdxX9oDTRAuAcAAMAkKAAA/unSRxt44eDy+xlI2IazlwoUxgY6aYhDhBoAYIvNQ6n2aDeXt5ucGdT1t/2H/8/IEKsA5THzb2lcbtLyBwcAwCN9GhUUAIApdD1qPWimLzX7byVf4Ob6AMLYwCXpl4MC5mjo4f9N1v8pU/orTYORubq9sKg9LaYCLAcHBwAAsKJno4ICAOAH57pjWVp56q2YHy4ViCaGPvSYCgC4ouJob3eEVq/2w9L/E3J25h/hfiH4OwMAsBwUAADXTdw6Lx1MyOI/A3NzKgDrAzhinEOEqQCASMfjbegDp+/zc2DfMvQqQCeE+3mjSQsAAKzo36igAACMrvGBa63n1fNUke8H11wzcPHZf7v5AtYHQKmRDxHKAFi0zsfb8Ov/WMHwfxcw82+uHD84+NsCAOALK40KCgCAi5xrlF9tUOUJiDyCVVwqEHlTHSKUAYBplB3zrY/Eisq96vh0108BC5gKAAAAgGu2WtwUAIBxNTh2vexnebnRfmAqADKTp9q4MACWpeshN+1hcnurrUzXW4DRVgE6IdzPyeQhGwAAII8CAOAQX7pXLP5TZbh8gbOXChTGBi4JUwGAaVkb/s/iP64i3M9Au51dtszmCCjqAwDgPostbgoAwIhqj92hWuG2u+gs/jMVN9cHEMYGjsKpQ4QyAOavzyE33QUAKsvzpfcQtV01xFQAwv1omu7j68eNXgkgmgMA4DK7LW4KAMD0PGp5l5yAPNp6/3GpwGVyM9XG4EHAI0rOR6zqeei6eUoawPirAOU5OxWAcN/XzT/BuPGVMgAAAA6y3gqlAABMzO8GN4v/XBkhX+DmVADWB1ggsgaYpx5nwgkPh5rh/xfZ/6ufwlmEe+/c2K8tpweNXAbgTwoAwIxRAADGUtbo96upzdL/7uBSgcvh/iFCGQBwTs0BaYjaPmHmn0esZf8nQjQHAMARQ7QaKAAAk/G7hW1l653vCznO2fUBhLGBlnh0iJA4wEz0POomOmgvXtZc/Si/+A/Z/zamXQXohKkA3uu2n6cYls9UAAAApjVQ45MCADCKqyPYu7Z1ZXLBu3cyvDHzBW4mBYSxgTa4kHVqi8QB/ObhUVe/yTaX/sekmPnnuLq92POi4lPUAIRoDgDAvFAAAMbmfXvaVvbfwzyLm1xeH0AYG9iVv8cHiQNgOIVkYMmJwrT5tu1rY2rM/HPTUNn/SVHRBwBgfMM1HCgAAMPLHcGetqTP74Dsv6uYCgCnkDiAfzwJTHWbeb34z+k2i/905cgqQCdDTAUg3PcxePZ/uoBKRR8AgDEN2uakAADgBqf6vb6YJF/g8lSAZSYFOpvHQUfiAEtj/9PeKpZcvzyL/1wLxQSiQqNCkb2SSEw89SZ14uxUAML92TxiORV9AABGMXTDgQIAMB7vW88M//eBm1MBWB+guZkdHyQO4AevD7yyY+yU9FfVj2nKk51j7sWERkIxoTGhSM5MADMAACAASURBVGhMaExgJExVaFRodJiqMFWBUWGqglRiLVGgosBEWiItOy2RlkhJJLJTZj/1+7mFcO+Cyl1l96iZOpRS0QcAwHcUAICReNpoLk8cLCCP4DUuFQinkDgAurs5CaA0+194wHyjttESvzHp28S8jdM3kawTE6QqSFWYKmWUEqWMUkaUiDJGGVFGlFFKRBlllEqVSvXxhlKpMvtQ9oFEgey1iYKsMGAilXwK0s3U7/YKM/+mNVL23xlTlyEAAJitEdoOFAAA3EJjv5PJVw12dn0AWdLYwLZmmjQQIXEAlzl+4FWFk5ojytbiP67umeSVRG/S9E2cvt2bNzvzEEmQqjBROlVKjDLq/E/UIelv9PFG9lWZLPuvJVVilKRaUqWNPhQDcuUBSXTydJ98XsefVsmnIH2Z+v1fYiqAW5rttPZ1PSdQ0QcAwLpxWtwUAIAxeNxQrl5TuAtXUwlz5WZSQBYzNrCt2R8fJA4wc22P4eaFYnP5/LVHkc3Ff1xiVrJ/NNHbJH6zN28i87iTMDFBImEiQarCWIeJ0qlcZP+NUkaJUbm8fzYJQCQVo7N/KlXK6MNUgPO3Sp8mByRBcL+TbwOJg/3TffzlLvm0ij/p9NmV0zYz/8ZXvmNu7a6Gu9PxcOn45gEA4JHRWpMUAICBqWHbx9dPbvH0UTJykOy/b5xeH6DHBszPco4PEgdwi0fHXoPD5iJwz2Lxn/jRRG/S/Zs4frNPH3fmfi9BImFqgkSCRMJEBbEKExXEJoi1TvIFAC0m/60cvk2zG6cCgBzy/lqbLON/uHEsAGiVBBKHkoSyD4O7SL4J5Ocg/noffbnbf1rFn3X61Ymd5e7MPzO3c/6g2f/84035N05wb4sAAEAlCgCAT5q0swuP6d5rs9uod6JrPLbJVwE6cXMqAF3HE0c+J2Pirw9Yl8/+K/+z/9t3ZvdjtPuwSV5lg/1TCRMJkmPqP5Yg0UGsglgFiQpireM0iNUx6X9K92f3iIg6vit1/KpFlBgtShmtD6sE5bP/2bdKklAlq0MNIM7+BcF9dPcukH8F8df7/Ze76PMq+qyTqSsBQ0wFsDDzT4maXQ2gqHb/dN55jsdKxzcPAADHjdlwpAAADKt/s7jnM5hjErrdmcWUPZ42vs/cnAqw3DWCwVQAuMBeo7vjJ9kcp0P1Vsz++2zz1mx/iHbfv8SPW7PemzDJpf5jCRIJYhXEOojTIFY61kEiOk5VokWds/xKRNThX5b/Pzl8e2rrGCUi2dr/IsqYY2FAsnqApCuVhJKsKisB3wSyD+Lnu/2Xu83Hu82fetwdVuTiVAAlai7hvmQ3DJP9P/26y7vN8c0DAMBZIw8boQAAuMtue7rpaPSqV/V8ICEyjk4FmO8awU0s/PggdwD0lz+NHLL/fkbtzVvz8n20+34TP27SdWxWe7OOZLWXIDFZ6l8fUv+i4zSItY5FlMny/iY4ZvxF5JDVPyq8pWMu/LCXTjursO8OhQEdbHVgJA1Vsjp8TbIawEqSUMWhxKGJg/B+d/82vH9/f//x9fPv6+jzlGd3Z8O978sBtcr+2/oEOB4oKecDAOA+CgDAgLo1he03oI+5/8JFBFu8tp95BPu0mFBUICowEikT3f4Nd1YBOnHzUoGLrQG49vGYBLkDTGMuh988LtizfWOePux337/EbzbpOk5We1nvzSqSVSSrnQSx6ET0IfVvlDkkotNT0v+U+j++AVVRALgoDJwnAVz8zOQrAdlqSkbJsRIQhCoNJVmp8Dgt4DgnwOzXQRiv73d33756+fjq5bdVvLG7n1pwc+af18sBTZL9Pz2b4zvN/S0EAMAd47e4KQAAQ+nQCB6w3dwkD73sZrtZi4RiQmNCkdCY0JjAZDd0aFSYqsPXVAVGaSNRoCJt9oHstERaIiWRkp2SSEnkQS7bwfUBZjAwEH1QBsBC9VsFqJD993Hxnyz1v/lwTv2nq72solP2X4U70XulEy3KqGywv86l+0sLAPl9Wjr8P1M5CUCdvz0VAI43VKqDndZbFQaSrFS6OqwIlKxMeGeCdRqugyB+eLV5+Pb188eHl9/CNLa4w9pxcSpAtk7TDCJ+xR4YqEvv/lAJagAAADQxyXgbCgCAKwZvMefmARRPN1evrWp/2oLb4ytNIPEbk75NzNs4fYwkTE2QqjDVQar04SqCSmWrCxijjCgRZZQ2kq0VbJRKlUr14SqDcSD7QKLARIFEWiJtsq8bHX9Wkkz9bq+QFJic28fHNCgDYIm6ThYrz/77E7W3b8zX7/bPH172b7bpap+u98lqn4aRrCNZRRLuJIxUuFPKKFEqW+HHXGT8zSFulBUAKt/VZQGg/EZ+FSAjl/+UiEia3dbBTgdbFQQSrCRZm2BvgjsT7tPgLg3jt2Hy+Gbz9M3j14/3Lx8nuzCAmzP/vJsKUHyv42b/fUEQBwDATRQAgEG0aviO10rOrwHU8FX9ySM0F78y+zdp+jZJ3+7N4848RBKkKkyUNkoZUSa7CKCq+CfKKBFldC77f6wBZLfPtQGljDbbMPlyH39eJ5+C+JN2qhLA+gATcvX4cAIZBAxuiCOwx6JvylyuTNPw5S6f4frOlhvR43db2r02T9/vv363id5sDqn/cJ+eRv2HOwkjCXdKGW20Ooz3P/5T1xl/dawNiHQuAOTXAjImN2rCiBhRpwJAdqcWMWJMvhKg9M7oSHRkgvv0UAmI03D9TRg/vr17+ub1lz/udv9Mdu53cOafR1X/Jtn/Ef60vgyxJ4gDAFBlqrYgBQDAPkez/7mXrM9RzDIvma5k/ybdv0niN7F5E5nHnYSJhIkJEhUmKvuq0kN+/zj2P7uhD3n/NOv/qyzLYpQYLUZLqrOVgSV7bKpUqvWxEqCNkvsgfNzc/RCm29X+6T75vIo/hfEnLdMtCFDg6FSAbDd3fQLHzfIos44MAobi5hHYqn5gbL+JEffJ0/v0079fnt9/TVf7478oXUVpGEkYyWqngp3SRos+LvVzWQDITQJQh03PFQNOZYDzO8ot53Nxj8l9d0hCHwsxxwikjMkmAZj8JADJLQqkjpUAo8RovVOrvdF7o+/SYG/COxPssxWBVuvo8d3905+vv3xcRc/TfARdDPfiYdV/ouz/6YV82VcebSoAAOOYsBdCAQCwzPXsfyueDCSsET+a6E26fxPHb6L0MTL3ewkSCVMJEhMkEsZZ6l8HsQpipbOh+8ZIPu9vRKWHvL/KDQPMsv9GHx6Y6myU4iH1b7ROVfb1sDRwHAb3wepxIx+CdLfeP93tP6/3n8Lkszb7qXeTm1MB1CHD5fphgoHxGcBCqCyXevOcWZb6tzD8fyx//RR/+vfX7ZuX5G6Xrg95/3QVmWzIfxgplYpoleZT/5cFgNLh/xffiki2N0r35tUMgOJlAHK3L/L+hX8ikh5PUdm/VEQpSYNgIyoyam/UPtV3abBPgzgN10EY3z/sHt+++vLnq39+DU1qY4e25GK4F9drAKrym9I7cEYhHwAAR1AAAAZW3Zl3szV83ljPs//b9+n2xyj6dpu82kmYmCCVMJEgkSAxYSJBrIJEBbEKYh3EafatSrK1e/Qp3a+MOgz5zxIJhzemDgkJo7O1gEyW/Vf6UA84lAG00SrVkoQqWZ3KABKHwX20er2RD4HZrfZf7qMv6+hzuP+sTTTpLnNwbKA6p7rcPF66ceD48Mz8PgOYksNHoDqeM8s30pT/xJfFf/Zr+fNfu8//+rJ/2MV3u+Rul663abgzYWSy5f5Vqo0+rPWvcql/U1UDkKvbhTejLu4oKQmYim+vLwmQ+3daDsioyx+p01claRB+FRUZHaX6zuisErBOg3UQxA+Pm/XdN3/+5y7e9d2r3TgX7sXd5YAczP57Vxf3boMBABjCtL0QCgCATc1bt262g+2cj6bOrbx8m25/iHbfv8SvdrLemyA5p/6DWI55/3P2X8dKx1plA/dEHfP854UFzGVO4SI7Y0SylYLi7KFaRBnRYg5lABNIslJpKMlKklDF+UpAIHdh+Gr7kFUCnu6jz+uX31b7iVYGkNzbKh8b2ONT231s4PF1Z9N7nPr48NhsPgNAjcPnvOyzXn728CT7//XR/P3z5un7L/t1dMz+b9L1xoRbk6X+RUmW+j8k/U8Z/9yNiysAn5LNVQWAy6hd+VbLBv6fb58LAEYkuyCwMYcrA5uLSwQfhv+LqOPMAKV1rNQ+mwpwqAHofRqsE33/3b/+CVbv/vrlfjvdckBSFe7Lf9CIycr2fcoA7tUADtzI/nuKCA4AWLjJmw0UAIAJeNAC9mATS7x8k778EO0+vCSvd8lqL+u9We8l3EuQSBBLkIg+pv51rINYdGxEiSh1zjsokdPbvxpFmFdcMaA4WlCJSFYY0HttRNKVSlayCiVZqawGkKwOZYA4lLtw9Wr78D68//bVy++vnn8P00lnA5T303onBaTb2MAZ1QAmj/q+YyoA+hrsILR4gip8zus22fqVAIbx9/vk75+fX7553q938XqX3G3T9TZdb8xqI3p/SP2fh/kXUv/5sf9aRHLBujT1f1UAKP/25CLpr4r3ny4JcL4h5xvlw/9F1PHyAKmICfROrSKjIqMio+8SdR+LUZK+/94E4Zu//vPq+ZNuv1PtGGQqgIj0WwDQnYsAqZJbVXeMyscWkY/bDADAbFAAAKxp2Kh1tu177sk4P5Dw2ss35uuHKPr+JX69TVb7dL2X1d6sI1lFEkYSxKITpWMVxFrHokzWu1RpkEsr5BIHxdFrl+/KHJMzuUkAuRtZt9XkHm1EjFaxCvda5DAhIFyp5DAt4LQ0kNmvgzC+f7O5e//48vv9y+/BhB+XIZIC0nkqQK4GIA4fRBgHSQS4q3rdv7aaPE0xCg30Mj2kWj7+uP/np6fd42a/jvbrbbLepuutWW9MuBGVaKON0eqU61eXqX9TmARQCNZlBYBTDr8YbK5y+yW7LzdZwBxjupJz3t8UCgCnG8eB/0rJ5SSAbOaAklQHW6X2qYq1pPp4HaFvvzVhkP4Zvv7yZ9B5J/dUNxVgkpl/x5ee/FRfs/leFN4cNPnfFACASbjQcqAAANjhV/a/fjjhMM87lJd35uuHaPvhJX7cpqv9MfsfHf6FOwkj0XutE1FKiTKi1GFhAblMKOTTB4U3U3r7NEe9rABw8a05Dh40IkbrWOu9CjaSHmoAKgwlCSVep+HeBOs0WL8N49dv757ev375/W7715QDA6UqKVD+g0bMKZXf6gOT6zV62oF0IerPBqUgLN11DbqDgc9K23vz8cfdp58+79fR/i6KV1n2f5OuNibcKFHaBCL6YqS/KZ0EUBWs1THJrIyIyv9ILkfzl77bYhAvvW2OSzKV5f3PP1KiUhFljFHFqQDnSoCSNAyfU0mzywtllYB3b80qSILVm39+nbJfVh5Ye4f71rH+coMmPNWrym9cieZet4V83HIAALpxpOVAAQAYj8uNXUdOSa1s3pmnD9HLd6fUf5yuIrOKTC77r4KdUqJFGRMoU5VEuMop1K0bkCsA3LhRtUrA4d9hTkCwya4QYII7HdyZYG/COA3XYRiv1tHj24enb189/76Onib7Ew01FeCYsOm2KZ72e2EXeQS042OoK+XDyj9f3qUff9g8ffeULfuzX++S1SZdb81qY8KtNlpES/b1/O9y+H9J6j8fuKUidp/2zfVOKt1ttwoAh6/nG+rqHpHTBYHzw/+Pqf/zckCiJA2DFy1Z9v88FWD1cxKG7/78z8qkLXayXUOE+17LAR1P8RNHfCez/76jFQcAwMgoAAAWNGnCutzMPXdmHB5ImLd5a54+RM/fvewft+kqTlb7dBWlq73JhvxnX4OdViJGn/P+6lYBID/w/9xbPY0lLLzP0zj2sgKAOT2gsgAgYpQxIkapRIWx0pEJIpPVAIK7NFibcB2Eyd397vHdw9Mfr54+rpKt/Z3ZRN1UgH41AGk7PPCyBlC+VU4iZTAc8ghYFuNB1P7z+/iPH55f3j5ny/7Eq228Oi36H+s0ENGijnl/oytW/7+O3XJYFOh8+V8pKQOc4nXxPaqrPVZITpvc//nFf06nmVze3+TvMblHpiLKSKqy5YBMvhJw+BeqbRCmsTHaGC1GK6OV+fdPySr45uN/7+LpLgJUF+7Lf9CIlakAY57nq7bUtVDudfjzqxUHAEA37jQeKAAAfXnXcq08AbmaRyj49GPy6eev23cv6SpOV/skS/2HUbraSRipcCdhpJTRh7WDc0mEGzMA5PCYQ4+kdNT/5V5SxxxB8We5YYOmrgBwUQyQNAiejdobtTd6b4K7NNybIE6DdRDGD6+3j+9ef/nz4eljYBKrO7Sx8n5m7w7cYSqAdPwUedH7dSfqzxV5BDik7dymlk/ufvb/4w/xr//+tLvf7tdRvN7G620cbg/L/iijTsv+HGJ07t8paueT/od1fi5DtikO+b9c/+dUub85CcBc7kZz/OsZI0qZQupfSgb+i8jhkr/Z8H8R0YcPgTlWAo7D//MCFenQJCZNDlMBUiXmp+//WQXvfv/tfvs8ZdwYcCqAdP/sjXaeVxXfEMqH4EUrDgCAGaAAAIzBbtO28Gw9OyQe9WeSQP76Ofr881P0apvc77JR/2kYpeHOhJGEO5Wl/k1+OKFqsIzA5bcX+7d09xw7oedHli4gcJ0mqPl3GB6oVaxXkdGR0Xuj71K9T4O7NFybcB2G8eu3L2/ePX754/5pogsDDDcVQJ3/a7ARpv4Ot3h0iPnO8U8CZm/wT6AP2f+/vk1+//Fpc7fd322zsf/JapOEG1ltlGiVHnL9RrIL/+bH/p8uBXyzYH815L/8a+W7zUUbVX53Mekvl1MBDjfUOYhn9NVsgPOcgNM/dbyhZa+DNDBGr4w+zgb4/rtPK/32999fPX2ZuAYgQ0wFkPar/8nFoTXqeZ7s/ygo4QMA5sqp9gMFAKCXJq1V06Gr0+aFWg2ernyYq6mEk82D+fvf2y8/fdmvo+Rul9xt0/UuS/2b4JD6F3NKKORT/7oslVDIJsjl1+xm2cjBfNL/4ufm+OV6HsDxRulyQIdVgA5pAiVGjApUpFZ7oyKj7tJzJWCdBuswjB9eP4SrN//8NtkJvLz73T8pIC0/zdZeHPPBJwFOGGISgA/r/n96TH//8fn54SVa7farzX71kqw2SbiVIFJpoPLL/hyT/rnLAGQj/Y8RXFXl/fPD/w9XAJbiVyn+AU4jz831z07ry+TPH/nbRiRbKSj37WFA+/VkvnNkz5UB1GEGQOFPaERLGgRftUmDVRpIGojZS/r+W7MKkl/V46fP0xT7Tyqz7T3S8B2nAlzWAGSw87wqueX0oTebyvds3gj80+QI59MJoD3X2g8UAIDuGmX/h3+JPlT/lxnlrPb5m/Tvn1+e3z/t76JknWX/t+l6Y8KdCXZaGRGtjM7l+qtXEy7J+FcUAG4uHWBKB/7nb5uSfEHp8P/zGsHpMalhdLBVKpsKsE91VgmIE70OVBr8zzRYvf3rl5WZqD06VFLgVAbo9LlysPfoWtRfCMoAKOH10Whqv21lsP3w9d789uPLl8evu9Vuv9ruw20cbpLwWVSiT1n+4lV/c5WAkix/1T+piN2l2f9z3r/CqbR/nfpXkq0FdBHBlTkk+lWjVf5MKqJLz0rHjU5XwbM2qTYmMEabNDBGv5E00bvo9WYz8Qe38nTaL+J2mQpw+YpDRHzvsv8zQ+zGeNoe2FflWwDwDgUAwFFt2xVNulH2+zCj9Ir++CH+5+evmzcv8XoX3+2S9TZdb9L11qxeRCdalDGBOi/3X576Pw8qPG938/RB1T0tCwBXN4wYdcj4m6uvqYhSkurgRVRkVGT0ndH7RD1oMVrMj/8jCcJ3f/1nsqsF1iUFyn/QyMVUAGn9GXOqBkDWYFpOfRiwEOdPncVJAIVB605m/3cr+fWHzed3T7vVLgq3+3ATBy9J8CKSKBNc5f2VKtYAqqr1Van/JuG7bRxXcq4GqMsbuUrA4bdO0VzVFgDSw7pARmrfV7IKXgJjEpNqk2ojkqpv3jxFPwT/95eHZKIL/+SVn077h/veNYAer1/7KiU33TWzYDeztwPnWDmq80/C5xVAGQebEBQAgI6GG/4/WitCjfx67e3X8vuPu88/fYkedvv1Ll4fsv9mvTHhRomoNDikDFRF6r9wHeBTmiDr5k9SADCF9IESMaLyw/9PiwUbMUpJqiTR4VejolTFWlIlqVZGK/PDT2mwevfXL/e7l8niS2U/rUcHLj8Is5icafCcp30x7UfbwZC/QAwnhN+On13HzyeJlv982P793ZftarcLd/vVNg43SbAxEuv0esh/Yez/8as6xu5s7H/+OsDZDjCX314GdHO8dfqROZ8BSlb0yz/y8KiLe0wu5BwK87lrAqtzFV9VxMDD3fqiDFB4zYtXVoHe6iDVxqhUS6LNSn//7ZddFPz313X1vh/PUOG+7XJAV6d1W/niJs0+jIPYjaEMcVQ70vEA4BI3mxAUAIChdGgDDNpsUKXfOjmQMPP8aD7+uPny/Zf9XRSvd/vVLllvzHqbrjYm3OjTmj/SaOUfc77snhxH+A1dAMinDyqmAmSrB2TLCch1DeCiEqDVXodpKkaJ0WKUpErM9x8+rYK3f/z31cvnKWsAMsz6AFLY6S2fbcJBZG6G/MUilYAxnc88PScBFAb+5+7saLAT0y/fRX/88HkbRrtwF4WbONgkwSZVkTbaSKBuFwCUiDbmfHXcXFgXkfz9p0idC9kmd7uY7u+cV1aXQTz/7eWdFasAHZcJyub55csAYqou52AkUJEKlAmDbCaApPqn909R9M2ffwWN38iA6sJ9+Q8aMad6Sas/l9UagLq6RRyfHLEblg19VFMJAOA2CgBAFzfDumvZ/wL3s/9/v0/++PH55Zvn/Tob+79NVtt0vTGrjQR7lQYi2kj+kr83F/0vHTZYmUGwVACQ49g2c1wB4KIAcLpaoBGjSvILp6+HSoCWNAie9SrNVgHK/r3/9vNKJ7+Hj09/TXm1wPLud+/eW88E2oQ1ALiGfhkm0O0UVjXw38mo/d/38ccfvuzCaBduo2C7DzdJ+JLqzdXKP0FV6j8XoOuXAJLrG7l0f4cgnqmaaFZI918/W742UPWvkP0//Yq6HJqgTtsXqq0EgQRaQi2JNnf6p+++RtHbL0+uZKQrA2uPiHvYL63KAJcNjD7NjRlk/2fc2qEMADvGPKr51ALL5mwrggIAMIraFMAIzQNnz0HXUi0ff9z/8cPT7vUmW/YnXm3jY/ZfqUQZXRxOaMpqAMXVA8rSB5XzAOTqdu6ecz81/4DqAsBV3j934/BPnYcQqpLlgA7zA7ItyK4WaLSkWg6VgHfvzCpIfw8f//59yrN63djAJdUAPDrclmnGiRLU6XkqaePiM9bqdS8HNdvcoGH88S759cPTZr3dhdso3EbhJgk2id6oNFCiRZVc+/d8SR6lj0nwQt5fX10BWC5r9vkFf0ojeH0oL/xNCrHrOpQVC/PHgr0oya4DfH7C43UC0uMLnLP/h/uNPk0FqGBCtZEgkOM8gHev1E8fwl30erer/qVxDRvu284GsPX63mb/l4CEKnqZ5KimuQnAMRQAgNbsDv8fv2Gg+r/wkK2oX3+OPv70T7SO4tPY/3CTLfujRJVeS/DyooLHfEExfZDP/ldlDW6OH8zdaQp70ORu5PspJpc4OH17ulH2r3w5oPwSRukqeA5MmqyySQCHSkCok1Xw9vdfV9M2N8ubu/26bj2yAf1fvMtrwXGkEjCqJjWAm6l/9z6v/zym//nu6/PDyy7c7cJtFGzi8CUJXtTh6jvalBbsT9fsKV6k59bwf6OO4/2rKvdNCvlVrssA18P/a75VuTguIvq4NFB6+PY8CeDyegAXDs0DLelKbY6TAAKT6A+PT9GH8P/8967Y9JjUsOG+YeC/2oi2r68u/iOIO42EKrqY8KimuQksj8sNCQoAgGVTZf9rTjQlXWFXs/+//bj/+MPn7Xq3v9vG61282iThNlltJNwqc7nov1wPJ7y57M/1PTVfb77b/I/y2Z18JeA69a+ubpT+U0ZSJUokNaLUcfj/8YWViAr1VgdGmzQwaWAOywH9/FOyCt799ttdvG+14y2r7KFZGRvYFf1GFPCRwHCKn66abGZZ+rL+Ma03ZQBf781/vnv58vh8yv7vg02iN2IkF6yDq5CtjNGqGLLrywByXCpHyiL4dc2+pop/fY+5ekDpPde5/ovHqJJKQCH1L5fZ/9JJAKfWgmiJV2oj+jAPQFL9r3efd/tvf/24uvqtKVVmt2yF+yaBv+y1Gr5+IfvvtYVENBKqaMeFo3shBycAN045NSgAAO1YDN+0BAr+eJ/8/sPTdrWNVrv9ahOvNnG4SVcb0bFOg/NKAoXh/+Y8D8CIUsWVf6S2HiDnr0ZyZ2wlcn3+rkkcHPsjJndb1HE529Kx/6cbpZMAzPlJssRBWTAJ1C4I0sSk2hgt2eQI89MP6Sp498t/HvZT1wDEyakAgx53jod8XCOVsCw9q4gtlZxwbn3UfMn+x1p++bD5583TKfsfBZskeBFJ1HW8PofpU35fi6jzVID8j65n7+VW/sm9pdJafukNUXV74focoHKh5vr+65/mHmbyv6JFlc4AMBULARlRWmU3jIiYUCKlNkprE2gJtVnpn999jaJ3f32a8no/pcpja+9wf64BiNT9DSu2oG3EJ4J7hIQqGnHnqOYjC8ABFAAAm5pHdrttgJtd24vb7qUSROTvN+nvPz69PLzsVrv9KruK4HMSvihlDmMJK4b/iygj+rg8Tn7hYClL/dckDq6yBhclgVKnn+a7p7mMgMnnC06jAk83Ck+VHzMo6pAmOD5byYpGSokotddBqo0JTJqtFhyI+fD+c7zX/+8/d833/0Aqm7u2xga2N1zC151eBtqiDICBtDrVeXQODbKORAAAIABJREFU+eNt/Nfj112w2wXbKNxEwSbWG6NiZQJ1Ea/V1ZJ9uRqAyQKZPka00pCdhT9pFsHV8dbphjTbr+dzgDkUDPLJ/es/Y2mR4Oph2VD+4gyA043D12NEM8ffPg8dCNWLaJ2tBWRS/Xiv/v1NuIsev74492EZItxfZP5vlgEqagBS/foq959zO7QUUSqHhCpucO2o5iMLzJ1rZ51rFACAFqxE7YlDv5PZ/y+vzG8/Pj+9ftmFUXTI/m+SYCMiKr1eQCC/4n8hoXDzn1zeltydUnEjL3+nubq/UAa4zv6rXMf++l9Gl91f82dTWlIdPAfZPABjdCpmpb7/8GUXffvxj+lP8pXd7941AKn4IzVhPeHrfrzHTZQBFmHcSQDS7HNVt0XuRe1NKH+82eyCfRTso2AXBZt98JLqSKeHAG0OS/MVLgCgDhP1LoJyzeI/cvlgKUv934zgdndBTdxStaMGrmYA5GoAxwWRTs+cPc/h60ptRAWig6wM8M2rr//+Jvxf0cM+tvrObKgL9+U/aOTikK2P/RUvVPpn8y/738DSEoxLe78AAGd50ZaYPjcE+OJmE7NJG3Tkdqoq3HYvjyAim7X59fuXz2+/ZisJxOEmDjax3ogx+jSKsGo9AdXm+oFyGkcvhxF+xVRC1VutLwaYy3uuhw0ev5rLe1RpGeD473j9QJXPC5RuiBElaRg8ZwUAlapsveAfv/8aRW8/fXZirYDyTlrvhGvPVB5dR1yjDIAhVJ1tbpzBnIzaH9/sP7163utor6NY72K1S9Q2X60vXvg3i8Iqf7We0wp+V+FbKXNx0fvrEr6UfGtEqWIQP47l7xYpCn8xdRnrTcWN8+PVMY4bSQ9nFaWVpMdpAacagMp9K6LMcV2gw0JASpKVfhGts6kAJlQ/vv28jYL/8+e69XsaxRBV/2Lav0kZ4HIjyrdqXtn/ZSJko5ybBzYdD5codVxzF+jNzVPONQoAgB23w8dgow5v9oBqHjO5fSC/fNj+/e1hHeH9ahOHmzh4ERUrc8omBPn1f45j5bQRpcx13r/k+oFltyW3qoDk9lCH3VbofRQyAlVfT4MFq2oAxYWDjYgqWwjo8KomDfWLBFrCwCSBJPrNvf7x+3AXvd5snPj7D5EUkFt5gJusNMWd2L+wipzCnI0+CSAzjxPF57X58+1LpPeR3kc62qtdrLaSXtXpLwf+Hwvw528v1u7LRzRTiG6l30rJV9Mnjtf8QocoUfiV07r/klsFSMmhPpEvA4gx2dvIlQHEBCbOLgZwqAHo4LvHzV9Pq687dz9TlQl3iyX/m+H/qhKgrn7k7h5EG6RVcYEDGw2Q/ccCUQAAGrESIMaMMiUtHycHEv7yYffXhy+HqwhmY/+DjVH73EoCQcVKAoWsQZMlgCT39TLvr3Lf5ifxX7zx009P35mrn+ZS/+eeZ1n2/5z6r1JcOPhiKkBRlkpIQ72RQJtQS6pNqt8/Pu1+CP/fL/dJUv064yrvpE2abe354vQyZowywGxNVANox8mo/fFN9HT3Eun9Xkex2sVql0qijT7m929equfwrzz7Xxm4KwsA6njbHAP56Ua/HXHzI1KI5tf3F5zL+epyCaDjfjiXAY7PYE5LAwVqK1obHSR6tQr2b9ebD28evu4cnQRwUnIK7XdWLcn5NzmWyx5g8RC5fqoJo8ZiU+GLfeMocj+4A5gXj846FAAACyZc/KfJ6aaYqR7iNTr55bv9xw+ft+Eut/jPS6p22pxXEL5c8+d65Z+K4f/5IYRZbrxm2KAc19q9fsNGKt//+UelPf/8gj9lPy2WAaomAVwtHGy0kYv0xrGskI0tTFZ6I2EgaWASLYn+19vP0Q/Bf39dNf2rDK+yk9aj99ZzHoCU/S2BDGmFeXK8BuBk1P7zPv3zzXM29j/Su73eJWonqcqFbHW5CpDKheyaJYCyh90sAJwDtzreNrnbFe///G2nv3nfD8plGywrWBTaIdnSQKfsv4gYo0QdVwHK7gnULtTrlYoSvYr1/v3j5s+vq69blz/EB+VlgDGnAlRsUk81T3L6EbFjTARrOB3WAcyRX2cdCgDAbf1bkyO3R1XFbaf89k3y64cv2/Vut9pF4WYfbOJgkwYbZbIFfyqv/SuipbjyT7EMcLF88Hmpn/Lxg8ctKnwrjXdeoW9bSO7nv5Y+IH9n5fo/uRupiD6tIXROgZxyBLIXtZFAy+owD+Dnd1+i6Js//wqavZ0xVGbbe48N7PmBb9t7dPb4gl3Uh+bJ8RpAN4O9o1TJ74+7l9X2MPxfZ8P/U31Yry8fjrWYLKF/Wvzn8qcliX5lTOH6wCU1AHV+h1Vfr/dCwz0yzqdBZ4V8qZsEoI5R/jQPQESMluRQA9DrONi/CzcfHh++bl2fBHByPovaOJOW/LWa/QH7/41bPQOVgJERrAEAo/GuG0EBAOjrZitzuGZo6RmnpNfr3kDCv96k//3u6eV+sw13UbCNgs0+eEmCl9PKP7UrCSjVYhUgOd9Ql6n/LCWhCu8z+1HunvweMBffXO6c3IWEVCGNrETMcSEgOb/ieenB8+OPHdji+j+5G1qd6weX2yOS/Wh1WCw4MKE2iTZ3+udvnne7N09f3QpSldn2HoO4xpwK4NbexPDILGAkTn7Ifn+V/P34NdL7SEV7FR2v/ZsPx5f5fVOf9G+ycF+xAHC8fVrnp5D6v47ppd9eq04et/tbNIle17MW1GUZINsak78MQFYaCU2U6ijR0Uqv4mD/4dXmr/vVkw+TAA6O7SYrH/C2ywGNnPq//t2+77rx71t4Lc+xBxbKn3MhAEyCAgBwg80W5CSDDd3L/n+9N//57vnp1fMuy/6HmyjYJMFGpcqowlr/+fV/csMJa1L/RonKigRXuQMjl/dI7s7CG86988pVgNTVSLZjx/Zisr+5+q2re06PORQJjKiaGQDmcvzgqQ6RGypoTKg2ogMJtKwCk+p3r9S/34f/O3q1i8reynQqcwH9em/jTwXAclAGmBUHJwG4F7VFZBfI76822zCK1D7S0V7vYrUzqShRp8v/GlHqsKqPLi76fwzf6jCH73z/ZbyuLwDI8cEiF19z3xYW22m0a64/BE3vMZcXHLgtW6tPUlGF4f/Zxl9OAji/bvat0ZKEskvUeqVXsV69DTcfHh+evJgEcPmptlsGKNYApOSv3b9J0B+xY0y04hbHtVAOYO58POtQAADq9B/dP/3wf7svYMPHt9GnV19PF/6N9EscvIgYZbSY4so/p3WEjWhVXEagogxwTvSXpA9ufZWWe7GqQ1foelR9m79fnacYmMIMgIqFgM7LAeWHCoqIUZKu1Ea0Fq0l1JLo7x+/7N4H//u3u1vvaALl/bR+feWhpwL4GPJhEamc+XCqBuBk9l9Efnu1/+f1c6T2ex3t1S5W20TtlAlEtDGHXP9xcl7JYH9liveca/Y3sv9VEfwq9V/cCzdbSZn6XH/hp/ll/Y5PZ6p+t+q1j+P7z1t+WQYoZv/1YWSAKMkmAahsEsA6Dvbf37/86f4kgIpPta2zaNupAK1Y37MkpgEA8J3bDa9KFACAAY3cxC/JW7vXyfj7Vfrnm+co2Ec6yhb/ifXGSKLSmkX/T8mC66/XZYDrlIESkcv1f5SI5K4NILdu3FTM41f+SF39SUzpb2XqFgISSdVFmiD/dFlOJV5l8wCytYBS/a93n6Po/X/+dvHMX9kl7tdXHmgqgKchH9bl04TwmFM1APd8XZmPrzaHa/+qXayya/8WYnST0Nx82Z+KUH799eKCuvkbhdtVah5z+ljkPh8lh3rbT09p6SJfBjitbnTK/p/LAFqS0OxWsk50FOvVm3Dz/etXjk4CaHZatJUQL5kKcPy+88E90FmBGsA42M8L4ksEt/KJpOkJTM2XU841F9NAgCMmHN1/U/1Jx0L2f5izmhH5+Hb3st7u9f9n703XHMeRdE0zgKTka1RmRGRmV/WZ+7+seeZUdWdsmeGxuLtIAjY/uGGlKJGiSMneRy4HKe4LDPgMMJitCIs69D966j9VM2M+AGwGBMZG34+pBpH4P+63c+YnkI7JnD+wbiIiPoDq9DVAFYEB3H4ARAnk0PYDkIJS8c9/fH/evfn7pxh3aichWqDF4NyhTN4VYL0mnzkdLDSsniX4AJZntSv+vC2ets919H+xK3GnoBRV9H9spH+y9X1EACO8j9/SP2C17Q810X58/735Tfakey0OvS49z8GQuEDDtl/faEGgEQSAJkv0N10C1FxkwuYiJ5BryBXmpchKmb7fPH/ept+W0wng8Md4nJF39zzJhTj11TzYZBx4ddgkVfB1uAoWk/mdFv802RPAMOdg1VkOOwAY5lSEbfHJJAZ3q4vUET7cqS/VEIIiL0Reil2Jr0hOrH8z8o8gMof8tT7W/HAYAQilIf7tnPxUFwKNuAE9C/TgBP+BRhqo3QBEnRACXYIAqBkQWIAQJITO5Lv7m79/bqc4r1MR1gLGVeMm6QrAMD1MpWExZ+O8PoBFWm0A+JrRx9ufneGGXYm7Ru6vDHeTbgwuAXoxf1zrTLQ39H9jnbvuesMt+BGm3FGPQ83/u8ngzCpFGN1jvZjxs6n1a+iGQwDjIgggqxMAgkpgl0KmMFcifUhffru5+fZ67vh+o/O+qbRa82Yc92rMkw0cdr6sZB8LX7kLZ0Wl86MfxL3nyAVQhpmLFWU5QdgBwDBhltz83+cUovXk7CR8enx9lXkdRFhUIgJ0OgKYIwAbVd++78inbXvYNfx3xAJHJmgm3eH8hgsHaMkF+0FDbuh3DOhmUngDAKCdaHV/YXzrakBgEomSaSmKX++ef7nLltkJwCRwLccVcCdsG8gwMbgWtm6W0A9gYTxl5XOyK7DIcVfgrsRdE7Wvtq0xJ/0gcX/oAuCZ75DtbobRsX4yJiP31jcOtvqP7RwyduF4AsxHJ7jB4P7bs6DIuZvWvF1GJLDTkCnISsxSkb7fvHzaZk9n6QQwaWY3Vf455j3mDODyYB8As2KGZ0n8oDPMibmAEgI7ABhmfezPehbZkPDDbfnXXdv8v4ohsEMtEUUTJUCYzoDe4D9NwAE0PQHQVZ7Jr0UPcwCQc/4HXo79Vz5aOrMPCJvaPjSt/7QhX7TfbZvB1ivQHkTzTSBASdwlIktFrmR6m+ze3ecL7wTQEnYDnLUrAMPshWthK+YsecQirTYAEMBTVpSgSihLKEvIS9yhbk2zY6abjxPeJ2iR0TR6nvRPANhGEAJ79dA3mVuDULoHZzHvCaBoe3970gjvU/sM4kdC5orYuBm6K2B0oRAA1FzSqjxACDqBPIVcQV5iep++/La5eZq5E8DJ8rhJ8s/j3uOZX/0DzpQtCsP4rKhAP88rPJUTlWGYC4UdAAwTYHzz/zktr1vZXaSO8COlTw/PrfpfRf+vY/50or+h/oMXUNj7eEEGwFkA3Zmw79u/CkdckUnCBPtUskIt9GOkEwABoesGoLq1IGYaMyWyTBRvb5+/3GV/Lb4TQEtXoJ3i1WIfADMDXAtbMTPnEYu02hVPkr6lrwWoEpSCUkNBRAjSGf63GZHeV/x9ww3eqAD+OZkLQMiCO4Y7OAn+5L4bG4sCZE763+aSTfyf/feU7AM2RyoKlGeAnE4AKGmXQJZilmFaYvp++/zpJfuan/7BnSVTm8QHcMRO5+d0Z3qWa7hY+GpcICsqyp8u+E9sLX7cGWZqVpTl9MAOAIZZN1Oq/6fkw23x7eY5r8b+FbnCnW6HELTV/yYA7r7m/+EPeA0PIewAQLDnBBMQn9PSE1fG0w4Io/oDOhPk3Ups1qzucztgoOMGqNV/AtGIEHWUpQRzLfJE5KlMb+Xu7UP+10o6AdTQlIoq+wCYeeBa2FpZRR5x+iN8ytSLzKvm/wpKRSXo1qTGewDs+UD/AkbDf/BWgabrQGvfzQthl4nc6xO7XmQt0LmaTXHfeCAsw36cy9+JF+RfFicikACgrq9kbdZVArmCXQpZisWd3D2m6mt+sprdajOygW/J8l93hmGuizG5Epc+GWZSLqaQwA4AhnE5eev+cbJCbNUJcqWTZWx/b+jTfT2EYI55ia/VEILUBf3HttV/XcW1YvsMVxPAa3gIXhoGCQcuQyT+4Jzoyu56e2jDJThjAIDlBqBGHcD6YJrYSlR1AkirTgCyeL9ZTycA++JM5QbgIQGYeZjQccXMyjw+gGU/GU9pXjbN/xUVCgrUrUAf+aBtc1ujjGCbY4huIWy7Y+Y7bsrdmH4xLEXfmNOmnSA/web/wRV9rJ8QqBviGClwBdwijTB9AwnsFGwSKBNQCaiHtJi+Zne+53OkfnXQu3veksD+M112LsEwZ2BFxffj3t/xJ8g+AIaZiBXlN3thBwDDTM9s1tbNjJYaRuDPu92PzWsuihzzaghBDUpoGWnmjwBIZOoLVvN/T3cwj77XN+C7AdyTDzoDTNr5e9VjaiT7oRpS73LOj57uX09i5x6oFQozaAAklCvME5GnIr2Vr+/vFt8JIP5IT1WsXUUzX+YCYDfAKjl1BrFUq13xJOlbtmsGACgUlEQUGfK3PSYEw3xj9yt2SnfEDYChmUYahn2Dl/Yng8Ti//hCv/MdXBgidzdYiqDQ9bH8AVQP9mNZdoRSQCmhlKAk6vs0v5E3L2rAuQ68GKtlReo/Mxssh14O/NIOhIueDDOaC8tv2AHAMBYLN5F9svRSdYQPt+rz7Y8cG/Vf7BTuQFvhgx31v//jtD3c0xSxPr0mgWDMdL6dC9F/UYK/9mhFtViAVQXEigXkrkVNEGG01vX37rsB2kR7bTuxAIES2GnIFGZKZKVM321+frrP/vqxyE4AA57nqYq13BWAmQ2uizEdS7XaLV8T9Sx3TfP/UlFhxP859ANB0T90Js58K/xdxBkAxiLD7bi/3wpbmqd+9T/oA9i7az8EEPiivz0p7Mn6mCV1PoB73D2k+kWNsOkLy5vGKrZ+FKjQLpbA2DNlGGaZnKv5v7M1zl8Y5igWUkiYEHYAMMxhTGNAp2hUaNV6l2rXCwEfbncvSZ6LPBe7AncKd00TQi98MAlCv4F//anH+8Xwr4PUfwiEDrCVBTPhpPcSrM9H7/S+TZt3FL2Fqxl+838wxk4UzcmZqgEkkKewU5iVIr2Vu9+2+V8/ltQJ4PAneUI3wOXZeGaZcF1sTVxx1vA1yUtQRT0AQKGhcQAgQuV9bwLUhDzxUNvuJtGo8451bj6EhMFOALGE8w2NiQfvhg28f+1L6ev43jAA3SpBH4CxOnX9Ab3NNqtQuy4SAGJ3TTyXQHcFqmtVq/+oElApqoe0/PiaDTvf0KlfBMPf13W82Zd1dxhmLOt4bwFgGeq/uU3OTBjmEFaU2QyHHQAM07FwsxjNg0Ye9ynztg+35eeb77nIC5EXmJe4K/FVkASK9ABww916n70L9DkAzEmw5wcTwckgQfXf+dVf5uAwQaFDNVv9g3et3IgBEnYJZCnmJaZVJ4C3d9mXs48EMPrdm0RR5a4AzGxwXWxNnMIHsPjm/0+Svqc7I/p/Hf/H1P2bozHdAAD1BXNm+iFuzPNBQ753lhms/kcTPsH3z9PurXQwEJA5CXbC3BTYpxbcS3s6VZ8D06dieibcj6ji/0ApUUlQD0khINPxM7e4xAwIjRRb80mKRgzDXAinzhHMPJezHmblXGoRgh0ADDMxJ7J3MU0aF6z+A8BTVhRYVtH/S9yV4eA/GIkp3K/pH6D+N+0KoVMi3G/nWvjXJXilyPu13xPgz3TCCwyBvGOOXRxH/a8uNbSBgEosbpPdb9vdl583w3Y9NZO+LVMVa6+4vS8zN6zOrIZp84XFq/8A8FWqn0k9AICCUvfF/3GOL2yI7YWDqwfnBBPeNwYXDk7G5vf2ACBH5fd9AE6i25S3+1AngC6BRqL7YDwtqAoBVCagHzB/SG+fit6n5Doynb0vytIMfdgcXMfNYpihLO29nZxTn6Bjo062h/ZE6MS7Y5hTcME5DTsAGKZmiG2a0n5NGgVomXyT9C3txg8sYachx2rsX3SC/5ixfULOADc4wAHqf739ek7PN9hXdMjV9eWDWJU+3Pw/tGQ/xjJVOCM0lYIujfVVNQcBrtMJ5RryFPIM0xLT99nz55vNp5d5OwGcpizIciqzOk5fF2Mm4sp8g09JUZtvKhWUCkrQURkawA7QR65eT1VgG1fiR7Di+4E5v003LblN4+58g7dHP70X00aD/Wqaon9s0kmYm3IeHm9HTQggo+dEez7Bgg0Cdh0iJZSSlAQlUd1i/pCqpyJUv7uCXCZwjfcuyVwTXEpk5mPJj9qJ3oRQMam2/NXvS74mDNNw2SUEdgAwzArA2OTSm/+rn8lrUbcfLBQUXQAfQgCBhwX/gbB2EJD+wUgHE923Ucd2EsHJGP0N+sCbH5wMzun5lQydJeId6a6qPRIA7RRkKaQZZjdy99t29+lllk4Apy/5ce2OWSPsBlgHk/gA1tD8/5ukJ9k1/1dUEGkEGdWjLdEfjJ+Mg7Y0enRPJmLijbSZQGdmI52Dt3xwMkisB0DtbO8V/R2L7/cOdO572McQEv3Nyxo07iiglLpMqIoFpB5ladXv1pytHHTsPbe/Z8lFs+Z7xzDTs5pX99iXd84TPEd9qY57yzkbs2BWlM0cBzsAGAbgXGXsYVJCrBa78OA/APCUFSXoEktVtR+kErRAa+xfDLX3h7gbALzJ4IlhVVunQS0HY/qCv1mfYDf/WNN+/9vcDjY31PEluHsxV7HPApvDcC5aoBOAhDyFXGGuMC8wfZPuHtLt9/6gAWOY9wVjHwCzUtgNsAJG+gDWoP4DwFepnpPXElRJjf/ejf8D0FlY8xB9kw32JPYu381vrrSzKXObQAgY/tVP7yWg3aNvx7tYQGDbdDAS/taC+4JQgcHc2p5SENbfpaBSkkqgTFA9YL4R25065NQvC+y97Uuu23PphWGiHPLqDn+PTpIhrOU1PlOhs82i2RPALI0llxCmgh0ADHNW9kkJMfV/+XyX9C3dFVAqKsu6/aBCSKAe2q6J/t/1/Q9/0K3xQmxJt5JMvuLQ/20mnHQMZxmnTZ+tGrgVfj8QENjLBPaC3a/mMRN0AwaScymMgQSbTgCECCBhl8BGQpmAupW7h1R/L+SAUz4ELtgxzOGwG2DpHO0DWIn6DwBfk6Lomv+XGkrQYIrO7sc14ka7+YCFtRezTBx6i5nreleBnPmm28Ch5wr6BteR+NtEz3e7FqK7hW43hhG3tl+b77YbBIakYALj5KzrLKGUVAcCusf8IdU7NW9Yv3MTLMatG7YBJ4Av6mVzxP0NtrE6D+c6gmkdj1Q55oftGdkHwCyI82cCs8AOAIYZyqIs1PKb/9vjBxYKSiARjfOzN/5PfdBIVtCe2JLgrOUlwF7SnHTSzaQ5z67so9fiz1hhSAggNxRAW3AKBRIy9YX2F/MEnXT7MUMAIVRBA6CsogYnoB7SEmAiB8ACXpUxpdkrMf/MwnFkSGZZHOEDWMW9JACAEuEVS01aUW3BiRSCxICVgTptn10Xko5CC9vLEvkttoMW2bfgQWse2EXoVIML9EUBqhJ2LCCM2fHQpL9Hv9FAs3Bvoci4Yo1Bp8oBUEpQGRaPqD7D6h0AR7bh7b3bq7Hv02UX04p7DHMe9r264x9yv//1qA2ti2lLnIcUkDgoELMQVlM8GA07ABjm3MTNZKwKu3z1HwCe0mb8QCgVlJr8AAKtoB8Q+r3qva9rQ2xOM1ywvyKEvoMJJ+2ViqxJRzuICgfeNwQSBJFSaLCLQHsiprjvdgKo1H/nUgtQAlTtAwB9n+Sp2BQajuciSm/XY/6ZVcBugOVykA9g4VbbPrwcoACliTQRaU2oG/NtHlDMcFszCcBoCxi03f4G6zn2imbC3Gy71AA7vh9fwfdi+1A7aX7bywS24xArLTjbjB2kW4ISUEoqE1IJKQn6URSA6aozjuPU//77zfadYVbJ6dV/c1PnySiWkD1N5y0k2hOKzd0zdwVgzsoS3r/ZYAcAwyygihQqbqxa/f8u6Fu2K6EsQVXjB2rQgszxA8Gs6hsXoF4g4hWwlgldJARomyIG1H+sJ53vYCI4GcNX54P6/l4fgLOR2JGQt6/uTAkQu5lN7wrsLmkdNZiwUf9Vgupe5A+p/mt3eJvB878/DHPhTFcpYyZloFSwWKsdObAcKAdVqf8aSWtdhdr3HPZB0d857qBA36RDF9DWDaI+APss9pry4Bx7t+6SPV58f3NoW/CwJwChPWXHW2DsgpodOVGVmj4BwbuAAFUngESrhNQD5g/i5ru6qirthaj/yI1hGWYwp3hXRvkA1vPyhguWjgUbwaGXkbsCMOdiLcWDqWAHAMMskWhOtBK7+DVRP5Jdpf5XPQBAD9EOgqK/Pwner8Gf2rSZcL9brSEgOhxgEKpiC4R69IOhGvjfEBEOYvsOL+mK/v4FDMUTEFS2gYC2UDyk6gAHwOIfxXDRdt8qDLNMpquUMZOyt467wHu275B2SAUq0qSRqm4AA3NH33NfS95hXd60Tc6FDBtx7zgcZ0BM/R9y8P4bFrTRg6x520HBDY0E5k4cO+7ugsi5IM7RBgy61HU/gBvMbwWt1wFwXPP/0HT/7EWywByDYc5I/O093btypA/g6AM6Rw7V19fsiBpUbAcHwm4AZmbWVDyYCHYAMNfOUkyM3QbeoalCj86k5srknmQT/4cKBUUs/s8hIn6rJjiLdTRV7la7d3RwaGR6c1jCXonBki36CTb3C6kGVeM+ROOW2wly9o2h7Ttrtedo+gD8DgSeXgB1yOBqJIAHWQKkQ0/04rjCEgCzOiaplDET01PLHX+3JsyYBh9MDqRIa9SaNGlNoJvDcI11bwW/R5f3jXjQsse24Jhv4yd05h96+XyD21jY/YIEhlaMHUmk+b+bMA16fzEJBGgkjURIJIiS1WYVx6n/PXfPv+1tAAAgAElEQVR6TcZ9rTeNYebm1O/KqH4A68GxedYPoy/xoYGAup2zG4CZhWt4x33YAcAwgzioQnKkwWrbjHkbXJf6/0PQUz38r1JQaioJFFJi112dhFOP636lLm4PegvEnQfUKxwQhn4C7zCGY+ryEK/SN99kthz01wpuEEJzjC0QGn6F7tPT6wJBSyollAmoBPSDyG/lzbMKnd8KC2ErPGSG2U+0tsackWDHrYWo/4cfRg6aiDRUAwBUyc7C2s0VEIygNEY/vxbDvNbPLlo/uOcYXz2Mbc0puG7PHAhdIETLFvvdAqxv7NL+6uC+sm3z/7ZsF3YzdBtsLrhfTLLTSAhUd6kgSsbGizwPkx/0mqr3q7xjDHNKIi/wEt+VVTX/9w8h7AOAsdf6aB8AAA8MwDAngR0ADLMg0BMRJlP/Z+Rron+muxLKkqr4PwXpHhm6xZ8fFOj7r0SoVuxusLqo5ngA4B2Jmdx77an9shV/8Jr598QNgFAaQnMCC2Pn2EDzUGwC17+KGiyxlKDucfeQqecX6ZzZNbCi94thgN0Ay2RR92PEweSoNRGBJqgGANAIbXQ433YHqa2tJV4HO9VFAxAE092W4z/1rNW/L1/oh/icsGDieAK8zRqQubCTQKrNuvmh2MWvrjASAVDVAwCJ5DodAMPpd/X0z18iF367GGYyZntXLr4TgHmCMZMW/2HwXiod46hLyV0BmNNx2W93D+wAYK6aRRmULhs6xWHNmMl9lUUBqgoBpKiN/wOeIr/XH9Ct0rQrhL7VEewFzO1ASPcPL2OlB90LRz5wBPpQDwBX/Q+t2O3a8Qv17AUGXFsE42LKKgoQKQkqAfUo1AeQy3oxTs/VlgCYtTO6XsYsmOMypikeiBy0JtJAmjShBh2UnocfX8zCxqx/bPuDZd5m6AGMLRDdjnH5KKrO2wY3aM29re3fac9e+lY20oTYOAAA1hgC6KC+tsH0WlnfvWKY0+O928t9Udbc/L8l6rLu9WUPhLsCMItiSW/e3LADgGGm5whRJpgNBSvNxzBjJvdT0Lcq/g+VCgoNpQaFJM2z8UIEmJhaQFCgj58Mob2AuUdzzl71v04Mkw9iTfX7a/VobJ68FcF7guqNkOXoiHUvMNfqrontRIHqRiA0wwaCSkA9YCEw0+svZh2nIzDM6piiXsYsj0MzpumeAI2QgyIiAiLQRLpy4ZNpPyw74+n41M6P4Qj07Uz0F4POc+/MDy9su8/9xfoZ41PrV/+twyACRH/hJmEO/+tEhqx/QgDsrny9JcMBQJSgPvZEzsNx133PQ7YKOPtmmAEs90VZ7pHtx+/lELWCo5ucjPQBAHcFYCZiNcWD08AOAOZ6WY4ROa36PxsEAPCC9Iy5IlVCqaBUUEAT/8fsz94TmL7ZmBP631H2oys2CwC4qwfpcQP06xf+FtpAQDHp31wlOOlIeebeyd1Rs2JzlUwfQPuJOQOsj6TaByBBPYjdQ3L7lK/moTsj/ddoOdkLcw2wG+CiGJ4Bn+CW7wByVJq0Bq2BSFMd/qe2bqbBNdXnFtsN0G9kA8Rk/di6B21tD4Y9bWf0GPGYHFIF8HGOwVuSwqu7R0xDfqqKVVXb/8YBsKr84KBjHfIkraYQEzrz0TobE4av6pqw3+Fz3bv9UYDGHNlS86lokXJ0WXOMDwCAuwIwE7DU124+2AHAMCdhePG9X/2f5lBOin2eOVIJWhMRaa21RmU0zN/7aY/YUe2D4n63f+x+8jcCUIUhRnObse3bOzqskGEWizrVAK05trLQDgWM4P0E8ScovKNQaILoxxiqEQUoSWVCKgF9A/kjqqeVm4ZTN/8/yC8EXOdk5oLdANfCKe9xDlTUPQB00wMAwOhAZhjc4YxfeKDQ31daiMz0r2ZPCc7/KegbcCf3nX/PRoLFpPBGEE0HwMp6AAxkvxiHR1r2uZkxp2aPArMylqH+XznRfGNchjJmSIB2RXYDMMexjuLBiVm3ysMwS2aIfdxbPR2bT50un4uc2w6IiDRprYlQkyZAoECT/9jh9qj/6F2b4KaC1w+NOvhe9b9/m86smBbQanFBjd7W68lfK7xrtESBmA8A409fwA0gqjEAqtGAQT+KctWm4XTq/9EvU7sil1eZGWCtZ930ZDSz3NfKAVCNAUCgSeujauoYmAoq4eFtn6Ls0lPw6LmyvqkdvpHIKp0ZD7kTyLTybROBdsVeNwAS1A4ASIAkglpDXnCc1e65nStgDfeFYdZI/7s1cf5wEc3/e3o5RJuVjC5oclcAZn4W886dmRWrPAwzhnmsRn8rsr3zl6j+77twOWhNmjQRak2aSGPTFd4uYWDo4zctjLkB+j0BGFoxSL/6P+QK+upuqEo/NASQL/07kolZGIv5GNolA1fY+wkQavU/IZWQusMiwW3J5SqDqd4k9gQw8xCtszELJ5bXzHgvc6AStAZNpKthAIwfa8MxuI11xFFusv/UMDSJoZ961tq7i36rfRoG7iGsvviWHZCo7gQAJEkna3AAHHeAK1b/F39HGObM2K/x8dmkt6l6MQr+cjgXof4PIWwORxc02QfAMGeBHQAMc1p8vXbvwn76/Aw2sTlWLQe1JiJNBBpBAoDX9jx4gr5ODaE5MYIygbEdNNKBBWDcHcCQD6BLYKPUd04O6tHuYXBjQ/R8ABgXL/yLjwCIpOqRA4EkkkRYqQNg2ub/p3sH2RPAzAC7AVbPOW7ervLiQ9MDAHRvw3PLskccAzEL6zvvRzLGH3Co4r93+YEbjLUAOAhs/tUOAEGUkE4AdsducYHsLQjW83uas54XzosZ5kCGvDTuMsMq25UnYJlZxfzszTWj9mmcr3y8DwA4HBAzDH7ZW9gBwFwj81uKM2Q6k+zy8CuVg6YqdIDWhNoYACC8+fhhWho9uhvpFyOcjbT7C7YZHKj+B3fX02YwVqt39PqeFQN7J9N/0B1AeJuRyEv+HGijBiNBAjpB2q3QUE6o/s928uwJYE4NuwFWg5nvnO+GdUa8HgOA4rJ+a0Gc+YfS7zbYt+WT59cD7DUFl5xwp0OWB0BdjwMMJLWWsbYWi+EIq71H/W+3u6jz5vx3AfBNWAfGm7v3lh0s/XvLU9Uq6giupvl/S7QwOa6UOXJIgGpd9gEw/azznTsV7ABgmKXQLz8fv60jONaO7hByUJqIgDRp0s4YdI4zAEOf9qcYsS04y/hrBecMUf/3NioMSANopCMCfXC+LzSAvQv0Jq1dEFAj+sd6ADjnXLefaBwAJEmv0TBMKLqchWVIf8zFwq6mpbMYR40kRECk2u9+VLX8CB/riKyXjtjdknHNNxlPh3MmTn++Sv0HJARKSCfLPu8jHvZB6n+79SWc/gLeaIZZI/2vzljp39wUHu4DuD71vyXqmh7n/uauAMzpWPk7Nz1r1HkYZhTLtA5Tqv9HM/rS5EBFEwKoGgYANIQ0el+yN3/y0wMdA8FJDP8+aDsD14mJ8hApEBlOAgrMb7bYU5iK7gJ7y2axrSF2DoCEdLL4ZoMOh7eTPPinOVmMEshcIPx0LZTF3JKUBAIiISAiYFxeJzOF7k/9uempTcxC8vJjOPDSoD1BtTWvxgDQyzXlU1ntvl+p/+dTspjXmWHWRPO2zqb+1xs8wgdwHAvNjwEOMT2n6wowxgcA3BWACbHgd+5ssAOAYc7PxOr/oZuYzljmQDkoqgcP1ER138oDjygg5VMtbe9dMX4trfg/seb/Y25Fv+4fa+zvNOGvfyIAPDi+8MDWF2h82v0Rgu56AFyutTyg/eC5YaGWOR3jmmoxl0xCiABYBZMHRBCECsDrhwaB5nbU9jI78+O1XOEbYF+Hr30H3pwbNmoJGiUJAtJVCCABlJz7NkxCfwOQ/bfZf25PxyVcb4Y5EwPe0Mmlf2tTofc33LuIOU1XgEnCAUGgbMJcKQsuCJ4TdgAwzJmZOG8avrkTWMcdUAlVD4CqE4A2ReZQV4Aepi3WzbAKRHwAhxaFhi9v7cI74m4AAG8EBbNbRCXX1CMHIpGglakGRwQRHjh/CbAbgDkR/GgxQRICBESNWNmOagB780khU1/3nyAaEJPHZ4xkf+oOB1O/JYe1bu1ZrlrUvNwI0ARwguXatqmC/xx2fqfwCnEGyjBTE3urTqj+D88erjj4j09fV4DzhQMC7grAAMAFvnCTwQ4A5rpYmjkItHU/Nae8BDlQNQBA3QkANTRxhL1lMfLxF3MS0Lu8v2TvAhjbRRBngeMuJTb6CLkzJ9svRjbYc8XqqMEIgETJTF1hJ2D8ga6ifMBaLXMizt9Wm1kYCaAkUfUAwCo+HADUdqt9WMynhuyPn6fGZoLtxt671tkjC7V7Cab7fxpKc5Gjq9tFFjMAIGhEQkGEBFig3C3Svh0R/GcC9b9/33sfK4aZluNezQt+FOMu5fD8s+Rtl67+H2dBw8XIcfUW9gEwI1nDC3c22AHAMGdjevW/ZxOzWMEcdD0AABABkdYRA95zoD3VveC6/W6Dns06dechqwdnDo/D4/80fs5BjoQoVdv/NnbwunoADGQyBeF8sFbLnAJ2LzEmCaLU9TAAVRQgWxSg41r4V+serun3b2T46kOcBz2Te5c/mqBPxSLQSCFgDAiQCAQBahAasBAyX5mJcxleEBwLZ3+Xzvnv8CSPrLmR85/S9JxZ/edC9rFEr9yIS8rhgJijWXfR5/SwA4C5IhZlAuZT/2c87Rw0ka7j/4Am0kiyObj+Zvv97C0AjL9+R9c0hyj+B5eAml3ivpEAhhyGv6R/IwirTgBESLAWB8DI4D9rLB+wVsucCK75MhUSQJJAgq4TwDGPBjXP1NEtXftV/iN6A/SsQgM3eiD7Nf3jVm8ljaYhA1X/SQgNggAJRAFLdAAcYbUHtshgmCVyuof1koqD8RZZ0abls2Dt6tKb/48k+jxyVwBmXq7gbRsLOwAYZm5OUpmJda+flxyVJqJK/QcNGkyh+fDTHHOpBi55CjNxqJIW8xMc1JngMBqZo701um3+j0QJ6DEbn4eRD/iqyweXVO9jlgM/VwwAJIhJGwJII+IkzwUZz9cpct+B2/fDDe0Vnfa30O9d7NC1yJsMRlty0u3PovIBaMAcZN677/k54hli9Z9ZK/M8qRdttg9S/3uuwQS34mrU/5EWmrsCMOdlVW/b2WAHAHMtLCTbn6Myc6ZTLbDqAUAaah+Af3Jev4ej+wT4xDZy6MaP6JvRlWtWaHgaH0Cl/mPlAFjI6xLlUAfL3jlrhJtsM6fgovUEZj8JQusAABLYxPvBgBh9ChyNfoznILbKmCP31x0ULMg5FKqv5/Cdulp/cBlCJKqiAGEOYr1vMRrf/vzJdwSc4zETMn8Rc9XPceRyDVT/h5yx7/gdwmXUFOaHuwIw54Lf2YGwA4Bh5uNUGdMyBBukps5Grbd9zxpGCoPzQ5OMQ7TfAAFWLpZ9pab6Aao7ARDJNfQAGMhlPz3LePWZC4QfraslAZBUDwBQjw1vx0EwPAFBVZoiAXyGSPkDA/v4381Prth+nNvASKM/E4zTD644xElwRCcDZ3XnAwSCtNCIVRSgHMXgrc3BocF/TqH+92xhb6+QOTn7ATBHcvYS52ot90E9p4Yv4yx/5P25mub/E7LYrgDsA7hUrvVVOwZ2ADBXwdlz+6lap4c3cfbTAwCABJpmg4hIiCD8I7OLX2RU3E2N4UQhAtbEAKVk7433HQPuxs1fuxBAQAmQRFDLeK58Rh7X5T1bi8kDmEuDH60rBAES6AYBRtpbY25dAlViiCn3W2QG2mi2BQMcVSo4aF1Pl6fYr33zQ637Xb0+fgQUa0DRnEl49br5f90DQOSwIAfA+GxkZFSKI5bnrO9imONWLqpkuaIOAaEcLSoc711mH3uMQXCj16f+T1UJ7+sKMOKqjuwKwOGALpJ1vmpngx0ADHNyTqj+w7KKdwkJBETdBPbZf4ZtZbhnUfYHDIL2jRhcFZnIiLxkXdlG/UeEBHSCoE5/zEdw0PN+Pc8NCxbMieBH6wpJUKCGdhxgAHBk66FZK4Ua6Rs/G2lnkwSBMoSzNX9O/4729i0Y+BN538El40K/JX94zf9NZz3ZPgMks+VEt2o1n6RGoTVqFBpxtxgHwBFWe6p+oNdTBmDOyWKfs4V7AoZfN7Pt2AkOpHeHZ9zEJRBW+zkcEDMd/KodCjsAmMtnkhz+OHf1wO7GF4MkrBwACIjYdsavuQYhf8ZzDDYw7FsALaGhaq3pLlH/eBH3KXgSF3FmUbi9NnMi+NG6KpKq+T9g1Q/AsBV7mrFTbWh8vz7ZNf4hcX5ik6GZZn/CJhHyH4C393CbT2PrrS4ffQPINa/BnVrzmmav/lpUFZxCPSnILk8Zn6qvAgrSSCg0oAKRr9DcTaX+T9V8lTM9Zj8nfs+GP4R9B7JgT/7+HNPNi2dkkVdsdSw2HFC7HWa9rK+gswDYAcAwJ+E686OEEAFq4YCqZuZVf/YZDGyP3HDQ3RgiPfir9EweA3qJvXsPlZADYk1kg0RCaBIahAbMUeaHH/MMjKwIXcNbyZoFcyIWLCAwEyPRjP8jULu17E6SDnz2Ns+HuGH13QPO6j3bH7I1c34MCqVb8d1v/h9wxjveCc9f4k/27HrPsbamn1AQCq2RBBZCLsQBMKfVnvyE2Z4yfZzyDTviwXNWCRzdoqx4Y0JMZlD/YwYD/fSYXS4i9z2eyVuCRR897grAjGDl79nZYAcAc+HMn6sPyYwuNcNKtMBGOICqE0BHX7PBCH4JpEc1OO6ieiv2H92QkkrgQAbKDcMdCYOuYeyKePOrBodIUH9yEAssDo08pEt96XwWVcVjLgx+uq6BBFEQChKChCQpKNFQ1k37q6A0aJhyCtpRairWjg8AYg8Rdj/VGyEA7BYe3ifA0f2HFw/2muDYg996AkxrHly9xxNgJKrTduP/hDZCBEjVsAEEqLUgFJowh0U4AI7IKI476POfKnNtnOyZm1DpNkEntTwrPoP6f8Dur1j9Px3cFYCZEH7PjoYdAAwzJdes/kMTNwAIqihAgIFxgI9ifFuEvVvob0XYUwj1ZlH8p4M5yFkSJSDN2P4YAkEoCNoeAEuJHXwcF/yKDYebLjKnY6kCAjMNjyg3mL6SlCSFloISTUVrMggIybcj5sds20lGbjSwW15Mwa+i+jQbJCO9p5NB+6j29xWIzSF7DtWdGwes2Huq/i6aBPn+A/JmNpPNPC0EEWpCTaKAhXbji4HGtzNzyIoMMyuneexOalLNjSOEcsc58Zr/9ztXT02g+T9zGrgrADMJ/J6OgR0AzCUzZ04+MCe67AwrQURdDxtYRQEi0KEF+9u27+3U77QEhAM7Chy6i569BNeN0SMouMsYeoYXqb+vzBzb7B4IkbTQKAhQo8gXM3hgy/BTGh9G4GJgHwBzUiZ0A8Tya+YsPArxgNkPkolOJCWSklI7rdf9xuzg/eTr8mQ/NbHgPBiaHGLWncVipryf/oagrUDfWufW7FJoFcfXDuheOvK2UP/ULonWRqIfAqFRkBaEmINUA071pBxqtQ9V/y/VrHO+Ny3TX88Vqv+xfZ3RE7Bf/T9N9Njw3SP71zE7vpSMqdd7PZbFdgVgH8AquJSX7GywA4BhJoDV/4qkGTOw9QEQxopwoQZu4eq6W6u3N9gTB8BOR436QOGgbxOhBYJaAABQ6Dno37JTz9+7C4hf3lZZsOeTpEr9B1GFAOo9nrkZWR67+PeuB26pzZyakepBz+vJT++5SAU8YPYJhCSZUCK0FCQBWhG7FUsq8+FYqLp5fCWOezF8qPeRcbz7gU4AIS++Pwmh9F56pP9Omg8VDnxb7Jtgf2bQZ2A6A7wtdOqUcdmRAEiD1CS1FhpR0/m78R362rL6z6yDEzx5Z7RxVm3Kb1t1IoZ17ZntsriHw+r/LPR1BRhxC0Z2BeBwQMuHX7LxsAOAYYYSM0ms/rckCJJE7QPQCNiW4HokbEs1GE1MuF/S5afoRGTOMdtt5vRf8+b2oCCNWggCLEHslnS5Droc/nEv6EzOB3cFYGbgCL1+eIgPfoBn5lEkEmUCsuoBIHSioXDNh+MPAPDsS7Bt/l5RPqjgmwlEMEMAgbcv8NItA137MWm+SSDtWwyiOn7gopmJ+PUkgq5DgLkSaZEpSBQkJSQlyB9Chk5qJk5qtdmmM2fjstR/h/ZIum4BJzu4YPbXM+e0NPvjvGV+wtWTcY8fhwO6YPglnQR2ADAXywxZN2dDDgmi1EIQIiAAot5zhSI/96gGZtPC2IrdJNqTXsvB6C6adPwYA7sOTgYr9n4lH7zy8N7n191Fc+hHPPhEQmgSRKgBC7GIwQOPgNX/HtgHwMzDwIrboe8mP8Az85iIO9y84k5S7QPQlHv6fn1PjBg1js0l49ahbVWj97PtNIDNwuT2JOix491aESM+3LYCdEH8ybawrfPDXN5M+PbdvWj2kmRvLba8/1P9USJRkFbq/0+5+Zqc0wEwHDS+wUvHlp+Ty8h5LuAUzs/UD99ib0qXb47s3xcE3RwwsPcZ33Nu/t9DvLo9JdE8dkTmyyMDXyQX94adDXYAMMwB4LHm8EryLAmQkEBqhgHQ2LSSA+i9CPsEfeqtzzsLx9YNliT8+xnbTg89Un6ERkiwJXvyfh+4R2eO+2v8EtTigtaCUBAJDVjAghwAXBSfEG5JzcxGTDcY81ZehhK3Fu4TfMDsCepOAIKSWvI2A/6gkbYDASF6JpjIy4Rij4Nj6F2Jf5gvHwbvztxpcNKx0cFvJwHG1ejA8E+1MwHbXgWu6uBebSRrJoFUlCqqmv8nT3LzXZzNAB76kg5U/891PpznMABXpP63WPnmVMXHc6v/gW2T8RNXOc5E9PnirgBMA79hE8IOAOYyOV12zep/DwmiBNGNAVB1VQ81YXMKgWQNczewbb5Dv2rfIxn4q/g/+buIHcZe1cBM9uv4EFIQ/ElrecOp0F5Vc9L/CQCABJJGjUggcpB56MTm56C3+HrespGwisrMybQvJj+9c/KIqQQpUdadAHSiQZkqfyP6h+j8AX4PANhXrff74ZkJtBetYwF5gw2Yq5uTe+kx4mBH/qHwMmGrbX7AW7K6nv5G/KtN1q9E0MT/qdT/AuTXJB12ptNzqNVeuPo/P5y/Tcs01/P61H+TLhudtEPAGPV/byVwIG0TtbF3+HpyqFPCXQGYGPyGTQs7ABjmtFxVnpUgJCgM9b9u0Nbqznbt1xQITAI+AKobjgxsjI/NhBMFKLZKUDjwV9xr/3uFA2syOLNNk7f7+lqhO6dnm8GNB38lAqFRaBIaMAepeldYIP6tuqr37lC4KwCzXtgHMBsPmczy5BVkAonUiaCEqAzJ2WRYKMest3bKFPHB8wQ4mL86a/nuBIzP97fWQ1iPchOdRu98OwkwLou/WV/KN3fkeguM8kD4sitMFNXxf56TzZNcQfwf7J3cO59hZuK61f8WK1c9rhCJbk7nbHzvpd5bmRl1r1Z6Y07M2Kt6INwVgPHhYsDksAOAuUCWk0VfW56VAEio1X9BQpCsmqfZDfzbOSbm/FjHf/Aq9g5OQcVenchWCvb3M8C+ffm7tiabDQU1+uYb/QViG4ztjuxtmnNiK/riAhIJ0oIQtcAcRe+uZ2L4W8zq/3GwG4BZKewDmIeHVDxg9lx1AoBEUqKo8UPXptjRsgGAbKPfuu8ddR56c6BgGQBs2w221R5SWhj41FBvOvbtJKwV7SKQuYwxWYf1IQQnBBDVVrr7yfggaRAK0nb432/p5ps8jw081GqjPRlbjGHOxqSP4AWYLSv7PrYQeaj6P3wP/Wq163QkY/6Ye8P51NQstisA+wCYy4AdAMylwZnzGUGAB5EmIJNm2EDUCYC2W7F1mrVRK3Zq6Xuk+WZv1T9HUHAWoF6L37+jZlMU3wD6T1xQDgjsspkdVQ1Caj71br89UF/oDzgGauEEBRFqwmoc4BzO7wDgt3g2WEtlGCbIRsC9zD6DlJBIlIIS1FXT8sagGCbMMOWmxUEjTNDwTgDmT/66ZOdbaOyoZy/BHQV36k+SbbOD304CzOvgdYIMuQFq50qwgBSJ/wMERFq06n9Sni/+z6F2pF/9v05JjW3xBXNhN7fLfwe6AYyApIHtRF74GS4aq//LpK8rwIj7NbIrAIcDmh9+w04BOwAY5lRcZ571iIkEUYUMFlS1GaxCysfrvdZkWJFHa067SoU/M9aHwP/2l/E3An3FDVfb99OtRtKqAM53bMVuDlm6QHj7njQQfAIDXgFCqUEQCk2oCfOR/STnhZv/j4d9AMzq4Id2Hh7SJHmVsvIB1J0ACtuC6IDob9qXWvnxpXyI1/F9Q7zXlPvm2y8hDHxkQna2q/EPUf+d4o2/zaCaH9xC+GP6BhSmTQ8A+VNuvi4+/g/2qv9swZmlMN2zuBxr1X8kR5wxDesN4Kv//Udy9BWjyFnEmv8ze4ld0hkIl/TG9V/mcEArgssDJ4IdAMxFwRny2XkU4h43r7BLSEqSghJFu1ANFsDqEOD2AMChnfqNtSzR21iyC0EwyAeA/hb2Q5HJXtUAHVnBSCN524TwkoHtm4vt/2iQmqTWSIgaxe7cPQCGv8WsHUzFuOI0w5wB9gHMwEMqbyB7hZ0EKUEKSjTlhvnw1H9qDXo7EyuLhrWBdmT9Hrpl0FqxxwcAoaICedv08Y8kKFX1q/9gzwHrytiFH1f3R/PqGR/reO1lkAgIQGhISqqb/3/PtmeJ/3OQ1V6R+s85zPWytGfxcI54entW6bke7Vqx3gB+J+puEYzMPyUc/GdFREt6I4qAPDLwKuDX63SwA4C5HBaVD19ttnWX4ANmf4OUlCQ6kZSU2mkeGBgSwP44Df1gmGoQkv6rNHVav3FfIj4AotDWIHJLQ6oBWr9StOdruy9fFGhn7JUPnER1tj1aA9mHQQCkZNKGD/4psmdxzoeXi+JnhBVVhmFMHlK8T4MjjOAAACAASURBVLNvpZCQCEgEJFAbdN1ouWRMOlbGSHcOeAjlNH7mbVreZi2rtt2Yb6TaxKOxMKG3EWezMewFkOz5ZtQjw+yid777izr2dgzp31qlmR/YFBEAKZGWxgAAT+kZ4v8cZzWWr/7PzOmML5v1g5n0WZzz+s/5FEVz7bblVTUTjZ+cTWFo5lyw+r8WojV/7gpwufDrdVLYAcBcCIvKga8823qshgEAKSkROhEkNal4BdindQN46rwl7gdXBLuqH2jgv28yuCkY8IiR/Z/c+dak/6ury/fuKLa6v2I3xwwu3H40Sk1dBOFvcvPjfA6Ag97iK3/LTgT7AJgVwY/rqREAD2mavMgEZAqpgkxDpvTOlvs11DK8ad+9TgBh9T9WgzcXCzb/b+hsYNDBcJxCYFpz38h63/VirqwfMVJWKagbLTnqKqi8Ajo4X2OqKCkhKUA+y83XdO74P4da7eA1YWvOnIIjrcMK1f+z2MF+l4DZ6r9f/Z/h4M0D4+A/R0ALyKW5K8D1cPaH7eJhBwBzCXDGuygepNxg8gpSgpQkhU40lP2VW/sTU+ehvzKPjnzftQFEAkBsNkgIdYih/r34kzGCEr+ZthUENGY6+gI66wY/5q/Voft7GfgBjakyAgica/zAQ+HGg6djXJMahmEuil+z9M9kW5RFiVkJGwV5HQWINAEiYKP+a8DKwpqmykg3JrixcntzGsco+z4A/9tZ0tx+1EQ05t8/EooknG9/sl/QB3cS42P8QhsPkBA0EAFoQA2gAYgAFSQK0sp8f0s3T+eI/zMQU/1Hez7DLIKpn8VTl6OWVk7rPx7r10iee2o4+M+q6esKMOJuclcA5tpgBwDDTAyXCh5ScY+bH/BajRkoKVFEnnH254Bd7/Xb+vnN+nwIOk+ALQdQUDKAdhK7zZr+AxharHCLk3HtgIy0kyCIXBnnHF03QERxCK5lLaMwKdvxA5Nzjh/IZadFwW2rmVXAD+qp+WWD7ze3z/lrCZnCQkGmYaP0q98JwAthD1baKgYMv2+hdv21nSej1b+xMIIRFAgO9Gnai/lj/yIYsftsd35zstT1t4v6AKxl/ND/xofIbP6v2+UJtBaZolr9L0E+Zdmwc5yMg1691an/c2YsnImdn1Wp/+t6YNyjxVmPv7uxrP5fBOHSw+hwQDC6KwD7ACaBX68ZYAcAs26WltlytgUAmYAHzD6jSEBWPgAkCQRebHq/rmsOwEt2zR9CaZ/gkn5j/6APILaLYT0AyJ500xT5dhJOuv7Ew/pDSHGA4Ea87RCh0FAHECgheUo238/UfvA4HaFnDjMe7grAMAwA/LbJvrxsy7IsIS1hoynXtINamMbmQ95IAGAad89+DScSAoicBRxnP9R+gmPyMN+IN3NM9R+c4D89ZZvgZ8iSulH/TR+ABtCAWOK2wKzEpITkRW6esln998dZ7bWo/8x1sR71f4FFsoMOyR8QeCZoopt8rdkWLenUl9kVgMMBjWc5z9hlww4AZsVwHrtYHtJE5lKClCAFJVInCgpPhoY23QrTXjxcOLBHf1v/h9DqAR8AusuAnYZDHrQe6b9NON92As2ZfnkrqB2APRPaWMxeUGZnC6QxU9CNAPy0hvg/XDiYGW5hzSwcfkRPzT9u8P3L7c/8NcWshELBRlGu6NVu9a/rVvmtTa+j7TW/orBssXvP/Ky9564Gg/+YP/np2F6Oi/8DtlU1JnFPi/76g7aNDq7SXEyg1geg2/ml2Ja4KcQmxywX6V+bm6d0oeaxDf7D6n+Qk2ZfnDdeEjPfzQl3R7D/nR+yu735BkYmrQS/FRfBiboCcDgg5uJhBwCzVhaYu3J9puUxFXeYveIugURCIihRVdRg92Nizm9HFMTGH+DH/+lWN658j/QPIdWgtwcAeZsPQ/ZSPW6AXvW/VgHAmBm8XAEfQHwUQfDcAN1HYVI2AQTOGP9n+LscvBP83p0a7grAMFfO+5vs88tNWZaqzBRkCjKtd53u33YCII3hTgDQhLIRUH+3DMlgfGvurBHU+h3HwN69+GWSUCIQ9geg7Yc3RP1vFguaZs8r4PYAINKEssRNgZX6n/3Itn/ezRr/50RWOzZ/fuvD9u6KWEPz/3keyMn3EqynjdkjHXe7mt1w8J8aAZQAJIAJIQHkWNfR10a0/ceIhiE8MvC5uJjXa/mwA4BZH8vMUTnbMrlP8V5kT9U4wJBISsqqYuvUddEMHIz2r2bEgFgnAGiLc3YjPnd5dOUDp+XgFD0AyFkyoh0E1H9/MfI2Rd58R14JLgMAbcxiW1kA0ihUHf0/KUB+SzffzhH/Z5mvM+PD7ayZxcIP56l5c4PvNzc/85dSpmWZKdhoyO2RAGpPAEHrA6iEfjMBnuGD+K3radrvT/bM7JnvM8CIk38ilZrfJii0u2GOgU7o7z5GFKC6E4BKNiVuCswKkeUi/Xh79/dGeHs8FYe+a07zf7+cMbxV7+W95pd3Rgth6IW9evV/2o1H1djpdnqED6DLgq5B/UegG6CEKAFIiBKihEASJYQJYaKbb42SMNFIALnEQlIuIBewE5ALyBFyhB1CDqTrDR/pfTkxUd8+dwVYFQt8tC4YdgAwa4Iz0rWAAA9pKndCopQgBSRCJwQKnYouOaI/hCZDTf/qSQjPIefXqj9B0AdgToIxJ7hlC8/rYP7ipH0fQMwZQE3rQrCvgzOnZzveZYw0SNSYNsF/khLkU7b0+D/c/P/scFcAhrla3t9lX15uyrIsRaYwU5Bp2lW6P1LTAwDNYQCgMUBgzqGqpTxWcswRvhs/yE9/8/9Dd9HjAwiY8vp0ogUYy47XPvgqtg+ZTfsN3d+MDkQawI3/ozEpxabATS6yHaZP29sPtws1337wnyPUf2dhNkDMVXGiB37CzfZtyn69T/3yhuP/XEeWoW5APWj9WNJjQbcFyUri1ygIkQAJKwd9/QFCQiQQhARIAjWiRiQELVAjKkGFhFxCISmXlWOAcqF/CvV13kGch8FdAVYN1+Vnhh0AzArgnHONPKRJhslr1QMApIBE6dJS/+um6WbEgCGdAGCfDtkt2Yr+uEcmiPUwOKjZGUUmad+3nSAgb2gE2xMQ1hdicX78T7V9hamiagAA+Sw3T+eI/zP81ebCwXJgCYZZIPxYnprHG3y3vflRdQJQWdp1AjDMOrVt/8Fr/m/G/2kj7FUGegrItONHq/8QMeXNzCb+T6P7Q0TuD85pPoZXPmC4rWEVqCkmtVdYl+mdGf3/493Njxmj/x9ktfsP67iDnuFNny0n4SzrzCy++f+SN7h/U83lPcVZDO82dMHN/0lC8UDFo6LHgh4Kut9RWoJUmGhEQiREaNR/6yOw9QpUNUIELapvJEQtsHIcaKx8A1BtTyO9ZOW3jfqalU+J+oqg9x/kbPR1BRhx67krwKlZ5Lt14bADgFkunFuumodU3MnNc/GaYJJglrStBeumgq1MEBP9zSr0ofV5pzm/mYh9g7cX8HbkGKngYVAoPVj9H6TgA3TBfAKrWIICunJDNZMAFSQl1AMAfMs2T7PH/xn/gnOh4VxwVwCGuULe3WdfnptOACJTaqOpChDQmnVt6C2++t/arHYYgJPmIj0RgaB373E7Hon/YyQGOAAMcd9I69Ck0wNAK7kx1f+/7+/nbP5/3N0KNv8fY77Z28cskCWL9RNubeh2Zm8mHs5SyEtMtumzUd5R8aCLh1I/FPohh21OiQapIFEoFSYKZdlq/Yb6DwIJUNczoekNAABaAFWfzhMAJKBxAIjWE7DdJQ8v8LvUL1nxfVN+zdTXpHxCUOe+KA1h6zA6HBCM7grAPgBmObADgFkinEleAFsJv2Sbp11SQFqCGTK4q/RSPcav4waAULoy34Zlr/83BrkzzIaIT76yD57uD/YysU4A0PtgUmSSIpOefIBgn3VwFwOVBeNDbfhgQ0ogUiJVVIUAkgXIp3TW8QPhwHecg/8sE3YDMMxV8XiD725vf+QvpUirQEAaspJeG8dz5eDXAG1dWTTuZwi5AQCw8QQck48cFz7I3RmFpSpfNwpK/zDEAWBH/iHo/PR2xH/0fADtOMCoq04TJW7Keuzf9FVmH++2u7k67x1qtfcG/xkD+wCGwJeoj2UXIie8d5Ns6qCNnP3Ba4P/OLnQetEZFPe6eFTFfVE+5HS/A6khUSQVJAqkAqlEUqJUKEuUJQpCqB0Aom3sD7X6XxkjrL4BkASQaDoGCFGJ/iSQhGj6BAhC1ALKpPrIzS69f4H3Ur+mxbdt/i0rv8ryq1iCJ2CZXQHYBxDkAt7NNcIOAGZBcMZ4Yfy2zT6/3BRlWUJW+wDc1oK++u9UpGNRgMCdQ+ZM8FYZHgKIQtvpg8L9XIeoBtCdHXWN+iPBfKqlKRJuuPuQ1QnAaUVYpxUm7QAAL3LzlJ0h/s9AuHCwcFiIYRYCP4oz8O4h/eun0QkATbMO3Xc9bqDTDwCMSQ0ggCB+02hY9h/z5dtb2gN1x+6uYyWoG+y3/jasLfT4AFrp37bL9je10X40OrabCEAruS1xU4h67N8vD/cfbpdou/eq/5OY9bW/76s+eMZh2rs51dbOJv0PfsODGz8ifwj0LjLV/zEX4tyVEJXCy2/l7rfX4vGVNmWj+GtKykr3B1miVJiUdUKUKEtEhY2uX7X3x1b3984KARBIANTqP9XqP5IQhksAtQSVokqgTEA1noCtTO9eb99LvUuLb9viKc2fkuJJUHmey2WeVNgHAMc/D+N9AECc83ec+926XtgBwCwCzg0vkjcb/G17+zN/KTFVkCnKNGUlvdrV4EYgqJ3jfi0amnKcaTb32vCgpt9OxvwB/urOTJNgQTesGhgJW/d3J8E+96AbgAAIMDq6r7eA3QMASYmsxG2JWQlJAfJbunlKZrXC4993LjQsCu4KwDBXwsMWf727/b5rOwFsCItS/6xVhUZyIQCEpjdAcAwAgOa7DQd0KAM9BC39inFXwKCmT0Czgy7ifxt5z3YDxBwAzkwdWsD/Vdufeg6hqIL/FCLbifRnuv34sNFz2cIJs/eFm282ZMx5mV+1n2QjAxceslhbeTuei1D/VQrP79Trb6/5ux86LSlrFP+kJKlAltC295elEKWWJQolRAm1pm92ka9EftszW9m06gIh1HatCvYPCoEEgah9A4haIFUOgBR0gmUKAU/AC7yTtEuL79vXvzbPH1L1Ou8ls4ma/BHe47HhgKoxCdnMLL4kcNmwA4A5J5wBXjy/bdO6EwBmJWYaNlrnje5vdwLo+sWHOgTUlfGKw023Wcpx8V0Fvs+g20ps66HJmPRvfHeRf8A+630+gH71v17AC/4DRKSV6GIIFCL5ezNr/J+D7hyXD1YEuwEY5hp495D+9fO2LEslNiQVaAVKa6oq+o1ODs13LT1orMMBQW2SUAA10j9WRQLRiDVkNCJvISOPwdD8kZC9ISMnq6IkD4r+b6btyD+daY40/6/iM7S6P2lADaAJNIImICW2JW4K3OQiyzH98nD3ZXuc4+RgDrXaPc3/w70sAr9dOGwoz8ykD9uEd3Mh6v8o6T9ybQ89qp6cvadHkav4r1P91wl8r6T/tz91VuisoLSALK/a+1Pd6r+sGvsLUYIsCSvTi0QCARs3PABAo/tXBC9e6/82DZw1iQiIpRAFJog6gSQFlaBKQSWg0soTgGVCpYRtkt6+3vyabn+9e/5w8/ynpPONFRytmJy7K8CV+wCuydovEXYAMOfh8vK9qeqgF8bDFt9vbn/sXkuRKsjqjz0SgOUGaGrRbYRcAoHhEsleYsp+sPl/f4+BvfT7AGKegNaxcZADwAnyE5sZCf4jtwVuCswKkeWYfb55+PN2oYZgYNGfWRTsBmDOyNqjgqyChy2+f7h93r2C0qA0CA1Cl1ppXdjab9VKHp2hgLEO/lMZdw21YAF2y4CjObosFjfiAWWEQpMRO05Bu1x/tzF/Gp+90eqfun4AWiR183+Z5SL7vr39+Dj3yD1DGKr+B99SchYatDt+35mLYQnq/yjpf4ptOise2snLTKyxsqAT+P5Wvbx/3b37qbNCpQW16n+6A1mAVCBq9V+IEgEJEKk1o+iZUTQuhOPjttu3Wc6Txpy55q+KJlSKpECJqBPQKVY+AJVAmaBKoUyoSKjIpCxvHl+2v949/7l9+TyTuzrIArsCsA+AOSML1X2YS+Wy8zr2AQR5f5t+frkpy6IUWQobBXl0JABsasgk7Oq0BhBN7F0Mjde3l73N/w9aKwiF0jHpv/s2YgoP/UQGCTAkBtTBHgCEWA8hKDY5Zi9p9uF2U85YKht+NVn9XzXsBmCYC+Zfv6R5/uZ//tbU+AAQVUHaCP0PjZxgVpCdEEBt8B83CpCXz5ORqQT7AYwn7gBwJ2OegDod9NA78X/iQ/UYnQCaDwlRiptCbKvQ/7lIPj/efstmMoYHWe0e9X/o5rgkPRHXbH+v9tyXpv7PcCPc3GK1wX+0hB/v1I93tfSv01KlOWUFpDmlOaQ7SHKUOxClQMA6lIwkQHRFf88B0F2HWFcAar721l6tqiiKUogCJdRxgZKmQ0CS6aQgmcmkTNLi9nH74+3dzz+z/OlsmXtfV4ARz8mYrgBX6wNgC3922AHAzMSVZHFcc/F52OJvm5sfu5dUpEpkSmUaNiW9NEJ26wkgYyDBtlYcGTkwRvucYfvPe/T67D/amzj0se3xARiTCKbub8QUPsAHsOdDBG27wjb4D2gttqWo1f+dSD/fzjqE4PhMgF+udcHNMxnmIpEC/vXLZpc/fFEEikBpEAqELsofANoZD8Awx0ZXAMLGoHcJAuHUpbGWJGJ5P3kd+yCUaBeG0Kb8XMoVQexuiEHp39T9oQv709liQ+vHJhAQ2k76rsm/qh35oAGhFLe5vM2Tm53c5CL7env38TGNXJCJOSgDD4aW6Cbj23KWpJ4bfmLmMVhsFs/MpE/XVHdz/HbGbGFMbccCByxzGlzFfz3qv5bw4636/m63e/tTbXKdliotdK3755DmkOwg2aEsBAgEAYSNUULvA3baOZleB0BfIuAACHkCXlCnoFKSGyE3JAtKSp1kMik3293d4833z3c/PyTl8zndAGENAI5/YMZ0BbhCHwBX5JcAOwCYk3NlORv7AAK8u687ASiRlrhRlGvaGZ0A2gow1pGCraq1P2ag80wdpPL730F6egxEd+DN96T/SiMg8yfy0oFPpLVg++0v6cT/qYYQlFXz/wKzXKQ/N9s/7zaRszgz/AZdDNwVgJkZdjvNw80W//uX27woSWvSGpQG1IC6VD+bunAjRtQ6RWvcTdHf6QHQOgZayDa7/c3//fnBOf10C0TCD5pW25k0dX/nYxjrdhgAK+yP1Q+g9QeU6V0hb3J5k8vtLtn+3N58+Mft6/Jqb32t/uPqf3jhQ3wA/L4zF8CK1P8hC0/4Svo5QUzDrn2v/vxl8+039ePt7rmR/nVaqDSnNKeklf5zlDmCQC2hGpS3CvuDjtzfiP4UcwD0XEjDxqExx6quQu299mupjb1rogPlIHISOVU+ALnRMqMkk0l5c/v6/Obu+6ebHx8TXUx2DQ9iaV0BsGoAefye18SKXszLZnlFSOaCuJLszId9AA73W3x3c/M9fylFVopCiY1WedUJoKkAY908EMHoDeA3/7dq481F1uOud7D5f3Ax8Bbwl/cdAOCWn6zvfgdAbH7IH4DGHCIAbTgDNCCVYlOKbY5ZLrJcpJ/u7/7ezPecDs8NYsfE79R6YTcAw1webx7Fv/L7oihJaVKatAKtQCutdgAAtTnvBJmmaGSq/1YPAMMf4DRpNNZ2E+Y3eAuD91M/R1hwPxHR/a1JewAAM+BPFwVIleltKW8LeZMn212yec02//Pr48c3M/XbO8hqO7pbd8WJbboF28FLYiF38+jDOHTFmdX/4aDzPeYg5sqYyhT+/lf+9b++ldudzkqdFjrNdZLrWv3fYZKDyAUKpFr6J0BsfABGT7tQDwBq5GhLlm7SZM4h40cn6H+TIKq9uAHftu8MQAQlkx+EBYmi9gEklSegfEjKu4fnH28evn3a/Pg0Xx90hxN1BTgyHBACXoEP4Dot/jJhBwBzEi4+F9sL+wAc3j2kX37elEVRilRhpjHTegekm9iFjYhPSKARwBwz0HMDRKIABR+7vbdhUDOAft9AzCtgJLoCqScfkCcc1FI+hEIJx5v/kw4tVs9UmJSiGftXpN9ubj/czhRDAHqv3UD4bboA2A3AMBfGb++SXf74f0tFmkArUBpRF6CJQgMC1zRGnHz1v0m4OX4TM28/vp8Amlynfwv9dtyfrBPdKD5omnUd8tM3afTMNBmRf0ADagKt5VbJm0Le7JKbXbJ5TTf/+/bNf97OVHE7KKOeXP0/qBMAw6yakYWiRan/c2JlOEHX7ZiNnpjnO/r7X6/ffn9Sm1xt8lb610nX6l+AQJJAwtL9u4TvAAAv3V4N/8S8HgCu9A+W4aMeP7fzqcbqQylyxJwwJyxIbLQotNzoJNNJ+SZRd4/Zj3/cP33cPJ9pYIBoxX5EV4DjwwFdug+AjfmiYAcAMzEXnHkdypAa5/Vwv8F3d1YnAKVypV+M9vtVEa4NHWCq/5YbgAAOGAeYzNSxrvne3XnKRFdganZJRBAPIuyVqKItLNyAP54nwI8kULUx1ErelbgpxCbHLMf008PNj3SmZ/MIHWHITGalsBuAYS6JP95nef74QWtQmpQGrUHpQiu0BgR2aDsHOIGAmjmEtq7RVqgdfd/4bu0tGstYxtmrmhNYWZGpWztSSCPut8UPMsbvadSTvVa7SZNpuN0hfyt/gJaZkrdlcrNLtrtks0s2H9+++fe7bOBNGckYq73etv8zWKXZDB9b2Cthaer/qR+8cNZB3U/LzFscvv6i//7vn8+//CizXG1e1Wan01dKavW/DfhDIBBNxb+J/g/C6Frnq/8Q9slaxtC/Ts1FdMxioNLaJMxwQGSaubZVHyKQEDtMCxJ55QMgsdEi07JMZZllxeb27su/779/iY/td0qi9ZEzdQUY4XpYNKt4K68KdgAwU3KR2dZIuA1Ti9MJQOEGoCAqmwq1XWpBABBNgCDTDWB877fQ81x+5wjMhmtB0R/A1Q48H0D7wWDpyhH9ibAOGoCkm8EDO5eAEpta/RdZLtK/7u/nbP4/HH5Trgd2AzCn41IrUcskTeGP99u8uP9LadIadB0LqNQ/ADqhoZHOqybzVW3fDwQEzWjAWPUO9BPYbsyy76YnAIyfqgfBFP3jj0ZY+IDGH2BMBtWQvo8OJdpQP85HaZEoeVMmN7msHQB//fr4n3dbdR6RpA9HcKrpff0OMPRcgGYWzCRWZsxGrkH93+tEREf9H3MQp89tCOHT78XXf/54vX8uN7navKrsVWevOnshuYO21X8t+neKv9X2v5qJCIBICGjmwX6LumZmd2WC50nGMj0OADBEfwgZO/S/EbSUL1B3BSh01RtAFEpsBGqZ6CS9//vPs2mSS+kKgFB5zbn4ypwadgAw08C5VQ9cham42+Db+5vvry+lyLQsSSkQuix/NHEA2m8wXALmsIG1WNBI5xqorg3vubzO0xleOmJz/XX9pdwiZ1/JyW0zuNcBENb9PR+AM5YgGV0BEErcFLipgv+8Jtmnh+1urriL43MGfncuGHYDMMwFcHeHv7+7y4uSFEHjBgCtlHpxveEt9WwBoI2uAJXkEdlN3bQfmw4E1WadEpYp/ZvuAdhnTMifNPwJjR/DjPPTJvzYCEhAbYj/yucRivzjfQg0oFTytpS3RdP8/+ubh3+/vXmdy2t/UIYcaGhK1k/B5RmGORcrUv/34o4uvmz1f7eBT3/snv7rqdjkRbZr1P8XSl8oeUHESvonwsYmetK/Gf+nHQOAzGzY6QoQPLG4AyAwOcTz3aad6nzXFQCAELRMflAVFEhstNgo1AJIAKX/R8n08cu/0x4H/Unp6wow4pAOdgNcovzPRn+BsAOAmYCLy6ymh30AFe/u068/73SpQGmSGpRCVIX62en+9bemuhdAMARQrRdQJxwEcer/jX0PhxIedouCzzp1f3Yriy5hzN9fhIqH/vc/9XyMaAoEWsmq+X899u+X+/uPd4sbQhBYLLhi2A3AMGvn119Ent/nRQFak9agFEiNBLp8sW1cQzfVqf/YuQFCH3IMeqClofFruw/0drk3y3GMuDFJ5kzLdltW2/XcVzMdM+2YbAWgAaFMbsrkpki21cC/3+/v/vf97Y+bmYzhctT/y1NCLux01sqSipVjHonj1l21+h+TtOuMaNnq//cH+vTH84/334ssL7Ndmb2q7JWyF52+UPIqSCCJTu7vIv+0rf4FNN3gTEc4AaLbA6B1DFT/ex0AXd034gBoJXkky/YRmOav+TSKP3qmuW7qhwJLkeaEuUZVqf/V549/Kpm8+fLvTVmMus5jCBud0VWUQ90Al2T7lpTdMh3sAGBGcTE51Aw4NdHr5G6D//rHXV6UoIiUBqlBa0Ct9AsQBKqTXSNBQnJGAO4SzfhIscvri/tB38DRDGk3AXZRCfySUzeWYFT67xr+t00LAc32/ob0jxpBa5GW4qYQdfP/53T76XGjl/cUsvrPsBuAYVbN77+lef7mz0/1SABIoAgVCVU+A7Vmh2yTR3U0PzI7AQz5DPQBQKjwFctmgqY86Axw3fb7PnuC/hNoRE2gVXJXypsiuSnS7S7Z/Ly5/fP3+7/vlxf6x25iClOr/x3jC2gMcwLOW1ZZlPp/LtzgPwvmyzv1+fcfz29+lpu8SHdl9qqzF529UvoComhi/ohY5B9P90cjA471AGi+A43e7Dlk/XPmdmaO0DZ5dqL1eSM23u7GGWB0AqgnCaXIRUICqh4AWiAJoD9+10ny5vN/trvns93PaE1ktCpP1aA4w87sMnwAC38rrxl2ADDHcwF50/xwXebdo8jz+/+3KEhrTVo3VQAAIABJREFUUBqEBqFBK025V3DRjVeAvAEA0E6AnSbsykbmk2pc/qq26owQiL524E86xBtNWK0q/JaDdXsKqttpmB/wmvw7EYSN70b9d6MJk9YiKeVtIW9zeZPLTSHTzw93X25nkhKG5w+s/jMt7AZgmJWCCL//tsnzhy+KkEASFiRKEqLxAQCE3u26pmur/90gwMLQ+hEACREBgZBqQ1+1rLN8ANjuyeo00OzPqlt7B2Qq2f3qP1U7iIj+6Mwxo/NRZ6+x9QGASu6qgX/zZFuk25fN9uPvD5/ezBWw70Cr7epuvSsv36BfktG5pHO5YC7mNh1xIgPrwn3LGHtFGHc1T5k9KQkffy++/P5td/taZLsy2xVppf5XYX+0qFrAgyAQaPoAOgtomD93UBzwMuPY994T3ucAsL6DCTI8AY76331jnQCBhZAaU0LQ2LgB3r/9msrHj/9z+/ztnEYjrL+Prp9UMsBAN8DafQDLN/rXDDsAmGNYdZZ0dtgH8F9v013+5j9Kk6p8AApQlaBJq6o2D903NJV5e/hfgFACDAWhhQyjbSSsh7j5Kfpkk31I5szgklCpAxgoQhkJ8ubYH7Sk/5D6H4kjDKBJoJI3hbjJk+0u2b4mm7/v7j89ZrEznBbOIpgxrL3gy5wdfoTOwmYDf/xxCwBf//peEAoSkrAELAl1+UK6NExeQ2cHO6UDXZtuLk+Gla+8BWRNWvbat93U+2SYvwaFD+gx2db8WgfRFIrRR63Pnip7LVVyo5KbMtmW6U2ebnZJ9vG3x/99N1817Yj3pSulUWBmz5wrhLMjZiqOe5YOXetET+zRm0U7YVUUl6r+v9zQx993f/32VIX9KbLXMn1V6Wsd9B8QtSRsdX/RxPkJNfxHI926yWstOS791wYwlCs7F82KoGQIFXVrtl4HAAWtYaP7t+GACI1KOmI9OPBPRVoBlU0soF9/eUqF/vDh7tuXc3Z9i5YhRxcuD3IDrJELPa2Lgh0AzMFwKXY8QTn5ekCEf77d5MXDR6WbeMEatS7oR9U3sFqq/kbdlEQE1AP/+j0AjOb/VoO/Hm9LUyKJegvAW3fvs0+RxN4GFG0Bq1MN7IKU0QMA2/g/4eA/1RVT4raUt3lys5PbXbL5fnf3n7d33+eKIzyc4AEt7iiZ2Rnd1IZhmDNwf4f/55+3qZSfPz8JQEkoqk4AJBQ9kypaXcH4iGaOsG167EN2AkKTbebh1Nd98+JnM2TPp7gRD0n/nVknqKMXmh/Xc6+TTKc3KtmW6U2ZbYt0k6ebT+/f/Pn7TA77Q3EaWZxd/WdvH7M6Zn5iT6f+H7RlM1PeS98yvXnOQnjZ0v/86/nrr9+KLC+yXVmr/y86fYEkF2SJ/kYIoHZOJ/oTIFLIFBLscQBUhIe+c/ziTl3Y/AlsG4f2JARsn7mkFQ6oSpgHoVP5UxCJVJegEQiB3ryhVKoP8uHLx/k6wPlEqyFT1E86N0DvAazOtC32fWRM2AHAHMbqcqIl0yNOXzybDfzz19s8L79qIqVBKVAaUJXqJ9R+cbv9Qo2AbuBfsweAIRm4haQKU9k39IdujrEAov2TQdQaU93KAcAoanViASEgEBARAGJl9wnNklO4AYXf/D86kCCBalsUlumdSm5yWan/2583N//59f6vueIID88lrvb5ZwbCbgCGWR2bG/zv/96mQnz69JQDCkAJWBKWhIpeSO2qxez8n5poP+133UjQsuloxjuOSP8IVJtZcG02eiacrH/NMVHz5er+TcAfaE0zNTNrm25G/umMNQW89agJNCU3Or1R6U2Zbot0W2ZZsdn89fbxzz+2asbmjwdZbVtbOj9sIJiZOeMjd51PO5oJsrOgMVfkZLlYIeHPP17//uVbvtkV2WuRvqr0pUyroP+l0GbAH/ODRsKvz/rV22ACnAs24FSDLeHMdFD6Ry8RcwO0PgDwz6LyAUjSkkgSSdLV2ACJUKl8/PPP9LwPfbTeP4U8T82WLoOLOZGLhx0AzAFcZ7HjpFxY1n8QDw/4X/l9XpSkNFX9ALQCrbV6BYB4HdNU/2NjAFhLh9R/R/QH61dySj/GYQTeAWr/N0JAl+gWILLTe1tPhKR/K9F+lB1YQKnkVsnbQt7sku0u3bxkm/99+/jxHzM1oxifS1zn68D0wG4AhlkXMoF//j9ZJv/x4eP3VxQChYC2HwCq8sUzeQKACAR6PoDOE9AJItAnfJBZLzftCTaG2Iec/470j9acXvPt9uHzm/y3PnvU6b3OblS6LdNtmW2LLMtvt5/fP378IytnrJ+NEtBM94r/64gtXxJsvBYEXd1zeejjN3z5Ix7sgdd+72J1dr9I9Z8A/veP3Ze3T7s0z5PXIn1W2UuZVGF/ALUEEICe6E/uAAAEiH7kH9PwkWf+3G8j0Z1vqEqLhtHs/OJOlbldzDB/RM3MkBugGvsHBHWD82nvyiMQJuJVSBKkJWkJVBAJoH/+oVL55v/+e3PA1T8B0TrIFD6AdsurzpZWffBXCDsAmKFw+fV0XF9xtObdW5nnD/9fWdaDAWgNWpdKaSqMAgo17eyq5nWVxC+aToWWG4Dq/gH2QElYNTGoeziipf47ngA/Ac3kECiSMCeNwlPVlCXc9j/kA0Byxw80owChBtBKblVyUyY3eXKzSza7ZPPh/Zv/vJ0pqz8olwg+89f5IjBDmKikzTDMTLz7V5okbz7+KX7WAX9RVB8Sunyu2wPWTvPWFpueAHTcABAe+dBMOLY7lvah0KT7TZ0zIOa8N0X/mA9Ak0xUekPpjaql/22RZa+Pd59+v/v8ftaq2aFW29KWetX/yTh9EfmSLMslnculcvQ9OmLFRT0Px6n/lpJN9uQi+Z/3xaf3T7skz9PXPH0tkxclf2r5guSE+7c+aNm7nk4AYBtE8DNmbxKsZmlBuhBAjvvcriyTWXFuZ7a2z78pwnaN9xXlpdhJqRVpQSRISyBJ9P4d5vkvHz6eX7EMH/h0DZSCzRaWz7qOlqk4/+vELJ9FlR4ulQtw/x7H779lu/zxz24wAAValYVuWg14dI9jNVCS4wYIfbryE4SqkhEfAHn3xC8ddT+Zszq5v90W/P/svXmXpDjS7mkmCdw9tsyq3Lqq+85558z3/05z5nbfrsysrMolMiKcRTZ/sAkQODsCt9/xE4HjIIQktNhjEqW/ht9EYTLIf2pe/Z+o7kVYvFGQdCz9WN5E8hSqY2L9//Ptq/+8cXEd4Sss6sx4putpM/uHFSMXeP1BSu/h8/+R37+hAIwABQgBIibU0TNSbBgRMktB0SILQKprAADQOBUAobyWsW3bXjLaBAAqzCeGeJ9/Ndvr0gwAncv2ZnsdqyN5J+0d9eGUL/vz+Ob+84fT91drvvawHaw03B3WL2aAayFmU3QvrvYjR8t17db/fNv8OsFlpuOP1/GnD99f/PPZO4fqJVTPsXyO5bMgWbP7o239n9yDTVRbPcsIF8ob5r21CyX1gXDlJ9McTc1/oUEFr37SF9eBIAK03Aimd4KhkFoQJSsCoQbS4v27H0Hw6u+v6zeOjWOQSbubG2oyuJXfKCwAMBfYUDW0A+b3c3IOKeEf749BcP9X+jKAZC0graNn0HHTMB0BTN9/bFj/p3ZSRQaopHdNA0i3E+reE2TbpIaNugaQu0tYe0sa8kkP9pV/Kp9YCxXLU6ROoXcK1PFFHf765eHfb4/RUq9QGl9XXFvhZ4bBhl2G2RD3vwqp7rz/iL//+pFqAIgIqEFQfKboXDgJVl8DkKsCFlOIfSpA0qgW2LYbDVf1rw3NN0GtNa/7+ydHaqNB14gUqxutTto76UO67E94OHz/7eHTh8PLcek2cFhFWo8lt90MMzdX2O2pKI6lPU5a/7/c648ffjwdn8/qHKiXUD1H8knLJ6wv+o+pGICZ6T+bsG40dlS8B9jm1gal5g/Ne2uy/jfduW3MW3yt2f1L8wCSq6dNYTZuNT6FKC7AXE+vHhdCABSghfwpSCMhxIK0uD/i+/fqHNw9PTnR1NjHII3iwD5xIieYobAAwLRxNfXYZZqSYvIaMG9sr4fTCT+8vwmzlwGA1oIgIqHpiXQEAHZP+dQx0HgbMDVPAgAkSF4LmO8hAMx89Oum/3o+XHwayLad9HLK5oPqOwBaPjrrVOVL/ZRlAMxmAKCI5Snx/U/c/7+9uv/v+9PLUt7/veqKLu4oDNPClfW0GWbb3Dyg8m6kEn99/i4AIxAIqEFR5Gv0ITqTDoGovkBftnAwNbTs0OoI2bJdp48AAFC87JeM9rpJCUANQIQiUrfaO2r/pA/H6HCMfP/l9vTt9/tPHzy9uHdj31a7lLjzu/9z9c64yciSuWTB7nut1R+6xrrbsBgjOLr0/7cb+u/7n4+3P8/qfE6t/8+xeEbC+vt+sToVAAGEMWG9/T3AkG9QroUX4nfd+t/9niuda8PEX2udjb9kFJ1sT0kdh0zX19lGW2lD0ko8g5SgBGkBnvj19jF4r/73v09R1PlW5qTRD+kKHJR4tL4DWABgGtl7DdaJ7kbfaSvEa5MBXr8SQXAXBCFoEoQSMHtb4BPFQe3wxNxvfm0z/ZedJoiqnoN1DQBsfaAm6mWEyvut3oJQ6yE1fKhm9K+9AICEiOVNpG4idQrUMVCHH3e3f3y4+XFysQSx9Z+ZCpYBmC0xU3dhI/gn/PA/J+mJv/77TSBKFFp4OvC08DR6FJ0pOgPFFcf/7MU/hQCQv+An/+QNOhYpWzGIGDvzHaWKgypHXRIAKFskMDPxk830X6z/I7Q6aHkg7xgfjto/xodDeDg8vb79+vvtX2+WmqY3FKyUWa5zGYZpgga2cU0nVa3/w2I1M08H+s/bp2/3P84qOKuXQD1H6jmWTwCE2rT1y/qaP5al/8tfsbjvJvG78te6Ac17oFytV4bDlZ8q1v98pznCNRFGy1jIAARgWwsoPyf2xHMqAGhJWnx4+B68l//5P66sats4ANnvyMTNR48ZAAsAjJ09Vly96ZUIcwztr0oGeP9ORdHrT5++nQlDQkEiIowIYxI6em49lQBTv4mGhQUrHypvQP7XMA9U0r5jWaDyRrPzYEfrf8n0TwAaywIAgSblaXVKXvwbesfAO/48nf74cPf33XL+hN2fFLb+M5NzBQ43zKbo7jhgcgX1oPTgw/8clPrl7//+CISMpdLC06GnhUfC1+hTdKb4BUiXpgIQQXVZZDCVgLIkAJCaFXLK25bEp9q3elNeb7ih3EyXTf+Zfk9CaHkgdSDla+Vrz9eHY3w4hL7/+P7hr3+eft6tk/G9Wu3CkkTFTvOAeZn5ArM2Hwu3TdwU7pUBObutwoANX01n/5I4O8llJiJU8O83L19ff0+t//IllIn1P0YtAQWBRMu7f7MWrbLyT2UVoMoYFuGSBmDduHjzdbt1xb7f1Nc29lOmBGAuk1MmhAOWJgFoJNGUl4mALyEE8QxKgpYUC/LEb69/BMHrz386JJk3DkD2NTK5gv7pdcECAGNhR1XWcAYnwuRKwPXIAL//5nny9cePP54fURBKQkkiIhETxtEzQuWtuXnHQgBRbb5kxfxNRv8JahuJI0LZnSGxAuT2hEbI8rWwINTNB1C9hcrOYiHFxNxP2eI/2ct+jcV/tDpqdYrUKfKPkXcMvcPL4fDxw8OfvyzXPRpZXVxDwWbmZr8ON8x2GFn+5nAicJI3//KOt6+/fz4//vkYyUBLpYVH4qyFp4VHkUfRmeJzZkrI3f9FWb+ve0RiKuJD5U0AZQtItbKoZVtpdYmWJfsqe6rr/5AQII/kpaZ/8vxY+drztO+Hp8O33x7++uchXMmXsVdRbbf+zwRX5syM0GrVrMsFe8q49Uzhi9Z/qx7Qm3kynRD+/eb856/fXlLr/3Oy+A9hKKhu9M/2YDpoJRBYX/mnum20d9Ri+u8iA7RTaSCtGkD9b+VgQwkADQSG4z8YK/eSMRUgvSoWVycAUPgCKEGKdCpAjL/9os7B/ffv678QOKdxALIXDWDvfdJrhAUAhpmLiow+PrRrqILfvVeefPj4h3z8gQJRgBCEMWFEQkdPQIkTgUFqajffEygAIHstcKXnlNsOKO1CodmpAsrsB8VFUjtCextu0wDKf7M+Td1qANU3JmWvS8pe/2tb+h80AMTerfZOkXeKvGPkH0L/EBwPn96/+vje0Yr9GgowsyJ76WwzkzF7kZgj9CtQAm5/EbevT4+v/O+fXx7/etRKUaC0TKYCeIQeoUfxmeIgb7XTOQGWBZEhM/0Xr0akwtIPVZtIoQ1YG25TvwejUYZKw10R7EuTABC1OoI6gPLJ80n52vNjz9Oep33vfH/69o/bv393tJmucNHutnoh5TqfYTaN3fpPlhp89drGyr/fhH+8+Xb2kpV/XgL5HMknLc5CG47/1U/SWllX/im2G1bIsW5AtaVrs/5bE7JutDBM/5jtJSh2VsM0pYL814rpnyrW/+I1ftWYJE01efiMQoCSycsAXp1+/v6LCs43L2e3ioO9t9koDmwDt5KYmY5tdECZJdlsNTUlkyfCJCP6vGndN6/fSE/effxDfP36Q4BIXhgosnkAoOPq4BwEAWGhAVBz/wmqG0Tl/Wb3BWrbLVBt2/q3Ykcwt212BIvdXwNoLRSpk/aOsXeK/GPoHcLD4eXu5vP7u08fFq3Vuz8p1nJrL8xNge6+6DOj2Xhnm9kOCxSyfTf5CHcf5M0vt48fDz/+fHr+9qSV0srTgUfSo9Cn0Cc8U/wCOgLCzEzQYBAhhGItoOwCpY36/gYBoLTd0liDIdin7TWlpn8f1AE8nzxfe772vNjztO+F96fHN6fHt97LSsv+5LfUEavFrUvU91pm+8ItETMJmyxI3dzWmqz/lT31/b2Zp1b6+Dr+76/fX7zzWZ0T638on7V4Tlb+sTj+1y3+aJUB0DgYCCvL3LWY/pvau4up0DraJQBAAkL7ABmx6K9Qdrx5zfoMAAAQQMY7gfMrocgvCUQI2sNncx7A27sfwRv5//73qB17MBoHIB0NCY7B7fiOYQGAKbHBCmp6Zk2E8UpAtz7Vtrl9LX73bj0pv3z5JgAFYAQo0jcDP4GOACA19KfDb6wqAdUVFREhXxagqf9k7TB1TOyOAgBccCHM3P+zn/I3/WbboLX0yTtpP132J/IPoe8//nr/6cPNt9eLTooc+aSUUra7yLL70s+MZpudbWYjLFy2dl3vCR8e/pc6/Xr/9Mfxx59P58dnLZWWipSnpUehR5EHUYg6gjgEioAQqzIA2L5W/lo3phAAshacEEkeUfngpaZ/yk3/nhc9HH++vXl8473cr5yRwwpv0+I/M90M197M7AwdSm2lcDoRz0uJ3OIDZKnK3bP+nyV8vn9+8l/O6nyWz4F8DsVTLJ+waeUfYwYAgUjfalOaClDaLl5vY1n2p0UJsG50SYWKBdvqFVf6ipZfK4clMrkGAAKRTW0vTQXIpuxndv80sGLgnL4QWAiQAqQkT/z28D0I5L//9C7d0QrYk6NRHHCRnfY3mQIWAJiCjdRL87JYIoxRAq5BAzje4j/+r5MnxadPXwWgABEBJhMCdPicrQxAhsu/qQQkrhNUcxiE5g3q1mFq6uhcEACSOGV7ytYEoprXf7ZBxiQA0gRae6fE+h/7x8g/hv4h9P1vHx4+fTg+3yxaKHo9KfWYFXv6PnK7NocxU7GpzjazEVYsT7tu9b1bfPX/+MdfvaePx8c/f4bPKpkHoKVHoQ8qwjiEKAIdYRymSoB9eYSsTU/Ffki/5n/R3E/lDDWdTm0CAJlqfbqTEBGVlgqkl3j9g+eT71Nq+lfRw+nl7c2Pt+ub/qF/qz2B3c15dn1zDLMlWt4ysn7taePTQ/j37c9AhIE4Jy/+jeQzEGLN9G8sBGRa+c2/xUZb03bB9N8kA0CfJDTHuY2mf9twuOXXhhkA6QZm27kMAJVBtIQAUICQICVJAR7+/vrHOXj1+btDLwTOabIUNP/gEG4+a8y0sADApDhfI+2WYUrANVhBvQO8/5+DVL98/vg9SOcBCATUIDQIis9AcbaGT0UJMBcFyuZXZg4UaOk8de8zdRcAoGzrN/8myym2mv6Nv4QaQRMKre60d9L+UfunMFn0/+b492/3nz74scN1eVspHVzv7NocxkzFFjrbzEZYvSTtvdU//IKHX47HT/7Pjy9Pfz5qpbRSFPoQRRBFEEcYRRBHEIcYRxCHoCPQJSUA7U258bVwW2x1/ydzv9GOZwv+kFQoFAkFUsVSgVAgFXk++D55Hvle7Kno/nR+d/P4zgnTf1/QVtDqnaE5WP05m4rd3AgzFS4XiQFx69S/am62sHxYk+96Whe55/7/w6M/758DEQYyCEUQinOIz0BUWP/zJYAKPQAJRdZgGdZ/QoB8PxIgYrsGUNmAcp09UgCw0pTbaORxszaQfhOA9RkAOlsLKJnelwRAxYQASJzowMNnFDKbB4Dki3++Vj9f7p8CF1vYRickh72TXExHZh4cNhoxzOKsWyEPUAJ2bwUVAt79X75Ur7/898cTImYyAKHSoU/RGfQZksUHCyXA0AOShYDSn+pOgtkGmm6DMFYAwMR8UDP9o8WOYLj51zQAzDQAIi0VqZP2T9o/6UO67M/zq9u/fr/78nYF94eRT8rY3nweiX2XfmYKWANgxrJSAbJbTvYuA5zei+OvN8eP/tPH5/PfPyGKIYwpiiGKIIoxTsSAMPmLcQQ6xEwJSNOlcP9vUgIq2MSAopXKWmqUJDyUid1faqkgNf1LUIqkBM8j39Op6f/0/M5/fnAon7qX4pIlafXaszUJV4+dU3Bq9GMjfcg9ZGstqTHfb36tHVA5bAizZfGn+/D76SkQYWL9j/CsIRQkyyv+1xz/qwv+1MSA5FNa86cuAFhN/7MKAGY4JYW8Nkhu7XQT1GYAmL7/2QaZjnQAaXKQwmcQItEASMpXp6c396enLy4uBJSQJ041ReYfmTReuvVg5kpgAYAB2Ef3Ykf0GuBvpAc7il9/V9J7+Os/4hEwQpRC6sDT6GnhUehRfIZ0RaDackCUTQIw11LMPrm3IBaTA3Ks/acWjGeIrF7/UDL6pztb3f9JAxAJQfKo1YH8U+wf9eEYHQ6h7z++vf/r99OPh0UX/Qfjljpi79ZPVeNcQ+lnRuOwww3jPMuWm/rVzD1Y2bvT2g8V3P1THX+9P385Bd/C8Ps5+nmGKIYohjCCKIZEBkg0gGRagA4xjpA0EgFopOTdOdBsGTEhy3bSjiNS4ukvlJZKSKWlQilBKkj+KklSgpSgJCkZ3R3P727OH9wy/cPQUmxa/+uuEHPAtTTDbIgeZsxulYllyOCk9f+vo/5ynyz+E4QYRHiO8QVIEJmr/ZS2bbb+mt2/zeJf2VPZMO+2VQAw99UdD7um9uWcb017c/EfqFr/U1UAshF9Hi1CiD18BiFJqFh4kfDe3D1/+aHcnARgYhmGzKkBWJ+pphkczBXCAgDDHW5H6T7A37UpIOXVO6m8e/lv+fj3oxYqFp6Wng48Ql9HPuGZojPoKHM9QCyUgGqPKlcCCkkAAcC6anB924rNf7D6t279bzb9Q2b6V0dQvlYHrXztHfThGB398HD4/o9XX34/nI8jEnQR5rX+M0wf5ne4YVxkVL4vWGKo6Qtadhd+kftt+NUJ1L/U7b9U/OMYfotTJeAxUwKiKPsbQRRhHIGOhCYkLbRGIiQtSCNpQYSkkw+kCkHWMUAEQESRbSSTDBEBAZEQATEx/aNUQkqtMrt/YvGXEpQAKenoRXeH8M47v/fPr7edJWiWLmMnwzBbYcmuTt8WtqUyqQzAXK52Pt0Fj4eXQASF+z/p1P0f0Vzzp3hrPVYM/dUlgMxWaUYBwJpb1f5HS9pbf0XK5izUKe2n/HwBWCwBhKb1n5I3JOfiD2VzAgQCIURSnJXwPRFEwnvtP729P/1/Dk8CMKnKAMt6J7n8QDELwwLAtcMGkRw3k6KXDLDvyv32tVD+7emT/+PPn+fHFy0VCZXIAFp4hB5F5/TFAKV3Aiddq+YP5gfAIAHAav2HwsRPxrZp/U9WBCpeA2As+4NCqyMon9SBPF8rX3t+7Hn64L/cnb79fv/ld6+xqzUz3R+Thaz/uy/3zHSwBsD0YEnrf8u1bGJA0TG4Av1f3qO8V8d/Kf14jL7q6FsYfDtHjy8VJQC1Rq0FEWqNREJrJJ1IAkgaM1UASAMlayxnRplkO9MACFGjgZQoFUpJKrX7gxSkJChJd4f43g/vvfBehvdIro6oerXaufW/i51uWizxnLlgz/eIc0OzDbgDOQXde1b2cYFtI/3rpPv/x5v4y91jgGGAYWL9j/CMlFnwSVC6oL9osPKXlIDsyMwprTpiheavYPtr3RicIk1PyIAnJykmhKnvPxjLHEFh/Qcs9AAquf9nf0lBoDGIReBJPxLem9vnLz/UT+cnAeQUZv/Z2gmu2Jh2XO2uMsxVcakNSHzauwSz7xr/cIPv/se/f+19/3T+8eVn9Kx04GnpUeBp4ZPwKfJ19ELxOWtaW03/VXkAWvtP9aRtMf2Dzdm//LVYEch836/Q8gjKB+9Ayteerz0vtf576vn17dd/3nxfY9F/88aGMYv1n2F6whoA04nFSgn1uVR5CkDR4u++7QcAAHGH/p30/yWPPw/R17v4axglSkAYQxRndn/CxOivUyUg+QukQWsgAq0xSa/Mwg/ZJzH9A5aRAlO7vwQl6eTTva8fvPBehfciPq2dKJcYUpDL1v9lStaAeHJNzmwIx4vryK7RRVdmazXSpAe4bP0PBXy8fXlWQSCCQJxDPEd4JiJM7f7VpX7IMPGb++tKQMMHOgsAuaWgpf6+mC6WzkTe3ZjolTB5+JV3AEBFBgAAAsJiFaAkbkJArPAc55MAvKc396efG5kEkJI1so5XC8xeYQHgquF6J2edpOhz1XRB2uRLcwt+Be6AcHyNx9catvl4AAAgAElEQVTHu0/+988vj18etVJaKR14JD0KPRIeRT5FZ4jPZft+4yfrnwEVOsvEAgBaNYBcAEAiFKQOqA6gfPIOVDb9B69uHt+cfrxVwc028tYSS65uGDfgbjdzgQWt/6NORKPFv4a2P0Pcon8r4Z9S/zxE3+7ir2H8PYAgpjCGKAZN6Yc0aEqN/poMMYDQMPpD6vIPWFj/ofgnEA4e3SV2fxXdy/A+s0g4T9+lOS5aj6BFy7+OssfsjT7qKXcb2skT0jZvrfFg6zHOVid/3ER/3T2mi/9gEIlzjC+oJaDp72972W91CaDMBQ1rOzsJAGD5W8wNbxm6tiRt5WGY0LMAjfFvJiiAqVjUXghMhQyQnoUCgJAIANNJAJhOAnh3ev5yUD/PzpYag3IlMt945DrcQpiBsABwvXA/Zk2Gpj5VRl8Ntfs11Ps378Xx15u7P/wfn5+fvv7UUpFUWiqSns5kAIxD0CHoGOwzK/NuGWamlEqfqdxBrfZtGwSAxEeCSuZ+KjQASBf/yfcjankEdUgc/8H3tfK052tfxZ4Xvjr9fHN6fOudb1fO0lGL/8xa3VxDcWemBrkRZFaEKv9HBFKRAa6pMkyVgN8lnY8QEAUEAcGZKNAQaAhiCjSEMQQxhVEmBlCyBBAg6tzgLxClQCVQClAClNASQQlSSAdJDzJ+EPEBYL+pm5uULpfIpgNGlD17kHtN63ng5mw4V1ZtLkCv5LSPu5x0/39S9OnmORBhIMIgtf6f0xX/0wFmxdMfi+V9ihFoyf2fKHsd3XgBwG73tyZHZWe7B0H+hKQbteO6PEKJOoGWfdVbyGUAzFb0Fdm8+XSPgFjB2RN+LIJIeq+8p7d3x59n/1IcVqW5SPN4hFkYFgAYZvFqd9z1Sq10sxJwDe6AQsHdv9Txzf3TH8fHz08vP55IZYsCSQ+iEKII4hDjCOIQdAQUAVh6Wli8AwCae1FYyziyb1PLEkCUrfhAgEBCgVBxYvdXPvk+JI7/vhd7XvRwen53+uGA6R8msVIxjGNwn5uxsECZmMT6XwktlwGuoe2vgQeAA2L9tkOAM0EAEBAEGs4aAg0xgRKgELMPJR8JpAAUkgJabaW9yZh+8Z/WFwMMZn+V8P7uiFmLvZaldr90Nxf/AYA/bsNvNz8T63+I5whfYgiElplhOrXsY+PaPiIfgZaHotBTAIDS15JNvrsGAK0HtNj0Ke0+E2LtsIbTmkJLHf8JNFbXAspvUOSL/xgLAaGicwy+h34k/Eh47w7PXw7eo4OTALoV5lxgmfbK7iUH4wQsAFwpe+1SuM506W5pchOw9bA9ok7w8H97x18fnj4ef37+GT69aKV0oNIXA0YRxBFEIcQRxiHoCOMQKALT5wJN9/9mAaBKgwBgWwKo+AhFUpFQJCUIBVKB8sDzyfPB97Tnxb4XPZzOien/zonc67uMQOVkJ+6BYWywBsCUWND6P0uw+XD8KmUACx6AZ/oVptDeH/zpF/+ZJ70aQ71UdMdHZ98FgOnHNQyWLrFYd6ilnsGR+TBnJn490KfT0zmx/mfv/gUtAISxvE/+vl8EwGSxOdOCX/X3L9b/yW/govW/NlYt+dFbN6Bn0pjPQ9X93wrWDms5uthP2VHVtYAwSUZMd6br/xSJTIQQexBoCCJMJgE8v7s5PbozCWDQszS5DMAVG2OFBYBrhHu9JsulxtRXslfrtWH/ldgB/FfovzocfvWePp6fPz9q5SUCQPY3kQEiiDMlII5Qh6CjrK8GWQck6U5BqXdVcq8AgNzNP/0Cxe9k7MzX/EEQidFfgVQgVSwkyOSrBKXI98DztO+F96fz+9PTO+/FDdN/X5YxGTDMhLAGwOwHAjBn2vPgb2r2l6JY77zUMfs4HeiYSlzxMsy1YbVJl2zbrtYLH2/OPw7PAYaBCNJ3/0KMJLNRpKis7WMqAY2f6gEVWqz/LS5rTY5rHatwq/W/8mv7Md3JTqyuBVS5a2Gu/5MrAZLOCnwf/Vh4kVDvjs+fXZgEMLoATzsq2V+nhRkPCwAMswhzOk81ygDGb1ciA5zeiuOvp6df/eBrGH47hz9fIHkrYC4DRBGmSkAqA0AcAsVIGkgDUeGPYHahCMp7bL7/ZGwjABAJhUKRUFoqlEpIpROLv1QgJSUbSpCU4Kvw/nR+d/Py3nu5dyuXhpfcniYDhlkL1gAYgKXd/3tcrenQpurVlAF48Hd9dC9ahidms7PoDE35hRhyiWWWZ0dV5eAuzax9oSZrdGnE5eriP08SvvnnEKL03b94jvCMOnNItyz9P/kHShsIxs7KX+sG1EaydTJTQZEZFSt/+SFJ3qZD5umJI113CRizf+0CQJ62hQyAQAixgiCGwAPfl9GDen5/Wm8SwKRPDo9KmFlhAeDq4Aplf7S1tGUlYEf920ZQwO1v8vY3GT8ewm93wbcw/H4OH6tKABZzAiLUsSBC0oI0ahKkMf1QsgGky7MgASCbuYkCk20UmO5BQtQoEos/ptZ/GUsFSqam/2RDSfBVdHcIXh3OH7zzg3OZM3zxH7b+M5uCe9s7xpXMHRCJ9lPMX+u1bSYDXOdbAa6WvqXsQrkY+uQ0FbpJnkQnHucGXI4bc4FrGCOtRHu6poZeV63/APDNi3+ol8yDLIjgTKSRpGn6x8JOba7tU/pgsToQtL77t836T8n53U3/XVMnOyx1aKtY/6H8lQAw85mrLxN08VnKDki151zYSL370bxxQgKBQICGDEAIgArOGvwY/Ag8T6p3h+fPvvcjWPAxnq3Gd6XjyuwRFgCYq2ahunX+y1we4GdHXI8pQN6hvJPHf0r98xB9vYu+hcH3c/jjJZMBYggjiCOMItSEpIXxF7U2JAFDCQBATDt2mFj80+3U7g+IGpOvQkuFSqJUqKSWmcVfSlCCbg/xvR/ee+G9DO+FdmbRwsGw9Z/ZOtzbvmoczPvWKFWb8tq0v3w/YeFlx4xnNwmZ21ca3f+ptqc9uBpDnqp9JC6zUXbzeA9ljo5QPUXbzdUO8s0PM6+xdAxprN5je/1vp8V/Ei6a/sHYttr9J9QAoNa56GjTt2oAAJZZiHU7hDHznszbrGwjUPWFwAJiBWcP/Bi9CLwH7/nD8fQjmH9QvUiPkUclzEywAHBdcD2yAgsmei8ZwPHO1oSIW/Rvpf9PeXw6xl/voq9h9D2IfrxQGEEUYxQjEWoSiaFfU2rx14nvP4FOlgbSoDVAYu1HQAGI6TakRv9sByIiCBRSYW7xlxKOHt0f9L0X3qvwXkTOr/I/ZBmBXqcxjGNwb5uZi74Fq+F4unSUxd+fjOWA4Jra/utj2OI/DrFU4XTqpsezs9vZH9vKoAk7QtYHumKfzmXIiS8zHc8CvntBIgDEFMUQaooESQLzjb5dFv8B42+DjbvxJ0gvh1ALqvIXjBcBtYgvFfIVgpp6D6Z9v/7XOLg6e6A9AubB9bs2JwRUrP/pMYoCDUEMgY9+CNGDFyj0o5keucWfZB6VMHPAAgBzvey1Su0oAxC2HrNHxA2IG+n9LvXzMf56G32N4m9n/RRAGKOmzPqf2PoJNBXW/+wvAhR2/3Spn9zqb1j/ERGBlAQl6e6g73168KIHGd4jbaTSHfh09HIYZBj34N42Mz21InWhjHWz/rccU7X1Z40+2PzxmH0wePEfu5voaPf/meD6mZkXriGn6Ag1JaHV+u/y4j8A8FXGP7yXYgYARZCs/p+v80NlDaBh/R8gJERjvXvT0A/GzspXLP1E5a8V07+5UaRqlzSq2uWx2Fmx7LdqANlGFxtD2R0xt+lTLXGMREbD+k/JywBiCYGCSEKsIL7zzvfe7d/TrgLErQ6zLzZii2KmgKuvFVgv0S/3YAkIqlr/lSBOIE7S+03Sy0E/EQREgYazhkCnngRhTEEMYYxaYyEGpEvbEOYu/wgCMFkNSEqhBEjUSoAUpATdKv2g9IOIbvacyAPtBQzjKqwBXB1O5fcI63/l4CYZgKcCjMRBC2Gv4lEYmZwq+a6laX+cSk5mOA4+4T2ZyoI/WFZs/9UuOrrKNz8Kk9fHJe7/EKbGaEIANOYBZE76Lev/JAPvqs87Nu8090BVDLioAVS5mNiVxQSpvI22Z6NRA6hcsXxm00JDFcGjogcIokRaEASU6QcoIBIQSYgkxkcI773472AiC6cDdToPSZjJYQGAYXZLpzE+AQGg+/2vecAjyGPuuJERAQQAZ4KAINAQEJwzYQABlACF2V9EhaRQSwSFpIAUggJSQKJ0oW2NJsYsI7Ch22SYJrjDzUzGAMv9uDDMsxBqzQ+/FeC6qbTadkvc8g16tytxtcwsBFePANBNBujrXl61T7vt/v+C8M07RxBHECer/2uKsLr+T8leX/4JACpe//lGPh/Pqhakd0jFGD1b/6eYBFD7i2B8rW9fpOwvUDHlU9nKn0efoNhZ2gCbKcLimIClExGAymlYmRMgshkG6Ucm1n+Ik8+9F461cO6opeFqjKnDAsC1sKOqbBqWSBA3Er1LJzZp07mRSFEACuCmJAwgAMSACCQcydi5cLsrzjALwRrAtbDrbG6aCpCOuXkqwC6YZPGf4YwPhUsg4yCsAWRUzLd9U6XF+u/44j8A8FXFj9n6PzFEMYWk09e/FR/T5b/tJ7B5uDfdBtYOhiw0aPxLYARYCbljejX55lc2knWBcvd/aNgoSQS1aNSWAEp9EmtJRBUNoLT+EoJOZgAoiBXqexncyNNT3O1267e+I7gCY6ywAMAw+6fHVIDLx10xcn99gyp9lxEwT+OCw+wM1gCYsczn/t/HJ/OyDMDVd0/cSbOpFv+pNOg9QhxJ5xAG1sa18C3hcEXPWHHnOe/PTB2YKa3/S8ZjKF9VFECcCQChhgiofZEfrN10drvV5fvBNoegLgzUZIDmv8aUAjMCvTAt8tBs36/8hS6rABnYZhgk22kC1iz+ldCyLEh0FUnJEkCRhPgOz/eeforLM/FbcL7+H/Ysb7bqYmaHBYCrwPmajVmCTlMBIJ27yEzOlscRFgYaCxhmU7AGwCxE93LW5UjbuLtYEQhKY3x+K8CV4KJmP18keoVcOZgrfSZn1b771nsg1pQrLNbO39sLwjcVpO/+hTCmSFOIJJuN9TmVn+oWfKtOUJJosXSMVT3J7P4IWD4gn9hvlN+2cmz8RuV9NrN+yfe/SQOA8nbTBWuTAIo1lOoagDVVAQCFsQqQwvjejz6++C332xgvJ9lOTJltwAIAc41cz/o/FTqO8YkAuxzH7IvuZbZu/efCwuyVrY/AmTZmzdrJA+8bYGUgb/YByoNufivARunbateNSdavLTt7HjHn6fMFy3oAY7LZinHd3ou1kpnG+r9UdnxT+ke2/k+iAYBGgNI6P/lbfwkb3gpA+SlQtvs36QdoO6yyUf5beisAlAPsm1gVqz0aHYeWGQCWxX8uWR1sRyZ+iJCnGFnOKNIf0iMJBUSSYgmRwlhBfI+hAF+3XHbvbLPGYhaCBYD9cx0VXQ84QXpMBQBuQ66FIc8FW/+Z64A1AGZ9xhTBytT55Ftl2j0A8FSAPqxuFRxYItpPG+IIMIiep3eK1wKKQnM8uI3YM6s/7UNZpfdST6rqni1Y/wHgqwxDiNL1fyhb/wcalgAq7TSjm9jozag3yQAN50LpXMy+krFtPbJ8N10Sru7+b26bG71nANQun++on0UAYE1kNJIay/sFRVIn8wD0PZ7vvZtvoXHNa6qgt1lXMcvBAgDDzIDzzUzXqQDA5oAp2ewIokSnHiV1OYhhNgNrAEw/ehWXZcpWTQZAqI7TCxmAK+0d0eIdWv+6BJNb/zsHOPZRq1yIWwUnmaW95lqxA9YUqtQ8W0nFM8I3FYTZ6v/JG4BFp/V/oPJrtqYNlI+/eG7lMCwnYf5WYailMZRD7p7kplE+/9o8A6DtPcBgPIWlCGRPUvtaQ2hoJja5pfaRySQAihXGJwzuVfwtVFdYRW/l+WJWhAWAnXN99R6T0SHvS9aAhhajcBXkJmW/DF78x1IorGGZO7kgMduENYDtsvm8mzD2RsNfWhEIitE3vxXAfQYu/jNVQRpTMCYvVCuWUvPS265imA5ss1ZcrPnrav3fivu/0t/lS7r4D0WaLOv/pN7olT0WC35pw9ADoFVLsBrxjRrdvvJPu+m/JQXrQzWr6d88psn6b51J0BST6ulYaABYu2h+LlJ5EoCgSFKkKJIQK9APIgLag52TGxZmcvbwYDBMd/Zfjfa/w9JqAAm17gFPBdgxgxf/qZaFjgFNLiZxmWSWYvN2ZMbEkbxcJRrG8NwiA/BUgG6slTx9i4xp/W+xMHUNd1nrf1uknCqcFXPWUjhSjV0RG6wV5+66tJv+S9ubsP4TAMAL6hDiGOIYohiiyvo/hukZGhb/AdtbfCt7rAb6pmNM/cAMsBI+VI6vbTdRr8KajPv1AoXGg3F5EkC3q+S/VhLcTEk0DogEJZMAIonxAwYHeTzHHe7bYQa29QzTCgsADLMXxnXuSuZ9mxhQ0gm4hRnKBgcOBY1GhL5lb9OpwFw3rAEwl5m2iMxa4LI2vUkGSKtqrrGdoVdxqNjdxmbjyPMntP67XCDNuHFrsUsW7MRO1eWYr+vS3frv8lMLUE2gADQRaSKtSaPWFNuX/reIqk1G/HbTfxoFtBzTkIRY2bIeNiDVsck6j8UerC0BhGVbwsVJABa1AEuBYHOxtQskMp0EECuK7/D8IPXnWPS89w3j+vPFOAMLAHuGu51XxESZbTHvl1twlgF2Ru/Ff8Zb//OzNlt+Rkaca2aGYdwiq5Uotz3kwgAB8FSAbVL3CLUe0PWnMQVg2sLjXlG8LFRww78z+oyDHPEbaDLEjgywfX/JJu2m+39DrM6gUwUANWlNoBGkIYmjsQIPApbnBIBxWLFd2cgPaJcTrBvJmByreyzhW0Oz0n0JoLrdv35MJZBKNKzHVIICMNb5KS34Q0iAiGUZgFBAJHUkdSQp9jB6wPgzbFgAGCj2M8wlWABgmKlZuJc3w+Xs3dr6igFsEdg4vctOxfrvwoBmTmYq3U3B7j0594Mjg3nGUXoVjg4H9ytvI1dxKSsB+dqAPBWgiYX7QRMu/tM/lKVPt9zsuJisWW+zErBLNugONUkHpqN26LT1/1J8AtSa0g+RTtfFo7qJvx6aVQmob7QIKBfPrfytH9yy3US9kmoy/beEUD+3HoHKYkH18OtKgO1atQkZIlsFSFGsSN+KCMBru2OHYes/Mx8sAOwW7mHW2WGazHlLjev2GSsG8FSAATiimwzrW0xp/XckIQzWjQ4LAxuCNYDNs5X8m1pOsB9Zr30IIJuQn9TVPBVgdaZd/MeSjfV6bXxeO2P9dwVWAvbHzLXi5P2NJsfs7ucO+3Ug0wba7bZDhBBiSmcAkEYCfeHWDRf1Yqfxt9jATrdUDQerO2s/tp7eB6sdv7SBlsV/zAOgwzyAyk9WDQAqJv76x5wZgECCtCCNREgkuZJlGBssADDMZlmqXWucEMBTAa4D04UQ6tvrMrrguV9yG0xzzMqwBsC4xZji2CT4kzEVAAH4rQAbweoa2uPMqUyaU1n/d1nYxlhhGdfYoDtU9wLY8baafNHRhULeMw4BUACxJtKgiTRpTQiIUF2RJtlrufVsgaDSMjWVjezI0qcWTrad1clm7Y4Axsp9lkuUhGCs7TSoa79FtwCrNvoWsJvp33pKeSNN29rp5R1FAlN6IiYfAoVaIGgXil9P2P2fmRUWABiGacbo0Vo6t5WpANwEbYfufQvT+j99Dq9XbDZdWms2OmYdWANgqqxVIC5dt6uVp64E5FMB8hafslH5pqvR6VgmJfq22vO23d3j4U44bsITAvbEZmWAycOxWrsnCLcvQx+rM0CIiQBAWmtCXdj0e10i/dVqmrfem1UGqGxYaVcaLoZQN9Zb5wGYXxGK1/bWu8PWPZVItMwzAAAgAuPNCnWZBOuZgkBAlM0A0Aoh2FrVytZ/Zm5YANgnW6vrlmBvaTLH/TSFaewnw/2g+JWXA+rJupaTsWVny8/SLMneJdA5E828/pYzZ5OwBsCszxTWfyiPxauWLGpYDgi40V+CgZVM82mzZ9qICxSxnjSWrlfUPCFgN8xQMTre02izWI+J9+A0HJ1YAVAIMSXu/0SktRGZJof9OiU1to8s0pSi9WAr6wk1hdwxKa2G/oYD7Mu0Nc0VsBRhKuYWpAdQej/1ZYXqF0LrV8TU+o9ACrQCCBpu1U1cfsyZ3cACAMNskMnbh+4B5s6Atp3AUwH2hcWFcJt9k+mLZN8Ql/LyYzGAYTbDIo/ogIsU9ivTkkXpDPvSckA8FcAxKgYnWCVz3LP+bwaWAXbD1eijc1n/BzDd5QLQmrQGIkjeA6ABwGr0z2zWFhf1ctvYd1JE0/yAGmTdP7gRqNRBTTb9LmZ6a4D1X+trDQ0hsUwkpv9EA1Baq01ZJfre/GZujHEMFgAY5ooZ3M7mhoBKaDwVwHlcWfxnEjpEa33Tv/X0RQZFLAYsg+OuecwOaCtjwwpfy2C8fEju9Z/vrSwHVJzkaDuxBLPaGHq32tNdt3eAV2n6n77+ZxlgN+x9QNR2ZyMLcPdEm+FJCYA0EQFp0KQ1Jd7qFprmATRJBdbDmuYTIBR7uySHVWOYvPC1eOXXpYIuHeSm6QIlPaDbbSAA5ZMAJGl1nY8ew7TCAsAO4R5jneXSZIErTXWJ8eHkDoDlMHkqQBdWSZ7eeU6tX2tU7mj1umjKFJ42txYf3rMYMCusATAAq07O6x5Ae5ilZYDSHVYNAIDfCrAEA4rAxel6c+XSVVr/Z4RlgBG41SjX6tUBuHVHANDqdj72xb9dEmrO5DjnbwAGnbwHGElkMUPIXgXcIaSmCRKl7azxtKRorV3Fhr/dqcsQ9VV9rEb8vmVw2jJrLW5V7QRBIxISIIEkrbbTL+mbsgwzGBYAGOYqma5FTh0AbTIATwXYLpYuak/rP6w6XJm40M1Uhlca3rMYMAcODs4ZpkSvAmo034UGAKVaq/pWgLK/4rUxh5lhvEVgZJR63NR46/+1lpwLsAywJ6ZQAlxgjtqmK4s8CwFqSt4ADEREmR1+8C22aCUd9w+7VrfrkrWWudirzWV/cxGi9rOa/MTaNQY0rtUy5SL5R/kqQJJIjVWiFmIbsWT2AgsADHN9zNDOWGUAngrgFLMu/tPSk52rW9McM3cd/5susV7Xz3DqZZir4QqL++CVgjINAKxTASDz/YdsKgDkhzLLgZWNCZ08umTm0OxezPF/8088ywAOMKazVj13kBLgiJ/BRev/LO7/C955hBBArEkTpB/QlpbNcL/H8qdySP5l2IiqWrt3O2vAMdXyhY0/1UviVHsux6oatepOwmQVICDcjgDQC+5eMSNhAWBv7LCeY0ymWLdnPohqUwH4rQDNLKmMzLr4T78e5ZxszPRfudaq1TcrAeNxZHDOMFXGlMv2qQCJ/F+eCsDLAU2CO5XJhcwclNG85s8QuI3ZH1ubE7CC9X/xMh8AhJDOAMgEANPD3TT0WxzSa6sDdXf/n5YmIWFkmDPlh1VjaLmcvSQiEGA2CQBI0QZqTF78h1kYFgCY/bOBun8x5k+L1AGwbE00Fwpglmdw32JyX8JZ2ar1v3LRtVOblYAxsH2GmYmVi5YxFQDLe5Lt6nJAeI2T/ya842Gt9nxNduOt9bzhatSurIRMgBv9BGZ6KsbPBlZsCFpm+hbbE1r/1yvkAVAAsabE+k8EGgBstv4L9deI6g0tW0tw0dN/qpB707F5RSAkDemrgEE5X1e6Hj9mj7AAwDBMBzpOv8uPrU0FYA1gE5jmg2FTVZuOmbiLgy3fJg58aZwxIbOdgWGYgnydn/R/mwaQfuUWf35aEnjatLd4Kne+gKUd4YIxBqOf4EyXgZmObmLAkly2/tN0MV27QAsCkSveAIg4d5TcyOQJ6V4tta0LdKkHUZmNkZ6EQNmrgMl9AaAXuysnzDqwALArdlXJMXVGZvDgJYDb9zc0R/WpAMUrAZrPujYWMI90z3a786Dz1crmHf/ruGR65wkBfWFzDOMW0xbHylSAigYAxvo/ZPzoSNW6EXq32ub2AhM969fuC5eH8bjUT2BmpCYGLN/H6GL9nwY3yrMiVCAEISIiYDJ8zUaxszBoMOhgNbrmBJXM85Ay939CIJXO3nAUXvyHWQUWAJid40Zfwmnaksj4rbHhaTbop6v/Ny0H1BYoMw2Dy/9CpoTRBWCH1v8cxwzJbG3ojmNZxyzC9WR5XQOAov4s3gEA5eWAwL06dgbGi/oLlKMJPA8Gn38FZWBRuLG5KirTPuZ/mjpNMOozaXgTKABJiIRIAgER8tf3UlkGsD97ZHSYxydLcyAOzrAbWxkNuKXyKYnnYfYOACTpcP3obsyYvcMCAMNcHWNmAvSVAaoaAKRegcArAjnGMIv/OrmHlf9ThukW7hnd3YsRw2yPrla78nEO2foqGkBpK9MA8p2VmQHMRKzi/l+98DInMi04VC8wbUyfUX1WZx3AZes/lb/uBYWgtEBCTMerlSWAKPlQoXHXufLWrntJLx05OsnyEml0UJzMh75VgZM3wWwVFgAY5jqg/E+/U6z72mQAmwYAleWAoLZe8BUzXwIMXvxnUVPCUPZv/c9xb3jPMsBF3Ms0hunApcX9Ske2agBQnu1XzAxwvL5dlWGL/ywKW/8dhNub68Oe59ZiMOjRu3zSTq3/ACAAFAkkRI2IiNT+gNV/qjm+9R7vztFSdgmTWr9a93S/+ixHYrGd9jYIhQbUIDSIQIgecXSV/T1izLqwALAfuO/HtDBt8WiTAXpNBWANYDY2UCEMy/TJi8omyp6TFvfy4l5MFbbJMKvTrxBSbbu9emzVAIomPtt/JRrA4Ptbcsm+gZF0pNXuyVXUw052EtZl901w1xvsqQpceF7LL/td++GeC0WIAPkqQIaabYXKH7N3bFECRrWC/ZSIQA0AACAASURBVFr0XlfpJXIYDC8E9T5H6WuHgPM0z1NWEAoiQYAaMAAXBYBe9dJeHzFmRVgAYPbMvnt+3RmSDh3OaetZNEwFqC4ETPxKgFkY0reY0/1/8idxspKyrSLn6nCWLQ/MxnD1UbrIkIiPudmLBoTBGgBsrfqdmcktAtOnroPW/76Bb/Op78FmazZmGMMzvOG0i4/UlVTbKlv9H3XyDoBqP3eACX+Y+z92lQ26HNb+U/ejqfaty1wB6l9ai1OwYX/l+Mz6LzQIAjy7JwCw9Z9ZHRYAGGbPzD0QGKIB1HYlL1S65qkAK973yOuuEvMrtf4nOGxrdzhqq8HWGGZ25i5knTUA+x6bBgBwFVMBZgKtX/uXgX7J7471f0yY1zBtjVudORmZunNkzoRh9rX+77j+VoQIyXuAEUAgmXWH+elIezNp/lY9snbmhaA6XBcadtbPbYfMwwxngItn1VOPahtNpzSa/lMhAoVGoQEJUKOjMwAYZl1YANgJ3Nlj6ixTKto8+Wy/kW0toMLDgS0Coxkz4WOOtLfHZ6g1YZoYbr2MOTzCZxmAYaZk9MM+QW3RTQMoHVXWAOrb+14OqO9tuV5hDsijybN12gD33VA53ENg5mB8hq8zqchhFCLqZAkgQEIEQwIAACM1WpLF9lNT0wipNt5o3LeNnIe3oB2N+5XtAf77Bj1iWpIWWg+rSgIEgnTyGgARogocK7bs/s+4AAsADLNPFu7895oKwBrAfAzpW3SZuNl6xfYpqRPC1v8Ct0f4bsduUTgpmPVZoBTWNQDzR8pWUICizeDlgBLGWARmn8O3uuP/rAVjxzIANzxXxpiyPMz6v+86W2bu/8mrgAERSNsObEnyLjZ68/T6YQOk5LpaMGBQ3V6Omn616gRk3exw4gAIkiWA0pcAYwAyGBfitLD1n3EEFgCY3XLNXd9V7r1XF+OyBgDX1fotr3pM2JtvNPp0v3YH8JrKQyfcNl64HbtFYVPMVbBUNluuM9WlW8IZZkMoezc2agCww0Z/jja9zc1iaATaDlvX+r9YYdhrW8UNz/XRtyx3fMiuzfoPyQwAUwOg5B12ZLvxyqI0hOmejr78TViN+E2iQvueXu0rNXy17m9y1a9oAFT5De1HWs9timT9AwBAAkkLEoIAQ5TnbRbVTUaa2Q4sADDM3lixw9/YqenT2ymO5akAPRnS6Z+ouNR7l44OPPdXotwe5O/VtMIwE7CkZtDxWk2HDWjBwTLQr/9EBAg9DSM7YnJT3bA4VAMffLFJYrlKGajYyhhmHhao9S/ahseEZt2zPxSioJIAgMXQNP3gZbt/xV5P5U5xLSEJbL8SANrKjVUMaPH971IQmqz/LXsMUpXEdiRWZYDaMVSOoik8EABRkcItcSAioQk1JTMARNgW3UXhhoVxBxYA9gDXKQ6xtjFusovPfyOWSQBQWBAgnwpwDT1NABh9ryOzaxJJoNOpK7r/77UsrV3tXIRlAOeziHGYLqVnwhJWt4ROVXM2mCOScT+i7ZhdM1mrPZpSqq9o/Xch6zfYXDU+/Vff8Fx9AgBM/VS58IwugAKUWiAka/9j1dpvo2bmNw30Vvd/6xgY2prJNvf/1ktUjfLW61Z+afHEp/wvpbMiqNhhPauIgKmg1I8sXahPdyBRCIBAEAoioUEEILuePTO9aqErecSYFWEBgGGYKek1eG/RAIr/12QOWIbJ3f/nhq3/l9mCzYKH4gwzCQMepR6TAMxzpqa0EBCU/f12pwFMeB8LpAeNbGpHRtG1HN9Ck9oJbnen5qpSFFu/7hiFoEAkr/9NZgAAVX3Pyz7p9U/TTAyzGawUpZbJG1b9vMnH33JiOejmImzx369Y55vOtRrx2x32Kwd0vJB5YmlOQGr610IjEmKAojWEhVi728UwVVgAYJj9MHGXdNpObm9lwPhfd4lgynTPqJVTca3LX0nhcX5guhujygCczxzGYaaYBLBYCSy19rWm/6o0gBYWaLV7pKLZ4+rLzkz/JvuotfdxFztiKxlytdZ/AFCIEoUgRBKChCAV0xkA6kbnZnIZwLDLU7ZRt8mDGWRl3Fux7Nft+3XTf+UY2+Uao239Whc/8p31pKgHQrUoVNKQoGiI8q9VK78hulhiTiQ0ICEmGwE4IQB056oeMWZFWABg9skmula9GdNndLW/aZ8EADUNAMqdmZ0y7BadzFgbg7JvAvf/vRebEq4+6SZbiCOzdxwphUOjYT/vYmhu3HVLS7czDaDpJsZnwmRpY/ML7RH4ipMGlmEfqrUbDz6zITbxdM7HDcI9+l9JKpJSK0ESNZjG6Evu/00me2Nwa28f6nWw1abf/JVajoeWtrdlJ2G+XE+7xd/qzm8cQ1b5hGqn10PLIpL9YEv80gwALdCFNwBzvcs4CAsADLMTZmljJhwzNLfCjcP8q9QA+tIrf6q+F0vCvv+LsYWh/j6MKn3ZQs4wrjJF6UHovxDQHLTaMUoaAOywDh/earcc1DdPW8PtmvCDs2ZzedraYrnwSDHMrGzukR3Pg/AkSEVKkZJaCVKaYpuhv4lcBqhqAAjQXKc0meytikKDulDasIZ5kbKBnuo78691i7+5cfFyTadXAil9miYBECKBIERNGIEMpllAdjjTt/UMMwUsAGwe7nQyMGsxWGZk0+ImV9cAgNvJIfRYCmDyHB+aX2M7b1dbTjZikNhINBmmG8sW6GGTAFbQAGzte3VfkwbQcPqGGBP9fp74tjytXr1PVC7E/Hqs/zmbFq65uXUJx3Nju8/ohDygPKB6ISlJSlJCKw2R1R7d8DHnt2eWeoJ0n3XyO1W26q0gASCW6uaLGkA9tHasVv76Rv2vdQOKBKk2+cmEAON4hNrKQk0vXy4OMD8alCahSWjCEGSwnYK8mYgyu4AFAIaZGme7dYMj1udEext2qWVrG2fWNYALJ2ybXnc2V0GbtgyvZSbYaQnpirMVUZmNRHMyru1+mct0LxMdj+yoAcDUZREr/1upN3U1DQBwh8J/9yTvfbsNJ4zpLjUm/LAQd5GDG5YBuPlhOlB/TPfx4PblXok7PPyEF0lKkhKk6u8BbnVyL5SA7KuxQVA+EWtf80CgFgLYv1L9QvWgoCE/63dB9e1sLaCOGkA5zIvr/5A1QDDOKk6vLwFEALFUEXgRqJjko/CfxJoll+taxllYAGB2yJ7r3JHd9zEaAFw+d5j1/zKGBlCEt18NoCO9cvJiUs2VnGvl0XWXjZSN2CnYKMEwXSk/LY3PThcNoMNh/SLWk8uNDgGhccxmG31auzIemXJVGWBAWNvMuDYca7cci45DzDSpda+pzdb/HF/APfp/glAgMw1AZob7iiG7bqE2a/18zZ8m0zzkP2FpD9SONzcqf8G2Abbq/2LhrYsc2UZVtzAPsJr+6xZ/aDisfmlrqjZ+tPBi8GJQEagQ5Dflx5fucz6mHaEzzLSwAMAwm2e5bmjdjaDj8R0oTfm3/FydSFlEg1vOSwxJofHmitH++8MD4CJhsoWh6kakimnYQoZcB+7kRK+YTDQPoDgMRqdDe5Xb/GvVMmGzVBDsZzmg7jh4l2mHi63/OVtst9yp9K4eB7Nir0/qYB6EUiglSFWsAhQ2GKBNKLP71w3xdbM+VPdQbY/dvp/o4y0aQP1C7TncZNlv2ah/tcoAANiUYnUZoClVKxGrCQCgYlIRqAjkWfpfpWy9WVfgh45ZHhYAGGbb9O4+TtLlrHgtVPYNbc0uDO3LGkBxnd1ZBLrcUC9j0fDzR04ZGcy+MnR9HBxo2thINBmmmWUKMZZa3sYLthooSycONmWOdCzHWntna/92sBzQ6v6A03SUsH/yby2nerNFGYBhurH7x7ede0/cnP1nOEtQUitJSlPQYP0nY1GayiA5rzUrG1BzrKuPri0zAKh4DUDlb3YM1U80w+xCxXZf2Um1v5WNyjalWn4Rm5KJn4q1/uthlj5kpDOW9kMsUvf/iOR3dfiqROebnZixI3SGmRkWALYNdzgdZVYTgBH4wItMG70lm69yt6r4tjsNoJ2ZCpc9FXsNbifJBRYP5mAjxvWNRHMsV3KbTAvVMjBfmWgO2RKHhC4xaahpJ6iArU3RXpYDWpGx0km593U5KGcyaIn6dkN1+oaiunecyor68+rME7wa9x7eo/8NpQQpQQlSoMHqft78sTrpw4VJAKVZ0OaJpRkADYEjGFotGmH29y4jY5/V3J//rWxYg6pIJmD7Wgmz6ydZ/ycCLwIZgfwmV1v/x50nmmGaYAGAYa6SqXudU3UTLw/qr0MDmOpuJkuSdsPQhCnP1v/5cGqs2cxGoskwU9Or6HecBHDpCPsvy1aklvauoQks7d5Xo5/T8Z4G3/3AExuyo+GXfWZNCwhA3HRdE5zb1wAC3EtPgpAgJch0EgDENht0hcQt3XRsz4pM8are/CLJH6NCpcoBZNso/mZrAQGWjoFaJd29zJJt+6L1v34YAZAxhwAsrwLG3Je/EmaaCvU5gfVPTCpz/1cvwvuq1rFw9qoTrqyRZByCBQCG2SQT9Dun671O24axBtBOLwPRgMDX9Olj6//cbGTM2mvayUbZSFbsnTmyYbGsHSoY1H+B+aLcXi1TYe64XH9T6ta4Yw1gmbvpl2yXDrWEtm4/YcWKddWmq0d9cGXNj8u360jc2P2/iXtP+S/qjFKCSiYBEEVQXn/G3M4+aNuGcoYb9UWjDl+x5nedAQBUlwGgJVeNpri8u9Tcmgc0fbVZ8Km0xziFAPIXC1MtEHO/5WMsBwRaeJkGIH94x69ytfV/OsLPF7MiLAAwe8OFjtTcTOZn5EjHswZrANBwKwtk12pJuBXrf5cLOflYpbj61NfZTkwZZiL6FvpeBsfWgyd53MZUw9Wmp6kJ3LsGsAymcaiNbmlbCm3y7OgbYOX45VsRbrqYjcN1as69J+7g8AQvEqQAKUjFumrIJrsGAIap2ly1P6FSTTRVWxUZwNyo/60cA7WavnvFRMb/FqM/lfe3WfBrSVQckDXsQz6Urv+Trv4fgfzu+9EaJZhrfWYrsADAMPMw2wBg4lBdHaj0HdTvUgOoMMAu1PazU/nuuPV/jAHCqXROcC33m9lOTBnGoEPBbfx9QKFHQOp8UutUgOk7GH3o1HrvVwPoexPj7/uCDDAg+ybMiKmCGmD7muqiLrde3Lg6w+pZsYvqcy6OEu48/0soJUqJSoKMKPEuTwzQuuzsb3wo248VQ7npoQ+lyqLICcwt75j+ULHvg/G1LgyUr9WmNJjUiyE1bFj/2jaMxX+oOhUgOxIr59Y/+V3ZD9CkIkpeAKDOK63/M+UInWFmhgUAhtkSRQMzYYdxXFDzNWMXBre1n4vOzl7MAYPvY7xRYNH0czaz+kfMMup30xCw+oizM9uJaW92fGtbwsFsGKQB9DiluVIakxjV+nJQxd7dsr8zDWDF6NsTb0CEcIp8mC8hlm+LHaxbmOnYa/ZuvCqdnntPKRQKpAQpQAnwtA4AMPkQEKYyQF0JAADKVr0HMBbrBwBLlVQtTxfd/ysHV8QAI0TqI4RmdvrM5l45sd36D6U9Vos/lHdSJYR2McAiD8TCi0HFIMMtrP/DzxezOiwAbJhddjuYFmbM8RHd2MnG3bZQBgRO+9IAchZ+3pdLv/GXWWnBgZajGo0pLf385dnO4HU7MWWYjMVLLaLhU9jpBACwRHIaM+mIavly62NYJvahAQyO+FQ3bVqMBpKdPDxKc2Zf8TjOJgPYn3iXWy+X43ZlcFa4zL0nj8J/gbMCzwM/Jj+ks/Ey2+ZJAKX9+XZCU543WfbrG01/K4E0VXnNkgDV99ft++bf+leLEb+8yA/0Odj86GQjX3ZJC1W8ARjkN88PF+8JdH94N9tJYXYFCwAMMxuTduhmH1es66fc0cuvG3vSAKh/Pk9100ukn2vW/9bQJriU8ZRVQlvnydvOoHM7Me3HXu9rYziYDcOiNKAdn0gGwMYvQ+je+uyinXeFwmLUN03Lx/fOlOWzcMke74LXcrAmcwROmSa4/rzIKx9/9U9PwUsEXgx+DAdNgaZiEoBdCTAc/22rAFHxLd0D2QFo7Egc5BFSH/4WDcAMypR0K0pAjvWBoHLtTQ0bLX9z9YAQCchiys9t94RgMfSTsY1V0z8aGkASVEzZCwBABcL75i9t2+SKhdkcLAAwjOu0NS2Td2lXkQEudT+HaQCQzGK8sr5tj9vtUHjmTT+nrP/NQU2fAraUN6+y6PO3nWHxdmLKOMcmBbbBpw84scEXsF9IA+rKKbT/wtSxzRZ/ZJSnv2ksWY+6HF+na6zWza+FZQAHGzA3Y3WVcFY4CwK8Pxy+PPtRFEUQRuDHcAh0ULZNY/am34rJG6tigMVeD5bMp/xXqmygvaQMmATQBNm22zWA+ldqmJNopA+Vj28TA7TxN9nQABoQYlDJ6v8hyB/e4Zvn7vo/G+yeMPuEBQBmVzjXeRrdoVvNbLHMtTs3hsOGuNu0BlRZt1TPkoaThDhVtBrCmbfktD5iS4sB2xl3biemDAMAS2sAxeGDG/Fa7XM5JJyrtmxrfWq/bbS5nyTOU9572fnzcrCDJRx3cmu6duVCSNyA7Y5ps5QLiLO8uxGfn29fwsBHP8JDDIEGP9aXJgGULONk/JTXf13y3KIBGKG1TwJouUSD7G/ZaTX9Q/nu0q8EuVO/eYw1Nah2SuPHcPzXhQyAFOExwkOEXrb+zyFYtmXp/sC60+IxDAsADOMoXRuV+TqM7X2DqQLvzEANgPpPZt8svW+0W+GZ2LDivPV/ufLSIf1nfQp7xcQRthPTruzvjjaJs9kwRo8fLz90iMXwCrPDmb1aH7rKaX8J09x3LQiy7+5KY6zWyKO2p2FJrxc36xmGybjWSvQC74+HL8+HSEQ+eDEcNCSrAJku6qYSYDV5awJh+O8nkwMaroeVL8VaQM1xrP9q3QNZlCqYygSU5xlYNYCq9R8AyosdldfzMRMEwbbfVAXMiRTatqEJRISHEA8B+oHwntTh78Oihk2uy5mNwgIAw8zMoO5+vzPmHlFcckju2lkc3aMcqAFwX7aFJTUAp7JhXdO/ecluD+/s0wK2Y5jYTkwZZqLyOjiQSS2bdcvBrNb//Fr9NIBB0VmFaaM69t4H+PIPkHBczp5lZICZG7DewXOD6hKO5Ma2KtJleHsr3j3dvITnSPgRhDH5MfmaAixNAijJAIYvvLmhAUS23cyFctDF/b++/3K45QPq23UloCwDUOWWG4z7hjBw0f2/bvpPvkbqmAgAofAD9D7d3H45Lrf+T6/nlJ8mxilYAGAY5xjS+Vusz9jQiC3maD+kV0rFu5MYC501ABichtMm/fjQaiGsWTb6P7xzTQtwZOjZge3ElNkOs06nW1EDgOktm2MrzDkr3K1M+5sjjqOa6Ush780JoIkFWhduwNZgplSfPNiFS0fTo80aQJ13p8OXl0MURZHwYjzEEIQUlNepT2UARDT2g8VQXvytYy0CTWb99rM6/lrBqgHUTf/F32ycfdn6b3zyw3TTMdS0+A+QFip1/xf+Gf3vx5uPN37nG1wUfo4Y12ABgGHmp0+zO7zbt+6IYsH2Le0B9b/ivruzY13/uhWe3mk4eYqPDNARx/96DAY9vHMpAVtgTyaUPd0LY2cqDQAckgEG0r3CzY7s2+7su62/SJ7DPRKhmy9/6cC+czgcyJKuT+GIJ2WBSzDMMlx5RVrnzZ14+/P2OThHwo8xjOGgKdB0zrz+jUkAJVVApBsk0mce84XvDQgu1AvVicDd3f8H9D9aNIDib2r3x8Kaj9l7fRv9+rGLNqBr27q8oWO8ifAYCj9APxDq083Nd3+50so1N7NpWABgGIcY26KsakZatKeIAy9XHcEyOZ2Ho11Tfo5Untr671BJGGcOmMyYsClT9KYiy2yBWYuUEfioi4yM5IqWxxEV7s40gGXiVsnkFROEZntT9Ixcz1QAR6KxWbY+CaAFxyvS5Xl352eTAHyFfgx+oM+p7dtY/4dAZ+mWWf/NDUq+agBhJO+lbC9+rB/ZJAMY25Xfk/3VnZaL1QWArFRkdv/M6J99zO3apySN1K38ViWgsv4PxcKP0pV//EB4X2/v/rj12pJuUno9m/z4MA7CAgCzHxzpLdnp3qzPfKFZWci8PtQrMIdlgEa62YbaEnC+ZB0TspuO/3XGPb/TTAhwZ+jJMMuzlAYwNhyYQgYYGciAy404pdTitwdIhXeli/X8SnTP6pZEowHu/NvNgwXUshkqHG7Dd4A7mcgagMmvt+Ltj9uX8BwJLxZ+jMnbgM9A5RkAAAAIWF8TH2x/obY9jI6lJmkda5fO/mTVHmHlgJoSkP1tEwDy1/mSfVqAhtp+AkLUABqIKjMACAiRIjyG4hAk6/8I79PN8UUOSK7Z4QeHcRMWABhmKZrb5Yk7eWvPLJ63s1gOeswInzu1jXSzDRUJuEA6XoP1P2GKYd9Y4547Q89LbCemF9jNjTAXmDCnJ2nrZ1UCBtezDScOaLUdbOhdi0+KkVLtidYvSUd7bEzLkOev5zm9L8G1/1JsK6UXi+3FZ9ORh9cR3t77fz0fQ5FOAtDFJIB8BoBO/xb5JwDIdPwHIEAwctjmp99Opw7AxbkCJmT9muvphrBuRj7x6882agIAgHXZH50liGHiRyqc/YlKPxkbkThE4hCKQyj8QHh/3d/9cbucPXMSHZ1h1oUFAIZZmbm6d2tPBZi+5WsOcfDl9uQhuHCa5xftcNTs0eh7rus5Pt3DO9xCuJ2B8nZiymyEuYvUtOFPJfmb1WKH0JoO6e0e3h4T23V3oAE4R27AgfQFS9NoADWPja1mxLbqhC1GYOPMlH7uZMuGH96p+fVWvLm5ec4mAWhx0HEQ63Nu+icAY80zhOJNttk7AJKvlIsBAKAxFQ/yTwuGKb4f7QWq/hM1bBgNht3oX/5KVP41/VQnBFT0ANv6P4QY4yFM1//xntTh490xXqpoOvIwMsxIWABgmAWptbzztiWrTgWY0jTcIZQxl+N+7UjmlQGm9iHdRl5POuwb6OPrztDzEtuJKbMRFrD3gZMygBlaM11MCANr2i7Nff8F5bmVb6Hq9kkTaQBzpPhFs9h8zN2jnrrR5zZxH7iTlVyL5ry98/96PEQijIUfo6/FgXQElLj8J4mUeMgjgs5OMl8GUBED3KHaGjRs1E3/+VerHpB90jWRKob+ipW/+IvZBoFGJACKxSmz/vtn4X25v/18EjOmx1D4SWFchgUAhlkWoyu3UJu/aRmg55n27mk3/WAHrfW6d7HKFIS+J24pl2cY9q1aGTAXcGecz8xKkcuTZ/kiT3jH4Ie0/p2PvtDc2H7bRys/Oe25OXmiDQmw+wmVI9dzyebK3Fnmy5othpzT8cGc191nO/xyK369SycBaHkkHQPqKH4svQCg9De3bpcnAYCg1PdfWLKAKl86DmzKqwn1Kz01AQAr+5OQ01soVgRq0gAqi/8U1n9jDyavByg0AEzd/80PAWmNMsJi8Z/Hw+nT/aH7vY2keyryA8I4DgsADLM4CECLjw0G+gBPQ+8u44jGc3D3lPu145nSWDD1sj8jg1yHeYZ9PYyE2zFjbCemzEZYpkjNYbKfs7nvG2TXhnWwkbcPrAGYtE3jyCYBgDXRWn6qHTOcCUOg6u5RT8amDb3OXp1pxanM4YoUAN7e+d+fTjqKISYQGoQG1HH8s0EASBDGWkDmXzA2LkJGvyGbq2U/AGy/Xgy8/JUq++2+/2UlgCB95S/UFv8pefoTaIR85R8qL/5j+UTqFOIhRD8QfiC8P+9vvh4WKonuPH0MMx4WABhmBWjFrtx6SsAFQ8CkjXjR/ZlkDsF2WD3+0+gobP3Pma2u6Gp4dGrc2cp2YspshMWK1EwXmlpdGBxSW7vQq2oumzUGBMhKP3TLR1MD6M2lLFh67aDJZbYOAQ58prkZ2zKz5t7cRaPX8GH1scbq/HIr/vn6LgwjiDXFmkQMIgYd6/iMAAAIgJCu/5MlFWL2HmBCAEMDQHOj5wQ286fx2VITAEob1r+lDaqu7A9lu3/F0F/6m80DqHxiAA2oI3kKxSkUfigPofS+nW4/P3jjbrYrvZ67K38umE3AAgDDLE3akKzeyzfbqAVjUgy/Z24kCQD6rxQM2+/XuhD/UXGYwfq/beasKzoZRlavrDqznZja2Xr8d8iSGgCMvZb97IlU//HJkIcwTJ6vHz+4oVm3lVz96gNO6WJ8moBZ08V4xKZ5rOfT7aYIlluT/eFUnrKY+o9f1Dl4+N9RTJogjpNJABFooqi8FpCmZDyaWf9Ljv9kzABAANAEiCCgMtWKjO3SBrR9RSoH015hlwsXUW1/k+k/+4r5/qaPfa3/5K/N+p9+tPBDeQrlKVCnxP3/88Ppp+dc6XMuQgxjgwUAZie40yVqpxRPd7py1iZrqrg1ODO0XHnCiw7roW69X7thDWAe6//qqTGWmeuKy7ZHdyorhunJ2JI7aeG/LLZdPmgoI5SAKWOEg1qHhhOqQXUOd3Ur/PJX75uJFxYCamLYjS2WHNM+Ym5rAIyVTfvpzxr+gHpp68Olkfz+xg+Chz+0plhDrEFoFDqMHoEqLwOAciJZHP8BNBAaKwKh8Umo58+lPVTek36tRMZaoC5a/82/lGoUVOyp2/3R2LYd0Gz9l14kb0Jxc1anszyelf/p4eHzw0I2TK6Jmf3BAgDDLIelFXG5l79Ih24WJcDmJzjgEpvu125PAxjp3blj63/C/HXFrLbHxXC5TmW2ysKlaiKf/cvhd7vENLEoV8T9mtfW44qWomdd39jE1PeOTIK6iWZxprxmzcQ0oHM1TLaZjAmf6OagRl1klZaMm88puDYNYPBZO8BT8Puvx3N491esQWvSGuIYMI70TyCdOOFnx5a/ETa8AyDXA7KT0hfxUrlnUPf3t+60Hg+XShDVti9OAqiY/sFm69eWbdQEEcVxjwAAIABJREFUGslu+ifQIGQsT6E8ndXxrA5nz//y8PDvt8dQtt7BRPR60K7zEWC2CAsADLMQja0Id7gBYBIl4NKZE/Rre9pN1mVLPfJ5HP/3xiJ1ReNFtlNTbSemzHZYRR/rc9GBUbtkFhhlwbxEJxmgWzjDFqyvRqApkDG5Xw8zMefQQm3XmDI7cBJAd9ZrvhENd9ixYTk6D4Cbwia2njKzNkeDx0pwTf3xnNtb/Ocvt2EY/dBEsQatUWvQOtbPAJAkCQKm7wMo8sx4D3AhBmC+EFDq+0+2eQCU57/V/Z+MAlLxJriYP9Tw1e7+T/nrf7GuAWQfqpn+0dAAqGkGQAygCTGWN5G8OctToI5ndfh+f/eftzdPS737tzvORYhhmmEBgGGW4EIvbR+etxMxpBPZ+eiBUwHKfhvVi67iGWrSHIFlNIAW8eZyBEbG79Lpo4LvfvJiZWApDQCs19nOoHk7Ma2y3ZhfBeOyZ6yNfoGSUfbsXoYenvhNDFtWyIxAl5MHdNVagsXZNYBJcrCLBlDs73U/LphMpqpwG8oG1+fXyTL5Pt9VBlen1ykD/PKLOAd3QRhSnEwC0BDrEGKtgywx0iQpJ2xlIaD8rQDpDACbIb++r939v74fmmvxpj1NkwCMDartaZkBQOYrf63Wfw2gCUmrm0idQnkM1PFFHR5vbv/96923m4XKV/eH69oKPLN1WABgGGfggYJBpwkBQ5vcHj3UsjWk0UixTMYNck6cVQPoct9tEXDQ+j8sSgtb6BYpb/ZixdUUc+Ws+Ag01zOTx2jhW+zqiW9lpIs69jy3ewG4GGKiW8yT1o7X04TrG03SnJzQBWfyyoEbXKaV+fzHxpjyl/E9cooP770gePWfOH0ZAOmYdBxpDRSnR1Apl9IkQkQypgKkYgDU3gFQmQdgtf43uf/Xc/JieaHadtNUAOtG5ZPa/Q2LPwEWpn+kqgxAoBF1LG8jeRPJ01mdzurwfDj99+3dlwcBi8D1LrNjWABgmNnp0YrwVIAaVSVguk5lW++24SqNpyyQcR1MCU1xmKkvbnUX6WFDGcm0tzRVaMsoAQsaBbZrf9huzBnXWb2lnrmeWevOhhiFyyf0a+wGn9ulAHSOR3rX0yX69GpQr0kAXXDQOjjhVACXNIDeZ19Nwzn3jS6ZkLNOBYBBz+u1TQVAhA/vD0Fw/zl9GUAMyUJA6QuBc8rGcUrM2fV3AhsfQsCqDGCXramsAWC+WBB0zhBq+EppgMXOLqZ/m/t/tvIP2h3/42QjlqdIniKVrvzz4h0+vn/44xcX7ZbXU8iZ3eDig8Qwe2JIn2x144JrZD0dmKGhLXWguoXeJgPMlGvdb7uh8EyrAbTcpfVC1Z2LWP/7zvCYmLmf4mU1ADCvth0DwXZiymwQF4qX0ThOFZl17mmAF3+zTn85hDHnmoE0JVbPliXxQx8vAyyTd5QYgnZh9sA5mrZyOBOE6kJVw/RnHxoA1D2x5j9xixwO8OH9TRBGX7OXAUAco9Zx9FzWAKwgZtZ/hJqTO1W+FLMBstXvKjUypb9Y9qeXawrdtj8z8VP5a6Pp39xpe/FvYe6nquN/av33Y3kTqVOojmd1PCv/0/tX/37jtaXfpHR/lNoLdr1Nt4d8DY8H4xIsADDMjIzqjV25DNDqg9/8+xCKsXfPsxaaCjDgVm3RmGrAPuzmKOurToA7gXS8ynyy0IL1wyyGEobZNH0e8NmfmC4j+kss/Vjb6uFOYn/rz22N3aWav5+rwaQ1ISWOnU7mnTkJYCwuGzumarK5iWQWZ4FCN0YJcPm5n4r7e3z/7i4IouxtwCQIIxI6eiKKK6sAJWSDoyR5EEAAtaz/g0ZC5koA1f5CeQNqyd9eUqhtI7dqk8Xuj22+/wSos3cC1z6YbsTCi+VNJE9h4v7v+X++ffWftwdaqgCNfIjahXx7C0O1IxhmTlgAYBi3uR4ZYIDH3PiG0jh/QICNp0zYDR9zh7XCM7IL3vGeGq+yoOG+7ajle1ezTg1ZVgOArdVGbIdhZmfVB+OyQ1nniC10B51n2o1pR9oU+jFXbwpzogE85YagPpmxTMZdXAioU6KVO10u2DosbcQkzYYRCE8CcJMFEnXhfFusLRpQ4TnyvM/N2zcyCO7/898osf4LEoJETBhHz0QhAhTWcCLAfLuyFlD2oZr1HxEAs8q42GjQAKBWKJoyoV5qrDJA/r7f8o2YX8G4r4oMQIbvPxVGfwCd/KSlF8tTpE6hdwy9w9nz/3798N/3x3Apg2WvZwfrX7ud3/aczuHnyDBlWABgmLmYsgdm6v27YYq2baAM0HzCgB7qjFMBJrSYZzEZ1gWfoNzhwEtXAlnz9PGXnuP5XdwukF6Q7REMY+KsPtZNDJgx4kMr3mqT0d9LoDijfxz6NVjTNS6ZQ+jlLHGwrO2HqTWAFXEjFtfL8um/8BW7iwHTeG45z4f3fhy//vzpmyAUhJIwIoxIxPRM8bl0qMUVXFxw/y9JApVJAGD7W0n1LkWDatvWv1TbNiWB0odAI9R8/6n0VXvHWJ1idQrVKVTHwDt8v7//4x83TwcXi8xg6795StsZrAQws8ECAMNsCmdNDBeZswHr1Er2cf3rc3h6SqOvoiOZZcSkl11jWPRHWl7sdA5njOPnvLhTHsaxrfvYVmxhgxFmChpG2TNlaO9grW7b0zJpZVtYwwefPtIff/jZo6/bWhGsUkWUJgHYFgVaIcWm6BXbU3pSDYAnAbjJMom6ew0gp4sYsHsZQEr4X/88+PKXj5++vyBKQkkoSMTJVID4ObOPl8HE+p9pAGR7GQCAYfTPrfzmRjYJADFzzK/NALD7BBjxwWxPydm//tdi/SdzFSAsvmJh7q/JAKgBIPZuY3WKvGPkHSP/GHqHx5vbPz7cfr9ZrqR0f2Sq1v+hD1un55SVAGZqWADYMNwVdJl5s8b9CQErtVKWbuUIw0Gvs9s0ABiUU5OnoRGTLnc3snBZE2S4dWBMaixeGtsq5zkq7jUag8Q+ZVvRlGEcYrUSuhW1HgFcjiaOsBZltzamBVhRA4CGS6+bWaNeBjBeMGg6eiZla0fzAHqwuQgzNlbPxna75VpV60IgfPjN8+Trjx/lT0CBQoCIAKNECYieEJLXAhvmckqt/wSE6TwAaJgHAACUTQWg0pG5YF4smY+laAE1Fwsq/6dif6oEGAIAlq3/ZFv2p5gHYLP7547/KMk7ae8UeafIO4b+IfL9p7ubjx/u/35tlUBmYfDDMtj635su8hrDdIAFAIbZMnYZf6UIOANB1cVhTFB9neWnmQowX8Jemgowdznqm6Rj3D/nLp8twbe5Pu1FAwAwfIzcZvWRMHOlzKnWTxWki4/GSEtx+dCNagDgZtZcYpbkGhBiTwWusY2YrvFYcRIAt4Crs0oWOJLvTXXCzjUAgF/fS08+/PGH/PH9hwAUgIJEDOkrAYC0eXBm9zfnAaSzAdCuAdh8/9MxBhhJWxEA2iHbV5vjf33d/8LcD7nRP1v2x7T+l5QArfzM+n8M/WPkH0Lff3x99+nD7ddflrP+96KUoKMfsIEPaeWcfT9IzNSwAMAw07NOf2smV7ENNSq1Mf//z96bNktqM/u+mRJUrbHb7sFtP94n4kTc7/+B7qtzYx97u93toYe1VhWT8r5gKAGCYhAgIH9RUYtiMQghlNI/paS2bgg2pwK40BKHqg9gOuw07nseAg1LNhl5VMz/rEI374I75Zph3MX9eXuO0FzDdjIoDVus1wfgIMUkAMNsgFyDspld4+f/jXzoxhs5NpMOM9vN2bkPAJqHHG24dn18K6T38OkP8eXfb6kPIAYU+TwAoERTz7Gi/hMITJcx0/0zTwAZHQBQ8wdA+f53KQtdHABQFvr1DwCmb/rVB/6bZgCgIlDKuyXvVvm38eEmPtxEh2N0OHz96dWnDzcvM0b+gT7PiF313xr6TWaYa7ADYN04YtcZd9mDJWi9RotuADs+AOjw0G7orjVNMuh6iS6N/bd7SPt64FL2AAFbphQ7A5tLZilKBW/xeXtlHEgCAHStXttsx7Uj7N0HMM0YkWmzxdahuzW92myEJR/AgpMAGBfY+d1rcQOsu3Zt5e41/uLf+VL8/ddXBEQQIv9W0YlUlKrk+fB/yoYIocgzJo32U3k5MFyZCpBCer5i8VWCaj+w8r/yywAQGhwAAKDyeQB16T9zAKRvAyZA5T8o/1YdbpJDOvb/ENzd/vvz46cPh3heeXLYI2lR/bdZLbAngOkAOwAYxjJ7btvNR0/DZsUNYMcHANdM/Tw2e85eyIxNewT755oq7Vhqadu5G8t1LnferWWYIYxwBmzhcRtUtxrsSefjrMwHYOsetyR6qPm5jP1vyJTueXVlwIRFOlipaX0Ai7Ly5E/LnJkz/41w7dY3DQyC7YqWxzv88L9vPU98/vNrOgkAUSSACQiKzxQH+VQALfhPNiegGg4onwoA1zwBUF7Il68UBSpvYxr7nzkDjDMAmqR/7ScqEl7i35J/mxxuk+MxPtxEh8Pp1f0/vzz89V4Oyd+5uGSrU0+UEfYEMM2wA4BhmJUw2oaNb1/2OkLbxk297i3a6eGTAAbkhtUMnPxuaN0y+xMC5sTW2EaG2S1LzAxY4IG1UavSCF/vyGbAfD4AK/emY1oH1d2XQECV81jJo4lyeaS/faSR40kAzEK4VmR2OBXA8+Gn/32U3o9///HtjCgQY0CBgtBT6FMcQBLkg/2povtnrwdAKk8CQABEgn4OgCuQ6WflOx27VHMAtEX+IcBsWckb8m6Vf5McbtXxJjoe48Ph6e3j3/+5/fZqgaD/3Z+LNan/OuwJYGqwA2D12Bw9umZca9wwdpjAXFlxA8wxFWDn9L9D1WDEs57c8qmHl4tFCxWXaIaxwJVJ+mtjgqHcKxvL35c51X9942HnHT0JYG5aDdUVK2bDB8A4yLYnASx10naGDw9aJ4jw7n/50n/9z+/yBVAAKvSU8JU4KDwQHigOgCLM1HOsfhNS/hoAMrwZGEwOgJZlI50dAJdX/l4d/q8AiEiB9GN5R95RHW6T44063sSHQ3Q8fv3l1d+/HM83C9z2IU/ENE/RHI8newKYHHYAMIxNXGtdrZJZLNP14fnXdkfoer+v+AAWYd6SanhVoPVWvqVjzX1DGhp96/Ps5hfiYCezwOW0MVvFTpErV0xk47gTPgvTVaPakZfyAUyuT82v/g9lqkkAMyR+jA+AYUbDPoAUY19s/Dgtl/nxZ0/6r/75Tbx8FUp4iUx9AD4JX6FPSUBxACoBQMwnAeSvB6gO/zd5AgCHTwIwqv/FQqHy68vFmkbpH0CR9Mm7I+9A3lF5h+RwVMdjfDwG97df/vP49y++WmDof79nwZiba2XbDxjTAXYAbAQHjTrD9GN+U4SjjCAZh5w3PIcODWlZqKYw+gAa6ZtZK1X/i7M23JSBboCl7cHS52eYjC2Xw6baahFVaaFTbLAbO/72jVTeh04CIDSc2bJjwC6DL3b0JACk0fd5UBrYNLczc/7w7SjY21SAV2/F4fj47dPx+9/P4fNZSY+kn00FiLKpAJSc8xcD5JMAyOAAKH1Qnw0AWpV8zQdQjCmo1tfaN7Wo/6ax/0gARMIj7w78TPpX/kF5vvL95Ob48sP9l1/vv7xzOuh/yiUft/TEbrD9xHSFHQAMwyzHzIanWUSwlhDjOBbrZ9kKdvIEi6+Rx3CU9c0G4J4twyxFrxF+dg9unW6nG2hHmuIIdciiqay5C3enZ93d6Nrvk0elbd0wxleyYfMWbvMX6AbzZ7OzN7bJBwCuVAmWuXnAm4fj4w/+t8/B97+fk1OgpKekr6RHoU/CpyifDQBgGP5fcQbgZaX2gvbiXcEpTaPYyz3VUgGpKf6k/cSKS6D01l9CT3lH8A/gHZSfqv++8v3k4Cvfe3r3+OW/bl8eF7u33Z+CHuq/vsFaSu2GnzGmGXYAbAdnjfp+2Hn+97h8k6WZyvp0VhAGpOF6D7f4N3XbflKWLqBdJwH0yiMbGbp8y6dD9d3PDbCIPSif1E2T5GaqKrifQmZBRhWPvLJzuoz1r5Gv2Nbm/xl2xPK/52HZgf/jyCx7x0kATtFqDyY0FlYmATDMIJZpBXXQRps6Yk7XIeO4fSNuf7x9+PPw/fP56Z8n5Xkq9JT0KfJJ+BT7lL4fWIWpvl+L+9/gD8hA7du4kELNPysOAH25Picgl/6ll3gH8I7gHeBwVJ6v/EL9985vHr6/u3l6KxPfQgYOY0j5b9qnff1aCu66UsuMhh0Am2IV0gazdoaXsVbTYjzscGM0aM8Brcyuu+Ty7TINWYfrhVEZsg31vw9cyTPMiuCntQfj6uKqKRk2/LzCDNOvHJT+B5uZBh+A0wy+2MWN8aAELJ5qx+Eh+TbpopnW6ou9TQUAhPuf5e2b+/s/j98/v5y+vijPo9BX0qfQV+JA8gBxCEmEKkrfDXA9FlBx6Mt3eaFk2oY6AC7zANL1ioRH3hHSgf/+gfyDLv2Hb++f3t58f+ctKP33pclnktFxTkC3grt8VbDlx4wpwQ4AZjssX3VuFCvjDa2cuutw+9Fn7HWkHhI25tvPWVKtnKt9vEhn+r0JoB00Lg47QPMhZrtTneuvrpIUTwJgGMZ97PU2p1Kfp3MDOKj+D8Js2Ulr83Q80CKX02yorpiwMRaOJwEwORv0OnQ/erElVtftaiqAOMDj//Ju3jy+fLx5+vwSPJ9Iekp6yvMp9EFGkMSQRJh+qxhV3NUBQPlP1NaXbhAVXwB6B1VfaPYBEJGQIPxEeOgdwD9Sqv4fMulf+V749v75/c33t15ysJttQ+heNi/5Vd+Hqtu0HXxdwvq6UssMgh0AW4MFF8YWC+r+TZgaipOcq28rs/f2CJDuMvXjOpG+MEIQsekDGAFCt3umb+NS3TrDyFQrsEliGItYeZrmeSSvKKelv+MoK0cDjrmAruS49N+/4u4aCMhNDW+wD4DZHA6MnVjzGQcftNbB291UAAD/Hl//P4ebt/7Lx5vnz8/xyVOhT54PcQJxDHEMSQRxnLoBQMWYRCZPAJjH/lP554XykH+qrzc5ABBIeCg9Eh5JD4QHUoKXSf90uEj/p/c3T++82AHpH4YVz87qP7Q/Vm7avia2/aTtHnYAbBBurTJjcFD3r0NgW0EwnWLqqyG4vKppkqOP5Or1W0p8Kas7ZvqYezN4lKh7ort7KWIYJmO3D2bjhddqXrNTvyMN+0xlu221rR2X/u3S+Wa4rI203XmeBMBYYgudd7s1JDaKkC5XF+M5/oDHH25u3hye/zyf/nqisHAA5G6AOC5NCEhiUBdPQDHATOsql6V/fQAUldX/Aqyszz/CI+GDkCQ9kJ6SHqXSv0wdAB4dDnTwlO+Hb+7PP908v/Pio/0smoHCY10tac3qf7G+zQfQsqeDbPtJ2zHsANgmW2hGDGLBC99Ahg+/hDnNg10FoZVehm+UlbSu4448VK8rGTxU0BI9jnRpF48+3xQP/ND6q3E/N0ayuWaPXEsPMxF8l+fHkOed9d+um3c44ABzPFM/d0Xq/3STANxkjYZhUJrXeKEzs1QWzXxem6ebbhgTNk4FcLo+Gc3tT+Lmzd3Ln8fwaxR9C6KnM8RJ/omxcAPEJU8AUgJESApIIemZpHkCLo6BBvW/Mg8ARTHMX0lPSJlK/6noT6n670mSEjxBvh++uQve37785Jz0PyT4T8MhRpW9dZXd1TktmA6wA2CzcAuP6c7AorKo7l9nCk/A1D6A6uD38Q/tIvrCaB9Av6wblsKhuzYe0KUa1vGpAI7lltNwRjFrpFpuh1a1jZ3Nngd0rou9IunfdNrhya/fCefuzXWmmgTAMGVW6QOYNMUEAOv0KY4GPbj/Vd7/KtXTMfz6cPEERE2egFiQQqUEKSRKl7OF/ANEAEo/CQACIqKoLSAgKkAhMsUfpcRU9/ckSEnSA09kPgBP0t0huj+Eb47nn7z4ZrFMa8JC8J/O6v/1x2p1ZZfdANuCHQBbhhulzFV6l5D5a//+Z5xjLKGDj9ay+oLF8Ah2yxgaFy0d2XoxGHdMw95sBhhmKzgSgabtmJYq2ayzOe5o03Wxex92zjl5FsFLHIiOlmTDkwAmsaUcBchVFmw6razVNk9aCchkEVyvUiwhHvDmQd78KtXzMf7yEH+Nwm9B9P0McZI6A3I3QIKkhCJUShAhKVSkuwTw4gnIbSwiIAIKbRkBkRBV+htRCYHSQ89DKdGTSkryJKSi/8Gjh2P86EePXvQoo8fBIVanpVchNQf/sTL2v3JAJ/OqjTWmmTHBDoCNs7JmBDMvPcrGGnT/OlWP9YgxidVdsfzvli17MfiJXeHowo6TAKrr+4YnmhT3algnUmRKhBMJY5i52E9pv4QTsEV+tPFjzpbvrq7QNI88s8EH0P02LH7DhhkqNm9bZCc+gOHnmj13jOFDF68z5kTc4+FeHn6VNy83yZeH+EuUfA2jpzNFMcQJJgqVEoqQCJXCYoEUKIJ0+L9S6QICQKbwC4RC+gdCJCwjRCr9gydJCvAk3R/p8ZA8+tGjjB6FcizUzxja1X/LrLHs8lSATcAOgO2zt3bp3q53MF1zaZ3Svw6hhaO2mekGZ8BMuKMvuPbsYcsvq2dx6qqdDwfEMMxGmGy2lo5r/c2uKXHHNI85uWbgprZ17oshE+XA/I0I95otTBXXfQBLFSCTPXC/6rCOuANxJ/3/SHW6Sb7cx1/i5GtA54jCJJP+M/U/dQAQKMrU/9QToCgN81OM94d0yD9iHv4nBQARBaKUdOPTw5Fe+eqVFz2K+GE1Wd479H+z+m//mldadleabCaHHQC7gPUgRqdTSVi/7l9vIE5xkvpJCS6z5nvTa0cH9YWe3YjxbwPu5JWZ+r7b7ahZOpqDPXwHk+QanD9MEw6VjfLMLYtHa2GwBbfWUV27dZ6RlkkA2V+X5YNhgYAGm7eRdpHN6pRw7ppZLlMuFQhULZGzNcqkiFsQt9L/RarTEQKikCAgCBWFCkJFYQJhAlEaKUjpMwMAQNP9M9EfJAopQArwBKXfngCJ6pVPr2T8iMpfWT4P8GzNp/4Xp1hXnqa4NjSD6QM7AHYEt2MYuFoGFqnKJ5b+dcYYrB42egaf21b0Bd0HcD2HO6Z5TvW/OId71SsW0TnmT5uTGcIwzEBM1eioR3xQvXzFRjTNISBrVuD6cTZhmrHyY8QkgNnEjaazrMIQscFk6jg6CWDpknqpUso9upXqqLYQtwC3mOeBzNYqgAAgpOwTqMI3ABJBIngCPUw/5CF5qDwkD8hD8IAkkGw+5bYwFJ7Zivp6y+56U75v2AGwL3YyFYBb0k00ZstS1fcsAQTqDDZY/XacqCA6ri8s/vhxW0TDtTp/8dLB7IRli9l2CvmM7vmOXDSf7ocqthw3Oe/KCR03zZboWIcXkwBKma9PArCapKv/7Xdzhk0CYLbIsrfbOR+Ag0VfcwOwGllFANQcA8UcLAIgANXzkCvK5FHBf8i0wdWjD86aFWVrBZ4KsELYAbBHxneFmDXSOG15ERaS/gsmNVgXO36tQd0007Dt0GNw0kJfnQTQe+5F24rJcFgVcDhpDMPMh8UAgL2rFKuD8Ad2lod5RHem/ttKSGHZzTer6Rb2vLVoWDLcDru+cLNJXZWhXVVid40rPgCXiouhksg7devVUWdlB3k0IPhP0849uuozxRlwD3YDrAp2AOwa9gTsh+otXrCOXlr61xlgbXvv0nW83OgNuqRkHkb2V+y1geYu5rY6atY7fAhIrtTzrDs0wdnCLMPUc8KmOUgvQ1HauH8d1HaiFZnmwZRzrF/+9blPfY0/lv7U/mFyA7hYzWI28tTFtDEOFJvlfQBrKZoEAEAIsIZqlZmOXgUW8x2KeQBjD7pqKX8Mu73wtcEOAAbgyriZVbJ4c81dWPovH6SftRpWqhqKY9fzrlZf6D7mbvzbgC/nYxqYu1bkWphhLDH2SbJdN1qcSTDmCMM7m7XaqfE47e8P2NycPLspqr4NuBIFaJxY0Kj+V7ao3eiupffa9E22b/th8du9ZAJWV9BzN4B79etG2JLMi5UF4+yx2R6BDeQsTwVYA+wAYKpYm/xr6TjMeLJ7wdJ/w759I8yQ9QZBy6FWqy/0nlqhhwsY0AZaMPjPeli8E5viSDKcgjOEGYtTVZ5tM93E8M5ml2poOtMMjt0vAOiTou51uB3vvikBPTYd5gPofeCxh2bLyLQwW/EoncjVEnm9l0D5Nu7VtMykDA/+Y9yz/yNAMETKp+LPBkosuwHchh0AzFRUnnpXmxDbh2DpKtji2SfTFIYY3O7j29uTM36y4fVzzA1qHYh99Wmdv1rnE8isAL0IOdjCX7aET352B3NcZ3DynLwuQ6LWbJqHM1JGr00CaN+4axZ132xA4tdiLNeSzpXjQjbP7QNY/IJHk17F9dlCzFYYHvzHOFN/kXi22/ABALsB3IUdAMxMzO8PcKGttjybkf7HHK3bjgMN7shyZmm4QZVF7/vgk7e8M3BgaGDGBNeNzGDqJWczXRUXWVfOLif9jyyE5n2NwX9Wbp1bmC5dxkkATVGAutxKK0m1YgdXbUxXnfjdMtNdy2u/bZSQTIdkOdIeG2j4ldT/OlaKPvsAQMvJzVzR+mEHALMMW2pYOEoupC549uUP2H/+3Tw+gLazbEZfGDQJ4KIU9LoZVi+5/WDbqLVm6kM2n4alB521ZEXLbCVHap1105CJ6ygeA0qAy4Wmrv5vxjQPZqJJAPNUH6a0OuQDwIsUtY7nfZc4cncmT4Z2dEcu2QpNTkdmM3Qvq1fse4tjwF4adkd71vBTOSPsAGCWpHjYJ6out9Rw6UGerRtR/1cRTwCrLeYStT6zcf029IWmJPR9GOdvog8OObCOSmakdsPsm6tFhfvUQ9hGls3inreP5p+u/wtNWw5n8Yu9xph2VpfsaZsEYN43Naq6AAAgAElEQVRhpkxjO8j0wpECM1UyjFFQ3LhkK1x8ALCCapnpRe9S2hT8x25x50kAHekdvIkZDjsAGCfYUvNiSRavENc58H9OzOr/VvWFQQ920yQAgq4vB76+SW0LQxOwM5WxjCuCK14XcPwWOJ481+gUWHxLrGTgf4tnusr+1P95IAIEC5MAhmdng8Frs4PdbGRpq9Fmle0y0wX75aT5cFsqkxfxn90A41ivQF0K/tOg/tsc/s8+AOt0qY8465phBwDjCltqXixDuaZbIDNZ/Tee3zgzYKPqf3tCnJgEYJT+9aXRXXdAIFfCCrhzHrdOzXRk+Qe2Py4WKhfyZQqWVf8rh+pbWK+q/xsyze30SOb4QDqkmVo0VxprjNSxUnO20mQvizuZZi0lfb1cbjCmhrjsu6p6hmliYPCfScf+W4HL52Cu3s0dZyw7ABiHmKJ54WCTxT6LV2FTJMBh9d+oGpipq/8blf6hW1r6hgswxg0YiOk4hnU2qozisKuofHZRSbqKyzk/IG3cVSmx7bzoe3XTSf+557WpyBq98vWVNgf+b/vWDyU16OZaYtG6g40g0wt3CszYlPTZ2cYgGYegSr+MK+3VMjz4T8NRbA7/L/7XetDNPFZroj3TN10hsAOAcQt3GlWrYfEaamfqf48kTKr+O3CxnRj6SNuU/qGz+l/8w1I1tLH+0hDcq9MdS46LjMki9gHs4voXUf+tHMTU88ftOuav0juxNiYB6IGAHKkxxlsqi7bOPbPJuMvA0jIi6KUjhXN8vVGqfBypiVaFC3nWqzSWYv44Uo6v4kIu75CW4rH+28EOAMY5rLct3Gms2KehDprvet1R/+c6c8s2XdX/LeoL3VPUOxCQ5gkY2Arqpf5PgPtugC1Xkg7jZp6PT9Vueys0wWW7WEhw9hqjNWMN/vXWXRvV/y2a5tnoO72vtLIp5s/VqsRqXcN2kOmOU6Wlrwdu/OlsHMYqvWXgy34lHwDsuhrfNtiwDNCh/FQ2MFmx60ewELKKcYD1+wbYAcC4iFPtKkdZvIqZKAGDD7tghtQLa5P6v099we4kgHmaQRPUQY5Xa44nj5kBiwVgqd7KYmV4jTXzMAZc6ZjMubZvP/Wfqmusqf/rLAADUz3OWrS9DbjYZp05ukYzusY0O4JTWdcpMVaTu/Dlj5yomILZL8P4rTVWQPtjWPCf6p7tRzH+t1aEup6dfQDbZiW+AXYAMI5it2ExQzNl1pbQ4pXIFtX/gcP/qfavypodjC4cEA2iX2aMafcsPfy/fl53eoyL41T/eWZcu3DX0rMaXK2WJ2FL6j9d2b0HuyoDzfQehozlBTDZ+nlVD8MlLGSl9mwcV4RTt6ktMdOkcoE2bb3PZeNo6ey9qhuAK/YOLJhPg4P/YOue9e78lUSMvn536hBmQlzyDbADgHEXp9pVDrF4i8Q19X8uWiYMmoMM7ED6H0yvcAEt+sCETc/JKqD5B2p1hKvc2XAtn6dIz/a7zyu8vFHPOFZ/XT/U1Op/Z+m/shL3PCfPCuMnAbRa9gUZaQfTyQ3OVfHMPjAUvemL4kxugElPYBz1z1MBtkLbPWwqV53LG/WyXjwJgKnTXtgmuOnsAGD2wkYa5B1qgWkv00H1f5bh/03qv2HeqC5M7EBf6JTAEY9fFgjInlTgQo46OxVgI/Uk04fp7viWeyubvbAGZr7e6dR/HvifMzb5JmvRdxKAIRBQvnxZ116PuFfLsBndFa7d7kt65k3WhM3a8kGne9zT6GTLTkJiOtK9pFUsfmn4v+kodG2KgCEZsxUSLo07pG+t2qGEsAOAcRrX2lULs3il76D6Pwus/k9Br6fbzaGCI3GzfrOfKpeu04WEuJCGlBlSMufTOlPGNlzPRLMonKDhkq882YNv/FX1v/msdaNcWdPiOejBNizQNPSa3qdXEF0V/+lxxGQ5kgzmKq7dKaTF0qM31O0wl/pfnA3r9Q9PBWhl/gq7d+nqNSO/r/pf/OiYC+Uth1wLF0WmhQ5Fih0AjOtYbFe51kTrx+LV/eIJmIz2K7uu/lfW0I70hR7JHD0JoPRO4I4NoEWysc9lOjsVgNk8s5W6TfVWtnMldpjEX9j+/y4D/5ukfxid3K0UADvXYTEQkGOBESwEArKWlslZV2rdxJU81Pojy6bHjifAONxqYi4DwHkqwPqpuPyN4/bg6spRGzKMc7ADgFkBi7djlmfxNsd0CRh55NEJ66H+19sQ9VH/wPpCDwY/2tUJAZ3b5f1yd66qx7UqzrX0bAkXMtaFNKySTVTOvZ/uDtF4ZipR1JiWNvWfY/7MTt9AQIUnPIsIlK/sMSegSxugZwFgO8isjNpgeRcKsHEw/XWaa/v5qLsBeCpAA27O9WwawNd4FLr8bbmcxgRwICBmJbADgNkXjrSHVoaz6v/E52+M8FNfs8uYP1YS2ytcQGkSwLbgqmkPuHCLF0nDDL2Vya9rozXPFTo7Vqv5bze76iP6jaeqbcYv+zVi82oaTFc/y24lSQM1m6mwYtMXaxi4YKvWxpKtuNZ5UU7dzE5P+tIpJlNfrzqpYVtGYS2MCf5juKet+9XvsM3AViPLD/sAmBGwA4BZB6tQx6ZKZJ8q3n4Ctmtghqv/+lQA1he6YyNcgD5acGOZ5lRXbbYqdxV1uxUWv8xlE7Du5/Va0he/ub2Y4qGzdsxKQeko/de35Jg/szHeskPjJABtu25j/K3eLwSgdT3bzKIs0J7pcL6VtbKcTWtF9193m2aV9Coavafm17YZUhJ7loqxAbK4BDKDYAcAsztW1hLacOW+6KV1Vf+7hP3Zpb4wMNWmx6/XI7ld/T+DFYfp2HO+unDta31mV5loG/S8cJs+gA6paBz4zzF/WpntmvpZ9obpfb0rjabhukMZU6pX1t1gbDDrTe98Ji6KfWmseXQ3AE8F0HCqgdc+OMDA/I+HU/nF7Ax2ADCrgZsvC+C+cRpkQUeq/7uN+TMdAwMB5XefoPQaANLv18w5bKOScqSucyQZ22DBnNzDTZzwGjdaRU/0dNudxtSS91fUfzbNM2Nlel/248okgK5WfSsV3zyGmM29RebIzP4nWMctXkSHbfoPNlc1ejXESu4sDA/+46D6b+u0XPaYQbADgFkTtpov62gGwdLV+qRnX+jSrA3837e+MCrt9pSCAS2ftTSWVlNHMR1g9b9gLQ9gxqJpXfjejbj28dVXd+lfX8MR+a4y4ZWNn95Xc+07Ak8CYPoy7X0femjXS+OciesbE6ZeI1WmArhUZW0PO8F/5lH/uxcGK8WGyx7TH3YAMCvD8eaLzeRtWP23S2fj10n9NwYfKIQG1hcmw0q4gC3hQl1nLQ0uXMxCsPpfYTW9lXWkcjiTPpTFlKze/fYu/61J/zzw31n6Te+r62mmSQDz0+9hcbPmZeZlkgp29BHtTtKyyTxp6nkWqnQDjW4ADgeU40Lrrq7+G3v3DLNn2AHA7JQd61EOYL2BcK3RcfWEde2AB/43YeEKrIYLcC4K0EjKs0+4mrLFIjm51O1zvNjYfSgnudg+6XM8t1uYoYaxdaOvxPyB0VeyLjMxiMkvcbxlB8OI2tXZ8ApWJsSst5LZM5ZvnL1jOVeinFT/G4+AzSvXXls5Sff7Vsr7vpM85sVyzcCljukDOwCY9eFcw2UKePh/XxrsX5dLQdO8dZb+p8VSuAC3mj0TVEyLD9faRX07Daz+rxWH6pTJMT/gLuVA48B/ftnvSrA8vY9aY3PbZRX+eEeTxQBYLDa277JD5Xkt6n/lUHU3AE8FmIDe9608U7+tkTBdwZs5CpDF4zD7gB0AzCqx0nBxqPXDWEFrdXW0g+3S/2UD1hfmole4ANM/WqfoNm/iCg0Xb6Gy4spuH6zlPlvs9TAjmaotZOMeG6bi5Ws45k8vZrpQe9P72l4JsIpAQDZ2nJNVJHKNjM3Yye7K4uNLZmLcFTZWNnWhn6cCLHrdjU2CmdX/Dmz/oWPchh0AzFrZclN128P/Jz7+RdNvORGZ/88D/69i82osKQWXhZZYwcPapC5VMVuu8aZn/qzbwxlH4mgH2cU0Tc5UStCIezz5wP9d3ug5sDS9r++adXMtd7gBsGqG377p7/rCRWvqc89zfKMbgKcCjKZf8J9iazKtHHbcFeFok5pxEXYAMLtminbPsm2pPXcSqoavOSOMJlKfDWBn4D9b4kF0L8P1EYIEpTcBzMp2+2l7rlWGwer/PNi/6v5Vx5ZyfhI3QP8KuVH6Bx74PxAXLnfA9L4rKv/Utt6U3G1PAmCmY0gBmKvELDYVYO3qf+VExiFILMsOpd/dW4/6Xz0/lxBmdtgBwKwYbk8zKV0D/jSt55g/3bB/TaMnAQC0vQmgaRKAo82tDlnBlZ77zHyDVl0eHH0SHWDZYQRAQBZvTLeBkKX/U209S/8rwpJlr6ysrlnI3799K7zxy1uMfiVn9ruwtYhAtq6ky7RyfUvUfqJp/daxUiv3unv1QQONh9hO+TbBTWqmG+wAYNbN+Ia4c035bcf/sc1I6R/q6j/rCzMzOlxA2uIpBQJq0vo30TZapMpyrp7sw5wpZ/W/L4MfSheG/28Y+2JQw3jqq2mwFvPn6vk2ijsXPcCOlHR/Y2XhmlnfQKXMTEanR2DRIjSfG2DSc4w/uGn+WYmmakevkXTp37WaaisYRwbU3/AHps3mx4EkMAw7AJj1s2plihnMWOmfSv/igf9XmfnKuocLANTkoauBgCZqgtvrbHShd6XHVeQssPo/jAEP5Wau3XEqOoato/XYkgf+r5QGK9XDstcnAdQsOxXqj/X725rKYf2Okb2VGTo73J+agSuZ7MYNmNwN4MZlmumYtpZx/fWpAKDdeDZG1xhYOgqfjQvqv8kqNSbBogljPxPTAXYAMFvAtVY1t6EnZbj0T+b1PPB/YeYMBNS6srrFbHB9wfRhe+WlV59lkssfVIFv70ZcKN8SrP2znWH2sB4FyIL6v2/TvOTV27Ls+lsBmq6oQw3SQ9PssJEr7XwnEsH0o7HwOHY31xoUaEyKy/terz+L7Y3dDH09aRm6das05hJ73b1LC6F9t/UV4hHsoIAxI2EHALMRXGmLj4Tj/1yjSxrrIoJxXx71351FLnFMuIBZJwEsUfXMX+OttI6dLc3bO9HMdHwu3VH/t0/zLbGeYRWrzTF/NozdQECXX91qkGUNmfnsW63TmWYMJcHVYmB3HpjVA9k+eF/137iv0VuO5WWeCmCDrsF/3HiyrqSCVXtmRtgBwDAAS3cJ7OL6tQw1cv0G/rc341hfcA2L4QKo3F/B6rrityvNraGF0PUnfU/McyM2f7uvdoo3nwPOMXEtaR7yD2yat4KlSQAAZWve37uP5eW2RHVOMLvhmTGU7uYa7qt9T4BTNA8XG3Kcjm4AtlNluhetkvpfXxhwxCmZOxVctJhW2AHAbAenWsZOJWYDdB343yAil37ywP+ezHShtgMBXWn/tP+7e0oWfc475RnXRBPD6r9dWsS9SdhNTe4UjdI/jL7TfENznMgJk5XqZe3T4f9Vz772m+qFp3bllVrFVgyW6nH2U00zNsjKz9qKzShPgJvD//MdrdWZxgkBdTeA5bM6xAAJeuDda9ltbU8Ww8wDOwCYTTFGQlxest9iC8BMn3ZBlwiMTduYowoA6wtrYsiD2WUSgHlVvn42Rp9r+YrLYTaTM5u5kI7ofee9XbtzWA1WcCVAHzvmd0P36X2XFwBQ+bXALS3JYeMfZ6lrHK/THE/e9kBacYbXxe0rOKn+m98Za4u6Aa24AXgqQH8uA/70e0eNDYxl6ZoQu8WACxXTDDsAmK3Bjdd10E1TMP+fOmxTGxHG+sIwZr1ci5MASAvy09wMorS9OPgiRxanGaoqq6fg2rXO1Bmy8wx3+fKnS5uLVz26M8nS/8w4lB8TBQKq/yztdiULbA3eH2sWXXvaXUvPttG6J2vP+N7OgEWxNiCsI/UJAZXZylYd7Y7Qq9XQ/T6Yp/tT68/lcCYhDHOBHQAMc8GuvLWgWLYana65xdPFjc/S/wYZHS6g0uTUZYLGxugiT4u9k67med8crP6vHq7VuzPIB9Biym1KMHwfV8gAy1V497tuDR02tjUPr/NxrE1WZguxamr14WbuZ/2Z6/WADKH54PaNw9ULaY8vWomghOWFXdqygUVjZIuifTMbN2IzTzSzMdgBwGyQLbWidkG3Ef0VjJtNFfOn6Xxbx52L7h4uADBvRXdrSQ9vb7tUy3ClV2fV6jzfTcZF6sMYG7hqfNkxPynO5YrFSQDFyquTAIrtoDVYEMPMj6ngbbghN2m4zeHVXa8UdN9Y39LsDNnXVIB2et2EpuA/9s833hkz4Ah2PUB79ScxV2EHALNNBreiFmt+cR0NAN2y4Yr0T+U1rC+sjtFKQRrVp/ABdJoEMDMzTzp2mDWk0QCr/1vAibpgnQwdbmkzRC/fvnUxenrf5W3AFUPexa5TvpUWb8Vig3/gobiu3yet9ec2C0WDw2O6g1tm8CmavOb7mApg9zrGBv+Z99Ha5oPMbAJ2ADDMhHAUoO5YGPjP0r9VXLv67kW63uJs9wEMaaGOKV3TPJmre+QnZaUCPd9BZqVcrUJtSv9dzrdeBIAEkgCSQAJKQI8gRAgQgmwsfDvrypvu0/uwpouR3t7r6OCfqJLteVi21/vl2o3HTlsxoxk2on/M6ep9D9j7VICxwX+6d/wHnGmE+8KV53crniTGLuwAYDYLN6/XwhjpH8ojAjjmz0awFC6g+1DB+dpIs9VKXP2tDb5jK4JvVgFL/31Rd6ReEXhEMv+WRJJQEkqFkkS+gEKlKyGUEHoUSgwEBBICQYGAIPcKJEtfUkfGW/aWEH8drPikhn7uTgfXQZtmU33YlY7IsH58o6avr9zuVAAjFoL/THGm0Yw626bvOOMI7ABgmCp2W108CaAFC9I/8MD/SVg+D0aHC7gMCayNDRzbvho/F3gaZnjk3a9VYJ2dTfdzdWssX8etmO6ZxzF/UpIbiB8UvUroMaKHiO4DkAoREAgRELNvQAIAShcQACnbgBCVAIWoEJVAhaiQQg8ijwIJoaTMJSAowPiLgLW4BABgkGUvFkrWnIDwSjGZRF0ZVrBdrfRXYeXXSp+c5akAE7Jgtja5AVqmAqzZ9llJfnvwnyvd/xWp/wwzC+wAYLYMt2KdZaB8UFnP0v/+6B4uAJqEgJGTAFZRp6wikStkinzle8Wsgr7mkQf+qwPEDyp6UOoxoseI7gM4JCAT8BKUCoUqdH/NDUCEhAACKf0JQIAAhKAEph/KfAAidQlQ5g9IV1LoJV9v4i+H+F8v/oLO1S8NJryvZaemsH6LzgOYp9Mx4VlcKy0bY1D2rr4nu8YRGTMcv+4GaJkKAKu0gAXGKrd71pbU//pC++FG3sGe1sLFR3XlDiRmCtgBwGycYS2n1be3chy8kEmkf7BxnWwgXcN2IKCrraDrzaQxxWyWR9HBR35mpu6vuX9MhrHFMKu4Z+mfJMQPFD0m0UOcPETwENIxAk+BTNIPegnK9KMQMpU/nwpAiCSyhdRuUeYeIAFKAAlIFxSmC6n6LzLfAAolIBHe/fn4k0xOhyjzBMjkq0uZaNGyg8Fsd5E7bFa8Q4/FxnpfjLjZXFQs405u1murpqkAKS5V5GMYeAcanMdbG/vPkj0zMewAYJjJud50m7Kid6rhODLmD/DA/1lwPD/GhAsoVjYFCpiq3TXnQ+jOA3+N9aTUflJXdO0bxPE6bmkGZ091xz1Z5+CtCt5G8UMU34dwG5JUIBNd+geZCBmjl6CIUSZCJJCN9yeBBIUPANL4P+m0AMoykQTmH0HpPAAhCgcAZZMDBAmIJcQexJ48hoeHE/wk45dj9O0m/OLH/8rku7sZOtCy13/NqZ7UUtz1KsaPS2VWxOj7tdZwQPsc/t9Lr786FaASDqjjYR1jcK186fXrt6NpGTqsn4D1PZjMvmEHALN9dj4JAHp1SCZrVYyU/usefjvq/wpbUfti/FBBPVyAXsYH+ADGdwaYiZkop1n9Z3S2evtG2sPdDvx/eauCn8Lw3UtydwZPkUxAFrp/nA38l7GQiZIxyliIBESsRIxIAgAvI/2zwHVYuvp0DSGAABIAuhtAFF6BzBkgMPEg8SH2IE6/pbwJj48v8EHGzzfh12P45RB8Fepp0fy1a9nrkwAIcD3lh2E6sqWe6Qap3xt9zdUJyJVtKsP/sbyw5vptYPCfXvuz+q+z8gLDWIcdAMwuWLzNtHgCFmSs9G8aYAWwL31hTpzLFdPD0/2BqgcC0v7XeLWG/6zlAaZd1zargO8O4xTj6/ydSv8IT29V8D4I3r0ktwEdYjpE4MUX6V8kIGOUCYpYyFiJRIgYREyY7o3Z31T5JwDAzC1N+QkukL6A6euAJWDqEgAqPAGY+JD4kHiYugEyZ4AHkSeP4fGVRz/L+Okm/HoMv/ovnyQt9brgcZYdILPThQ+g6tGfQfJoSOv1q2AbsCus3u41TQVwc/h/186D7cN2mRZQ76SAdssrUwHWYihzqG8NDxW7V14AAJNEMDNLn59hhsAOAGYvDBDFtqSjLdVkHKP+s/TPtDA4XEApFFDzOMFqSIHBbKYScR73h/9zWWCcwuaQ/5R9WGcS8PRWnd4F4bvn5DZUfkx+RIcQ/Aj8MNX9QSQoYyEyN0D6al8CRBCZ4g9QvloE0DOwnhFZHAQs/8y+KfMKCC9Ciah8UB6kzoDYh8TD2KPYTwMEecfw5rVUoXf75uH5z5vTX8J2Dg2no2XPJgG0e/En1chs1OYDexkTufnZPk3BNLm6JjfAHuh1J9rD+HSfCgCtx3GSXvlk6IJV9l80+M+0J1mhg4dZEewAYJiZcMGd0GlckiWTM2rgP1XX25Fi2065LQSABJCAEiAAirvu52jeWAwXUC/hrQpCvSj2PPeYnYefzoXapoQpNW6lsAFW/zeIo9XcrLD0PwyS8PQ2eX4bhO9ekptQ+ZHy41T6Jz9ELwA/BBGDSOP8JATZ+32BBBVD/i8xfoprbmzy6CcvL1DDSkIgFJEQIUrExAfPB302QD4ngHzf8+Pj65uXH++fPx7C+V8PYDsQkCGs30QyCtfmjAM419LTcTdlbnDVDdAyFQC0SQAbVYovM+H6qvwbUP+nYKPlhBkGOwCYHbHzSQApU19RL/tiFBFY+m+BPKBHIkkggSSBJJIAkkgS5h8hVb6sUBAEHoUCAgmBgEDQGSFACJCipS+mF5bCBdQWc68ANA64HN5qWkj93ydTXL2tY+77zjBuYV/6h9FFfA2mWXnw9DZ5ehsEb5/VMVJ+pA6R8iLyQ0g/XghegDJEBJFK/EoCFnL/RfTPvQKQCdgAbQ6Ai3kyOgDyBbo4ACB9dTDpngAB0keVOwASD2Kf5FF5Bynj40148/ru6dPd80dPhXazbQiDLXvFWheOAcsFrEPi2i6B7cF+mP5e73QqgFMXPH6KcIvD1zgVoFjesA+grv53mho2SVrmPQPDTA47ABhmPhxxJ1xpLw5qSfTdo9/A/31L/yQgfiT1qOgxpoeIHgJIJX4kRAIERMoGGSIhAiARAuQ/USEqkcYGQIUQSwg9CiSFEgJJgaBAQIAUIJ2RHOj896JfuIDsR7U8UEVdqe8+oASx+j8jrP4zzFVclP4bj+sQiZ+O+j+f3qTSf6z8SPmR8sJU/UcvBC8AGaZv6AXCi+5PkMn9pVH/2nKWgddmABCY4iDUpwJk0n+2kH8QCGUoxBmlhMTHxCd5IBmRPCp5VF4ivfju/vT0+uH7p5uXP6WtrLuOlUkA2th/s7FuH2zb75Q2DrLqBDAdmfFOOecGcCgpZdxMWLsboGUqwJrDAbVwfYrhQvfRzeLDMH1hBwCzL3gSQIE+jGDkESzsyAP/a8QPFD2q5DGhh4geQrgLQSYgFXpJKv0jAiIBZAu6G0Dky4AESoASqARS5gPIPAGULmPhFVAnL/lyjL948b+ie9Sg+RitFCBA8YrFSqyAqxJ/Px/AJqsMpj9cELbHDPfUeqtjvA3cbcyfl0f6+l+np3dP6hAlmfQfkhepYsi/F4IXCEBMX8cL6Ut5jYq/yQGQMSAEULFQcwAAYW0hNWIIhDIQ4kzyCDIiEanMDXBQ8vDaS+4fT99eP3z/dAy+zHV7xk/v0zFOAih+wPVS19g2tvJMWrzSKXEzVUw7fNfWTetcZHOPpbJg0dO5HJfxWHWLV/5ZvdApS/8CT9YUc9dWXjYYW7ADgNkdy7aQHGyfGRobJiNhxWo0WWse+J8S31H8SNFDHD9E8BDRfQBSgZeAVCQT4SUoE5QJCoVICNl4/3RZpMuQzwwAgtQfQKn6L0AJIAEK04XCGSBylwAmkt5JCvzo20385RD/68VfBCRLZ8o1uj9Tpcmyg3wA0KVMzf+Eu1andMBikt0c/r/Ce8JskEmkf9iLdf7yPvn66/PLD8/JMUhH/ZMXKj8kTwv4A0IoSYiYSv+AkHoCsNEBgLWXAJsiATXpQKntgpo6kn9jdfj/5ZO+hxgIgCSG6IWEkRJRPhUgVjKWMn57iB4eb7/9df/tkx+fbOVlb/pN79MnAeQvW8g2qGRlZ43sMqp6UGmfpLU/nV1hi2WXhfLTuakAUzDD5S2Yg01dkfpUgLoPAPROzippn40NLf+d7JZt/Glidgk7ABjmOg6q9pOSGWCrDYh+0j+MzvFVtX7CHyn8IY4foug+oocgVfxBJuAlIBOSme4PMkEZo0xQJJmEkKv/gAT5mlQCSD0BAAC59I8kkIRQInUJiNwHUKzBWELswdHz7s7wVqrzIfx2E31NPQHoxDNgYxKAPsCvrw/g+jbOqP87qbVY/WeYOpM47FP2If0nHvz9S/j1l+/h/Tk+BuoYJIeAvFB5Icl01H8oQCBJbci/gCy+vz4JALT4P7o/ADIZH0sNLtKcATXItEwXv0DaEiAAIKqO/U9/orYGAa4TOwQAACAASURBVEjKs8SIREQiUiJWMlJeTN7Bk/HN3fnh1cPXz7ffPklSVjO3zmjLnvoAipy74gMA0+nQvIFNRZUNAzMLC7sBuJyPpONUgLr0X6xcsw8go2n4f3kTa+MFW5PAMBuDHQDMHuFJAJ2w14AwDL8yHXuHA/9PP6rgpyh895Lcnykf7A8y0/0vir+MUSYgYxSxEAmIJFP5M/UfsoH/QFCTDxBAACEJUfgASPMBkECFqTMA4uzdgBB7EHvyJvTvz/BeJqdD9O0m/OIHX71ktrAATYwLF0BQGipYL+SjfADOqP/MYEbmKN8QxgqD2wks/Y/n5Z7+/uX8/cO3+BjGh0Adg+R4Jv+kvIC8EGUoQKCSACKP75+P/a8ugD78H7Of5W9qHAVhgordKiuz71r0/+KT2z3dAaAAUIBC+QIQEUZK3JCIlYgScZRe4nnJ3cPp4fHhr99vgpcFbl7fp6Bkmvu2YFvPZKHdzo7l/eDGndrFbIAN02UqgHF5tT6AgcP/Jyjil0OuLQ8ZpgvsAGCYTqxGtbfL6AaEUfqvrt+l9P/8RgXvw+DdS3IfKD+CQwxenEr/IBMQsTbePxYyBpGQiAETyqWEfDB7LiUQVgcPXkaFEAAAEmKMAFmEAqI0bHE2FYDSdwN6kPgQ59+pJ+AYHh5O9z/J5OUYfj2GXw/hFxl+dyuvOz6hV8MFdKTTiMKpWUuVVEunrYRbzwBW/5k1Yrcu3rn6/++b5N9fXl7ePEWHMD4EyfGsDmfyT8o/g3fGXPrPjbAW979YoGIZ9IH/hJC/gyZ3Bphd9mYadP/UqhVrSFvIdf90XqDuG0CVJ4wAEEgJjNGLACOFIeGNErESkRIHT8Teh1h6rz7/dnea1OhbmgRQehuwPidgdAEckMCddhwYl5jbDTD1mcYcf3VP43gfAKzG8prlgtlv2erKSD9W6BZipoAdAMxO4UkAXRnagGiS/iv/woZthrASq/b8Vr28C8N3L8ldoPxIHSLwIziE4MWp7g8i0/1RpuP943SYIQEiyUJQoFRRKGHKAtL/6N+QDT7EBGUiIER5AuVj4oHvpw4AzNwAPsUexFIew8PDC3yQ0ffb5z/vnj/6STBBBl3FilKgN5WvhgswHsdu6e3LamqQ1bCrfiXjPlfrOesWb+fSv5Lw6UP05efvwcMpU/8PZ3U40eFM3glELJQELAf6r439L8X/wfJ4/2z4vr6yWA/llU2YHAClBTIsVKcFKCAEUJhPAkgXEAhFIL2IMCIRKrxRIkowEUA/vP0mpPrr9/unf0X/fO2MqcR3t/a2fAAtmw1vekwwvcBmP4INmEWczEyeDdAPd3rpXaqtJh8ArG0qQP/h/1lk1+lPvgwrunfMqmAHAMN0xW57wJ3WRSf6uAE6Sv+XnzvRFxCe3qrnd0H47iW5DRI/Vn5EqfTvp28UDEFoun8mUyOoQvQvhIN8oX3kIOWl7DKvsiIZZPGC8zUkMBZehHCCxEfPhyQPCqSFBoLY8/z4+HC++fH++ePt85/SkaLc75kqt6tG+QBY/V8Cu3nA6j/ThZnv9ZyWbRL1fxWmGQAATnf06UPw7cPX6BjGhzDyz8nhTIez8k/knRBBpN53EuZR/6WF/ENlB0D2XXMAXOjnAMDqSkLNmmsL+rLSNKH0uzQbAEEJeQKMCCPCmwSVABJAP/xAUiopH77+JfvkqwXGtpataihDEmOl1mAzswrcvk2TuwHcvvypmLo/b6zBKivb5wQ4ryOXelKzD//fZ7Fl9gw7AJj9sjIJ3gUq+YUNv0zZumfpnwQ8vU2e3oXB2+fkJlR+rPxI+RH4IfkhZup/ADIUSACAWIwuxCJWQB5ruD6iELQ1OloD8KLygyYQ1L8vHyFiISKUCImPXhoRqHAD+Mo7kHd4lPHtY+oGOJ4nHRhYx8okgMpLgCsugS6Fi5ZoV6+82nJQDGH1n9kzOx/4DwBfflSfPzw//5iF/YkPQeKf1eFE/om8syCR6/7FJx3df5kNUH7Tr/ZBuAT8qYr+FU8AdMiylhkApNnFsieAdBOPmRsAkbJJAGVPACGCQkiE96QgQSCk9PrpFZD3X0p4j/9+nKzzaNuyX5z0qP1cMo0MszA8G2Aj9PUBgKNGuWXU4JX1lH0NvqwrT4GT2cUw42EHAMP0YNeTAOoUg8uvYe7g7kBfUBKe3iXPb4PTm+fkGKW6f6H+gxeCH4AXogwQQYDIdH+Cmo4ApmUdY17o7fy6WFBZaf4IEQkRokSQPiY+eB7FR5JHJWOSB+nF/iG6e3Xz/fP980c/mvM9gabnp/czVQ8XAJe8bGxWUvXXfJe97irDOVj6Z3aOoe7agWkuIAGfPkR/vX86P7zEhzA6BPHhnPhn5Z/IP4GMBclC9ycQ+St/RRrkB2uj/jVXPWrW/Op3QWPeUdnn32rTW2YA5G4AUtrw/2wSAF5mAygAlOKEfqb+IxACCSDvP0rKV3/97vfP7G5YsuxVO27VB9AdJBuWgo3NKljVbVqlG8DZ5C4yCaC+vt0H0HKcxannXp/87OvdcLYcMcw8sAOA2TUDTPbqVXt79JX+Lz/3oS+8vKYvv55e3jwnx8uof+WF5IXkh+iF4IXoBQiAJLTXBqaCdLMDoCIoZFlRHrsOxcr8R6Ztp4o3aIoAZPMDsNEHkEYQFiJCEaCUIEOSEclIySPJg5KJlPHNbXD/6v7b59unPz2Kp8jOrnR8Qos3AZRWFkpBZc3Vo81TJHs+OE5UVhOkwNYhWf1n9kwX76a94zrK//wn/Pzzl+gYRIcwPpxj/xz7Z/JP5J8QIX3fL2VCf3kGQFn6r+r+2uy9sr1ucgkUtGdfyQGgadr6d3rm1HCn56s7AEhX/ymX/qnsA0AADwPySJBK1f/08+vPypOvP/1+UMngjO9NX8tuMMr2fABdbasVOzG1sWFjtmNsugF2XpCWanAP8AGAQ2a61OdqysBuGatvVb++nRfPC846gZgZYQcAs3eW1cicUOh60tFw1DerSKvTnn5pvvyUfPn1+fz6KR/4HyovUn5Iue4PMhQIeeiAchDhTCAwqf/FMgFi1pct/AClvKk0hahltCAUKn/7BwkRKA0QrEQkRETiSDJW8qDk4VEm94+n768fvn66eflrlohA454fYyCgetPIFR/A6moKE+MvYnH1fxP3gdk1LP2n/PFz9PnDl/PxHB3P8SGI/XPinVQe9qcm+ue6/+U1AAiQxgIyfqC2DIa3AhSR/C+5p6n6JfQ31ehD3ElrXaU++3wB6gu6az8/DpbmAQApXSqSEApJgiimy2yA/3xQnnj95x83UTA091sYbdkzD0glEBDYtNPX08imYles+XbbdANshgG10KRd+u5111UfQK+jTUmj+t/J2Tv4nwyzd9gBwDC9sWvi1+IDGKz7g971micRixL78Pcv4bdfvod35/gYqGOg/CCV/kkWo/6FyEb9i3zYne4DgLqOUH4BgC4QoD6wo5xDeeHKtqw3r2ojB6980gMqgYn0vhOGJCIlI5JH5cXKi5V3+EHG9w83317df/18CL5Pf8NMz0/vZ8p9H4AzdYQzCRkFq//MPmHpv+DPd/GnD99OhyDyg8g/xf4p8U/KO4OMkCSAoIsPANuG/9MV9R8vP43fUDboLWBhz7VYQMUv3UYXP4sFMt0ikXsCsBILqHJREiMhlSBKiASlsYDgw7svnnj9x8ebYK7Qf90tO+XSktkHAOXX/0yRnsU91QzTE70NzAxkkS59e9+Dare2occ4M663GlxPH8MMhx0ADOOAm39pM9wOS//deX6gf/5z+v7T9/gQxsdAHc/qcFaHM8mQ0lH/IFBJ7f2BlbH/VR1B0w6ahQND5uixgknbIF9rmBCQLzTFAqJCXMi+JYboRYSRwohEpMRRiVjJg/Tiw03w8Oru66f7v/9nGSvT8Qm9BAIqP4NjfABgt7SOGQhpLxWOpMDK8Vj9Z3ZIY6W0G+us8/l18unD95ebU5ir/7H3nMgTohKqPvA/+2jT9aqh/7NPOhuAwOQMuHyXzbaefVezkvKNKhJOVfqnPPgPXaIAFZ6A6kK+e+EDMCRIgJLyOSESpAQpSSSA3r/54olXHz/ePT3ZLgTjGtlNll3bos0HMLZJXk55l0tZRDYEYKtmiW1l4xBPwAw5MH8mD3ssiyrZLr2qpEoV1vTTfo+lJ1RbgFrWbevh6o3bAhGzUtgBwDDLs1jT/xoWpH8YfW3rsXz/vEv+/eX59MNzdAjjQ6H+n8g/kQwECCSZaQeEeWThmg8gdQNgZRQh1L+LHm4th2vate4MMC9obgAqDRvUPuZ3Bgp5khgSZp4AJY5KHJQ4eCI53ITSe/35twOVJAXbWFIKSI+vYNyyc2G01mBzs14YysirWVD939Z9YAay0mLA0n+Ffx/Unz8/Pd29BF4Q+efIOyXylMgTAoGSAAKwPt5fi/yDZXutf6rSv74G8GKxW3wAjZR1/3xdNvY/88pnsjaW/frU+o4fKAIAakc2pQhJ+fJZEEkiSSSIBNHbH756qP7v7w/P1ucBmCx7L2uf5Uue3VW7bEP8MqTHYjWx0hqH2QpDPAFMyswd+6ujlirVnf5zCYkZQcufFvV/KdbZtmGYjrADgGEABllqu8bdQR9AF/M3rfTfMREOEHvw+UP45Zfv4d05OgTxIVCHszqeVfouQZFk0j/pgwdrun/JBwAV6b8sGejfFeorS20rNKzXO/9N6v9FYiiPFkQAEqCE90QYEkZKRCSOSsSxSATST78qIX/46/djHA7M28EMeKZ0H8DIJrGFFrVrNcL6YfWfAdjXiKqp1P/VZuC3O/rjw/P3h+fAD8JU/fdOiXwBIChi/lBl4H/ZcJP+yl9RdtVfPvXZewSAFwtc9uJfaMlZKm+j/8xMc/b6W9KNdeoDwAazXnj3IY8IBNkohOrlpP9QvngWnkrV/9QTgI8Yvvf++7fbeJZ3Ane07EaPvmEgrN0xsKz+75Ad3KmNeAJm7upX6ukxDKugTF4BLNeApCdyLrPe4zzrLnAM4yjsAGAYpkpH21zfbIfSPwC83NOnD+fvH76lA/+jVP0/nMg/k3dCAFAyH+kvtOA/l7j/SPWxhJCONNQEAk1EaBs5eMUBYFo2OQDM8wAUYCb9Y8UTQCgwQj/KpwLEAlT6wsCffv5Xeq8//3YTnia7qeMcaKTlabsPoJdyOFxm3EaT1+pVjD/YgCNs4z4w+2Qq6b/t0K7zcqT/ef/y9fVT4AeBd468U+y9JPIFIBHp2H8QALKm+2cLNd1f9wSYLHjZiw8Gw11eqNsbHdSkGip+p4MFKu75yjflVq4JcTHxFzdAnfRyEl+8pK8FFkSggHzx/sdvQej9/offeIZhWLLsV6f3jdG/LmlcnfrfepZxec9sk4r/rLbE1LDoBmjHWMVTzbaU6zosHMRNR5iIkcP/J83P1bZwGKYj7ABgmIyZRwZUyQO1Lstg6R/qzYsZ0uEA/75NPv/08vLjU6r+x/45SYP++yfyzgIElsYSViYBoDmMQCEWUF07aNIRoHlNVwdAHhH4siZbyJwBChCBqoGANOkBEUiIM/qhgiRV/9PPu3dfpaTPv9+epnstsOlp7PWEFk1fuz4A6FucF68FpmHMZbH6zzC9MNc5ezLNdQIffnt3/vfHb4EXBN459E6xd0rkiSAWSuZhf2RtBgDmnvuKvTbbbgSoBfEDzaxD20LpBtUsD+nrK8P/i4XyN2n/ao8CpKv/lC5kbYLqPScAQE+chCRUCJ4EJSkRP735fg5e//2P7HFLhjKk7Z1fiWESgOlHPymMrQWzP8zOgO1hpas/xg0wwuwiXHEMZJtRVtvP4APIklQkA0zLTWvWhbPtpTk9PYyTsAOAYS4s7ANYesRNF3PA0n9BIuHzh+ivn74H96f4EEap+u+fUvUfRYyXEYXlUMJXpH9dOMgXEPLO61UHQH2ldlew/j4AupS7VCDI2oC6JyD9iHwQoQJAAoVlT4DmElCe94xACAqBEAiJ3v5InlCf/ufu6YsYnun96fhMVUcItjaP+radum7vfGO3RwJduhZW/5kq2+3/NF7WnqxznUTA7+/Of7/9Gvhh4J2DTP1/IQxTJz0V8X+qH9Q890bDLaAa8AeBIJ8cAKbwfcYFaM3iuu5f/Gy3cvl/8yhAVH3970X9R90NAPnAgIZUeXgGKcETpAT54l6JD2+9IHh8erZaUMY1iy+TAHKRC9qf/koGXz/B8LTNekxmIvhmAUBbr2MT2OqcD3ADDO1sYMP6xpXFu1KGDFwaRHs+LFWG1tzOYZiOsAOAYRiADiavaYNs/c7EhVjC//yv898/fc0H/gfx4Rx7Zzqc0rA/SLIs+heRhWsKAl7CCJApcHA+MEOXD4pebKsDgCpjLQCIal1gunyX3gSoLVBFKcC8qZj2qeniCSBEUACAkL0tMJsEQCSAfnxNnlAfvYdvf03jA7CkFEAhFkCW9+PL5pWDbK23VGXM9Y3Mm767b/1WMFvGXMnszDob+e1d+Oe7i/ofyVMiX5QIhDKK/hW3vSb9Y8UfUP3kZqRux3V7XVug2rsACkrGoywgpZp+Nj6A9G/MXwOQzyw1WMeyVRKlSQCQTwLI99KCHRXpIB9PICVJSZ6kRP549xy884PgNoqv3I5+mCz7AGuv+wCq641bF2cyrp+I2SwQmzrGCqaCZP+h2VJxLXLn6kX1t7xXXJvGG6OnR4uGOoXdr3RJqylhGGYW2AHAMCUGdCpsjQwoTO/MdnCs9A+70xcI4OMv4V/vv4aH9JW/59g/J/5ZeSfwzpiG/cljCBAU4wrx4gNATTKovm0PTMtQ/s4XCK7cn2ojjhoWNGX/8rNYY/oQasP/IV+4pDz1AUhSsU+Yvw/gFZCH6qN4/OfTHIECIE9Z9xKqO0ZQ/92wWXcaR9Vsr+Fr74pY/WeYjkxSt6zKNDfx+7voj7df08g/gXcK5SnxXhJxEiQJBEI1+r8WAij10ItL6P+yvUZ9TSbENxvuxgl8hTXX0bKeqPYvTZ8u/bfqCShWor4BlSIC5QP/AUBA5sIXmQ9ACwdUhQhBpT4A8CQoQYn48PgteC9/++PQdC8s0tGyG1z73fz69REUg1PStRHCFmhd8P3qhvFZW1nmTdE5r8vxQw1uqT/echDjf/WVucVIq762LuYw6n1QMGXsUoVjEw0ehrkKOwAYpsqSPoAJjtZ+omHb7Fb6T/njQ/T5py+BH0R+EB1fYv+ceCflnUDGxYsEq7p/ZThhVfQ3qv913T8VGLJlLedaMrGi/tdHnhil/0L3L9YYP1gJ/pOLCMWp0RNnIUkSxUQCCIEE0H8J5YnHTx9tvzAQxj485lcFknl05rC2emmvlXWAVkav3OVbsTumGeC2FLvwLA7i8+vkf95+Ox/OgRek6n8sT4k46X76xsg/F33/SvR/AsSLvl+x2s0u/NKC8ae+khrWVOT+FrRt8ohAAIoyxZ9qMwCyn5lufqFoG4CA2BcnkII8Sb4kJX55/f0c/vDX31Z9/KMtO2DZ8paz2YnKYM4HlisHZmnanzgXS+jUnfOedVDj5iN9ANrPzA1go3IsOVPb1f+ruFg4GGZNsAOAYQzMJsFXmbEXMkz9L60ZmUfL97eG8PFt/Onnb+dDEPpB5J8i75R4z8o7IRKmwQSwpCNkbgDCkgMAypMAjOo/AQAi6nK/HgsILmJ1KSe1H9UBbLlkQNpyWtgR8j5yXfdHaAoZnHb/s21y9b82pMXDQEgSlH+ABNCvPyeeeP3xz4NKRt0OA6ant9cTTeXcSZcs+wCmr1+sn6HrAWvbDU7JmEtg9Z/ZD9VayGKBLhTmdRJI+PTq/HI4B34QeOdQZsF/kBBItqr/Ig96Uwz/N0v/BjueW+2q9J9aXoRLhpaUlau5XHcDVHbVPAHV6ECF4q9vmVIo/lDMANDVfwCFoEftIz06EAJ5cAaUICR4ghJBB/Hr6+cgePz+NHm5GWDZi2ZRS9Slyi5zwEZodfAtgwkzweJzZzONi6kD2cm7YsUHkJ8SrUwF6K7+8/B/hpkYdgAwjB3stgombWMMk/5LK3c58B8APr9O/vz5+8vxFHq5+i9fMjUBZK7119/6K8rCQX0SADQ6Awi0NWBagLbhGaVWm67llEcOkt41rkwCKN1s7UxC9wcQAWbBkS9XUWwsMRSShFLSJ0kUEQmAX39Wvvjhv387ztPY6/hMpZMADM3jBiGst0BALQdjLrD6z0yOE+N+xzKh+q8fc50Z9elV9O/DUyiiUISp+h+LFyBAanLVC0DUzDdC9m6ekgUvXALaC341200Vsw6l75JRNhn0K1xMGRUh/qnBvl3WXzYoRZq8mCLjDACqBgKCwn1N+aQBAenLAFCASF8GIF7fiv/84P2f4C6MOl9Wj+vusrqKeXpflx1nKPszGyG2ecyewMqPbuW/cauJfQDWapurPoD6yeor8zWF03QYZvXAKfXfOutsMjE7gR0ADGNmgIm30yrIbfYUbYyO9qi+GUv/APDPo/r489Pz7UvgB5F/jrxT7J2UPAGlgYClrvijQfqvDB5Mu9No+kBtOS0XpYXiXzXqzTd9fcUNgA3fFR/AlQ8C1OIUFydFAJAQSakSIuFdZgO8f/c1CN98/GTbEo17eHSlQG9CE/R5hWB9I9O61T4NDdQuc9h9mEf930xHg9ktpQpk0gK9wtrq24H+ev0SiiiUYSSCSASROAEoTMf+G+L/IAASpfq+7gMQuUu7aQYANPyE5m+ArnH86ugWzmjtWkyg6V+XFaJ5KoDKHB6kvxy4cAYoH88o8nkASvz0+C0Ivf/vk9WXAYy27IX3o9CzSvZ98JGHJ4rtEMO4SFttMLEPwBpXK7UuUwG0NcOnAujmAmrL7SvnYW3NG4YZAzsAGKaRxU283QSw+j+Gb3f0x0/P3x+eAz8IvUz9T+QLEqE5jjBeFlCANoowDyxg+GS90cr4wSzTqtpBOYFNOat3b9PfFTdATf2/zAYAgNKrAsuKf75MVI4X3JAWwvS1wIJIkhJEqJB88f7d0zl49eWrMO1jmV4PVGPDWBdeWjfO/nHtLDDBY7FMxWVJ/beaBAtbMltmhbp2gU3T3IW15dWnV+G3m5dAhJEIIxHEGCiIivf0mFz1FW99OWpfe/wfBG32GwDgJRDQRTKpGnEtR8fnrG7H8zWoG3rI7bt+St3NXyj+uk2HQv0HEGWjV/oWEHt4IiFJCJKCPPHLq2/n8IePXybvaQ5oKhc+gC4ZP2HBd9lGMi1wNsK+M2FxgaAjg30AUF6frylcp90xhDydZ4wPwzANsAOAYWxip0kwQW+jy/FY+m/i+YZ+f//y9dX3wAuCTP1/ScQLQAKURv4xRhPOJYPLCwDMOkLuDwBtJeT9U8SqfADl3Ozl2akPGywC3pA2Nk7TEQjSYR/m1wBkiJIc0FBaEKDwAYDCNF7w44348N4LgvvT2WoRaXgUOz6hTZMASj/LBxo5ftDx5+N6ptlrlA8+UscdufvAbIo5C/REHssJ+Hyr/nr1HIooEmEogkgECZ5BCSpE/yIEUOHCR4N1vljtQt9Hkxe/9AbgwpEPl58G8z3Ajpe4vs+VspHZw9yiCUJCUABAILDkA8CLG4AEIFVeAwAASORBgOnLAIQEKeEg/vP6+fvp1XNgr8SMa2RX3wbcautLO05R6tkUMcx6WYsP4CpNtZvRDZD6jTv7AAwyQlOm8fB/hpkLdgAwTBuL2ffCys6ivRs3MKqck5zebQIffnt3+veHb4EfBt45TNV/eSKMK2MJayMKK3J/XVkQNd0/+6lFFgYtB5vfH4i1pYvUr9/CVJgojwrE4mg19f8yPPByVLwsFK8BqMQKyK6lrJIUKVCeOIEnQUlSkpR4c/8UvPf++7cbZfdhs6cUNPoAKruM8wHAep8VUz4Py/up99pGf42xifv+txYWKdDO11YxwqfH84t/TuP/xNnwfyWyF/+irvtfAvtQfeB/ZRJA4QkAk5MATMswnQOghfLcgso8gNbdygP/MTfrmE8CAFBAArJhoJVwQOThCYQEIUgIJeTj8fTu4e458G1em+k6Rln7ReYBLPjkMgxjhVX4ALrUXC02vd7/AYB8dlm7GwBBMw5dErAIU7RkHG4dMQywA4BhrtLXvltrD1jyAQxQ/w0e++lO7zaxhN/enf9+8y3wg1T9j+QpkSeFoahH/snWyItSgHXpX1vW3wOMaUspjwJUVRDyNVT+WSy33CkqRGxte10RaHp/oMENYPig8bWBZIoFlCceIfHEiWT2wkDyxc+vvwWh/ONPqzJBA1ae0Cl8AON3Lw4yK0v3f3jgPzOWVfsAlsJhN8DH+/jvx+zdvxEGEQYJnlFdnPEERaB/Y5yf0rf2yl9RcmmjyQHQ/gZgBC3L2hwAVN654Z/l5Y51XGGHix2qO+oD//XvIhYQlqf9Xd4GjKDSQEBK+InwD8J/c3/6/N17CScvKB0te/VNAI25ado3P1F95ZWT9t2BcRm+g6vGrmq/GR9Ay2atUwGgxQ1QV/+NecVDeOaHG737hh0ADHOd9foA2qt3lv6v8tu78M+3XwMvCLwgkKdInhLvRYkASQAVwQQq8wA0NYFEruk3Df/vphpUpYA24aCBihugIu6bv2ulr+IJKIUMxpIbQAEIUymi9GoFRCBOIAV4khJJvvz5h6dz8PrfL1ZfBjDuUWyZBNC212gfAIw4whTN47ZjNvxvtuH/rP4zzJK45wZ49unT4ymQYSjCEINYBAmeiRAzBV8z05Q6AxDbgv80f1JrVvcBZDJ/xZSDtguU14ONHCxZnrbDdRqMKQD1V/uo8tVpr/zJ7CTliyAg8TBMMPTFIRbR68PLu4fb//5n8kkA3akHAqq4UdpvxihrsqApYivIWGTO4uR40d2YDwC6uQG0n0Y3AHZR/xfPN5eaLgwzG+wAYBi3GaEmDlT/F7fHzvDbu+iPN18y9d87Rd4p5c1tKgAAIABJREFUki9KnPLIP8a4//rQQoT8xb+66G96AzBcllPp4LJG0+GragKYowCVWmFU3gMBKG+3GRutpZcEFg3bBj9UofhD7YWBukCgc2kS+mmsACnJF6TEwxF/+dEPgvuXk9UWmekqezXXjeME6z+NuwzGPVXNhNW6gtV/Zkl4PNQYXKqwPt5HX++es+H/InUAhHiJ16c74HVPQG1lyU9v/ECLA0Cbzwdl292k+8/jYu4MVdKcuQHw4g8oBv6DZtkJACUEnjgmIvTFwRf+2/vT5+/eKZo82X2FuJJxn7oML2uH2AoyzHRsyQcA/d0AAICtL/sdOfB/XbjREGKYFtgBwDCd6GvcbTYGUgm3NcpLX9D4064xXrme8v2G/nr1cvbDwA8C7xTKUyxeEvmCSnt5oCH+j/4iweYX/xLq0QO0iP/6oEIofRuC/5Tz1zwJM93XoOlrtxu1gf/p73r51dZQ/hNLMwDKUwHSGQAEekHA4qzZaMGLD8CT4Is399+DN97/+eNGKZiajk9o+obARXwA0F9Vm7U53Xyy6aT8vrtstX/BTMLKbdbyOOAG+PdGfb5/LoL/xCKI8QyqMj+vi7iPWuQfo9v+igMg95sbpf8GI349766aoPIazQK3TRAg4+p0ryL6P+hugMLHnw//F/kZEEAISDwIEjx4IjwI/wf/5d3D7f/9161JAGjM7Px6LJdiVv83A2fmBphCr9+YDwA6uAH0/7Zcuy31f4pb5j6rSCSzNtgBwDBdWdIHANlwsu4HbDEZc6j/xTFXa7o+vQq/3byEMgxlEMpTJF8SeUISABLq0f/1oYWlsYSXhWogIMJ8GnqzdlD9aVyADrlcyNeQn7Ro2Zl8A+aftfXUPgOAqpMAqDpaUEDi4wlQkJTkCUrEz6++BqH87fMalIL0vxP7AKCzqjZR18NwWNtnYvWfcYU12yxXWM4NQAAf74On4zkQYYhhhEGMgaJE6C/myebhibIb/hKsT7PUdQ9BhRaXAGTf1XH0UF6AhsMa86+7tSk8+g2af6fKUU9tJQpQ/rNo7aTNmew1AASEHqRRgKJYRL7w392ePz/556jLeTtjsuzdrX0ljy6ZlbZusDGv+8F2iNkeXKqNbM8HANcMet0TUP9Xy14Lwi09ZsewA4BherCwcSfApnHenTH0Oye9JAeGBA7g853669VTKKIojyEQixMQYFn6x6r6n4oIAi5jBqtugGa9IP+J+sph8kEXWrR+zIqa/l/SHQalEpOPEISy6H9xBuT5ANqO6VURAEmIUJxBSJACPEGJ+OX193P4+q+vctB19aDX49w0CcC4psu/+tJlLM7kdDjTDIm5egoXOhfMWlmXD8BZxWEJ0//nffLp7ikUYYRhJIIIgxjPSKlOXY//k7rhm0b3D/hAVf0vuQ36GvGKoF/frG6U2stuj5JdHgRf0/2zn0VmUj4aoDIJoHgTQPyj//L24fb3f+fodXZ8JtrfBgxkwwfQ+dmc8CF2s35gmE1SVCUuM8A6twj9lQ06HqcvjmdpwYpaj8yOYQcAw0zIJH3zDpa7iwFCmNGgrkpSiQV8fn1+8YNQ5mEE8KxICcqDCOshgCiXErApfHARXzjdrK44QGm59DZg4zeUc7N7zhrLI5LRqUSoFbXWgkIAKACIQCFAbQYAAmlBA7JiJy7fBB6cAEURC+jhKP7z2j8HD09ne4Vm3KNYmQTQdxKtxbI/cxuYGn902H7AKWxsv5ZuAuMuMxqsihu2776u29UZ3QAJwp9357MMQ7wYbiAC0qP/d43/0/jBzHPQ5gBA0H5Wvus/wfQTrv23j9+ZShJ3/qO9+NTbGNm3FhSouOoi/k/hBiAA9ChQcEjwEItDLL33x/Nf3kMQt15oXyxZdnNejCm9jtghR5KxGTg/N8Mk/fNZDm6LYQ2Iq56AqzsOwHpmut5yYphpYQcAw/Sjr1m33gzIDjioZ3LpgM7fNFnPVICP9/Hfj9/1VwjGGCCJvOsr9BBAWAgKZFT/9VFycF04uCL9j3QAELQFswFtPJwRBNOLCLI9AYqB/9qcACyHDNbLbunbwzOgBCFJCpDizd1T8IP3/3687XxpHTBdWa/HU28tV1rO1gZeOsiUdQWr/4yjzGKwyPSz3zlXUeJnycwvB/XtcI4wLtT/BAOhZO6b14wy6a/q6fkxW3DdiIM2b69d+u/kAzCZD6MtMholw95Y3aZ+8EpiEAC0NxWBfuEIkAdT0l8DgABCUOJR6EMYizAW/hv/+d39ze9fHZoEANdMOemt5S4F2HTWxZ7RVVQODLNJ1uIDABs+zvb+z35YcU+P2RfsAGCYyZnKBwBmV/x1A7SgPXbeDfDs0+dXp7L6f4ZqDIGLA4BKIYPrH+N66OwAaNIL9KnpnXLzslHj3TeW076FtxQFCEvqv9LypEhH9o2QeHgClCAECKmE/PHu5c398Z9n0XgqS3S8wsrbgA0bOF2uB9GzorCu5vfafle9DGYmpnyqm0psDyNJq6p2Jrb+Xw7xWYQxxjHEMUQxhNn8PM12lx32Vz65R7zLBxoWjN/GBePP+qpKJtbvv677179LuxCA6cVS9TSka4zxf/KfVHoJMAKlbgEPwgRSH8DhIPz3h/Nn+RAm9ascQYMJ727ZoSUQUCW/VsS6UsusCC5aHVmFDwBsmOZJL5OH/zOMbdgBwDC9cdGmVxLU6o1f3vY57Ab4eB99vXsOCgcABgmEqCSipv6TNvAfWoP/pEIDCkOH+YoDwKgXaD9Ld3xIVtYUCL2fW3/hdFOpT7ckAKW5oyqSRxExQOQnIQKB2UkBgIBIQgziREIq4cXSv/OCdw/hP883Ay6tkXGP7tVAQBtxD1Dlb6+dltneuQqZ2QyDJ7x3O2rLBldOuNJCP01+RghfD2EMSQxJAlECkYIIlSB9ll7mA+io6WPZSHZW/6vmr9mIX8kIXeXXVlYEfNCXO0xFI+hpjqiWD9lCnpOlUf+QtQeyNQJiDwIfDjGEsfDfHp7f3978/mS772nVskPPGX5L0XbFK60cGMbIRL3uGTrzLuoFDbjZMV+R+j/FkV27HcxWYAcAwwyhr0233ga4csBVNDjca238c6M+Pz6Hmvof4zkL+EPFC35NEn9tJepj/wkBKqH/oV1EwMsa0Bb0zGpa7kh7ZIABFGP/IQ/6XxkwmP/MsitNg/YyAAAPAsJDIiIfo1j4b+5efrw7/PviyiSAlLro0vTflh0dZa56w6JrYRVVHbMF7CnXHQutocZoCDDiesVixGoD4IunvvnnCJIY4hjiBCJSgKhb2NRYCwIsr2/X9Jvsdd09kC9QZU3l27jQnhG1f1H7PIAuw/8JWuMBmo5cyPpkyofKmsvLADwIFYQJRDFGvvDfH4NPJy+yOwmgge6WvYv/ZB2wRZwOzltmACvyAcBUHnpX2ORFMUx/2AHAMDMxTxugi3Vzq5PjTGoI4ON98Hw8hSKM0iDC4qwgEUpqfdp6fP9h7xKEdgdANisdoZt2UP9Zv7imbWraAV1C3VRvTt1lQE1zT2q6f/YTAUR+2Hz4f6aaEAJIDDw8+iKMhX8ng3cP4b8vzk0CGFxmnSnsZRpUxXEHmGPjFfWqmA2QVR7mqVBdD9Kp0GqzcNZn03thyQ3w5RCHIkqD/6QzAPLAfTUfAFyJ/0MlB7xR6zf+rCy3fBsXoHmNTl36L342idgtPoCW0zX5GIwNGILsDUmkqf/ZfyVEHoUehAc4xBi9PTz/dLz5/UW2XmZ/Rlt26BgIqO+RLW1j7WQMw8zMunwAKS54AlaXaStirU1Gxg7sAGCYgSxu0BdPgDXcmArw8T756+EpHf4fYundv5r0jwACKNcIagMMseYhoMaYA9DiAMi+CcproJxNHbWDXuPUW7le4OoOACi7AQSAyvWXfPh/6u8gkY0WFEEiDr44HET09ubl893hi91JAKYnp/vTNCYQ0NX/zsoS1Qer/8w2maw4OlRjTMe4NkCA8PUQXIb/U6wozm13109upqHZRl/SV7LpCOX/VmyfcX3lag1X3nDfjdJ/sXlFvm4SsSlvXdSPU12D1bSgdmQ96yjPE5EbycLEk4TAh0MCBx98H+NXfvQ72HYAwFjLXmDdBzATtqsgtrDM9pmtI73eHvtSngDr2TVp+qc4uNP2hlk37ABgmOH0NejuNABc7MksOtzgLOHP+9PZC0MRhRhGWfAfyuL/QMUN0GkgYesHGtaA6SdcW6jQpBHUaerqFgfqUVLKG7dFAcqlAciiBJAeOBg8ChMMPRH6wr/3gncP0ZeXY8c0jKHX41mPjND03/Z9F2A5mcCW+u9ILcowfRlWdF2011Mw1A2Qxv8pgv8kEBEBat73Bjc8dLDOYNxMmyUAeTMA8vVQPk7l27hg/NlE0yQAo/rfMvy/Lvobk1G3dU25p/9Xs+mEEiIJkcRYYiIhufeig7gJVbfLHU1Hy15MAmjbpmfxnMlUsUVkZoML22DckQCGMWcnndV/hpkSdgAwzCiW9QGsvTlhZokJAR8fon/unkIRhRhEIojxnGCASiKkbxHE0gyAqgpQ+Vd94GGdJsUB8j5oRX2A2s/6svEsdVr6sGXJgPDaXu3BBPJARqYoQPkaLIcLEAgkIfQg9EUYi4Mv4neHl8+3h68nqwVi3JNTDwS0Ah9An+udolax5SfYYI3HMNe4WmNsx0nQvwHwrxeFGMeQxBAnECcQgWpS9i8nSV0C5YH/0KD7Q8OaBgveqP53NuKor6fy37r0D2VbbFT/jT6AhrMbjg+axavL/aWPFgvo8pEQS4olJB6oexk+eOqfcIK3+4y27NAaCMi4ZknYHM4GZ/VWmbMjvY1O+6SegCnyx6Eqm2GcgB0ADLNusI8tbpcs3WJGN8B3n/68e87G/oswwiDBAJQAFKBJ/AgC0qgCWFX5LwMMizkB118zWH8nMGQ/CWoroWEBeuaRsQiMEaub7xNV3mGgRQEiTf1P3QSZZEAAiKQ8CBI4+CKKRfTgnd/d/f/svemS47iStukOgJQUocilMrOqTldPt43N/d/TjM3p07nVycg1QiIB/35wA7FQ1A5S/hgtgiLBDRLowOuAY/v16dSDAEIV8UtWzi9UAM/8PCNPfxL1fwbtJuaWOfIHfEM+ANijAvAs4Gu2qbr/l1BoKgwVSO3MPTuWfp99W+B21rF/N9YWBC89xP8GV7yPFNkeyB3fDeBb+fYv9FfAmsXHvkpskEF7wh0+AH8RoAVoCVqiXkDxkJ3HARDhGMueqA+AzSHDTJF5+AAqTusJmGi2nMkYXN/GMHOGHQAMcyz7WvM5Wf+zcxE3wNdc/8iet1huxaYK/mOwRBJAGIr/szPyD4xI46V3lYKgZOAIEMH1GH6TPtiqHXASETRq/TDY/m88GaHACI34UmdyNwgAgBRtDWw1bkuRlyJ7t3j6tMy/PV+iQjSyeB4/CGBMgsM5phfk6e5irxNyx38mfa5rvpNQHi/JiArAF6W/58+F3f3fM76RaXiC9rdnoZqLxA6Exta3yeyV2F/nkYKP5xsTf2/QDRDz6+8cAWBfwtkYvAS2NrAXYanKfK8DhKBSQilBV4MA1qo4Vwv0uCLqDwKAQ30AJ/eOH3oAwzCDcLP8SILW44Bjz8Ft1ZkYZhTsAGCYE7Bv5eG0lY29rNuUBgG0nNkN8Lgo2ugBJW5L2ICppvMNhP059QLdCtpbgn+dXHByJJZBfvbt/BV42gEFNYUB2pR+/J92xZ4nEO2owQhGwTaDbYnbUmRr9fRudfftOR936dEcrRSk5QO4eANmlKfk6FNxu4xhRjIZm74XgxWAR1UUUJZYllBUEwBE4v+0mjV6ZjRkjnu7gvgpnZXg3+BK9Wngq7N2EYwYAQCWUh/0DQQPt4n1GKD+k7an7Wdj3weDgAjG9gGsRbGUq2cdf+JjCFn2A6z9Mcb99GaLBj4xzNw5rrqeFnN6Fp/go2F811k5d31ohvUt5iZgBwDDnIaJGvQp6QXncQN8lfQtq/sPllBqKoh0F0AABUA7FKDaEphRsIv/04j4kVkHHTECelGAaFg4GOMDCDLQbnVa9bG/Xcpx0wJbaapBALW0gW3Xul6HQcT+IAAEEhI2ivIc8hLzXGTv8l8fF9n3zSV+racty2f3AZz0vXOVlxir/8zsOdUveUom+7SEKgC/BDzKTYGd+TZUinD8H4fgLl/Qh9CpWisP2Fk36CR825Q3tq8x99CuuBd1fyK2mQ7uGuieT81t9C9rH0I04hL+VewV2wfgH2tVjQABQIKWVEcBusfNWplnfbkoQDDastuzAe/0AUDot3Vis8VW8OrwVzB7LtyGn6hkcDBXedjpVpWme+fMRGAHAMOcjL0M+hWt/7QVhFO7AR5z/VPVEYTr/oMkuoA/VDVirREAQ7F97I5v0NxoPLG7DqGP0D+bsxL8GGNnn77YUYHGf+SYyGgDCj5vJ/d7gwCoGgSgYZvDtsTsIXv+fXH3fTO9QQCxjXslCB9zVU7V/Z/Vf4bZi5Hv63nSvhQQoIr/kz2XUJZUme9yn+B7PWMUi1MXv5XW6Lf+fudwAPBn9AmuBD/GtvezoCf9A3QWydpObYLgIbGrD1cY2r2xvg7uUwgqq0EACnQO+iHTnzdncwAcbdkBo9+Hm7i5oP1x7IUO3Mcw14B/k8xUuEA1aM41LWbmsAOAYW6RnZJl6pzIDUAAj/m2BF3WIwAKDQV2AQTsyD+jogD1O/XH3ADWRrQTwK6/wRWb4EYK7Q3L+sPd/60+g7F2gC9VOE+B1pmdrBPY365ga2ofQF5i8W7x9DHLvhen/rWGlIJ91YPL+QDO2QAbf+5zq//czGSS5Thp8TTctA+gggDq+D+6ieBXmMH4P0F5uh8RKGheh9XtgcNHWHB0No7BMsGdsg/9Puu2Rg/eFtuIDwjXzi7//E5RsB8mkGkCSkllOwjgAQuAbJ8HPwEHFN6RteWTvROu/nJhmNvkwqY9hZoEczDnq2PNv/bGXB92ADDMKdnLoLP1P5aj3QCPir7lz618oKkkQ4DC7dHWdmBHhKH+bp6+74oFzi5nxICvlYO33XngMQ/vi/LtxqD6D5Et9vYxF6aQJOFnV7W48wADEYKWsFGQZ7jNIH9QTy+y++/FhczWyOJJNBi6+VRM7U3B6j/DXJGb8AEgbEAbMpq0hlJTaahEK/5PL8Kef3DYdvtGPHLt3nvfOqTtiA8AVuQfN1kF+ecf/t5i3f/tdc+LT806tgmgseNknTVYVRg+v73QcNhDBJBURwFSoNdY3En6pc/2O42Y8PGWHcYFAjoYin5gGIZhDmX+tZ/j4Py5edgBwDAnZio+gMkPAmg5wg3wRZVPcltAWUJRVv0HqQ7yEw7i701tF9KyBzbGRAfwtvh/nYd0Hjjy/N0vbFg4sA8PfhzoOegTPLmTJz35wMrt3mQAirYaCgWlRC3BPCh9FrN1XDk8YyCgC74dTtj9/+AErIEwzEimbbVPwQZgi9oYMsIQGUPGj/+zayaeiuCKs+4dRcFkzUkC77KByznrA9gm2PnomDE7FlCzizCU2DZc3Rq6251j3cuFctVdBLWDAMw9bB4U/Spxj6ffl6MtO4wOBHToNc56doZh9uG4N0bql7sRLlAxuvG6FzN92AHAMKeHbfoV2N8NoAG+Ztuynf4Xyn78n1aPHpbvB9q9EEoG3XZEABgKH9x7pINUg+6H6DfmYbBhj5bE5K0QRHLcjypgH+j0GfS7EApnI4KWUArQCrQEc59tM1wUlypdexRkT4071gdw2TfICa/G6j8zexIx8cPvk9l7CDZAG9SGjDFk0BAaMNWeqLH25FzHUvtbAllI7nnQ6+Nvd/wP2vHK9MMu8duR+2PbY9J87CP0LbVzfqeq4J4fuy2ONbfPEK4OCSilKatxABnqByw/QO7ezvk5oPyesseMPeji8BMwzI2RiN09LbN8qCsy9UrP1O+fmQjsAGCYK3NF6z+fQQAtfp+2OI/KfMufC0v9J0OInuJPSNgLJtC07REAsepyiBCUG0L3YW3vdSEcUP+HfQBDT+uJ0va2mL6/0wfQO0n/l2Nf0D8WyY0S0IgIZKn/bZwlQqwCB0M9c+BabNcZfdme4ad6XDkk69l2pxzcnXhJPPJlxeo/w5yQW/YBbIAKKA0QkSEyZExlO7yEAdG/H/q/TbND+vfPE7pKpZDvUP/j9xa8UIujs7chhGKif3ucv91Z9y/hWPzYSpvYqfYg1I6QLoGAUpCWplSkJZgHLBBzak9zDjfAkZbd8uQcW2FmIzdR+Iu7KViRny6Xqe7MuFLF3AzsAGCYs7BXFSKR+sasxIIRLclHpZ9FOwKgMNTF//F8APUpsT6pI+5bkwQ428OLvbddt1fG+ACs9bFfG9aP4fb+G1b//WZ/7wyhiw+c38koywfQ5Xbvo6jmDAQtUS+geMj0l+15LFeoHB5TNmMFKrz9eq+A8Vc+uHf/wN4UXn0Mw0yODVIJxpCpBgEQmsZidQ57Z4oW7907xsjGLHgwsXUlF/+tP+AJiFEb077tjyny1DgJfDsW8wQErzgg/dt23DkqvAiqRwBI0mvcrsXdd2cagJO7AY607ONc+wOHj9vGMGnDv9ozkYgEwIzhrCrJfCQYJnXYAcAw52ISNn1Wor9PvCVZIjyqTVFP/1tqKDQUaPyAPzCo47f6QlD3j9E/OdpXAQBsRgZAvxdhcKV6zJHf4ZiefVi3d+vO+J183+sGR84J/QeELnF9QPssBO4UC7ac4ekFUAoqBVQzB5q1Ki9suUYW5OAggFHlyzv7REslq/8Mc3ludhDABo0hY4CMMYTGREcAwKCOP2BkYznn7Iqdbdi7cID676T3FXzLiIePirrz0U05cP7Yhdy8DU6kJOtpAEpF+g63d0jfg48fHJxwUvay7NVt7DcIIHL2SVi9Sdwkw5yFy7feJ6EXJM5cKzoMcwbYAcAwSZCI9Z+nWBByA3xR5qt6brv/ayjAQCMfWAuht7HFdgNA3A0Q0x2ae6LeqXp/m4a5NQwdAmcYy16tetsTEEw8fPXIIIA6P71e/9CMorByu9IOkIwELaGsBgGscbsQy43Z57nHc1w5HO8D6DbGNYLLlMRTdf/fd28KrzuGOZhETHbFPA33LjZgDBGBISBDhsggiWZnzObWNCZ1jDofI2DNe18E+ucJG/HIuITgXTvJBnzwlimn2GA+R77vLoTdlthRnR1vhH47HqBfF2o/GCSDREiEBBJ3FaOTDAg42rKDM5ak2e5uG/c0x5DOa4dhLs3F7C77AKbFZSpAZ73KDdbhmOvBDgCGOSN7GfRrWX+nDTNbKaHfjHxU5UYUrfpvqHTi/3Q919xvxZEV0NoI3jp4sYahaTMHfQYDfwHciLr9c+742qj547TqoSfQ+z6AZlfT/nV7Dlo376sJ/lWoyWQKPUbr8+gJN3XQANAS9Bq368xsNgLORKgc7lc2R04GQDvSXKAksvrPMLNnrjZ9g9oQGSAD1QQA5BiOlnE5EFbnd/kSYlvsmkPMzeAfPnybA2Y3Juv7Z4ip/8O+hPHX8nPPWSckqNR/JJIjrcFJ3AAe+1p2QPe3VPsGRp+AYRiGOSWzrN+cD84uhh0ADMPAfAWCAAQAUAh4lJum+3+pqTSh+D/+6HVPBfAb9s66/7GR/f3ztOp8E/kn0vE/Ih/saFy2KX0F32nP+3+dNM2BvdBD1O8h10vcPEh32hF5W992PQ8wlVUIoBzKB6X/Pp8DIMJIpaAuSuR+M+gkSoBT3QWr/wyzk/2kxv25ISMOAAAFVg4AY8AYMoQGzEAGRA2N17F7QJ23TxU8ZN9vYOe1YkcN+wDs7T07joEz+E6FgSsGr+XkKoWyqDsVAlU+AKhHAOx+4I5j3ADHlcD6yk0/Cgzu3XU4wzCT4dw2O4UrzoCL1XvOeqGbqr0xCcAOAIY5L3sZ9ESs/+ylhA3QM5YaTAm6mgCADKAf/wdgUPq31f/gLrASQG8LBc/TrHRN9QH1P3TaKAN986uLDfgA7I3Qb+oO9zTsEjf+Dee0wTsPKDWCtCBd+QAk6gcsAbL4pY/maKUAQ/4QP9TBzoJ2vpK41/MNJN5rVwovN4Y5FYnY65aB18X8bPoGaAvVCABDUP2v9ux0KrcEzLSVUcMZNmJvbwqfYQt+gOdgTDf/BopsHzrPXm6G2BmCWwmRYN8RADYHuwFCN3tYKQ4WKP++Tv5+SOqFwzDMiUmtVsFUzKz+xNw87ABgGAbAa8/MTy+w2SBtUVdRg8kYQt2I8mOEA/RiBwel//oMTed3/4ROYujvCn4MpfcD04buuVkhTwvY2bB3VfvWW4CBW22JjTNo/9qLv6W3IBhJpTRaklZg1rhdydVTeenf6N41c0+ECUoG1/IBjITVf4aZCrfjA9gAbbGspH8DxhgDYkDrH0nMIg8njhw49pV32D3b5ihmxHceOHAnjsvfPzBwHu9J3JpPXWGqJgAAQgC1cw6AGMeMBvDucuTwvjEXZEs3K/jrrLjNfLiKHM8+gPHMo04zj6dgJgU7ABjm7Oxlza9o+mcmEAywQSpBG0MGyaAhE4vhGpOk273gfYR+Gpdmujz7Ev2zdUFmB85vrez3c/EV+d5Kc/HmpOSo/9W9kzVGgZxTj7iQ/xv3M9bN8DoKEJXVNAAP0jyV4oQSgMtx5dB2dBzPyQvmud8wNPiRYWbDVIx7xZxM/AaoqEcAEKE9AgBCxjqGY2SHCOXevtk50sEQ3Ov/doalfAr9HTg8dradB46irfYgECAhEBLBYSMAbGz3xLj7ONKyt5eaU2limMlwdTt6AW7hGY/nYu9fftEzs4MdAAxzCaYlE1TMuHmzAVMJBtUgAEMGACkQmL4Fofe53Yv9j+AdFZMh0GoPO4K+c2YIfQQvcjEMfl3hBnxz6b7ED9T3BPR3hYMJOJeO9ky0ZgKwF3s8QdCPgvU8wKQl6QzKB9QfQQSudkJC5XDfsjmxeqHkAAAgAElEQVRmYM2YgnbCwrjv/e+7i9V/hrkiMzbcNhs01Mb/AUNgGjMStIzuEsqi1sqPNOKBw72Pgw4GDO5qtrgGNmJ8whZ5JyPV/9NS1zcQCbEeB3CsA6DluN4Aez2/PTjikmWNjSnDXJpEGuSMw2zU/1uorjHpwQ4AhmE6bkc7MGQMkCFjjCE0SMHntlv+vnzga/TBdf9UAHVWB90GOz86W8Z8Y22a2HB+f0vsYxXUiKD3CP6pwGqRB1b6vzSE2gHTfYTeOgrQwpSStCItwTxgEZgG4HwDAixGNgdOrhGc5DxnVf9Z+meYFBh4V8zGxD+DqWcArucA0IiBmeExorKP0/THHDJmSyRB4BUZOXYPlX84zZFqVrhWYDnvd+YqQl2HICBAAmkN3DgBY+oAx+WB79TfecHJwbabYTqu4gNgx8MAM3vhXhLOOgYA2AHAMBdjL2t+RdNvN29mIxY4bNAYIoJqAgBDxiBIS4bGZj3afZt6PgBnJZBn1rFOgu7ATk9He1fwEv7hI7F/WWFpvpfGDwFkHY71k+28VvhwawludMZkQB0CiLQkvcbtnbj7FZQOTtgiP6lSsO/GkSfc635OkpjVf4ZpSdOyz94HUEBlxInAGDDV6DRPiXYfNKTWD7jbMbbjIM5hk07yg9p5kh3dBageMmjvHXhYrAcBwElHANjsrAOEnnh8Vvqnv0CZYpPKMDU3Io7fyGPuyyWrL+e+1gyqYsw0YQcAw1yOy1tz/3JsbkqETR072Jgq/g8NtVcpoBE4BKT5yMS//iHgJcCuU93gJUb3PRxougel+fYjAtgjI4IH2le1PUex83s+hi4lVkciVc4F2w0AQIBghDFV3ACBJF0HTeihz/Nz37cgD3iShjfuPNX4GzhVelb/GWYSzEPojyFJICASAiA2g8eOeOTAcZFTnTBTB3wPE2Lvii0iIREiIYEEkgj6HDZj/zrAXk8yxqwzDDMHrqXFsw/AgV+yDHMK2AHAMIlyJrs/ppUy70EAG4AtlqbqP0iGTNuN3O85GFzvtHgvjk1wHbwDg2kGtgyp/809DPd2g3i3wTHS/PCWoO4P/dPGVtqlv8U6ffV0WPcxJCRCAklGxh+443g3wHHl8BzFZ99znvA1slP956YKc4NMrpE+A7OuqHIMIyICoRXVv/dV7FRp5z5U4oq/zV7FqTdrERFgOwmwkQD6fHcRqwOcwrJfzAcwrdcLw8wQ9gFcnQvb43NfbvrVC2a6sAOAYS7KJU157EL7iqKzaAZ3bIC22IwAMIbQgPHVeRghqceSDfQlxMix/UOCMxTucQMDh4S78EfSk5XEl++j57F8Eq7iT92QgnojjX+YqttgNXMg7RoB4NzQ7rMPXPeocAHtLexUCsYXtJFF+LBXzfB7Y2AjN1IYZicXrgPMyXbbSEIERIJqQlm05WWgylvc5vSIfNidZMaZeTbcTg/UePgRq3GOJIDkBYpE0GReyrIzDMMcDvsAYHbqP8NcFXYAMMylGW/Kz2r0hxsqM27GbMBsoTRkCAwBETmB5IPOAJ9Y3/zwsf5YAUsH905I4y+x77eE4Enwg4MABuR+x6MQvdzwJSIPYA8OaDYRYfWXSIwcAeBw0rhAI4snWTl1WqUg5tc45qVxmPrPbRPmxkm2hR57z0zdxEsCAESDAM0gACbAjl/lzp/BXgnG/qjaEEAAEswevvwjGVcB2Neyj9x+MGm+WBjmmlzF4l7RzCdbw2AO4yoVFq4lMQ3sAGCYKzA5H8DUxQKbDVI1eaABY8CQMY0gb8vN9oqzDGB3QhxOeoBw7x94fIf2WL/+gUNiW3Yea/se7L31gl72ej4SAqTqj6hHABzEAW6A48rhXl37D/hST/WKGK/+c8d/hnFIxKz7zMl8t0iDKACrruRViLhTZCoB7Zry54TZef3u4xf/YSAAIFXeG0IgiSQvbEKoX5862rL7X9sJexpc3ryyQWeYKOwDuArc/Z9hTgo7ABjmphluqMzSB7ABU8X/oWqpJwGu6ffTDz6xv30gWdRtYHUMdyL+OB3/41cIc1x38K5fvn9gtPpJAJ5oMua6sasEU0IVN6CLAjTiAkPs20YP3ey+FfLr6z1xWP1nmCOZVgs9qffPvshaQK59AIhIYEIJB2xWTL89Eye50Dl+X0Pn3HnHgwnC1qOavqE3OO7C7LL+JynIJx1wyDDXY0JWbd5Mq4ZxEi7/Ar3AFdkqMNeGHQAMcx2S6i040DCdnw9gA9qQMWAMUOUAQKjE5PE9/aFJH/voH37clsAvYMzhEGlhD/ym/PEBu7v/Y38LjrrEcILwRiRCqAcByJMUi6Pb6COLZywQ0EDKS3KA+n9rzRCGOSE8COBIJDYTAFRT+OCAlhzbGMgSjKdBb0uEY97x+35Rl/kR7fQCdx0HvDTUXwAASAgCYUAYwA3KzbV+m2NGP444x7AT6eCixxaWYYa4lhp+XRX+Wk7TqzCzWgvDJAM7ABjmaiTly5+fQBDEIGxQE5AhIqxGAoCj+I/p3j+4P3bE8Rk8cIbhk49U/A+79s4zHPtLb8ZEECAh1MvhIYB8RroBTqEUHLzxfLD6zzCnIinX/k6ma/clgCRRBY7DamxY/SQxub8VoAnCcX5iL2Ob8W9ra8L7/fTh4cQxvHdz+ByxV/jIX+LAUeQt0ZMQVeq/MCC2oDbjrn0uInd65PA+/woJOnYYhjmEqxvvq9/ABbhK1YS7/zO3ATsAGGYCXMbWxxuy8xkEsAXYgq5mAK6iAO2aPLAbE7ArNPAYHDcD+tuHjxqXfuSdjB8WYO89RuDa69huKEbzq6viPQMCnWwEgM2howFOqBRcnpHqP0v/DDOSNNvmSb12jkciChJIgARonEFoAzL0SbrwB1Oe0Jnrm6LxIxuaXe7OnT/JAxzBNaOfsP5GCAWBIERDuEGRYGGpOO3wvvH1iytmSLLfBcMwPdKsZ5wKVv/ndGkmPdgBwDDXJDULPnsfgCSQJJAQALHpWB5nQHnfc5zAeRlz9TP91i7wE8bmHyE1gZ+J1JmuO9xMn9cgAFb/GeaKpFABmKhBlwCS6r7/1ewwA4nj+6hxHRwm0+/75g5qxQPVruGr70xJob0Dr3PfZTJwld2/XPTGBGCl/hthsB4BsIFjZ/NJgZE+ANhVv7j624BhpsQVLWgKxrt96cyJa1VHplgNYphDYQcAw1yZFGoRNrH2yURlAgcJIE2j/hMC4mDuH/DNzCCTEqSt59ZKz1lGANgMNNNDJfYcgwDOXeJY/WeYM5GaWa8Y4+CfCgprX367QBMUzn6WPZ9rLyEXwx8pePjwln2/gfG99S0CV4gNLPDHT8TsQHCYRWB7mztElfqP9RwAIIbu+dqcqSAn+HJgGGY/EjHzidzGSZhcRWQv5v10zKRgBwDDTIMLm/ig/jm+cZwsog4cDEiIgJUfwEnTfzrqP/W+ugBTccjvt5+VCACIBESAhESSzl8gxg/a3z9cwM6NA9uPZy/1fzaNC4a5GCNfCHNqvF8SCSgIG6cwQLW+ow+78zoNSvD2indE4ED/o7N9r8Rj2Omi9V/bBABAbWVm9+iBXXcTdAP4ngOyboaq2hShIKomAUaDuMGkHQDjmUH1mGGmx3UtaCL2ewZDAdIfRc8wM4IdAAxzfRKpQvgMdFqbbiNHEiIiGkTE8FyALnazdh6RkBKCAKqJHAlwxFfRhnug844A6F0UALxupUdcPCgLDSc+LbEem8GPab6aGIY5gNkMApAIsp7+F5pAQHYnfFuJ7j5S7SigUF0mWLuhkLgSE/SHX+3RC9ljB8aFkIl93Jk+uD3Wi99PvO/Vu2TtMxshjBFGCAOiQLVJ/nd3gLWfXGliGGbyTNQNcPV35WVugD0cTEqwA4BhkmBMG+MqfgK/STr1to2s+v5XSnIlPlv5eujTTT1Xroj9u+7yMJShWKv/RIAgwaCACwwD6G4IdnzJx5TQgR/QaX9b49X/ybUjGCYppjUIYFo2TAJIrCYBRqiXPQ63+sLbkj01fmhH1ocRfhOC1ofdKd47vb3BXhbQv+JIEd9e8Q7ByLCA3kf/QuSdGQjs+RZ64n789tpTERlRDQIwgNspOADgoOF90ypNFSm8hRhmYiRiv1sm5AaY3CvyYG7nSZmJwA4AhmF24zRm9urCnBqSsNL8sZ4D4JLVpVPlWfAL2XnIwMcxVzzmPAPJqL8MnqX52VXjBQSBHnf5k9FqQRccBAAn/d0Mb+SO/wxzQlITB2CyhttBAOQoBAlBQpKUJEsDnjXxjYuzxRVsrSh/w/nkeAV2HuV6GqIOhl5vhD69E495bVuif/i0McXfN8f+Jcg/pD/AAoKnIiEMoSEkEBuQG+8OJs3UfQAMMz2ubmWvfgM+ibsB0nkzpnMnDHNB2AHAMKmQ7CCAimBjZootHEUIVegAgxXNnkBnt4M4QNfdqTX46sDpsHotHsSY/onRlOithD62+oIw9YIbIS+t/tu3A+HSeKZBADv3jjz/8EaKJ2MY5jAStOzDKvVUeMBMglAkJUlhlCBFpB0J2+uu7ne3960w9eWTYA/9oAPeWQn6ebs0GN4eh7p/oTqBsxL7a69AW+2JXz5YLzrMVhAAEQoy9TzAW7yeEd+TBCU+hmFSIc0XxM5RZBcmterFLQT/YZgQ7ABgmIRIswrRMo/OTRLaqMGABF7kedrzSzhA7j+YgTOM6a4Y2bXjcY//SR5whkA/RAJBQlDrAIBrTx4Yeay9wgXExKGBQw5j5y2x+s8wzIQs+wPKHNQzSUlKkhJGaSh7nc27OD+Oit0uA9J/V9npZ0hzyu6fdUg1urAdqhb2BMQcDO0dxrwzAxuH+i5QM7rBT9bPIv8hfQcxNLGW2r/use05+wMCKjeDMJX6T8LAxGYAnncgIDb9zIRJofGcwj3EuK4nIM2X4O2o/yncA5MY7ABgmIlx3TqG37aZUAunopk5sA4cjIg7JXCPneKtP7lwsO9hcMtA58Gdh0Nko39sjGCHQed6/uG+12TkJQYSeE9SBw4WBGhAbEDGzzANLuYDiOX1kf05GYYZw4QGAUyIByHucfETnqsQQIKkJoBagHf9x1Dt2WF2ob8Cg/YXvNoQAFli+5irEERuadxwAHf0XvCN3vwlP41vtQfyzVnpToWBY8MVj9qIkzCEBsX1vfjnYQaFi2GYvUnZB1DhNDYuc6EESfz2GObMsAOAYdIi/frD1H0AEhENQD0IAIEQ0IQSHvk9OIe7LXzqhh7YWsDA2QI9E0MZv/O2A336dhzlqgyx9DHtIHaILxkEhYN6O4GsHAAGhEFMefLAfUvxXiVoWJeKpR9OkPg7h2GmToKWPfbamYpBX0p4wOzfICRJaZQkVRACOer/zmWghz5YCn33BWL7p0lwWAig0X3/Hai/GlX/KdBbv00w4AZwruWe34uqFF3aL6JyyRAAoTBtCCCc2AgAOKgUT6U0Mcy0SdDEJs4Bzcfxp2KAs4VJF3YAMAyzN5Nu0kgABKwXQqwayr22fUvViK33WrIC9hMHm/QD9JJ5Jzk7e1+JYnL/zvMH1fyhY61krj+g1g5AGMAC5eS0gyAxH87OL2j8T214F6v/DJMI6SgYUzHxD5hJkBLqaQAkKVNHARp2KtvUMr0l8dsrTrJmtfvn++N76j9a6yMGGdi3urOvZuucsBPELKz/vrfSoJNX5KX0z78zewOeAAPSkKxGABQgN24AxvlAoV9GsiTy2mGYyZOOFT+AxN9Tp+J2gv8wTAR2ADBMcuysP5y7ghE8eSx+TbCTW8pIREGIXcvUbqb1Gsl9aZ58OYC6UD9u2z6eIYHzeB/9v4FLNOswOu8p8jGoHQR1BDtbur0jvv0jf7AEQCQEkTCIhsQW1Oa4M56b8YU0+vMacSAMJptuM4Rh5keCysCEDHeQByUWW/Xc+gBIGipC4vXAElPwISLHtxvtMXzQdxvE7HhlQLHxqcfcAM6FfPq7MKb+Bz+ClznO+QbqCbG6wc5MrhctM02qBKVB/hT5zwl68Q8rxVMvaAwzARIxsYncBhPk1l7Et/a8zDjYAcAwTMdwi3MePgAJIIyo4v/UUYB6uJ+7HeHe+jHVYExwhf6x0dwfyPu2999IuTj2Mbilu0BIO+g+xl0dwZWel8U7JLgAABAJYwQJYQC3KFMOAVRxZCtgZIEK/tq49cEwU+Ty0kHsPTMJg75W4h4WP+BJgqzmAbbGq5Fvg7wnsk2MPwIA+obeP4ej48cGAhIAYriqMHx+/25DW+o/9WhF6moFzl9nxVnfucVeIS9ZeLHDMRmRachKyEpQBahHtXienv4PMLqQxmp7qcG1BYY5MewDSJOLvYLTfNczTAM7ABgmRU4yCGDfGsjOxL7UONDkTRaJ2M4DjPVMAD0V22o/x5rEsWZ8zDHQMtwetD0pQa9K8PztR/8SEPlWKZ7AUw0C8X/8Q4Jtficx+TcdFPojEIEgFIaEAbFBWQymnhbBL3vfMxyG/xtlGOZMnMNwn4/0zXou4AGzz9UIgMoHQCKkRAOGjRRCb8V34Q/o/s1ZA26Adgt522ODDHxPwDBBIx6T6cGysH3rjM7lyDuzf2D9F93E/nl6i4FMgypBFSC3qB5VNu5JJ8xUfAAMMxPSMZ/p3AlTweo/wzSwA4BhEuXClYfx1xqvSaeJAlAoBaEgIUiikVUbnsCZPNDxBAyI9RBaAVsXCHU83Cvbhn0MTsqdp/LXo0dZ1/B1gZ0MCxMDF3JEByJEQ8IYYRAJcAtyxNWvzwFOuKvoBdxUYZjLkGBZG3jPpG/T15lShZAgBUhBUpgx0wDY5t7xAUBEmrfPEDDxo0V/Oxn0IxA6Jw/Sf5CefD+s/vtpqNkQEOtdP0FgYIG9pbdEJmEGLbKSshJUCepbvvyiptn/HwCOKMXpFyiGYU5Dgsb+ZrnN1+5tPjUzAnYAMMxUOWHVYt/zBGXKqTRs1gIfIH8kKUlKUpJUOaoN7KQJ9vQfMxyCgMA+nBDrVjQFu/+Hz98X5UdmvCuCRFa8v3aI4Z4MQu1hIQ9H7PxV+rALgXohmLoERIJAmGoEAOLzdGIHHxYuYOf2E8KNFIZJissXyamYb58HJZeQP8FGgpKgBCkDBVBfiW4nuSVH9Cfq5untS/P1OoSk/2ACT9zvGXGIW3P7VDD6m2/le/uQYVPurDjrwUsEE5OXYPeiRaZBVSMASpBf5WI70R9cw8GWPamyxqafmRtJ1WiTupmb5ZIv3HRe7gwThx0ADJMuKdccrqhXHs9aZBKEIimNFKSEUQTakgMg1O7tnqxZi84AHNLlY+38NoYAxvNtp4/BcSoEz+DcUVSdjxzdJes0+np7TCaIXqgv8VPoJPW+VsEhlIYEETYhgCbjABgP9SUiZ/u5SfltwzBzIs2yNlGbvs7wXuTfmyhAAiQYrxM6+YYGQx+HBwGAu5egn9jv6d8SdAb4Z4ZRdjy8ZS/1nwDIqtEEF2dvNGX/F+LsIgQiIAOqbCYA2Ijsi7rp5mfiZYphmJORpr2/HVj9ZxiPm66BMUz6pFxzoH7j1e+yniwvUGagZDUIwEhBUlNp7W+b9b6WHZQMICLN28f6zoDYGIJgXu7lY4jTk/LtdVc7aMR36OeGfaDT7IeQ9B89f1Dx7x9I7e0iAGEzAQBhiSL9GYBt9i3Fly9BjjrFMMxZ2VnWrlIYp+gDUAhrlcmtUCAFKElKGBkSqY3V0z/oAwBPnYfQIACI7Ap2/B8w6NYhBFYGj/jaA4H7gyu+6N9P1vhFBhT8/hbo95DwnQSxBXQ9A7AqQX7Ll49Tjv/TcszwvhTKFJt7Zp6kVpcdMCPMWbll9T+1+2FSgh0ADDNhTlLJOeYMKbRhDuBBinvMf8FzFQJIkNK0CUkG0G/rBntmj5Hm282+9A/9w4clg5iPYeSXsK9q4PytVrz++4GL+9KAuzI4CCCwGBDGoEFhELdCbQbGS0yZKQpwDMPMhim+ah4yJbsRAEpAZmjT7/VvPLnf9wEMDwJocapdIUF/hw/APwpC53Sg0Oq+Rjyg6fc/2rdBfnp0U9r1JUIMDrYgS/1XBciveV5M7kd2HMn6ABhmN6np6WNI8J4TvKV5w69XhonADgCGSZ1j6gyXrG8ERes0WUl4wPwLCAnVIABVUlAdGF4GuvW1604+xKR/sM/TF8c9HwA5h4P1Je8SDpwtaO+tzkveKRzhwFkHqxchhcZMDKzssRghDUlDSCS2KCfnADh+EMC5yxQPAmCYS5LmIACYoFi5VmIl8g1uMshKyDXkW2N79A0BRmam9dXwygIGVX5oEvctL9npd/oAnDRgXWjkIACKfHRWgn+tFQRr46iccZZAlobUfwAyTfCfEuSzzOcU/+fIQnrFYsWGnmEuDdewL8Pl36rJVo8YJsR8KmEMM2MG6gxXr05cXqk8CQ/Y9BkkJUgKkoZ0pA3sYg0LqBv2TXicwAgAJzf6Ke39zkp12t5H73zOqWDEb6GXgKiV+6m/N/jROTyaP95e/7QDeRuSD1AayJr5A8VPkW3jF06WfcMFXKtkXf2VwjA3QrJlbRJGvOVFhm8Xq1/b5wKzEvISFga22mwAsFlqT0BoHMDAIICK4foXRKT/2OHBZLHRBsOQtz7sA+ivWGL9oHfEOTZsowcWQtRUm+8C1LdsJvF/9iVWpqZV1hhmMqRpX9O8qznB6j8keUtMSrADgGGYY4mJ0ykboLWS+VapehBANQ9wGRK4Y0tPrKdOrIddjfnaBeD5DFz1f9AlAP2chhGZTfGPdsT/dteAD6BZR4jnj5PYX4kei96KwbykOoBACepRZrsedtoMlJ2zFquYgsUwzJkYLmtJlcSUbfrvi/zz06IoixLzEnJN+egoQLZhcqzeSF1+uPs/hLbEBgHArjyO2fEB8+387VZCA/7ASrbn0sX/Md35kdrgPyWoEuXXPNeDTzhj0vEBpPNWYZibIym7PjNY/WeYEbADgGGmwRUrDM51dzZPfQE7QR4ysYb8Zz0IQApSmgh6vdsquqe3nsV2AwTb+d4RPfyW/3DcAPC0AwAgIPsS438dZN3aSPmAquu14YfqXKLeRUP5A6EVO8GoRYuq/2BWgvqhFl+UHP2wabFvKU65BDEMM2OCL59k30i/LcXb/O7X9jlvogAZyg0Vje7fjgOo3QD9buzo2Szor1if+s+P3f/WIsdMuX2QY/dhwJSH8pxC6yN8AGjbYmed+tuh7yQYYampk/4BTL1OpKVqfQBPIn9ccMMzwCVLFmuPe8Ba7aRJ9utL9samy1WqJmnWh9K8KyYluB7GMJPnwhWJUCs40HpJVimoWAhYi/xzPXOgkiRLEqEItuCJBdQPKBzsyrdrEEAgZWwEAEEvFlDsEnuOAOhp9zuFA2fFOXZIPmjW7FhD5J1k6GyESpNqIwh/VcsfMuVf1g5Gllbyfhn+rnPAgwAY5sIkOwggcSPu8G6Rf3palKJsZgJYbOsoQK01GTMIAAZfgehss0zbzndnbBDAwCH1vhA7LTgEnovA0vRhIJjPHrp/s2Ct/hvbDWBQtPF/SpTf8uWjmtBv6vQMlKnLFDc268xtkWxdduQAM2YMN21VGGZv2AHAMJPhsGrMkZWf2LEDumRQqE6QtVJqK7qZAIwydRSgMUuvzyA6bgBqZ9gL9aEke5ejBQRHAPhpwMvd8V/ygHDQrjh/q2fsi/u4U8S3P9rbyUs5oEGQxkzXEwDIAuQXNfP4Pxdj55sh2XYTw8yMZMvagKFPjTf34u3T3XOxyUReDQLQsDAmNAgAqw77lWwdcwA44XHGqDXBnv7BQQADR0Ekg/3Dd/oAAnY8YpFjC9S2PjK7b70gAbXqPzWZbABMKe5LsSgwL0EVIB/zLM3f+Y3Amc/cIsnaV0j73ibBFasjadaEGGYE7ABgmCkx3LUsHaivTKdpJR8yuYDsCaSoFlKGingzGHwfQBMVJ9Snj1y9AJ3/YekfQqpB3LHSdeQfPQIA+x976+NVg/agnXnlHBs7Krpo0QUQ+JEtv0x//sDEBwEwDJMUqdn3ZHm3XHx+ygtRlCLXOteUF7SB3og9A4DBoX5U92EXVuf3lr3ke2guMZA4Nlxgp5uBIh9jK7aO3//YLnZuYMgKu1Mp1OsEpgkGWKv/WCcwAAaIjMhLXBS42GK+Ffm3fPWF4/8Mmu+zWnZ+hzBMivBQgINh9d8n2RtjUoKrYgzDRBmukAzrko56nSBrhWuR/wCpQElQAiQSAbkBf2zFP74FIj30od1CbvgAP/2ADyB2/v1HAAwF/2lXAsJ9c/+efOB+9DcGE5B1WvvkvcWg0s30vwXIr/nil0j2B3V6rqIUjI1MwTDM6Ui2rA0b+qR4sxZvf94/F9tSZCXmWT0TQNlJ/+0gANcrICxTZX+EPcdKDQwCCKaPnWcAinyMGXFodPxmpd2yQ+sPL+QFRWwyjez4PwSmlJX6v9iKfCOyD3d33/M0fziHc1iZvbxlT/PdwjAXIln72pL+HSbFdS3J3OwYc3OwA4BhJkZSlYSR0kCaekEm4D7L5VZIkAKkBKVJERmvWWvr/r4PACIyPYTU+WYLdfI99g4M+gDsj2Btgf5vYSCP/Z/MsGoA/aezPwJAO5dg/wxkH9gGQQorDiNDDFfxf0rIClAFqtnE/9m3FF+4BLEPgGEuz779zC/GVHwACPD2bvH5OS+rQQC40LDd0hYILdvdhACqJe9W7nfsXbUdwI0FtPsemv/k7RnYstc3HLK/3frAX4KIr/3ABQ2QsdT/NoeNFstSLAqx2Ip8K7J/36/f38/EfLecqUievHCxBWeOZQYVwfQfgYcCjOHqNY+r38AAKd8bkxLsAGCY6REZc34d7WCgrUKWbp2mXvCgpAQhQWaQlZRrykvz1Jf+Eeqh7q1k4PgAgoMAYFeuO1J+UPS3EzvOAOhfCPpbBqDIx1HCgSWaQD8T7KXZRb7030kPA+q/3cFQi2b+QJDfs+WjkiOecavzWdgAACAASURBVFaQ9X1fS4NLv93EMMxZmYoP4M1avP1xtym2JWalyHVwEAAZAGzuv1X/nXEAzl+L4NsQ7TWKp4z1Fdj3FRv0AfRvGCs7TdhtJwDouvkHA/70FuP97S/U20tgEAyAIRClWBRV/B+RP6nF+/vl9uas9xDDxeeEhYttN8PUTKIuy26AGClUOFK4B4Y5GnYAMAxzLE5bJU1dIMjrXL7K7oqyLLGaNnBhaFOpA70Og1HR39a4sdG1x1ffguF9KP4XPNXAv5DzVQTZqR0MqP914kgcAOqf0PcBhJbQHIMERCLTlJXNBABf88XT5OP/d4xviVylQE2iocQwMyPlcjcJH4BAeLNefH7KSpmVZaZFrnVuzBb78X8ADAD0rbywVmKTAcSxNH9sTn1mfDvet+YUs+C2fL9jQW+l0frdLW38HwLSalnislL/tyL7/LD+cDc3+f/c5fQkhSvZlwnDMEOwG6AlnUpGOnfCMMfBDgCGmST7DgK4MGTVXlIeBHCn8F2++rr5VULW+gA0PVkNYEO9YMFBUTsyCKD3ZWD/f5smqOyDp/uDl8b3BASu2s9z59cRlP6h/3Shj9jKAf3jWh0fx+n+lgyBod6Fmrru/1tUj/ncAgjsy4UFOA4ExDCXJ1bWTlsGg8ZjzFGpGXGfN2v55vv9ttwWIlci17gwsCUqALAJ51N1/2/dANBX/1vj1TgDUOyT9XUmNXlV1R/2HaLpZ7OfxncAtCtE9Xg7CNVYwDG1rZofkfXJHQSAzcde/J96xQilsQr+s9iK7Mdi9f5+sTvbJsVJSuLO0nRMcWN7zTABplWXvXE3QFK1jaRuJkj6d8gkAzsAGOYmOHed56wtmbPybqk+Pd0VRZlBXkKuKe8PArD7DNod/4ODACCUzf0aHPkbjwkBtG/1MOgDCHoCIuo/kDWNsKPjNxubfoWNBjEwVsARGnpuAC1U2/3/W758zGbU/39PUig+02o3MQzjM6Aij3zDOO+iFF5NDhLhzXrx96+sEIXGXIvc4LI0ZWPWwftbGSkTcQNAM0+A96StycN98oBg19s0uHdwSzcrj2u+G+MLlmmGRsffaZQj2/uRf2z1H8BovCvEshB5Nffvpxd3j4vUfiOHc2EjaNfzxqdnTglXfWbG5L7QtvxP67YPJkFzkeAtMcwRsAOAYaZKaoMAYroA9WsvqZnR+xzfLVZfN79yzJooQFtrJoBW/e/awG2L2tK1IeQAOCAWkP0x5g+IHWVftCV4LO1a8XV/aDT9nZP3wmB0IF9f8OYPBANIGnMN9QiAAuXXfLFJ7aeTBmctU5NrKDHMDDjTIIDhY8dLjen7AN6s5W8/7ouy0CI3YklCg9Gl/mmJ/nYUoApnKmCDTSwgALBWQgzl7LHZYx0f9wE0XvnWQFvBi/qiv72+OwRQcA4AX/Sv6gYGwWi5KMWiUv+3Inu8X7+/z495/KS4Yu26ZUwNj2GYISZatZ23JyC1akRLsjdmM4mbZJKBHQAMc+ucsCI07ANIUCaoeLtSn55WZVlkmGmopg3cWL3+W/Xfngo42KIGKy+bfB1ougXiBe1UfmIhg8DK7CAU+RiT/qH3jAjuU/vaAZKbJr74oQbQGgFAZEq1KCEvMCtRbjH7mt+6tRooRBcrWRNtNDHMbDi4DI48aqQbIFlrXqEEvFsvvz8tqTSgDQgDaAC1MRsgbIw7ACCBQQBAbIyarftXmWEIBAJEfACHeEPQWxmH+x02cn+3YoX9gVDlJG6jW/Md6N1PAIYqG41NXagO/mMAmxUwBFjiosBlIRZbkT/L/NOL1dMsTPfJDR/H9mEmxszqf5N+nNl4AlKuRlSkf4cMsz+zqJcxzK2y1yCAy9R2hpu/I8WFC7Ne4NvF6tvmKRNZiZmCXEOuzXO/HxwCYF+2hnYvgeg3vCGc2YFtMWU/2P0/pv5Hpf9+Vg/7APyP1nYndEBw6bkEBlN2uWow0J3QaLkscbEViy3mW8y+LFaP+dzi/xxQGAd8AIcx5p0Q+wkyDHM+rlvQxlhq6qsQqZn131/I5+2L/68oSRvShoQG1AVpIg0AbhQgciYDAKi7tCNY4wDaLVaXfOu5e19Ys6ueAaCXkoDwkAzzfxFBI16F4+tvwV0+gM58O/39e9uJTOOqb5ZuGgDSclngsu3+//fD+v395Of+PUcxTK2wMMwtMoPqrON9Tp9pvfumcrdTuU8mGdgBwDDMGSFLnE5ZLHh7l316WhVlUYpcQ65pYWjT9AfsDQKwugpCvyFtAETTEQ/xkLrYYb6bveqwFFqPiAi9v/WTjojtM6z4D283AIZQ1L0IcbEV+VO2fH+/LOam/5+Ys5Yp9gEwTDpcrADudAMkbtb/43X2vH3x/tGQNqA1CANCl+VPIIPQdwDU/7A15V7wn6oTQLulHh3oudi9AC20X64M5jZ5l6tX+jGCfNsN/RpLvZBr0AOif93xv/noqv/doo1QJS5KsdjKxVbmP/Plp5cLk9pvYh9Y+meYmTOn6mya0cGm+8qb7p0zzC7YAcAw02avQQCXwWkEOz4A+286PCzx3WL1Y/OrFFkp8gxzQwtNzz3pv2rz1xMJxiYMdLSDPmF53/u6wl9esPv/AV/1gA+g/xftLZ58MBQCyJESTDtywpseoNfxv9qo5bK0ehF+vLv/eDf5XoQOBxfPNIsPwzAn54R2/JjzDLsBUn4XKQl/vVputut/a0PGkDaAGlCX5mcXMsf+i9U4AAFggAS6PgDoWXZ33N2ANyRY9/FXgqfqruGr/33dv13xrXbv465JeoLT8xjAtrN/f0EDYAwKLVaFXG1FXoh8K7LPD/d/r6bqt2fpn2FuhTn5AGwGbPYlLzdRJvQ4E7pVJhnYAcAwk2d87eVi0YGm6AN4s84+/VoVZVmKXGMzEwBZ0n/nBvDVf8cN0P4d8YyBrB+IAuQnC26PntrbTv0t1kc37E+7Elk8lwD23ABBoaFT/6uehlqoEheFWGxxsRXZj+Xq/f0i8hRT5UzV77MWKB4EwDDpcPkCOOb1kppNB4D7O/zr5d1mW5A2pDVoDagBjKFnAGi68NcOAKL2/oXly0cAUTv+0d5iX6d5dLLWo/lhbUfnEKD6nqiXvE3cbSRvt1/9sKw2Nivtlp6lri2yMzFP729dF3IGAWgiAwha3RXybitXW7Xcyuzb6u7jy0nO/cviGMPcHDdVo+WX0U44i5i5ww4AhpknV6/PDLd9E1QKXizx7Wr1Y/tUDQIoRS51rmkDgNi5AaAJ7FsNBRD9RrXvBtiXnT6aneGA/Ev76X0HAPRvO6j7tyvhxepX2Ov+399iAImAkJxuhtRE/78rxHKL+VbkG5F9uL//ukjtl3JlBsrOAcXqYPfh1d8wDDN7kiplrQXyt6ccCOi31/J58/D/FkU9GYAxYHRhtDFF49pv/fo2dtifpuM/GT8KUD8WkJ8BdpXH2WsH9bF2xb5y8nfvdAM0K2QH/Gl3OcZ6wHbHIv8YQCrVfSnvtmq1VcuNWvxc3r1/df9zaoabpX8mSlIv4isy43yY8aMx45nc+3pyN8ykATsAGGYOHD8I4NwkqAv4vFnnn38uy7IsRaYxN7gwemM19S2ZgNBS/w2AqHvkYesGCEUBimU92ilC+RQ40P8mB77bAR9AfwUJOlmCANrpBEM+gF7Mn27p3ABkp2mUBQpP/AtgtFiUdej/xVZkj/frD/dZ5ImmyqlK3yQKFMMwR3KxcXsjCb55En8d/fF79rx9+b+N+l+NAyhI9/z6AXOKgaEAvckAHMa7Quq9/dmAYx4W6CfwP4704kPParsj9jzpH6tpkBsD3cT/ITAIuo3/U8p7LVdbudrK5UYtnxar/3nz4sPryUTtO18hSrlEMAzjEuxAxdwOk3tlT+6GmWRgBwDDzIS99OCDz7kXXm+33bFvr8vLFb65u/u5fW4HAShcaf2rcQBA02hHrHsLeuGAyG+Bj+AENc7wdxXvVRjRDoig00J80d9erxWEUFh/6y/5fQzbZD03ACFWwX+qCMLPMv+4Xj3NyEZdpllx1jLFgwAY5vJMyweQmlkHACngz3eLzXb9dzUIQBsyGowpyh/9zvv1vyYGT63+Y0D9d0cAtJWExneO7Uo92y7YfyGeYe1XOvz1xlz47ZbaZPfc+a5Hv7HO1Hr0rQF5ZFvq8FLKO61WhbzbqOUmW26y/H/fvPjXmwmY7XMXnNSKAMMwo+B67W3Cr2zmlphALY1hmAtwpjrPgBaQoEwAAG/W+d8/l2VZlGJhUIPQYDSZbXjOwEb9R8cNMDwVsMtlciLmAABLL/D/DjoA7AUprvW3PQob4YA69b/qY0hojFiWYlmIRTX3798P6/f3k+lFuJNLNijYB8AwzFmpSn0w2A2c+RV0GHcr/PPt/WZbWg4ATUYb/dx/hTmWq7Lg/UBAlvpPgNhfsboLUDOOwFH/u3zCLiWEsjNGRP0HaLsgkBvwx3+0iBsgMAlwYCEwRi20XJXqbqOWG7XcqPzDu1f/fJd66H+W/plD4LpOxS3kwy08I9My0Vf2RG+bSQN2ADDMfBg5COCKdRtfF0hNKXh1h2/Wd0/bDQkD0oDWgKYkTdR2+ff/BqYCJjDYDxcw9Jju90EAGDog9NWN/y6RLJ2BLLGm7SEI/R6CdUSgfqwAaLoK9pdeGm/+wK5HoS8lVG4AaXX/z3/mq48vFiapX8ahnLys2T++gYvOIvMYhgFIbxBAxbTeM69fiefNelsUTSAgA9qUhsg81yn6WUldl34BYIC6oQA7Hhy7oyNfT9BX0iYd6DUR/NitNLo/9Ix1Z9OHF1OP58PaLveD9WkgA2gANIAxMtfyrlR320r9zxZ/v335P2/TtdqXKSapPj3DMPvAPoAbgV/ZzE3CDgCGmRUJVlqCTeVgd7hE+M+Xi83m4WPVT1DW8YJL8wMoNBlAre9TEyugdQNAtUIA2G3xcdr/2NszlHg/CACJug/RLoR2V8FW+oe+TABe+OCY7u/5AELqP4Ap1aoQy0LkW5lvRPb5xf3fyzGDJ5LmTNL/Xle/QMlK8J3DMPMjzYLm2KSgsJ0Of7zLNpsX7z/rahwAGhKEmlCXT9HcrTf34v/0OvsTAiCgt6XOg0D3/y46ULVC0MstpP6l+2C3z5L729T2x56xDs0A3Nf9W2NN0b7/RAbBGJFpeVeqVZHV6v+X1y/++W65TbJNydI/wzB7Y4/OYmbJdN/a071zJg2SrKwxDHM6qiHoHdeozcRF7hRZ5PDXq9WmKB+1IW1AGjAGtNb01PgAoNf9v34e0bScnVgB9QoBojsUYDhjTuslcTsPYm/FUf+hrxSAJxmAKxkM+wCwDvuDjqCA1dy/eSkWZTP377fV3ccX05779xyF7LCvf6cb4IBbHTnSiGGYC5Ba6UvZByAE/PH7YrN9+PJIaEASCsKSUBih9S8gbZk8HxyK/k+hjX0fALrW3KkAwOCggTahvZuaf9Q/MuK5D7jtg0bc9dBbizZCabnSclWo1VYtN2rx9eXDv35fPeWpfdsTk/59q8owzJVJzb4yJ2HSr9dJ3zyTBuwAYJi50auu+HYCmzRenSZYzzlf5ee08vZpebkW/7G932wLMoa0BqFB6K3RhraeA6DKILT6/ptOJiABYACxHzu4Wqcuyk8vi8n6kvxbo2bOwljPyyCu+t/pBhVdlAD7b7OCQ9qBNQOwOwFA4yRog/773Qk1CVWKu0Isq9D/G5F9enH3I0vq57AHl5H+982dmBvghHfLrSSGOTdplrLUzPcwqyX++fsdGPr6FQtCQShIlISCUJe/AAx2xtFakKz5AGy5PzhSrdX97dpNNVDAqvJ0NrdzAzRrjmOgO6m1Qr00nZlu/vqx+/yli+bXyP3BSH3N/D2Eoor7X2ZV9//Ft4f1//5x932V3E9g0uo/w6RLmnbofNza886e5IwVw1wadgAwzAypqyvY/+gngiSqNWnKB3/8pjabF/9/WVJmwBjSmlCXZIh0I8/bf6twQP70vwZAAIG1BawuhC1+l0DqD9yAQKs/4AMALy/r1OhtsdZtf0DIB2AFAqKe4t/T/RsXSJVmaP7AzgEgpJZ3hVxt5WqrFoXM/71++PhykobpMtL/MSRQ1hmGOYrD/PQnUzAivZRj7ug0jfvLF0KJtRLiy5fvAlASFoQlibLyAdTz/fTpTKSwpX/srLnb97+ZFhh6ngCyP0KbTwO6f+xW+usxF34zMoAcub9ZOidBz2S7FpwMgDFSGbUqVR38Z5stftzff/hz/bhOK17fPKT/NMvOTcNC8M2STHuZOYoZvFJn8AhMAkxSZ2EY5nh6ToLzV2uo37QNDoBPza79+TZ/3r74qAm0Ia1BG0RdmJ8ApmmwN3+xvX9hRQHCyIqT7QOPbvUWdDfGjh3+LiPaQeBv72Oj+7e7WonfdQN069VEgkSByD+gAQwJ0HJVytVWrTZquVHLb3d3H14ti7TEhB2cr+ikVhxsLjxaiGGY6+MXb0vETtCCD3C/xv/8604J8fnvrwIQoRoKUM8HQKYEzwPfqP+tDwAi0j86e6kZ7de4BOysclbI2w6R12rYfGNvS89k9+V+y6mPBrte/2G3PYGhbKnVSqtlma3KbLnNFk/Luw9/Pvz9MiGDPQ/pn2GYFOE67qSZUAUlxgwegUkDdgAwzDxxKio76i3N7mvpekGd++rkGfzjzWqzLR+NIW3IGNAajCn1T3cEQC+D2kkCw5MB9AMI2EMB+p4RsnPFyaG4D2Do29qp/oMr/WNIRHDVhEb6R9sNEIkkYAUXLsV9Ke+2crWVy41a/lyt/ufNw78fElIThrmi9J9aSWnh9hHDnJvrmOmBs1tuAHtbyoMAAGCxwr/+c6WE+PjpEQEFoADUhCWhpicyBQD0jV2r/jd/UbTWvDctsN3l3x8EUH8E+0trQ/sBAGCzvctzL/erMQbNLss97/4NDdqDxl5blrpvrwlMPaQPDSEYdW/USmfLMluW+aLIF5vF8sOfD5/eyKO/h5MxP/U/2bLD3Do3W9XjoQBThF+jDNOHHQAMM1/2aj1cqVqTeAvnYY1//na/KYrWAUBGg9bGbACgr8KTlX3oTgZgrfiP3O9AOZwlMR+AdZrAIbEt4V6E3RMR9CSD3uKNAOj7AyjU8b/dWGb3Wt1t5Wqrlpts+Wux/NfbFx9fJaQmxDhrERlfFhIvOAzDzIdxaqU1rc00UDn8478WSrz+8PHrFlAQloBIKEho+kV6a6WtjJpAIABBQAii8QrEBwGQ4wCoxH30TDY2Efyh8f37dCMSsD6qscv2OoA3GQA1N98fxlcr/k68PtdtT6iMWpm87vhf5osizzf3q8/vHj78kR2e9SdlftK/nXhaZYph5g+7AabCnN6ec3oW5tqwA4BhZsvOQQCBPhyX6tbht2ooyUEAAPDujdxsHv5ZlKQNaU3GgNGl0USlr7lT3RdPAEI1A7A1FMBeqSID1N0G65UqkhAh1CfZ2f3f6Xg58pvbrf438w8QdSEFYkvnBqj7DDbjANCKHdxzBqAu1Z2Wd4VabdVyo5bP2eL9u5f/+i11e3TukrHvL98p3Ylwsz3DGOZiXGwQgF9nGE5t+wBG+rSvixDwx3/lSr768OH7MwBW4YCa0QCknxvHtmgsHVof7YhA7lLp/W7f/84lAL2VLqOH3f/+R9uOEwDYIwM8ew2e8970//YWrZaUrUy+1NmyzJdFvijz/Oer9ac/7r78loS3flrSP7BxnDpcv2nhrGA3QMokW+c4jJk9DnNtUhdcGIY5BvSnkt15SKj/2UlqejEVwJEJEhQL/vg932wfPn4xpDVpA1KDMbr8BUZHM6be3A0FQABb/Q8snTQQU//7WUX2lZo8G/iqqo6BvaMAgOz+hE2sALD+EvRiCECg77+93oskQFW4/56mIJalXJVqVcha/f/47uU/36bSl9DnAtX743/wl3cGDP3QuE3EMGfm3KUseHLH5xxOgYFPCZp1mzd/ZUK9/PQv8RNQAJaECIiEhgTpZ6DK0AvsAgFRIBaQa9Ch8QS0gwCgGwrQbbFXoBcLyCXoAADLWENP6EfoW2qojTLZVruasIcsk1356UGre5MtKV+V+bLMF1Xf/6+/v/j0x/LXfRJf5rTU/4PvNvGywzA3Ddd3U2N+r8v5PRFzbdgBwDAzx66cjK2oXKRCM6FWTabgj3erzaZ8rAIBGY2GSkJtfoEpASCeX04gIGxcAkHIShMjpqg0yox7IxRa7QsH5OsI9Upf9ydvGduRsI3/Y2Su1V2p7opsuc2Wz9ni77cv//lusa+b6jJMS19oSaFkcZuIYc7NqFF9I3btwDts+A1Dgxp2srz+Q0r54vP/iO/NCIAS0KCkIjP6GcqN5YbvRgPUYx7qCXscNwA469aYAGj2QuMnaDd7eWcbbgzuoLZHQC/OT+3Wj1ltf8Ke2nYboUitKFuaxUrniyJflnm+uVs9/rn++EeuE2g4Tss0H3+3Kdh0hmHC8FCARJjlW3KWD8VcmwTqcQzDnJXBpkNUFPB2nFXRS7+f4Poe/3h3v90WpA0aECQEidJUswVum6zp5PK6HY7UyP1Vl0BrHuDwApYboDmq0x2cTPK7Y+78imwtoY3z0+gQzVCAJujBgPTfBhQ23ZNSrfWj4wZA08gKmZZ3pVqVarlRy41afPntxT/fLYskYgn0mK6+kHg5YhjmhJzJLo8xJDvGAUCnb0/lpfTirRBqLf8pvj1+F4AChcHMYGbKjCAn/Qy6rMwfNt3/sfMHiL4LP+gJsFcg5AmAwWkUqP/FRD333d9qpt/+9DzeSjNtDxoEY+TCZCvKVyZf6kUd9P/p1f2//3H/7zSm/J2udWaYWcEdPWzYDXBFEq9bHMxcn4u5NuwAYJj5s+8ggItV6mKKQJpKwZvf5Hb74sNHeCaUhAWhINQkSkKq5wS2EQB2PqI3DqCJ+YO+XkDN9ACtoEDtx04qcOubw50y7d0UXiFbRyBrY7OgPSag36OQwlMIttMAGCG1XJVqVarVNltu1OLx1cO/fl8956O/gIswLXEBvBtOR27jtiHDXJ6rm+9uV+dbTuWltJP1KyHlvVDi2+dvUkgjMiOUFpnBjMqMcGPKJiIQ2aMBRBNvMbR0Jh66FYKQJ6BZD3yL1MvG1ls/9Jc68x32AdjxfwhQaHlH2crkK50vzWJR5otikf/4/cXf/1j+XF//q5uWdT7t3aZfdm4FrtkwA7SllH8kl4FfiwyzP+wAYJgbYLDpkOYggDRbO//4M1Py5fsP4ukHCkJBWNZuANTlEwxNuNA1/tFRByzVgLq9nfTfaCmtSwCsrIJ965vWrBCDwkG3Yi3Udvz3HAD2NL99NwCBIZkZuSrVXaX+b7PFtxfr93/e/Vil9SXPTF+4eiHiljLDnJuTl7LxZxvlAwAA7FJe/aW0k9UD/vHfq+VKffv8c/Pj2UhlZGZEZkRWDQigckPlc5PxTYWFXCPeWXPXMQC+9N/v+D8Yyq/bMNJ2Q7zvP9Xxf4Q0KieZG7WgbKEXS7NYlIvFdrX8+h8Pf/+Zl9eeo2dmpplhZgJX8mLwgICzkng14iTcwjMyV4IdAAxzE9izAe8xCODUdTu/8W9vSV8aAIB375QSL96/Fz8ABaBs3QAGtX4C0FVXu64Fj1Xnu/5UgbUi4EwGQJ5GYK9QoxK00zQ3FcxWQ3BO1oJeN/FOPmjFBit2cP3XX5rtaIkIFNb92+A/Rq2MWpbZqlTLMltus8WP+/sPf66/3sfmQrgCc9IXLlaOuOnHMDfOqLfNJEy7Rb7Cd/+dr19mXz9uvn/+UUpppDJSUeMGIMyofCaz7Yy72/FfhKz5gA/AGwRQE3nFtjadfNEfQlY7EvZHSiMXoHKjclK5yXKTZZX6//Ty/utf91/eXT/sz5ys85FnnlQxYpibh90Ap+V23oC386TMNWAHAMPcCgNq3Xgh7zKSX8qDAADg9RuZiYf/fS++fUUEREBJWBKWJIz+BWSstARUTxhIILAWC2rpHwOTATQ6Qu1+qaIBWJIBgTXDot2Vf/CbseT+9nO3UrkTMNKFkKi/q9nYyf2e7t9tRJ3d62yl1arMl2W+KLL8aXX34c+Hf7+8LfX/hL/kMXebTn9bdhIwzLlxSlms0F24MPZePvVsuVNi9QpXr5brF9m3T88/vvyofABGKpKZ2WYkMio2pJ/RaHs8H9b23XcJNP77zr6DlUPt3gGov9rYZSt2H4JnrOuBGG7ffxKKVI4qJ7WgLDcqq9X/TJk8+/7uxeNfq18vrvyNsfTvX2JahWiecLXGhnNjJ/uN02Y8bu2td2vPy1wcdgAwzM1gNR32qLBdtm7nCJfJtnbWr8Vfcq2EePzyTQCWgAhtLCDPB1DLAe0ztXMG1rqAFRQIQs4A6osFbvSAMV8S1nfS3ZW1xXIDBLsQUv+jqya4bgCCai5BRdnKZKsyW5b5sswWRZ4/r+8+/f7wOY25BOE29IWrFyJuHjLMjBl4wzg+gNYOpWnWg6z/lKvX9+v3+bdPv56+/zJKmU1GUjU+gIzKZ9AFmNKx3W6sP8BmGGbAmhMgdl6BGBT6GOvy75vv2kyTzIxcQJaDyiHLjap0/2pRmxd3P98sv/4jL686Pc+0TDOwjWMYZifsCdiLCVUUTshtPjVzWdgBwDA3hK3E7ewz2G3p7zu5nDctOaDl7gX+h7rLhPj891cBKABLEAgoAHXxC0jXsfKdqQKBqJ3RF51AAa0E4DgDwJYVmux3vswx2AID7fpLza1CvVKF/aFgNOFO968i/xi1NNmK8lWZVdL/oszzH68fPv5x9/V1En3/Z68vUL+twT4AhpkxIwcBnImRPgCibhb7CRl9uYAX/50tX7/4+X75/fOPrdzQVhmZGZmRzKjIQRegC9Al1p4A41h2z6w7f/2PQboamfVxlwPAGgRAItPqDlUOWQ6ZFfMnUyZXxau7H7+tfrxR23vu+L8HFzZtONzRlgAAIABJREFU0yo7zE3ANbx9YU9AjBt/u9344zOXgh0ADHNLEKA/vdz+nLWyN5VBAACwuMM//mslJX76+LUa4Y+AGgQSmvIJ9Lbf/V+4f8kdCtDr+A8QWYF6pdP/IznkCEL9EQD136p7IFpb6r+emhCW/t1BAIRg1D1lK5Mvq77/RZ6Xef74x8tPfyye7pL4Mm9QX0i5HDEMcxkOsd3jjhn5hnHs+4TIX2D+YrF8nf18//zj7++mnRhA5VCWUJaoC9Bl5QxAXQDpvmVvjDh5ptz522ZQH3QseDt4wBq9V/vsse8AAGGEQpWDykEtIMshyyjPjcpMrnSWFa/vfr1Z/nirihVL/3vA2h3DMEfBngCYWlXgfHA+MJeCHQAMc7scPAggmPgYYlpA+j6AbAF//PdSSvHp/ddtPQ4AEZBQGtxQuQGj4yGAyAsHBPVQgDEOgBYakT11HIDuM3WCgj0nsOMAAEvuJwCDvRV3EICRitSqUv91vqw6/m/Wq3//+fDpj8wk0PX/pvQFp+BctxxxFzGGOSunKmLnHeSXsjkfweqdWLy+W7zPf314en78aZSCUkNZUlFCWYIuoSywcQPUYwI6TwDs+guuzt/RhQKsbXedLNTrnwAQQUgSCqTSQoJQoLKq4z/kXcCf4rf757fLH29VsTx1Tu3PTVnnIy895TI0C7hC48AZciROZX328CvMgTOEuSDsAGCYG6M/CCDNOtu0mjdCwu//nQv16u9/fXsGFIAShEFlMDeYUbmhcgNgmhmAvdEAGJkwEKBeIQBsXAI9rT/kDBgiNALA+ksAdZyfemOrJrS6v93rnwAIsOn4D4bUkrKVyZYmX+rFsswXRZ7/er3++x93j79dP+j/tMQFOE/Hf/YBMMyNcPniNvB6sXdNNBBQi1Dw8J9q+Xr98/1i8+W5+PZMRQmlrjwBUNRuAChL0J0zAE0BZEKDALBZG4gCROGP9dA9aySfUCQUSQlCkawdACAlCAlKUZbV6n+utr/db98tf7xV5eLE+XMA07LOKVixiZYdZs5wDe9UzM8ZwG+rYTh/mMvCDgCGuT3igYDSGQTgBApIv7Xz9q9Mypef/0c8AxqUWiojMiMygzlhZsoNmE0l+jdievNY1Kn/5M0caOn+dQZglxN7OADa/v5NTnbSP9kTApPd95+8aD/dClWDAMgAEAlh1F015a9ZLPViUan/3/988fefy1/XDigMrC/0Tz6J+2QYZl/GGOWzqjQjXy+tD2C6ZPf46v/J9VO+/Xq/fSy2XzfF9+faDVCNBmiXZigAGo3GIBkkg8YAmSawXnx4H0BU/YfKXgMJhVKRVEZIIRUIZaREqYyU0CxUreTK5Nn2zf3m3eLXW6WvOs1vBZtmhmGYKP7LK/3X0MSN+6Xh7GIuDjsAGObW2SkHDPsA4Py1kan4AF7/qaR68fVT/uPvH0YqIzItMiOUERlhVo0GAFP6gYB83d/dgs1QAICITLAzb4Iigt3Zv11xPg6E/icSgtSSZE4qN3nT93+Rb+5X3/7x8Pkfmb5213/WF/yCc8WixF3EGOY2cV87jVM6cbM+jFzBaiVXf0rzc1E+rjePRfFtU/x4btwA2vIEFJX6L4xBIjRGkKm3NC4BrFwCpAGgtfuIovL7IyDWW6oVAERCAVIZoYSUIGvpv9X9QUmSAqQEKWiZFS+Xm3fL53dSZ1fNtQa2zscw9bIzebg248N5cgEGxtldEn77nATORuYasAOAYW6SfQYB7GT8IcOWLqlwJYfx4q1cv7j/+mLx7dOvp6+/pFSmHgqgSNQ+ANLPQNr2ayD58X8sTwCiNQIg1lUQYSi7/O/HUvmbKQStOD8AQIDVrp7uT0BYhf0R0sgcVE5qYVROWWayhV4sykX+9Or+8a/7x7fX1v4vUh9OLebP+GuxD4Bh5oddvg4ua8cU0rGBgABwFj6ACnGP+b3M/5L6x6L8cq8fi+3XTfnrGYomNFCp0Rg0RhiqxwFU64303w0OIFPVCxCqQYDYSv9VxwBCNI0bABFRKpQSpERld/kXoCTkitZ5uc6LtSrXsnhASqPZx9L/SZhH2WEY5gTwu2By8FfGXIk0aoIMw1weywdwzCCAwc3H4gQCmgQih9d/qfXLh28fFt8+/9z+kqZxA5DIjMioyEhvqHxuBgFE1f966dwDMOgGGDNSlPor1Oj+EOj+34UDMlQnMwBEKI3KQS1A5UblJsspy4zKdJ6ZPP/++4vH/1z+Wl/562J9wTl/8MfBPgCGuUH2LoB7HrCvD2BOyDXKtYL/Sy2+Lcsv6/Jxq79u9K8NaI2GsFL/279Ur0MVEcgQkAFjqu79dZ//vvQPgIStTwBRIEqJUpJqevrnCu4XtM7KB1WuZbEWJoEo/y3TMs3ApooZgKsyPpwnDDOGedV8mGnBDgCGYVyC9bcxPgAYrPiNCg08mGxCWkG2xjfrxd2r7PuHzfe/v2uljFJmm5HMjMyoyAgz0s+oSwCA+AgAb6ky4ZQOgMH4P73gPyQlyQWqHLKcslr9N1lmMmWybPuw+vFm9fWvvLxqeAHWF2JXOa0PgJt4DMPEGPlu6cbBnf2OLo18gfKFWvy30o+r8rE0PzVtNG01bDQVJZYGjAFDYAj6zgA0BqCT/gkBEE2l94P1r/4PqCQoSfc5rXN6yPSDLNdCr679/CGmZZ0nYeNmWXaYacM+AIYZht/azFVhBwDD3DDxQQDHhA6wTh/YuC/OIIBptXZWb8Ty1eruQ/7jw6+fjz/roQCyHgoAZd5ODwimgKaPX0D3RySqg/5C+C90foHum+vWmkRUhwoiR+WHvuJf9cy0RgCIzKgcqo7/lfqfZc2iipern29WP95kG+74vw8pNJGuVaC4hcgw5+BUxer4Ehp7t7jbZxQIKIh8hfJVBpABAGwANgQbog3BxtDG0EZD5RjYllRoMAaIKltPCNQE/EchUKCQAiSiECDRSAQpSCLdK3pQ9IDFfbozK7NpZhiGYZjZ1nWY6cAOAIa5bU4aCMhPf9Adzco4ooT1f8jl64fV+8WPj782P54qNwDJjMoCdIllAboEXYAuUBdAJVgzAGMTBQiHpX8AACRyss75ksj6P+wAoDoNAgml1QrVApqO/9RK/7kqXt79erv68UZt71n634Or6AsDehzs+WgnuX/2ATDMaRlfoC5T+sb4AGYZCCjKAmBRWXYAkPVDF41X4JlgY2BjEAEltgtIAIVGopFAEkAi1CsTeIVOyzTDBK3S7ZSeFOF6TBDOFoYJwi9rJgHYAcAwN0+k9bAjENA1mOIggAq1gpf/d758nf18v/z58Uf5LEllUJZQllSWUJZQFti4AeoxAVTWowGw9gEAwHD0f+xlzED8H7BF/ya+P1A7D7BQICUJRUKBlJX036n/uTJZVry6e363/PFGbe9Y+t+Pq5eg2C4YHbWDYZhJcIzJPom5r/V9+6Tt9n4awulZ9tOQAWQI69orUG2j/TM/wdyblnWermmbYq2YmTnsA2AYB35NM2nADgCGYbp62pgKW53mnHU7pzHjt20m2tpZvMLFq+Xidfbr/7D3pu1u28q2bhVAUtPtjGM7zmru//9R98s5d++sZMVO3MxGEhvU/cBGYCtS7MnxPtMyRVFsIIkFjAEU/jidvx/lHEgQURhSGD+GFAYUhhRdzAA2AZkomwAg7TlIRT8gswCSV4ufjaTJf9J0C1Ts+M+alCvaEaVJO5HSpB3SDilNjkOeJ54rca7/dy/9X+4e37vBArIMQ18Ylqtpu4a9BLQQARibul9Zt1/fzb9Vycdry2lcaRwHbUBoBmDvoIYHQAaqO2AxwAAAANQyjHAwKBuQDF590i9+fuV/f+F/C/3v5+DHyfhhYgMEYeoEpDZAGFAUsBgWw8aQpH9W33+mgh9Q3/df7P+ElBLlsHJEOUZrpRzS2miHtCbtiNaU/onniuf4P786f7x7/uCEd2MWUDugL9xGy1/QZCeMFiIAc9Eh799tWJGnnPe/uGLPgwB6s6hyQ3Seng3UjdcKKjEAgGZwdwZLAgYAAKDbTACUbYZBALeiXLr7oO4+eHL2gm+v/G+B//0cPJwkCCmIrAEBsQEQKmPYGBajjInNgHhBiWExlD7mD8JEzHH6IFacTCHMHE8pzEysRWtSjtEOa620YxK5P5b+FTnxUyWuDt69PH+8O310wsMc5VVigtbWNnL+AAB2QsN9ps4ZbsPtcV6Kz9rM/bvqyA4g/c8IfjtgccAdATsHN2WwPGAAAACIqNtMAFdfGpUtNXL4QN4n5X06mNMh/Poq/Br4P87BwylvA0SpASBsUg/AGCXC6TJnTgBRrPvHQj/FDkCykEr/RMKslIqlf9YOay2OTrv8x9K/kleH6LUXvHbC147/kzLezGUVA32hP0v7BaGFCEB/buvO3yHvX2+axwHsazbgzbGu0EwIOmBAUIlpAIUDdgvqM2CRwAAAAFxhIR5Acc7A5emYfVB35P1De//Qd8934dfX4bcg/HYKH88UhhQZZYSN4ewxFv1NYgCQMSRCJhkEkPb6j20ASvv7s0mNgNgeMIrZ0ax1rP6TVqQ1vfTktRe9dsM3OnitwtcLKuB16QsLb+8s7beDFiIAE3CzB9Bluy6U0/4gEVB3Zi8uROeFsLTIDgARanhgl+BeDJYKDAAAQA+YWMaq17VJDrC91o56Sd5L7f1LR4+H8GsUfQvMMaRzJH5IYRSL/mSEjFC8IJkxYDie+zfV+Ykplv6FUt3/YgAQK2atxVF058lrT1675o0TvlHBayY1dymUgL4AAADNjNedf6xBAPlV2wvomwehGQAiaNzXQPmA/YB6DFg2MAAAAMVYVa6nNdfc+uQXvhmxjrvJUKtfs37t0P/jUEByFjqTnI2cDcWPfiSxKxBEHCVOABnJuvwbTmcAyOwApZRm0koUk1ailbxx5Y0jb3T4hmWp0QD6wkgs7YeD5iEA/Wn4UYu1TacQ336b2yinBsIggPbMVVDrCs20m/iytMgOAAA7AvdfsHiWKvkAACaDiYZo/A+vDthyhbWOq5a3jEvsMr0mJk2kk5UR0ZnoLHSW2BWgs5FzxJoLf6SZHDaaSHOomTSJJtIsmsRK67/AkoS+sDfgAQAwHtzfA2CSlj/R+s0wCGADrCs67y2s4EcElghqeGDz4M4L1gAMAAD2TU2s6pAp2Fo7+FCApBkj1t5BjCZ6SfSSiThL2RPPoCjdP4IFFi30hQmATADArhjAAxjndodBALcxfREhNANQDQTuq6CIwFZBfQWsBxgAAOyVm2JVG4l/rApeqhBsezbgXmyiFKAvTMnSfjtoHgIwKhN5AF1/yeVBAPAAlgei81pYWmQHIAGVPLA9cLcFqwIGAACgujLWUElrkw6obre9qG/ToLVzM4sqtwnaBcj5s3DQPASgiG16D7Gz5kl9mg7CRNL3R1odr+EBdGTKkoH0vzpQK54BVF/agFICWwL3WbA2YAAAsD9ax6oGHX+KXAGV70+z3OxuJoCtA31hLhb4C0LzEACiql/mQL+NbDcd0v1VndrwGf/AwlhXaCbEDgv8psBCQSUPbADcXsE6gQEAwJ64Ne3PbB5AlwYMmjo3sIQSW5e+sMk2C347ACyLhh/k0NLJzR7A8OdSNQgA96ZKpikVRGcAugFpuyUoKLBqUDMBqwUGAAD7oDFQ3ZzSJ3lj4/t79ROs3zMGAWwD6AugErQNwU6ZKpg1TwZQt7L8woA/1cpQjvg+CwjN2wA/H7BcxkoXC8CY4JYKVg4MAAB2wECxqk4joFGHAnR5J5o6nZi3rKAvLAr8dgCYmfaD3YY74I0ewE2btQIzAbRg1NJYV2imHURnsDLQf6ETKC6wIlAXAesHBgAAW6ddrCrr+Pb7mjWChvXNh2hLzd4rBwFAx1w+0BdAG9AqBHthvqB1owdQWlux2U0/YETweVlXdEaAaAN+U2DpoLYHlg9uo2ArwAAAYNN0DFfFzdMKGfMwHkD7zarPDO2Y9QN9YbEs8OeFViHYPp1+dXP8HtoHdxrkBDEIYA4QmjfMAoP7xkHdpSsoMbBkcAMFGwIGAAAbpX+sktzy/B4AXbkoDALoxPRFBH1h+eC3A8CkLED9vzoIoGH9lS1r3oabTB/GKL0J4h3G5AEAmoAHABYI6itgc8AAAGCL9AxXlTWwhXgAxZNCaF4H0BfAbaBJCLZJ1xvWmD+DWzyA+l9m81CAq9ddCOsYBDAqMOYBGAXUXW4A0wKDRYE6B9giMAAA2BZjSP/Wq7UegPVk6nQBFhgE0JIpCwf6wrpY4G8H7WiwNZak/pfpPw6AsktkEsmvaUPNbWiBd6eJWZ3PvboT3ir47cwA6i63gXIDs4PbJdguMAAA2BCjqv/ZNtZRBk4XcBPVvQXR2lkA69IX0NwAAIzODTesSe5NhVjcKri3i9+D5CNkQlAfEkTnHYIfEFgN8ADAjOBGCTYNDAAANsEE0n92KCEZ2gNoOAVE4cGZpkihL6yXBcoEaAyC1XPbj2rC7/1YGfwG/PXuOxHQIBeO0LxnFhjcNw7qLjeDdEBgenB/BDsABgAA62dC9T87YOeugi32WT6XlldWNwgAzAL0hQ2wwB8R2tFgxSy143+BNpMB5F6aatqfBd6RVse6QjPhhj8O+CmBNYGaH5gM3BnBPoABAMDKmVz9T96VP+4gHgANHXzRzikzaoFAXwAAgCKrjUNzeQBNsXuvgwBmqet1BcY8AEUgYfcEQwHABOywVgH2CgwAANZMn3DVry5VSAREw3kAYKVAX9gYC/TPcDMBK2PxaX/KtBzhRzfIMq3fwJgBeDgQmkEB/JSmBnWX/qAMwUjgbgh2BgwAANbJfNL/5RSEJH8mM3oAmAq4DSMVBfSFrYKfDwC3s0L1P6a9B5C82j/fX0f2dmta8lcJY/LWyN5+QWALYCgAGBzcB8H+gAEAwNpY0jhwJpJ2zYgZu26gnTMqE3ys0BdABjqBgRVw8z1rMV/urh4AMcnN+f6kamUDCOotWJf6v5gvPgDjgLrLUMAGAIOAWgTYKzAAAFgVS1L/YxIPwH7asOXwxwdtGbyqA31hD0BqA6ADsw/Oy05g6Fve1QjOXT0Ae9e1z0DnAllXaCZE55lAcJ8atIIGBIUJ+oB7H9gxMAAAWAn9Y9VoVaURUwb3QPJKCGL9gKxLX0AbYWOg3QcWyowd/8uH7h1uyz+0Nh4AyQwhHmQgOgOwXFB9GRAMBQA3gHoD2D0wAABYA8vr+F+gc8rg0c8IjAX0hb2xQKEN9xCwLBYl/ZdfvfUoN3gAxMRy+zGXdquZnfYFgtAMurLA4A5AN2ADgPbgfgcADAAAls6CO/43szQPYOftnEGuHfrCbtn5zweAWubN+dPy6D0kkts8AJLOuYhuKEjcl2LWFZoJ0XlJ4Ec0Nei/MAawAcBVcKcDgIhgAACwaBbf8d+mq0wwQR0YDZuhgL4AlgYa0WB+Ftvxf1Aqgzs1X4T1nsKZFgYL3sg+onubS1xXdMZNGwBUX8YCNgCoZAe1BQDaAwMAgEWyzo7/C/QAbPahGFSwCiMJ+sLC2e3PB4BqVqf+94i4lW+9sr+al3EbGQqEZjAICO5gU8AGADa4uwGQBwYAAMtjner/Bfv8ZVkeAOgE9AVgszSZAHcPMA+rSPtT994FeAA3n8CuaLjedYVmwo168SwtuG8fVF/GBjYAwE0NgCpgAACwMFbRW7seJhK7JcFERCyzeQBS9CN2Vx+YsZfqVaAvAADWxOwBuv9Nc+iIe0VmGVmF2WFMX5f6j9AMQDXwACYANsA+2Vu1AIAuwAAAYDGsveN/SkWdllNjoP1bwHxAXwB1LE1uw60DTMe86v8CfngNP7frQwFo0N/qAkpjVCqvb12hmXBzXhVLC+67ADWYaYANsCtwIwOgERgAACyDJaj/w9WQWEhKV8Sc7rsmL/A0dbNdNXK6Xum69AVU5mdhV78gABLWm/ancm+DJgLKXqLRbADcc2Yek9dx7mZE5zWC4A62DGyAzYP7FwAtgAEAwNzMLv1zzdN+u23KF1xziD4eQHO2XFQJ2oCcP6Ali/pNoQsdGJdNdvwfxwOg9jbAlY2q31HzfGsUrm9mY75w+GuFj7sxAB1ADWZiYANskq3XCgAYEBgAAMzKvOp/89F7VpLaaISF9PxTVcwWJV+OR/trRMd/AACoYJPqf2+ualatQnkLJ6CiAJZaJmMwf2jueAaIzmtnJ9XjZQEPYHoG6usG5gc3LAA6AgMAgPmYUVlof+ih+wnW7s86peZJgxvfCroxv77QBVTUl8OiZAI0n8HwbGC+36v773GSbd7dVmOp6fHODdtsl4mFqbYd/69sjTvwdlhUcAdgXDAgYL3gPgXATcAAAGAOltzxv3L7aTwAa4tLlezaoW8ry823cNpcHdR/AACoYE8d/2+O8O3fWBrs17hll8vfZBxfovRf/waEZgD6gl4M84IBAetik4EfgKmAAQDA5KxL/c/edcNBJZHyb/EA0s3o2tTBrU+k1cqdAOkf9GdRvyA0n8EwLG1WnlFJfzYTeAD2W0ADC52MB+r/nlhUcN8LqMQsATgBCwc3JgB6AwMAgAlZtbIwkwdA2VmjWtaa5s95ofpCDfi0lwxkArAp9tTxf6hjzihb4eZzA1cKrbKu1m5DAEAv4AEsB6QGWhQI9gAMBwwAAKZijR3/R+bGui6qZbeCjv8AAFDBqu35nsftNwig53vBZLT6irX7HuLj3jxw9wFAz7P5wW0IgKGBAQDAJGyjX+HQgwBu3mXyzmz/t7LJFk7lFUH6ByOxnB8RVEhwI3tV/7n0tI8HQNP+ABdy21kFGJMHwDpAPWaZwAmYGAR4AEYDBgAAI7MxZWHoCYH77TJ9P92+i+XIl+OBnD9gVPbwIwKbZRv2fB+Gk5ymEa+WUGYrAsY8uA1E9nmAB7Bk2s9oD24DNx0ARgYGAABjgrQ/ecbyAAh5gRIKnzk6/gMAQDUbs+eHOHj/WDxqR8nlVWoWDYx50BN4APMAD2AVYFjAgOBGA8BUwAAAYBw2rCzcUDG12hAjegC37mWTLRxI/2BKFvIjQqsZtAUd/22sX85QP6IBTfmlldYqqC20wkdyrXBxR905CwnuACwXDAu4DdxZAJgDGAAAjAA6/pdp0YYYzAOgndbAJu6MAvUf2EAmAOtgw/b8QKcwoJF2c1RaQCGtlbbS/zUQmgGYDXRnWCkwA66C6A7ArMAAAGBQdqIspBXT2yqoDe8arMbbcUcb0C6nND4g/YNKNvA7AhtnJzH6BgbXm7jhGe7+o9D05aobetllc7BPENnnAR7A2oEZkIE7CACLAQYAAMOxK2WBiUdIBNT8UjfyOQ02z7qkf9p9ZXirQCkAC2WQ7+X2BufVcHsgbnmZlZshKvSgm/oP6R90AZF9HuABbIZZpmibEdwvAFgqMAAAGIi9pv3pVjtt7QEQ0gEtD3T8B8sH7WVQwa7s+WvUnkv+xzPDT2lvEslAdPtyQfoHAIB5Kd+H137/XVIlBwDQAAwAAHqzW2WhXyKgNrunIfbMUAR7A+kftAddBcGy2Ks9PwPDXuwQfsDmczB0KPLGTTdZOGBAENnnAU2YnbCiUXG4EQCwZmAAANCPvSoLtx+53SCAwoFuKyQuLC2zIrVskPMHALBWdmvP38zsgwAauDah8NXCXtbl9AbSPwC7YGN3LtCe5rv8qN+K1dVeAADtgAEAwK1AWbAYIxFQYf/2uzttX1xb8350biqDjv/gNvBrAvOzV3u+mevn1ccDmOaXn9Ufun9Am1HSEJ3B9CCyz8Zm7lxgQPBrBAB0BwYAADexb2Uh17Neyoud99bpjX2vHqMB2jH7lJlg1cylFOArB2DPD8sSpKcmU5+6nd8SLqcPkP7BjMADmI2137kAAAAsABgAAHSnZ+V3u8pCh9ppvg0x52SDqE9XAfUf9AdKAZgBqP/96ROSh/jZd95Bx0GC61XSEJoBAAAAAMBtwAAAoAtQFsqncHNLenYPIDtwJXttIkNfAAMCDwBMyr4H512lW9b4CRMBDVlyV/e1zviEjv9gISCsz8Z6rUsAAADLAAYAAK2BstCCPjLBsmq2u8wUBPUfDA7EAjAFsOdHZtgAPWdpMRERy5pCFUIzWBQI67OxrJYSAACAlQEDAIAWQFlIqc3De3NXwaY9tTz8mLXhPdkAs3/NAQDgFmDPj8EIiYAWVFS8Dg8A0j8AIAc8AAAAALcCAwCAa0BZGJWSTFAtuTeXwxAJ/ZuOsIPa9rwTW4BtM1lvQXwPd8fsvuV6YvQtZzpQIqCFFtKyIzty/oAlg0EAc7LsexcAAIDFAgMAgEYw329Lhs4XfGPP+/Gm9t1ubXt2AQ3sAYgFYHhmv3nt4Tvde4TfjcZDRtXxGvbZ6fSYSZYXwCD9g1WAsA4AAACsCxgAANQAZaFEpzMaRDBPbIAbiqLj4ZdX2BOBjv9gMsYWC/Bt3BcYnDcTHaJrp998Xcqgwvprxy7vZl13Bqj/AIDrbLdbEgAAgPGAAQBAFVAWbmCEfMHZjrOtup1P5/dc2+G2att9vmjbKgkwEeN5APhC7gjY893pdcqjBffL/q+sqHqt9Sk1DydYziAApPsHqwODAOZkc60SAAAAYwMDAIA8UBb6MHQioPLuC+/oekpX99lzbysC6j/YDPhC7gjY87PQI7iLELcrt87hmDp/mpVvmt0DQK9/AMAtbKhVAgAAYAJgAABgAWWhnrZn1ztfcHvajvSvkQmWXd7jctu1o5UB+jN4h0F8LfcC7PlbGePEBwvuPScKvmmE4KJEs9m/1wD0AYMAZmZRtzMAAADLBgYAAES0gBbYdqvPY+ULrjnWldcGmZdgf1Xt/V0xGJH464R8F6ADiNGz0yP2XR0EMEDpdnQCClczyyAA3APBNoAHAAAAAKwCGAAAQFkYmsHzBQ/YthhqbuI1t7k7leWaLxQsmv4/a3w5dwEzGIP1AAAgAElEQVQC9HIYJxHQwAXc2uyfN5LP/r0GAGyElbdKAAAATAYMALB7kPanBZ1Pc+TJAHox+OTAGwUlBMbm5qEA+HLuhdlV0pXE6LlYru7ULtDb5z/ZIAB0/AfbA4MAZma592IAAAALAgYA2DFQFiZkcVXTnie0uOsZjI1eFlgu7W0AfDn3Bez5IRi+f/2giYDan17XCxFqZQNM6QFA+gcAjMV2GyYAAACGAgYA2CtQFlpz+9R8AyYCGqNz0V6HAtSlWQJgRi4aXM16sCNgzy+ZcRIBNR/wBthaEmo6ywl0s6G+ULgfgsWCQQDzAw8AAABAIzAAwP6AsjAZS04ElHFzdXnN9ezVnjjYPvhy7h3Y88unT3Av0BjrB5z9p9kGyC5h8EEAkP7BfoAHMD9rbpsAAAAYG6cQIxC2wcaBsjAxU8kEvbh5KADq2QAAMBSw54dmsgtqHwwLgwAaAvsoo/7qbYAx4jly/gAAAAAAgIVQHAFQrmVurjUEdkzPb/MulYUxWuBtC3LirkRQ8wEAYC6g/q+LoScDmIzEBqj3AAYZBICO/2CfYBDA/KA5AwAAoIbrKYDsCIKIDtYKlIUZGXYygFG54VRRzwYAgJ5gcN4aGWoygKpAP/ZnUqfyD+IBoOM/2DPwAOYHbRMAAABVdJsDoG6OPgAWDZSF2RlqMoAJWhWoNAMAwGTAnh+N6a/stvg5l1wYOxDI+A8A2CBozgAAAChx4yTAyUxZA54IAGMAZaEf453+ouulXacEWPTFAADAUoE9v3aGSgQ0X5/hcmf/2wYBDHj6qFCAtYNBAIsAzRMAAAB5bjQAYjAgACwaKAuLYqhEQJO1KjqdMCrZAADQHtjzm2GIREBtA3t5oyEiLzORFFOedvIAuHBuN50VKhEAgIFB8wQAAIBFLwMgAwMCwOLAfL8LZKhEQJOBejMAAAwO1P/xmfQS+wT3jJuifG4MQf2OW+yIWG457aL0f1nbrRRQ1wAbA4MAlgLaMgAAAFKGMQBiYAOARQBlYSBGuY75ZIIbaX+KqGEDAMBVMDhvB7SPhx0GAXDDs/wwwbavFLezPYCrgwD46nepXSmg4gC2CjyApYAWCgAAACIa1gCIQbAHcwJlYW10qJTOkgiI4AEAAMAQwJ7fMINMBmBF9uYg3/RJ1ve+v+4EVHkA1RsO9F1ClQEAAAAAAEzD8AYAYSgAmAUoC2th6MkA6goe7WoAMrpOaAnAwMCen5B5LneIyQAaN+pyYY1JeJpeLHkAlL95dpP+60sB92OwB9AvcCmgixIAAICRDIAY2ABgOqAsDM241zTQZADNJ9npEhpOIMnw23IgP2rYYJHE+ho8ADADsOf3Std4KETcehBA2zOgJhugzgMoVFEG/wrhTgwAmBq0UAAAYPeMaADEwAYA4wJlYRPcUCllGrJn0fXdtDhFVK3BkoH6D2YA9vyu6J0IqOAB1B2k81lRU1Kg8isFu3TA4I7bMNgbGASwINBQAQCAfTO6ARCD2A9GAer/ehk6EdBCKKcLAACAPYIAPRMzX/eoiYD6RPxGG6DCA8ivrDzs9UuThmcA7IWFVdX3DTwAAADYMRMZAIShAGBw0K9wNCa6uIESAU3XsJhkNuDCpaCWDgBYH1D/90yf4J5PBDR8eK85m4rV+ckAuuysAoRyAMBSgAcAAAB7ZToDIAZdAMAAQFnYKN26Cs7yObQ7xRszGtWvR0UdALAaYM+DPB2E8lIioGKs7x/7J/MApPA/APsFCsCygAcAAAC7ZGoDgDAUAPQEysKW6JcISDj9QJbXsGg/4erCThzsCEwLDAYG9vzcLKUAek8G0LTBIJMDd/EA6Iarue1dAGyX5VXV9w08AAAA2B8zGAAxqASAzkBZmISpr3LDiYCu7QaAeUk626IFCAYB9jywGS8RUI9ZgqWwUemc6s7zhvMHAIBFAw8AAAB2xmwGAGEoAOgElIUN008mWCx10mpt38ZtXDZYFVD/wQDAngct6JMIqNNR2rwq9edUXJc+b3v+17IGAbBb0P9vcWym0QUAAKAFcxoAMagKgOv0/IpAWVgbXWWC9MniBgEUPIDmtAaohQMA1gfU/8WwuJLonQgo8wBaDgJoXwLNp1bnAVwFlioAYGWg9QEAALthfgOAMBQANABlYVpmu9yh8gUv2FG8ov4nG1WUA6rlAICFgsF5oJkeI/waJgO4bXBA+dSISNrPB1C1MnsK6R+ANiy4nr5j4AEAAMA+WIQBEIMKASgCZWFXDCUTTHMr6TIIoM+eUSEHACwR2POD4xBpopAomvtMxuTmyQCuxvbbvhFc7wGUtqt9Eeo/AGDdwAMAAIAdsCADgDAUAGRAWQB96qJbsRNRFQcALBHY833QZDySgxhP6GDYEz4Y8iL2DDuGQkURp4/M8XJIFHK8hkLikCki8vMp5ua7oCsMMsKvSyKgrtR5AFdPXCD9A9CdrVTSNwc8AAAA2DrLMgBiUC3YO1AWZmL+Sx8qERBNch9BRRkAsDdgz3dBFJm3YjwRT8zBiGfEi8gz7EXsReyGrI0oYRZWQsowEwuRMCd/lFswiiIVWwIcKTlr8+CaH070oMzz3JfaTL8RfsTEVAzrwwb5ulNCnAcA7Ajc8gAAYNMs0QAgeAB7BvP97pwB8wWv/D6CGjgAYFlA/W+HKPLvxbwLo5988/YsbsjasBJKhH5hNvYCK6NYWBlhidV/EiZiEiYhip8KU+oExJYBR8yRlkBHT4fo0TM/XPOgox9MwdzXX0mP4E5WPB8vsDNf687PRAJ9DIABWHkNfdPgHgcAANtloQYAIR3QDoGyAGL6yQQ51tjCkOwBAAAWAwbnXSPW/YOfYt3/JC8CcSJ2QtLG6uMvrAyxsDLMopSh2AxgI8owC+XUf0v6J86GCsQeAEWaI02hdl6e6Z2iSMvJDZ+88NELHxzzQ5kHXnIsaR/cC4mA8q8N+d1oOfEvAABsGdz1AABgoyzXAIhZo3wHbgHKAqjnFpngsqrV59vyK1A8jcGryIIqNwBgYcCebyTW/c/3YfiTb+5PcheIE4kTkhOxE7ITso4yxZ+UiXv9KzasjLBhNsQiyjAbIkOkMgOAibP+/upiACSPZDRHDkWaQycxA+6080ZzqCjS0bMXPXrhgxs+OOGDio5zFxP1zfKXJQISHm00QPczvD5uAABQBdr4iwYeAAAAbJGlGwCEoQB7AGl/FsCyimHAyQBaHOq2jYevGEP9BwAsDdjzNYgm/60530fn+5zuL05ETshOSIn6H7COOBb6lSg2lCwbYWE2FP9Zalgs/ysiRYn0n+j+xEpUZgaQcTjSFDlsHIrS5VBTpCnS+u5MbzVFigInfDycv7w4/umEj3MX6CCTARRfGH0QwCAbAwAKwANYNJnRCgAAYJl0D6IrMABiUEXYLDOq//hKLZnxJwPo+flnbx+mbgz1HwCwKNDxvwb/jZzeh+d7P3hb1P3FCUmHfJH+A9Yh64BZVNrZPxWxs8tjEcW5NTGX4mM2sfadDA2QdME4bBwyDhuHo8QGSIYFRA5FmkLNkXbvTof758OHl0uxASxu0dCtQQD51cN8ZcqndFlTNQ0ABgEAcDNo4C8d+JwAALAEBgqWqzEACEMBtgc6/oMuLHQyACbunbUf2gEAYFmg438VoUdPvwbHT0f//kmcSJyIYvVfh+SEkkj/lu6vQsVCiSXNJHF6n5iiDUAVkUoal4XZKH1mfU4GBCROQDYsQHPkUKgpcCXwHCd88fb5xYeXx3ltgEESAWVragrudjqeXrw5PAAAwGaBBwAAABMzWiV9TQZADHoKbAR0/B8Wl4iIwhtLZqFFMmYioMEv+baRspAMAADLAh3/qxCmh1+i50/H04cn4wZy8MW96P7kBBQr/umjimVhiXP5pFo/U6Xuf1ko1nELRSnW/0IsmRNAJEqFzIHSwo5OBgQkZoBLridBIK4nvqud8MWb5/P7l09/zWcD9E8EVDMIYDLsc4Y4BkAf0LRfAbjNAQDAqEwVCNdnABCGAmwAqP+3Ii6JJ8YjOQh5hg+Gk8eIiChiClXyGDJHTGH6FxGFTCFxyBSRBHNfSUvGTwQ0LJ1sAKj/AIBlAfW/isd35vHT+fTxMbrzIy8QzxfPF9cnHYgOydb9OdX9SVGWsycT/YWION+LveQHFAND/SAAyRkA2R8TsQqU8lmIjabII+2JPpDjieMlNoAbvnjzfP755dNfL54+z2ED9AjuCWlMtwcBTJYFqOItGAQAANgw8AAAAGBY5mj1rNIAiEF/gbXS52PborJQhzmQeSniiRyM8Yx4RmKh34vYjdgLWQmxiBJWRrEwEQsnj8lfumyYIsWRoogpUhwq8+yYH675oaMHlnDuS22mv0yQMdVdA5VkAMD6QNqfEqfX8v2jf/z0FL46RW6Qqv9ncs/inkkHpALWgWLK9/ev/IspjQMQqr34RNhOEftpqvgz5QwAIcsJEGajnCOpM2lfHI/0QWIbIPDESWyANz+/fPrrxeMXJ5h1boD2cTNLBFQxXmLCKygPAoAHAMBtoFG/DtC8AQCAnswd7VZsABCGAqyRudT/9XxLogOF9yZ6F0T3vrzyyQ1JCSshJawMc7wgzIaUMBulhJWRWPtPm/ylv4sroIRYmCLNoTJnN3o8RA+u+eGYB2Ue1lFMnWSCYiKgMRoZVSd0W0YgAACYB8zKkyc40PePwePH5+D+OfLCyPUjzxfPT6R/50zOiZVhYrZEfyHmyoVLwhrmnOKfcwLYXkcFxf+yioTS/D85xb84ICAZIiCKDOsjq7MoPx4NII5nijbAi6e/Xv740w2Oo5RnBf2z/BUGAVBxeVigfQEwHvAA1gGaNwAA0JUlhbd1GwAxqDGsA3T8byQ6kH9vwp+C6N43b0/iheRE7ISJ9M+GlZAyzMLKEKe9/pUhNqIMs0gyAD79S5ct6f/yyJGmUKsX2n19pI+aQhU9H6JHL3xwwx/aPCg5zV0iNsNOBoBbBgAAZKDjfx5R9P1j9P3D8fz+KfKCyPUjNzDuWTxf3DO5Z3JOpENFzKIT6V/qev0nNgARpdP/lnL+JE8sJTtXnjUpgKRK8a/KCJTtlMlofSQ+k/JFeaIOoj3RnnE8cVzHCV68Od69fvP1txfP39SwRVpLjxF+RkjxJcQPmQiIEzPm6slUZArCIAAAwOaBHQoAAM0sr4ETswUDgDAUYPmg438Nse5/vs/p/uKE7ETkhuyEzCZT/JmFlFFskmU2pIywYTbEhoguoj+lWj+xElaiWFgRJSuN4sihSMePFGkOtX5x5ntNkRLfiZ4O4YMXPjjhgw4fWKKZS4mol0xwdULg8UANGQCwaNDxP8/jz+b7x/Pj+8fo4Kfqv2/iXv9Jx/+zIsVGpVn+rZw/bOn+OUuA8gtUWMgJ1tVFWjkxQEsPwGQpamIbQKWjAURfbADRbqQP9x+Ncwi//ef19z9077K8hW7BPd5++vl+aob9IdwD0Af0z1kTuOUBAECBNcSwjRgAMag3bI3NKQsZme7vv/XN/UncRPcXJyIn5OxPB6QiTtR/Eyf8oVjuZ0Mcq/+StX/jjoiKSMUGgP1oLbAoMg5HDkcORQ4bTWHsB2gKNd9p5+Xp8LOmUJmTF3y/O30+nP7Ui7ABLHrVPCe8WTScJ7oKAgDmBOp/ni//Cr7+88F/Gaf7j6V/3zgX9Z9JlOi89G8ZAOVxAEm2n7z6n3X6T7DEf+E4Q1AVYv0v1hohsjICVRgAie5fbQNwkhTIaE+rKGTzho3nBe7d26+/eVEwSLk20m+EH1mTAVwCu1gplwal1bAARHYAbgVt+TWBdEAAAEAri1ubMgAIQwGWyW2fx+aUhZjjB3N+F5zf+uHbE7mhWLq/OCHrkJyQnYB1yDpgHbCKmI1ik8zqW+5CKFy62kvZMQuR4bQlzJLmBhLFxon/yDicjAZwONLJY6g50nR39l4dX7z3jh9fzW8DDJoIaMphAfAAAACLY0b1f3kBOtT05d/+139891+cIs+PPN+458jxjZPk/GFlVGyvS1n9v3gAacIfu9d/KfNPxRwAdHlakfc/Q/IL1qPUjQnI6f4VowGcI6szKd/wnWGj2ERsFIv3r8i9e/v1t7vTBJMD9R/hV54MYCod0T7bbBmRHQCwFzAUAACwT5bXnGnD1gyAGHQfWBA3fBIblf5Pb+T5V//4y1P08hyL/pnuTzoUHbITxL3+WYesAqWDWE1Iegwmmf1j6jIJZFSmC7gsMIlSIatAkaQ2gGbjZDZAmh3IEd8TN3Q8/9XP3tPsNsCwiYCWMQ4AAAAmBR3/85wP9Plfp2+/fg+8c3g4R4dT5J6MczbOmZwz65CJ2ehU61dCzOXu/xV/VLVAQmwFo3RJ8k8rxwFIIZQ3POa6/ye6P0ssTRMZkkQ152Q0wLOiyLBRZBQZxSZk8+GDObiv//rj1cPn8acE6BPcC93/8+snBrEegJ6gFb8+cOMDAOyE9cenbRoAhKEAC6HrB9C/9rDIjzw40OOn4Pjp2X/7bLzAHAJyQtFh+hiQTnV/HSiV6P5k9EUyYKZYNSAq6f511yzVy1JMF8AsrHylhImSYQFxXqDIodAVx5PAE8cTN7z3gjfv3KcPr56/zD0aIKVvnXOQdka7M6g7VXQVBABMBzr+53l6JZ//efzx8Ycfq//eMfKOxj2KcyZ95jjdP1fq/io204u6f5LhJx13dwnc9tA9uiznyPyAyoIuh3W7v3/6KPlBAGyIWEhY0gXbFZB4TAAr9pUTGYoURyodCnDP5uCGXw6v//6PK+am8r2V9sE9SQRkXVjmtNyYCCg9cJ8KBiI7AGBHIB0QAGDDLLIJcxubNQBi0IlgTiZW/xf5SRtFPz5Gz78ezz8/Rl5oPF/iP8ePe/1TnOdHh1l/f5JE95c0mYBQlhO4lEkgt1Cm0gOwBYKcTMAkzJHSodLCRrFxxDlQ6In2LBvAvfeC1+/c5/evnr4cjp8ntwFKLfJOMoE9CCC5PyxgHACUAgDAFPS5120xRn9/az7/8/nx54fAOweHc3Q4Rt7ROEfjPDObS7r/2rQ/2SNdDAChKt2f03z1lC+LGgOgurjtOG5n8iGyDYBCFiCx5P6KpEDZUABmMlo/GYkHASROgGbjucHBe/vl94N/HPNT7CG3NyQCGnwygPJpFtagLywAPUH7fa3g9gcA2BJbDEUbNwAIdYi56FToG+34//CzefjldPr4GB2CyAsS9d89k3sW90w6IGXp/qao++f/6CId2AqCUPXFZ23fjHLqAL5oBJzIBDlXgFWolS/KE32g0BPHE+cggSuOp13XtWyApy/T2gD9ZYLi2vk9AAAAGJe51P9FBmgi+uvn6PM/Hp/vnwLv7HunyDsZ92jcZ+McmYhFiyhO9H1FxMSJGZCuLEfqihEAl4UsXueCUN6Uzp5WTwUs6fjW+uQ/QqX8P6VcQJeF7DE7G+PqJ01RRFFEJiITslEsv/xiDu6bz/998fhtzHRA+QDZKVwmVzhrrr8CsPYBuBm039cK2jkAgFWz9dizfQOAkA5o4WxR/T++ke8f/edfnsKXp8gLItc3ni+uT95Z4hkF9YmZVKz7c4Puz4k9QJTKB1Q9AkByz9Kn9SMALnI/WQKBNSZAEoFAcagcn5Qn6iDKF30QJ5DANYkN4L9+58U2wI8/9ZCF2EwPmcDG7ic4rwcApQAAMCI339+22PGfiP77Mfzyj4fj6+dU/T9G7tG4R9HPLIolSftzUf9JsVhd/iWeoKf4l47YqzIAssecc185AkCsKX9s0g/jMuVv3WOlByBETJIkBUq7x5vyVWg+a20iMRFFmkxIRrF597M5uOEX79WXP6drO7QP7pKOsIg9ADukVxv/Y5KdNiI7AGB35JqiAACwBpbaYBmcXRgAMehKMB0tC3qL0n9wR98/Bj8+PAf3z5EbJOq/e6a4779zIufMyqhYPkjEgppehOURAAmVT1Ok7knJACilACqtyYQDVhyy65PyRPukPVEHowNxXON4jhO6nv/qp4P36u3X37zIH6AYb6CTTFCtBdx2j0AFFwCwcKD+WxhFv3/yv/z64/ziFHjnwDsF7tF4R+M+iz6laX8USU79Lz0yVcduqgrcuad8WU9UXK5bQ/kQ1Sz9X0T/tONA3gawkwLJZQSAddTY+3/UFBmJZwSKQjKajeeERPcjegA9eo9mHkDytOABULsv5E1HL581esEC0BO03NcNboIAgOWzvzCzIwOAUJOYhtHaV7ccaEKMpu8fwx8fTsd3j5EXRq4feb5xfRMn/HHO5J5ZhUzMSbYfK4FAuSNhUVkguqjWzcJBGSk9LYj+sR5QZwBc8gXHowGEPVK+aC/+M9oTx3NU9OHfoXt4+/U/L04/Jvls+skEWWnmbgtT3SMwCAAAMBG33dO2KP0Tke/SH7+ev/zyzT/4gXcOvGPgnoz3bJyjKF8ZbfX6L44AKAbuwlQ9WbDmUvd/icVntsqlsuN/AwX13+pgybHubSf/IaIsy1/mBHA+y59tG1CxHiKGiBQZrR+NRDpW/8loMkro0ycVhW+//j3amL98gOwU6pPu/2mIL5baQCG+0ykhsgMAdgqGAgAAlslS2ykTsC8DgOABzM4WpX8i8g/0179P3z79iLys178fuWfjnilW//VZseKKHoW1HQnTkQF0eUwuvM4AqGuTSv7/fG9BtoWAwh832ADEcVIgT7Rn1F08W+C7X8S9i77+9vLh8yTpgHrLBMlywQOg1t+xHt9neAAAgIWyUfX/+EJ+/3T6+/13/+AH3ilwT4F7jNyjOM/EYRyghRSTItJWth91iddcn/o/DtxElIvd+SAulOuj3tT3v9yhPFvP1kLWnd9+jB2Bcky3ECJSyZbMRCYfTC+VE0edxDFKokgiFiLDr1/yp191GL5++DHmfAAW3YJ7vP1tHsCgMT1bg8gOwG2g2b4FMBQAALAQEFF2aABQR30PDMZQsX95n9zzS/nr38fvv/wID+e413+c9sc4Sc4fxcyiSZQQMykiFkpmESzn/bfWUEFEEMkpBzVdCBs9gMty+ljoM1j8u+j+RRtAhaySuQEMR4ojzUax3LM5uIF79+brb64YmpjBKplt2hy9j4QqMQBgXLpGzI1K/0QUKvr9l9OX998y9T90j6HzLM6ROFKirZw/lSMALm69FGM3lZyAS39/LkZzSmyAHA1OQIzk19uKPxXDes7Cv/pnSGId3x7HkAvein3lREqIRbFhMnz/isNfVRS+en4e5yPvER0LkwEUX6V0BuXyiZeOiAANwOzAA9gCGAoAAJgRRBGLPRoAMahPTMSAwX6RH9iPt+avfz8//PwQHs7h4RwdjsY5G/ds9DlJ9y9KJJ7xl9PMwnb3f1Wj+3PahqW0TR6/UmUA2GoCU1Whi7VOqh4r5YNU7ue8DSAmFQhYcchOYChSHBk2ik3ERrHx/h14h/u//3PwR1IHMvrJBNWDAGpX5V8dB3QVBAAMQ6e7b//bziJjdMYfv/hffv5xdn3fPQbuMXSPoXMU55mJ2M78w6rkBHAh80/LrH3xYz6UZ2su/3LUxp3MOcjU69JQgOIgACGK6wyW3F8cFmAu4wDIlI54+VMUsX4mYTLJ3/s3D9E/1G//89IfaeKffHDvFOovwT0eC5EvVKmrKPWmfJLZGkR2AMDegQ0AAJiSZTdM5mK/BgDBAxiJkeL6Ij+qv99Ff/376en+MfDOkXcKvZNxj8Y9GufMKlTEJFm6f1v3zxILZMuUaQqpWGB3KiRL668yAHIbVH6v60cA5B7tAQHxicTdA7lqNEDcnDVExtHPhowhEycCiv9++UUO3psvf7x4+nvkLAGDyAR1HkC20/LKIejhXwAAwEBsXf3/86foz/cPZ8f3nbPvnAL3OXKejXNkYSLFduYfUVbUji2BnFtfJfcXnIDMqq97pESWzmE7+ja1knLJDKjwAESSE6ru+5+o/9kypf0PmDM/QBLzQVPI6siOIqNIFBn+5e2P6B/qt/+9i6LOn0gr+gT3KpOk8CoAYBXgB7sp0PIBAIwHosU1dm0AkNV4AotmkZ/Qnx/Dv/75cHzzHHjn8HCKvFPkPRv3KM4zZ+n+Ja/4p49c7FRIlqZA5S6EOdXgQuVyXa1K8gvxqIG6cQB2b8GC+l+0AWK7wlFncSJDkUpsgChieffOHNzgs/f66x8j32p6yAStGLOqWtFnEF0FAQA9aR83e95tFhmgbb69lP9+fHo+nBL13zmG6hipZxbFpEhyE/9yOfOPFEfslf84F6zTXP9U+UilImsuwXJwb+MBZCtTJyDZURbHiVP1ny9OADV/GzT7rBQ5zIbJYzL8j5++R6H67Tev8RIGo31wLycC6uoBNB2oy0/m8nkgsgMAAFnGLAAADMLiGyPLYe8GQAx6FiyXRX4wwvT7p+Dvf/44vTwG3jnwTsY7Rt7ROM+iT0rsBALxnIGW3F8tJVCS5+fSbTCvGmR9+OoMgExu4Mr6lFyGDnBF3/+kdSwlA8AeDVBhA9gZA1hTpPWTFhORiciE8VAAZQ5ueOe9/fyHF46UKKCKTjJB0yCASYAHAACYga13/Ceik0t/fDj+ePV4ds6xARCqU6SOLDnpP6/756K2XDz7St2/8EdpJ3ri3DgAIio8pariK2jU5Zcu4j7nFgqbV9sA6UpFJMkIv/ipXAYEWFYBF86HiRw+smJ2FCUegPrnu+9h+O6//x2nQdHDz2/jAVDNV/jmX0bz+Q7fOwGAfYCm+gaBDQAA6AkCQ3dgACSgYrFEFvmRBC79/un89z+++4dzrP5H3tG4z8Y5ivKV0anin+X5KQ8CuCxIoeegFHoLFmwAyhdKuReh5HMEZVjVq4vKbz/a3QYrDQBJ29C2+m+rHnGPQuPqR01RRJEmE5HRHGk2v/4aHry3n/948fQ42ofaTyZYoAcAAAC303wv24H0H/Ofn89///QjSf6jj6E6hvpIQjWif2UL46oAACAASURBVDnv/0X6z+b+rdL9kzCdJfonolJAJ6vUyuG7TLZS8iuvdv+v28b+M3EhXJ4my4aT/IQmPYXC8EFy+ZmVYs3kqHgcwL/e/YiC+y9/6zafSGfy0bFTrMw8gIafgz2wgsYJxJdzRqQHAAAb3BUBAF1ZSRtkmcAAuNDQFQhMzVI/huOd/PHp9PWX7/7BD7xz4B4j9xi5R3GeSYVW2p9qHSFTDaRRQag1ABKaPYBK7Ab8pZErJJwTC2wnoMYDyEkM2QiA3FU46qR1FEkUSaQp0mQ0mffvxdXmt/999fw0kQfQVSbg0c6rJYUTxiAAAMAo7Eb9/+1d+Pn9j7P2z/rs61OgT6E+CoVKdD5S63LIznf8t0cAFEYDUIWXnywUHstPqV1RFpyAZg8ge0N5Mxtb+s+eGntBKB4xmHU9uFQkXH5mzWwUO8wek/C/3usofPP1x8hT/tRfTzPJtZQmBC5s03ZfjefQfHqI7ADcBvrqbZbx3FcAwJZADBgCGABFUL2Yn6V+AI+v5fdPz99//hEcfN87Be4p9OIpf5+ZI2USBUFy/Qoz3T8vGbC1LPZKKsoHkn+aW6B2hWV/qeWyhi96QTpPoKRpAZhT0Z9z0r/9l54Acy4XkBARKQ6UY5JBAPGfCN9TFKnf/ufl+dyh2PtwW7eS5dwE0C0GAHA7hX4NQ91NFnJ/vMZfr80f7x+P7vnsxOr/MdRHwz6b6l7/+bl5Kuf+rZsDgKyYTiULn0rrKb++QBttuVrc54o1tt5su/smPXim+BOJbQNk0wJLtmtKLQEm4/IzK2bN8VCAty/53+9VGL5+eB7h+9EjFl4SAbXwAMYD0RyA/iynfg6GBzYAAKAS3PcHBQZABahezMlSi/7bvfnj09OP+4fg4PvuKXRPgXs0zrNxjszCoqUyn4DkpIRLx/9M9Lf/km52Nb0IhZjYKp9ySdVVnWz5x9qmLhdQrpt/wzgASqT/S4/D3J8So/WjliiiSIthIRJ+/9OPMFS//c9dFN32OVyjn0zQJxFQ5fZdz6V4+tAMAAA9GfAestQAXebxIL+9e3588Zyq/6dAH+PU/zWROpuzJxX9xVb/FV3G7SmSgmF/LXxfHisXyk/tZSltVtmvn/ODAMrBo7BGicQGf2EEAGXqPyeJ/hTF8fFyMvHVGlcdSSnSihzFhn96Rf9+r//f88twjPieP/1OsfGSCChbc/N3ud1RK0/v8tlgEAAAt4JG+saBDQAAINzoRwQGQDWoXszAgkv84aX8/uvj97cPgecH7inwjqF7jJyjcZ4VMdV0J7QGATT0HMz3ImzsQpiOx2/uP1hYUxYIsoWskiXWmkIf/zobwKTTBmYnaagC4+hnJUY5REaxYTL86efvUah++1+vZeF3pp9McJsHULel7b2031XlZwYAAHOy4BhdIFT0n3enb28ezvrs67T7vzqyxLE4G6jHdpgWUnwR/a8l/8kNzquM4JW6fxv1n6peqogDaZBq0PpzmnPN/iumAUg9AE4HAdjHvJyMotBVcS4gZs3i8M+vHz7ee7//PcWEwDcEx2wQAJWGx0wDPAAAALgObAAA9sl62hrrBQZALbO0DfbLggvad+i/H4/fXz+d3XPgHQP3FDrHSD8b56hEiSgme+Jfew6AgnaQ9S7M/116DjJd5g+s1w6Y8i+lVCvWmZ6dfqNZ0iMWBIJcx3/hNPlPts0lC5CxJAPKt8grBjc46sSOIlEkTEaR4V/ffw/Dn/77h9v902hHb5lgcDqdQ27jJZw9AGDPLDhAV/LbO//Py8S/p0AdQ30UMmy0FZfbTPxbaQNc/oSYmVoYAFYoL7r46VP7Vl/h4+df5lz4lmp9vxzliSjrRnAJ6GndQaVGflYPsRMBXU7FfqopYD6yUqK10U6knQ9vj98eXx/9Kb407cNjMREQXWyADifaJRYjdAMwEuiltxdgAwCwE3BPnxAYAFdAJWN0Fl++f3zw/3r34+ycfeccOKfAfYqco+gTG0v3L078q0gqMwhXKAip7h+/Sum7qLbnoJSLLHupsF5K20j69nKugEwRICIuJf23KXcYpPQCc+MAODkFcfiZNJPDZJK/f3x4iKL7L591c+EPRSeZoOsggJZf4ZvrsdARAACzsfgYXeC/b6Pff3o4O76vz2d98tUx0CehgMthulrubxgBYOn+1RY+5RcqH6lUpqkUnXH1jl+9Aecjex2FDSqS/2SPnBsKIMnMwJczEBJy+ERKG+Ua5UbaeXf39PH+8P99HmeQX49wmPMA6BLg29bzux+38mSrbRkAQBfQPN8RsAEA2CS4ic8EDIDroJIxIosv2d/fhX9+SDsSOqcwnkVQnZXoat3/MhPAJe2PJPMAX/0jIpY41z9RC+HgavFlG9gdC8uif+Et+aEA2TzAlVmAijZAthMunJ7LR9aKHWajyDDd8T/fqzB8++1rIcPAQPSTCfpMBtBMy/PK99hMh20AAMBkLD5AlwkUfX5zevZOZ30+61OgjoE6RuqoRJOo6ql6iIlUqulf1H/OLZR0/1LsblygmoXKp53IxQorYNXpzHUhSNV5AGmBVA4FSB41nx3lGe1G2gm18/HN87cn98cYswGXrqBTqK/wACg1cRo+hqoDDBCR4QAAAEBLYAMAsA1W2LjYGDAAWgEPYBQWX6ZfX5k/PzwevVNsAAT6FOqj4RMbnQoHumwAZPKBEKdZhqt7EeZ0/0RZoGQNW8vX5YPCyso+Z1SWAzh7KgU/wG6YZpZAqvsnx1HJ/IFi2wDxFWXjALIDCZNx+Zk1k6PiQQBvXvK/PqgwfPP4sESZgFufVNf7wy2tfigFAIApWXyAruTLq+jryydfBb7yfX0K1ClURzZF6b88AYA1E4A12W85fBfm/q31ACg/SUD80MsAaCtPN+0s7ywXY8olBdDlbCWn/lud5mMzIO4foDRFwmejXFe5kXbfuMeP93c/nu/aXFd/bguPl8Bt2QAVhdcv8lae26WmhZkAALgVtM33CGwAAFYK7teLAQZAW1DPGJI1FOWzJ7+/Pz68fD5r33dOgT6GySyCmY6QU/+zjv9pf/820n9ZO0iXk6wClL5Ufko1TwtrKrr+WR3gLo3QKvU/k/5tVJodKOstmB8NIEqorJ4LESkyLh9ZKU5yAamfXj1GH/T/DV8ej1N8J/rKBNOeCVd+eAAAMDZriNFlAkVfXp/OHAQcBMoP+RyoI4lkwfoyPq9K+s8/rXlVWhoAWRr99LEiplPN0yvk91KKt5Ku7bTThFTrl8IggHQogCiizDoQKx8gO3Qy7BrlRcqNtPPLi6dvb72/fixxhB+lvS1ypZSWW/LqVOEWkR0AALoBGwCAVbDO1sTmgQHQAXgAw7CGQjRM/3l3/nr/cHb8s3MK9CnQp0gdSbiQ9qeUT6Cu52Auk4CVRoBq+w9WPFK++NoUZaGWxKVB8JLfusEGsJP/UNVMAFYuoFxOgguKQlc9s2LWil1Fht+/+RGF6v/88SIIWlxNV/rJBOMlAiI0+wEAy2QNMbqSzy+iry+fAhUEKgjYD9k3FLDomuz/5ZBdduvr/Hu6agBYOjOV4nh+mfNPK+NNQbcekfgYDYmAOB32Zz8KU+TSWZQXaTfSzkvH+fT69P3xZWiuHO/20+ydCIgqC1tyFabqPbQ+Vh2XE0ZVAIBbQcN818AGAGCZ4L68bGAAdANVjV6sp+x+exd8fv/j7PhnffJ1kvpfKFJp8p+GVMKluX9V2m3QXknJQi6fQGGBrCJrlA+uYzcxC83NSieAk6S4UtU2ZVv6p6T9LwUbINs4e7uQxMpBwOqZNZNJpgX+5f57GKr/+9+DGUMp6CcTtE8EdANXTya3AWYCAABMwFX5c5Fk3f99jtX/c0RnMpUqf17W57ziLzW6f24zKr4qdHmpIoNf2cLPK/7Va+znbK2vrYqy/Y54BdeF/vy7Lq+kiYCyq0guTREZKzmSZOp/POSRSRw6G3Jd5RrlRNr9ePf49d77/etoDY0ewZ2sQmyo2A8ScLsFegBAF9Aw3zuwAQBYArgRrwcYAJ3JOkWDbqynyD6/if74+eHknM/67Oss9b+vKqcQzDICcab1V6YOyAkKkuUHyKUJrlMN6hQEql9DVYmBu66pfCok5fw/9rLiS29BshLcXnblsM9KsVKsmR3miP/57lsQ/Py/X9yqCxmY2xrbI7Uxup0MPAAAwGSsqrrz512x+38Ud//Pif6K4xjNWcZ/xUV7vviXzAogjQbAZQ1ZaYIKj5QvzTahvOVnUBegROQGF1sJGZZC93+yKjNZiM9lASJil85CnlFuPA7g04vjt6fXR3+i71D7eFpw92fWEBHZAQCgD9kdHHdSACZjJQ0EUAAGwI2gx0EHVlVSD3fyn3fPT3fHs+P7+uTrY6iPkTpaE//m+/snowEy3b8hC1D5j64tkFV8dQZAXfmWO0WwpSYIEUku+3/x1dIb7ZWKSIQMp8uWDZDPFXAZDZBzAhw+smLSzEaRwxKpD2+fvz2+fTyN8HUpXdDNMsH0oG8gAGBO1mADBIq+vDqdVdb934/IJ1Fpyj47Fmdj8qpjtJWgr1Lob2EANEfwS2o5LrySrrfXVFoCnPxfrIpeonk2o+2tKndy8mmynMrIXswCRCSafIfOLrsRu5F2fj48fXp1+D++1/q4HekRIO1EQDRHrR7BHYBBQJMcXCi3fQEAw4Ib7sqBAXA7qHC0YlVl5Gv67d3p2+vHVP0/BYn6n4r+fOn4X5tBOBsKEK+xug2KLS5wS+k/LyJIvkALfdioVOCFHD7coPJTfn2yUPwAc28qTANgEsHh0nPQ3rGkb493Ia46xuMARGujnbd3zx/v7x5Ph6qz6k0/mYBTvaXu69znbtDt1NBVEAAwPcu2AZLu/xwEHIRJ/h9fGW0F6Mos/yov9+e2SeJ1LvNPlQGQ9fevzfzTEMQzKT9P7S3eDjVtctgQkRSrDVS7+SVdULLFpfs/ly88p/5nT8WhkyHXsBuxG2rn08vnr0f3+xjWfnamQyQCon5xvJnrZ4XIDgAAQwEbAIBhWWr9H9wADIBewAO4wtpK57d3/p/338/6HKf+99UxVEciueT5EVtHKIgFinOK/0Vo4JzoQJZqUGhOU40TULlARNxOOLDanrdUhiqdALZ6/dNlqkCpnDYwPnQhF5CQGEcdRTlGuZF2IuV8ePP07cn9+ph3Doain0wQM0sioB7mBQAADIedT34xBIo+v8h3/2ffNulT671hNF79S1ky/Ur1315Tm/nnWhxvyzWhv/hqbk3xyRXsU81VS0qWSSELkFIUuXSODYBIOW/c468v776f7lpeZH/aR0yxRkEma6b9aiO4AzAIaI+DCuw7OwCgK7irbhQYAH1BnaOaFRbKtxfy+c3TWftn55yp/8Lhpfu/9cc5ZaGsHeRGBsiVrAJUs0BWOfYUDhrgfP4feyWV1mdr4pXF5D+cqv+cSxegJGdWSPbIFDnqbJTrajfS7iv39PH+/P3phZmkrtZVJkiWZ/92o6sgAGBeljQgwO7+H0//a8jniu7/XBWOM5++zhvg0nVWqf+tInhD+L5alJXZfurWV6zkio3bHIstO7/S/FCSzgOc2QDxbMCGPcNupJxfXjx9vvP+Po1j7VMvHb2c5W+2EI/IDkAP5q+cg8UCJwCAluA2ugNgAAwA6hxF1lkcX175T+4pUGGgzoE6BeoYqRMbzRf5oND33xL62Z7+V6WCgmUM5NII0BX54NLhPstQW2kAlJ9WUv8NtQYGMBMRZ8kCrnyri4mADBGVEiDYAwK4MBkAkxAJk9J0cpSbzBmonI8vHr/fe//9pltcV3cGlQmGpfnU6nwYAACYjQUMCAgUfb47+Zfu/+eQzmIUZ9PzFIJ1ow2QC8TVyX+YxH4v1WTzq3y0FwrLdWuoymwpq/91Edtan3xYaSir+8iK4/0KBUL5yZArRgAQCVPk0NmwF7Hrsnun/HsvGtEAoGJE7BQfC5MB0LS1+typwgMAAIDxyAImACBjncIduBkYAMMADyBhtaXw9SB/vXoOVBCwH7AfqHOoTmyspP8Vf5asIJx7Wh4TIPmnRI0egPV4yT9QWKh8WkHbz6RVfYipIp0wJSq/KLEGARQTAaWjADhRChLJgIkcOhvlucqPtHvn+L+8Pn57en0OWp53R/rJBA29KHETAADslPmcgD/vor9fPPpZ93/yDQcsue7/Db37a5wAqorazZtR6bEQtVt6AGUK21ydjKZ2EEBuuTryJWYD21smj3aOI7sEinMAEDGJcuhs6OBQ4FDkcHR/8DW70YSyS6fgTo3lBQBYBfjZglbYwRCAfYJ75Y6BATAYqHasmrj7v89h7AFEdBYRJm3lEc4y/2SqQdUMwNczCXQ0ABKqtAOmVl+6Qupf4fy+suQA9n4r60TNDepMC7B1f0pLxsSiQL63YDZn4FnINdo12omU8/Pd08f7w/9+ca9f2hB0lQkWARPJOs8cALBhpnUC0u7/YdL9n84Rn8kUTHqusOSrA3HLlzj1gltG8MY4XvG0JEXnlvI6/mWhbg6A4igBvuyqTKUGXr7Ay4Vb8yskNkA6vI8UBZpCh0OHo7fO6f7wYspBAJ0oTwZA89XqEdkBAGAK4ASAXQGlEhARDIBhgQewqLzA7fnqxd3/w0D58RSCIftsLvp+PgtQusxVskK5z2B1GgHKv4XSLalGMrDLNF2W0poi6efRtSMcZUJB0zs5dxrlREDJsADKzweQ/FBYpW6Eij0AQ16SCEg7n148f7t7+3ga55vUWyagcX7sVw2W8qtQCgAAS2QSJ+Bvz/x9eLp0/2c/okCl2f+twJ035u2gnAvZRNmYgJxRXtqeqfCua+r/VQ8gv7Io+hfeYte02s4BUNz4Sm3NHiVQrqJwovCTydR/SaX/rGQ0hZrjv2iKLEBUjIid4uOMHgAXanMI6gDcClri4BbgBICtghsiKAEDYGBQ8yBanw3w+WXc/T8IOAj5HHHc/b+c6iffo7CYCbfwmOoI1WkEqOLpJfduD+HgQvHLmG/bNqgGWcu/69c5lsYVkZHLPMCUCjHZLIKx9J9LHKwpdOls2I2UGyn33nv+9Pru8XTocvSOZzpCIiD8/AEAIMeYTsCDEwYUhRSFFIR0juicDtorRuc4HNcHZSouiP1S+aLsl8rqf/sIfrVQyjPU2u+qHARQmQjIXkj2IERcmwiI0/OTdGPmYgogsZYrxj5qCTQFDoUOhQ5F994kWYB6BPdKZgnrcAEAAGAG4ASADQAxAjQCA2B4IAImrMQGuHT/Zz9QcRdCP84hYI1tL0//W5D+L3+Nsws2GgANj0z5omxTrPXb1Fdr+n1cBYGDS7mACuVmewDi0MmQazgZBPDLi6evr9y/n0brMDi0TDABuZNMn6zizAEAe2doJ8AQPTpBSFEoUURhRFFEASfd/yuDdUUUzoJ1PDCtJOu3DOKUX0NVj/krL84p31wopbKT8iCA9h4A1dfP6gwGqrrqysiee1VLqDmIswDdq/GzAFXRPkRWDgKgSWr15ZNEZAfgNtAMBwMAJwCsCNzyQBdgAIwCKh8XFm8D/PnCf/JOPgc+ByGfw7j7v5QV/6qFLJOA2OupJu1Pqdks+TH1yULhMd6S8muoe5lWdv+v+6oKp+tbJ70Ra3rgwkwAl2tMFRZFJEkWIErmBmCKnHgQALuRcl65+teX56/PL2SquldXmYBGGASANj8AYOMM5AQ8KHnUfkQmojCSyEhIphBnm6b/rVKx7didnmJhuEAxNZB9JQ1xvBS7K2J6A/ZmBemf8ip/gcwDoPwCERHbwZVz/13eK/Z+OI1+WYHYn+Xlj9NHTYGW0EmyAAVTZAGiXnF0Rg/gAuoBAPQDzXAwGHACwDLBPQ7cCgyAsVi87j0tk6QDvoGvnvz18jngMGA/YD8kPyKfizkE6jsSymUDrlh/VW7I0v40qwZ1un/70mxW/yt7C1obS3knOTh/MmlXSqq+5Kzvfz4LEJFKZgMmL/YAPt49fn3l/f6oW19mR3rLBNUvjfk1rzxlyAUAgPXRr2Lw4Jhn7YcSRRJFFBoJqyJvy+l/06dSXp8/xdwGyXJ+9EBDBC8vtL/+QmGVRwBQTShvygJUsfuKDSRXROlCG3NFS+BIoJMsQOFPepIsQFQMip1C5FweQPkkEdkBAGApFBrHAEzPwmQ0sFJgAIwL+iAUWZgTUNn9n0TlM/9cRIRSep8GcYFq3lKnjJP9mMrL/YWD+hH9iRTQ3HOwUiQot4vrTiwrJcO5S7bz/2SWgDAZh86GvSgeB6D9Ty+On59eh+PVtEaQCWiOHz6UAgDAWrmpYvDgxGl/ooiiSMJIQjLN0TZ7lbhiA3uBqHE/+W0KCw2PlPeNO0UJO4hnTyU1JApDAa56AFRV6HVr7Pfax2rzR1pCR5IsQG9nygJEN4XImccBEBEiOwAALJBy2ARgDJYhl4GNAQNgdOABVLMAJ+CrJ1/ungMOAvZD9kP2I/bZpLo/FxL7VP9VdILL5RDIv2TPoXc9+U9eOKj1AOpWlnX5fONfyi9dHQQQv1LupmYdMNk6PXnOl0NaAkKKLx7AZUHT2SHXIzdiN2T3jXe6P7z8a0K9YBCZoG7lIEARAABsk9YVA5/pQfuhmFj9NxKKRCyaLqZ7tqM64b4QZ5mIiimAygMCmHLb5861RRCvzvzTJlbcNgKgnPynVDEQa8+X1Zm1kB0926dtAxSoqCMlWYAk1By9UP5PTvQ3TRLQe0TKRY3wAwDcAFrfYAoKzWUAeoLbFhgfGABTgFpIE/M5AWn3/9DnIOBzFGf/z+b7lXLS/6a/SxKAXBaCMpkUni2TJVi0l/6vlldhA7tJW9kfsEzVZq3qN7bekTcAchMDFEYAxMWiHPIj9h0OHQ7vVHB/CP86eW2OeiPjyAQ3c/PpQDsAAGyEaxWDH8o8OeeIolDCiMKIsvw/ZL2zGIWrBuTZToAt0FcGcc4fJf/ey+leHrPBfGkQ7RTE84fOLuLytFLfLzxSfRag0jlIYf9Us+fcX3MuICWBlsBJsgBFP6mpsgCRVTAVz66wnBF+iOwA3AZa32BSYAaAG8BNCkwODICJqEySAnJM6wR89eTLwer+T35EPpnq7P951YAqVIPqpw1/VLmQughEucfKBWu5rsTE/s9uz1eJAmw176VBOGgeF1+ZMaChxIpDAZiEKdAUagocihyK3nq+w96IWYBofTIBFAEAwC6oqRg8qOjMYZjk/4mMRKUZgJvjr73TSk3fflr5xrroXPdIjfl/rsaKxhEAwtaaTiMAypGk8JIdze19UsvSZiItoZbAlTQLkPvib3+qUX09gntMXXCnceI7gjsAAwIPAMyD/bXDPR3Y4JYE5gYGwKSgItKKSZyAvw/Bk3vyOQjYD9Ls/yxp939W9gS/yR+XGreXzoDZBlRq/VaqD7H2TfmZAy9iQT6DQaEsSuUiVSury7FO2bczAlV3HuT6Nm+6Si6nfTmlzFqwLvAySOIyGUBWUEykKdAc6jhrsHOeIgtQH5lAktKp/Aym/MlDOwAAbJN8QPuhw1j6j7P/Gwm5YgZgrrkDF2KxtZKtZTtLnrXeGvfFVry+pv6nITQ/bKx9fLB1/8LTSrm/IoiXbABrV2JtxxWDDPjSLSCrEaU7yUWdik9BU+BI6EjoSHSn/Hd6qixAVbSPktkIv7o43j6+94zLiOwAALBWYAbsHGh/YGHAAJgaeAAdGM0JMEQPThBKFFIUcBB3/2extH5hIlXU7ovZbEqafkVygPJmlHtLlfpfJUyUC+JqoRQ2iM++Uv2/bHPNLcgt13T8L7yYlFs6iiKXQKAuIYOWUFOYZAFif/QsQFV0kAlqSpPqVw5AzclBKQAAbBmhJyWP2g/FhBRGFBkKrfR9lTZA5eTAVLFQF8SlcBePgxpxxU6yeFe1f2uhFBrqAkVJrM/VjVKBvtg9v7DnCk2/av/xikq3gPPHLWQ7bCrwOAtQnAjIpegn5StyTc3VDk+PoHg1y1+boQBdD155vojsANwG2t1gQRSb5vOcBRgX3HHAsoEBMAPjjR3eLEM7AQ9aHrUfkokojCSMYvlA4vw/tu7fnP2fqLoXP9efaO69LdT/kmpQ0Rht2fa0WvuX5r29UH4sb1PYZ+U5xJ3/7XcVCie+DKb63MGKAkeCLAvQvTN+FiAaRiaYzAOAHAAA2CcP2jzpczYCwEhIhmpidEb5aabUFwJxsly6aZfNgzp9vxS+uXACdcuVlCNvjUAvdaG8Mv9P2VeoO0plPSHbpqJeVPhjIi2BlkhLpMR4KvSYTqbd1Q9CPl52ip4NWf4u21h7rnupAwjtAACwB+AHbAAoemBtwACYDXRJuIWBnIAHxxy1H1EUSpw+OMynD67R/ZM2PBPl+75d1pPlB+Q3u6QPsre5vLGtByBdr7/csG9u1RceC1vmT6PiLPI+gRQuhK091/5xqhc4lGYB0pNkAaIBZILaVzt+ZleO2/hyp9MGAIB18UNFIUfxBACGwjSCx5Q1+kphOtugvFClzudu35Xyfd0NPo6YlZt1rccUhHu2BvaJtaYcyovblE5ArJgR77I0yEAKu+X8ri5vL9aCkr2IIqPEKBGPI0/JydhVmqkrxDdEyTZBfIDIK0R1JYvIDsBNoMUN1gH8gFWAuwlYOTAA5gRDAW6nX6PxwQnDWP3P5IOiDF32ACg/4J0uj8XUQJR/ytY2VNpDblf5GYCJik/LF9zm+q91G2zyAApbEtmphC5VkypBwfIPxErxX2rGVtsAWkItVhYgb4YsQNS1yS1E9V0FB2uBpCfUcG5QCgAAm8QwPaggTLP/RxIaCll0Fk/TCFUZc8laWV4urLTCtHDNq7m3p9o3Ue6x+aANazLKvfUbZP3saTnKUyGa1x/wam0hGzzRkGEpOxlmMiyGRRSJK9GhnABpbCegR1C8OhkAAAAAMDDleIOm3fQg6oPNEXIW6gAAIABJREFUAQNgftCi6EX3RqPPFKcPjihREEQiFm21ZjMFgUpifSYx5HR8K+FvZQOY8nsonHG1GZBeH6ctz24zB1ZtnG/55xIBFTL8FPSCwgKV6iD2+VSOM8h22PSXyxpMgZbAkSQL0E9qkixA2ZnehKTfnhE9AFT+AAA75oeSR3WOKB4BEJYG8JX/MgorCxp9wTAokh+6V36LvVC5Z7aeNTsBZcp6fV6Ul2zNJYBxbks7sDFXbGM/LXcaKNcWGiJlRfkzxeq/YTGOGE81eA/5gw9I/pQ7hfqJPIBrp9ejegLArkFbG2wBWAJjg9sE2AEwABYBhgIMQOtG4w9lHp1zov5TaCQUk3W9z3ZRFusrZQVONIHKjavy/1gb1C0QFUwIKZwb1T+tpOUIAKpp5xfWW7sqfnHzz6Xw9uSInJVG/RSCdMkaHA8CmDALEPWTCahibsfiBjefWJd6HpQCAMD2iEgCMkbEiDESGY7KBoAV0MvRPFtPlNussFC6V7O9lKn4FW+sOpBFU0BvphyFW47nK+ykajyBUMXK6gPZT7PSrizk5O1MTGQUGxZRIoqMx4ZIX7ncMZyAHsE9+0KMoiS2Pg9EdgBuAx4A2CCV32kEiZbgjgB2CQyABQEbYBiuNRofVHTmMKQopMhIZCRqVqIb/QBKhWx7vc1FKC/1H6xbaH7aeG3VZBtX9s1vSBRQqS9cdsXpYn7MROWBrO7/UqE0xC8VMgloCZ34j8M79n/S4V80QxYguqnJ3dDSuLERUnUG0AIAALsiFIrIiBEhERIiqbmfXqJtFmUKQ/esLQsLRFYkSiNXef+VC+XHq8cq7zOjuRd4OUZXUrlZQeWvO5nK/XP9EbnySZoCyCgSFjkoU3/cEuONCegSQ8vDJAej5gwQ3wEAAHSmIT7tOahAYgOAiGAALBDYAINR02h80GFEUZRmEDZUl0Cgso1XkA+4tJIKO6n3DwoLVLWQdWRsUA2avyxVff2S5diXYEujL+xWqt9Sf1CxZwiw3mVNS9jhT1OgJU4EFDkc/aSnygJEFeXRTSaQRC8axgO49ZIhHwAANkZIIiJCJCIkIiYzAIoRvB2VwbdOnefSmsJC+0MUXq/bQ7o+Z5wz52JxIcrXDQKodA7KJ5kcr3H/TZTmA4gvT5hMnAJIiXjUxQCoPKmb6REXxUrFOJgH0P1kENkBuA0MAgB7Z/PeAH7hAFwDBsBCGbPD0/6wSvOJ5dHxQzIRhfEMwGKESVlZevKpaTjfjs119o//z9ruTFROJVSobhZsAHvB3thyBUrpAkrd7ZvJNrshb4C9AVUtF1dz9ZacpgOqUmoKwy/YXklagmQQgERv1ekn5+WXYJIsQNRPJujnAUjF0o1AKQAAbInEAIgtABJRWQb8MpXWcvYSlZ5W7ae4osIDsG7mXHqs3dFlfat79FUhvvZmz8VXy7uiZChf5d44G/JXHr2XK9v6Skn8qmESRaJIPIkUk7k5OPWsIucvpVuUtD7sAcTEaweuOzdEdgAAAEPSKZ7NEoEgigEwBDAAlg6cgCERenDMkz5HEoUSGQkjCROtuZgFKHsLl8qeU3WX06Z7QVawd/L/s/fm3W3jSph3FcBFsh07vSTpvveeeWfm+3+qeefe7ttJOouT2JYI1PzBDSRBipK4Ss/v6NgUBW7gUsRThYJvnfXChW7e9pdaFu+Ykx9wvUwzOU8xcTBssCb9Vzcq7r/atqQ8nKw+PS8OWf6f6onQkmjZB7QPKdnw/rVOPuwnzAJ0jkxAue/jJB9A9nMPIAQAAK4Hk8n/uRfAirBXei7nNdzwbSV9q/E8XtucDV667fVRb3ZtPgDv/Jodr/xaDSNoMyD5slLZULU/X3PlrW8+irJBgFlsJCZiej7fdA30itzfjBbe/fLryVuF5QZgctAJAIABwF0EwGqBA2A1wBMwCF+V2ZNJyBgy6QjAeU/0srFajWur/dpUCpiyF8qmq6BGc4Xuqhp/2b+t6gq7KQrULh+falDJBeSq9lJdihpfm1t0xYLahuqJ/rs/ivZaEi1Gi9ViY7YVF8PkHCcTEB3cW09TZGhRAB4CAMDFkKS5f0jEkpAVlWWeO0bWca1tbWYxXft419Cc7t7iCQs219PxOG/71esqOMoyHFu49jV9s7JpFiAlEomJlTzb4Qz5sa/IZ9jFAXwAx2z64EkFABwFfAAAAACuFjgA1gc8ASdjmR7VvhgAwEpiJVGifS3/fIKd9r9UCxTOAqLKGooYdnfZyvrr03le2aoqIbXy5wgHTQU/kwPyCPVatH5bqgGvJ6BYulksn6iNNJBVXS3eP5vpegiYRIlVkoYNSqRMZYsn1MSxnCkTpBPSlRzBdRWgPQ8AAB3kYwDkf7OYdHL1+rYnrk/36S8Yt8yp/8KNv0dRW8SbdcdjyntZj2rkf/t23A16OhCcZBc5GwMgTwEUse9snE//d4PqQRx1SGf5AGDmAQAAAADAHMABsGJmjYReJY9KvqmXJI39Jyf/j/9DRG5TraHmN8cDqBcg//zK17YTWJMPOtT/7kugQ8rvEP2b873Sf3URaXUzVHsVsJNSwHMg1eNhTocNTFUDNpGinTt84ASegDNlgnRCunbwTEHg4C6d4cUAAIAFYcQW0j8J5aPjDmgAuo1yAzlUwL+Gdo+CZ6bXOndvqMWXL26BPrSWdLwJjbcj39tU1gNALJPVYmMZ2SjVDr0HRxvKY10Ypx5x247BsgNwGugEAAAA4DqBA2D1wA3Qn6/aPKmdEWvEGDGWDdk26b/FH5D9REXQtjPqb5/Gv7f93wwYrKr/nlxARGW/gYMbpQMpgCqKQPOvU9Kzwu4t1n0AUiYF6lPt6SyrSFisEonIRCw7b1VP2DXmtFb3vO0NKAUAgAsgTwGUZgFKvcH1JyvXv9XteMN69nw2t3nfuxfvs/KDazhXrz9UprYD3gJNP0RlZt57r7KWwuLngwBbJaLIRmKJ9KFdHYi2F+Uz7GLZdY+dr907cAbwAQAwLPABAAAAuELgALgQkBeoD4mIFbFiDZk0/w8JFdKAk8S/LRCP66P1sitbExFVuhS0pgBqUw28W+dGLqC2Peyg1kis6/Jud4bOZb0SABEVrgTJZZWspBBz+r/cVkdLtjmHmVP13zLZSEykhEzn4Y9xM5wvEwAAADgPcQcBTr0AXXnkvfa346t3zrEF+hQ8uBv9OWSc/P3tDq6kdSmpZw70FvBuw2Y+ALFMEov1lRoT77tB9TiOMvVu+j5in62HNg8AAAAAABYDHACXBjoEdJBFDoqIFVFC1ttQbQ1FrxYgIsr9B76fqCMFkFuyzeXQIRC0+SdqeJX6tvYoVzX6jqRAnhWyO1Ooehm25SBq242yzrOqkUI1kFBMzL2b1MdnADiwa+cnAhrt3jw57BMAANZCQpSQFRFKs/+IkHJ/d02251mbR6NP8o408NO2zYvfMed0pGbWO9fss2uVN6giSl651pwmdwAU1F6UzzDutXWO6u/vCJ2AZQcAAAAAAAeBA+AygRvASzZ4oE0lahElXMnj36b4k29+U0Q4NnKwX/i/dyXcUaD2k9MwzLT69F+tE0DbJr3Fun0JlcLcmJN/bX6aK2HKRw5UYpUIk0TqpKbuIN0CBpEJAAAAnIQhMlSOASAkhYxcTT4z7+vP0l++jtL3HU4zeuVrlmT+ABbhhFXXQhPQ/lbQ/zibiv8sPgAAAAAAAAAOAgfAJYO8QDWSInWAiAiRzXPU5N24/RGDHnm8kBpqgfzFdLeo3ZzpndNceT7hSQrURkcYfm2ioxNATf2vt0CznhSShvq1baXwQDA1o/jdjgSukCPEZJmFRZisEon5vLDBQe+KE2SCeTMCQT4AAKyXhMRkYwDkf7juFQcHObWy6n0evWvjrGTxKX5Qkqr/xCJk+oxhNA3nGcWJbbrXiMOyA3ACSNEJAADg2oAD4CrAK06KIZsHDQpRmjrgUMW0NaoqDWEupWtPW/hgkH43bWWOOqXnNA871P/CDVJI/N2F80hAcRaurIuJuDKOAhGTZbHZyIGSjhw4BKd5As6oyLF9AD13DUoBAGClJE4PgCwFENHBB+qhrgFeR/5Vc1pFdPsGRJiYi04AScfAQwvgKENZs+mzvHLDsgMAAAAAgG7gALgWkBRI6j0ArNiOCLSOHDU+4foABxfhlr9tS51wJr3SvEejb29IegsfLNZRshWn/cxMrNLBA8UqkljMwC3dYz0B1a2fJhPAJwcAAMdiSMoUQOlIAEfqyL5n7+AP5nU94A9asIHMLXPZA4A4WU4PgBbOedFAIiAAAAAAALA04AC4LtbVKh2WSu5gK5Jmoy0pVX7v0MBpGVeYTuPeS9mgKkMI16q6zQfQX3VuW1XbzKO6iR9U7Xuo/6WXqelg4Fypyb8y58mXiqj/1sPJhgEgYZFITMS0G6P5e2p2oNNa4zPejJAPAABrJBIORSthJYqJmRSJ+J5nUvuy/jcf6fzqnTMScmhz3p8kPWlCLERCbC7gnDgsYTAAWHYAjuKinkEAAABAD+AAuDqutitAQlTPHZzBPeR1H/50uO6vNU4L2z+5WHdjsK0TQG2lxzczDzdAU2eAd77b2aI8L0yWSdJxgCMxMctu1Ev4oCdgiERANIJA0H+/oBQAAFZHRByJVlaxsCLFVAwkK9UPdebv63gGH3wqD/LYHjBnzCDx+6e5E/xCf+vMNLlf1g+AkjW8h56TCMg7Z0BgxAE4hxU8gAAAAIChgQPgSrmIgLjjMCSJlKkD8h4Ax1ZDhyR8zuL+Ar7T1J0aqPnTudl4TuWwg6FGyzWZdhSwaRYgJTYUE012/XZ4AoZIBERXeScCAMBpxEyRaM6CyVMHgLQ/RWteAZGyb95pEnzbUt2L91n5wTX0RFqmK2Va8iDVPCh99qFewy0FiIiEWSTL/2NIJQcOZCmc+c40sQ8AXgEA+oAXbwAAANcJHADXy7Upj/XBA217pp/DDFJzXPxJv1byCw22J8e2Bzm/NJylitQ9lfQ+Dr224Fuzjzw1UHmJMgnnKYBCMbFM3sL1dpw5wwdQW/csdyKUAgDA6ohEK2JllSLFkvYAkE43wLG0ren8zgEHOx9Qy8zmero5sBSf6FHoj8cfIGnsv2IhNqxWlAKov630XgETm3hYdgC6Wc2jBwAAABgaOACumqvyASRERmya/0daY9wmp690Pga1dmKPZuPZdVa95Go5f9IChVum/CkdATjrByASTe8AyHeu3DUfJ8sEA96JaPwDAC6YWJQSxcJpDwBvFqBjhgb2RvH3yebS7Sdw/zZ+P7A/3fuaF2C38GRPfansQ+23LkMmRCySdgKgFfUAOBbI/QAslutp8wIAAABe4AC4dq7HB2BI0h4AqTog4g4CzFW52aVP9XB1ui5qtxf2z+l9UhZ16irtUCFi4iGiMoWyHgicpgMSofkjB4tIzTMa3+P5APoD7QAAsC5iygYAUJYVMZPyDinT+WxrV+dbF+9Q/4/K8HPsgu175Tm+1pw/vgJ9wiBOcTC0OT2EcyPObFitYgyAgpOz/LXNGRAkAgKgD2t64gAAAADjAAcAuBYfQEKSyf95kCBnLSSu/CspRPw2NZ89Uy0FjmLo0zFNY7CyCW5M+L6K8/EUcEIdVZo4WIhFOFnI9eqr1HNkAvgAAACgm4hZEau0B4AoJiWUtMjZ0vJpk2d7PoO9In7/xEEn+wCk5at3vvh+6lhJx+ba/AflVMOyF1NS2HFRRQ8ATkiZ9g0vkyXbSvgAAOhgIY0GAAAAYHbgAABE1+EDSNLMP5KlAMp7AHBVcfZNOnM65ezFMlcz8LTtpnpBqeYwkS3Uf2JLS48cPKfhPcidiJY/AOBSiZjDbBxgVlkWoAKPU7mFWieA/j6Ag/0Gqvl//Dp5bZr69SFo/8rNArV98mv3+ddajblfuyfaFqz8lB2tTY04iWIjnKzQSp2c5c87Z1hg9wHwsugGAwAAADAtcACAjIv3ASQkGSRU9AQ4gnNiA7vXc3LdX9RJ6zoSZuJMO1hd6oBuppcJmkA4AACshVhxKFqRYlJsFWcPsF5J/31lvM9gb9nmfMkjA/p4Bajz9aB7ox0zpTqjGf5/IH6/OrMp6HsnKouzZ0HPykWxpD4ApoSU7Sx9Acz+fgbLDsDltBYAAACAIYADAJTM3lwZlViUslnuYM4SB5vME5AjXATSSePDjeaU5CPWilR6EhSFhqrOkc5M/1QAY2zOh7fByiJODwBDyiz+Ml1yvmAAAFgvEXMkOg//Z2WVYZ/VZinsu6eLALMzQo04z+xaYL5Lm17fXE/tb7MMNabbNtpEnCW94r5/kUaxw5J99+LsXQOn0RWNc0EklrMsQJZN/0GaF8bsPfw6gOIPQMFaHzEAAADAmMABACpcsPIYCQekVJ41OHMAZIcsRCIkLJ2tJ3Fb3efXUz0AkBu6QI9tHNyTY9uDnvi+HpGDx66//49ppbOo1DXDCfMqegCc2RSf+E6EcAAAWAURU0SahZk4NejkNdyVoHjHkZ/N6yPQt+EV8asLirdwx+J9aLPLjfB8poYp97sKxN9zQjoXbO6G85FauqG0iFjWlrQVbUVZ4R0rz2ZXwjmJgMamtm+w7OAKWUELAQAAAJgJOABAnUv1AcTCoWhlWVExcuAJQWh11f4Y1aBPsRMKdCzSbPpN1hjsGZnYayVZ6gDOegAkZ+7aVJwpE5xzJ57Q7IdSAABYBRFrlav/LKoa/k/kRMf7jGXVE+D3ATQXJd9Pbf4Dr3ehTfcvnrvdSYTa5jTUf+dPSy+BWuR+Kd+7Y/Ye2oo01tNWQIjIqsBwYEgnpBPSX9W6Wx8nG/fZXQIAXDYX2YAFAAAAhmLdr+BgJC7SBxApjpKASXHqAyBXNXDgfHjg4TmhXs/xK3Q3+noK9N5fR+oE4CZtcM4Lk1gmlacAEmXO2/wyGdwHAAAAF0mslBKlJHfniyIq/cXZh4lIRNxR5V3pvzbtzc9TgYs/+a9FDsBica6r/x3yr9eX34dugd792h28f3BznT6GSrGa1W7UOYuVwEqQSGBE/9Dxo1pxD4AzGdusQ/EHVwtemAEAAIBu4AAAfi5PeYyYolQysFkKIOdHp6Xq9QqUZbpb790RfEN1HfB2XTjU4uPuMp7jre6ZV184Ym3FP24pIGn2YO/K0hGAFRNRQmzW07o9qik+uw8AwgEAYPlEnA7kwyyshNkqIluPZJemHfe6Aag6AEAx0UH9TUCyjgUdKYCaW6R+26pt1/vVJ9BzTx9ARy21r9wj+jdnVl6rrAoMZT0AvqnoUa3+BXPJiYBqwLKDi2f1DxQAAABgEuAAAK3M3mgZFkUUiVbEilhZziP1asK0S60R2yb9+3+qrq1jEXdO82/bSo4/OZUsyV4toPja1lTs6QPIXCicb7XFi+BVELyrk2zkQMl6AHQUXiDnt73hAwAAgIJYcSBKi9YSaAm1hIk8NyL9S6k6z2zjGtCaG8CbBYgahtudWZPy+6QAqpTn+mqp5Unv8Zf7prPjzbwRQk4NNEv6Rf9G/p829b+5P11uAMuBkcCINqINqa8q9B3m+lhsIiDYcXA9XFJDFQAAABgbOABAFxfmA4hFsXCaMUBJ0Qmgrhd0HHKLrN/tHmgs51lDB21ljjo59cbgMQt7tYbiq1dzaUs93Nkw5XrZbIVMYrP8P7KSEYBPZpCTDQAAF8xrre/15jl5CSTQFGgJE3kq0sdJMwVNw52fqeRZ0h5qSP/eREC1mW1ugFTc9/oAqLpFajzXD8q2bW74Dmm+VkzyvWnzvtfmF4u4r0bd1VtfodWBkcBQkJDeUfCo9aHDvEDmNeJwCYCLBC/GAAAAwFHAAQAOcEnKY8RKWaUkHzzQObJqLF5HQ6nSqi+WbzgGGiuu/NQWdejfStlaPyWMzNe859pPzeC+pmrQscKu7XJtJdK+eHaApXyTHZ4wlT0AyKzwejw/EVDH/MGBUgAAWDIbRa9V/DcHIQWBhFoCbUMh0+mQTqm4AfI55IvQbyzqmeldifNX/D6AQxvy4pf+nZcDyV9KvOZbGnPcVXnle+8ibSp/c1tlSSuBJZ2INqS/B/HXKx4AYDKadhyWHVwS62sJAAAAAAsADgBwmIvxAcSsFKXDBjJbxaRyyaBJW2ibPy1PHhzXXU/dFVkT/dsWaRZIaa657bgKqd1frGUvj/IBeFUD99em3NCqKQhz3gOARHGythRAKYO0vS/mTgQAgHN4rcOQdSA6kEBLqCQwkrQb7qYFb7oB+vYAKPq3+XoP1H0AXDHrTenfu6FuvLY4nxBX/e8wxB06fptp9s4XqnS5qMzPfmU2oo0EhnQi+lsQPV+rGatdf9daDQCcBW4cAAAA4GTgAAC9uIy2SqQ4EMXCSrJ+AIYNExWpA1KRuVzA0/anrDLK5PZ92nS1pr5b0qv7N1d1cCv9RXn/r1JG63sVhOa0uF/YM78xwXkOgcq+iDP4rzS3IsKiWCwLs8iKUwD19wF03G7NK2kMECoIAFgyr0N1r7bP/BJIkPoAjJMFqPykNr05P32IZr9Sew8AatHo23oPeHwAuSeAWt4ZqN8TXdq/1kxt7W/bBDlf3U9tvrNIaqkrqZbK3c9eIWpVzWIpsBIYCtIBAB71RbU7jrWVR71Ln/CeV1tcDs0BYF2stQEAAAAALIOLehEHo3IBPoCIOZRAETOpdCSAekaajraRkCMc1FT7NuGgoBmz703+46zWo66fphq45ZtfC2m+57LN9Xi1A98mSJw6rBYu56f7UilgObCiLSsryq7ZAUAD+QAO/joIUAoAAIsl1vRaRX9zEHAQUBhIkHizAHmSzgkVEeviWnOqPvaaur/XjucTQtnIAs2XBE+4QM2CH6shN6clX1/TB9A2IY3ttrkB8gkhcoL9uZzffBMo69+owFCQiE5IP6voq7qoAQDGs5JeE9/xltm2EvgAwGWw4ld/AAAAYDHAAQCOYO0+gFhxJDoN/2dSbJuDAHd/2KmBmmpAPaqn2eZvzRvQcDB0qAZ9Nur92pTmvX+bSx1sP7bpDj3lhsonGz9QgkT0DxUhfXDK2m9GAAA4h9dBGLIOSGsJtIRagqSaBahlNODCmBZfvep/t5nzmWbpsOMNh0GxVPm1m4OmXPJ5NSPunfDaXGrM9C54+OPWvOV0BGBtSH/T0aO+dsPVfLFr0l1HEPHBtXHtTw0AAABgIOAAAMexatkxYoqUZsMsSqWDADvhgZ7jqrexhEiIi47vHoFeiNizpg7hvq1G23wAzUX8LUHfej3qf34wzZU0fQB93AA1ZSHdDSGqZw+ol/QmamARIiuBYZ3JByp6VOu9AImG6wTQp8CZQGUAACyWh1jdP22f+SXgShagQnrmwmRTZl8cfwA7hqbQ7qmfNN/0ExzMAuQt3NxcHzx2vDHRrf6TM+39UOfiDX8ANxfP3ABCgaHAkDaiE9KPQZgcc6iXyvm2u791RicAsGrW/cYPAAAALAw4AMDRrNcHwERRNg4wszCLYkvemLWWTyqVF03c/lmAmnl+atOOWCDSUr62IB1zHqTla3U+S2dhanEDdAgHxeJuJwNqrKFwFlQ/IsKBzcP/E1KPKryAhuuKfAAAALBMYk2vdZYFSHOoJVCtWYDqxt3xfEtV/e+S5tnzUxFG0OIDKPsNuqEDzfUf1ZPPnSP5oqklrTkAGgWyCfGt0/2pTfpvfi1q2OZzbFHMKm1FGwkS0omox+CiGh3nvI102O6eNv0cHwAAqwDvtwAAAMCwXNS7OAAHiZXWorRkSQOUhCINvcDTNm64AbKZ9bBBX/i/uxKqaweV9RyXAsgZKKBtox2qgTvd1Oi9jX93wbbmpNcN0O0zyD7s21ujsth/Q3pPwVd1dY+seX0AEA4AAIvlIQrDZx2Q1hQEFCaNLEAtxqsw5YXjP+2655pUz5Ov+rAtrT+XPzZ8AOIpn6+Mqq8TfWi+mZDPcFf/llELtQppq6jyq5SmudV2Ox9bmzYcpuq/Ef1Dx48BMvgd4ChrfrKBhmUHCwfSPwAAADAGV6emgUEYVXMclZ9UEHEQiA4kCCRIJMz0Aif5jJAwU3Wm20o/kAUoL9mMH2yL/utQ/73Sf004oH5NuQ7pv5ho/q1NHJDyufJrOSFMXNZng/KwqitkSsP/0x4A33X0qC9EPlhR83tFuwoAuCoeYnWvNmkWIE2BljCRp8x8SG6sMzG6kONrerc7kVJ75rXl9KN2O+7SkQiIfIXbttU202uvvYfWPOq2D1UWZzd7km9ZdrtZ2MwNwGJVlHC85yihICH9LYi+XdAAAAsxiz0NNBIBgRVxOY8JAAAAYGHAAQCui9eBulebH/QSUD5soPU1gOup6nPHgD8LEDVa9VR8bWjinfl/3J9EfGJBscKaBtFGs4nnU/a5VrLNB0ANN0Bz5U1hhdyxFnrWNpGIsFV5BmHS34Lox1U2C6THOb7KigEAXDWxpoco/nsXBKw1h5oDbUIhwxXjkqr/loiI1CEHwIEsQL75+SLifm3+dUpycxFq2ZZL00a7051/uansH/zkJVsMdNWC2+pMS2ITFSUU7Tnacfiiwg9x3Hl0a2Ik6fw0Ow4pH1wMeJUFAAAARgUOAHAiK9UcQ0WvdfyRdSjZsIFaQuvPAlSjMn6go8IzZdl42sSCgrpvoDrRXM/hLECHtlg/BN90KkP4vALZ34aan3/1jZpAjcK1NdTrNj8wj6xgVRr+r7MBAHTY+0gvjXlvN+gLAIBl8hCFIeuAgoACTaGmILFFFqBU+mciK8S+MX6oOnE4C5BDs3D3Io7FF3dB6t5Q4+HfJv3Xvlb/lmMDFOMfNLfT5QlomvvGHOu6AUwe/r/ncM/hx83tX1vdcZgrYoHWsI+NRicAsGTW2KIEAAAAVgccAOB0VuoD+EkFEYfPnDkAlIS2kQUoDZfjSvibI9+XwXRFVoGsJSVZKmEvzTZ/Tesnn+jvlf5rq+pGWr7Gn94BAAAgAElEQVT2Ef2bBeoKfnXNDemf3fnN9VQ/laQNkuX/ocCQfuHwq7oQ+WAa0LYHAFw8D7F6FWye9y86HQqYIyu7TPrPRve1RFyYlWo8O1UmOLPlzrDA3XQn/OnsBNC6eI02sb57omnKvROdppmrJaWxYF6lLNXU/2yJrOEi/D96UeH7zSZZ4/tig0GsKjt/azPPWecJPgAAlsBFPBsAAACAFQAHALg6HmJ1/xz/oOeAUh9AYCz5lWj/h0up/4A6X+NwDwDPaoV9K++ZMaCGeKbLRj75JprCATlfuxUEKlSDrvTBLW4AIbEcWNJGdEL6WxA/XlD64BOQWdtIEA4AAAsk1vQQx5+fg4hDQ5GljaW9sU9O+D8LWRbOH2PtDgBpavQHYK+yL20r6VD/D3YFkJavLdI/k+cAyTXHbUY8s79cvgVV0vtIPTaiKJMXkzL7/56jPQd/31xI+P946v9cwLKD2VnIvQAAAABcA3AAgLOYV5Q8jTQL0AfWQdkJIBB/FqAO2brZ5b/SkpJK9v/0Z7ck+UR/ytMUcF64lh2IGjLB2T0AhJxj9P6lWj0cSiDgWcRXjfVlq9UhwtqINhIkpI2ob0G473Golw18AAAAUOPdNvr0dJMkieHYcGJ5J7J3bHo+AnA50eIAqBu+njR9+bW3gu7kK4ezAOV0+wBqlr12ON3vM20vPLaR5KdZvhr+T5bIJipKuMz+fwHh/0PZvrZqGKR6kAgIrIuVPxUAAACA9QEHADiXNfoAXodB/By+cKA5CCRIJEz8WYBS3EMUEiEuRPBCQ+9sz3NRhqrF2lSD9DtTNuxwU/ofrgfAAfXfq+lTWUt+3T9V89NqpHrFpoW5uYZKAUuBoVT91wnpxwAPK6Ijbzc07AHownsv4Z5ZG/cxv9vefHt5MokxtLcUW9rv5VueBYjdwQDSAXg5G7dWOfZLKv3h6pdB7VqR4l/vB3LPLEDddBhxKg+k8tc7ccgHwHWrXXoCODXx6RybjwBss5kshivh/59uVx/+P8gjgVumm1/P3AoeYGD5rK7ZCAAAAFwG0NTANfIQq/vvWRYgTaG2QSKN8WzF1/RNm1f12DqvRu8gtQa/d0Fp/0tpTHzLJrpfpGtOhXwOuz8dVP+7pf82NwA16spbvbYxIURiVJCOAZCQ/qGjx0B1HubKWGkTHeICWD0HhYf+AdkXQfNA1yjNvLsNPz3dvU+MSSLDseW9yN7K3pH+JR8YwDpPssK4k2S57FXuGHDr4WDYtPcr5x3bhr2YOnwAuXZfm1Mx0F0WvCW9T9VMN34qnQFkSWwSVMP/tysO/x/qzHFjYjwOXnDoBADmZbXPAwAAAGD1wAEABkDW9j4XKnoI4g87HXCQ+QAk9GYB8mXLpYozwB/WR9WhgMVVlbhS+KAPwFuYnCo/0HCr6P7ZHMm7N3T7ALwTbZtz5QPvUgc0iFJBICvMlgIj2pA2pL8F8Ve1rktsRI663QZv2EMpAKvk2OfHFV/oTRuzfDYhvbvZfHn+kejImMTS3tJ+Z3eOfs2UDgaQdQggJ/y/Yvikkq+PjrwU2gr3D//3dzVomembEHLtbH44vRwARXXl5lh8aX+aPvtyAACjIsNxsv7w/8Glf2q5oWa5y+ADALOwIpsCAAAAXCRwAIBhWJ0P4HUcxE/BC2vNgaZAF1mA6qFt3AiIo4pqkErpeYx/XgkdKgB1yvrF16ZjwF2QfM23GscLB+Wheb46qf9dyb6YIKdasprJBxKkyvx0gp1lJZcP8pkJbxKK9xwlFCSiLiz/D5rZAEzEOWbp4iUxaRwf135vzlsub18Fn5/u/pMkqQZteW9pZ+SFhHPRv5oFqLBKlcB/5292AbRZUn/FHJ8RqLZoc3Pdc5oW3P1b5Crs4QAo8vLVY/yrcj+nHgJbGfiXRMgyWZNK/yoL//9wu77w/wFv+rnU/z6Prot/vIFFsbbHAAAAAHCZXJSsBuZlXT6A+1jd682P/UtAgeZQU5DYmq7dHDDQ5wCo9/Gvt+F91dKQ/qUm+jdLFusuFqHqitubclz7tVi8WzigXA4g34F7P1QpI+TrP1GTGKrjB4olEmHORAQOdxx+C7d/R2Hr0a2N6Zvc6AQAro6hTNGlXuttB1WzY8685Rt3rejt7ebzU2ySxOjImtjSXmSf5/xJRf9aFiBxRgKg+t+sNo67AnKL3xLdL21rO3ipSctXyc+N3wHgTBwy4h7dv9YJwObFrPNTJfw/oTT8P9yr9YX/D24oaxNtBcbghEfXpT7twLws33YAAAAA1wMcAOBKiRTdR/GHlywL0L6SBahIF5DrBVWZW5zEwVXtu38DKhUj3EV6pACqjyXQb1seFUNaJmryx1Hqfz+JIf9IxdFSURyMzsL/9xztOPiw2X6OLqQFgdY1ACMyxnPi8lSxPofT8ATIGnScX16pt9/vnvc7oyKr9pZjK7vEPrlmPZ+w+ULKcQM0/xKVXd8KmpXRVj09r54+xdodAI6JlzLkn/o7AKSS7dCb+aea6J+k2gnAElliayjP/q9WFv4/kvRP7ZfFEri8ZxtYGgu51AEAAACQAgcAGJJVaAQFD3EQfwt2rDUHAYWG48R+K0IFiSwJExd6QREi18ioyzWZ3t2IL0i/TBdUXdBPs9gYKYA6/rqHKY0AxoaawFKdT/lSQkRlkoGshgvFwRJbIhEqwv+jHYffo+1fm6i9ctbEUC3t5pk+eNOhEwC4cFZkeObl2PvWsTmrsO9vH6LPzzcmSYyOjUoM74T2IsaR/jP1v8zvR5akUPxL8ydkmV1PQD67VzW4xQ717etFuwMgW2Ut+z/1dwA4drmI8a/5AyQ10ERFyj7rGG5LZI3a7NVmr6K9Cvccfrq5fX+7gvD/wQ3ZvIH/tQ0dPDpvAkoAzmf5xgIAAAC4QuAAAAOzCo0g5SFWr6LNc7KLODKcpgvYWUkcmSBt63I1Po586jaxp93UbFulS0ijQCPwv54UqMVbwEW0n7hbqG625pbwSv9EJE6yoPLopHB4SPPwJV/SzadcCyesfupJBmz1J5voTcJxnv8n+LC9kPD/QRrVi6oIKAVgESzqrlg4J9+xuV1fvn1/teW3tzffX56MyhIBidnv7TeuhP+nFBOu+l8N/8+MdfZrvpIOajXkmvUTcRYWZ47rixAhcobboca7Cjk2l6om2LXLtdF9/Zl/8sD/Yr4lskaHidru1WanNjsVvegVhP+PJ/1Ty/mevj6QCAjMwrJvfQAAAOB6gQMADM/yNYKUSNG77fbL81PCkeG9pdjyfm8fncawKxlwZ1uafFp/WzOq3sI6FDLW1iJrJBT2lpLaD83pwpNRTpe6f1agXTtozK+q/5VMAr7MP1n8oJAQX2D4/1Bt6TPvKbTqwaWxqEjaiyetAV6BfX/zEH7+cWsSY1RiVGzV3pqdyD7P5ZM5urlyHAcTAdn2456sSrx2XKj0zhSBCL0cAIU55twc50l+0hF9pZr5pzDWpdXObLdSidru1XanNy9B/KKj969efbhbbvj/GHfzcgL/m9vtPl483sCALNw6AAAAAFcOHADgqvntNvj0dPdHkhiODSeW9lZ2Vl6IOI/Bz/P/cJE4WDlh7EVOGxI3U3DrQH9N2jsBHMgbcGyrrVU7cCYK3Z/6RA56OkZwrulLVfqvZP5x1ASxTgFrVJH9P9yp4OPt6sP/B2lXd1fBjBUE4QDMw7qfCmtGVuAD2ET05n779ekp0ZHRe2tiq/ZJYkjSvP9Fb738IJgzEbyeCMgSKxIrxEzK38GuUhnFdFsNNQoc8wzN3jG4dOoXun/LX78pz9fjGuumV96x5tkitp78hyyxFRKjbjL1X29edPTp7u7fP20SRQvkqqT//iAREBiE5V/qAAAAwJUDBwAgqorKQ61wFS+CWtFvN5svz7FJEsORpdjybmf3aeafSg8AYSLLmTTgqN5SCxtMObf1VCbj8Sf/Oa2BVvcBZGJOJfMPUYtqUJ1u+dQz/NQkBluZWU5YEhHmhKJE5eH/4brD/4dqP6/iPgJgInA/nMxQj6Q1+ADePASfv98lSWJUJNqQsawkSb7nlj0l7wqQ+e4zI54HvCsmInF6AAhLOrOSk6ejGpoefV+Rw+shR+4v1X93+aMcAC3G2tYnpJxTTftTdNqzLNYE20Tn6n8Qf9ve/Pun22/xEq+O6dX/hdQCBH0wNgu51AEAAADQDRwA1440vl6bD+DXO/Xp6fZlvzMqTwQk+8Q+5W3dIvmPMxRw2U6ujBnoU+r7VMIgrbPmdprrrDsAsolSUGgTESoKQlt+/1wX6tQUSh1BnNBCIbLGzf6/5vD/KaX/oypoDBUAygKYiFU+DC6UxfsAtKI3D5tvTxsxlq2QFrbCLIn9TmLyUt7BAKTh0U9NvztdfpwaKOqjj1egWax4efAu0jZHWr728wF0mel8gsvs/82P0VGit/vMARA/B/EfP736eLfE4P/x1P9hA/+7L52TOTYRECw76M9iDQEAAAAAasABAOpcoQ/g3W38+WmbJIlRseW94b3QTtJQQXHcAFz6ALIkAvXwf0ukiIvxA4/ZiTQJQT1msPKzb7ptTkofH0Am/zuZf8irF3D1a/nhooCb+cd6yzg+gEo4oSVOOEq4DP9/f7O+8P8BG8yDq//jAaUAjMhCrvIL5fSbd/E+gF9eqd3u/v8klqyQEbZCxrK1if1BxCxUdwCUR5KbcmlK/8pdokSasr5rysn3E5Gn/g6ejaYFLyY6LDg5hphKWyy2apedCS7S9DkmW/LAf7ZEVlgnervXm1T9fwmiv365/89Pi2tWjCf9U8v1f+xN0bN8cdGcxrE3Oyw76GaxD38AAAAAtLG4N3WwBK7NB3B/w++2N993z0YnxsSW9kL7vf3G/ixARYeAWr7g/K9QNR1QP/wtLacJJo052dfWhVtWLfly/eUDISfAv5r6X0Ta+gQ4noBKCKFQPsBgUbgM/1erDP8ftp28IvUfgBHBVb5kFu8D+P2XIEnu/+97Q1bYCmvLVtiKMc9EVFdxSwtbGHeuTlAZDSBczfjX1PHZ87XwE3CHD8CH5P+4OkuK37pT+aXlhKtpf7hiqasT7VH/qSdAmEyw3evtPlf/P71+9e+f4qVJxuOp/+dL/+d0ERjpuJZ2+sBiWexjHwAAAAAdwAEARmfJAkHBm4fo0/OtSYxRiVGxMXtLO7FJMwuQkGWiqvpvhRTX8wbIwaOeqmZ8DgBx57dJ/+SKBY2v7XJ/LfyfbJHnh31qgjCbNPxfZeH/H+5WE/6/Lul/pBY+hAMwMMu3GYBWYN3/+SYyyes/Pn5K+wGQFbaWrbVm55TiyjS7sf+1iUz3FyKupAMqfAhN3d/9m8+sPC6lsQ9NanEAUp9P9VR+uc2lqsOeqk592+UD6Pwk4W2itnu9eQk2Lzp+vL39z8/b57DzIKZlPOmffGer/30wyB1zms1FJwBwPst+5AMAAACgCzgArh2836dsI3p7t/36/JToyOi9NbGY3U72TmM4ja9zc/4qorQTvarnz2GqdwKoN/h94YG1+YfPTc+z53MAFBNF2KA0HQCHfABlYh9H8a/EGNq8TB77L7mCwI6UoG/3K8z+P/iNc/CYl1wpeJKAYVjyVb5exrs5hYSXe9KUon+8iU3y8Nenz2SFRdgIsyQkVvaUGfTq7osQqVL0l5ZOAJx5+h03gBvm3/SNNO2+u2n3DHlnUmOmx4UvaeRBJdVPNlGE/7NrwatZgISF3ex8kvvsc3stZJlsojeJ2ibBdhdsXnT8FG/+++vd55sFpf4fT/0/Wfof/B45rStAt6WGHQfdLPZRDwAAAIA+wAEApmDxYYJERG8egi/f7/5IEqMio/aWY0t7a1+ImLO0AFSNABO3E0BlJAAplIKuJvGBOhmsHdbpAKiGDR7nAJBUTagFD1Y7AYj7ayWHQKom2GCzV9ud2u5UvFPh92gF4f+Q/gEYnsVe5ZDEulm2DyCK6B9vNia5/2i/kLVkbdoJIEmsiO1ctKsTQNpLoIgJyOuA8ySB7PQJqPn4Xenf+3LUdsGJb1o8fytmvf2TuwTKrgBS5Ohr2muTDdijgkRt98FmrzcvQfwSRH/9+urPB91Zk5Mymfq/hGR9g0v2tRXCJQBSFvuEBwAAAEB/4AAA/vf7wSX75fsAAkW/3m++PMUmSYxOrN5bs9/Ljsg66XyyibzZ71P/87+SZQlwfQBFtGBtDjXmDFhhnQ6Aytc+PgB3jncIweYcV01wPQTW6jBR253a7HIp4f3dzZfNcq+U8ZSFcwosBCgF4ESWfIlf+jU9zG27bB/A9oZ/f3tjEvPZClshm+YCkiT5TlJz7aekliu13cyNkQCc8YGq4f8VE++a8poPoDZR4K1C7wuaO9HmDGh+yGepa/779g9bq0Kjb7Kxf8P4RUcff7r/45el+OyXI/1PeS+MndgHlv3KWeyDHQAAAADHAgcAIOrdDL14fr1XX77fPu93RkVWxVbtRSVJ8r2sDykag9apoyLqP294V1IAOZmCu8YFaIsE9C3SnSHAsxL3HHdGDmZ/awpCPs1VyUDqQn9exlbjCm19UEG2QpZYJWq7V9tM/dfRX/f3//5pKVJCjZEawN032hi3IRrzYEEs3NLgVunPsp38r17x72/vTGIfcwcAiSVrTfKds+59mVETEi7c9kyOAqy4bs09HyFirnn6hYjLEYA9PgBySnaTF2AiEpE86xAT+8031Qb+rXwcY12q/+LR/dMJo2MbbJNwuw+3+2DzEkRfXt39+XazX0b0/zTq//Qmuw/Davp4SQApC36iAwAAAOAU4AAAJWO/6i1bH8h48xB/+bE1SWJ0LNqQtcxizI/8d6c9mOnq7EkEJK7035EFqI8i4Csp1Fm4tqD7X+rzu6T/xoRHSmgMHijpkL+dYwmKJZIkvEn0Zqc3L3rzoqMvr+7+/dNSpIQa4ykLx/60cCAcgL6s9yoHLSzcxL/+SSXJnfnDZA4Aa9kKi5jkB/lyAUmWNz+14I0UQEWpphtA2JlPRVKgFouf0tuUZ7tWnd9rCJ/GpzToreH/QpbFEFkTbm1wkwTbfbjZR5t9FH/f3vz3ze23BfTYm1369//ULbEPzbDGt7Y2WPZrY/67GgAAAAAjAAcAmJSFCwREdH/Dv97fPL28kLGsLRlhJWytMS9E1GwVSpYfwFH/G+kCfKoBdVaGKxZ4gwRrXQA61tP2VQ79rckHlAf1F0MINqT/elcAXyxhMfBvsE30dq+3L3qzC+Lv25t/v16ElNBkSvV/icd/JFAKwGFWcaFfzXU82D277ERARPTrmyBJ7v+T+wDYCokoYZs8kTUNibwq9LMi4aonoBHmX/lI3idAaj6AMoKgtTdfs1tmxfnv/NrHfHd+0n4A7caaWJLw1gbbJNjuo9QBEH2/u/3z3au/X88/8O9IBrqP9M8n70FzkSFum6Nu5GE7DYCLYckPcAAAAACcCRwAYGqW7wP4x8/Rfv/w599CVsgIW5tmDRa7qzfAKW/LE3FlEGBuuAGIyBJzXTgQ8kUFeul2GJQ71NJMaxMOal99IYRl5h/KQvvrkYOO6F98LQWFMrRQyDJZo6NEbzL1X8fPQfTnz3cfXs0vJdSYTPpf+B0BwGCs5Vqf5uafTFGbTL1bvA/gt99Dk7z+87+fyAqLKOJEtBVt5EnsntIkPhVESJgVCdXV/3oWICrdAOz2A6DKX3bnFBtruvnru+GblnQH85cJz0TzIyTsZvMTx0aLY6nTzD9MSXBrg20S3uzDzT7c7OPo8fWrP9/dfn6Y32SPrf777fUYW629xE1C91MBiv8VsuRHNwAAAADOBw4AAOoEmv71yyZJ7j98Th0AwkZY2b21ZJO8VP6eXEnrX/gAyFH/nR4AUluYfOp/GSdY/VsrQy3v6j3V/2LiYPwg+TL/NKT/TPSXmuLv6v7ZwL9KG7Xd6+1Ob3Y6fgmi978+/Pt16NvtOZlGALzI5haEA+BhRdf64Jdvdwjxhd0ti3fyv/s9SszDx79EEWtRWrQhlZC2yZNNnhqWThEVrvr0ayn6+0YDltQ3IJnS33AASDpEQC3E/OBF0MeIeyYkC1uo2u6KC78w2VXDLcYqLcGNDbZJtE3CzS7a7OPo6y/3f7y7+XY38zkez0D71X+Z8KLueL87xLDGt7Y2WPYLZtnPbAAAAAAMAxwAYAYWrw9QHNO/ftmaxH6yRT8AIZZEvufJghuNxEzK8eb/qY8GLKS4CBUk8kn8BW0+AOrXTJSWr9Ly1TvR9mnLApQpCFx1A2ThhGqb6O0+2OyC+CWI/n549e+f4q7RkSdnGum/bSYAl8a6LvRh7/8+x74WN0Cxh4cOSqToGrdEgoB+/8cmUD99fP91R0qzSkhp0oaUEWWTp9TKF07+iicgM8EVN4DfB1CX/l2PvrP63rKqL/8PVY117WvFiDfS91nOp9nntrc6kODGhFvjqP+f3z78+dvmae5kfSPZ6JpPptjYPEd7qhvgKJkemj6gtZloAAAAAJwMHABgUGrBQp0FF/7GeXvL//rlxiTma5Ys2JK1bG2SfM+l/BRHJci+czUREFU6AWTF3TU44r7kE8xZ6F5Jm5PAW+neNp20TOR/mWqSQTldBAyyoyCkM7O0P+XYv1Xd37j9AIy+TfR2pzf7YPOio683t3/8un1aUvQ/1P9BgKwAMtZ1oU+v/ruF57tnDmy8zY/cfoALN/FRRP/8H3Ec/fThv9+evilNypBKSBnRhrRJnsgmhe7f6LGnmm4Ark2U5tt1ADhfuRjIt6WesncAlyMcAJncz+RYczf8vxb1n9rxwpRb0ZENtja8MdE2iTa7KN7H8ad/PPzxLt7Paq8nDfyX6td+6zmN1uM6yQ0woA8AnQAumyU/pQEAAAAwOHAAgCHoVptbXjAXLhAQ0f2D+mdyZxJbjBbIRlhJknxnMnmp7CDywyniBLme/4co9wo0P84K0glx19qU/tsahW1NszbhoPpXmjpC41NJB1S4ATzxg82PCTbpwL/7YPOi4x/x9q83d59v5s8jXDCN+r/wy34ooBRcO6u70Ae8Xk879mXeM75dykxRhxVfvoEn+vW3YBPdv/8z+Pr5MWGlWJm8H4CRZzHPDQuYqf9CzD43gGvT8yQ/XDXi+d+yVlscxBVzTO3TPt2/mCNU3f9avL/kvnlhZyQAG2xsHvtvos0uil9uNp/+8fDHb+G8ffVGMtAe6Z/yiIhDyw64G/lmfcx6N8EHcKks/gkNAAAAgIGBAwCczcGmQLsnYPkSwc+/aJO8+v+NIStkLVkhsWytMT/SNnwu+hcflR9UTf2veQKklxugnGjOpx5xmM1z067+e6II+3xsrjj4Ev6UmX+M0Rujtnu92QVxmvznr19f/fla9z8XozKN9N82E4CLYnVX+bD3/+oO/3hKm9RuxReeCCjl7mcVxrdxFHx8/8WwSt0AinWaEcgmT0SWiHLPd3HcmfrvZPNTxLkpl87Y//IvNaabM49zANQnih4A4or+hbe+6gZgIaUTvbXhxkZbE22TeLOP4ue7m7//+eqvtzO3F6ZT/6X6tbHIqLR6Ao58XR6wEwC4PBb/YAYAAADAKMABAM7g2BZDW9j6snnzLkjM/f/9ryErJELWshW2Ys1zpVwlREqIhLPAwMaoAMLVVl6uGrAbKuiI/tLwATBVBx/ucybyMkyOQOD+VFMQimB/Z06qFLCQL6EwkSWp6/557P/WBDdJuN0H212w2YXRh5/u//w16rHbUwD1fyQgK1wda7zEB7xGzz/88e6ZQ2v2/96+SEUwXbMPIL7l3/7XJo70h78eX3IfgKGsQ4Dsn0hMrvirqjl2OgGw5En/fLq/OF+5mEllgTp5/4FKFqDqNJcz64H/xUStx14z+Q/ntptZ9MYEsQ1iG8Ym2ph4k0Tx94fbv/919/fPc7rqr0T6b25uSh/AUauCZV8vi38eAwAAAGBE4AAAp3Ly63+1DXNki2Ye3v0WJcnrP99/omw8ACFrE2vFvPjj4kVRdmiu+l8OAuwP/C+zAzV8AGXsIaU9D3q7U6T+VWrzXb2g9rXlI67uL9SM9y+S/otlliS8NcE2CW724WYfbfZh/Pnu7s+3m/0Cov8h/QMwDGu8xIe9/4eqgTWqa6sw5O0oTb/8jzDcPPz9R/Dti0q7AhhWirQhJckLmZfc7HLpDHC+irhJgVz1P/WAOHOkUPcpL+NDmtdB1QFQMeU9DHdutSUf/lfIsgiRlSA24cZm6n9kwshEkYnjx59fffzXzePDnGn6RlX/lyn917ZbqYHRbrSDT501PpaAy5qf0AAAAAAYBjgAwBz01K4Xg1L022+xSR7ef/xM1qYfFrJpioDWVlHa5FbMygn8b/tQVTuQahO16QwoNuHS2n28rh14/h6lI0geOSj1qH/3ozgJbm2w3QfbfbTZR5t9FH99uPvj3e337fynH+r/BEA1uArWeIkPeF2u8fB9nHW3tkiTq+gEkHL/Vofxq+g/wecPX9NOAAlrxVpUJEkkya5wA7C3E0DFgld8AJSF/eczmasV3auCpOz3ly/MjhGvBPsX/fM8DgD3r+jQhDEFsQ1jG8YmU/9DE4aPv776+K/t091sJ29U6Z+KCal+bZSfnfpdeYwP4Kg7erzCYHaWcCUDAAAAYHbgAAAnMciLf96MWUXsYBjRu983SXL/6ZOkyXI1qYS0FW2SNEWAGx2fSf9OL35FxFxoAa3OACFuZhCmSt6ASsurVnPeE9OU/vOJSpIfKtV/bh5LIfoX/QDqGYSdjyGyVmkJbmy43YfbJNruongfxZ9/uf/z3fbb7cwnfKSG62IVBBc02sGQLO367slQt8FIhz+Suja2aNdmy1dh44mIaPvA4eYmjPXnP7/ulFJKWRXIfmdVJGonSUTJTswLEaVHxWlGIHb77WUmXjKLT0LMXPUHCDVq5KjOfPm0uIbbZ7JTT4BruPPs/6K0BDEFscZk4AQAACAASURBVESxDWMbRCbK1P+Xh5svb26+vgmS8MzqPJ0p1P/FS/8F5/gAxtsN+ADWwqIuZgAAAADMCBwA4HgGfOVflQ9gu+Xffr8xxjx+IU0qEa1JJ6J06gOwe88yIkQipJwkAIc+IhWlIKsbyRMC1NT/Piej3QHQFv7v0xFSfcEZ8rdN/bdCVnQowdaEN0m4TcJNqv7//fbhz9/i5/gC1f/lKwgzApngMlnp9Y3A/07OvVt9tnwV9r0giOnN/47DzU+f//Pt+VGL3lsdWh3Jfic6cwOI2VWSAkkxFHDzQ4XLX+rpgKi3G8BruKlquP0fIeHaSADMNthQuJEgpqjM+WOjcH+3efz19sub8GU+J/2Ugf8rMtwn+wAQ13/NLPNiBgAAAMBcwAEAjmTwxsGqtIG7V/yPf9691/rLp0dNSpPSogzphJRNnmzy3Gh+F8lzhUiJ2wmg9UNV6Z98f6kxTdX5tfPU9AF0OgDqE0XWYKnq/mU4YZZQmCyxtTqywY0Ntyba7sPNLop3cfz37w9/vIuTuR85UP8BGICVXt8LD/yvbWKlUpzXB7CeREApr/8RhNv77x823z/+SH682P1edqHsI6t3so8k8fQGaO/bR75pyqdduD6ZXQOnOwCyT94JwAYxBRsJYwpjCSMbxSYKbRgm2/jpzd2XN9H31xeb8Z8OBf4fe4U2y6/0lu0POgGshVU9bgEAAAAwEXOrcQBQHnTO63hhfXWvNtFtHAUf339RrDSrhJQmZUgb0TZ5YrJO8aIFXgj6Km+QelMGF8kB2qT/PPlwW8sry0Ug1R+rDgAmJ3WA+zef4LoDoJT7peoA4HonAKO3Ntia8MZEmzT2/+V2+/H3+z9+my+bQH4kg9PhgVkmc7XVIRNcDqde3N0XwBS3zCCX4Bz39pC3T491DbA5r19/Vc5+Irr9Sd3+tHn6Jfr+Yff944/9jxdJ3QBBJLusNwAlkZidZL0BXDvuVf+LVH5MVNhxFy7+VI12UbThAMgk7Xr+HyFhJ/OPcEA6NDqUIKIwljCWKLJRZKPQhOHzm7tvbzdf3ugRavEIRlX/uwP/e16YB4vVCgx7RMvsBADjvkBW9aAFAAAAwHTAAQCOYdTXfFmNDyDc0D//v3gT/fz+v4/PzIqVYW1IG1GGlEmeyJo8WD4NppNc7u8eMLAZIej7m52FlrhzqcUG+qbLoMIWB0A+NkBLwp+mPyB1BkgS3NjwxkZbE22TKN5F8dOr24//vHv/68yPGqj/NHcrHTLB6jn+yu5/xuXELRy5gTOZ+N5e9T3TUCeFiNfmAyCi7c9q+/Pm1ceqGyAIZR+J3kkSyT5zA7DZkzWFKee6QW86+Cn/tTrtOeneHgCS/2mJ+hcRFZAORYeiA9Eh6UCCUKKoUP9fXt88vbv58jYwszroFx74f/I1Wyw41AGe7AMYkFU/li6etT1fAQAAADApcACA3kzwyr8eHwAx/fKPII4e3v8ZPH75lrBS2Udr0UaexO6cuLwi/L+m/iuidPzAXCwQVy+g9r/U8p7vzmx3AJQTfgeAsOR+Bpsfgk/9dzoBWBVIENtwa8KtibYm3uyi6Pvru4//uvt0cSkFqEftA3BRHHNxn3zTjSJnrVH6b2x5fZ0A6EL6AaTEv6j4l83tx+jpvd8NQMmOTMLGsE3YJmQTElvrFsBUs++Uf6X2rymNwH/3b9EJoIj3z3V/0oHo0OpAgoB0IEEgQSBhKHG0v9s8vb399lv4cjPz+Rg98H8O6d+7nhl181FVe7gElsAKH6sAAAAAmBo4AMDCWJU6cPerCjZ38X+Cvz98NawSVoqUIaVY2b2yyRMRVcL/K84ARSTETKJcjYCI6g4AKfIGFHOopZoc/4m4LTJnmrsdAEJE9TEDM0+Go/uz4wZgZYOtDWIbxDaITLRN4s0+jh5/vv/4r5vHV3OeTgT+FyyhfQ6ZYJX0u7KXeGYHDLudCeaKfDllJY+xuVWZdw+pG2D7MXr+a/ej6gagZE+JYZNQ6gMwCVnHH0DU0r2PiCh3/KezvT6T9J9UOwgUPfbI1f2tDlkHrMNU9KcglCAQrSkIJNASaLuJnt7e/fgteprVN0+jGegySkIq82vFulcyBoO4ASboBHDw3ocpXxSrfqgCAAAAYErgAACLY12jBW7u+N3/2kaR/vuvx5fUB8DasLKkLWtJXsjs2R/+L0Sp+p+mRqiND0yViVLx74gcJKJCSqi1zhzJQNyZ9dj/2ocrPgDnr1ghy8w22FAQ2zC2QWzCyIaRieJ9HH19+/DxX5sfW6j/84OGOjidQ1f2QjXi9Uv/4268n4A3gMx3QZ0AClI3wOZj9PzX7vlD5gagxLAx2V+T5H8TNoZswqboFmByg05+a+7x7reE/zMTK2IlrIi11QHrkIOQdcBBwEHqANCS6/4SBuZ+s7uPXx6CH+9mTvdP8wX+zyL91zZxeUb5Ig9qFaz5UQoAAACAqYEDAPRj2lf7dfkAdEi//s8o3Lz+9J9v3x857QpgWIsKhCNRO0leyO494f/lV85HC+Dqh4jzvEDsOgMoL+yl1hZrTJeNYyGi2uCBLZ962h/RsQnjVP03YWzDyISRicIkjr68u//4r3gXnV+1JwLpv2BpbXLIBKuh88pe9EkcZOfmvrHZnZqpE8Awm6zK/VLYn7lr+ExcN8DLp2f744USQ4klYzgxZEw+kRTdAsgkbBO2hkVYhEnSCaLsL4k4Bt7x5bNyP8yKVKn+i1LM7Er/FJS6P4WBvd/sH+KXh+DlQZvNTPVVZVz1//icP9l88c4dnjPvqtMWP2qpEzYB4z4xK3+CAgAAAGAG4AAAS2VtAsHDbzrcvPr07+Drx0fFyrCyKhQVSbJL/1LqBvD3BlDlAYvjAGDnazk8gEufOmo4ACoDBeefphuAy2nJxwEWEtKBCW4piCWKbRDZMDZRmKr/Tz/ffvl1++WNlvlSC1yn+o+GNxiM9st6BZfZRaj/HawyEdDarHl/UjdA8hgnjzZ5TJJve/PtxT7vydjMDVD2DEjIGDYJW8skSoRFlOQTlH+l0jdAJKXur9IJJqWEFSllmVkpYmalmBUFmoOAg0ACTYGWMLD3G/M63i1J96cJ0/70elWSzguz5osZlCFv5DnuL8j983KhD1QAAAAAjAscAKAHc7zmr1ExuHmtws1tuAm+/PHFKG2DvQ1C2UVWR6JyN4B5IVNzA7gTVOkBILU55LZznS13VFUjb0D9a/unMRKAKCXBlsJYwpjCIudPZKJg93Dz+OvNlzfhPj63Gk9mSum/+6cJWG/bG8LBomm5rNdxys7fy2VYnSn2ovd9mI5DMPwFsEYb30LwioNXmkjTPjaPt+bRJI9787hPvr/QPiFj3QRBbC0Xur+QcnoDkIhIbnlFmESyeH8litnR/ZmZlGLFxIoVs2LSmgvd/yE2Py1L909Zivrf3kXAzziegHNM4TLN6DL36sK4lKcmAAAAAGYADgCwYFYoEIQbevu/4/j2l+8fnn/8/d3u9hLs7S4UHck+TQcUCe/EuL0BFJHkkf7kyQJ0lAOgmJTKv1McAG4/AGarN4X0L1Fsw8hEoQnD/e3mx9vbL2+i57s5z9bE6v9coHUNxgLS/2Kp6mqzqGznbnSF1vwUQtI/s/45iCiQp635au1jYr/uzbededpRYjixLFYJZVmA0v515Sf/SqkzgFilIf/MSlnFwooUs+LCAZAWoFDLNkx1/+T14nR/mlD6p47giJYyRzD0ZbzkDj1Q8xfINTxEAQAAADAecACA5SJpo251L7xMD7/pV7/cfn+/+f7x+cff322wl93eBqEEkewj2u8kaSYFyhuzbgqgQvRnrrR2hfyN3GaLrZLth8qfueoAkKrcX2b+EVahVSHpoJD+JYoy9T+Ont7ePb6Nv/00X8af/DAGp/u6m/6qRFMcjMWSpP9T7qzLUv+n25f+Ch+XyVLOrOy6ILukmh8W3lKwVfQuIons4439as1jYh93nAgZS8aysWQs2ar0X3EJECkWpUSxMJNiDgOOdPqRWEmkbKQkVjZi2bDdzn3MLcyv/p8v/ddWtbTrdjFZgOA2GImlXXEAAAAAWCNwAIBDzPouv1YfAJEK6dU/9O2b26e/cjfAruEG0BElOzE7MgmLJaJG+H/uABCuNnjJP+05WV4HQJHQwZ/2R0hYh6JD0oHRoeiAdChhKGEscWSj0IThyy+3P37bfnmjZdazM0vgP9T/M4FGsCB8V/Nqzs5lSf9dLKATQLHplKN3QPKR7KszV3MKToZJ3bO61wFpSmIyxIlQQmSIEqFEyAglRIllU3y1ZESs2EhxpDlWEimOlY3ZRiQR27C5kYUyrvrvJPH3p/0R38zzGe66PfleXqwZXeyOrZTF3toAAAAAWB1wAIA1sFqNQIV0+0+9fXv7/Nfmx4fn739/l4YbgMyeTMImYZt+DBH5MwIxkRxo7VaRlgkhanQCyOP9U93f6pCCgFL1PwhEBxIGEkU2CncPN0/vto/vwiQapJJO5xrUfzSkwVgsT/o/7ua6RPW//x4No7Id2QmgNqPg4DqWV9MzERAFJLHfjrvVyIbIkO1nZJdZveMZ6Oxve1w/NzY/fBWt87102CxA0PpHZYXXFwAAAAAWDRwAYOlI0ZZb7buwCunmn3rz9vbmr83z++fvf3+TXSj7vd2FEoScGDKGTcImIWtSTwClf8VUHABtmX/8X+nAAABZdqA83l+FpAMbhKwDDkrdn4JAAi1BIIFObjdPb2+efps53T/Nl/F/Iep/RSeaYkeGB8LBnCxP+j+OS5T+Z2OIW7FvdXrt+JqN+3iIJtJz78QZTBb4T0Or/30KD3t0w1jDJd1HsO/ns5iTCQAAAIDLAQ4AsAIuwAdAjhtg81f8/NfL09/fJAhlH5ExhQ+AEsMmIWPYpv6AomeAIbJE7dn//V99un+aiIFV+hEVpPH+qe5PQcAN3V+iILnf7O7j55+D559nTvdP86n/E9NH/afV3xZgchqXy0KUml6X8UVL/wd2zReDP+m5G297eIpdEGMH/h9Q/xubP6pXzXElheZNgbgE2p4K8AGczNVfUwAAAAAYCzgAQCfjvb97xw7rLH4BPgDK3ADB5k2weR8///Wyf3yW5z0ZQ4ktPAGlG8BkXQHYGLIJi2WR7ENC+d9yCF+iaormUugvP4qJlbCyrFgxKc065CBIPzXd395v9g/xy0Pw8qB65iIYlZGux57X1JSXnvdIO1wCK78twCScL/2PFhwL9X8a/N3EzkgEBGZnURf12OP9dqn/4qmKsY07D+QDOO3Gwu14eSzqdgYAAADAhQEHAJicU9OaXIwPgIhURDf/DLZvg+TxJnk0yeM++bZLvr/Q3lBi2RhK+wQkhkzCJvvLYpUIiygRJlHphIiiciZno/gSl4p/OsHphFWKmVkpYmalmBWHAQUB57o/RYG539jXC9L9UxD4373UAo+lG4gXk1K9PvrW/AlXleuCHGojVyD999rBEToBcP5PpvEBtD2q1vgIAw4TBP4Ttaj/Pumf+l1Q5190MGRtoGaOAs8/AAAAAIwNHABgWnq2BopiF60UcEjhzxz+HBAF8mNrHq35miTf9sm3nf2xS7sFOM6AhEWUUKr4l56A/ENFbwBJk/wwKSWsUq1flCJmoxQzk1KsmFmRYlbMQUCBligwDxv7ECevl6X7p8yu/k9wxXUcI1rR4HROkP4Hudz7yT+jq/8XYSw6GEpl43xFh9c2krB3KZb9ChlX/W9I/7QY9b9Yz6Xa6D6HdsGHPw147AEAAABgGuAAABNyQhOhmtWm1AcuTingGwpuVPAuiiWyX2/sozVfE/u4S76/yEs6NoDN5X7iXOjPw/0d6V+ERJg5Ff1ZsbAyijlL+ONMKCbFFAb2LrIPsfkp2C9P96cFSP9jczC0v/96FnVcYH6OzfnTcgG1LXX4ejukDB1YwxUE/qcsZTfZI6f6TwJ8AMtgCbU19ni/1B7437F4n00MQ5ptESK4D1RLN0u4fwEAAABwPcABAKbizKQBlL0pX7APIINJPbB60AFp2cX26619NOZrIk97NkKJJWPZWDKWUvVfxJnIpoVJmEml0j+zYo4CDjVFmmMtkZJI2UhJrGzE9pZlebp/ymWr/weP7tjDv9R7ApzCUYH/JyUI6u6pVf7Wsrqupa5G+j8aX332V9lOeET4hw04aqvn7wRYJBNL/1T9qTKnWebQVsCAdDwM4APwgisQAAAAANMDBwBoZ2nv7Lkb4PJ9ADkckf5V6V9VSCHttmSIE6GEKBEyRIlQImSEEuHEphNkLCVWmG2kOVYcKYqVRMrGLBFJxDasbmKmQzvIvOP9js3g0j8AJf3V/NMGBmgwiKw8wH4cXvUSWdb+9pDr2J3KCw/2yLp0s34xjKj+S/m18qvP9bUczlS6JxPKocjPy6IuWgAAAABcFXAAgEkYsLUh1+UDKImIyvQM9WMuQ3EtUULEVBP618WlBv4fG1h92iaWcKRgHk6S/ge53Y668FpLIvC/D+d1Ajht/V2FyZ+HvWsd3ZcLnmL9mKuSZsj479vk7Kn/QU/gcijAtQcAAACAGYEDAKyQmg8AOIjKXAXrZZnq/5nX21Gp/M9kLbcGFIGBOV79n+UU+C/Oa5X+B9zrnipbr+fDsYpdi0+iuekj9wMsjhHV/2bgv88Z4J3TPf/YMuAEoPIfBNceAAAAAOYFDgAwPqMNFShFvBheq09iadW2TOn/HI46IjSewYn0H+yXO38dg+rGoP4Pw3li22Q+gGYRdx+69gNmfZFMl/anRfpvm9kx/9gyp5Bfrhcpgg9yUBdZM/3BwwwAAAAASwAOALBaXIEAYsH6mVr9926v8yqSw0VaV3yQYQ9/+TfENWsBA3Nk4P+MNe+5Jq9b+j9r38dOBHQCeTqg/mVJSOADOImJ62bStD/jqP9gbLqfP9fpA8CVCQAAAIDlAAcAWDNCwtc3GMDFsQjpv/jp0FU0+N6O1CTGDXH59A/8p6zL1HgcDO0fXv3H9e1jzkRAJy3l+vHr4Cm2DKZL+9My/G/HzI75pxUDYChwyQEAAABgUcABAEZm7IAf1wcAjmEhlTad+t9/BN4Jq2bU+2Ox6tkVhgEOjO+8zqj+++lQ/6878D9lgCNYYCKgYik6+pF71EJgGiaQ/gnq/wWBTgApuN4AAAAAsEDgAAALptlQ8L5TFz6AxeqdoIWJ1P9jNzPVhXQlLeEa13nUQ7I89b/jpoP0Pz0Dq2wnr67/gs4jt+4GgFn3MU2VTKf+d3cVQuYfsB5wTQIAAABgscABAJaKt+nZloVdHNkAb99rYArpf8FK82S7trQbYsHnZA0cK/33KzA8I6n/i7qUz2OwQ5mgEwARcUWxPbhOd8HTul5V3ABLe4pdB9On/SHfeT7lGdJYyyounysxjpfdCWAVVxoAAAAArhY4AMAi6W4f+NwAkAiOYt66Gl39X3b7cuK9W86tsezTsmxaTuFh9X/8c9+m0A18Sy7kIl4g448GfFTdu4WFalp+J41HVbnocp5iV8Asgf/UcYZbduhAeVwwLUzQIax7ExfpA8DlBgAAAIDlAwcAWB6n5WpHIqA1sJrA/3Guostr9IIpOFX9H5u2zD+Q/ruZ5oCGGg2Yj1qdd9nUEXXq3mBsgCmZSP33uayau9JxZR6+iapjS0zD9N59sAQuzkYBAAAA4DKBAwCMyQmtk6MWafgAkAhoySDwf8ZNz35DLPvkLJiT0v4cVew0EPi/IEZLBMS1L6duhYmISc7wSLBMMZrFKhjpnhjRQFel/MPqf9OP2F2+jcks3xIeU0vYh95cTCeAVdU6AAAAAK4dOADAyqk28MRpcOLFvI3pK2akll55IItvSs6+g/PeELMf/io5L/C/zL4yAofVf0j/LUx5WMOrbOetkZlIzliBEMMHMA7TBP43v1LtjmgbPqSt/MF9wKvgHPR5TlyADwBXFgAAAADWBRwAYEmc1hpwG3hFIiCwGBD4vxDmUkKWUwNrYi1pf5oDeEL6n4UROgH4z8OZul0+nnDXOjofVcgINDhj2WipW+qua6xzKOBVgGvyeljpJQoAAACAKwcOAHBxIBHQkhhX/V98g3tpO4h7Yh2crf5PMdiGsxkE/vdk3IMbfzTgcqV0xno504JP8QFc/SNs2KOfLO1Pl/rfcyjgHr+ul6W9KkzDGjsBXOoVCAAAAIBrAA4AcBHUEgEJMXwAc7Nu6f/sy2Z1zdqRQD0cR/uFN29NIufPehlqNOAT19u+7IkrECI+1wcBaMLAf+roXzLBKRz7PZCzjUxMZYvLe3iuUdzvYHkVDAAAAABwNHAAgMXQ3lZwf2l9C4cPoB/TVMaocYXLZ8n7OOUNseR6WCKrUP/HGOz3Oh7RUxzlaKMBj7E56u5LANs9GvMG/lPuJ2hdSfcmjgVX0YJZhZ8AVxAAAAAALgM4AMDSkcbXo30AYELmDPwvfp3vvC+/KUtTCWurqIqlsArpnxD4v1ZGUtm4fdWHN8eVy+m43csfYatQDwdkkHtlIvW/JfCffOMAV1ayQs6s0imv4QXeL0u+i9d7TQIAAAAANIEDAKyPI3wARaAZ3uInYYbAf+/8mTwBi23EgkUz9Hi/Q12HBwL/If0vkxY57YREQN2Wk90p39gDxUq6VgEfwISM7Z4/R/1fK7yAyw8P0nFAvQIAAADgwoADAKySvpI+BgSuMmodTKr+99zYaef9pGqaXwU4hrFviHXVxmwg8P9qmPRwx08EVC/QvsX+yfo9Jbt3BWb9GMY20DVj7TkzLemAWssfWeCqWIWF7f8cWpobDxcbAAAAAC4SOADAxYFEQJMzddqfRbUUF7c7fRlPPVtphUzNaOr/mYOzencF0v8FMJTK5j+BnWtvdQM0ljphJ5emHo7EOffNlGl/qOUxco76v1CGCP8/dw1rrbvlghoFAAAAwKUCBwBoZ9mt6v6JgHosAE5n6rQ/Y1+TR14kC75F5gEV0ouh0/6cT5v0T93341nbuBZmOO7xOwGcsFG/G8DnA/AU84JEQIeYKPCfTlf/R0V4xE3PdcnNnh1uDJZwC1+rgQIAAADAtQAHABiTGd/oHQGj7ARw3T6AwQ99HYH/o5302Rur5zN43VxAnYzOJGl/skdvvxN8WPqnIXbuip+989CSmv+oM3n0I6KHeO/ZhyF2dQkC4tKYOO0P1a6Wfpvvc4Gd2fthlJefIVaK8H+XGW/hy6pIAAAAAAA/cACAFdO/XYdEQINzaYH/lT04zMUoTQOKIxdTJ2OxvIz/kP4nYGlH31NlO+vJ0KMrQE8fAB3c2+vw6x97iBMY6P7q/2znB77/uVm+W+4KHh4AAAAAABlwAIDLBYmAxmGk5lyr+n9+QvShWXiD9lgGuScurE4GprN+R72hxHd2veNz+n9d3t0HjmCIREDTpQNq0fsPHwQSAVUZS/1vl/47Nt998UzzhJCBtlUe2Xzh/xeZ/6dg4lsYBgoAAAAAVwUcAGDdnNIJ4Cp9AEuO8p4/8L+yH12sogl9AmfeE5daLcMwa+B/Kdt5d6Ml5tr700nbvnaWWQdnqmx9Hxc9NuPvCkD1i7b/qADwAUyh/vu2MdhIIUPRuEbPMXODH9YAK1zmw+VsprmFL7TyAAAAAAC6gAMALJ2zGgPVBt+V+wDOZ1L1f2Id4YrV/5ST74nLrpazaKnQeWqshxTbs+Rh8HRdDjN2Aui3dX8R3/a6VnbRNr1vlMN4m+6j/nd4BbpXPiHHdgXwV+l8V9qwXRCuFlQeAAAAAK4TOABAJ+eH4vRfAyL3FswlS/90uDl4JRfmCRraldTM0SxK+j/EkDl/6qu7apZSEy3p9Se6Gvv5AKh3gH9ryetOBDSn+r/A6j4m6xp3/nrUmnsycfKfM5c9kxPux/Fu4aU8kwEAAAAA5gAOAHDpoBPA2Qe6YvW/z5FD/Xc4IqfWuDuyWlYl/RMC/6+YniqbtxPAcfaz35aOihagzsJX5QOYxkD7VX7pugxmC/8/ctUTXyqrS/5zGbcSrBMAAAAAABwAYAV0N+YPKxHwAZzKiEFYSwj8J6j/HvrcFtPFDk+9yTNYm/RPA6btxoO0wbKqZIhEQBPswFG76Sl8iTa9+4DGzvjf/Opu+2T1f0RG3fDZKz/5fJ2T/GfJNqiNAX14F/dIAAAAAAA4ETgAwJJof+XvaAzg5X4kpgv8n6uz9zFZAq6KDhltimrxbruYucwT4xtwcrEg5w9IOUplO01dLzfR2wdA53QFuKZEQFOo/y3SPy3zvl+2+n8ya0/9f9rNOEgKUgAAAAAAUAAHADjElMMAjLeaK+4EcNrxTaAsjLWZnkD976R5Z8wm/TfLLOr0VPd5UbtWo167kP5HY4kVM0QnAPexcNB4cmOCmKTfPhzrBvCWXNqj4gSmdMTWDbQ3wN8dD6B7VYPS601t8er/LFfj2m+B01jiExgAAAAAYG7gAABrotmex1v+4EwR+D9Bk/RUfeI6W8teJq2Ko+7ko9TB8XB9ivPtxUH8VYvQyoMoIk2iiQNhInph2c+9S2fiU8RHUsnbLhBmIhks179bsizmqMUX4ANoMm/gPw1x9w///Lhc9X/t4f/ncHLXAQAAAAAA0AQOALAwDr3vny79XXEngP6MrixMI8ZA/V8Xp92AM2p71yz9t653Tdgbkq0lTRIIaRFNpEW0kBbWorSwtpxNW9bCRLTT8qLpRdOLohclL0wvTC8sL/WVr656et5JJ3QCaNteqi+fGeDfLNZ/nStl9sB/Oje13ul0XWzLVv/POWtnqv/nXzADJuKfPfADAAAAAODK+X/svWmX20aytRuRCYCsSbItybK7z3nP/f//6a5113nd3RpsqzRUFYkh437AwASQAAESJBPgfsRVAkGMicS0d2QkDABwFqZ+9p/4KX+5HsDw3VpI4P9eOkrEh027Uo459S7iAcxB/Yf038asKLk38iqTh1Tuuv4pYwAAIABJREFUY7qNSQtz/iFmISYmyQco/0tC+a9EbJiNYsMsTEZxqiQOcldAYk1bJVslW0VbNs8s8aX3tospEgENWcmQzRi+LaObAiyiEcAZkrA17Pm9WcJ8vAAsV/3f4WO5n4lR/h8AAAAAAOgBBgAYwHxfoPtZru4/lvPEFY7bgoMPzfgZF1m758EkuSTOdvzmK/3Tlar/ElJyL8l9Jg+pPMRyF9MqFZ1xYFhnhfRPhQdAlRlAwiwqH0lSxKsbRaLIMOU2gFFKSkvAKDKcD0gcmKcofYzSL0H6qCi9cAk4ODoR0ASNAGi0B0CDbQBnqkBvT9iBzEX9P+lFwlHZFq3+y3GbMfc6X9F//s7wvgQAAAAAcBlgAAD/ON3LujQlPO746aq4TNqfvWuVakFjVzzu18W8JM+Pqc64M8h73kv/fWV5ZdK/KEofJLk36X2a5aL/OqHAkM5EG9YZBxnrjHVqh/9zGeyvWMp2AEJUNhHIHQGjWPJGALthMvlwYQYoo+hBr95qswmTb+v0cZV+CdJHJnPpctnHqERAx09TrXJs6p6hp/vM7+m2xXKqhXep/65VXlz9P/f6oP77erOzmfMpDgAAAABwbmAAgHPhZwzezDWCfobINKdaaY/6f7pq0LPDkP59Y9rz7qSXF7/Vf0j/OaJo+y5LXqXJfZLdxXKbkM5IG9KZBBnpjHSu+2dKp6UBYCr1vwj5p6IpQC79l+H/QsQkeQsARaJKA4BVYQMolY/JDYA0oDTQqyC829DbINuE8bd1UrQJ8KNAj04ERJM0Ajh0i/Z6BotJBHTye7RYY7pXOa5d0Snr+O6QQv2fG8ecg85mPQAAAAAAYBQwAMAwzvz2fK7VXXMjgAuk/bmUAAP13zdOcaKd4qIxX+mfrkj9N5pe3mbbX7fxmyezSqmQ+/OQ/3w4V/wz1qnSqdEZq1SplNjkaX+Idpl/iISJmJisAmAiJlIkqnAKlMptACmlf1GVGUBpyFlAaVg6ATq829A7nT1H8bd1/BjFjzr75mPhHpwIaJJVTtsUYO6JgM5mz9cegXrm6p3GsZyTVXDhE1+Zjlv68QfuePV/RvV8OPb56+PVEwAAAADAe2AAgDPiw/t3W7SYJp+xd/Tsx2Uy/p8xSUv/+IvXwWvndKfYhFeY+kb6Vmcg/eeYgJ7eZtt32/jNU7ZKJEooygP/S+lflcH+OlUqI5UanSqVUnGxL4OJOR9gknKghG15moWImIk5Y8qYRAkpEibOjQElikzAWUhZQFlIachpQOVHr+Lo4YV+1enzKvm63n4N4y86ebpQWR/dCMAOxJZ2BvlDt2RsU4A9HkD9nu7DM8ilcN6jh6r/+0rNcfRP9DTFJ1x2tfyDmax2eaD+e3imzOS+BAAAAADgKTAAgK9cpBGA4/vSuEDG/2EpBY7aMKj/IGeS68Y1B/7P5OqXhfTjTbZ5t4nfPJlVYsJUooTCmMKEgpRUWqr/KatM6ZRUKrl8nyfzIVvo53qhtYugrFVFWhlqXe9yY8AQGaUT1i9sQspCCnIzIKQ0KP6WTsDq4fn+t2D7ePv08ebpQyAX6SvYdbIc2AhgbPT+vu0avrCx7QCuEIeIP1z996r46lfm6a9VnsjuM7kIAwAAAACAeQEDAAxmuW/SIsRX8MZ1oqPXp/6PyiZ81BYMYqH1d1aMPPz90l7nD8dkGh6waieHbOpIEPhPRGlET2/T5zfb+M2TiRITpiZMJIopTCiMKdhSkJBKWWVKpaQyJpYiz78iojKdD9c8ACviv7aymhqXi6a7i10Ri9x0BfKOBFIVJKyZTUBZSEFYNAtI878BpYEkgQ6S9cPL+qf7p4/rl89q6qI6kIEnkJQh9lwFZVvK/Th9tsOKoGFbsmeDr7sRgDPtD9lFcrT6P/ayceBlxmXnT3nFOnRZE1an49X/q6rbAAAAAABgFDAAwHmZsHn/tCwrEdDE8fX9axms/o8u1CEz9Ezjdxw36GfvIZs+HfBBFWa4VHokfUu4Eul/RT/epj/ebJJfnkyYmigxYWKCXP2PKYg5iCnYso6ZmImFmUXXFf9K9K8bAJ1Zt+0xzkYAlWRXfd19mIRVqlTCARdNAaw2ARJEJohMkL0O0ptX6x8/3T19jOIz9w0wVSKg45fZMdfAhfVMds2JgJxpf2hAYh/2rYA6TovJHtag/p9sUQAAAAAAwBNgAIDrw/XKOH/Nv4+Tp/1pr+N49X/oRuwfj1dZXzhNWw1pL3uswjde+j+gfcAxp0DnvNch/Sdr+vE2fXrzsv3p2URpFiYmTCRMTBBTGJeB/zHrWJEi0Zzr+1JT/IWYra/1LEDUGm6PcaT9aX2Vxod3TkCiVMxaVW0CRK9Er4xORUc6SFer7e3r2++fbp8+htl2gkI7hsMSAdUyAk132R3YFGCUB7B4utL+uH9qMObYnaNQe9cxwYEdP//kDxVXkvnnerw3AAAAAAAPgQEAxjDJwzsaAZyRuQT+O7Zz72wD1H+8as6Rw45a0wYYHjk8cr1nrlQI/P/2a/b19+fNT09ZmOYJf0wYS5CYMKZgy0GcS/9Mik0e7692En/Vu29D9K95AxWDDYCd/9luFiDEhe5fcwKkcgJipbakA1KJ6ER0YvTK6Eh0pIPs9m7z4/X9t0/rp496qgLcw/GNAJy3Sx7fM3DvlgzZzD0ewMilzZcee36I+j/8qB12/Rj3eDVgUocHPOny2+ualknUf4T/AwAAAACAfmAAAO8515t686V0th6A7WKcZOFTB/6P3k6k/Zkjw86mI49a7aztv3R4L/3T1Qf+Z5q+/CP5+o/v8d1LFsUmD/wPYhPGkuv+wZZ0okixaEv3twwA2RkAXIv6z9P28K4gmLqbDlk1iXeKvxBxqf0zSamFC0lT/a9/FIlRZDj4QSo2KlE6KZsCpEZHr9+ldw+rb6/vv31abR7PcpxcZ8phjQAOX8SAWYY0BejzAKSsDodunf/0BP73jbF+8uvCMGZrDrEBxi//JPik/gMAAAAAgKUCAwBcAm/fvBsyxmw9gHME/rdXc5D637uyMT91aXdgPkxy1GoyUNelhmsTD1zmOUHg//OdfPnH5vv7b+kqzlbbbLXNdX8JYtF5wp9UEbOppH+XAVBT/J3DVAy4S7XVAkCs4d1A+bewA0TI0Qig/JiyXrLiRAeJcCKcGJUYvZIgNUGkg+xNmNw/3Hz78/brpyh5maJAxzPwRm03AjjDLXTvVvVNUN8gb59EDmPvPbrp3zeYtiyOP/AHLaHaif1zn8WT3r/8OVyNAQAAAADAAoABAEZykTfmSzUCmCEnKqo9gf/OMcNwzHec+r8kQQcczM4GaOf+sCYYuJwz467mk2zKTC5wX37JHv/x/Pzzj0L9jzZmtTHhRnRMwZZVxsRsFJEibkn/Yo8hlxnQ+GsN1NTrRmE5r4BtJ8Aa4Lr0X7QMYMsGYCZRasNhLJwYTkQlRq2MSjMd6SBd32zuHu4eP95+/XzijEDH3Tly44PbtWvqRgADJ+kx/pZ6g9h7jx6i/o+6PJz2WjJdULx7Sb3LP1slmUT9n3Zrl3qCAAAAAAAAGADgQox9ET/bi/vMGwGcXP13rsA1ckixQf2/FgbUhlMcO2lldhm1lkWp/zO5jhlFn98nX//xfXP/kkZxGm3NamOijYQvJnxhlTIxi9pF/Ysd9W8n/1FEVAzvegLo+UtEdjm3y6spr7a8pLr6T0JSpQkylhNQhP9zMWyImMno4DlvCpA7AUatjIqMigKdRatU6YcvHy7wwHbAjXeCW+gwD4B6p+r0ABaXCGh/4zwqMld1MnkRHHm1mfRi1dw5b66EHqr/AAAAAABgwcAAAOO51BvzWdZrZzOwRp18vX7SVBamC/wfPes+9R+vwbPmdIfvgCWfykU7bJprCvx/uZXP77fffvuaRnGyitNoY6JC/ZfwhVkK6X8X499O+9PI/5MnphlsABR0ldeeRgBlIqGWE1DIzo0sQLtGAPlXxakK411GoLwpABsmef/fRgev/vwjHF+og+m4ww688dqNAGr3zFPeuA9b9pI8gAnU/8ailo03O+ln5p+T5zs68fIBAAAAAEAPMADA5ThNcoCTcJUewN6UAj3HYm9puWcdEOPvHIm3SjAJF6xIjjp+TdI/ET3+bP58//z8y/ckitNom0RbE20kejHhCwUbFsWihRRX0j/XFX9pS/+lAUBstQSp/5XqV5v6d+nXWaUUWPO0P1Q4AVKOqX6qqf+5WL5rBEDELELEWm2psgE4S0mYRJG8/6fR+vXnPyKTHVvUnRx3h5VyGe3xp2gEsHfCvmXM/54+RPqnIer/QUe8r/CGtffqnOp0x8WbIz6V+o8nHwAAAAAAMBwYAGBunN4DcDQCuDIGKQuTx/4fpP7jBRgcz6lr0d5rSXOCK5P+henz++TP9z+29y9JtE2jbRptsnAj0YuEL6RTFs2Up/2xAv+lJ+q/ZQBIFZ7eZQPYtMpOav/Vh+spgOq6/65NgFgpgFiIDMlO+m+2CRCj9QtxkpFhMoqMyj2A340KXv/5xzrZHlzYhzDirlveOs/Zju4AD6AxfnaNACa7R5fTTHZ0jlwQ1P/hy5lmMSdcIAAAAAAA8AoYAOAgpnpdPrABPxEdsQGHvX1djSUwibLQVVqd80H9v2Iu1arnPED972ezls+/bf9+9y1ZFYH/abgx0YsJN3naH2Uq6d/+NKR/K+O/0wCw/sou5t/+a9N19Wpo2vaAWPcz6RgoP5K7zIYt0b/RGoDIMKdB8JRL//mHSd6/k0C9/vSv9fb5NMd4ukYAu/I6+EY/eK5DPIDZJgIafo8+92UA6v8+vFX/AQAAAADA4oEBAGbLKd/Xr7YRwISx/3b57ZkD6j84C+evM+PU/yuT/ono+Vb+/d9P3376kUTbNIrTaJOGmyx8kXAjwQuLYlHEdem/6ACgJf2zS/0XR/KfWjsAKYeb5VbGse8Qa5ra5U2EmCuhn9wGwK4FgNUUIJf7uRb+T1L1DcBMRusnJcIiXOQCondvHgP16uN/bp+/nc8DGNUIQNpJlU6cCGjktOUs4mkq9i5qFdA57Jy4i2kviCNLslkfoP4PX840izn5MgEAAAAAgFfAAACHctlGANW8NGb2I9++Fm0JDFUWxhysoypId1Qu3lTBQHyuKlce+E9Em4g+/P7y+Pp7strkgf9ZuEnDjYQvpBMlulT87cw/xYeFm6K/MBELMTfC/8XdDsD6S6UT0KAR729PI9aofC224s/dA3ZXwIUHIMJcTwFEZKyNMKF+UmIyEiVFU4Bffv4WKPNB333/oo4+DkMZeK+27XNP7pkDt9zzRgAOe779tRw5qtgnOEZ+xv77UPlKfLaazlPtfT65AAAAAACuARgAwAOOfO0eEmfu8auXD7jV/3Ovu3ck1H8wGE8qSc9V58oD/4ko0fTht83fv3yLo20cbtLoJQ1fsmAjwQuzYSvtT9nxb8MG2In+3R+yhzvS/rQHnLTMgN1wNdCXBaiu+9tOQLVAI1Q5AVw2AsjXnXsAosLCA2CSn15JwOaDuv/ylx5Z9gM47qac2yPcKtGBfkAjMFyuPhFQ5w26Q/0fxFT7ecSVR8oz8yT4dEmcUP33sH4CAAAAAIBZAAMAHIFv78qTvu+5swB5EtA4Ke64woFjpll370hI/2Aw/lSSQer/VUr/RCRM/3m//fPd122wjcNtEm7S4DkLnk3wokiR0Uy7zD9c6f5WU4BapL8z+Q+RZRLQnvB/agw6LzrSmMi6H1jSP7ucAJFS6bQ9AENEJKqZESgfv9uowucI1bPWogLRIkpEibwiCZQJ1MPnz2d6lht3z6+aSIyp5w7zt1zrkVH8M/UADrhcDLokHLGf7Bg6lKWr/7n7N+XSpsar2g4AAAAAAE4HDADgB769c18NnW+mpz4cXSuG+g8OwqsaAvW/n3+9Sz69/7oNt9twmwQvafCS6RejXtho4obc3+j4t939b7sdAHV8pZbobxcfE/UflHbUf3Xf4tIyFhbO8wHxTtYvPYDm0pVlBuTTMJHZeQqtdQdqowLJxCgRLZKQKJJ//m40v/7waerHueMbAZTSf6PFxMF1duAWLeZpYs+14vjkPzw6WdBuxkngo+pDz2I9Ydq0P7Ou1bPeeAAAAACAZQADAHjDXN7aF9QIYGIt8pAV946H+g+G4VUNGVS7j9/i2V6F/vNLWqj/wTYOc/X/OdPPSlSv7t8wABrDTunfqf63A/+dRdke2WgBkIt7eWB/IfRz6QRUf2U3svNjZQcyRKpcj70LOzRvlS5aAJQf+vWt2mxfP36duj8A10151I167N2yc+KqfUW52L3LcQf7z6cRwCHq/8GrGbg064587OXHmn/KRyqfroqzUP8vXs8BAAAAAMDZgAEAjmPaF2UfXruvkDOk+qkYFBq9+4rqAPbiVSXZr/5fsfRPRJ9+yj68//6y2myCbRy8pMFLGjwb/cxGCWluSv+t2H9u6f6idqnyq7xAtY5/y4Fcqy9wqv/9JdvQSu2vdd1fpD6mHOBuG0DyXn+VtUA7EVC1s0REmmOl80YARhnhjO9v1Pt34XZ797I5R+UYeKPuCvw/UPC11nrMk8JAb+CyDyMHq/8HHv4zX1Jaq5vGA/Dmwjht2h+C+g8AAAAAAKYABgDwjFl4AItoBFDswajSPlJ3GfgTAv/BYPypJIMq+HWr/38/mP+8+/F0+7wNt3HwkhTh/y8sXJf+tbvLX1K5si+kdh0AkLMrYHIMSHXNq7qn7W8E4KRtAzg8ALaGc4k/zxDUjSqbAhgiVf51VhgmIkWp0k/KEAVKjBajfrn/vv01+N8/1sYM3pUhHHdHroqmPf7UHkDPBO6f/Lit1zbhPOr/menYymOL35udn1z9XwD+3KYBAAAAAK4ZGADgaCaX7GfhASyDA8p57NHZ+yaMwP9r4DTimj/1ZL/6f93SPxF9vZV/vX36fv+0DbZx8BIHL6l+ydQzibDYon81YHX/S7nuz9xM+9OTBagxUOn+O9G/rJWN7t67CroRIE516d/+qaH+VyP3fmz1X6jvvGFFGesXMooCLZmSUL1//W271f/5GO49FsdzwF168mvAMR7AkOnP/ySyX/2fNfsO/+G2kB/scnedYrFTs7z6BQAAAAAA+oEBALzEAw9gz0ucH9GCRzF5DoXGNEOW4xpz6SMPwDiclX1iLW/mV5untfzr3dPXV9+3wXYbvMR6k6f+J8pYdDPhj+zN/NPIAsRFvHz+lcsBagyQVY7O2P/+Um7E/lNH+H97Hi5aH0g1ZTsdUJUCyBCpoimANHZqt/78SqkpIb0RoyVQlGkJ1W8//9hsX395nLQzgOkaAUybCOiYTetqBMB8GQ+gWRRda51v+P+w7RtdK/zY7RNJ/3Sy6nfOpyw80QEAAAAAeAIMADAFp3hL9sADWDJynIVx5Ituh/RPOOZgMJ5UlT3q/9VL/0S0CemPty9/v/6+DbfbYBMHm0Q/Z/pZOFWiXb3+2gYAV5l/WvH+HeH/UsX7twP/h9gA/TRsAKcH4GwEQLXh5hVYWS0AXE0BhLi2m8U2BPxCSlOg80RA96vn338Jttv755dJ643rdjzqFj2lYz7GA+j51R8PYKj6P2QJIzf3HFeXkevoSht15GJPB9R/AAAAAAAwC2AAAI+5nAcw6FVuvo0ALvj+B+kfLIju6jxRhZ7pFabOv95tPv/0bRtsi+Q/+jkLnoUTNruk/1Ik/LEz/9i5/rvD/50eQF/4/0705+ZAhbPcu2ThgTeq9ixdLQCorv6X/QE0tyRvUCAhv5BSojUFSkL1y92P7Zvw//v3Opu2M4B9u9RDV+B/+xY69KY6kQcw4SwHM0L99+keOehIHXH52rN8Dy6MtaMxH/X/zCxjLwAAAAAAlgEMADARJ3pjvoQHwI6hDmbnAUjz2/k2vzdSGm+Jy2fS2uZDhelU/yfcuKHRsP7y6VX2+eFHof7rl1g/p+rFcMxGMe+C/dlK/rPL9V/L/LPT/bnWLMDO+cNWvH+X9N/VCMD5tfFTf2h4T/h/S/23ZhUiZmcLAOEiEZDtAQixIqIiTl2EyYS8IaVJawm0ZOq3V183sf7Xp0k7AzjuXtyVCGiq7Tls67oaAeR1aqzDc8DaHas+fjmXi5yobcPRdF78Ln09dOb5Ou0q5rDky64LAAAAAADsBQYAmI6TviXjTeJ4XGV4Dg8A0j9YHOdQ/ytmZzSWbDV9fr3ZBNtEJbHexMFLol8y9aLyvP/t7P+1Xn8tiZ/UTvdvR/1LI+S/IfT36/5dToCTxt3Iqe/vXYKrHYA4WwCQ1RSg8gAq86BqByCaEio8ACWhkkz94/X3Tfz6r0c9bKvGb3vfuE4OD/kfsD09WzL6p9ZmTf50cyr1vxp7+jur+9hNfaWq2QCXvgyeQfp3rGUOS77sugAAAAAAwBBgAICZcC4bYET4f85ctLnuonPswSTyQX8o7Z6NAktkLifLSE4o/VfMs+g+vUr+vvsR6yTWcaK2CW9S9cyievP+F+r/rh1A7a/7Uw/875H+88YCTtF/ePkW18dWpHj1U1f4f039r6eatxX/dlOA6i/vbIBigrzHYAr5hZUipUlpCfWdUf98HW6290/TdgbgYuDtoj/5zyTrPswDcNPqDGAq3Du+d02uCfrK8MyREyeuZXm6q0tdAt2lODf1HwAAAAAAXDkwAMCknDru7MTvtKPV/7kwWXKEYTMOmABvuaBibF3zqvKcQ/23VzGfS9O3SD6/eo5VEqs4UdtEbTPekKlr/bwzA1ppf/paA+RKftnTb1v3d0r//VH/o0v2iBjx2sRcDCoiIW61AJC2+l/NU/4VIpKiQ2CtJVMUqJ9vf/zXT8H/G9+k2dg9G7jhoxlShc9vdQ20DU5oi08Y+++c6ERXp7Mdp1bEwHnW3FdsM1T/Ef4PAAAAAHDlwAAAM+Q077SHq/+eB+cOKKjOtvyjCnmY9D9si8BC8eNkcdbAURHgu4Ez12Y/CnAIHx/ib+vnWMWJihO1TXmbcaoasf/W1yLkXyyVvxn1b311pP1xxv63A/8dor/sfp0cZ/i/GyFiaWcBqja+sgGYpGoHsPvLZAJ+IdakFWVatHr/8O1lG/zv35N2BuDiAHG8qsjH1ugpGgG4f5o6EdC51X/n1JPuwKVSCJ7OCRhUPFD/vVkXAAAAAAAYDgwAMDWnbgRgryjn6NUtNvCfRhROXz7fnoUMLDFrMrwcggvSU/0OEbMuUpvn0BTg843569VTHv4fq23Cefg/SxXpT/XA/1LfF1L1oP4uM2CoAcDFAFlFtq8FwFE3F67n/+maoAclefe/zQ4A6o0AiixA1VYKkWhKSb0Ia9FatDJa/3L38vl7+JIcsCOHbn4vJ6m8Uzx1dC2jMf6wVXXu7xnU/ylnPi8DNrVRfgcEb4zjZKUH9R8AAAAAAJwaGADgBJzNA6hWZzNm1excwgHMJyy3h86dOGbXIP2DBpc7WYbk2T6gHcBl8NgGSBV9fNg8h5vcAEjVNuOtEaNEE1vqv7DVGqA7+U8V8s9tA6DtAVCp+9uiP1OR1J2cA9Y05X999FcT56+Dbor1VStiW/fPf1dMJm/9IKS4mKHoDIBEiCigDSlNrI0KUhW+Xj+/ub/548ukjQBcezNhI4BDrhDW6o/xXpybeJLAf7qE+n8CTnI5P3SJp33GgPrv07oAAAAAAMBYYACA03BmD6Cx6smmmjnjD8GUL/P1BeHNEPRwhgvGwOXPzMvz0gb4cJf+9fBjW2b/T9U25Q2LKnR8q6ffWpj/Lst/+1PNu9cDaA83/joHnF+H0FVfpqhHstP9640AynYAQo0sQPnfgLdGRYFKQpVEKnxzv/nzR7iZsBFABwPP4lPV2aMvIu4FtDyA4evp28fxm+rZWX4a/NzJU27VYh6NFrMjAAAAAABLBQYAuEZO8jbnm3B46NvYNPuBwH/Qw9lPllGVcODWCRFf0Om08ckGeArl091LrGJb/SfhZry/0wPIuwEoQt2b01Rd/vb2/Wt/JSKu5ffnhhNArVLbW4jdir9MfgCcHgY3nAAhYisLEBErygKOM45DFaUq/Tl8fnN386/HSZ/3jqv57Qo7QSMAa6umbARAB3oA16P+T/7M4BdzVv/Pdnfy4TYIAAAAAAD6gQEAToYn0piFty+Y03NcyR/1Po/Af+AZB1RC3+y8QfhhA3y4Sx5vn2KVJCqOeZvyNqOYjWZWltxf5f/JU9m44v2rqH935h+3AcC7YXL83Wn0XY0AnGMqjpCpR1WposraWYDsPgAMUdUPMNWyAJFw0RnANlBRpqJQRaEO3t5t/vxxv00Hb8ChjL3t95TKRc7Bzu0fuTUTqv+XPqFPj897CPXfpxUBAAAAAIBjgAEATok3HoDPL5jTM0WZH6IlQvoHw2kJaie6Why8zCGKn0eNACouagP8vTafbsu+f3mbch7+n2f8V1IE+Ldi/3e5fVw9/QrtNQCsBgFUNhegdjcAHbq/s7AaI/uLtSubfX2CwfWkWoRIoxPjpg1QFmnlARTFqCkLKM44DlQcqvCX8OnN/frfnjUC4I6BC25V/wKGNALYswtjto0bX7y6ztQ5vMWGt5x426D+AwAAAACAMwMDAJyYi761nvvt0oew4UlLe8QOWdPhhRAsGx9O9D1cwgYQog9326fVS6zimOOEtylvDGXKaEvftzP89Or+Iz5UDdd1/3KA7bIY7gFQ7wR7g9fzpEBHVhanh1Hb5bIApUyaVCQCCiQ2HGccpypKVfh2vfms75PsiG1xbl3rcj/8nt8ummkTAR36ewdW3apOr8ZyplL/3cvx2wMYh+cXUKj/nq0IAAAAAAAcDwwAcHrO/tbq+avlvNgvwUD6B4dxeh39yAo510YAFee1AT7dZp9vf2xVEnOcqG3K20xtyTAREytL9FckLMR25p9aWv8yOxBRlf+Hhqj/1jDV/kprTG3A+bUfu2rksLmnAAAgAElEQVQMDGE/KNI933KuAv+pLMCiDwBL9M8LWYqRwprSQOKQ41QlqUreRE9v79f/+XqOp76BJ4Qto/d7KQdsAcvhJ2Xn9pdbM1r6p0ElcuZHl2lXN+hI+f9wdvot9PNecQCL2REAAAAAgCsBBgA4C6cXyPx/rzwHpynkThWRHZMBcDDeCultmmqXz5tuZy05JY9RulVJwkmsivB/EWHSef6fMlC9lt+/1Z1v67PLDlTtQ/+YbhvAIf0f5gE41f/Gr+2/XUvoW1ERcV4kQarZAK2sR4pEysnyRgAS0DajKOQ4VWGkwner7WcVpGbYXg7kuJo/JBHQKdoB7N3qvR5ANdkgerdkBFUZzYu5PJ8tQv0/T+2YXR0EAAAAAAAwAMC5OI1A5t175QWTg5z4hWxnA7R2EK+C4EC8T6YztBHAObblaE7ZIOBZ0ddom1KWUppSkvI25a0yutbxb3Og50Pd4ysa5gHVBnaXqm4PoGi+QcWw/ZMbqwSZXE5AvS40r4z9zkHPxCRl/8ZS6+i42t+qAwC7JwBWlAQUh5RnAUreRE9v79Yfvuve9U7D8Bv+ZIr/1PR7ACOk+PpEE+zaaZ6mjsdhi86FRUj/C1sLAAAA4AO46+XM6LEO9AADAJyRid5acfW5GCO6sQRgGCdT+6aqq10bOKdGADansQEeo+x7+JIbABklmaRF8p9ClW6l/ucOGyAP+eedoL+/lcAu9b9lAwhZX+0dbnkAI8rCFvd71PyeRgDWlCI7+6GJPZm96jxvEtn7zsWWsJBitmwAESIJZJvRKvcAIpX8utp+frrNztIIYOAJ4UwE5CzQqTZsggVYuYAao2sjT3dBGOE/nJWy0cqsgPrv0yoAAACACtx3/GHyYzG/J8ZFAAMAnJeDXshxdbg8OAbgLMxFRW/gSczyCKa2AR6jJOUsoTSlNKM0o8TK3lOlqudd+L/1q9MGcIW6d32oY8D51zkwqiwaZdcQ9/uTAhE1PIDmopwbI7tdEHs37RLIRX9uNALQlISyzShKKY5U+CZ6erdefXieuhHAcedtT/6f9jTj6N6wYy81rjOI7Z/OQLvinJ+ZXfjqnGXjof4DAADwClzPgQ+Mqoezft70ChgA4Ozsi1zD6X0Ip440BOB0eC+fD2kEUKSTmdcz9UQ2wA8l36JtUuT/STNKjKRKdBW8LzUPwPHpSOZjK/WOia04bGsupvpCqiWUf+2of0cMfo8E3ZpALO26tiinGSClfN8OcO9ZdUMeZ2uAW2NUa1g0xSHFGcUpRSsV/7rafnq5NWepqMNPiLYHMFlqoOPOyj1ze3K+t0vqDGvpwPvLOREtSvo/z4o8qekAADAWXL4AWAA9J/IMHjt9AgYAuBDWWzVOWh/BUQEXwk8VfWmJgGyOtgEeg+xHuKnU/0wSMnkaH+vTCPnntu7PJNVc1Ipw7x9DuwGxxzT+tgakMbIHp8hqidVSDgxKATSk0HfTcGUeuMuHS3chL8CyH2BhItYU5z0BRBynFL4ON6+Cm8dk6kv8cTV/YB300QPwkCHlOGSyQ/HdAzj9xp2zwkD9B2AW4DwCAIBT4Ly6ev0gelFgAIALwPZ/eCDyDVwvwfnxXTEaxLx34ghN8DFKkiL7f5pRUuT/EdXX328Rht/4kOUTtCewcToBjQHnX+eA86uTRrR+ywboTgHEtcmoNXt7RdweFsldk3YjgKpglRTrqj4qkG1KK02ppmyt4vswe0xO8PjnUspHqefdyZKa05x52+bnAfRzFgXcx4vhWbYJ6j8AswP1HAAAlkTjqu7jQ+mFgAEAzkHfKQcbwB9waQRz4FJi3JIbAVSMtwG+afkabfPw/5SSjFKRjCWo1GdbrLfKqqHsc7MX36a47/yQYzgX23e7Uf9b85/bw3txavdClK+0oV33fG0vqmdFjWG2ltbzKdsBkChKNWWaMs1ZQOY+TM/5+DfwhJhS7j9mO04y9zXilwewOOn/PKtDnQf+gNoIAADgMOw7iEdPp5cABgCYnqmi82bJTK8oM91ssCTq2vCR14MTXU72Slp+aV4HM8YGeAyyp2CTFi0A8vw/qpn/h9hKUFN1CVAK91XSnl1+G2rZALsxLRehGqZyadXX1l8hxyytr73HsS3fs2ugywMgqylAexu6VlQOi1hlS+0SllZiJSZiMjsPgLL7IInUOjbdaz6Y407daofdzR+6xwwFHkDJeS5TXlwPlyj9n2eNi6nt4DygwgAAAPCfEyfC9B0YAOAopjxz+qMhwSm42isf8BMv5KIpWIZSOOz56EsQp5QlRQcAaZ7/h8WhRO9sgKZwXyrXtbxA1Jx3h3MJjQFqTUOtr+3hBs4D2bhXccdAlwdALX27vQ29HsOulPobAdRsEiVp6QGYe47vA/N3rLp3fGImPyGm8gDGbpjtxoAhXPKivlDp/zwrRSVfJDisAAAAAF2xDQADAOzn3CcG3rAP4DAZAwBfOVIxPJ0CvzdRyWJcjIKqHF179VXLt2CbVOH/lIgYFt3Q8dldJA1x36npt6dsj28MO82APBS+sSeNrerayAaNEmnJ+tJW/3usgsYyGystev51rWiIB7D7VOH/mrMVJffhyQyA4869gY0AukY2luPm6KvD6S4vi+Qyl8TTr/JSdQDq/wJACQMAAAAXZ2mv7QOAAQAKvKv6XcII6GJviXl3jAFoMSwR0AwEuBls4khckRJfguw5KDsAkDSjhAwTcS0FUBX1384LVCjmRMTlYOUWNBV/a80NJ6AxTK2vRFQFzrcWNvrK2PCo2ZLpdwOtv9Qx0LUNRXHLrozKWYSpyASUF6nYM5Ub0DwEilItqaYsoEyTeQiSEz4Buir/ASfEkR5AH43iP2gBCzu/T8pZ36+WK/1fdtW+gaIAAAAAwJFcmwcAA+DqmF/9npET4EnherIZACyUoY0AFqkRWjaAED0GsR3+bySpFOd9+X9a8n3VE0DtV3Ish6g5b2244QG0xzhtgPbXnv2vJm6k6WdrzP4WAFbDiCGJhsiqZeWSrSJtFlS9tJmMklRTqikLOLvnZK1uNqfoBqCbgSfEqDa5PRmUJtugk8x9dZy8qfVZnnwue8TPs/autaC2AwAAAADMGhgAS2ZpOrDnTsDSihuACzGsEYDnLDyaQIiIHkP5GmyK8H9KM0pEhEW19Hpb1m/L9z3qfG36er1wSv9UX1oeDc9UNCxoL59cw/106fLkkPtruYCoY2C3oa11da2Cyl3uOTkch0BTpk3ZDzBt70Oz2Z6sG4DjTt3hiYDs6Q+hdhwOmXuOF6gLXppOcmE8/f7M8SgfxvXsKQAAAAAALf61vQ4MgIVwPVWWqL63nrysXNcBAODEeH8fHpqQZKYC4TC2ZDacZGQyyjJJjLTz/1ARis52wTi9AbK+Ur0gnXYCdc9SX2Y984/U4u7JdRh7ap59LLt0+UL05/pX12SN1XU1AqjZANyct+vTXhArSZXkiYBMSNmDzv6k8/UDXG3J5ImAjqQ6Woedqos+xU/ClE0BTnyb8OrIerUxAAAAAABgdsAAmCV+K2Pn5eJmAA4GAKekS1+7uO521YmAiIhoy2JEjBHDRtgYKRLKcJl8ZpeUppn8py3Bd4XzO6+wziW0B9pLdk7s/OqkKdZbcrzDA6jPw1JzBapf2qYC1ce3nYbmKqwpG0VX+xTdAEiWZwF64JQkHLrrB3BczXdmPjotPV7MvvkWeoqfkJ7uLwZxTdI/XTr5DwAAAADAgvE++HAyYADMgCupixPgVOPOti4AwIQs4j68iJ3oZMPGiBgx+UfE5Kn/7Vw97CqAUgdvtACgln5NHV/bI7sWVa2/R/0/4BA1wvA7PABpiPVtKb+xAVL91+oewGkwNCR+OzuQ80NKMi1ZIKkmc8/xbXDznJ4yO7tLGh8ll7fPoHOcU+OdAHgABzO69p3s8Ht7BL3dMAAAAAAAMCNgAPjIggWjc+MsymPepXBsADgbpdTXJa5dXHS78kRAMWVSSP8iRoQMkyamneIsLLkHwG49moTK7EANNZ+skc6odmpNXA3vxggRS3t8j/rff4kfmAKoGnCG6jdmpHbl4Oa3fB+cC7cnaxds/SOkKdUm1ZJpye55+6DMc5UF6HQ2QIuBJ0RX8p/z+Wpt86F32iWe5Wdif4OAExzyWRyvs23kLEoDAAAAAOAULDturwIGwOW5hnrmFyhxAObCPg+gh/PocUMTAS0Ow7TlzIiIEcPGsCny/Ihjd0WchVCo0i2JvxruoSHiO3X8ph9Q/zpK+rensfewS/p3zttQ7Z3GgM2ARga7T8sMkKolQdUmo+wGwGSBZAFlD5x+pKhznZNw3Hl4gURAPewLKZiFB+D55aiZ1mrSzfX/6Nicc2vnVTIAAAAAAOAAYABcAM/fvgAAAAxnkAcwC2lwDFuimDMjxlCZ/8dUPxaqtOzkO1f4fxOnDUBds0gzv9BO7nc1wuhpBND+2k8jbN+lzjuS/zSMEW5p7e36MaSRQWP6XUHVy6f4SVEe/p9qyQIx95xSwwComLBBgKvyjzohLpMIaCBdx3VZ5/u5KUu1y0zrYRkFv4y9AAAAAACYCx69X5wMGADnYPHVCAB/WJzQevX0NgKY0+Ge07buZ0uypcyQ5Oq/GENMZdoZC+HKBag/VDFJLlD3SPNDnID2DbYd/s/tkreqVZt9kd7FND0egJPiVyFmx7x75nIMNPP8WMu0e13OpynGEFPGIkqERDTLnvWfMi/QwBPi8omARsJlzqYaCzr3T8Xgw7n4sjzzDi6+PAEAAAAAAMEAOBF+vpQCsHiWJbGCYzlbfRiaCGhBFXRLknCa9/xrjBE2Tp2dqF9fbngD7Yh+J10eQP+9tz1Zl3nQM3sj005PPh9HI4B9czW2YUAKIJGmrdKmtgZmEhYhERbRZDRR1r3PtSUc82RzXM3vWv8kHsApzkjH7vrhB3j3dOrdBl0eqP8AAAAAABfB2wCjqYABMBnLrigA+M+CxFVQ54ieAM7GtXUGsGWTiTFkhIyIMXkLgF00eiNavzayVRSjhPhiAlf+n8ZXbv7EVN8koj1NARzr7fYA6mPcNbXfM6i2yB5TqN9SuCVOM8BuBGCXfO1AcLlUFmERFtIsmikbeFKdoDXAAWd0o/KcsonCUezZtfY+XA8eHi1vuLa6AAAAAAAAzgYMgMPBKwwA/uCzNAwmoNsD8OfQL0zi72dLxogIGUNGxIgYbu19ecTa4x0jOwLzuZXr3zlLzxhrfGdFGXXcGk0BnKH91ddG+D+15nJ+dU45vA2BvZ3VUKn/5+H/JEyiRXTHzJ0crLhP1wjA6y4BLNptRvqmq/DkcjYhHh4bLzn/kV9eXQMAAAAAOAY/XyumAgbAOBZcFQCYL/5IwOCEHHQ3vnjdaDYCuPgGTURuABgSIZOnAGJRRNTRAsBJK0K/+bUx7Gw94Jy4f409a5mErmPcpeDT+G4A+jbaWazVuFz6ZyEWUWL0YedVY/MH4iqYw06IuXgAdMAOnqxxwJnKx8/D4D2LuC0AAAAAAAB/gQGwB7zIAOA5SxFUwVBm1whgkR7AljIjxlCRBYhMlXzmMEbG8ru+1pP5tP8OXK9zpDNT/95GAKdj17BgZIkzsTAR5SmARPSRTzlTpOAZWGp7V3XAtpzzaB04ZwN/Lh14Pp6OixxVf6oSAAAAAIA/eBtXdDwwABws9WADsEjwEntFzKERwIKfGCq2TDFnhsSIEc5bAuwN9nc2C+gJ/+9f2gFzDZy+xxIYazw5J+hL4DPWqTgAFmEu+gHWxoxOAeRkuPR+ykRAY7flnJeFya5CXTu2b+mj68/ir2LegGcYAAAAAABwBmAAFOBNBwAA5sLsYugX1ghgS7KlTMQISdkCIP+lLfTvFH/rPttvAzjHDOEAZX/sEgYetj6Vv3d8O7FNY+Kuubr8lfZIIS6yACkxQU9SpbEc0Rpg1AlRbXHPpneVeNdPZ+C0a8dT7Dy5VG2c8/0HAAAAAOC0LDWk73oNgEUeTgAAWDgdd+N+AdGHRgDNkXP2AGKShFIjhfovlLcAGKHj770F75tgknv4kS0JGvMe0BrggGmGzNWX14hJmIU57wNANE9dC/faAEc3AuDur3vnvTgXOe/xxOshPtRGAAAAAABwPVyXAYBXIAAAmD1CxG7V0x9JpV+XnHtMgRZWoliIiYm5OB7Hwq2BvonGLHBCGrXs1NXw4KX1eQAkQiwswsf3AdBFvw3g2q3DGgHQDM8mr65U4CJcvGt6AAAAAADQw+xeMYawcANgeQcMAADAAfggui0mEZAW0sIkTHmfsswkCGs+M3ntYensfsHtARQtAKRosqFPeuBGJgUaeEJItfP1MTPissmIwGWB+g8AAAAAAM7PAg2Aeb0EAgAAGM1BjQB8TAQ0TzSRFsVlLhkudmlE6R5cDuNnXEB5ExEJMU9Tf4VZmIpGAPoM54TTBpg0EZBzjP+cxwaYXbEsGIjvAAAAAABzYY7vF/0swQBY2CEBAACwnzkkAupnvo0ANLESZqnC/5k4PyB9O7PvEarWt+uA562Bj2TLe3LbQ784ziQku0YA5zAAqo2gQX07HJYIaL6c9BqwgPJZDD5c6X3YBgAAAAAAcBHmagDglQYAAACNlM/QCGASNJM2ioXZMDNzrQ8Au4Avnuti8pKW3q/nY28IfPnVUQGZcvNMiM/SAqC2ctptVMfZeD2JgCqQEWjx4OACAAAAAMyO+b5fOJmNAbCkQgcAADABHTdk/xMB1X6dYSMATaQNlxoycTM5jZQfmt++Hcu0+9vpprQbW3RUs26JnYmFlLrEA1ZlA0yRCGgZHgCdwAaYb1EsCX+ugP5sCQAAAAAAOD/+GgB4bwEAALCH+ScCIprd5pIi0qIoj/w3TLxrAeBSYJ37dqRUO3g9Ixayd5N6VrB33ULUWHx7FulZznB9vwyvdy9KSBlSQmxIGeItqa41npzu8j7mhJi1B0DT2QCzLoTFMKvrOgAAAAAAaDL3lwsbXwyAxRQoAACA8zOvREALiFnWefe/UnyIixLt1anF+uzS/Y/q0tU1trG0LoaUdM80Tr2+ZzmNv/vmqE1kN6FozNkO/Bdyf+xpdosV1rkHYIgNqUsaADTBeeisQDM9p2yOtAHmvvsLwDfp37ftAQAAAAAAZ+YyBgDeTAAAAEzDQYmAwJHYBkAZbt5f3uP092HdBR9Gv/9iZ6lvjJyESfpIGDijww8QpcpGACpltWFPn8jGnr/L8wDoIBtgAXu9AHDrAQAAAABYDMt4s6AzGADLKCYAAAD+Mj4R0MXtgaYIffENGolmZiEWorwRAHMZZt7JSE2/Pkbs/9xKbz0LUfvvKIal9GkO77VABox0bGnX8g+oMSJGmcID4FgFW48f00b1Bjx8/OywLxRDJgMXxM+ruJ9bBQAAAAAAzslkBgBePAAAAFyMDg+gh3NK7oO0yFl5AJqYhUmYhdjkDkZOZyKaHupKP5f/tWfnli0wKpeSM1lQx6r6tnWs0N8v3Is9xM25+5fWtSWNQ7AbNkqJsGFlRMWsfTYAhtPl8yzGA8hZ0r4skvlcvwEAAAAAwAiW8VqxxwBYwB4CAAC4WnwW1ceq114RFC0AKhvggM3fybalBj8klX9j9iEjneMPLX7pku+d4x0jXU0lenT8rmns9bY/RLtdqi1ESBnOOwDgLem4Y8WecGQiIOcYACbH27sM+b1tAAAAAADgbAR4LwIAALAE/E4EtFeInFciIE1l+L8wE5NxxK4TUYc8LR0pehweANtLaub5sX5jO4q/J/9PU/evq+R7D1HXmL1ZgAY0C+BGs4mGcF9zDsbr2kWZiyiRohuALetxy/CYngKBBwBOh+cXbM83DwAAAABgLizgneIynQADAAAA09PhAfRwWcndIU7PxAPQzExUhf+zsEhnKHoHzhh8e4A6YvzJoekL7Xsqc7UwkPYRINd6XTviNjzs6TsbAbgXWzMwnJM1BtwbYFkFzQMhpISVMcooZZhjVt274AvHNwIA4BTM4ToNAAAAAABAAQwAAAAAC8Kl/81EVJ8TmkiJKj9aSZDJ1jVhO6pdhMSV8Mcl0DuOW1uvd86+p4UB78n/s7e+SF2vtwe6dPkqTVJD3N9rk3QNkFW8DuultWMirI1RhpURFuINzcAAoPG9AbePKFwBMCFzuZvMZTsBAAAAAMAZgAEAAABgafiZCKhLae7KUOMzr5S64+hFNoFoZbQSnRWa8+5jSd5UfnUKs+0Q/rIpR2Nax7yNYbH7I3ZN3GMzDCz4roh+53jpmKY9cekEcGNk14xVUH/XdjssAUMqTwFkRKWktnNoATCKntNnLmcW8BlI6gAAAAAAV8vcXyhgAAAAAFgWpXrsoQcwCL+2xs2d4geOHklr0VoCZYJSwe8MSCdqT9DbDYBQd4++1KX+WwP1JTez/VDrEa7fBmgfknokPtsju/5SqyhayxRp/VQVRLupgVRdH+zVvoVEuAj/N8KJCrbzeYKdJBHQ3B/ZwQXx/pLcZHYbDAAAAAAATgoMAAAAAOBMqvuSGgE8cKhJaSk9AAlEsn3Sv1uCZ2vYFZvfwBm571D/2Rqu/2TPRS4bYC9Ngb70PzrhWlHY8/bMJa65Go4CWU7A7iOtMUQkSkmeAojVlvSMDIDhOKsRAAczRyV9jtsMAAAAAOA/s37FgAEAAABgcYxvBHBZmk8S3m6oxQPrkIKAtJYgbwSQUVr9au2R7BTqqpUAi0OqFanp8o1cPtL+0tMIoLEhldPQYzPsZux9qpOOr06BPt+lPeH/O7Ged6H9JK6JubZwl9FSzMLNMUQkefIfI0qEYw6cnTZ4y/BzYm9jCACG4P01GAAAAAAAgKHAAAAAALBExnsAF2wE4JjGew/gPlB3vHqmohsA7eoGwCWX28lsxBWkT5YfUB1Gsn6lDu3e0QjAmqot/TcORXvh7Z8c+1CboCnQN/6256qXT7MZwW4CqTkornkdxV5vE8Aq7wFYhI3wlpTf9csBEgGBszG7s6NivlsOAAAAAABOBwwAAAAAC6XDA+jhgqp7W432XKa80fTA4d+kNBUpgNiwywBo56KpPgMz8zileef09sDuL3dK/83Y//rquqhEeaoH2jcE+sb07vD/bqekci2kPi/RrnflroJ1FLtRQcZhSkEqQSr6WS35CbD/9HEedQBo/ur53LcfAAAAAMBz/H9P72LJr38AAAAAtWT9fpX/DB7A0IcG7xsBPKhAky4+JlASCKUuMbqGte+VDWAJ90J1db6eC2iXGGdUJ8D90r/TYGjTPhjtuH7bG2iPb0j51gKZOkqsPb0jur+1VY5PRmFKYUZBSvpFR1+CWT4BTtIIYO9P4Arx+1oLAAAAAADAUczy9Q8AAAAYRIfI56e03thY/wXK+0CvONjkBoBoLTqVxAo/p/4QdSKWXRYg6gzMF7G+9DQC6Ff/hSgPnW9L//19DjtpafRcjewS/Z3Sf1kUtTU3lP3d9FLE/jt9AudcxUeYTaH+BwkHX4P1Y+B55epk4MnbrlUAOPHwXnAYi9kRAAAAAAAwOTAAAAAALBrxsTOALlGy6QFwmVXeSx5CdU+rH/SiSSsKlAmsCH1yhb2TNUYsab4S4u1gfHsup0Y/KP9PdwuA/hRDTto7Ug0K7QR6sva98bXq6bhHwW+MbAy7/IN9n0yFqYQphQkFKenHIPS4Wk3GXg8A3sCVcw1nAQAAAAAAmJaZvkTAAAAAALB0OjyAHvxsIuAbkaJ7HQZJmQVIAhbVyvif4xT97a92kD41Y/N3NkiVdb9tFez1AJwTU4e70E9Nprc0ffvXhl7flPKlyu/PVmlIK5Cfh0j/u6U5e1yowv9TCp716nGe+X8qJjw9Z/r4Do5hkdf2Re4UAAAAAACYinm/AQIAAAAHc9nOAAZGJXsuUN6Hgd6qgLQmrUhrExhHNwDGKfrLTq2mDl2+HHYcCWcq/2F9AEh7RnuZPYjjK9vj2x6A0wyoDzv2zvIApLGctrnSM4EQibDKKMwoTClISX+LVo/a5zo1iAkTAXl+ioEJWapKvtT9AgAAAAAAUwEDAAAAwBUgoxMBXZCaIskk4q9A+RDoNYUb0poCTYGiwJhkn/q/8wDqw1yG9zt0+apM6jl2RmUBakzTWAgNrgv1yXZZj2Tf372B/NLxa3P2sjS6Zq99MhVlUoT/J6Qfg2jYbl4R8ACWjYcXeQAAAAAAMF/m+PoAAwAAAMAV4VVnAHN8bmhwH/JdsPqe5t0AaEWBFbFeqP/Szksjlu5fS3FDheNR0HACynFCvdI/9ar/zqYD9tK7aNcF6RgYov5Lue/2rG0boJ0CSMgqT0fZtj6GgrQM/38O1l8C3b2Pc2La3oAXcDKCNtcg/V/DPgIAAAAAgCOBAQAAAOA6KCVfrzyALhpypLfqZMB0H4SaVEBBQGFAkZEok5iIy8/OCXBJ1VQzA3bqvkv6p2JyqyjaEf1dR6xtBhCRlNOOagRgb8FAD8A5IC3R315sVT67r91a/24uIWEyRCYfMEX+nzz8P/garb7NP//PWOABXBvXo4lfz54CAAAAAIBjgAEAAAAAXIwezXEucuQvUfhRrVJOU4oyiTKKjNnW5emuLEBSH0/1UP0GjcIQ3o13GjpDEgERdToNnQ5EObpL+qf6vkg5dbXBTcm+Q9DfE9c/4GOIJFNRSmHVAuBrFLr3CBDRfE460AUEcQAAAAAAcAZm9+IAAwAAAMA1IbNpBNDA2yeMd2v1aXX7kmxDDlOOMloZio0khe4vTMTELRtALLGbbTOgLO9doTsTAeU58BuCfn8KIGpNQ62fKpzHXDq+dkn/7b+Wvs92IXQE9XMe1F/0XNycnptyf/uTUZh3AJBQ8CNcTv6fnOHnpljH28/zCByDD5fo83Odew0AAAAAAA4ABgAAAIArwzMPoEeRnItY+etq9ddLlKZJSlFKUUZRbLZ1ud/RGUA5zDUzwKHIu8peqgm6PDxTMcAAACAASURBVIAGPY0AelfUve76cL8H4BroU//bNkn/r6b8W9gAeVEbDrK8AwAOUtJfw9WP68v/M5a5nHQgByI4AAAAAAAAe4EBAAAAAMwDb6XJt7fqzfPdJolDDqtEQGLSUpXmmhngjnZ3GgBDxD3m/eH/XY0A6MAUQH0eQM9fe6Bnyc6S2fsx1oAhEmLJOCw7ANAJ6a+rq87/M7wRgLcnGrC5cun/yncfAAAAAODizOutAQYAAACA62O2jQD8fMhgol9X0V+bKFFJngUokzjZdQVsi9ROJ4BcBgATyZ5C5/K/Zi6grqn3HvCe3oCl+2tjy7vVf7Z22R3dn2/CLoqfGn3/SiPzT1GwXG8BQGQMqZRXKUUJF/l/HqNF5f+h8afk/tPH6qjB0dMz8AAcDQAAAAAAAMYCAwAAAMBV4pkHMHfe3Os3L7fbJE5VmHIUUGQkMpLWpf9C/ee26l1Mo+pR/H1ILqe76Q3/b7gFRVh4GR3epxBLuWLrq2Og+68MiOuXIRO0Y/8bLQBMFtwlvIrVKuYo4eDLav28NP3/cBxOQOu4i2Uw7UaBC4Gyt0FpAAAAAACAUcAAAAAAcK2M9wBOvC3uH2bRCEAxvVuv/noJExWmHGYUGYpiUzUC4JoNwI389YXuLyRcfM3/Nva0R4jlUsLv7wPAhdR/3X/s7XYJAz2AhqZvD+//lE0B7G5+TX3A7gPAEInRUcqrhFcJR7GKvq7vPtxFe3fsGqhOn9p51HFGNc+1UV1FgClAYbdBmQAAAAAAeIKf7+ZOYAAAAAC4YkbesT1pBODnc8abe/3m6W6bxqmKUhVl6SqjWJqNAITIkLA1UpHUw//ZVs9zuDAIqq91DZ6p7Q70Z//nrqUNQHqHXX/zPRoS/k+ODD+tBhNS70652QMwkSnUf7WKVRSr8OPt7ffIwypzFAefiaNOn862Aj5cCJYLShcAAAAAAIAJgQEAAADg2vG3EYB0/+QfWtHbu9VfmzBVacpRpqIsixITs6MRQPVX1YPiKz+AGtr6/t2XnbjP/cdTun8aihSz1hbXbQAU7sUgA0DETv1fS+/T0v0Nu/L/pHqd5uo/R1sV/nV//+Fuac97k5yeA0+ryjKq4YkZuCxQontBEQEAAAAAgANY2gshAAAAMA5xJwLq4ny6nzS/+Z8I6M29/uX7XZwmeU8AGUdGQqGsjPHPN5nLIleWbF1l/6++FlmAypE2h+29M/y/a8rG6tpUiYCkNVnDA2iI/vZw18cMG+/I/yOsMl6lah3zKlbRJlx9vFvHyP5f54AKhKYApwNFOBAUFAAAAACAb/j5Yt4GBgAAAICrx3XTvkhvwNIOK3f+6vrqA4Git/frvzdhqsJURZmKMl6l2Y+yBUAj/D+3AVxZgCjvjFeo2EerKUQ37QLZVz7Oth9dq2mbEI3hugFQJTJydOpb/9rK+VN8xNL3eRfjL47GAbv8P5laJ2odc5Qn//nz4f7j7dLk/4nPvsFnUbXeZscA0GUPAsUGAAAAAADAeYABAAAAABCNTAQE0a+LN/f65x93SZIkKgw4yjgSWknRGzA1/goZLkT/Sv0vw/+laAQgRFx4BnmDgC7jo+aejHQCqql6jupeA6AaECKq6/5EtrLf+CpCVnqfVlafappanp+G+p/3n5ypIFWrROXZ/8Pv65sPd6tBuz4TJj/pDnPRmheKs18ODl6bD5YhrpyHgXIDAAAAAAAHAwMAAAAAKITAUTreiUQ/qWLHu37t/uoDoaY396svz0GoQqPWojJSJjEZidkF9Df/Fuo/N3MBUb2MDRHz/j0eVCrtKQaUZJcH4DIAmlp/NeD8Wn16kvwIkSEWLhoHGCIRuxsANhnfJrxOVLTNw/9f3T2ufKsgh+OtAFrU63L7Tlfix5eAuxHD6fH22AEAAAAAAHAkHr6Vt4EBAAAAABCR+759gUh/2fMA4f/jxdu74MvdvUkz0kayjDgjztLsRxnIz0SGhIkNEZWh/a5GAMVftjoDKLGOSpkjqKNUuPGNy9j8A3AZAGyPt/v7JetvhwHQlfyHjBXyb4gtD6CQ/o2d9icfzniV8ipRUaKiWIWPd/cf7sJD99QvJj8HT3IGlRcLqY87khNdf6bdyL2rAEeCwgQAAAAAAMcAAwAAAADYceFGAOMX56EfEAX0X69vtnEqmRFtKDPChjjLsufSACAiFqn6AMhHNWwAsvoBNm0PoGOvq/KQnTVQK6VjCsxlAEg1vudvR9S/UJX5p67m15sC7DoDKD68+5oRGSFDzCmvErVO1CpW0VZHn17dvMz/KW8e0r+99PoWt7d/yAacWe3tWl3/pkKSBgAAAAAAYC7M/9UQAAAAmApxJALqV/mn9ACkNji8EYCHHsDrO/XP+D6OE8kM6YyyjFRGxphsWxkATGRtOBPnSndlAzCRKjsJICHDzqYARIMLQPLsSgd1D1AtoT3cGOiJ/aedpt/6tGyALleg9WFDRd+/q0RFsY62Kvzr4f7j/bz7/j2FvuzDaTIj3XxGm7pscCAAAAAAAMCRwAAAAAAALMar6VXA+bHrHTm55x7A+5/1Jn71f9NUjJHMSGaIs4QykoyIHN0ACBGpMrZdEVHpB1hZgLidEUhIeDfcCP8vBrpKq2EJlP6PPWE1xI2Rew2A1gA7PADeDdvSf62P37KVQJ7ufxf4X3zEZDpK1U2i1rFaxSp6jtafXq+MbxViMGeV/icvJfQPDgAAAAAAwJXh4ft4AxgAAAAAQB0h5hGNAIZP07NG5zjPnyH28vsv0Xb76mMmhQGQZcQmNT9IjKs34Apb9K9nASoKqvrVLqQhBdaepuUNCFFL7G8MDg7/p5oBILth2SP9V7p/d+B/+TE6TPVtrG9jvY51FOvw86u7v2+dTSV85wJR/0IyoF9pAC4F7CQAAAAAAHA8MAAAAAAAB6MSAQ2fxkH3PHNPBBSF9PsvN9s4/WKMZIZMln8y81SG7bcMgKIQC/Wfm2aAHf5vOtR/Z/h/T2k5XYEeutT/asAO/699rev+jU9HCwA2rY5/d+0AjA4yfZvo2zhYb4P1Nlj9fX//+VXUu/3ecTqJc+AZ4eG5AwAAAAAAAABTAQMAAAAAaOFSBE/iAeybepQH4CEP9/z7z3fbJJHM5LmAKMsSyjLZEtGuQ+BKs5eqQ+A85N9uAcBEqswC1P4Q5/MXvQu01f+GE9DwAGhAWYpruL8RgJT7V8v/U9gA0tD9JU/oX/MAbPVfiqT/QobJGNaZvk31TazX2+BmG66+Pjz86+3d09rzSrHj4tJ/sRE83amEgG0wHahNAAAAAABzwfN3cxgAAAAAgItWIiCa3AMYNt1wD8DPZ463b/Q2fvjfJJXMSJaJycQYMpmYtEy+4tzqKv+PHfhvSHqS2zjF/eGTddkA7ePUdgJ6GgHkaYXEGtno6deyAUSEjNUhsOsjGZER5iy4zfRNrG+2wc02WH2/u//j7d3XOw+rQJOTypqH7H/pARw4u70cAAAAAAAAAPAMGAAAAABAJ201fzIPYIxWOFzZ99MDeP8u2mxffXzMWwAY0hmZLM1+EBkiKvXxNpU9oLiZCKjrQ70pgNrSf1v13Xtghqj/1Mr8Q+TI/2PcX9lK/b9rAWB1/MuSBrdZcJMEhfr/dHPz77f3fz/4nvr/1Ar54ZVfrP/5oEVB/QeTggoFAAAAAACmAgYAAAAA0EGHmj6BBzBe2ulR9v0U/W2CgN6/W8fx3d+ZEZM3AsjIGJM+k5hqMiHhvKfcImdOLme3uwFgEhZitqV/7rIBqtLOswOdLAUQV3J/EU1eZvynzrz/Uur+XPcAxN33b575h8ikwV2mb5PgJg5utuHqZb3+z6+vPv2kBx+Qc3MGKXPKU0CIqDBtBi0WSi0AAAAAAADAY2AAAAAAAN24EgHRkR7AoXLhQA/ATz/g/o5/fXu3jdOiEYARZSgVNukzmcID2O2C7IZLGdaO/XfFuYs91LABhGtF0hX+Xy2iPaZrZdbArh2DKwtQ7UPN2H9p9ABsqND6pRX+n6XBbapv0zAP/19twtXHX1//5xdPn+jOo42fpMJL8Yd7zjoATgMqFwAAAADA7PDzTTzH09dFAAAAwBcm9ACOFnVm7QG8+UVv44f4PwkZYSElrERlhjPzQpKSdNolzKX6L3aHwMWn2RSgJv0f1gNwz3HqaQQg5WLy0P524D9Vw2XjAGONMcydsf9Vx7+ZvkmDmzS4SYKbOFhtwtXn96//eBsOKP5zMy/p397axjJtOwoAAAAAAAAAZgcMAAAAAGAfHR7AEHYewESC6KxzAb1/F6bJT58+P2phLZwIp8KpqCx9FkmIKFfDuRmaL0SK6x0AsJQDnVmAGgNtD4CK8O48eJ+pWX52q4JmybrU/2Y7gCrJT70pwC7hT+4EVD0AV4H/LRtADJHJdJTpmzS4TcJ1Eq424eqvt6//eLcSz476vKR/am1w+zzy/8wCAAAAAAAAgC5gAAAAAAADcHkAgzr7zSebVBMdIkf6KVlqTf/1z1Wgfv74+asiVlJ8UuFMniWLrWlt9Vzl35mUq+NfVU7fGN9oBEDWV3JkAdpp9y5qKYbyJXZ4AMVfl/TfdAWaaX/c4f9kiIwJ1nn4fxKsk3C9DVZ///Lq3+/XqWeZ/+el/g/fWj9PKLBIfMv/49v2AAAAAAB4i7dvDTAAAAAAgGEc7AGcQD7perDw9oGjQin6xz+jQP388dO3FyJFnLcGSIUzYZNtOoortwGESBEzid0TQNUsoJqyvxEA1Y9bfdj2BWprtyanytJx/C3NhA7139EOwJAINxV/qZL/EFMW3GXBOg1v0nCdhOs4XD3+9PDx95tNtL/Az8aSpH/nedRolwIAAAAAAAAAswAGAAAAADAYly7Y5wHIgGmm3JZxE1yQX38PQv36w0f1RKyEmViJSoUzozLz7BLQ1e6vqLqs3+oAYNcUQMqs/MyFuF/NSOVA3QnYeywln1SIOmwALrdZyNqRWuafejsAS/SXUvSnIum/4UCCmyxcZ+FNGq7SaJ2E0beH+4+/3f1Y+3J45yX907ANnq/HBmYNwu0BAAAAAMDkwAAAAAAARsBC7ZTrbn1fBkxzHF1xyp73Bpzz869a61cf/6O+EzNxWtgArISz7JnFlBMqhwew++v0ANqNAKqcPW0DgFzDTqQ1bGcBKseI/Wt/5h9xp/2RXdofKaT/dRqt02iVRNGP1/effrv79uDLgZ2X+j/J1vp8WgEAAAAAAABAAxgAAAAAwDhYLCXZyRljOGetRb56o7S+//Rv9UjfFbEiTokVKUVs0mcSU+rmOw+g7AmgnhGokv6lK/kPWU4A2QZAWYBDDJq2AUB1J8DW/UlIuC7318cYa2CX9icP/BcmE95JuDZRrv6vkmiVRNHXd68+vV8/33lxzBcv/fecXLM+74C3IPwfAAAAAGDu+PmmAAMAAAAAOIi6ksyl6NvDKRoBOJlLIwAiuvtJ/RbcBX+ov798U8RMnP81wibdkElLxVyVZgCXun8jI1A9C1A9HRATlT4BlSOpnMwasEuqq00HW19rwf5Uy/9D1cBO9y/3xVQD1d9c98+dAFHahDcmvDFRFfi/im/Xf79/+Pw+Sv14djtDTT5zzp8DlunzmQUAAAAAAAAAOX68RAIAAACzYifli2vkeZm7EHlzz+//50Zr/uvPr0ycErNwRiwcmHQr6YYkK/eyCv/PMzFVfgDX0wFRlxNQjqkOVN0A2HP8pD6NK/ZfpEo21JHtpyb9W05AlfZnJbn6v1pn0TqJVmkUPf9099f7u7/f6kPLeEoWH/jfmH3WJxcAAAAAAAAAwAAAAAAADuEwuR+NAJxEN/zr/3OjAvXXh695LqCMteFQODQcSrqVbEPS3RkAq7JxQPlhLsP825YA2QPcHNODuL62kv9w/StJkcuoFfVfBP7z7msW3uaB/yYq1f9V9P3Xhz9/u3m6v/wxvCrp314OEgGBM4D8PwAAAAAAy8DD1wQYAAAAAMCBtNV8rxoBzMsDCEJ6/z8rHfz017+/JawyFRgVGBUaFRZOQLolk5QyepkIKP/bUP93nQE42wTQiE6Aa00FasH/3Jb+8wEhS+6vMv+YshNgQyS0E/3z8UZ0mAbrQv1frbNolUar7e362+8Pn3+L0vDo8j2a61T/h6zI8zMLAAAAAAAAcOXAAAAAAADOyqVMAv9hRe/+OwrCn759fnl+fMp0YHRYeACqagqwJZNZ3QJXKYAGfopVtQZoj5Ar7vD/ygkoE/1TK/NPOwVQIfrnw6LDLLgRvTJBVKj/q1UaRS8/3X39x/1f7y6f9gfSf7/KDw8AHA9uCgAAAAAA4HTAAAAAAAAOB40AJufn34L71w/fPq2+/fm8/f5idGB00RRAVGiSULKtpBsi2jUCaMb7uz67ToDtv1RkCiq+WSVUO4rS+LEW+C9Euw5+iWuiP9V0/3JASJiN6FD0LQWRCSIJIhNGJoqy1SpdRT/evXr8582PhwsfsXlJ/wQVFQAAAAAAAOABvr19wwAAAAAAzs2JTILFeADhDb35n+j+dfj10/r7nz/SjS5sAF16ABxKuqVs6wj/b5sBbI+nPhvAHkPUPErSGCPlbLkNYHcDYBsAIkVnv6ZsJWBERyaIKFhJEEkYmSA0YWTC0IRBcrP+9vurL/+Mkmiq4jyQean/p95aNAIAAAAAAAAAzBQYAAAAAMBR+NMIoIuZqpOrn/jXn9b3r8NvnzY//v5hgsDEodGh0aEkoahQ0lDyjgH6WwDUfiW3AeBOCtQ+jE0DwBqw2wQ0xlix/0GU6RWHERXSf67755/g5ee77+/WX3+/8OMZpP+uFcEDAKfAq/sFAAAAAABYHjAAAAAAgGM5QPE/ZyOAURP4xu17vfr57vZD9OPTy8u3J6N10RRAhyYOSa8oSzhLKEvJJO4WAI7egOm0BsCuHQBVBoDRKw4iCiIKVxSGYkn/WRRsf7l7erP+8SZIVxMU2jGcQYtcas6f2Z1cAAAAAAAAgGsABgAAAAAwPUP0/UslApodOqJX/ydc/xw8fVg9fX6Knzd5RiDRIaUppSllCaUJZSlnCZmETLrfAJC6AcAdBkCjJ4BmNwDtXn+r3oDLdgCsREdZEFGw4iCSMKIolDAyYZiFgYnC+M3d05vVjzdBhpw/Y/Cnp41REwDQwCsTCwAAAAAATIVXrwYwAAAAAIAJ8C3tTw9ePYgMJ3rg6GG1/jl8+rB5/vNHFgSShJRmlOQeQJp7AJQmlCVs8jYBbSeAWsH+th9g/+oK/xeq/9rRCIBJVMA6EKUzFZAOKIgojCiMJCoT/qzC+M3t89v10xudhZOV0mFA+h+1dngAAAAAAAAAgBkBAwAAAAA4CZdvBCDlOuyRreF5cfNWrX++XX+Inj6+JN83sk0pzSRNKUkpzYp2AHabAJNQlpBk3eH/lvTP1QS9yX92hStS9gMsRKwCUZp1ICognUv/mnRASlMYUhRKLv2vw/jN3ebd6scbbTx4EIP6PznzPb/AmZlFfQYAAAAAAHPHg/dOAAAAYBF41AhALAmy2iZeiC7Jmu7/Gdy+e4i/3sWPSfw1jr+9UJLbAFmRFChNKU05S4oEQVmixLAYNobFEBkS42gK0Hn86j/s7AEhFZAKRGtSgeiAlDa61P213n0CLVGYraP4ze3219XTWy1q+pIZC6T/wxhyHi3jXAMAAAAAAAAcjD8vBTAAAAAAgFNxmUYAsvu/mbKeu77NDxXR+p1av1vJZpU83saPafx1m3x/kSSjNLcB8r8JpSlnKRujRHIDQBnDUhtgMZR/bMOEy8RBrIqvxT8mYmE2SisdGBWw1koHRmvWWnbSv6JAk9ailazC+O3t9t3q5Z0SD8p9XtI/+aT+D2fupxg4NXOs1QAAAAAAYI7AAAAAAAAuzJQeQP+C8pYBrqRA84XXFP2mo9+0eVmlX+7SxyT+uk2+b3Y2QJJylrEIG6NM3ghAyoHyb9U4QAwRcy79M+dDxJx/IWbJi4+ZmElpozXrgPMY/13Iv6JASxTQXZTcR+l9kNzr+BdfCnte6r+fIunAc2cZpxgAAAAAAABg1sAAAAAAAE7IWfMCtdbk1B9Fdh7AklA3FN3o6B96/bRKH+/TL0n6dZP+2FKaciZsDJu8BYA1XHgAUhgAuQfATIX2n9sAxMzCbCofoPifWCkOAtaKAp3lun8YyF0k91H2ECT3OnlQZnXpcrGA9D8h8AAAAAAAAAAA/XjyOgADAAAAALg8p/MJ3A8c5VhPHkemRd1xdKejf+rs+yp9zNIvCcWZbDP6/9m78+5GkWXv9xEJQnbNPVR3n33Off/v6q71nPv07nl3V7nKsiTIuH8gEEOC0AzS97O81AghJGHjcv8iM1iltkrVW5H+m5qJ9+LzWy9m4n01+pdN9L+pAWwnAuQLTiWOJHLyem5vEns7y95G6zcue3XtQ9AyrehfRp/+7+UmzzIc6ZZ+wgEAADByFAAAADivgeH+sTWAfZ5sInrTNYBc9Fajt/H8/4klE1mKLM2WZksvS2/LzJZeVqmtMlulmvmyMLDp86PitTIXQEWdk0jFOYnUIieR2iyytzN7G/m3bv1mvNMqppX+TygYHX7u3PBZBgAAAGDkKAAAAHB2F20E1BJuBFSpAdy+SOSVyKtN5/7NSttUBWRp9uJl6WXpZe010u1XrBqpROojlUhscysSqUUisVixs3EeSKL/8bifsw073faPOgAAAKrG8D8CFAAAALiEITWAw+sEu57WXwMYw18kV6AiDyIPKtWqgIiI2E0kdKT/F7DXuXOnJxoAAACAq6IAAADAhZy3BgAUiP4viRoA9jL1H3gAAABMDgUAAABOZmd8f5YawLCtmQRwPuM5dNOK/uUuw1DONQAAAOCuXP1/ASgAAABwUVcc49/5Z8fV/x7BKUwr/b+l6H/fE4gT7m7d0o89AAAApoICAAAABwkOpz/RGP9LFgnKLJJQ8jBjOGhE/1dHDQAAAABAl+v+/U8BAACAffT8o63bGoAcHfGfqQbQPwmAUHJyphX9y42m/4fhdAMAAABwARQAAAAYZkhWV4ntq5sfFnoOqgGcIk/dBJHkkfu77gGbVvp/89H/AScQ59xduflTAAAAAONEAQAAgAGOS+mCcwLOckHgI5mYkkhOANH/zaAGAAAAANyDK/7lTwEAAIBep/snup3mX6UGEPyzgxTyANc6YhfI0+n5c5jDziPOvntwVycCAAAARoUCAAAA3Q7o6HGed3G58IhJACPGwP/xO7gGIJQBAAAAAJwBBQAAADqcIY07bBLA8M0G2jEJgBrAABc+PkT/94CpALeKMwIAAAByvT/4KQAAANBy2L/JwzKeq9UA2k/u+ZgkkWNC+j8tx5w9nHkAAAAATosCAAAAdedM/8tXGMM8gHbWWF1BENnjYkeG6P8OcerdGM4LAAAAlK7y1z4FAAAACueP/vtffGAN4JDX7HpCT+txYsirmlb0L6ScdUeePZx8AAAAAE6FAgAAACJy6fT/yLH8+z09tGktYazcYRLAGEwr/Sf6D6IGAOHsAAAAQMvl/9SnAAAAuHvHtOs+7mUPawR0wMa77bguMLbOekiI/lHi/AMAAABwPAoAAID7dqX0v3zxEdYAapMATJQM8lIukKfT8+eSjk/wqQFMGucIAAAAxoACAADgXh3ZnuNsxlAD2LXufp3pUDDw/1ZRAwAAAADQcOE/8ikAAADu0mjS/2CCv28NQE6dyXZdIQAnR/SPnTgFp4iTBQAAACNBAQAAcGdGE/2Xjq8B9G2/73sOTgKgEZCInCGEJf2/ByeJ76kBAAAAALfkkn/hUwAAANyT8aX/ufPWAPZF1nh+RP/YF+flhHDKAAAAYDwoAAAA7sNYo//ShWsAO46HiWltGyYBTCtM52K/o3Kq7J4aAAAAAHAzLvbnPQUAAMAdGH36nztVDUB2PeWw40EN4HjTSv+J/k+IGsD94MQBAADAqFAAAADcuoPTstGkOAf09tmWAVpPHno8WpMA7tlJerhfANH/PaAGAAAAANyGy/xtTwEAAHC7JjLwv6or6z+sv/+xf0m0/hhhEsABphX9C+n/2Zzwj3tqAKPF6QMAAICxoQAAALhRE0z/c6etAeB44/9RYuD/HaIGAAAAANyAC/xhTwEAAHCLxh/Z9hpPDaA95J9JAAMR/aPttH/cUwMYG84jAAAAjBAFAADAbZl49L9TswZwgZrAfaeMB14w+cTvIoz0f4qoAQAAAACoOvdf9RQAAAA35Lrpf/nqp8hTe4L96/cCInTsRvSPC+N0HAnOJgAAABzsrH/VUwAAgDuT/5Nye0HFFaP/9kufqBJwshrAqSsG9xM47vUxif4x0MnPoPs5JQEAAADsiwIAANyT6w8dP49Rpf/tR494ibHMA2jniySOdaT/uC7OyOvinAIAAMCRzvcnPQUAALgbN5n+jzb6b2x51hrATX5nR2Dgd5joHwc4xx/31AAAAAAAtFEAAID7cJMZ8RWzrn1f+pw1ABmy73N0Abr7uPFipxTp/02iBnAzOLMAAABwEmf6e54CAADcujz5vb184lpj/w9+3XPWYM637+3HvbNkcednZeA/xunOztTr4+QCAADACZ3j73kKAABw624vnJhE25+upx/6BnY8VUVNZNc2p/9huMuscVoD/2/vF8AtOdMJdJfnJQAAAIAwCgAAgOk4MtO6bvp/tCEB/tFXHQ7s7Q71fPAL5OknPOyk/+NHDQAAAABA1cn/mKcAAACYiNtI/892MYDGZhLccp9XH/qJ7yZopOcPpuVuTs1r4kQDAADAOZz2j3kKAACA0buN6P9EOjP81gPHzAYIf+iuv0FuK2hsfxSif5zV+U6g2zo1R4dzDQAAAJNAAQAAMG5X7Ph/5Kv37PO4N7bXDspPYAOefPjHvd2gkfQfk3a7pyYAAABwy074lzwFAADAWN3wwP8z1QAGhvsnuhrwNb9uvAAAIABJREFUTQaL1U9E9I+LOevZlP9s3N7Zel2ccQAAADi3U/1vAgUAAMAo3XD6fyJHxfinqAE0D9INFQQuFu2R/qN07hPohk5QAAAA4F6c5M94CgAAgJG5bvR//BsY/iqniOCb+zjR6P77dMxVEw57rSPxrcZeqAGcCqceAAAAJoQCAABgTO5s4P/xifPhgf85SgUTzxcnFP0LEeQtusAJNPFzdBQ49QAAAHBJx/8NTwEAADAOx4dSU0v/ywj+yCi++fThuzui/kCGeACif4wBNQAAAABgWo78G54CAABgBK6b/l8pDNP68vGfYBRTAQgXO5D+Y4jLnECcpgfjBAQAAMBVHPM3PAUAAMC13Vnbn+arny5P2u5s393uuT3R4V6I/rEXagCjxTkIAACAKzr4b3gKAACAq7rX9L/9yiepBRw+FWDIM3XYASNZrCD9x2hxpu6FcxAAAABXd9jf8BQAAABXcq/Rf1Ml+D/VfAAVscP2VR4WC6wfzzGbBKJ/HOxi0Tw1AAAAAGBaDvgbngIAAOAa7jv9v8DrdyX5ez//sHd797HiST490f89owYwKpyMAAAAGI99/4anAAAAuKz7jv7DzjAJoNzXwZWAcR6q8SP6x+RQA+jH+QgAAICx2etveAoAAIALIv0f8C5OXgMY+LondpeZIuk/TuiS59Bdnq8AAADAhA3/G54CAADgUkj/e5wy9R+Ne8oU6fiPqbun83UPnJIAAAAYrfyP1Z1/xlMAAACc3/Gp0jEZzAQzrTNNAsCZMPAfZ3LhUJ4aQANnJQAAAMZv55/xFAAAAOd03ej/JG/gpPreTj2ppwYwfoz6xwVQA7gWTkwAAABMRf9UAAoAAICzYeD/cW6hBnC7aSKj/nGrbves3QPnJgAAACanqwxAAQAAcB50/D/AWWP6C9YAug7/bWRqRP+4sMsn8ndeA+D0BAAAwHRV/5rN/6qnAAAAOIMrpv8jTq0GvbXzNQI6/e5qO77q618O6T/uxJ3XAAAAAIAbkP+/JwUAAMBJMfD/1E5fA5AT7HGPI13PEadbAyD6xxVdJY6/zxoAJykAAABuDAUAAMDpkP6fxAUy8v1f4oRHd4o1gKtfzRqgBnABnKcNHBAAAIAbQAEAAHAKXO93l/3e41kbAZU7lb79TuGgXgID/3Hn7qcGwHkKAACAm0QBAABwNAb+n8MFagBSKwNc8kBOZRIA6T9G5VpZ/D3UADhPAQAAcKsoAAAAjsDA/ws6eWiujaXLBmAjrwEQ/WOcqAGcA6cqAAAAbhgFAADAoRj4P9iBb/Y8GXnnmykfuO8wjOgfCLrVGgBnKwAAAG4bBQAAwP6ufkXUm0yhgk7aCGjoYbtUJWCEkwCu/qMN7HTFIP72agCcsAAAALh5FAAAAHui7c9VHRaaHz4FIXcfIRnpP7DTzdQAOFsBAABwJygAAAAGu3o+Os3Y6dh3ffQ4+RMctlYlYOA+bdemI5kEcPUfbWAv103hb6AGwAkLAACA+0EBAAAwDAP/r+ikjYAOe/3m0rB3oPVvXPBJ160BEP1joqgBHIxzFgAAAHeFAgAAYACu93t116gB9B34nZWA0JO19xkXxsV+gWPkP/yT+/XMOQsAAIB7QwEAANDr6gOkJxcv1Z3v7Q+vAew7Vne/97z/J2yXAS48CeBU3xSSRFzXGIbhj+E9DMQJCwAAgPtEAQAA0I22P6NyREw+MKS75CHXa9QAGPUPnNwkagCctgAAALhbFAAAACEM/B+nczYCuvwhv+TAf0b94yaNJHwfczsgzlkAAADcOQoAAIAWBv6fyAU+yqkaAV3rqFfbAZ2vHnD1ehZwPiOpAciY3kmO0xYAAAAQCgAAgJqrB6WjSo/G6fyNgAa+i4GveF30/AEuaTxTAThtAQAAgBwFAABAgfR/Ko5oBHRMDeCAJzae0vU+9TyTAK7+Ew1cxjiH3l/rLXHaAgAAAFUUAAAAInLtrHRU2dWJXPGCugc8/cgN9nqh4Fs9bQ2Agf/A1ZVn0GV+GXLCAgAAAEEUAADg7l19mPQtpv9nd8FGQCf//nSVAYj+gYONbRJA1bkrAZywAAAAQA8KAABw3xj4P12nawS0vauVVfUVJxd8w1q8mcN+rEj/cc/GXAPInbYSwKkKAAAADEEBAADuFQP/z+lCH+6IGkBgV62723XnSdqCUwEO+xTkicCEVM+14ScvZygAAABwAAoAAHCXGPh/o4an57XBwsMvAnCGBK5nKsDAp58E2SJuwPgnAbRx6gEAAABnRQEAAO4P6f8tOf5iAB3fkfDqnsv4HkFbI4IHNgJi4D/QMMUaAAAAAIDzoQAAAPeEtj8XcelPeeTFAExE9wwN62WAIU8cEuW3awA738LxSP8BAAAAADeMAgAA3A0G/t+Noy4GUNhdEtBigwEvNqSHUPttd30QBv4DPZgEgFM5yb8mAAAAuC4KAABwBxj4f/OObwR00GvWloa9gf5KQPVzBBsBMfAfAAAAAIDhKAAAwK1j4P9lXe0TH9MIyET37AIU2HLPCwX3jO6v1gBOjugf94BJAAAAAAByFAAA4KYdmQAx8H9ajqgB5Bqh4YEZ4uBKQNfMgeCo/5ME96T/uB/UAAAAAAAIBQAAuFm0/cE+NYB8EsDw3e6x3YAywM4aQPtFD6htAAAAAABwbygAAMAtou3PlVz/ox9zMYBQI6ATDCIeMCEgWCno/yjDpwUQ/eNuMQkAAAAAAAUAALgtDPzHZS8GsIddmX3XqP/+MkD/pyP9x52jBoAjHVFWBgAAwChQAACAG8LAf4RcK75pzCSore14QwPbAQ1BYgUAAAAAAAUAALgJDPwfgREdg5M2Atp3BHFw42YfoO4ywElqAKT/QIlJADjS4Cu7AwAAYIwoAADA9JH+o+1KjYB2PqUWJHWUAY6pAZBPAW3UAHASVAIAAACmiAIAAEwcbX/Q5YgaQMPA9HDfiQI9swEG1gCsYxkAcD7DL8MOAACAq6MAAACTxcD/MZnEwRheA+ifBBBcf/B0gW0ZYEANoPE2gssA2pgEgJOjDAAAADAJFAAAYJoY+H9akchcJBPJRNJrv5kTOvpiABegR9QAhOwJAK7qWteZBwAAwEAUAABggo6MZe954H8kPhFLzM9NEpO518TrPNPEa5KJV8lUUle5Vc1U0s2XZiqpSCqaqX+59mcZ6LiLAYhux3ie7zu/HUa66/1VHydyAvbCJACcCVMBAAAAxowCAABMCgP/95G9NXswn5jNvU+8JV6STOeZzjJNMokydSZq5kydd2oqoqbFV2vZq2ROMyeZk0z9U+KfZv5zlD2prK/9Ufex72jNi4WGGqoBMLYUACaBX9cAAADjRAEAAKaD9H+Y9RvLPmTZh3X2fmmPa40zUVNn4kydqfrqgnNe1dR5cSYmYiqmIiqm27ubr3w3RVXgWydp5Jez7Ms8e5r5p9h/dv5plIfpRI2AqpWAM1UFgjUAAKfCJACcFb+8AQAARogCAABMBG1/dlm/sdX7Ivd/u7Q4kzjVyOfhvqpXZ5LfOq+aR//eNpUAb+qL3H9TAFDb3lWTPPrfFAC80yySNIoeo9mbhXx0mkbZ8zz9kqRPs/Qp8k/OFtc+IlUnagR0Ae0aQOPdlndJmoADUAPAWfGbGQAAYGwoAADAFFwx/R99ULR+Y8v32fr92r9f+rdLizOLU4kzjVOJU40zVa/Oi5o6r+qliP7FeVET9aLe8jV58GxORdU0v93k/qLNAoCPJY00iyWL8mKAe1wm7yPJnK3i7GteDIjTz9H6k44wDjmsEdBlosP2eyNRAgAAAADgABQAAGDcGPjfYf3alh+y1ft1+q7M/TOJU4tSiTOdrTVKNU41Wufj/fMygNNNDSAf8q95+i9WDnBXEZcXAUScbKL/Te4v6ooF9ZH6WLJYs1h8UQZIY8kiySJNo/j1y/zbSFPnX5LV34+LP+Yvv7srZ9gnagR08IvXdjjwObsuCDx0VwAqmASAs6JkCwAAMCoUAABgxBj43+Ijef4xW71fLd/VxvtblG56/sRrKaJ/jdYuSkWzzZB/MdE8xy4/Xh715wuNT709fKo+74GjJiKmIi4yNac+Fh+rj9Xn8wDiYkJAXg+IJIvkYTl79fLwXfLy8fX1ywCnaARkleR9yI9JcJud2X1/I6BqF6D+/QAAAAAAcM8oAADAWB0Twd/owP+v3/vnH19efvjik1Ti1KLM4rQY+L+WONUo1XitUarR2rm1uMxE8+h+E/SbVhraV8oAueZxs95lU5c6t1axfEJAUQnYlgE0iyyd2WwWzdazh9Wrb5Pnq5cBjqgBlE41fLg/vueCwMD5MAkAZ8VvbgAAgPGgAAAA40Pbn5bFW/v602rxw5f01dIna0tWFmcSry1KJUqtmvtHa9FMRU1UfSR5+i9qUoz+F5FgAcDq6zZaub+VK638UhHn1upWTmQzLSCL1UeaxZYmFicWJ7aeRfF69rB6803y9ePr56vPBigMj2kOuBrwwCkCA98AkwCAE6IGAAAAANwDCgAAMDK0/albPcjTD+vnH5/X756z2XqT/ifLIv1fS5n7u0zFmahaXMv9N8t5UC+tgf+NMkDjCAYnATQLANW7qplGqXN5j6CZRYlE800NoFIGePsh+fr9669/zheXLwMcdzGAaiOgc08FGNgICMBhqAEAAAAAN48CAACMCW1/Knwknz6mX394WX77JUvW2Wztk5UlK0mWNltKvBSXarRWl4/3d+rjsuHPtvOPiNQWZEcBoHkcg3friX9luVoSUDF1qyhZWrSSKLGiDCBx4qtlgO9ef/1z/vWPy5YBjrsYQPCCwGdKEoe8NyYBAAAAAAAQRAEAAEbjWun/+KJ/EXn6zn/6+PLy8UuWrLNknSUrm60sj/5nS4lfJFo5URFVH5m6Iu5vf+URfd65pqMAYNW7VtukcWStiP5VgsP/t1+bTkEqYpGudbYUt7JKGcAaZYBvX3/9c/70pzv1gRxqvxpA5QidMPfvew8DJgEwIQA4AJMAAAAAgNtGAQAARoCB/xWLt/bp4+rLxy/pq2WWrLPZys9WPllto/94qSrOtv39xVxn+l8s66aVjLQKAFKpAbQHk7dmAJiIlvl+Vw2gvJuH0l5EnVs7tzS3MpeIm1uU+CixKLF45uP1bL569e5h9vj2759n5k95PDsd1whIdZsbthdO+6YGTgIg/QcOQw0AAAAAuGEUAADg2kj/C6sH+fRx/fn7r6t3i2y2zpK1n62y2dLn0f9sKfFS1Ttz+WV3K0G/68z9myvzpXYNoLjbvAZArj0PYJvyW7gLkOS5f7MMoGs3W5pbiUvMzS1KLEp8nFg0i1w2S9LZw7u/f56vni/yvTmiEZA3cbo7NzxlsNgxCaBjEwAAAAAAQAEAAK6Ltj+Fr+/sr/95/vLtU97uP0tWfrbycZH+x0t1qZqqRCZ5wx9npqrhzj8quun807wAgJhWO9hXFq0sDXTUAJrTAuoD/8umQFau0fo8gG0ZYNsUyCUWzW2dePfg1Efqv/vJz+Zv//758evfF2kHdMzFAOofrHMSgNafs9872v3GyodI/4GDMQkAAAAAuFUUAADgeg6OW25r4L+IfPrG//U/X75++JI+LLPZ2s+W2WzlZ0uL854/KydOLNJtt5/itpbyB1oAFVcDlu3Htu7h/1Ie2/55ABa4tXo9YFsJ8K2cfNsUaFMG0LnXzEnm1Du1D9/6JFn/5+HNP79c4Z/p4TG6WXFthcb6nh+x84f0lAEA4Or4PQwAADAeFAAA4Equkv6PL/oXkb8+Zn/999Pi7dd0vkznL1nykg/8t2gp8dKpqo+2if/mtuz546p9fooFafUC2gzIL9bk/620/m8fGi0eC1wPIFQA2Nw2ygC+mvi3ywAqqm6ls9Srd5oXAHw+FWAe5+2AknR1xMEd4riLAZQ9lbpy/+b6AS/XuUlHF6DGJABqAMABmAQAAAAA3CQKAABwDYelLDc38F9Efvtp/Z//fnp59byeL7P5S5a8ZMmzj1+saPdvorpN/Gu32rwSgIioqObhvdYLAI1GQCLVqQDSfEis8Wh1dkAj9NfqmuY8ABUx0Y4ygG2WVXwcffGSOdmk/6l6p/6nn9L57N1fvz4uPp/5m3dMI6DKBYGHXg34oISeWB8AJoHf1QAAAKNCAQAALu6ALPf4/5keX/qfRfLLT6u///Vp9fCyTpZp8pIlCz9b+PjZomXe80c2PX8C6X/ltt7wx9qJfyP6720BtNU+6LUCgNbLAMVtqxHQNuhvlAHKW8nnLsTuxWIfSZZJ5tRnmjn1339v8yT987fXn/6I9jzARzngYgDt5XKVhToFnRaTAIDjMQkAAAAAuD0UAABg9G5x4P9yLr/+9PLPT59WyXKd5J1/Ftns2eKFubWzKJT7N0L/RvpfiftVamusVQBQ2a4JdwGqdAyyzY1WCwAqzejfpLMAUJ0NEOgFVJYBJNJVFGeR+Ex8UQaw9+rns3SevP3jl5n5Y498pxM1AtqurN5t1wBOkdB37YP0HzgYNQAciV+/AAAAY0MBAAAua69k5RYH/ovI11f260+Lzx8/5+n/OllkyYufPfv4WTVT7zZxv7pW7u9EVPPrAKtapft/rQBg0lyzWZDNgkl9TfuuhQ6+VR+1RvOf2rjzUAHAWmUA9ZV3uPly4qPoS2RZJlkkPpXNVQH+9a/1fPb+j9/mL4uzfVPP2ghoz3kAwVfvf0uNRykDAMCF8VsXAABghCgAAMAo3Wj0LyKf3vlff3z+8u1TJf1f+Hjh42dVU4uK0N81JgFoYwaAbUPzohIg3bm/Vo5I9dC0awAWWi+hWLun+U++H9PwVADbjPq32vD/Ch9Hz058ZD6yLBIfS7ZW/8MPPnYffv6/j6vzXRb4mBpAqBFQowaw2enp3mH/VtQAgAMwCQCH4fctAADAOFEAAIALGpip3G76/59vsl9+/PL8/usqWa6Tl/XsJZst/OzZxwsVUe9MXH7J33Khmftv4v5m9//a3bwFUHMewOa21aumZx5AqRFuSyVe1mJoe2Psv4o2xv5vv4qnlNG/SnM2g8a6tDiLJO8IlEXi12bffvcpzfTn/+/Bn68XUN3wDD3YCKi5jYieNCLqGfVP+g8cjBoA9sXvWwAAgNGiAAAAF7QzU7nd6F9Efv+Y/vrD0+LNc5H+L9LZws8WFj27zXj/yvD/zSQArXQBUtFaRN7xJVK7DvBmQTf1gGJ14Eg11ljzoU2X/2Jou0ol9JdWAcCKCkSgF1AxM0CKD9WO81VMnWRlOyBnpl7Fux++/5Sl7t8/J/sc+30cEZzvbgR08WCRMgAAnBW/YwEAAEaOAgAAjMCp/u95xOn/n99k//7p8+LVJv1PZ4t0tshmzxa9OIvMNuP9Q1/htj99BYBQF6Dyqr62KQaUuo5aI7UuVxbBvVUj7mro3Uj8JbQyvwyAq1xsoHFJA1+8no+jr85MYxW/+frx+89p+uH33872j/gRjYC8idtVAzitnZMAqAEAB2ASAIbgtysAAMD4UQAAgMuqtkE/7f83jziq+fzKfvv49Xm+WM1eVrNFOntJ4+csXohbqW+N+t92/qmM/RdXy8erUwEqVYGiA0250LitLwQS/uro/upD1f4/1Uy7mu9L4wIArTJAuWUe7rtKGaBjEkBRw4ijZzEnpuLzW/3p4+c0ff+fv6Lh34Vj7JWh77wYwDHBImk+cDHUANCDX8UAAABTQQEAAK7hbqJ/EVnG8tvHxec3X1ez5SpermaLNH7O4mfR1Hknmqf8Xc1/VLTa7r9rKoDUVm47/HSn/1L9LjSOoNa/QY3mQdXQvzEhoFiv5QbBSoBrTgLI5wFsnlWZBGDbNzBzzxqpzJyaindvvP7rY5Rlbz/94w75rux0okZAndtcak4AkwAA4LT4dQoAADAtFAAAYOLGnf6LyC/fL//68HkZLVfxchW/pNEijZ5FU82b/tuOzj9m7Uv+7ur/Y2rbEf6N9L+V9e/WmK+hRUmgnS0XazZX/ZUiCQ98aTX9z5ctOA+gfN9+5hYaubIX0LtX+q+PLk3ffP1ynh+CMzcC2lkhOPSdAjgZJgGgxK9ZAACAiaIAAACTNYVU5t8f0j++e1rGq1W8XEUv62iRuoVJqt5pe9R/pfNP0fBn2wKoJ/cvs35rNP9pTwXYplntw9cYtV7POizfsRX73473V6tWAhpTBNrXACgj/k0LIK0si7jiLWj9HZqIOPEz9yyRSuzEO/H6zWvNPrr/TV+9vFzip+GARkCNGkBwm3NgEgAAnAS/OQEAAKaOAgAATNMU0v//vPa/f/yySF6W8XIVv6zjl9QtMl06HxWdf1pf5oqeP8HoP1gGEKvUAGqJv4UmAVjvJIBAX6BK+mGNfL+6cfCqACpim+5Am/25ejGg1Q5o03DIbz5Z/T04SRP3rE7LeQDfv31K0+h/f3lI0/7vxkGOawQk2vw5bSf+wRrAAa/Z/06pAQAHYxLAPeN3JgAAwA2gAAAAEzSFMOY5sV++e3569byMlqtoucrTf7dQK4J+2Xb/Lwb+a637f0f0v70UcKPzT7AG0NcCaOBxDLcAaqfKxe5UO8sA4UkA1WUVV3QEklD2YpGs1S3UOY2czlS8/vT+U5bq//46t3NENUc0AqpeDKCrEZCcLWAi7geAY/ArFAAA4DZQAACASZlC9C8iXuXnb5Z/v/uyjJbLeLmK8tb/CzFVc1Zp9RPs+y9Svfavq1zytzEDQIqNe6L/Vhlge7ndunJmQG3off6QiKjoJpLXZrYcHF+edweSUBBdjPrXIvq3ahkgnwpQdtEv372VbyXSpTrVSNWrxCqJ/uubT1n6zf/9Y9b5LTnGMTWAUCOgowYUd792+43RCAg4CSYBAAAAANNFAQAApmM6AczP36x//+7TMl4uo23rf5FMrd33X4vZACpaaf2/nQdQj/6tTPwrcf+21U+wEiDNu7u7APX3/6k+cecaCW3gTUSt1f+nSP9FnJoXcWX2r5XdqkisC3FOnZNI86kA//rm83L14Y9PUe+rn8bwGD3YCOhaSP+Bg1EDAAAAACaKAgAATMGkcpff32a/fPO0jFeb9N8t0mjhde18R9//2kj/dut/V+n840RkWwYY2v8nWAyQjrsSetQq1+YNtfcJzwmQ+nqtXBa4Gv1LO/0vbqt7LesSm6kAM31WpxJtrgfgE/f9u5d/vrxeZ70f6DBHZOeNY3eCSQD7OKBEAyCIGsBd4fckAADAzaAAAACjN6nE5fOD/fubr18fFpvW/9HLOnrJ3ML5qLvzT6v/T+81AGzT8afd/KcS9G8mCkh9TeVuYLlHO8cX2/b6l8oL1csARVf+VtegkjMxbfX/KW7LmocUL1pOBTAVP3MLdU6c8y7Oovi7118+vZ//+z/n+cf9mEZAxfdM6jUAOfVP98D5F9QAAAAAAAB3ggIAAIzbpNL/dSQ/f7P49PrrMs7T/8XaLTK3qFzyt9X8R12R6YcH/pcL1rz8r1SmAkh4EkCtmU9jHoCE7nbasV0tTu4bdF7bz3Y8rSumAqiIaC3992JO1BqVABETExUfuxdzsY9mWTTLovj798//fH37vLzEz81+NYDiKVIfR3yBMcXE/cCpMAkAAAAAmBwKAAAwVhNMWf58k/759umlHPvvXtJoIWLa0/xn2/YnON7f1UP/xpd0NwIK3krrsJ7gKBdTAbq6AHVREQte/rd41Gm5xlzliVa9jWRlbuld4t0yi+JvkueP7x/+z+/J8Z+r6y0fxiqHSVo1ABn4nRjw6kwCAAAAAACgRAEAAEZpgun/ysmfr5crl67deuWWK7dYu4WXNNT6X1sXAGh2/tHmQ8WXVpbzAf5aLwBsR/3Xb7eH9IAWQK0nhMLj4nq39ce0tWZzMeFypQvVAAKNgIr+P67yDkzEYll6l2TRLIlmWRT/8PrrP69nn76e52foiEZA3sR11ABkcBngsNQ++D6pAQAHYBIAAAAAMC0UAABgZCabrPz5Ov371ZeVrtduvXbL1C0zfVEfiTZa/Lsy3A91/ml+NTv/WGMD6egC1LrdZr17FACKhwORV9ecgubWgzLmWgug1m0+CUA31wrWfKdOxPILIzvLYn3xbubdLItmb2YvP7xbfvr6MOSFD3FEDaAceh+sAUh/GcCOyuur75PoHzgGNQAAAABgQigAAABOoDr8f62rVFepLDet/8MXAOjq9d9uB9T/JR0LjdvgQvDuTsHUevhONuFz6wldw//r1wMQV1wMeFMDyF89tqVpUl4J4IeHL3+/m/35Odrzox1oeJ5ulVkSXTUAOTqdH/5+qAQAAAAAAG4bBQAAGJk9GqKPyB+PxfB/Xa91lcrKy1p9pJvk2hUD/0ON/rVYaI7ur3xp10NSeZZsbnWvAkBwTdXAfN+0Ns+gd98WfDxfbOT+Ukn/q4euVgNQyWJZepllbpZF8UMc//j65dPX1+tswBs/wBHB+cAawLkR/QPHYBIAAAAAMBUUAABglCZVBtgO/9f12q1SXWa6rAz838b9mq/U+qh/09rd4PD/ztqABBYsGP1Xj+bAGkD729C4eG1w8Lo2VwyyfaJtKhjaKAZsqikmolYpA6iIU7HYXrzOEk3yGsDHhy//vJ3//M/Z/qHXZhljj0ZAB9QATpHWB98klQDgMNQAbhi/FQEAAG4JBQAAGLGJlAF+f9gM/1/ppv9PJmvnozyRt23T/+pI/+Dlf7dfzdb/nV/SHP7feVtdaCy3Vw4/9H2XB9gnIKtuuMn9TbzWPmMxD8C2MwC0MhVgJkuTJNOZj2ZZFP/4avH34s3z8kI/QIcl6dUagHQdrv33O+TNaOVFSbsAAAAAADeJAgAAjN64ywArJ3++Kob/azH837tK7r+N+EOxfi36D4T+WpkBUDYCal74Nx8xPyT6DxYD2romBHRl+6ELA5iGtw7vu7G2fP/V5j9Sr464xpUAIlnF+pLozOsrliL1AAAgAElEQVQsjeIPydefXs3/32XS+0mPcERwXk4CaK4f7086gCZOWAAAAGD8KAAAwESUSevI4pY/KsP/03L4v0WVfL8y8L/Wyr9rEoDU1lvHcnv4f63zTyPoHz4JoEsw/Q+WAfprA9a6BnCwwlP7LFr/vK30v7xrsSy9JDOZzTXOoviHx+f/LGb/vJzth6ZeA9irItBuBLRZ3zgWJx2c33iHTAIAjkQNAAAAABg5CgAAMDVjqgSsnPz+UB/+L0vxZff/eq//IsEv5wEUubYrB/4P7vzTSv8DA/93TgII3g3qb/3flYD1tLhvpNDt99H4CJsPWD84TsRs2wVIRZyTbKYvXmdeZ5nO3s4WPz4+/vMyH/AZD3VEDaDUrgHI0T/gZPoAAAAAAFAAAIDJGkEloD3838tafVQPqYPj/fvjfqnPFWgP/5fNQrPtz5BJAI3lrjW5Rha9swtQYxKAiWg4h24H3s1dSf1zNZa3NYDqDAARzScBeJ2lOpvp7F2yfIzni7Tj853B8OS92ggoXFc5f4TPJADgSEwCAAAAAMaMAgAATN+VKgGN4f9rWWa6FGuM/S+jf+kb3d/sDlSG5v1j/7Vop9M//D849n/gwWpH/I3lncP/871sQubt2s6w2VpvuKsA4ExMK9G/iBMzlSzWVSbrWNNYs7fxy/vk9SKNhn3egxyRnffVAOwswWLPm6UGAByGGgAAAAAwWhQAAOCGXPZywX88pP95aHX/983u/0XD+t4cvxblN7Zpa+9Bimy9UQyQjrvScbdL1zyAxsLOawDsTMlMpLx0sEnzKEnr4ARmAIios3Ukaf410/T9fP3r8zkLANLMzvdK0sM1gOL5FwgWyf0BAAAAALeKAgAA3JxLTQj4a76qDf+vdf9vDv/vGMsfyPE3X80JAfW7284/wdz/gOH/XaP4248OSf9DjYDyHVlHoK2VGsN29oMUjXBUtx+nfdzyqQBalFs0ktRpPgMgjTV7l6we44dLdgGSfVN125Y+2p1/LlkDoBgAAAAAALglFAAA4HadsxLwFNmXeJVatpY0lVWWd/+35vD/AaF/O8SXQOLfeG6z809/+n/w8P/GBtV5AANbAFU3qz63fKAInK39Wo2P0FEAMCdq7d5KkaVR0QXonZ6/C1Dloxxg82m7R/2ftgbQ/06pAQAHoAvQzeAXIAAAwI2hAAAAd+AMlYCn2H+NlplkmaSZpJmtxZzYNvTv7vzTVQmQ+krp2Fi6nyutlY31waOgzRVN1hqSX12uLgQH/ve3AAquae9fi4S8eh2F4tFQsSSSi3cBkmZ2vleS3nO15a41p0XuDxyJGgAAAAAwQhQAAOCeVAPO43KapzjNLEslyyzLLPWWis93unPs/zbRrrfuKRa0HP7fVrYGko7OP+1iQHChvrwj960+q+c6wP01ANmuMQl8uu0AeC3eUmMnWlkoqyzNNeWXkzSybReg99EVugDJ/ql6f4B4sXiRYgBwGGoAAAAAwNhQAACAe3XEtIBU5Clep+IzyzJJvWVeMrWosicVUbVWK/+OSkBtIXw14OJrOxi/Oca/0iW/cdv4kK0ZALs1DlajXXww/e+6VMBmh81XbVYFrP0B6wu7vyJLIysuA+Au0gVIjsrOO6+c0Npm4N56EPEDAAAAAO4BBQAAuHv7VwI+R5b3/9nMAJBU/CZ0rveoCbbyl64agNXmBEjoiRJ8Yn2hcRta0H0+bfWJNrD/T08NYOdLN68WUNnVNrVuN/0vv8qrAUeyjm0dWxpLOpP0Q5z+KucvAEj1bbbv7dDI97tqALLn928grZ8NVAiAAzAJAAAAABgVCgAAgMLgSsBnl724dWpZZmnR/2fgsHSRrs4/4Yi/SkXyKQWNd9keKS+VikL7I2kl2d13NHk7ly8Xum4bWxZPDwz5b7ylYIGh/Gqk04GjnXcBiiSNNl2Alo/x/EJdgI6oAciwAHFns6DjUQMAAAAAAEwdBQAAQMuuSsBTvM7EZ5JlknlJvaXBAenta+C2xu9L/W6wDFBfb+2nSGif1XevxXuolwH2UA3ly7vHzwCodvpvFwPy6kX1OsCdHf+bEy+Kr8jSOP/S7J1bfIgv0gUoZHiYPqQRULmlhB49JrUn9AeOxyQAAAAAYDwoAAAAuoUqAQuVL9E63VwBOM0sNTO1nRf+7cr3y+UyB9eO7KhMuhvvqbHQTP+bH0C0vvshY80rG5tUkucjZgAUd+sv39X/R4rDUn3u7i9n6yjvAmTpTLIP0fqXy3QBkvqb3dOQRkDVRw/T9QYbR5l6AAAAAABguigAAAAGqASin53/Gi/L7v/eMvHSkfIHbbexWhgfLA8UI9xroX37KdWFduef7fZWvvAe41NrA/aLNQdfB7i+w0q6XDkaPfMMyq92Lh2YDZB3AcovAxBr9j5aPUYPi2zwRz9S/W3uFaa350owoBiYEM5ZAAAAYCQoAAAA9mHyFKVrSTPZpP9eUrFqj51qIt8/CUBa8b20NihfNziBIF9qbN819r/9usFnNe5Ya3Ug+tftmv4ZAI3l9jvpnwFQnQow9CvyRQ3Asndu8cG9XmTX6QIkrQ+/UzVDvGSeyCQA4HjUAAAAAIAxoAAAANiHypNbp+KzyhWAK/1/io0CZYD6Xpq3EtpD13PbmX5j4H9w5/l/u6oF3R94oxUIW7VHf9c8AKkviASGtldfacgMgEb9YFcBwNaRpZFlkWWR2dxZ8y2c1RHxefsaEtfKE6kBAIehBjA5/K4DAAC4PRQAAAB7+Ozsya0yydLiCsCh/j89wX21NiBFcC/tRkDaeK42dhiuAXSXFoqFItuwVilgl2A0L624v/2s7jJA/RmVpKy5f93uoXGEGy+67f9TLqh4J17Nq5iKT9RvX6/6audTf5t7hent9PBieSKhPwAAAADgBlAAAADs4cn552iVVob/5815tBlMS289oDugLzRzXis3aM8Y6KkB7HiV7jXNF65s2XUlgDyp3zkJoKf/T+MV+55ugWOuRSml/I7kd7yaOTFn3pklrnUFgAtUAo6oAcie1wQ+4n2dZksAAAAAAMaDAgAAYA+fo7z7/2b4v7fmDIBiLH8woW3PAAguiLSi7dZ+GstdgfDO9F8D66qsfCzYE74d6A9M/6tj/IPvufYUE9XtmsDY/+IotD9dXgbwat6ZObFEvapY8JUvNidgnzy93QhITloD6BH87gLYC12AAAAAgOuiAAAA2MPavDfz5r1l3jIvmbPmFWUrNYCdCX51S2ktN57Szv1F8nRJq7tq3AYX6suDYt1t/KuBKDicyIvV03+tP73vdatPlFCXof4aSW1XKt6Zz6cCJJIlKsv+j3yOSsAR8fkVawAAjsfZCgAAAFwRBQAAwB4yMfNmsvkSJ6EkOphHt9e3B+/vzIi0slSJ9Xck6e2dB99eWzvll8bY/NZC7yQAaw/873nd9suVt8E0XSXUF0hEnHgVc2bOfGJZ4mzph8Vxp60E1N/1XhWBa9UAmAQAAAAAAJg0CgAAgKG8SCp5/L8hXntD2DKS3typ/Lc/gu8vJLSXe4b/9zwreLf9kNW364r+e3bScwGA/ksHD32LvbbXAXZiiWVJ/zctaGfN4iDH5+mMLAYmgVMVAAAAuBYKAACAoVKRzLzZZhJA2dNGNuFONbLfZveth6SxoM0125io8twqlW2c1Op739qyd82QSGp4Uh8c/p//V8vDVXwoG9wCqD0JYEfRpVE+UTEV78TULLZsrsel7sdMCzgi8g9OApDzB4tMAgBOghoAAAAAcBUUAAAAQ2UiaT76XzaTAIpHGqm9BoPaYsv2crBLT+N+M7ivVQm2G/QP/z8g/a9u3HXt3+Ca0Hrr27j1bnpmGLT7/ARnS0gxA8MX1wH2Tiw5sgBQOqwScOpGQHLxYJEaAAAAAABgKigAAACGSsU2MwCsuAZAOYJ/gDKlrce1nel/SyPj3vnE/h0eOXx9YP+fnidWewpV7ewm1POK4eY+eQsgJ96JOfNz87ve8J72rQQcUQPofwtnKgOQ+AMnwSSAkeMXHQAAwE2iAAAAGCoVyfJrAIiJmXmrDsQfrDlI/5SRUGBHwTpBeLB8a81eYUir/49pednf+jbD13SWGboPWnNagJUXARZT8WqWSLbP59rHod2BhifsjWsrBx8d7uC0i5IAAAAAAGASKAAAAIbKxDLxm+H/3sR19aU5QPhZoTy3J8fXHe3xO1+rZw7BkHR+P/XZD4MuAnwcFVEtLgLsxObmVcXOGmDvrAQcfTEA6Yj7zzQVgMQfOAkmAQAAAAAXRgEAADDU5iLAYra5EkB5EeBg7j+oQ33HygOedcA2Q7bct8NPz6ONzY58VjnAX1vXWA7uzqsU1wCwLFFZXibP7qkEHH0xgGM2OB4lAeAw1AAAAACAS6IAAAAYKhXb9P/JLwBg1j3i/oTB/Uni/mPipr2C+91MRA/ZyZDJB+2iS1mi8XkjIDWfWDYXW144ghswMv+Ag9KTJO58wWO+AV1rAAAAAAAYFQoAAIChMqlc/nczA2DfoL95AYAz21lOGOM41JMMb69sU20B5J1YbNnMrjQGtzEh4GyNgLpesLESwFUwCQAAAAC4GAoAAICh0k0BII//yxkAQ3XHPWcdv395Q1PtXRFY5xWAB76J2kupmjkTNdFMXHr1Q1rtY3R0I6CBFZHjMQkAAAAAADAtFAAAAEOlReq/mQHgvYrLHzqyw8592vXBrbUw8ImB/ZhTMd0UANRl++3hbELZ+WGR+sUGFJP4AwAAAAAmhAIAAGCoVLyZSHER4FZnfC26zWzu77J7k/usDez1qYdWEfwm/TeRTPX6MwBOZAyNRCgJALgB/B4DAAC4VRQAAABDldcAECn+u3FYBrs7vC22GEPMO9yOFOXMn8Qay5smOa4sAGgmo5kB0OF8jYBOgkZAAAAAAICpoAAAABgqMedM1ZyKc+LEyrjVql/757DnS27be26smVZpodSVNltx25ifISZqqiJqoqm5dPRx9cgbAQEAAAAAMAkUAAAAQyXi1NSZOnMqrrwAgDSDV2utaWxYe7D13P7IPmjnNj0bDHluz929mYju3kl7g/630Uz8pXFUvYorZwDo6PN/kX1qANVv4WVqAEwCAI5BoQ4AAAC4GAoAAICh5uacOeedymYegEnWEXu282gzMe0M9xsRblU7KerKeA/OgfvLA0OePuTRwwoJPc+qTrzYsQdzzkzzTdNK5eZmXL4G0EYNABiC9B8AAAC4JAoAAICh5qbxZhLApgZgkjeTt44wurryVJnPzqT3sNkAwZkHwef2b7BzD9WHreONniZG3k41UDW/6QJkolnXy47PYRcDkIvUAIj7gX1N5lcPAAAAcEMoAAAAhkpUZz52puqd27QAOrhPe3vs/yky28Db6ZpkEKwBHP3aatvlQe/PqkvdB2KvGLxWjDERMZXNDAA10XRSKdyYc3YaAQHDTen3DgAAAHBDKAAAAIZKnCZp5GR7KWARCXb7OdUr1gMjq69rBOb7XjzggHpDcPi/dW6yfZnaE8tX1e0mJmIankLR3rs1Vg24QkIe/Ys5NZFpFQD2MoZGQADaOBkBAACAa6EAAAAYaq6SWKSmas5552qt5BstgIJVAauH9ZuEVgdNAmg81H8ZgK6NB+6k6w0El7vWNNbv0TuoUhjYLLTe6PAZBiJios5MN1+iaftaDOM2rUZATAIAGib1++ZO8VsLAADghlEAAAAMpfl1gEWdqJrqJursyQ3C1wDQ2gbBzvu6a40Wz+zPxndOCxg2gL7zbuihHV2Agk/s2cBk+0kb69tf4f0Ul//V/DIA2QTjuINTdWoAwBVN73cNAAAAcHMoAAAA9pCoc16dOWeu1QWolkR35z79g/TLu6WeBD/YBWjn/rtea8dFgNvNfMJbBp5XWyhLF42LAFdKGu097zUCvlkSMFXzefovU5wBkBueqh8z0eMwJP5A2wR/zQAAAAA3iAIAAGAPiTo1p6Yqznmn4qx74HlDeMB+LcSX3siotkHZOEh3BLzB/j/BpwwflS/dGX37trHQnjTQNYq/5yXa2xdD/NsfQkVMxamYmtcscumOjzleY64BNFASwD0j+gcAAADGgwIAAGAPc81bADlnquLUVMTL5gK2G93Xtg0O2Jd6sh8sA4S7ADUXrGsSQKAGoOEX6tLTBSi8phhkHxzCPyQZ7i8wNNooWWWTVkXBzLs4szgz582tJJr0RYAPrgGcG4k/kJvw7xcAAADgFlEAAADsIXEam3Omm3kA3plkxYPBYeyN9L0sA5S3YqKtyQGNKDVwSYBib11XD95dA6juLTxwPvBORGqX5A2O9LdiKwk9Wi53xcUWekpjYsH2q7fcstlVpnGmUSZxKu6LSz67ewzoLlAPaNQAKAngDt3jLxcAAABg3CgAAAD2MFdNLFZRJ3kvIJePMa9vlfeiaSfRPTMAhncB6roAQHu3gc0qo/K7LjnQs65/FH9ezLCOC/aWC+0yQDOvbz+rVXJovG5j/7Uv7yJvkbc4syiz6MnNpp5K0wgIGCfSfwAAAGCEKAAAAPaQOJ1p5MypaX4d4CLg3CbOJqLNkoDUU+meLj0SDOiLO2W6vn2i6SbW131aAA0oNjTefPBucHR/dcf7Dv9vb9BVRSiPdmPUf+Mjmbc4kziVKJVordFnd1//9F849Cfxx30i+p80fmsBAADctvtKAQAAR0pUHlwUmYssiiSOLM689ubRjbH/uVpMryKV4DTYBSjUtKfcz7Ye0HUr9YVKQWH7KsH8Khjr90fz5UsMH/7ftcYqO7TQHoKa8wm8xt7yawBEX6P5U+R27WECDs7ZmQQAnBzpPwAAADBmFAAAAHtQkQ9R8ptEscSxxbHNUou9paE+NsEyQF4k0KJ9f0dAHwiUGkl9qwxQiXYr3XL2mGQw4NNbZauy7FBbkMCtVI5JY7m6xrS5pvHGwjMA+usupppZnEmcSZSK+xInz/eX1QWrT5dEDQA37P5+owAAAAATQwEAAI7VH+3dXjjywcWPLlmmy9jiyGaRzbytO7L+tuqEgHZAL/XlqmB2Xy7XU36zVm1A6gvVXcmu71I7r28vF4m/Vpa3nzf4xM5KQP3u9rmVlL/rfTa/vMRe4syi1KJMos9u1vtJp2SvVP2SoT9xP+7H7f0DBwAAANweCgAAcLhhg8a3biMreZ/o+5f5F13EFuc1gLTeBah3EoBW1lfnAUgr+m8crZ0zAMqFwMB/bW4m2yH7tZ13aDb0ry7XF6xrBkC50Az9Q7F+sAxQW9ndaqleAHBxZnEqcSbRi86e3C30/zmM1Ss/TAIAjnEb/5wBAAAA94ACAAAc6IA4rzH+fKJcpQtQfhmAKNwFyERNLBhM523/Taw9PF86JgGU7X022+t2m8ZOglcX6JlksPMbYq1vdkf6H74NTgXoGf5vrW02yxrYLP8EJlIe6vzTbo5/JnFmUWZRKtHXOPl8xwWASyLux22b+r9iAAAAwF2hAAAAhzgm3buBMsD7OH50yVKX+WUA1l1dgMyk+Umrjw7p/9Pdrqenz49qsf/2o9L6Dgz/ZgZnAJTLJtozA8BEiqS+9sThw//Lu615ABZ4LRMxibxF+QUAMnNPcbKe9I/d0S4/8L+KqgBuw33/FrlB/F4CAAC4eRQAAGBvJ/m/5etmkUd6P9f3z/Mvuog1jiyObZaFuwCFx/5r7W412W+Pyu9f0xj4L8Xw91ZJoKvM0K5QtGn7G94xAyCQ/tfTfJPKys1Xq5mPdCx3fllgcoCJmHezzOJ8+H8q0VPMP/qNH5QzCsb91AAwddP9ZwsAAAC4W2QBAHA1050K4ETeF12AYokji124C1CjO00ZWKtWtxHprgS0tS//217ouSutA78rkrXAUmcNIJz+B5L9MoCu/AAEB/jX9qbN/ZhovtLXDrKaiHmLM4nzLkCLKHmKbqr/D0k6cElT/KcKAAAAgFAAAIB9nTx2nGgZ4P08fnxJlrqMNI5sFvV2Acp1jHOX7usAS+vAFA8FLh7QLhsEiwFDXiX0iuE1A9P/Yhutbt/+ajwkrYXQnAArCwN+W2IxU5FM48yi1KJUoi9x8uQm91N2Fuce+19iEgBuBr87AAAAgOmiAAAAezhfcnexUPJU3s/1vSu6AGkc2Sz1Ks1uNo2B/83mPyamnQl+z6WAazm+Nq8EELyV0Kt0TgIIfTus424r5e8sBpTN+svIfmcZQDq2aXz5ysJmOXMznw//lygT9xTNbil3PvKzDD/dgj+FwF2Z1r9NAAAAABooAADAWEyrBuBE3iXJb8uiC5DEkcVe0nojGl8E7lbpj98/CUAGhK49/YLa6X/1Iam8XPAlqt+B4BsYPvxfWp9R6p+9sdtqpSTc07+9pWjZXslXVnoRn0mylmQts1TitcRcAOAAWlk4rAbAJABM2oT+ScJh+F0EAABwD4gDAGCoC/x/8rRqAO/n8ePXoguQzCKZeV/tAuSLRj2+knmGCwC6PboD09E8Ia8WD7qe2H40WBiQ4v0E9Q//l8rHkcZH29xqGe5LKNwXCa/cNQmgdnGFYvi/mXdJqvNUk7UmK43/mr/6z/x2LgBwmcTq8pcIBkZlQv8YAQAAAOhBAQAAxmVCNYD3ib6bzb+sF7HGscapxKmpWB73axFy5ne9iLaCb2kl3cE+/g3t8f5ST/l7JgFIqxIg+ySxRwz/t44h/N1lAOvtp5R/Fa3/K8P/xYv4NErWkqw1WetsGc1+f3xIp/JTtcu1cvPTTgIAxuxWflsAAAAAEKEAAAADXTLCm0oNwKm8S5LfF9GmBZAkscwze8kHoZt43ZQBvKiKeDHXVwBQFbPKB+/v0tP1Delq/mOthfZOOi443LnS6mv6JgF0d/UJlgFMtEj/zVr7sXyD+qObMoCpeZdkMk81WWmy0vg/D6//eIg6DtfEnOQ0HHJyBbc5bZRPYQDjNIl/fQAAAAAMRwEAAMZoKjWA9/P4MUpWuko0yXTuZW22su0kABPxJqrWnXSXCxaM7Lu0Mn3rGuwfTP+D0X/XK1rH3WD0L9sPpa3POPwrfMTaG7SG/5vP3Hyttzb8/1RZubYWLqC/OxUwHtP/VQEAAACgiQIAAIzUJGoA38z1h8fXi9Uy0yTT1MvaZJ3656LzTzEDYFsP6C4ABAbX9+sK9PsnAbR3MuTl9qoBlCUNaaf/GvikPaURq+f7my/bNv9plgG8S1JNyuH/f7+6heH/Jx/4f/CZRWSPWzX+f25wcvw2AwAAuBMUAABgt2v9T/IkagA/PSb/LF5laZZpmk8C8LIyy9SsHv3njYCqQ9ddPe/2Ik40OJZ/oP6O/+01+75QsEQRiv5rt8Hh/4GHKk3/8zTf168B4EWsvJhwcSTrw//Vl9f+XetsGSVTH/5/8oH/J994+D6ZBIDRmvIvCQAAAAA7UAAAgFEbfw3g7YP++Pjqy/IlS5NM117mXlZr/6V1HeDy+sB5zC3VNHzbIn8zan5f9Ry1uYPgXIEDoteeAkC5YKLS/lz1KF+kebexpvFVSfmtvXI7/D/TJE//V5qs3OSH/58p/R/5CQVcGGcEAAAAcNsoAADA2I2/BvDjm9k/i9e/pWmWzTNNvaxM1t6vKl2AyjKAryTvZR8bV64xERUv4g79yMH+P3s9vRTcw4BJACb1S/5KK9CvrbFmeaAj+m+G/rVHTbyKT11l+L9L/ng9n+jw//MN/N95PIZscKr5KUfuEDjeNH9DAAAAANgDBQAAmICR1wDmM/nx1eOn5SJL08wlXuZe1itbSa0LkBZdgLyIiLhiMLsUWbYrbuVEiWjPZQAGXge4vwbQUQCo3e4uAGxXarWrTyj61814fxUT86Kb3L8c/r+ZAeAmPPz/hFH4Aen/tVADwFWM9owAAAAAcEIUAABgh5EEcyOvAXx8H/29eLNerzOXZG7us7WXdWYvIloM8691Aark2q77NqT9/eg5Lib7Xxhg50u2CgBanb5QKwBs75rVAv1tm6DKPIBim0DrfzERb1bOmfAiXsxXqgU+i/Ox//nw/9mfbyY2/H8M0f+5DxhBP8ZjUr8ecHr8LgIAALgfFAAAYDLGXANwKj+8nX96ecjSNIsSn819tjZbFYm/VqYCeBHRzYJrtABq3Q74yMfGGDtT2Z5pASYiZqbbu3nNoX8GQHmpg3C3H9tc/rfR8Me0jP4rub+IN/HeJZvL/26G/7/543Eyw/9Pm0NdYOD/OXJ8agMAAAAAgHOgAAAAUzLmGsC3b9zHL68Xq2Xqksylma69rFJbVKJ/K/r/qInXPP03F5wKYOJVK5MAAuHoJQ+GVZas7BZUH+8v9cRfKuG+tOL+Sut/rYz6N1/fxhfb+OLRWvSfNwUydal7WOfpfz78fyLd/88d/XetHP70kyPoxxhM4dcDAAAAgNOgAAAAV9aTBgYzmjHXAD6+Tz4tXmVplkWpj+Y+W5mtzbIisy7feLlQTf9bw/+tuxFQ52HY//AEewrZ9kGVsslP+zldExfynj/5E/PR/t1X+t12B/Kt2+o2PvBlXsSnszdr97iKHlduvnbx369e//lm7MP/zzGCfuDK0aI2gMuY1nkBAAAA4EgUAADganaGffkGEwpr3j7qx7evviwXmUu8W2du7nWd+i+6GTS/+SgmWnworaT/wWsA9NQAqhq5/3FVkvY3xqy+x1oBoNH3v7jNaxjNSQAdlYBmt59qJcC2zX8aXYC8iE/jxzRP/6OHZZR8mT/+/vZhzMP/z5FxH9/255IHrCfopwaAcxvx7wZcDr9nAAAA7goFAAC4juH/+90uA4x7EsDsn69v0jTLoiRzc+/W4lPvlyKyjcqrUwHUiZhaM/3XIvo38SoqoqquaJ2vHccjWAY4ydGy0HLXPID+hW3bn7w8oIH03xdTB4q7Fhr+r967JI0eV+5hFc2XUfIym//y4e0fb0c6/P8y0X/Xyn13svMpJGiYnNH+wwEAAADgfCgAAGpA+YIAACAASURBVMAVHBAdnnSI+xnNZ/Lx/ePnxXOWJj5KxXv1lmbm/aqyVWUqwOZYNMb+e9vUAHQ7A2B7PeEQqy5VJhhsV5cTD/Y6loHpAJt9WyX312rnokrcr9XoX7aZvgXG+2u1DGDbgf9FhWBzvd9yEoDXKIse1y4f+/+wjOe/fXj38zez7s9yTeMc+H8tTAIAAAAAAFwGBQAAmIzGVIDR1gA+vov++fomSzPxXrxJ5tVZ6s38unjL7YsBSJ776/ZiANUuQCrirPVkEdkx/N/q21jPxq0db9dXH+sY/l8tBlTj/moxoFYGaLf6aS94UQuP/RcvKql7TKOHVfSwjB+WUfL3u7c/fzP34/uZGHn0P74DRg0AZzHCH3UAAAAAF0ABAAAu7choz0ZfA3BO/ufbx2zt//psmplGpt7U+9R7M79pm7/RGKpfXAzAtJgNoPUygBZdg/InmVgj0B8+ur/90M7vzM7mP9Xb1kK1q09zBoCXzeUBfLHlZh5AoAWQehOfRa/T6HEVPS6jh2WUPL1+/fM3j4tkdD8OpP/AGPCjjhL1RQAAgHtDAQAApmecuX/V60f9n+9eZZn/x5t4k8zUmTpL069i1UHq1YH1WkThjfS/jP7bvYBCob9J3luomARg9SsBFAtanRBQV25Y32nlIatv0ewCVF7pt5iGkIf+Ehr1Hxr4byaVVj/NL/NZ9JBGD+vN8P9kkTz8+u3rv18PuVry5ZwpY9Leu8fs6sL6h/kzCQAAAAAAcBIUAABgxNoRYCUwD454H493b91/p6+zNHvyJt7Ue/Fe1af+a5HgS/29l2vyXkA9kwCqSXd7RkS1+Y+Etmx0B6q+en1d604R/Usz/d/m+9Vbay03vrqb/4S+8gsAeBen0eM6elzHD8t4vozmv3//7pcP4/oHfeQD/498LjAt/LQDAAAA92xceQEAYCuYoVaa1Y+/BvDtN1GWvv3f1MumBmCamXjL/Nf8LWstfs+ze1fcd0WdoDkJwMSrqoiaqermma3h/NWB/+1DdUD/n8Y27bY/sgnxtbq+7PbTWQMopgv4okeQb/f82dYA1KXRYxo9rvML/0bJX9+++/c3yYA3fzkXSP/HEP2fe5A+kwBwEuP81wEAAADAxVAAAICLGpro9W9XlAHGXwP4+DHO0nf/+0cm3vJrAqv3a++z7KXYpKhmiFSG9rdbAG0nAaioWNECyFRELV+5PSSmXdMCtHrMpLiEQF31qdUlrd9tTwLIb2utfrpmAPjAXQsO/8+qxYB09iZP/1fxwzJOPr1988v3D6vR/GM+8oH/YztHdkb81AAAnBa/UgAAAO7QaDIDAMC+Rpv61/340yxN3//7T8vnAYj36m3tzWdLEWl8hkrTfLfjSgDVZ9S+usb7l51/LJjx1y9I0H5UKk1+pDLjoGz0Xz66s/NPoBJgPU3/N2P/JYtf5+n/Op4vZ8nXh8ffvn/19DCWH4I7GfgPTAg/9gAAAAAoAADA+FhwMRTl2Has+2jLAary03/Ns/Tdb39vGgGJNzGfem9+XYz/r7bKKSJ+zZ+96QWkxSSAYrx/+zrAKqKi9dDfmjWAyoyDvboAtfv/lAuttj8ijc4/2sz9rWj4s6kBaKXtT60SoN7Ei2oWv8rix3X8uI4fVvF8GSe/f//2z/fR8G/E+Yw5+h/nSTEckwAAAAAAAMegAAAAI9OR/ktHaD2JGkAcy4//9ZCm/q/NJABTKy4G4NPgU0xMzWqXBNhOAgh+iYiZqFp5V0W07JOk2xqA1LoAbe92acwaCHX+sR1j/61dA7ByfbPzTzEbIMsvBmAuzuLHLH61nj2sk8d1Ml/P5n989/7Xj7OBx/+szp3+33b0T76P85nEKYBL4rcNAADAfaIAAAAj1fU/6qNN+fs9POhP//Uqy/w/m0kAXrx3Jj598dlL6Eq51VY/20BfmsP/g/1/tssmotunt7sDSXF3p2A5Jpj7S+VTSOtzbdv+VO52fvlo5mevstljOntcJw+rZL5O5n9+fP/LDw9+BD8H40z/R3BgTokiAQAAAADgYBQAAGBMhuV8zRrAFCYBiMjrN/rjT6+zzD95E++daSpRJlFmzqcLER/6+Hm3nHIeQO3av7tqAFK9tU3zn+Jqwbu6K9XfQ3lkK8P/TSoRv7QSfwlF/9XmP0Xnn3zZ6tF/3ikpmvvZYxZv0//Vw8MfP7z/5acku3bvnxFG/6P9yT8eNQDs64ZPBwAAAAB7oQAAAGO0M+ybaA3g/QeXpm+yNFOTSFwkLjWXWZRZ5LOFWNbqle82w/g3cwIaFwG2rhrANu7flgSKW5NW1Nx/vK3yX6uvz/e1ZwHAykkA1eY/5cKm84+PHzdj/5OH9exhlcxfXr3646e3v/54/c4/o0r/R/vTPhDhPgAAAADgfCgAAMBo7JkCtmsAk4hCv/s+cvr+j9/iL5/cpgYgUSYuE+ftxbJVffNqR6DyVotiQPMKwLpdWY3+pXVbHuuB6auFlvtzfwlE/7UWQMH035t4VUtnr/3s0eedf2YPq3myePv69/96++d3Vx75P57ofwo/7KdEnQDD3dvZgSH4BQIAAHC3KAAAwA5jzt2aneyLce8jrwV88100T97+MYv//utzpC6SKNuUAaLMnM8Wkl8y10zEbRr2iIlV0n81EdW8DGDllX6rFwQOdAGq30pruYdVtuspANTvanvsvw8VACxv+KP5XZU0fu3jx2z2Kkse1snDap58/fD2j/96/feH6uyHKzhr+j/kOzHmn2oAAAAAAEaIAgAAjMOh2epEawCv3up/z1/Nk/iv3z+v1KXqnLpMXGZRJpFPF0VWXvbMcUWXI2diatqaDVCtAYieugAQWt4E/bbpAtQqADSvbNyR/m8uAGAi3rvI4scsfuWTx2y+Sf+fvnv3x79ePb255vfzTDWw/7+9O+1u20jQBVwFkqDkxHF6SXK77ywf5v//rLlzbnu6M4ndcSwRqPlAiaLEDSSxFMDnOT5umqIIYk3zfYHC7vo48rIJa9gy5lxGAgAAkCcFAMDojbQDmJfh//x7eVd+//H/f/7t02/VugYIxSwU6+sAUr0KYTPy/ibrf3mQni4CSCHEELdvDhxavwJg3+MDJ/5vHm+uAHgpMw4N/pNCTPV8Wa/v+lveV+Xdqrx7WJa//Pjh41/vvtxPLf1vcuJ/zlvvUHQAwAUcNwAAbpkCACAD6ci/mr7BGDuAEML3f5kvlt/9/b/mv/7P5yLGKharOCtiUT3O0uOXuvr9eSbWZ/3vqQFCiCEWz9H/6wIghT3Rf4xbi/jQ4nkTtKatJZmef+94AfD6CoC9g//Epxogzcp6vnz6Uy5Xy/tquXxYLn/+64ePf10+DnrT3+7S/yOLHriYPQgAANimAACYiEMdQP6++WOxuPtm+Z/zv3/8ZVUURZxVsSjirApFEWdp9TXU1aYG2Pp7qwZIm2fCm5sBxOe0OT796PV4/hdfAZDCseh//5+d0/9TSsUsLd6ldfS/KKvFslqU1bJ8eHf3j79++Ph/F2m4ldhz9D+SrfVFu8un+an9LgIAAACgOQUAwGndJm7tvfXeDiD/iwBCCOW7+ON/3C3uZj//16ffn28JUMQiFYtUlGn1kKqvoVrXAEU8VAM8Lcr4pgM4MBzQRpPFk3YerEf4CfvG+g+nov8UQh2Kop7fhfkyLZb1OvdflPWirMrF12/v//GXb//xlyH/G91n+p//9pkbHQDQnMMFAMCNUwAATMreuH8UHUAswh//dbG4+/Dzfy7++cvnVYyzYlYXZZqVqXhIqzLFh1CtrwbYPut/cwOA9HwPgBBCTCGu7wO8/vHTHQJCuKgDOHADgJebE2w6gLAT/afnOwFsp/+pni/X0f8m/a/LRbUoV++Wn/70zS8/LH/7MNga6ygqurh7AQAAAC6mAACYmu24f0Q3A1h7/8Nssfz25/83+/Vvn9JsXs9W9eMizcr6cV0DLMPqa6oeQr0KTxH/Ovd/ug4gvQz18/oigLT1+OBNgPcuoTfn/u/+89QQQOmlAEihDrOyWtyl+TIslql8Oeu/Khdf/vz+lx/ufv1zcf1ivFhvJ/6PYmvMmYsAAAAAaEIBADCo7jO8Ed0MYO3uu/jj3bu7b8t//v3Ll//5LT3M03xVz8o0e0iPD2lWpseHVH1Nq68hVa+uA3iqBMLhUYDeZNGHQ+mXeLVJARAOpv8vFwHUqZin+bu0WIb5MpXLVD5H/4vF1z988/nH+19+mFeD/mdZ+j8smT7Xs3PxhqMKAAAKAIAJenu+//os+fFkQ7My/OFf5t/96f3n/75/qgHm8/S4qB/K9PiQZg/hsUxFGdb3BqhXmwLgaT73FQBPgwLF7ZsAH8il43baf7QAWN8G4O09ALZH/klhfSeD2SLNFmGxTItlKMu6XNaLRVUuHt7ff/np3S8/LB7uh1w53UX/4fVSHssWOAoKAwAAAE5SAAA00mfW1vq0NtH/iDqAEMLsPnz41/m3f37/5eP951c1wCLNy/RYhseHtCrD6iFUj7FehVSHELYuBXhVAMRX9cDGvsevlv7RAiAdOf2/SLNFmC3q2TzNFmH9d1mmskxlWZWL1f3yy4/ffP5p+dt3A6+TfE7833v7ijxl8sF0AMARjg8AAAQFAEDPXgV2XX41f5P1j24goI3Zffj23+b3P77//W/3//zvTQ2wWtcA4bFMjw+hWsV6FatVqFexXoW62lwHsHNLgHD0lPRDEfThGiBu3Q04Pp3vH2aLer6Is/lT7j+fp9k8zOdpMU9lWS0XX3749stPd58GHe4/9Bj9733m3B9JsgAAAOBcCgCAyRr7QEDbZnfhm3+b3/34/ve/3X/5+FIDpHUNUFWxqkK1itW6BqjWfUCsVyFVOwVAeL4z8NbjjbiTOb8MBJSeX7tVA6zH/4mz5/P9F3E2j/NFWCf+67/n8zSfhfkszWdf//DNl5/uP/80r4f+L3AOJ/6ftSm+3Nr6nN8alwvO6HcRAAAAAEcMHT8AjMfYg7a0HrJ+tB1A2KoB7v92//vHL7///Fuaz9OqilUVnv5ePTcBVaxXT5XA+pqAehVCdeAGAFuPX40RtHO+/6YMKIoQixCLFItQzDe5f5zP42ye5m9z//qb5cP75cOH8suf56v7rpZPc4On/9dsgZvxrIBtIz2w0xEHSQAA1hQAAAPp5av5eLP+I7avBvj945fV54f09TGsqlDVcVWFqgpVFVdPNUDYXBOwHiYopac/4elBCOu/65BSfFkrz3VPLF7/iTEW6/Q/xaIuYoxFLGZhHf3P53En96++Wz58KL9+mD0MPdb/Wg73+21lQQxYyOUWq429mwQAAKA7CgCA0dqb+R3NVidwEcDGugZ495f3q0/16lO9+vS4+vxQ/fNreqziqg4v1wRUsVqF6unvIqSYUpFSkZ4exPDq8boSeLpnwib3L2J4yf1jKooUi1jEEItYxFjM4nweFvOwlfvXH+5WHxb55P5roz7xf++7Cb7XLApgmwMCAAAbCgCAcTr05f5UtL/pAKYhLsLij8Xij0UI8/TbffWprn5drT4/rj4/1L89hKoKq/plgKCqes76w0vov8n9N3+eC4AUi1gUIcZUFDEWqYh1UYQY4/PfsYhPDxYvuX/1/eIxs9w/9Hji/+4/Tz5/5WeQcwEAAMAhCgCAM7SSNsb1jWO7m9DrfP9Q2j+hFuBJfBfm74r5T+UylfWv7+pPdfXrqv78UH1+qL8+xqqKVR2fo/+YwqvQP4VX6X9K6TnoT0URilitg/4YQ7Ed/ce4mNWLef1hWX2/qL/PLvdfm9iJ/7tv3mcHkG3foAsB1hwKAADYpgAAGKHrv9xPL/7fFkPxIRYfZvMwSw/L+lNd/1rVn1bp6/qCgDpUKVZ1qOpU1a/S/5cHKcaYiljHIhRPQX8s57GcxbKoy1kqi1QW9TKmsqjLUH+b6dLsKAbKJ/1nmw4AAACANxQAAFN0NN/f/HDaLcBaLMPsT8XsT0UIi1CFWIWwCmGVQhXCKoVVCqsQqhRWKa7q9YNQ1WGV0jymchbLIi6LsIx1WdRlSGWsy51JDDFfTUx12J+9UxF8AwQHQwAAdigAAHrX7Nv5wUyz4Zf7k+n+VO4GfIZZSLMQyrD3FPbt5RqrUM96/GAdyGXYn0OfY5ybXdfJWjsjjLXwQQAAAJgIBQDAeVq7DUALn4WupMbpf4Y5dhYn/p/8ELdVPQH0wf+1AABglwIAIF/X9gQuArg9w6f/qfHmNLYtbyzJmn4RAACADQUAQL/ySObGFr1yWqfRfziZ/qcDzwPQizz+/wUAANlRAACcrc9RgFwEkLN8FlqfJ/7veebi9H88W964kjUXAdym8exPAABAfxQAADdPBzByQ6b/6cDzDE0HADfF/g4AwCEKAIBL9BmuvZrW+VONTaJ98f849Tzsz9tnpP+tkt8BAADQOgUAQI9eJ3zNW4Qu+obdzF8LcJbBl1XPJ/73MflcjXReXQQAN8KeDgDAEQoAgHF4yvLOjPSeYlwXAUxL/yf+v31+p8oalvAL1hzIAQCANxQAABfq81bAmxeH819/hhRSFB7lbuD0//ay9lHPsYsAYPLs4wAAHKcAAOjLge/o5yZ0DWuAtzGu80InQfq/K8sPdbbu5kIHABNm7wYA4CQFAMDlBkzWtoPadOD5S7gIoJn+F1HX0f/uP7ud9olJ5kK4xuhoewEAgG0KAIDhXVkkNM16GsZC0qP8DJb+p8M/ykOnAX1v6X/XE3IRAEyS/RoAgCYUAABZyCqhSynEnEPfW9JD9L/nmX62xau3sXx2GYCeOQACANCQAgDgKk2D+xa/qV/TFTQ/u991AIf1tmD6Tv/3TS/braDr8Gti4VpWFSNdc/wGAAA2FAAAuegpoWuWDKUQogxpOB1tCfuH/bl6YpdsKddtWlNK/+XydMHxe9ocNwAAaE4BAJCRrDqA815Je3o68b/BZPJc+VNKvvqcFxcBwDTYkQEAOIsCAOBap2O1c76s99YBnBzlX/i/V9fLpI/0P7U/F+dtLVdMvoe9Q7jGNDiGAwAAQQEAkKHTHUArLUGDcMhAQH3q58T/5un/uau96ZYi/R9iWmsuAoCxswsDAHAuBQBAC1qP1brO6V4Gf2+SxuoAutfPif97nmxVd1uKzKstOoCb4uA9MXZeAAAuoAAA6Nil39fjVb99+p2fpN2nOKiL5dTbif/7n2/biQ3q/E8w1VPyBXn0QwcwGQ4aAABcRgEA0I6Ozqvt4tqCPQ5HRE8/kSF1o7cT//c836VWtpee0y7hGpAtBygAAC6mAADIXYuXAhzLZE9GtjqAtvV0ecfu8+e+z0U2E395q8Zv2n/UdVNlg1GAbo2DNwAA3DIFAEBr9sRq7cVsm/jmsrdslP7oAI5qcdb7jP7f/qh3b2Z274cZNo++qfR/TQdwa2774D169lYAAK6hAABoUw+x2naIc3JaZyc++1IiyVG7bir9f/H8OXJLsnL7PNARR/KRcowCAOBKCgCAznT/rb2TNOd4SiRDukLP0f/bn57Deu5IPlmeiwBukP16dOykAABcTwEA0LIpxGo6gB153tL2ePrPSTc4+A8wFo4YAAC0QgEA0D4dANs62hhe1sCBCVhFx0n/p3Ck4kwO3mNh3wQAoC0KAIBuTOC7e3o1brvM6DLjPfG/zZWe39YzgR0ULuN4nj8HKAAAWqQAAOjERE6tPRIU3VKGdM1I+q07eeJ/i6a6kvvfN7M9GkzkSMWZprprT4NdEgCAdikAALoykWRNB3CRwaN/a+YQ6T8Ex+9cOVwAANA6BQBAB56/wesAblOH6X/Dtz70svNXVrrw93I0yM6Y/xFgIocpzjelvXsa7IkAAHRBAQDQrU7CtSHOYU7xRnOis+Z6yBP/m0z70sLmqqInj+1G+g97qXEz4XABAEBHFAAAbdv5Ej+RE2wPpUTSo2fDpP/nTvXSk35HvZ6l/8dN5BjFpUa9d0+AvQ8AgE4pAAD6MJF8TQdwWN/D/gyxPY1xPQ+1301hf+eWGA5oKI4VAAB0TQEA0JN1sNLCV/1h04Ib6wCazNMAJ/5fP8krxgIK41nV0v/mJlJScp2JHsjzZacDAKAHCgCAVp36Nj+FlO3GOoDj+k7/89h68l/VAy6nPFYRXGhcJd94OVAAANAbBQBA36bcAdySUZ74/+bdrliJm8+S24Yw+BUyMAGO8Z1yoAAAoE8KAID2NP5O39pwQAPamw9NKzQ6MiujT//bk8n5wjksnhw+wzWm0E3Snkx27YmxiwEA0D8FAMBgLonbsgoPbqAD2Gs69/ttb2UNdUFAPjtEPp8EWqQGaItDBAAAQ1EAAAxp9JcCTLcDOHSbgw4nlOuI/2fZ/sgdbQUZLpUMP9JlXATAXmqAK9mtAAAYkAIAoCVXfL9vWgMMGiEcjH4mEfc30euJ/1dPLIfVsjsTF3yk/IOz/D8htCLbO3/kzPEBAIDBKQAAcrGJVMaXF+yGzTnEz+2Zzpg/e6fV45oa37Z91MRmBxpyQUATjg8AAGRCAQCQnQzHBTod9EyrA9j+4B2uiI7H/BnzGhiBrPbQFhkFiIZcELCX3QcAgNwoAADa0ME3/rcXBOQfKkwub+5oke8/97+biU1uneQi/90ReqMJWHNYAAAgTwoAgNwNfkHA5ZnOOOPnThd43H3rjlft8Cth+E/QJhkfHHKbTYBjAgAAmVMAAIxB6mtQmh3n5ThTGQioq0H/r4n+j7z41BI+sRLGuY76J+aDhrZ3lqkeXRwQAAAYCwUAwNV6jwG6KgN23itekNyMP03uI/1vOI3mL7uyA+AUYR9c5s2+M+oDkeMAAABjpAAAyN7RyGH3hPtWJrFnnPrmsU3a6ShGHflcLZ4b/V+wFnUAnRH5QYtG1wc4AgAAMHYKAIBJ2Q1TToQXr398YqCYE684+rv5xzzdOCP9vzJn0gG0TfAHXdvdy4Y9RtnrAQCYHgUAwHWyTwtOZ/pnaVgDTOVmANdoGv33uwntXw9dr50Rrv3s92yYpuO7XisHEns3AAA3RQEAkLfugopr3rlJnnvbHcDp9L+LNdtsCV9zLcctEA5CtuyeAABwLgUAwE26PkRpkiLfUuK/7SX97y36P9+trpxj8lgzAAAA0BoFAMAVus4LO3r/Ft/2ZIp8azcETlvzt7ucMwuYXQqwkdmaAQAAgHYoAAC4jg7g2Ylhf/rJmM9fvDdeA4j+AQAAmDAFAMCNGW7o+ctfn79Rnfi/1+YzTmzNHDGG1TIAiwUAAGBKFAAAueooqe/I8Ux/eon/tk36P87o/41buCBghKsFAAAALqEAALiUEPGNszqAyVQCk0n/X6+R9muADNb4uFYIAAAAXE8BAHAzeog/b6oDSCGEA+n/VJLmyYwLNJUV0jkLCgAAYGIUAABZGm8OdyMdwA2k/9u252ksa2yK6wEAAADOowAAuA19pqGTvx/Alen/kdeMYclkflmA3P9iFh0AAMD0KAAAbkD/wd7xIeS3O4Bx9QHpwONDzzT/6fZrRrJA3szQgJ9acg0AAAB7KQAALiJxbOJIuD+6DuB19P/2814f/bdooOXZTx9gz+uIBQsAADBJCgCA/EwpimsYRmfeAVyc/l+2KjNfGs1MaSsGAACAkVIAAEzd4EHsoTh7LDF3evUg7v3R8V/s2SiWKjkZ/CABAABARxQAAHSvSQeQZx9wWfovT90rz1V882ytAAAAE6YAAMhMu2lcPtne6K4DOHK/3zxP/AcAAAB4TQEAcD4J72VOZv35lAHSf26DDRYAAGDaFAAA9GhvxJ/bQECXpf+SVMbGNgsAADB5CgCA6coz3su5Azge90v/mRDbLAAAwC1QAADk5EYyuTw7gHzS/8GvgWDqbuRIAwAAgAIAgCHkMNTPtiPD/ux9psmPAAAAAAalAACYqPyD6d0OYKiLAKaU/mdVq5ClDDdbAAAAOqIAACAnPXcAJ8f5SW//1XnALsGnS9J/AACAm6IAADhTd/nZDSZzw94MoPko/69fFc95/Xkmn/7nNvTTjbnBYwwAAMCNUwAAMKiTHUBHEz35zKEnu3P9LMvWOUz6DwAAcIMUAABT1HXUt/f9L06fj3cArZcBV6f/KYTY+hKW3dMl6T8AAMBtUgAAcI4md8S9IMvupwNoHvSfikvXP28ttG/ljVQI7CP6BwAAuGUKAACaaZ4jXtYEdN0BNDzxv6EYQmqpmBDc0xnpPwAAwI1TAADQwGU54rnnye99/fUdwLnn+J8zs1d1AC1G/1oEdkj/AQAAUAAA5CHnrO7Kz3ZuRr77+mtS9nNP/D9/Zi/8dCJ7upTzEQUAAIDeKAAAJifD5O+CSwH2dgBnZe0XjPlz1qKLL68/b/5aj/51CbyW4TEAAACAQSgAAM5xg7lai7N8bnx/TQeQjv6zya80EV/91umP1kVSL/0HAAAADlAAANCjs06Vv6wD6PrE/zcadgBienpxgx0lAAAARygAADisozSx+aUAu4XB8Q6gnxP/39jco3jrf2PoJfTXK7BF+g8AAMAbCgAAhnDNpQB7O4BBov9tr+flmvsWXzZFbpnoHwAAgL0UAAAZaHec/Qzf6vgkGo7pv7cDOPSeTabbsT46AJD+AwAAcJgCAIChnXVf3/j6nxdMq0dnXedwntbftLeyQjHSHtE/AAAAxykAAMjAWZcCNHzl3l8cQvuJtwAd6T8AAAANKAAA2GeQcPHcGuDcFw/HWe+0KIMtGgAAgHFQAACQmbPC8vFEoa0NB6RJuGHj2d4BAADIggIAgPx0OHb+wK69FGCKy4QmRP8AAABcQAEAwI5MssaJ1gCGA+IsmeyOAAAAjJECAIC8TbEGuHCeprUQaEL6DwAAwDUUAACN5R/F5f8JLzbF0+bPm6fJzT7HTXhvBgAAoDcKAABGQgfAbRD9AwAA0BYFAMDQpH3N3exwQNOaZQ5xMAAAAKBdCgAAxmaKp81PeAqPaQAAAOpJREFUcZ44g+gfAACALigAAHhtFEnkRC8F2D9D05pN3hjFDgcAAMBIKQAAGK3J1QB7OoAJzR1viP4BAADomgIAgJGbVg1gLKBbIPoHAACgHwoAACZhQjWADmDapP8AAAD0RgEAwIRMpQZ46gDGPyNsE/0DAADQMwUAAJOzyVnHHKC7DmBipP8AAAD0TwEAwHSN+oKAqAOYCNE/AAAAQ1EAADB1Y87Rx/zZCUH6DwAAwKAUAADcADk6Q5D+AwAAMCwFAAC3YbQdwGg/OAAAADAwBQAAN0OUTo+c/g8AAMDgFAAAAAAAADBB/wuFJ/U+2zZdXgAAAABJRU5ErkJggg==" + }, + { + "uri" : "data:image/png;base64,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czpzdEV2dD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL3NUeXBlL1Jlc291cmNlRXZlbnQjIgogICAgeG1sbnM6cGx1cz0iaHR0cDovL25zLnVzZXBsdXMub3JnL2xkZi94bXAvMS4wLyIKICAgIHhtbG5zOkdJTVA9Imh0dHA6Ly93d3cuZ2ltcC5vcmcveG1wLyIKICAgIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgeG1wTU06RG9jdW1lbnRJRD0iZ2ltcDpkb2NpZDpnaW1wOjM2MzE4MWE4LTg5MDMtNGVkMy1hODQ4LWZhOGE3YWU0NzgyNyIKICAgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpkOTdiYmVhMy1mY2FlLTRjOTMtYTIyYS02ZGZhZjZjZDBkNjEiCiAgIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0ieG1wLmRpZDo4ZTY3MjU2NC01NTZiLTQwYjMtODhmYS0yM2YxZTM1MjQ5OTAiCiAgIEdJTVA6QVBJPSIyLjAiCiAgIEdJTVA6UGxhdGZvcm09IldpbmRvd3MiCiAgIEdJTVA6VGltZVN0YW1wPSIxNTE5OTQyODkzNTY5MTAyIgogICBHSU1QOlZlcnNpb249IjIuOS42IgogICBkYzpGb3JtYXQ9ImltYWdlL3BuZyIKICAgeG1wOkNyZWF0b3JUb29sPSJHSU1QIDIuOS8yLjEwIj4KICAgPGlwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25DcmVhdGVkPgogICA8aXB0Y0V4dDpMb2NhdGlvblNob3duPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6TG9jYXRpb25TaG93bj4KICAgPGlwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6QXJ0d29ya09yT2JqZWN0PgogICA8aXB0Y0V4dDpSZWdpc3RyeUlkPgogICAgPHJkZjpCYWcvPgogICA8L2lwdGNFeHQ6UmVnaXN0cnlJZD4KICAgPHhtcE1NOkhpc3Rvcnk+CiAgICA8cmRmOlNlcT4KICAgICA8cmRmOmxpCiAgICAgIHN0RXZ0OmFjdGlvbj0ic2F2ZWQiCiAgICAgIHN0RXZ0OmNoYW5nZWQ9Ii8iCiAgICAgIHN0RXZ0Omluc3RhbmNlSUQ9InhtcC5paWQ6YmU3YTg0MzgtOTJkMi00MjExLThmODYtZWNiMGY2YWRjMWUzIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJHaW1wIDIuOS8yLjEwIChXaW5kb3dzKSIKICAgICAgc3RFdnQ6d2hlbj0iMjAxOC0wMy0wMVQxNzoyMTozMyIvPgogICAgPC9yZGY6U2VxPgogICA8L3htcE1NOkhpc3Rvcnk+CiAgIDxwbHVzOkltYWdlU3VwcGxpZXI+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpJbWFnZVN1cHBsaWVyPgogICA8cGx1czpJbWFnZUNyZWF0b3I+CiAgICA8cmRmOlNlcS8+CiAgIDwvcGx1czpJbWFnZUNyZWF0b3I+CiAgIDxwbHVzOkNvcHlyaWdodE93bmVyPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6Q29weXJpZ2h0T3duZXI+CiAgIDxwbHVzOkxpY2Vuc29yPgogICAgPHJkZjpTZXEvPgogICA8L3BsdXM6TGljZW5zb3I+CiAgPC9yZGY6RGVzY3JpcHRpb24+CiA8L3JkZjpSREY+CjwveDp4bXBtZXRhPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgCjw/eHBhY2tldCBlbmQ9InciPz7YnkG3AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4gMBFhUhCzMkFAAAIABJREFUeNrs3SF6E2/fhuGUgyhWgIpGjcSwAGx02QHrYQdEY1kApnJUNYoVVCH6iu/7c1AoJbTJZObKebpWVNz2ufqbi5vrq9sVAACz9OnrdyMAADAbl8NoBAAAmLFnJgAAAAAAAACA5RMAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAMzYdrM2AgAAAAAAexEAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAPBXl8NoBAAAmDkBAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAACYue1mbQQAAAAAAP5KAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAADwoMthNAIAACyAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAsADbzdoIAAAAAAA8SAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAIA/uhxGIwAAwEIIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAALMR2szYCAAAAAAB/JAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAADgXpfDaAQAAFgQAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAIAAAQAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAAAAQIAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAAAAAIEAAAAAAAAAAAQIAAAAAAAAAAAgAABAAAAAAAAAAAECAAAAAAAAAAAIEAAAAAAAAAAAAABAgAAAAAAAAAACBAAAAAAAAAAAECAAAAAAAAAAAAAAgQAAAAAAAAAABAgAAAAAAAAAACAAAEAAAAA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" + } + ], + "materials" : [ + { + "doubleSided" : true, + "name" : "Material", + "normalTexture" : { + "index" : 1 + }, + "occlusionTexture" : { + "index" : 0 + }, + "pbrMetallicRoughness" : { + "baseColorTexture" : { + "index" : 2 + }, + "metallicRoughnessTexture" : { + "index" : 0 + } + } + } + ], + "meshes" : [ + { + "name" : "NormalTangentTest_low", + "primitives" : [ + { + "attributes" : { + "NORMAL" : 2, + "POSITION" : 1, + "TANGENT" : 3, + "TEXCOORD_0" : 4 + }, + "indices" : 0, + "material" : 0 + } + ] + } + ], + "nodes" : [ + { + "mesh" : 0, + "name" : "NormalTangentTest_low" + } + ], + "samplers" : [ + {} + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0 + ] + } + ], + "textures" : [ + { + "sampler" : 0, + "source" : 0 + }, + { + "sampler" : 0, + "source" : 1 + }, + { + "sampler" : 0, + "source" : 2 + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png new file mode 100644 index 0000000000..fde7999381 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentMirrorTest_BaseColor.png differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentTest_Normal.png b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentTest_Normal.png new file mode 100644 index 0000000000..2310cd7174 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/normalmapCompare/NormalTangentTest_Normal.png differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_0.png b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_0.png new file mode 100644 index 0000000000..0ed44019b0 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_0.png differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_1.png b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_1.png new file mode 100644 index 0000000000..eda994b51e Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_1.png differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_2.png b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_2.png new file mode 100644 index 0000000000..b1206cf77b Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Image_2.png differ diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Material.001.material b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Material.001.material new file mode 100644 index 0000000000..79ba02d1f9 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/Material.001.material @@ -0,0 +1,38 @@ +// generated by blender2ogre 0.8.3 on 2023-11-08 13:20:42 +material Material.001 { + receive_shadows on + technique { + pass { + diffuse 0.8 0.8 0.8 1.0 + specular 0.5 0.0 0 0 0 + + // additional maps - requires RTSS + rtshader_system { + lighting_stage metal_roughness texture Image_0.png + lighting_stage normal_map Image_1.png + } + alpha_to_coverage off + colour_write on + cull_hardware clockwise + depth_check on + depth_func less_equal + depth_write on + illumination_stage + light_clip_planes off + light_scissor off + normalise_normals off + polygon_mode solid + scene_blend one zero + scene_blend_op add + shading gouraud + transparent_sorting on + + // - base_color_texture + texture_unit { + texture Image_2.png + tex_address_mode wrap + colour_op modulate + } + } + } +} diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentMirrorTest.scene b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentMirrorTest.scene new file mode 100644 index 0000000000..1b89b14e87 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentMirrorTest.scene @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentTest_low.mesh.xml b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentTest_low.mesh.xml new file mode 100644 index 0000000000..f81e82bae0 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/normalmapCompare/ogre/NormalTangentTest_low.mesh.xml @@ -0,0 +1,22260 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/jme3-examples/src/main/resources/jme3test/scenes/unlit.bin b/jme3-examples/src/main/resources/jme3test/scenes/unlit.bin new file mode 100644 index 0000000000..20a8b8d46c Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/scenes/unlit.bin differ diff --git a/jme3-examples/src/main/resources/jme3test/scenes/unlit.blend b/jme3-examples/src/main/resources/jme3test/scenes/unlit.blend new file mode 100644 index 0000000000..a76d772646 Binary files /dev/null and b/jme3-examples/src/main/resources/jme3test/scenes/unlit.blend differ diff --git a/jme3-examples/src/main/resources/jme3test/scenes/unlit.gltf b/jme3-examples/src/main/resources/jme3test/scenes/unlit.gltf new file mode 100644 index 0000000000..746095c133 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/scenes/unlit.gltf @@ -0,0 +1,236 @@ +{ + "asset" : { + "generator" : "Khronos glTF Blender I/O v1.4.40", + "version" : "2.0", + "author" : "Markil 3" + }, + "extensionsUsed" : [ + "KHR_materials_unlit" + ], + "scene" : 0, + "scenes" : [ + { + "name" : "Scene", + "nodes" : [ + 0, + 1, + 2, + 3 + ] + } + ], + "nodes" : [ + { + "name" : "Light", + "rotation" : [ + 0.16907575726509094, + 0.7558803558349609, + -0.27217137813568115, + 0.570947527885437 + ], + "translation" : [ + 0.851825475692749, + 8.599525451660156, + -0.8687540292739868 + ] + }, + { + "name" : "Camera", + "rotation" : [ + 0.483536034822464, + 0.33687159419059753, + -0.20870360732078552, + 0.7804827094078064 + ], + "translation" : [ + 7.358891487121582, + 4.958309173583984, + 6.925790786743164 + ] + }, + { + "mesh" : 0, + "name" : "Plane", + "scale" : [ + 13.14516544342041, + 13.14516544342041, + 13.14516544342041 + ] + }, + { + "mesh" : 1, + "name" : "Cylinder", + "translation" : [ + 0, + 5.575394630432129, + 0 + ] + } + ], + "materials" : [ + { + "doubleSided" : true, + "extensions" : { + "KHR_materials_unlit" : {} + }, + "name" : "Shadeless", + "pbrMetallicRoughness" : { + "baseColorFactor" : [ + 1, + 0.012044749222695827, + 0, + 1 + ], + "metallicFactor" : 0, + "roughnessFactor" : 0.9 + } + } + ], + "meshes" : [ + { + "name" : "Plane", + "primitives" : [ + { + "attributes" : { + "POSITION" : 0, + "NORMAL" : 1, + "TEXCOORD_0" : 2 + }, + "indices" : 3 + } + ] + }, + { + "name" : "Cylinder", + "primitives" : [ + { + "attributes" : { + "POSITION" : 4, + "NORMAL" : 5, + "TEXCOORD_0" : 6 + }, + "indices" : 7, + "material" : 0 + } + ] + } + ], + "accessors" : [ + { + "bufferView" : 0, + "componentType" : 5126, + "count" : 4, + "max" : [ + 1, + 0, + 1 + ], + "min" : [ + -1, + 0, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 1, + "componentType" : 5126, + "count" : 4, + "type" : "VEC3" + }, + { + "bufferView" : 2, + "componentType" : 5126, + "count" : 4, + "type" : "VEC2" + }, + { + "bufferView" : 3, + "componentType" : 5123, + "count" : 6, + "type" : "SCALAR" + }, + { + "bufferView" : 4, + "componentType" : 5126, + "count" : 192, + "max" : [ + 1, + 1, + 1 + ], + "min" : [ + -1, + -1, + -1 + ], + "type" : "VEC3" + }, + { + "bufferView" : 5, + "componentType" : 5126, + "count" : 192, + "type" : "VEC3" + }, + { + "bufferView" : 6, + "componentType" : 5126, + "count" : 192, + "type" : "VEC2" + }, + { + "bufferView" : 7, + "componentType" : 5123, + "count" : 372, + "type" : "SCALAR" + } + ], + "bufferViews" : [ + { + "buffer" : 0, + "byteLength" : 48, + "byteOffset" : 0 + }, + { + "buffer" : 0, + "byteLength" : 48, + "byteOffset" : 48 + }, + { + "buffer" : 0, + "byteLength" : 32, + "byteOffset" : 96 + }, + { + "buffer" : 0, + "byteLength" : 12, + "byteOffset" : 128 + }, + { + "buffer" : 0, + "byteLength" : 2304, + "byteOffset" : 140 + }, + { + "buffer" : 0, + "byteLength" : 2304, + "byteOffset" : 2444 + }, + { + "buffer" : 0, + "byteLength" : 1536, + "byteOffset" : 4748 + }, + { + "buffer" : 0, + "byteLength" : 744, + "byteOffset" : 6284 + } + ], + "buffers" : [ + { + "byteLength" : 7028, + "uri" : "unlit.bin" + } + ] +} diff --git a/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.frag b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.frag new file mode 100644 index 0000000000..3be23b6876 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.frag @@ -0,0 +1,107 @@ + +struct PointLight { + vec3 position; + float radius; + vec4 color; +}; + +struct DirectLight { + vec3 direction; + vec4 color; +}; + +struct TestStruct{ + int nDirectLights; + DirectLight test1; + int test2; + PointLight test3; + int test4; + mat3 test5; + int test6; + mat4 test7; + int test8; + DirectLight directLights[1]; + int nPointLights; + PointLight pointLights[2]; + PointLight pointLights2[2]; + int test13; +}; + +layout (std140) uniform m_TestStruct1 { + TestStruct testStruct1; +}; + +layout (std140) uniform m_TestStruct2 { + TestStruct testStruct2; +}; + +out vec4 outFragColor; + +void main(){ + if( + testStruct1.nDirectLights==1 + &&testStruct1.test1.direction==vec3(0,1,0) + &&testStruct1.test1.color==vec4(0,0,1,1) + &&testStruct1.test2==111 + &&testStruct1.test3.position==vec3(7.,9.,7.) + &&testStruct1.test3.radius==99. + &&testStruct1.test3.color==vec4(1.,0.,0.,1.) + &&testStruct1.test4==222 + &&testStruct1.test5==transpose(mat3(1,2,3,4,5,6,7,8,9) ) + &&testStruct1.test6==333 + &&testStruct1.test7==transpose(mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)) + &&testStruct1.test8==444 + &&testStruct1.directLights[0].direction==vec3(0.,0.,1.) + &&testStruct1.directLights[0].color==vec4(0.,1.,0.,1.) + &&testStruct1.nPointLights==2 + &&testStruct1.pointLights[0].position==vec3(5.,9.,7.) + &&testStruct1.pointLights[0].radius==9. + &&testStruct1.pointLights[0].color==vec4(1.,0.,0.,1.) + &&testStruct1.pointLights[1].position==vec3(5.,10.,7.) + &&testStruct1.pointLights[1].radius==8. + &&testStruct1.pointLights[1].color==vec4(0.,1.,0.,1.) + &&testStruct1.pointLights2[0].position==vec3(3.,9.,7.) + &&testStruct1.pointLights2[0].radius==91. + &&testStruct1.pointLights2[0].color==vec4(0.,1.,0.,1.) + &&testStruct1.pointLights2[1].position==vec3(3.,10.,7.) + &&testStruct1.pointLights2[1].radius==90. + &&testStruct1.pointLights2[1].color==vec4(0.,0.,1.,1.) + &&testStruct1.test13==555 + + && + + testStruct2.nDirectLights==1 + &&testStruct2.test1.direction==vec3(0,1,0) + &&testStruct2.test1.color==vec4(0,0,1,1) + &&testStruct2.test2==111 + &&testStruct2.test3.position==vec3(7.,9.,7.) + &&testStruct2.test3.radius==99. + &&testStruct2.test3.color==vec4(1.,0.,0.,1.) + &&testStruct2.test4==222 + &&testStruct2.test5==transpose(mat3(1,2,3,4,5,6,7,8,9) ) + &&testStruct2.test6==333 + &&testStruct2.test7==transpose(mat4(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)) + &&testStruct2.test8==999999 + &&testStruct2.directLights[0].direction==vec3(0.,0.,1.) + &&testStruct2.directLights[0].color==vec4(0.,1.,0.,1.) + &&testStruct2.nPointLights==2 + &&testStruct2.pointLights[0].position==vec3(5.,9.,7.) + &&testStruct2.pointLights[0].radius==9. + &&testStruct2.pointLights[0].color==vec4(1.,0.,0.,1.) + &&testStruct2.pointLights[1].position==vec3(5.,10.,7.) + &&testStruct2.pointLights[1].radius==8. + &&testStruct2.pointLights[1].color==vec4(0.,1.,0.,1.) + &&testStruct2.pointLights2[0].position==vec3(3.,9.,7.) + &&testStruct2.pointLights2[0].radius==91. + &&testStruct2.pointLights2[0].color==vec4(0.,1.,0.,1.) + &&testStruct2.pointLights2[1].position==vec3(3.,10.,7.) + &&testStruct2.pointLights2[1].radius==90. + &&testStruct2.pointLights2[1].color==vec4(0.,0.,1.,1.) + &&testStruct2.test13==111 + + ){ + outFragColor=vec4(0,1,0,1); + }else{ + outFragColor=vec4(1,0,0,1); + } +} \ No newline at end of file diff --git a/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.j3md b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.j3md new file mode 100644 index 0000000000..91bfb29121 --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.j3md @@ -0,0 +1,16 @@ + +MaterialDef TestUBO { + MaterialParameters { + UniformBufferObject TestStruct1 + UniformBufferObject TestStruct2 + } + + Technique { + VertexShader GLSL150 : jme3test/ubo/TestUBO.vert + FragmentShader GLSL150 : jme3test/ubo/TestUBO.frag + + WorldParameters { + ViewProjectionMatrix + } + } +} diff --git a/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.vert b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.vert new file mode 100644 index 0000000000..1f78b5f9da --- /dev/null +++ b/jme3-examples/src/main/resources/jme3test/ubo/TestUBO.vert @@ -0,0 +1,7 @@ + +in vec3 inPosition; +uniform mat4 g_ViewProjectionMatrix; +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + gl_Position = g_ViewProjectionMatrix*modelSpacePos; +} \ No newline at end of file diff --git a/jme3-ios-native/build.gradle b/jme3-ios-native/build.gradle new file mode 100644 index 0000000000..75dc000b40 --- /dev/null +++ b/jme3-ios-native/build.gradle @@ -0,0 +1,35 @@ +import org.apache.tools.ant.taskdefs.condition.Os + +task deleteXcframework(type: Delete) { + delete 'template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework' +} + +task buildNativeLibIos(type: Exec) { + executable "xcodebuild" + args 'archive', '-project', 'jme3-ios-native.xcodeproj', '-scheme', 'jme3-ios-native', '-configuration', 'release', '-destination', 'generic/platform=iOS', '-archivePath', 'build/archives/jme3-ios-native_iOS', 'SKIP_INSTALL=NO', 'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' +} + +task buildNativeLibSimulator(type: Exec) { + executable "xcodebuild" + args 'archive', '-project', 'jme3-ios-native.xcodeproj', '-scheme', 'jme3-ios-native', '-configuration', 'release', '-destination', 'generic/platform=iOS Simulator', '-archivePath', 'build/archives/jme3-ios-native_iOS-Simulator', 'SKIP_INSTALL=NO', 'BUILD_LIBRARY_FOR_DISTRIBUTION=YES' +} + +task buildNativeLib(type: Exec) { + dependsOn 'deleteXcframework' + dependsOn 'buildNativeLibIos' + dependsOn 'buildNativeLibSimulator' + executable "xcodebuild" + args '-create-xcframework', '-framework', 'build/archives/jme3-ios-native_iOS.xcarchive/Products/Library/Frameworks/jme3_ios_native.framework', '-framework', 'build/archives/jme3-ios-native_iOS-Simulator.xcarchive/Products/Library/Frameworks/jme3_ios_native.framework', '-output', 'template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework' +} + +// buildNativeProjects is a string set to "true" +if (Os.isFamily(Os.FAMILY_MAC) && buildNativeProjects == "true") { + // build native libs and update stored pre-compiled libs to commit + compileJava.dependsOn { buildNativeLib } +} else { + // TODO: (like android natives?) use pre-compiled native libs (not building new ones) + // compileJava.dependsOn { copyPreCompiledLibs } + println "Native build disable or not running on OSX" +} + +jar.into("") { from "template" } diff --git a/jme3-ios-native/export.sh b/jme3-ios-native/export.sh new file mode 100755 index 0000000000..545f78e482 --- /dev/null +++ b/jme3-ios-native/export.sh @@ -0,0 +1,11 @@ +rm -rf intermediate-builds release template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework +mkdir intermediate-builds release +xcodebuild archive -project jme3-ios-native.xcodeproj -scheme jme3-ios-native -destination generic/platform=iOS -archivePath intermediate-builds/jme3-ios-native_iOS SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES +xcodebuild archive -project jme3-ios-native.xcodeproj -scheme jme3-ios-native -destination generic/platform="iOS Simulator" -archivePath intermediate-builds/jme3-ios-native_iOS-Simulator SKIP_INSTALL=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES + +xcodebuild -create-xcframework -framework intermediate-builds/jme3-ios-native_iOS.xcarchive/Products/Library/Frameworks/jme3_ios_native.framework -framework intermediate-builds/jme3-ios-native_iOS-Simulator.xcarchive/Products/Library/Frameworks/jme3_ios_native.framework -output template/META-INF/robovm/ios/libs/jme3-ios-native.xcframework + +cd template +zip -r ../release/jme3-ios-native.jar META-INF +cd .. + diff --git a/jme3-ios-native/jme3-ios-native.xcodeproj/project.pbxproj b/jme3-ios-native/jme3-ios-native.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..889f913eb9 --- /dev/null +++ b/jme3-ios-native/jme3-ios-native.xcodeproj/project.pbxproj @@ -0,0 +1,416 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 52; + objects = { + +/* Begin PBXBuildFile section */ + BB0987B82CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.c in Sources */ = {isa = PBXBuildFile; fileRef = BB0987B62CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.c */; }; + BB0987B92CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.h in Headers */ = {isa = PBXBuildFile; fileRef = BB0987B72CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBAA18642C9CC9B40015DF5E /* jme3_ios_native.h in Headers */ = {isa = PBXBuildFile; fileRef = BBAA18622C9CC9B40015DF5E /* jme3_ios_native.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBAA18752C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.c in Sources */ = {isa = PBXBuildFile; fileRef = BBAA186A2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.c */; }; + BBAA18762C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.c in Sources */ = {isa = PBXBuildFile; fileRef = BBAA186B2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.c */; }; + BBAA18772C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.h in Headers */ = {isa = PBXBuildFile; fileRef = BBAA186C2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBAA18782C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.h in Headers */ = {isa = PBXBuildFile; fileRef = BBAA186D2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBAA18792C9CCACB0015DF5E /* JmeAppHarness.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAA186E2C9CCACB0015DF5E /* JmeAppHarness.m */; }; + BBAA187A2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.h in Headers */ = {isa = PBXBuildFile; fileRef = BBAA186F2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.h */; settings = {ATTRIBUTES = (Public, ); }; }; + BBAA187B2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.c in Sources */ = {isa = PBXBuildFile; fileRef = BBAA18702C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.c */; }; + BBAA187C2C9CCACB0015DF5E /* JmeIosGLES.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAA18712C9CCACB0015DF5E /* JmeIosGLES.m */; }; + BBAA187D2C9CCACB0015DF5E /* JmeAppHarness.java in Sources */ = {isa = PBXBuildFile; fileRef = BBAA18722C9CCACB0015DF5E /* JmeAppHarness.java */; }; + BBAA187E2C9CCACB0015DF5E /* jme-ios.m in Sources */ = {isa = PBXBuildFile; fileRef = BBAA18732C9CCACB0015DF5E /* jme-ios.m */; }; + BBAA18822C9CCB720015DF5E /* OpenGLES.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = BBAA18812C9CCB720015DF5E /* OpenGLES.framework */; platformFilter = ios; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + BB0987B62CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = com_jme3_util_IosNativeBufferAllocator.c; sourceTree = ""; }; + BB0987B72CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = com_jme3_util_IosNativeBufferAllocator.h; sourceTree = ""; }; + BBAA185F2C9CC9B40015DF5E /* jme3_ios_native.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = jme3_ios_native.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BBAA18622C9CC9B40015DF5E /* jme3_ios_native.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = jme3_ios_native.h; sourceTree = ""; }; + BBAA18632C9CC9B40015DF5E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + BBAA186A2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = com_jme3_audio_ios_IosEFX.c; sourceTree = ""; }; + BBAA186B2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = com_jme3_audio_ios_IosALC.c; sourceTree = ""; }; + BBAA186C2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = com_jme3_audio_ios_IosEFX.h; sourceTree = ""; }; + BBAA186D2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = com_jme3_audio_ios_IosALC.h; sourceTree = ""; }; + BBAA186E2C9CCACB0015DF5E /* JmeAppHarness.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JmeAppHarness.m; sourceTree = ""; }; + BBAA186F2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = com_jme3_audio_ios_IosAL.h; sourceTree = ""; }; + BBAA18702C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = com_jme3_audio_ios_IosAL.c; sourceTree = ""; }; + BBAA18712C9CCACB0015DF5E /* JmeIosGLES.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = JmeIosGLES.m; sourceTree = ""; }; + BBAA18722C9CCACB0015DF5E /* JmeAppHarness.java */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.java; path = JmeAppHarness.java; sourceTree = ""; }; + BBAA18732C9CCACB0015DF5E /* jme-ios.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "jme-ios.m"; sourceTree = ""; }; + BBAA18812C9CCB720015DF5E /* OpenGLES.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OpenGLES.framework; path = System/Library/Frameworks/OpenGLES.framework; sourceTree = SDKROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + BBAA185C2C9CC9B40015DF5E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + BBAA18822C9CCB720015DF5E /* OpenGLES.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + BBAA18552C9CC9B40015DF5E = { + isa = PBXGroup; + children = ( + BBAA18612C9CC9B40015DF5E /* src */, + BBAA18602C9CC9B40015DF5E /* Products */, + BBAA18802C9CCB710015DF5E /* Frameworks */, + ); + sourceTree = ""; + }; + BBAA18602C9CC9B40015DF5E /* Products */ = { + isa = PBXGroup; + children = ( + BBAA185F2C9CC9B40015DF5E /* jme3_ios_native.framework */, + ); + name = Products; + sourceTree = ""; + }; + BBAA18612C9CC9B40015DF5E /* src */ = { + isa = PBXGroup; + children = ( + BB0987B62CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.c */, + BB0987B72CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.h */, + BBAA18702C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.c */, + BBAA186F2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.h */, + BBAA186B2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.c */, + BBAA186D2C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.h */, + BBAA186A2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.c */, + BBAA186C2C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.h */, + BBAA18732C9CCACB0015DF5E /* jme-ios.m */, + BBAA18722C9CCACB0015DF5E /* JmeAppHarness.java */, + BBAA186E2C9CCACB0015DF5E /* JmeAppHarness.m */, + BBAA18712C9CCACB0015DF5E /* JmeIosGLES.m */, + BBAA18622C9CC9B40015DF5E /* jme3_ios_native.h */, + BBAA18632C9CC9B40015DF5E /* Info.plist */, + ); + path = src; + sourceTree = ""; + }; + BBAA18802C9CCB710015DF5E /* Frameworks */ = { + isa = PBXGroup; + children = ( + BBAA18812C9CCB720015DF5E /* OpenGLES.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + BBAA185A2C9CC9B40015DF5E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + BBAA18772C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.h in Headers */, + BBAA187A2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.h in Headers */, + BB0987B92CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.h in Headers */, + BBAA18782C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.h in Headers */, + BBAA18642C9CC9B40015DF5E /* jme3_ios_native.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + BBAA185E2C9CC9B40015DF5E /* jme3-ios-native */ = { + isa = PBXNativeTarget; + buildConfigurationList = BBAA18672C9CC9B40015DF5E /* Build configuration list for PBXNativeTarget "jme3-ios-native" */; + buildPhases = ( + BBAA185A2C9CC9B40015DF5E /* Headers */, + BBAA185B2C9CC9B40015DF5E /* Sources */, + BBAA185C2C9CC9B40015DF5E /* Frameworks */, + BBAA185D2C9CC9B40015DF5E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = "jme3-ios-native"; + productName = "jme3-ios-native"; + productReference = BBAA185F2C9CC9B40015DF5E /* jme3_ios_native.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + BBAA18562C9CC9B40015DF5E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1130; + TargetAttributes = { + BBAA185E2C9CC9B40015DF5E = { + CreatedOnToolsVersion = 11.3.1; + }; + }; + }; + buildConfigurationList = BBAA18592C9CC9B40015DF5E /* Build configuration list for PBXProject "jme3-ios-native" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = BBAA18552C9CC9B40015DF5E; + productRefGroup = BBAA18602C9CC9B40015DF5E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + BBAA185E2C9CC9B40015DF5E /* jme3-ios-native */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + BBAA185D2C9CC9B40015DF5E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + BBAA185B2C9CC9B40015DF5E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + BBAA18762C9CCACB0015DF5E /* com_jme3_audio_ios_IosALC.c in Sources */, + BBAA187E2C9CCACB0015DF5E /* jme-ios.m in Sources */, + BBAA187D2C9CCACB0015DF5E /* JmeAppHarness.java in Sources */, + BBAA18752C9CCACB0015DF5E /* com_jme3_audio_ios_IosEFX.c in Sources */, + BBAA187B2C9CCACB0015DF5E /* com_jme3_audio_ios_IosAL.c in Sources */, + BBAA18792C9CCACB0015DF5E /* JmeAppHarness.m in Sources */, + BBAA187C2C9CCACB0015DF5E /* JmeIosGLES.m in Sources */, + BB0987B82CA2B31900AF4C26 /* com_jme3_util_IosNativeBufferAllocator.c in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin XCBuildConfiguration section */ + BBAA18652C9CC9B40015DF5E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; + MTL_FAST_MATH = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + BBAA18662C9CC9B40015DF5E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = NO; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.15; + MTL_ENABLE_DEBUG_INFO = NO; + MTL_FAST_MATH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + BBAA18682C9CC9B40015DF5E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + HEADER_SEARCH_PATHS = ( + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Headers/, + "/Library/Java/JavaVirtualMachines/jdk-11.0.6.jdk/Contents/Home/include/**", + "/Users/runner/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.26-4/arm64/Contents/Home/include/**", + ); + INFOPLIST_FILE = src/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + OTHER_CFLAGS = ( + "-fno-stack-check", + "-fno-stack-protector", + ); + PRODUCT_BUNDLE_IDENTIFIER = "jme3.jme3-ios-native"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + USER_HEADER_SEARCH_PATHS = ( + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Headers/, + "/Library/Java/JavaVirtualMachines/jdk-11.0.6.jdk/Contents/Home/include/**", + "/Users/runner/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.26-4/arm64/Contents/Home/include/**", + ); + }; + name = Debug; + }; + BBAA18692C9CC9B40015DF5E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + HEADER_SEARCH_PATHS = ( + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Headers/, + "/Library/Java/JavaVirtualMachines/jdk-11.0.6.jdk/Contents/Home/include/**", + "/Users/runner/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.26-4/arm64/Contents/Home/include/**", + ); + INFOPLIST_FILE = src/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 11.0; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + "@loader_path/Frameworks", + ); + OTHER_CFLAGS = ( + "-fno-stack-check", + "-fno-stack-protector", + ); + PRODUCT_BUNDLE_IDENTIFIER = "jme3.jme3-ios-native"; + PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; + SKIP_INSTALL = YES; + SUPPORTS_MACCATALYST = YES; + USER_HEADER_SEARCH_PATHS = ( + /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/JavaVM.framework/Headers/, + "/Library/Java/JavaVirtualMachines/jdk-11.0.6.jdk/Contents/Home/include/**", + "/Users/runner/hostedtoolcache/Java_Temurin-Hotspot_jdk/11.0.26-4/arm64/Contents/Home/include/**", + ); + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + BBAA18592C9CC9B40015DF5E /* Build configuration list for PBXProject "jme3-ios-native" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBAA18652C9CC9B40015DF5E /* Debug */, + BBAA18662C9CC9B40015DF5E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + BBAA18672C9CC9B40015DF5E /* Build configuration list for PBXNativeTarget "jme3-ios-native" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + BBAA18682C9CC9B40015DF5E /* Debug */, + BBAA18692C9CC9B40015DF5E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = BBAA18562C9CC9B40015DF5E /* Project object */; +} diff --git a/jme3-ios-native/src/Info.plist b/jme3-ios-native/src/Info.plist new file mode 100644 index 0000000000..9bcb244429 --- /dev/null +++ b/jme3-ios-native/src/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + + diff --git a/jme3-ios-native/src/JmeAppHarness.java b/jme3-ios-native/src/JmeAppHarness.java new file mode 100644 index 0000000000..cfd7de092a --- /dev/null +++ b/jme3-ios-native/src/JmeAppHarness.java @@ -0,0 +1,130 @@ +import com.jme3.system.ios.IosHarness; +import com.jme3.input.ios.IosInputHandler; +import com.jme3.math.Vector2f; +import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.system.JmeContext; +import com.jme3.system.AppSettings; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * You can extend this class to perform iOS-only operations from java, + * native methods can reside either in .c/.m files in this directory + * or in the XCode project itself. + * @author normenhansen + */ +public class JmeAppHarness extends IosHarness{ + + private static final Logger logger = Logger.getLogger(JmeAppHarness.class.getName()); + protected Renderer renderer; + protected IosInputHandler input; + protected boolean autoFlush = true; + protected Vector2f resizePending = null; + + + /** + * An instance of this object is created when your application + * has started on the iOS side. + * You can e.g. attach special AppStates or do similar things here. You can + * access classes from this source directory as well as your main projects + * sources and classpath. + */ + public JmeAppHarness(long id) { + super(id); + app = new mygame.Main(); + AppSettings settings = new AppSettings(true); + this.app.setSettings(settings); + app.start(); + logger.log(Level.FINE, "JmeAppHarness constructor"); + app.gainFocus(); + } + + @Override + public void appPaused() { + logger.log(Level.FINE, "JmeAppHarness appPaused"); + } + + @Override + public void appReactivated() { + logger.log(Level.FINE, "JmeAppHarness appReactivated"); + } + + @Override + public void appClosed() { + logger.log(Level.FINE, "JmeAppHarness appClosed"); + app.stop(); + } + + @Override + public void appUpdate() { + logger.log(Level.FINE, "JmeAppHarness appUpdate"); + //app.update(); + } + + @Override + public void appDraw() { + logger.log(Level.FINE, "JmeAppHarness appDraw"); + if (renderer == null) { + JmeContext iosContext = app.getContext(); + renderer = iosContext.getRenderer(); + renderer.initialize(); + input = (IosInputHandler)iosContext.getTouchInput(); + input.initialize(); + } else { + if(resizePending != null) { + appReshape((int)resizePending.x, (int)resizePending.y); + resizePending = null; + } + app.update(); + if (autoFlush) { + renderer.postFrame(); + } + } + } + + @Override + public void appReshape(int width, int height) { + logger.log(Level.FINE, "JmeAppHarness reshape"); + AppSettings settings = app.getContext().getSettings(); + settings.setResolution(width, height); + if (renderer != null) { + app.reshape(width, height); + resizePending = null; + } else { + resizePending = new Vector2f(width, height); + } + + if (input != null) { + input.loadSettings(settings); + } + } + + public void injectTouchBegin(int pointerId, long time, float x, float y) { + if (input != null) { + logger.log(Level.FINE, "JmeAppHarness injectTouchBegin"); + input.injectTouchDown(pointerId, time, x, y); + } + } + + public void injectTouchMove(int pointerId, long time, float x, float y) { + if (input != null) { + logger.log(Level.FINE, "JmeAppHarness injectTouchMove"); + input.injectTouchMove(pointerId, time, x, y); + } + } + + public void injectTouchEnd(int pointerId, long time, float x, float y) { + if (input != null) { + logger.log(Level.FINE, "JmeAppHarness injectTouchEnd"); + input.injectTouchUp(pointerId, time, x, y); + } + } + + /** + * Example of a native method calling iOS code. + * See the native code in IosHarness.m + * @param text The message to display + */ + public native void showDialog(String text); + +} diff --git a/jme3-ios-native/src/JmeAppHarness.m b/jme3-ios-native/src/JmeAppHarness.m new file mode 100644 index 0000000000..1a3cb35be0 --- /dev/null +++ b/jme3-ios-native/src/JmeAppHarness.m @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2009-2013 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. + */ +#include +#import + +/** + * Author: Normen Hansen + */ + +#ifndef JNIEXPORT +#define JNIEXPORT __attribute__ ((visibility("default"))) \ + __attribute__ ((used)) +#endif + +#ifndef _Included_JmeAppHarness +#define _Included_JmeAppHarness +#endif + +JNIEXPORT void JNICALL +Java_JmeAppHarness_showDialog(JNIEnv* e, jobject c, jstring text) { + const char* chars = (*e)->GetStringUTFChars(e, text, 0); + NSString* string = [[NSString alloc] initWithUTF8String : chars]; + (*e)->ReleaseStringUTFChars(e, text, chars); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle : @"Message" + message : string + delegate : nil + cancelButtonTitle : @"OK" + otherButtonTitles : nil]; + [alert show]; + [alert release]; +} diff --git a/jme3-ios-native/src/JmeIosGLES.m b/jme3-ios-native/src/JmeIosGLES.m new file mode 100644 index 0000000000..016a24c02d --- /dev/null +++ b/jme3-ios-native/src/JmeIosGLES.m @@ -0,0 +1,2392 @@ +#import +#define __LP64__ 1 +#import +#import +#import +#import +#import + +/** + * Author: Kostyantyn Hushchyn, Jesus Oliver + */ + +#ifndef JNIEXPORT +#define JNIEXPORT __attribute__ ((visibility("default"))) \ + __attribute__ ((used)) +#endif + +#ifndef _Included_JmeIosGLES +#define _Included_JmeIosGLES +#endif + +#define glBindVertexArray glBindVertexArrayOES + +static int initialized = 0; + +static jclass bufferClass = (jclass)0; +static jclass byteBufferClass = (jclass)0; +static jclass shortBufferClass = (jclass)0; +static jclass intBufferClass = (jclass)0; +static jclass floatBufferClass = (jclass)0; +static jfieldID positionID; +static jfieldID limitID; + + +static void +nativeClassInit(JNIEnv *e); + +static int +allowIndirectBuffers(JNIEnv *e); + +static void * +getDirectBufferPointer(JNIEnv *e, jobject buffer); + +static void * +getPointer(JNIEnv *e, jobject buffer, jarray *array, jint *remaining, jint *offset); + +static void +releasePointer(JNIEnv *e, jarray array, void *data, jboolean commit); + +static void +jniThrowException(JNIEnv *e, const char* type, const char* message); + +static jint +getBufferElementSize(JNIEnv *e, jobject buffer); + +static int getNeededCount(GLint pname); + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glActiveTexture(JNIEnv* e, jobject c, jint texture) { + glActiveTexture( + (GLenum)texture + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glAttachShader(JNIEnv* e, jobject c, jint program, jint shader) { + glAttachShader( + (GLuint)program, + (GLuint)shader + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBindBuffer(JNIEnv* e, jobject c, jint target, jint buffer) { + glBindBuffer( + (GLenum)target, + (GLuint)buffer + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBindFramebuffer(JNIEnv* e, jobject c, jint target, jint framebuffer) { + glBindFramebuffer( + (GLenum)target, + (GLuint)framebuffer + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBindRenderbuffer(JNIEnv* e, jobject c, jint target, jint renderbuffer) { + glBindRenderbuffer( + (GLenum)target, + (GLuint)renderbuffer + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBindTexture(JNIEnv* e, jobject c, jint target, jint texture) { + glBindTexture( + (GLenum)target, + (GLuint)texture + ); +} + + // TODO: Investigate this + /* +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBindVertexArray(JNIEnv* e, jobject c, jint array) { + glBindVertexArray(array); +} +*/ + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBlendFunc(JNIEnv* e, jobject c, jint sfactor, jint dfactor) { + glBlendFunc( + (GLenum)sfactor, + (GLenum)dfactor + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBufferData(JNIEnv* e, jobject c, jint target, jint size, jobject data_buf, jint usage) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *data = (GLvoid *) 0; + + if (data_buf) { + data = (GLvoid *)getPointer(e, data_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < size) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < size < needed"; + goto exit; + } + } + if (data_buf && data == NULL) { + char * _dataBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + data = (GLvoid *) (_dataBase + _bufferOffset); + } + glBufferData( + (GLenum)target, + (GLsizeiptr)size, + (GLvoid *)data, + (GLenum)usage + ); + +exit: + if (_array) { + releasePointer(e, _array, data, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBufferData2(JNIEnv* e, jobject c, jint target, jint size, jbyteArray data, jint offset, jint usage) { + jbyte *dataNative = (*e)->GetByteArrayElements(e, data, NULL); + + glBufferData( + (GLenum)target, + (GLsizeiptr)size, + (GLvoid *)dataNative, + (GLenum)(usage + offset) + ); + + (*e)->ReleaseByteArrayElements(e, data, dataNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBufferSubData(JNIEnv* e, jobject c, jint target, jint offset, jint size, jobject data_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *data = (GLvoid *) 0; + + data = (GLvoid *)getPointer(e, data_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < size) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < size < needed"; + goto exit; + } + if (data == NULL) { + char * _dataBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + data = (GLvoid *) (_dataBase + _bufferOffset); + } + glBufferSubData( + (GLenum)target, + (GLintptr)offset, + (GLsizeiptr)size, + (GLvoid *)data + ); + +exit: + if (_array) { + releasePointer(e, _array, data, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBufferSubData2(JNIEnv* e, jobject c, jint target, jint offset, jint size, jbyteArray data, jint dataoffset) { + jbyte *dataNative = (*e)->GetByteArrayElements(e, data, NULL); + + glBufferSubData( + (GLenum)target, + (GLintptr)offset, + (GLsizeiptr)size, + (GLvoid *)(dataNative + dataoffset) + ); + + (*e)->ReleaseByteArrayElements(e, data, dataNative, 0); +} + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCheckFramebufferStatus(JNIEnv* e, jobject c, jint target) { + GLenum _returnValue; + _returnValue = glCheckFramebufferStatus( + (GLenum)target + ); + return (jint)_returnValue; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glClear(JNIEnv* e, jobject c, jint mask) { + glClear( + (GLbitfield)mask + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glClearColor(JNIEnv* e, jobject c, jfloat red, jfloat green, jfloat blue, jfloat alpha) { + glClearColor( + (GLclampf)red, + (GLclampf)green, + (GLclampf)blue, + (GLclampf)alpha + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glColorMask(JNIEnv* e, jobject c, jboolean red, jboolean green, jboolean blue, jboolean alpha) { + glColorMask( + (GLboolean)red, + (GLboolean)green, + (GLboolean)blue, + (GLboolean)alpha + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCompileShader(JNIEnv* e, jobject c, jint shader) { + glCompileShader( + (GLuint)shader + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCompressedTexImage2D(JNIEnv* e, jobject c, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint imageSize, jobject pixels_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + glCompressedTexImage2D( + (GLenum)target, + (GLint)level, + (GLenum)internalformat, + (GLsizei)width, + (GLsizei)height, + (GLint)border, + (GLsizei)imageSize, + (GLvoid *)pixels + ); + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCompressedTexSubImage2D(JNIEnv* e, jobject c, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint imageSize, jobject pixels_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + glCompressedTexSubImage2D( + (GLenum)target, + (GLint)level, + (GLint)xoffset, + (GLint)yoffset, + (GLsizei)width, + (GLsizei)height, + (GLenum)format, + (GLsizei)imageSize, + (GLvoid *)pixels + ); + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCreateProgram(JNIEnv* e, jobject c) { + GLuint _returnValue; + _returnValue = glCreateProgram(); + return (jint)_returnValue; +} + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCreateShader(JNIEnv* e, jobject c, jint shaderType) { + GLuint _returnValue; + _returnValue = glCreateShader( + (GLenum)shaderType + ); + return (jint)_returnValue; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCullFace(JNIEnv* e, jobject c, jint mode) { + glCullFace( + (GLenum)mode + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteBuffers(JNIEnv* e, jobject c, jint n, jintArray buffers, jint offset) { + jint *buffersNative = (*e)->GetIntArrayElements(e, buffers, NULL); + + glDeleteBuffers( + (GLsizei)n, + (GLuint *)buffersNative + ); + + (*e)->ReleaseIntArrayElements(e, buffers, buffersNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteFramebuffers(JNIEnv* e, jobject c, jint n, jintArray framebuffers, jint offset) { + jint *buffersNative = (*e)->GetIntArrayElements(e, framebuffers, NULL); + + glDeleteFramebuffers( + (GLsizei)n, + (GLuint *)buffersNative + ); + + (*e)->ReleaseIntArrayElements(e, framebuffers, buffersNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteProgram(JNIEnv* e, jobject c, jint program) { + glDeleteProgram( + (GLuint)program + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteRenderbuffers(JNIEnv* e, jobject c, jint n, jintArray renderbuffers, jint offset) { + jint *buffersNative = (*e)->GetIntArrayElements(e, renderbuffers, NULL); + + glDeleteRenderbuffers( + (GLsizei)n, + (GLuint *)buffersNative + ); + + (*e)->ReleaseIntArrayElements(e, renderbuffers, buffersNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteShader(JNIEnv* e, jobject c, jint shader) { + glDeleteShader( + (GLuint)shader + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDeleteTextures(JNIEnv* e, jobject c, jint n, jintArray textures_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLuint *textures_base = (GLuint *) 0; + jint _remaining; + GLuint *textures = (GLuint *) 0; + + if (!textures_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "textures == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, textures_ref) - offset; + if (_remaining < n) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < n < needed"; + goto exit; + } + textures_base = (GLuint *) + (*e)->GetPrimitiveArrayCritical(e, textures_ref, (jboolean *)0); + textures = textures_base + offset; + + glDeleteTextures( + (GLsizei)n, + (GLuint *)textures + ); + +exit: + if (textures_base) { + (*e)->ReleasePrimitiveArrayCritical(e, textures_ref, textures_base, + JNI_ABORT); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDepthFunc(JNIEnv* e, jobject c, jint func) { + glDepthFunc( + (GLenum)func + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDepthMask(JNIEnv* e, jobject c, jboolean flag) { + glDepthMask( + (GLboolean)flag + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDepthRangef(JNIEnv* e, jobject c, jfloat zNear, jfloat zFar) { + glDepthRangef( + (GLclampf)zNear, + (GLclampf)zFar + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDetachShader(JNIEnv* e, jobject c, jint program, jint shader) { + glDetachShader( + (GLuint)program, + (GLuint)shader + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDisable(JNIEnv* e, jobject c, jint cap) { + glDisable( + (GLenum)cap + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDisableVertexAttribArray(JNIEnv* e, jobject c, jint index) { + glDisableVertexAttribArray( + (GLuint)index + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawArrays(JNIEnv* e, jobject c, jint mode, jint first, jint count) { + glDrawArrays( + (GLenum)mode, + (GLint)first, + (GLsizei)count + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawElements(JNIEnv* e, jobject c, jint mode, jint count, jint type, jobject indices_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *indices = (GLvoid *) 0; + + indices = (GLvoid *)getPointer(e, indices_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count) { + _exception = 1; + _exceptionType = "java/lang/ArrayIndexOutOfBoundsException"; + _exceptionMessage = "remaining() < count < needed"; + goto exit; + } + if (indices == NULL) { + char * _indicesBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + indices = (GLvoid *) (_indicesBase + _bufferOffset); + } + glDrawElements( + (GLenum)mode, + (GLsizei)count, + (GLenum)type, + (GLvoid *)indices + ); + +exit: + if (_array) { + releasePointer(e, _array, indices, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawElements2(JNIEnv* e, jobject c, jint mode, jint count, jint type, jbyteArray indices, jint offset) { + jbyte *indicesNative = (*e)->GetByteArrayElements(e, indices, NULL); + + glDrawElements( + (GLenum)mode, + (GLsizei)count, + (GLenum)type, + (GLvoid *)(indicesNative + offset) + ); + + (*e)->ReleaseByteArrayElements(e, indices, indicesNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawElementsIndex(JNIEnv* e, jobject c, jint mode, jint count, jint type, jint offset) { + glDrawElements( + (GLenum)mode, + (GLsizei)count, + (GLenum)type, + (GLvoid *)offset + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glEnable(JNIEnv* e, jobject c, jint cap) { + glEnable( + (GLenum)cap + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glEnableVertexAttribArray(JNIEnv* e, jobject c, jint index) { + glEnableVertexAttribArray( + (GLuint)index + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glFramebufferRenderbuffer(JNIEnv* e, jobject c, jint target, jint attachment, jint renderbuffertarget, jint renderbuffer) { + glFramebufferRenderbuffer( + (GLenum)target, + (GLenum)attachment, + (GLenum)renderbuffertarget, + (GLuint)renderbuffer + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glFramebufferTexture2D(JNIEnv* e, jobject c, jint target, jint attachment, jint textarget, jint texture, jint level) { + glFramebufferTexture2D( + (GLenum)target, + (GLenum)attachment, + (GLenum)textarget, + (GLuint)texture, + (GLint)level + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenBuffers(JNIEnv* e, jobject c, jint n, jintArray buffers_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLuint *buffers_base = (GLuint *) 0; + jint _remaining; + GLuint *buffers = (GLuint *) 0; + + if (!buffers_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "buffers == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, buffers_ref) - offset; + if (_remaining < n) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < n < needed"; + goto exit; + } + buffers_base = (GLuint *) + (*e)->GetPrimitiveArrayCritical(e, buffers_ref, (jboolean *)0); + buffers = buffers_base + offset; + + glGenBuffers( + (GLsizei)n, + (GLuint *)buffers + ); + +exit: + if (buffers_base) { + (*e)->ReleasePrimitiveArrayCritical(e, buffers_ref, buffers_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenFramebuffers(JNIEnv* e, jobject c, jint n, jintArray framebuffers_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLuint *buffers_base = (GLuint *) 0; + jint _remaining; + GLuint *buffers = (GLuint *) 0; + + if (!framebuffers_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "buffers == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, framebuffers_ref) - offset; + if (_remaining < n) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < n < needed"; + goto exit; + } + buffers_base = (GLuint *) + (*e)->GetPrimitiveArrayCritical(e, framebuffers_ref, (jboolean *)0); + buffers = buffers_base + offset; + + glGenFramebuffers( + (GLsizei)n, + (GLuint *)buffers + ); + +exit: + if (buffers_base) { + (*e)->ReleasePrimitiveArrayCritical(e, framebuffers_ref, buffers_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenRenderbuffers(JNIEnv* e, jobject c, jint n, jintArray renderbuffers_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLuint *buffers_base = (GLuint *) 0; + jint _remaining; + GLuint *buffers = (GLuint *) 0; + + if (!renderbuffers_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "buffers == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, renderbuffers_ref) - offset; + if (_remaining < n) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < n < needed"; + goto exit; + } + buffers_base = (GLuint *) + (*e)->GetPrimitiveArrayCritical(e, renderbuffers_ref, (jboolean *)0); + buffers = buffers_base + offset; + + glGenRenderbuffers( + (GLsizei)n, + (GLuint *)buffers + ); + +exit: + if (buffers_base) { + (*e)->ReleasePrimitiveArrayCritical(e, renderbuffers_ref, buffers_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenTextures(JNIEnv* e, jobject c, jint n, jintArray textures_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLuint *buffers_base = (GLuint *) 0; + jint _remaining; + GLuint *buffers = (GLuint *) 0; + + if (!textures_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "buffers == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, textures_ref) - offset; + if (_remaining < n) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < n < needed"; + goto exit; + } + buffers_base = (GLuint *) + (*e)->GetPrimitiveArrayCritical(e, textures_ref, (jboolean *)0); + buffers = buffers_base + offset; + + glGenTextures( + (GLsizei)n, + (GLuint *)buffers + ); + +exit: + if (buffers_base) { + (*e)->ReleasePrimitiveArrayCritical(e, textures_ref, buffers_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenerateMipmap(JNIEnv* e, jobject c, jint target) { + glGenerateMipmap( + (GLenum)target + ); +} + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetAttribLocation(JNIEnv* e, jobject c, jint program, jstring name) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLint _returnValue = 0; + const char* _nativename = 0; + + if (!name) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "name == null"; + goto exit; + } + _nativename = (*e)->GetStringUTFChars(e, name, 0); + + _returnValue = glGetAttribLocation( + (GLuint)program, + (char *)_nativename + ); + +exit: + if (_nativename) { + (*e)->ReleaseStringUTFChars(e, name, _nativename); + } + + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } + return (jint)_returnValue; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetBoolean(JNIEnv* e, jobject c, jint pname, jobject params_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *params = (GLvoid *) 0; + + if (params_buf) { + params = (GLvoid *)getPointer(e, params_buf, &_array, &_remaining, &_bufferOffset); + } + if (params_buf && params == NULL) { + char * _paramsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + params = (GLvoid *) (_paramsBase + _bufferOffset); + } + + glGetBooleanv( + (GLenum) pname, + (GLboolean *) params + ); +} + + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetError(JNIEnv* e, jobject c) { + GLenum _returnValue; + _returnValue = glGetError(); + return (jint)_returnValue; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetFramebufferAttachmentParameteriv(JNIEnv* e, jobject c, jint target, jint attachment, jint pname, jintArray params_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, params_ref) - offset; + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base + offset; + + glGetFramebufferAttachmentParameteriv( + (GLenum)target, + (GLenum)attachment, + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetIntegerv(JNIEnv* e, jobject c, jint pname, jintArray params_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType; + const char * _exceptionMessage; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + int _needed = 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, params_ref) - offset; + _needed = getNeededCount(pname); + // if we didn't find this pname, we just assume the user passed + // an array of the right size -- this might happen with extensions + // or if we forget an enum here. + if (_remaining < _needed) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < needed"; + goto exit; + } + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base + offset; + + glGetIntegerv( + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT jstring JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetProgramInfoLog(JNIEnv* e, jobject c, jint program) { + GLsizei size = 0; + glGetProgramiv((GLuint)program, GL_INFO_LOG_LENGTH, &size); + + GLchar *infoLog; + + if (!size) { + return (*e)->NewStringUTF(e, ""); + } + + infoLog = malloc(sizeof(GLchar) * size); + if (infoLog == NULL) { + jniThrowException(e, "java/lang/IllegalArgumentException", "out of memory"); + return NULL; + } + + glGetProgramInfoLog((GLuint)program, size, NULL, infoLog); + jstring log = (*e)->NewStringUTF(e, infoLog); + free(infoLog); + + return log; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetProgramiv(JNIEnv* e, jobject c, jint program, jint pname, jintArray params_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, params_ref) - offset; + if (_remaining < 1) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < 1 < needed"; + goto exit; + } + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base + offset; + + glGetProgramiv( + (GLuint)program, + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT jstring JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetShaderInfoLog(JNIEnv* e, jobject c, jint shader) { + GLsizei size = 0; + glGetShaderiv((GLuint)shader, GL_INFO_LOG_LENGTH, &size); + + GLchar *infoLog; + + if (!size) { + return (*e)->NewStringUTF(e, ""); + } + + infoLog = malloc(sizeof(GLchar) * size); + if (infoLog == NULL) { + jniThrowException(e, "java/lang/IllegalArgumentException", "out of memory"); + return NULL; + } + + glGetShaderInfoLog((GLuint)shader, size, NULL, infoLog); + jstring log = (*e)->NewStringUTF(e, infoLog); + free(infoLog); + + return log; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetShaderiv(JNIEnv* e, jobject c, jint shader, jint pname, jintArray params_ref, jint offset) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + if (offset < 0) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "offset < 0"; + goto exit; + } + _remaining = (*e)->GetArrayLength(e, params_ref) - offset; + if (_remaining < 1) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length - offset < 1 < needed"; + goto exit; + } + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base + offset; + + glGetShaderiv( + (GLuint)shader, + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT jstring JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetString(JNIEnv* e, jobject c, jint name) { + const GLubyte* value = glGetString((GLenum) name); + + return (*e)->NewStringUTF(e, (const char*)value); +} + +JNIEXPORT jint JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetUniformLocation(JNIEnv* e, jobject c, jint program, jstring name) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + GLint _returnValue = 0; + const char* _nativename = 0; + + if (!name) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "name == null"; + goto exit; + } + _nativename = (*e)->GetStringUTFChars(e, name, 0); + + _returnValue = glGetUniformLocation( + (GLuint)program, + (char *)_nativename + ); + +exit: + if (_nativename) { + (*e)->ReleaseStringUTFChars(e, name, _nativename); + } + + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } + return (jint)_returnValue; +} + +JNIEXPORT jboolean JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glIsEnabled(JNIEnv* e, jobject c, jint cap) { + GLboolean _returnValue; + _returnValue = glIsEnabled( + (GLenum)cap + ); + return (jboolean)_returnValue; +} + +JNIEXPORT jboolean JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glIsFramebuffer(JNIEnv* e, jobject c, jint framebuffer) { + GLboolean _returnValue; + _returnValue = glIsFramebuffer( + (GLuint)framebuffer + ); + return (jboolean)_returnValue; +} + +JNIEXPORT jboolean JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glIsRenderbuffer(JNIEnv* e, jobject c, jint renderbuffer) { + GLboolean _returnValue; + _returnValue = glIsRenderbuffer( + (GLuint)renderbuffer + ); + return (jboolean)_returnValue; +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glLineWidth(JNIEnv* e, jobject c, jfloat width) { + glLineWidth( + (GLfloat)width + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glLinkProgram(JNIEnv* e, jobject c, jint program) { + glLinkProgram( + (GLuint)program + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glPixelStorei(JNIEnv* e, jobject c, jint pname, jint param) { + glPixelStorei( + (GLenum)pname, + (GLint)param + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glPolygonOffset(JNIEnv* e, jobject c, jfloat factor, jfloat units) { + glPolygonOffset( + (GLfloat)factor, + (GLfloat)units + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glReadPixels(JNIEnv* e, jobject c, jint vpX, jint vpY, jint vpW, jint vpH, jint format, jint type, jobject pixels_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + if (pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + glReadPixels( + (GLint)vpX, + (GLint)vpY, + (GLsizei)vpW, + (GLsizei)vpH, + (GLenum)format, + (GLenum)type, + (GLvoid *)pixels + ); + if (_array) { + releasePointer(e, _array, pixels, JNI_TRUE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glReadPixels2(JNIEnv* e, jobject c, jint vpX, jint vpY, jint vpW, jint vpH, jint format, jint type, jintArray pixels, jint offset, jint size) { + GLint* bufferNative = malloc(size); + + glReadPixels( + (GLint)vpX, + (GLint)vpY, + (GLsizei)vpW, + (GLsizei)vpH, + (GLenum)format, + (GLenum)type, + (GLvoid *)bufferNative + ); + + (*e)->SetIntArrayRegion(e, pixels, offset, size, bufferNative); + + free(bufferNative); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glRenderbufferStorage(JNIEnv* e, jobject c, jint target, jint internalformat, jint width, jint height) { + glRenderbufferStorage( + (GLenum)target, + (GLenum)internalformat, + (GLsizei)width, + (GLsizei)height + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glScissor(JNIEnv* e, jobject c, jint x, jint y, jint width, jint height) { + glScissor( + (GLint)x, + (GLint)y, + (GLsizei)width, + (GLsizei)height + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glShaderSource(JNIEnv* e, jobject c, jint shader, jstring string) { + const char *stringNative = (*e)->GetStringUTFChars(e, string, NULL); + glShaderSource(shader, 1, &stringNative, NULL); + //jsize stringLen = (*e)->GetStringUTFLength(e, string); + //const char** code = { stringNative }; + //const GLint* length = { stringLen }; + + printf("upload shader source: %s", stringNative); + + //glShaderSource(shader, 1, code, length); + + (*e)->ReleaseStringUTFChars(e, string, stringNative); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glStencilFuncSeparate(JNIEnv* e, jobject c, jint face, jint func, jint ref, jint mask) { + glStencilFuncSeparate( + (GLenum) face, + (GLenum) func, + (GLint) ref, + (GLuint) mask + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glStencilOpSeparate(JNIEnv* e, jobject c, jint face, jint sfail, jint dpfail, jint dppass) { + glStencilOpSeparate( + (GLenum) face, + (GLenum) sfail, + (GLenum) dpfail, + (GLenum) dppass + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexImage2D(JNIEnv* e, jobject c, jint target, jint level, jint internalformat, jint width, jint height, jint border, jint format, jint type, jobject pixels_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + glTexImage2D( + (GLenum)target, + (GLint)level, + (GLint)internalformat, + (GLsizei)width, + (GLsizei)height, + (GLint)border, + (GLenum)format, + (GLenum)type, + (GLvoid *)pixels + ); + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexParameteri(JNIEnv* e, jobject c, jint target, jint pname, jint param) { + glTexParameteri( + (GLenum)target, + (GLenum)pname, + (GLint)param + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexParameterf(JNIEnv* e, jobject c, jint target, jint pname, jfloat param) { + glTexParameterf( + (GLenum)target, + (GLenum)pname, + (GLfloat)param + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexSubImage2D(JNIEnv* e, jobject c, jint target, jint level, jint xoffset, jint yoffset, jint width, jint height, jint format, jint type, jobject pixels_buf) { + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + glTexSubImage2D( + (GLenum)target, + (GLint)level, + (GLint)xoffset, + (GLint)yoffset, + (GLsizei)width, + (GLsizei)height, + (GLenum)format, + (GLenum)type, + (GLvoid *)pixels + ); + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1f(JNIEnv* e, jobject c, jint location, jfloat x) { + glUniform1f( + (GLint)location, + (GLfloat)x + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1fv(JNIEnv* e, jobject c, jint location, jint count, jobject v_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *v = (GLfloat *) 0; + + v = (GLfloat *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLfloat *) (_vBase + _bufferOffset); + } + glUniform1fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1fv2(JNIEnv* e, jobject c, jint location, jint count, jfloatArray v, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, v, NULL); + + glUniform1fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, v, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1i(JNIEnv* e, jobject c, jint location, jint x) { + glUniform1i( + (GLint)location, + (GLint)x + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1iv(JNIEnv* e, jobject c, jint location, jint count, jobject v_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLint *v = (GLint *) 0; + + v = (GLint *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLint *) (_vBase + _bufferOffset); + } + glUniform1iv( + (GLint)location, + (GLsizei)count, + (GLint *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform1iv2(JNIEnv* e, jobject c, jint location, jint count, jintArray v, jint offset) { + jint *vNative = (*e)->GetIntArrayElements(e, v, NULL); + + glUniform1iv( + (GLint)location, + (GLsizei)count, + (GLint *)(vNative + offset * sizeof(GLint)) + ); + glUniform1iv(location, count, vNative + offset); + + (*e)->ReleaseIntArrayElements(e, v, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform2f(JNIEnv* e, jobject c, jint location, jfloat x, jfloat y) { + glUniform2f( + (GLint)location, + (GLfloat)x, + (GLfloat)y + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform2fv(JNIEnv* e, jobject c, jint location, jint count, jobject v_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *v = (GLfloat *) 0; + + v = (GLfloat *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count*2) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count*2 < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLfloat *) (_vBase + _bufferOffset); + } + glUniform2fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform2fv2(JNIEnv* e, jobject c, jint location, jint count, jfloatArray v, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, v, NULL); + + glUniform2fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, v, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform3f(JNIEnv* e, jobject c, jint location, jfloat x, jfloat y, jfloat z) { + glUniform3f( + (GLint)location, + (GLfloat)x, + (GLfloat)y, + (GLfloat)z + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform3fv(JNIEnv* e, jobject c, jint location, jint count, jobject v_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *v = (GLfloat *) 0; + + v = (GLfloat *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count * 3) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count*3 < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLfloat *) (_vBase + _bufferOffset); + } + glUniform3fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform3fv2(JNIEnv* e, jobject c, jint location, jint count, jfloatArray v, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, v, NULL); + + glUniform3fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, v, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform4f(JNIEnv* e, jobject c, jint location, jfloat x, jfloat y, jfloat z, jfloat w) { + glUniform4f( + (GLint)location, + (GLfloat)x, + (GLfloat)y, + (GLfloat)z, + (GLfloat)w + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform4fv(JNIEnv* e, jobject c, jint location, jint count, jobject v_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *v = (GLfloat *) 0; + + v = (GLfloat *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count * 4) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count*4 < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLfloat *) (_vBase + _bufferOffset); + } + glUniform4fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniform4fv2(JNIEnv* e, jobject c, jint location, jint count, jfloatArray v, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, v, NULL); + + glUniform4fv( + (GLint)location, + (GLsizei)count, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, v, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniformMatrix3fv(JNIEnv* e, jobject c, jint location, jint count, jboolean transpose, jobject value_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *value = (GLfloat *) 0; + + value = (GLfloat *)getPointer(e, value_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count*9) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count*9 < needed"; + goto exit; + } + if (value == NULL) { + char * _valueBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + value = (GLfloat *) (_valueBase + _bufferOffset); + } + glUniformMatrix3fv( + (GLint)location, + (GLsizei)count, + (GLboolean)transpose, + (GLfloat *)value + ); + +exit: + if (_array) { + releasePointer(e, _array, value, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniformMatrix3fv2(JNIEnv* e, jobject c, jint location, jint count, jboolean transpose, jfloatArray value, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, value, NULL); + + glUniformMatrix3fv( + (GLint)location, + (GLsizei)count, + (GLboolean)transpose, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, value, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniformMatrix4fv(JNIEnv* e, jobject c, jint location, jint count, jboolean transpose, jobject value_buf) { + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLfloat *value = (GLfloat *) 0; + + value = (GLfloat *)getPointer(e, value_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count * 16) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count*16 < needed"; + goto exit; + } + if (value == NULL) { + char * _valueBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + value = (GLfloat *) (_valueBase + _bufferOffset); + } + glUniformMatrix4fv( + (GLint)location, + (GLsizei)count, + (GLboolean)transpose, + (GLfloat *)value + ); + +exit: + if (_array) { + releasePointer(e, _array, value, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUniformMatrix4fv2(JNIEnv* e, jobject c, jint location, jint count, jboolean transpose, jfloatArray value, jint offset) { + jfloat *vNative = (*e)->GetFloatArrayElements(e, value, NULL); + + glUniformMatrix4fv( + (GLint)location, + (GLsizei)count, + (GLboolean)transpose, + (GLfloat *)(vNative + offset * sizeof(GLfloat)) + ); + + (*e)->ReleaseFloatArrayElements(e, value, vNative, 0); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glUseProgram(JNIEnv* e, jobject c, jint program) { + glUseProgram( + (GLuint)program + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glVertexAttribPointer(JNIEnv* e, jobject c, jint indx, jint size, jint type, jboolean normalized, jint stride, jobject buffer) { + GLvoid *ptr = (GLvoid *) 0; + + if (buffer) { + ptr = (GLvoid *) getDirectBufferPointer(e, buffer); + if (!ptr) { + return; + } + } + glVertexAttribPointer( + (GLuint)indx, + (GLint)size, + (GLenum)type, + (GLboolean)normalized, + (GLsizei)stride, + (GLvoid *)ptr + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glVertexAttribPointer2(JNIEnv* e, jclass c, jint indx, jint size, jint type, jboolean normalized, jint stride, jint offset) { + + glVertexAttribPointer( + (GLuint)indx, + (GLint)size, + (GLenum)type, + (GLboolean)normalized, + (GLsizei)stride, + (GLvoid *)(offset) + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glViewport(JNIEnv* e, jobject c, jint x, jint y, jint width, jint height) { + glViewport( + (GLint)x, + (GLint)y, + (GLsizei)width, + (GLsizei)height + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBeginQuery(JNIEnv* e, jobject c, jint target, jint query) { + glBeginQuery( + (GLint) target, + (GLint) query + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glEndQuery(JNIEnv* e, jobject c, jint target) +{ + glEndQuery((GLint)target); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGenQueries(JNIEnv* e, jobject c, jint count, jobject v_buf) +{ + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLint *v = (GLint *) 0; + + v = (GLint *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLint *) (_vBase + _bufferOffset); + } + glGenQueries( + (GLsizei)count, + (GLint *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetQueryObjectuiv(JNIEnv* e, jobject c, jint query, jint pname, jintArray params_ref) +{ + jint _exception = 0; + const char * _exceptionType; + const char * _exceptionMessage; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + int _needed = 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + + _remaining = (*e)->GetArrayLength(e, params_ref); + _needed = getNeededCount(pname); + // if we didn't find this pname, we just assume the user passed + // an array of the right size -- this might happen with extensions + // or if we forget an enum here. + if (_remaining < _needed) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length < needed"; + goto exit; + } + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base; + + glGetQueryObjectuiv( + (GLint)query, + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glGetQueryiv(JNIEnv* e, jobject c, jint target, jint pname, jintArray params_ref) +{ + jint _exception = 0; + const char * _exceptionType; + const char * _exceptionMessage; + GLint *params_base = (GLint *) 0; + jint _remaining; + GLint *params = (GLint *) 0; + int _needed = 0; + + if (!params_ref) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "params == null"; + goto exit; + } + + _remaining = (*e)->GetArrayLength(e, params_ref); + _needed = getNeededCount(pname); + // if we didn't find this pname, we just assume the user passed + // an array of the right size -- this might happen with extensions + // or if we forget an enum here. + if (_remaining < _needed) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "length < needed"; + goto exit; + } + params_base = (GLint *) + (*e)->GetPrimitiveArrayCritical(e, params_ref, (jboolean *)0); + params = params_base; + + glGetQueryiv( + (GLenum)target, + (GLenum)pname, + (GLint *)params + ); + +exit: + if (params_base) { + (*e)->ReleasePrimitiveArrayCritical(e, params_ref, params_base, + _exception ? JNI_ABORT: 0); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glBlitFramebuffer(JNIEnv* e, jobject c, jint srcX0, jint srcY0, jint srcX1, jint srcY1, jint dstX0, jint dstY0, jint dstX1, jint dstY1, jint mask, jint filter) +{ + glBlitFramebuffer( + (GLint) srcX0, + (GLint) srcY0, + (GLint) srcX1, + (GLint) srcY1, + (GLint) dstX0, + (GLint) dstY0, + (GLint) dstX1, + (GLint) dstY1, + (GLbitfield) mask, + (GLenum) filter + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawArraysInstanced(JNIEnv* e, jobject c, jint mode, jint first, jint count, jint primcount) +{ + glDrawArraysInstanced( + (GLenum) mode, + (GLint) first, + (GLsizei) count, + (GLsizei) primcount + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawBuffers(JNIEnv* e, jobject c, jint count, jobject v_buf) +{ + jint _exception = 0; + const char * _exceptionType = NULL; + const char * _exceptionMessage = NULL; + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLint *v = (GLint *) 0; + + v = (GLint *)getPointer(e, v_buf, &_array, &_remaining, &_bufferOffset); + if (_remaining < count) { + _exception = 1; + _exceptionType = "java/lang/IllegalArgumentException"; + _exceptionMessage = "remaining() < count < needed"; + goto exit; + } + if (v == NULL) { + char * _vBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + v = (GLint *) (_vBase + _bufferOffset); + } + glDrawBuffers( + (GLsizei)count, + (GLint *)v + ); + +exit: + if (_array) { + releasePointer(e, _array, v, JNI_FALSE); + } + if (_exception) { + jniThrowException(e, _exceptionType, _exceptionMessage); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glDrawElementsInstanced(JNIEnv* e, jobject c, jint mode, jint count, jint type, jlong indices, jint primcount) +{ + glDrawElementsInstanced( + (GLenum) mode, + (GLsizei) count, + (GLenum) type, + (const void *) indices, + (GLsizei) primcount + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glVertexAttribDivisor(JNIEnv* e, jobject c, jint index, jint divisor) +{ + glVertexAttribDivisor( + (GLint) index, + (GLint) divisor + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glFramebufferTextureLayer(JNIEnv* e, jobject c, jint target, jint attachment, jint texture, jint level, jint layer) +{ + glFramebufferTextureLayer( + (GLenum) target, + (GLenum) attachment, + (GLuint) texture, + (GLint) level, + (GLint) layer + ); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glReadBuffer(JNIEnv* e, jobject c, jint src) +{ + glReadBuffer((GLenum) src); +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCompressedTexImage3D(JNIEnv* e, jobject c, jint target, jint level, jint internalFormat, jint width, jint height, jint depth, jint border, jint imageSize, jobject pixels_buf) +{ + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + + glCompressedTexImage3D( + (GLenum) target, + (GLint) level, + (GLenum) internalFormat, + (GLsizei) width, + (GLsizei) height, + (GLsizei) depth, + (GLint) border, + (GLsizei) imageSize, + (GLvoid *)pixels + ); + + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glCompressedTexSubImage3D(JNIEnv* e, jobject c, jint target, jint level, jint xoffset, jint yoffset, jint zoffset, jint width, jint height, jint depth, jint format, jint imageSize, jobject pixels_buf) +{ + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + + glCompressedTexSubImage3D( + (GLenum) target, + (GLint) level, + (GLint) xoffset, + (GLint) yoffset, + (GLint) zoffset, + (GLsizei) width, + (GLsizei) height, + (GLsizei) depth, + (GLenum) format, + (GLsizei) imageSize, + (GLvoid *)pixels + ); + + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexImage3D(JNIEnv* e, jobject c, jint target, jint level, jint internalFormat, jint width, jint height, jint depth, jint border, jint format, jint type, jobject pixels_buf) +{ + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + + glTexImage3D( + (GLenum) target, + (GLint) level, + (GLint) internalFormat, + (GLsizei) width, + (GLsizei) height, + (GLsizei) depth, + (GLint) border, + (GLenum) format, + (GLenum) type, + (GLvoid *)pixels + ); + + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + +JNIEXPORT void JNICALL +Java_com_jme3_renderer_ios_JmeIosGLES_glTexSubImage3D(JNIEnv* e, jobject c, jint target, jint level, jint xoffset, jint yoffset, jint zoffset, jint width, jint height, jint depth, jint format, jint type, jobject pixels_buf) +{ + jarray _array = (jarray) 0; + jint _bufferOffset = (jint) 0; + jint _remaining; + GLvoid *pixels = (GLvoid *) 0; + + if (pixels_buf) { + pixels = (GLvoid *)getPointer(e, pixels_buf, &_array, &_remaining, &_bufferOffset); + } + if (pixels_buf && pixels == NULL) { + char * _pixelsBase = (char *)(*e)->GetPrimitiveArrayCritical(e, _array, (jboolean *) 0); + pixels = (GLvoid *) (_pixelsBase + _bufferOffset); + } + + glTexSubImage3D( + (GLenum) target, + (GLint) level, + (GLint) xoffset, + (GLint) yoffset, + (GLint) zoffset, + (GLsizei) width, + (GLsizei) height, + (GLsizei) depth, + (GLenum) format, + (GLenum) type, + (GLvoid *)pixels + ); + + if (_array) { + releasePointer(e, _array, pixels, JNI_FALSE); + } +} + + +static int +allowIndirectBuffers(JNIEnv *e) { + return 0; +} + +static void * +getDirectBufferPointer(JNIEnv *e, jobject buffer) { + if (!buffer) { + return NULL; + } + + if (!initialized) { + nativeClassInit(e); + } + + void* buf = (*e)->GetDirectBufferAddress(e, buffer); + if (buf) { + jint position = (*e)->GetIntField(e, buffer, positionID); + jint elementSizeShift = getBufferElementSize(e, buffer); + buf = ((char*) buf) + (position << elementSizeShift); + } else { + jniThrowException(e, "java/lang/IllegalArgumentException", + "Must use a native order direct Buffer"); + } + return buf; +} + +static void * +getPointer(JNIEnv *e, jobject buffer, jarray *array, jint *remaining, jint *offset) { + jint position; + jint limit; + jint elementSizeShift; + jlong pointer; + + if (!buffer) { + return NULL; + } + + if (!initialized) { + nativeClassInit(e); + } + + position = (*e)->GetIntField(e, buffer, positionID); + limit = (*e)->GetIntField(e, buffer, limitID); + elementSizeShift = getBufferElementSize(e, buffer); + + array = (void*) NULL; + *remaining = (limit - position) << elementSizeShift; + *offset = position; + + return getDirectBufferPointer(e, buffer); +} + + +static void +nativeClassInit(JNIEnv *e) { + if (!byteBufferClass) { + jclass byteBufferClassLocal = (*e)->FindClass(e, "java/nio/ByteBuffer"); + byteBufferClass = (jclass) (*e)->NewGlobalRef(e, byteBufferClassLocal); + } + + if (!shortBufferClass) { + jclass shortBufferClassLocal = (*e)->FindClass(e, "java/nio/ShortBuffer"); + shortBufferClass = (jclass) (*e)->NewGlobalRef(e, shortBufferClassLocal); + } + + if (!intBufferClass) { + jclass intBufferClassLocal = (*e)->FindClass(e, "java/nio/IntBuffer"); + intBufferClass = (jclass) (*e)->NewGlobalRef(e, intBufferClassLocal); + } + + if (!floatBufferClass) { + jclass floatBufferClassLocal = (*e)->FindClass(e, "java/nio/FloatBuffer"); + floatBufferClass = (jclass) (*e)->NewGlobalRef(e, floatBufferClassLocal); + } + + if (!bufferClass) { + jclass bufferClassLocal = (*e)->FindClass(e, "java/nio/Buffer"); + bufferClass = (jclass) (*e)->NewGlobalRef(e, bufferClassLocal); + } + + if (!positionID && bufferClass) { + positionID = (*e)->GetFieldID(e, bufferClass, "position", "I"); + } + + if (!limitID && bufferClass) { + limitID = (*e)->GetFieldID(e, bufferClass, "limit", "I"); + } + + initialized = floatBufferClass && bufferClass && shortBufferClass && byteBufferClass + && intBufferClass && positionID && limitID; + + printf("Initializion of java.nio.Buffer access functionality %s\n", initialized ? "succeeded" : "failed"); +} + +static void +releasePointer(JNIEnv *e, jarray array, void *data, jboolean commit) { + (*e)->ReleasePrimitiveArrayCritical(e, array, data, + commit ? 0 : JNI_ABORT); +} + +static void +jniThrowException(JNIEnv *e, const char* type, const char* message) { + jclass excCls = (*e)->FindClass(e, type); + if (excCls != 0) { + (*e)->ThrowNew(e, excCls, message); + } +} + +static jint +getBufferElementSize(JNIEnv *e, jobject buffer) { + if (!buffer) { + return 0; + } + + if ((*e)->IsInstanceOf(e, buffer, floatBufferClass) == JNI_TRUE) { + return 2; + } else if ((*e)->IsInstanceOf(e, buffer, intBufferClass) == JNI_TRUE) { + return 2; + } else if ((*e)->IsInstanceOf(e, buffer, shortBufferClass) == JNI_TRUE) { + return 1; + } + + //TODO: check other buffer types + return 0; +} + +static int getNeededCount(GLint pname) { + int needed = 1; +#ifdef GL_ES_VERSION_2_0 + // GLES 2.x pnames + switch (pname) { + case GL_ALIASED_LINE_WIDTH_RANGE: + case GL_ALIASED_POINT_SIZE_RANGE: + needed = 2; + break; + + case GL_BLEND_COLOR: + case GL_COLOR_CLEAR_VALUE: + case GL_COLOR_WRITEMASK: + case GL_SCISSOR_BOX: + case GL_VIEWPORT: + needed = 4; + break; + + case GL_COMPRESSED_TEXTURE_FORMATS: + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &needed); + break; + + case GL_SHADER_BINARY_FORMATS: + glGetIntegerv(GL_NUM_SHADER_BINARY_FORMATS, &needed); + break; + } +#endif + +#ifdef GL_VERSION_ES_CM_1_1 + // GLES 1.x pnames + switch (pname) { + case GL_ALIASED_LINE_WIDTH_RANGE: + case GL_ALIASED_POINT_SIZE_RANGE: + case GL_DEPTH_RANGE: + case GL_SMOOTH_LINE_WIDTH_RANGE: + case GL_SMOOTH_POINT_SIZE_RANGE: + needed = 2; + break; + + case GL_CURRENT_NORMAL: + case GL_POINT_DISTANCE_ATTENUATION: + needed = 3; + break; + + case GL_COLOR_CLEAR_VALUE: + case GL_COLOR_WRITEMASK: + case GL_CURRENT_COLOR: + case GL_CURRENT_TEXTURE_COORDS: + case GL_FOG_COLOR: + case GL_LIGHT_MODEL_AMBIENT: + case GL_SCISSOR_BOX: + case GL_VIEWPORT: + needed = 4; + break; + + case GL_MODELVIEW_MATRIX: + case GL_PROJECTION_MATRIX: + case GL_TEXTURE_MATRIX: + needed = 16; + break; + + case GL_COMPRESSED_TEXTURE_FORMATS: + glGetIntegerv(GL_NUM_COMPRESSED_TEXTURE_FORMATS, &needed); + break; + } +#endif + return needed; +} diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosAL.c b/jme3-ios-native/src/com_jme3_audio_ios_IosAL.c new file mode 100644 index 0000000000..d256405a77 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosAL.c @@ -0,0 +1,138 @@ +#include "com_jme3_audio_ios_IosAL.h" +//#include "AL/al.h" +//#include "AL/alext.h" + +#include "OpenAL/al.h" +#include "OpenAL/alc.h" +#include "OpenAL/oalMacOSX_OALExtensions.h" + +JNIEXPORT jstring JNICALL Java_com_jme3_audio_ios_IosAL_alGetString + (JNIEnv* env, jobject obj, jint param) +{ + return (*env)->NewStringUTF(env, alGetString(param)); +} + +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGenSources + (JNIEnv *env, jobject obj) +{ + ALuint source; + alGenSources(1, &source); + return source; +} + +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGetError + (JNIEnv *env, jobject obj) +{ + return alGetError(); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alDeleteSources + (JNIEnv* env, jobject obj, jint numSources, jobject intbufSources) +{ + ALuint* pIntBufSources = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufSources); + alDeleteSources((ALsizei)numSources, pIntBufSources); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alGenBuffers + (JNIEnv* env, jobject obj, jint numBuffers, jobject intbufBuffers) +{ + ALuint* pIntBufBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufBuffers); + alGenBuffers((ALsizei)numBuffers, pIntBufBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alDeleteBuffers + (JNIEnv* env, jobject obj, jint numBuffers, jobject intbufBuffers) +{ + ALuint* pIntBufBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, intbufBuffers); + alDeleteBuffers((ALsizei)numBuffers, pIntBufBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceStop + (JNIEnv *env, jobject obj, jint source) +{ + alSourceStop((ALuint)source); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcei + (JNIEnv *env, jobject obj, jint source, jint param, jint value) +{ + alSourcei((ALuint)source, (ALenum)param, (ALint)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alBufferData + (JNIEnv* env, jobject obj, jint buffer, jint format, jobject bufferData, jint bufferSize, jint frequency) +{ + ALuint* pBufferData = (ALuint*) (*env)->GetDirectBufferAddress(env, bufferData); + alBufferData((ALuint)buffer, (ALenum)format, pBufferData, (ALsizei)bufferSize, (ALsizei)frequency); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcePlay + (JNIEnv *env, jobject obj, jint source) +{ + alSourcePlay((ALuint)source); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcePause + (JNIEnv *env, jobject obj, jint source) +{ + alSourcePause((ALuint)source); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcef + (JNIEnv *env, jobject obj, jint source, jint param, jfloat value) +{ + alSourcef((ALuint)source, (ALenum)param, (ALfloat)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSource3f + (JNIEnv *env, jobject obj, jint source, jint param, jfloat value1, jfloat value2, jfloat value3) +{ + alSource3f((ALuint)source, (ALenum)param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3); +} + +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGetSourcei + (JNIEnv *env, jobject obj, jint source, jint param) +{ + ALint result; + alGetSourcei((ALuint)source, (ALenum)param, &result); + return (jint)result; +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceUnqueueBuffers + (JNIEnv* env, jobject obj, jint source, jint numBuffers, jobject buffers) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffers); + alSourceUnqueueBuffers((ALuint)source, (ALsizei)numBuffers, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceQueueBuffers + (JNIEnv* env, jobject obj, jint source, jint numBuffers, jobject buffers) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffers); + alSourceQueueBuffers((ALuint)source, (ALsizei)numBuffers, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListener + (JNIEnv* env, jobject obj, jint param, jobject bufferData) +{ + ALfloat* pBufferData = (ALfloat*) (*env)->GetDirectBufferAddress(env, bufferData); + alListenerfv((ALenum)param, pBufferData); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListenerf + (JNIEnv *env, jobject obj, jint param, jfloat value) +{ + alListenerf((ALenum)param, (ALfloat)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListener3f + (JNIEnv *env, jobject obj, jint param, jfloat value1, jfloat value2, jfloat value3) +{ + alListener3f((ALenum)param, (ALfloat)value1, (ALfloat)value2, (ALfloat)value3); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSource3i + (JNIEnv *env, jobject obj, jint source, jint param, jint value1, jint value2, jint value3) +{ + alSource3i((ALuint)source, (ALenum)param, (ALint)value1, (ALint)value2, (ALint)value3); +} diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosAL.h b/jme3-ios-native/src/com_jme3_audio_ios_IosAL.h new file mode 100644 index 0000000000..e3285f4fa0 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosAL.h @@ -0,0 +1,173 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_audio_ios_IosAL */ + +#ifndef _Included_com_jme3_audio_ios_IosAL +#define _Included_com_jme3_audio_ios_IosAL +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alGetString + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_jme3_audio_ios_IosAL_alGetString + (JNIEnv *, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alGenSources + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGenSources + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alGetError + * Signature: ()I + */ +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGetError + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alDeleteSources + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alDeleteSources + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alGenBuffers + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alGenBuffers + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alDeleteBuffers + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alDeleteBuffers + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourceStop + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceStop + (JNIEnv *, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourcei + * Signature: (III)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcei + (JNIEnv *, jobject, jint, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alBufferData + * Signature: (IILjava/nio/ByteBuffer;II)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alBufferData + (JNIEnv *, jobject, jint, jint, jobject, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourcePlay + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcePlay + (JNIEnv *, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourcePause + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcePause + (JNIEnv *, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourcef + * Signature: (IIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourcef + (JNIEnv *, jobject, jint, jint, jfloat); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSource3f + * Signature: (IIFFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSource3f + (JNIEnv *, jobject, jint, jint, jfloat, jfloat, jfloat); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alGetSourcei + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_com_jme3_audio_ios_IosAL_alGetSourcei + (JNIEnv *, jobject, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourceUnqueueBuffers + * Signature: (IILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceUnqueueBuffers + (JNIEnv *, jobject, jint, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSourceQueueBuffers + * Signature: (IILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSourceQueueBuffers + (JNIEnv *, jobject, jint, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alListener + * Signature: (ILjava/nio/FloatBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListener + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alListenerf + * Signature: (IF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListenerf + (JNIEnv *, jobject, jint, jfloat); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alListener3f + * Signature: (IFFF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alListener3f + (JNIEnv *, jobject, jint, jfloat, jfloat, jfloat); + +/* + * Class: com_jme3_audio_ios_IosAL + * Method: alSource3i + * Signature: (IIIII)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosAL_alSource3i + (JNIEnv *, jobject, jint, jint, jint, jint, jint); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosALC.c b/jme3-ios-native/src/com_jme3_audio_ios_IosALC.c new file mode 100644 index 0000000000..5ed39bd7fa --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosALC.c @@ -0,0 +1,178 @@ +//#include "util.h" +#include "com_jme3_audio_ios_IosALC.h" +//#include "AL/alc.h" +//#include "AL/alext.h" + +#include "OpenAL/al.h" +#include "OpenAL/alc.h" +#include "OpenAL/oalMacOSX_OALExtensions.h" + +static jboolean created = JNI_FALSE; + +/* InitAL opens the default device and sets up a context using default + * attributes, making the program ready to call OpenAL functions. */ +static int InitAL() +{ + ALCdevice *device = NULL; + ALCcontext *ctx = NULL; + + /* Open and initialize a device with default settings */ + device = alcOpenDevice(NULL); + + if(device == NULL) + { + fprintf(stderr, "Could not open a device!\n"); + goto cleanup; + } + + ctx = alcCreateContext(device, NULL); + + if (ctx == NULL) + { + fprintf(stderr, "Could not create context!\n"); + goto cleanup; + } + + if (!alcMakeContextCurrent(ctx)) + { + fprintf(stderr, "Could not make context current!\n"); + goto cleanup; + } + + return 0; + +cleanup: + if (ctx != NULL) alcDestroyContext(ctx); + if (device != NULL) alcCloseDevice(device); + return 1; +} + +/* CloseAL closes the device belonging to the current context, and destroys the + * context. */ +static void CloseAL() +{ + ALCdevice *device; + ALCcontext *ctx; + + ctx = alcGetCurrentContext(); + + if (ctx == NULL) + { + return; + } + + device = alcGetContextsDevice(ctx); + + if (device == NULL) + { + return; + } + + if(!alcMakeContextCurrent(NULL)) { + return; + } + + alcDestroyContext(ctx); + alcCloseDevice(device); +} + +static ALCdevice* GetALCDevice() +{ + ALCdevice *device; + ALCcontext *ctx; + + ctx = alcGetCurrentContext(); + + if (ctx != NULL) + { + device = alcGetContextsDevice(ctx); + + if (device != NULL) + { + return device; + } + } + + return NULL; +} + +JNIEXPORT jboolean JNICALL Java_com_jme3_audio_ios_IosALC_isCreated + (JNIEnv* env, jobject obj) +{ + return created; +} + + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_createALC + (JNIEnv* env, jobject obj) +{ + created = (InitAL() == 0); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_destroyALC + (JNIEnv* env, jobject obj) +{ + CloseAL(); + created = JNI_FALSE; +} + +JNIEXPORT jstring JNICALL Java_com_jme3_audio_ios_IosALC_alcGetString + (JNIEnv* env, jobject obj, jint param) +{ + ALCdevice* device = GetALCDevice(); + if (device == NULL) return NULL; + return (*env)->NewStringUTF(env, alcGetString(device, param)); +} + +JNIEXPORT jboolean JNICALL Java_com_jme3_audio_ios_IosALC_alcIsExtensionPresent + (JNIEnv* env, jobject obj, jstring extension) +{ + ALCdevice* device = GetALCDevice(); + + if (device == NULL) return JNI_FALSE; + + const char* strExtension = (*env)->GetStringUTFChars(env, extension, NULL); + + if (strExtension == NULL) + { + return JNI_FALSE; + } + + jboolean result = alcIsExtensionPresent(device, strExtension); + + (*env)->ReleaseStringUTFChars(env, extension, strExtension); + + return result; +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcGetInteger + (JNIEnv* env, jobject obj, jint param, jobject buffer, jint bufferSize) +{ + ALCdevice* device = GetALCDevice(); + + if (device == NULL) return; + + ALCint* pBuffers = (ALCint*) (*env)->GetDirectBufferAddress(env, buffer); + + alcGetIntegerv(device, (ALCenum)param, (ALCsizei)bufferSize, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcDevicePauseSOFT + (JNIEnv* env, jobject obj) +{ + ALCdevice* device = GetALCDevice(); + + if (device == NULL) return; + +// alcDevicePauseSOFT(device); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcDeviceResumeSOFT + (JNIEnv* env, jobject obj) +{ + ALCdevice* device = GetALCDevice(); + + if (device == NULL) return; + +// alcDeviceResumeSOFT(device); +} diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosALC.h b/jme3-ios-native/src/com_jme3_audio_ios_IosALC.h new file mode 100644 index 0000000000..6093d8f384 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosALC.h @@ -0,0 +1,77 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_audio_ios_IosALC */ + +#ifndef _Included_com_jme3_audio_ios_IosALC +#define _Included_com_jme3_audio_ios_IosALC +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_audio_ios_IosALC + * Method: createALC + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_createALC + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: destroyALC + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_destroyALC + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: isCreated + * Signature: ()Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_audio_ios_IosALC_isCreated + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: alcGetString + * Signature: (I)Ljava/lang/String; + */ +JNIEXPORT jstring JNICALL Java_com_jme3_audio_ios_IosALC_alcGetString + (JNIEnv *, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: alcIsExtensionPresent + * Signature: (Ljava/lang/String;)Z + */ +JNIEXPORT jboolean JNICALL Java_com_jme3_audio_ios_IosALC_alcIsExtensionPresent + (JNIEnv *, jobject, jstring); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: alcGetInteger + * Signature: (ILjava/nio/IntBuffer;I)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcGetInteger + (JNIEnv *, jobject, jint, jobject, jint); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: alcDevicePauseSOFT + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcDevicePauseSOFT + (JNIEnv *, jobject); + +/* + * Class: com_jme3_audio_ios_IosALC + * Method: alcDeviceResumeSOFT + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosALC_alcDeviceResumeSOFT + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.c b/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.c new file mode 100644 index 0000000000..d0d8b222f8 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.c @@ -0,0 +1,79 @@ +//#include "util.h" +#include "com_jme3_audio_ios_IosEFX.h" +//#include "AL/alext.h" + +#include "OpenAL/al.h" +#include "OpenAL/alc.h" +#include "OpenAL/oalMacOSX_OALExtensions.h" + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenAuxiliaryEffectSlots + (JNIEnv* env, jobject obj, jint numSlots, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alGenAuxiliaryEffectSlots((ALsizei)numSlots, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenEffects + (JNIEnv* env, jobject obj, jint numEffects, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alGenEffects((ALsizei)numEffects, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alEffecti + (JNIEnv* env, jobject obj, jint effect, jint param, jint value) +{ +// alEffecti((ALuint)effect, (ALenum)param, (ALint)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alAuxiliaryEffectSloti + (JNIEnv* env, jobject obj, jint effectSlot, jint param, jint value) +{ +// alAuxiliaryEffectSloti((ALuint)effectSlot, (ALenum)param, (ALint)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteEffects + (JNIEnv* env, jobject obj, jint numEffects, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alDeleteEffects((ALsizei)numEffects, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteAuxiliaryEffectSlots + (JNIEnv* env, jobject obj, jint numEffectSlots, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alDeleteAuxiliaryEffectSlots((ALsizei)numEffectSlots, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenFilters + (JNIEnv* env, jobject obj, jint numFilters, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alGenFilters((ALsizei)numFilters, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alFilteri + (JNIEnv* env, jobject obj, jint filter, jint param, jint value) +{ +// alFilteri((ALuint)filter, (ALenum)param, (ALint)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alFilterf + (JNIEnv* env, jobject obj, jint filter, jint param, jfloat value) +{ +// alFilterf((ALuint)filter, (ALenum)param, (ALfloat)value); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteFilters + (JNIEnv* env, jobject obj, jint numFilters, jobject buffer) +{ + ALuint* pBuffers = (ALuint*) (*env)->GetDirectBufferAddress(env, buffer); +// alDeleteFilters((ALsizei)numFilters, pBuffers); +} + +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alEffectf + (JNIEnv* env, jobject obj, jint effect, jint param, jfloat value) +{ +// alEffectf((ALuint)effect, (ALenum)param, (ALfloat)value); +} diff --git a/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.h b/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.h new file mode 100644 index 0000000000..50e38bec65 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_audio_ios_IosEFX.h @@ -0,0 +1,101 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_audio_ios_IosEFX */ + +#ifndef _Included_com_jme3_audio_ios_IosEFX +#define _Included_com_jme3_audio_ios_IosEFX +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alGenAuxiliaryEffectSlots + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenAuxiliaryEffectSlots + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alGenEffects + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenEffects + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alEffecti + * Signature: (III)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alEffecti + (JNIEnv *, jobject, jint, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alAuxiliaryEffectSloti + * Signature: (III)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alAuxiliaryEffectSloti + (JNIEnv *, jobject, jint, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alDeleteEffects + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteEffects + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alDeleteAuxiliaryEffectSlots + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteAuxiliaryEffectSlots + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alGenFilters + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alGenFilters + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alFilteri + * Signature: (III)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alFilteri + (JNIEnv *, jobject, jint, jint, jint); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alFilterf + * Signature: (IIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alFilterf + (JNIEnv *, jobject, jint, jint, jfloat); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alDeleteFilters + * Signature: (ILjava/nio/IntBuffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alDeleteFilters + (JNIEnv *, jobject, jint, jobject); + +/* + * Class: com_jme3_audio_ios_IosEFX + * Method: alEffectf + * Signature: (IIF)V + */ +JNIEXPORT void JNICALL Java_com_jme3_audio_ios_IosEFX_alEffectf + (JNIEnv *, jobject, jint, jint, jfloat); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.c b/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.c new file mode 100644 index 0000000000..5f01e8e934 --- /dev/null +++ b/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.c @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/** + * @file com_jme3_util_IosNativeBufferAllocator.c + * @author Jesus Oliver, taken from pavl_g's AndroidNativeBufferAllocator. + * @brief Creates and releases direct byte buffers for {com.jme3.util.IosNativeBufferAllocator}. + * @date 2024-09-24. + * @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. + * - iOS logging : + */ + +#include "com_jme3_util_IosNativeBufferAllocator.h" +#include +#include +#include + +// TODO: iOS log + +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_IosNativeBufferAllocator_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_IosNativeBufferAllocator_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); +} + + diff --git a/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.h b/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.h new file mode 100644 index 0000000000..1dfb40120b --- /dev/null +++ b/jme3-ios-native/src/com_jme3_util_IosNativeBufferAllocator.h @@ -0,0 +1,29 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class com_jme3_util_IosNativeBufferAllocator */ + +#ifndef _Included_com_jme3_util_IosNativeBufferAllocator +#define _Included_com_jme3_util_IosNativeBufferAllocator +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: com_jme3_util_IosNativeBufferAllocator + * Method: releaseDirectByteBuffer + * Signature: (Ljava/nio/Buffer;)V + */ +JNIEXPORT void JNICALL Java_com_jme3_util_IosNativeBufferAllocator_releaseDirectByteBuffer + (JNIEnv *, jobject, jobject); + +/* + * Class: com_jme3_util_IosNativeBufferAllocator + * Method: createDirectByteBuffer + * Signature: (J)Ljava/nio/ByteBuffer; + */ +JNIEXPORT jobject JNICALL Java_com_jme3_util_IosNativeBufferAllocator_createDirectByteBuffer + (JNIEnv *, jobject, jlong); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/jme3-ios-native/src/jme-ios.m b/jme3-ios-native/src/jme-ios.m new file mode 100644 index 0000000000..2d7aa685bf --- /dev/null +++ b/jme3-ios-native/src/jme-ios.m @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2009-2013 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. + */ +#include +#import + +/** + * Author: Normen Hansen + */ + +#ifndef JNIEXPORT +#define JNIEXPORT __attribute__ ((visibility("default"))) \ + __attribute__ ((used)) +#endif + +BOOL checkJNIException(JNIEnv *e){ + if ((*e)->ExceptionCheck(e)) { + (*e)->ExceptionDescribe(e); + (*e)->ExceptionClear(e); + return YES; + } + return NO; +} + +#ifndef _Included_com_jme3_system_ios_IosImageLoader +#define _Included_com_jme3_system_ios_IosImageLoader +#endif + + +static void flipImage(int scanline, int height, char* data) +{ + char tmp[scanline]; + + for (int y = 0; y < height / 2; y++) + { + int oppY = height - y - 1; + int yOff = y * scanline; + int oyOff = oppY * scanline; + // Copy scanline at Y to tmp + memcpy(tmp, &data[yOff], scanline); + // Copy data at opposite Y to Y + memcpy(&data[yOff], &data[oyOff], scanline); + // Copy tmp to opposite Y + memcpy(&data[oyOff], tmp, scanline); + } +} + +JNIEXPORT jobject JNICALL +Java_com_jme3_system_ios_IosImageLoader_loadImageData(JNIEnv* e, jclass obj, jobject imageFormat, jboolean flipY, jobject inputStream){ + // prepare java classes and method pointers + jclass imageClass = (*e)->FindClass(e, "com/jme3/texture/Image"); + jclass imageFormatClass = (*e)->FindClass(e, "com/jme3/texture/Image$Format"); + jclass inputStreamClass = (*e)->FindClass(e, "java/io/InputStream"); + jclass bufferUtilsClass = (*e)->FindClass(e, "com/jme3/util/BufferUtils"); + jclass colorSpaceClass = (*e)->FindClass(e, "com/jme3/texture/image/ColorSpace"); + + jmethodID imageConstructor = (*e)->GetMethodID(e, imageClass, "", "(Lcom/jme3/texture/Image$Format;IILjava/nio/ByteBuffer;Lcom/jme3/texture/image/ColorSpace;)V"); + jmethodID readMethod = (*e)->GetMethodID(e, inputStreamClass, "read", "([B)I"); + jmethodID newBufferMethod = (*e)->GetStaticMethodID(e, bufferUtilsClass, "createByteBuffer", "(I)Ljava/nio/ByteBuffer;"); + jmethodID bitsPerPixel = (*e)->GetMethodID(e, imageFormatClass, "getBitsPerPixel", "()I"); + jfieldID sRGBFieldID = (*e)->GetStaticFieldID(e, colorSpaceClass, "sRGB", "Lcom/jme3/texture/image/ColorSpace;"); + jobject sRGBVal = (*e)->GetStaticObjectField(e, colorSpaceClass, sRGBFieldID); + + if (checkJNIException(e)) { + return nil; + } + + int bpp = (*e)->CallIntMethod(e, imageFormat, bitsPerPixel); + int comps = 4; // Components (Bytes) per Pixel + + if ((bpp % 8) == 0) + { + comps = bpp / 8; + } else { + jclass assetExClazz = (*e)->FindClass(e, "com/jme3/asset/AssetLoadException"); + (*e)->ThrowNew(e, assetExClazz, "Unsupported ImageFormat: Bits per Pixel is not multiple of 8"); + } + + // read data from inputstream via byteArray to NSMutableData + jbyteArray tempArray = (*e)->NewByteArray (e, 1000); + if (checkJNIException(e)) { + return nil; + } + + NSMutableData *inData = [[NSMutableData alloc] init]; + jint size = (*e)->CallIntMethod(e, inputStream, readMethod, tempArray); + if (checkJNIException(e)) { + [inData release]; + return nil; + } + while (size != -1) { + jbyte *data; + data = (*e)->GetByteArrayElements(e, tempArray, false); + [inData appendBytes:data length:size]; + (*e)->ReleaseByteArrayElements(e, tempArray, data, JNI_ABORT); + size = (*e)->CallIntMethod(e, inputStream, readMethod, tempArray); + if (checkJNIException(e)) { + [inData release]; + return nil; + } + } + (*e)->DeleteLocalRef(e, tempArray); + if (checkJNIException(e)) { + [inData release]; + return nil; + } + + // decode image data + NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; + UIImage* inputImage = [UIImage imageWithData:inData]; + if(inputImage == nil){ + [inData release]; + [pool release]; + return nil; + } + CGImageRef inImage = [inputImage CGImage]; + int wdth = CGImageGetWidth(inImage); + int ht = CGImageGetHeight(inImage); + + // NewDirectByteBuffer seems to fail? -> Creating ByteBuffer in java + jobject nativeBuffer = (*e)->CallStaticObjectMethod(e, bufferUtilsClass, newBufferMethod, ht*wdth*comps); + if (checkJNIException(e)) { + [inData release]; + [pool release]; + return nil; + } + void *rawData = (*e)->GetDirectBufferAddress(e, nativeBuffer); + NSUInteger bytesPerRowImg = CGImageGetBytesPerRow(inImage); + NSUInteger bitsPerComponentImg = CGImageGetBitsPerComponent(inImage); + CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB(); + CGContextRef context = CGBitmapContextCreate(rawData,wdth,ht,bitsPerComponentImg,bytesPerRowImg,colorSpace,kCGImageAlphaPremultipliedLast| kCGBitmapByteOrder32Big); + CGColorSpaceRelease(colorSpace); + CGContextDrawImage(context,CGRectMake(0,0,wdth,ht), inImage); + CGContextRelease(context); + [inData release]; + [pool release]; + + if (flipY) { + flipImage(wdth * comps, ht, rawData); + } + + //create image + jobject imageObject = (*e)->NewObject(e, imageClass, imageConstructor, imageFormat, wdth, ht, nativeBuffer, sRGBVal); + return imageObject; +} + +#ifndef _Included_com_jme3_system_ios_JmeIosSystem +#define _Included_com_jme3_system_ios_JmeIosSystem +#endif + +JNIEXPORT void JNICALL +Java_com_jme3_system_ios_JmeIosSystem_showDialog(JNIEnv* e, jobject c, jstring text) { + const char* chars = (*e)->GetStringUTFChars(e, text, 0); + NSString* string = [[NSString alloc] initWithUTF8String : chars]; + (*e)->ReleaseStringUTFChars(e, text, chars); + UIAlertView *alert = [[UIAlertView alloc] initWithTitle : @"Error" + message : string + delegate : nil + cancelButtonTitle : @"OK" + otherButtonTitles : nil]; + [alert show]; + [alert release]; + [string release]; +} diff --git a/jme3-ios-native/src/jme3_ios_native.h b/jme3-ios-native/src/jme3_ios_native.h new file mode 100644 index 0000000000..b30ceab3cc --- /dev/null +++ b/jme3-ios-native/src/jme3_ios_native.h @@ -0,0 +1,18 @@ +// +// jme3_ios_native.h +// jme3-ios-native +// +// Created by joliver82 on 19/09/2024. +// + +#import + +//! Project version number for jme3_ios_native_lib. +FOUNDATION_EXPORT double jme3_ios_nativeVersionNumber; + +//! Project version string for jme3_ios_native_lib. +FOUNDATION_EXPORT const unsigned char jme3_ios_nativeVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/jme3-ios-native/template/META-INF/robovm/ios/robovm.xml b/jme3-ios-native/template/META-INF/robovm/ios/robovm.xml new file mode 100644 index 0000000000..f94452a59f --- /dev/null +++ b/jme3-ios-native/template/META-INF/robovm/ios/robovm.xml @@ -0,0 +1,9 @@ + + + libs + + + jme3-ios-native + + + diff --git a/jme3-ios/build.gradle b/jme3-ios/build.gradle index bc93216107..9dd715218e 100644 --- a/jme3-ios/build.gradle +++ b/jme3-ios/build.gradle @@ -1,8 +1,3 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') - compile project(':jme3-plugins') + api project(':jme3-core') } diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java index 7812dc7482..0b807f5d98 100644 --- a/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosAL.java @@ -10,44 +10,64 @@ public final class IosAL implements AL { public IosAL() { } + @Override public native String alGetString(int parameter); + @Override public native int alGenSources(); + @Override public native int alGetError(); + @Override public native void alDeleteSources(int numSources, IntBuffer sources); + @Override public native void alGenBuffers(int numBuffers, IntBuffer buffers); + @Override public native void alDeleteBuffers(int numBuffers, IntBuffer buffers); + @Override public native void alSourceStop(int source); + @Override public native void alSourcei(int source, int param, int value); + @Override public native void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency); + @Override public native void alSourcePlay(int source); + @Override public native void alSourcePause(int source); + @Override public native void alSourcef(int source, int param, float value); + @Override public native void alSource3f(int source, int param, float value1, float value2, float value3); + @Override public native int alGetSourcei(int source, int param); + @Override public native void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers); + @Override public native void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers); + @Override public native void alListener(int param, FloatBuffer data); + @Override public native void alListenerf(int param, float value); + @Override public native void alListener3f(int param, float value1, float value2, float value3); + @Override public native void alSource3i(int source, int param, int value1, int value2, int value3); } diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java index f1579c9e53..775fd55813 100644 --- a/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosALC.java @@ -8,19 +8,27 @@ public final class IosALC implements ALC { public IosALC() { } + @Override public native void createALC(); + @Override public native void destroyALC(); + @Override public native boolean isCreated(); + @Override public native String alcGetString(int parameter); + @Override public native boolean alcIsExtensionPresent(String extension); + @Override public native void alcGetInteger(int param, IntBuffer buffer, int size); + @Override public native void alcDevicePauseSOFT(); + @Override public native void alcDeviceResumeSOFT(); } diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java index d7a569c1fd..e050626cec 100644 --- a/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/IosEFX.java @@ -8,25 +8,36 @@ public class IosEFX implements EFX { public IosEFX() { } + @Override public native void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers); + @Override public native void alGenEffects(int numEffects, IntBuffer buffers); + @Override public native void alEffecti(int effect, int param, int value); + @Override public native void alAuxiliaryEffectSloti(int effectSlot, int param, int value); + @Override public native void alDeleteEffects(int numEffects, IntBuffer buffers); + @Override public native void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers); + @Override public native void alGenFilters(int numFilters, IntBuffer buffers); + @Override public native void alFilteri(int filter, int param, int value); + @Override public native void alFilterf(int filter, int param, float value); + @Override public native void alDeleteFilters(int numFilters, IntBuffer buffers); + @Override public native void alEffectf(int effect, int param, float value); } diff --git a/jme3-ios/src/main/java/com/jme3/audio/ios/package-info.java b/jme3-ios/src/main/java/com/jme3/audio/ios/package-info.java new file mode 100644 index 0000000000..d241ac1ab0 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/audio/ios/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio support for devices running the iOS mobile operating system + */ +package com.jme3.audio.ios; diff --git a/jme3-ios/src/main/java/com/jme3/input/ios/IosInputHandler.java b/jme3-ios/src/main/java/com/jme3/input/ios/IosInputHandler.java index 725fe14541..25155bfb79 100644 --- a/jme3-ios/src/main/java/com/jme3/input/ios/IosInputHandler.java +++ b/jme3-ios/src/main/java/com/jme3/input/ios/IosInputHandler.java @@ -22,12 +22,11 @@ public class IosInputHandler implements TouchInput { private boolean mouseEventsInvertX = false; private boolean mouseEventsInvertY = false; private boolean keyboardEventsEnabled = false; - private boolean dontSendHistory = false; // Internal private boolean initialized = false; private RawInputListener listener = null; - private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue inputEventQueue = new ConcurrentLinkedQueue<>(); private final TouchEventPool touchEventPool = new TouchEventPool(MAX_TOUCH_EVENTS); private IosTouchHandler touchHandler; private float scaleX = 1f; @@ -49,8 +48,7 @@ public void initialize() { @Override public void update() { - logger.log(Level.FINE, "InputEvent update : {0}", - new Object[]{listener}); + logger.log(Level.FINE, "InputEvent update : {0}", listener); if (listener != null) { InputEvent inputEvent; @@ -107,13 +105,14 @@ public void setSimulateKeyboard(boolean simulate) { this.keyboardEventsEnabled = simulate; } + @Override public boolean isSimulateKeyboard() { return keyboardEventsEnabled; } @Override public void setOmitHistoricEvents(boolean dontSendHistory) { - this.dontSendHistory = dontSendHistory; + // not implemented } // ---------------- @@ -134,8 +133,10 @@ public void loadSettings(AppSettings settings) { scaleY = 1.0f; width = settings.getWidth(); height = settings.getHeight(); - logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", - new Object[]{scaleX, scaleY, width, height}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Setting input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", + new Object[]{scaleX, scaleY, width, height}); + } } public boolean isMouseEventsInvertX() { @@ -176,8 +177,10 @@ public void addEvent(InputEvent event) { // ---------------- public void injectTouchDown(int pointerId, long time, float x, float y) { - logger.log(Level.FINE, "Using input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", - new Object[]{scaleX, scaleY, width, height}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Using input scaling, scaleX: {0}, scaleY: {1}, width: {2}, height: {3}", + new Object[]{scaleX, scaleY, width, height}); + } if (touchHandler != null) { touchHandler.actionDown(pointerId, time, x, y); } diff --git a/jme3-ios/src/main/java/com/jme3/input/ios/IosTouchHandler.java b/jme3-ios/src/main/java/com/jme3/input/ios/IosTouchHandler.java index ea157e2506..28ba9681bf 100644 --- a/jme3-ios/src/main/java/com/jme3/input/ios/IosTouchHandler.java +++ b/jme3-ios/src/main/java/com/jme3/input/ios/IosTouchHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,18 +45,15 @@ import java.util.logging.Logger; /** - * AndroidTouchHandler is the base class that receives touch inputs from the - * Android system and creates the TouchEvents for jME. This class is designed - * to handle the base touch events for Android rev 9 (Android 2.3). This is - * extended by other classes to add features that were introducted after - * Android rev 9. + * IosTouchHandler is the base class that receives touch inputs from the + * iOS system and creates the TouchEvents for jME. * * @author iwgeric */ public class IosTouchHandler { private static final Logger logger = Logger.getLogger(IosTouchHandler.class.getName()); - final private HashMap lastPositions = new HashMap(); + final private HashMap lastPositions = new HashMap<>(); protected int numPointers = 1; @@ -73,8 +70,10 @@ public void destroy() { } public void actionDown(int pointerId, long time, float x, float y) { - logger.log(Level.FINE, "Inject input pointer: {0}, time: {1}, x: {2}, y: {3}", - new Object[]{pointerId, time, x, y}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Inject input pointer: {0}, time: {1}, x: {2}, y: {3}", + new Object[]{pointerId, time, x, y}); + } float jmeX = iosInput.getJmeX(x); float jmeY = iosInput.invertY(iosInput.getJmeY(y)); TouchEvent touch = iosInput.getFreeTouchEvent(); @@ -82,7 +81,7 @@ public void actionDown(int pointerId, long time, float x, float y) { touch.setPointerId(pointerId);//TODO: pointer ID touch.setTime(time); touch.setPressure(1.0f); - //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure + //touch.setPressure(event.getPressure(pointerIndex)); //TODO: pressure lastPositions.put(pointerId, new Vector2f(jmeX, jmeY)); @@ -97,7 +96,7 @@ public void actionUp(int pointerId, long time, float x, float y) { touch.setPointerId(pointerId);//TODO: pointer ID touch.setTime(time); touch.setPressure(1.0f); - //touch.setPressure(event.getPressure(pointerIndex)); //TODO: preassure + //touch.setPressure(event.getPressure(pointerIndex)); //TODO: pressure lastPositions.remove(pointerId); processEvent(touch); diff --git a/jme3-ios/src/main/java/com/jme3/input/ios/TouchEventPool.java b/jme3-ios/src/main/java/com/jme3/input/ios/TouchEventPool.java index f986db1551..a2deb0b085 100644 --- a/jme3-ios/src/main/java/com/jme3/input/ios/TouchEventPool.java +++ b/jme3-ios/src/main/java/com/jme3/input/ios/TouchEventPool.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ /** * TouchEventPool provides a RingBuffer of jME TouchEvents to help with garbage - * collection on Android. Each TouchEvent is stored in the RingBuffer and is + * collection on iOS. Each TouchEvent is stored in the RingBuffer and is * reused if the TouchEvent has been consumed. * * If a TouchEvent has not been consumed, it is placed back into the pool at the @@ -87,7 +87,7 @@ public TouchEvent getNextFreeEvent() { TouchEvent evt = null; int curSize = eventPool.size(); while (curSize > 0) { - evt = (TouchEvent)eventPool.pop(); + evt = eventPool.pop(); if (evt.isConsumed()) { break; } else { diff --git a/jme3-ios/src/main/java/com/jme3/input/ios/package-info.java b/jme3-ios/src/main/java/com/jme3/input/ios/package-info.java new file mode 100644 index 0000000000..fe6b63bde9 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/input/ios/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * user-input classes specific to the iOS mobile operating system + */ +package com.jme3.input.ios; diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java index 826ce82a2d..b65224d6eb 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/IosGL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,8 +33,11 @@ import com.jme3.renderer.RendererException; import com.jme3.renderer.opengl.GL; +import com.jme3.renderer.opengl.GL2; +import com.jme3.renderer.opengl.GLES_30; import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLFbo; +import com.jme3.util.BufferUtils; import java.nio.Buffer; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; @@ -43,14 +46,17 @@ import java.nio.ShortBuffer; /** - * Implements OpenGL ES 2.0 for iOS. + * Implements OpenGL ES 2.0 and 3.0 for iOS. * - * @author Kirill Vainer + * @author Kirill Vainer, Jesus Oliver */ -public class IosGL implements GL, GLExt, GLFbo { +public class IosGL implements GL, GL2, GLES_30, GLExt, GLFbo { + private final float[] tmpFloatArray = new float[16]; private final int[] temp_array = new int[16]; + private final IntBuffer tmpBuff = BufferUtils.createIntBuffer(1); + @Override public void resetStats() { } @@ -95,6 +101,15 @@ private int toArray(IntBuffer buffer) { return remain; } + private void fromArray(int n, float[] array, FloatBuffer buffer) { + if (buffer.remaining() < n) { + throw new BufferOverflowException(); + } + int pos = buffer.position(); + buffer.put(array, 0, n); + buffer.position(pos); + } + private void fromArray(int n, int[] array, IntBuffer buffer) { if (buffer.remaining() < n) { throw new BufferOverflowException(); @@ -116,175 +131,219 @@ private static void checkLimit(Buffer buffer) { } } + @Override public void glActiveTexture(int texture) { JmeIosGLES.glActiveTexture(texture); } + @Override public void glAttachShader(int program, int shader) { JmeIosGLES.glAttachShader(program, shader); } @Override public void glBeginQuery(int target, int query) { - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glBeginQuery(target, query); } + @Override public void glBindBuffer(int target, int buffer) { JmeIosGLES.glBindBuffer(target, buffer); } + @Override public void glBindTexture(int target, int texture) { JmeIosGLES.glBindTexture(target, texture); } - public void glBlendFunc(int sfactor, int dfactor) { - JmeIosGLES.glBlendFunc(sfactor, dfactor); + @Override + public void glBlendFunc(int sFactor, int dFactor) { + JmeIosGLES.glBlendFunc(sFactor, dFactor); } - public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) { - JmeIosGLES.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); + @Override + public void glBlendFuncSeparate(int sFactorRGB, int dFactorRGB, int sFactorAlpha, int dFactorAlpha) { + JmeIosGLES.glBlendFuncSeparate(sFactorRGB, dFactorRGB, sFactorAlpha, dFactorAlpha); } + @Override public void glBufferData(int target, FloatBuffer data, int usage) { JmeIosGLES.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferData(int target, ShortBuffer data, int usage) { JmeIosGLES.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferData(int target, ByteBuffer data, int usage) { JmeIosGLES.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferData(int target, long data_size, int usage) { JmeIosGLES.glBufferData(target, (int) data_size, null, usage); } + @Override public void glBufferSubData(int target, long offset, FloatBuffer data) { JmeIosGLES.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glBufferSubData(int target, long offset, ShortBuffer data) { JmeIosGLES.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glBufferSubData(int target, long offset, ByteBuffer data) { JmeIosGLES.glBufferSubData(target, (int) offset, getLimitBytes(data), data); } + @Override public void glGetBufferSubData(int target, long offset, ByteBuffer data) { throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); } + @Override + public void glGetBufferSubData(int target, long offset, IntBuffer data) { + throw new UnsupportedOperationException("OpenGL ES 2 does not support glGetBufferSubData"); + } + + @Override public void glClear(int mask) { JmeIosGLES.glClear(mask); } + @Override public void glClearColor(float red, float green, float blue, float alpha) { JmeIosGLES.glClearColor(red, green, blue, alpha); } + @Override public void glColorMask(boolean red, boolean green, boolean blue, boolean alpha) { JmeIosGLES.glColorMask(red, green, blue, alpha); } + @Override public void glCompileShader(int shader) { JmeIosGLES.glCompileShader(shader); } + @Override public void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, ByteBuffer data) { JmeIosGLES.glCompressedTexImage2D(target, level, internalformat, width, height, 0, getLimitBytes(data), data); } + @Override public void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, ByteBuffer data) { JmeIosGLES.glCompressedTexSubImage2D(target, level, xoffset, yoffset, width, height, format, getLimitBytes(data), data); } + @Override public int glCreateProgram() { return JmeIosGLES.glCreateProgram(); } + @Override public int glCreateShader(int shaderType) { return JmeIosGLES.glCreateShader(shaderType); } + @Override public void glCullFace(int mode) { JmeIosGLES.glCullFace(mode); } + @Override public void glDeleteBuffers(IntBuffer buffers) { checkLimit(buffers); int n = toArray(buffers); JmeIosGLES.glDeleteBuffers(n, temp_array, 0); } + @Override public void glDeleteProgram(int program) { JmeIosGLES.glDeleteProgram(program); } + @Override public void glDeleteShader(int shader) { JmeIosGLES.glDeleteShader(shader); } + @Override public void glDeleteTextures(IntBuffer textures) { checkLimit(textures); int n = toArray(textures); JmeIosGLES.glDeleteTextures(n, temp_array, 0); } + @Override public void glDepthFunc(int func) { JmeIosGLES.glDepthFunc(func); } + @Override public void glDepthMask(boolean flag) { JmeIosGLES.glDepthMask(flag); } + @Override public void glDepthRange(double nearVal, double farVal) { JmeIosGLES.glDepthRangef((float)nearVal, (float)farVal); } + @Override public void glDetachShader(int program, int shader) { JmeIosGLES.glDetachShader(program, shader); } + @Override public void glDisable(int cap) { JmeIosGLES.glDisable(cap); } + @Override public void glDisableVertexAttribArray(int index) { JmeIosGLES.glDisableVertexAttribArray(index); } + @Override public void glDrawArrays(int mode, int first, int count) { JmeIosGLES.glDrawArrays(mode, first, count); } + @Override public void glDrawRangeElements(int mode, int start, int end, int count, int type, long indices) { JmeIosGLES.glDrawElementsIndex(mode, count, type, (int)indices); } + @Override public void glEnable(int cap) { JmeIosGLES.glEnable(cap); } + @Override public void glEnableVertexAttribArray(int index) { JmeIosGLES.glEnableVertexAttribArray(index); } @Override public void glEndQuery(int target) { - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glEndQuery(target); } + @Override public void glGenBuffers(IntBuffer buffers) { checkLimit(buffers); JmeIosGLES.glGenBuffers(buffers.remaining(), temp_array, 0); fromArray(buffers.remaining(), temp_array, buffers); } + @Override public void glGenTextures(IntBuffer textures) { checkLimit(textures); JmeIosGLES.glGenTextures(textures.remaining(), temp_array, 0); @@ -293,100 +352,125 @@ public void glGenTextures(IntBuffer textures) { @Override public void glGenQueries(int num, IntBuffer buff) { - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glGenQueries(num, buff); } + @Override public int glGetAttribLocation(int program, String name) { return JmeIosGLES.glGetAttribLocation(program, name); } + @Override public void glGetBoolean(int pname, ByteBuffer params) { - // TODO: fix me!!! - // JmeIosGLES.glGetBoolean(pname, params); - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glGetBoolean(pname, params); } + @Override public int glGetError() { return JmeIosGLES.glGetError(); } + @Override + public void glGetFloat(int parameterId, FloatBuffer storeValues) { + checkLimit(storeValues); + JmeIosGLES.glGetFloatv(parameterId, tmpFloatArray, 0); + fromArray(storeValues.remaining(), tmpFloatArray, storeValues); + } + + @Override public void glGetInteger(int pname, IntBuffer params) { checkLimit(params); JmeIosGLES.glGetIntegerv(pname, temp_array, 0); fromArray(params.remaining(), temp_array, params); } + @Override public void glGetProgram(int program, int pname, IntBuffer params) { checkLimit(params); JmeIosGLES.glGetProgramiv(program, pname, temp_array, 0); fromArray(params.remaining(), temp_array, params); } + @Override public String glGetProgramInfoLog(int program, int maxLength) { return JmeIosGLES.glGetProgramInfoLog(program); } @Override public long glGetQueryObjectui64(int query, int pname) { - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glGetQueryObjectuiv(query, pname, temp_array); + return temp_array[0]; } @Override public int glGetQueryObjectiv(int query, int pname) { - throw new UnsupportedOperationException("Today is not a good day for this"); + JmeIosGLES.glGetQueryiv(query, pname, temp_array); + return temp_array[0]; } + @Override public void glGetShader(int shader, int pname, IntBuffer params) { checkLimit(params); JmeIosGLES.glGetShaderiv(shader, pname, temp_array, 0); fromArray(params.remaining(), temp_array, params); } + @Override public String glGetShaderInfoLog(int shader, int maxLength) { return JmeIosGLES.glGetShaderInfoLog(shader); } + @Override public String glGetString(int name) { return JmeIosGLES.glGetString(name); } + @Override public int glGetUniformLocation(int program, String name) { return JmeIosGLES.glGetUniformLocation(program, name); } + @Override public boolean glIsEnabled(int cap) { - // TODO: fix me!!! + // kept this always returning true for compatibility if (cap == GLExt.GL_MULTISAMPLE_ARB) { return true; } else { - throw new UnsupportedOperationException(); + return JmeIosGLES.glIsEnabled(cap); } } + @Override public void glLineWidth(float width) { JmeIosGLES.glLineWidth(width); } + @Override public void glLinkProgram(int program) { JmeIosGLES.glLinkProgram(program); } + @Override public void glPixelStorei(int pname, int param) { JmeIosGLES.glPixelStorei(pname, param); } + @Override public void glPolygonOffset(float factor, float units) { JmeIosGLES.glPolygonOffset(factor, units); } + @Override public void glReadPixels(int x, int y, int width, int height, int format, int type, ByteBuffer data) { JmeIosGLES.glReadPixels(x, y, width, height, format, type, data); } + @Override public void glScissor(int x, int y, int width, int height) { JmeIosGLES.glScissor(x, y, width, height); } + @Override public void glShaderSource(int shader, String[] string, IntBuffer length) { if (string.length != 1) { throw new UnsupportedOperationException("Today is not a good day"); @@ -394,199 +478,241 @@ public void glShaderSource(int shader, String[] string, IntBuffer length) { JmeIosGLES.glShaderSource(shader, string[0]); } + @Override public void glStencilFuncSeparate(int face, int func, int ref, int mask) { - // TODO: fix me!!! - // JmeIosGLES.glStencilFuncSeparate(face, func, ref, mask); + JmeIosGLES.glStencilFuncSeparate(face, func, ref, mask); } + @Override public void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass) { - // TODO: fix me!!! - // JmeIosGLES.glStencilOpSeparate(face, sfail, dpfail, dppass); + JmeIosGLES.glStencilOpSeparate(face, sfail, dpfail, dppass); } + @Override public void glTexImage2D(int target, int level, int internalFormat, int width, int height, int border, int format, int type, ByteBuffer data) { - JmeIosGLES.glTexImage2D(target, level, format, width, height, 0, format, type, data); + JmeIosGLES.glTexImage2D(target, level, internalFormat, width, height, 0, format, type, data); } + @Override public void glTexParameterf(int target, int pname, float param) { - // TODO: fix me!!! - // JmeIosGLES.glTexParameterf(target, pname, param); + JmeIosGLES.glTexParameterf(target, pname, param); } + @Override public void glTexParameteri(int target, int pname, int param) { JmeIosGLES.glTexParameteri(target, pname, param); } + @Override public void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, ByteBuffer data) { JmeIosGLES.glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data); } + @Override public void glUniform1(int location, FloatBuffer value) { JmeIosGLES.glUniform1fv(location, getLimitCount(value, 1), value); } + @Override public void glUniform1(int location, IntBuffer value) { JmeIosGLES.glUniform1iv(location, getLimitCount(value, 1), value); } + @Override public void glUniform1f(int location, float v0) { JmeIosGLES.glUniform1f(location, v0); } + @Override public void glUniform1i(int location, int v0) { JmeIosGLES.glUniform1i(location, v0); } + @Override public void glUniform2(int location, IntBuffer value) { // TODO: fix me!!! // JmeIosGLES.glUniform2iv(location, getLimitCount(value, 2), value); throw new UnsupportedOperationException(); } + @Override public void glUniform2(int location, FloatBuffer value) { JmeIosGLES.glUniform2fv(location, getLimitCount(value, 2), value); } + @Override public void glUniform2f(int location, float v0, float v1) { JmeIosGLES.glUniform2f(location, v0, v1); } + @Override public void glUniform3(int location, IntBuffer value) { // TODO: fix me!!! // JmeIosGLES.glUniform3iv(location, getLimitCount(value, 3), value); throw new UnsupportedOperationException(); } + @Override public void glUniform3(int location, FloatBuffer value) { JmeIosGLES.glUniform3fv(location, getLimitCount(value, 3), value); } + @Override public void glUniform3f(int location, float v0, float v1, float v2) { JmeIosGLES.glUniform3f(location, v0, v1, v2); } + @Override public void glUniform4(int location, FloatBuffer value) { JmeIosGLES.glUniform4fv(location, getLimitCount(value, 4), value); } + @Override public void glUniform4(int location, IntBuffer value) { // TODO: fix me!!! // JmeIosGLES.glUniform4iv(location, getLimitCount(value, 4), value); throw new UnsupportedOperationException(); } + @Override public void glUniform4f(int location, float v0, float v1, float v2, float v3) { JmeIosGLES.glUniform4f(location, v0, v1, v2, v3); } + @Override public void glUniformMatrix3(int location, boolean transpose, FloatBuffer value) { JmeIosGLES.glUniformMatrix3fv(location, getLimitCount(value, 3 * 3), transpose, value); } + @Override public void glUniformMatrix4(int location, boolean transpose, FloatBuffer value) { JmeIosGLES.glUniformMatrix4fv(location, getLimitCount(value, 4 * 4), transpose, value); } + @Override public void glUseProgram(int program) { JmeIosGLES.glUseProgram(program); } + @Override public void glVertexAttribPointer(int index, int size, int type, boolean normalized, int stride, long pointer) { JmeIosGLES.glVertexAttribPointer2(index, size, type, normalized, stride, (int)pointer); } + @Override public void glViewport(int x, int y, int width, int height) { JmeIosGLES.glViewport(x, y, width, height); } + @Override public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - throw new UnsupportedOperationException("FBO blit not available on iOS"); + JmeIosGLES.glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); } + @Override public void glBufferData(int target, IntBuffer data, int usage) { JmeIosGLES.glBufferData(target, getLimitBytes(data), data, usage); } + @Override public void glBufferSubData(int target, long offset, IntBuffer data) { JmeIosGLES.glBufferSubData(target, (int)offset, getLimitBytes(data), data); } + @Override public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { - throw new UnsupportedOperationException("Instancing not available on iOS"); + JmeIosGLES.glDrawArraysInstanced(mode, first, count, primcount); } + @Override public void glDrawBuffers(IntBuffer bufs) { - throw new UnsupportedOperationException("MRT not available on iOS"); + JmeIosGLES.glDrawBuffers(getLimitBytes(bufs), bufs); } + @Override public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { - throw new UnsupportedOperationException("Instancing not available on iOS"); + JmeIosGLES.glDrawElementsInstanced(mode, indices_count, type, indices_buffer_offset, primcount); } + @Override public void glGetMultisample(int pname, int index, FloatBuffer val) { throw new UnsupportedOperationException("Multisample renderbuffers not available on iOS"); } + @Override public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { throw new UnsupportedOperationException("Multisample renderbuffers not available on iOS"); } - public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { + @Override + public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedSampleLocations) { throw new UnsupportedOperationException("Multisample textures not available on iOS"); } + @Override public void glVertexAttribDivisorARB(int index, int divisor) { - throw new UnsupportedOperationException("Instancing not available on iOS"); + JmeIosGLES.glVertexAttribDivisor(index, divisor); } + @Override public void glBindFramebufferEXT(int param1, int param2) { JmeIosGLES.glBindFramebuffer(param1, param2); } + @Override public void glBindRenderbufferEXT(int param1, int param2) { JmeIosGLES.glBindRenderbuffer(param1, param2); } + @Override public int glCheckFramebufferStatusEXT(int param1) { return JmeIosGLES.glCheckFramebufferStatus(param1); } + @Override public void glDeleteFramebuffersEXT(IntBuffer param1) { checkLimit(param1); int n = toArray(param1); JmeIosGLES.glDeleteFramebuffers(n, temp_array, 0); } + @Override public void glDeleteRenderbuffersEXT(IntBuffer param1) { checkLimit(param1); int n = toArray(param1); JmeIosGLES.glDeleteRenderbuffers(n, temp_array, 0); } + @Override public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { JmeIosGLES.glFramebufferRenderbuffer(param1, param2, param3, param4); } + @Override public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { JmeIosGLES.glFramebufferTexture2D(param1, param2, param3, param4, param5); } + @Override public void glGenFramebuffersEXT(IntBuffer param1) { checkLimit(param1); JmeIosGLES.glGenFramebuffers(param1.remaining(), temp_array, 0); fromArray(param1.remaining(), temp_array, param1); } + @Override public void glGenRenderbuffersEXT(IntBuffer param1) { checkLimit(param1); JmeIosGLES.glGenRenderbuffers(param1.remaining(), temp_array, 0); fromArray(param1.remaining(), temp_array, param1); } + @Override public void glGenerateMipmapEXT(int param1) { JmeIosGLES.glGenerateMipmap(param1); } + @Override public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { JmeIosGLES.glRenderbufferStorage(param1, param2, param3, param4); } @@ -614,6 +740,74 @@ public Object glFenceSync(int condition, int flags) { @Override public void glFramebufferTextureLayerEXT(int target, int attachment, int texture, int level, int layer) { - throw new UnsupportedOperationException("OpenGL ES 2 does not support texture arrays"); + JmeIosGLES.glFramebufferTextureLayer(target, attachment, texture, level, layer); + } + + // New methods from GL2 interface which are supported in GLES30 + @Override + public void glAlphaFunc(int func, float ref) { + } + + @Override + public void glPointSize(float size) { + } + + @Override + public void glPolygonMode(int face, int mode) { + } + + // Wrapper to DrawBuffers as there's no DrawBuffer method in GLES + @Override + public void glDrawBuffer(int mode) { + ((Buffer)tmpBuff).clear(); + tmpBuff.put(0, mode); + tmpBuff.rewind(); + glDrawBuffers(tmpBuff); + } + + @Override + public void glReadBuffer(int mode) { + JmeIosGLES.glReadBuffer(mode); + } + + @Override + public void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, + int border, ByteBuffer data) { + JmeIosGLES.glCompressedTexImage3D(target, level, internalFormat, width, height, depth, border, getLimitBytes(data), data); } + + @Override + public void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, + int height, int depth, int format, ByteBuffer data) { + JmeIosGLES.glCompressedTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, getLimitBytes(data), data); + } + + @Override + public void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, + int format, int type, ByteBuffer data) { + JmeIosGLES.glTexImage3D(target, level, internalFormat, width, height, depth, border, format, type, data); + } + + @Override + public void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, + int depth, int format, int type, ByteBuffer data) { + JmeIosGLES.glTexSubImage3D(target, level, xoffset, yoffset, zoffset, width, height, depth, format, type, data); + } + + @Override + public void glBindVertexArray(int array) { + throw new UnsupportedOperationException("Unimplemented method 'glBindVertexArray'"); + } + + @Override + public void glDeleteVertexArrays(IntBuffer arrays) { + throw new UnsupportedOperationException("Unimplemented method 'glDeleteVertexArrays'"); + } + + @Override + public void glGenVertexArrays(IntBuffer arrays) { + throw new UnsupportedOperationException("Unimplemented method 'glGenVertexArrays'"); + } + + } diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/JmeIosGLES.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/JmeIosGLES.java index 2a2eee5b5a..a0a19d0a08 100644 --- a/jme3-ios/src/main/java/com/jme3/renderer/ios/JmeIosGLES.java +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/JmeIosGLES.java @@ -3,236 +3,273 @@ import com.jme3.renderer.RendererException; import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.util.logging.Logger; /** - * The iOS GLES interface iOS alternative to Android's GLES20 class + * The iOS GLES interface iOS alternative to Android's GLES20 and GLES30 classes * - * @author Kostyantyn Hushchyn + * @author Kostyantyn Hushchyn, Jesus Oliver */ public class JmeIosGLES { private static final Logger logger = Logger.getLogger(JmeIosGLES.class.getName()); - private static boolean ENABLE_ERROR_CHECKING = true; + private static boolean ENABLE_ERROR_CHECKING = true; public static final int GL_ALPHA = 0x00001906; public static final int GL_ALWAYS = 0x00000207; - public static final int GL_ARRAY_BUFFER = 0x00008892; - public static final int GL_BACK = 0x00000405; - public static final int GL_BLEND = 0x00000be2; - public static final int GL_BYTE = 0x00001400; - public static final int GL_CLAMP_TO_EDGE = 0x0000812f; - public static final int GL_COLOR_ATTACHMENT0 = 0x00008ce0; - public static final int GL_COLOR_BUFFER_BIT = 0x00004000; - public static final int GL_COMPILE_STATUS = 0x00008b81; - public static final int GL_COMPRESSED_TEXTURE_FORMATS = 0x000086a3; - public static final int GL_CULL_FACE = 0x00000b44; - public static final int GL_DEPTH_ATTACHMENT = 0x00008d00; - public static final int GL_DEPTH_BUFFER_BIT = 0x00000100; + public static final int GL_ARRAY_BUFFER = 0x00008892; + public static final int GL_BACK = 0x00000405; + public static final int GL_BLEND = 0x00000be2; + public static final int GL_BYTE = 0x00001400; + public static final int GL_CLAMP_TO_EDGE = 0x0000812f; + public static final int GL_COLOR_ATTACHMENT0 = 0x00008ce0; + public static final int GL_COLOR_BUFFER_BIT = 0x00004000; + public static final int GL_COMPILE_STATUS = 0x00008b81; + public static final int GL_COMPRESSED_TEXTURE_FORMATS = 0x000086a3; + public static final int GL_CULL_FACE = 0x00000b44; + public static final int GL_DEPTH_ATTACHMENT = 0x00008d00; + public static final int GL_DEPTH_BUFFER_BIT = 0x00000100; public static final int GL_DEPTH_COMPONENT = 0x00001902; public static final int GL_DEPTH_COMPONENT16 = 0x000081a5; - public static final int GL_DEPTH_TEST = 0x00000b71; - public static final int GL_DITHER = 0x00000bd0; - public static final int GL_DST_COLOR = 0x00000306; - public static final int GL_DYNAMIC_DRAW = 0x000088e8; - public static final int GL_EQUAL = 0x00000202; - public static final int GL_ELEMENT_ARRAY_BUFFER = 0x00008893; - public static final int GL_EXTENSIONS = 0x00001f03; - public static final int GL_FALSE = 0x00000000; - public static final int GL_FLOAT = 0x00001406; - public static final int GL_FRAGMENT_SHADER = 0x00008b30; - public static final int GL_FRAMEBUFFER = 0x00008d40; - public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x00008cd1; - public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x00008cd0; - public static final int GL_FRAMEBUFFER_COMPLETE = 0x00008cd5; - public static final int GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x00008cd6; - public static final int GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x00008cd9; - public static final int GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x00008cd7; - public static final int GL_FRAMEBUFFER_UNSUPPORTED = 0x00008cdd; - public static final int GL_FRONT = 0x00000404; - public static final int GL_FRONT_AND_BACK = 0x00000408; - public static final int GL_GEQUAL = 0x00000206; - public static final int GL_GREATER = 0x00000204; - public static final int GL_HIGH_FLOAT = 0x00008df2; - public static final int GL_INFO_LOG_LENGTH = 0x00008b84; - public static final int GL_INT = 0x00001404; - public static final int GL_LEQUAL = 0x00000203; - public static final int GL_LESS = 0x00000201; - public static final int GL_LINEAR = 0x00002601; - public static final int GL_LINEAR_MIPMAP_LINEAR = 0x00002703; - public static final int GL_LINEAR_MIPMAP_NEAREST = 0x00002701; - public static final int GL_LINES = 0x00000001; - public static final int GL_LINE_LOOP = 0x00000002; - public static final int GL_LINE_STRIP = 0x00000003; - public static final int GL_LINK_STATUS = 0x00008b82; - public static final int GL_LUMINANCE = 0x00001909; - public static final int GL_LUMINANCE_ALPHA = 0x0000190a; - public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x0000851c; - public static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x00008dfd; - public static final int GL_MAX_RENDERBUFFER_SIZE = 0x000084e8; - public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x00008872; - public static final int GL_MAX_TEXTURE_SIZE = 0x00000d33; - public static final int GL_MAX_VARYING_VECTORS = 0x00008dfc; - public static final int GL_MAX_VERTEX_ATTRIBS = 0x00008869; - public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x00008b4c; - public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x00008dfb; - public static final int GL_MIRRORED_REPEAT = 0x00008370; - public static final int GL_NEAREST = 0x00002600; - public static final int GL_NEAREST_MIPMAP_LINEAR = 0x00002702; - public static final int GL_NEAREST_MIPMAP_NEAREST = 0x00002700; - public static final int GL_NEVER = 0x00000200; - public static final int GL_NONE = 0x00000000; - public static final int GL_NOTEQUAL = 0x00000205; - public static final int GL_NUM_COMPRESSED_TEXTURE_FORMATS = 0x000086a2; - public static final int GL_ONE = 0x00000001; - public static final int GL_ONE_MINUS_SRC_ALPHA = 0x00000303; - public static final int GL_ONE_MINUS_SRC_COLOR = 0x00000301; - public static final int GL_POINTS = 0x00000000; - public static final int GL_POLYGON_OFFSET_FILL = 0x00008037; - public static final int GL_RENDERBUFFER = 0x00008d41; - public static final int GL_RENDERER = 0x00001f01; - public static final int GL_REPEAT = 0x00002901; + public static final int GL_DEPTH_TEST = 0x00000b71; + public static final int GL_DITHER = 0x00000bd0; + public static final int GL_DST_COLOR = 0x00000306; + public static final int GL_DYNAMIC_DRAW = 0x000088e8; + public static final int GL_EQUAL = 0x00000202; + public static final int GL_ELEMENT_ARRAY_BUFFER = 0x00008893; + public static final int GL_EXTENSIONS = 0x00001f03; + public static final int GL_FALSE = 0x00000000; + public static final int GL_FLOAT = 0x00001406; + public static final int GL_FRAGMENT_SHADER = 0x00008b30; + public static final int GL_FRAMEBUFFER = 0x00008d40; + public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME = 0x00008cd1; + public static final int GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE = 0x00008cd0; + public static final int GL_FRAMEBUFFER_COMPLETE = 0x00008cd5; + public static final int GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT = 0x00008cd6; + public static final int GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS = 0x00008cd9; + public static final int GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT = 0x00008cd7; + public static final int GL_FRAMEBUFFER_UNSUPPORTED = 0x00008cdd; + public static final int GL_FRONT = 0x00000404; + public static final int GL_FRONT_AND_BACK = 0x00000408; + public static final int GL_GEQUAL = 0x00000206; + public static final int GL_GREATER = 0x00000204; + public static final int GL_HIGH_FLOAT = 0x00008df2; + public static final int GL_INFO_LOG_LENGTH = 0x00008b84; + public static final int GL_INT = 0x00001404; + public static final int GL_LEQUAL = 0x00000203; + public static final int GL_LESS = 0x00000201; + public static final int GL_LINEAR = 0x00002601; + public static final int GL_LINEAR_MIPMAP_LINEAR = 0x00002703; + public static final int GL_LINEAR_MIPMAP_NEAREST = 0x00002701; + public static final int GL_LINES = 0x00000001; + public static final int GL_LINE_LOOP = 0x00000002; + public static final int GL_LINE_STRIP = 0x00000003; + public static final int GL_LINK_STATUS = 0x00008b82; + public static final int GL_LUMINANCE = 0x00001909; + public static final int GL_LUMINANCE_ALPHA = 0x0000190a; + public static final int GL_MAX_CUBE_MAP_TEXTURE_SIZE = 0x0000851c; + public static final int GL_MAX_FRAGMENT_UNIFORM_VECTORS = 0x00008dfd; + public static final int GL_MAX_RENDERBUFFER_SIZE = 0x000084e8; + public static final int GL_MAX_TEXTURE_IMAGE_UNITS = 0x00008872; + public static final int GL_MAX_TEXTURE_SIZE = 0x00000d33; + public static final int GL_MAX_VARYING_VECTORS = 0x00008dfc; + public static final int GL_MAX_VERTEX_ATTRIBS = 0x00008869; + public static final int GL_MAX_VERTEX_TEXTURE_IMAGE_UNITS = 0x00008b4c; + public static final int GL_MAX_VERTEX_UNIFORM_VECTORS = 0x00008dfb; + public static final int GL_MIRRORED_REPEAT = 0x00008370; + public static final int GL_NEAREST = 0x00002600; + public static final int GL_NEAREST_MIPMAP_LINEAR = 0x00002702; + public static final int GL_NEAREST_MIPMAP_NEAREST = 0x00002700; + public static final int GL_NEVER = 0x00000200; + public static final int GL_NONE = 0x00000000; + public static final int GL_NOTEQUAL = 0x00000205; + public static final int GL_NUM_COMPRESSED_TEXTURE_FORMATS = 0x000086a2; + public static final int GL_ONE = 0x00000001; + public static final int GL_ONE_MINUS_SRC_ALPHA = 0x00000303; + public static final int GL_ONE_MINUS_SRC_COLOR = 0x00000301; + public static final int GL_POINTS = 0x00000000; + public static final int GL_POLYGON_OFFSET_FILL = 0x00008037; + public static final int GL_RENDERBUFFER = 0x00008d41; + public static final int GL_RENDERER = 0x00001f01; + public static final int GL_REPEAT = 0x00002901; public static final int GL_RGB = 0x00001907; - public static final int GL_RGB565 = 0x00008d62; - public static final int GL_RGB5_A1 = 0x00008057; - public static final int GL_RGBA = 0x00001908; + public static final int GL_RGB565 = 0x00008d62; + public static final int GL_RGB5_A1 = 0x00008057; + public static final int GL_RGBA = 0x00001908; public static final int GL_RGBA4 = 0x00008056; - public static final int GL_SAMPLE_ALPHA_TO_COVERAGE = 0x0000809e; - public static final int GL_SCISSOR_TEST = 0x00000c11; - public static final int GL_SHADING_LANGUAGE_VERSION = 0x00008b8c; - public static final int GL_SHORT = 0x00001402; - public static final int GL_SRC_COLOR = 0x00000300; - public static final int GL_SRC_ALPHA = 0x00000302; - public static final int GL_STATIC_DRAW = 0x000088e4; - public static final int GL_STENCIL_BUFFER_BIT = 0x00000400; - public static final int GL_STREAM_DRAW = 0x000088e0; - public static final int GL_SUBPIXEL_BITS = 0x00000d50; - public static final int GL_TEXTURE = 0x00001702; - public static final int GL_TEXTURE0 = 0x000084c0; - public static final int GL_TEXTURE_2D = 0x00000de1; - public static final int GL_TEXTURE_CUBE_MAP = 0x00008513; - public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x00008515; - public static final int GL_TEXTURE_MAG_FILTER = 0x00002800; - public static final int GL_TEXTURE_MIN_FILTER = 0x00002801; - public static final int GL_TEXTURE_WRAP_S = 0x00002802; - public static final int GL_TEXTURE_WRAP_T = 0x00002803; - public static final int GL_TRIANGLES = 0x00000004; - public static final int GL_TRIANGLE_FAN = 0x00000006; - public static final int GL_TRIANGLE_STRIP = 0x00000005; - public static final int GL_TRUE = 0x00000001; - public static final int GL_UNPACK_ALIGNMENT = 0x00000cf5; - public static final int GL_UNSIGNED_BYTE = 0x00001401; - public static final int GL_UNSIGNED_INT = 0x00001405; - public static final int GL_UNSIGNED_SHORT = 0x00001403; + public static final int GL_SAMPLE_ALPHA_TO_COVERAGE = 0x0000809e; + public static final int GL_SCISSOR_TEST = 0x00000c11; + public static final int GL_SHADING_LANGUAGE_VERSION = 0x00008b8c; + public static final int GL_SHORT = 0x00001402; + public static final int GL_SRC_COLOR = 0x00000300; + public static final int GL_SRC_ALPHA = 0x00000302; + public static final int GL_STATIC_DRAW = 0x000088e4; + public static final int GL_STENCIL_BUFFER_BIT = 0x00000400; + public static final int GL_STREAM_DRAW = 0x000088e0; + public static final int GL_SUBPIXEL_BITS = 0x00000d50; + public static final int GL_TEXTURE = 0x00001702; + public static final int GL_TEXTURE0 = 0x000084c0; + public static final int GL_TEXTURE_2D = 0x00000de1; + public static final int GL_TEXTURE_CUBE_MAP = 0x00008513; + public static final int GL_TEXTURE_CUBE_MAP_POSITIVE_X = 0x00008515; + public static final int GL_TEXTURE_MAG_FILTER = 0x00002800; + public static final int GL_TEXTURE_MIN_FILTER = 0x00002801; + public static final int GL_TEXTURE_WRAP_S = 0x00002802; + public static final int GL_TEXTURE_WRAP_T = 0x00002803; + public static final int GL_TRIANGLES = 0x00000004; + public static final int GL_TRIANGLE_FAN = 0x00000006; + public static final int GL_TRIANGLE_STRIP = 0x00000005; + public static final int GL_TRUE = 0x00000001; + public static final int GL_UNPACK_ALIGNMENT = 0x00000cf5; + public static final int GL_UNSIGNED_BYTE = 0x00001401; + public static final int GL_UNSIGNED_INT = 0x00001405; + public static final int GL_UNSIGNED_SHORT = 0x00001403; public static final int GL_UNSIGNED_SHORT_4_4_4_4 = 0x00008033; public static final int GL_UNSIGNED_SHORT_5_5_5_1 = 0x00008034; public static final int GL_UNSIGNED_SHORT_5_6_5 = 0x00008363; - public static final int GL_VENDOR = 0x00001f00; - public static final int GL_VERSION = 0x00001f02; - public static final int GL_VERTEX_SHADER = 0x00008b31; - public static final int GL_ZERO = 0x00000000; + public static final int GL_VENDOR = 0x00001f00; + public static final int GL_VERSION = 0x00001f02; + public static final int GL_VERTEX_SHADER = 0x00008b31; + public static final int GL_ZERO = 0x00000000; - public static native void glActiveTexture(int texture); - public static native void glAttachShader(int program, int shader); - public static native void glBindBuffer(int target, int buffer); - public static native void glBindFramebuffer(int target, int framebuffer); - public static native void glBindRenderbuffer(int target, int renderbuffer); - public static native void glBindTexture(int target, int texture); -// public static native void glBindVertexArray // TODO: Investigate this + /** + * A private constructor to inhibit instantiation of this class. + */ + private JmeIosGLES() { + } + + public static native void glActiveTexture(int texture); + public static native void glAttachShader(int program, int shader); + public static native void glBindBuffer(int target, int buffer); + public static native void glBindFramebuffer(int target, int framebuffer); + public static native void glBindRenderbuffer(int target, int renderbuffer); + public static native void glBindTexture(int target, int texture); +// public static native void glBindVertexArray // TODO: Investigate this public static native void glBlendEquationSeparate(int colorMode, int alphaMode); - public static native void glBlendFunc(int sfactor, int dfactor); - public static native void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha); - public static native void glBufferData(int target, int size, Buffer data, int usage); - public static native void glBufferData2(int target, int size, byte[] data, int offset, int usage); - public static native void glBufferSubData(int target, int offset, int size, Buffer data); - public static native void glBufferSubData2(int target, int offset, int size, byte[] data, int dataoffset); - public static native int glCheckFramebufferStatus(int target); - public static native void glClear(int mask); - public static native void glClearColor(float red, float green, float blue, float alpha); - public static native void glColorMask(boolean red, boolean green, boolean blue, boolean alpha); - public static native void glCompileShader(int shader); - public static native void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, int imageSize, Buffer pixels); - public static native void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int imageSize, Buffer pixels); - public static native int glCreateProgram(); - public static native int glCreateShader(int type); - public static native void glCullFace(int mode); - public static native void glDeleteBuffers(int n, int[] buffers, int offset); - public static native void glDeleteFramebuffers(int n, int[] framebuffers, int offset); - public static native void glDeleteProgram(int program); - public static native void glDeleteRenderbuffers(int n, int[] renderbuffers, int offset); - public static native void glDeleteShader(int shader); - public static native void glDeleteTextures(int n, int[] textures, int offset); - public static native void glDepthFunc(int func); - public static native void glDepthMask(boolean flag); - public static native void glDepthRangef(float zNear, float zFar); - public static native void glDetachShader(int program, int shader); - public static native void glDisableVertexAttribArray(int index); - public static native void glDisable(int cap); - public static native void glDrawArrays(int mode, int first, int count); - public static native void glDrawElements(int mode, int count, int type, Buffer indices); - public static native void glDrawElements2(int mode, int count, int type, byte[] indices, int offset); - public static native void glDrawElementsIndex(int mode, int count, int type, int offset); - public static native void glEnable(int cap); - public static native void glEnableVertexAttribArray(int index); - public static native void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer); - public static native void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level); - public static native void glGenBuffers(int n, int[] buffers, int offset); - public static native void glGenFramebuffers(int n, int[] framebuffers, int offset); - public static native void glGenRenderbuffers(int n, int[] renderbuffers, int offset); - public static native void glGenTextures(int n, int[] textures, int offset); - public static native void glGenerateMipmap(int target); - public static native int glGetAttribLocation(int program, String name); - public static native int glGetError(); - public static native void glGetFramebufferAttachmentParameteriv(int target, int attachment, int pname, int[] params, int offset); - public static native void glGetIntegerv (int pname, int[] params, int offset); - public static native String glGetProgramInfoLog(int program); - public static native void glGetProgramiv(int program, int pname, int[] params, int offset); - public static native String glGetShaderInfoLog(int shader); - public static native void glGetShaderiv(int shader, int pname, int[] params, int offset); - public static native String glGetString(int name); - public static native int glGetUniformLocation(int program, String name); - public static native boolean glIsFramebuffer(int framebuffer); - public static native boolean glIsRenderbuffer(int renderbuffer); - public static native void glLineWidth(float width); - public static native void glLinkProgram(int program); - public static native void glPixelStorei(int pname, int param); - public static native void glPolygonOffset(float factor, float units); - public static native void glReadPixels(int vpX, int vpY, int vpW, int vpH, int format, int type, Buffer pixels); - public static native void glReadPixels2(int vpX, int vpY, int vpW, int vpH, int format, int type, byte[] pixels, int offset, int size); - public static native void glRenderbufferStorage(int target, int internalformat, int width, int height); - public static native void glScissor(int x, int y, int width, int height); - public static native void glShaderSource(int shader, String string); - public static native void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, Buffer pixels); - public static native void glTexParameteri(int target, int pname, int param); - public static native void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, Buffer pixels); - public static native void glUniform1f(int location, float x); - public static native void glUniform1fv(int location, int count, FloatBuffer v); - public static native void glUniform1fv2(int location, int count, float[] v, int offset); - public static native void glUniform1i(int location, int x); - public static native void glUniform1iv(int location, int count, IntBuffer v); - public static native void glUniform1iv2(int location, int count, int[] v, int offset); - public static native void glUniform2f(int location, float x, float y); - public static native void glUniform2fv(int location, int count, FloatBuffer v); - public static native void glUniform2fv2(int location, int count, float[] v, int offset); - public static native void glUniform3f(int location, float x, float y, float z); - public static native void glUniform3fv(int location, int count, FloatBuffer v); - public static native void glUniform3fv2(int location, int count, float[] v, int offset); - public static native void glUniform4f(int location, float x, float y, float z, float w); - public static native void glUniform4fv(int location, int count, FloatBuffer v); - public static native void glUniform4fv2(int location, int count, float[] v, int offset); - public static native void glUniformMatrix3fv(int location, int count, boolean transpose, FloatBuffer value); - public static native void glUniformMatrix3fv2(int location, int count, boolean transpose, float[] value, int offset); - public static native void glUniformMatrix4fv(int location, int count, boolean transpose, FloatBuffer value); - public static native void glUniformMatrix4fv2(int location, int count, boolean transpose, float[] value, int offset); - public static native void glUseProgram(int program); - //public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, byte[] ptr, int offset); - public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, Buffer ptr); - public static native void glVertexAttribPointer2(int indx, int size, int type, boolean normalized, int stride, int offset); - public static native void glViewport(int x, int y, int width, int height); - - + public static native void glBlendFunc(int sfactor, int dfactor); + public static native void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha); + public static native void glBufferData(int target, int size, Buffer data, int usage); + public static native void glBufferData2(int target, int size, byte[] data, int offset, int usage); + public static native void glBufferSubData(int target, int offset, int size, Buffer data); + public static native void glBufferSubData2(int target, int offset, int size, byte[] data, int dataoffset); + public static native int glCheckFramebufferStatus(int target); + public static native void glClear(int mask); + public static native void glClearColor(float red, float green, float blue, float alpha); + public static native void glColorMask(boolean red, boolean green, boolean blue, boolean alpha); + public static native void glCompileShader(int shader); + public static native void glCompressedTexImage2D(int target, int level, int internalformat, int width, int height, int border, int imageSize, Buffer pixels); + public static native void glCompressedTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int imageSize, Buffer pixels); + public static native int glCreateProgram(); + public static native int glCreateShader(int type); + public static native void glCullFace(int mode); + public static native void glDeleteBuffers(int n, int[] buffers, int offset); + public static native void glDeleteFramebuffers(int n, int[] framebuffers, int offset); + public static native void glDeleteProgram(int program); + public static native void glDeleteRenderbuffers(int n, int[] renderbuffers, int offset); + public static native void glDeleteShader(int shader); + public static native void glDeleteTextures(int n, int[] textures, int offset); + public static native void glDepthFunc(int func); + public static native void glDepthMask(boolean flag); + public static native void glDepthRangef(float zNear, float zFar); + public static native void glDetachShader(int program, int shader); + public static native void glDisableVertexAttribArray(int index); + public static native void glDisable(int cap); + public static native void glDrawArrays(int mode, int first, int count); + public static native void glDrawElements(int mode, int count, int type, Buffer indices); + public static native void glDrawElements2(int mode, int count, int type, byte[] indices, int offset); + public static native void glDrawElementsIndex(int mode, int count, int type, int offset); + public static native void glEnable(int cap); + public static native void glEnableVertexAttribArray(int index); + public static native void glFramebufferRenderbuffer(int target, int attachment, int renderbuffertarget, int renderbuffer); + public static native void glFramebufferTexture2D(int target, int attachment, int textarget, int texture, int level); + public static native void glGenBuffers(int n, int[] buffers, int offset); + public static native void glGenFramebuffers(int n, int[] framebuffers, int offset); + public static native void glGenRenderbuffers(int n, int[] renderbuffers, int offset); + public static native void glGenTextures(int n, int[] textures, int offset); + public static native void glGenerateMipmap(int target); + public static native int glGetAttribLocation(int program, String name); + public static native void glGetBoolean(int pname, ByteBuffer params); + public static native int glGetError(); + public static native void glGetFloatv (int pname, float[] params, int offset); + public static native void glGetFramebufferAttachmentParameteriv(int target, int attachment, int pname, int[] params, int offset); + public static native void glGetIntegerv (int pname, int[] params, int offset); + public static native String glGetProgramInfoLog(int program); + public static native void glGetProgramiv(int program, int pname, int[] params, int offset); + public static native String glGetShaderInfoLog(int shader); + public static native void glGetShaderiv(int shader, int pname, int[] params, int offset); + public static native String glGetString(int name); + public static native int glGetUniformLocation(int program, String name); + public static native boolean glIsEnabled(int cap); + public static native boolean glIsFramebuffer(int framebuffer); + public static native boolean glIsRenderbuffer(int renderbuffer); + public static native void glLineWidth(float width); + public static native void glLinkProgram(int program); + public static native void glPixelStorei(int pname, int param); + public static native void glPolygonOffset(float factor, float units); + public static native void glReadPixels(int vpX, int vpY, int vpW, int vpH, int format, int type, Buffer pixels); + public static native void glReadPixels2(int vpX, int vpY, int vpW, int vpH, int format, int type, byte[] pixels, int offset, int size); + public static native void glRenderbufferStorage(int target, int internalformat, int width, int height); + public static native void glScissor(int x, int y, int width, int height); + public static native void glShaderSource(int shader, String string); + public static native void glStencilFuncSeparate(int face, int func, int ref, int mask); + public static native void glStencilOpSeparate(int face, int sfail, int dpfail, int dppass); + public static native void glTexImage2D(int target, int level, int internalformat, int width, int height, int border, int format, int type, Buffer pixels); + public static native void glTexParameteri(int target, int pname, int param); + public static native void glTexParameterf(int target, int pname, float param); + public static native void glTexSubImage2D(int target, int level, int xoffset, int yoffset, int width, int height, int format, int type, Buffer pixels); + public static native void glUniform1f(int location, float x); + public static native void glUniform1fv(int location, int count, FloatBuffer v); + public static native void glUniform1fv2(int location, int count, float[] v, int offset); + public static native void glUniform1i(int location, int x); + public static native void glUniform1iv(int location, int count, IntBuffer v); + public static native void glUniform1iv2(int location, int count, int[] v, int offset); + public static native void glUniform2f(int location, float x, float y); + public static native void glUniform2fv(int location, int count, FloatBuffer v); + public static native void glUniform2fv2(int location, int count, float[] v, int offset); + public static native void glUniform3f(int location, float x, float y, float z); + public static native void glUniform3fv(int location, int count, FloatBuffer v); + public static native void glUniform3fv2(int location, int count, float[] v, int offset); + public static native void glUniform4f(int location, float x, float y, float z, float w); + public static native void glUniform4fv(int location, int count, FloatBuffer v); + public static native void glUniform4fv2(int location, int count, float[] v, int offset); + public static native void glUniformMatrix3fv(int location, int count, boolean transpose, FloatBuffer value); + public static native void glUniformMatrix3fv2(int location, int count, boolean transpose, float[] value, int offset); + public static native void glUniformMatrix4fv(int location, int count, boolean transpose, FloatBuffer value); + public static native void glUniformMatrix4fv2(int location, int count, boolean transpose, float[] value, int offset); + public static native void glUseProgram(int program); + //public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, byte[] ptr, int offset); + public static native void glVertexAttribPointer(int indx, int size, int type, boolean normalized, int stride, Buffer ptr); + public static native void glVertexAttribPointer2(int indx, int size, int type, boolean normalized, int stride, int offset); + public static native void glViewport(int x, int y, int width, int height); + + // New methods for GLES3 + public static native void glBeginQuery(int target, int query); + public static native void glEndQuery(int target); + public static native void glGenQueries(int num, IntBuffer buff); + public static native void glGetQueryObjectuiv(int query, int pname, int[] params); + public static native void glGetQueryiv(int query, int pname, int[] params); + public static native void glBlitFramebuffer(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter); + public static native void glDrawArraysInstanced(int mode, int first, int count, int primcount); + public static native void glDrawBuffers(int size, IntBuffer data); //TODO: use buffer or intbuffer? + public static native void glDrawElementsInstanced(int mode, int indices_count, int type, long indices_buffer_offset, int primcount); + public static native void glVertexAttribDivisor(int index, int divisor); + public static native void glFramebufferTextureLayer(int target, int attachment, int texture, int level, int layer); + + // New methods from GL2 interface which are supported in GLES30 + public static native void glReadBuffer(int mode); + public static native void glCompressedTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, + int border, int size, ByteBuffer data); + public static native void glCompressedTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, + int height, int depth, int format, int size, ByteBuffer data); + public static native void glTexImage3D(int target, int level, int internalFormat, int width, int height, int depth, int border, + int format, int type, ByteBuffer data); + public static native void glTexSubImage3D(int target, int level, int xoffset, int yoffset, int zoffset, int width, int height, + int depth, int format, int type, ByteBuffer data); + + public static void checkGLError() { if (!ENABLE_ERROR_CHECKING) { return; @@ -271,4 +308,4 @@ public static String gluErrorString(int error) { } */ -} \ No newline at end of file +} diff --git a/jme3-ios/src/main/java/com/jme3/renderer/ios/package-info.java b/jme3-ios/src/main/java/com/jme3/renderer/ios/package-info.java new file mode 100644 index 0000000000..e41d4023af --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/renderer/ios/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * graphics-rendering code specific to the iOS mobile operating system + */ +package com.jme3.renderer.ios; 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 14ab252a54..b18429ec99 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 @@ -31,18 +31,20 @@ */ package com.jme3.system.ios; -import com.jme3.input.*; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; import com.jme3.input.dummy.DummyKeyInput; import com.jme3.input.dummy.DummyMouseInput; -import com.jme3.system.*; import com.jme3.input.ios.IosInputHandler; import com.jme3.opencl.Context; import com.jme3.renderer.ios.IosGL; -import com.jme3.renderer.opengl.GL; -import com.jme3.renderer.opengl.GLDebugES; -import com.jme3.renderer.opengl.GLExt; -import com.jme3.renderer.opengl.GLFbo; -import com.jme3.renderer.opengl.GLRenderer; +import com.jme3.renderer.opengl.*; +import com.jme3.system.*; +import com.jme3.util.IosNativeBufferAllocator; +import com.jme3.util.BufferAllocatorFactory; + import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -63,10 +65,18 @@ public class IGLESContext implements JmeContext { protected Timer timer; protected SystemListener listener; protected IosInputHandler input; - protected int minFrameDuration = 0; // No FPS cap + protected int minFrameDuration = 0; // No FPS cap + + static { + final String implementation = BufferAllocatorFactory.PROPERTY_BUFFER_ALLOCATOR_IMPLEMENTATION; + + if (System.getProperty(implementation) == null) { + System.setProperty(implementation, IosNativeBufferAllocator.class.getName()); + } + } public IGLESContext() { - logger.log(Level.FINE, "IGLESContext constructor"); + logger.log(Level.FINE, "IGLESContext constructor"); } @Override @@ -83,6 +93,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"); @@ -112,13 +133,13 @@ public KeyInput getKeyInput() { @Override public JoyInput getJoyInput() { - /* + /* if (androidSensorJoyInput == null) { androidSensorJoyInput = new AndroidSensorJoyInput(); } return androidSensorJoyInput; */ - return null;// new DummySensorJoyInput(); + return null; // new DummySensorJoyInput(); } @Override @@ -132,13 +153,12 @@ public Timer getTimer() { } @Override - public void setTitle(String title) { - } + public void setTitle(String title) {} @Override public boolean isCreated() { logger.log(Level.FINE, "IGLESContext isCreated"); - return created.get(); + return created.get(); } @Override @@ -149,30 +169,27 @@ public void setAutoFlushFrames(boolean enabled) { @Override public boolean isRenderable() { logger.log(Level.FINE, "IGLESContext isRenderable"); - return true;// renderable.get(); + return true; // renderable.get(); } @Override public void create(boolean waitFor) { logger.log(Level.FINE, "IGLESContext create"); - - GL gl = new IosGL(); - GLExt glext = (GLExt) gl; + IosGL gl = new IosGL(); -// if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugES(gl, glext, (GLFbo) glext); - glext = (GLExt) gl; -// } + if (settings.getBoolean("GraphicsDebug")) { + gl = (IosGL) GLDebug.createProxy(gl, gl, GL.class, GLExt.class, GLFbo.class); + } - renderer = new GLRenderer(gl, glext, (GLFbo) glext); + renderer = new GLRenderer(gl, gl, gl); renderer.initialize(); - + input = new IosInputHandler(); timer = new NanoTimer(); -//synchronized (createdLock){ - created.set(true); - //createdLock.notifyAll(); + //synchronized (createdLock){ + created.set(true); + //createdLock.notifyAll(); //} listener.initialize(); @@ -188,14 +205,13 @@ public void create() { } @Override - public void restart() { - } + public void restart() {} @Override public void destroy(boolean waitFor) { logger.log(Level.FINE, "IGLESContext destroy"); - listener.destroy(); - needClose.set(true); + listener.destroy(); + needClose.set(true); if (waitFor) { //waitFor(false); } @@ -209,8 +225,7 @@ protected void waitFor(boolean createdVal) { while (renderable.get() != createdVal) { try { Thread.sleep(10); - } catch (InterruptedException ex) { - } + } catch (InterruptedException ex) {} } } @@ -219,4 +234,56 @@ public Context getOpenCLContext() { logger.warning("OpenCL not yet supported on this platform"); return null; } -} \ No newline at end of file + + /** + * 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"); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java index e47de3a503..07a881ee5f 100644 --- a/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java +++ b/jme3-ios/src/main/java/com/jme3/system/ios/IosImageLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,16 +45,17 @@ */ public class IosImageLoader implements AssetLoader { + @Override public Object load(AssetInfo info) throws IOException { boolean flip = ((TextureKey) info.getKey()).isFlipY(); Image img = null; InputStream in = null; try { - in = info.openStream(); + in = info.openStream(); img = loadImageData(Format.RGBA8, flip, in); } finally { - if (in != null) { - in.close(); + if (in != null) { + in.close(); } } return img; 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..4f99bbe476 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2024 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,8 @@ import com.jme3.system.JmeContext; import com.jme3.system.JmeSystemDelegate; import com.jme3.system.NullContext; +import com.jme3.system.Platform; +import com.jme3.util.res.Resources; import com.jme3.audio.AudioRenderer; import com.jme3.audio.ios.IosAL; import com.jme3.audio.ios.IosALC; @@ -54,9 +56,16 @@ */ 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"); + return Resources.getResource("com/jme3/asset/IOS.cfg"); } @Override @@ -64,18 +73,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) { @@ -107,6 +109,18 @@ public void initialize(AppSettings settings) { // throw new UnsupportedOperationException("Not supported yet."); } + @Override + public Platform getPlatform() { + String arch = System.getProperty("os.arch").toLowerCase(); + if (arch.contains("arm")) { + return Platform.iOS_ARM; + } else if (arch.contains("aarch")) { + return Platform.iOS_ARM; + } else { + return Platform.iOS_X86; + } + } + @Override public void showSoftKeyboard(boolean show) { throw new UnsupportedOperationException("Not supported yet."); diff --git a/jme3-ios/src/main/java/com/jme3/system/ios/package-info.java b/jme3-ios/src/main/java/com/jme3/system/ios/package-info.java new file mode 100644 index 0000000000..a4cfecba22 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/system/ios/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * code specific to the iOS mobile operating system + */ +package com.jme3.system.ios; diff --git a/jme3-ios/src/main/java/com/jme3/util/IosNativeBufferAllocator.java b/jme3-ios/src/main/java/com/jme3/util/IosNativeBufferAllocator.java new file mode 100644 index 0000000000..4c29d4e842 --- /dev/null +++ b/jme3-ios/src/main/java/com/jme3/util/IosNativeBufferAllocator.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.util; + +import java.nio.Buffer; +import java.nio.ByteBuffer; + +/** + * Allocates and destroys direct byte buffers using native code. + * + * @author Jesus Oliver based on pavl_g's AndroidNativeBufferAllocator. + */ +public final class IosNativeBufferAllocator implements BufferAllocator { + + @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 IosNativeBufferAllocator#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 IosNativeBufferAllocator#allocate(int) + */ + private native ByteBuffer createDirectByteBuffer(long size); +} + diff --git a/jme3-ios/src/main/java/com/jme3/util/RingBuffer.java b/jme3-ios/src/main/java/com/jme3/util/RingBuffer.java index 1d3c22d7e5..f4b50c24d5 100644 --- a/jme3-ios/src/main/java/com/jme3/util/RingBuffer.java +++ b/jme3-ios/src/main/java/com/jme3/util/RingBuffer.java @@ -49,6 +49,7 @@ public T pop() { return item; } + @Override public Iterator iterator() { return new RingBufferIterator(); } @@ -58,14 +59,17 @@ private class RingBufferIterator implements Iterator { private int i = 0; + @Override public boolean hasNext() { return i < count; } + @Override public void remove() { throw new UnsupportedOperationException(); } + @Override public T next() { if (!hasNext()) { throw new NoSuchElementException(); diff --git a/jme3-jbullet/build.gradle b/jme3-jbullet/build.gradle index 473f03b046..eb6ce8af88 100644 --- a/jme3-jbullet/build.gradle +++ b/jme3-jbullet/build.gradle @@ -1,20 +1,24 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - sourceSets { main { java { srcDir 'src/main/java' - srcDir '../jme3-bullet/src/common/java' } } + test { + java { + srcDir 'src/test/java' + } + + System.setProperty "java.awt.headless", "true" + } } dependencies { - compile 'java3d:vecmath:1.3.1' - compile 'jbullet:jbullet' - compile 'stack-alloc:stack-alloc' - compile project(':jme3-core') - compile project(':jme3-terrain') + api libs.jbullet + api libs.vecmath + 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-bullet/src/common/java/com/jme3/bullet/BulletAppState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/BulletAppState.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/BulletAppState.java index 2dc46906ce..d18b8526b8 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/BulletAppState.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/BulletAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -182,6 +182,7 @@ private boolean startPhysicsOnExecutor() { executor = new ScheduledThreadPoolExecutor(1); final BulletAppState app = this; Callable call = new Callable() { + @Override public Boolean call() throws Exception { detachedPhysicsLastUpdate = System.currentTimeMillis(); pSpace = new PhysicsSpace(worldMin, worldMax, broadphaseType); @@ -191,15 +192,13 @@ public Boolean call() throws Exception { }; try { return executor.submit(call).get(); - } catch (InterruptedException ex) { - Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex); - return false; - } catch (ExecutionException ex) { + } catch (InterruptedException | ExecutionException ex) { Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex); return false; } } private Callable parallelPhysicsUpdate = new Callable() { + @Override public Boolean call() throws Exception { pSpace.update(tpf * getSpeed()); return true; @@ -207,6 +206,7 @@ public Boolean call() throws Exception { }; long detachedPhysicsLastUpdate = 0; private Callable detachedPhysicsUpdate = new Callable() { + @Override public Boolean call() throws Exception { pSpace.update(getPhysicsSpace().getAccuracy() * getSpeed()); pSpace.distributeEvents(); @@ -385,9 +385,7 @@ public void postRender() { try { physicsFuture.get(); physicsFuture = null; - } catch (InterruptedException ex) { - Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex); - } catch (ExecutionException ex) { + } catch (InterruptedException | ExecutionException ex) { Logger.getLogger(BulletAppState.class.getName()).log(Level.SEVERE, null, ex); } } @@ -484,6 +482,7 @@ public void setSpeed(float speed) { * @param space the space that is about to be stepped (not null) * @param f the time per physics step (in seconds, ≥0) */ + @Override public void prePhysicsTick(PhysicsSpace space, float f) { } @@ -494,6 +493,7 @@ public void prePhysicsTick(PhysicsSpace space, float f) { * @param space the space that is about to be stepped (not null) * @param f the time per physics step (in seconds, ≥0) */ + @Override public void physicsTick(PhysicsSpace space, float f) { } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java index 42ac5040a5..7d0735cb25 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsSpace.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -127,7 +127,7 @@ protected ConcurrentLinkedQueue> initialValue() { return new ConcurrentLinkedQueue>(); } }; - private ConcurrentLinkedQueue> pQueue = new ConcurrentLinkedQueue>(); + private ConcurrentLinkedQueue> pQueue = new ConcurrentLinkedQueue<>(); private static ThreadLocal physicsSpaceTL = new ThreadLocal(); private DiscreteDynamicsWorld dynamicsWorld = null; private BroadphaseInterface broadphase; @@ -135,19 +135,19 @@ protected ConcurrentLinkedQueue> initialValue() { private CollisionDispatcher dispatcher; private ConstraintSolver solver; private DefaultCollisionConfiguration collisionConfiguration; - private Map physicsGhostObjects = new ConcurrentHashMap(); - private Map physicsCharacters = new ConcurrentHashMap(); - private Map physicsBodies = new ConcurrentHashMap(); - private Map physicsJoints = new ConcurrentHashMap(); - private Map physicsVehicles = new ConcurrentHashMap(); + private Map physicsGhostObjects = new ConcurrentHashMap<>(); + private Map physicsCharacters = new ConcurrentHashMap<>(); + private Map physicsBodies = new ConcurrentHashMap<>(); + private Map physicsJoints = new ConcurrentHashMap<>(); + private Map physicsVehicles = new ConcurrentHashMap<>(); /** * map from collision groups to registered group listeners */ - private Map collisionGroupListeners = new ConcurrentHashMap(); + private Map collisionGroupListeners = new ConcurrentHashMap<>(); /** * queue of registered tick listeners */ - private ConcurrentLinkedQueue tickListeners = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue tickListeners = new ConcurrentLinkedQueue<>(); /** * list of registered collision listeners */ @@ -156,7 +156,7 @@ protected ConcurrentLinkedQueue> initialValue() { /** * queue of collision events not yet distributed to listeners */ - private ArrayDeque collisionEvents = new ArrayDeque(); + private ArrayDeque collisionEvents = new ArrayDeque<>(); private PhysicsCollisionEventFactory eventFactory = new PhysicsCollisionEventFactory(); /** * copy of minimum coordinate values when using AXIS_SWEEP broadphase @@ -182,7 +182,7 @@ protected ConcurrentLinkedQueue> initialValue() { private com.bulletphysics.linearmath.Transform sweepTrans2 = new com.bulletphysics.linearmath.Transform(new javax.vecmath.Matrix3f()); /** - * Get the current PhysicsSpace running on this thread
                + * Get the current PhysicsSpace running on this thread
                * For parallel physics, this can also be called from the OpenGL thread to receive the PhysicsSpace * @return the PhysicsSpace running on this thread */ @@ -192,7 +192,8 @@ public static PhysicsSpace getPhysicsSpace() { /** * Used internally - * @param space + * + * @param space which space to simulate on the current thread */ public static void setLocalThreadPhysicsSpace(PhysicsSpace space) { physicsSpaceTL.set(space); @@ -259,6 +260,7 @@ public void create() { private void setOverlapFilterCallback() { OverlapFilterCallback callback = new OverlapFilterCallback() { + @Override public boolean needBroadphaseCollision(BroadphaseProxy bp, BroadphaseProxy bp1) { boolean collides = (bp.collisionFilterGroup & bp1.collisionFilterMask) != 0; if (collides) { @@ -334,6 +336,7 @@ public void internalTick(DynamicsWorld dw, float f) { private void setContactCallbacks() { BulletGlobals.setContactAddedCallback(new ContactAddedCallback() { + @Override public boolean contactAdded(ManifoldPoint cp, com.bulletphysics.collision.dispatch.CollisionObject colObj0, int partId0, int index0, com.bulletphysics.collision.dispatch.CollisionObject colObj1, int partId1, int index1) { @@ -344,6 +347,7 @@ public boolean contactAdded(ManifoldPoint cp, com.bulletphysics.collision.dispat BulletGlobals.setContactProcessedCallback(new ContactProcessedCallback() { + @Override public boolean contactProcessed(ManifoldPoint cp, Object body0, Object body1) { if (body0 instanceof CollisionObject && body1 instanceof CollisionObject) { PhysicsCollisionObject node = null, node1 = null; @@ -359,6 +363,7 @@ public boolean contactProcessed(ManifoldPoint cp, Object body0, Object body1) { BulletGlobals.setContactDestroyedCallback(new ContactDestroyedCallback() { + @Override public boolean contactDestroyed(Object userPersistentData) { System.out.println("contact destroyed"); return true; @@ -377,7 +382,8 @@ public void update(float time) { /** * updates the physics space, uses maxSteps
                * @param time the current time value - * @param maxSteps + * @param maxSteps the maximum number of steps of size accuracy (≥1) or 0 + * for a single step of size timeInterval */ public void update(float time, int maxSteps) { if (getDynamicsWorld() == null) { @@ -389,10 +395,10 @@ public void update(float time, int maxSteps) { public void distributeEvents() { //add collision callbacks - int clistsize = collisionListeners.size(); + int cListSize = collisionListeners.size(); while( collisionEvents.isEmpty() == false ) { PhysicsCollisionEvent physicsCollisionEvent = collisionEvents.pop(); - for(int i=0;i Future enqueueOnThisThread(Callable callable) { - AppTask task = new AppTask(callable); - System.out.println("created apptask"); + AppTask task = new AppTask<>(callable); + System.out.println("created AppTask"); pQueueTL.get().add(task); return task; } /** * calls the callable on the next physics tick (ensuring e.g. force applying) - * @param - * @param callable + * + * @param the return type of the Callable + * @param callable the Callable to invoke * @return a new AppTask */ public Future enqueue(Callable callable) { - AppTask task = new AppTask(callable); + AppTask task = new AppTask<>(callable); pQueue.add(task); return task; } @@ -431,7 +438,7 @@ public void add(Object obj) { Spatial node = (Spatial) obj; for (int i = 0; i < node.getNumControls(); i++) { if (node.getControl(i) instanceof PhysicsControl) { - add(((PhysicsControl) node.getControl(i))); + add(node.getControl(i)); } } } else if (obj instanceof PhysicsCollisionObject) { @@ -468,7 +475,7 @@ public void remove(Object obj) { Spatial node = (Spatial) obj; for (int i = 0; i < node.getNumControls(); i++) { if (node.getControl(i) instanceof PhysicsControl) { - remove(((PhysicsControl) node.getControl(i))); + remove(node.getControl(i)); } } } else if (obj instanceof PhysicsCollisionObject) { @@ -600,8 +607,8 @@ private void addRigidBody(PhysicsRigidBody node) { physicsBodies.put(node.getObjectId(), node); //Workaround - //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non kinematic again afterward. - //so we add it non kinematic, then set it kinematic again. + //It seems that adding a Kinematic RigidBody to the dynamicWorld prevent it from being non-kinematic again afterward. + //so we add it non-kinematic, then set it kinematic again. boolean kinematic = false; if (node.isKinematic()) { kinematic = true; @@ -678,7 +685,9 @@ public Collection getVehicleList(){ /** * Sets the gravity of the PhysicsSpace, set before adding physics objects! - * @param gravity + * + * @param gravity the desired acceleration vector + * (in physics-space coordinates, not null, unaffected, default=0,-10,0) */ public void setGravity(Vector3f gravity) { dynamicsWorld.setGravity(Converter.convert(gravity)); @@ -686,7 +695,10 @@ public void setGravity(Vector3f gravity) { /** * Gets the gravity of the PhysicsSpace - * @param gravity + * + * @param gravity storage for the result (modified if not null) + * @return the acceleration vector (in physics-space coordinates, either + * gravity or a new instance) */ public Vector3f getGravity(Vector3f gravity) { javax.vecmath.Vector3f tempVec = new javax.vecmath.Vector3f(); @@ -712,7 +724,8 @@ public void clearForces() { * Adds the specified listener to the physics tick listeners. * The listeners are called on each physics step, which is not necessarily * each frame but is determined by the accuracy of the physics space. - * @param listener + * + * @param listener the listener to register (not null, alias created) */ public void addTickListener(PhysicsTickListener listener) { tickListeners.add(listener); @@ -741,8 +754,10 @@ public void removeCollisionListener(PhysicsCollisionListener listener) { /** * Adds a listener for a specific collision group, such a listener can disable collisions when they happen.
                * There can be only one listener per collision group. - * @param listener - * @param collisionGroup + * + * @param listener the listener to register (not null, alias created) + * @param collisionGroup which group it should listen for (bitmask with + * exactly one bit set) */ public void addCollisionGroupListener(PhysicsCollisionGroupListener listener, int collisionGroup) { collisionGroupListeners.put(collisionGroup, listener); @@ -754,15 +769,28 @@ public void removeCollisionGroupListener(int collisionGroup) { /** * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults + * + * @param from the starting location (physics-space coordinates, not null, + * unaffected) + * @param to the ending location (in physics-space coordinates, not null, + * unaffected) + * @return a new list of results (not null) */ public List rayTest(Vector3f from, Vector3f to) { - List results = new LinkedList(); + List results = new LinkedList<>(); dynamicsWorld.rayTest(Converter.convert(from, rayVec1), Converter.convert(to, rayVec2), new InternalRayListener(results)); return results; } /** * Performs a ray collision test and returns the results as a list of PhysicsRayTestResults + * + * @param from the starting location (in physics-space coordinates, not + * null, unaffected) + * @param to the ending location (in physics-space coordinates, not null, + * unaffected) + * @param results the list to hold results (not null, modified) + * @return results */ public List rayTest(Vector3f from, Vector3f to, List results) { results.clear(); @@ -787,12 +815,17 @@ public float addSingleResult(LocalRayResult lrr, boolean bln) { } /** - * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults
                + * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults
                * You have to use different Transforms for start and end (at least distance greater than 0.4f). * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center. + * + * @param shape the shape to sweep (not null, convex, unaffected) + * @param start the starting physics-space transform (not null, unaffected) + * @param end the ending physics-space transform (not null, unaffected) + * @return a new list of results */ public List sweepTest(CollisionShape shape, Transform start, Transform end) { - List results = new LinkedList(); + List results = new LinkedList<>(); if (!(shape.getCShape() instanceof ConvexShape)) { logger.log(Level.WARNING, "Trying to sweep test with incompatible mesh shape!"); return results; @@ -803,9 +836,15 @@ public List sweepTest(CollisionShape shape, Transform st } /** - * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults
                + * Performs a sweep collision test and returns the results as a list of PhysicsSweepTestResults
                * You have to use different Transforms for start and end (at least distance greater than 0.4f). * SweepTest will not see a collision if it starts INSIDE an object and is moving AWAY from its center. + * + * @param shape the shape to sweep (not null, convex, unaffected) + * @param start the starting physics-space transform (not null, unaffected) + * @param end the ending physics-space transform (not null, unaffected) + * @param results the list to hold results (not null, modified) + * @return results */ public List sweepTest(CollisionShape shape, Transform start, Transform end, List results) { results.clear(); @@ -863,7 +902,7 @@ public void setBroadphaseType(BroadphaseType broadphaseType) { /** * Sets the maximum amount of extra steps that will be used to step the physics * when the fps is below the physics fps. Doing this maintains determinism in physics. - * For example a maximum number of 2 can compensate for framerates as low as 30fps + * For example a maximum number of 2 can compensate for frame rates as low as 30fps * when the physics has the default accuracy of 60 fps. Note that setting this * value too high can make the physics drive down its own fps in case it's overloaded. * @param steps The maximum number of extra steps, default is 4. @@ -882,7 +921,8 @@ public float getAccuracy() { /** * sets the accuracy of the physics computation, default=1/60s
                - * @param accuracy + * + * @param accuracy the desired time step (in seconds, default=1/60) */ public void setAccuracy(float accuracy) { this.accuracy = accuracy; @@ -894,7 +934,9 @@ public Vector3f getWorldMin() { /** * only applies for AXIS_SWEEP broadphase - * @param worldMin + * + * @param worldMin the desired minimum coordinates values (not null, + * unaffected, default=-10k,-10k,-10k) */ public void setWorldMin(Vector3f worldMin) { this.worldMin.set(worldMin); @@ -906,7 +948,9 @@ public Vector3f getWorldMax() { /** * only applies for AXIS_SWEEP broadphase - * @param worldMax + * + * @param worldMax the desired maximum coordinates values (not null, + * unaffected, default=10k,10k,10k) */ public void setWorldMax(Vector3f worldMax) { this.worldMax.set(worldMax); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsTickListener.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsTickListener.java index 7bbd704aa1..55281caaf8 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/PhysicsTickListener.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/PhysicsTickListener.java @@ -50,8 +50,8 @@ public interface PhysicsTickListener { public void prePhysicsTick(PhysicsSpace space, float tpf); /** - * Callback from Bullet, invoked just after the physics has been stepped, - * use to check for forces etc. + * Callback from Bullet, invoked just after the physics has been stepped. + * Use it to check for forces etc. * * @param space the space that was just stepped (not null) * @param tpf the time per physics step (in seconds, ≥0) diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/BoneLink.java similarity index 99% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/BoneLink.java index 1c76a31272..893baf32e1 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/animation/BoneLink.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/BoneLink.java @@ -110,9 +110,9 @@ protected BoneLink() { * created) * @param bone the linked bone (not null, alias created) * @param collisionShape the desired shape (not null, alias created) - * @param linkConfig the link configuration (not null) + * @param mass the desired mass (>0) * @param localOffset the location of the body's center (in the bone's local - * coordinates, not null, unaffected) + * coordinates, not null, unaffected) */ BoneLink(DacLinks control, Joint bone, CollisionShape collisionShape, float mass, Vector3f localOffset) { diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacConfiguration.java similarity index 99% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacConfiguration.java index ea03107b6b..2d234f9f7d 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacConfiguration.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacConfiguration.java @@ -91,11 +91,11 @@ abstract public class DacConfiguration extends AbstractPhysicsControl { */ private float torsoMass = 1f; /** - * map linked bone names to masses + * Maps linked bone names to masses. */ private Map blConfigMap = new HashMap<>(50); /** - * map linked bone names to ranges of motion for createSpatialData() + * Maps linked bone names to ranges of motion for createSpatialData(). */ private Map jointMap = new HashMap<>(50); /** diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacLinks.java similarity index 99% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacLinks.java index d80adc4a21..6e1a44a606 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DacLinks.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/DacLinks.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019 jMonkeyEngine + * Copyright (c) 2018-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,10 +91,6 @@ public class DacLinks * local copy of {@link com.jme3.math.Transform#IDENTITY} */ final private static Transform transformIdentity = new Transform(); - /** - * local copy of {@link com.jme3.math.Vector3f#ZERO} - */ - final private static Vector3f translateIdentity = new Vector3f(0f, 0f, 0f); // ************************************************************************* // fields @@ -887,7 +883,7 @@ public void prePhysicsTick(PhysicsSpace space, float timeStep) { * Add joints to connect the named bone/torso link with each of its * children. Also fill in the boneLinkList. Note: recursive! * - * @param parentName the parent bone/torso link (not null) + * @param parentLink the parent bone/torso link (not null) */ private void addJoints(PhysicsLink parentLink) { List childNames = childNames(parentLink); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/DynamicAnimControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/DynamicAnimControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/DynamicAnimControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/DynamicAnimControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/KinematicSubmode.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/KinematicSubmode.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/KinematicSubmode.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/PhysicsLink.java similarity index 98% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/PhysicsLink.java index d6f5679176..e652281a3a 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/animation/PhysicsLink.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/PhysicsLink.java @@ -156,8 +156,10 @@ protected PhysicsLink() { this.bone = bone; rigidBody = createRigidBody(mass, collisionShape); - logger.log(Level.FINE, "Creating link for bone {0} with mass={1}", - new Object[]{bone.getName(), rigidBody.getMass()}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Creating link for bone {0} with mass={1}", + new Object[]{bone.getName(), rigidBody.getMass()}); + } this.localOffset = localOffset.clone(); updateKPTransform(); @@ -577,7 +579,7 @@ public void write(JmeExporter ex) throws IOException { /** * Create and configure a rigid body for this link. * - * @param linkConfig the link configuration (not null) + * @param mass the desired mass (>0) * @param collisionShape the desired shape (not null, alias created) * @return a new instance, not in any PhysicsSpace */ diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/RagUtils.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/RagUtils.java index 89b112283f..0a51b894e8 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagUtils.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/RagUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018-2019 jMonkeyEngine + * Copyright (c) 2018-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -468,7 +468,7 @@ private static String findManager(Mesh mesh, int vertexIndex, int[] iArray, */ private static List listGeometries(Spatial subtree, List addResult) { - List result = (addResult == null) ? new ArrayList(50) : addResult; + List result = (addResult == null) ? new ArrayList<>(50) : addResult; if (subtree instanceof Geometry) { Geometry geometry = (Geometry) subtree; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RagdollCollisionListener.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/RagdollCollisionListener.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/RagdollCollisionListener.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/RagdollCollisionListener.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/RangeOfMotion.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/RangeOfMotion.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/RangeOfMotion.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/RangeOfMotion.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/TorsoLink.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/TorsoLink.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/TorsoLink.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/TorsoLink.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/VectorSet.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/VectorSet.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/VectorSet.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/VectorSet.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/animation/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/animation/package-info.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/animation/package-info.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/animation/package-info.java diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java index 4b1ec58364..9ba347e384 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -74,6 +74,11 @@ public void clean() { /** * used by event factory, called when event reused + * + * @param type the desired type + * @param source the desired first object (alias created) + * @param nodeB the desired 2nd object (alias created) + * @param cp the desired manifold (alias created) */ public void refactor(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) { this.source = source; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java index 07357c0ed8..d4ea7a0062 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionEventFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -40,7 +40,7 @@ */ public class PhysicsCollisionEventFactory { - private ConcurrentLinkedQueue eventBuffer = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue eventBuffer = new ConcurrentLinkedQueue<>(); public PhysicsCollisionEvent getEvent(int type, PhysicsCollisionObject source, PhysicsCollisionObject nodeB, ManifoldPoint cp) { PhysicsCollisionEvent event = eventBuffer.poll(); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionGroupListener.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionListener.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionListener.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/collision/PhysicsCollisionListener.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionListener.java diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java index 35c5dd789e..4cfbc9d65f 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsCollisionObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -105,7 +105,8 @@ public void setCollisionGroup(int collisionGroup) { * Add a group that this object will collide with.
                * Two object will collide when one of the parties has the * collisionGroup of the other in its collideWithGroups set.
                - * @param collisionGroup + * + * @param collisionGroup the groups to add, ORed together (bitmask) */ public void addCollideWithGroup(int collisionGroup) { this.collisionGroupsMask = this.collisionGroupsMask | collisionGroup; @@ -113,7 +114,8 @@ public void addCollideWithGroup(int collisionGroup) { /** * Remove a group from the list this object collides with. - * @param collisionGroup + * + * @param collisionGroup the groups to remove, ORed together (bitmask) */ public void removeCollideWithGroup(int collisionGroup) { this.collisionGroupsMask = this.collisionGroupsMask & ~collisionGroup; @@ -121,7 +123,9 @@ public void removeCollideWithGroup(int collisionGroup) { /** * Directly set the bitmask for collision groups that this object collides with. - * @param collisionGroups + * + * @param collisionGroups the desired groups, ORed together (bitmask, + * default=COLLISION_GROUP_01) */ public void setCollideWithGroups(int collisionGroups) { this.collisionGroupsMask = collisionGroups; @@ -158,8 +162,8 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + InputCapsule capsule = importer.getCapsule(this); collisionGroup = capsule.readInt("collisionGroup", 0x00000001); collisionGroupsMask = capsule.readInt("collisionGroupsMask", 0x00000001); CollisionShape shape = (CollisionShape) capsule.readSavable("collisionShape", null); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java index 5652edf651..e88ffa27e0 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/PhysicsRayTestResult.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,7 +71,7 @@ public Vector3f getHitNormalLocal() { /** * The hitFraction is the fraction of the ray length (yeah, I know) at which the collision occurred. - * If e.g. the raytest was from 0,0,0 to 0,6,0 and the hitFraction is 0.5 then the collision occurred at 0,3,0 + * If e.g. the ray test was from 0,0,0 to 0,6,0 and the hitFraction is 0.5 then the collision occurred at 0,3,0 * @return the hitFraction */ public float getHitFraction() { diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/RagdollCollisionListener.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/RagdollCollisionListener.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/collision/RagdollCollisionListener.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/collision/RagdollCollisionListener.java diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/package-info.java new file mode 100644 index 0000000000..6402ed9c9a --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * physics-based collision detection, including ray tests and sweep tests + */ +package com.jme3.bullet.collision; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java index 596756f198..a8c47006fb 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/BoxCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,12 +64,14 @@ public final Vector3f getHalfExtents() { return halfExtents; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); capsule.write(halfExtents, "halfExtents", new Vector3f(1, 1, 1)); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java index d6e517b1ca..5eaead802c 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CapsuleCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -68,9 +68,11 @@ public CapsuleCollisionShape(float radius, float height) { /** * Creates a capsule shape around the given axis (0=X,1=Y,2=Z). - * @param radius - * @param height - * @param axis + * + * @param radius the desired unscaled radius + * @param height the desired unscaled height of the cylindrical portion + * @param axis which local axis for the height: 0→X, 1→Y, + * 2→Z */ public CapsuleCollisionShape(float radius, float height, int axis) { this.radius=radius; @@ -91,6 +93,7 @@ public int getAxis() { return axis; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -99,6 +102,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(axis, "axis", 1); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java index 70a082a066..43987206a3 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,6 +61,9 @@ protected CollisionShape() { /** * used internally, not safe + * + * @param mass the desired mass for the body + * @param vector storage for the result (not null, modified) */ public void calculateLocalInertia(float mass, javax.vecmath.Vector3f vector) { if (cShape == null) { @@ -75,6 +78,8 @@ public void calculateLocalInertia(float mass, javax.vecmath.Vector3f vector) { /** * used internally + * + * @return the pre-existing instance */ public com.bulletphysics.collision.shapes.CollisionShape getCShape() { return cShape; @@ -82,6 +87,8 @@ public com.bulletphysics.collision.shapes.CollisionShape getCShape() { /** * used internally + * + * @param cShape the shape to use (alias created) */ public void setCShape(com.bulletphysics.collision.shapes.CollisionShape cShape) { this.cShape = cShape; @@ -125,12 +132,14 @@ public Vector3f getScale() { return scale; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule capsule = ex.getCapsule(this); capsule.write(scale, "scale", new Vector3f(1, 1, 1)); capsule.write(getMargin(), "margin", 0.0f); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule capsule = im.getCapsule(this); this.scale = (Vector3f) capsule.readSavable("scale", new Vector3f(1, 1, 1)); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java index 5cbc208d4d..33157fc97c 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CompoundCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ */ public class CompoundCollisionShape extends CollisionShape { - protected ArrayList children = new ArrayList(); + protected ArrayList children = new ArrayList<>(); public CompoundCollisionShape() { cShape = new CompoundShape(); @@ -77,6 +77,8 @@ public void addChildShape(CollisionShape shape, Vector3f location) { * adds a child shape at the given local translation * @param shape the child shape to add * @param location the local location of the child shape + * @param rotation the local orientation of the child shape (not null, + * unaffected) */ public void addChildShape(CollisionShape shape, Vector3f location, Matrix3f rotation) { if(shape instanceof CompoundCollisionShape){ @@ -125,12 +127,15 @@ public void setScale(Vector3f scale) { Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CompoundCollisionShape cannot be scaled"); } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); capsule.writeSavableArrayList(children, "children", new ArrayList()); } + @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java index 4e87559d22..f82ce96ea1 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/ConeCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -101,6 +101,7 @@ public int getAxis() { return axis; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -109,6 +110,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(axis, "axis", PhysicsSpace.AXIS_Y); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java index 82245facb9..88f8a5a4ce 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/CylinderCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -90,11 +90,12 @@ public int getAxis() { */ @Override public void setScale(Vector3f scale) { - if (!scale.equals(Vector3f.UNIT_XYZ)) { - Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CylinderCollisionShape cannot be scaled"); - } + if (!scale.equals(Vector3f.UNIT_XYZ)) { + Logger.getLogger(this.getClass().getName()).log(Level.WARNING, "CylinderCollisionShape cannot be scaled"); + } } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -102,6 +103,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(axis, "axis", 1); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java index 3d828d03d1..7dadd68835 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/GImpactCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,11 +81,14 @@ private void createCollisionMesh(Mesh mesh, Vector3f worldScale) { /** * creates a jme mesh from the collision shape, only needed for debugging + * + * @return a new Mesh */ public Mesh createJmeMesh(){ return Converter.convert(bulletMesh); } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -99,6 +102,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(vertexBase.array(), "vertexBase", new byte[0]); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java index 2df8728883..d6102d660f 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HeightfieldCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,89 +46,90 @@ * Uses Bullet Physics Heightfield terrain collision system. This is MUCH faster * than using a regular mesh. * There are a couple tricks though: - * -No rotation or translation is supported. - * -The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being - * equal on either side. If not, the whole collision box is shifted vertically and things don't collide - * as they should. + * -No rotation or translation is supported. + * -The collision bbox must be centered around 0,0,0 with the height above and below the y-axis being + * equal on either side. If not, the whole collision box is shifted vertically and things don't collide + * as they should. * * @author Brent Owens */ public class HeightfieldCollisionShape extends CollisionShape { - //protected HeightfieldTerrainShape heightfieldShape; - protected int heightStickWidth; - protected int heightStickLength; - protected float[] heightfieldData; - protected float heightScale; - protected float minHeight; - protected float maxHeight; - protected int upAxis; - protected boolean flipQuadEdges; - - protected HeightfieldCollisionShape() { - - } - - public HeightfieldCollisionShape(float[] heightmap) { - createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ); - } - - public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { - createCollisionHeightfield(heightmap, scale); - } - - protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { - this.scale = worldScale; - this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale - - this.heightfieldData = heightmap; - - float min = heightfieldData[0]; - float max = heightfieldData[0]; - // calculate min and max height - for (int i=0; i max) - max = heightfieldData[i]; - } - // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the - // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect. - if (max < 0) - max = -min; - else { - if (Math.abs(max) > Math.abs(min)) - min = -max; - else - max = -min; - } - this.minHeight = min; - this.maxHeight = max; - - this.upAxis = HeightfieldTerrainShape.YAXIS; - this.flipQuadEdges = false; - - heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); - heightStickLength = heightStickWidth; - - - createShape(); - } - - protected void createShape() { - - HeightfieldTerrainShape shape = new HeightfieldTerrainShape(heightStickWidth, heightStickLength, heightfieldData, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges); - shape.setLocalScaling(new javax.vecmath.Vector3f(scale.x, scale.y, scale.z)); - cShape = shape; - cShape.setLocalScaling(Converter.convert(getScale())); + //protected HeightfieldTerrainShape heightfieldShape; + protected int heightStickWidth; + protected int heightStickLength; + protected float[] heightfieldData; + protected float heightScale; + protected float minHeight; + protected float maxHeight; + protected int upAxis; + protected boolean flipQuadEdges; + + protected HeightfieldCollisionShape() { + + } + + public HeightfieldCollisionShape(float[] heightmap) { + createCollisionHeightfield(heightmap, Vector3f.UNIT_XYZ); + } + + public HeightfieldCollisionShape(float[] heightmap, Vector3f scale) { + createCollisionHeightfield(heightmap, scale); + } + + protected void createCollisionHeightfield(float[] heightmap, Vector3f worldScale) { + this.scale = worldScale; + this.heightScale = 1;//don't change away from 1, we use worldScale instead to scale + + this.heightfieldData = heightmap; + + float min = heightfieldData[0]; + float max = heightfieldData[0]; + // calculate min and max height + for (int i=0; i max) + max = heightfieldData[i]; + } + // we need to center the terrain collision box at 0,0,0 for BulletPhysics. And to do that we need to set the + // min and max height to be equal on either side of the y axis, otherwise it gets shifted and collision is incorrect. + if (max < 0) + max = -min; + else { + if (Math.abs(max) > Math.abs(min)) + min = -max; + else + max = -min; + } + this.minHeight = min; + this.maxHeight = max; + + this.upAxis = HeightfieldTerrainShape.YAXIS; + this.flipQuadEdges = false; + + heightStickWidth = (int) FastMath.sqrt(heightfieldData.length); + heightStickLength = heightStickWidth; + + + createShape(); + } + + protected void createShape() { + + HeightfieldTerrainShape shape = new HeightfieldTerrainShape(heightStickWidth, heightStickLength, heightfieldData, heightScale, minHeight, maxHeight, upAxis, flipQuadEdges); + shape.setLocalScaling(new javax.vecmath.Vector3f(scale.x, scale.y, scale.z)); + cShape = shape; + cShape.setLocalScaling(Converter.convert(getScale())); cShape.setMargin(margin); - } + } - public Mesh createJmeMesh(){ + public Mesh createJmeMesh(){ //TODO return Converter.convert(bulletMesh); - return null; + return null; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -142,6 +143,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(flipQuadEdges, "flipQuadEdges", false); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java index ebf7d79cd9..24746934a5 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/HullCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -86,7 +86,7 @@ public void read(JmeImporter im) throws IOException { } protected void createShape(float[] points) { - ObjectArrayList pointList = new ObjectArrayList(); + ObjectArrayList pointList = new ObjectArrayList<>(); for (int i = 0; i < points.length; i += 3) { pointList.add(new Vector3f(points[i], points[i + 1], points[i + 2])); } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java index 072a7a4ae4..acb93ee043 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/MeshCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -91,11 +91,14 @@ private void createCollisionMesh(Mesh mesh, Vector3f worldScale) { /** * creates a jme mesh from the collision shape, only needed for debugging + * + * @return a new Mesh */ public Mesh createJmeMesh(){ return Converter.convert(bulletMesh); } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -108,6 +111,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(vertexBase.array(), "vertexBase", new byte[0]); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java index d7551ae1a1..e5f6b304d1 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/PlaneCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,12 +63,14 @@ public final Plane getPlane() { return plane; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); capsule.write(plane, "collisionPlane", new Plane()); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java index 4f8a352a05..2ae60bceee 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SimplexCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -77,6 +77,7 @@ public SimplexCollisionShape(Vector3f point1) { createShape(); } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -86,6 +87,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(vector4, "simplexPoint4", null); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java index 1169f8f2f9..ec4f08fc58 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/SphereCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,8 @@ protected SphereCollisionShape() { /** * creates a SphereCollisionShape with the given radius - * @param radius + * + * @param radius the desired radius (in unscaled units) */ public SphereCollisionShape(float radius) { this.radius = radius; @@ -63,12 +64,14 @@ public float getRadius() { return radius; } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); capsule.write(radius, "radius", 0.5f); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java similarity index 98% rename from jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java index e6b5d97b36..9665efde65 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/ChildCollisionShape.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,6 +89,7 @@ public ChildCollisionShape(Vector3f location, Matrix3f rotation, CollisionShape * @param ex exporter (not null) * @throws IOException from exporter */ + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule capsule = ex.getCapsule(this); capsule.write(location, "location", new Vector3f()); @@ -102,6 +103,7 @@ public void write(JmeExporter ex) throws IOException { * @param im importer (not null) * @throws IOException from importer */ + @Override public void read(JmeImporter im) throws IOException { InputCapsule capsule = im.getCapsule(this); location = (Vector3f) capsule.readSavable("location", new Vector3f()); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java new file mode 100644 index 0000000000..c011d57fd8 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/infos/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * supplemental classes for physics-based collision detection + */ +package com.jme3.bullet.collision.shapes.infos; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/package-info.java new file mode 100644 index 0000000000..96f7085469 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/collision/shapes/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * collision shapes: standard shapes used in physics-based collision detection + */ +package com.jme3.bullet.collision.shapes; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/AbstractPhysicsControl.java similarity index 98% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/AbstractPhysicsControl.java index 739701d9fc..38d0aa6d0e 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/AbstractPhysicsControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/AbstractPhysicsControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -231,6 +231,7 @@ public void cloneFields( Cloner cloner, Object original ) { * * @param spatial the spatial to control (or null) */ + @Override public void setSpatial(Spatial spatial) { if (this.spatial != null && this.spatial != spatial) { removeSpatialData(this.spatial); @@ -262,6 +263,7 @@ public Spatial getSpatial(){ * * @param enabled true→enable the control, false→disable it */ + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (space != null) { @@ -284,13 +286,16 @@ public void setEnabled(boolean enabled) { * * @return true if enabled, otherwise false */ + @Override public boolean isEnabled() { return enabled; } + @Override public void update(float tpf) { } + @Override public void render(RenderManager rm, ViewPort vp) { } @@ -326,6 +331,7 @@ public void setPhysicsSpace(PhysicsSpace newSpace) { * * @return the pre-existing space, or null for none */ + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/BetterCharacterControl.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/BetterCharacterControl.java index 66e0306466..57755eebde 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/BetterCharacterControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/BetterCharacterControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,18 +56,16 @@ import java.util.logging.Logger; /** - * This class is intended to replace the CharacterControl class. + * Intended to replace the CharacterControl class. *

                * A rigid body with cylinder collision shape is used and its velocity is set * continuously. A ray test is used to test whether the character is on the * ground. *

                * The character keeps their own local coordinate system which adapts based on - * the gravity working on the character so they will always stand upright. + * the gravity working on the character, so it will always stand upright. *

                * Motion in the local X-Z plane is damped. - *

                - * This class is shared between JBullet and Native Bullet. * * @author normenhansen */ @@ -194,6 +192,7 @@ public void render(RenderManager rm, ViewPort vp) { * @param space the space that is about to be stepped (not null) * @param tpf the time per physics step (in seconds, ≥0) */ + @Override public void prePhysicsTick(PhysicsSpace space, float tpf) { checkOnGround(); if (wantToUnDuck && checkCanUnDuck()) { @@ -218,7 +217,7 @@ public void prePhysicsTick(PhysicsSpace space, float tpf) { float designatedVelocity = walkDirection.length(); if (designatedVelocity > 0) { Vector3f localWalkDirection = vars.vect1; - //normalize walkdirection + //normalize walkDirection localWalkDirection.set(walkDirection).normalizeLocal(); //check for the existing velocity in the desired direction float existingVelocity = velocity.dot(localWalkDirection); @@ -245,6 +244,7 @@ public void prePhysicsTick(PhysicsSpace space, float tpf) { * @param space the space that was just stepped (not null) * @param tpf the time per physics step (in seconds, ≥0) */ + @Override public void physicsTick(PhysicsSpace space, float tpf) { rigidBody.getLinearVelocity(velocity); } @@ -306,7 +306,7 @@ public boolean isOnGround() { * Toggle character ducking. When ducked the characters capsule collision * shape height will be multiplied by duckedFactor to make the capsule * smaller. When unducking, the character will check with a ray test if it - * can in fact unduck and only do so when its possible. You can test the + * can in fact unduck and only do so when it's possible. You can test the * state using isDucked(). * * @param enabled true→duck, false→unduck @@ -512,6 +512,8 @@ protected void checkOnGround() { /** * This checks if the character can go from ducked to unducked state by * doing a ray test. + * + * @return true if able to unduck, otherwise false */ protected boolean checkCanUnDuck() { TempVars vars = TempVars.get(); @@ -613,13 +615,17 @@ protected final void calculateNewForward(Quaternion rotation, Vector3f direction } else { newLeft.set(0f, direction.z, -direction.y).normalizeLocal(); } - logger.log(Level.INFO, "Zero left for direction {0}, up {1}", new Object[]{direction, worldUpVector}); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Zero left for direction {0}, up {1}", new Object[]{direction, worldUpVector}); + } } newLeftNegate.set(newLeft).negateLocal(); direction.set(worldUpVector).crossLocal(newLeftNegate).normalizeLocal(); if (direction.equals(Vector3f.ZERO)) { direction.set(Vector3f.UNIT_Z); - logger.log(Level.INFO, "Zero left for left {0}, up {1}", new Object[]{newLeft, worldUpVector}); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Zero left for left {0}, up {1}", new Object[]{newLeft, worldUpVector}); + } } if (rotation != null) { rotation.fromAxes(newLeft, worldUpVector, direction); diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/CharacterControl.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/CharacterControl.java index 2ee7fbe622..834a937650 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/CharacterControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/CharacterControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -76,7 +76,9 @@ public boolean isApplyPhysicsLocal() { /** * When set to true, the physics coordinates will be applied to the local * translation of the Spatial - * @param applyPhysicsLocal + * + * @param applyPhysicsLocal true→match local coordinates, + * false→match world coordinates (default=false) */ public void setApplyPhysicsLocal(boolean applyPhysicsLocal) { applyLocal = applyPhysicsLocal; @@ -120,6 +122,7 @@ public void cloneFields( Cloner cloner, Object original ) { this.spatial = cloner.clone(spatial); } + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -136,6 +139,7 @@ public Spatial getSpatial(){ return this.spatial; } + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (space != null) { @@ -152,6 +156,7 @@ public void setEnabled(boolean enabled) { } } + @Override public boolean isEnabled() { return enabled; } @@ -172,6 +177,7 @@ public void setUseViewDirection(boolean viewDirectionEnabled) { this.useViewDirection = viewDirectionEnabled; } + @Override public void update(float tpf) { if (enabled && spatial != null) { Quaternion localRotationQuat = spatial.getLocalRotation(); @@ -195,6 +201,7 @@ public void update(float tpf) { } } + @Override public void render(RenderManager rm, ViewPort vp) { } @@ -225,6 +232,7 @@ public void setPhysicsSpace(PhysicsSpace newSpace) { space = newSpace; } + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/GhostControl.java similarity index 98% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/GhostControl.java index 09a0f6a99f..6bfb71fe8c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/GhostControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/GhostControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -196,6 +196,7 @@ public void cloneFields( Cloner cloner, Object original ) { * * @param spatial the spatial to control (or null) */ + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -222,6 +223,7 @@ public Spatial getSpatial(){ * * @param enabled true→enable the control, false→disable it */ + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (space != null) { @@ -244,6 +246,7 @@ public void setEnabled(boolean enabled) { * * @return true if enabled, otherwise false */ + @Override public boolean isEnabled() { return enabled; } @@ -255,6 +258,7 @@ public boolean isEnabled() { * * @param tpf the time interval between frames (in seconds, ≥0) */ + @Override public void update(float tpf) { if (!enabled) { return; @@ -271,6 +275,7 @@ public void update(float tpf) { * @param rm the render manager (not null) * @param vp the view port to render (not null) */ + @Override public void render(RenderManager rm, ViewPort vp) { } @@ -307,6 +312,7 @@ public void setPhysicsSpace(PhysicsSpace newSpace) { * * @return the pre-existing space, or null for none */ + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/KinematicRagdollControl.java similarity index 95% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/KinematicRagdollControl.java index 8a99ddc226..19f054be32 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/KinematicRagdollControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/KinematicRagdollControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,7 +60,7 @@ * model from Ogre or blender.
                Note enabling/disabling the control * add/removes it from the physics space
                *

                - * This control creates collision shapes for each bones of the skeleton when you + * This control creates collision shapes for all bones in the skeleton when you * invoke spatial.addControl(ragdollControl).

                • The shape is * HullCollision shape based on the vertices associated with each bone and based * on a tweakable weight threshold (see setWeightThreshold)
                • If you @@ -93,8 +93,8 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P */ protected static final Logger logger = Logger.getLogger(KinematicRagdollControl.class.getName()); protected List listeners; - protected final Set boneList = new TreeSet(); - protected final Map boneLinks = new HashMap(); + protected final Set boneList = new TreeSet<>(); + protected final Map boneLinks = new HashMap<>(); protected final Vector3f modelPosition = new Vector3f(); protected final Quaternion modelRotation = new Quaternion(); protected final PhysicsRigidBody baseRigidBody; @@ -123,8 +123,8 @@ public class KinematicRagdollControl extends AbstractPhysicsControl implements P * accumulate total mass of ragdoll when control is added to a scene */ protected float totalMass = 0; - private Map ikTargets = new HashMap(); - private Map ikChainDepth = new HashMap(); + private Map ikTargets = new HashMap<>(); + private Map ikChainDepth = new HashMap<>(); /** * rotational speed for inverse kinematics (radians per second, default=7) */ @@ -283,7 +283,7 @@ public void update(float tpf) { } if(mode == Mode.IK){ ikUpdate(tpf); - } else if (mode == mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) { + } else if (mode == Mode.Ragdoll && targetModel.getLocalTranslation().equals(modelPosition)) { //if the ragdoll has the control of the skeleton, we update each bone with its position in physics world space. ragDollUpdate(tpf); } else { @@ -322,7 +322,7 @@ protected void ragDollUpdate(float tpf) { //if the bone is the root bone, we apply the physic's transform to the model, so its position and rotation are correctly updated if (link.bone.getParent() == null) { - //offsetting the physic's position/rotation by the root bone inverse model space position/rotaion + //offsetting the physics position/rotation by the root bone inverse model space position/rotation modelPosition.set(p).subtractLocal(link.bone.getBindPosition()); targetModel.getParent().getWorldTransform().transformInverseVector(modelPosition, modelPosition); modelRotation.set(q).multLocal(tmpRot2.set(link.bone.getBindRotation()).inverseLocal()); @@ -337,8 +337,8 @@ protected void ragDollUpdate(float tpf) { link.bone.setUserTransformsInModelSpace(position, tmpRot1); } else { - //some bones of the skeleton might not be associated with a collision shape. - //So we update them recusively + // Some bones in the skeleton might not be associated with a collision shape, + // so we update them recursively. RagdollUtils.setTransform(link.bone, position, tmpRot1, false, boneList); } } @@ -351,7 +351,7 @@ protected void ragDollUpdate(float tpf) { * @param tpf the time interval between frames (in seconds, ≥0) */ protected void kinematicUpdate(float tpf) { - //the ragdoll does not have control, so the keyframed animation updates the physics position of the physics bonces + //the ragdoll does not have control, so the keyframe animation updates the physics position of the physics bones TempVars vars = TempVars.get(); Quaternion tmpRot1 = vars.quat1; Quaternion tmpRot2 = vars.quat2; @@ -360,7 +360,7 @@ protected void kinematicUpdate(float tpf) { // if(link.usedbyIK){ // continue; // } - //if blended control this means, keyframed animation is updating the skeleton, + //blended control means keyframe animation is updating the skeleton, //but to allow smooth transition, we blend this transformation with the saved position of the ragdoll if (blendedControl) { Vector3f position2 = vars.vect2; @@ -368,7 +368,7 @@ protected void kinematicUpdate(float tpf) { position.set(link.startBlendingPos); tmpRot1.set(link.startBlendingRot); - //interpolating between ragdoll position/rotation and keyframed position/rotation + //interpolate between ragdoll position/rotation and keyframe position/rotation tmpRot2.set(tmpRot1).nlerp(link.bone.getModelSpaceRotation(), blendStart / blendTime); position2.set(position).interpolateLocal(link.bone.getModelSpacePosition(), blendStart / blendTime); tmpRot1.set(tmpRot2); @@ -409,7 +409,7 @@ private void ikUpdate(float tpf){ while (it.hasNext()) { boneName = it.next(); - bone = (Bone) boneLinks.get(boneName).bone; + bone = boneLinks.get(boneName).bone; if (!bone.hasUserControl()) { Logger.getLogger(KinematicRagdollControl.class.getSimpleName()).log(Level.FINE, "{0} doesn't have user control", boneName); continue; @@ -421,7 +421,7 @@ private void ikUpdate(float tpf){ } int depth = 0; int maxDepth = ikChainDepth.get(bone.getName()); - updateBone(boneLinks.get(bone.getName()), tpf * (float) FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(boneName), depth, maxDepth); + updateBone(boneLinks.get(bone.getName()), tpf * FastMath.sqrt(distance), vars, tmpRot1, tmpRot2, bone, ikTargets.get(boneName), depth, maxDepth); Vector3f position = vars.vect1; @@ -554,7 +554,7 @@ protected void createSpatialData(Spatial model) { model.setLocalRotation(Quaternion.IDENTITY); model.setLocalScale(1); //HACK ALERT change this - //I remove the skeletonControl and readd it to the spatial to make sure it's after the ragdollControl in the stack + // I remove the SkeletonControl and re-add it to the Spatial to make sure it's after the ragdoll control in the stack. //Find a proper way to order the controls. SkeletonControl sc = model.getControl(SkeletonControl.class); if(sc == null){ @@ -673,10 +673,10 @@ protected void scanSpatial(Spatial model) { * @param model the spatial with the model's SkeletonControl (not null) * @param bone the bone to be linked (not null) * @param parent the body linked to the parent bone (not null) - * @param reccount depth of the recursion (≥1) + * @param recursionCount depth of the recursion (≥1) * @param pointsMap (not null) */ - protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int reccount, Map> pointsMap) { + protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, int recursionCount, Map> pointsMap) { PhysicsRigidBody parentShape = parent; if (boneList.contains(bone.getName())) { @@ -693,10 +693,10 @@ protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, shape = RagdollUtils.makeShapeFromVerticeWeights(model, RagdollUtils.getBoneIndices(link.bone, skeleton, boneList), initScale, link.bone.getModelSpacePosition(), weightThreshold); } - PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / (float) reccount); + PhysicsRigidBody shapeNode = new PhysicsRigidBody(shape, rootMass / recursionCount); shapeNode.setKinematic(mode == Mode.Kinematic); - totalMass += rootMass / (float) reccount; + totalMass += rootMass / recursionCount; link.rigidBody = shapeNode; link.initalWorldRotation = bone.getModelSpaceRotation().clone(); @@ -721,7 +721,7 @@ protected void boneRecursion(Spatial model, Bone bone, PhysicsRigidBody parent, for (Iterator it = bone.getChildren().iterator(); it.hasNext();) { Bone childBone = it.next(); - boneRecursion(model, childBone, parentShape, reccount + 1, pointsMap); + boneRecursion(model, childBone, parentShape, recursionCount + 1, pointsMap); } } @@ -817,6 +817,7 @@ protected void removePhysics(PhysicsSpace space) { * * @param event (not null) */ + @Override public void collision(PhysicsCollisionEvent event) { PhysicsCollisionObject objA = event.getObjectA(); PhysicsCollisionObject objB = event.getObjectB(); @@ -878,21 +879,21 @@ protected void setMode(Mode mode) { animControl.setEnabled(mode == Mode.Kinematic); baseRigidBody.setKinematic(mode == Mode.Kinematic); - if (mode != Mode.IK) { - TempVars vars = TempVars.get(); - - for (PhysicsBoneLink link : boneLinks.values()) { - link.rigidBody.setKinematic(mode == Mode.Kinematic); - if (mode == Mode.Ragdoll) { - Quaternion tmpRot1 = vars.quat1; - Vector3f position = vars.vect1; - //making sure that the ragdoll is at the correct place. - matchPhysicObjectToBone(link, position, tmpRot1); - } - - } - vars.release(); - } + if (mode != Mode.IK) { + TempVars vars = TempVars.get(); + + for (PhysicsBoneLink link : boneLinks.values()) { + link.rigidBody.setKinematic(mode == Mode.Kinematic); + if (mode == Mode.Ragdoll) { + Quaternion tmpRot1 = vars.quat1; + Vector3f position = vars.vect1; + //making sure that the ragdoll is at the correct place. + matchPhysicObjectToBone(link, position, tmpRot1); + } + + } + vars.release(); + } if(mode != Mode.IK){ for (Bone bone : skeleton.getRoots()) { diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/PhysicsControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/PhysicsControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/PhysicsControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/PhysicsControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/RigidBodyControl.java similarity index 99% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/RigidBodyControl.java index ddcd0e82b1..6377ee413b 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/RigidBodyControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/RigidBodyControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -193,6 +193,7 @@ public void cloneFields( Cloner cloner, Object original ) { * * @param spatial the spatial to control (or null) */ + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -249,6 +250,7 @@ protected void createCollisionShape() { * * @param enabled true→enable the control, false→disable it */ + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (space != null) { @@ -271,6 +273,7 @@ public void setEnabled(boolean enabled) { * * @return true if enabled, otherwise false */ + @Override public boolean isEnabled() { return enabled; } @@ -347,6 +350,7 @@ private Quaternion getSpatialRotation(){ * * @param tpf the time interval between frames (in seconds, ≥0) */ + @Override public void update(float tpf) { if (enabled && spatial != null) { if (isKinematic() && kinematicSpatial) { @@ -366,6 +370,7 @@ public void update(float tpf) { * @param rm the render manager (not null) * @param vp the view port to render (not null) */ + @Override public void render(RenderManager rm, ViewPort vp) { } @@ -401,6 +406,7 @@ public void setPhysicsSpace(PhysicsSpace newSpace) { * * @return the pre-existing space, or null for none */ + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/VehicleControl.java similarity index 98% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/VehicleControl.java index 5188ddf96a..275cb215af 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/VehicleControl.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/VehicleControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -159,6 +159,7 @@ public Control cloneForSpatial(Spatial spatial) { * * @return a new control (not null) */ + @Override public Object jmeClone() { VehicleControl control = new VehicleControl(collisionShape, mass); control.setAngularFactor(getAngularFactor()); @@ -231,6 +232,7 @@ public void cloneFields( Cloner cloner, Object original ) { * * @param spatial spatial to control (or null) */ + @Override public void setSpatial(Spatial spatial) { this.spatial = spatial; setUserObject(spatial); @@ -250,6 +252,7 @@ public void setSpatial(Spatial spatial) { * * @param enabled true→enable the control, false→disable it */ + @Override public void setEnabled(boolean enabled) { this.enabled = enabled; if (space != null) { @@ -272,6 +275,7 @@ public void setEnabled(boolean enabled) { * * @return true if enabled, otherwise false */ + @Override public boolean isEnabled() { return enabled; } @@ -282,6 +286,7 @@ public boolean isEnabled() { * * @param tpf the time interval between frames (in seconds, ≥0) */ + @Override public void update(float tpf) { if (enabled && spatial != null) { if (getMotionState().applyTransform(spatial)) { @@ -301,6 +306,7 @@ public void update(float tpf) { * @param rm the render manager (not null) * @param vp the view port to render (not null) */ + @Override public void render(RenderManager rm, ViewPort vp) { } @@ -336,6 +342,7 @@ public void setPhysicsSpace(PhysicsSpace newSpace) { * * @return the pre-existing space, or null for none */ + @Override public PhysicsSpace getPhysicsSpace() { return space; } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/control/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/package-info.java new file mode 100644 index 0000000000..f73d77c936 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * physics controls: scene-graph controls for linking physics collision objects + * to spatials + */ +package com.jme3.bullet.control; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/HumanoidRagdollPreset.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java similarity index 92% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java index 637011c955..dbcc38df46 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollPreset.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,11 +50,11 @@ public abstract class RagdollPreset { /** * map bone names to joint presets */ - protected Map boneMap = new HashMap(); + protected Map boneMap = new HashMap<>(); /** * lexicon to map bone names to entries */ - protected Map lexicon = new HashMap(); + protected Map lexicon = new HashMap<>(); /** * Initialize the map from bone names to joint presets. @@ -96,11 +96,13 @@ public void setupJointForBone(String boneName, SixDofJoint joint) { JointPreset preset = boneMap.get(resultName); if (preset != null && resultScore >= 50) { - logger.log(Level.FINE, "Found matching joint for bone {0} : {1} with score {2}", new Object[]{boneName, resultName, resultScore}); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Found matching joint for bone {0} : {1} with score {2}", new Object[]{boneName, resultName, resultScore}); + } preset.setupJoint(joint); } else { logger.log(Level.FINE, "No joint match found for bone {0}", boneName); - if (resultScore > 0) { + if (resultScore > 0 && logger.isLoggable(Level.FINE)) { logger.log(Level.FINE, "Best match found is {0} with score {1}", new Object[]{resultName, resultScore}); } new JointPreset().setupJoint(joint); @@ -121,7 +123,6 @@ protected class JointPreset { public JointPreset() { } - public JointPreset(float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { /** * Instantiate a preset with the specified range of motion. * @@ -132,6 +133,7 @@ public JointPreset(float maxX, float minX, float maxY, float minY, float maxZ, f * @param maxZ the maximum rotation on the Z axis (in radians) * @param minZ the minimum rotation on the Z axis (in radians) */ + public JointPreset(float maxX, float minX, float maxY, float minY, float maxZ, float minZ) { this.maxX = maxX; this.minX = minX; this.maxY = maxY; @@ -170,13 +172,13 @@ public void addSynonym(String word, int score) { put(word.toLowerCase(), score); } - public int getScore(String word) { /** * Calculate a total score for the specified bone name. * - * @param name the name of a bone (not null) + * @param word the name of a bone (not null) * @return total score: larger value means more likely to correspond */ + public int getScore(String word) { int score = 0; String searchWord = word.toLowerCase(); for (String key : this.keySet()) { diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java similarity index 96% rename from jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java index 9e354a1fa0..f24ccf5f72 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/RagdollUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -155,7 +155,7 @@ private static Map> buildPointMapForMesh(Mesh mesh, Map> pointsMap, List boneIndices, Vector3f initialScale, Vector3f initialPosition) { - ArrayList points = new ArrayList(); + ArrayList points = new ArrayList<>(); for (Integer index : boneIndices) { List l = pointsMap.get(index); if (l != null) { @@ -192,14 +192,14 @@ public static HullCollisionShape makeShapeFromPointMap(Map> * @return a new list (not null) */ public static List getBoneIndices(Bone bone, Skeleton skeleton, Set boneList) { - List list = new LinkedList(); + List list = new LinkedList<>(); if (boneList.isEmpty()) { list.add(skeleton.getBoneIndex(bone)); } else { list.add(skeleton.getBoneIndex(bone)); - for (Bone chilBone : bone.getChildren()) { - if (!boneList.contains(chilBone.getName())) { - list.addAll(getBoneIndices(chilBone, skeleton, boneList)); + for (Bone childBone : bone.getChildren()) { + if (!boneList.contains(childBone.getName())) { + list.addAll(getBoneIndices(childBone, skeleton, boneList)); } } } @@ -265,7 +265,7 @@ private static List getPoints(Mesh mesh, int boneIndex, Vector3f initialS boneIndices.rewind(); boneWeight.rewind(); - ArrayList results = new ArrayList(); + ArrayList results = new ArrayList<>(); int vertexComponents = mesh.getVertexCount() * 3; @@ -323,7 +323,7 @@ public static void setTransform(Bone bone, Vector3f pos, Quaternion rot, boolean setTransform(childBone, t.getTranslation(), t.getRotation(), restoreBoneControl, boneList); } } - //we give back the control to the keyframed animation + // return control to the keyframe animation if (restoreBoneControl) { bone.setUserControl(false); } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/package-info.java new file mode 100644 index 0000000000..97f9fb923d --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/control/ragdoll/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * supplemental classes related to KinematicRagdollControl + */ +package com.jme3.bullet.control.ragdoll; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/AbstractPhysicsDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletCharacterDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java similarity index 97% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java index 3e2f6aef1b..9bbfb86f5c 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletDebugAppState.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletDebugAppState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,6 +67,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 */ @@ -110,23 +116,23 @@ public class BulletDebugAppState extends AbstractAppState { /** * map rigid bodies to visualizations */ - protected HashMap bodies = new HashMap(); + protected HashMap bodies = new HashMap<>(); /** * map joints to visualizations */ - protected HashMap joints = new HashMap(); + protected HashMap joints = new HashMap<>(); /** * map ghosts to visualizations */ - protected HashMap ghosts = new HashMap(); + protected HashMap ghosts = new HashMap<>(); /** * map physics characters to visualizations */ - protected HashMap characters = new HashMap(); + protected HashMap characters = new HashMap<>(); /** * map vehicles to visualizations */ - protected HashMap vehicles = new HashMap(); + protected HashMap vehicles = new HashMap<>(); /** * Instantiate an app state to visualize the specified space. This constructor should be invoked only by * BulletAppState. @@ -144,7 +150,7 @@ public DebugTools getNewDebugTools() { /** * Alter which objects are visualized. * - * @param filter the desired filter, or or null to visualize all objects + * @param filter the desired filter, or null to visualize all objects */ public void setFilter(DebugAppStateFilter filter) { this.filter = filter; @@ -165,9 +171,12 @@ 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); + } /** @@ -177,8 +186,8 @@ public void initialize(AppStateManager stateManager, Application app) { * is invoked. */ @Override - public void cleanup() { - rm.removeMainView(viewPort); + public void cleanup() { + rm.removeMainView(viewPort); super.cleanup(); } diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletGhostObjectDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletJointDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletJointDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletJointDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletJointDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletRigidBodyDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/BulletVehicleDebugControl.java diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/debug/DebugTools.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/DebugTools.java similarity index 100% rename from jme3-bullet/src/common/java/com/jme3/bullet/debug/DebugTools.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/debug/DebugTools.java diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/debug/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/package-info.java new file mode 100644 index 0000000000..0ff8d4a71e --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/debug/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * visualize physics objects for debugging + */ +package com.jme3.bullet.debug; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java index cd25721d87..c0838f1c55 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/ConeJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,6 +62,8 @@ protected ConeJoint() { } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B */ @@ -73,8 +75,14 @@ public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param rotA the joint orientation in A's local coordinates (rotation + * matrix, alias created) + * @param rotB the joint orientation in B's local coordinates (rotation + * matrix, alias created) */ public ConeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB) { super(nodeA, nodeB, pivotA, pivotB); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java index b918b5d31e..2d6de96013 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/HingeJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,8 +63,15 @@ protected HingeJoint() { /** * Creates a new HingeJoint + * + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param axisA the joint axis in A's local coordinates (unit vector, + * alias created) + * @param axisB the joint axis in B's local coordinates (unit vector, + * alias created) */ public HingeJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Vector3f axisA, Vector3f axisB) { super(nodeA, nodeB, pivotA, pivotB); @@ -125,6 +132,7 @@ public float getHingeAngle() { return ((HingeConstraint) constraint).getHingeAngle(); } + @Override public void write(JmeExporter ex) throws IOException { super.write(ex); OutputCapsule capsule = ex.getCapsule(this); @@ -145,6 +153,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(((HingeConstraint) constraint).getMaxMotorImpulse(), "maxMotorImpulse", 0.0f); } + @Override public void read(JmeImporter im) throws IOException { super.read(im); InputCapsule capsule = im.getCapsule(this); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java index 314c8b4c94..f2f670578a 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/PhysicsJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ import java.io.IOException; /** - *

                  PhysicsJoint - Basic Phyiscs Joint

                  + *

                  PhysicsJoint - Basic Physics Joint

                  * @author normenhansen */ public abstract class PhysicsJoint implements Savable { @@ -54,6 +54,8 @@ protected PhysicsJoint() { } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B */ @@ -85,12 +87,12 @@ public boolean isCollisionBetweenLinkedBodys() { } /** - * toggles collisions between linked bodys
                  - * joint has to be removed from and added to PhyiscsSpace to apply this. - * @param collisionBetweenLinkedBodys set to false to have no collisions between linked bodys + * toggles collisions between linked bodies
                  + * joint has to be removed from and added to PhysicsSpace to apply this. + * @param collisionBetweenLinkedBodies set to false to have no collisions between linked bodies */ - public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodys) { - this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodys; + public void setCollisionBetweenLinkedBodys(boolean collisionBetweenLinkedBodies) { + this.collisionBetweenLinkedBodys = collisionBetweenLinkedBodies; } public PhysicsRigidBody getBodyA() { @@ -110,13 +112,14 @@ public Vector3f getPivotB() { } /** - * destroys this joint and removes it from its connected PhysicsRigidBodys joint lists + * destroys this joint and removes it from its connected PhysicsRigidBody's joint lists */ public void destroy() { getBodyA().removeJoint(this); getBodyB().removeJoint(this); } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule capsule = ex.getCapsule(this); capsule.write(nodeA, "nodeA", null); @@ -125,6 +128,7 @@ public void write(JmeExporter ex) throws IOException { capsule.write(pivotB, "pivotB", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule capsule = im.getCapsule(this); this.nodeA = ((PhysicsRigidBody) capsule.readSavable("nodeA", null)); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java index 58652ab915..bd862f8540 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/Point2PointJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,8 +44,8 @@ /** * From bullet manual:
                  * Point to point constraint, also known as ball socket joint limits the translation - * so that the local pivot points of 2 rigidbodies match in worldspace. - * A chain of rigidbodies can be connected using this constraint. + * so that the local pivot points of 2 rigid bodies match in worldspace. + * A chain of rigid bodies can be connected using this constraint. * @author normenhansen */ public class Point2PointJoint extends PhysicsJoint { @@ -54,6 +54,8 @@ protected Point2PointJoint() { } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B */ diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java index d72f7e2aac..d041689b94 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SixDofJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,9 +50,9 @@ /** * From bullet manual:
                  * This generic constraint can emulate a variety of standard constraints, - * by configuring each of the 6 degrees of freedom (dof). - * The first 3 dof axis are linear axis, which represent translation of rigidbodies, - * and the latter 3 dof axis represent the angular motion. Each axis can be either locked, + * by configuring each of the 6 degrees of freedom (DOF). + * The first 3 DOF axes are linear axes, which represent translation of rigid bodies, + * and the latter 3 DOF axes represent the angular motion. Each axis can be either locked, * free or limited. On construction of a new btGeneric6DofConstraint, all axis are locked. * Afterwards the axis can be reconfigured. Note that several combinations that * include free and/or limited angular degrees of freedom are undefined. @@ -61,7 +61,7 @@ public class SixDofJoint extends PhysicsJoint { private boolean useLinearReferenceFrameA = true; - private LinkedList rotationalMotors = new LinkedList(); + private LinkedList rotationalMotors = new LinkedList<>(); private TranslationalLimitMotor translationalMotor; private Vector3f angularUpperLimit = new Vector3f(Vector3f.POSITIVE_INFINITY); private Vector3f angularLowerLimit = new Vector3f(Vector3f.NEGATIVE_INFINITY); @@ -72,8 +72,15 @@ protected SixDofJoint() { } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param rotA the joint orientation in A's local coordinates (rotation + * matrix, unaffected) + * @param rotB the joint orientation in B's local coordinates (rotation + * matrix, unaffected) + * @param useLinearReferenceFrameA true→use body A, false→use body */ public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { super(nodeA, nodeB, pivotA, pivotB); @@ -92,8 +99,12 @@ public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivo } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param useLinearReferenceFrameA true→use body A, false→use body + * B */ public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) { super(nodeA, nodeB, pivotA, pivotB); @@ -111,8 +122,8 @@ public SixDofJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivo private void gatherMotors() { for (int i = 0; i < 3; i++) { - RotationalLimitMotor rmot = new RotationalLimitMotor(((Generic6DofConstraint) constraint).getRotationalLimitMotor(i)); - rotationalMotors.add(rmot); + RotationalLimitMotor rMotor = new RotationalLimitMotor(((Generic6DofConstraint) constraint).getRotationalLimitMotor(i)); + rotationalMotors.add(rMotor); } translationalMotor = new TranslationalLimitMotor(((Generic6DofConstraint) constraint).getTranslationalLimitMotor()); } diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java index 9b7ade3bf4..7b75ef7789 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/SliderJoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,8 +56,16 @@ protected SliderJoint() { } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param rotA the joint orientation in A's local coordinates (not null, + * alias unaffected) + * @param rotB the joint orientation in B's local coordinates (not null, + * alias unaffected) + * @param useLinearReferenceFrameA true→use body A, false→use body + * B */ public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, Matrix3f rotA, Matrix3f rotB, boolean useLinearReferenceFrameA) { super(nodeA, nodeB, pivotA, pivotB); @@ -68,8 +76,12 @@ public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivo } /** + * @param nodeA the body for the A end (not null, alias created) + * @param nodeB the body for the B end (not null, alias created) * @param pivotA local translation of the joint connection point in node A * @param pivotB local translation of the joint connection point in node B + * @param useLinearReferenceFrameA true→use body A, false→use body + * B */ public SliderJoint(PhysicsRigidBody nodeA, PhysicsRigidBody nodeB, Vector3f pivotA, Vector3f pivotB, boolean useLinearReferenceFrameA) { super(nodeA, nodeB, pivotA, pivotB); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/package-info.java new file mode 100644 index 0000000000..22adb8a329 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/motors/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * motors: supplemental classes related to physics joints + */ +package com.jme3.bullet.joints.motors; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/joints/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/package-info.java new file mode 100644 index 0000000000..086277e53e --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/joints/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * physics joints: join pairs of rigid bodies in order to constrain their + * relative motion + */ +package com.jme3.bullet.joints; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java index 12643fcc77..a5a2fda8b2 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsCharacter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -97,7 +97,8 @@ protected void buildObject() { /** * Sets the location of this physics character - * @param location + * + * @param location the desired physics location (not null, unaffected) */ public void warp(Vector3f location) { character.warp(Converter.convert(location, tempVec)); @@ -149,10 +150,13 @@ public float getJumpSpeed() { return jumpSpeed; } - //does nothing.. -// public void setMaxJumpHeight(float height) { -// character.setMaxJumpHeight(height); -// } + /** + * Alter the character's gravitational acceleration without altering its + * "up" vector. + * + * @param value the desired downward acceleration (in physics-space units + * per second squared, default=29.4) + */ public void setGravity(float value) { character.setGravity(value); } @@ -219,6 +223,7 @@ public void setPhysicsLocation(Vector3f location) { } /** + * @param trans storage for the result (modified if not null) * @return the physicsLocation */ public Vector3f getPhysicsLocation(Vector3f trans) { @@ -261,6 +266,8 @@ public float getCcdSquareMotionThreshold() { /** * used internally + * + * @return the pre-existing object */ public KinematicCharacterController getControllerId() { return character; @@ -268,6 +275,8 @@ public KinematicCharacterController getControllerId() { /** * used internally + * + * @return the pre-existing object */ public PairCachingGhostObject getObjectId() { return gObject; @@ -292,9 +301,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); stepHeight = capsule.readFloat("stepHeight", 1.0f); buildObject(); character = new KinematicCharacterController(gObject, (ConvexShape) collisionShape.getCShape(), stepHeight); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java index 4613e696a7..24c182bbc1 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsGhostObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +66,7 @@ public class PhysicsGhostObject extends PhysicsCollisionObject { protected Transform tempTrans = new Transform(Converter.convert(new Matrix3f())); private com.jme3.math.Transform physicsLocation = new com.jme3.math.Transform(); protected javax.vecmath.Quat4f tempRot = new javax.vecmath.Quat4f(); - private List overlappingObjects = new LinkedList(); + private List overlappingObjects = new LinkedList<>(); protected PhysicsGhostObject() { } @@ -138,6 +138,7 @@ public com.jme3.math.Transform getPhysicsTransform() { } /** + * @param trans storage for the result (modified if not null) * @return the physicsLocation */ public Vector3f getPhysicsLocation(Vector3f trans) { @@ -150,6 +151,7 @@ public Vector3f getPhysicsLocation(Vector3f trans) { } /** + * @param rot storage for the result (modified if not null) * @return the physicsLocation */ public Quaternion getPhysicsRotation(Quaternion rot) { @@ -162,6 +164,7 @@ public Quaternion getPhysicsRotation(Quaternion rot) { } /** + * @param rot storage for the result (modified if not null) * @return the physicsLocation */ public Matrix3f getPhysicsRotationMatrix(Matrix3f rot) { @@ -199,6 +202,8 @@ public Matrix3f getPhysicsRotationMatrix() { /** * used internally + * + * @return the pre-existing instance */ public PairCachingGhostObject getObjectId() { return gObject; @@ -272,9 +277,9 @@ public void write(JmeExporter e) throws IOException { } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule capsule = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule capsule = importer.getCapsule(this); buildObject(); setPhysicsLocation((Vector3f) capsule.readSavable("physicsLocation", new Vector3f())); setPhysicsRotation(((Matrix3f) capsule.readSavable("physicsRotation", new Matrix3f()))); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java index de8adefc69..71e7fe154a 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsRigidBody.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,14 +70,15 @@ public class PhysicsRigidBody extends PhysicsCollisionObject { protected javax.vecmath.Matrix3f tempMatrix = new javax.vecmath.Matrix3f(); //TEMP VARIABLES protected javax.vecmath.Vector3f localInertia = new javax.vecmath.Vector3f(); - protected ArrayList joints = new ArrayList(); + protected ArrayList joints = new ArrayList<>(); protected PhysicsRigidBody() { } /** * Creates a new PhysicsRigidBody with the supplied collision shape - * @param shape + * + * @param shape the desired shape (not null, alias created) */ public PhysicsRigidBody(CollisionShape shape) { collisionShape = shape; @@ -91,12 +92,12 @@ public PhysicsRigidBody(CollisionShape shape, float mass) { } /** - * Builds/rebuilds the phyiscs body when parameters have changed + * Builds/rebuilds the physics body when parameters have changed */ protected void rebuildRigidBody() { boolean removed = false; if(collisionShape instanceof MeshCollisionShape && mass != 0){ - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + throw new IllegalStateException("Dynamic rigid body cannot have mesh collision shape!"); } if (rBody != null) { if (rBody.isInWorld()) { @@ -175,6 +176,8 @@ public void setPhysicsRotation(Quaternion rotation) { /** * Gets the physics object location, instantiates a new Vector3f object + * + * @return a new location vector (in physics-space coordinates, not null) */ public Vector3f getPhysicsLocation() { return getPhysicsLocation(null); @@ -182,6 +185,8 @@ public Vector3f getPhysicsLocation() { /** * Gets the physics object rotation + * + * @return a new rotation matrix (in physics-space coordinates, not null) */ public Matrix3f getPhysicsRotationMatrix() { return getPhysicsRotationMatrix(null); @@ -190,6 +195,8 @@ public Matrix3f getPhysicsRotationMatrix() { /** * Gets the physics object location, no object instantiation * @param location the location of the actual physics object is stored in this Vector3f + * @return a location vector (in physics-space coordinates, either + * location or a new vector) */ public Vector3f getPhysicsLocation(Vector3f location) { if (location == null) { @@ -202,6 +209,8 @@ public Vector3f getPhysicsLocation(Vector3f location) { /** * Gets the physics object rotation as a matrix, no conversions and no object instantiation * @param rotation the rotation of the actual physics object is stored in this Matrix3f + * @return a rotation matrix (in physics-space coordinates, either + * rotation or a new matrix) */ public Matrix3f getPhysicsRotationMatrix(Matrix3f rotation) { if (rotation == null) { @@ -214,6 +223,8 @@ public Matrix3f getPhysicsRotationMatrix(Matrix3f rotation) { /** * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value, * instantiates new object + * + * @return a new rotation Quaternion (in physics-space coordinates) */ public Quaternion getPhysicsRotation(){ return getPhysicsRotation(null); @@ -222,6 +233,8 @@ public Quaternion getPhysicsRotation(){ /** * Gets the physics object rotation as a quaternion, converts the bullet Matrix3f value * @param rotation the rotation of the actual physics object is stored in this Quaternion + * @return a rotation Quaternion (in physics-space coordinates, either + * rotation or a new instance) */ public Quaternion getPhysicsRotation(Quaternion rotation){ if (rotation == null) { @@ -234,6 +247,8 @@ public Quaternion getPhysicsRotation(Quaternion rotation){ /** * Gets the physics object location * @param location the location of the actual physics object is stored in this Vector3f + * @return a location vector (in physics-space coordinates, either + * location or a new vector) */ public Vector3f getInterpolatedPhysicsLocation(Vector3f location) { if (location == null) { @@ -246,6 +261,8 @@ public Vector3f getInterpolatedPhysicsLocation(Vector3f location) { /** * Gets the physics object rotation * @param rotation the rotation of the actual physics object is stored in this Matrix3f + * @return a rotation matrix (in physics-space coordinates, either + * rotation or a new matrix) */ public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) { if (rotation == null) { @@ -259,7 +276,9 @@ public Matrix3f getInterpolatedPhysicsRotation(Matrix3f rotation) { * Sets the node to kinematic mode. in this mode the node is not affected by physics * but affects other physics objects. Its kinetic force is calculated by the amount * of movement it is exposed to and its weight. - * @param kinematic + * + * @param kinematic true→set kinematic mode, false→set dynamic + * (default=false) */ public void setKinematic(boolean kinematic) { this.kinematic = kinematic; @@ -308,9 +327,11 @@ public void setCcdSweptSphereRadius(float radius) { } /** - * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection
                  + * Sets the amount of motion that has to happen in one physics tick to trigger the continuous motion detection
                  * This avoids the problem of fast objects moving through other objects, set to zero to disable (default) - * @param threshold + * + * @param threshold the desired minimum distance per timestep to trigger CCD + * (in physics-space units, >0) or zero to disable CCD (default=0) */ public void setCcdMotionThreshold(float threshold) { rBody.setCcdMotionThreshold(threshold); @@ -334,12 +355,13 @@ public float getMass() { /** * Sets the mass of this PhysicsRigidBody, objects with mass=0 are static. - * @param mass + * + * @param mass the desired mass (>0) or 0 for a static body (default=1) */ public void setMass(float mass) { this.mass = mass; if(collisionShape instanceof MeshCollisionShape && mass != 0){ - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + throw new IllegalStateException("Dynamic rigid body cannot have mesh collision shape!"); } if (collisionShape != null) { collisionShape.calculateLocalInertia(mass, localInertia); @@ -367,9 +389,9 @@ public Vector3f getGravity(Vector3f gravity) { } /** - * Set the local gravity of this PhysicsRigidBody
                  + * Set the local gravity of this PhysicsRigidBody
                  * Set this after adding the node to the PhysicsSpace, - * the PhysicsSpace assigns its current gravity to the physics node when its added. + * the PhysicsSpace assigns its current gravity to the physics node when it's added. * @param gravity the gravity vector to set */ public void setGravity(Vector3f gravity) { @@ -418,8 +440,9 @@ public float getRestitution() { } /** - * The "bouncyness" of the PhysicsRigidBody, best performance if restitution=0 - * @param restitution + * The "bounciness" of the PhysicsRigidBody. Best performance with restitution=0. + * + * @param restitution the desired value (default=0) */ public void setRestitution(float restitution) { constructionInfo.restitution = restitution; @@ -515,16 +538,18 @@ public void applyTorque(final Vector3f torque) { /** * Apply an impulse to the PhysicsRigidBody in the next physics update. * @param impulse applied impulse - * @param rel_pos location relative to object + * @param relativePosition location relative to object */ - public void applyImpulse(final Vector3f impulse, final Vector3f rel_pos) { - rBody.applyImpulse(Converter.convert(impulse, tempVec), Converter.convert(rel_pos, tempVec2)); + public void applyImpulse(final Vector3f impulse, final Vector3f relativePosition) { + rBody.applyImpulse(Converter.convert(impulse, tempVec), Converter.convert(relativePosition, tempVec2)); rBody.activate(); } /** * Apply a torque impulse to the PhysicsRigidBody in the next physics update. - * @param vec + * + * @param vec the torque impulse vector (in physics-space coordinates, + * not null, unaffected) */ public void applyTorqueImpulse(final Vector3f vec) { rBody.applyTorqueImpulse(Converter.convert(vec, tempVec)); @@ -539,10 +564,11 @@ public void clearForces() { rBody.clearForces(); } + @Override public void setCollisionShape(CollisionShape collisionShape) { super.setCollisionShape(collisionShape); if(collisionShape instanceof MeshCollisionShape && mass!=0){ - throw new IllegalStateException("Dynamic rigidbody can not have mesh collision shape!"); + throw new IllegalStateException("Dynamic rigid body cannot have mesh collision shape!"); } if (rBody == null) { rebuildRigidBody(); @@ -566,7 +592,7 @@ public boolean isActive() { /** * sets the sleeping thresholds, these define when the object gets deactivated - * to save ressources. Low values keep the object active when it barely moves + * to save resources. Low values keep the object active when it barely moves * @param linear the linear sleeping threshold * @param angular the angular sleeping threshold */ @@ -604,6 +630,8 @@ public void setAngularFactor(float factor) { /** * do not use manually, joints are added automatically + * + * @param joint the joint to add (not null, alias created) */ public void addJoint(PhysicsJoint joint) { if (!joints.contains(joint)) { @@ -612,7 +640,7 @@ public void addJoint(PhysicsJoint joint) { } /** - * + * @param joint the joint to remove (not null, unaffected) */ public void removeJoint(PhysicsJoint joint) { joints.remove(joint); @@ -629,6 +657,8 @@ public List getJoints() { /** * used internally + * + * @return the pre-existing object */ public RigidBody getObjectId() { return rBody; @@ -679,14 +709,15 @@ public void write(JmeExporter e) throws IOException { /** * De-serialize this body, for example when loading from a J3O file. * - * @param e importer (not null) + * @param importer importer (not null) * @throws IOException from importer */ @Override - public void read(JmeImporter e) throws IOException { - super.read(e); + @SuppressWarnings("unchecked") + public void read(JmeImporter importer) throws IOException { + super.read(importer); - InputCapsule capsule = e.getCapsule(this); + InputCapsule capsule = importer.getCapsule(this); float mass = capsule.readFloat("mass", 1.0f); this.mass = mass; rebuildRigidBody(); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java index 8b53342b79..265d5eb5a5 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/PhysicsVehicle.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,7 +53,7 @@ * vehicle model as provided in btRaycastVehicle. Instead of simulation each wheel * and chassis as separate rigid bodies, connected by constraints, it uses a simplified model. * This simplified model has many benefits, and is widely used in commercial driving games.
                  - * The entire vehicle is represented as a single rigidbody, the chassis. + * The entire vehicle is represented as a single rigid body, the chassis. * The collision detection of the wheels is approximated by ray casts, * and the tire friction is a basic anisotropic friction model. *

                  @@ -64,7 +64,7 @@ public class PhysicsVehicle extends PhysicsRigidBody { protected RaycastVehicle vehicle; protected VehicleTuning tuning; protected VehicleRaycaster rayCaster; - protected ArrayList wheels = new ArrayList(); + protected ArrayList wheels = new ArrayList<>(); protected PhysicsSpace physicsSpace; protected PhysicsVehicle() { @@ -115,7 +115,9 @@ protected void postRebuild() { } /** - * Used internally, creates the actual vehicle constraint when vehicle is added to phyicsspace + * Used internally, creates the actual vehicle constraint when vehicle is added to physics space + * + * @param space the PhysicsSpace to use (alias created) or null for none */ public void createVehicle(PhysicsSpace space) { physicsSpace = space; @@ -180,7 +182,8 @@ public VehicleWheel addWheel(Spatial spat, Vector3f connectionPoint, Vector3f di /** * This rebuilds the vehicle as there is no way in bullet to remove a wheel. - * @param wheel + * + * @param wheel the index of the wheel to remove (≥0, <count) */ public void removeWheel(int wheel) { wheels.remove(wheel); @@ -211,8 +214,8 @@ public float getFrictionSlip() { * Use before adding wheels, this is the default used when adding wheels. * After adding the wheel, use direct wheel access.
                  * The coefficient of friction between the tyre and the ground. - * Should be about 0.8 for realistic cars, but can increased for better handling. - * Set large (10000.0) for kart racers + * Should be about 0.8 for realistic cars, but can be increased for better handling. + * Set large (10000.0) for kart racers. * @param frictionSlip the frictionSlip to set */ public void setFrictionSlip(float frictionSlip) { @@ -221,10 +224,12 @@ public void setFrictionSlip(float frictionSlip) { /** * The coefficient of friction between the tyre and the ground. - * Should be about 0.8 for realistic cars, but can increased for better handling. - * Set large (10000.0) for kart racers - * @param wheel - * @param frictionSlip + * Should be about 0.8 for realistic cars, but can be increased for better handling. + * Set large (10000.0) for kart racers. + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param frictionSlip the desired coefficient of friction between tyre and + * ground (0.8→realistic car, 10000→kart racer, default=10.5) */ public void setFrictionSlip(int wheel, float frictionSlip) { wheels.get(wheel).setFrictionSlip(frictionSlip); @@ -235,6 +240,10 @@ public void setFrictionSlip(int wheel, float frictionSlip) { * This is a bit of a hack, but it's quite effective. 0.0 = no roll, 1.0 = physical behaviour. * If m_frictionSlip is too high, you'll need to reduce this to stop the vehicle rolling over. * You should also try lowering the vehicle's centre of mass + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param rollInfluence the desired roll-influence factor (0→no roll + * torque, 1→realistic behavior, default=1) */ public void setRollInfluence(int wheel, float rollInfluence) { wheels.get(wheel).setRollInfluence(rollInfluence); @@ -259,8 +268,11 @@ public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { /** * The maximum distance the suspension can be compressed (centimetres) - * @param wheel - * @param maxSuspensionTravelCm + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param maxSuspensionTravelCm the desired maximum amount a suspension can + * be compressed or expanded, relative to its rest length (in hundredths of + * a physics-space unit, default=500) */ public void setMaxSuspensionTravelCm(int wheel, float maxSuspensionTravelCm) { wheels.get(wheel).setMaxSuspensionTravelCm(maxSuspensionTravelCm); @@ -273,7 +285,9 @@ public float getMaxSuspensionForce() { /** * This value caps the maximum suspension force, raise this above the default 6000 if your suspension cannot * handle the weight of your vehicle. - * @param maxSuspensionForce + * + * @param maxSuspensionForce the desired maximum force per wheel + * (default=6000) */ public void setMaxSuspensionForce(float maxSuspensionForce) { tuning.maxSuspensionForce = maxSuspensionForce; @@ -282,8 +296,10 @@ public void setMaxSuspensionForce(float maxSuspensionForce) { /** * This value caps the maximum suspension force, raise this above the default 6000 if your suspension cannot * handle the weight of your vehicle. - * @param wheel - * @param maxSuspensionForce + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param maxSuspensionForce the desired maximum force per wheel + * (default=6000) */ public void setMaxSuspensionForce(int wheel, float maxSuspensionForce) { wheels.get(wheel).setMaxSuspensionForce(maxSuspensionForce); @@ -314,8 +330,10 @@ public void setSuspensionCompression(float suspensionCompression) { * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.
                  * k = 0.0 undamped/bouncy, k = 1.0 critical damping
                  * 0.1 to 0.3 are good values - * @param wheel - * @param suspensionCompression + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param suspensionCompression the desired damping coefficient + * (default=4.4) */ public void setSuspensionCompression(int wheel, float suspensionCompression) { wheels.get(wheel).setWheelsDampingCompression(suspensionCompression); @@ -342,8 +360,9 @@ public void setSuspensionDamping(float suspensionDamping) { /** * The damping coefficient for when the suspension is expanding. * See the comments for setSuspensionCompression for how to set k. - * @param wheel - * @param suspensionDamping + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param suspensionDamping the desired damping coefficient (default=2.3) */ public void setSuspensionDamping(int wheel, float suspensionDamping) { wheels.get(wheel).setWheelsDampingRelaxation(suspensionDamping); @@ -359,17 +378,23 @@ public float getSuspensionStiffness() { /** * Use before adding wheels, this is the default used when adding wheels. * After adding the wheel, use direct wheel access.
                  - * The stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car - * @param suspensionStiffness + * The stiffness constant for the suspension. 10.0 - Off-road buggy, 50.0 - Sports car, 200.0 - F1 Car + * + * @param suspensionStiffness the desired stiffness coefficient + * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, + * default=5.88) */ public void setSuspensionStiffness(float suspensionStiffness) { tuning.suspensionStiffness = suspensionStiffness; } /** - * The stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car - * @param wheel - * @param suspensionStiffness + * The stiffness constant for the suspension. 10.0 - Off-road buggy, 50.0 - Sports car, 200.0 - F1 Car + * + * @param wheel the index of the wheel to modify (≥0, <count) + * @param suspensionStiffness the desired stiffness coefficient + * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, + * default=5.88) */ public void setSuspensionStiffness(int wheel, float suspensionStiffness) { wheels.get(wheel).setSuspensionStiffness(suspensionStiffness); @@ -466,6 +491,8 @@ public Vector3f getForwardVector(Vector3f vector) { /** * used internally + * + * @return the pre-existing instance */ public RaycastVehicle getVehicleId() { return vehicle; @@ -477,6 +504,7 @@ public void destroy() { } @Override + @SuppressWarnings("unchecked") public void read(JmeImporter im) throws IOException { InputCapsule capsule = im.getCapsule(this); tuning = new VehicleTuning(); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java index b2f0db305e..8f254f9d3f 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/VehicleWheel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -150,7 +150,10 @@ public float getSuspensionStiffness() { /** * the stiffness constant for the suspension. 10.0 - Offroad buggy, 50.0 - Sports car, 200.0 - F1 Car - * @param suspensionStiffness + * + * @param suspensionStiffness the desired stiffness coefficient + * (10→off-road buggy, 50→sports car, 200→Formula-1 race car, + * default=20) */ public void setSuspensionStiffness(float suspensionStiffness) { this.suspensionStiffness = suspensionStiffness; @@ -164,7 +167,9 @@ public float getWheelsDampingRelaxation() { /** * the damping coefficient for when the suspension is expanding. * See the comments for setWheelsDampingCompression for how to set k. - * @param wheelsDampingRelaxation + * + * @param wheelsDampingRelaxation the desired damping coefficient + * (default=2.3) */ public void setWheelsDampingRelaxation(float wheelsDampingRelaxation) { this.wheelsDampingRelaxation = wheelsDampingRelaxation; @@ -180,7 +185,9 @@ public float getWheelsDampingCompression() { * Set to k * 2.0 * FastMath.sqrt(m_suspensionStiffness) so k is proportional to critical damping.
                  * k = 0.0 undamped/bouncy, k = 1.0 critical damping
                  * 0.1 to 0.3 are good values - * @param wheelsDampingCompression + * + * @param wheelsDampingCompression the desired damping coefficient + * (default=4.4) */ public void setWheelsDampingCompression(float wheelsDampingCompression) { this.wheelsDampingCompression = wheelsDampingCompression; @@ -193,9 +200,11 @@ public float getFrictionSlip() { /** * the coefficient of friction between the tyre and the ground. - * Should be about 0.8 for realistic cars, but can increased for better handling. + * Should be about 0.8 for realistic cars, but can be increased for better handling. * Set large (10000.0) for kart racers - * @param frictionSlip + * + * @param frictionSlip the desired coefficient of friction between tyre and + * ground (0.8→realistic car, 10000→kart racer, default=10.5) */ public void setFrictionSlip(float frictionSlip) { this.frictionSlip = frictionSlip; @@ -224,7 +233,10 @@ public float getMaxSuspensionTravelCm() { /** * the maximum distance the suspension can be compressed (centimetres) - * @param maxSuspensionTravelCm + * + * @param maxSuspensionTravelCm the desired maximum amount the suspension + * can be compressed or expanded, relative to its rest length (in hundredths + * of a physics-space unit, default=500) */ public void setMaxSuspensionTravelCm(float maxSuspensionTravelCm) { this.maxSuspensionTravelCm = maxSuspensionTravelCm; @@ -238,7 +250,9 @@ public float getMaxSuspensionForce() { /** * The maximum suspension force, raise this above the default 6000 if your suspension cannot * handle the weight of your vehicle. - * @param maxSuspensionForce + * + * @param maxSuspensionForce the desired maximum force per wheel + * (default=6000) */ public void setMaxSuspensionForce(float maxSuspensionForce) { this.maxSuspensionForce = maxSuspensionForce; @@ -296,6 +310,9 @@ public PhysicsCollisionObject getGroundObject() { /** * returns the location where the wheel collides with the ground (world space) + * + * @param vec storage for the result (not null, modified) + * @return a location vector (in physics-space coordinates) */ public Vector3f getCollisionLocation(Vector3f vec) { Converter.convert(wheelInfo.raycastInfo.contactPointWS, vec); @@ -304,6 +321,8 @@ public Vector3f getCollisionLocation(Vector3f vec) { /** * returns the location where the wheel collides with the ground (world space) + * + * @return a new location vector (in physics-space coordinates) */ public Vector3f getCollisionLocation() { return Converter.convert(wheelInfo.raycastInfo.contactPointWS); @@ -311,6 +330,9 @@ public Vector3f getCollisionLocation() { /** * returns the normal where the wheel collides with the ground (world space) + * + * @param vec storage for the result (not null, modified) + * @return a unit vector (in physics-space coordinates) */ public Vector3f getCollisionNormal(Vector3f vec) { Converter.convert(wheelInfo.raycastInfo.contactNormalWS, vec); @@ -319,6 +341,8 @@ public Vector3f getCollisionNormal(Vector3f vec) { /** * returns the normal where the wheel collides with the ground (world space) + * + * @return a new unit vector (in physics-space coordinates) */ public Vector3f getCollisionNormal() { return Converter.convert(wheelInfo.raycastInfo.contactNormalWS); @@ -327,6 +351,9 @@ public Vector3f getCollisionNormal() { /** * returns how much the wheel skids on the ground (for skid sounds/smoke etc.)
                  * 0.0 = wheels are sliding, 1.0 = wheels have traction. + * + * @return the relative amount of traction (0→wheel is sliding, + * 1→wheel has full traction) */ public float getSkidInfo() { return wheelInfo.skidInfo; @@ -335,6 +362,8 @@ public float getSkidInfo() { /** * returns how many degrees the wheel has turned since the last physics * step. + * + * @return the rotation angle (in radians) */ public float getDeltaRotation() { return wheelInfo.deltaRotation; @@ -403,7 +432,7 @@ public void setApplyLocal(boolean applyLocal) { /** * write the content of the wheelWorldRotation into the store * - * @param store + * @param store storage for the result (not null, modified) */ public void getWheelWorldRotation(final Quaternion store) { store.set(this.wheelWorldRotation); @@ -412,7 +441,7 @@ public void getWheelWorldRotation(final Quaternion store) { /** * write the content of the wheelWorldLocation into the store * - * @param store + * @param store storage for the result (not null, modified) */ public void getWheelWorldLocation(final Vector3f store) { store.set(this.wheelWorldLocation); diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java index 3249e96074..5ca251eb41 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/RigidBodyMotionState.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,7 +42,7 @@ /** * stores transform info of a PhysicsNode in a threadsafe manner to - * allow multithreaded access from the jme scenegraph and the bullet physicsspace + * allow multithreaded access from the jme scenegraph and the bullet physics space * @author normenhansen */ public class RigidBodyMotionState extends MotionState { @@ -67,19 +67,22 @@ public RigidBodyMotionState() { } /** - * called from bullet when creating the rigidbody + * Called from Bullet when creating the rigid body. * @param t caller-provided storage for the Transform * @return t */ + @Override public Transform getWorldTransform(Transform t) { t.set(motionStateTrans); return t; } /** - * called from bullet when the transform of the rigidbody changes - * @param worldTrans + * Called from Bullet when the transform of the rigid body changes. + * + * @param worldTrans the new value (not null, unaffected) */ + @Override public void setWorldTransform(Transform worldTrans) { if (jmeLocationDirty) { return; @@ -100,7 +103,9 @@ public void setWorldTransform(Transform worldTrans) { /** * applies the current transform to the given jme Node if the location has been updated on the physics side - * @param spatial + * + * @param spatial where to apply the physics transform (not null, modified) + * @return true if changed */ public boolean applyTransform(Spatial spatial) { if (!physicsLocationDirty) { diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/package-info.java new file mode 100644 index 0000000000..20b7686e92 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/infos/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * supplemental classes related to physics collision objects + */ +package com.jme3.bullet.objects.infos; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/objects/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/package-info.java new file mode 100644 index 0000000000..0e1a3dba36 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/objects/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * physics collision objects, including vehicles + */ +package com.jme3.bullet.objects; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/package-info.java new file mode 100644 index 0000000000..f2f7515340 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * central classes of the physics system + */ +package com.jme3.bullet; diff --git a/jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/CollisionShapeFactory.java similarity index 96% rename from jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java rename to jme3-jbullet/src/main/java/com/jme3/bullet/util/CollisionShapeFactory.java index 19e14d49be..80d71c53ce 100644 --- a/jme3-bullet/src/common/java/com/jme3/bullet/util/CollisionShapeFactory.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/CollisionShapeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,6 +51,11 @@ * @author normenhansen, tim8dev */ public class CollisionShapeFactory { + /** + * A private constructor to inhibit instantiation of this class. + */ + private CollisionShapeFactory() { + } /** * Calculate the correct transform for a collision shape relative to the @@ -180,7 +185,7 @@ private static CompoundCollisionShape createBoxCompoundShape(Node rootNode) { * @param spatial the spatial on which to base the shape (not null) * @return A MeshCollisionShape or a CompoundCollisionShape with * MeshCollisionShapes as children if the supplied spatial is a Node. A - * HeightieldCollisionShape if a TerrainQuad was supplied. + * HeightfieldCollisionShape if a TerrainQuad was supplied. */ public static CollisionShape createMeshShape(Spatial spatial) { if (spatial instanceof TerrainQuad) { @@ -229,7 +234,7 @@ public static CollisionShape createDynamicMeshShape(Spatial spatial) { */ public static CollisionShape createBoxShape(Spatial spatial) { if (spatial instanceof Geometry) { - return createSingleBoxShape((Geometry) spatial, spatial); + return createSingleBoxShape(spatial, spatial); } else if (spatial instanceof Node) { return createBoxCompoundShape((Node) spatial); } else { @@ -293,10 +298,12 @@ private static HullCollisionShape createSingleDynamicMeshShape(Geometry geom, Sp /** * This method moves each child shape of a compound shape by the given vector - * @param vector + * + * @param compoundShape the shape to modify (not null) + * @param vector the offset vector (not null, unaffected) */ public static void shiftCompoundShapeContents(CompoundCollisionShape compoundShape, Vector3f vector) { - for (Iterator it = new LinkedList(compoundShape.getChildren()).iterator(); it.hasNext();) { + for (Iterator it = new LinkedList<>(compoundShape.getChildren()).iterator(); it.hasNext();) { ChildCollisionShape childCollisionShape = it.next(); CollisionShape child = childCollisionShape.shape; Vector3f location = childCollisionShape.location; diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java index f9956d0804..096c926e8d 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/Converter.java @@ -259,11 +259,11 @@ public static Mesh convert(IndexedMesh mesh) { jmeMesh.setBuffer(Type.Index, 3, BufferUtils.createShortBuffer(mesh.numTriangles * 3)); jmeMesh.setBuffer(Type.Position, 3, BufferUtils.createFloatBuffer(mesh.numVertices * 3)); - IndexBuffer indicess = jmeMesh.getIndexBuffer(); + IndexBuffer indices = jmeMesh.getIndexBuffer(); FloatBuffer vertices = jmeMesh.getFloatBuffer(Type.Position); for (int i = 0; i < mesh.numTriangles * 3; i++) { - indicess.put(i, mesh.triangleIndexBase.getInt(i * 4)); + indices.put(i, mesh.triangleIndexBase.getInt(i * 4)); } for (int i = 0; i < mesh.numVertices * 3; i++) { diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java index ab1e7b9b10..3e74f1ab3c 100644 --- a/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/DebugShapeFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,10 +64,17 @@ public class DebugShapeFactory { /** The minimum corner for the aabb used for triangles to include in ConcaveShape processing.*/ private static final Vector3f aabbMin = new Vector3f(-1e30f, -1e30f, -1e30f); + /** + * A private constructor to inhibit instantiation of this class. + */ + private DebugShapeFactory() { + } + /** * Creates a debug shape from the given collision shape. This is mostly used internally.
                  * To attach a debug shape to a physics object, call attachDebugShape(AssetManager manager); on it. - * @param collisionShape + * + * @param collisionShape the CollisionShape to use or null * @return a new Spatial or null */ public static Spatial getDebugShape(CollisionShape collisionShape) { @@ -144,7 +151,7 @@ private static FloatBuffer getVertices(ConcaveShape concaveShape) { BufferedTriangleCallback triangleProcessor = new BufferedTriangleCallback(); concaveShape.processAllTriangles(triangleProcessor, aabbMin, aabbMax); - // Retrieve the vextex and index buffers + // Retrieve the vertex and index buffers return triangleProcessor.getVertices(); } @@ -177,22 +184,22 @@ private static FloatBuffer getVertices(ConvexShape convexShape) { final int numberOfFloats = 3 * 3 * numberOfTriangles; FloatBuffer vertices = BufferUtils.createFloatBuffer(numberOfFloats); - // Force the limit, set the cap - most number of floats we will use the buffer for + // Force the limit, set the cap - the largest number of floats we will use the buffer for vertices.limit(numberOfFloats); // Loop variables - final IntArrayList hullIndicies = hull.getIndexPointer(); + final IntArrayList hullIndices = hull.getIndexPointer(); final List hullVertices = hull.getVertexPointer(); Vector3f vertexA, vertexB, vertexC; int index = 0; for (int i = 0; i < numberOfTriangles; i++) { // Grab the data for this triangle from the hull - vertexA = hullVertices.get(hullIndicies.get(index++)); - vertexB = hullVertices.get(hullIndicies.get(index++)); - vertexC = hullVertices.get(hullIndicies.get(index++)); + vertexA = hullVertices.get(hullIndices.get(index++)); + vertexB = hullVertices.get(hullIndices.get(index++)); + vertexC = hullVertices.get(hullIndices.get(index++)); - // Put the verticies into the vertex buffer + // Put the vertices into the vertex buffer vertices.put(vertexA.x).put(vertexA.y).put(vertexA.z); vertices.put(vertexB.x).put(vertexB.y).put(vertexB.z); vertices.put(vertexC.x).put(vertexC.y).put(vertexC.z); @@ -205,7 +212,7 @@ private static FloatBuffer getVertices(ConvexShape convexShape) { /** * A callback is used to process the triangles of the shape as there is no direct access to a concave shapes, shape. - *

                  + *

                  * The triangles are simply put into a list (which in extreme condition will cause memory problems) then put into a direct buffer. * * @author CJ Hare @@ -235,7 +242,7 @@ public FloatBuffer getVertices() { final int numberOfFloats = vertices.size() * 3; FloatBuffer verticesBuffer = BufferUtils.createFloatBuffer(numberOfFloats); - // Force the limit, set the cap - most number of floats we will use the buffer for + // Force the limit, set the cap - the largest number of floats we will use the buffer for verticesBuffer.limit(numberOfFloats); // Copy the values from the list to the direct float buffer diff --git a/jme3-jbullet/src/main/java/com/jme3/bullet/util/package-info.java b/jme3-jbullet/src/main/java/com/jme3/bullet/util/package-info.java new file mode 100644 index 0000000000..5aecdec428 --- /dev/null +++ b/jme3-jbullet/src/main/java/com/jme3/bullet/util/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * utility classes related to Bullet physics + */ +package com.jme3.bullet.util; diff --git a/jme3-jbullet/src/test/java/com/jme3/jbullet/test/PreventBulletIssueRegressions.java b/jme3-jbullet/src/test/java/com/jme3/jbullet/test/PreventBulletIssueRegressions.java new file mode 100644 index 0000000000..bb704578e9 --- /dev/null +++ b/jme3-jbullet/src/test/java/com/jme3/jbullet/test/PreventBulletIssueRegressions.java @@ -0,0 +1,204 @@ +/* + * Copyright (c) 2019-2021 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.jbullet.test; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.bullet.BulletAppState; +import com.jme3.bullet.PhysicsSpace; +import com.jme3.bullet.PhysicsTickListener; +import com.jme3.bullet.collision.shapes.BoxCollisionShape; +import com.jme3.bullet.collision.shapes.CollisionShape; +import com.jme3.bullet.collision.shapes.SphereCollisionShape; +import com.jme3.bullet.control.BetterCharacterControl; +import com.jme3.bullet.control.GhostControl; +import com.jme3.bullet.control.KinematicRagdollControl; +import com.jme3.bullet.control.RigidBodyControl; +import com.jme3.bullet.objects.PhysicsRigidBody; +import com.jme3.export.JmeExporter; +import com.jme3.export.JmeImporter; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.VertexBuffer; +import org.junit.Assert; +import org.junit.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.ByteBuffer; +import java.util.concurrent.ConcurrentLinkedQueue; + +/** + * The Test Suite to prevent regressions from previously fixed Bullet issues + * @author Stephen Gold <sgold@sonic.net> + */ +public class PreventBulletIssueRegressions { + /** + * Test case for JME issue #889: disabled physics control gets added to a + * physics space. + */ + @SuppressWarnings("unchecked") + @Test + public void testIssue889() throws IllegalAccessException, NoSuchFieldException { + // throws are added so that we don't have to catch them just to assert them again. + // If they throw, the Unit Test should fail + Field f1 = PhysicsSpace.class.getDeclaredField("tickListeners"); + f1.setAccessible(true); + Field f2 = BetterCharacterControl.class.getDeclaredField("rigidBody"); + f2.setAccessible(true); + + BulletAppState bulletAppState = new BulletAppState(); + bulletAppState.setSpeed(0f); + bulletAppState.startPhysics(); // so that we don't need an AppStateManager etc + PhysicsSpace space = bulletAppState.getPhysicsSpace(); + ConcurrentLinkedQueue tickListeners = (ConcurrentLinkedQueue)f1.get(space); + + float radius = 1f; + CollisionShape sphere = new SphereCollisionShape(radius); + CollisionShape box = new BoxCollisionShape(Vector3f.UNIT_XYZ); + + Node rootNode = new Node("RootNode"); + + RigidBodyControl rbc = new RigidBodyControl(box); + rbc.setEnabled(false); + rbc.setPhysicsSpace(space); + rootNode.addControl(rbc); + + BetterCharacterControl bcc = new BetterCharacterControl(radius, 4f, 1f); + bcc.setEnabled(false); + bcc.setPhysicsSpace(space); + rootNode.addControl(bcc); + PhysicsRigidBody bcc_rb = (PhysicsRigidBody)f2.get(bcc); + + GhostControl gc = new GhostControl(sphere); + gc.setEnabled(false); + gc.setPhysicsSpace(space); + rootNode.addControl(gc); + + Assert.assertFalse(space.getRigidBodyList().contains(rbc)); + Assert.assertFalse(tickListeners.contains(bcc)); + Assert.assertFalse(space.getRigidBodyList().contains(bcc_rb)); + Assert.assertFalse(space.getGhostObjectList().contains(gc)); + } + + /** + * Test case for JME issue #931: RagdollUtils can miss model meshes or use the + * non-animated ones. + */ + @Test + public void testIssue931() { + Node sinbad = (Node)new DesktopAssetManager(true).loadModel("Models/Sinbad/SinbadOldAnim.j3o"); + Node extender = new Node(); + for (Spatial child : sinbad.getChildren()) { + extender.attachChild(child); + } + sinbad.attachChild(extender); + + //Note: PhysicsRagdollControl is still a WIP, constructor will change + KinematicRagdollControl ragdoll = new KinematicRagdollControl(0.5f); + sinbad.addControl(ragdoll); + } + + /** + * Test case for JME issue #970: RigidBodyControl doesn't read/write velocities. + * Clone a body that implements Control by saving and then loading it. + * */ + @Test + public void testIssue970() throws IOException { + // throws are added so that we don't have to catch them just to assert them again. + // If they throw, the Unit Test should fail + CollisionShape shape = new SphereCollisionShape(1f); + RigidBodyControl rbc = new RigidBodyControl(shape, 1f); + rbc.setAngularVelocity(new Vector3f(0.04f, 0.05f, 0.06f)); + rbc.setLinearVelocity(new Vector3f(0.26f, 0.27f, 0.28f)); + + Assert.assertEquals(new Vector3f(0.04f, 0.05f, 0.06f), rbc.getAngularVelocity()); + Assert.assertEquals(new Vector3f(0.26f, 0.27f, 0.28f), rbc.getLinearVelocity()); + + // Write/Serialize the RBC + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + JmeExporter exporter = BinaryExporter.getInstance(); + exporter.save(rbc, baos); + + // Load/Deserialize the RBC + JmeImporter importer = BinaryImporter.getInstance(); + RigidBodyControl rbcCopy = (RigidBodyControl)importer.load(new AssetInfo(null, null) { + @Override + public InputStream openStream() { + return new ByteArrayInputStream(baos.toByteArray()); + } + }); + + Assert.assertNotNull(rbcCopy); + Assert.assertEquals(new Vector3f(0.04f, 0.05f, 0.06f), rbcCopy.getAngularVelocity()); + Assert.assertEquals(new Vector3f(0.26f, 0.27f, 0.28f), rbcCopy.getLinearVelocity()); + } + + /** + * Test case for JME issue #1004: RagdollUtils can't handle 16-bit bone indices. + */ + @Test + public void testIssue1004() { + Node sinbad = (Node)new DesktopAssetManager(true).loadModel("Models/Sinbad/SinbadOldAnim.j3o"); + + Geometry geometry = (Geometry) sinbad.getChild(0); + Mesh mesh = geometry.getMesh(); + VertexBuffer.Type bufferType = VertexBuffer.Type.BoneIndex; + VertexBuffer vertexBuffer = mesh.getBuffer(bufferType); + + // Remove the existing bone-index buffer. + mesh.getBufferList().remove(vertexBuffer); + mesh.getBuffers().remove(bufferType.ordinal()); + + // Copy the 8-bit bone indices to 16-bit indices. + ByteBuffer oldBuffer = (ByteBuffer) vertexBuffer.getDataReadOnly(); + int numComponents = oldBuffer.limit(); + oldBuffer.rewind(); + short[] shortArray = new short[numComponents]; + for (int index = 0; oldBuffer.hasRemaining(); ++index) { + shortArray[index] = oldBuffer.get(); + } + + // Add the 16-bit bone indices to the mesh. + mesh.setBuffer(bufferType, 4, shortArray); + + sinbad.addControl(new KinematicRagdollControl(0.5f)); // should not throw + } +} diff --git a/jme3-jogg/build.gradle b/jme3-jogg/build.gradle index f48531b187..7bf0052577 100644 --- a/jme3-jogg/build.gradle +++ b/jme3-jogg/build.gradle @@ -1,8 +1,4 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') - compile 'de.jarnbjo:j-ogg-all:1.0.0' + api project(':jme3-core') + api libs.j.ogg.vorbis } diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java index 9ac18a6029..4a63ac0fe7 100644 --- a/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/CachedOggStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,16 +53,15 @@ public class CachedOggStream implements PhysicalOggStream { private boolean closed = false; private boolean eos = false; - private boolean bos = false; private InputStream sourceStream; private HashMap logicalStreams - = new HashMap(); + = new HashMap<>(); - private IntMap oggPages = new IntMap(); + private IntMap oggPages = new IntMap<>(); private OggPage lastPage; private int pageNumber; - private int serialno; + private int serialNumber; public CachedOggStream(InputStream in) throws IOException { sourceStream = in; @@ -80,27 +79,28 @@ public OggPage getLastOggPage() { return lastPage; } - private LogicalOggStream getLogicalStream(int serialNumber) { - return logicalStreams.get(Integer.valueOf(serialNumber)); - } - + @Override public Collection getLogicalStreams() { return logicalStreams.values(); } + @Override public boolean isOpen() { return !closed; } + @Override public void close() throws IOException { closed = true; sourceStream.close(); } + @Override public OggPage getOggPage(int index) throws IOException { return oggPages.get(index); } + @Override public void setTime(long granulePosition) throws IOException { for (LogicalOggStream los : getLogicalStreams()){ los.setTime(granulePosition); @@ -109,8 +109,8 @@ public void setTime(long granulePosition) throws IOException { public LogicalOggStream reloadLogicalOggStream() { logicalStreams.clear(); - LogicalOggStreamImpl los = new LogicalOggStreamImpl(this, serialno); - logicalStreams.put(serialno, los); + LogicalOggStreamImpl los = new LogicalOggStreamImpl(this, serialNumber); + logicalStreams.put(serialNumber, los); for (IntMap.Entry entry : oggPages) { los.addPageNumberMapping(entry.getKey()); @@ -125,9 +125,7 @@ private int readOggNextPage() throws IOException { return -1; OggPage op = OggPage.create(sourceStream); - if (!op.isBos()){ - bos = true; - } + op.isBos(); if (op.isEos()){ eos = true; lastPage = op; @@ -135,7 +133,7 @@ private int readOggNextPage() throws IOException { LogicalOggStreamImpl los = (LogicalOggStreamImpl) logicalStreams.get(op.getStreamSerialNumber()); if (los == null) { - serialno = op.getStreamSerialNumber(); + serialNumber = op.getStreamSerialNumber(); los = new LogicalOggStreamImpl(this, op.getStreamSerialNumber()); logicalStreams.put(op.getStreamSerialNumber(), los); los.checkFormat(op); @@ -150,6 +148,7 @@ private int readOggNextPage() throws IOException { return pageNumber-1; } + @Override public boolean isSeekable() { return true; } diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java index 2302c5f4e4..511e847675 100644 --- a/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/OGGLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -143,6 +143,7 @@ public SeekableJOggInputStream(PhysicalOggStream ps, LogicalOggStream ls, Vorbis super(ps, ls, vs, maximum); } + @Override public void setTime(float time) { if (time != 0.0) { throw new UnsupportedOperationException("OGG/Vorbis seeking only supported for time = 0"); @@ -173,7 +174,7 @@ public void setTime(float time) { * of bytes in the input is returned. */ private int getOggTotalBytes(int dataBytesTotal){ - // Vorbis stream could have more samples than than the duration of the sound + // Vorbis stream could have more samples than the duration of the sound. // Must truncate. int numSamples; if (oggStream instanceof CachedOggStream){ @@ -259,6 +260,7 @@ private InputStream readToStream(boolean seekable) { } } + @SuppressWarnings("unchecked") private AudioData load(InputStream in, boolean readStream, boolean streamCache) throws IOException{ if (readStream && streamCache){ oggStream = new CachedOggStream(in); @@ -294,6 +296,7 @@ private AudioData load(InputStream in, boolean readStream, boolean streamCache) } } + @Override public Object load(AssetInfo info) throws IOException { if (!(info.getKey() instanceof AudioKey)){ throw new IllegalArgumentException("Audio assets must be loaded using an AudioKey"); @@ -308,7 +311,7 @@ public Object load(AssetInfo info) throws IOException { in = info.openStream(); AudioData data = load(in, readStream, streamCache); if (readStream && !streamCache) { - // we still need the stream in this case .. + // We still need the stream in this case. in = null; } return data; diff --git a/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java b/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java index 7c3e7ced96..72141d3c7f 100644 --- a/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java +++ b/jme3-jogg/src/main/java/com/jme3/audio/plugins/UncachedOggStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,9 +48,9 @@ public class UncachedOggStream implements PhysicalOggStream { private boolean eos = false; private boolean bos = false; private InputStream sourceStream; - private LinkedList pageCache = new LinkedList(); + private LinkedList pageCache = new LinkedList<>(); private HashMap logicalStreams - = new HashMap(); + = new HashMap<>(); private OggPage lastPage = null; public UncachedOggStream(InputStream in) throws OggFormatException, IOException { @@ -91,6 +91,7 @@ private void readNextOggPage() throws IOException { pageCache.add(op); } + @Override public OggPage getOggPage(int index) throws IOException { if (eos){ return null; @@ -117,21 +118,26 @@ private LogicalOggStream getLogicalStream(int serialNumber) { return logicalStreams.get(Integer.valueOf(serialNumber)); } + @Override public Collection getLogicalStreams() { return logicalStreams.values(); } + @Override public void setTime(long granulePosition) throws IOException { } + @Override public boolean isSeekable() { return false; } + @Override public boolean isOpen() { return !closed; } + @Override public void close() throws IOException { closed = true; sourceStream.close(); diff --git a/jme3-jogl/build.gradle b/jme3-jogl/build.gradle deleted file mode 100644 index b71be027a2..0000000000 --- a/jme3-jogl/build.gradle +++ /dev/null @@ -1,12 +0,0 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -dependencies { - compile project(':jme3-core') - compile project(':jme3-desktop') - compile 'org.jogamp.gluegen:gluegen-rt-main:2.3.2' - compile 'org.jogamp.jogl:jogl-all-main:2.3.2' - compile 'org.jogamp.joal:joal-main:2.3.2' - compile 'org.jogamp.jocl:jocl-main:2.3.2' -} diff --git a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAL.java b/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAL.java deleted file mode 100644 index cde0e01d7d..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalAL.java +++ /dev/null @@ -1,138 +0,0 @@ -/* - * Copyright (c) 2009-2014 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.audio.joal; - -import com.jme3.util.BufferUtils; -import com.jogamp.openal.AL; -import com.jogamp.openal.ALFactory; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; - -/** - * Exposes OpenAL functions via JOAL. - * - * @author Kirill Vainer - */ -public final class JoalAL implements com.jme3.audio.openal.AL { - - private final AL joalAl; - - public JoalAL() { - this.joalAl = ALFactory.getAL(); - } - - public String alGetString(int parameter) { - return joalAl.alGetString(parameter); - } - - public int alGenSources() { - IntBuffer ib = BufferUtils.createIntBuffer(1); - joalAl.alGenSources(1, ib); - return ib.get(0); - } - - public int alGetError() { - return joalAl.alGetError(); - } - - public void alDeleteSources(int numSources, IntBuffer sources) { - joalAl.alDeleteSources(numSources, sources); - } - - public void alGenBuffers(int numBuffers, IntBuffer buffers) { - joalAl.alGenBuffers(numBuffers, buffers); - } - - public void alDeleteBuffers(int numBuffers, IntBuffer buffers) { - joalAl.alDeleteBuffers(numBuffers, buffers); - } - - public void alSourceStop(int source) { - joalAl.alSourceStop(source); - } - - public void alSourcei(int source, int param, int value) { - joalAl.alSourcei(source, param, value); - } - - public void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency) { - joalAl.alBufferData(buffer, format, data, size, frequency); - } - - public void alSourcePlay(int source) { - joalAl.alSourcePlay(source); - } - - public void alSourcePause(int source) { - joalAl.alSourcePause(source); - } - - public void alSourcef(int source, int param, float value) { - joalAl.alSourcef(source, param, value); - } - - public void alSource3f(int source, int param, float value1, float value2, float value3) { - joalAl.alSource3f(source, param, value1, value2, value3); - } - - public int alGetSourcei(int source, int param) { - IntBuffer ib = BufferUtils.createIntBuffer(1); - joalAl.alGetSourcei(source, param, ib); - return ib.get(0); - } - - public void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers) { - joalAl.alSourceUnqueueBuffers(source, numBuffers, buffers); - } - - public void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers) { - joalAl.alSourceQueueBuffers(source, numBuffers, buffers); - } - - public void alListener(int param, FloatBuffer data) { - joalAl.alListenerfv(param, data); - } - - public void alListenerf(int param, float value) { - joalAl.alListenerf(param, value); - } - - public void alListener3f(int param, float value1, float value2, float value3) { - joalAl.alListener3f(param, value1, value2, value3); - } - - public void alSource3i(int source, int param, int value1, int value2, int value3) { - joalAl.alSource3i(source, param, value1, value2, value3); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalALC.java b/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalALC.java deleted file mode 100644 index 3d6c69e2d2..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalALC.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2009-2014 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.audio.joal; - -import com.jogamp.openal.ALCcontext; -import com.jogamp.openal.ALCdevice; -import com.jogamp.openal.ALFactory; -import com.jogamp.openal.util.ALut; -import java.nio.IntBuffer; - -/** - * Exposes ALC functions via JOAL. - * - * @author Kirill Vainer - */ -public final class JoalALC implements com.jme3.audio.openal.ALC { - - private final com.jogamp.openal.ALC joalAlc; - - public JoalALC() { - joalAlc = ALFactory.getALC(); - } - - private ALCdevice getALCDevice() { - ALCcontext ctx = joalAlc.alcGetCurrentContext(); - - if (ctx == null) { - return null; - } - - ALCdevice device = joalAlc.alcGetContextsDevice(ctx); - - if (device == null) { - return null; - } - - return device; - } - - public void createALC() { - ALut.alutInit(); - - /* - // Get handle to default device. - ALCdevice device = joalAlc.alcOpenDevice(null); - if (device == null) { - throw new ALException("Error opening default OpenAL device"); - } - - // Create audio context. - ALCcontext context = joalAlc.alcCreateContext(device, null); - if (context == null) { - throw new ALException("Error creating OpenAL context"); - } - - // Set active context. - if (!joalAlc.alcMakeContextCurrent(context)) { - throw new ALException("Failed to make OpenAL context current"); - } - - // Check for an error. - if (joalAlc.alcGetError(device) != com.jogamp.openal.ALC.ALC_NO_ERROR) { - throw new ALException("Error making OpenAL context current"); - } - */ - } - - public void destroyALC() { - /* - ALCcontext ctx = joalAlc.alcGetCurrentContext(); - - if (ctx == null) { - return; - } - - ALCdevice device = joalAlc.alcGetContextsDevice(ctx); - - if (device == null) { - return; - } - - if (!joalAlc.alcMakeContextCurrent(null)) { - return; - } - - joalAlc.alcDestroyContext(ctx); - joalAlc.alcCloseDevice(device); - */ - - ALut.alutExit(); - } - - public boolean isCreated() { - return getALCDevice() != null; - } - - public String alcGetString(int parameter) { - return joalAlc.alcGetString(getALCDevice(), parameter); - } - - public boolean alcIsExtensionPresent(String extension) { - return joalAlc.alcIsExtensionPresent(getALCDevice(), extension); - } - - public void alcGetInteger(int param, IntBuffer buffer, int size) { - joalAlc.alcGetIntegerv(getALCDevice(), param, size, buffer); - } - - public void alcDevicePauseSOFT() { - } - - public void alcDeviceResumeSOFT() { - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalEFX.java b/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalEFX.java deleted file mode 100644 index d7271fc3ba..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/audio/joal/JoalEFX.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (c) 2009-2014 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.audio.joal; - -import com.jme3.audio.openal.EFX; -import com.jogamp.openal.ALExt; -import com.jogamp.openal.ALFactory; -import java.nio.IntBuffer; - -/** - * Exposes EFX extension for JOAL. - * - * @author Kirill Vainer - */ -public final class JoalEFX implements EFX { - - private final ALExt joalAlext; - - public JoalEFX() { - joalAlext = ALFactory.getALExt(); - } - - public void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers) { - joalAlext.alGenAuxiliaryEffectSlots(numSlots, buffers); - } - - public void alGenEffects(int numEffects, IntBuffer buffers) { - joalAlext.alGenEffects(numEffects, buffers); - } - - public void alEffecti(int effect, int param, int value) { - joalAlext.alEffecti(effect, param, value); - } - - public void alAuxiliaryEffectSloti(int effectSlot, int param, int value) { - joalAlext.alAuxiliaryEffectSloti(effectSlot, param, value); - } - - public void alDeleteEffects(int numEffects, IntBuffer buffers) { - joalAlext.alDeleteEffects(numEffects, buffers); - } - - public void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers) { - joalAlext.alDeleteAuxiliaryEffectSlots(numEffectSlots, buffers); - } - - public void alGenFilters(int numFilters, IntBuffer buffers) { - joalAlext.alGenFilters(numFilters, buffers); - } - - public void alFilteri(int filter, int param, int value) { - joalAlext.alFilteri(filter, param, value); - } - - public void alFilterf(int filter, int param, float value) { - joalAlext.alFilterf(filter, param, value); - } - - public void alDeleteFilters(int numFilters, IntBuffer buffers) { - joalAlext.alDeleteFilters(numFilters, buffers); - } - - public void alEffectf(int effect, int param, float value) { - joalAlext.alEffectf(effect, param, value); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java deleted file mode 100644 index 8ae4270742..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtKeyInput.java +++ /dev/null @@ -1,589 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.input.jogl; - -import com.jme3.input.KeyInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.KeyInputEvent; -import com.jogamp.newt.event.KeyEvent; -import com.jogamp.newt.event.KeyListener; -import com.jogamp.newt.opengl.GLWindow; -import java.util.ArrayList; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class NewtKeyInput implements KeyInput, KeyListener { - - private static final Logger logger = Logger.getLogger(NewtKeyInput.class.getName()); - - private final ArrayList eventQueue = new ArrayList(); - private RawInputListener listener; - private GLWindow component; - - public NewtKeyInput() { - } - - public void initialize() { - } - - public void destroy() { - } - - public void setInputSource(GLWindow comp){ - synchronized (eventQueue){ - if (component != null){ - component.removeKeyListener(this); - eventQueue.clear(); - } - component = comp; - component.addKeyListener(this); - } - } - - public long getInputTimeNanos() { - return System.nanoTime(); - } - - public void update() { - synchronized (eventQueue){ - // flush events to listener - for (int i = 0; i < eventQueue.size(); i++){ - listener.onKeyEvent(eventQueue.get(i)); - } - eventQueue.clear(); - } - } - - public boolean isInitialized() { - return true; - } - - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - public void keyTyped(KeyEvent evt) { - } - - public void keyPressed(KeyEvent evt) { - int code = convertNewtKey(evt.getKeySymbol()); - KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), true, evt.isAutoRepeat()); - keyEvent.setTime(evt.getWhen()); - synchronized (eventQueue){ - eventQueue.add(keyEvent); - } - } - - public void keyReleased(KeyEvent evt) { - int code = convertNewtKey(evt.getKeySymbol()); - KeyInputEvent keyEvent = new KeyInputEvent(code, evt.getKeyChar(), false, evt.isAutoRepeat()); - keyEvent.setTime(evt.getWhen()); - synchronized (eventQueue) { - eventQueue.add(keyEvent); - } - } - - /** - * convertJmeCode converts KeyInput key codes to AWT key codes. - * - * @param key jme KeyInput key code - * @return awt KeyEvent key code - */ - public static int convertJmeCode( int key ) { - switch ( key ) { - case KEY_ESCAPE: - return KeyEvent.VK_ESCAPE; - case KEY_1: - return KeyEvent.VK_1; - case KEY_2: - return KeyEvent.VK_2; - case KEY_3: - return KeyEvent.VK_3; - case KEY_4: - return KeyEvent.VK_4; - case KEY_5: - return KeyEvent.VK_5; - case KEY_6: - return KeyEvent.VK_6; - case KEY_7: - return KeyEvent.VK_7; - case KEY_8: - return KeyEvent.VK_8; - case KEY_9: - return KeyEvent.VK_9; - case KEY_0: - return KeyEvent.VK_0; - case KEY_MINUS: - return KeyEvent.VK_MINUS; - case KEY_EQUALS: - return KeyEvent.VK_EQUALS; - case KEY_BACK: - return KeyEvent.VK_BACK_SPACE; - case KEY_TAB: - return KeyEvent.VK_TAB; - case KEY_Q: - return KeyEvent.VK_Q; - case KEY_W: - return KeyEvent.VK_W; - case KEY_E: - return KeyEvent.VK_E; - case KEY_R: - return KeyEvent.VK_R; - case KEY_T: - return KeyEvent.VK_T; - case KEY_Y: - return KeyEvent.VK_Y; - case KEY_U: - return KeyEvent.VK_U; - case KEY_I: - return KeyEvent.VK_I; - case KEY_O: - return KeyEvent.VK_O; - case KEY_P: - return KeyEvent.VK_P; - case KEY_LBRACKET: - return KeyEvent.VK_OPEN_BRACKET; - case KEY_RBRACKET: - return KeyEvent.VK_CLOSE_BRACKET; - case KEY_RETURN: - return KeyEvent.VK_ENTER; - case KEY_LCONTROL: - return KeyEvent.VK_CONTROL; - case KEY_A: - return KeyEvent.VK_A; - case KEY_S: - return KeyEvent.VK_S; - case KEY_D: - return KeyEvent.VK_D; - case KEY_F: - return KeyEvent.VK_F; - case KEY_G: - return KeyEvent.VK_G; - case KEY_H: - return KeyEvent.VK_H; - case KEY_J: - return KeyEvent.VK_J; - case KEY_K: - return KeyEvent.VK_K; - case KEY_L: - return KeyEvent.VK_L; - case KEY_SEMICOLON: - return KeyEvent.VK_SEMICOLON; - case KEY_APOSTROPHE: - return KeyEvent.VK_QUOTE; - //case KEY_GRAVE: - // return KeyEvent.VK_DEAD_GRAVE; - case KEY_LSHIFT: - return KeyEvent.VK_SHIFT; - case KEY_BACKSLASH: - return KeyEvent.VK_BACK_SLASH; - case KEY_Z: - return KeyEvent.VK_Z; - case KEY_X: - return KeyEvent.VK_X; - case KEY_C: - return KeyEvent.VK_C; - case KEY_V: - return KeyEvent.VK_V; - case KEY_B: - return KeyEvent.VK_B; - case KEY_N: - return KeyEvent.VK_N; - case KEY_M: - return KeyEvent.VK_M; - case KEY_COMMA: - return KeyEvent.VK_COMMA; - case KEY_PERIOD: - return KeyEvent.VK_PERIOD; - case KEY_SLASH: - return KeyEvent.VK_SLASH; - case KEY_RSHIFT: - return KeyEvent.VK_SHIFT; - case KEY_MULTIPLY: - return KeyEvent.VK_MULTIPLY; - case KEY_SPACE: - return KeyEvent.VK_SPACE; - case KEY_CAPITAL: - return KeyEvent.VK_CAPS_LOCK; - case KEY_F1: - return KeyEvent.VK_F1; - case KEY_F2: - return KeyEvent.VK_F2; - case KEY_F3: - return KeyEvent.VK_F3; - case KEY_F4: - return KeyEvent.VK_F4; - case KEY_F5: - return KeyEvent.VK_F5; - case KEY_F6: - return KeyEvent.VK_F6; - case KEY_F7: - return KeyEvent.VK_F7; - case KEY_F8: - return KeyEvent.VK_F8; - case KEY_F9: - return KeyEvent.VK_F9; - case KEY_F10: - return KeyEvent.VK_F10; - case KEY_NUMLOCK: - return KeyEvent.VK_NUM_LOCK; - case KEY_SCROLL: - return KeyEvent.VK_SCROLL_LOCK; - case KEY_NUMPAD7: - return KeyEvent.VK_NUMPAD7; - case KEY_NUMPAD8: - return KeyEvent.VK_NUMPAD8; - case KEY_NUMPAD9: - return KeyEvent.VK_NUMPAD9; - case KEY_SUBTRACT: - return KeyEvent.VK_SUBTRACT; - case KEY_NUMPAD4: - return KeyEvent.VK_NUMPAD4; - case KEY_NUMPAD5: - return KeyEvent.VK_NUMPAD5; - case KEY_NUMPAD6: - return KeyEvent.VK_NUMPAD6; - case KEY_ADD: - return KeyEvent.VK_ADD; - case KEY_NUMPAD1: - return KeyEvent.VK_NUMPAD1; - case KEY_NUMPAD2: - return KeyEvent.VK_NUMPAD2; - case KEY_NUMPAD3: - return KeyEvent.VK_NUMPAD3; - case KEY_NUMPAD0: - return KeyEvent.VK_NUMPAD0; - case KEY_DECIMAL: - return KeyEvent.VK_DECIMAL; - case KEY_F11: - return KeyEvent.VK_F11; - case KEY_F12: - return KeyEvent.VK_F12; - case KEY_F13: - return KeyEvent.VK_F13; - case KEY_F14: - return KeyEvent.VK_F14; - case KEY_F15: - return KeyEvent.VK_F15; - //case KEY_KANA: - // return KeyEvent.VK_KANA; - case KEY_CONVERT: - return KeyEvent.VK_CONVERT; - case KEY_NOCONVERT: - return KeyEvent.VK_NONCONVERT; - case KEY_NUMPADEQUALS: - return KeyEvent.VK_EQUALS; - case KEY_CIRCUMFLEX: - return KeyEvent.VK_CIRCUMFLEX; - case KEY_AT: - return KeyEvent.VK_AT; - case KEY_COLON: - return KeyEvent.VK_COLON; - case KEY_UNDERLINE: - return KeyEvent.VK_UNDERSCORE; - case KEY_STOP: - return KeyEvent.VK_STOP; - case KEY_NUMPADENTER: - return KeyEvent.VK_ENTER; - case KEY_RCONTROL: - return KeyEvent.VK_CONTROL; - case KEY_NUMPADCOMMA: - return KeyEvent.VK_COMMA; - case KEY_DIVIDE: - return KeyEvent.VK_DIVIDE; - case KEY_PAUSE: - return KeyEvent.VK_PAUSE; - case KEY_HOME: - return KeyEvent.VK_HOME; - case KEY_UP: - return KeyEvent.VK_UP; - case KEY_PRIOR: - return KeyEvent.VK_PAGE_UP; - case KEY_LEFT: - return KeyEvent.VK_LEFT; - case KEY_RIGHT: - return KeyEvent.VK_RIGHT; - case KEY_END: - return KeyEvent.VK_END; - case KEY_DOWN: - return KeyEvent.VK_DOWN; - case KEY_NEXT: - return KeyEvent.VK_PAGE_DOWN; - case KEY_INSERT: - return KeyEvent.VK_INSERT; - case KEY_DELETE: - return KeyEvent.VK_DELETE; - case KEY_LMENU: - return KeyEvent.VK_ALT; //todo: location left - case KEY_RMENU: - return KeyEvent.VK_ALT; //todo: location right - } - logger.log(Level.WARNING, "unsupported key:{0}", key); - return 0x10000 + key; - } - - /** - * convertAwtKey converts AWT key codes to KeyInput key codes. - * - * @param key awt KeyEvent key code - * @return jme KeyInput key code - */ - public static int convertNewtKey(short key) { - switch ( key ) { - case KeyEvent.VK_ESCAPE: - return KEY_ESCAPE; - case KeyEvent.VK_1: - return KEY_1; - case KeyEvent.VK_2: - return KEY_2; - case KeyEvent.VK_3: - return KEY_3; - case KeyEvent.VK_4: - return KEY_4; - case KeyEvent.VK_5: - return KEY_5; - case KeyEvent.VK_6: - return KEY_6; - case KeyEvent.VK_7: - return KEY_7; - case KeyEvent.VK_8: - return KEY_8; - case KeyEvent.VK_9: - return KEY_9; - case KeyEvent.VK_0: - return KEY_0; - case KeyEvent.VK_MINUS: - return KEY_MINUS; - case KeyEvent.VK_EQUALS: - return KEY_EQUALS; - case KeyEvent.VK_BACK_SPACE: - return KEY_BACK; - case KeyEvent.VK_TAB: - return KEY_TAB; - case KeyEvent.VK_Q: - return KEY_Q; - case KeyEvent.VK_W: - return KEY_W; - case KeyEvent.VK_E: - return KEY_E; - case KeyEvent.VK_R: - return KEY_R; - case KeyEvent.VK_T: - return KEY_T; - case KeyEvent.VK_Y: - return KEY_Y; - case KeyEvent.VK_U: - return KEY_U; - case KeyEvent.VK_I: - return KEY_I; - case KeyEvent.VK_O: - return KEY_O; - case KeyEvent.VK_P: - return KEY_P; - case KeyEvent.VK_OPEN_BRACKET: - return KEY_LBRACKET; - case KeyEvent.VK_CLOSE_BRACKET: - return KEY_RBRACKET; - case KeyEvent.VK_ENTER: - return KEY_RETURN; - case KeyEvent.VK_CONTROL: - return KEY_LCONTROL; - case KeyEvent.VK_A: - return KEY_A; - case KeyEvent.VK_S: - return KEY_S; - case KeyEvent.VK_D: - return KEY_D; - case KeyEvent.VK_F: - return KEY_F; - case KeyEvent.VK_G: - return KEY_G; - case KeyEvent.VK_H: - return KEY_H; - case KeyEvent.VK_J: - return KEY_J; - case KeyEvent.VK_K: - return KEY_K; - case KeyEvent.VK_L: - return KEY_L; - case KeyEvent.VK_SEMICOLON: - return KEY_SEMICOLON; - case KeyEvent.VK_QUOTE: - return KEY_APOSTROPHE; - //case KeyEvent.VK_DEAD_GRAVE: - // return KEY_GRAVE; - case KeyEvent.VK_SHIFT: - return KEY_LSHIFT; - case KeyEvent.VK_BACK_SLASH: - return KEY_BACKSLASH; - case KeyEvent.VK_Z: - return KEY_Z; - case KeyEvent.VK_X: - return KEY_X; - case KeyEvent.VK_C: - return KEY_C; - case KeyEvent.VK_V: - return KEY_V; - case KeyEvent.VK_B: - return KEY_B; - case KeyEvent.VK_N: - return KEY_N; - case KeyEvent.VK_M: - return KEY_M; - case KeyEvent.VK_COMMA: - return KEY_COMMA; - case KeyEvent.VK_PERIOD: - return KEY_PERIOD; - case KeyEvent.VK_SLASH: - return KEY_SLASH; - case KeyEvent.VK_MULTIPLY: - return KEY_MULTIPLY; - case KeyEvent.VK_SPACE: - return KEY_SPACE; - case KeyEvent.VK_CAPS_LOCK: - return KEY_CAPITAL; - case KeyEvent.VK_F1: - return KEY_F1; - case KeyEvent.VK_F2: - return KEY_F2; - case KeyEvent.VK_F3: - return KEY_F3; - case KeyEvent.VK_F4: - return KEY_F4; - case KeyEvent.VK_F5: - return KEY_F5; - case KeyEvent.VK_F6: - return KEY_F6; - case KeyEvent.VK_F7: - return KEY_F7; - case KeyEvent.VK_F8: - return KEY_F8; - case KeyEvent.VK_F9: - return KEY_F9; - case KeyEvent.VK_F10: - return KEY_F10; - case KeyEvent.VK_NUM_LOCK: - return KEY_NUMLOCK; - case KeyEvent.VK_SCROLL_LOCK: - return KEY_SCROLL; - case KeyEvent.VK_NUMPAD7: - return KEY_NUMPAD7; - case KeyEvent.VK_NUMPAD8: - return KEY_NUMPAD8; - case KeyEvent.VK_NUMPAD9: - return KEY_NUMPAD9; - case KeyEvent.VK_SUBTRACT: - return KEY_SUBTRACT; - case KeyEvent.VK_NUMPAD4: - return KEY_NUMPAD4; - case KeyEvent.VK_NUMPAD5: - return KEY_NUMPAD5; - case KeyEvent.VK_NUMPAD6: - return KEY_NUMPAD6; - case KeyEvent.VK_ADD: - return KEY_ADD; - case KeyEvent.VK_NUMPAD1: - return KEY_NUMPAD1; - case KeyEvent.VK_NUMPAD2: - return KEY_NUMPAD2; - case KeyEvent.VK_NUMPAD3: - return KEY_NUMPAD3; - case KeyEvent.VK_NUMPAD0: - return KEY_NUMPAD0; - case KeyEvent.VK_DECIMAL: - return KEY_DECIMAL; - case KeyEvent.VK_F11: - return KEY_F11; - case KeyEvent.VK_F12: - return KEY_F12; - case KeyEvent.VK_F13: - return KEY_F13; - case KeyEvent.VK_F14: - return KEY_F14; - case KeyEvent.VK_F15: - return KEY_F15; - //case KeyEvent.VK_KANA: - // return KEY_KANA; - case KeyEvent.VK_CONVERT: - return KEY_CONVERT; - case KeyEvent.VK_NONCONVERT: - return KEY_NOCONVERT; - case KeyEvent.VK_CIRCUMFLEX: - return KEY_CIRCUMFLEX; - case KeyEvent.VK_AT: - return KEY_AT; - case KeyEvent.VK_COLON: - return KEY_COLON; - case KeyEvent.VK_UNDERSCORE: - return KEY_UNDERLINE; - case KeyEvent.VK_STOP: - return KEY_STOP; - case KeyEvent.VK_DIVIDE: - return KEY_DIVIDE; - case KeyEvent.VK_PAUSE: - return KEY_PAUSE; - case KeyEvent.VK_HOME: - return KEY_HOME; - case KeyEvent.VK_UP: - return KEY_UP; - case KeyEvent.VK_PAGE_UP: - return KEY_PRIOR; - case KeyEvent.VK_LEFT: - return KEY_LEFT; - case KeyEvent.VK_RIGHT: - return KEY_RIGHT; - case KeyEvent.VK_END: - return KEY_END; - case KeyEvent.VK_DOWN: - return KEY_DOWN; - case KeyEvent.VK_PAGE_DOWN: - return KEY_NEXT; - case KeyEvent.VK_INSERT: - return KEY_INSERT; - case KeyEvent.VK_DELETE: - return KEY_DELETE; - case KeyEvent.VK_ALT: - return KEY_LMENU; //Left vs. Right need to improve - case KeyEvent.VK_META: - return KEY_RCONTROL; - - } - logger.log( Level.WARNING, "unsupported key:{0}", key); - if ( key >= 0x10000 ) { - return key - 0x10000; - } - - return 0; - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java b/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java deleted file mode 100644 index bb7a9aebfa..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/input/jogl/NewtMouseInput.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.input.jogl; - -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.MouseInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jogamp.common.nio.Buffers; -import com.jogamp.newt.Display.PointerIcon; -import com.jogamp.newt.event.MouseEvent; -import com.jogamp.newt.event.MouseListener; -import com.jogamp.newt.opengl.GLWindow; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.logging.Logger; -import com.jogamp.nativewindow.util.Dimension; -import com.jogamp.nativewindow.util.DimensionImmutable; -import com.jogamp.nativewindow.util.PixelFormat; -import com.jogamp.nativewindow.util.PixelRectangle; -import com.jogamp.nativewindow.util.Point; -import com.jogamp.newt.event.WindowAdapter; -import com.jogamp.newt.event.WindowEvent; - -public class NewtMouseInput implements MouseInput, MouseListener { - - public static int WHEEL_AMP = 40; // arbitrary... Java's mouse wheel seems to report something a lot lower than lwjgl's - - private static final Logger logger = Logger.getLogger(NewtMouseInput.class.getName()); - - private boolean visible = true; - - private RawInputListener listener; - - private GLWindow component; - - private final ArrayList eventQueue = new ArrayList(); - private final ArrayList eventQueueCopy = new ArrayList(); - - private int lastEventX; - private int lastEventY; - private int lastEventWheel; - - private int wheelPos; - private Point location; - private Point centerLocation; - private Point lastKnownLocation; - private Point lockPosition; - private boolean isRecentering; - private boolean cursorMoved; - private int eventsSinceRecenter; - private volatile int mousePressedX; - private volatile int mousePressedY; - - public NewtMouseInput() { - location = new Point(); - centerLocation = new Point(); - lastKnownLocation = new Point(); - lockPosition = new Point(); - } - - public void setInputSource(GLWindow comp) { - if (component != null) { - component.removeMouseListener(this); - - eventQueue.clear(); - - wheelPos = 0; - isRecentering = false; - eventsSinceRecenter = 0; - lastEventX = 0; - lastEventY = 0; - lastEventWheel = 0; - location = new Point(); - centerLocation = new Point(); - lastKnownLocation = new Point(); - lockPosition = new Point(); - } - - component = comp; - component.addMouseListener(this); - component.addWindowListener(new WindowAdapter(){ - - @Override - public void windowGainedFocus(WindowEvent e) { - setCursorVisible(visible); - } - - @Override - public void windowLostFocus(WindowEvent e) { - //without those lines, - //on Linux (OpenBox) the mouse is not restored if invisible (eg via Alt-Tab) - component.setPointerVisible(true); - component.confinePointer(false); - } - - }); - } - - @Override - public void initialize() { - } - - @Override - public void destroy() { - } - - @Override - public boolean isInitialized() { - return true; - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return System.nanoTime(); - } - - @Override - public void setCursorVisible(boolean visible) { - this.visible = visible; - component.setPointerVisible(visible); - lockPosition.set(lastKnownLocation.getX(), lastKnownLocation.getY()); - hack_confinePointer(); - } - - private void hack_confinePointer() { - if (component.hasFocus() && !component.isPointerVisible()) { - recenterMouse(component); - } - } - - @Override - public void update() { - if (!component.hasFocus()) return; - if (cursorMoved) { - int newX = location.getX(); - int newY = location.getY(); - int newWheel = wheelPos; - - // invert DY - int actualX = lastKnownLocation.getX(); - int actualY = component.getSurfaceHeight() - lastKnownLocation.getY(); - MouseMotionEvent evt = new MouseMotionEvent(actualX, actualY, - newX - lastEventX, - lastEventY - newY, - wheelPos, lastEventWheel - wheelPos); - listener.onMouseMotionEvent(evt); - - lastEventX = newX; - lastEventY = newY; - lastEventWheel = newWheel; - - cursorMoved = false; - } - - synchronized (eventQueue) { - eventQueueCopy.clear(); - eventQueueCopy.addAll(eventQueue); - eventQueue.clear(); - } - - int size = eventQueueCopy.size(); - for (int i = 0; i < size; i++) { - listener.onMouseButtonEvent(eventQueueCopy.get(i)); - } - } - - @Override - public int getButtonCount() { - return 3; - } - - @Override - public void mouseClicked(MouseEvent awtEvt) { -// MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(arg0), false); -// listener.onMouseButtonEvent(evt); - } - - @Override - public void mousePressed(MouseEvent newtEvt) { - mousePressedX = newtEvt.getX(); - mousePressedY = component.getSurfaceHeight() - newtEvt.getY(); - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(newtEvt), true, mousePressedX, mousePressedY); - evt.setTime(newtEvt.getWhen()); - synchronized (eventQueue) { - eventQueue.add(evt); - } - } - - @Override - public void mouseReleased(MouseEvent awtEvt) { - MouseButtonEvent evt = new MouseButtonEvent(getJMEButtonIndex(awtEvt), false, awtEvt.getX(), component.getSurfaceHeight() - awtEvt.getY()); - evt.setTime(awtEvt.getWhen()); - synchronized (eventQueue) { - eventQueue.add(evt); - } - } - - @Override - public void mouseEntered(MouseEvent awtEvt) { - hack_confinePointer(); - } - - @Override - public void mouseExited(MouseEvent awtEvt) { - hack_confinePointer(); - } - - @Override - public void mouseWheelMoved(MouseEvent awtEvt) { - //FIXME not sure this is the right way to handle this case - // [0] should be used when the shift key is down - float dwheel = awtEvt.getRotation()[1]; - wheelPos += dwheel * WHEEL_AMP; - cursorMoved = true; - } - - @Override - public void mouseDragged(MouseEvent awtEvt) { - mouseMoved(awtEvt); - } - - @Override - public void mouseMoved(MouseEvent awtEvt) { - if (isRecentering) { - // MHenze (cylab) Fix Issue 35: - // As long as the MouseInput is in recentering mode, nothing is done until the mouse is entered in the component - // by the events generated by the robot. If this happens, the last known location is resetted. - if ((lockPosition.getX() == awtEvt.getX() && lockPosition.getY() == awtEvt.getY()) || eventsSinceRecenter++ == 5) { - lastKnownLocation.setX(awtEvt.getX()); - lastKnownLocation.setY(awtEvt.getY()); - isRecentering = false; - } - } else { - // MHenze (cylab) Fix Issue 35: - // Compute the delta and absolute coordinates and recenter the mouse if necessary - int dx = awtEvt.getX() - lastKnownLocation.getX(); - int dy = awtEvt.getY() - lastKnownLocation.getY(); - location.setX(location.getX() + dx); - location.setY(location.getY() + dy); - hack_confinePointer(); - lastKnownLocation.setX(awtEvt.getX()); - lastKnownLocation.setY(awtEvt.getY()); - - cursorMoved = true; - } - } - - // MHenze (cylab) Fix Issue 35: A method to generate recenter the mouse to allow the InputSystem to "grab" the mouse - private void recenterMouse(final GLWindow component) { - eventsSinceRecenter = 0; - isRecentering = true; - component.warpPointer(lockPosition.getX(), lockPosition.getY()); - } - - private int getJMEButtonIndex(MouseEvent awtEvt) { - int index; - switch (awtEvt.getButton()) { - default: - case MouseEvent.BUTTON1: //left - index = MouseInput.BUTTON_LEFT; - break; - case MouseEvent.BUTTON2: //middle - index = MouseInput.BUTTON_MIDDLE; - break; - case MouseEvent.BUTTON3: //right - index = MouseInput.BUTTON_RIGHT; - break; - case MouseEvent.BUTTON4: - case MouseEvent.BUTTON5: - case MouseEvent.BUTTON6: - case MouseEvent.BUTTON7: - case MouseEvent.BUTTON8: - case MouseEvent.BUTTON9: - //FIXME - index = 0; - break; - } - return index; - } - - @Override - public void setNativeCursor(JmeCursor cursor) { - final ByteBuffer pixels = Buffers.copyIntBufferAsByteBuffer(cursor.getImagesData()); - final DimensionImmutable size = new Dimension(cursor.getWidth(), cursor.getHeight()); - final PixelFormat pixFormat = PixelFormat.RGBA8888; - final PixelRectangle.GenericPixelRect rec = new PixelRectangle.GenericPixelRect(pixFormat, size, 0, true, pixels); - final PointerIcon joglCursor = component.getScreen().getDisplay().createPointerIcon(rec, cursor.getXHotSpot(), cursor.getHeight() - cursor.getYHotSpot()); - component.setPointerIcon(joglCursor); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclBuffer.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclBuffer.java deleted file mode 100644 index 6cee05af07..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclBuffer.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.*; -import java.nio.ByteBuffer; -import com.jogamp.opencl.*; -import com.jogamp.opencl.llb.CL; -import com.jogamp.opencl.llb.gl.CLGL; - -/** - * - * @author shaman - */ -public class JoclBuffer extends Buffer { - - final long id; - final CL cl; - - public JoclBuffer(long id) { - super(new ReleaserImpl(id)); - this.id = id; - this.cl = CLPlatform.getLowLevelCLInterface(); - } - - @Override - public long getSize() { - Utils.pointers[0].rewind(); - int ret = cl.clGetMemObjectInfo(id, CL.CL_MEM_SIZE, Utils.pointers[0].elementSize(), Utils.pointers[0].getBuffer(), null); - Utils.checkError(ret, "clGetMemObjectInfo"); - return Utils.pointers[0].get(); - } - - @Override - public MemoryAccess getMemoryAccessFlags() { - Utils.pointers[0].rewind(); - int ret = cl.clGetMemObjectInfo(id, CL.CL_MEM_TYPE, Utils.pointers[0].elementSize(), Utils.pointers[0].getBuffer(), null); - Utils.checkError(ret, "clGetMemObjectInfo"); - long flags = Utils.pointers[0].get(); - return Utils.getMemoryAccessFromFlag(flags); - } - - @Override - public void read(CommandQueue queue, ByteBuffer dest, long size, long offset) { - long q = ((JoclCommandQueue) queue).id; - int ret = cl.clEnqueueReadBuffer(q, id, CL.CL_TRUE, offset, size, dest, 0, null, null); - Utils.checkError(ret, "clEnqueueReadBuffer"); - } - - @Override - public Event readAsync(CommandQueue queue, ByteBuffer dest, long size, long offset) { - Utils.pointers[0].rewind(); - long q = ((JoclCommandQueue) queue).id; - int ret = cl.clEnqueueReadBuffer(q, id, CL.CL_FALSE, offset, size, dest, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueReadBuffer"); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - - @Override - public void write(CommandQueue queue, ByteBuffer src, long size, long offset) { - long q = ((JoclCommandQueue)queue).id; - int ret = cl.clEnqueueWriteBuffer(q, id, CL.CL_TRUE, offset, size, src, 0, null, null); - Utils.checkError(ret, "clEnqueueWriteBuffer"); - } - - @Override - public Event writeAsync(CommandQueue queue, ByteBuffer src, long size, long offset) { - Utils.pointers[0].rewind(); - long q = ((JoclCommandQueue)queue).id; - int ret = cl.clEnqueueWriteBuffer(q, id, CL.CL_FALSE, offset, size, src, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueWriteBuffer"); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - - @Override - public void copyTo(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset) { - Utils.pointers[0].rewind(); - long q = ((JoclCommandQueue)queue).id; - long did = ((JoclBuffer) dest).id; - int ret = cl.clEnqueueCopyBuffer(q, id, did, srcOffset, destOffset, size, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueCopyBuffer"); - ret = cl.clWaitForEvents(1, Utils.pointers[0]); - Utils.checkError(ret, "clWaitForEvents"); - } - - @Override - public Event copyToAsync(CommandQueue queue, Buffer dest, long size, long srcOffset, long destOffset) { - Utils.pointers[0].rewind(); - long q = ((JoclCommandQueue)queue).id; - long did = ((JoclBuffer) dest).id; - int ret = cl.clEnqueueCopyBuffer(q, id, did, srcOffset, destOffset, size, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueCopyBuffer"); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - - @Override - public ByteBuffer map(CommandQueue queue, long size, long offset, MappingAccess access) { - long q = ((JoclCommandQueue)queue).id; - Utils.errorBuffer.rewind(); - long flags = Utils.getMappingAccessFlags(access); - ByteBuffer b = cl.clEnqueueMapBuffer(q, id, CL.CL_TRUE, flags, offset, size, 0, null, null, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clEnqueueMapBuffer"); - return b; - } - - @Override - public void unmap(CommandQueue queue, ByteBuffer ptr) { - long q = ((JoclCommandQueue)queue).id; - Utils.pointers[0].rewind(); - ptr.position(0); - int ret = cl.clEnqueueUnmapMemObject(q, id, ptr, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueUnmapMemObject"); - ret = cl.clWaitForEvents(1, Utils.pointers[0]); - Utils.checkError(ret, "clWaitForEvents"); - } - - @Override - public com.jme3.opencl.Buffer.AsyncMapping mapAsync(CommandQueue queue, long size, long offset, MappingAccess access) { - long q = ((JoclCommandQueue)queue).id; - Utils.pointers[0].rewind(); - Utils.errorBuffer.rewind(); - long flags = Utils.getMappingAccessFlags(access); - ByteBuffer b = cl.clEnqueueMapBuffer(q, id, CL.CL_FALSE, flags, offset, size, 0, null, Utils.pointers[0], Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clEnqueueMapBuffer"); - long event = Utils.pointers[0].get(0); - return new com.jme3.opencl.Buffer.AsyncMapping(new JoclEvent(event), b); - } - - @Override - public Event fillAsync(CommandQueue queue, ByteBuffer pattern, long size, long offset) { - throw new UnsupportedOperationException("Not supported by Jocl!"); - } - - @Override - public Event copyToImageAsync(CommandQueue queue, Image dest, long srcOffset, long[] destOrigin, long[] destRegion) { - if (destOrigin.length!=3 || destRegion.length!=3) { - throw new IllegalArgumentException("origin and region must both be arrays of length 3"); - } - Utils.pointers[0].rewind(); - Utils.pointers[1].rewind(); - Utils.pointers[2].rewind(); - Utils.pointers[1].put(destOrigin[0]).put(destOrigin[1]).put(destOrigin[2]).position(0); - Utils.pointers[2].put(destRegion[0]).put(destRegion[1]).put(destRegion[2]).position(0); - long q = ((JoclCommandQueue)queue).id; - long i = ((JoclImage) dest).id; - int ret = cl.clEnqueueCopyBufferToImage(q, id, i, srcOffset, Utils.pointers[1], Utils.pointers[2], 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueCopyBufferToImage"); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - - @Override - public Event acquireBufferForSharingAsync(CommandQueue queue) { - Utils.pointers[0].rewind(); - Utils.pointers[1].rewind(); - Utils.pointers[1].put(0, id); - long q = ((JoclCommandQueue)queue).id; - ((CLGL) cl).clEnqueueAcquireGLObjects(q, 1, Utils.pointers[1], 0, null, Utils.pointers[0]); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - @Override - public void acquireBufferForSharingNoEvent(CommandQueue queue) { - Utils.pointers[1].rewind(); - Utils.pointers[1].put(0, id); - long q = ((JoclCommandQueue)queue).id; - ((CLGL) cl).clEnqueueAcquireGLObjects(q, 1, Utils.pointers[1], 0, null, null); - } - - @Override - public Event releaseBufferForSharingAsync(CommandQueue queue) { - Utils.pointers[0].rewind(); - Utils.pointers[1].rewind(); - Utils.pointers[1].put(0, id); - long q = ((JoclCommandQueue)queue).id; - ((CLGL) cl).clEnqueueReleaseGLObjects(q, 1, Utils.pointers[1], 0, null, Utils.pointers[0]); - long event = Utils.pointers[0].get(0); - return new JoclEvent(event); - } - @Override - public void releaseBufferForSharingNoEvent(CommandQueue queue) { - Utils.pointers[1].rewind(); - Utils.pointers[1].put(0, id); - long q = ((JoclCommandQueue)queue).id; - ((CLGL) cl).clEnqueueReleaseGLObjects(q, 1, Utils.pointers[1], 0, null, null); - } - - private static class ReleaserImpl implements ObjectReleaser { - private long mem; - private ReleaserImpl(long mem) { - this.mem = mem; - } - @Override - public void release() { - if (mem != 0) { - int ret = CLPlatform.getLowLevelCLInterface().clReleaseMemObject(mem); - mem = 0; - Utils.reportError(ret, "clReleaseMemObject"); - } - } - - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclCommandQueue.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclCommandQueue.java deleted file mode 100644 index 702300ddf6..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclCommandQueue.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.CommandQueue; -import com.jme3.opencl.Device; -import com.jogamp.opencl.CLPlatform; -import com.jogamp.opencl.llb.CL; - -/** - * - * @author shaman - */ -public class JoclCommandQueue extends CommandQueue { - - final CL cl; - final long id; - - public JoclCommandQueue(long id, Device device) { - super(new ReleaserImpl(id), device); - this.id = id; - this.cl = CLPlatform.getLowLevelCLInterface(); - } - - @Override - public void flush() { - int ret = cl.clFlush(id); - Utils.checkError(ret, "clFlush"); - } - - @Override - public void finish() { - int ret = cl.clFinish(id); - Utils.checkError(ret, "clFinish"); - } - - private static class ReleaserImpl implements ObjectReleaser { - private long id; - - private ReleaserImpl(long id) { - this.id = id; - } - - @Override - public void release() { - if (id != 0) { - int ret = CLPlatform.getLowLevelCLInterface().clReleaseCommandQueue(id); - id = 0; - Utils.reportError(ret, "clReleaseCommandQueue"); - } - } - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclContext.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclContext.java deleted file mode 100644 index 8e06cb3e25..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclContext.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.*; -import com.jme3.opencl.Context; -import com.jme3.opencl.Image.ImageDescriptor; -import com.jme3.opencl.Image.ImageFormat; -import com.jme3.scene.VertexBuffer; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Texture; -import com.jogamp.opencl.CLContext; -import com.jogamp.opencl.CLImageFormat; -import com.jogamp.opencl.CLMemory.Mem; -import com.jogamp.opencl.CLPlatform; -import com.jogamp.opencl.llb.CL; -import com.jogamp.opencl.llb.gl.CLGL; -import com.jogamp.opencl.llb.impl.CLImageFormatImpl; -import com.jogamp.opengl.GL; -import java.nio.ByteBuffer; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * - * @author shaman - */ -public class JoclContext extends Context { - private static final Logger LOG = Logger.getLogger(JoclContext.class.getName()); - - final CLContext context; - final long id; - final CL cl; - private final List devices; - - public JoclContext(CLContext context, List devices) { - super(new ReleaserImpl(context.ID, devices)); - this.context = context; - this.id = context.ID; - this.cl = context.getCL(); - this.devices = devices; - } - - public CLContext getContext() { - return context; - } - - @Override - public List getDevices() { - return devices; - } - - @Override - @SuppressWarnings("element-type-mismatch") - public CommandQueue createQueue(Device device) { - assert (devices.contains(device)); //this also ensures that device is a JoclDevice - long d = ((JoclDevice) device).id; - long properties = 0; - long q = cl.clCreateCommandQueue(id, d, properties, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateCommandQueue"); - return new JoclCommandQueue(q, device); - } - - @Override - public Buffer createBuffer(long size, MemoryAccess access) { - long flags = Utils.getMemoryAccessFlags(access); - long mem = cl.clCreateBuffer(id, flags, size, null, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateBuffer"); - return new JoclBuffer(mem); - } - - @Override - public Buffer createBufferFromHost(ByteBuffer data, MemoryAccess access) { - long flags = Utils.getMemoryAccessFlags(access); - flags |= CL.CL_MEM_USE_HOST_PTR; - long mem = cl.clCreateBuffer(id, flags, data.capacity(), data, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateBuffer"); - return new JoclBuffer(mem); - } - - @Override - public Image createImage(MemoryAccess access, ImageFormat format, ImageDescriptor descr) { - if (descr.type != Image.ImageType.IMAGE_2D && descr.type != Image.ImageType.IMAGE_3D) { - throw new UnsupportedOperationException("Jocl only supports 2D and 3D images"); - } - long memFlags = Utils.getMemoryAccessFlags(access); - Utils.errorBuffer.rewind(); - //fill image format - CLImageFormatImpl f = CLImageFormatImpl.create(); - f.setImageChannelOrder(JoclImage.decodeImageChannelOrder(format.channelOrder)); - f.setImageChannelDataType(JoclImage.decodeImageChannelType(format.channelType)); - //create image - long mem; - if (descr.type == Image.ImageType.IMAGE_2D) { - mem = cl.clCreateImage2D(id, memFlags, f, descr.width, descr.height, - descr.hostPtr==null ? 0 : descr.rowPitch, descr.hostPtr, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateImage2D"); - } else { - mem = cl.clCreateImage3D(id, memFlags, f, descr.width, descr.height, descr.depth, - descr.hostPtr==null ? 0 : descr.rowPitch, descr.hostPtr==null ? 0 : descr.slicePitch, - descr.hostPtr, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateImage3D"); - } - return new JoclImage(mem); - } - - @Override - public ImageFormat[] querySupportedFormats(MemoryAccess access, Image.ImageType type) { - if (type != Image.ImageType.IMAGE_2D && type != Image.ImageType.IMAGE_3D) { - throw new UnsupportedOperationException("Jocl only supports 2D and 3D images"); - } - long memFlags = Utils.getMemoryAccessFlags(access); - CLImageFormat[] fx; - if (type == Image.ImageType.IMAGE_2D) { - fx = context.getSupportedImage2dFormats(Mem.valueOf((int) memFlags)); - } else { - fx = context.getSupportedImage3dFormats(Mem.valueOf((int) memFlags)); - } - //convert formats - ImageFormat[] formats = new ImageFormat[fx.length]; - for (int i=0; i devices; - private ReleaserImpl(long id, List devices) { - this.id = id; - this.devices = devices; - } - @Override - public void release() { - if (id != 0) { - int ret = CLPlatform.getLowLevelCLInterface().clReleaseContext(id); - id = 0; - devices.clear(); - Utils.reportError(ret, "clReleaseContext"); - } - } - - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclDevice.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclDevice.java deleted file mode 100644 index adeeb77202..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclDevice.java +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.Device; -import com.jogamp.opencl.CLDevice; -import java.util.Collection; - -/** - * - * @author shaman - */ -public final class JoclDevice implements Device { - - final long id; - final CLDevice device; - final JoclPlatform platform; - - public JoclDevice(CLDevice device, JoclPlatform platform) { - this.id = device.ID; - this.device = device; - this.platform = platform; - } - - public long getId() { - return id; - } - - public CLDevice getDevice() { - return device; - } - - @Override - public JoclPlatform getPlatform() { - return platform; - } - - @Override - public DeviceType getDeviceType() { - CLDevice.Type type = device.getType(); - switch (type) { - case ACCELERATOR: return DeviceType.ACCELEARTOR; - case CPU: return DeviceType.CPU; - case GPU: return DeviceType.GPU; - default: return DeviceType.DEFAULT; - } - } - - @Override - public int getVendorId() { - return (int) device.getVendorID(); - } - - @Override - public boolean isAvailable() { - return device.isAvailable(); - } - - @Override - public boolean hasCompiler() { - return device.isCompilerAvailable(); - } - - @Override - public boolean hasDouble() { - return hasExtension("cl_khr_fp64"); - } - - @Override - public boolean hasHalfFloat() { - return hasExtension("cl_khr_fp16"); - } - - @Override - public boolean hasErrorCorrectingMemory() { - return device.isErrorCorrectionSupported(); - } - - @Override - public boolean hasUnifiedMemory() { - return device.isMemoryUnified(); - } - - @Override - public boolean hasImageSupport() { - return device.isImageSupportAvailable(); - } - - @Override - public boolean hasWritableImage3D() { - return hasExtension("cl_khr_3d_image_writes"); - } - - @Override - public boolean hasOpenGLInterop() { - return hasExtension("cl_khr_gl_sharing"); - } - - @Override - public boolean hasExtension(String extension) { - return getExtensions().contains(extension); - } - - @Override - public Collection getExtensions() { - return device.getExtensions(); - } - - @Override - public int getComputeUnits() { - return device.getMaxComputeUnits(); - } - - @Override - public int getClockFrequency() { - return device.getMaxClockFrequency(); - } - - @Override - public int getAddressBits() { - return device.getAddressBits(); - } - - @Override - public boolean isLittleEndian() { - return device.isLittleEndian(); - } - - @Override - public long getMaximumWorkItemDimensions() { - return device.getMaxWorkItemDimensions(); - } - - @Override - public long[] getMaximumWorkItemSizes() { - int[] sizes = device.getMaxWorkItemSizes(); - long[] s = new long[sizes.length]; - for (int i=0; i 0) { - p2 = Utils.pointers[2].rewind(); - p2.put(workGroupSize.getSizes(), 0, workGroupSize.getSizes().length); - p2.position(0); - } - long q = ((JoclCommandQueue) queue).id; - int ret = cl.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointers[1], - p2, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clEnqueueNDRangeKernel"); - return new JoclEvent(Utils.pointers[0].get(0)); - } - - @Override - public void RunNoEvent(CommandQueue queue) { - Utils.pointers[1].rewind(); - Utils.pointers[1].put(globalWorkSize.getSizes(), 0, globalWorkSize.getSizes().length); - Utils.pointers[1].position(0); - PointerBuffer p2 = null; - if (workGroupSize.getSizes()[0] > 0) { - p2 = Utils.pointers[2].rewind(); - p2.put(workGroupSize.getSizes(), 0, workGroupSize.getSizes().length); - p2.position(0); - } - long q = ((JoclCommandQueue) queue).id; - int ret = cl.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointers[1], - p2, 0, null, null); - Utils.checkError(ret, "clEnqueueNDRangeKernel"); - } - - private static class ReleaserImpl implements ObjectReleaser { - private long kernel; - private ReleaserImpl(long kernel) { - this.kernel = kernel; - } - @Override - public void release() { - if (kernel != 0) { - int ret = CLPlatform.getLowLevelCLInterface().clReleaseKernel(kernel); - kernel = 0; - Utils.reportError(ret, "clReleaseKernel"); - } - } - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclPlatform.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclPlatform.java deleted file mode 100644 index 65d572526f..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclPlatform.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.Platform; -import com.jogamp.opencl.CLDevice; -import com.jogamp.opencl.CLPlatform; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -/** - * - * @author shaman - */ -public final class JoclPlatform implements Platform { - - final CLPlatform platform; - List devices; - - public JoclPlatform(CLPlatform platform) { - this.platform = platform; - } - - public CLPlatform getPlatform() { - return platform; - } - - @Override - public List getDevices() { - if (devices == null) { - devices = new ArrayList<>(); - for (CLDevice d : platform.listCLDevices()) { - devices.add(new JoclDevice(d, this)); - } - } - return devices; - } - - @Override - public String getProfile() { - return platform.getProfile(); - } - - @Override - public boolean isFullProfile() { - return getProfile().contains("FULL_PROFILE"); - } - - @Override - public boolean isEmbeddedProfile() { - return getProfile().contains("EMBEDDED_PROFILE"); - } - - @Override - public String getVersion() { - return platform.getVendor(); - } - - @Override - public int getVersionMajor() { - return Utils.getMajorVersion(getVersion(), "OpenCL "); - } - - @Override - public int getVersionMinor() { - return Utils.getMinorVersion(getVersion(), "OpenCL "); - } - - @Override - public String getName() { - return platform.getName(); - } - - @Override - public String getVendor() { - return platform.getVendor(); - } - - @Override - public boolean hasExtension(String extension) { - return getExtensions().contains(extension); - } - - @Override - public boolean hasOpenGLInterop() { - return hasExtension("cl_khr_gl_sharing"); - } - - @Override - public Collection getExtensions() { - return platform.getExtensions(); - } - - @Override - public String toString() { - return getName(); - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclProgram.java b/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclProgram.java deleted file mode 100644 index 2f265290ae..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/opencl/jocl/JoclProgram.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2009-2016 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.opencl.jocl; - -import com.jme3.opencl.*; -import com.jogamp.common.nio.Buffers; -import com.jogamp.common.nio.PointerBuffer; -import com.jogamp.opencl.CLPlatform; -import com.jogamp.opencl.llb.CL; -import com.jogamp.opencl.util.CLUtil; -import java.nio.ByteBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static com.jogamp.common.nio.Buffers.newDirectByteBuffer; -/** - * - * @author shaman - */ -public class JoclProgram extends Program { - private static final Logger LOG = Logger.getLogger(JoclProgram.class.getName()); - - final long program; - final CL cl; - private final JoclContext context; - - public JoclProgram(long program, JoclContext context) { - super(new ReleaserImpl(program)); - this.program = program; - this.context = context; - this.cl = CLPlatform.getLowLevelCLInterface(); - } - - @Override - public void build(String args, Device... devices) throws KernelCompilationException { - PointerBuffer deviceList = null; - int deviceCount = 0; - if (devices != null) { - deviceList = PointerBuffer.allocateDirect(devices.length); - for (Device d : devices) { - deviceList.put(((JoclDevice) d).id); - } - deviceCount = devices.length; - deviceList.rewind(); - } - int ret = cl.clBuildProgram(program, deviceCount, deviceList, args, null); - if (ret != CL.CL_SUCCESS) { - String log = Log(); - LOG.log(Level.WARNING, "Unable to compile program:\n{0}", log); - if (ret == CL.CL_BUILD_PROGRAM_FAILURE) { - throw new KernelCompilationException("Failed to build program", ret, log); - } else { - Utils.checkError(ret, "clBuildProgram"); - } - } else { - LOG.log(Level.INFO, "Program compiled:\n{0}", Log()); - } - } - - private String Log(long device) { - Utils.pointers[0].rewind(); - int ret = cl.clGetProgramBuildInfo(program, device, CL.CL_PROGRAM_BUILD_LOG, 0, null, Utils.pointers[0]); - Utils.checkError(ret, "clGetProgramBuildInfo"); - int count = (int) Utils.pointers[0].get(0); - final ByteBuffer buffer = newDirectByteBuffer(count); - ret = cl.clGetProgramBuildInfo(program, device, CL.CL_PROGRAM_BUILD_LOG, buffer.capacity(), buffer, null); - Utils.checkError(ret, "clGetProgramBuildInfo"); - return CLUtil.clString2JavaString(buffer, count); - } - - private String Log() { - StringBuilder str = new StringBuilder(); - for (JoclDevice device : context.getDevices()) { - long d = device.id; - str.append(device.getName()).append(":\n"); - str.append(Log(d)); - str.append('\n'); - } - return str.toString(); - } - - @Override - public Kernel createKernel(String name) { - Utils.errorBuffer.rewind(); - long kernel = cl.clCreateKernel(program, name, Utils.errorBuffer); - Utils.checkError(Utils.errorBuffer, "clCreateKernel"); - return new JoclKernel(kernel); - } - - @Override - public Kernel[] createAllKernels() { - Utils.tempBuffers[0].b16i.rewind(); - int ret = cl.clCreateKernelsInProgram(program, 0, null, Utils.tempBuffers[0].b16i); - Utils.checkError(ret, "clCreateKernelsInProgram"); - int count = Utils.tempBuffers[0].b16i.get(0); - PointerBuffer buf = PointerBuffer.allocateDirect(count); - ret = cl.clCreateKernelsInProgram(program, count, buf, null); - Utils.checkError(ret, "clCreateKernelsInProgram"); - Kernel[] kx = new Kernel[count]; - for (int i=0; i 0) { - return MemoryAccess.READ_WRITE; - } - if ((flag & CL.CL_MEM_READ_ONLY) > 0) { - return MemoryAccess.READ_ONLY; - } - if ((flag & CL.CL_MEM_WRITE_ONLY) > 0) { - return MemoryAccess.WRITE_ONLY; - } - throw new OpenCLException("Unknown memory access flag: "+flag); - } - - public static long getMappingAccessFlags(MappingAccess ma) { - switch (ma) { - case MAP_READ_ONLY: return CL.CL_MAP_READ; - case MAP_READ_WRITE: return CL.CL_MAP_READ | CL.CL_MAP_WRITE; - case MAP_WRITE_ONLY: return CL.CL_MAP_WRITE; - case MAP_WRITE_INVALIDATE: return CL.CL_MAP_WRITE; //MAP_WRITE_INVALIDATE_REGION not supported - default: throw new IllegalArgumentException("Unknown mapping access: "+ma); - } - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java deleted file mode 100644 index 545bd9c64d..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGL.java +++ /dev/null @@ -1,662 +0,0 @@ -package com.jme3.renderer.jogl; - -import com.jme3.renderer.RendererException; -import com.jme3.renderer.opengl.GL; -import com.jme3.renderer.opengl.GL2; -import com.jme3.renderer.opengl.GL3; -import com.jme3.renderer.opengl.GL4; -import com.jogamp.opengl.GLContext; - -import java.nio.*; - -public class JoglGL implements GL, GL2, GL3, GL4 { - - private static int getLimitBytes(ByteBuffer buffer) { - checkLimit(buffer); - return buffer.limit(); - } - - private static int getLimitBytes(ShortBuffer buffer) { - checkLimit(buffer); - return buffer.limit() * 2; - } - - private static int getLimitBytes(FloatBuffer buffer) { - checkLimit(buffer); - return buffer.limit() * 4; - } - - private static int getLimitCount(Buffer buffer, int elementSize) { - checkLimit(buffer); - return buffer.limit() / elementSize; - } - - private static void checkLimit(Buffer buffer) { - if (buffer == null) { - return; - } - if (buffer.limit() == 0) { - throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); - } - if (buffer.remaining() == 0) { - throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); - } - } - - @Override - public void resetStats() { - } - - @Override - public void glActiveTexture(int param1) { - GLContext.getCurrentGL().glActiveTexture(param1); - } - - @Override - public void glAlphaFunc(int param1, float param2) { - GLContext.getCurrentGL().getGL2ES1().glAlphaFunc(param1, param2); - } - - @Override - public void glAttachShader(int param1, int param2) { - GLContext.getCurrentGL().getGL2ES2().glAttachShader(param1, param2); - } - - @Override - public void glBeginQuery(int target, int query) { - GLContext.getCurrentGL().getGL2ES2().glBeginQuery(target, query); - } - - @Override - public void glBindBuffer(int param1, int param2) { - GLContext.getCurrentGL().glBindBuffer(param1, param2); - } - - @Override - public void glBindTexture(int param1, int param2) { - GLContext.getCurrentGL().glBindTexture(param1, param2); - } - - @Override - public void glBlendEquationSeparate(int colorMode, int alphaMode){ - GLContext.getCurrentGL().glBlendEquationSeparate(colorMode, alphaMode); - } - - @Override - public void glBlendFunc(int param1, int param2) { - GLContext.getCurrentGL().glBlendFunc(param1, param2); - } - - @Override - public void glBlendFuncSeparate(int sfactorRGB, int dfactorRGB, int sfactorAlpha, int dfactorAlpha) { - GLContext.getCurrentGL().glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); - } - - @Override - public void glBufferData(int param1, long param2, int param3) { - GLContext.getCurrentGL().glBufferData(param1, param2, null, param3); - } - - @Override - public void glBufferData(int param1, FloatBuffer param2, int param3) { - checkLimit(param2); - GLContext.getCurrentGL().glBufferData(param1, getLimitBytes(param2), param2, param3); - } - - @Override - public void glBufferData(int param1, ShortBuffer param2, int param3) { - checkLimit(param2); - GLContext.getCurrentGL().glBufferData(param1, getLimitBytes(param2), param2, param3); - } - - @Override - public void glBufferData(int param1, ByteBuffer param2, int param3) { - checkLimit(param2); - GLContext.getCurrentGL().glBufferData(param1, getLimitBytes(param2), param2, param3); - } - - @Override - public void glBufferSubData(int param1, long param2, FloatBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().glBufferSubData(param1, param2, getLimitBytes(param3), param3); - } - - @Override - public void glBufferSubData(int param1, long param2, ShortBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().glBufferSubData(param1, param2, getLimitBytes(param3), param3); - } - - @Override - public void glBufferSubData(int param1, long param2, ByteBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().glBufferSubData(param1, param2, getLimitBytes(param3), param3); - } - - @Override - public void glClear(int param1) { - GLContext.getCurrentGL().glClear(param1); - } - - @Override - public void glClearColor(float param1, float param2, float param3, float param4) { - GLContext.getCurrentGL().glClearColor(param1, param2, param3, param4); - } - - @Override - public void glColorMask(boolean param1, boolean param2, boolean param3, boolean param4) { - GLContext.getCurrentGL().glColorMask(param1, param2, param3, param4); - } - - @Override - public void glCompileShader(int param1) { - GLContext.getCurrentGL().getGL2ES2().glCompileShader(param1); - } - - @Override - public void glCompressedTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) { - checkLimit(param7); - GLContext.getCurrentGL().glCompressedTexImage2D(param1, param2, param3, param4, param5, param6, getLimitBytes(param7), param7); - } - - @Override - public void glCompressedTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) { - checkLimit(param8); - GLContext.getCurrentGL().getGL2ES2().glCompressedTexImage3D(param1, param2, param3, param4, param5, param6, param7, getLimitBytes(param8), param8); - } - - @Override - public void glCompressedTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) { - checkLimit(param8); - GLContext.getCurrentGL().glCompressedTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, getLimitBytes(param8), param8); - } - - @Override - public void glCompressedTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) { - checkLimit(param10); - GLContext.getCurrentGL().getGL2ES2().glCompressedTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, getLimitBytes(param10), param10); - } - - @Override - public int glCreateProgram() { - return GLContext.getCurrentGL().getGL2ES2().glCreateProgram(); - } - - @Override - public int glCreateShader(int param1) { - return GLContext.getCurrentGL().getGL2ES2().glCreateShader(param1); - } - - @Override - public void glCullFace(int param1) { - GLContext.getCurrentGL().glCullFace(param1); - } - - @Override - public void glDeleteBuffers(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glDeleteBuffers(param1.limit(), param1); - } - - @Override - public void glDeleteProgram(int param1) { - GLContext.getCurrentGL().getGL2ES2().glDeleteProgram(param1); - } - - @Override - public void glDeleteShader(int param1) { - GLContext.getCurrentGL().getGL2ES2().glDeleteShader(param1); - } - - @Override - public void glDeleteTextures(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glDeleteTextures(param1.limit() ,param1); - } - - @Override - public void glDepthFunc(int param1) { - GLContext.getCurrentGL().glDepthFunc(param1); - } - - @Override - public void glDepthMask(boolean param1) { - GLContext.getCurrentGL().glDepthMask(param1); - } - - @Override - public void glDepthRange(double param1, double param2) { - GLContext.getCurrentGL().glDepthRange(param1, param2); - } - - @Override - public void glDetachShader(int param1, int param2) { - GLContext.getCurrentGL().getGL2ES2().glDetachShader(param1, param2); - } - - @Override - public void glDisable(int param1) { - GLContext.getCurrentGL().glDisable(param1); - } - - @Override - public void glDisableVertexAttribArray(int param1) { - GLContext.getCurrentGL().getGL2ES2().glDisableVertexAttribArray(param1); - } - - @Override - public void glDrawArrays(int param1, int param2, int param3) { - GLContext.getCurrentGL().glDrawArrays(param1, param2, param3); - } - - @Override - public void glDrawBuffer(int param1) { - GLContext.getCurrentGL().getGL2GL3().glDrawBuffer(param1); - } - - @Override - public void glDrawRangeElements(int param1, int param2, int param3, int param4, int param5, long param6) { - GLContext.getCurrentGL().getGL2ES3().glDrawRangeElements(param1, param2, param3, param4, param5, param6); - } - - @Override - public void glEnable(int param1) { - GLContext.getCurrentGL().glEnable(param1); - } - - @Override - public void glEnableVertexAttribArray(int param1) { - GLContext.getCurrentGL().getGL2ES2().glEnableVertexAttribArray(param1); - } - - @Override - public void glEndQuery(int target) { - GLContext.getCurrentGL().getGL2ES2().glEndQuery(target); - } - - @Override - public void glGenBuffers(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glGenBuffers(param1.limit(), param1); - } - - @Override - public void glGenQueries(int num, IntBuffer buff) { - GLContext.getCurrentGL().getGL2ES2().glGenQueries(num, buff); - } - - @Override - public void glGenTextures(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glGenTextures(param1.limit(), param1); - } - - @Override - public void glGetBoolean(int param1, ByteBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().glGetBooleanv(param1, param2); - } - - @Override - public void glGetBufferSubData(int target, long offset, ByteBuffer data) { - checkLimit(data); - GLContext.getCurrentGL().getGL2GL3().glGetBufferSubData(target, offset, getLimitBytes(data), data); - } - - @Override - public int glGetError() { - return GLContext.getCurrentGL().glGetError(); - } - - @Override - public void glGetInteger(int param1, IntBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().glGetIntegerv(param1, param2); - } - - @Override - public void glGetProgram(int param1, int param2, IntBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().getGL2ES2().glGetProgramiv(param1, param2, param3); - } - - @Override - public void glGetShader(int param1, int param2, IntBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().getGL2ES2().glGetShaderiv(param1, param2, param3); - } - - @Override - public String glGetString(int param1) { - return GLContext.getCurrentGL().glGetString(param1); - } - - @Override - public String glGetString(int param1, int param2) { - return GLContext.getCurrentGL().getGL2ES3().glGetStringi(param1, param2); - } - - @Override - public boolean glIsEnabled(int param1) { - return GLContext.getCurrentGL().glIsEnabled(param1); - } - - @Override - public void glLineWidth(float param1) { - GLContext.getCurrentGL().glLineWidth(param1); - } - - @Override - public void glLinkProgram(int param1) { - GLContext.getCurrentGL().getGL2ES2().glLinkProgram(param1); - } - - @Override - public void glPixelStorei(int param1, int param2) { - GLContext.getCurrentGL().glPixelStorei(param1, param2); - } - - @Override - public void glPointSize(float param1) { - GLContext.getCurrentGL().getGL2ES1().glPointSize(param1); - } - - @Override - public void glPolygonMode(int param1, int param2) { - GLContext.getCurrentGL().getGL2().glPolygonMode(param1, param2); - } - - @Override - public void glPolygonOffset(float param1, float param2) { - GLContext.getCurrentGL().glPolygonOffset(param1, param2); - } - - @Override - public void glReadBuffer(int param1) { - GLContext.getCurrentGL().getGL2ES3().glReadBuffer(param1); - } - - @Override - public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) { - checkLimit(param7); - GLContext.getCurrentGL().glReadPixels(param1, param2, param3, param4, param5, param6, param7); - } - - @Override - public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, long param7) { - GLContext.getCurrentGL().glReadPixels(param1, param2, param3, param4, param5, param6, param7); - } - - @Override - public void glScissor(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().glScissor(param1, param2, param3, param4); - } - - @Override - public void glStencilFuncSeparate(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().getGL2ES2().glStencilFuncSeparate(param1, param2, param3, param4); - } - - @Override - public void glStencilOpSeparate(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().getGL2ES2().glStencilOpSeparate(param1, param2, param3, param4); - } - - @Override - public void glTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) { - checkLimit(param9); - GLContext.getCurrentGL().glTexImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9); - } - - @Override - public void glTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) { - checkLimit(param10); - GLContext.getCurrentGL().getGL2ES2().glTexImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); - } - - @Override - public void glTexParameterf(int param1, int param2, float param3) { - GLContext.getCurrentGL().glTexParameterf(param1, param2, param3); - } - - @Override - public void glTexParameteri(int param1, int param2, int param3) { - GLContext.getCurrentGL().glTexParameteri(param1, param2, param3); - } - - @Override - public void glTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) { - checkLimit(param9); - GLContext.getCurrentGL().glTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9); - } - - @Override - public void glTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, int param10, ByteBuffer param11) { - checkLimit(param11); - GLContext.getCurrentGL().getGL2ES2().glTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11); - } - - @Override - public void glUniform1(int param1, FloatBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform1fv(param1, getLimitCount(param2, 1), param2); - } - - @Override - public void glUniform1(int param1, IntBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform1iv(param1, getLimitCount(param2, 1), param2); - } - - @Override - public void glUniform1f(int param1, float param2) { - GLContext.getCurrentGL().getGL2ES2().glUniform1f(param1, param2); - } - - @Override - public void glUniform1i(int param1, int param2) { - GLContext.getCurrentGL().getGL2ES2().glUniform1i(param1, param2); - } - - @Override - public void glUniform2(int param1, IntBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform2iv(param1, getLimitCount(param2, 2), param2); - } - - @Override - public void glUniform2(int param1, FloatBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform2fv(param1, getLimitCount(param2, 2), param2); - } - - @Override - public void glUniform2f(int param1, float param2, float param3) { - GLContext.getCurrentGL().getGL2ES2().glUniform2f(param1, param2, param3); - } - - @Override - public void glUniform3(int param1, IntBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform3iv(param1, getLimitCount(param2, 3), param2); - } - - @Override - public void glUniform3(int param1, FloatBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform3fv(param1, getLimitCount(param2, 3), param2); - } - - @Override - public void glUniform3f(int param1, float param2, float param3, float param4) { - GLContext.getCurrentGL().getGL2ES2().glUniform3f(param1, param2, param3, param4); - } - - @Override - public void glUniform4(int param1, FloatBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform4fv(param1, getLimitCount(param2, 4), param2); - } - - @Override - public void glUniform4(int param1, IntBuffer param2) { - checkLimit(param2); - GLContext.getCurrentGL().getGL2ES2().glUniform4iv(param1, getLimitCount(param2, 4), param2); - } - - @Override - public void glUniform4f(int param1, float param2, float param3, float param4, float param5) { - GLContext.getCurrentGL().getGL2ES2().glUniform4f(param1, param2, param3, param4, param5); - } - - @Override - public void glUniformMatrix3(int param1, boolean param2, FloatBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().getGL2ES2().glUniformMatrix3fv(param1, getLimitCount(param3, 3 * 3), param2, param3); - } - - @Override - public void glUniformMatrix4(int param1, boolean param2, FloatBuffer param3) { - checkLimit(param3); - GLContext.getCurrentGL().getGL2ES2().glUniformMatrix4fv(param1, getLimitCount(param3, 4 * 4), param2, param3); - } - - @Override - public void glUseProgram(int param1) { - GLContext.getCurrentGL().getGL2ES2().glUseProgram(param1); - } - - @Override - public void glVertexAttribPointer(int param1, int param2, int param3, boolean param4, int param5, long param6) { - GLContext.getCurrentGL().getGL2ES2().glVertexAttribPointer(param1, param2, param3, param4, param5, param6); - } - - @Override - public void glViewport(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().glViewport(param1, param2, param3, param4); - } - - @Override - public int glGetAttribLocation(int param1, String param2) { - // JOGL 2.0 doesn't need a null-terminated string - return GLContext.getCurrentGL().getGL2ES2().glGetAttribLocation(param1, param2); - } - - @Override - public int glGetUniformLocation(int param1, String param2) { - // JOGL 2.0 doesn't need a null-terminated string - return GLContext.getCurrentGL().getGL2ES2().glGetUniformLocation(param1, param2); - } - - @Override - public void glShaderSource(int param1, String[] param2, IntBuffer param3) { - checkLimit(param3); - - int param3pos = param3.position(); - try { - for (final String param2string : param2) { - param3.put(Math.max(param2string.length(), param2string.getBytes().length)); - } - } finally { - param3.position(param3pos); - } - GLContext.getCurrentGL().getGL2ES2().glShaderSource(param1, param2.length, param2, param3); - } - - @Override - public String glGetProgramInfoLog(int program, int maxSize) { - ByteBuffer buffer = ByteBuffer.allocateDirect(maxSize); - buffer.order(ByteOrder.nativeOrder()); - ByteBuffer tmp = ByteBuffer.allocateDirect(4); - tmp.order(ByteOrder.nativeOrder()); - IntBuffer intBuffer = tmp.asIntBuffer(); - - GLContext.getCurrentGL().getGL2ES2().glGetProgramInfoLog(program, maxSize, intBuffer, buffer); - int numBytes = intBuffer.get(0); - byte[] bytes = new byte[numBytes]; - buffer.get(bytes); - return new String(bytes); - } - - @Override - public long glGetQueryObjectui64(int query, int target) { - LongBuffer buff = LongBuffer.allocate(1); - GLContext.getCurrentGL().getGL2ES2().glGetQueryObjectui64v(query, target, buff); - return buff.get(0); - } - - @Override - public int glGetQueryObjectiv(int query, int pname) { - IntBuffer buff = IntBuffer.allocate(1); - GLContext.getCurrentGL().getGL2ES2().glGetQueryObjectiv(query, pname, buff); - return buff.get(0); - } - - @Override - public String glGetShaderInfoLog(int shader, int maxSize) { - ByteBuffer buffer = ByteBuffer.allocateDirect(maxSize); - buffer.order(ByteOrder.nativeOrder()); - ByteBuffer tmp = ByteBuffer.allocateDirect(4); - tmp.order(ByteOrder.nativeOrder()); - IntBuffer intBuffer = tmp.asIntBuffer(); - - GLContext.getCurrentGL().getGL2ES2().glGetShaderInfoLog(shader, maxSize, intBuffer, buffer); - int numBytes = intBuffer.get(0); - byte[] bytes = new byte[numBytes]; - buffer.get(bytes); - return new String(bytes); - } - - @Override - public void glBindFragDataLocation(int param1, int param2, String param3) { - GLContext.getCurrentGL().getGL2GL3().glBindFragDataLocation(param1, param2, param3); - } - - @Override - public void glBindVertexArray(int param1) { - GLContext.getCurrentGL().getGL2ES3().glBindVertexArray(param1); - } - - @Override - public void glGenVertexArrays(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().getGL2ES3().glGenVertexArrays(param1.limit(), param1); - } - - @Override - public void glPatchParameter(int count) { - GLContext.getCurrentGL().getGL3().glPatchParameteri(com.jogamp.opengl.GL3.GL_PATCH_VERTICES, count); - } - - @Override - public void glDeleteVertexArrays(IntBuffer arrays) { - checkLimit(arrays); - GLContext.getCurrentGL().getGL2ES3().glDeleteVertexArrays(arrays.limit(), arrays); - } - - @Override - public int glGetUniformBlockIndex(final int program, final String uniformBlockName) { - return GLContext.getCurrentGL().getGL3bc().glGetUniformBlockIndex(program, uniformBlockName); - } - - @Override - public void glBindBufferBase(final int target, final int index, final int buffer) { - GLContext.getCurrentGL().getGL3bc().glBindBufferBase(target, index, buffer); - } - - @Override - public int glGetProgramResourceIndex(final int program, final int programInterface, final String name) { - throw new UnsupportedOperationException(); - //return GLContext.getCurrentGL().getGL4bc().glGetProgramResourceIndex(program, programInterface, name); - } - - @Override - public void glShaderStorageBlockBinding(final int program, final int storageBlockIndex, final int storageBlockBinding) { - GLContext.getCurrentGL().getGL4bc().glShaderStorageBlockBinding(program, storageBlockIndex, storageBlockBinding); - } - - @Override - public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { - GLContext.getCurrentGL().getGL3bc().glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLExt.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLExt.java deleted file mode 100644 index a87e8a035d..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLExt.java +++ /dev/null @@ -1,88 +0,0 @@ -package com.jme3.renderer.jogl; - -import com.jme3.renderer.RendererException; -import com.jme3.renderer.opengl.GLExt; -import com.jogamp.opengl.GLContext; - -import java.nio.Buffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; - -public class JoglGLExt implements GLExt { - - private static int getLimitBytes(IntBuffer buffer) { - checkLimit(buffer); - return buffer.limit() * 4; - } - - private static void checkLimit(Buffer buffer) { - if (buffer == null) { - return; - } - if (buffer.limit() == 0) { - throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); - } - if (buffer.remaining() == 0) { - throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); - } - } - - @Override - public void glBufferData(int target, IntBuffer data, int usage) { - checkLimit(data); - GLContext.getCurrentGL().glBufferData(target, getLimitBytes(data), data, usage); - } - - @Override - public void glBufferSubData(int target, long offset, IntBuffer data) { - checkLimit(data); - GLContext.getCurrentGL().glBufferSubData(target, getLimitBytes(data), offset, data); - } - - @Override - public void glDrawArraysInstancedARB(int mode, int first, int count, int primcount) { - GLContext.getCurrentGL().getGL2ES3().glDrawArraysInstanced(mode, first, count, primcount); - } - - @Override - public void glDrawBuffers(IntBuffer bufs) { - checkLimit(bufs); - GLContext.getCurrentGL().getGL2ES2().glDrawBuffers(bufs.limit(), bufs); - } - - @Override - public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { - GLContext.getCurrentGL().getGL2ES3().glDrawElementsInstanced(mode, indices_count, type, indices_buffer_offset, primcount); - } - - @Override - public void glGetMultisample(int pname, int index, FloatBuffer val) { - checkLimit(val); - GLContext.getCurrentGL().getGL2ES2().glGetMultisamplefv(pname, index, val); - } - - @Override - public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { - GLContext.getCurrentGL().getGL2ES2().glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); - } - - @Override - public void glVertexAttribDivisorARB(int index, int divisor) { - GLContext.getCurrentGL().getGL2ES3().glVertexAttribDivisor(index, divisor); - } - - @Override - public Object glFenceSync(int condition, int flags) { - return GLContext.getCurrentGL().getGL3ES3().glFenceSync(condition, flags); - } - - @Override - public int glClientWaitSync(Object sync, int flags, long timeout) { - return GLContext.getCurrentGL().getGL3ES3().glClientWaitSync(((Long) sync).longValue(), flags, timeout); - } - - @Override - public void glDeleteSync(Object sync) { - GLContext.getCurrentGL().getGL3ES3().glDeleteSync(((Long) sync).longValue()); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLFbo.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLFbo.java deleted file mode 100644 index b3451be253..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/JoglGLFbo.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.jme3.renderer.jogl; - -import com.jme3.renderer.RendererException; -import com.jme3.renderer.opengl.GLFbo; -import com.jogamp.opengl.GLContext; - -import java.nio.Buffer; -import java.nio.IntBuffer; - -/** - * Implements GLFbo - * - * @author Kirill Vainer - */ -public class JoglGLFbo implements GLFbo { - - private static void checkLimit(Buffer buffer) { - if (buffer == null) { - return; - } - if (buffer.limit() == 0) { - throw new RendererException("Attempting to upload empty buffer (limit = 0), that's an error"); - } - if (buffer.remaining() == 0) { - throw new RendererException("Attempting to upload empty buffer (remaining = 0), that's an error"); - } - } - - @Override - public void glBlitFramebufferEXT(int srcX0, int srcY0, int srcX1, int srcY1, int dstX0, int dstY0, int dstX1, int dstY1, int mask, int filter) { - GLContext.getCurrentGL().getGL2ES3().glBlitFramebuffer(srcX0, srcY0, srcX1, srcY1, dstX0, dstY0, dstX1, dstY1, mask, filter); - } - - @Override - public void glRenderbufferStorageMultisampleEXT(int target, int samples, int internalformat, int width, int height) { - GLContext.getCurrentGL().glRenderbufferStorageMultisample(target, samples, internalformat, width, height); - } - - @Override - public void glBindFramebufferEXT(int param1, int param2) { - GLContext.getCurrentGL().glBindFramebuffer(param1, param2); - } - - @Override - public void glBindRenderbufferEXT(int param1, int param2) { - GLContext.getCurrentGL().glBindRenderbuffer(param1, param2); - } - - @Override - public int glCheckFramebufferStatusEXT(int param1) { - return GLContext.getCurrentGL().glCheckFramebufferStatus(param1); - } - - @Override - public void glDeleteFramebuffersEXT(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glDeleteFramebuffers(param1.limit(), param1); - } - - @Override - public void glDeleteRenderbuffersEXT(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glDeleteRenderbuffers(param1.limit(), param1); - } - - @Override - public void glFramebufferRenderbufferEXT(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().glFramebufferRenderbuffer(param1, param2, param3, param4); - } - - @Override - public void glFramebufferTexture2DEXT(int param1, int param2, int param3, int param4, int param5) { - GLContext.getCurrentGL().glFramebufferTexture2D(param1, param2, param3, param4, param5); - } - - @Override - public void glGenFramebuffersEXT(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glGenFramebuffers(param1.limit(), param1); - } - - @Override - public void glGenRenderbuffersEXT(IntBuffer param1) { - checkLimit(param1); - GLContext.getCurrentGL().glGenRenderbuffers(param1.limit(), param1); - } - - @Override - public void glGenerateMipmapEXT(int param1) { - GLContext.getCurrentGL().glGenerateMipmap(param1); - } - - @Override - public void glRenderbufferStorageEXT(int param1, int param2, int param3, int param4) { - GLContext.getCurrentGL().glRenderbufferStorage(param1, param2, param3, param4); - } - - @Override - public void glFramebufferTextureLayerEXT(int param1, int param2, int param3, int param4, int param5) { - GLContext.getCurrentGL().getGL3().glFramebufferTextureLayer(param1, param2, param3, param4, param5); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java b/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java deleted file mode 100644 index 17bf21bfdf..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/renderer/jogl/TextureUtil.java +++ /dev/null @@ -1,529 +0,0 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.renderer.jogl; - -import com.jme3.renderer.RendererException; -import com.jme3.texture.Image; -import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ColorSpace; - -import java.nio.ByteBuffer; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jogamp.opengl.GL; -import com.jogamp.opengl.GL2; -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GL2ES3; -import com.jogamp.opengl.GL2GL3; -import com.jogamp.opengl.GLContext; - -public class TextureUtil { - - private static boolean abgrToRgbaConversionEnabled = false; - - public static int convertTextureFormat(Format fmt) { - switch (fmt) { - case Alpha8: - return GL.GL_ALPHA; - case Luminance8Alpha8: - return GL.GL_LUMINANCE_ALPHA; - case Luminance8: - return GL.GL_LUMINANCE; - case BGR8: - case RGB8: - case RGB565: - return GL.GL_RGB; - case RGB5A1: - case RGBA8: - return GL.GL_RGBA; - case Depth: - return GL2ES2.GL_DEPTH_COMPONENT; - default: - throw new UnsupportedOperationException("Unrecognized format: " + fmt); - } - } - - public static class GLImageFormat { - - int internalFormat; - int format; - int dataType; - boolean compressed; - - public GLImageFormat(int internalFormat, int format, int dataType, boolean compressed) { - this.internalFormat = internalFormat; - this.format = format; - this.dataType = dataType; - this.compressed = compressed; - } - } - - private static final GLImageFormat[] formatToGL = new GLImageFormat[Format.values().length]; - - private static void setFormat(Format format, int glInternalFormat, int glFormat, int glDataType, boolean glCompressed){ - formatToGL[format.ordinal()] = new GLImageFormat(glInternalFormat, glFormat, glDataType, glCompressed); - } - - static { - // Alpha formats - setFormat(Format.Alpha8, GL2.GL_ALPHA8, GL.GL_ALPHA, GL.GL_UNSIGNED_BYTE, false); - - // Luminance formats - setFormat(Format.Luminance8, GL2.GL_LUMINANCE8, GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.Luminance16F, GL2.GL_LUMINANCE16F, GL.GL_LUMINANCE, GL.GL_HALF_FLOAT, false); - setFormat(Format.Luminance32F, GL2.GL_LUMINANCE32F, GL.GL_LUMINANCE, GL.GL_FLOAT, false); - - // Luminance alpha formats - setFormat(Format.Luminance8Alpha8, GL2.GL_LUMINANCE8_ALPHA8, GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.Luminance16FAlpha16F, GL2.GL_LUMINANCE_ALPHA16F, GL.GL_LUMINANCE_ALPHA, GL.GL_HALF_FLOAT, false); - - // Depth formats - setFormat(Format.Depth, GL2ES2.GL_DEPTH_COMPONENT, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.Depth16, GL.GL_DEPTH_COMPONENT16, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_SHORT, false); - setFormat(Format.Depth24, GL.GL_DEPTH_COMPONENT24, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); - setFormat(Format.Depth32, GL.GL_DEPTH_COMPONENT32, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_UNSIGNED_INT, false); - setFormat(Format.Depth32F, GL2GL3.GL_DEPTH_COMPONENT32F, GL2ES2.GL_DEPTH_COMPONENT, GL.GL_FLOAT, false); - - // Depth stencil formats - setFormat(Format.Depth24Stencil8, GL.GL_DEPTH24_STENCIL8, GL.GL_DEPTH_STENCIL, GL.GL_UNSIGNED_INT_24_8, false); - - // RGB formats - setFormat(Format.BGR8, GL.GL_RGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.ARGB8, GL.GL_RGBA8, GL.GL_BGRA, GL2.GL_UNSIGNED_INT_8_8_8_8_REV, false); - setFormat(Format.BGRA8, GL.GL_RGBA8, GL.GL_BGRA, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.RGB8, GL.GL_RGB8, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.RGB16F, GL2ES2.GL_RGB16F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); - setFormat(Format.RGB32F, GL.GL_RGB32F, GL.GL_RGB, GL.GL_FLOAT, false); - - // Special RGB formats - setFormat(Format.RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_UNSIGNED_INT_10F_11F_11F_REV, false); - setFormat(Format.RGB9E5, GL2GL3.GL_RGB9_E5, GL.GL_RGB, GL2GL3.GL_UNSIGNED_INT_5_9_9_9_REV, false); - setFormat(Format.RGB16F_to_RGB111110F, GL2ES3.GL_R11F_G11F_B10F, GL.GL_RGB, GL.GL_HALF_FLOAT, false); - setFormat(Format.RGB16F_to_RGB9E5, GL2.GL_RGB9_E5, GL.GL_RGB, GL.GL_HALF_FLOAT, false); - - // RGBA formats - setFormat(Format.ABGR8, GL.GL_RGBA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.RGB5A1, GL.GL_RGB5_A1, GL.GL_RGBA, GL.GL_UNSIGNED_SHORT_5_5_5_1, false); - setFormat(Format.RGBA8, GL.GL_RGBA8, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); - setFormat(Format.RGBA16F, GL2ES2.GL_RGBA16F, GL.GL_RGBA, GL.GL_HALF_FLOAT, false); - setFormat(Format.RGBA32F, GL.GL_RGBA32F, GL.GL_RGBA, GL.GL_FLOAT, false); - - // DXT formats - setFormat(Format.DXT1, GL.GL_COMPRESSED_RGB_S3TC_DXT1_EXT, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT1A, GL.GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT3, GL.GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - setFormat(Format.DXT5, GL.GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - } - - //sRGB formats - private static final GLImageFormat sRGB_RGB8 = new GLImageFormat(GL2.GL_SRGB8,GL.GL_RGB, GL.GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_RGBA8 = new GLImageFormat(GL.GL_SRGB8_ALPHA8,GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_Luminance8 = new GLImageFormat(GL2.GL_SLUMINANCE8,GL.GL_LUMINANCE, GL.GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_LuminanceAlpha8 = new GLImageFormat(GL2.GL_SLUMINANCE8_ALPHA8,GL.GL_LUMINANCE_ALPHA, GL.GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_BGR8 = new GLImageFormat(GL2.GL_SRGB8, GL2GL3.GL_BGR, GL.GL_UNSIGNED_BYTE, false); - private static final GLImageFormat sRGB_ABGR8 = new GLImageFormat(GL2.GL_SRGB8_ALPHA8, GL2.GL_ABGR_EXT, GL.GL_UNSIGNED_BYTE, false); - - //FIXME cannot find GL_COMPRESSED_RGB_S3TC_DXT1,GL_COMPRESSED_RGBA_S3TC_DXT1,GL_COMPRESSED_RGB_S3TC_DXT3,GL_COMPRESSED_RGB_S3TC_DXT5 in JOGL used constants - //GL_COMPRESSED_RGB_S3TC_DXT1 = 33776; - //GL_COMPRESSED_RGBA_S3TC_DXT1_EXT = 33777; - //GL_COMPRESSED_RGBA_S3TC_DXT3_EXT = 33778; - //GL_COMPRESSED_RGBA_S3TC_DXT5_EXT = 33779; - private static final GLImageFormat sRGB_DXT1 = new GLImageFormat(33776, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT1A = new GLImageFormat( 33777, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT3 = new GLImageFormat(33778, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - private static final GLImageFormat sRGB_DXT5 = new GLImageFormat( 33779, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, true); - - - public static GLImageFormat getImageFormat(Format fmt, boolean isSrgb){ - GL gl = GLContext.getCurrentGL(); - switch (fmt){ - case ABGR8: - if (!gl.isExtensionAvailable("GL_EXT_abgr") && !abgrToRgbaConversionEnabled) { - setFormat(Format.ABGR8, GL.GL_RGBA, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, false); - abgrToRgbaConversionEnabled = true; - } - break; - case BGR8: - if (!gl.isExtensionAvailable("GL_VERSION_1_2") && !gl.isExtensionAvailable("EXT_bgra")){ - return null; - } - break; - case DXT1: - case DXT1A: - case DXT3: - case DXT5: - if (!gl.isExtensionAvailable("GL_EXT_texture_compression_s3tc")) { - return null; - } - break; - case Depth: - case Depth16: - case Depth24: - case Depth32: - if (!gl.isExtensionAvailable("GL_VERSION_1_4") && !gl.isExtensionAvailable("ARB_depth_texture")){ - return null; - } - break; - case Depth24Stencil8: - if (!gl.isExtensionAvailable("GL_VERSION_3_0")){ - return null; - } - break; - case Luminance16F: - case Luminance16FAlpha16F: - case Luminance32F: - if (!gl.isExtensionAvailable("GL_ARB_texture_float")){ - return null; - } - break; - case RGB16F: - case RGB32F: - case RGBA16F: - case RGBA32F: - if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_ARB_texture_float")){ - return null; - } - break; - case Depth32F: - if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_NV_depth_buffer_float")){ - return null; - } - break; - case RGB9E5: - case RGB16F_to_RGB9E5: - if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_texture_shared_exponent")){ - return null; - } - break; - case RGB111110F: - case RGB16F_to_RGB111110F: - if (!gl.isExtensionAvailable("GL_VERSION_3_0") && !gl.isExtensionAvailable("GL_EXT_packed_float")){ - return null; - } - break; - } - - if(isSrgb){ - return getSrgbFormat(fmt); - } - return formatToGL[fmt.ordinal()]; - } - - public static GLImageFormat getImageFormatWithError(Format fmt, boolean isSrgb) { - GLImageFormat glFmt = getImageFormat(fmt, isSrgb); - if (glFmt == null) { - throw new RendererException("Image format '" + fmt + "' is unsupported by the video hardware."); - } - return glFmt; - } - - private static GLImageFormat getSrgbFormat(Format fmt){ - switch (fmt){ - case RGB8 : return sRGB_RGB8; - case RGBA8 : return sRGB_RGBA8; - case BGR8 : return sRGB_BGR8; - case ABGR8 : return sRGB_ABGR8; - case Luminance8 : return sRGB_Luminance8; - case Luminance8Alpha8 : return sRGB_LuminanceAlpha8; - case DXT1 : return sRGB_DXT1; - case DXT1A : return sRGB_DXT1A; - case DXT3 : return sRGB_DXT3; - case DXT5 : return sRGB_DXT5; - default : Logger.getLogger(TextureUtil.class.getName()).log(Level.WARNING, "Format {0} has no sRGB equivalent, using linear format.", fmt.toString()); - return formatToGL[fmt.ordinal()]; - } - } - - public static void uploadTexture(Image image, - int target, - int index, - int border, - boolean linearizeSrgb){ - GL gl = GLContext.getCurrentGL(); - Image.Format fmt = image.getFormat(); - GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); - - ByteBuffer data; - if (index >= 0 && image.getData() != null && image.getData().size() > 0){ - data = image.getData(index); - }else{ - data = null; - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - - if (data != null) { - if (abgrToRgbaConversionEnabled) { - convertABGRtoRGBA(data); - } - gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); - } - - int[] mipSizes = image.getMipMapSizes(); - int pos = 0; - // TODO: Remove unnecessary allocation - if (mipSizes == null){ - if (data != null) - mipSizes = new int[]{ data.capacity() }; - else - mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; - } - - boolean subtex = false; - int samples = image.getMultiSamples(); - - for (int i = 0; i < mipSizes.length; i++){ - int mipWidth = Math.max(1, width >> i); - int mipHeight = Math.max(1, height >> i); - int mipDepth = Math.max(1, depth >> i); - - if (data != null){ - data.position(pos); - data.limit(pos + mipSizes[i]); - } - - if (glFmt.compressed && data != null){ - if (target == GL2ES2.GL_TEXTURE_3D){ - gl.getGL2ES2().glCompressedTexImage3D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - mipDepth, - data.remaining(), - border, - data); - }else{ - //all other targets use 2D: array, cubemap, 2d - gl.glCompressedTexImage2D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - data.remaining(), - border, - data); - } - }else{ - if (target == GL2ES2.GL_TEXTURE_3D){ - gl.getGL2ES2().glTexImage3D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - mipDepth, - border, - glFmt.format, - glFmt.dataType, - data); - }else if (target == GL2ES3.GL_TEXTURE_2D_ARRAY){ - // prepare data for 2D array - // or upload slice - if (index == -1){ - gl.getGL2ES2().glTexImage3D(target, - 0, - glFmt.internalFormat, - mipWidth, - mipHeight, - image.getData().size(), //# of slices - border, - glFmt.format, - glFmt.dataType, - data); - }else{ - gl.getGL2ES2().glTexSubImage3D(target, - i, // level - 0, // xoffset - 0, // yoffset - index, // zoffset - width, // width - height, // height - 1, // depth - glFmt.format, - glFmt.dataType, - data); - } - }else{ - if (subtex){ - if (samples > 1){ - throw new IllegalStateException("Cannot update multisample textures"); - } - - gl.glTexSubImage2D(target, - i, - 0, 0, - mipWidth, mipHeight, - glFmt.format, - glFmt.dataType, - data); - }else{ - if (samples > 1){ - if (gl.isGL2GL3()) { - gl.getGL3().glTexImage2DMultisample(target, - samples, - glFmt.internalFormat, - mipWidth, - mipHeight, - true); - } - } else { - gl.glTexImage2D(target, - i, - glFmt.internalFormat, - mipWidth, - mipHeight, - border, - glFmt.format, - glFmt.dataType, - data); - } - } - } - } - - pos += mipSizes[i]; - } - } - - private static void convertABGRtoRGBA(ByteBuffer buffer) { - - for (int i = 0; i < buffer.capacity(); i++) { - - int a = buffer.get(i++); - int b = buffer.get(i++); - int g = buffer.get(i++); - int r = buffer.get(i); - - buffer.put(i - 3, (byte) r); - buffer.put(i - 2, (byte) g); - buffer.put(i - 1, (byte) b); - buffer.put(i, (byte) a); - - } - - } - - /** - * Update the texture currently bound to target at with data from the given Image at position x and y. The parameter - * index is used as the zoffset in case a 3d texture or texture 2d array is being updated. - * - * @param image Image with the source data (this data will be put into the texture) - * @param target the target texture - * @param index the mipmap level to update - * @param x the x position where to put the image in the texture - * @param y the y position where to put the image in the texture - */ - public static void uploadSubTexture( - Image image, - int target, - int index, - int x, - int y, - boolean linearizeSrgb) { - GL gl = GLContext.getCurrentGL(); - Image.Format fmt = image.getFormat(); - GLImageFormat glFmt = getImageFormatWithError(fmt, image.getColorSpace() == ColorSpace.sRGB && linearizeSrgb); - - ByteBuffer data = null; - if (index >= 0 && image.getData() != null && image.getData().size() > 0) { - data = image.getData(index); - } - - int width = image.getWidth(); - int height = image.getHeight(); - int depth = image.getDepth(); - - if (data != null) { - gl.glPixelStorei(GL.GL_UNPACK_ALIGNMENT, 1); - } - - int[] mipSizes = image.getMipMapSizes(); - int pos = 0; - - // TODO: Remove unnecessary allocation - if (mipSizes == null){ - if (data != null) { - mipSizes = new int[]{ data.capacity() }; - } else { - mipSizes = new int[]{ width * height * fmt.getBitsPerPixel() / 8 }; - } - } - - int samples = image.getMultiSamples(); - - for (int i = 0; i < mipSizes.length; i++){ - int mipWidth = Math.max(1, width >> i); - int mipHeight = Math.max(1, height >> i); - int mipDepth = Math.max(1, depth >> i); - - if (data != null){ - data.position(pos); - data.limit(pos + mipSizes[i]); - } - - // to remove the cumbersome if/then/else stuff below we'll update the pos right here and use continue after each - // gl*Image call in an attempt to unclutter things a bit - pos += mipSizes[i]; - - int glFmtInternal = glFmt.internalFormat; - int glFmtFormat = glFmt.format; - int glFmtDataType = glFmt.dataType; - - if (glFmt.compressed && data != null){ - if (target == GL2ES2.GL_TEXTURE_3D){ - gl.getGL2ES2().glCompressedTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtInternal, data.limit(), data); - continue; - } - - // all other targets use 2D: array, cubemap, 2d - gl.getGL2ES2().glCompressedTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtInternal, data.limit(), data); - continue; - } - - if (target == GL2ES2.GL_TEXTURE_3D){ - gl.getGL2ES2().glTexSubImage3D(target, i, x, y, index, mipWidth, mipHeight, mipDepth, glFmtFormat, glFmtDataType, data); - continue; - } - - if (samples > 1){ - throw new IllegalStateException("Cannot update multisample textures"); - } - - gl.glTexSubImage2D(target, i, x, y, mipWidth, mipHeight, glFmtFormat, glFmtDataType, data); - continue; - } - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java deleted file mode 100644 index c4f38f970d..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglAbstractDisplay.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.awt.AwtKeyInput; -import com.jme3.input.awt.AwtMouseInput; -import com.jme3.system.AppSettings; -import com.jogamp.opengl.util.Animator; -import com.jogamp.opengl.util.AnimatorBase; -import com.jogamp.opengl.util.FPSAnimator; - -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Logger; - -import com.jogamp.opengl.GLAutoDrawable; -import com.jogamp.opengl.GLCapabilities; -import com.jogamp.opengl.GLEventListener; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.GLRunnable; -import com.jogamp.opengl.awt.GLCanvas; - -public abstract class JoglAbstractDisplay extends JoglContext implements GLEventListener { - - private static final Logger logger = Logger.getLogger(JoglAbstractDisplay.class.getName()); - - protected GraphicsDevice device; - - protected GLCanvas canvas; - - protected AnimatorBase animator; - - protected AtomicBoolean active = new AtomicBoolean(false); - - protected boolean wasActive = false; - - protected int frameRate; - - protected boolean useAwt = true; - - protected AtomicBoolean autoFlush = new AtomicBoolean(true); - - protected boolean wasAnimating = false; - - protected void initGLCanvas() { - device = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - - GLCapabilities caps; - if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { - caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); - } else { - caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); - } - - caps.setHardwareAccelerated(true); - caps.setDoubleBuffered(true); - caps.setStencilBits(settings.getStencilBits()); - caps.setDepthBits(settings.getDepthBits()); - - if (settings.getSamples() > 1) { - caps.setSampleBuffers(true); - caps.setNumSamples(settings.getSamples()); - } - - canvas = new GLCanvas(caps) { - @Override - public void addNotify() { - super.addNotify(); - onCanvasAdded(); - } - - @Override - public void removeNotify() { - onCanvasRemoved(); - super.removeNotify(); - } - }; - canvas.invoke(false, new GLRunnable() { - public boolean run(GLAutoDrawable glad) { - canvas.getGL().setSwapInterval(settings.isVSync() ? 1 : 0); - return true; - } - }); - canvas.setFocusable(true); - canvas.requestFocus(); - canvas.setSize(settings.getWidth(), settings.getHeight()); - canvas.setIgnoreRepaint(true); - canvas.addGLEventListener(this); - - //FIXME not sure it is the best place to do that - renderable.set(true); - } - - protected void startGLCanvas() { - if (frameRate > 0) { - animator = new FPSAnimator(canvas, frameRate); - } - else { - animator = new Animator(); - animator.add(canvas); - ((Animator) animator).setRunAsFastAsPossible(true); - } - - animator.start(); - wasAnimating = true; - } - - protected void onCanvasAdded() { - } - - protected void onCanvasRemoved() { - } - - @Override - public KeyInput getKeyInput() { - if (keyInput == null) { - keyInput = new AwtKeyInput(); - ((AwtKeyInput)keyInput).setInputSource(canvas); - } - return keyInput; - } - - @Override - public MouseInput getMouseInput() { - if (mouseInput == null) { - mouseInput = new AwtMouseInput(); - ((AwtMouseInput)mouseInput).setInputSource(canvas); - } - return mouseInput; - } - - public TouchInput getTouchInput() { - return null; - } - - public void setAutoFlushFrames(boolean enabled) { - autoFlush.set(enabled); - } - - /** - * Callback. - */ - public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { - listener.reshape(width, height); - } - - /** - * Callback. - */ - public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { - } - - /** - * Callback - */ - public void dispose(GLAutoDrawable drawable) { - - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java deleted file mode 100644 index b56eb43c1e..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglCanvas.java +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.system.JmeCanvasContext; -import java.awt.Canvas; -import java.util.logging.Logger; -import com.jogamp.opengl.GLAutoDrawable; - -public class JoglCanvas extends JoglAbstractDisplay implements JmeCanvasContext { - - private static final Logger logger = Logger.getLogger(JoglCanvas.class.getName()); - private int width, height; - private boolean runningFirstTime = true; - - public JoglCanvas(){ - super(); - initGLCanvas(); - } - - @Override - public Type getType() { - return Type.Canvas; - } - - @Override - public void setTitle(String title) { - } - - @Override - public void restart() { - } - - @Override - public void create(boolean waitFor){ - if (waitFor) - waitFor(true); - } - - @Override - public void destroy(boolean waitFor){ - if (waitFor) - waitFor(false); - if (animator.isAnimating()) - animator.stop(); - } - - @Override - protected void onCanvasRemoved(){ - super.onCanvasRemoved(); - created.set(false); - waitFor(false); - } - - @Override - protected void onCanvasAdded(){ - startGLCanvas(); - } - - @Override - public void init(GLAutoDrawable drawable) { - canvas.requestFocus(); - - super.internalCreate(); - logger.fine("Display created."); - - // At this point, the OpenGL context is active. - if (runningFirstTime){ - // THIS is the part that creates the renderer. - // It must always be called, now that we have the pbuffer workaround. - initContextFirstTime(); - runningFirstTime = false; - } - listener.initialize(); - } - - @Override - protected void startGLCanvas() { - frameRate = settings.getFrameRate(); - super.startGLCanvas(); - } - - @Override - public void display(GLAutoDrawable glad) { - if (!created.get() && renderer != null){ - listener.destroy(); - logger.fine("Canvas destroyed."); - super.internalDestroy(); - return; - } - - int newWidth = Math.max(canvas.getWidth(), 1); - int newHeight = Math.max(canvas.getHeight(), 1); - if (width != newWidth || height != newHeight) { - width = newWidth; - height = newHeight; - if (listener != null) { - listener.reshape(width, height); - } - } - - boolean flush = autoFlush.get(); - if (flush && !wasAnimating){ - animator.start(); - wasAnimating = true; - }else if (!flush && wasAnimating){ - animator.stop(); - wasAnimating = false; - } - - listener.update(); - renderer.postFrame(); - - } - - @Override - public Canvas getCanvas() { - return canvas; - } - - @Override - public void dispose(GLAutoDrawable arg0) { - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java deleted file mode 100644 index 0d3af3a70f..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglContext.java +++ /dev/null @@ -1,383 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.input.JoyInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.opencl.Context; -import com.jme3.opencl.DefaultPlatformChooser; -import com.jme3.opencl.Device; -import com.jme3.opencl.PlatformChooser; -import com.jme3.opencl.jocl.JoclDevice; -import com.jme3.opencl.jocl.JoclPlatform; -import com.jme3.renderer.Renderer; -import com.jme3.renderer.RendererException; -import com.jme3.renderer.jogl.JoglGL; -import com.jme3.renderer.jogl.JoglGLExt; -import com.jme3.renderer.jogl.JoglGLFbo; -import com.jme3.renderer.opengl.GL2; -import com.jme3.renderer.opengl.GL3; -import com.jme3.renderer.opengl.GL4; -import com.jme3.renderer.opengl.GLDebugDesktop; -import com.jme3.renderer.opengl.GLExt; -import com.jme3.renderer.opengl.GLFbo; -import com.jme3.renderer.opengl.GLRenderer; -import com.jme3.renderer.opengl.GLTiming; -import com.jme3.renderer.opengl.GLTimingState; -import com.jme3.renderer.opengl.GLTracer; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeContext; -import com.jme3.system.NanoTimer; -import com.jme3.system.SystemListener; -import com.jme3.system.Timer; -import com.jogamp.opencl.CLDevice; -import com.jogamp.opencl.CLPlatform; -import com.jogamp.opencl.gl.CLGLContext; - -import java.nio.IntBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jogamp.opengl.GL; -import com.jogamp.opengl.GL2GL3; -import com.jogamp.opengl.GLContext; -import java.util.ArrayList; -import java.util.List; - -public abstract class JoglContext implements JmeContext { - - private static final Logger logger = Logger.getLogger(JoglContext.class.getName()); - - protected static final String THREAD_NAME = "jME3 Main"; - - protected AtomicBoolean created = new AtomicBoolean(false); - protected AtomicBoolean renderable = new AtomicBoolean(false); - protected final Object createdLock = new Object(); - - protected AppSettings settings = new AppSettings(true); - protected Renderer renderer; - protected Timer timer; - protected SystemListener listener; - - protected KeyInput keyInput; - protected MouseInput mouseInput; - protected JoyInput joyInput; - - protected com.jme3.opencl.Context clContext; - - @Override - public void setSystemListener(SystemListener listener){ - this.listener = listener; - } - - @Override - public void setSettings(AppSettings settings) { - this.settings.copyFrom(settings); - } - - @Override - public boolean isRenderable(){ - return renderable.get(); - } - - @Override - public AppSettings getSettings() { - return settings; - } - - @Override - public Renderer getRenderer() { - return renderer; - } - - @Override - public MouseInput getMouseInput() { - return mouseInput; - } - - @Override - public KeyInput getKeyInput() { - return keyInput; - } - - @Override - public JoyInput getJoyInput() { - return joyInput; - } - - @Override - public Timer getTimer() { - return timer; - } - - @Override - public boolean isCreated() { - return created.get(); - } - - public void create(){ - create(false); - } - - public void destroy(){ - destroy(false); - } - - protected void waitFor(boolean createdVal){ - synchronized (createdLock){ - while (created.get() != createdVal){ - try { - createdLock.wait(); - } catch (InterruptedException ex) { - } - } - } - } - - protected void initContextFirstTime(){ - if (GLContext.getCurrent().getGLVersionNumber().getMajor() < 2) { - throw new RendererException("OpenGL 2.0 or higher is " + - "required for jMonkeyEngine"); - } - - if (settings.getRenderer().startsWith("JOGL")) { - com.jme3.renderer.opengl.GL gl = new JoglGL(); - GLExt glext = new JoglGLExt(); - GLFbo glfbo = new JoglGLFbo(); - - if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugDesktop(gl, glext, glfbo); - glext = (GLExt) gl; - glfbo = (GLFbo) gl; - } - - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (com.jme3.renderer.opengl.GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } - - if (settings.getBoolean("GraphicsTrace")) { - gl = (com.jme3.renderer.opengl.GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); - } - - renderer = new GLRenderer(gl, glext, glfbo); - renderer.initialize(); - } else { - throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); - } - - if (GLContext.getCurrentGL().isExtensionAvailable("GL_ARB_debug_output") && settings.getBoolean("GraphicsDebug")) { - GLContext.getCurrent().enableGLDebugMessage(true); - GLContext.getCurrent().addGLDebugListener(new JoglGLDebugOutputHandler()); - } - - renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); - renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - - // Init input - if (keyInput != null) { - keyInput.initialize(); - } - - if (mouseInput != null) { - mouseInput.initialize(); - } - - if (joyInput != null) { - joyInput.initialize(); - } - - if (settings.isOpenCLSupport()) { - initOpenCL(); - } - } - - @SuppressWarnings("unchecked") - protected void initOpenCL() { - logger.info("Initialize OpenCL with JOGL"); - - //load platforms and devices - StringBuilder platformInfos = new StringBuilder(); - ArrayList platforms = new ArrayList(); - for (CLPlatform p : CLPlatform.listCLPlatforms()) { - platforms.add(new JoclPlatform(p)); - } - platformInfos.append("Available OpenCL platforms:"); - for (int i=0; i devices = platform.getDevices(); - platformInfos.append("\n * Available devices:"); - for (int j=0; j choosenDevices = chooser.chooseDevices(platforms); - List devices = new ArrayList<>(choosenDevices.size()); - JoclPlatform platform = null; - for (Device d : choosenDevices) { - if (!(d instanceof JoclDevice)) { - logger.log(Level.SEVERE, "attempt to return a custom Device implementation from PlatformChooser: {0}", d); - return; - } - JoclDevice ld = (JoclDevice) d; - if (platform == null) { - platform = ld.getPlatform(); - } else if (platform != ld.getPlatform()) { - logger.severe("attempt to use devices from different platforms"); - return; - } - devices.add(ld.getDevice()); - } - if (devices.isEmpty()) { - logger.warning("no devices specified, no OpenCL context created"); - return; - } - logger.log(Level.INFO, "chosen platform: {0}", platform.getName()); - logger.log(Level.INFO, "chosen devices: {0}", choosenDevices); - - //create context - try { - CLGLContext c = CLGLContext.create(GLContext.getCurrent(), devices.toArray(new CLDevice[devices.size()])); - clContext = new com.jme3.opencl.jocl.JoclContext(c, (List) choosenDevices); - } catch (Exception ex) { - logger.log(Level.SEVERE, "Unable to create OpenCL context", ex); - return; - } - - logger.info("OpenCL context created"); - } - - public void internalCreate() { - timer = new NanoTimer(); - synchronized (createdLock){ - created.set(true); - createdLock.notifyAll(); - } - if (renderable.get()){ - initContextFirstTime(); - } else { - assert getType() == Type.Canvas; - } - } - - protected void internalDestroy() { - renderer = null; - timer = null; - renderable.set(false); - synchronized (createdLock){ - created.set(false); - createdLock.notifyAll(); - } - } - - protected int determineMaxSamples(int requestedSamples) { - GL gl = GLContext.getCurrentGL(); - if (gl.hasFullFBOSupport()) { - return gl.getMaxRenderbufferSamples(); - } else { - if (gl.isExtensionAvailable("GL_ARB_framebuffer_object") - || gl.isExtensionAvailable("GL_EXT_framebuffer_multisample")) { - IntBuffer intBuf1 = IntBuffer.allocate(1); - gl.glGetIntegerv(GL2GL3.GL_MAX_SAMPLES, intBuf1); - return intBuf1.get(0); - } else { - return Integer.MAX_VALUE; - } - } - } - - protected int getNumSamplesToUse() { - int samples = 0; - if (settings.getSamples() > 1){ - samples = settings.getSamples(); - int supportedSamples = determineMaxSamples(samples); - if (supportedSamples < samples) { - logger.log(Level.WARNING, - "Couldn''t satisfy antialiasing samples requirement: x{0}. " - + "Video hardware only supports: x{1}", - new Object[]{samples, supportedSamples}); - - samples = supportedSamples; - } - } - return samples; - } - - @Override - public Context getOpenCLContext() { - return clContext; - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java deleted file mode 100644 index 3bac0ab53a..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglDisplay.java +++ /dev/null @@ -1,356 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.system.AppSettings; -import java.awt.Dimension; -import java.awt.DisplayMode; -import java.awt.Frame; -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.awt.Toolkit; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.lang.reflect.InvocationTargetException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import com.jogamp.opengl.GLAutoDrawable; -import javax.swing.JFrame; -import javax.swing.SwingUtilities; - -public class JoglDisplay extends JoglAbstractDisplay { - - private static final Logger logger = Logger.getLogger(JoglDisplay.class.getName()); - - protected AtomicBoolean windowCloseRequest = new AtomicBoolean(false); - protected AtomicBoolean needClose = new AtomicBoolean(false); - protected AtomicBoolean needRestart = new AtomicBoolean(false); - protected volatile boolean wasInited = false; - protected Frame frame; - - public Type getType() { - return Type.Display; - } - - protected void createGLFrame(){ - if (useAwt){ - frame = new Frame(settings.getTitle()); - }else{ - frame = new JFrame(settings.getTitle()); - } - frame.setResizable(false); - frame.add(canvas); - - applySettings(settings); - - // Make the window visible to realize the OpenGL surface. - frame.setVisible(true); - - canvas.setVisible(true); - - //this is the earliest safe opportunity to get the context - //final GLContext context = canvas.getContext(); - - /*canvas.invoke(true, new GLRunnable() { - @Override - public boolean run(GLAutoDrawable glAutoDrawable) { - context.makeCurrent(); - try { - startGLCanvas(); - } - finally { - context.release(); - } - return true; - } - });*/ - } - - protected void applySettings(AppSettings settings){ - final boolean isDisplayModeModified; - final GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - // Get the current display mode - final DisplayMode previousDisplayMode = gd.getDisplayMode(); - // Handle full screen mode if requested. - if (settings.isFullscreen()) { - frame.setUndecorated(true); - // Check if the full-screen mode is supported by the OS - boolean isFullScreenSupported = gd.isFullScreenSupported(); - if (isFullScreenSupported) { - gd.setFullScreenWindow(frame); - // Check if display mode changes are supported by the OS - if (gd.isDisplayChangeSupported()) { - // Get all available display modes - final DisplayMode[] displayModes = gd.getDisplayModes(); - DisplayMode multiBitsDepthSupportedDisplayMode = null; - DisplayMode refreshRateUnknownDisplayMode = null; - DisplayMode multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode = null; - DisplayMode matchingDisplayMode = null; - DisplayMode currentDisplayMode; - // Look for the display mode that matches with our parameters - // Look for some display modes that are close to these parameters - // and that could be used as substitutes - // On some machines, the refresh rate is unknown and/or multi bit - // depths are supported. If you try to force a particular refresh - // rate or a bit depth, you might find no available display mode - // that matches exactly with your parameters - for (int i = 0; i < displayModes.length && matchingDisplayMode == null; i++) { - currentDisplayMode = displayModes[i]; - if (currentDisplayMode.getWidth() == settings.getWidth() - && currentDisplayMode.getHeight() == settings.getHeight()) { - if (currentDisplayMode.getBitDepth() == settings.getBitsPerPixel()) { - if (currentDisplayMode.getRefreshRate() == settings.getFrequency()) { - matchingDisplayMode = currentDisplayMode; - } else if (currentDisplayMode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { - refreshRateUnknownDisplayMode = currentDisplayMode; - } - } else if (currentDisplayMode.getBitDepth() == DisplayMode.BIT_DEPTH_MULTI) { - if (currentDisplayMode.getRefreshRate() == settings.getFrequency()) { - multiBitsDepthSupportedDisplayMode = currentDisplayMode; - } else if (currentDisplayMode.getRefreshRate() == DisplayMode.REFRESH_RATE_UNKNOWN) { - multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode = currentDisplayMode; - } - } - } - } - DisplayMode nextDisplayMode = null; - if (matchingDisplayMode != null) { - nextDisplayMode = matchingDisplayMode; - } else if (multiBitsDepthSupportedDisplayMode != null) { - nextDisplayMode = multiBitsDepthSupportedDisplayMode; - } else if (refreshRateUnknownDisplayMode != null) { - nextDisplayMode = refreshRateUnknownDisplayMode; - } else if (multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode != null) { - nextDisplayMode = multiBitsDepthSupportedAndRefreshRateUnknownDisplayMode; - } else { - isFullScreenSupported = false; - } - // If we have found a display mode that approximatively matches - // with the input parameters, use it - if (nextDisplayMode != null) { - gd.setDisplayMode(nextDisplayMode); - isDisplayModeModified = true; - } else { - isDisplayModeModified = false; - } - } else { - isDisplayModeModified = false; - // Resize the canvas if the display mode cannot be changed - // and the screen size is not equal to the canvas size - final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - if (screenSize.width != settings.getWidth() || screenSize.height != settings.getHeight()) { - canvas.setSize(screenSize); - } - } - } else { - isDisplayModeModified = false; - } - - // Software windowed full-screen mode - if (!isFullScreenSupported) { - final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); - // Resize the canvas - canvas.setSize(screenSize); - // Resize the frame so that it occupies the whole screen - frame.setSize(screenSize); - // Set its location at the top left corner - frame.setLocation(0, 0); - } - } - // Otherwise, center the window on the screen. - else { - isDisplayModeModified = false; - frame.pack(); - - int x, y; - x = (Toolkit.getDefaultToolkit().getScreenSize().width - settings.getWidth()) / 2; - y = (Toolkit.getDefaultToolkit().getScreenSize().height - settings.getHeight()) / 2; - frame.setLocation(x, y); - } - - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosing(WindowEvent evt) { - // If required, restore the previous display mode - if (isDisplayModeModified) { - gd.setDisplayMode(previousDisplayMode); - } - // If required, get back to the windowed mode - if (gd.getFullScreenWindow() == frame) { - gd.setFullScreenWindow(null); - } - windowCloseRequest.set(true); - } - @Override - public void windowActivated(WindowEvent evt) { - active.set(true); - } - - @Override - public void windowDeactivated(WindowEvent evt) { - active.set(false); - } - }); - - logger.log(Level.FINE, "Selected display mode: {0}x{1}x{2} @{3}", - new Object[]{gd.getDisplayMode().getWidth(), - gd.getDisplayMode().getHeight(), - gd.getDisplayMode().getBitDepth(), - gd.getDisplayMode().getRefreshRate()}); - } - - private void initInEDT(){ - initGLCanvas(); - - createGLFrame(); - - startGLCanvas(); - } - - public void init(GLAutoDrawable drawable){ - // prevent initializing twice on restart - if (!wasInited){ - wasInited = true; - - canvas.requestFocus(); - - super.internalCreate(); - logger.fine("Display created."); - - renderer.initialize(); - listener.initialize(); - } - } - - public void create(boolean waitFor){ - if (SwingUtilities.isEventDispatchThread()) { - initInEDT(); - } else { - try { - if (waitFor) { - try { - SwingUtilities.invokeAndWait(new Runnable() { - public void run() { - initInEDT(); - } - }); - } catch (InterruptedException ex) { - listener.handleError("Interrupted", ex); - } - } else { - SwingUtilities.invokeLater(new Runnable() { - public void run() { - initInEDT(); - } - }); - } - } catch (InvocationTargetException ex) { - throw new AssertionError(); // can never happen - } - } - } - - public void destroy(boolean waitFor){ - needClose.set(true); - if (waitFor){ - waitFor(false); - } - } - - public void restart() { - if (created.get()){ - needRestart.set(true); - }else{ - throw new IllegalStateException("Display not started yet. Cannot restart"); - } - } - - public void setTitle(String title){ - if (frame != null) - frame.setTitle(title); - } - - /** - * Callback. - */ - public void display(GLAutoDrawable drawable) { - if (needClose.get()) { - listener.destroy(); - animator.stop(); - if (settings.isFullscreen()) { - device.setFullScreenWindow(null); - } - frame.dispose(); - logger.fine("Display destroyed."); - super.internalDestroy(); - return; - } - - if (windowCloseRequest.get()){ - listener.requestClose(false); - windowCloseRequest.set(false); - } - - if (needRestart.getAndSet(false)){ - // for restarting contexts - if (frame.isVisible()){ - animator.stop(); - frame.dispose(); - createGLFrame(); - startGLCanvas(); - } - } - -// boolean flush = autoFlush.get(); -// if (animator.isAnimating() != flush){ -// if (flush) -// animator.stop(); -// else -// animator.start(); -// } - - if (wasActive != active.get()){ - if (!wasActive){ - listener.gainFocus(); - wasActive = true; - }else{ - listener.loseFocus(); - wasActive = false; - } - } - - listener.update(); - renderer.postFrame(); - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglGLDebugOutputHandler.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglGLDebugOutputHandler.java deleted file mode 100644 index 5a8b587153..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglGLDebugOutputHandler.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) 2009-2015 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.system.jogl; - -import java.util.HashMap; - -import com.jogamp.opengl.GL2ES2; -import com.jogamp.opengl.GLDebugListener; -import com.jogamp.opengl.GLDebugMessage; - -class JoglGLDebugOutputHandler implements GLDebugListener { - - private static final HashMap constMap = new HashMap(); - private static final String MESSAGE_FORMAT = - "[JME3] OpenGL debug message\r\n" + - " ID: %d\r\n" + - " Source: %s\r\n" + - " Type: %s\r\n" + - " Severity: %s\r\n" + - " Message: %s"; - - static { - constMap.put(GL2ES2.GL_DEBUG_SOURCE_API, "API"); - constMap.put(GL2ES2.GL_DEBUG_SOURCE_APPLICATION, "APPLICATION"); - constMap.put(GL2ES2.GL_DEBUG_SOURCE_OTHER, "OTHER"); - constMap.put(GL2ES2.GL_DEBUG_SOURCE_SHADER_COMPILER, "SHADER_COMPILER"); - constMap.put(GL2ES2.GL_DEBUG_SOURCE_THIRD_PARTY, "THIRD_PARTY"); - constMap.put(GL2ES2.GL_DEBUG_SOURCE_WINDOW_SYSTEM, "WINDOW_SYSTEM"); - - constMap.put(GL2ES2.GL_DEBUG_TYPE_DEPRECATED_BEHAVIOR, "DEPRECATED_BEHAVIOR"); - constMap.put(GL2ES2.GL_DEBUG_TYPE_ERROR, "ERROR"); - constMap.put(GL2ES2.GL_DEBUG_TYPE_OTHER, "OTHER"); - constMap.put(GL2ES2.GL_DEBUG_TYPE_PERFORMANCE, "PERFORMANCE"); - constMap.put(GL2ES2.GL_DEBUG_TYPE_PORTABILITY, "PORTABILITY"); - constMap.put(GL2ES2.GL_DEBUG_TYPE_UNDEFINED_BEHAVIOR, "UNDEFINED_BEHAVIOR"); - - constMap.put(GL2ES2.GL_DEBUG_SEVERITY_HIGH, "HIGH"); - constMap.put(GL2ES2.GL_DEBUG_SEVERITY_MEDIUM, "MEDIUM"); - constMap.put(GL2ES2.GL_DEBUG_SEVERITY_LOW, "LOW"); - } - - @Override - public void messageSent(GLDebugMessage event) { - String sourceStr = constMap.get(event.getDbgSource()); - String typeStr = constMap.get(event.getDbgType()); - String severityStr = constMap.get(event.getDbgSeverity()); - - System.err.println(String.format(MESSAGE_FORMAT, event.getDbgId(), sourceStr, typeStr, severityStr, event.getDbgMsg())); - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java deleted file mode 100644 index 48f191cb3a..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtAbstractDisplay.java +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.jogl.NewtKeyInput; -import com.jme3.input.jogl.NewtMouseInput; -import com.jme3.system.AppSettings; -import com.jogamp.newt.opengl.GLWindow; -import com.jogamp.opengl.util.Animator; -import com.jogamp.opengl.util.AnimatorBase; -import com.jogamp.opengl.util.FPSAnimator; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Logger; - -import com.jogamp.opengl.GLAutoDrawable; -import com.jogamp.opengl.GLCapabilities; -import com.jogamp.opengl.GLEventListener; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.GLRunnable; - -public abstract class JoglNewtAbstractDisplay extends JoglContext implements GLEventListener { - - private static final Logger logger = Logger.getLogger(JoglNewtAbstractDisplay.class.getName()); - - protected GLWindow canvas; - - protected AnimatorBase animator; - - protected AtomicBoolean active = new AtomicBoolean(false); - - protected boolean wasActive = false; - - protected int frameRate; - - protected boolean useAwt = true; - - protected AtomicBoolean autoFlush = new AtomicBoolean(true); - - protected boolean wasAnimating = false; - - protected void initGLCanvas() { - GLCapabilities caps; - if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { - caps = new GLCapabilities(GLProfile.getMaxProgrammable(true)); - } else { - caps = new GLCapabilities(GLProfile.getMaxFixedFunc(true)); - } - caps.setHardwareAccelerated(true); - caps.setDoubleBuffered(true); - caps.setStencilBits(settings.getStencilBits()); - caps.setDepthBits(settings.getDepthBits()); - - if (settings.getSamples() > 1) { - caps.setSampleBuffers(true); - caps.setNumSamples(settings.getSamples()); - } - canvas = GLWindow.create(caps); - canvas.invoke(false, new GLRunnable() { - public boolean run(GLAutoDrawable glad) { - canvas.getGL().setSwapInterval(settings.isVSync() ? 1 : 0); - return true; - } - }); - canvas.requestFocus(); - canvas.setSize(settings.getWidth(), settings.getHeight()); - canvas.addGLEventListener(this); - - //FIXME not sure it is the best place to do that - renderable.set(true); - } - - protected void startGLCanvas() { - if (frameRate > 0) { - animator = new FPSAnimator(canvas, frameRate); - } - else { - animator = new Animator(); - animator.add(canvas); - ((Animator) animator).setRunAsFastAsPossible(true); - } - - animator.start(); - wasAnimating = true; - } - - protected void onCanvasAdded() { - } - - protected void onCanvasRemoved() { - } - - @Override - public KeyInput getKeyInput() { - if (keyInput == null) { - keyInput = new NewtKeyInput(); - ((NewtKeyInput)keyInput).setInputSource(canvas); - } - return keyInput; - } - - @Override - public MouseInput getMouseInput() { - if (mouseInput == null) { - mouseInput = new NewtMouseInput(); - ((NewtMouseInput)mouseInput).setInputSource(canvas); - } - return mouseInput; - } - - public TouchInput getTouchInput() { - return null; - } - - public void setAutoFlushFrames(boolean enabled) { - autoFlush.set(enabled); - } - - /** - * Callback. - */ - public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) { - listener.reshape(width, height); - } - - /** - * Callback. - */ - public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) { - } - - /** - * Callback - */ - public void dispose(GLAutoDrawable drawable) { - - } -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java deleted file mode 100644 index e4e81a5df9..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtCanvas.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.system.JmeCanvasContext; -import com.jogamp.newt.awt.NewtCanvasAWT; -import java.util.logging.Logger; -import com.jogamp.opengl.GLAutoDrawable; - -public class JoglNewtCanvas extends JoglNewtAbstractDisplay implements JmeCanvasContext { - - private static final Logger logger = Logger.getLogger(JoglNewtCanvas.class.getName()); - private int width, height; - private boolean runningFirstTime = true; - - private NewtCanvasAWT newtAwtCanvas; - - public JoglNewtCanvas(){ - super(); - initGLCanvas(); - } - - @Override - protected final void initGLCanvas() { - super.initGLCanvas(); - newtAwtCanvas = new NewtCanvasAWT(canvas) { - private static final long serialVersionUID = 1L; - - @Override - public void addNotify() { - super.addNotify(); - onCanvasAdded(); - } - - @Override - public void removeNotify() { - onCanvasRemoved(); - super.removeNotify(); - } - }; - } - - @Override - public Type getType() { - return Type.Canvas; - } - - @Override - public void setTitle(String title) { - } - - @Override - public void restart() { - } - - @Override - public void create(boolean waitFor){ - if (waitFor) - waitFor(true); - } - - @Override - public void destroy(boolean waitFor){ - if (waitFor) - waitFor(false); - if (animator.isAnimating()) - animator.stop(); - } - - @Override - protected void onCanvasRemoved(){ - super.onCanvasRemoved(); - created.set(false); - waitFor(false); - } - - @Override - protected void onCanvasAdded(){ - startGLCanvas(); - } - - @Override - public void init(GLAutoDrawable drawable) { - canvas.requestFocus(); - - super.internalCreate(); - logger.fine("Display created."); - - // At this point, the OpenGL context is active. - if (runningFirstTime){ - // THIS is the part that creates the renderer. - // It must always be called, now that we have the pbuffer workaround. - initContextFirstTime(); - runningFirstTime = false; - } - listener.initialize(); - } - - @Override - protected void startGLCanvas() { - frameRate = settings.getFrameRate(); - super.startGLCanvas(); - } - - @Override - public void display(GLAutoDrawable glad) { - if (!created.get() && renderer != null){ - listener.destroy(); - logger.fine("Canvas destroyed."); - super.internalDestroy(); - return; - } - - int newWidth = Math.max(canvas.getWidth(), 1); - int newHeight = Math.max(canvas.getHeight(), 1); - if (width != newWidth || height != newHeight) { - width = newWidth; - height = newHeight; - if (listener != null) { - listener.reshape(width, height); - } - } - - boolean flush = autoFlush.get(); - if (flush && !wasAnimating){ - animator.start(); - wasAnimating = true; - }else if (!flush && wasAnimating){ - animator.stop(); - wasAnimating = false; - } - - listener.update(); - renderer.postFrame(); - - } - - @Override - public NewtCanvasAWT getCanvas() { - return newtAwtCanvas; - } - - @Override - public void dispose(GLAutoDrawable arg0) { - } - -} diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java deleted file mode 100644 index 56eab406a3..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglNewtDisplay.java +++ /dev/null @@ -1,247 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.jogl; - -import com.jme3.system.AppSettings; -import com.jogamp.newt.MonitorMode; -import com.jogamp.newt.Screen; -import com.jogamp.newt.event.WindowAdapter; -import com.jogamp.newt.event.WindowEvent; -import com.jogamp.newt.util.MonitorModeUtil; -import java.util.List; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import com.jogamp.nativewindow.util.Dimension; -import com.jogamp.opengl.GLAutoDrawable; - -public class JoglNewtDisplay extends JoglNewtAbstractDisplay { - - private static final Logger logger = Logger.getLogger(JoglNewtDisplay.class.getName()); - - protected AtomicBoolean windowCloseRequest = new AtomicBoolean(false); - protected AtomicBoolean needClose = new AtomicBoolean(false); - protected AtomicBoolean needRestart = new AtomicBoolean(false); - protected volatile boolean wasInited = false; - - public Type getType() { - return Type.Display; - } - - protected void createGLFrame(){ - canvas.setTitle(settings.getTitle()); - - applySettings(settings); - - // Make the window visible to realize the OpenGL surface. - canvas.setVisible(true); - - //this is the earliest safe opportunity to get the context - //final GLContext context = canvas.getContext(); - - /*canvas.invoke(true, new GLRunnable() { - @Override - public boolean run(GLAutoDrawable glAutoDrawable) { - context.makeCurrent(); - try { - startGLCanvas(); - } - finally { - context.release(); - } - return true; - } - });*/ - } - - protected void applySettings(AppSettings settings){ - active.set(true); - canvas.addWindowListener(new WindowAdapter() { - @Override - public void windowDestroyNotify(WindowEvent e) { - windowCloseRequest.set(true); - } - - @Override - public void windowGainedFocus(WindowEvent e) { - active.set(true); - } - - @Override - public void windowLostFocus(WindowEvent e) { - active.set(false); - } - }); - canvas.setSize(settings.getWidth(), settings.getHeight()); - canvas.setUndecorated(settings.isFullscreen()); - canvas.setFullscreen(settings.isFullscreen()); - - /** - * uses the filtering relying on resolution with the size to fetch only - * the screen mode matching with the current resolution - */ - Screen screen = canvas.getScreen(); - /** - * The creation of native resources is lazy in JogAmp, i.e they are - * created only when they are used for the first time. When the GLWindow - * is not yet visible, its screen might have been unused for now and - * then its native counterpart has not yet been created. That's why - * forcing the creation of this resource is necessary - */ - screen.addReference(); - if (settings.isFullscreen()) { - List screenModes = canvas.getMainMonitor().getSupportedModes(); - //the resolution is provided by the user - Dimension dimension = new Dimension(settings.getWidth(), settings.getHeight()); - screenModes = MonitorModeUtil.filterByResolution(screenModes, dimension); - screenModes = MonitorModeUtil.getHighestAvailableBpp(screenModes); - if (settings.getFrequency() > 0) { - screenModes = MonitorModeUtil.filterByRate(screenModes, settings.getFrequency()); - } else { - screenModes = MonitorModeUtil.getHighestAvailableRate(screenModes); - } - canvas.getMainMonitor().setCurrentMode(screenModes.get(0)); - } - - MonitorMode currentScreenMode = canvas.getMainMonitor().getCurrentMode(); - logger.log(Level.FINE, "Selected display mode: {0}x{1}x{2} @{3}", - new Object[]{currentScreenMode.getRotatedWidth(), - currentScreenMode.getRotatedHeight(), - currentScreenMode.getSurfaceSize().getBitsPerPixel(), - currentScreenMode.getRefreshRate()}); - } - - private void privateInit(){ - initGLCanvas(); - - createGLFrame(); - - startGLCanvas(); - } - - public void init(GLAutoDrawable drawable){ - // prevent initializing twice on restart - if (!wasInited){ - wasInited = true; - - canvas.requestFocus(); - - super.internalCreate(); - logger.fine("Display created."); - - renderer.initialize(); - listener.initialize(); - } - } - - public void create(boolean waitFor){ - privateInit(); - } - - public void destroy(boolean waitFor){ - needClose.set(true); - if (waitFor){ - waitFor(false); - } - if (animator.isAnimating()) - animator.stop(); - } - - public void restart() { - if (created.get()){ - needRestart.set(true); - }else{ - throw new IllegalStateException("Display not started yet. Cannot restart"); - } - } - - public void setTitle(String title){ - if (canvas != null) { - canvas.setTitle(title); - } - } - - /** - * Callback. - */ - public void display(GLAutoDrawable drawable) { - if (needClose.get()) { - listener.destroy(); - animator.stop(); - if (settings.isFullscreen()) { - canvas.setFullscreen(false); - } - canvas.destroy(); - logger.fine("Display destroyed."); - super.internalDestroy(); - return; - } - - if (windowCloseRequest.get()){ - listener.requestClose(false); - windowCloseRequest.set(false); - } - - if (needRestart.getAndSet(false)){ - // for restarting contexts - if (canvas.isVisible()){ - animator.stop(); - canvas.destroy(); - createGLFrame(); - startGLCanvas(); - } - } - -// boolean flush = autoFlush.get(); -// if (animator.isAnimating() != flush){ -// if (flush) -// animator.stop(); -// else -// animator.start(); -// } - - if (wasActive != active.get()){ - if (!wasActive){ - listener.gainFocus(); - wasActive = true; - }else{ - listener.loseFocus(); - wasActive = false; - } - } - - listener.update(); - renderer.postFrame(); - } -} - diff --git a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java b/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java deleted file mode 100644 index c912c4f9e8..0000000000 --- a/jme3-jogl/src/main/java/com/jme3/system/jogl/JoglOffscreenBuffer.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.system.jogl; - -import com.jme3.input.JoyInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.dummy.DummyKeyInput; -import com.jme3.input.dummy.DummyMouseInput; -import com.jme3.system.AppSettings; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jogamp.opengl.GLCapabilities; -import com.jogamp.opengl.GLDrawableFactory; -import com.jogamp.opengl.GLOffscreenAutoDrawable; -import com.jogamp.opengl.GLProfile; -import com.jogamp.opengl.JoglVersion; - - -public class JoglOffscreenBuffer extends JoglContext implements Runnable { - - private static final Logger logger = Logger.getLogger(JoglOffscreenBuffer.class.getName()); - private GLOffscreenAutoDrawable offscreenDrawable; - protected AtomicBoolean needClose = new AtomicBoolean(false); - private int width; - private int height; - private GLCapabilities caps; - - protected void initInThread(){ - // not necessary as JOGL can create an offscreen buffer even without full FBO support -// if (!GLContext.getCurrent().hasFullFBOSupport()){ -// logger.severe("Offscreen surfaces are not supported."); -// return; -// } - final GLProfile profile; - if (settings.getRenderer().equals(AppSettings.JOGL_OPENGL_FORWARD_COMPATIBLE)) { - profile = GLProfile.getMaxProgrammable(true); - } else { - profile = GLProfile.getMaxFixedFunc(true); - } - caps = new GLCapabilities(profile); - int samples = getNumSamplesToUse(); - caps.setHardwareAccelerated(true); - caps.setDoubleBuffered(true); - caps.setStencilBits(settings.getStencilBits()); - caps.setDepthBits(settings.getDepthBits()); - caps.setOnscreen(false); - caps.setSampleBuffers(true); - caps.setNumSamples(samples); - - offscreenDrawable = GLDrawableFactory.getFactory(profile).createOffscreenAutoDrawable(null, caps, null, width, height); - - offscreenDrawable.display(); - - renderable.set(true); - - logger.fine("Offscreen buffer created."); - - super.internalCreate(); - listener.initialize(); - } - - @Override - protected void initContextFirstTime(){ - offscreenDrawable.getContext().makeCurrent(); - try { - super.initContextFirstTime(); - } finally { - offscreenDrawable.getContext().release(); - } - } - - protected boolean checkGLError(){ - //FIXME - // NOTE: Always return true since this is used in an "assert" statement - return true; - } - - protected void runLoop(){ - if (!created.get()) { - throw new IllegalStateException(); - } - - listener.update(); - checkGLError(); - - renderer.postFrame(); - - int frameRate = settings.getFrameRate(); - if (frameRate >= 1) { - //FIXME - } - } - - protected void deinitInThread(){ - renderable.set(false); - - listener.destroy(); - renderer.cleanup(); - offscreenDrawable.destroy(); - logger.fine("Offscreen buffer destroyed."); - - super.internalDestroy(); - } - - @Override - public void run() { - logger.log(Level.FINE, "Using JOGL {0}", JoglVersion.getInstance().getImplementationVersion()); - initInThread(); - while (!needClose.get()){ - runLoop(); - } - deinitInThread(); - } - - @Override - public void destroy(boolean waitFor){ - needClose.set(true); - if (waitFor) { - waitFor(false); - } - } - - @Override - public void create(boolean waitFor){ - if (created.get()){ - logger.warning("create() called when pbuffer is already created!"); - return; - } - - new Thread(this, THREAD_NAME).start(); - if (waitFor) { - waitFor(true); - } - } - - @Override - public void restart() { - } - - @Override - public void setAutoFlushFrames(boolean enabled){ - } - - @Override - public Type getType() { - return Type.OffscreenSurface; - } - - @Override - public MouseInput getMouseInput() { - return new DummyMouseInput(); - } - - @Override - public KeyInput getKeyInput() { - return new DummyKeyInput(); - } - - @Override - public JoyInput getJoyInput() { - return null; - } - - @Override - public TouchInput getTouchInput() { - return null; - } - - @Override - public void setTitle(String title) { - } -} diff --git a/jme3-lwjgl/build.gradle b/jme3-lwjgl/build.gradle index c7154d8090..b0f5a08d3b 100644 --- a/jme3-lwjgl/build.gradle +++ b/jme3-lwjgl/build.gradle @@ -1,9 +1,13 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - dependencies { - compile project(':jme3-core') - compile project(':jme3-desktop') - compile 'org.lwjgl.lwjgl:lwjgl:2.9.3' -} + api project(':jme3-core') + api project(':jme3-desktop') + + api libs.lwjgl2 + + /* + * Upgrades the default jinput-2.0.5 to jinput-2.0.9 to fix a bug with gamepads on Linux. + * See https://hub.jmonkeyengine.org/t/linux-gamepad-input-on-jme3-lwjgl-splits-input-between-two-logical-gamepads + */ + api libs.jinput + api(variantOf(libs.jinput){ classifier('natives-all') }) +} \ No newline at end of file diff --git a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java index 9cb52c688c..247f5b6da9 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglAL.java @@ -12,94 +12,114 @@ public final class LwjglAL implements AL { public LwjglAL() { } + @Override public String alGetString(int parameter) { return AL10.alGetString(parameter); } + @Override public int alGenSources() { return AL10.alGenSources(); } + @Override public int alGetError() { return AL10.alGetError(); } + @Override public void alDeleteSources(int numSources, IntBuffer sources) { if (sources.position() != 0) throw new AssertionError(); if (sources.limit() != numSources) throw new AssertionError(); AL10.alDeleteSources(sources); } + @Override public void alGenBuffers(int numBuffers, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numBuffers) throw new AssertionError(); AL10.alGenBuffers(buffers); } + @Override public void alDeleteBuffers(int numBuffers, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numBuffers) throw new AssertionError(); AL10.alDeleteBuffers(buffers); } + @Override public void alSourceStop(int source) { AL10.alSourceStop(source); } + @Override public void alSourcei(int source, int param, int value) { AL10.alSourcei(source, param, value); } + @Override public void alBufferData(int buffer, int format, ByteBuffer data, int size, int frequency) { if (data.position() != 0) throw new AssertionError(); if (data.limit() != size) throw new AssertionError(); AL10.alBufferData(buffer, format, data, frequency); } + @Override public void alSourcePlay(int source) { AL10.alSourcePlay(source); } + @Override public void alSourcePause(int source) { AL10.alSourcePause(source); } + @Override public void alSourcef(int source, int param, float value) { AL10.alSourcef(source, param, value); } + @Override public void alSource3f(int source, int param, float value1, float value2, float value3) { AL10.alSource3f(source, param, value1, value2, value3); } + @Override public int alGetSourcei(int source, int param) { return AL10.alGetSourcei(source, param); } + @Override public void alSourceUnqueueBuffers(int source, int numBuffers, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numBuffers) throw new AssertionError(); AL10.alSourceUnqueueBuffers(source, buffers); } + @Override public void alSourceQueueBuffers(int source, int numBuffers, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numBuffers) throw new AssertionError(); AL10.alSourceQueueBuffers(source, buffers); } + @Override public void alListener(int param, FloatBuffer data) { AL10.alListener(param, data); } + @Override public void alListenerf(int param, float value) { AL10.alListenerf(param, value); } + @Override public void alListener3f(int param, float value1, float value2, float value3) { AL10.alListener3f(param, value1, value2, value3); } + @Override public void alSource3i(int source, int param, int value1, int value2, int value3) { AL11.alSource3i(source, param, value1, value2, value3); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java index ffce940b09..df05317000 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java +++ b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglALC.java @@ -2,6 +2,8 @@ import com.jme3.audio.openal.ALC; import java.nio.IntBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; import org.lwjgl.LWJGLException; import org.lwjgl.openal.AL; import org.lwjgl.openal.ALC10; @@ -9,35 +11,71 @@ import org.lwjgl.openal.ALCdevice; public class LwjglALC implements ALC { + /** + * message logger for this class + */ + private static final Logger logger + = Logger.getLogger(LwjglALC.class.getName()); + @Override public void createALC() { - try { - AL.create(); - } catch (LWJGLException ex) { - throw new RuntimeException(ex); + int numRetriesRemaining = 4; + int retryDelayMsec = 100; // 0.1-second delay between retries + + while (true) { + try { + AL.create(); + break; + + } catch (LWJGLException exception1) { + if (numRetriesRemaining < 1) { + throw new RuntimeException(exception1); + } + + // Retry to mitigate JME Issue 1383. + --numRetriesRemaining; + String message = String.format( + "Caught an LWJGLException from AL.create(). " + + "Will retry after %d msec, " + + "with %d more retr%s remaining.%n", + retryDelayMsec, + numRetriesRemaining, + (numRetriesRemaining == 1) ? "y" : "ies"); + logger.log(Level.WARNING, message); + + try { + Thread.sleep(retryDelayMsec); + } catch (InterruptedException exception2) { + } + } } } + @Override public void destroyALC() { AL.destroy(); } + @Override public boolean isCreated() { return AL.isCreated(); } + @Override public String alcGetString(int parameter) { ALCcontext context = ALC10.alcGetCurrentContext(); ALCdevice device = ALC10.alcGetContextsDevice(context); return ALC10.alcGetString(device, parameter); } + @Override public boolean alcIsExtensionPresent(String extension) { ALCcontext context = ALC10.alcGetCurrentContext(); ALCdevice device = ALC10.alcGetContextsDevice(context); return ALC10.alcIsExtensionPresent(device, extension); } + @Override public void alcGetInteger(int param, IntBuffer buffer, int size) { if (buffer.position() != 0) throw new AssertionError(); if (buffer.limit() != size) throw new AssertionError(); @@ -47,9 +85,11 @@ public void alcGetInteger(int param, IntBuffer buffer, int size) { ALC10.alcGetInteger(device, param, buffer); } + @Override public void alcDevicePauseSOFT() { } + @Override public void alcDeviceResumeSOFT() { } diff --git a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java index 639758c4ef..01292a1288 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java +++ b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/LwjglEFX.java @@ -6,58 +6,69 @@ public class LwjglEFX implements EFX { + @Override public void alGenAuxiliaryEffectSlots(int numSlots, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numSlots) throw new AssertionError(); EFX10.alGenAuxiliaryEffectSlots(buffers); } + @Override public void alGenEffects(int numEffects, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numEffects) throw new AssertionError(); EFX10.alGenEffects(buffers); } + @Override public void alEffecti(int effect, int param, int value) { EFX10.alEffecti(effect, param, value); } + @Override public void alAuxiliaryEffectSloti(int effectSlot, int param, int value) { EFX10.alAuxiliaryEffectSloti(effectSlot, param, value); } + @Override public void alDeleteEffects(int numEffects, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numEffects) throw new AssertionError(); EFX10.alDeleteEffects(buffers); } + @Override public void alDeleteAuxiliaryEffectSlots(int numEffectSlots, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numEffectSlots) throw new AssertionError(); EFX10.alDeleteAuxiliaryEffectSlots(buffers); } + @Override public void alGenFilters(int numFilters, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numFilters) throw new AssertionError(); EFX10.alGenFilters(buffers); } + @Override public void alFilteri(int filter, int param, int value) { EFX10.alFilteri(filter, param, value); } + @Override public void alFilterf(int filter, int param, float value) { EFX10.alFilterf(filter, param, value); } + @Override public void alDeleteFilters(int numFilters, IntBuffer buffers) { if (buffers.position() != 0) throw new AssertionError(); if (buffers.limit() != numFilters) throw new AssertionError(); EFX10.alDeleteFilters(buffers); } + @Override public void alEffectf(int effect, int param, float value) { EFX10.alEffectf(effect, param, value); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/package-info.java b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/package-info.java new file mode 100644 index 0000000000..f74b154fc0 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/audio/lwjgl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * audio support based on the Lightweight Java Game Library (LWJGL) + */ +package com.jme3.audio.lwjgl; diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java index d89a07af51..30ad12d7f3 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/JInputJoyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,6 +48,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.logging.Level; import java.util.logging.Logger; import net.java.games.input.*; @@ -60,63 +61,66 @@ public class JInputJoyInput implements JoyInput { private static final Logger logger = Logger.getLogger(InputManager.class.getName()); - private boolean inited = false; + private boolean initialized = false; private JInputJoystick[] joysticks; private RawInputListener listener; - private Map joystickIndex = new HashMap(); - + private Map joystickIndex = new HashMap<>(); + + @Override public void setJoyRumble(int joyId, float amount){ - if( joyId >= joysticks.length ) + if( joyId >= joysticks.length ) throw new IllegalArgumentException(); - + Controller c = joysticks[joyId].controller; for (Rumbler r : c.getRumblers()){ r.rumble(amount); } } + @Override public Joystick[] loadJoysticks(InputManager inputManager){ ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment(); Controller[] cs = ce.getControllers(); - - List list = new ArrayList(); + List list = new ArrayList<>(); for( Controller c : ce.getControllers() ) { if (c.getType() == Controller.Type.KEYBOARD || c.getType() == Controller.Type.MOUSE) continue; - logger.log(Level.FINE, "Attempting to create joystick for: \"{0}\"", c); - + logger.log(Level.FINE, "Attempting to create joystick for: \"{0}\"", c); + // Try to create it like a joystick - JInputJoystick stick = new JInputJoystick(inputManager, this, c, list.size(), c.getName()); + JInputJoystick stick = new JInputJoystick(inputManager, this, c, list.size(), c.getName()); for( Component comp : c.getComponents() ) { - stick.addComponent(comp); + stick.addComponent(comp); } - + // If it has no axes then we'll assume it's not // a joystick if( stick.getAxisCount() == 0 ) { logger.log(Level.FINE, "Not a joystick: {0}", c); continue; } - + joystickIndex.put(c, stick); - list.add(stick); + list.add(stick); } joysticks = list.toArray( new JInputJoystick[list.size()] ); - + return joysticks; } + @Override public void initialize() { - inited = true; + initialized = true; } + @Override public void update() { ControllerEnvironment ce = ControllerEnvironment.getDefaultEnvironment(); @@ -125,75 +129,82 @@ public void update() { Event e = new Event(); for (int i = 0; i < cs.length; i++){ Controller c = cs[i]; - + JInputJoystick stick = joystickIndex.get(c); if( stick == null ) continue; - + if( !c.poll() ) continue; - + int joyId = stick.getJoyId(); - + EventQueue q = c.getEventQueue(); while (q.getNextEvent(e)){ Identifier id = e.getComponent().getIdentifier(); if (id == Identifier.Axis.POV){ - float x = 0, y = 0; + float rawX = 0, rawY = 0, x, y; float v = e.getValue(); - + if (v == POV.CENTER){ - x = 0; y = 0; + rawX = 0; rawY = 0; }else if (v == POV.DOWN){ - x = 0; y = -1f; + rawX = 0; rawY = -1f; }else if (v == POV.DOWN_LEFT){ - x = -1f; y = -1f; + rawX = -1f; rawY = -1f; }else if (v == POV.DOWN_RIGHT){ - x = 1f; y = -1f; + rawX = 1f; rawY = -1f; }else if (v == POV.LEFT){ - x = -1f; y = 0; + rawX = -1f; rawY = 0; }else if (v == POV.RIGHT){ - x = 1f; y = 0; + rawX = 1f; rawY = 0; }else if (v == POV.UP){ - x = 0; y = 1f; + rawX = 0; rawY = 1f; }else if (v == POV.UP_LEFT){ - x = -1f; y = 1f; + rawX = -1f; rawY = 1f; }else if (v == POV.UP_RIGHT){ - x = 1f; y = 1f; + rawX = 1f; rawY = 1f; } - JoyAxisEvent evt1 = new JoyAxisEvent(stick.povX, x); - JoyAxisEvent evt2 = new JoyAxisEvent(stick.povY, y); + x = JoystickCompatibilityMappings.remapAxisRange(stick.povX, rawX); + y = JoystickCompatibilityMappings.remapAxisRange(stick.povY, rawY); + JoyAxisEvent evt1 = new JoyAxisEvent(stick.povX, x, rawX); + JoyAxisEvent evt2 = new JoyAxisEvent(stick.povY, y, rawY); listener.onJoyAxisEvent(evt1); listener.onJoyAxisEvent(evt2); }else if (id instanceof Axis){ - float value = e.getValue(); - + float rawValue = e.getValue(); + float value = JoystickCompatibilityMappings.remapAxisRange(stick.povY, rawValue); + JoystickAxis axis = stick.axisIndex.get(e.getComponent()); - JoyAxisEvent evt = new JoyAxisEvent(axis, value); + JoyAxisEvent evt = new JoyAxisEvent(axis, value, rawValue); listener.onJoyAxisEvent(evt); }else if (id instanceof Button){ - - JoystickButton button = stick.buttonIndex.get(e.getComponent()); + + JoystickButton button = stick.buttonIndex.get(e.getComponent()); JoyButtonEvent evt = new JoyButtonEvent(button, e.getValue() == 1f); listener.onJoyButtonEvent(evt); } - } + } } } + @Override public void destroy() { - inited = false; + initialized = false; } + @Override public boolean isInitialized() { - return inited; + return initialized; } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public long getInputTimeNanos() { return 0; } @@ -201,30 +212,30 @@ public long getInputTimeNanos() { protected class JInputJoystick extends AbstractJoystick { private JoystickAxis nullAxis; - private Controller controller; + private Controller controller; private JoystickAxis xAxis; private JoystickAxis yAxis; private JoystickAxis povX; private JoystickAxis povY; - private Map axisIndex = new HashMap(); - private Map buttonIndex = new HashMap(); + private Map axisIndex = new HashMap<>(); + private Map buttonIndex = new HashMap<>(); - public JInputJoystick( InputManager inputManager, JoyInput joyInput, Controller controller, + public JInputJoystick( InputManager inputManager, JoyInput joyInput, Controller controller, int joyId, String name ) { super( inputManager, joyInput, joyId, name ); - + this.controller = controller; - - this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, + + this.nullAxis = new DefaultJoystickAxis( getInputManager(), this, -1, "Null", "null", false, false, 0 ); - this.xAxis = nullAxis; - this.yAxis = nullAxis; + this.xAxis = nullAxis; + this.yAxis = nullAxis; this.povX = nullAxis; - this.povY = nullAxis; + this.povY = nullAxis; } protected void addComponent( Component comp ) { - + Identifier id = comp.getIdentifier(); if( id instanceof Button ) { addButton(comp); @@ -236,99 +247,104 @@ protected void addComponent( Component comp ) { } protected void addButton( Component comp ) { - - logger.log(Level.FINE, "Adding button: \"{0}\" id:" + comp.getIdentifier(), comp); - - Identifier id = comp.getIdentifier(); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Adding button: \"{0}\" id:" + comp.getIdentifier(), comp); + } + Identifier id = comp.getIdentifier(); if( !(id instanceof Button) ) { throw new IllegalArgumentException( "Component is not an button:" + comp ); } String name = comp.getName(); String original = id.getName(); - try { + try { Integer.parseInt(original); } catch (NumberFormatException e){ original = String.valueOf(buttonIndex.size()); } - String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); - if( logicalId != original ) { + String logicalId = JoystickCompatibilityMappings.remapButton( controller.getName(), original ); + if (logger.isLoggable(Level.FINE) && !Objects.equals(logicalId, original)) { logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); } - + JoystickButton button = new DefaultJoystickButton( getInputManager(), this, getButtonCount(), name, logicalId ); - addButton(button); + addButton(button); buttonIndex.put( comp, button ); } - + protected void addAxis( Component comp ) { + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + comp.getIdentifier(), comp ); + } - logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + comp.getIdentifier(), comp ); - Identifier id = comp.getIdentifier(); if( !(id instanceof Axis) ) { throw new IllegalArgumentException( "Component is not an axis:" + comp ); } - + String name = comp.getName(); String original = id.getName(); - String logicalId = JoystickCompatibilityMappings.remapComponent( controller.getName(), original ); - if( logicalId != original ) { + String logicalId = JoystickCompatibilityMappings.remapAxis( controller.getName(), original ); + if(logger.isLoggable(Level.FINE) && !Objects.equals(logicalId, original)) { logger.log(Level.FINE, "Remapped:" + original + " to:" + logicalId); } - - JoystickAxis axis = new DefaultJoystickAxis( getInputManager(), + + JoystickAxis axis = new DefaultJoystickAxis( getInputManager(), this, getAxisCount(), name, logicalId, - comp.isAnalog(), comp.isRelative(), + comp.isAnalog(), comp.isRelative(), comp.getDeadZone() ); - addAxis(axis); + addAxis(axis); axisIndex.put( comp, axis ); - + // Support the X/Y axis indexes if( id == Axis.X ) { xAxis = axis; } else if( id == Axis.Y ) { yAxis = axis; } else if( id == Axis.POV ) { - + // Add two fake axes for the JME provided convenience // axes: AXIS_POV_X, AXIS_POV_Y - povX = new DefaultJoystickAxis( getInputManager(), - this, getAxisCount(), JoystickAxis.POV_X, + povX = new DefaultJoystickAxis( getInputManager(), + this, getAxisCount(), JoystickAxis.POV_X, id.getName() + "_x", comp.isAnalog(), comp.isRelative(), comp.getDeadZone() ); - logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_x", povX.getName() ); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_x", povX.getName() ); + } addAxis(povX); - povY = new DefaultJoystickAxis( getInputManager(), - this, getAxisCount(), JoystickAxis.POV_Y, + povY = new DefaultJoystickAxis( getInputManager(), + this, getAxisCount(), JoystickAxis.POV_Y, id.getName() + "_y", comp.isAnalog(), comp.isRelative(), comp.getDeadZone() ); - logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_y", povY.getName() ); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Adding axis: \"{0}\" id:" + id.getName() + "_y", povY.getName() ); + } addAxis(povY); } - + } - + @Override public JoystickAxis getXAxis() { return xAxis; - } + } @Override public JoystickAxis getYAxis() { return yAxis; - } + } @Override public JoystickAxis getPovXAxis() { return povX; - } + } @Override public JoystickAxis getPovYAxis() { return povY; - } + } @Override public int getXAxisIndex(){ @@ -339,7 +355,7 @@ public int getXAxisIndex(){ public int getYAxisIndex(){ return yAxis.getAxisId(); } - } + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java index e7b9f9268a..7bc0a1e640 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglKeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,6 +55,7 @@ public LwjglKeyInput(LwjglAbstractDisplay context){ this.context = context; } + @Override public void initialize() { if (!context.isRenderable()) return; @@ -72,6 +73,7 @@ public int getKeyCount(){ return Keyboard.KEYBOARD_SIZE; } + @Override public void update() { if (!context.isRenderable()) return; @@ -89,6 +91,7 @@ public void update() { } } + @Override public void destroy() { if (!context.isRenderable()) return; @@ -97,16 +100,23 @@ public void destroy() { logger.fine("Keyboard destroyed."); } + @Override public boolean isInitialized() { return Keyboard.isCreated(); } + @Override public void setInputListener(RawInputListener listener) { this.listener = listener; } + @Override public long getInputTimeNanos() { return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS; } - + + @Override + public String getKeyName(int key){ + throw new UnsupportedOperationException("getKeyName not implemented for lwjgl input"); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java index b53d93660b..275ddf6454 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/LwjglMouseInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,15 +55,13 @@ public class LwjglMouseInput implements MouseInput { private LwjglAbstractDisplay context; private RawInputListener listener; - - private boolean supportHardwareCursor = false; private boolean cursorVisible = true; /** * We need to cache the cursors * (https://github.com/jMonkeyEngine/jmonkeyengine/issues/537) */ - private Map cursorMap = new HashMap(); + private Map cursorMap = new HashMap<>(); private int curX, curY, curWheel; @@ -71,6 +69,7 @@ public LwjglMouseInput(LwjglAbstractDisplay context){ this.context = context; } + @Override public void initialize() { if (!context.isRenderable()) return; @@ -78,7 +77,7 @@ public void initialize() { try { Mouse.create(); logger.fine("Mouse created."); - supportHardwareCursor = (Cursor.getCapabilities() & Cursor.CURSOR_ONE_BIT_TRANSPARENCY) != 0; + Cursor.getCapabilities(); // Recall state that was set before initialization Mouse.setGrabbed(!cursorVisible); @@ -91,14 +90,17 @@ public void initialize() { } } + @Override public boolean isInitialized(){ return Mouse.isCreated(); } + @Override public int getButtonCount(){ return Mouse.getButtonCount(); } + @Override public void update() { if (!context.isRenderable()) return; @@ -139,6 +141,7 @@ public void update() { } } + @Override public void destroy() { if (!context.isRenderable()) return; @@ -154,6 +157,7 @@ public void destroy() { logger.fine("Mouse destroyed."); } + @Override public void setCursorVisible(boolean visible){ cursorVisible = visible; if (!context.isRenderable()) @@ -190,10 +194,12 @@ private void sendFirstMouseEvent() { listener.onMouseMotionEvent(evt); } + @Override public long getInputTimeNanos() { return Sys.getTime() * LwjglTimer.LWJGL_TIME_TO_NANOS; } + @Override public void setNativeCursor(JmeCursor jmeCursor) { try { Cursor newCursor = null; diff --git a/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/package-info.java b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/package-info.java new file mode 100644 index 0000000000..0eaeb013cd --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/input/lwjgl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * user input based on the Lightweight Java Game Library (LWJGL) + */ +package com.jme3.input.lwjgl; diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java index 96b34eb101..e95a2a56d0 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java @@ -163,7 +163,7 @@ public Buffer bindVertexBuffer(VertexBuffer vb, MemoryAccess access) { } @Override - public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int miplevel, MemoryAccess access) { + public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int mipLevel, MemoryAccess access) { int imageID = image.getId(); if (imageID == -1) { throw new IllegalArgumentException("image was not yet uploaded to the GPU"); @@ -171,7 +171,7 @@ public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, i long memFlags = Utils.getMemoryAccessFlags(access); int textureTarget = convertTextureType(textureType); Utils.errorBuffer.rewind(); - CLMem mem = CL12GL.clCreateFromGLTexture(context, memFlags, textureTarget, miplevel, imageID, Utils.errorBuffer); + CLMem mem = CL12GL.clCreateFromGLTexture(context, memFlags, textureTarget, mipLevel, imageID, Utils.errorBuffer); Utils.checkError(Utils.errorBuffer, "clCreateFromGLTexture"); return new LwjglImage(mem); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java index b7f992c95d..c193c20670 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java @@ -59,7 +59,7 @@ public void waitForFinished() { return; } CL10.clWaitForEvents(event); - release(); //short cut to save resources + release(); // shortcut to save resources } @Override @@ -69,7 +69,7 @@ public boolean isCompleted() { } int status = event.getInfoInt(CL10.CL_EVENT_COMMAND_EXECUTION_STATUS); if (status == CL10.CL_SUCCESS) { - release(); //short cut to save resources + release(); // shortcut to save resources return true; } else if (status < 0) { Utils.checkError(status, "EventStatus"); diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java index 92ea60beb7..cfb7c1de26 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java @@ -233,8 +233,8 @@ public Event Run(CommandQueue queue) { } CLCommandQueue q = ((LwjglCommandQueue) queue).getQueue(); int ret = CL10.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], - p2, null, Utils.pointerBuffers[0]); + globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], + p2, null, Utils.pointerBuffers[0]); Utils.checkError(ret, "clEnqueueNDRangeKernel"); return new LwjglEvent(q.getCLEvent(Utils.pointerBuffers[0].get(0))); } @@ -251,8 +251,8 @@ public void RunNoEvent(CommandQueue queue) { } CLCommandQueue q = ((LwjglCommandQueue) queue).getQueue(); int ret = CL10.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], - p2, null, null); + globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], + p2, null, null); Utils.checkError(ret, "clEnqueueNDRangeKernel"); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java index 367aeafb01..a950c64768 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java @@ -123,9 +123,9 @@ public Collection getExtensions() { return Arrays.asList(platform.getInfoString(CL10.CL_PLATFORM_EXTENSIONS).split(" ")); } - @Override - public String toString() { - return getName(); - } + @Override + public String toString() { + return getName(); + } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/Utils.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/Utils.java index 29b141325e..e91fc81df6 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/Utils.java +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,11 +55,12 @@ private Utils() {} /** Maps OpenCL error token values to their String representations. Taken directly from org.lwjgl.opencl.Util */ - private static final Map CL_ERROR_TOKENS = LWJGLUtil.getClassTokens(new LWJGLUtil.TokenFilter() { - public boolean accept(final Field field, final int value) { - return value < 0; // Currently, all OpenCL errors have negative values. - } - }, null, CL10.class, CL11.class, CL12.class, KHRGLSharing.class, KHRICD.class, APPLEGLSharing.class, EXTDeviceFission.class); + private static final Map CL_ERROR_TOKENS = LWJGLUtil.getClassTokens(new LWJGLUtil.TokenFilter() { + @Override + public boolean accept(final Field field, final int value) { + return value < 0; // Currently, all OpenCL errors have negative values. + } + }, null, CL10.class, CL11.class, CL12.class, KHRGLSharing.class, KHRICD.class, APPLEGLSharing.class, EXTDeviceFission.class); public static int getMajorVersion(String version, String prefix) { String s = version.substring(prefix.length()); @@ -111,21 +112,21 @@ public static void checkError(IntBuffer errorBuffer, String callName) { } public static void checkError(int error, String callName) { if (error != CL10.CL_SUCCESS) { - String errname = getErrorName(error); - if (errname == null) { - errname = "UNKNOWN"; + String errorName = getErrorName(error); + if (errorName == null) { + errorName = "UNKNOWN"; } - throw new OpenCLException("OpenCL error in " + callName + ": " + errname + " (0x" + Integer.toHexString(error) + ")", error); + throw new OpenCLException("OpenCL error in " + callName + ": " + errorName + " (0x" + Integer.toHexString(error) + ")", error); } } public static void reportError(int error, String callName) { if (error != CL10.CL_SUCCESS) { - String errname = getErrorName(error); - if (errname == null) { - errname = "UNKNOWN"; + String errorName = getErrorName(error); + if (errorName == null) { + errorName = "UNKNOWN"; } - LOG.log(Level.WARNING, "OpenCL error in {0}: {1} (0x{2})", new Object[]{callName, errname, Integer.toHexString(error)}); + LOG.log(Level.WARNING, "OpenCL error in {0}: {1} (0x{2})", new Object[]{callName, errorName, Integer.toHexString(error)}); } } diff --git a/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/package-info.java b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/package-info.java new file mode 100644 index 0000000000..4836f59bd5 --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/opencl/lwjgl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * OpenCL access based on the Lightweight Java Game Library (LWJGL) + */ +package com.jme3.opencl.lwjgl; diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index bca6d0dbb1..82e5a40394 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -5,6 +5,7 @@ import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; import com.jme3.renderer.opengl.GL4; +import com.jme3.renderer.opengl.GLFence; import com.jme3.util.BufferUtils; import org.lwjgl.opengl.*; @@ -26,17 +27,21 @@ private static void checkLimit(Buffer buffer) { } } + @Override public void resetStats() { } + @Override public void glActiveTexture(int param1) { GL13.glActiveTexture(param1); } + @Override public void glAlphaFunc(int param1, float param2) { GL11.glAlphaFunc(param1, param2); } + @Override public void glAttachShader(int param1, int param2) { GL20.glAttachShader(param1, param2); } @@ -46,166 +51,243 @@ public void glBeginQuery(int target, int query) { GL15.glBeginQuery(target, query); } + @Override public void glBindBuffer(int param1, int param2) { GL15.glBindBuffer(param1, param2); } + @Override public void glBindTexture(int param1, int param2) { GL11.glBindTexture(param1, param2); } + + @Override + public void glBindImageTexture(final int unit, final int texture, final int level, + final boolean layered, final int layer, + final int access, final int format) { + GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format); + } + + @Override + public void glDispatchCompute(final int numGroupsX, final int numGroupsY, final int numGroupsZ) { + GL43.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ); + } + + @Override + public void glMemoryBarrier(final int barriers) { + GL42.glMemoryBarrier(barriers); + } + + @Override + public GLFence glFenceSync(final int condition, final int flags) { + GLSync nativeSync = GL32.glFenceSync(condition, flags); + return new GLFence(nativeSync.getPointer(), nativeSync); + } + + @Override + public int glClientWaitSync(final GLFence sync, final int flags, final long timeout) { + return GL32.glClientWaitSync((GLSync) sync.getNativeSync(), flags, timeout); + } + @Override + public void glDeleteSync(final GLFence sync) { + GL32.glDeleteSync((GLSync) sync.getNativeSync()); + } + + @Override public void glBlendEquationSeparate(int colorMode, int alphaMode){ GL20.glBlendEquationSeparate(colorMode,alphaMode); } + @Override public void glBlendFunc(int param1, int param2) { GL11.glBlendFunc(param1, param2); } + @Override public void glBlendFuncSeparate(int param1, int param2, int param3, int param4) { GL14.glBlendFuncSeparate(param1, param2, param3, param4); } + @Override public void glBufferData(int param1, long param2, int param3) { GL15.glBufferData(param1, param2, param3); } + @Override public void glBufferData(int param1, FloatBuffer param2, int param3) { checkLimit(param2); GL15.glBufferData(param1, param2, param3); } + @Override public void glBufferData(int param1, ShortBuffer param2, int param3) { checkLimit(param2); GL15.glBufferData(param1, param2, param3); } + @Override public void glBufferData(int param1, ByteBuffer param2, int param3) { checkLimit(param2); GL15.glBufferData(param1, param2, param3); } + @Override + public void glBufferData(int target, IntBuffer data, int usage) { + checkLimit(data); + GL15.glBufferData(target, data, usage); + } + + @Override public void glBufferSubData(int param1, long param2, FloatBuffer param3) { checkLimit(param3); GL15.glBufferSubData(param1, param2, param3); } + @Override public void glBufferSubData(int param1, long param2, ShortBuffer param3) { checkLimit(param3); GL15.glBufferSubData(param1, param2, param3); } + @Override public void glBufferSubData(int param1, long param2, ByteBuffer param3) { checkLimit(param3); GL15.glBufferSubData(param1, param2, param3); } + @Override public void glClear(int param1) { GL11.glClear(param1); } + @Override public void glClearColor(float param1, float param2, float param3, float param4) { GL11.glClearColor(param1, param2, param3, param4); } + @Override public void glColorMask(boolean param1, boolean param2, boolean param3, boolean param4) { GL11.glColorMask(param1, param2, param3, param4); } + @Override public void glCompileShader(int param1) { GL20.glCompileShader(param1); } + @Override public void glCompressedTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) { checkLimit(param7); GL13.glCompressedTexImage2D(param1, param2, param3, param4, param5, param6, param7); } + @Override public void glCompressedTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) { checkLimit(param8); GL13.glCompressedTexImage3D(param1, param2, param3, param4, param5, param6, param7, param8); } + @Override public void glCompressedTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, ByteBuffer param8) { checkLimit(param8); GL13.glCompressedTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, param8); } + @Override public void glCompressedTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) { checkLimit(param10); GL13.glCompressedTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); } + @Override public int glCreateProgram() { return GL20.glCreateProgram(); } + @Override public int glCreateShader(int param1) { return GL20.glCreateShader(param1); } + @Override public void glCullFace(int param1) { GL11.glCullFace(param1); } + @Override public void glDeleteBuffers(IntBuffer param1) { checkLimit(param1); GL15.glDeleteBuffers(param1); } + @Override public void glDeleteProgram(int param1) { GL20.glDeleteProgram(param1); } + @Override public void glDeleteShader(int param1) { GL20.glDeleteShader(param1); } + @Override public void glDeleteTextures(IntBuffer param1) { checkLimit(param1); GL11.glDeleteTextures(param1); } + @Override public void glDepthFunc(int param1) { GL11.glDepthFunc(param1); } + @Override public void glDepthMask(boolean param1) { GL11.glDepthMask(param1); } + @Override public void glDepthRange(double param1, double param2) { GL11.glDepthRange(param1, param2); } + @Override public void glDetachShader(int param1, int param2) { GL20.glDetachShader(param1, param2); } + @Override public void glDisable(int param1) { GL11.glDisable(param1); } + @Override public void glDisableVertexAttribArray(int param1) { GL20.glDisableVertexAttribArray(param1); } + @Override public void glDrawArrays(int param1, int param2, int param3) { GL11.glDrawArrays(param1, param2, param3); } + @Override public void glDrawBuffer(int param1) { GL11.glDrawBuffer(param1); } + @Override public void glDrawRangeElements(int param1, int param2, int param3, int param4, int param5, long param6) { GL12.glDrawRangeElements(param1, param2, param3, param4, param5, param6); } + @Override public void glEnable(int param1) { GL11.glEnable(param1); } + @Override public void glEnableVertexAttribArray(int param1) { GL20.glEnableVertexAttribArray(param1); } @@ -215,6 +297,7 @@ public void glEndQuery(int target) { GL15.glEndQuery(target); } + @Override public void glGenBuffers(IntBuffer param1) { checkLimit(param1); GL15.glGenBuffers(param1); @@ -225,226 +308,288 @@ public void glGenQueries(int num, IntBuffer ids) { GL15.glGenQueries(ids); } + @Override public void glGenTextures(IntBuffer param1) { checkLimit(param1); GL11.glGenTextures(param1); } + @Override public void glGetBoolean(int param1, ByteBuffer param2) { checkLimit(param2); GL11.glGetBoolean(param1, param2); } + @Override public void glGetBufferSubData(int target, long offset, ByteBuffer data) { checkLimit(data); GL15.glGetBufferSubData(target, offset, data); } + @Override + public void glGetBufferSubData(int target, long offset, IntBuffer data) { + checkLimit(data); + GL15.glGetBufferSubData(target, offset, data); + } + + @Override public int glGetError() { return GL11.glGetError(); } - + + @Override + public void glGetFloat(int parameterId, FloatBuffer storeValues) { + checkLimit(storeValues); + GL11.glGetFloat(parameterId, storeValues); + } + + @Override public void glGetInteger(int param1, IntBuffer param2) { checkLimit(param2); GL11.glGetInteger(param1, param2); } + @Override public void glGetProgram(int param1, int param2, IntBuffer param3) { checkLimit(param3); GL20.glGetProgram(param1, param2, param3); } + @Override public void glGetShader(int param1, int param2, IntBuffer param3) { checkLimit(param3); GL20.glGetShader(param1, param2, param3); } + @Override public String glGetString(int param1) { return GL11.glGetString(param1); } + @Override public String glGetString(int param1, int param2) { return GL30.glGetStringi(param1, param2); } + @Override public boolean glIsEnabled(int param1) { return GL11.glIsEnabled(param1); } + @Override public void glLineWidth(float param1) { GL11.glLineWidth(param1); } + @Override public void glLinkProgram(int param1) { GL20.glLinkProgram(param1); } + @Override public void glPixelStorei(int param1, int param2) { GL11.glPixelStorei(param1, param2); } + @Override public void glPointSize(float param1) { GL11.glPointSize(param1); } + @Override public void glPolygonMode(int param1, int param2) { GL11.glPolygonMode(param1, param2); } + @Override public void glPolygonOffset(float param1, float param2) { GL11.glPolygonOffset(param1, param2); } + @Override public void glReadBuffer(int param1) { GL11.glReadBuffer(param1); } + @Override public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, ByteBuffer param7) { checkLimit(param7); GL11.glReadPixels(param1, param2, param3, param4, param5, param6, param7); } + @Override public void glReadPixels(int param1, int param2, int param3, int param4, int param5, int param6, long param7) { GL11.glReadPixels(param1, param2, param3, param4, param5, param6, param7); } + @Override public void glScissor(int param1, int param2, int param3, int param4) { GL11.glScissor(param1, param2, param3, param4); } + @Override public void glStencilFuncSeparate(int param1, int param2, int param3, int param4) { GL20.glStencilFuncSeparate(param1, param2, param3, param4); } + @Override public void glStencilOpSeparate(int param1, int param2, int param3, int param4) { GL20.glStencilOpSeparate(param1, param2, param3, param4); } + @Override public void glTexImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) { checkLimit(param9); GL11.glTexImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9); } + @Override public void glTexImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, ByteBuffer param10) { checkLimit(param10); GL12.glTexImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10); } + @Override public void glTexParameterf(int param1, int param2, float param3) { GL11.glTexParameterf(param1, param2, param3); } + @Override public void glTexParameteri(int param1, int param2, int param3) { GL11.glTexParameteri(param1, param2, param3); } + @Override public void glTexSubImage2D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, ByteBuffer param9) { checkLimit(param9); GL11.glTexSubImage2D(param1, param2, param3, param4, param5, param6, param7, param8, param9); } + @Override public void glTexSubImage3D(int param1, int param2, int param3, int param4, int param5, int param6, int param7, int param8, int param9, int param10, ByteBuffer param11) { checkLimit(param11); GL12.glTexSubImage3D(param1, param2, param3, param4, param5, param6, param7, param8, param9, param10, param11); } + @Override public void glUniform1(int param1, FloatBuffer param2) { checkLimit(param2); GL20.glUniform1(param1, param2); } + @Override public void glUniform1(int param1, IntBuffer param2) { checkLimit(param2); GL20.glUniform1(param1, param2); } + @Override public void glUniform1f(int param1, float param2) { GL20.glUniform1f(param1, param2); } + @Override public void glUniform1i(int param1, int param2) { GL20.glUniform1i(param1, param2); } + @Override public void glUniform2(int param1, IntBuffer param2) { checkLimit(param2); GL20.glUniform2(param1, param2); } + @Override public void glUniform2(int param1, FloatBuffer param2) { checkLimit(param2); GL20.glUniform2(param1, param2); } + @Override public void glUniform2f(int param1, float param2, float param3) { GL20.glUniform2f(param1, param2, param3); } + @Override public void glUniform3(int param1, IntBuffer param2) { checkLimit(param2); GL20.glUniform3(param1, param2); } + @Override public void glUniform3(int param1, FloatBuffer param2) { checkLimit(param2); GL20.glUniform3(param1, param2); } + @Override public void glUniform3f(int param1, float param2, float param3, float param4) { GL20.glUniform3f(param1, param2, param3, param4); } + @Override public void glUniform4(int param1, FloatBuffer param2) { checkLimit(param2); GL20.glUniform4(param1, param2); } + @Override public void glUniform4(int param1, IntBuffer param2) { checkLimit(param2); GL20.glUniform4(param1, param2); } + @Override public void glUniform4f(int param1, float param2, float param3, float param4, float param5) { GL20.glUniform4f(param1, param2, param3, param4, param5); } + @Override public void glUniformMatrix3(int param1, boolean param2, FloatBuffer param3) { checkLimit(param3); GL20.glUniformMatrix3(param1, param2, param3); } + @Override public void glUniformMatrix4(int param1, boolean param2, FloatBuffer param3) { checkLimit(param3); GL20.glUniformMatrix4(param1, param2, param3); } + @Override public void glUseProgram(int param1) { GL20.glUseProgram(param1); } + @Override public void glVertexAttribPointer(int param1, int param2, int param3, boolean param4, int param5, long param6) { GL20.glVertexAttribPointer(param1, param2, param3, param4, param5, param6); } + @Override public void glViewport(int param1, int param2, int param3, int param4) { GL11.glViewport(param1, param2, param3, param4); } + @Override public int glGetAttribLocation(int param1, String param2) { // NOTE: LWJGL requires null-terminated strings return GL20.glGetAttribLocation(param1, param2 + "\0"); } + @Override public int glGetUniformLocation(int param1, String param2) { // NOTE: LWJGL requires null-terminated strings return GL20.glGetUniformLocation(param1, param2 + "\0"); } + @Override public void glShaderSource(int param1, String[] param2, IntBuffer param3) { checkLimit(param3); GL20.glShaderSource(param1, param2); } + @Override public String glGetProgramInfoLog(int program, int maxSize) { return GL20.glGetProgramInfoLog(program, maxSize); } @@ -459,6 +604,7 @@ public int glGetQueryObjectiv(int query, int pname) { return GL15.glGetQueryObjecti(query, pname); } + @Override public String glGetShaderInfoLog(int shader, int maxSize) { return GL20.glGetShaderInfoLog(shader, maxSize); } diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java index e3b9e6c77a..62360e67d6 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/LwjglGLExt.java @@ -51,8 +51,8 @@ public void glDrawBuffers(IntBuffer bufs) { } @Override - public void glDrawElementsInstancedARB(int mode, int indices_count, int type, long indices_buffer_offset, int primcount) { - ARBDrawInstanced.glDrawElementsInstancedARB(mode, indices_count, type, indices_buffer_offset, primcount); + public void glDrawElementsInstancedARB(int mode, int indicesCount, int type, long indicesBufferOffset, int primcount) { + ARBDrawInstanced.glDrawElementsInstancedARB(mode, indicesCount, type, indicesBufferOffset, primcount); } @Override @@ -62,8 +62,8 @@ public void glGetMultisample(int pname, int index, FloatBuffer val) { } @Override - public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedsamplelocations) { - ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedsamplelocations); + public void glTexImage2DMultisample(int target, int samples, int internalformat, int width, int height, boolean fixedSampleLocations) { + ARBTextureMultisample.glTexImage2DMultisample(target, samples, internalformat, width, height, fixedSampleLocations); } @Override diff --git a/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/package-info.java b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/package-info.java new file mode 100644 index 0000000000..0ed3e68a3b --- /dev/null +++ b/jme3-lwjgl/src/main/java/com/jme3/renderer/lwjgl/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * rendering support for the Lightweight Java Game Library (LWJGL) + */ +package com.jme3.renderer.lwjgl; diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java index 528c788ead..5aabff1e54 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglAbstractDisplay.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,22 +63,26 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna /** * @return Type.Display or Type.Canvas */ + @Override public abstract Type getType(); /** * Set the title if it's a windowed display - * @param title + * @param title the desired title */ + @Override public abstract void setTitle(String title); /** * Restart if it's a windowed or full-screen display. */ + @Override public abstract void restart(); /** * Apply the settings, changing resolution, etc. - * @param settings + * @param settings the AppSettings to apply + * @throws LWJGLException for various error conditions */ protected abstract void createContext(AppSettings settings) throws LWJGLException; @@ -89,15 +93,18 @@ public abstract class LwjglAbstractDisplay extends LwjglContext implements Runna /** * Does LWJGL display initialization in the OpenGL thread + * + * @return true if successful, otherwise false */ - protected boolean initInThread(){ + protected boolean initInThread() { try { - if (!JmeSystem.isLowPermissions()){ + if (!JmeSystem.isLowPermissions()) { // Enable uncaught exception handler only for current thread Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { + @Override public void uncaughtException(Thread thread, Throwable thrown) { listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); - if (needClose.get()){ + if (needClose.get()) { // listener.handleError() has requested the // context to close. Satisfy request. deinitInThread(); @@ -106,7 +113,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { }); } - // For canvas, this will create a pbuffer, + // For canvas, this will create a Pbuffer, // allowing us to query information. // When the canvas context becomes available, it will // be replaced seamlessly. @@ -115,15 +122,20 @@ public void uncaughtException(Thread thread, Throwable thrown) { created.set(true); super.internalCreate(); - } catch (Exception ex){ + } catch (Exception ex) { try { - if (Display.isCreated()) + if (Display.isCreated()) { Display.destroy(); + } } catch (Exception ex2){ logger.log(Level.WARNING, null, ex2); } listener.handleError("Failed to create display", ex); + synchronized (createdLock) { + createdLock.notifyAll(); // Release the lock, so start(true) doesn't deadlock. + } + return false; // if we failed to create display, do not continue } @@ -131,7 +143,7 @@ public void uncaughtException(Thread thread, Throwable thrown) { return true; } - protected boolean checkGLError(){ + protected boolean checkGLError() { try { Util.checkGLError(); } catch (OpenGLException ex){ @@ -150,9 +162,9 @@ protected void runLoop(){ listener.update(); - // All this does is call swap buffers + // All this does is call update(). // If the canvas is not active, there's no need to waste time - // doing that .. + // doing that. if (renderable.get()){ assert checkGLError(); @@ -184,8 +196,8 @@ protected void runLoop(){ Display.processMessages(); } - // Subclasses just call GLObjectManager clean up objects here - // it is safe .. for now. + // Subclasses just call GLObjectManager. Clean up objects here. + // It is safe ... for now. renderer.postFrame(); } @@ -200,6 +212,7 @@ protected void deinitInThread(){ super.internalDestroy(); } + @Override public void run(){ if (listener == null) { throw new IllegalStateException("SystemListener is not set on context!" @@ -237,6 +250,7 @@ public void run(){ deinitInThread(); } + @Override public JoyInput getJoyInput() { if (joyInput == null){ joyInput = new JInputJoyInput(); @@ -244,6 +258,7 @@ public JoyInput getJoyInput() { return joyInput; } + @Override public MouseInput getMouseInput() { if (mouseInput == null){ mouseInput = new LwjglMouseInput(this); @@ -251,6 +266,7 @@ public MouseInput getMouseInput() { return mouseInput; } + @Override public KeyInput getKeyInput() { if (keyInput == null){ keyInput = new LwjglKeyInput(this); @@ -258,15 +274,22 @@ public KeyInput getKeyInput() { return keyInput; } + @Override public TouchInput getTouchInput() { return null; } + @Override public void setAutoFlushFrames(boolean enabled){ this.autoFlush = enabled; } - public void destroy(boolean waitFor){ + @Override + public void destroy(boolean waitFor) { + if (needClose.get()) { + return; // Already destroyed + } + needClose.set(true); if (waitFor) waitFor(false); diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java index 0c5392b192..5fa76754b2 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -1,490 +1,519 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.lwjgl; - -import com.jme3.system.AppSettings; -import com.jme3.system.JmeCanvasContext; -import com.jme3.system.JmeContext.Type; -import com.jme3.system.JmeSystem; -import com.jme3.system.Platform; -import java.awt.Canvas; -import java.util.logging.Level; -import java.util.logging.Logger; -import javax.swing.SwingUtilities; -import org.lwjgl.LWJGLException; -import org.lwjgl.input.Keyboard; -import org.lwjgl.input.Mouse; -import org.lwjgl.opengl.Display; -import org.lwjgl.opengl.Pbuffer; -import org.lwjgl.opengl.PixelFormat; - -public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { - - protected static final int TASK_NOTHING = 0, - TASK_DESTROY_DISPLAY = 1, - TASK_CREATE_DISPLAY = 2, - TASK_COMPLETE = 3; - -// protected static final boolean USE_SHARED_CONTEXT = -// Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true")); - - protected static final boolean USE_SHARED_CONTEXT = false; - - private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); - private Canvas canvas; - private int width; - private int height; - - private final Object taskLock = new Object(); - private int desiredTask = TASK_NOTHING; - - private Thread renderThread; - private boolean runningFirstTime = true; - private boolean mouseWasGrabbed = false; - - private boolean mouseWasCreated = false; - private boolean keyboardWasCreated = false; - - private Pbuffer pbuffer; - private PixelFormat pbufferFormat; - private PixelFormat canvasFormat; - - private class GLCanvas extends Canvas { - @Override - public void addNotify(){ - super.addNotify(); - - if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) { - return; // already destroyed. - } - - if (renderThread == null){ - logger.log(Level.FINE, "EDT: Creating OGL thread."); - - // Also set some settings on the canvas here. - // So we don't do it outside the AWT thread. - canvas.setFocusable(true); - canvas.setIgnoreRepaint(true); - - renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); - renderThread.start(); - }else if (needClose.get()){ - return; - } - - logger.log(Level.FINE, "EDT: Telling OGL to create display .."); - synchronized (taskLock){ - desiredTask = TASK_CREATE_DISPLAY; -// while (desiredTask != TASK_COMPLETE){ -// try { -// taskLock.wait(); -// } catch (InterruptedException ex) { -// return; -// } -// } -// desiredTask = TASK_NOTHING; - } -// logger.log(Level.FINE, "EDT: OGL has created the display"); - } - - @Override - public void removeNotify(){ - if (needClose.get()){ - logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas."); - super.removeNotify(); - return; - } - - // We must tell GL context to shutdown and wait for it to - // shutdown, otherwise, issues will occur. - logger.log(Level.FINE, "EDT: Telling OGL to destroy display .."); - synchronized (taskLock){ - desiredTask = TASK_DESTROY_DISPLAY; - while (desiredTask != TASK_COMPLETE){ - try { - taskLock.wait(); - } catch (InterruptedException ex){ - super.removeNotify(); - return; - } - } - desiredTask = TASK_NOTHING; - } - - logger.log(Level.FINE, "EDT: Acknowledged receipt of canvas death"); - // GL context is dead at this point - - super.removeNotify(); - } - } - - public LwjglCanvas(){ - super(); - canvas = new GLCanvas(); - } - - @Override - public Type getType() { - return Type.Canvas; - } - - public void create(boolean waitFor){ - if (renderThread == null){ - logger.log(Level.FINE, "MAIN: Creating OGL thread."); - - renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); - renderThread.start(); - } - // do not do anything. - // superclass's create() will be called at initInThread() - if (waitFor) { - waitFor(true); - } - } - - @Override - public void setTitle(String title) { - } - - @Override - public void restart() { - frameRate = settings.getFrameRate(); - // TODO: Handle other cases, like change of pixel format, etc. - } - - public Canvas getCanvas(){ - return canvas; - } - - @Override - protected void runLoop(){ - if (desiredTask != TASK_NOTHING){ - synchronized (taskLock){ - switch (desiredTask){ - case TASK_CREATE_DISPLAY: - logger.log(Level.FINE, "OGL: Creating display .."); - restoreCanvas(); - listener.gainFocus(); - desiredTask = TASK_NOTHING; - break; - case TASK_DESTROY_DISPLAY: - logger.log(Level.FINE, "OGL: Destroying display .."); - listener.loseFocus(); - pauseCanvas(); - break; - } - desiredTask = TASK_COMPLETE; - taskLock.notifyAll(); - } - } - - if (renderable.get()){ - int newWidth = Math.max(canvas.getWidth(), 1); - int newHeight = Math.max(canvas.getHeight(), 1); - if (width != newWidth || height != newHeight){ - width = newWidth; - height = newHeight; - if (listener != null){ - listener.reshape(width, height); - } - } - }else{ - if (frameRate <= 0){ - // NOTE: MUST be done otherwise - // Windows OS will freeze - Display.sync(30); - } - } - - super.runLoop(); - } - - private void pauseCanvas(){ - if (Mouse.isCreated()){ - if (Mouse.isGrabbed()){ - Mouse.setGrabbed(false); - mouseWasGrabbed = true; - } - mouseWasCreated = true; - Mouse.destroy(); - } - if (Keyboard.isCreated()){ - keyboardWasCreated = true; - Keyboard.destroy(); - } - - renderable.set(false); - destroyContext(); - } - - /** - * Called to restore the canvas. - */ - private void restoreCanvas(){ - logger.log(Level.FINE, "OGL: Waiting for canvas to become displayable.."); - while (!canvas.isDisplayable()){ - try { - Thread.sleep(10); - } catch (InterruptedException ex) { - logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); - } - } - - logger.log(Level.FINE, "OGL: Creating display context .."); - - // Set renderable to true, since canvas is now displayable. - renderable.set(true); - createContext(settings); - - logger.log(Level.FINE, "OGL: Display is active!"); - - try { - if (mouseWasCreated){ - Mouse.create(); - if (mouseWasGrabbed){ - Mouse.setGrabbed(true); - mouseWasGrabbed = false; - } - } - if (keyboardWasCreated){ - Keyboard.create(); - keyboardWasCreated = false; - } - } catch (LWJGLException ex){ - logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); - } - - SwingUtilities.invokeLater(new Runnable(){ - public void run(){ - canvas.requestFocus(); - } - }); - } - - /** - * It seems it is best to use one pixel format for all shared contexts. - * @see http://developer.apple.com/library/mac/#qa/qa1248/_index.html - */ - protected PixelFormat acquirePixelFormat(boolean forPbuffer){ - if (forPbuffer){ - // Use 0 samples for pbuffer format, prevents - // crashes on bad drivers - if (pbufferFormat == null){ - pbufferFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - 0, // samples - 0, - 0, - 0, - settings.useStereo3D()); - } - return pbufferFormat; - }else{ - if (canvasFormat == null){ - int samples = getNumSamplesToUse(); - canvasFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples, - 0, - 0, - 0, - settings.useStereo3D()); - } - return canvasFormat; - } - } - - /** - * Makes sure the pbuffer is available and ready for use - */ - protected void makePbufferAvailable() throws LWJGLException{ - if (pbuffer != null && pbuffer.isBufferLost()){ - logger.log(Level.WARNING, "PBuffer was lost!"); - pbuffer.destroy(); - pbuffer = null; - } - - if (pbuffer == null) { - pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null); - pbuffer.makeCurrent(); - logger.log(Level.FINE, "OGL: Pbuffer has been created"); - - // Any created objects are no longer valid - if (!runningFirstTime){ - renderer.resetGLObjects(); - } - } - - pbuffer.makeCurrent(); - if (!pbuffer.isCurrent()){ - throw new LWJGLException("Pbuffer cannot be made current"); - } - } - - protected void destroyPbuffer(){ - if (pbuffer != null){ - if (!pbuffer.isBufferLost()){ - pbuffer.destroy(); - } - pbuffer = null; - } - } - - /** - * This is called: - * 1) When the context thread ends - * 2) Any time the canvas becomes non-displayable - */ - protected void destroyContext(){ - try { - // invalidate the state so renderer can resume operation - if (!USE_SHARED_CONTEXT){ - renderer.cleanup(); - } - - if (Display.isCreated()){ - /* FIXES: - * org.lwjgl.LWJGLException: X Error - * BadWindow (invalid Window parameter) request_code: 2 minor_code: 0 - * - * Destroying keyboard early prevents the error above, triggered - * by destroying keyboard in by Display.destroy() or Display.setParent(null). - * Therefore Keyboard.destroy() should precede any of these calls. - */ - if (Keyboard.isCreated()){ - // Should only happen if called in - // LwjglAbstractDisplay.deinitInThread(). - Keyboard.destroy(); - } - - //try { - // NOTE: On Windows XP, not calling setParent(null) - // freezes the application. - // On Mac it freezes the application. - // On Linux it fixes a crash with X Window System. - if (JmeSystem.getPlatform() == Platform.Windows32 - || JmeSystem.getPlatform() == Platform.Windows64){ - //Display.setParent(null); - } - //} catch (LWJGLException ex) { - // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); - //} - - Display.destroy(); - } - - // The canvas is no longer visible, - // but the context thread is still running. - if (!needClose.get()){ - // MUST make sure there's still a context current here .. - // Display is dead, make pbuffer available to the system - makePbufferAvailable(); - - renderer.invalidateState(); - }else{ - // The context thread is no longer running. - // Destroy pbuffer. - destroyPbuffer(); - } - } catch (LWJGLException ex) { - listener.handleError("Failed make pbuffer available", ex); - } - } - - /** - * This is called: - * 1) When the context thread starts - * 2) Any time the canvas becomes displayable again. - */ - @Override - protected void createContext(AppSettings settings) { - // In case canvas is not visible, we still take framerate - // from settings to prevent "100% CPU usage" - frameRate = settings.getFrameRate(); - allowSwapBuffers = settings.isSwapBuffers(); - - try { - if (renderable.get()){ - if (!runningFirstTime){ - // because the display is a different opengl context - // must reset the context state. - if (!USE_SHARED_CONTEXT){ - renderer.cleanup(); - } - } - - // if the pbuffer is currently active, - // make sure to deactivate it - destroyPbuffer(); - - if (Keyboard.isCreated()){ - Keyboard.destroy(); - } - - try { - Thread.sleep(1000); - } catch (InterruptedException ex) { - } - - Display.setVSyncEnabled(settings.isVSync()); - Display.setParent(canvas); - - if (USE_SHARED_CONTEXT){ - Display.create(acquirePixelFormat(false), pbuffer); - }else{ - Display.create(acquirePixelFormat(false)); - } - - renderer.invalidateState(); - }else{ - // First create the pbuffer, if it is needed. - makePbufferAvailable(); - } - - // At this point, the OpenGL context is active. - if (runningFirstTime){ - // THIS is the part that creates the renderer. - // It must always be called, now that we have the pbuffer workaround. - initContextFirstTime(); - runningFirstTime = false; - } - } catch (LWJGLException ex) { - listener.handleError("Failed to initialize OpenGL context", ex); - // TODO: Fix deadlock that happens after the error (throw runtime exception?) - } - } -} +/* + * Copyright (c) 2009-2021 jMonkeyEngine All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this list of conditions + * and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided with + * the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY + * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.system.AppSettings; +import com.jme3.system.Displays; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.JmeContext.Type; +import com.jme3.system.JmeSystem; +import com.jme3.system.Platform; +import java.awt.Canvas; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.swing.SwingUtilities; +import org.lwjgl.LWJGLException; +import org.lwjgl.input.Keyboard; +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.Display; +import org.lwjgl.opengl.Pbuffer; +import org.lwjgl.opengl.PixelFormat; + +public class LwjglCanvas extends LwjglAbstractDisplay implements JmeCanvasContext { + + protected static final int TASK_NOTHING = 0, TASK_DESTROY_DISPLAY = 1, TASK_CREATE_DISPLAY = + 2, TASK_COMPLETE = 3; + + // protected static final boolean USE_SHARED_CONTEXT = + // Boolean.parseBoolean(System.getProperty("jme3.canvas.sharedctx", "true")); + + protected static final boolean USE_SHARED_CONTEXT = false; + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + private Canvas canvas; + private int width; + private int height; + + private final Object taskLock = new Object(); + private int desiredTask = TASK_NOTHING; + + private Thread renderThread; + private boolean runningFirstTime = true; + private boolean mouseWasGrabbed = false; + + private boolean mouseWasCreated = false; + private boolean keyboardWasCreated = false; + + private Pbuffer pbuffer; + private PixelFormat pbufferFormat; + private PixelFormat canvasFormat; + + private class GLCanvas extends Canvas { + + @Override + public void addNotify() { + super.addNotify(); + + if (renderThread != null && renderThread.getState() == Thread.State.TERMINATED) { + return; // already destroyed. + } + + if (renderThread == null) { + logger.log(Level.FINE, "EDT: Creating OGL thread."); + + // Also set some settings on the canvas here. + // So we don't do it outside the AWT thread. + canvas.setFocusable(true); + canvas.setIgnoreRepaint(true); + + renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); + renderThread.start(); + } else if (needClose.get()) { + return; + } + + logger.log(Level.FINE, "EDT: Telling OGL to create display .."); + synchronized (taskLock) { + desiredTask = TASK_CREATE_DISPLAY; + // while (desiredTask != TASK_COMPLETE){ + // try { + // taskLock.wait(); + // } catch (InterruptedException ex) { + // return; + // } + // } + // desiredTask = TASK_NOTHING; + } + // logger.log(Level.FINE, "EDT: OGL has created the display"); + } + + @Override + public void removeNotify() { + if (needClose.get()) { + logger.log(Level.FINE, "EDT: Application is stopped. Not restoring canvas."); + super.removeNotify(); + return; + } + + // We must tell GL context to shut down and wait for it to + // shut down. Otherwise, issues will occur. + logger.log(Level.FINE, "EDT: Telling OGL to destroy display .."); + synchronized (taskLock) { + desiredTask = TASK_DESTROY_DISPLAY; + while (desiredTask != TASK_COMPLETE) { + try { + taskLock.wait(); + } catch (InterruptedException ex) { + super.removeNotify(); + return; + } + } + desiredTask = TASK_NOTHING; + } + + logger.log(Level.FINE, "EDT: Acknowledged receipt of canvas death"); + // GL context is dead at this point + + super.removeNotify(); + } + } + + public LwjglCanvas() { + super(); + canvas = new GLCanvas(); + } + + @Override + public Type getType() { + return Type.Canvas; + } + + @Override + public void create(boolean waitFor) { + if (renderThread == null) { + logger.log(Level.FINE, "MAIN: Creating OGL thread."); + + renderThread = new Thread(LwjglCanvas.this, THREAD_NAME); + renderThread.start(); + } + // do not do anything. + // superclass's create() will be called at initInThread() + if (waitFor) { + waitFor(true); + } + } + + @Override + public void setTitle(String title) {} + + @Override + public void restart() { + frameRate = settings.getFrameRate(); + // TODO: Handle other cases, like change of pixel format, etc. + } + + @Override + public Canvas getCanvas() { + return canvas; + } + + @Override + protected void runLoop() { + if (desiredTask != TASK_NOTHING) { + synchronized (taskLock) { + switch (desiredTask) { + case TASK_CREATE_DISPLAY: + logger.log(Level.FINE, "OGL: Creating display .."); + restoreCanvas(); + listener.gainFocus(); + desiredTask = TASK_NOTHING; + break; + case TASK_DESTROY_DISPLAY: + logger.log(Level.FINE, "OGL: Destroying display .."); + listener.loseFocus(); + pauseCanvas(); + break; + } + desiredTask = TASK_COMPLETE; + taskLock.notifyAll(); + } + } + + if (renderable.get()) { + int newWidth = Math.max(canvas.getWidth(), 1); + int newHeight = Math.max(canvas.getHeight(), 1); + if (width != newWidth || height != newHeight) { + width = newWidth; + height = newHeight; + if (listener != null) { + listener.reshape(width, height); + } + } + } else { + if (frameRate <= 0) { + // NOTE: MUST be done otherwise + // Windows OS will freeze + Display.sync(30); + } + } + + super.runLoop(); + } + + private void pauseCanvas() { + if (Mouse.isCreated()) { + if (Mouse.isGrabbed()) { + Mouse.setGrabbed(false); + mouseWasGrabbed = true; + } + mouseWasCreated = true; + Mouse.destroy(); + } + if (Keyboard.isCreated()) { + keyboardWasCreated = true; + Keyboard.destroy(); + } + + renderable.set(false); + destroyContext(); + } + + /** + * Called to restore the canvas. + */ + private void restoreCanvas() { + logger.log(Level.FINE, "OGL: Waiting for canvas to become displayable.."); + while (!canvas.isDisplayable()) { + try { + Thread.sleep(10); + } catch (InterruptedException ex) { + logger.log(Level.SEVERE, "OGL: Interrupted! ", ex); + } + } + + logger.log(Level.FINE, "OGL: Creating display context .."); + + // Set renderable to true, since canvas is now displayable. + renderable.set(true); + createContext(settings); + + logger.log(Level.FINE, "OGL: Display is active!"); + + try { + if (mouseWasCreated) { + Mouse.create(); + if (mouseWasGrabbed) { + Mouse.setGrabbed(true); + mouseWasGrabbed = false; + } + } + if (keyboardWasCreated) { + Keyboard.create(); + keyboardWasCreated = false; + } + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Encountered exception when restoring input", ex); + } + + SwingUtilities.invokeLater( + new Runnable() { + @Override + public void run() { + canvas.requestFocus(); + } + } + ); + } + + /** + * It seems it is best to use one pixel format for all shared contexts. + * + * @see http://developer.apple.com/library/mac/#qa/qa1248/_index.html + * + * @param forPbuffer true→zero samples, false→correct number of samples + * @return a new instance + */ + protected PixelFormat acquirePixelFormat(boolean forPbuffer) { + if (forPbuffer) { + // Use 0 samples for pbuffer format, prevents + // crashes on bad drivers + if (pbufferFormat == null) { + pbufferFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + 0, // samples + 0, + 0, + 0, + settings.useStereo3D() + ); + } + return pbufferFormat; + } else { + if (canvasFormat == null) { + int samples = getNumSamplesToUse(); + canvasFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D() + ); + } + return canvasFormat; + } + } + + /** + * Makes sure the pbuffer is available and ready for use + * + * @throws LWJGLException if the buffer can't be made current + */ + protected void makePbufferAvailable() throws LWJGLException { + if (pbuffer != null && pbuffer.isBufferLost()) { + logger.log(Level.WARNING, "PBuffer was lost!"); + pbuffer.destroy(); + pbuffer = null; + } + + if (pbuffer == null) { + pbuffer = new Pbuffer(1, 1, acquirePixelFormat(true), null); + pbuffer.makeCurrent(); + logger.log(Level.FINE, "OGL: Pbuffer has been created"); + + // Any created objects are no longer valid + if (!runningFirstTime) { + renderer.resetGLObjects(); + } + } + + pbuffer.makeCurrent(); + if (!pbuffer.isCurrent()) { + throw new LWJGLException("Pbuffer cannot be made current"); + } + } + + protected void destroyPbuffer() { + if (pbuffer != null) { + if (!pbuffer.isBufferLost()) { + pbuffer.destroy(); + } + pbuffer = null; + } + } + + /** + * This is called: 1) When the context thread ends 2) Any time the canvas becomes non-displayable + */ + @Override + protected void destroyContext() { + try { + // invalidate the state so renderer can resume operation + if (!USE_SHARED_CONTEXT) { + renderer.cleanup(); + } + + if (Display.isCreated()) { + /* + * FIXES: org.lwjgl.LWJGLException: X Error BadWindow (invalid Window parameter) + * request_code: 2 minor_code: 0 + * + * Destroying keyboard early prevents the error above, triggered by destroying keyboard in + * by Display.destroy() or Display.setParent(null). Therefore, Keyboard.destroy() should + * precede any of these calls. + */ + if (Keyboard.isCreated()) { + // Should only happen if called in + // LwjglAbstractDisplay.deinitInThread(). + Keyboard.destroy(); + } + + // try { + // NOTE: On Windows XP, not calling setParent(null) + // freezes the application. + // On Mac it freezes the application. + // On Linux it fixes a crash with X Window System. + if ( + JmeSystem.getPlatform() == Platform.Windows32 || + JmeSystem.getPlatform() == Platform.Windows64 + ) { + // Display.setParent(null); + } + // } catch (LWJGLException ex) { + // logger.log(Level.SEVERE, "Encountered exception when setting parent to null", ex); + // } + + Display.destroy(); + } + + // The canvas is no longer visible, + // but the context thread is still running. + if (!needClose.get()) { + // MUST make sure there's still a context current here. + // Display is dead, make PBuffer available to the system. + makePbufferAvailable(); + + renderer.invalidateState(); + } else { + // The context thread is no longer running. + // Destroy pbuffer. + destroyPbuffer(); + } + } catch (LWJGLException ex) { + listener.handleError("Failed make pbuffer available", ex); + } + } + + /** + * This is called: 1) When the context thread starts 2) Any time the canvas becomes displayable + * again. In the first call of this method, OpenGL context is not ready yet. Therefore, OpenCL + * context cannot be created. The second call of this method is done after "simpleInitApp" is + * called. Therefore, OpenCL won't be available in "simpleInitApp" if Canvas/Swing is used. To use + * OpenCL with Canvas/Swing, you need to use OpenCL in the rendering loop "simpleUpdate" and check + * for "context.getOpenCLContext()!=null". + */ + @Override + protected void createContext(AppSettings settings) { + // In case canvas is not visible, we still take framerate + // from settings to prevent "100% CPU usage" + frameRate = settings.getFrameRate(); + allowSwapBuffers = settings.isSwapBuffers(); + + try { + if (renderable.get()) { + if (!runningFirstTime) { + // because the display is a different opengl context + // must reset the context state. + if (!USE_SHARED_CONTEXT) { + renderer.cleanup(); + } + } + + // if the pbuffer is currently active, + // make sure to deactivate it + destroyPbuffer(); + + if (Keyboard.isCreated()) { + Keyboard.destroy(); + } + + try { + Thread.sleep(1000); + } catch (InterruptedException ex) {} + + Display.setVSyncEnabled(settings.isVSync()); + Display.setParent(canvas); + + if (USE_SHARED_CONTEXT) { + Display.create(acquirePixelFormat(false), pbuffer); + } else { + Display.create(acquirePixelFormat(false)); + } + if (settings.isOpenCLSupport()) { + initOpenCL(); + } + + renderer.invalidateState(); + } else { + // First create the pbuffer, if it is needed. + makePbufferAvailable(); + } + + // At this point, the OpenGL context is active. + if (runningFirstTime) { + // THIS is the part that creates the renderer. + // It must always be called, now that we have the pbuffer workaround. + initContextFirstTime(); + runningFirstTime = false; + } + } catch (LWJGLException ex) { + listener.handleError("Failed to initialize OpenGL context", ex); + // TODO: Fix deadlock that happens after the error (throw runtime exception?) + } + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} 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 4d2d776c52..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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,11 +35,11 @@ import com.jme3.input.lwjgl.JInputJoyInput; import com.jme3.input.lwjgl.LwjglKeyInput; import com.jme3.input.lwjgl.LwjglMouseInput; +import com.jme3.opencl.DefaultPlatformChooser; import com.jme3.opencl.Device; import com.jme3.opencl.PlatformChooser; import com.jme3.opencl.lwjgl.LwjglDevice; import com.jme3.opencl.lwjgl.LwjglPlatform; -import com.jme3.opencl.DefaultPlatformChooser; import com.jme3.renderer.Renderer; import com.jme3.renderer.RendererException; import com.jme3.renderer.lwjgl.LwjglGL; @@ -50,7 +50,7 @@ import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; import com.jme3.renderer.opengl.GL4; -import com.jme3.renderer.opengl.GLDebugDesktop; +import com.jme3.renderer.opengl.GLDebug; import com.jme3.renderer.opengl.GLExt; import com.jme3.renderer.opengl.GLFbo; import com.jme3.renderer.opengl.GLRenderer; @@ -60,7 +60,6 @@ import com.jme3.system.*; import java.util.ArrayList; import java.util.List; - import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; @@ -92,6 +91,17 @@ 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; } @@ -117,6 +127,10 @@ protected int[] getGLVersion(String renderer) { maj = 3; min = 0; break; + case AppSettings.LWJGL_OPENGL31: + maj = 3; + min = 1; + break; case AppSettings.LWJGL_OPENGL32: maj = 3; min = 2; @@ -155,7 +169,7 @@ protected int[] getGLVersion(String renderer) { protected ContextAttribs createContextAttribs() { int vers[] = getGLVersion(settings.getRenderer()); - if (settings.getBoolean("GraphicsDebug") || (vers != null && vers[0] != 2)) { + if (settings.isGraphicsDebug() || (vers != null && vers[0] != 2)) { ContextAttribs attr; if (vers != null && vers[0] != 2) { attr = new ContextAttribs(vers[0], vers[1]); @@ -163,7 +177,7 @@ protected ContextAttribs createContextAttribs() { } else { attr = new ContextAttribs(); } - if (settings.getBoolean("GraphicsDebug")) { + if (settings.isGraphicsDebug()) { attr = attr.withDebug(true); } return attr; @@ -189,11 +203,11 @@ protected int determineMaxSamples(int requestedSamples) { listener.handleError("Failed to check if display is current", ex); } if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) { - // No pbuffer, assume everything is supported. + // No PBuffer, assume everything is supported. return Integer.MAX_VALUE; } else { Pbuffer pb = null; - // OpenGL2 method: Create pbuffer and query samples + // OpenGL2 method: Create PBuffer and query samples // from GL_ARB_framebuffer_object or GL_EXT_framebuffer_multisample. try { pb = new Pbuffer(1, 1, new PixelFormat(0, 0, 0), null); @@ -221,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; @@ -246,64 +260,88 @@ protected int getNumSamplesToUse() { return samples; } + /** + * Reinitializes the relevant details of the context. For internal use only. + */ + protected void reinitContext() { + initContext(false); + } + + /** + * Initializes the LWJGL renderer and input for the first time. For internal + * use only. + */ protected void initContextFirstTime() { + initContext(true); + } + + /** + * Initializes the LWJGL renderer and input. + * @param first - Whether this is the first time we are initializing and we + * need to create the renderer or not. Otherwise, we'll just reset the + * renderer as needed. + */ + private void initContext(boolean first) { if (!GLContext.getCapabilities().OpenGL20) { throw new RendererException("OpenGL 2.0 or higher is " + "required for jMonkeyEngine"); } - - int vers[] = getGLVersion(settings.getRenderer()); - if (vers != null) { - GL gl = new LwjglGL(); - GLExt glext = new LwjglGLExt(); - GLFbo glfbo; - - if (GLContext.getCapabilities().OpenGL30) { - glfbo = new LwjglGLFboGL3(); - } else { - glfbo = new LwjglGLFboEXT(); - } - - if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugDesktop(gl, glext, glfbo); - glext = (GLExt) gl; - glfbo = (GLFbo) gl; - } - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } - if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + + int version[] = getGLVersion(settings.getRenderer()); + if (version != null) { + if (first) { + GL gl = new LwjglGL(); + GLExt glext = new LwjglGLExt(); + GLFbo glfbo; + + if (GLContext.getCapabilities().OpenGL30) { + glfbo = new LwjglGLFboGL3(); + } else { + glfbo = new LwjglGLFboEXT(); + } + + if (settings.isGraphicsDebug()) { + gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); + glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); + } + if (settings.isGraphicsTiming()) { + GLTimingState timingState = new GLTimingState(); + gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); + glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); + } + if (settings.isGraphicsTrace()) { + gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); + glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + } + renderer = new GLRenderer(gl, glext, glfbo); } - renderer = new GLRenderer(gl, glext, glfbo); renderer.initialize(); } else { throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); } - if (GLContext.getCapabilities().GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { + if (GLContext.getCapabilities().GL_ARB_debug_output && settings.isGraphicsDebug()) { ARBDebugOutput.glDebugMessageCallbackARB(new ARBDebugOutputCallback(new LwjglGLDebugOutputHandler())); } renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - // Init input - if (keyInput != null) { - keyInput.initialize(); - } + if (first) { + // Init input + if (keyInput != null) { + keyInput.initialize(); + } - if (mouseInput != null) { - mouseInput.initialize(); - } + if (mouseInput != null) { + mouseInput.initialize(); + } - if (joyInput != null) { - joyInput.initialize(); + if (joyInput != null) { + joyInput.initialize(); + } } - } @SuppressWarnings("unchecked") @@ -360,7 +398,7 @@ protected void initOpenCL() { PlatformChooser chooser = null; if (settings.getOpenCLPlatformChooser() != null) { try { - chooser = (PlatformChooser) Class.forName(settings.getOpenCLPlatformChooser()).newInstance(); + chooser = (PlatformChooser) Class.forName(settings.getOpenCLPlatformChooser()).getDeclaredConstructor().newInstance(); } catch (Exception ex) { logger.log(Level.WARNING, "unable to instantiate custom PlatformChooser", ex); } @@ -368,10 +406,10 @@ protected void initOpenCL() { if (chooser == null) { chooser = new DefaultPlatformChooser(); } - List choosenDevices = chooser.chooseDevices(platforms); - List devices = new ArrayList<>(choosenDevices.size()); + List chosenDevices = chooser.chooseDevices(platforms); + List devices = new ArrayList<>(chosenDevices.size()); LwjglPlatform platform = null; - for (Device d : choosenDevices) { + for (Device d : chosenDevices) { if (!(d instanceof LwjglDevice)) { logger.log(Level.SEVERE, "attempt to return a custom Device implementation from PlatformChooser: {0}", d); return; @@ -390,13 +428,15 @@ protected void initOpenCL() { return; } clPlatform = platform; - logger.log(Level.INFO, "chosen platform: {0}", platform.getName()); - logger.log(Level.INFO, "chosen devices: {0}", choosenDevices); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "chosen platform: {0}", platform.getName()); + logger.log(Level.INFO, "chosen devices: {0}", chosenDevices); + } //create context try { CLContext c = CLContext.create(platform.getPlatform(), devices, null, Display.getDrawable(), null); - clContext = new com.jme3.opencl.lwjgl.LwjglContext(c, (List) choosenDevices); + clContext = new com.jme3.opencl.lwjgl.LwjglContext(c, (List) chosenDevices); } catch (LWJGLException ex) { logger.log(Level.SEVERE, "Unable to create OpenCL context", ex); return; @@ -446,25 +486,31 @@ protected void waitFor(boolean createdVal) { } } + @Override public boolean isCreated() { return created.get(); } + @Override public boolean isRenderable() { return renderable.get(); } + @Override public void setSettings(AppSettings settings) { this.settings.copyFrom(settings); } + @Override public AppSettings getSettings() { return settings; } + @Override public Renderer getRenderer() { return renderer; } + @Override public Timer getTimer() { return timer; } @@ -473,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 0fb787efef..bec632f993 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,255 +1,325 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.lwjgl; - -import com.jme3.system.AppSettings; -import com.jme3.system.JmeContext.Type; -import java.awt.Graphics2D; -import java.awt.image.BufferedImage; -import java.nio.ByteBuffer; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.LWJGLException; -import org.lwjgl.opengl.*; - -public class LwjglDisplay extends LwjglAbstractDisplay { - - private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); - - private final AtomicBoolean needRestart = new AtomicBoolean(false); - private PixelFormat pixelFormat; - - 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))) { - return mode; - } - } - } catch (LWJGLException ex) { - listener.handleError("Failed to acquire fullscreen display mode!", ex); - } - return null; - } - - protected void createContext(AppSettings settings) throws LWJGLException{ - DisplayMode displayMode; - if (settings.getWidth() <= 0 || settings.getHeight() <= 0){ - displayMode = Display.getDesktopDisplayMode(); - settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); - }else if (settings.isFullscreen()){ - displayMode = getFullscreenDisplayMode(settings.getWidth(), settings.getHeight(), - settings.getBitsPerPixel(), settings.getFrequency()); - if (displayMode == null) { - throw new RuntimeException("Unable to find fullscreen display mode matching settings"); - } - }else{ - displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); - } - - int samples = getNumSamplesToUse(); - PixelFormat pf = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples, - 0, - 0, - 0, - settings.useStereo3D()); - - frameRate = settings.getFrameRate(); - allowSwapBuffers = settings.isSwapBuffers(); - logger.log(Level.FINE, "Selected display mode: {0}", displayMode); - - boolean pixelFormatChanged = false; - if (created.get() && (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() - ||pixelFormat.getAlphaBits() != pf.getAlphaBits() - ||pixelFormat.getDepthBits() != pf.getDepthBits() - ||pixelFormat.getStencilBits() != pf.getStencilBits() - ||pixelFormat.getSamples() != pf.getSamples())){ - renderer.resetGLObjects(); - Display.destroy(); - pixelFormatChanged = true; - } - pixelFormat = pf; - - Display.setTitle(settings.getTitle()); - Display.setResizable(settings.isResizable()); - - if (displayMode != null) { - if (settings.isFullscreen()) { - Display.setDisplayModeAndFullscreen(displayMode); - } else { - Display.setFullscreen(false); - Display.setDisplayMode(displayMode); - } - } else { - Display.setFullscreen(settings.isFullscreen()); - } - - if (settings.getIcons() != null) { - Display.setIcon(imagesToByteBuffers(settings.getIcons())); - } - - Display.setVSyncEnabled(settings.isVSync()); - - if (created.get() && !pixelFormatChanged) { - Display.releaseContext(); - Display.makeCurrent(); - Display.update(); - } - - if (!created.get() || pixelFormatChanged){ - ContextAttribs attr = createContextAttribs(); - if (attr != null) { - Display.create(pixelFormat, attr); - } else { - Display.create(pixelFormat); - } - renderable.set(true); - - if (pixelFormatChanged && pixelFormat.getSamples() > 1 - && GLContext.getCapabilities().GL_ARB_multisample){ - GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); - } - } - - if (settings.isOpenCLSupport()) { - initOpenCL(); - } - } - - protected void destroyContext(){ - try { - renderer.cleanup(); - Display.releaseContext(); - Display.destroy(); - } catch (LWJGLException ex) { - listener.handleError("Failed to destroy context", ex); - } - } - - public void create(boolean waitFor){ - if (created.get()){ - logger.warning("create() called when display is already created!"); - return; - } - - new Thread(this, THREAD_NAME).start(); - if (waitFor) - waitFor(true); - } - - @Override - public void runLoop(){ - // This method is overriden to do restart - if (needRestart.getAndSet(false)) { - try { - createContext(settings); - } catch (LWJGLException ex) { - logger.log(Level.SEVERE, "Failed to set display settings!", ex); - } - listener.reshape(settings.getWidth(), settings.getHeight()); - logger.fine("Display restarted."); - } else if (Display.wasResized()) { - int newWidth = Display.getWidth(); - int newHeight = Display.getHeight(); - settings.setResolution(newWidth, newHeight); - listener.reshape(newWidth, newHeight); - } - - super.runLoop(); - } - - @Override - public void restart() { - if (created.get()){ - needRestart.set(true); - }else{ - logger.warning("Display is not created, cannot restart window."); - } - } - - public Type getType() { - return Type.Display; - } - - public void setTitle(String title){ - if (created.get()) - Display.setTitle(title); - } - - private ByteBuffer[] imagesToByteBuffers(Object[] images) { - ByteBuffer[] out = new ByteBuffer[images.length]; - for (int i = 0; i < images.length; i++) { - BufferedImage image = (BufferedImage) images[i]; - out[i] = imageToByteBuffer(image); - } - return out; - } - - private ByteBuffer imageToByteBuffer(BufferedImage image) { - if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { - BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); - Graphics2D g = convertedImage.createGraphics(); - double width = image.getWidth() * (double) 1; - double height = image.getHeight() * (double) 1; - g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2), - (int) ((convertedImage.getHeight() - height) / 2), - (int) (width), (int) (height), null); - g.dispose(); - image = convertedImage; - } - - byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4]; - int counter = 0; - for (int i = 0; i < image.getHeight(); i++) { - for (int j = 0; j < image.getWidth(); j++) { - int colorSpace = image.getRGB(j, i); - imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); - imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); - imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); - imageBuffer[counter + 3] = (byte) (colorSpace >> 24); - counter += 4; - } - } - return ByteBuffer.wrap(imageBuffer); - } - -} +/* + * 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.lwjgl; + +import com.jme3.system.AppSettings; +import com.jme3.system.Displays; +import com.jme3.system.JmeContext.Type; +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; +import org.lwjgl.LWJGLException; +import org.lwjgl.opengl.*; + +public class LwjglDisplay extends LwjglAbstractDisplay { + + private static final Logger logger = Logger.getLogger(LwjglDisplay.class.getName()); + + 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) || + 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; + } + } + } catch (LWJGLException ex) { + listener.handleError("Failed to acquire fullscreen display mode!", ex); + } + return null; + } + + @Override + protected void createContext(AppSettings settings) throws LWJGLException { + DisplayMode displayMode; + if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { + displayMode = Display.getDesktopDisplayMode(); + settings.setResolution(displayMode.getWidth(), displayMode.getHeight()); + } else if (settings.isFullscreen()) { + displayMode = + getFullscreenDisplayMode( + settings.getWidth(), + settings.getHeight(), + settings.getBitsPerPixel(), + settings.getFrequency() + ); + if (displayMode == null) { + // 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 { + displayMode = new DisplayMode(settings.getWidth(), settings.getHeight()); + } + + int samples = getNumSamplesToUse(); + PixelFormat pf = new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples, + 0, + 0, + 0, + settings.useStereo3D() + ); + + frameRate = settings.getFrameRate(); + allowSwapBuffers = settings.isSwapBuffers(); + logger.log(Level.FINE, "Selected display mode: {0}", displayMode); + + boolean pixelFormatChanged = false; + if ( + created.get() && + (pixelFormat.getBitsPerPixel() != pf.getBitsPerPixel() || + pixelFormat.getAlphaBits() != pf.getAlphaBits() || + pixelFormat.getDepthBits() != pf.getDepthBits() || + pixelFormat.getStencilBits() != pf.getStencilBits() || + pixelFormat.getSamples() != pf.getSamples()) + ) { + renderer.resetGLObjects(); + Display.destroy(); + pixelFormatChanged = true; + } + pixelFormat = pf; + + Display.setTitle(settings.getTitle()); + Display.setResizable(settings.isResizable()); + + if (settings.isFullscreen()) { + Display.setDisplayModeAndFullscreen(displayMode); + } else { + Display.setFullscreen(false); + Display.setDisplayMode(displayMode); + } + + if (settings.getIcons() != null) { + Display.setIcon(imagesToByteBuffers(settings.getIcons())); + } + + Display.setVSyncEnabled(settings.isVSync()); + + if (created.get() && !pixelFormatChanged) { + renderer.resetGLObjects(); + Display.releaseContext(); + Display.makeCurrent(); + Display.update(); + } + + if (!created.get() || pixelFormatChanged) { + ContextAttribs attr = createContextAttribs(); + if (attr != null) { + Display.create(pixelFormat, attr); + } else { + Display.create(pixelFormat); + } + renderable.set(true); + + if ( + pixelFormatChanged && + pixelFormat.getSamples() > 1 && + GLContext.getCapabilities().GL_ARB_multisample + ) { + GL11.glEnable(ARBMultisample.GL_MULTISAMPLE_ARB); + } + } + + if (settings.isOpenCLSupport()) { + initOpenCL(); + } + } + + @Override + protected void destroyContext() { + try { + renderer.cleanup(); + Display.releaseContext(); + Display.destroy(); + } catch (LWJGLException ex) { + listener.handleError("Failed to destroy context", ex); + } + } + + @Override + public void create(boolean waitFor) { + if (created.get()) { + logger.warning("create() called when display is already created!"); + return; + } + + new Thread(this, THREAD_NAME).start(); + if (waitFor) waitFor(true); + } + + @Override + public void runLoop() { + // This method is overridden to do restart + if (needRestart.getAndSet(false)) { + try { + createContext(settings); + } catch (LWJGLException ex) { + logger.log(Level.SEVERE, "Failed to set display settings!", ex); + } + listener.reshape(settings.getWidth(), settings.getHeight()); + if (renderable.get()) { + reinitContext(); + } else { + assert getType() == Type.Canvas; + } + logger.fine("Display restarted."); + } else if (Display.wasResized()) { + int newWidth = Display.getWidth(); + int newHeight = Display.getHeight(); + settings.setResolution(newWidth, newHeight); + listener.reshape(newWidth, newHeight); + } + + super.runLoop(); + } + + @Override + public void restart() { + if (created.get()) { + needRestart.set(true); + } else { + logger.warning("Display is not created, cannot restart window."); + } + } + + @Override + public Type getType() { + return Type.Display; + } + + @Override + public void setTitle(String title) { + if (created.get()) Display.setTitle(title); + } + + private ByteBuffer[] imagesToByteBuffers(Object[] images) { + ByteBuffer[] out = new ByteBuffer[images.length]; + for (int i = 0; i < images.length; i++) { + BufferedImage image = (BufferedImage) images[i]; + out[i] = imageToByteBuffer(image); + } + return out; + } + + private ByteBuffer imageToByteBuffer(BufferedImage image) { + if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { + BufferedImage convertedImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB_PRE + ); + Graphics2D g = convertedImage.createGraphics(); + double width = image.getWidth() * (double) 1; + double height = image.getHeight() * (double) 1; + g.drawImage( + image, + (int) ((convertedImage.getWidth() - width) / 2), + (int) ((convertedImage.getHeight() - height) / 2), + (int) (width), + (int) (height), + null + ); + g.dispose(); + image = convertedImage; + } + + byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4]; + int counter = 0; + for (int i = 0; i < image.getHeight(); i++) { + for (int j = 0; j < image.getWidth(); j++) { + int colorSpace = image.getRGB(j, i); + imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); + imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); + imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); + imageBuffer[counter + 3] = (byte) (colorSpace >> 24); + counter += 4; + } + } + return ByteBuffer.wrap(imageBuffer); + } + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index d4c86a780b..fc5c21c6ec 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -1,207 +1,233 @@ -/* - * Copyright (c) 2009-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -package com.jme3.system.lwjgl; - -import com.jme3.input.JoyInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.dummy.DummyKeyInput; -import com.jme3.input.dummy.DummyMouseInput; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.LWJGLException; -import org.lwjgl.Sys; -import org.lwjgl.opengl.*; - -public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { - - private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); - private Pbuffer pbuffer; - protected AtomicBoolean needClose = new AtomicBoolean(false); - private int width; - private int height; - private PixelFormat pixelFormat; - - protected void initInThread(){ - if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0){ - logger.severe("Offscreen surfaces are not supported."); - return; - } - - int samples = getNumSamplesToUse(); - pixelFormat = new PixelFormat(settings.getBitsPerPixel(), - settings.getAlphaBits(), - settings.getDepthBits(), - settings.getStencilBits(), - samples); - - width = settings.getWidth(); - height = settings.getHeight(); - try{ - Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in "+thread.toString(), thrown); - } - }); - - pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); - pbuffer.makeCurrent(); - - renderable.set(true); - - logger.fine("Offscreen buffer created."); - printContextInitInfo(); - } catch (LWJGLException ex){ - listener.handleError("Failed to create display", ex); - } finally { - // TODO: It is possible to avoid "Failed to find pixel format" - // error here by creating a default display. - } - super.internalCreate(); - listener.initialize(); - } - - protected boolean checkGLError(){ - try { - Util.checkGLError(); - } catch (OpenGLException ex){ - listener.handleError("An OpenGL error has occurred!", ex); - } - // NOTE: Always return true since this is used in an "assert" statement - return true; - } - - protected void runLoop(){ - if (!created.get()) { - throw new IllegalStateException(); - } - - if (pbuffer.isBufferLost()) { - pbuffer.destroy(); - - try { - pbuffer = new Pbuffer(width, height, pixelFormat, null); - pbuffer.makeCurrent(); - - // Context MUST be reset here to avoid invalid objects! - renderer.invalidateState(); - } catch (LWJGLException ex) { - listener.handleError("Failed to restore pbuffer content", ex); - } - } - - listener.update(); - assert checkGLError(); - - renderer.postFrame(); - - // Need to flush GL commands - // to see any result on the pbuffer's front buffer. - GL11.glFlush(); - - int frameRate = settings.getFrameRate(); - if (frameRate >= 1) { - Display.sync(frameRate); - } - } - - protected void deinitInThread(){ - renderable.set(false); - - listener.destroy(); - renderer.cleanup(); - pbuffer.destroy(); - logger.fine("Offscreen buffer destroyed."); - - super.internalDestroy(); - } - - public void run(){ - loadNatives(); - logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); - initInThread(); - while (!needClose.get()){ - runLoop(); - } - deinitInThread(); - } - - public void destroy(boolean waitFor){ - needClose.set(true); - if (waitFor) - waitFor(false); - } - - public void create(boolean waitFor){ - if (created.get()){ - logger.warning("create() called when pbuffer is already created!"); - return; - } - - new Thread(this, THREAD_NAME).start(); - if (waitFor) - waitFor(true); - } - - public void restart() { - } - - public void setAutoFlushFrames(boolean enabled){ - } - - public Type getType() { - return Type.OffscreenSurface; - } - - public MouseInput getMouseInput() { - return new DummyMouseInput(); - } - - public KeyInput getKeyInput() { - return new DummyKeyInput(); - } - - public JoyInput getJoyInput() { - return null; - } - - public TouchInput getTouchInput() { - return null; - } - - public void setTitle(String title) { - } - -} +/* + * Copyright (c) 2009-2020 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.system.lwjgl; + +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; +import com.jme3.system.Displays; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import org.lwjgl.LWJGLException; +import org.lwjgl.Sys; +import org.lwjgl.opengl.*; + +public class LwjglOffscreenBuffer extends LwjglContext implements Runnable { + + private static final Logger logger = Logger.getLogger(LwjglOffscreenBuffer.class.getName()); + private Pbuffer pbuffer; + protected AtomicBoolean needClose = new AtomicBoolean(false); + private int width; + private int height; + private PixelFormat pixelFormat; + + protected void initInThread() { + if ((Pbuffer.getCapabilities() & Pbuffer.PBUFFER_SUPPORTED) == 0) { + logger.severe("Offscreen surfaces are not supported."); + return; + } + + int samples = getNumSamplesToUse(); + pixelFormat = + new PixelFormat( + settings.getBitsPerPixel(), + settings.getAlphaBits(), + settings.getDepthBits(), + settings.getStencilBits(), + samples + ); + + width = settings.getWidth(); + height = settings.getHeight(); + try { + Thread.setDefaultUncaughtExceptionHandler( + new Thread.UncaughtExceptionHandler() { + @Override + public void uncaughtException(Thread thread, Throwable thrown) { + listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); + } + } + ); + + pbuffer = new Pbuffer(width, height, pixelFormat, null, null, createContextAttribs()); + pbuffer.makeCurrent(); + + renderable.set(true); + + logger.fine("Offscreen buffer created."); + printContextInitInfo(); + } catch (LWJGLException ex) { + listener.handleError("Failed to create display", ex); + } finally { + // TODO: It is possible to avoid "Failed to find pixel format" + // error here by creating a default display. + } + super.internalCreate(); + listener.initialize(); + } + + protected boolean checkGLError() { + try { + Util.checkGLError(); + } catch (OpenGLException ex) { + listener.handleError("An OpenGL error has occurred!", ex); + } + // NOTE: Always return true since this is used in an "assert" statement + return true; + } + + protected void runLoop() { + if (!created.get()) { + throw new IllegalStateException(); + } + + if (pbuffer.isBufferLost()) { + pbuffer.destroy(); + + try { + pbuffer = new Pbuffer(width, height, pixelFormat, null); + pbuffer.makeCurrent(); + + // Context MUST be reset here to avoid invalid objects! + renderer.invalidateState(); + } catch (LWJGLException ex) { + listener.handleError("Failed to restore PBuffer content", ex); + } + } + + listener.update(); + assert checkGLError(); + + renderer.postFrame(); + + // Need to flush GL commands + // to see any result on the pbuffer's front buffer. + GL11.glFlush(); + + int frameRate = settings.getFrameRate(); + if (frameRate >= 1) { + Display.sync(frameRate); + } + } + + protected void deinitInThread() { + renderable.set(false); + + listener.destroy(); + renderer.cleanup(); + pbuffer.destroy(); + logger.fine("Offscreen buffer destroyed."); + + super.internalDestroy(); + } + + @Override + public void run() { + loadNatives(); + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Using LWJGL {0}", Sys.getVersion()); + } + initInThread(); + while (!needClose.get()) { + runLoop(); + } + deinitInThread(); + } + + @Override + public void destroy(boolean waitFor) { + needClose.set(true); + if (waitFor) waitFor(false); + } + + @Override + public void create(boolean waitFor) { + if (created.get()) { + logger.warning("create() called when pbuffer is already created!"); + return; + } + + new Thread(this, THREAD_NAME).start(); + if (waitFor) waitFor(true); + } + + @Override + public void restart() {} + + @Override + public void setAutoFlushFrames(boolean enabled) {} + + @Override + public Type getType() { + return Type.OffscreenSurface; + } + + @Override + public MouseInput getMouseInput() { + return new DummyMouseInput(); + } + + @Override + public KeyInput getKeyInput() { + return new DummyKeyInput(); + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } + + @Override + public void setTitle(String title) {} + + @Override + public Displays getDisplays() { + // TODO Auto-generated method stub + return null; + } + + @Override + public int getPrimaryDisplay() { + // TODO Auto-generated method stub + return 0; + } +} diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java index 213d472c59..59156a3891 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglSmoothingTimer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -85,6 +85,7 @@ public LwjglSmoothingTimer() { logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES); } + @Override public void reset() { lastFrameDiff = 0; lastFPS = 0; @@ -108,6 +109,7 @@ public void reset() { /** * @see Timer#getTime() */ + @Override public long getTime() { return Sys.getTime() - startTime; } @@ -115,6 +117,7 @@ public long getTime() { /** * @see Timer#getResolution() */ + @Override public long getResolution() { return LWJGL_TIMER_RES; } @@ -125,18 +128,21 @@ public long getResolution() { * * @return the current frame rate. */ + @Override public float getFrameRate() { return lastFPS; } + @Override public float getTimePerFrame() { return lastTPF; } /** - * update recalulates the frame rate based on the previous + * update recalculates the frame rate based on the previous * call to update. It is assumed that update is called each frame. */ + @Override public void update() { long newTime = Sys.getTime(); long oldTime = this.oldTime; diff --git a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java index fb69a55500..85d67884ad 100644 --- a/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java +++ b/jme3-lwjgl/src/main/java/com/jme3/system/lwjgl/LwjglTimer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,6 +70,7 @@ public LwjglTimer() { logger.log(Level.FINE, "Timer resolution: {0} ticks per second", LWJGL_TIMER_RES); } + @Override public void reset() { startTime = Sys.getTime(); oldTime = getTime(); @@ -83,6 +84,7 @@ public float getTimeInSeconds() { /** * @see Timer#getTime() */ + @Override public long getTime() { return Sys.getTime() - startTime; } @@ -90,6 +92,7 @@ public long getTime() { /** * @see Timer#getResolution() */ + @Override public long getResolution() { return LWJGL_TIMER_RES; } @@ -100,18 +103,21 @@ public long getResolution() { * * @return the current frame rate. */ + @Override public float getFrameRate() { return lastFPS; } + @Override public float getTimePerFrame() { return lastTPF; } /** - * update recalulates the frame rate based on the previous + * update recalculates the frame rate based on the previous * call to update. It is assumed that update is called each frame. */ + @Override public void update() { long curTime = getTime(); lastTPF = (curTime - oldTime) * (1.0f / LWJGL_TIMER_RES); diff --git a/jme3-lwjgl3/build.gradle b/jme3-lwjgl3/build.gradle index 324c027b4b..e360ab7054 100644 --- a/jme3-lwjgl3/build.gradle +++ b/jme3-lwjgl3/build.gradle @@ -1,34 +1,72 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} +dependencies { + api project(':jme3-core') + api project(':jme3-desktop') + api (libs.lwjgl3.awt) { + exclude group: 'org.lwjgl', module: 'lwjgl' + } -def lwjglVersion = '3.2.3' + api libs.lwjgl3.base + api libs.lwjgl3.glfw + api libs.lwjgl3.jawt + api libs.lwjgl3.jemalloc + api libs.lwjgl3.openal + api libs.lwjgl3.opencl + api libs.lwjgl3.opengl + api libs.lwjgl3.sdl -sourceCompatibility = '1.8' + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.base){ classifier('natives-macos-arm64') }) -dependencies { - compile project(':jme3-core') - compile project(':jme3-desktop') - - compile "org.lwjgl:lwjgl:${lwjglVersion}" - compile "org.lwjgl:lwjgl-glfw:${lwjglVersion}" - compile "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}" - compile "org.lwjgl:lwjgl-openal:${lwjglVersion}" - compile "org.lwjgl:lwjgl-opencl:${lwjglVersion}" - compile "org.lwjgl:lwjgl-opengl:${lwjglVersion}" - runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl:${lwjglVersion}:natives-macos" - runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl-glfw:${lwjglVersion}:natives-macos" - runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl-jemalloc:${lwjglVersion}:natives-macos" - runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl-opengl:${lwjglVersion}:natives-macos" - runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl-openal:${lwjglVersion}:natives-macos" + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.glfw){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.jemalloc){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.opengl){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.openal){ classifier('natives-macos-arm64') }) + + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-windows') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-windows-x86') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-linux') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-linux-arm32') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-linux-arm64') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-macos') }) + runtimeOnly(variantOf(libs.lwjgl3.sdl){ classifier('natives-macos-arm64') }) } + +javadoc { + // Disable doclint for JDK8+. + if (JavaVersion.current().isJava8Compatible()){ + options.addStringOption('Xdoclint:none', '-quiet') + } +} + diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java index 6f3f6a3799..68dfa45c94 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwJoystickInput.java @@ -34,30 +34,65 @@ import com.jme3.input.*; import com.jme3.input.event.JoyAxisEvent; import com.jme3.input.event.JoyButtonEvent; +import com.jme3.math.FastMath; + import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.HashMap; import java.util.Map; +import java.util.logging.Level; import java.util.logging.Logger; +import com.jme3.system.AppSettings; +import org.lwjgl.glfw.GLFWGamepadState; import static org.lwjgl.glfw.GLFW.*; /** * The LWJGL implementation of {@link JoyInput}. * - * @author Daniel Johansson (dannyjo) + * @author Daniel Johansson (dannyjo), Riccardo Balbo * @since 3.1 */ public class GlfwJoystickInput implements JoyInput { - private static final Logger LOGGER = Logger.getLogger(GlfwJoystickInput.class.getName()); + private static final int POV_X_AXIS_ID = 7; + private static final int POV_Y_AXIS_ID = 8; - private RawInputListener listener; - + private final AppSettings settings; private final Map joysticks = new HashMap<>(); - private final Map joyButtonPressed = new HashMap<>(); + private final Map joyAxisValues = new HashMap<>(); private boolean initialized = false; + private float virtualTriggerThreshold; + private boolean xboxStyle; + private float globalJitterThreshold = 0f; + private GLFWGamepadState gamepadState; + private RawInputListener listener; + + public GlfwJoystickInput(AppSettings settings) { + this.settings = settings; + try { + String path = settings.getSDLGameControllerDBResourcePath(); + if (path != null && !path.trim().isEmpty()) { + ByteBuffer bbf = SdlGameControllerDb.getGamecontrollerDb(path); + if (!glfwUpdateGamepadMappings(bbf)) throw new Exception("Failed to load"); + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unable to load gamecontrollerdb, fallback to glfw default mappings", + e); + } + } + + @Override + public void initialize() { + gamepadState = GLFWGamepadState.create(); + + virtualTriggerThreshold = settings.getJoysticksTriggerToButtonThreshold(); + xboxStyle = settings.getJoysticksMapper().equals(AppSettings.JOYSTICKS_XBOX_LEGACY_MAPPER); + globalJitterThreshold = settings.getJoysticksAxisJitterThreshold(); + + initialized = true; + } @Override public void setJoyRumble(final int joyId, final float amount) { @@ -78,9 +113,9 @@ public void fireJoystickDisconnectedEvent(int jid) { public void reloadJoysticks() { joysticks.clear(); - + joyButtonPressed.clear(); + joyAxisValues.clear(); InputManager inputManager = (InputManager) listener; - Joystick[] joysticks = loadJoysticks(inputManager); inputManager.setJoysticks(joysticks); } @@ -90,41 +125,95 @@ public Joystick[] loadJoysticks(final InputManager inputManager) { for (int i = 0; i < GLFW_JOYSTICK_LAST; i++) { if (glfwJoystickPresent(i)) { - final String name = glfwGetJoystickName(i); - final GlfwJoystick joystick = new GlfwJoystick(inputManager, this, i, name); + boolean isGlfwGamepad = xboxStyle && glfwJoystickIsGamepad(i); + + String name; + if (isGlfwGamepad) { + name = glfwGetGamepadName(i); + } else { + name = glfwGetJoystickName(i); + LOGGER.log(Level.WARNING, + "Unknown controller detected: {0} - guid: {1}. Fallback to raw input handling", + new Object[] { name, glfwGetJoystickGUID(i) }); + } + + GlfwJoystick joystick = new GlfwJoystick(inputManager, this, i, name, isGlfwGamepad); joysticks.put(i, joystick); - final FloatBuffer floatBuffer = glfwGetJoystickAxes(i); + if(!isGlfwGamepad){ + // RAW axis + FloatBuffer floatBuffer = glfwGetJoystickAxes(i); + if (floatBuffer == null) continue; + + int axisIndex = 0; + while (floatBuffer.hasRemaining()) { + floatBuffer.get(); + + String logicalId = JoystickCompatibilityMappings.remapAxis(joystick.getName(), + convertAxisIndex(axisIndex)); + JoystickAxis joystickAxis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, + convertAxisIndex(axisIndex), logicalId, true, false, 0.0f); + joystick.addAxis(axisIndex, joystickAxis); + axisIndex++; + } - int axisIndex = 0; - while (floatBuffer.hasRemaining()) { - floatBuffer.get(); + // raw buttons + ByteBuffer byteBuffer = glfwGetJoystickButtons(i); + + if (byteBuffer != null) { + int buttonIndex = 0; + while (byteBuffer.hasRemaining()) { + byteBuffer.get(); + + String logicalId = JoystickCompatibilityMappings.remapButton(joystick.getName(), + String.valueOf(buttonIndex)); + JoystickButton button = new DefaultJoystickButton(inputManager, joystick, + buttonIndex, String.valueOf(buttonIndex), logicalId); + joystick.addButton(button); + joyButtonPressed.put(button, false); + buttonIndex++; + } + } + } else { + // Managed axis + for (int axisIndex = 0; axisIndex <= GLFW_GAMEPAD_AXIS_LAST; axisIndex++) { + String logicalId = remapAxisToJme(axisIndex); + if (logicalId == null) continue; + String axisName = logicalId; // no need to remap with JoystickCompatibilityMappings as + // glfw already handles remapping + JoystickAxis axis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, + axisName, logicalId, true, false, 0.0f); + joystick.addAxis(axisIndex, axis); + } - final String logicalId = JoystickCompatibilityMappings.remapComponent(joystick.getName(), convertAxisIndex(axisIndex)); - final JoystickAxis joystickAxis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, convertAxisIndex(axisIndex), logicalId, true, false, 0.0f); - joystick.addAxis(axisIndex, joystickAxis); - axisIndex++; + // Virtual POV axes for D-pad. + JoystickAxis povX = new DefaultJoystickAxis(inputManager, joystick, POV_X_AXIS_ID, + JoystickAxis.POV_X, JoystickAxis.POV_X, true, false, 0.0f); + joystick.addAxis(POV_X_AXIS_ID, povX); + + JoystickAxis povY = new DefaultJoystickAxis(inputManager, joystick, POV_Y_AXIS_ID, + JoystickAxis.POV_Y, JoystickAxis.POV_Y, true, false, 0.0f); + joystick.addAxis(POV_Y_AXIS_ID, povY); + + // managed buttons + for (int buttonIndex = 0; buttonIndex <= GLFW_GAMEPAD_BUTTON_LAST; buttonIndex++) { + String logicalId = remapButtonToJme(buttonIndex); + if (logicalId == null) continue; + String buttonName = logicalId; + JoystickButton button = new DefaultJoystickButton(inputManager, joystick, buttonIndex, + buttonName, logicalId); + joystick.addButton(button); + joyButtonPressed.put(button, false); + } } - final ByteBuffer byteBuffer = glfwGetJoystickButtons(i); - - int buttonIndex = 0; - while (byteBuffer.hasRemaining()) { - byteBuffer.get(); - - final String logicalId = JoystickCompatibilityMappings.remapComponent(joystick.getName(), String.valueOf(buttonIndex)); - final JoystickButton button = new DefaultJoystickButton(inputManager, joystick, buttonIndex, String.valueOf(buttonIndex), logicalId); - joystick.addButton(button); - joyButtonPressed.put(button, false); - buttonIndex++; - } } } return joysticks.values().toArray(new GlfwJoystick[joysticks.size()]); } - - private String convertAxisIndex(final int index) { + + private String convertAxisIndex(int index) { if (index == 0) { return "pov_x"; } else if (index == 1) { @@ -134,50 +223,194 @@ private String convertAxisIndex(final int index) { } else if (index == 3) { return "rz"; } - return String.valueOf(index); } - @Override - public void initialize() { - initialized = true; - } - @Override public void update() { - for (final Map.Entry entry : joysticks.entrySet()) { + float rawValue, value; + for (Map.Entry entry : joysticks.entrySet()) { + if (!glfwJoystickPresent(entry.getKey())) continue; + if (!entry.getValue().isGlfwGamepad()) { + + // Axes + FloatBuffer axisValues = glfwGetJoystickAxes(entry.getKey()); + + // if a joystick is added or removed, the callback reloads the joysticks. + // when the callback is called and reloads the joystick, this iterator may already have started iterating. + // To avoid a NullPointerException we null-check the axisValues and bytebuffer objects. + // If the joystick it's iterating over no-longer exists it will return null. + + if (axisValues != null) { + for (JoystickAxis axis : entry.getValue().getAxes()) { + rawValue = axisValues.get(axis.getAxisId()); + value = JoystickCompatibilityMappings.remapAxisRange(axis, rawValue); + // listener.onJoyAxisEvent(new JoyAxisEvent(axis, value, rawValue)); + updateAxis(axis, value, rawValue); + } + } + + // Buttons + ByteBuffer byteBuffer = glfwGetJoystickButtons(entry.getKey()); + + if (byteBuffer != null) { + for (JoystickButton button : entry.getValue().getButtons()) { + boolean pressed = byteBuffer.get(button.getButtonId()) == GLFW_PRESS; + updateButton(button, pressed); + } + } + } else { + if (!glfwGetGamepadState(entry.getKey(), gamepadState)) return; + Joystick joystick = entry.getValue(); - // Axes - final FloatBuffer axisValues = glfwGetJoystickAxes(entry.getKey()); + FloatBuffer axes = gamepadState.axes(); - // if a joystick is added or removed, the callback reloads the joysticks. - // when the callback is called and reloads the joystick, this iterator may already have started iterating. - // To avoid a NullPointerException we null-check the axisValues and bytebuffer objects. - // If the joystick it's iterating over no-longer exists it will return null. + // handle axes (skip virtual POV axes) + for (JoystickAxis axis : entry.getValue().getAxes()) { + int axisId = axis.getAxisId(); + if (axisId == POV_X_AXIS_ID || axisId == POV_Y_AXIS_ID) continue; + if (axisId < 0 || axisId > GLFW_GAMEPAD_AXIS_LAST) continue; - if (axisValues != null) { - for (final JoystickAxis axis : entry.getValue().getAxes()) { - final float value = axisValues.get(axis.getAxisId()); - listener.onJoyAxisEvent(new JoyAxisEvent(axis, value)); + rawValue = axes.get(axisId); + rawValue = remapAxisValueToJme(axisId, rawValue); + value = rawValue; // scaling handled by GLFW + + updateAxis(axis, value, rawValue); + } + + // virtual trigger buttons + if (virtualTriggerThreshold > 0.0f) { + float leftTrigger = remapAxisValueToJme(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER, + axes.get(GLFW_GAMEPAD_AXIS_LEFT_TRIGGER)); + float rightTrigger = remapAxisValueToJme(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER, + axes.get(GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER)); + updateButton(joystick.getButton(JoystickButton.BUTTON_XBOX_LT), + leftTrigger > virtualTriggerThreshold); + updateButton(joystick.getButton(JoystickButton.BUTTON_XBOX_RT), + rightTrigger > virtualTriggerThreshold); } - } - // Buttons - final ByteBuffer byteBuffer = glfwGetJoystickButtons(entry.getKey()); + ByteBuffer buttons = gamepadState.buttons(); + + for (int btnIndex = 0; btnIndex <= GLFW_GAMEPAD_BUTTON_LAST; btnIndex++) { + String jmeButtonIndex = remapButtonToJme(btnIndex); + if (jmeButtonIndex == null) continue; - if (byteBuffer != null) { - for (final JoystickButton button : entry.getValue().getButtons()) { - final boolean pressed = byteBuffer.get(button.getButtonId()) == GLFW_PRESS; + JoystickButton button = joystick.getButton(jmeButtonIndex); + if (button == null) continue; - if (joyButtonPressed.get(button) != pressed) { - joyButtonPressed.put(button, pressed); - listener.onJoyButtonEvent(new JoyButtonEvent(button, pressed)); - } + boolean pressed = buttons.get(btnIndex) == GLFW_PRESS; + updateButton(button, pressed); + } + + // D-pad to virtual POV axes + boolean dpadUp = buttons.get(GLFW_GAMEPAD_BUTTON_DPAD_UP) == GLFW_PRESS; + boolean dpadDown = buttons.get(GLFW_GAMEPAD_BUTTON_DPAD_DOWN) == GLFW_PRESS; + boolean dpadLeft = buttons.get(GLFW_GAMEPAD_BUTTON_DPAD_LEFT) == GLFW_PRESS; + boolean dpadRight = buttons.get(GLFW_GAMEPAD_BUTTON_DPAD_RIGHT) == GLFW_PRESS; + + float povX = dpadLeft ? -1f : (dpadRight ? 1f : 0f); + float povY = dpadDown ? -1f : (dpadUp ? 1f : 0f); + + JoystickAxis povXAxis = joystick.getPovXAxis(); + if (povXAxis != null) { + updateAxis(povXAxis, povX, povX); + } + + JoystickAxis povYAxis = joystick.getPovYAxis(); + if (povYAxis != null) { + updateAxis(povYAxis, povY, povY); } } } } + + + private String remapAxisToJme(int glfwAxisIndex) { + switch (glfwAxisIndex) { + case GLFW_GAMEPAD_AXIS_LEFT_X: + return JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X; + case GLFW_GAMEPAD_AXIS_LEFT_Y: + return JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y; + case GLFW_GAMEPAD_AXIS_RIGHT_X: + return JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X; + case GLFW_GAMEPAD_AXIS_RIGHT_Y: + return JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y; + case GLFW_GAMEPAD_AXIS_LEFT_TRIGGER: + return JoystickAxis.AXIS_XBOX_LEFT_TRIGGER; + case GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER: + return JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER; + default: + return null; + + } + } + + private String remapButtonToJme(int glfwButtonIndex) { + switch (glfwButtonIndex) { + case GLFW_GAMEPAD_BUTTON_Y: + return JoystickButton.BUTTON_XBOX_Y; + case GLFW_GAMEPAD_BUTTON_B: + return JoystickButton.BUTTON_XBOX_B; + case GLFW_GAMEPAD_BUTTON_A: + return JoystickButton.BUTTON_XBOX_A; + case GLFW_GAMEPAD_BUTTON_X: + return JoystickButton.BUTTON_XBOX_X; + case GLFW_GAMEPAD_BUTTON_LEFT_BUMPER: + return JoystickButton.BUTTON_XBOX_LB; + case GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER: + return JoystickButton.BUTTON_XBOX_RB; + case GLFW_GAMEPAD_BUTTON_BACK: + return JoystickButton.BUTTON_XBOX_BACK; + case GLFW_GAMEPAD_BUTTON_START: + return JoystickButton.BUTTON_XBOX_START; + case GLFW_GAMEPAD_BUTTON_LEFT_THUMB: + return JoystickButton.BUTTON_XBOX_L3; + case GLFW_GAMEPAD_BUTTON_RIGHT_THUMB: + return JoystickButton.BUTTON_XBOX_R3; + case GLFW_GAMEPAD_BUTTON_DPAD_UP: + return JoystickButton.BUTTON_XBOX_DPAD_UP; + case GLFW_GAMEPAD_BUTTON_DPAD_DOWN: + return JoystickButton.BUTTON_XBOX_DPAD_DOWN; + case GLFW_GAMEPAD_BUTTON_DPAD_LEFT: + return JoystickButton.BUTTON_XBOX_DPAD_LEFT; + case GLFW_GAMEPAD_BUTTON_DPAD_RIGHT: + return JoystickButton.BUTTON_XBOX_DPAD_RIGHT; + default: + return null; + + } + } + + private static float remapAxisValueToJme(int axisId, float v) { + if (axisId == GLFW_GAMEPAD_AXIS_LEFT_TRIGGER || axisId == GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER) { + if (v < -1f) v = -1f; + if (v > 1f) v = 1f; + return (v + 1f) * 0.5f; + } + return v; + } + + private void updateButton(JoystickButton button, boolean pressed) { + if (button == null) return; + Boolean old = joyButtonPressed.get(button); + if (old == null || old != pressed) { + joyButtonPressed.put(button, pressed); + listener.onJoyButtonEvent(new JoyButtonEvent(button, pressed)); + } + } + + private void updateAxis(JoystickAxis axis, float value, float rawValue) { + if (axis == null) return; + Float old = joyAxisValues.get(axis); + float jitter = FastMath.clamp(Math.max(axis.getJitterThreshold(), globalJitterThreshold), 0f, 1f); + if (old == null || FastMath.abs(old - value) > jitter) { + joyAxisValues.put(axis, value); + listener.onJoyAxisEvent(new JoyAxisEvent(axis, value, rawValue)); + } + } + @Override public void destroy() { initialized = false; @@ -195,41 +428,71 @@ public void setInputListener(final RawInputListener listener) { @Override public long getInputTimeNanos() { - return 0; + return (long) (glfwGetTime() * 1000000000); } - protected class GlfwJoystick extends AbstractJoystick { - + private static class GlfwJoystick extends AbstractJoystick { + private final boolean isGlfwGamepad; + private JoystickAxis xAxis; + private JoystickAxis yAxis; private JoystickAxis povAxisX; private JoystickAxis povAxisY; - public GlfwJoystick(final InputManager inputManager, final JoyInput joyInput, final int joyId, final String name) { + public GlfwJoystick(InputManager inputManager, JoyInput joyInput, int joyId, String name, + boolean gamepad) { super(inputManager, joyInput, joyId, name); + this.isGlfwGamepad = gamepad; } - public void addAxis(final int index, final JoystickAxis axis) { - super.addAxis(axis); + public boolean isGlfwGamepad() { + return isGlfwGamepad; + } - if (index == 0) { - povAxisX = axis; - } else if (index == 1) { - povAxisY = axis; + public void addAxis(int index, JoystickAxis axis) { + super.addAxis(axis); + if (isGlfwGamepad) { + switch (index) { + case GLFW_GAMEPAD_AXIS_LEFT_X: { + xAxis = axis; + break; + } + case GLFW_GAMEPAD_AXIS_LEFT_Y: { + yAxis = axis; + break; + } + case POV_X_AXIS_ID: { + povAxisX = axis; + break; + } + case POV_Y_AXIS_ID: { + povAxisY = axis; + break; + } + } + } else { + if (index == 0) { + xAxis = axis; + povAxisX = axis; + } else if (index == 1) { + yAxis = axis; + povAxisY = axis; + } } } @Override - protected void addButton(final JoystickButton button) { + protected void addButton(JoystickButton button) { super.addButton(button); } @Override public JoystickAxis getXAxis() { - return povAxisX; + return xAxis; } @Override public JoystickAxis getYAxis() { - return povAxisY; + return yAxis; } @Override @@ -244,12 +507,13 @@ public JoystickAxis getPovYAxis() { @Override public int getXAxisIndex() { - return povAxisX.getAxisId(); + return xAxis != null ? xAxis.getAxisId() : 0; } @Override public int getYAxisIndex() { - return povAxisY.getAxisId(); + return yAxis != null ? yAxis.getAxisId() : 1; } } + } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java index b8049a8f88..980b07527e 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -83,11 +83,50 @@ public GlfwKeyInput(final LwjglWindow context) { @Override public void initialize() { + if (!context.isRenderable()) { + return; + } + initCallbacks(); + + initialized = true; + logger.fine("Keyboard created."); + } + /** + * Re-initializes the key input context window specific callbacks + */ + public void resetContext() { if (!context.isRenderable()) { return; } + closeCallbacks(); + initCallbacks(); + } + + private void closeCallbacks() { + keyCallback.close(); + charCallback.close(); + } + + /** + * Determine the name of the specified key in the current system language. + * + * @param jmeKey the keycode from {@link com.jme3.input.KeyInput} + * @return the name of the key, or null if unknown + */ + @Override + public String getKeyName(int jmeKey) { + int glfwKey = GlfwKeyMap.fromJmeKeyCode(jmeKey); + if (glfwKey == GLFW_KEY_UNKNOWN) { + return null; + } + + String result = glfwGetKeyName(glfwKey, 0); + return result; + } + + private void initCallbacks() { glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() { @Override public void invoke(final long window, final int key, final int scancode, final int action, final int mods) { @@ -122,9 +161,6 @@ public void invoke(final long window, final int codepoint) { keyInputEvents.add(released); } }); - - initialized = true; - logger.fine("Keyboard created."); } public int getKeyCount() { @@ -149,8 +185,7 @@ public void destroy() { return; } - keyCallback.close(); - charCallback.close(); + closeCallbacks(); logger.fine("Keyboard destroyed."); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java index 0c130b9d4e..51baafbda2 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwKeyMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,6 +38,12 @@ public class GlfwKeyMap { private static final int[] GLFW_TO_JME_KEY_MAP = new int[GLFW_KEY_LAST + 1]; + /** + * A private constructor to inhibit instantiation of this class. + */ + private GlfwKeyMap() { + } + private static void reg(final int jmeKey, final int glfwKey) { GLFW_TO_JME_KEY_MAP[glfwKey] = jmeKey; } @@ -165,7 +171,26 @@ private static void reg(final int jmeKey, final int glfwKey) { reg(KEY_RMETA, GLFW_KEY_RIGHT_SUPER); } + /** + * Returns the jme keycode that matches the specified glfw keycode + * @param glfwKey the glfw keycode + */ public static int toJmeKeyCode(final int glfwKey) { return GLFW_TO_JME_KEY_MAP[glfwKey]; } + + + /** + * Returns the glfw keycode that matches the specified jme keycode or + * GLFW_KEY_UNKNOWN if there isn't any match. + * + * @param jmeKey the jme keycode + */ + public static int fromJmeKeyCode(final int jmeKey) { + for (int i = 0; i < GLFW_TO_JME_KEY_MAP.length; i++) { + if (GLFW_TO_JME_KEY_MAP[i] == jmeKey) return i; + } + return GLFW_KEY_UNKNOWN; + } + } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java index f198234a6c..1aba44d570 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/GlfwMouseInput.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,10 +36,11 @@ import com.jme3.input.RawInputListener; import com.jme3.input.event.MouseButtonEvent; import com.jme3.input.event.MouseMotionEvent; +import com.jme3.math.Vector2f; import com.jme3.system.lwjgl.LwjglWindow; +import com.jme3.system.lwjgl.WindowSizeListener; import com.jme3.util.BufferUtils; import java.nio.ByteBuffer; -import java.nio.DoubleBuffer; import java.nio.IntBuffer; import java.util.ArrayDeque; import java.util.HashMap; @@ -54,7 +55,7 @@ /** * Captures mouse input using GLFW callbacks. It then temporarily stores these * in event queues which are processed in the {@link #update()} method. Due to - * some of the GLFW button id's there is a conversion method in this class which + * some of the GLFW button IDs, there is a conversion method in this class which * will convert the GLFW left, middle and right mouse button to JME3 left, * middle and right button codes. * @@ -115,6 +116,7 @@ private static ByteBuffer transformCursorImage(final IntBuffer imageData, final private final LwjglWindow context; + private WindowSizeListener windowSizeListener; private RawInputListener listener; private IntBuffer currentCursorDelays; @@ -130,26 +132,25 @@ private static ByteBuffer transformCursorImage(final IntBuffer imageData, final private int mouseX; private int mouseY; private int mouseWheel; - private int currentWidth; private int currentHeight; private boolean cursorVisible; private boolean initialized; - + private final Vector2f inputScale = new Vector2f(); + public GlfwMouseInput(final LwjglWindow context) { this.context = context; this.cursorVisible = true; } private void onCursorPos(final long window, final double xpos, final double ypos) { + context.getWindowContentScale(inputScale); + int x = (int) Math.round(xpos * inputScale.x); + int y = (int) Math.round((currentHeight - ypos) * inputScale.y); + - int xDelta; - int yDelta; - int x = (int) Math.round(xpos); - int y = currentHeight - (int) Math.round(ypos); - - xDelta = x - mouseX; - yDelta = y - mouseY; + int xDelta = x - mouseX; + int yDelta = y - mouseY; mouseX = x; mouseY = y; @@ -176,7 +177,30 @@ private void onMouseButton(final long window, final int button, final int action @Override public void initialize() { + initCallbacks(); + + if (listener != null) { + sendFirstMouseEvent(); + } + + setCursorVisible(cursorVisible); + logger.fine("Mouse created."); + initialized = true; + } + + /** + * Re-initializes the mouse input context window specific callbacks + */ + public void resetContext() { + if (!context.isRenderable()) { + return; + } + + closeCallbacks(); + initCallbacks(); + } + private void initCallbacks() { final long window = context.getWindowHandle(); try (MemoryStack stack = MemoryStack.stackPush()) { @@ -186,7 +210,7 @@ public void initialize() { glfwGetWindowSize(window, width, height); - currentWidth = width.get(); + width.get(); currentHeight = height.get(); } @@ -212,29 +236,19 @@ public void invoke(final long window, final int button, final int action, final } }); - glfwSetWindowSizeCallback(window, new GLFWWindowSizeCallback() { - @Override - public void invoke(final long window, final int width, final int height) { - currentHeight = height; - currentWidth = width; - } - }); - - if(listener != null) { - sendFirstMouseEvent(); - } - - setCursorVisible(cursorVisible); - logger.fine("Mouse created."); - initialized = true; + context.registerWindowSizeListener(windowSizeListener = ((width, height) -> { + currentHeight = height; + })); } + private void initCurrentMousePosition(long window) { - DoubleBuffer x = BufferUtils.createDoubleBuffer(1); - DoubleBuffer y = BufferUtils.createDoubleBuffer(1); + double[] x = new double[1]; + double[] y = new double[1]; glfwGetCursorPos(window, x, y); - mouseX = (int) Math.round(x.get()); - mouseY = (int) currentHeight - (int) Math.round(y.get()); + context.getWindowContentScale(inputScale); + mouseX = (int) Math.round(x[0] * inputScale.x); + mouseY = (int) Math.round((currentHeight - y[0]) * inputScale.y); } /** @@ -295,9 +309,7 @@ public void destroy() { return; } - cursorPosCallback.close(); - scrollCallback.close(); - mouseButtonCallback.close(); + closeCallbacks(); currentCursor = null; currentCursorDelays = null; @@ -313,6 +325,13 @@ public void destroy() { logger.fine("Mouse destroyed."); } + private void closeCallbacks() { + cursorPosCallback.close(); + scrollCallback.close(); + mouseButtonCallback.close(); + context.removeWindowSizeListener(windowSizeListener); + } + @Override public void setCursorVisible(boolean visible) { cursorVisible = visible; diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlGameControllerDb.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlGameControllerDb.java new file mode 100644 index 0000000000..5f7c6020ea --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlGameControllerDb.java @@ -0,0 +1,39 @@ +package com.jme3.input.lwjgl; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import com.jme3.util.BufferUtils; +import com.jme3.util.res.Resources; + +public class SdlGameControllerDb { + private static final Logger LOGGER = Logger.getLogger(SdlGameControllerDb.class.getName()); + + public static ByteBuffer getGamecontrollerDb(String path) throws Exception { + try ( InputStream gamecontrollerdbIs = Resources.getResourceAsStream(path)) { + if(gamecontrollerdbIs == null) throw new Exception("Resource not found"); + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + byte data[] = new byte[4096]; + int read; + while ((read = gamecontrollerdbIs.read(data)) != -1) { + bos.write(data, 0, read); + } + data = bos.toByteArray(); + + ByteBuffer gamecontrollerdb = BufferUtils.createByteBuffer(data.length + 1); + gamecontrollerdb.put(data); + gamecontrollerdb.put((byte)0); // null-terminate + gamecontrollerdb.flip(); + LOGGER.log(Level.INFO, "Loaded gamecontrollerdb from {0}", path); + return gamecontrollerdb; + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unable to load "+path+" ", e); + throw e; + } + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlJoystickInput.java b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlJoystickInput.java new file mode 100644 index 0000000000..7e398d4096 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/input/lwjgl/SdlJoystickInput.java @@ -0,0 +1,621 @@ +package com.jme3.input.lwjgl; + +import com.jme3.input.*; +import com.jme3.input.event.JoyAxisEvent; +import com.jme3.input.event.JoyButtonEvent; +import com.jme3.math.FastMath; +import com.jme3.system.AppSettings; +import java.nio.ByteBuffer; +import java.nio.IntBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.lwjgl.sdl.*; +import org.lwjgl.system.MemoryStack; + +import static org.lwjgl.sdl.SDLInit.*; +import static org.lwjgl.sdl.SDLEvents.*; +import static org.lwjgl.sdl.SDLGamepad.*; +import static org.lwjgl.sdl.SDLJoystick.*; +import static org.lwjgl.sdl.SDLError.*; +import static org.lwjgl.sdl.SDLTimer.*; + +/** + * The SDL based implementation of {@link JoyInput}. + * + * @author Riccardo Balbo + */ +public class SdlJoystickInput implements JoyInput { + + private static final Logger LOGGER = Logger.getLogger(SdlJoystickInput.class.getName()); + private static final int POV_X_AXIS_ID = 7; + private static final int POV_Y_AXIS_ID = 8; + + private final AppSettings settings; + private final Map joysticks = new HashMap<>(); + private final Map joyButtonPressed = new HashMap<>(); + private final Map joyAxisValues = new HashMap<>(); + private final int flags = SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_EVENTS; + + private boolean initialized; + private float virtualTriggerThreshold; + private float globalJitterThreshold; + private boolean loadGamepads; + private boolean loadRaw; + + private RawInputListener listener; + + public SdlJoystickInput(AppSettings settings) { + this.settings = settings; + try { + String path = settings.getSDLGameControllerDBResourcePath(); + if (path != null && !path.trim().isEmpty()) { + ByteBuffer bbf = SdlGameControllerDb.getGamecontrollerDb(path); + if (SDL_AddGamepadMapping(bbf) == -1) { + throw new Exception("Failed to load"); + } + } + } catch (Exception e) { + LOGGER.log(Level.WARNING, "Unable to load gamecontrollerdb, fallback to sdl default mappings", e); + } + } + + @Override + public void initialize() { + if (!SDL_InitSubSystem(SDL_INIT_GAMEPAD | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_EVENTS)) { + String err = SDL_GetError(); + throw new IllegalStateException("SDL_InitSubSystem failed: " + err); + } + virtualTriggerThreshold = settings.getJoysticksTriggerToButtonThreshold(); + globalJitterThreshold = settings.getJoysticksAxisJitterThreshold(); + + String mapper = settings.getJoysticksMapper(); + switch (mapper) { + case AppSettings.JOYSTICKS_RAW_MAPPER: + loadGamepads = false; + loadRaw = true; + break; + case AppSettings.JOYSTICKS_XBOX_MAPPER: + loadGamepads = true; + loadRaw = false; + break; + default: + case AppSettings.JOYSTICKS_XBOX_WITH_FALLBACK_MAPPER: + loadGamepads = true; + loadRaw = true; + break; + } + + initialized = true; + } + + private void onDeviceConnected(int deviceIndex, boolean isGamepad) { + if (loadGamepads && !isGamepad && SDL_IsGamepad(deviceIndex)) { + // SDL will fire both GAMEPAD and JOYSTICK events for recognized + // gamepads, so here we check if the joystick is expected to be + // a gamepad, and skip it if so to avoid duplicates. + return; + } + + InputManager inputManager = (InputManager) listener; + + SdlJoystick joystick; + if (isGamepad) { + long gamepad = SDL_OpenGamepad(deviceIndex); + if (gamepad == 0L) { + LOGGER.log(Level.FINE, "SDL failed to open gamepad for id {0}: {1}", + new Object[] { deviceIndex, SDL_GetError() }); + return; + } + + String name = SDL_GetGamepadName(gamepad); + + joystick = new SdlJoystick(inputManager, this, deviceIndex, name, gamepad, 0L); + joysticks.put(deviceIndex, joystick); + + // Managed axes (standard layout) + for (int axisIndex = 0; axisIndex < SDL_GAMEPAD_AXIS_COUNT; axisIndex++) { + String logicalId = remapAxisToJme(axisIndex); + if (logicalId == null) continue; + + String axisName = getAxisLabel(joystick, axisIndex); + + JoystickAxis axis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, axisName, + logicalId, true, false, 0.0f); + joystick.addAxis(axisIndex, axis); + } + + // Managed buttons: map SDL gamepad buttons into your JME logical ids + for (int buttonIndex = 0; buttonIndex < SDL_GAMEPAD_BUTTON_COUNT; buttonIndex++) { + String logicalId = remapButtonToJme(buttonIndex); + if (logicalId == null) continue; + String buttonName = getButtonLabel(joystick, buttonIndex); + + JoystickButton button = new DefaultJoystickButton(inputManager, joystick, buttonIndex, + buttonName, logicalId); + joystick.addButton(button); + joyButtonPressed.put(button, false); + } + + } else { + long joy = SDL_OpenJoystick(deviceIndex); + if (joy == 0L) return; + + String name = SDL_GetJoystickName(joy); + joystick = new SdlJoystick(inputManager, this, deviceIndex, name, 0L, joy); + joysticks.put(deviceIndex, joystick); + + int numAxes = SDL_GetNumJoystickAxes(joy); + int numButtons = SDL_GetNumJoystickButtons(joy); + + for (int axisIndex = 0; axisIndex < numAxes; axisIndex++) { + String logicalId = String.valueOf(axisIndex); + String axisName = logicalId; + + JoystickAxis axis = new DefaultJoystickAxis(inputManager, joystick, axisIndex, axisName, + logicalId, true, false, 0.0f); + + joystick.addAxis(axisIndex, axis); + } + + for (int buttonIndex = 0; buttonIndex < numButtons; buttonIndex++) { + String logicalId = String.valueOf(buttonIndex); + String buttonName = logicalId; + JoystickButton button = new DefaultJoystickButton(inputManager, joystick, buttonIndex, + buttonName, logicalId); + joystick.addButton(button); + } + } + + // Virtual POV axes for D-pad. + JoystickAxis povX = new DefaultJoystickAxis(inputManager, joystick, POV_X_AXIS_ID, JoystickAxis.POV_X, + JoystickAxis.POV_X, true, false, 0.0f); + joystick.addAxis(POV_X_AXIS_ID, povX); + + JoystickAxis povY = new DefaultJoystickAxis(inputManager, joystick, POV_Y_AXIS_ID, JoystickAxis.POV_Y, + JoystickAxis.POV_Y, true, false, 0.0f); + joystick.addAxis(POV_Y_AXIS_ID, povY); + + ((InputManager) listener).fireJoystickConnectedEvent(joystick); + + } + + private void destroyJoystick(SdlJoystick joystick) { + if (joystick.isGamepad()) { + if (joystick.gamepad != 0L) { + SDL_CloseGamepad(joystick.gamepad); + } + } else { + if (joystick.joystick != 0L) { + SDL_CloseJoystick(joystick.joystick); + } + } + } + + private void onDeviceDisconnected(int deviceIndex) { + SdlJoystick joystick = joysticks.get(deviceIndex); + if (joystick == null) return; + + // clear all states associated with this joystick + joyButtonPressed.entrySet().removeIf(e -> e.getKey().getJoystick() == joystick); + joyAxisValues.entrySet().removeIf(e -> e.getKey().getJoystick() == joystick); + joysticks.remove(deviceIndex); + + // free resources + destroyJoystick(joystick); + + ((InputManager) listener).fireJoystickDisconnectedEvent(joystick); + } + + @Override + public Joystick[] loadJoysticks(InputManager inputManager) { + + for (SdlJoystick js : joysticks.values()) destroyJoystick(js); + joysticks.clear(); + + joyButtonPressed.clear(); + joyAxisValues.clear(); + + if (loadGamepads) { + // load managed gamepads + IntBuffer gamepads = SDL_GetGamepads(); + if (gamepads != null) { + while (gamepads.hasRemaining()) { + int deviceId = gamepads.get(); + onDeviceConnected(deviceId, true); + } + } + } + + if (loadRaw) { + // load raw gamepads + IntBuffer joys = SDL_GetJoysticks(); + if (joys != null) { + while (joys.hasRemaining()) { + int deviceId = joys.get(); + onDeviceConnected(deviceId, false); + } + } + } + + return joysticks.values().toArray(new Joystick[0]); + } + + @Override + public void update() { + handleConnectionEvents(); + handleInputEvents(); + } + + private void handleInputEvents() { + float rawValue, value; + for (SdlJoystick js : joysticks.values()) { + if (js.isGamepad()) { + long gp = js.gamepad; + + // for(int axisIndex=0; axisIndex 0f) { + if (jmeAxisId == JoystickAxis.AXIS_XBOX_LEFT_TRIGGER) { + updateButton(js.getButton(JoystickButton.BUTTON_XBOX_LT), + value > virtualTriggerThreshold); + } else if (jmeAxisId == JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER) { + updateButton(js.getButton(JoystickButton.BUTTON_XBOX_RT), + value > virtualTriggerThreshold); + } + } + + // Dpad -> virtual POV axes + float povXValue = 0f; + float povYValue = 0f; + + // button handling + // for (int b = 0; b <= SDL_GAMEPAD_BUTTON_COUNT; b++) { + for (JoystickButton button : js.getButtons()) { + int b = button.getButtonId(); + String jmeButtonId = button.getLogicalId(); + + boolean pressed = SDL_GetGamepadButton(gp, b); + updateButton(button, pressed); + + // Dpad -> virtual POV axes + if (jmeButtonId == JoystickButton.BUTTON_XBOX_DPAD_UP) { + povYValue += pressed ? 1f : 0f; + } else if (jmeButtonId == JoystickButton.BUTTON_XBOX_DPAD_DOWN) { + povYValue += pressed ? -1f : 0f; + } else if (jmeButtonId == JoystickButton.BUTTON_XBOX_DPAD_LEFT) { + povXValue += pressed ? -1f : 0f; + } else if (jmeButtonId == JoystickButton.BUTTON_XBOX_DPAD_RIGHT) { + povXValue += pressed ? 1f : 0f; + } + } + + JoystickAxis povXAxis = js.getPovXAxis(); + if (povXAxis != null) { + updateAxis(povXAxis, povXValue, povXValue); + } + + JoystickAxis povYAxis = js.getPovYAxis(); + if (povYAxis != null) { + updateAxis(povYAxis, povYValue, povYValue); + } + } + } else { + long joy = js.joystick; + + for (JoystickAxis axis : js.getAxes()) { + short v = SDL_GetJoystickAxis(joy, axis.getAxisId()); + rawValue = v; + value = v; + updateAxis(axis, value, rawValue); + } + + for (JoystickButton button : js.getButtons()) { + boolean pressed = SDL_GetJoystickButton(joy, button.getButtonId()); + updateButton(button, pressed); + } + } + } + } + + private void handleConnectionEvents() { + try (MemoryStack stack = MemoryStack.stackPush()) { + SDL_Event evt = SDL_Event.malloc(stack); + while (SDL_PollEvent(evt)) { + int type = evt.type(); + if (type == SDL_EVENT_GAMEPAD_ADDED) { + if (loadGamepads) { + int which = evt.gdevice().which(); + onDeviceConnected(which, true); + } + } else if (type == SDL_EVENT_GAMEPAD_REMOVED) { + int which = evt.gdevice().which(); + onDeviceDisconnected(which); + } else if (type == SDL_EVENT_JOYSTICK_ADDED) { + if (loadRaw) { + int which = evt.jdevice().which(); + onDeviceConnected(which, false); + } + } else if (type == SDL_EVENT_JOYSTICK_REMOVED) { + int which = evt.jdevice().which(); + onDeviceDisconnected(which); + } + } + } + } + + @Override + public void setJoyRumble(int joyId, float amount) { + setJoyRumble(joyId, amount, amount, 100f / 1000f); + } + + public void setJoyRumble(int joyId, float highFrequency, float lowFrequency, float duration) { + SdlJoystick js = joysticks.get(joyId); + if (js == null) return; + + highFrequency = FastMath.clamp(highFrequency, 0f, 1f); + lowFrequency = FastMath.clamp(lowFrequency, 0f, 1f); + + if (js.isGamepad() && js.gamepad != 0L) { + int ampHigh = (int) (highFrequency * 0xFFFF); + int ampLow = (int) (lowFrequency * 0xFFFF); + int durationMs = (int) (duration * 1000f); + SDL_RumbleGamepad(js.gamepad, (short) ampHigh, (short) ampLow, durationMs); + } + } + + private String getButtonLabel(SdlJoystick gamepad, int sdlButtonIndex) { + int label = SDL_GetGamepadButtonLabel(gamepad.gamepad, sdlButtonIndex); + switch (label) { + case SDL_GAMEPAD_BUTTON_LABEL_A: + return "A"; + case SDL_GAMEPAD_BUTTON_LABEL_B: + return "B"; + case SDL_GAMEPAD_BUTTON_LABEL_X: + return "X"; + case SDL_GAMEPAD_BUTTON_LABEL_Y: + return "Y"; + + case SDL_GAMEPAD_BUTTON_LABEL_CROSS: + return "CROSS"; + case SDL_GAMEPAD_BUTTON_LABEL_CIRCLE: + return "CIRCLE"; + case SDL_GAMEPAD_BUTTON_LABEL_SQUARE: + return "SQUARE"; + case SDL_GAMEPAD_BUTTON_LABEL_TRIANGLE: + return "TRIANGLE"; + + case SDL_GAMEPAD_BUTTON_LABEL_UNKNOWN: + default: + return "" + sdlButtonIndex; + } + } + + private String getAxisLabel(SdlJoystick gamepad, int sdlAxisIndex) { + switch (sdlAxisIndex) { + case SDL_GAMEPAD_AXIS_LEFTX: + return "LEFT THUMB STICK (X)"; + case SDL_GAMEPAD_AXIS_LEFTY: + return "LEFT THUMB STICK (Y)"; + case SDL_GAMEPAD_AXIS_RIGHTX: + return "RIGHT THUMB STICK (X)"; + case SDL_GAMEPAD_AXIS_RIGHTY: + return "RIGHT THUMB STICK (Y)"; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return "LEFT TRIGGER"; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return "RIGHT TRIGGER"; + default: + return "" + sdlAxisIndex; + } + } + + private String remapAxisToJme(int sdlAxisIndex) { + switch (sdlAxisIndex) { + case SDL_GAMEPAD_AXIS_LEFTX: + return JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_X; + case SDL_GAMEPAD_AXIS_LEFTY: + return JoystickAxis.AXIS_XBOX_LEFT_THUMB_STICK_Y; + case SDL_GAMEPAD_AXIS_RIGHTX: + return JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_X; + case SDL_GAMEPAD_AXIS_RIGHTY: + return JoystickAxis.AXIS_XBOX_RIGHT_THUMB_STICK_Y; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return JoystickAxis.AXIS_XBOX_LEFT_TRIGGER; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return JoystickAxis.AXIS_XBOX_RIGHT_TRIGGER; + default: + return null; + } + } + + private String remapButtonToJme(int sdlButtonIndex) { + switch (sdlButtonIndex) { + case SDL_GAMEPAD_BUTTON_NORTH: + return JoystickButton.BUTTON_XBOX_Y; + case SDL_GAMEPAD_BUTTON_EAST: + return JoystickButton.BUTTON_XBOX_B; + case SDL_GAMEPAD_BUTTON_SOUTH: + return JoystickButton.BUTTON_XBOX_A; + case SDL_GAMEPAD_BUTTON_WEST: + return JoystickButton.BUTTON_XBOX_X; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return JoystickButton.BUTTON_XBOX_LB; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return JoystickButton.BUTTON_XBOX_RB; + case SDL_GAMEPAD_BUTTON_BACK: + return JoystickButton.BUTTON_XBOX_BACK; + case SDL_GAMEPAD_BUTTON_START: + return JoystickButton.BUTTON_XBOX_START; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return JoystickButton.BUTTON_XBOX_L3; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return JoystickButton.BUTTON_XBOX_R3; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return JoystickButton.BUTTON_XBOX_DPAD_UP; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return JoystickButton.BUTTON_XBOX_DPAD_DOWN; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return JoystickButton.BUTTON_XBOX_DPAD_LEFT; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return JoystickButton.BUTTON_XBOX_DPAD_RIGHT; + default: + return null; + } + } + + private float remapAxisValueToJme(int axisId, short v) { + if (axisId == SDL_GAMEPAD_AXIS_LEFT_TRIGGER || axisId == SDL_GAMEPAD_AXIS_RIGHT_TRIGGER) { + // [0..32767] -> [0..1] + if (v <= 0) return 0f; + return Math.min(1f, v / 32767f); + } else { + // [-32768..32767] -> [-1..1] + if (v == Short.MIN_VALUE) return -1f; + return v / 32767f; + } + } + + private void updateButton(JoystickButton button, boolean pressed) { + if (button == null) return; + Boolean old = joyButtonPressed.get(button); + if (old == null || old != pressed) { + joyButtonPressed.put(button, pressed); + listener.onJoyButtonEvent(new JoyButtonEvent(button, pressed)); + } + } + + private void updateAxis(JoystickAxis axis, float value, float rawValue) { + if (axis == null) return; + Float old = joyAxisValues.get(axis); + float jitter = FastMath.clamp(Math.max(axis.getJitterThreshold(), globalJitterThreshold), 0f, 1f); + if (old == null || FastMath.abs(old - value) > jitter) { + joyAxisValues.put(axis, value); + listener.onJoyAxisEvent(new JoyAxisEvent(axis, value, rawValue)); + } + } + + @Override + public void destroy() { + // Close devices + for (SdlJoystick js : joysticks.values()) { + if (js.gamepad != 0L) SDL_CloseGamepad(js.gamepad); + if (js.joystick != 0L) SDL_CloseJoystick(js.joystick); + } + joysticks.clear(); + + // Quit subsystems + SDL_QuitSubSystem(flags); + + initialized = false; + } + + @Override + public boolean isInitialized() { + return initialized; + } + + @Override + public void setInputListener(final RawInputListener listener) { + this.listener = listener; + } + + @Override + public long getInputTimeNanos() { + return SDL_GetTicksNS(); + } + + private static class SdlJoystick extends AbstractJoystick { + + private JoystickAxis xAxis; + private JoystickAxis yAxis; + private JoystickAxis povAxisX; + private JoystickAxis povAxisY; + + long gamepad; + long joystick; + + SdlJoystick(InputManager inputManager, JoyInput joyInput, int joyId, String name, long gamepad, + long joystick) { + super(inputManager, joyInput, joyId, name); + this.gamepad = gamepad; + this.joystick = joystick; + + } + + boolean isGamepad() { + return gamepad != 0L; + } + + void addAxis(int index, JoystickAxis axis) { + super.addAxis(axis); + switch (index) { + case SDL_GAMEPAD_AXIS_LEFTX: { + xAxis = axis; + break; + } + case SDL_GAMEPAD_AXIS_LEFTY: { + yAxis = axis; + break; + } + case POV_X_AXIS_ID: { + povAxisX = axis; + break; + } + case POV_Y_AXIS_ID: { + povAxisY = axis; + break; + } + } + + } + + @Override + public JoystickAxis getXAxis() { + return xAxis; + } + + @Override + public JoystickAxis getYAxis() { + return yAxis; + } + + @Override + public JoystickAxis getPovXAxis() { + return povAxisX; + } + + @Override + public JoystickAxis getPovYAxis() { + return povAxisY; + } + + @Override + public int getXAxisIndex() { + return xAxis != null ? xAxis.getAxisId() : 0; + } + + @Override + public int getYAxisIndex() { + return yAxis != null ? yAxis.getAxisId() : 1; + } + + @Override + public void addButton(JoystickButton button) { + super.addButton(button); + } + + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/lwjgl3/utils/package-info.java b/jme3-lwjgl3/src/main/java/com/jme3/lwjgl3/utils/package-info.java new file mode 100644 index 0000000000..0f15c202e2 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/lwjgl3/utils/package-info.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * miscellaneous code for interfacing with v3 of the Lightweight Java Game + * Library (LWJGL) + */ +package com.jme3.lwjgl3.utils; diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java index 99b0610990..8fef7ad1f1 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglContext.java @@ -174,7 +174,7 @@ public Buffer bindVertexBuffer(VertexBuffer vb, MemoryAccess access) { } @Override - public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int miplevel, MemoryAccess access) { + public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, int mipLevel, MemoryAccess access) { Utils.assertSharingPossible(); int imageID = image.getId(); if (imageID == -1) { @@ -183,7 +183,7 @@ public Image bindImage(com.jme3.texture.Image image, Texture.Type textureType, i long memFlags = Utils.getMemoryAccessFlags(access); int textureTarget = convertTextureType(textureType); Utils.errorBuffer.rewind(); - long mem = CL12GL.clCreateFromGLTexture(context, memFlags, textureTarget, miplevel, imageID, Utils.errorBuffer); + long mem = CL12GL.clCreateFromGLTexture(context, memFlags, textureTarget, mipLevel, imageID, Utils.errorBuffer); Utils.checkError(Utils.errorBuffer, "clCreateFromGLTexture"); return new LwjglImage(mem); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglDevice.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglDevice.java index a6acd255fc..0d07be6837 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglDevice.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglDevice.java @@ -61,15 +61,19 @@ public long getDevice() { public LwjglPlatform getPlatform() { return platform; } - + @Override public DeviceType getDeviceType() { - int type = Info.clGetDeviceInfoInt(device, CL10.CL_DEVICE_TYPE); - switch (type) { - case CL10.CL_DEVICE_TYPE_ACCELERATOR: return DeviceType.ACCELEARTOR; - case CL10.CL_DEVICE_TYPE_CPU: return DeviceType.CPU; - case CL10.CL_DEVICE_TYPE_GPU: return DeviceType.GPU; - default: return DeviceType.DEFAULT; + long type = Info.clGetDeviceInfoInt(device, CL10.CL_DEVICE_TYPE); + + if ((type & CL10.CL_DEVICE_TYPE_GPU) != 0) { + return DeviceType.GPU; + } else if ((type & CL10.CL_DEVICE_TYPE_CPU) != 0) { + return DeviceType.CPU; + } else if ((type & CL10.CL_DEVICE_TYPE_ACCELERATOR) != 0) { + return DeviceType.ACCELEARTOR; + } else { + return DeviceType.DEFAULT; } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java index 96c471113a..46d0ae61f4 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglEvent.java @@ -56,14 +56,14 @@ public long getEvent() { @Override public void waitForFinished() { CL10.clWaitForEvents(event); - release(); //short cut to save resources + release(); // shortcut to save resources } @Override public boolean isCompleted() { int status = Info.clGetEventInfoInt(event, CL10.CL_EVENT_COMMAND_EXECUTION_STATUS); if (status == CL10.CL_SUCCESS) { - release(); //short cut to save resources + release(); // shortcut to save resources return true; } else if (status < 0) { Utils.checkError(status, "EventStatus"); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java index f5f97f1bdf..ff3f55f3ad 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglKernel.java @@ -188,8 +188,8 @@ public Event Run(CommandQueue queue) { } long q = ((LwjglCommandQueue) queue).getQueue(); int ret = CL10.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], - p2, null, Utils.pointerBuffers[0]); + globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], + p2, null, Utils.pointerBuffers[0]); Utils.checkError(ret, "clEnqueueNDRangeKernel"); return new LwjglEvent(Utils.pointerBuffers[0].get(0)); } @@ -206,8 +206,8 @@ public void RunNoEvent(CommandQueue queue) { } long q = ((LwjglCommandQueue) queue).getQueue(); int ret = CL10.clEnqueueNDRangeKernel(q, kernel, - globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], - p2, null, null); + globalWorkSize.getDimension(), null, Utils.pointerBuffers[1], + p2, null, null); Utils.checkError(ret, "clEnqueueNDRangeKernel"); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java index 7df32c44c5..6e9a7dd637 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/LwjglPlatform.java @@ -75,14 +75,12 @@ public List getDevices() { * * Copied from the old release. * - * @param device_type the device type - * @param filter the device filter - * + * @param deviceType the device type * @return the available devices */ - private long[] getDevices(int device_type) { + private long[] getDevices(long deviceType) { int[] count = new int[1]; - int errcode = CL10.clGetDeviceIDs(platform, device_type, null, count); + int errcode = CL10.clGetDeviceIDs(platform, deviceType, null, count); if (errcode == CL10.CL_DEVICE_NOT_FOUND) { return new long[0]; } @@ -95,7 +93,7 @@ private long[] getDevices(int device_type) { PointerBuffer devices = PointerBuffer.allocateDirect(num_devices); - errcode = CL10.clGetDeviceIDs(platform, device_type,devices, (IntBuffer) null); + errcode = CL10.clGetDeviceIDs(platform, deviceType, devices, (IntBuffer) null); Utils.checkError(errcode, "clGetDeviceIDs"); long[] deviceIDs = new long[num_devices]; @@ -162,9 +160,9 @@ public Collection getExtensions() { return Arrays.asList(Info.clGetPlatformInfoStringASCII(platform, CL10.CL_PLATFORM_EXTENSIONS).split(" ")); } - @Override - public String toString() { - return getName(); - } + @Override + public String toString() { + return getName(); + } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/Utils.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/Utils.java index 633ec2ac90..e952ac62b1 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/Utils.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -108,11 +108,11 @@ public static void checkError(IntBuffer errorBuffer, String callName) { } public static void checkError(int error, String callName) { if (error != CL10.CL_SUCCESS) { - String errname = getErrorName(error); - if (errname == null) { - errname = "UNKNOWN"; + String errorName = getErrorName(error); + if (errorName == null) { + errorName = "UNKNOWN"; } - throw new OpenCLException("OpenCL error in " + callName + ": " + errname + " (0x" + Integer.toHexString(error) + ")", error); + throw new OpenCLException("OpenCL error in " + callName + ": " + errorName + " (0x" + Integer.toHexString(error) + ")", error); } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/CLUtil.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/CLUtil.java index c63339aef5..e3cfaef323 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/CLUtil.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/CLUtil.java @@ -60,12 +60,12 @@ private CLUtil() { * specified {@code errcode_ret} buffer and throws an * {@link OpenCLException} if it's not equal to {@link CL10#CL_SUCCESS}. * - * @param errcode_ret the {@code errcode} buffer + * @param errorCodeBuffer the {@code errcode} buffer * * @throws OpenCLException */ - public static void checkCLError(ByteBuffer errcode_ret) { - checkCLError(errcode_ret.getInt(errcode_ret.position())); + public static void checkCLError(ByteBuffer errorCodeBuffer) { + checkCLError(errorCodeBuffer.getInt(errorCodeBuffer.position())); } /** @@ -73,12 +73,12 @@ public static void checkCLError(ByteBuffer errcode_ret) { * specified {@code errcode_ret} buffer and throws an * {@link OpenCLException} if it's not equal to {@link CL10#CL_SUCCESS}. * - * @param errcode_ret the {@code errcode} buffer + * @param errorCodeBuffer the {@code errcode} buffer * * @throws OpenCLException */ - public static void checkCLError(IntBuffer errcode_ret) { - checkCLError(errcode_ret.get(errcode_ret.position())); + public static void checkCLError(IntBuffer errorCodeBuffer) { + checkCLError(errorCodeBuffer.get(errorCodeBuffer.position())); } /** diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/Info.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/Info.java index 17e22461c6..cf1f19738b 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/Info.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/Info.java @@ -523,45 +523,45 @@ protected int get(long pointer, int arg, int param_name, long param_value_size, * Single int value version of: * {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static int clGetKernelArgInfoInt(long kernel, int arg_indx, int param_name) { - return KERNEL_ARG.getInt(kernel, arg_indx, param_name); + public static int clGetKernelArgInfoInt(long kernel, int argIndex, int param_name) { + return KERNEL_ARG.getInt(kernel, argIndex, param_name); } /** * Single long value version of: * {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static long clGetKernelArgInfoLong(long kernel, int arg_indx, int param_name) { - return KERNEL_ARG.getLong(kernel, arg_indx, param_name); + public static long clGetKernelArgInfoLong(long kernel, int argIndex, int param_name) { + return KERNEL_ARG.getLong(kernel, argIndex, param_name); } /** * String version of: {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static String clGetKernelArgInfoStringASCII(long kernel, int arg_indx, int param_name) { - return KERNEL_ARG.getStringASCII(kernel, arg_indx, param_name); + public static String clGetKernelArgInfoStringASCII(long kernel, int argIndex, int param_name) { + return KERNEL_ARG.getStringASCII(kernel, argIndex, param_name); } /** * String with explicit length version of: {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static String clGetKernelArgInfoStringASCII(long kernel, int arg_indx, int param_name, int param_value_size) { - return KERNEL_ARG.getStringASCII(kernel, arg_indx, param_name, param_value_size); + public static String clGetKernelArgInfoStringASCII(long kernel, int argIndex, int param_name, int param_value_size) { + return KERNEL_ARG.getStringASCII(kernel, argIndex, param_name, param_value_size); } /** * UTF-8 string version of: {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static String clGetKernelArgInfoStringUTF8(long kernel, int arg_indx, int param_name) { - return KERNEL_ARG.getStringUTF8(kernel, arg_indx, param_name); + public static String clGetKernelArgInfoStringUTF8(long kernel, int argIndex, int param_name) { + return KERNEL_ARG.getStringUTF8(kernel, argIndex, param_name); } /** * UTF-8 string with explicit length version of: * {@link org.lwjgl.opencl.CL12#clGetKernelArgInfo GetKernelArgInfo} */ - public static String clGetKernelArgInfoStringUTF8(long kernel, int arg_indx, int param_name, int param_value_size) { - return KERNEL_ARG.getStringUTF8(kernel, arg_indx, param_name, param_value_size); + public static String clGetKernelArgInfoStringUTF8(long kernel, int argIndex, int param_name, int param_value_size) { + return KERNEL_ARG.getStringUTF8(kernel, argIndex, param_name, param_value_size); } // ------------------------------------ diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQuery.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQuery.java index 53411b3851..fd269093c5 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQuery.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQuery.java @@ -15,7 +15,7 @@ /** * Base class for OpenCL object information queries. - *

                  + *

                  * All methods require the object being queried (a pointer value) and the * integer parameter name. * @@ -23,38 +23,38 @@ */ abstract class InfoQuery { - protected abstract int get(long pointer, int param_name, long param_value_size, long param_value, long param_value_size_ret); + protected abstract int get(long pointer, int parameterName, long parameterValueSize, long parameterValue, long parameterValueSizeRet); InfoQuery() { } /** - * Returns the integer value for the specified {@code param_name}, converted + * Returns the integer value for the specified {@code parameterName}, converted * to a boolean. * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's boolean value */ - boolean getBoolean(long object, int param_name) { - return getInt(object, param_name) != 0; + boolean getBoolean(long object, int parameterName) { + return getInt(object, parameterName) != 0; } /** - * Returns the integer value for the specified {@code param_name}. - *

                  + * Returns the integer value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's int value */ - int getInt(long object, int param_name) { + int getInt(long object, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, 4L, __buffer.address(), NULL); + int errcode = get(object, parameterName, 4L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -62,19 +62,19 @@ int getInt(long object, int param_name) { } /** - * Returns the long value for the specified {@code param_name}. - *

                  + * Returns the long value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's long value */ - long getLong(long object, int param_name) { + long getLong(long object, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, 8L, __buffer.address(), NULL); + int errcode = get(object, parameterName, 8L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -82,19 +82,19 @@ long getLong(long object, int param_name) { } /** - * Returns the pointer value for the specified {@code param_name}. - *

                  + * Returns the pointer value for the specified {@code parameterName}. + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's pointer value */ - long getPointer(long object, int param_name) { + long getPointer(long object, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, POINTER_SIZE, __buffer.address(), NULL); + int errcode = get(object, parameterName, POINTER_SIZE, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -102,21 +102,21 @@ long getPointer(long object, int param_name) { } /** - * Writes the pointer list for the specified {@code param_name} into + * Writes the pointer list for the specified {@code parameterName} into * {@code target}. - *

                  + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * @param target the buffer in which to put the returned pointer list * * @return how many pointers were actually returned */ - int getPointers(long object, int param_name, PointerBuffer target) { + int getPointers(long object, int parameterName, PointerBuffer target) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); + int errcode = get(object, parameterName, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); if (DEBUG) { checkCLError(errcode); } @@ -124,87 +124,87 @@ int getPointers(long object, int param_name, PointerBuffer target) { } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded. * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringASCII(long object, int param_name) { + String getStringASCII(long object, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, param_name, __buffer); + int bytes = getString(object, parameterName, __buffer); return __buffer.stringValueASCII(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringASCII(long object, int param_name, int param_value_size) { + String getStringASCII(long object, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueASCII(0, param_value_size); + return __buffer.stringValueASCII(0, parameterValueSize); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded. * * @param object the object to query - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringUTF8(long object, int param_name) { + String getStringUTF8(long object, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, param_name, __buffer); + int bytes = getString(object, parameterName, __buffer); return __buffer.stringValueUTF8(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringUTF8(long object, int param_name, int param_value_size) { + String getStringUTF8(long object, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueUTF8(0, param_value_size); + return __buffer.stringValueUTF8(0, parameterValueSize); } - private int getString(long object, int param_name, APIBuffer __buffer) { + private int getString(long object, int parameterName, APIBuffer buffer) { // Get string length - int errcode = get(object, param_name, 0, NULL, __buffer.address()); + int errcode = get(object, parameterName, 0, NULL, buffer.address()); if (DEBUG) { checkCLError(errcode); } - int bytes = (int) __buffer.pointerValue(0); - __buffer.bufferParam(bytes); + int bytes = (int) buffer.pointerValue(0); + buffer.bufferParam(bytes); // Get string - errcode = get(object, param_name, bytes, __buffer.address(), NULL); + errcode = get(object, parameterName, bytes, buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryInt.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryInt.java index b900887d76..6ee91ffefb 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryInt.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryInt.java @@ -15,7 +15,7 @@ /** * Base class for OpenCL object information queries. - *

                  + *

                  * All methods require the object being queried (a pointer value), a second * integer argument and the integer parameter name. * @@ -23,40 +23,40 @@ */ abstract class InfoQueryInt { - protected abstract int get(long pointer, int arg, int param_name, long param_value_size, long param_value, long param_value_size_ret); + protected abstract int get(long pointer, int arg, int parameterName, long parameterValueSize, long parameterValue, long parameterValueSizeRet); InfoQueryInt() { } /** - * Returns the integer value for the specified {@code param_name}, converted + * Returns the integer value for the specified {@code parameterName}, converted * to a boolean. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's boolean value */ - boolean getBoolean(long object, int arg, int param_name) { - return getInt(object, arg, param_name) != 0; + boolean getBoolean(long object, int arg, int parameterName) { + return getInt(object, arg, parameterName) != 0; } /** - * Returns the integer value for the specified {@code param_name}. - *

                  + * Returns the integer value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's int value */ - int getInt(long object, int arg, int param_name) { + int getInt(long object, int arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, 4L, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, 4L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -64,20 +64,20 @@ int getInt(long object, int arg, int param_name) { } /** - * Returns the long value for the specified {@code param_name}. - *

                  + * Returns the long value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's long value */ - long getLong(long object, int arg, int param_name) { + long getLong(long object, int arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, 8L, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, 8L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -85,20 +85,20 @@ long getLong(long object, int arg, int param_name) { } /** - * Returns the pointer value for the specified {@code param_name}. - *

                  + * Returns the pointer value for the specified {@code parameterName}. + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's pointer value */ - long getPointer(long object, int arg, int param_name) { + long getPointer(long object, int arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, POINTER_SIZE, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, POINTER_SIZE, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -106,22 +106,22 @@ long getPointer(long object, int arg, int param_name) { } /** - * Writes the pointer list for the specified {@code param_name} into + * Writes the pointer list for the specified {@code parameterName} into * {@code target}. - *

                  + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * @param target the buffer in which to put the returned pointer list * * @return how many pointers were actually returned */ - int getPointers(long object, int arg, int param_name, PointerBuffer target) { + int getPointers(long object, int arg, int parameterName, PointerBuffer target) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); + int errcode = get(object, arg, parameterName, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); if (DEBUG) { checkCLError(errcode); } @@ -129,91 +129,91 @@ int getPointers(long object, int arg, int param_name, PointerBuffer target) { } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringASCII(long object, int arg, int param_name) { + String getStringASCII(long object, int arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, arg, param_name, __buffer); + int bytes = getString(object, arg, parameterName, __buffer); return __buffer.stringValueASCII(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringASCII(long object, int arg, int param_name, int param_value_size) { + String getStringASCII(long object, int arg, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueASCII(0, param_value_size); + return __buffer.stringValueASCII(0, parameterValueSize); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringUTF8(long object, int arg, int param_name) { + String getStringUTF8(long object, int arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, arg, param_name, __buffer); + int bytes = getString(object, arg, parameterName, __buffer); return __buffer.stringValueUTF8(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query * @param arg an integer argument - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringUTF8(long object, int arg, int param_name, int param_value_size) { + String getStringUTF8(long object, int arg, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueUTF8(0, param_value_size); + return __buffer.stringValueUTF8(0, parameterValueSize); } - private int getString(long object, int arg, int param_name, APIBuffer __buffer) { + private int getString(long object, int arg, int parameterName, APIBuffer buffer) { // Get string length - int errcode = get(object, arg, param_name, 0, NULL, __buffer.address()); + int errcode = get(object, arg, parameterName, 0, NULL, buffer.address()); if (DEBUG) { checkCLError(errcode); } - int bytes = (int) __buffer.pointerValue(0); - __buffer.bufferParam(bytes + POINTER_SIZE); + int bytes = (int) buffer.pointerValue(0); + buffer.bufferParam(bytes + POINTER_SIZE); // Get string - errcode = get(object, arg, param_name, bytes, __buffer.address(), NULL); + errcode = get(object, arg, parameterName, bytes, buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryObject.java b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryObject.java index da9dc846f9..48ce3398a4 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryObject.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/opencl/lwjgl/info/InfoQueryObject.java @@ -15,7 +15,7 @@ /** * Base class for OpenCL object information queries. - *

                  + *

                  * All methods require the object being queried (a pointer value), a second * object argument (another pointer value) and the integer parameter name. * @@ -23,40 +23,41 @@ */ abstract class InfoQueryObject { - protected abstract int get(long pointer, long arg, int param_name, long param_value_size, long param_value, long param_value_size_ret); + protected abstract int get(long pointer, long arg, int parameterName, + long parameterValueSize, long parameterValue, long parameterValueSizeRet); InfoQueryObject() { } /** - * Returns the integer value for the specified {@code param_name}, converted + * Returns the integer value for the specified {@code parameterName}, converted * to a boolean. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's boolean value */ - boolean getBoolean(long object, long arg, int param_name) { - return getInt(object, arg, param_name) != 0; + boolean getBoolean(long object, long arg, int parameterName) { + return getInt(object, arg, parameterName) != 0; } /** - * Returns the integer value for the specified {@code param_name}. - *

                  + * Returns the integer value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's int value */ - int getInt(long object, long arg, int param_name) { + int getInt(long object, long arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, 4L, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, 4L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -64,20 +65,20 @@ int getInt(long object, long arg, int param_name) { } /** - * Returns the long value for the specified {@code param_name}. - *

                  + * Returns the long value for the specified {@code parameterName}. + *

                  * For integer parameters that may be 32 or 64 bits (e.g. {@code size_t}), * {@link #getPointer} should be used instead. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's long value */ - long getLong(long object, long arg, int param_name) { + long getLong(long object, long arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, 8L, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, 8L, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -85,20 +86,20 @@ long getLong(long object, long arg, int param_name) { } /** - * Returns the pointer value for the specified {@code param_name}. - *

                  + * Returns the pointer value for the specified {@code parameterName}. + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's pointer value */ - long getPointer(long object, long arg, int param_name) { + long getPointer(long object, long arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, POINTER_SIZE, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, POINTER_SIZE, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } @@ -106,22 +107,22 @@ long getPointer(long object, long arg, int param_name) { } /** - * Writes the pointer list for the specified {@code param_name} into + * Writes the pointer list for the specified {@code parameterName} into * {@code target}. - *

                  + *

                  * This method should also be used for integer parameters that may be 32 or * 64 bits (e.g. {@code size_t}). * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * @param target the buffer in which to put the returned pointer list * * @return how many pointers were actually returned */ - int getPointers(long object, long arg, int param_name, PointerBuffer target) { + int getPointers(long object, long arg, int parameterName, PointerBuffer target) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); + int errcode = get(object, arg, parameterName, target.remaining() * POINTER_SIZE, memAddress(target), __buffer.address()); if (DEBUG) { checkCLError(errcode); } @@ -129,91 +130,91 @@ int getPointers(long object, long arg, int param_name, PointerBuffer target) { } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringASCII(long object, long arg, int param_name) { + String getStringASCII(long object, long arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, arg, param_name, __buffer); + int bytes = getString(object, arg, parameterName, __buffer); return __buffer.stringValueASCII(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be ASCII encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringASCII(long object, long arg, int param_name, int param_value_size) { + String getStringASCII(long object, long arg, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueASCII(0, param_value_size); + return __buffer.stringValueASCII(0, parameterValueSize); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query + * @param parameterName the parameter to query * * @return the parameter's string value */ - String getStringUTF8(long object, long arg, int param_name) { + String getStringUTF8(long object, long arg, int parameterName) { APIBuffer __buffer = apiBuffer(); - int bytes = getString(object, arg, param_name, __buffer); + int bytes = getString(object, arg, parameterName, __buffer); return __buffer.stringValueUTF8(0, bytes); } /** - * Returns the string value for the specified {@code param_name}. The raw + * Returns the string value for the specified {@code parameterName}. The raw * bytes returned are assumed to be UTF-8 encoded and have length equal to {@code - * param_value_size}. + * parameterValueSize}. * * @param object the object to query * @param arg an object argument - * @param param_name the parameter to query - * @param param_value_size the explicit string length + * @param parameterName the parameter to query + * @param parameterValueSize the explicit string length * * @return the parameter's string value */ - String getStringUTF8(long object, long arg, int param_name, int param_value_size) { + String getStringUTF8(long object, long arg, int parameterName, int parameterValueSize) { APIBuffer __buffer = apiBuffer(); - int errcode = get(object, arg, param_name, param_value_size, __buffer.address(), NULL); + int errcode = get(object, arg, parameterName, parameterValueSize, __buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } - return __buffer.stringValueUTF8(0, param_value_size); + return __buffer.stringValueUTF8(0, parameterValueSize); } - private int getString(long object, long arg, int param_name, APIBuffer __buffer) { + private int getString(long object, long arg, int parameterName, APIBuffer buffer) { // Get string length - int errcode = get(object, arg, param_name, 0, NULL, __buffer.address()); + int errcode = get(object, arg, parameterName, 0, NULL, buffer.address()); if (DEBUG) { checkCLError(errcode); } - int bytes = (int) __buffer.pointerValue(0); - __buffer.bufferParam(bytes + POINTER_SIZE); + int bytes = (int) buffer.pointerValue(0); + buffer.bufferParam(bytes + POINTER_SIZE); // Get string - errcode = get(object, arg, param_name, bytes, __buffer.address(), NULL); + errcode = get(object, arg, parameterName, bytes, buffer.address(), NULL); if (DEBUG) { checkCLError(errcode); } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java index 0d8c32a8df..f449d1c6b1 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/renderer/lwjgl/LwjglGL.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,7 @@ import com.jme3.renderer.opengl.GL2; import com.jme3.renderer.opengl.GL3; import com.jme3.renderer.opengl.GL4; +import com.jme3.renderer.opengl.GLFence; import org.lwjgl.opengl.*; import java.nio.ByteBuffer; @@ -80,6 +81,38 @@ public void glBindBuffer(final int target, final int buffer) { public void glBindTexture(final int target, final int texture) { GL11.glBindTexture(target, texture); } + + @Override + public void glBindImageTexture(final int unit, final int texture, final int level, + final boolean layered, final int layer, + final int access, final int format) { + GL42.glBindImageTexture(unit, texture, level, layered, layer, access, format); + } + + @Override + public void glDispatchCompute(final int numGroupsX, final int numGroupsY, final int numGroupsZ) { + GL43.glDispatchCompute(numGroupsX, numGroupsY, numGroupsZ); + } + + @Override + public void glMemoryBarrier(final int barriers) { + GL42.glMemoryBarrier(barriers); + } + + @Override + public GLFence glFenceSync(final int condition, final int flags) { + return new GLFence(GL32.glFenceSync(condition, flags), null); + } + + @Override + public int glClientWaitSync(final GLFence sync, final int flags, final long timeout) { + return GL32.glClientWaitSync(sync.getFenceId(), flags, timeout); + } + + @Override + public void glDeleteSync(final GLFence sync) { + GL32.glDeleteSync(sync.getFenceId()); + } @Override public void glBlendEquationSeparate(final int colorMode, final int alphaMode) { @@ -87,14 +120,14 @@ public void glBlendEquationSeparate(final int colorMode, final int alphaMode) { } @Override - public void glBlendFunc(final int sfactor, final int dfactor) { - GL11.glBlendFunc(sfactor, dfactor); + public void glBlendFunc(final int sFactor, final int dFactor) { + GL11.glBlendFunc(sFactor, dFactor); } @Override - public void glBlendFuncSeparate(final int sfactorRGB, final int dfactorRGB, final int sfactorAlpha, - final int dfactorAlpha) { - GL14.glBlendFuncSeparate(sfactorRGB, dfactorRGB, sfactorAlpha, dfactorAlpha); + public void glBlendFuncSeparate(final int sFactorRGB, final int dFactorRGB, final int sFactorAlpha, + final int dFactorAlpha) { + GL14.glBlendFuncSeparate(sFactorRGB, dFactorRGB, sFactorAlpha, dFactorAlpha); } @Override @@ -120,6 +153,12 @@ public void glBufferData(final int target, final ByteBuffer data, final int usag GL15.glBufferData(target, data, usage); } + @Override + public void glBufferData(final int target, final IntBuffer data, final int usage) { + checkLimit(data); + GL15.glBufferData(target, data, usage); + } + @Override public void glBufferSubData(final int target, final long offset, final FloatBuffer data) { checkLimit(data); @@ -314,11 +353,23 @@ public void glGetBufferSubData(final int target, final long offset, final ByteBu GL15.glGetBufferSubData(target, offset, data); } + @Override + public void glGetBufferSubData(final int target, final long offset, final IntBuffer data) { + checkLimit(data); + GL15.glGetBufferSubData(target, offset, data); + } + @Override public int glGetError() { return GL11.glGetError(); } + @Override + public void glGetFloat(int parameterId, FloatBuffer storeValues) { + checkLimit(storeValues); + GL11.glGetFloatv(parameterId, storeValues); + } + @Override public void glGetInteger(final int pname, final IntBuffer params) { checkLimit(params); @@ -591,6 +642,7 @@ public int glGetQueryObjectiv(int query, int pname) { return GL15.glGetQueryObjecti(query, pname); } + @Override public String glGetShaderInfoLog(int shader, int maxSize) { return GL20.glGetShaderInfoLog(shader, maxSize); } @@ -646,4 +698,5 @@ public void glBindBufferBase(final int target, final int index, final int buffer public void glUniformBlockBinding(final int program, final int uniformBlockIndex, final int uniformBlockBinding) { GL31.glUniformBlockBinding(program, uniformBlockIndex, uniformBlockBinding); } + } 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/LwjglCanvas.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java new file mode 100644 index 0000000000..6fa525f355 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglCanvas.java @@ -0,0 +1,755 @@ +/* + * Copyright (c) 2009-2025 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.lwjgl; + +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.awt.AwtKeyInput; +import com.jme3.input.awt.AwtMouseInput; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeCanvasContext; +import com.jme3.system.lwjglx.LwjglxGLPlatform; + +import java.awt.AWTException; +import java.awt.Canvas; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.Toolkit; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.awt.event.ComponentListener; +import java.awt.geom.AffineTransform; +import java.text.MessageFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.lwjgl.awthacks.NonClearGraphics; +import org.lwjgl.awthacks.NonClearGraphics2D; +import org.lwjgl.opengl.awt.GLData; +import org.lwjgl.system.Platform; + +import static org.lwjgl.system.MemoryUtil.*; +import static com.jme3.system.lwjglx.LwjglxDefaultGLPlatform.*; + +/** + * Class LwjglCanvas that integrates LWJGLX + * which allows using AWT-Swing components. + * + *

                  + * If LwjglCanvas throws an exception due to configuration problems, we can debug as follows: + *
                  + * - In AppSettings, set this property to enable a debug that displays + * the effective data for the context. + *

                  
                  + * ....
                  + *  AppSettings settings = new AppSettings(true);
                  + *  settings.putBoolean("GLDataEffectiveDebug", true);
                  + * ...
                  + * 
                  + * + *

                  + * NOTE: If running LwjglCanvas on older machines, the SRGB | Gamma Correction option + * will raise an exception, so it should be disabled. + *

                  
                  + * ....
                  + *  AppSettings settings = new AppSettings(true);
                  + *  settings.setGammaCorrection(false);
                  + * ...
                  + * 
                  + * + * @author wil + */ +public class LwjglCanvas extends LwjglWindow implements JmeCanvasContext, Runnable { + + /** Logger class. */ + private static final Logger LOGGER = Logger.getLogger(LwjglCanvas.class.getName()); + + /** GL versions map. */ + private static final Map> RENDER_CONFIGS = new HashMap<>(); + + /** Type of operating system where this context is running. */ + private static final Platform OS = Platform.get(); + + /* + Register the different versions. + */ + static { + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL30, (data) -> { + data.majorVersion = 3; + data.minorVersion = 0; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL31, (data) -> { + data.majorVersion = 3; + data.minorVersion = 1; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL32, (data) -> { + data.majorVersion = 3; + data.minorVersion = 2; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL33, (data) -> { + data.majorVersion = 3; + data.minorVersion = 3; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL40, (data) -> { + data.majorVersion = 4; + data.minorVersion = 0; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL41, (data) -> { + data.majorVersion = 4; + data.minorVersion = 1; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL42, (data) -> { + data.majorVersion = 4; + data.minorVersion = 2; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL43, (data) -> { + data.majorVersion = 4; + data.minorVersion = 3; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL44, (data) -> { + data.majorVersion = 4; + data.minorVersion = 4; + data.profile = GLData.Profile.COMPATIBILITY; + }); + RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL45, (data) -> { + data.majorVersion = 4; + data.minorVersion = 5; + data.profile = GLData.Profile.COMPATIBILITY; + }); + } + + /** + * An AWT java.awt.Canvas that supports to be drawn on using OpenGL. + */ + private class LwjglAWTGLCanvas extends Canvas { + + /** + * A {@link com.jme3.system.lwjglx.LwjglxGLPlatform} object. + * @see org.lwjgl.opengl.awt.PlatformGLCanvas + */ + private LwjglxGLPlatform platformCanvas; + + /** The OpenGL context (LWJGL3-AWT). */ + private long context; + + /** + * Information object used to create the OpenGL context. + */ + private GLData data; + + /** Effective data to initialize the context. */ + private GLData effective; + + /** + * Constructor of the LwjglAWTGLCanva class where objects are + * initialized for OpenGL-AWT rendering + * + * @param data A {@link org.lwjgl.opengl.awt.GLData} object + */ + public LwjglAWTGLCanvas(GLData data) { + this.effective = new GLData(); + this.context = NULL; + this.data = data; + + try { + platformCanvas = createLwjglxGLPlatform(); + } catch (UnsupportedOperationException e) { + listener.handleError(e.getLocalizedMessage(), e); + } + } + + /** + * (non-Javadoc) + * @see java.awt.Component#addComponentListener(java.awt.event.ComponentListener) + * @param l object-listener + */ + @Override + public synchronized void addComponentListener(ComponentListener l) { + super.addComponentListener(l); + } + + /** + * Returns the effective data (recommended or ideal) to initialize the + * LWJGL3-AWT context. + * + * @return A {@link org.lwjgl.opengl.awt.GLData} object + */ + public GLData getGLDataEffective() { + return effective; + } + + /** + * Called after beforeRender() to release the threads (unlock); + * so that AWT can update its threads normally. + *

                  + * NOTE: It is very important to call this method and not leave AWT + * hanging (breaking it) regardless of whether an error occurs during OpenGL + * rendering. + */ + public void afterRender() { + // release the rendering context + platformCanvas.makeCurrent(NULL); + try { + platformCanvas.unlock(); // <- MUST unlock on Linux + } catch (AWTException e) { + listener.handleError("Failed to unlock Canvas", e); + } + } + + /** + * Called before afterRender() to prepare the AWT drawing surface. + */ + public void beforeRender() { + // this is where the OpenGL rendering context is generated. + if (context == NULL) { + try { + context = platformCanvas.create(this, data, effective); + } catch (AWTException e) { + listener.handleError("Exception while creating the OpenGL context", e); + return; + } + } + + /* + * To start drawing on the AWT surface, the AWT threads must be locked to + * avoid conflicts when drawing on the canvas. + */ + try { + platformCanvas.lock(); // <- MUST lock on Linux + } catch (AWTException e) { + listener.handleError("Failed to lock Canvas", e); + } + + /* + * The 'makeCurrent(long)' method converts the specified OpenGL rendering + * context to the current rendering context. + */ + platformCanvas.makeCurrent(context); + } + + /** + * Frees up the drawing surface (only on Windows and MacOSX). + */ + public void doDisposeCanvas() { + if (OS != Platform.LINUX) { + platformCanvas.dispose(); + } + } + + /** + * This is where you actually draw on the canvas (framebuffer). + */ + public void swapBuffers() { + platformCanvas.swapBuffers(); + } + + /** + * (non-Javadoc) + * @see java.awt.Component#addNotify() + */ + @Override + public void addNotify() { + super.addNotify(); + /* you have to notify if the canvas is visible to draw on it. */ + synchronized (lock) { + hasNativePeer.set(true); + } + requestFocusInWindow(); + } + + /** + * (non-Javadoc) + * @see java.awt.Component#removeNotify() + */ + @Override + public void removeNotify() { + synchronized (lock) { + // prepare for a possible re-adding + if ((OS != Platform.LINUX) && (context != NULL)) { + platformCanvas.deleteContext(context); + context = NULL; + } + hasNativePeer.set(false); + } + super.removeNotify(); + if (OS == Platform.WINDOWS) { + LOGGER.log(Level.WARNING, "Windows does not support this functionality: remove(__canvas__)"); + } + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() + */ + public void destroy() { + platformCanvas.destroy(); + } + + /** + * Returns Graphics object that ignores {@link java.awt.Graphics#clearRect(int, int, int, int)} + * calls. + *

                  + * This is done so that the frame buffer will not be cleared by AWT/Swing internals. + * + * @see org.lwjgl.awthacks.NonClearGraphics2D + * @see org.lwjgl.awthacks.NonClearGraphics + * @return Graphics + */ + @Override + public Graphics getGraphics() { + Graphics graphics = super.getGraphics(); + if (graphics instanceof Graphics2D) { + return new NonClearGraphics2D((Graphics2D) graphics); + } + return new NonClearGraphics(graphics); + } + } + + /** Canvas-AWT. */ + private final LwjglAWTGLCanvas canvas; + + /** + * Configuration data to start the AWT context, this is used by the + * {@code lwjgl-awt} library. + */ + private GLData glData; + + /** Used to display the effective data for the {@code AWT-Swing} drawing surface per console. */ + private final AtomicBoolean showGLDataEffective = new AtomicBoolean(false); + + /** Used to notify the canvas status ({@code remove()/add()}). */ + private final AtomicBoolean hasNativePeer = new AtomicBoolean(false); + + /** Notify if the canvas is visible and has a parent.*/ + private final AtomicBoolean showing = new AtomicBoolean(false); + + /** Notify if there is a change in canvas dimensions. */ + private AtomicBoolean needResize = new AtomicBoolean(false); + + /** + * Flag that uses the context to check if it is initialized or not, this prevents + * it from being initialized multiple times and potentially breaking the JVM. + */ + private AtomicBoolean contextFlag = new AtomicBoolean(false); + + /** Semaphort used to check the "terminate" signal. */ + private final Semaphore signalTerminate = new Semaphore(0); + + /** lock-object. */ + private final Object lock = new Object(); + + /** Framebuffer width. */ + private int framebufferWidth = 1; + + /** Framebuffer height. */ + private int framebufferHeight = 1; + + /** AWT keyboard input manager. */ + private AwtKeyInput keyInput; + + /** AWT mouse input manager. */ + private AwtMouseInput mouseInput; + + /** + * Generate a new OpenGL context (LwjglCanvas) to integrate + * AWT/Swing with JME3 in your desktop applications. + */ + public LwjglCanvas() { + super(Type.Canvas); + glData = new GLData(); + canvas = new LwjglAWTGLCanvas(glData); + canvas.setIgnoreRepaint(true); + + // To determine the size of the framebuffer every time the user resizes + // the canvas (this works if the component has a parent) + canvas.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + synchronized (lock) { + GraphicsConfiguration gc = canvas.getGraphicsConfiguration(); + if (gc == null) { + return; + } + + AffineTransform at = gc.getDefaultTransform(); + float sx = (float) at.getScaleX(), + sy = (float) at.getScaleY(); + + int fw = (int) (canvas.getWidth() * sx); + int fh = (int) (canvas.getHeight() * sy); + + if (fw != framebufferWidth || fh != framebufferHeight) { + framebufferWidth = Math.max(fw, 1); + framebufferHeight = Math.max(fh, 1); + needResize.set(true); + } + } + } + }); + } + + /** + * (non-Javadoc) + * @see com.jme3.system.JmeContext#destroy(boolean) + * @param waitFor boolean + */ + @Override + public void destroy(boolean waitFor) { + super.destroy(waitFor); + this.contextFlag.set(false); + } + + /** + * (non-Javadoc) + * @see com.jme3.system.JmeContext#create(boolean) + * @param waitFor boolean + */ + @Override + public void create(boolean waitFor) { + if (this.contextFlag.get()) { + return; + } + // create context + super.create(waitFor); + this.contextFlag.set(true); + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#createContext(com.jme3.system.AppSettings) + * @param settings A {@link com.jme3.system.AppSettings} object + */ + @Override + protected void createContext(AppSettings settings) { + try { + Thread.sleep(1000); + } catch (InterruptedException ex) { + LOGGER.log(Level.SEVERE, "LWJGL3-AWT: Interrupted!", ex); + } + + super.createContext(settings); + RENDER_CONFIGS.computeIfAbsent(settings.getRenderer(), (t) -> { + return (data) -> { + data.majorVersion = 2; + data.minorVersion = 0; + }; + }).accept(glData); + + if (settings.getBitsPerPixel() == 24) { + glData.redSize = 8; + glData.greenSize = 8; + glData.blueSize = 8; + } else if (settings.getBitsPerPixel() == 16) { + glData.redSize = 5; + glData.greenSize = 6; + glData.blueSize = 5; + } + + // Enable vsync for LWJGL3-AWT + if (settings.isVSync()) { + glData.swapInterval = 1; + } else { + glData.swapInterval = 0; + } + + // This will activate the "effective data" scrubber. + showGLDataEffective.set(settings.getBoolean("GLDataEffectiveDebug")); + + glData.alphaSize = settings.getAlphaBits(); + glData.sRGB = settings.isGammaCorrection(); // Not compatible with very old devices + + glData.depthSize = settings.getDepthBits(); + glData.stencilSize = settings.getStencilBits(); + glData.samples = settings.getSamples(); + glData.stereo = settings.useStereo3D(); + + glData.debug = settings.isGraphicsDebug(); + glData.api = GLData.API.GL; + } + + /** + * Returns the AWT component where it is drawn (canvas). + * @return Canvas + */ + @Override + public Canvas getCanvas() { + return canvas; + } + + /** (non-Javadoc) */ + @Override + protected void showWindow() { } + /** (non-Javadoc) */ + @Override + protected void setWindowIcon(final AppSettings settings) { } + /** (non-Javadoc) */ + @Override + public void setTitle(String title) { } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#getKeyInput() + * @return returns a {@link com.jme3.input.awt.AwtKeyInput} object + */ + @Override + public KeyInput getKeyInput() { + if (keyInput == null) { + keyInput = new AwtKeyInput(); + keyInput.setInputSource(canvas); + } + return keyInput; + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#getMouseInput() + * @return returns a {@link com.jme3.input.awt.AwtMouseInput} object + */ + @Override + public MouseInput getMouseInput() { + if (mouseInput == null) { + mouseInput = new AwtMouseInput(); + mouseInput.setInputSource(canvas); + } + return mouseInput; + } + + /** + * Check if the canvas is displayed, that is, if it has a parent that has set it up. + *

                  + * It is very important that this verification be done so that LWJGL3-AWT works correctly. + * + * @return returns true if the canvas is ready to draw; otherwise + * returns false + */ + public boolean checkVisibilityState() { + if (!hasNativePeer.get()) { + synchronized (lock) { + canvas.doDisposeCanvas(); + } + return false; + } + + boolean currentShowing = canvas.isShowing(); + showing.set(currentShowing); + return currentShowing; + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#destroyContext() + */ + @Override + protected void destroyContext() { + synchronized (lock) { + canvas.destroy(); + } + + // request the cleanup + signalTerminate.release(); + super.destroyContext(); + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#runLoop() + */ + @Override + protected void runLoop() { + if (needResize.get()) { + needResize.set(false); + settings.setResolution(framebufferWidth, framebufferHeight); + listener.reshape(framebufferWidth, framebufferHeight); + } + + // check component status + if (!checkVisibilityState()) { + return; + } + + //---------------------------------------------------------------------- + // AWT - RENDERER + //---------------------------------------------------------------------- + /* + * The same logic as AWTGLCanvas is used to draw on the awt drawing surface: + * + * 1. Lock any thread to avoid any conflict. + * 2. Buffer swap (this is where the framebuffer is actually drawn): swapBuffers() + * 3. Unlock so that the AWT thread can work normally. IF NOT DONE, IT WILL + * BE WAITING AND BREAK ANY AWT/Swing APP. + */ + canvas.beforeRender(); + try { + super.runLoop(); + if (allowSwapBuffers && autoFlush) { + canvas.swapBuffers(); + } + } finally { + canvas.afterRender(); + } + + // Sync the display on some systems. + Toolkit.getDefaultToolkit().sync(); + + //---------------------------------------------------------------------- + /* + * Whether it is necessary to know the effective attributes to + * initialize the LWJGL3-AWT context + */ + //---------------------------------------------------------------------- + if (showGLDataEffective.get()) { + showGLDataEffective.set(false); + System.out.println(MessageFormat.format("[ DEBUGGER ] :Effective data to initialize the LWJGL3-AWT context\n{0}", + getPrintContextInitInfo(canvas.getGLDataEffective()))); + } + + try { + if (signalTerminate.tryAcquire(10, TimeUnit.MILLISECONDS)) { + canvas.doDisposeCanvas(); + } + } catch (InterruptedException ignored) { } + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglContext#printContextInitInfo() + */ + @Override + protected void printContextInitInfo() { + super.printContextInitInfo(); + LOGGER.log(Level.INFO, "Initializing LWJGL3-AWT with jMonkeyEngine\n{0}", getPrintContextInitInfo(glData)); + } + + /** + * Returns a string with the information obtained from GLData + * so that it can be displayed. + * + * @param glData context information + * @return String + */ + protected String getPrintContextInitInfo(GLData glData) { + StringBuilder sb = new StringBuilder(); + sb.append(" * Double Buffer: ").append(glData.doubleBuffer); + sb.append('\n') + .append(" * Stereo: ").append(glData.stereo); + sb.append('\n') + .append(" * Red Size: ").append(glData.redSize); + sb.append('\n') + .append(" * Green Size: ").append(glData.greenSize); + sb.append('\n') + .append(" * Blue Size: ").append(glData.blueSize); + sb.append('\n') + .append(" * Alpha Size: ").append(glData.alphaSize); + sb.append('\n') + .append(" * Depth Size: ").append(glData.depthSize); + sb.append('\n') + .append(" * Stencil Size: ").append(glData.stencilSize); + sb.append('\n') + .append(" * Accum Red Size: ").append(glData.accumRedSize); + sb.append('\n') + .append(" * Accum Green Size: ").append(glData.accumGreenSize); + sb.append('\n') + .append(" * Accum Blue Size: ").append(glData.accumBlueSize); + sb.append('\n') + .append(" * Accum Alpha Size: ").append(glData.accumAlphaSize); + sb.append('\n') + .append(" * Sample Buffers: ").append(glData.sampleBuffers); + sb.append('\n') + .append(" * Share Context: ").append(glData.shareContext); + sb.append('\n') + .append(" * Major Version: ").append(glData.majorVersion); + sb.append('\n') + .append(" * Minor Version: ").append(glData.minorVersion); + sb.append('\n') + .append(" * Forward Compatible: ").append(glData.forwardCompatible); + sb.append('\n') + .append(" * Profile: ").append(glData.profile); + sb.append('\n') + .append(" * API: ").append(glData.api); + sb.append('\n') + .append(" * Debug: ").append(glData.debug); + sb.append('\n') + .append(" * Swap Interval: ").append(glData.swapInterval); + sb.append('\n') + .append(" * SRGB (Gamma Correction): ").append(glData.sRGB); + sb.append('\n') + .append(" * Pixel Format Float: ").append(glData.pixelFormatFloat); + sb.append('\n') + .append(" * Context Release Behavior: ").append(glData.contextReleaseBehavior); + sb.append('\n') + .append(" * Color Samples NV: ").append(glData.colorSamplesNV); + sb.append('\n') + .append(" * Swap Group NV: ").append(glData.swapGroupNV); + sb.append('\n') + .append(" * Swap Barrier NV: ").append(glData.swapBarrierNV); + sb.append('\n') + .append(" * Robustness: ").append(glData.robustness); + sb.append('\n') + .append(" * Lose Context On Reset: ").append(glData.loseContextOnReset); + sb.append('\n') + .append(" * Context Reset Isolation: ").append(glData.contextResetIsolation); + return String.valueOf(sb); + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#getFramebufferHeight() + * @return int + */ + @Override + public int getFramebufferHeight() { + return this.framebufferHeight; + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjgl.LwjglWindow#getFramebufferWidth() + * @return int + */ + @Override + public int getFramebufferWidth() { + return this.framebufferWidth; + } +} 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 8f3afca85a..535fd295b1 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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,12 +32,7 @@ package com.jme3.system.lwjgl; -import static com.jme3.util.LWJGLBufferAllocator.PROPERTY_CONCURRENT_BUFFER_ALLOCATOR; -import static java.util.stream.Collectors.toSet; -import static org.lwjgl.opencl.CL10.CL_CONTEXT_PLATFORM; -import static org.lwjgl.opengl.GL.createCapabilities; -import static org.lwjgl.opengl.GL11.glGetInteger; - +import com.jme3.input.JoyInput; import com.jme3.input.lwjgl.GlfwJoystickInput; import com.jme3.input.lwjgl.GlfwKeyInput; import com.jme3.input.lwjgl.GlfwMouseInput; @@ -63,26 +58,30 @@ import com.jme3.util.BufferAllocatorFactory; import com.jme3.util.LWJGLBufferAllocator; import com.jme3.util.LWJGLBufferAllocator.ConcurrentLWJGLBufferAllocator; +import static com.jme3.util.LWJGLBufferAllocator.PROPERTY_CONCURRENT_BUFFER_ALLOCATOR; +import java.nio.IntBuffer; +import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import static java.util.stream.Collectors.toSet; import org.lwjgl.PointerBuffer; import org.lwjgl.Version; import org.lwjgl.glfw.GLFW; import org.lwjgl.glfw.GLFWJoystickCallback; import org.lwjgl.opencl.APPLEGLSharing; import org.lwjgl.opencl.CL10; +import static org.lwjgl.opencl.CL10.CL_CONTEXT_PLATFORM; import org.lwjgl.opencl.KHRGLSharing; import org.lwjgl.opengl.ARBDebugOutput; import org.lwjgl.opengl.ARBFramebufferObject; import org.lwjgl.opengl.EXTFramebufferMultisample; +import static org.lwjgl.opengl.GL.createCapabilities; +import static org.lwjgl.opengl.GL11.glGetInteger; import org.lwjgl.opengl.GLCapabilities; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.Platform; -import java.nio.IntBuffer; -import java.util.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - /** * A LWJGL implementation of a graphics context. */ @@ -106,6 +105,7 @@ public abstract class LwjglContext implements JmeContext { private static final Set SUPPORTED_RENDERS = new HashSet<>(Arrays.asList( AppSettings.LWJGL_OPENGL2, AppSettings.LWJGL_OPENGL30, + AppSettings.LWJGL_OPENGL31, AppSettings.LWJGL_OPENGL32, AppSettings.LWJGL_OPENGL33, AppSettings.LWJGL_OPENGL40, @@ -125,7 +125,7 @@ public abstract class LwjglContext implements JmeContext { protected GlfwKeyInput keyInput; protected GlfwMouseInput mouseInput; - protected GlfwJoystickInput joyInput; + protected JoyInput joyInput; protected Timer timer; @@ -134,14 +134,26 @@ 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; } protected void printContextInitInfo() { - logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n * Graphics Adapter: GLFW {2}", - APIUtil.toArray(Version.getVersion(), Thread.currentThread().getName(), GLFW.glfwGetVersionString())); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n * Graphics Adapter: GLFW {2}", + APIUtil.toArray(Version.getVersion(), Thread.currentThread().getName(), GLFW.glfwGetVersionString())); + } } protected int determineMaxSamples() { @@ -173,7 +185,28 @@ protected int getNumSamplesToUse() { return samples; } + /** + * Reinitializes the relevant details of the context. For internal use only. + */ + protected void reinitContext() { + initContext(false); + } + + /** + * Initializes the LWJGL renderer and input for the first time. For internal + * use only. + */ protected void initContextFirstTime() { + initContext(true); + } + + /** + * Initializes the LWJGL renderer and input. + * @param first - Whether this is the first time we are initializing and we + * need to create the renderer or not. Otherwise, we'll just reset the + * renderer as needed. + */ + private void initContext(boolean first) { final String renderer = settings.getRenderer(); final GLCapabilities capabilities = createCapabilities(!renderer.equals(AppSettings.LWJGL_OPENGL2)); @@ -184,75 +217,81 @@ protected void initContextFirstTime() { throw new UnsupportedOperationException("Unsupported renderer: " + renderer); } - GL gl = new LwjglGL(); - GLExt glext = new LwjglGLExt(); - GLFbo glfbo; + if (first) { + GL gl = new LwjglGL(); + GLExt glext = new LwjglGLExt(); + GLFbo glfbo; - if (capabilities.OpenGL30) { - glfbo = new LwjglGLFboGL3(); - } else { - glfbo = new LwjglGLFboEXT(); - } + if (capabilities.OpenGL30) { + glfbo = new LwjglGLFboGL3(); + } else { + glfbo = new LwjglGLFboEXT(); + } - if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugDesktop(gl, glext, glfbo); - glext = (GLExt) gl; - glfbo = (GLFbo) gl; - } + if (settings.isGraphicsDebug()) { + gl = (GL) GLDebug.createProxy(gl, gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLDebug.createProxy(gl, glext, GLExt.class); + glfbo = (GLFbo) GLDebug.createProxy(gl, glfbo, GLFbo.class); + } - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } + if (settings.isGraphicsTiming()) { + GLTimingState timingState = new GLTimingState(); + gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); + glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); + } - if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); - } + if (settings.isGraphicsTrace()) { + gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); + glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); + glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); + } - this.renderer = new GLRenderer(gl, glext, glfbo); + this.renderer = new GLRenderer(gl, glext, glfbo); + if (this.settings.isGraphicsDebug()) ((GLRenderer)this.renderer).setDebugEnabled(true); + } this.renderer.initialize(); - if (capabilities.GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { + if (capabilities.GL_ARB_debug_output && settings.isGraphicsDebug()) { ARBDebugOutput.glDebugMessageCallbackARB(new LwjglGLDebugOutputHandler(), 0); } this.renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); this.renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - // Init input - if (keyInput != null) { - keyInput.initialize(); - } - - if (mouseInput != null) { - mouseInput.initialize(); - } - - if (joyInput != null) { - joyInput.initialize(); - } + if (first) { + // Init input + if (keyInput != null) { + keyInput.initialize(); + } - GLFW.glfwSetJoystickCallback(new GLFWJoystickCallback() { - @Override - public void invoke(int jid, int event) { + if (mouseInput != null) { + mouseInput.initialize(); + } - // Invoke the disconnected event before we reload the joysticks or we lose the reference to it. - // Invoke the connected event after we reload the joysticks to obtain the reference to it. + if (joyInput != null) { + joyInput.initialize(); + } - if ( event == GLFW.GLFW_CONNECTED ) { - joyInput.reloadJoysticks(); - joyInput.fireJoystickConnectedEvent(jid); - } - else { - joyInput.fireJoystickDisconnectedEvent(jid); - joyInput.reloadJoysticks(); + GLFW.glfwSetJoystickCallback(new GLFWJoystickCallback() { + @Override + public void invoke(int jid, int event) { + if (!(joyInput instanceof GlfwJoystickInput)) return; + + // Invoke the disconnected event before we reload the joysticks or lose the reference to it. + // Invoke the connected event after we reload the joysticks to obtain the reference to it. + GlfwJoystickInput glfwJoyInput = (GlfwJoystickInput) joyInput; + + if (event == GLFW.GLFW_CONNECTED) { + glfwJoyInput.reloadJoysticks(); + glfwJoyInput.fireJoystickConnectedEvent(jid); + } else { + glfwJoyInput.fireJoystickDisconnectedEvent(jid); + glfwJoyInput.reloadJoysticks(); + } } - } - }); + }); + } renderable.set(true); } @@ -287,6 +326,7 @@ private static long[] getPlatforms() { } } + @SuppressWarnings("unchecked") protected void initOpenCL(final long window) { logger.info("Initialize OpenCL with LWJGL3"); @@ -343,7 +383,7 @@ protected void initOpenCL(final long window) { PlatformChooser chooser = null; if (settings.getOpenCLPlatformChooser() != null) { try { - chooser = (PlatformChooser) Class.forName(settings.getOpenCLPlatformChooser()).newInstance(); + chooser = (PlatformChooser) Class.forName(settings.getOpenCLPlatformChooser()).getDeclaredConstructor().newInstance(); } catch (Exception ex) { logger.log(Level.WARNING, "Unable to instantiate custom PlatformChooser", ex); } @@ -353,8 +393,8 @@ protected void initOpenCL(final long window) { chooser = new DefaultPlatformChooser(); } - final List choosenDevices = chooser.chooseDevices(platforms); - final Optional unsupportedDevice = choosenDevices.stream() + final List chosenDevices = chooser.chooseDevices(platforms); + final Optional unsupportedDevice = chosenDevices.stream() .filter(dev -> !(dev instanceof LwjglDevice)) .findAny(); @@ -364,7 +404,7 @@ protected void initOpenCL(final long window) { return; } - final Set lwjglPlatforms = choosenDevices.stream() + final Set lwjglPlatforms = chosenDevices.stream() .map(LwjglDevice.class::cast) .map(LwjglDevice::getPlatform) .collect(toSet()); @@ -374,7 +414,7 @@ protected void initOpenCL(final long window) { return; } - final long[] deviceIds = choosenDevices.stream() + final long[] deviceIds = chosenDevices.stream() .map(LwjglDevice.class::cast) .mapToLong(LwjglDevice::getDevice) .toArray(); @@ -389,12 +429,12 @@ protected void initOpenCL(final long window) { .orElseThrow(() -> new RuntimeException("not found a platform")); logger.log(Level.INFO, "chosen platform: {0}", platform.getName()); - logger.log(Level.INFO, "chosen devices: {0}", choosenDevices); + logger.log(Level.INFO, "chosen devices: {0}", chosenDevices); // create context try { long context = createContext(platform.getPlatform(), deviceIds, window); - clContext = new com.jme3.opencl.lwjgl.LwjglContext(context, (List) choosenDevices); + clContext = new com.jme3.opencl.lwjgl.LwjglContext(context, (List) chosenDevices); } catch (final Exception ex) { logger.log(Level.SEVERE, "Unable to create OpenCL context", ex); return; @@ -429,6 +469,9 @@ private long createContext(final long platform, final long[] devices, long windo case MACOSX: properties.put(APPLEGLSharing.CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE) .put(org.lwjgl.opengl.CGL.CGLGetShareGroup(org.lwjgl.opengl.CGL.CGLGetCurrentContext())); + break; + default: + break; // Unknown Platform, do nothing. } properties.put(CL_CONTEXT_PLATFORM).put(platform); diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java index 3eb749adcf..f4a5582f33 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglDisplay.java @@ -31,6 +31,8 @@ */ package com.jme3.system.lwjgl; +import com.jme3.system.Displays; + /** * @author Daniel Johansson */ @@ -39,4 +41,6 @@ public class LwjglDisplay extends LwjglWindow { public LwjglDisplay() { super(Type.Display); } + + } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java index 96895b2557..6dc2d71851 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBuffer.java @@ -31,6 +31,12 @@ */ package com.jme3.system.lwjgl; +import com.jme3.input.JoyInput; +import com.jme3.input.KeyInput; +import com.jme3.input.MouseInput; +import com.jme3.input.TouchInput; +import com.jme3.input.dummy.DummyKeyInput; +import com.jme3.input.dummy.DummyMouseInput; import com.jme3.system.AppSettings; import com.jme3.system.JmeContext; @@ -39,6 +45,9 @@ */ public class LwjglOffscreenBuffer extends LwjglWindow { + private KeyInput keyInput; + private MouseInput mouseInput; + public LwjglOffscreenBuffer() { super(JmeContext.Type.OffscreenSurface); } @@ -50,4 +59,36 @@ protected void showWindow() { @Override protected void setWindowIcon(final AppSettings settings) { } + + @Override + public void setTitle(String title) { + } + + @Override + public MouseInput getMouseInput() { + if (mouseInput == null) { + mouseInput = new DummyMouseInput(); + } + + return mouseInput; + } + + @Override + public KeyInput getKeyInput() { + if (keyInput == null) { + keyInput = new DummyKeyInput(); + } + + return keyInput; + } + + @Override + public JoyInput getJoyInput() { + return null; + } + + @Override + public TouchInput getTouchInput() { + return null; + } } 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 e84c5c3ce6..5854a76424 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-2018 jMonkeyEngine + * Copyright (c) 2009-2025 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.system.lwjgl; +import static org.lwjgl.glfw.GLFW.*; +import static org.lwjgl.opengl.GL11.GL_FALSE; +import static org.lwjgl.system.MemoryUtil.NULL; + import com.jme3.input.JoyInput; import com.jme3.input.KeyInput; import com.jme3.input.MouseInput; @@ -39,12 +42,16 @@ import com.jme3.input.lwjgl.GlfwJoystickInput; import com.jme3.input.lwjgl.GlfwKeyInput; import com.jme3.input.lwjgl.GlfwMouseInput; +import com.jme3.input.lwjgl.SdlJoystickInput; +import com.jme3.math.Vector2f; import com.jme3.system.AppSettings; +import com.jme3.system.Displays; import com.jme3.system.JmeContext; import com.jme3.system.JmeSystem; import com.jme3.system.NanoTimer; import com.jme3.util.BufferUtils; -import java.awt.*; +import com.jme3.util.SafeArrayList; +import java.awt.Graphics2D; import java.awt.image.BufferedImage; import java.nio.ByteBuffer; import java.util.EnumSet; @@ -53,11 +60,15 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.logging.Level; import java.util.logging.Logger; +import org.lwjgl.PointerBuffer; import org.lwjgl.Version; -import org.lwjgl.glfw.*; -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.opengl.GL11.GL_FALSE; -import static org.lwjgl.system.MemoryUtil.NULL; +import org.lwjgl.glfw.GLFWErrorCallback; +import org.lwjgl.glfw.GLFWFramebufferSizeCallback; +import org.lwjgl.glfw.GLFWImage; +import org.lwjgl.glfw.GLFWVidMode; +import org.lwjgl.glfw.GLFWWindowFocusCallback; +import org.lwjgl.glfw.GLFWWindowSizeCallback; +import org.lwjgl.system.Platform; /** * A wrapper class over the GLFW framework in LWJGL 3. @@ -69,63 +80,108 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { private static final Logger LOGGER = Logger.getLogger(LwjglWindow.class.getName()); private static final EnumSet SUPPORTED_TYPES = EnumSet.of( - JmeContext.Type.Display, - JmeContext.Type.Canvas, - JmeContext.Type.OffscreenSurface); + JmeContext.Type.Display, + JmeContext.Type.Canvas, + JmeContext.Type.OffscreenSurface + ); private static final Map RENDER_CONFIGS = new HashMap<>(); static { - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL30, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL32, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL33, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL40, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL41, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL42, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL43, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL44, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); - }); - RENDER_CONFIGS.put(AppSettings.LWJGL_OPENGL45, () -> { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); - }); + 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); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL32, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL33, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL40, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL41, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL42, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL43, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL44, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 4); + } + ); + RENDER_CONFIGS.put( + AppSettings.LWJGL_OPENGL45, + () -> { + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 5); + } + ); } protected final AtomicBoolean needClose = new AtomicBoolean(false); protected final AtomicBoolean needRestart = new AtomicBoolean(false); private final JmeContext.Type type; + private final SafeArrayList windowSizeListeners = new SafeArrayList<>( + WindowSizeListener.class + ); private GLFWErrorCallback errorCallback; private GLFWWindowSizeCallback windowSizeCallback; + private GLFWFramebufferSizeCallback framebufferSizeCallback; private GLFWWindowFocusCallback windowFocusCallback; private Thread mainThread; - private double frameSleepTime; + private long monitor = NULL; private long window = NULL; private int frameRateLimit = -1; @@ -133,8 +189,16 @@ public abstract class LwjglWindow extends LwjglContext implements Runnable { protected boolean autoFlush = true; protected boolean allowSwapBuffers = false; - public LwjglWindow(final JmeContext.Type type) { + // temp variables used for glfw calls + private final int width[] = new int[1]; + private final int height[] = new int[1]; + + // state maintained by updateSizes() + private int oldFramebufferWidth; + private int oldFramebufferHeight; + private final Vector2f oldScale = new Vector2f(1, 1); + public LwjglWindow(final JmeContext.Type type) { if (!SUPPORTED_TYPES.contains(type)) { throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided"); } @@ -142,9 +206,28 @@ public LwjglWindow(final JmeContext.Type type) { this.type = type; } + /** + * Registers the specified listener to get notified when window size changes. + * + * @param listener The WindowSizeListener to register. + */ + public void registerWindowSizeListener(WindowSizeListener listener) { + windowSizeListeners.add(listener); + } + + /** + * Removes the specified listener from the listeners list. + * + * @param listener The WindowSizeListener to remove. + */ + public void removeWindowSizeListener(WindowSizeListener listener) { + windowSizeListeners.remove(listener); + } + /** * @return Type.Display or Type.Canvas */ + @Override public JmeContext.Type getType() { return type; } @@ -154,6 +237,7 @@ public JmeContext.Type getType() { * * @param title the title to set */ + @Override public void setTitle(final String title) { if (created.get() && window != NULL) { glfwSetWindowTitle(window, title); @@ -163,6 +247,7 @@ public void setTitle(final String title) { /** * Restart if it's a windowed or full-screen display. */ + @Override public void restart() { if (created.get()) { needRestart.set(true); @@ -177,13 +262,31 @@ public void restart() { * @param settings the settings to apply when creating the context. */ protected void createContext(final AppSettings settings) { - glfwSetErrorCallback(errorCallback = new GLFWErrorCallback() { - @Override - public void invoke(int error, long description) { - final String message = GLFWErrorCallback.getDescription(description); - listener.handleError(message, new Exception(message)); + glfwSetErrorCallback( + errorCallback = + new GLFWErrorCallback() { + @Override + public void invoke(int error, long description) { + final String message = GLFWErrorCallback.getDescription(description); + listener.handleError(message, new Exception(message)); + } + } + ); + + if (glfwPlatformSupported(GLFW_PLATFORM_WAYLAND)) { + + /* + * Change the platform GLFW uses to enable GLX on Wayland as long as you + * have XWayland (X11 compatibility) + */ + if (settings.isX11PlatformPreferred() && glfwPlatformSupported(GLFW_PLATFORM_X11)) { + glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); } - }); + + // Disables the libdecor bar when creating a fullscreen context + // https://www.glfw.org/docs/latest/intro_guide.html#init_hints_wayland + glfwInitHint(GLFW_WAYLAND_LIBDECOR, settings.isFullscreen() ? GLFW_WAYLAND_DISABLE_LIBDECOR : GLFW_WAYLAND_PREFER_LIBDECOR); + } if (!glfwInit()) { throw new IllegalStateException("Unable to initialize GLFW"); @@ -196,12 +299,18 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_TRUE); glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - RENDER_CONFIGS.computeIfAbsent(renderer, s -> () -> { - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }).run(); + RENDER_CONFIGS + .computeIfAbsent( + renderer, + s -> + () -> { + glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); + glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE); + glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); + glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); + } + ) + .run(); if (settings.getBoolean("RendererDebug")) { glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); @@ -217,7 +326,14 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits()); glfwWindowHint(GLFW_SAMPLES, settings.getSamples()); glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GLFW_TRUE : GLFW_FALSE); - glfwWindowHint(GLFW_REFRESH_RATE, settings.getFrequency()); + glfwWindowHint( + GLFW_REFRESH_RATE, + settings.getFrequency() <= 0 ? GLFW_DONT_CARE : settings.getFrequency() + ); + glfwWindowHint( + GLFW_COCOA_RETINA_FRAMEBUFFER, + settings.isUseRetinaFrameBuffer() ? GLFW_TRUE : GLFW_FALSE + ); if (settings.getBitsPerPixel() == 24) { glfwWindowHint(GLFW_RED_BITS, 8); @@ -231,54 +347,84 @@ public void invoke(int error, long description) { glfwWindowHint(GLFW_ALPHA_BITS, settings.getAlphaBits()); - // TODO: Add support for monitor selection - long monitor = NULL; + // long monitor = NULL; + /** + * Let's grab the display selected, if not found it will return + * primaryMonitor. if not full screen just use primary display data. + */ if (settings.isFullscreen()) { + monitor = getDisplay(settings.getDisplay()); + } else { monitor = glfwGetPrimaryMonitor(); } - final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); - - if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { - settings.setResolution(videoMode.width(), videoMode.height()); + final GLFWVidMode videoMode = glfwGetVideoMode(monitor); + int requestWidth = settings.getWindowWidth(); + int requestHeight = settings.getWindowHeight(); + if (requestWidth <= 0 || requestHeight <= 0) { + requestWidth = videoMode.width(); + requestHeight = videoMode.height(); + } + int requestX = GLFW_ANY_POSITION; + int requestY = GLFW_ANY_POSITION; + if (!settings.isFullscreen()) { + if (settings.getCenterWindow()) { + // Center the window + requestX = (videoMode.width() - requestWidth) / 2; + requestY = (videoMode.height() - requestHeight) / 2; + } else { + requestX = settings.getWindowXPosition(); + requestY = settings.getWindowYPosition(); + } + glfwWindowHint(GLFW_POSITION_X, requestX); + glfwWindowHint(GLFW_POSITION_Y, requestY); + } + // Lets use the monitor selected from AppSettings if FullScreen is + // set. + if (settings.isFullscreen()) { + window = glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), monitor, NULL); + } else { + window = glfwCreateWindow(requestWidth, requestHeight, settings.getTitle(), NULL, NULL); } - - window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL); - if (window == NULL) { throw new RuntimeException("Failed to create the GLFW window"); } - // Add a resize callback which delegates to the listener - glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() { - @Override - public void invoke(final long window, final int width, final int height) { - settings.setResolution(width, height); - listener.reshape(width, height); - } - }); - - glfwSetWindowFocusCallback(window, windowFocusCallback = new GLFWWindowFocusCallback() { - @Override - public void invoke(final long window, final boolean focus) { - if (wasActive != focus) { - if (!wasActive) { - listener.gainFocus(); - timer.reset(); - } else { - listener.loseFocus(); + glfwSetWindowFocusCallback( + window, + windowFocusCallback = + new GLFWWindowFocusCallback() { + @Override + public void invoke(final long window, final boolean focus) { + if (wasActive != focus) { + if (!wasActive) { + listener.gainFocus(); + timer.reset(); + } else { + listener.loseFocus(); + } + wasActive = !wasActive; + } } - wasActive = !wasActive; } + ); + + int platformId = glfwGetPlatform(); + if (settings.isX11PlatformPreferred()) { + if (platformId == GLFW_PLATFORM_X11) { + LOGGER.log(Level.INFO, "Active X11 server for GLX management:\n * Platform: GLFW_PLATFORM_X11|XWayland ({0})", platformId); + } else { + LOGGER.log(Level.WARNING, "Can't change platform to X11 (GLX), check if you have XWayland enabled"); } - }); - - // Center the window - if (!settings.isFullscreen()) { - glfwSetWindowPos(window, - (videoMode.width() - settings.getWidth()) / 2, - (videoMode.height() - settings.getHeight()) / 2); + } + + if (platformId != GLFW_PLATFORM_WAYLAND && !settings.isFullscreen()) { + /* + * in case the window positioning hints above were ignored, but not + * on Wayland, since Wayland doesn't support window positioning + */ + glfwSetWindowPos(window, requestX, requestY); } // Make the OpenGL context current @@ -294,7 +440,75 @@ public void invoke(final long window, final boolean focus) { setWindowIcon(settings); showWindow(); + // HACK: the framebuffer seems to be initialized with the wrong size + // on some HiDPI platforms until glfwPollEvents is called 2 or 3 times + for (int i = 0; i < 4; i++) glfwPollEvents(); + + // Windows resize callback + glfwSetWindowSizeCallback( + window, + windowSizeCallback = + new GLFWWindowSizeCallback() { + @Override + public void invoke(final long window, final int width, final int height) { + updateSizes(); + } + } + ); + + // Add a framebuffer resize callback which delegates to the listener + glfwSetFramebufferSizeCallback( + window, + framebufferSizeCallback = + new GLFWFramebufferSizeCallback() { + @Override + public void invoke(final long window, final int width, final int height) { + updateSizes(); + } + } + ); + allowSwapBuffers = settings.isSwapBuffers(); + + // Create OpenCL + if (settings.isOpenCLSupport()) { + initOpenCL(window); + } + + updateSizes(); + } + + private void updateSizes() { + // framebuffer size (resolution) may differ from window size (e.g. HiDPI) + + glfwGetWindowSize(window, width, height); + int windowWidth = width[0] < 1 ? 1 : width[0]; + int windowHeight = height[0] < 1 ? 1 : height[0]; + if (settings.getWindowWidth() != windowWidth || settings.getWindowHeight() != windowHeight) { + settings.setWindowSize(windowWidth, windowHeight); + for (WindowSizeListener wsListener : windowSizeListeners.getArray()) { + wsListener.onWindowSizeChanged(windowWidth, windowHeight); + } + } + + glfwGetFramebufferSize(window, width, height); + int framebufferWidth = width[0]; + int framebufferHeight = height[0]; + if (framebufferWidth != oldFramebufferWidth || framebufferHeight != oldFramebufferHeight) { + settings.setResolution(framebufferWidth, framebufferHeight); + listener.reshape(framebufferWidth, framebufferHeight); + + oldFramebufferWidth = framebufferWidth; + oldFramebufferHeight = framebufferHeight; + } + + float xScale = framebufferWidth / windowWidth; + float yScale = framebufferHeight / windowHeight; + if (oldScale.x != xScale || oldScale.y != yScale) { + listener.rescale(xScale, yScale); + + oldScale.set(xScale, yScale); + } } protected void showWindow() { @@ -303,8 +517,14 @@ protected void showWindow() { /** * Set custom icons to the window of this application. + * + * @param settings settings for getting the icons */ protected void setWindowIcon(final AppSettings settings) { + if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) { + // Wayland doesn't support custom icons. + return; + } final Object[] icons = settings.getIcons(); if (icons == null) return; @@ -312,7 +532,6 @@ protected void setWindowIcon(final AppSettings settings) { final GLFWImage[] images = imagesToGLFWImages(icons); try (final GLFWImage.Buffer iconSet = GLFWImage.malloc(images.length)) { - for (int i = images.length - 1; i >= 0; i--) { final GLFWImage image = images[i]; iconSet.put(i, image); @@ -326,7 +545,6 @@ protected void setWindowIcon(final AppSettings settings) { * Convert array of images to array of {@link GLFWImage}. */ private GLFWImage[] imagesToGLFWImages(final Object[] images) { - final GLFWImage[] out = new GLFWImage[images.length]; for (int i = 0; i < images.length; i++) { @@ -341,10 +559,12 @@ private GLFWImage[] imagesToGLFWImages(final Object[] images) { * Convert the {@link BufferedImage} to the {@link GLFWImage}. */ private GLFWImage imageToGLFWImage(BufferedImage image) { - if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { - - final BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); + final BufferedImage convertedImage = new BufferedImage( + image.getWidth(), + image.getHeight(), + BufferedImage.TYPE_INT_ARGB_PRE + ); final Graphics2D graphics = convertedImage.createGraphics(); final int targetWidth = image.getWidth(); @@ -386,7 +606,6 @@ protected void destroyContext() { } if (errorCallback != null) { - // We need to specifically set this to null as we might set a new callback before we reinit GLFW glfwSetErrorCallback(null); @@ -399,6 +618,11 @@ protected void destroyContext() { windowSizeCallback = null; } + if (framebufferSizeCallback != null) { + framebufferSizeCallback.close(); + framebufferSizeCallback = null; + } + if (windowFocusCallback != null) { windowFocusCallback.close(); windowFocusCallback = null; @@ -408,8 +632,6 @@ protected void destroyContext() { glfwDestroyWindow(window); window = NULL; } - - glfwTerminate(); } catch (final Exception ex) { listener.handleError("Failed to destroy context", ex); } @@ -422,31 +644,47 @@ public void create(boolean waitFor) { return; } - // NOTE: this is required for Mac OS X! - mainThread = Thread.currentThread(); - run(); + if (Platform.get() == Platform.MACOSX) { + // NOTE: this is required for Mac OS X! + mainThread = Thread.currentThread(); + mainThread.setName("jME3 Main"); + if (waitFor) { + LOGGER.warning("create(true) is not supported for macOS!"); + } + run(); + } else { + mainThread = new Thread(this, "jME3 Main"); + mainThread.start(); + if (waitFor) { + waitFor(true); + } + } } /** * Does LWJGL display initialization in the OpenGL thread + * + * @return returns {@code true} if the context initialization was successful */ protected boolean initInThread() { try { if (!JmeSystem.isLowPermissions()) { // Enable uncaught exception handler only for current thread - Thread.currentThread().setUncaughtExceptionHandler((thread, thrown) -> { - listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); - if (needClose.get()) { - // listener.handleError() has requested the - // context to close. Satisfy request. - deinitInThread(); - } - }); + Thread + .currentThread() + .setUncaughtExceptionHandler((thread, thrown) -> { + listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); + if (needClose.get()) { + // listener.handleError() has requested the + // context to close. Satisfy request. + deinitInThread(); + } + }); } timer = new NanoTimer(); - // For canvas, this will create a pbuffer, + // For canvas, this will create a PBuffer, // allowing us to query information. // When the canvas context becomes available, it will // be replaced seamlessly. @@ -455,13 +693,6 @@ protected boolean initInThread() { created.set(true); super.internalCreate(); - - //create OpenCL - //Must be done here because the window handle is needed - if (settings.isOpenCLSupport()) { - initOpenCL(window); - } - } catch (Exception ex) { try { if (window != NULL) { @@ -477,6 +708,8 @@ protected boolean initInThread() { } listener.initialize(); + updateSizes(); + return true; } @@ -486,14 +719,7 @@ protected boolean initInThread() { protected void runLoop() { // If a restart is required, lets recreate the context. if (needRestart.getAndSet(false)) { - try { - destroyContext(); - createContext(settings); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex); - } - - LOGGER.fine("Display restarted."); + restartContext(); } if (!created.get()) { @@ -502,13 +728,14 @@ protected void runLoop() { listener.update(); - // All this does is call swap buffers + // All this does is call glfwSwapBuffers(). // If the canvas is not active, there's no need to waste time - // doing that .. + // doing that. if (renderable.get()) { - // calls swap buffers, etc. try { - if (allowSwapBuffers && autoFlush) { + // If type is 'Canvas'; lwjgl-awt takes care of swap buffers. + if ((type != Type.Canvas) && allowSwapBuffers && autoFlush) { + // calls swap buffers, etc. glfwSwapBuffers(window); } } catch (Throwable ex) { @@ -516,8 +743,8 @@ protected void runLoop() { } } - // Subclasses just call GLObjectManager clean up objects here - // it is safe .. for now. + // Subclasses just call GLObjectManager. Clean up objects here. + // It is safe ... for now. if (renderer != null) { renderer.postFrame(); } @@ -530,28 +757,34 @@ protected void runLoop() { setFrameRateLimit(20); } - // If software frame rate limiting has been asked for, lets calculate sleep time based on a base value calculated - // from 1000 / frameRateLimit in milliseconds subtracting the time it has taken to render last frame. - // This gives an approximate limit within 3 fps of the given frame rate limit. - if (frameRateLimit > 0) { - final double sleep = frameSleepTime - (timer.getTimePerFrame() / 1000.0); - final long sleepMillis = (long) sleep; - final int additionalNanos = (int) ((sleep - sleepMillis) * 1000000.0); + Sync.sync(frameRateLimit); - if (sleepMillis >= 0 && additionalNanos >= 0) { - try { - Thread.sleep(sleepMillis, additionalNanos); - } catch (InterruptedException ignored) { - } - } + glfwPollEvents(); + } + + private void restartContext() { + try { + destroyContext(); + createContext(settings); + } catch (Exception ex) { + LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex); } + // Reinitialize context flags and such + reinitContext(); - glfwPollEvents(); + // We need to reinit the mouse and keyboard input as they are tied to a window handle + if (keyInput != null && keyInput.isInitialized()) { + keyInput.resetContext(); + } + if (mouseInput != null && mouseInput.isInitialized()) { + mouseInput.resetContext(); + } + + LOGGER.fine("Display restarted."); } private void setFrameRateLimit(int frameRateLimit) { this.frameRateLimit = frameRateLimit; - frameSleepTime = 1000.0 / this.frameRateLimit; } /** @@ -562,6 +795,7 @@ protected void deinitInThread() { destroyContext(); super.internalDestroy(); + glfwTerminate(); LOGGER.fine("Display destroyed."); } @@ -569,8 +803,9 @@ protected void deinitInThread() { @Override public void run() { if (listener == null) { - throw new IllegalStateException("SystemListener is not set on context!" - + "Must set with JmeContext.setSystemListener()."); + throw new IllegalStateException( + "SystemListener is not set on context!" + "Must set with JmeContext.setSystemListener()." + ); } LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); @@ -581,7 +816,6 @@ public void run() { } while (true) { - runLoop(); if (needClose.get()) { @@ -599,7 +833,19 @@ public void run() { @Override public JoyInput getJoyInput() { if (joyInput == null) { - joyInput = new GlfwJoystickInput(); + boolean useSdl = true; + + String mapper = settings.getJoysticksMapper(); + if (AppSettings.JOYSTICKS_LEGACY_MAPPER.equals(mapper) + || AppSettings.JOYSTICKS_XBOX_LEGACY_MAPPER.equals(mapper)) { + useSdl = false; + } + + if (useSdl) { + joyInput = new SdlJoystickInput(settings); + } else { + joyInput = new GlfwJoystickInput(settings); + } } return joyInput; } @@ -647,4 +893,154 @@ public void destroy(boolean waitFor) { public long getWindowHandle() { return window; } + + /** + * Get the window content scale, for HiDPI support. + * + * The content scale is the ratio between the current DPI and the platform's default DPI. + * This is especially important for text and any UI elements. If the pixel dimensions of + * your UI scaled by this look appropriate on your machine then it should appear at a + * reasonable size on other machines regardless of their DPI and scaling settings. This + * relies on the system DPI and scaling settings being somewhat correct. + * + * @param store A vector2f to store the result + * @return The window content scale + * @see Window content scale + */ + public Vector2f getWindowContentScale(Vector2f store) { + if (store == null) store = new Vector2f(); + + glfwGetFramebufferSize(window, width, height); + store.set(width[0], height[0]); + + glfwGetWindowSize(window, width, height); + store.x /= width[0]; + store.y /= height[0]; + + 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; + } + + /** + * Returns the Primary Monitor position number from the list of monitors + * returned by glfwGetPrimaryMonitor(). If primary monitor not found + * it will return -1 and report the error. + * + * @return returns the Primary Monitor Position. + */ + @Override + public int getPrimaryDisplay() { + long prim = glfwGetPrimaryMonitor(); + Displays monitors = getDisplays(); + for (int i = 0; i < monitors.size(); i++) { + long monitorI = monitors.get(i).getDisplay(); + if (monitorI == prim) return i; + } + + LOGGER.log(Level.SEVERE, "Couldn't locate Primary Monitor in the list of Monitors."); + return -1; + } + + /** + * This routines return the display ID by position in an array of display returned + * by glfwGetMonitors(). + * + * @param pos the position of the display in the list of displays returned. + * @return return the displayID if found otherwise return Primary display + */ + private long getDisplay(int pos) { + Displays displays = getDisplays(); + if (pos < displays.size()) return displays.get(pos).getDisplay(); + + LOGGER.log( + Level.SEVERE, + "Couldn't locate Display requested in the list of Displays. pos:" + + pos + + " size: " + + displays.size() + ); + return glfwGetPrimaryMonitor(); + } + + /** + * This returns an arraylist of all the Display returned by OpenGL get Monitor + * call. It will also has some limited information about each display, like: + * width, height and refresh rate. + * + * @return returns an ArrayList of all Display returned by glfwGetMonitors() + */ + + @Override + public Displays getDisplays() { + PointerBuffer displays = glfwGetMonitors(); + long primary = glfwGetPrimaryMonitor(); + Displays displayList = new Displays(); + + for (int i = 0; i < displays.limit(); i++) { + long monitorI = displays.get(i); + int monPos = displayList.addNewMonitor(monitorI); + //lets check if this display is the primary display. If use mark it as such. + if (primary == monitorI) displayList.setPrimaryDisplay(monPos); + + final GLFWVidMode modes = glfwGetVideoMode(monitorI); + String name = glfwGetMonitorName(monitorI); + + int width = modes.width(); + int height = modes.height(); + int rate = modes.refreshRate(); + displayList.setInfo(monPos, name, width, height, rate); + LOGGER.log( + Level.INFO, + "Display id: " + monitorI + " Resolution: " + width + " x " + height + " @ " + rate + ); + } + return displayList; + } } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java new file mode 100644 index 0000000000..3161f00a9b --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/Sync.java @@ -0,0 +1,173 @@ +/* + * Copyright (c) 2002-2012 LWJGL Project + * 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 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.lwjgl; + +/** +* A highly accurate sync method that continually adapts to the system +* it runs on to provide reliable results. +* +* @author Riven +* @author kappaOne +*/ +class Sync { + + /** number of nanoseconds in a second */ + private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L; + + /** The time to sleep/yield until the next frame */ + private static long nextFrame = 0; + + /** whether the initialisation code has run */ + private static boolean initialised = false; + + /** for calculating the averages the previous sleep/yield times are stored */ + private static RunningAvg sleepDurations = new RunningAvg(10); + private static RunningAvg yieldDurations = new RunningAvg(10); + + /** + * An accurate sync method that will attempt to run at a constant frame rate. + * It should be called once every frame. + * + * @param fps - the desired frame rate, in frames per second + */ + public static void sync(int fps) { + if (fps <= 0) return; + if (!initialised) initialise(); + + try { + // sleep until the average sleep time is greater than the time remaining till nextFrame + for (long t0 = getTime(), t1; (nextFrame - t0) > sleepDurations.avg(); t0 = t1) { + Thread.sleep(1); + sleepDurations.add((t1 = getTime()) - t0); // update average sleep time + } + + // slowly dampen sleep average if too high to avoid yielding too much + sleepDurations.dampenForLowResTicker(); + + // yield until the average yield time is greater than the time remaining till nextFrame + for (long t0 = getTime(), t1; (nextFrame - t0) > yieldDurations.avg(); t0 = t1) { + Thread.yield(); + yieldDurations.add((t1 = getTime()) - t0); // update average yield time + } + } catch (InterruptedException e) { + + } + + // schedule next frame, drop frame(s) if already too late for next frame + nextFrame = Math.max(nextFrame + NANOS_IN_SECOND / fps, getTime()); + } + + /** + * This method will initialise the sync method by setting initial + * values for sleepDurations/yieldDurations and nextFrame. + * + * If running on Windows, it will start the sleep timer fix. + */ + private static void initialise() { + initialised = true; + + sleepDurations.init(1000 * 1000); + yieldDurations.init((int) (-(getTime() - getTime()) * 1.333)); + + nextFrame = getTime(); + + + // On Windows, the sleep functions can be highly inaccurate by + // over 10ms making in unusable. However, it can be forced to + // be a bit more accurate by running a separate sleeping daemon + // thread. + Thread timerAccuracyThread = new Thread(new Runnable() { + @Override + public void run() { + try { + while(true)Thread.sleep(Long.MAX_VALUE); + } catch (Exception e) {} + } + }); + + timerAccuracyThread.setName("LWJGL Timer"); + timerAccuracyThread.setDaemon(true); + timerAccuracyThread.start(); + } + + + /** + * Gets the system time in nanoseconds. + * + * @return will return the current time in nano's + */ + private static long getTime() { + long time= System.currentTimeMillis()& 0x7FFFFFFFFFFFFFFFL; + long res= 1000; + return (time * NANOS_IN_SECOND) / res; + } + + private static class RunningAvg { + private final long[] slots; + private int offset; + + private static final long DAMPEN_THRESHOLD = 10 * 1000L * 1000L; // 10ms + private static final float DAMPEN_FACTOR = 0.9f; // don't change: 0.9f is exactly right! + + public RunningAvg(int slotCount) { + this.slots = new long[slotCount]; + this.offset = 0; + } + + public void init(long value) { + while (this.offset < this.slots.length) { + this.slots[this.offset++] = value; + } + } + + public void add(long value) { + this.slots[this.offset++ % this.slots.length] = value; + this.offset %= this.slots.length; + } + + public long avg() { + long sum = 0; + for (int i = 0; i < this.slots.length; i++) { + sum += this.slots[i]; + } + return sum / this.slots.length; + } + + public void dampenForLowResTicker() { + if (this.avg() > DAMPEN_THRESHOLD) { + for (int i = 0; i < this.slots.length; i++) { + this.slots[i] *= DAMPEN_FACTOR; + } + } + } + } +} \ No newline at end of file diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/WindowSizeListener.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/WindowSizeListener.java new file mode 100644 index 0000000000..2d2105b802 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjgl/WindowSizeListener.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.system.lwjgl; + +/** + * Listen to window size changes. Note, GLFW does not support registering multiple callbacks + * in {@link org.lwjgl.glfw.GLFW#glfwSetWindowSizeCallback(long, org.lwjgl.glfw.GLFWWindowSizeCallbackI)}, + * registering a new one will remove the previous one. Using this interface one can register + * multiple listeners. + * + * @author Ali-RS + */ +public interface WindowSizeListener { + + /** + * When registered by {@link LwjglWindow#registerWindowSizeListener(WindowSizeListener)}, + * it gets invoked on each glfw window size callback to notify the listener about changes + * in the window size. + * + * @param width the new window width. + * @param height the new window height. + */ + public void onWindowSizeChanged(final int width, final int height); + +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java new file mode 100644 index 0000000000..3f7be3bcf3 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxDefaultGLPlatform.java @@ -0,0 +1,66 @@ +/* + * 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.lwjglx; + +import org.lwjgl.system.Platform; +import static org.lwjgl.system.Platform.*; + +/** + * Class wjglxDefaultGLPlatform used to create a drawing platform. + * @author wil + */ +public final class LwjglxDefaultGLPlatform { + + /** + * Returns a drawing platform based on the platform it is running on. + * @return LwjglxGLPlatform + * @throws UnsupportedOperationException throws exception if platform is not supported + */ + public static LwjglxGLPlatform createLwjglxGLPlatform() throws UnsupportedOperationException { + switch (Platform.get()) { + case WINDOWS: + return new Win32GLPlatform(); + //case FREEBSD: -> In future versions of lwjgl3 (possibly) + case LINUX: + return new X11GLPlatform(); + case MACOSX: + return new MacOSXGLPlatform(); + default: + throw new UnsupportedOperationException("Platform " + Platform.get() + " not yet supported"); + } + } + + /** + * private constructor. + */ + private LwjglxDefaultGLPlatform() {} +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxGLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxGLPlatform.java new file mode 100644 index 0000000000..a04ad57923 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/LwjglxGLPlatform.java @@ -0,0 +1,48 @@ +/* + * 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.lwjglx; + +import org.lwjgl.opengl.awt.PlatformGLCanvas; + +/** + * Interface LwjglxGLPlatform; It is used to implement and manage + * the context of a specific platform. + * + * @author wil + */ +public interface LwjglxGLPlatform extends PlatformGLCanvas { + + /** + * Free the drawing surface. + */ + public void destroy(); +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java new file mode 100644 index 0000000000..bce9b4c6da --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/MacOSXGLPlatform.java @@ -0,0 +1,56 @@ +/* + * 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.lwjglx; + +import org.lwjgl.opengl.awt.PlatformMacOSXGLCanvas; +import static org.lwjgl.system.jawt.JAWTFunctions.*; + +/** + * MacOSXGLPlatform class that implements the {@link com.jme3.system.lwjglx.LwjglxGLPlatform} + * interface for the MacOS platform. + * + * @author wil + */ +final class MacOSXGLPlatform extends PlatformMacOSXGLCanvas implements LwjglxGLPlatform { + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() + */ + @Override + public void destroy() { + if (ds != null) { + JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface()); + awt.free(); + } + } + } diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/Win32GLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/Win32GLPlatform.java new file mode 100644 index 0000000000..9312606979 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/Win32GLPlatform.java @@ -0,0 +1,65 @@ +/* + * 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.lwjglx; + +import static org.lwjgl.system.jawt.JAWTFunctions.*; +import org.lwjgl.opengl.awt.PlatformWin32GLCanvas; + +/** + * Win32GLPlatform class that implements the {@link com.jme3.system.lwjglx.LwjglxGLPlatform} + * interface for the Windows (Win32) platform. + * + * @author wil + */ +final class Win32GLPlatform extends PlatformWin32GLCanvas implements LwjglxGLPlatform { + + /* (non-Javadoc) + * @see com.jme3.system.lwjglx.LwjglxGLPlatform#dispose() + */ + @Override + public void dispose() { + if (ds != null) { + super.dispose(); + } + } + + /* (non-Javadoc) + * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() + */ + @Override + public void destroy() { + if (ds != null) { + JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface()); + awt.free(); + } + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java new file mode 100644 index 0000000000..198b9b7317 --- /dev/null +++ b/jme3-lwjgl3/src/main/java/com/jme3/system/lwjglx/X11GLPlatform.java @@ -0,0 +1,123 @@ +/* + * 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.lwjglx; + +import org.lwjgl.opengl.awt.PlatformLinuxGLCanvas; +import org.lwjgl.system.jawt.*; + +import static org.lwjgl.system.MemoryUtil.*; +import static org.lwjgl.system.jawt.JAWTFunctions.*; + +/** + * Class X11GLPlatform; overrides the following methods: swapBuffers() + * and makeCurrent(long context). So that the canvas can be removed and + * added back from its parent component. + * + *

                  + * Works only for Linux based platforms + * + * @author wil + */ +final class X11GLPlatform extends PlatformLinuxGLCanvas implements LwjglxGLPlatform { + + /** + * (non-Javadoc) + * @see org.lwjgl.opengl.awt.PlatformGLCanvas#swapBuffers() + * @return boolean + */ + @Override + public boolean swapBuffers() { + // Get the drawing surface info + JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo()); + if (dsi == null) { + throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed"); + } + + try { + // Get the platform-specific drawing info + JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo()); + + // Set new values + display = dsi_x11.display(); + drawable = dsi_x11.drawable(); + + // Swap-Buffers + return super.swapBuffers(); + } finally { + JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo()); + } + } + + /** + * (non-Javadoc) + * @see org.lwjgl.opengl.awt.PlatformGLCanvas#makeCurrent(long) + * + * @param context long + * @return boolean + */ + @Override + public boolean makeCurrent(long context) { + // Get the drawing surface info + JAWTDrawingSurfaceInfo dsi = JAWT_DrawingSurface_GetDrawingSurfaceInfo(ds, ds.GetDrawingSurfaceInfo()); + if (dsi == null) { + throw new IllegalStateException("JAWT_DrawingSurface_GetDrawingSurfaceInfo() failed"); + } + + try { + // Get the platform-specific drawing info + JAWTX11DrawingSurfaceInfo dsi_x11 = JAWTX11DrawingSurfaceInfo.create(dsi.platformInfo()); + + // Set new values + display = dsi_x11.display(); + drawable = dsi_x11.drawable(); + + if (drawable == NULL) { + return false; + } + return super.makeCurrent(context); + } finally { + JAWT_DrawingSurface_FreeDrawingSurfaceInfo(dsi, ds.FreeDrawingSurfaceInfo()); + } + } + + /** + * (non-Javadoc) + * @see com.jme3.system.lwjglx.LwjglxGLPlatform#destroy() + */ + @Override + public void destroy() { + if (ds != null) { + JAWT_FreeDrawingSurface(ds, awt.FreeDrawingSurface()); + awt.free(); + } + } +} diff --git a/jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java b/jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java index 4c1c4bb3fc..77e6330696 100644 --- a/jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java +++ b/jme3-lwjgl3/src/main/java/com/jme3/util/LWJGLBufferAllocator.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.StampedLock; +import java.util.logging.Level; import java.util.logging.Logger; /** @@ -20,6 +21,21 @@ public class LWJGLBufferAllocator implements BufferAllocator { private static final Logger LOGGER = Logger.getLogger(LWJGLBufferAllocator.class.getName()); public static final String PROPERTY_CONCURRENT_BUFFER_ALLOCATOR = "com.jme3.lwjgl3.ConcurrentBufferAllocator"; + + /** + * The reference queue. + */ + private static final ReferenceQueue DUMMY_QUEUE = new ReferenceQueue<>(); + + /** + * The cleaner thread. + */ + private static final Thread CLEAN_THREAD = new Thread(LWJGLBufferAllocator::freeByteBuffers); + + /** + * The map with created deallocators. + */ + private static final Map DEALLOCATORS = new ConcurrentHashMap<>(); /** * Threadsafe implementation of the {@link LWJGLBufferAllocator}. @@ -63,11 +79,6 @@ Deallocator createDeallocator(final Long address, final ByteBuffer byteBuffer) { } } - /** - * The reference queue. - */ - private static final ReferenceQueue DUMMY_QUEUE = new ReferenceQueue<>(); - /** * The LWJGL byte buffer deallocator. */ @@ -128,19 +139,9 @@ protected void freeMemory() { } } - /** - * The cleaner thread. - */ - private static final Thread CLEAN_THREAD = new Thread(LWJGLBufferAllocator::freeByteBuffers); - - /** - * The map with created deallocators. - */ - private static final Map DEALLOCATORS = new ConcurrentHashMap<>(); - static { CLEAN_THREAD.setDaemon(true); - CLEAN_THREAD.setName("Thread to free LWJGL byte buffers"); + CLEAN_THREAD.setName("LWJGL Deallocator"); CLEAN_THREAD.start(); } @@ -164,7 +165,7 @@ public void destroyDirectBuffer(final Buffer buffer) { final long address = getAddress(buffer); if (address == -1) { - LOGGER.warning("Not found address of the " + buffer); + LOGGER.log(Level.WARNING, "Not found address of the {0}", buffer); return; } @@ -172,7 +173,7 @@ public void destroyDirectBuffer(final Buffer buffer) { final Deallocator deallocator = DEALLOCATORS.remove(address); if (deallocator == null) { - LOGGER.warning("Not found a deallocator for address " + address); + LOGGER.log(Level.WARNING, "Not found a deallocator for address {0}", address); return; } @@ -210,7 +211,7 @@ long getAddress(final Buffer buffer) { @Override public ByteBuffer allocate(final int size) { - final Long address = MemoryUtil.nmemAlloc(size); + final Long address = MemoryUtil.nmemCalloc(size, 1); final ByteBuffer byteBuffer = MemoryUtil.memByteBuffer(address, size); DEALLOCATORS.put(address, createDeallocator(address, byteBuffer)); return byteBuffer; diff --git a/jme3-networking/build.gradle b/jme3-networking/build.gradle index 0a77d7d720..510ad429c5 100644 --- a/jme3-networking/build.gradle +++ b/jme3-networking/build.gradle @@ -1,7 +1,10 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' +dependencies { + api project(':jme3-core') } -dependencies { - compile project(':jme3-core') +javadoc { + // Disable doclint for JDK8+. + if (JavaVersion.current().isJava8Compatible()){ + options.addStringOption('Xdoclint:none', '-quiet') + } } diff --git a/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java b/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java index 5a13a68239..2fe7b20b01 100644 --- a/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/AbstractMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,6 +57,7 @@ protected AbstractMessage( boolean reliable ) * Sets this message to 'reliable' or not and returns this * message. */ + @Override public Message setReliable(boolean f) { this.reliable = f; @@ -67,6 +68,7 @@ public Message setReliable(boolean f) * Indicates which way an outgoing message should be sent * or which way an incoming message was sent. */ + @Override public boolean isReliable() { return reliable; diff --git a/jme3-networking/src/main/java/com/jme3/network/Client.java b/jme3-networking/src/main/java/com/jme3/network/Client.java index 3ef9134dfe..28c966cb8f 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Client.java +++ b/jme3-networking/src/main/java/com/jme3/network/Client.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -90,12 +90,14 @@ public interface Client extends MessageConnection /** * Sends a message to the server. */ + @Override public void send( Message message ); /** * Sends a message to the other end of the connection using * the specified alternate channel. */ + @Override public void send( int channel, Message message ); /** diff --git a/jme3-networking/src/main/java/com/jme3/network/Filters.java b/jme3-networking/src/main/java/com/jme3/network/Filters.java index 1cc45e0170..be21aeb1bc 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Filters.java +++ b/jme3-networking/src/main/java/com/jme3/network/Filters.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,17 @@ */ public class Filters { + /** + * A private constructor to inhibit instantiation of this class. + */ + private Filters() { + } + /** * Creates a filter that returns true for any value in the specified * list of values and false for all other cases. */ + @SuppressWarnings("unchecked") public static Filter in( T... values ) { return in( new HashSet(Arrays.asList(values)) ); @@ -66,6 +73,7 @@ public static Filter in( Collection collection ) * list of values and false for all other cases. This is the equivalent * of calling not(in(values)). */ + @SuppressWarnings("unchecked") public static Filter notIn( T... values ) { return not( in( values ) ); @@ -118,6 +126,7 @@ public EqualToFilter( T value ) this.value = value; } + @Override public boolean apply( T input ) { return value == input || (value != null && value.equals(input)); @@ -133,6 +142,7 @@ public InFilter( Collection collection ) this.collection = collection; } + @Override public boolean apply( T input ) { return collection.contains(input); @@ -148,6 +158,7 @@ public NotFilter( Filter delegate ) this.delegate = delegate; } + @Override public boolean apply( T input ) { return !delegate.apply(input); diff --git a/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java b/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java index 263011820d..1aaf517301 100644 --- a/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java +++ b/jme3-networking/src/main/java/com/jme3/network/HostedConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -76,7 +76,7 @@ public interface HostedConnection extends MessageConnection public Object setAttribute( String name, Object value ); /** - * Retrieves a previosly stored session attribute or + * Retrieves a previously stored session attribute or * null if no such attribute exists. */ public T getAttribute( String name ); diff --git a/jme3-networking/src/main/java/com/jme3/network/Network.java b/jme3-networking/src/main/java/com/jme3/network/Network.java index 9dac8eae85..8e05e4c2f0 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Network.java +++ b/jme3-networking/src/main/java/com/jme3/network/Network.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,6 +53,12 @@ public class Network public static final String DEFAULT_GAME_NAME = "Unnamed jME3 Game"; public static final int DEFAULT_VERSION = 42; + /** + * A private constructor to inhibit instantiation of this class. + */ + private Network() { + } + /** * Creates a Server that will utilize both reliable and fast * transports to communicate with clients. The specified port @@ -80,7 +86,7 @@ public static Server createServer( int tcpPort, int udpPort ) throws IOException * * @param gameName This is the name that identifies the game. Connecting clients * must use this name or be turned away. - * @param version This is a game-specific verison that helps detect when out-of-date + * @param version This is a game-specific version that helps detect when out-of-date * clients have connected to an incompatible server. * @param tcpPort The port upon which the TCP hosting will listen for new connections. * @param udpPort The port upon which the UDP hosting will listen for new 'fast' UDP @@ -142,12 +148,12 @@ public static Client connectToServer( String gameName, int version, } /** - * Creates a Client that communicates with the specified host and and separate TCP and UDP ports + * Creates a Client that communicates with the specified host and separate TCP and UDP ports * using both reliable and fast transports. * * @param gameName This is the name that identifies the game. This must match * the target server's name or this client will be turned away. - * @param version This is a game-specific verison that helps detect when out-of-date + * @param version This is a game-specific version that helps detect when out-of-date * clients have connected to an incompatible server. This must match * the server's version of this client will be turned away. * @param hostPort The remote TCP port on the server to which this client should @@ -175,11 +181,13 @@ public NetworkClientImpl(String gameName, int version) super( gameName, version ); } + @Override public void connectToServer( String host, int port, int remoteUdpPort ) throws IOException { connectToServer( InetAddress.getByName(host), port, remoteUdpPort ); } + @Override public void connectToServer( InetAddress address, int port, int remoteUdpPort ) throws IOException { UdpConnector fast = new UdpConnector( address, remoteUdpPort ); diff --git a/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java b/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java index 2f2a8e70e7..846b283f22 100644 --- a/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/NetworkClient.java @@ -53,7 +53,7 @@ public interface NetworkClient extends Client /** * Connects this client to the specified remote server and ports. * - * @param address The hosts internet address. + * @param address The host's Internet address. * @param port The remote TCP port on the server to which this client should * send reliable messages. * @param remoteUdpPort The remote UDP port on the server to which this client should diff --git a/jme3-networking/src/main/java/com/jme3/network/Server.java b/jme3-networking/src/main/java/com/jme3/network/Server.java index a52cb33bfc..d5f40f1b23 100644 --- a/jme3-networking/src/main/java/com/jme3/network/Server.java +++ b/jme3-networking/src/main/java/com/jme3/network/Server.java @@ -100,7 +100,7 @@ public interface Server public void broadcast( int channel, Filter filter, Message message ); /** - * Start the server so that it will began accepting new connections + * Start the server so that it will begin accepting new connections * and processing messages. */ public void start(); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java index 100747ffd7..1e434806c1 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/ConnectorAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -101,7 +101,7 @@ public ConnectorAdapter( Connector connector, MessageProtocol protocol, // if a TCP outbound queue fills to capacity and a client sends // in such a way that they block TCP message handling then if the HostedConnection // on the server is similarly blocked then the TCP network buffers may - // all get full and no outbound messages move and we forever block + // all get full, and no outbound messages move, and we forever block // on the queue. // However, in practice this can't really happen... or at least it's // the sign of other really bad things. @@ -152,6 +152,7 @@ protected void handleError( Exception e ) errorHandler.handleError( this, e ); } + @Override public void run() { MessageBuffer messageBuffer = protocol.createBuffer(); @@ -203,6 +204,7 @@ private void write( ByteBuffer data ) } } + @Override public void run() { while( go.get() ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java index eec00323fd..2bb3d48031 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ */ public class DefaultClient implements Client { - static final Logger log = Logger.getLogger(DefaultClient.class.getName()); + private static final Logger log = Logger.getLogger(DefaultClient.class.getName()); // First two channels are reserved for reliable and // unreliable. Note: channels are endpoint specific so these @@ -68,18 +68,18 @@ public class DefaultClient implements Client private static final int CH_UNRELIABLE = 1; private static final int CH_FIRST = 2; - private final ThreadLocal dataBuffer = new ThreadLocal(); + private final ThreadLocal dataBuffer = new ThreadLocal<>(); private int id = -1; private boolean isRunning = false; private final CountDownLatch connecting = new CountDownLatch(1); private String gameName; private int version; - private final MessageListenerRegistry messageListeners = new MessageListenerRegistry(); - private final List stateListeners = new CopyOnWriteArrayList(); - private final List> errorListeners = new CopyOnWriteArrayList>(); + private final MessageListenerRegistry messageListeners = new MessageListenerRegistry<>(); + private final List stateListeners = new CopyOnWriteArrayList<>(); + private final List> errorListeners = new CopyOnWriteArrayList<>(); private final Redispatch dispatcher = new Redispatch(); - private final List channels = new ArrayList(); + private final List channels = new ArrayList<>(); private ConnectorFactory connectorFactory; @@ -151,16 +151,16 @@ public void start() // This is used to match the TCP and UDP endpoints up on the // other end since they may take different routes to get there. // Behind NAT, many game clients may be coming over the same - // IP address from the server's perspective and they may have + // IP address from the server's perspective, and they may have // their UDP ports mapped all over the place. // // Since currentTimeMillis() is absolute time and nano time - // is roughtly related to system start time, adding these two + // is roughly related to system start time, adding these two // together should be plenty unique for our purposes. It wouldn't // hurt to reconcile with IP on the server side, though. long tempId = System.currentTimeMillis() + System.nanoTime(); - // Set it true here so we can send some messages. + // Set it true here, so we can send some messages. isRunning = true; ClientRegistrationMessage reg; @@ -410,12 +410,13 @@ protected void fireDisconnected( DisconnectInfo info ) * Either calls the ErrorListener or closes the connection * if there are no listeners. */ + @SuppressWarnings("unchecked") protected void handleError( Throwable t ) { // If there are no listeners then close the connection with // a reason if( errorListeners.isEmpty() ) { - log.log( Level.SEVERE, "Termining connection due to unhandled error", t ); + log.log( Level.SEVERE, "Terminating connection due to unhandled error", t ); DisconnectInfo info = new DisconnectInfo(); info.reason = "Connection Error"; info.error = t; @@ -481,8 +482,8 @@ protected void dispatch( Message m ) } return; } else if( m instanceof ChannelInfoMessage ) { - // This is an interum step in the connection process and - // now we need to add a bunch of connections + // This is an interim step in the connection process, and + // now we need to add a bunch of connections. configureChannels( ((ChannelInfoMessage)m).getId(), ((ChannelInfoMessage)m).getPorts() ); return; } else if( m instanceof DisconnectMessage ) { @@ -514,7 +515,7 @@ public void messageReceived( Object source, Message m ) public void handleError( Object source, Throwable t ) { // Only doing the DefaultClient.this to make the code - // checker happy... it compiles fine without it but I + // checker happy... it compiles fine without it, but I // don't like red lines in my editor. :P DefaultClient.this.handleError( t ); } diff --git a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java index 3040fdcdae..1364e2b179 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/DefaultServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,7 @@ */ public class DefaultServer implements Server { - static final Logger log = Logger.getLogger(DefaultServer.class.getName()); + private static final Logger log = Logger.getLogger(DefaultServer.class.getName()); // First two channels are reserved for reliable and // unreliable @@ -73,20 +73,20 @@ public class DefaultServer implements Server private final KernelFactory kernelFactory = KernelFactory.DEFAULT; private KernelAdapter reliableAdapter; private KernelAdapter fastAdapter; - private final List channels = new ArrayList(); - private final List alternatePorts = new ArrayList(); + private final List channels = new ArrayList<>(); + private final List alternatePorts = new ArrayList<>(); private final Redispatch dispatcher = new Redispatch(); - private final Map connections = new ConcurrentHashMap(); + private final Map connections = new ConcurrentHashMap<>(); private final Map endpointConnections - = new ConcurrentHashMap(); + = new ConcurrentHashMap<>(); // Keeps track of clients for whom we've only received the UDP // registration message - private final Map connecting = new ConcurrentHashMap(); + private final Map connecting = new ConcurrentHashMap<>(); private final MessageListenerRegistry messageListeners - = new MessageListenerRegistry(); - private final List connectionListeners = new CopyOnWriteArrayList(); + = new MessageListenerRegistry<>(); + private final List connectionListeners = new CopyOnWriteArrayList<>(); private HostedServiceManager services; private MessageProtocol protocol = new SerializerMessageProtocol(); @@ -94,7 +94,7 @@ public class DefaultServer implements Server public DefaultServer( String gameName, int version, Kernel reliable, Kernel fast ) { if( reliable == null ) - throw new IllegalArgumentException( "Default server reqiures a reliable kernel instance." ); + throw new IllegalArgumentException( "Default server requires a reliable kernel instance." ); this.gameName = gameName; this.version = version; @@ -139,7 +139,7 @@ public int addChannel( int port ) throw new IllegalStateException( "Channels cannot be added once server is started." ); // Note: it does bug me that channels aren't 100% universal and - // setup externally but it requires a more invasive set of changes + // set up externally, but it requires a more invasive set of changes // for "connection types" and some kind of registry of kernel and // connector factories. This really would be the best approach and // would allow all kinds of channel customization maybe... but for @@ -210,14 +210,14 @@ public void close() services.stop(); try { - // Kill the adpaters, they will kill the kernels + // Kill the adapters, they will kill the kernels for( KernelAdapter ka : channels ) { ka.close(); } isRunning = false; - // Now terminate all of the services + // Now terminate all of the services. services.terminate(); } catch( InterruptedException e ) { throw new RuntimeException( "Interrupted while closing", e ); @@ -287,7 +287,7 @@ public boolean hasConnections() @Override public Collection getConnections() { - return Collections.unmodifiableCollection((Collection)connections.values()); + return Collections.unmodifiableCollection(connections.values()); } @Override @@ -368,8 +368,8 @@ protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationM { Connection addedConnection = null; - // generally this will only be called by one thread but it's - // important enough I won't take chances + // Generally this will only be called by one thread, but it's + // so important that I won't take chances. synchronized( this ) { // Grab the random ID that the client created when creating // its two registration messages @@ -389,8 +389,8 @@ protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationM c.setChannel(channel, p); log.log( Level.FINE, "Setting up channel:{0}", channel ); - // If it's channel 0 then this is the initial connection - // and we will send the connection information + // If it's channel 0, then this is the initial connection, + // and we will send the connection information. if( channel == CH_RELIABLE ) { // Validate the name and version which is only sent // over the reliable connection at this point. @@ -426,8 +426,8 @@ protected void registerClient( KernelAdapter ka, Endpoint p, ClientRegistrationM addedConnection = c; } } else { - // Need to keep getting channels so we'll keep it in - // the map + // Need to keep getting channels, so we'll keep it in + // the map. connecting.put(tempId, c); } } @@ -523,7 +523,7 @@ protected class Connection implements HostedConnection private Endpoint[] channels; private int setChannelCount = 0; - private final Map sessionData = new ConcurrentHashMap(); + private final Map sessionData = new ConcurrentHashMap<>(); public Connection( int channelCount ) { @@ -629,12 +629,12 @@ public void close( String reason ) send( m ); // Just close the reliable endpoint - // fast will be cleaned up as a side-effect + // fast. Will be cleaned up as a side effect // when closeConnection() is called by the // connectionClosed() endpoint callback. if( channels[CH_RELIABLE] != null ) { - // Close with flush so we make sure our - // message gets out + // Close with flush to ensure our + // message gets out. channels[CH_RELIABLE].close(true); } } diff --git a/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java b/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java index cb2dd21929..598858723d 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/KernelAdapter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,7 +64,7 @@ */ public class KernelAdapter extends Thread { - static Logger log = Logger.getLogger(KernelAdapter.class.getName()); + private static final Logger log = Logger.getLogger(KernelAdapter.class.getName()); private DefaultServer server; // this is unfortunate private Kernel kernel; @@ -139,7 +139,7 @@ protected HostedConnection getConnection( Endpoint p ) protected void connectionClosed( Endpoint p ) { // Remove any message buffer we've been accumulating - // on behalf of this endpoing + // on behalf of this endpoint messageBuffers.remove(p); log.log( Level.FINE, "Buffers size:{0}", messageBuffers.size() ); @@ -196,9 +196,9 @@ protected void dispatch( Endpoint p, Message m ) protected MessageBuffer getMessageBuffer( Endpoint p ) { if( !reliable ) { - // Since UDP comes in packets and they aren't split + // Since UDP comes in packets, and they aren't split // up, there is no reason to buffer. In fact, there would - // be a down side because there is no way for us to reliably + // be a downside because there is no way for us to reliably // clean these up later since we'd create another one for // any random UDP packet that comes to the port. return protocol.createBuffer(); @@ -231,12 +231,12 @@ protected void createAndDispatch( Envelope env ) for( int i = 0; i < len; i++ ) { sb.append( "[" + Integer.toHexString(data[i]) + "]" ); } - log.log( Level.FINE, "First 10 bytes of incomplete nessage:" + sb ); + log.log( Level.FINE, "First 10 bytes of incomplete message:" + sb ); throw new RuntimeException( "Envelope contained incomplete data:" + env ); } } - // Should be complete... and maybe we should check but we don't + // Should be complete... and maybe we should check, but we don't. Message m = null; while( (m = protocol.pollMessage()) != null ) { m.setReliable(reliable); @@ -264,6 +264,7 @@ protected void flushEvents() } } + @Override public void run() { while( go.get() ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java index 4a2e404917..4f75737e21 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/MessageListenerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,11 +50,11 @@ */ public class MessageListenerRegistry implements MessageListener { - static final Logger log = Logger.getLogger(MessageListenerRegistry.class.getName()); + private static final Logger log = Logger.getLogger(MessageListenerRegistry.class.getName()); - private final List> listeners = new CopyOnWriteArrayList>(); + private final List> listeners = new CopyOnWriteArrayList<>(); private final Map>> typeListeners - = new ConcurrentHashMap>>(); + = new ConcurrentHashMap<>(); public MessageListenerRegistry() { diff --git a/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java index 024c4d0099..6f1a552c6b 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/NioKernelFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,6 +45,7 @@ */ public class NioKernelFactory implements KernelFactory { + @Override public Kernel createKernel( int channel, int port ) throws IOException { return new SelectorKernel(port); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java b/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java index 1166dd32e0..5a952e040c 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/TcpConnectorFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,6 +52,7 @@ public TcpConnectorFactory( InetAddress remoteAddress ) this.remoteAddress = remoteAddress; } + @Override public Connector createConnector( int channel, int port ) throws IOException { return new SocketConnector( remoteAddress, port ); diff --git a/jme3-networking/src/main/java/com/jme3/network/base/protocol/GreedyMessageBuffer.java b/jme3-networking/src/main/java/com/jme3/network/base/protocol/GreedyMessageBuffer.java index 14831dfda9..0bdbf6f328 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/protocol/GreedyMessageBuffer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/protocol/GreedyMessageBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,6 @@ package com.jme3.network.base.protocol; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.LinkedList; @@ -46,9 +45,9 @@ * all messages as byte data comes in. In other words, if there * are four messages in the ByteBuffer passed to addBuffer() then * all of the messages will be deserialized during that call and - * queued up for later return. The down side is that if any of + * queued up for later return. The downside is that, if any of * those messages was going to alter the MessageProtocol serialization - * behavior in a way that affects later messages then problems occur + * behavior in a way that affects later messages, then problems occur * when those messages are all in one block. * * @author Paul Speed @@ -56,7 +55,7 @@ public class GreedyMessageBuffer implements MessageBuffer { private MessageProtocol protocol; - private final LinkedList messages = new LinkedList(); + private final LinkedList messages = new LinkedList<>(); private ByteBuffer current; private int size; private Byte carry; @@ -69,6 +68,7 @@ public GreedyMessageBuffer( MessageProtocol protocol ) { * Returns the next message in the buffer or null if there are no more * messages in the buffer. */ + @Override public Message pollMessage() { if( messages.isEmpty() ) { return null; @@ -79,6 +79,7 @@ public Message pollMessage() { /** * Returns true if there is a message waiting in the buffer. */ + @Override public boolean hasMessages() { return !messages.isEmpty(); } @@ -87,6 +88,7 @@ public boolean hasMessages() { * Adds byte data to the message buffer. Returns true if there is * a message waiting after this call. */ + @Override public boolean addBytes( ByteBuffer buffer ) { // push the data from the buffer into as // many messages as we can @@ -94,8 +96,8 @@ public boolean addBytes( ByteBuffer buffer ) { if( current == null ) { - // If we have a left over carry then we need to - // do manual processing to get the short value + // If we have a left-over carry, + // extra processing is needed to get the short value. if( carry != null ) { byte high = carry; byte low = buffer.get(); @@ -108,8 +110,8 @@ else if( buffer.remaining() < 2 ) { // byte in it... and in that case we will get an underflow // when attempting to read the short below. - // It has to be 1 or we'd never get here... but one - // isn't enough so we stash it away. + // It has to be 1, or we'd never get here... but one + // isn't enough, so we stash it away. carry = buffer.get(); break; } else { diff --git a/jme3-networking/src/main/java/com/jme3/network/base/protocol/LazyMessageBuffer.java b/jme3-networking/src/main/java/com/jme3/network/base/protocol/LazyMessageBuffer.java index fc66cb148f..a52799aac6 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/protocol/LazyMessageBuffer.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/protocol/LazyMessageBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -32,7 +32,6 @@ package com.jme3.network.base.protocol; -import java.io.IOException; import java.nio.ByteBuffer; import java.util.LinkedList; @@ -53,7 +52,7 @@ public class LazyMessageBuffer implements MessageBuffer { private MessageProtocol protocol; - private final LinkedList messages = new LinkedList(); + private final LinkedList messages = new LinkedList<>(); private ByteBuffer current; private int size; private Byte carry; @@ -66,6 +65,7 @@ public LazyMessageBuffer( MessageProtocol protocol ) { * Returns the next message in the buffer or null if there are no more * messages in the buffer. */ + @Override public Message pollMessage() { if( messages.isEmpty() ) { return null; @@ -77,6 +77,7 @@ public Message pollMessage() { /** * Returns true if there is a message waiting in the buffer. */ + @Override public boolean hasMessages() { return !messages.isEmpty(); } @@ -85,6 +86,7 @@ public boolean hasMessages() { * Adds byte data to the message buffer. Returns true if there is * a message waiting after this call. */ + @Override public boolean addBytes( ByteBuffer buffer ) { // push the data from the buffer into as // many messages as we can @@ -92,8 +94,8 @@ public boolean addBytes( ByteBuffer buffer ) { if( current == null ) { - // If we have a left over carry then we need to - // do manual processing to get the short value + // If we have a leftover carry, then we need to + // do manual processing to get the short value. if( carry != null ) { byte high = carry; byte low = buffer.get(); @@ -106,14 +108,14 @@ else if( buffer.remaining() < 2 ) { // byte in it... and in that case we will get an underflow // when attempting to read the short below. - // It has to be 1 or we'd never get here... but one - // isn't enough so we stash it away. + // It has to be 1 or we'd never get here. But one + // isn't enough, so we stash it away. carry = buffer.get(); break; } else { - // We are not currently reading an object so + // We are not currently reading an object, so // grab the size. - // Note: this is somewhat limiting... int would + // Note: this is somewhat limiting. int would // be better. size = buffer.getShort(); } @@ -146,7 +148,7 @@ else if( buffer.remaining() < 2 ) { // Note: I originally thought that lazy deserialization was // going to be tricky/fancy because I'd imagined having to - // collect partial buffers, leave data in working buffers, etc.. + // collect partial buffers, leave data in working buffers, etcetera. // However, the buffer we are passed is reused by the caller // (it's part of the API contract) and so we MUST copy the // data into "something" before returning. We already know @@ -154,13 +156,13 @@ else if( buffer.remaining() < 2 ) { // change. We are already creating per-message byte buffers. // ...so we might as well just buffer this in our queue instead. // The alternative is to somehow have an open-ended working buffer - // that expands/shrinks as needed to accomodate the 'unknown' number + // that expands/shrinks as needed to accommodate the 'unknown' number // of messages that must be buffered before the caller asks for // one. Obviously, that's way more wasteful than just keeping // per-message byte buffers around. We already had them anyway. // So in the end, I probably could have just altered the original // buffering code and called it a day... but I had to do the refactoring - // before I figured that out and now we have the ability to more easily + // before I figured that out, and now we have the ability to more easily // swap out protocol implementations. -pspeed:2019-09-08 } else { // Not yet a complete object so just copy what we have diff --git a/jme3-networking/src/main/java/com/jme3/network/base/protocol/SerializerMessageProtocol.java b/jme3-networking/src/main/java/com/jme3/network/base/protocol/SerializerMessageProtocol.java index 80493660ee..1d3cee2cc6 100644 --- a/jme3-networking/src/main/java/com/jme3/network/base/protocol/SerializerMessageProtocol.java +++ b/jme3-networking/src/main/java/com/jme3/network/base/protocol/SerializerMessageProtocol.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,8 +33,6 @@ import java.io.IOException; import java.nio.ByteBuffer; -import java.util.LinkedList; - import com.jme3.network.Message; import com.jme3.network.base.MessageBuffer; import com.jme3.network.base.MessageProtocol; @@ -61,6 +59,7 @@ public SerializerMessageProtocol() { * and the (short length) + data protocol. If target is null * then a 32k byte buffer will be created and filled. */ + @Override public ByteBuffer toByteBuffer( Message message, ByteBuffer target ) { // Could let the caller pass their own in @@ -84,6 +83,7 @@ public ByteBuffer toByteBuffer( Message message, ByteBuffer target ) { * Creates and returns a message from the properly sized byte buffer * using com.jme3.network.serializing.Serializer. */ + @Override public Message toMessage( ByteBuffer bytes ) { try { return (Message)Serializer.readClassAndObject(bytes); @@ -92,6 +92,7 @@ public Message toMessage( ByteBuffer bytes ) { } } + @Override public MessageBuffer createBuffer() { // Defaulting to LazyMessageBuffer return new LazyMessageBuffer(this); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java index 7f992e94ab..d953b972d4 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/AbstractKernel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ */ public abstract class AbstractKernel implements Kernel { - static Logger log = Logger.getLogger(AbstractKernel.class.getName()); + private static final Logger log = Logger.getLogger(AbstractKernel.class.getName()); private AtomicLong nextId = new AtomicLong(1); @@ -57,13 +57,13 @@ public abstract class AbstractKernel implements Kernel * Contains the pending endpoint events waiting for the caller * to retrieve them. */ - private ConcurrentLinkedQueue endpointEvents = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue endpointEvents = new ConcurrentLinkedQueue<>(); /** * Contains the pending envelopes waiting for the caller to * retrieve them. */ - private LinkedBlockingQueue envelopes = new LinkedBlockingQueue(); + private LinkedBlockingQueue envelopes = new LinkedBlockingQueue<>(); protected AbstractKernel() { @@ -73,7 +73,7 @@ protected void reportError( Exception e ) { // Should really be queued up so the outer thread can // retrieve them. For now we'll just log it. FIXME - log.log( Level.SEVERE, "Unhanddled kernel error", e ); + log.log( Level.SEVERE, "Unhandled kernel error", e ); } protected void wakeupReader() { @@ -82,7 +82,7 @@ protected void wakeupReader() { // envelopes. if( !hasEnvelopes() ) { // Note: this is not really a race condition. At worst, our - // event has already been handled by now and it does no harm + // event has already been handled, and it does no harm // to check again. addEnvelope( EVENTS_PENDING ); } @@ -96,6 +96,7 @@ protected long nextEndpointId() /** * Returns true if there are waiting envelopes. */ + @Override public boolean hasEnvelopes() { return !envelopes.isEmpty(); @@ -105,15 +106,17 @@ public boolean hasEnvelopes() * Removes one envelope from the received messages queue or * blocks until one is available. */ + @Override public Envelope read() throws InterruptedException { return envelopes.take(); } /** - * Removes and returnsn one endpoint event from the event queue or + * Removes and returns one endpoint event from the event queue or * null if there are no endpoint events. */ + @Override public EndpointEvent nextEvent() { return endpointEvents.poll(); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java index 55449eb86d..e05859daaa 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Endpoint.java @@ -80,7 +80,7 @@ public interface Endpoint /** * Closes this endpoint, optionally flushing any queued * data before closing. As soon as this method is called, - * ne send() calls will fail with an exception... even while + * new send() calls will fail with an exception... even while * close() is still flushing the earlier queued messages. */ public void close(boolean flushData); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java b/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java index b43f2aaf33..86599da771 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/EndpointEvent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,6 +79,7 @@ public Type getType() return type; } + @Override public String toString() { return "EndpointEvent[" + type + ", " + endpoint + "]"; diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java index 9b92a1b6d7..3739c86118 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Envelope.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,6 +71,7 @@ public boolean isReliable() return reliable; } + @Override public String toString() { return "Envelope[" + source + ", " + (reliable?"reliable":"unreliable") + ", " + data.length + "]"; diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java index 91edba1982..97362c5847 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/Kernel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,7 +64,7 @@ public interface Kernel /** * Dispatches the data to all endpoints managed by the - * kernel that match the specified endpoint filter.. + * kernel that match the specified endpoint filter. * If 'copy' is true then the implementation will copy the byte buffer * before delivering it to endpoints. This allows the caller to reuse * the data buffer. Though it is important that the buffer not be changed @@ -86,7 +86,7 @@ public void broadcast( Filter filter, ByteBuffer data, boolean public Envelope read() throws InterruptedException; /** - * Removes and returnsn one endpoint event from the event queue or + * Removes and returns one endpoint event from the event queue or * null if there are no endpoint events. */ public EndpointEvent nextEvent(); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java b/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java index 59dedd8835..efc055d633 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/NamedThreadFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,6 +70,7 @@ public NamedThreadFactory( String name, boolean daemon, ThreadFactory delegate ) this.delegate = delegate; } + @Override public Thread newThread( Runnable r ) { Thread result = delegate.newThread(r); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java index 0a022758ff..b124bf7572 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/NioEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,7 +55,7 @@ public class NioEndpoint implements Endpoint private long id; private SocketChannel socket; private SelectorKernel kernel; - private ConcurrentLinkedQueue outbound = new ConcurrentLinkedQueue(); + private ConcurrentLinkedQueue outbound = new ConcurrentLinkedQueue<>(); private boolean closing = false; public NioEndpoint( SelectorKernel kernel, long id, SocketChannel socket ) @@ -65,16 +65,19 @@ public NioEndpoint( SelectorKernel kernel, long id, SocketChannel socket ) this.kernel = kernel; } + @Override public Kernel getKernel() { return kernel; } + @Override public void close() { close(false); } + @Override public void close( boolean flushData ) { if( flushData ) { @@ -97,16 +100,19 @@ public void close( boolean flushData ) } } + @Override public long getId() { return id; } + @Override public String getAddress() { return String.valueOf(socket.socket().getRemoteSocketAddress()); } + @Override public boolean isConnected() { return socket.isConnected(); @@ -162,6 +168,7 @@ protected boolean hasPending() return !outbound.isEmpty(); } + @Override public void send( ByteBuffer data ) { if( data == null ) { @@ -173,6 +180,7 @@ public void send( ByteBuffer data ) send( data, true, true ); } + @Override public String toString() { return "NioEndpoint[" + id + ", " + socket + "]"; diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java index 4db90f68b0..50c432310e 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SelectorKernel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -56,12 +56,12 @@ */ public class SelectorKernel extends AbstractKernel { - static Logger log = Logger.getLogger(SelectorKernel.class.getName()); + private static final Logger log = Logger.getLogger(SelectorKernel.class.getName()); private InetSocketAddress address; private SelectorThread thread; - private Map endpoints = new ConcurrentHashMap(); + private Map endpoints = new ConcurrentHashMap<>(); public SelectorKernel( InetAddress host, int port ) { @@ -83,6 +83,7 @@ protected SelectorThread createSelectorThread() return new SelectorThread(); } + @Override public void initialize() { if( thread != null ) @@ -98,6 +99,7 @@ public void initialize() } } + @Override public void terminate() throws InterruptedException { if( thread == null ) @@ -114,6 +116,7 @@ public void terminate() throws InterruptedException } } + @Override public void broadcast( Filter filter, ByteBuffer data, boolean reliable, boolean copy ) { @@ -181,7 +184,7 @@ protected void closeEndpoint( NioEndpoint p ) throws IOException } /** - * Used internally by the endpoints to wakeup the selector + * Used internally by the endpoints to wake up the selector * when they have data to send. */ protected void wakeupSelector() @@ -193,7 +196,7 @@ protected void newData( NioEndpoint p, SocketChannel c, ByteBuffer shared, int s { // Note: if ever desirable, it would be possible to accumulate // data per source channel and only 'finalize' it when - // asked for more envelopes then were ready. I just don't + // asked for more envelopes than were ready. I just don't // think it will be an issue in practice. The busier the // server, the more the buffers will fill before we get to them. // And if the server isn't busy, who cares if we chop things up @@ -202,7 +205,7 @@ protected void newData( NioEndpoint p, SocketChannel c, ByteBuffer shared, int s // Must copy the shared data before we use it byte[] dataCopy = new byte[size]; - System.arraycopy(shared.array(), 0, dataCopy, 0, size); + System.arraycopy(shared.array(), 0, dataCopy, 0, size); Envelope env = new Envelope( p, dataCopy, true ); addEnvelope( env ); @@ -224,7 +227,7 @@ protected class SelectorThread extends Thread * Because we want to keep the keys to ourselves, we'll do * the endpoint -> key mapping internally. */ - private Map endpointKeys = new ConcurrentHashMap(); + private Map endpointKeys = new ConcurrentHashMap<>(); public SelectorThread() { @@ -276,8 +279,8 @@ protected void setupSelectorOptions() // For now, selection keys will either be in OP_READ // or OP_WRITE. So while we are writing a buffer, we // will not be reading. This is way simpler and less - // error prone... it can always be changed when everything - // else works if we are looking to micro-optimize. + // error-prone. It can be changed when everything + // else works, if we are looking to micro-optimize. // Setup options based on the current state of // the endpoints. This could potentially be more @@ -296,7 +299,7 @@ protected void accept( SelectionKey key ) throws IOException // Would only get accepts on a server channel ServerSocketChannel serverChan = (ServerSocketChannel)key.channel(); - // Setup the connection to be non-blocking + // Set up the connection to be non-blocking. SocketChannel remoteChan = serverChan.accept(); remoteChan.configureBlocking(false); @@ -431,7 +434,7 @@ else if( key.isReadable() ) reportError( e ); // And at this level, errors likely mean the key is now - // dead and it doesn't hurt to kick them anyway. If we + // dead, and it doesn't hurt to kick them anyway. If we // find IOExceptions that are not fatal, this can be // readdressed cancel( key, (SocketChannel)key.channel() ); @@ -439,6 +442,7 @@ else if( key.isReadable() ) } } + @Override public void run() { log.log( Level.FINE, "Kernel started for connection:{0}.", address ); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java index 1cb2373032..be4682bd9e 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/tcp/SocketConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,6 +81,7 @@ protected void checkClosed() throw new ConnectorException( "Connection is closed:" + remoteAddress ); } + @Override public boolean isConnected() { if( sock == null ) @@ -88,6 +89,7 @@ public boolean isConnected() return sock.isConnected(); } + @Override public void close() { checkClosed(); @@ -101,6 +103,7 @@ public void close() } } + @Override public boolean available() { checkClosed(); @@ -111,6 +114,7 @@ public boolean available() } } + @Override public ByteBuffer read() { checkClosed(); @@ -135,6 +139,7 @@ public ByteBuffer read() } } + @Override public void write( ByteBuffer data ) { checkClosed(); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java index bf0f7ddb1a..642b9d7815 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpConnector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,12 +53,6 @@ public class UdpConnector implements Connector private byte[] buffer = new byte[65535]; private AtomicBoolean connected = new AtomicBoolean(false); - /** - * In order to provide proper available() checking, we - * potentially queue one datagram. - */ - private DatagramPacket pending; - /** * Creates a new UDP connection that send datagrams to the * specified address and port. @@ -81,6 +75,7 @@ protected void checkClosed() throw new ConnectorException( "Connection is closed:" + remoteAddress ); } + @Override public boolean isConnected() { if( sock == null ) @@ -88,6 +83,7 @@ public boolean isConnected() return sock.isConnected(); } + @Override public void close() { checkClosed(); @@ -101,6 +97,7 @@ public void close() * This always returns false since the simple DatagramSocket usage * cannot be run in a non-blocking way. */ + @Override public boolean available() { // It would take a separate thread or an NIO Selector based implementation to get this @@ -110,6 +107,7 @@ public boolean available() return false; } + @Override public ByteBuffer read() { checkClosed(); @@ -128,6 +126,7 @@ public ByteBuffer read() } } + @Override public void write( ByteBuffer data ) { checkClosed(); diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java index 0c915ff81f..a88833da3a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpEndpoint.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,6 +66,7 @@ public UdpEndpoint( UdpKernel kernel, long id, SocketAddress address, DatagramSo this.kernel = kernel; } + @Override public Kernel getKernel() { return kernel; @@ -76,11 +77,13 @@ protected SocketAddress getRemoteAddress() return address; } + @Override public void close() { close( false ); } + @Override public void close( boolean flush ) { // No real reason to flush UDP traffic yet... especially @@ -95,23 +98,27 @@ public void close( boolean flush ) } } + @Override public long getId() { return id; } + @Override public String getAddress() { return String.valueOf(address); } + @Override public boolean isConnected() { - // The socket is always unconnected anyway so we track our + // The socket is always unconnected anyway, so we track our // own logical state for the kernel's benefit. return connected; } + @Override public void send( ByteBuffer data ) { if( !isConnected() ) { @@ -139,6 +146,7 @@ public void send( ByteBuffer data ) } } + @Override public String toString() { return "UdpEndpoint[" + id + ", " + address + "]"; diff --git a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java index 57ea178647..2f9ac14a18 100644 --- a/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java +++ b/jme3-networking/src/main/java/com/jme3/network/kernel/udp/UdpKernel.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,7 @@ */ public class UdpKernel extends AbstractKernel { - static Logger log = Logger.getLogger(UdpKernel.class.getName()); + private static final Logger log = Logger.getLogger(UdpKernel.class.getName()); private InetSocketAddress address; private HostThread thread; @@ -62,7 +62,7 @@ public class UdpKernel extends AbstractKernel // The nature of UDP means that even through a firewall, // a user would have to have a unique address+port since UDP // can't really be NAT'ed. - private Map socketEndpoints = new ConcurrentHashMap(); + private Map socketEndpoints = new ConcurrentHashMap<>(); public UdpKernel( InetAddress host, int port ) { @@ -84,6 +84,7 @@ protected HostThread createHostThread() return new HostThread(); } + @Override public void initialize() { if( thread != null ) @@ -101,6 +102,7 @@ public void initialize() } } + @Override public void terminate() throws InterruptedException { if( thread == null ) @@ -122,6 +124,7 @@ public void terminate() throws InterruptedException * Dispatches the data to all endpoints managed by the * kernel. 'routing' is currently ignored. */ + @Override public void broadcast( Filter filter, ByteBuffer data, boolean reliable, boolean copy ) { @@ -180,8 +183,8 @@ protected void newData( DatagramPacket packet ) { // So the tricky part here is figuring out the endpoint and // whether it's new or not. In these UDP schemes, firewalls have - // to be ported back to a specific machine so we will consider - // the address + port (ie: SocketAddress) the defacto unique + // to be ported back to a specific machine, so we will consider + // the address + port (ie: SocketAddress) the de facto unique // ID. Endpoint p = getEndpoint( packet.getSocketAddress(), true ); @@ -209,6 +212,7 @@ public MessageWriter( Endpoint endpoint, DatagramPacket packet ) this.packet = packet; } + @Override public void run() { // Not guaranteed to always work but an extra datagram @@ -263,15 +267,16 @@ public void close() throws IOException, InterruptedException join(); } + @Override public void run() { log.log( Level.FINE, "Kernel started for connection:{0}.", address ); - // An atomic is safest and costs almost nothing + // An atomic is safest and costs almost nothing. while( go.get() ) { try { - // Could reuse the packet but I don't see the - // point and it may lead to subtle bugs if not properly + // Could reuse the packet, but I don't see the + // point, and it may lead to subtle bugs if not properly // reset. DatagramPacket packet = new DatagramPacket( buffer, buffer.length ); socket.receive(packet); diff --git a/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java index cbf4355656..231fe19ff6 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/ChannelInfoMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,6 +67,7 @@ public int[] getPorts() { return ports; } + @Override public String toString() { return "ChannelInfoMessage[" + id + ", " + Arrays.asList(ports) + "]"; } diff --git a/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java index d8c19a20c9..50272e9deb 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/ClientRegistrationMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -79,6 +79,7 @@ public int getVersion() { return version; } + @Override public String toString() { return getClass().getName() + "[id=" + id + ", gameName=" + gameName + ", version=" + version + "]"; } @@ -91,6 +92,7 @@ public String toString() { */ public static class ClientRegistrationSerializer extends Serializer { + @Override public ClientRegistrationMessage readObject( ByteBuffer data, Class c ) throws IOException { // Read the null/non-null marker @@ -106,6 +108,7 @@ public ClientRegistrationMessage readObject( ByteBuffer data, Class c ) throws I return msg; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { // Add the null/non-null marker diff --git a/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java index d514232f43..29f732768c 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/DisconnectMessage.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,6 +71,7 @@ public void setType(String type) { this.type = type; } + @Override public String toString() { return getClass().getName() + "[reason=" + reason + ", type=" + type + "]"; } @@ -83,6 +84,7 @@ public String toString() { */ public static class DisconnectSerializer extends Serializer { + @Override public DisconnectMessage readObject( ByteBuffer data, Class c ) throws IOException { // Read the null/non-null marker @@ -97,6 +99,7 @@ public DisconnectMessage readObject( ByteBuffer data, Class c ) throws IOExcepti return msg; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { // Add the null/non-null marker diff --git a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java index d9b51d39aa..331c7ab096 100644 --- a/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java +++ b/jme3-networking/src/main/java/com/jme3/network/message/SerializerRegistrationsMessage.java @@ -1,8 +1,33 @@ /* - * $Id: SerializerRegistrationsMessage.java 3829 2014-11-24 07:25:43Z pspeed $ - * - * Copyright (c) 2012, Paul Speed + * Copyright (c) 2012-2021 jMonkeyEngine * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.jme3.network.message; @@ -12,6 +37,7 @@ import com.jme3.network.serializing.Serializer; import com.jme3.network.serializing.SerializerRegistration; import com.jme3.network.serializing.serializers.FieldSerializer; +import java.lang.reflect.InvocationTargetException; import java.util.*; import java.util.jar.Attributes; import java.util.logging.Level; @@ -22,9 +48,9 @@ * Holds a compiled set of message registration information that * can be sent over the wire. The received message can then be * used to register all of the classes using the same IDs and - * same ordering, etc.. The intent is that the server compiles + * same ordering, etcetera. The intent is that the server compiles * this message once it is sure that all serializable classes have - * been registered. It can then send this to each new client and + * been registered. It can then send this to each new client, and * they can use it to register all of the classes without requiring * exactly reproducing the same calls that the server did to register * messages. @@ -40,11 +66,11 @@ @Serializable public class SerializerRegistrationsMessage extends AbstractMessage { - static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); + private static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); public static final Set ignore = new HashSet(); static { - // We could build this automatically but then we + // We could build this automatically, but then we // risk making a client and server out of date simply because // their JME versions are out of date. ignore.add(Boolean.class); @@ -108,7 +134,7 @@ public SerializerRegistrationsMessage( Registration... registrations ) { public static void compile() { // Let's just see what they are here - List list = new ArrayList(); + List list = new ArrayList<>(); for( SerializerRegistration reg : Serializer.getSerializerRegistrations() ) { Class type = reg.getType(); if( ignore.contains(type) ) @@ -143,7 +169,7 @@ public void registerAll() { // assume that if the registry was compiled here then it means // we are also the server process. Note that this wouldn't hold true // under complicated examples where there are clients of one server - // that also run their own servers but realistically they would have + // that also run their own servers, but realistically they would have // to disable the ServerSerializerRegistrationsServer anyway. if( compiled != null ) { log.log(Level.INFO, "Skipping registration as registry is locked, presumably by a local server process."); @@ -178,6 +204,7 @@ public Registration( SerializerRegistration reg ) { } } + @SuppressWarnings("unchecked") public void register() { try { Class type = Class.forName(className); @@ -186,15 +213,13 @@ public void register() { serializer = fieldSerializer; } else { Class serializerType = Class.forName(serializerClassName); - serializer = (Serializer)serializerType.newInstance(); + serializer = (Serializer) serializerType.getDeclaredConstructor().newInstance(); } SerializerRegistration result = Serializer.registerClassForId(id, type, serializer); log.log(Level.FINE, " result:{0}", result); } catch( ClassNotFoundException e ) { throw new RuntimeException( "Class not found attempting to register:" + this, e ); - } catch( InstantiationException e ) { - throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); - } catch( IllegalAccessException e ) { + } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | IllegalArgumentException | InvocationTargetException e) { throw new RuntimeException( "Error instantiating serializer registering:" + this, e ); } } diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java b/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java index 3c3a22530e..277756a080 100644 --- a/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/LocalObject.java @@ -34,7 +34,7 @@ import java.lang.reflect.Method; /** - * Describes a RMI interface on the local machine. + * Describes an RMI interface on the local machine. * * @author Kirill Vainer */ diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java index 05634b3ab2..3e820ff5b4 100644 --- a/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/ObjectStore.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,29 +72,32 @@ public String toString(){ // Local invocation ID counter private volatile short invocationIdCounter = 0; - // Invocations waiting .. - private IntMap pendingInvocations = new IntMap(); + // Invocations waiting + private IntMap pendingInvocations = new IntMap<>(); // Objects I share with other people - private IntMap localObjects = new IntMap(); + private IntMap localObjects = new IntMap<>(); // Objects others share with me - private HashMap remoteObjects = new HashMap(); - private IntMap remoteObjectsById = new IntMap(); + private HashMap remoteObjects = new HashMap<>(); + private IntMap remoteObjectsById = new IntMap<>(); private final Object receiveObjectLock = new Object(); public class ServerEventHandler implements MessageListener, ConnectionListener { + @Override public void messageReceived(HostedConnection source, Message m) { onMessage(source, m); } + @Override public void connectionAdded(Server server, HostedConnection conn) { onConnection(conn); } + @Override public void connectionRemoved(Server server, HostedConnection conn) { } @@ -103,14 +106,17 @@ public void connectionRemoved(Server server, HostedConnection conn) { public class ClientEventHandler implements MessageListener, ClientStateListener { + @Override public void messageReceived(Object source, Message m) { onMessage(null, m); } + @Override public void clientConnected(Client c) { onConnection(null); } + @Override public void clientDisconnected(Client c, DisconnectInfo info) { } @@ -123,6 +129,7 @@ public void clientDisconnected(Client c, DisconnectInfo info) { Serializer.registerClass(RemoteMethodReturnMessage.class, s); } + @SuppressWarnings("unchecked") public ObjectStore(Client client) { this.client = client; client.addMessageListener(clientEventHandler, @@ -157,7 +164,7 @@ public void exposeObject(String name, Object obj) throws IOException{ localObj.theObject = obj; //localObj.methods = obj.getClass().getMethods(); - ArrayList methodList = new ArrayList(); + ArrayList methodList = new ArrayList<>(); for (Method method : obj.getClass().getMethods()){ if (method.getDeclaringClass() == obj.getClass()){ methodList.add(method); @@ -181,6 +188,7 @@ public void exposeObject(String name, Object obj) throws IOException{ } } + @SuppressWarnings("unchecked") public T getExposedObject(String name, Class type, boolean waitFor) throws InterruptedException{ RemoteObject ro = remoteObjects.get(name); if (ro == null){ @@ -320,7 +328,7 @@ private void onMessage(HostedConnection source, Message message) { private void onConnection(HostedConnection conn) { if (localObjects.size() > 0){ - // send a object definition message + // send an object definition message ObjectDef[] defs = new ObjectDef[localObjects.size()]; int i = 0; for (Entry entry : localObjects){ diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java index 66f6c35312..9f46c11bdb 100644 --- a/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RemoteObject.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,7 +59,7 @@ public class RemoteObject implements InvocationHandler { * Maps from methods locally retrieved from the RMI interface to * a method ID. */ - HashMap methodMap = new HashMap(); + HashMap methodMap = new HashMap<>(); /** * The {@link ObjectStore} which stores this RMI interface. @@ -100,7 +100,7 @@ private boolean methodEquals(MethodDef methodDef, Method method){ */ public void loadMethods(Class interfaceClass){ HashMap> nameToMethods - = new HashMap>(); + = new HashMap<>(); for (Method method : interfaceClass.getDeclaredMethods()){ ArrayList list = nameToMethods.get(method.getName()); @@ -129,6 +129,7 @@ public void loadMethods(Class interfaceClass){ /** * Callback from InvocationHandler. */ + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return store.invokeRemoteMethod(this, method, args); } diff --git a/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java b/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java index 8f55178d02..5e28d0ecee 100644 --- a/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/rmi/RmiSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ public class RmiSerializer extends Serializer { private static final Logger logger = Logger.getLogger(RmiSerializer.class.getName()); - // not good for multithread applications + // not good for multithreaded applications private char[] chrBuf = new char[256]; private void writeString(ByteBuffer buffer, String string) throws IOException{ @@ -235,6 +235,7 @@ private RemoteMethodReturnMessage readMethodReturn(ByteBuffer buffer) throws IOE } @Override + @SuppressWarnings("unchecked") public T readObject(ByteBuffer data, Class c) throws IOException { if (c == RemoteObjectDefMessage.class){ return (T) readObjectDefs(data); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java index 4e560fbfaa..31d9c2c5ac 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/Serializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -64,7 +64,6 @@ public abstract class Serializer { private static final List registrations = new ArrayList(); private static final Serializer fieldSerializer = new FieldSerializer(); - private static final Serializer serializableSerializer = new SerializableSerializer(); private static final Serializer arraySerializer = new ArraySerializer(); private static short nextAvailableId = -2; // historically the first ID was always -2 @@ -81,7 +80,7 @@ public abstract class Serializer { public static void initialize() { - // Reset all of the inexes and tracking variables just in case + // Reset all of the indices and tracking variables just in case idRegistrations.clear(); classRegistrations.clear(); registrations.clear(); @@ -226,8 +225,8 @@ public static SerializerRegistration registerClassForId( short id, Class cls, Se } /** - * Registers the specified class. The failOnMiss flag controls whether or - * not this method returns null for failed registration or throws an exception. + * Registers the specified class. The failOnMiss flag controls whether + * this method returns null for failed registration or throws an exception. */ @SuppressWarnings("unchecked") public static SerializerRegistration registerClass(Class cls, boolean failOnMiss) { @@ -266,12 +265,12 @@ public static SerializerRegistration[] registerPackage(String pkgName) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); String path = pkgName.replace('.', '/'); Enumeration resources = classLoader.getResources(path); - List dirs = new ArrayList(); + List dirs = new ArrayList<>(); while (resources.hasMoreElements()) { URL resource = resources.nextElement(); dirs.add(new File(resource.getFile())); } - ArrayList classes = new ArrayList(); + ArrayList classes = new ArrayList<>(); for (File directory : dirs) { classes.addAll(findClasses(directory, pkgName)); } @@ -289,7 +288,7 @@ public static SerializerRegistration[] registerPackage(String pkgName) { } private static List findClasses(File dir, String pkgName) throws ClassNotFoundException { - List classes = new ArrayList(); + List classes = new ArrayList<>(); if (!dir.exists()) { return classes; } @@ -447,7 +446,7 @@ public static void writeClassAndObject(ByteBuffer buffer, Object object) throws // In that case, the SerializerRegistration object we get back isn't // really going to be capable of recreating the object on the other // end because it won't know what class to use. This only comes up - // in writeclassAndObejct() because we just wrote an ID to a more generic + // in writeClassAndObject() because we just wrote an ID to a more generic // class than will be readable on the other end. The check is simple, though. if( reg.getType() != object.getClass() ) { throw new IllegalArgumentException("Class has not been registered:" diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java index 373d969eb5..7b1de57384 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/SerializerRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -75,6 +75,7 @@ public Class getType() { return type; } + @Override public String toString() { return "SerializerRegistration[" + id + ", " + type + ", " + serializer + "]"; } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java index 564f4a06e3..ade05a59f8 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ArraySerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine, Java Game Networking + * Copyright (c) 2009-2020 jMonkeyEngine, Java Game Networking * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,6 +67,7 @@ private void collectDimensions (Object array, int dimension, int[] dimensions) { } } + @Override public T readObject(ByteBuffer data, Class c) throws IOException { byte dimensionCount = data.get(); if (dimensionCount == 0) @@ -89,6 +90,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { return array; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { if (object == null){ buffer.put((byte)0); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java index 2bfb8e62b6..7393edd1a3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/BooleanSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class BooleanSerializer extends Serializer { + @Override public Boolean readObject(ByteBuffer data, Class c) throws IOException { return data.get() == 1; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.put(((Boolean)object) ? (byte)1 : (byte)0); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java index 4e085bbe6a..9c36954f97 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ByteSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class ByteSerializer extends Serializer { + @Override public Byte readObject(ByteBuffer data, Class c) throws IOException { return data.get(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.put((Byte)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java index 274bc30db1..be32765658 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CharSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class CharSerializer extends Serializer { + @Override public Character readObject(ByteBuffer data, Class c) throws IOException { return data.getChar(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putChar((Character)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java index 960e8be10f..c0580e348e 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/CollectionSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,12 +48,13 @@ public class CollectionSerializer extends Serializer { @SuppressWarnings("unchecked") + @Override public T readObject(ByteBuffer data, Class c) throws IOException { int length = data.getInt(); Collection collection; try { - collection = (Collection)c.newInstance(); + collection = (Collection) c.getDeclaredConstructor().newInstance(); } catch (Exception e) { log.log(Level.FINE, "[Serializer][???] Could not determine collection type. Using ArrayList."); collection = new ArrayList(length); @@ -77,6 +78,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { return (T)collection; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { Collection collection = (Collection)object; int length = collection.size(); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java index 5bd8c465aa..7ce507a702 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DateSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,10 +44,12 @@ @SuppressWarnings("unchecked") public class DateSerializer extends Serializer { + @Override public Date readObject(ByteBuffer data, Class c) throws IOException { return new Date(data.getLong()); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putLong(((Date)object).getTime()); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java index cb7494d9e6..5472d7b065 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/DoubleSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class DoubleSerializer extends Serializer { + @Override public Double readObject(ByteBuffer data, Class c) throws IOException { return data.getDouble(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putDouble((Double)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java index 3b89f7d5a8..6740ea0389 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/EnumSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ * @author Lars Wesselius */ public class EnumSerializer extends Serializer { + @Override public T readObject(ByteBuffer data, Class c) throws IOException { try { int ordinal = data.getInt(); @@ -57,6 +58,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { } } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { if (object == null) { buffer.putInt(-1); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java index 0d410bb4dd..3178892b05 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FieldSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine, Java Game Networking + * Copyright (c) 2009-2021 jMonkeyEngine, Java Game Networking * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,11 +50,12 @@ */ public class FieldSerializer extends Serializer { - static final Logger log = Logger.getLogger(FieldSerializer.class.getName()); + private static final Logger log = Logger.getLogger(FieldSerializer.class.getName()); private static Map savedFields = new HashMap(); private static Map savedCtors = new HashMap(); + @SuppressWarnings("unchecked") protected void checkClass(Class clazz) { // See if the class has a public no-arg constructor @@ -80,11 +81,12 @@ protected void checkClass(Class clazz) { throw new RuntimeException( "Registration error: no-argument constructor not found on:" + clazz ); } + @Override public void initialize(Class clazz) { checkClass(clazz); - List fields = new ArrayList(); + List fields = new ArrayList<>(); Class processingClass = clazz; while (processingClass != Object.class ) { @@ -92,11 +94,10 @@ public void initialize(Class clazz) { processingClass = processingClass.getSuperclass(); } - List cachedFields = new ArrayList(fields.size()); + List cachedFields = new ArrayList<>(fields.size()); for (Field field : fields) { int modifiers = field.getModifiers(); if (Modifier.isTransient(modifiers)) continue; - if (Modifier.isFinal(modifiers)) continue; if (Modifier.isStatic(modifiers)) continue; if (field.isSynthetic()) continue; field.setAccessible(true); @@ -108,7 +109,7 @@ public void initialize(Class clazz) { // The type of this field is implicit in the outer class // definition and because the type is final, it can confidently // be determined on the other end. - // Note: passing false to this method has the side-effect that field.getType() + // Note: passing false to this method has the side effect that field.getType() // will be registered as a real class that can then be read/written // directly as any other registered class. It should be safe to take // an ID like this because Serializer.initialize() is only called @@ -123,6 +124,7 @@ public void initialize(Class clazz) { } Collections.sort(cachedFields, new Comparator() { + @Override public int compare (SavedField o1, SavedField o2) { return o1.field.getName().compareTo(o2.field.getName()); } @@ -133,6 +135,7 @@ public int compare (SavedField o1, SavedField o2) { } @SuppressWarnings("unchecked") + @Override public T readObject(ByteBuffer data, Class c) throws IOException { // Read the null/non-null marker @@ -143,7 +146,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { T object; try { - Constructor ctor = (Constructor)savedCtors.get(c); + Constructor ctor = savedCtors.get(c); object = ctor.newInstance(); } catch (Exception e) { throw new SerializerException( "Error creating object of type:" + c, e ); @@ -171,6 +174,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { return object; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { // Add the null/non-null marker diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java index 519b43550f..42e65487f7 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/FloatSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class FloatSerializer extends Serializer { + @Override public Float readObject(ByteBuffer data, Class c) throws IOException { return data.getFloat(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putFloat((Float)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java index 7579e84c57..63b264e6c6 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/GZIPSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,6 +49,7 @@ public class GZIPSerializer extends Serializer { @SuppressWarnings("unchecked") + @Override public T readObject(ByteBuffer data, Class c) throws IOException { try { @@ -77,6 +78,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { } } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { if (!(object instanceof GZIPCompressedMessage)) return; Message message = ((GZIPCompressedMessage)object).getMessage(); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java index db8a4954cb..373ab89dbd 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/IntSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class IntSerializer extends Serializer { + @Override public Integer readObject(ByteBuffer data, Class c) throws IOException { return data.getInt(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putInt((Integer)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java index 28ebb28caf..6545d4b4d0 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/LongSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,10 +43,12 @@ @SuppressWarnings("unchecked") public class LongSerializer extends Serializer { + @Override public Long readObject(ByteBuffer data, Class c) throws IOException { return data.getLong(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putLong((Long)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java index 4d41c57f0f..09043ac412 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/MapSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,12 +71,13 @@ public class MapSerializer extends Serializer { */ @SuppressWarnings("unchecked") + @Override public T readObject(ByteBuffer data, Class c) throws IOException { int length = data.getInt(); Map map; try { - map = (Map)c.newInstance(); + map = (Map) c.getDeclaredConstructor().newInstance(); } catch (Exception e) { log.log(Level.WARNING, "[Serializer][???] Could not determine map type. Using HashMap."); map = new HashMap(); @@ -124,6 +125,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { } @SuppressWarnings("unchecked") + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { Map map = (Map)object; int length = map.size(); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java index 32eb2af2e1..927053e557 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/SerializableSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -45,10 +45,12 @@ @SuppressWarnings("unchecked") public class SerializableSerializer extends Serializer { + @Override public Serializable readObject(ByteBuffer data, Class c) throws IOException { throw new UnsupportedOperationException( "Serializable serialization not supported." ); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { throw new UnsupportedOperationException( "Serializable serialization not supported." ); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java index 5131626667..c306335165 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ShortSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,10 +42,12 @@ */ @SuppressWarnings("unchecked") public class ShortSerializer extends Serializer { + @Override public Short readObject(ByteBuffer data, Class c) throws IOException { return data.getShort(); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { buffer.putShort((Short)object); } diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java index 3452bfa37d..2f3bd80027 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/StringSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -93,10 +93,12 @@ public static String readString( ByteBuffer data ) throws IOException { return new String(buffer, "UTF-8"); } + @Override public String readObject(ByteBuffer data, Class c) throws IOException { return readString(data); } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { String string = (String)object; diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java index 685dc5b9e5..8c4251acb3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/Vector3Serializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,6 +42,7 @@ @SuppressWarnings("unchecked") public class Vector3Serializer extends Serializer { + @Override public Vector3f readObject(ByteBuffer data, Class c) throws IOException { Vector3f vec3 = new Vector3f(); vec3.x = data.getFloat(); @@ -50,6 +51,7 @@ public Vector3f readObject(ByteBuffer data, Class c) throws IOException { return vec3; } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { Vector3f vec3 = (Vector3f) object; buffer.putFloat(vec3.x); diff --git a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java index ad7d7162d9..cb8bd2a189 100644 --- a/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java +++ b/jme3-networking/src/main/java/com/jme3/network/serializing/serializers/ZIPSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,6 +50,7 @@ public class ZIPSerializer extends Serializer { @SuppressWarnings("unchecked") + @Override public T readObject(ByteBuffer data, Class c) throws IOException { try { @@ -83,6 +84,7 @@ public T readObject(ByteBuffer data, Class c) throws IOException { } } + @Override public void writeObject(ByteBuffer buffer, Object object) throws IOException { if (!(object instanceof ZIPCompressedMessage)) return; diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java index 485712b954..e7b6724070 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedConnectionService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,15 +49,15 @@ * stopHostingOnConnection() methods to handle service and connection initialization.

                  * *

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

                  * * @author Paul Speed */ public abstract class AbstractHostedConnectionService extends AbstractHostedService { - static final Logger log = Logger.getLogger(AbstractHostedConnectionService.class.getName()); + private static final Logger log = Logger.getLogger(AbstractHostedConnectionService.class.getName()); private boolean autoHost; diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java index 051c0d2591..6c6e8d2130 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractHostedService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,7 +62,7 @@ protected Server getServer() { /** * Default implementation does nothing. Implementations can - * override this to peform custom new connection behavior. + * override this to perform custom new connection behavior. */ @Override public void connectionAdded(Server server, HostedConnection hc) { @@ -70,7 +70,7 @@ public void connectionAdded(Server server, HostedConnection hc) { /** * Default implementation does nothing. Implementations can - * override this to peform custom leaving connection behavior. + * override this to perform custom leaving connection behavior. */ @Override public void connectionRemoved(Server server, HostedConnection hc) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java index 3123e8c822..46ffb22916 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/AbstractService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,6 +60,7 @@ protected S getServiceManager() { * Retrieves the first sibling service of the specified * type. */ + @SuppressWarnings("unchecked") protected > T getService( Class type ) { return type.cast(serviceManager.getService(type)); } @@ -82,7 +83,7 @@ public final void initialize( S serviceManager ) { /** * Default implementation does nothing. Implementations can - * override this to peform custom startup behavior. + * override this to perform custom startup behavior. */ @Override public void start() { @@ -90,7 +91,7 @@ public void start() { /** * Default implementation does nothing. Implementations can - * override this to peform custom stop behavior. + * override this to perform custom stop behavior. */ @Override public void stop() { @@ -98,7 +99,7 @@ public void stop() { /** * Default implementation does nothing. Implementations can - * override this to peform custom termination behavior. + * override this to perform custom termination behavior. */ @Override public void terminate( S serviceManager ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java index dc204fb2f8..667e6fe578 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ClientServiceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,7 +60,7 @@ public Client getClient() { /** * Returns 'this' and is what is passed to ClientService.initialize() - * and ClientService.termnate(); + * and ClientService.terminate(); */ @Override protected final ClientServiceManager getParent() { @@ -76,7 +76,7 @@ public void addService( ClientService s ) { } /** - * Adds all of the specified ClientServices and initializes them. If the service manager + * Adds the specified services and initializes them. If the service manager * has already been started then the services will also be started. * This is a convenience method that delegates to addService(), thus each * service will be initialized (and possibly started) in sequence rather diff --git a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java index 3606ba44bb..f918729582 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/HostedServiceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,7 +67,7 @@ public Server getServer() { /** * Returns 'this' and is what is passed to HostedService.initialize() - * and HostedService.termnate(); + * and HostedService.terminate(); */ @Override protected final HostedServiceManager getParent() { @@ -83,7 +83,7 @@ public void addService( HostedService s ) { } /** - * Adds all of the specified HostedServices and initializes them. If the service manager + * Adds the specified services and initializes them. If the service manager * has already been started then the services will also be started. * This is a convenience method that delegates to addService(), thus each * service will be initialized (and possibly started) in sequence rather diff --git a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java index b337ce6b73..0100b65111 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/ServiceManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,16 +39,16 @@ /** * The base service manager class from which the HostedServiceManager - * and ClientServiceManager classes are derived. This manages the + * and ClientServiceManager classes are derived. This manages * the underlying services and their life cycles. * * @author Paul Speed */ public abstract class ServiceManager { - static final Logger log = Logger.getLogger(ServiceManager.class.getName()); + private static final Logger log = Logger.getLogger(ServiceManager.class.getName()); - private final List> services = new CopyOnWriteArrayList>(); + private final List> services = new CopyOnWriteArrayList<>(); private volatile boolean started = false; protected ServiceManager() { @@ -157,7 +157,7 @@ public > void removeService( S s ) { /** * Terminates all services. If the service manager has not been - * stopped yet then it will be stopped. + * stopped yet, then it will be stopped. */ public void terminate() { if( started ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java index 4f7f7de32d..d2a8c2886a 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfo.java @@ -92,7 +92,7 @@ public MethodInfo getMethod( Method m ) { } private MethodInfo[] toMethodInfo( Class type, Method[] methods ) { - List result = new ArrayList(); + List result = new ArrayList<>(); short methodId = 0; for( Method m : methods ) { // Simple... add all methods exposed through the interface diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java index addc035939..61f47b3adf 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/ClassInfoRegistry.java @@ -48,7 +48,7 @@ public class ClassInfoRegistry { //private final LoadingCache cache; // Guava version - private final Map cache = new HashMap(); + private final Map cache = new HashMap<>(); private final AtomicInteger nextClassId = new AtomicInteger(); private final ReadWriteLock lock = new ReentrantReadWriteLock(); @@ -66,26 +66,25 @@ public ClassInfo getClassInfo( Class type ) { if( result != null ) { return result; } - // Else we need to create it and store it... so grab the write - // lock + // Else we need to create it and store it, so grab the write lock. lock.readLock().unlock(); lock.writeLock().lock(); try { // Note: it's technically possible that a race with another thread // asking for the same class already created one between our read unlock // and our write lock. No matter as it's cheap to create one and does - // no harm. Code is simpler without the double-check. + // no harm. Code is simpler without the double check. result = new ClassInfo((short)nextClassId.getAndIncrement(), type); cache.put(type, result); - // Regrab the read lock before leaving... kind of unnecessary but + // Re-grab the read lock before leaving... kind of unnecessary, but // it makes the method cleaner and widens the gap of lock races. // Downgrading a write lock to read is ok. lock.readLock().lock(); return result; } finally { - // Unlock the write lock while still holding onto read + // Unlock the write lock while still holding onto the read lock. lock.writeLock().unlock(); } } finally { @@ -94,7 +93,7 @@ public ClassInfo getClassInfo( Class type ) { } /* - would be more straight-forward with guava Guava version + would be more straight-forward with Guava. Guava version: private class ClassInfoLoader extends CacheLoader { @Override public ClassInfo load( Class type ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java index fa898e5e58..050bc21c1c 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/MethodInfo.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,11 +70,9 @@ public MethodInfo( short id, Method m ) { public Object invoke( Object target, Object... parms ) { try { return method.invoke(target, parms); - } catch (IllegalAccessException e) { - throw new RuntimeException("Error invoking:" + method + " on:" + target, e); - } catch (IllegalArgumentException e) { - throw new RuntimeException("Error invoking:" + method + " on:" + target, e); - } catch (InvocationTargetException e) { + } catch (IllegalAccessException + | IllegalArgumentException + | InvocationTargetException e) { throw new RuntimeException("Error invoking:" + method + " on:" + target, e); } } diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java index f42ada84f6..f1c168ac53 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RemoteObjectHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ public class RemoteObjectHandler implements InvocationHandler { private final byte channel; private final short objectId; private final ClassInfo typeInfo; - private final Map methodIndex = new ConcurrentHashMap(); + private final Map methodIndex = new ConcurrentHashMap<>(); public RemoteObjectHandler( RmiRegistry rmi, byte channel, short objectId, ClassInfo typeInfo ) { this.rmi = rmi; diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java index ea1506f720..0dc3eac2be 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiClientService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -69,7 +69,7 @@ public class RmiClientService extends AbstractClientService { private RmiRegistry rmi; private volatile boolean isStarted = false; - private final List pending = new ArrayList(); + private final List pending = new ArrayList<>(); public RmiClientService() { this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE); @@ -160,6 +160,7 @@ protected void onInitialize( ClientServiceManager s ) { } @Override + @SuppressWarnings("unchecked") public void start() { super.start(); diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java index 547e83dd45..de19304561 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,6 +46,12 @@ public class RmiContext { private static final ThreadLocal connection = new ThreadLocal(); + /** + * A private constructor to inhibit instantiation of this class. + */ + private RmiContext() { + } + /** * Returns the HostedConnection that is responsible for any * RMI-related calls on this thread. diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java index 766627464a..5e7effff07 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rmi/RmiHostedService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2018 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -75,7 +75,7 @@ */ public class RmiHostedService extends AbstractHostedService { - static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + private static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); public static final String ATTRIBUTE_NAME = "rmi"; @@ -83,7 +83,7 @@ public class RmiHostedService extends AbstractHostedService { private short rmiId; private byte defaultChannel; private boolean autoHost; - private final Map globalShares = new ConcurrentHashMap(); + private final Map globalShares = new ConcurrentHashMap<>(); public RmiHostedService() { this((short)-1, (byte)MessageConnection.CHANNEL_DEFAULT_RELIABLE, true); @@ -104,7 +104,7 @@ public RmiHostedService( short rmiId, byte defaultChannel, boolean autoHost ) { /** * Shares a server-wide object associated with the specified type. All connections * with RMI hosting started will have access to this shared object as soon as they - * connect and they will all share the same instance. It is up to the shared object + * connect, and they will all share the same instance. It is up to the shared object * to handle any multithreading that might be required. */ public void shareGlobal( T object, Class type ) { @@ -114,7 +114,7 @@ public void shareGlobal( T object, Class type ) { /** * Shares a server-wide object associated with the specified name. All connections * with RMI hosting started will have access to this shared object as soon as they - * connect and they will all share the same instance. It is up to the shared object + * connect, and they will all share the same instance. It is up to the shared object * to handle any multithreading that might be required. */ public void shareGlobal( String name, T object, Class type ) { @@ -124,16 +124,16 @@ public void shareGlobal( String name, T object, Class type ) { /** * Shares a server-wide object associated with the specified name over the specified * channel. All connections with RMI hosting started will have access to this shared - * object as soon as they connect and they will all share the same instance. It is up + * object as soon as they connect, and they will all share the same instance. It is up * to the shared object to handle any multithreading that might be required. - * All network communcation associated with the shared object will be done over + * All network communication associated with the shared object will be done over * the specified channel. */ public void shareGlobal( byte channel, String name, T object, Class type ) { GlobalShare share = new GlobalShare(channel, object, type); GlobalShare existing = globalShares.put(name, share); if( existing != null ) { - // Shouldn't need to do anything actually. + // Shouldn't need to do anything, actually. } // Go through all of the children @@ -152,8 +152,8 @@ public void shareGlobal( byte channel, String name, T object, Class local = new ObjectIndex(); - private final ObjectIndex remote = new ObjectIndex(); + private final ObjectIndex local = new ObjectIndex<>(); + private final ObjectIndex remote = new ObjectIndex<>(); // Only used on the server to provide thread-local context for // local RMI calls. @@ -162,7 +162,7 @@ public void share( byte channel, String name, T object, Class typ local.byName.put(name, newShare); local.byId.put(newShare.objectId, newShare); - // Make sure we are setup to receive the remote method calls through + // Make sure we are set up to receive the remote method calls through // the RPC service rpc.registerHandler(newShare.objectId, rmiHandler); @@ -378,9 +378,9 @@ public Object call( RpcConnection conn, short objectId, short procId, Object... * the remote objects and a lock that can guard them. */ private class ObjectIndex { - final Map byName = new HashMap(); - final Map byId = new HashMap(); - final Map classes = new HashMap(); + final Map byName = new HashMap<>(); + final Map byId = new HashMap<>(); + final Map classes = new HashMap<>(); final ReadWriteLock lock = new ReentrantReadWriteLock(); public ObjectIndex() { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java index a74a7c7d11..c8e44e33cf 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcClientService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -70,9 +70,10 @@ public RpcConnection getRpcConnection() { } /** - * Used internally to setup the RpcConnection and MessageDelegator. + * Used internally to set up the RpcConnection and MessageDelegator. */ @Override + @SuppressWarnings("unchecked") protected void onInitialize( ClientServiceManager serviceManager ) { Client client = serviceManager.getClient(); this.rpc = new RpcConnection(client); @@ -86,6 +87,7 @@ protected void onInitialize( ClientServiceManager serviceManager ) { * was previously added to the network Client. */ @Override + @SuppressWarnings("unchecked") public void terminate( ClientServiceManager serviceManager ) { Client client = serviceManager.getClient(); client.removeMessageListener(delegator, delegator.getMessageTypes()); @@ -93,7 +95,7 @@ public void terminate( ClientServiceManager serviceManager ) { /** * Performs a synchronous call on the server against the specified - * object using the specified procedure ID. Both inboud and outbound + * object using the specified procedure ID. Both inbound and outbound * communication is done on the specified channel. */ public Object callAndWait( byte channel, short objId, short procId, Object... args ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java index 00b96d122c..d8a71358df 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcConnection.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ */ public class RpcConnection { - static final Logger log = Logger.getLogger(RpcConnection.class.getName()); + private static final Logger log = Logger.getLogger(RpcConnection.class.getName()); /** * The underlying connection upon which RPC call messages are sent @@ -64,7 +64,7 @@ public class RpcConnection { * The objectId index of RpcHandler objects that are used to perform the * RPC calls for a particular object. */ - private Map handlers = new ConcurrentHashMap(); + private Map handlers = new ConcurrentHashMap<>(); /** * Provides unique messages IDs for outbound synchronous call @@ -78,7 +78,7 @@ public class RpcConnection { * response is received, the appropriate handler is found here and the * response or error set, thus releasing the waiting caller. */ - private Map responses = new ConcurrentHashMap(); + private Map responses = new ConcurrentHashMap<>(); /** * Creates a new RpcConnection for the specified network connection. @@ -117,8 +117,8 @@ public Object callAndWait( byte channel, short objId, short procId, Object... ar log.log(Level.FINEST, "Sending:{0} on channel:{1}", new Object[]{msg, channel}); } - // Prevent non-async messages from being send as UDP - // because there is a high probabilty that this would block + // Prevent non-async messages from being sent as UDP + // because there is a high probability that this would block // forever waiting for a response. For async calls it's ok // so it doesn't do the check. if( channel >= 0 ) { @@ -247,7 +247,7 @@ public synchronized Object getResponse() { wait(); } } catch( InterruptedException e ) { - throw new RuntimeException("Interrupted waiting for respone to:" + msg, e); + throw new RuntimeException("Interrupted waiting for response to:" + msg, e); } if( error != null ) { throw new RuntimeException("Error calling remote procedure:" + msg + "\n" + error); diff --git a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java index 2d6f285937..22b2e53e0c 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/rpc/RpcHostedService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -66,7 +66,7 @@ public class RpcHostedService extends AbstractHostedConnectionService { private static final String ATTRIBUTE_NAME = "rpcSession"; - static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); + private static final Logger log = Logger.getLogger(RpcHostedService.class.getName()); private SessionDataDelegator delegator; @@ -94,7 +94,7 @@ public RpcHostedService( boolean autoHost ) { } /** - * Used internally to setup the message delegator that will + * Used internally to set up the message delegator that will * handle HostedConnection specific messages and forward them * to that connection's RpcConnection. */ diff --git a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java index 61632282a6..ea4595947c 100644 --- a/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java +++ b/jme3-networking/src/main/java/com/jme3/network/service/serializer/ClientSerializerRegistrationsService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ public class ClientSerializerRegistrationsService extends AbstractClientService implements MessageListener { - static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); + private static final Logger log = Logger.getLogger(SerializerRegistrationsMessage.class.getName()); @Override protected void onInitialize( ClientServiceManager serviceManager ) { diff --git a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java index b5d7a9efea..2eba38e444 100644 --- a/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java +++ b/jme3-networking/src/main/java/com/jme3/network/util/AbstractMessageDelegator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015-2019 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,10 +57,10 @@ public abstract class AbstractMessageDelegator implements MessageListener { - static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName()); + private static final Logger log = Logger.getLogger(AbstractMessageDelegator.class.getName()); private Class delegateType; - private Map methods = new HashMap(); + private Map methods = new HashMap<>(); private Class[] messageTypes; /** @@ -133,7 +133,7 @@ protected boolean isValidMethod( Method m, Class messageType ) { * only works with methods that actually have arguments. * This implementation returns the last element of the method's * getParameterTypes() array, thus supporting both - * method(connection, messageType) as well as just method(messageType) + * method(connection, messageType) and method(messageType) * calling forms. */ protected Class getMessageType( Method m ) { @@ -148,7 +148,7 @@ protected Class getMessageType( Method m ) { */ protected Method findDelegate( String name, Class messageType ) { // We do an exhaustive search because it's easier to - // check for a variety of parameter types and it's all + // check for a variety of parameter types, and it's all // that Class would be doing in getMethod() anyway. for( Method m : delegateType.getDeclaredMethods() ) { @@ -190,7 +190,7 @@ protected final void automap() { * the parameters. */ public AbstractMessageDelegator map( String... methodNames ) { - Set names = new HashSet( Arrays.asList(methodNames) ); + Set names = new HashSet<>( Arrays.asList(methodNames) ); map(names); return this; } diff --git a/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java index b92ee09a86..f0e45dd2e3 100644 --- a/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java +++ b/jme3-networking/src/main/java/com/jme3/network/util/ObjectMessageDelegator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ public class ObjectMessageDelegator extends Abstrac *
                • void someName(Message msg) * * Where S is the type of MessageConnection and SomeMessage is some - * specific concreate Message subclass. + * specific concrete Message subclass. */ public ObjectMessageDelegator( Object delegate, boolean automap ) { super(delegate.getClass(), automap); diff --git a/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java index f2bee33737..26d809df83 100644 --- a/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java +++ b/jme3-networking/src/main/java/com/jme3/network/util/SessionDataDelegator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2015 jMonkeyEngine + * Copyright (c) 2015-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,7 +48,7 @@ */ public class SessionDataDelegator extends AbstractMessageDelegator { - static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName()); + private static final Logger log = Logger.getLogger(SessionDataDelegator.class.getName()); private String attributeName; @@ -63,7 +63,7 @@ public class SessionDataDelegator extends AbstractMessageDelegatorvoid someName(Message msg) * * Where S is the type of MessageConnection and SomeMessage is some - * specific concreate Message subclass. + * specific concrete Message subclass. */ public SessionDataDelegator( Class delegateType, String attributeName, boolean automap ) { super(delegateType, automap); @@ -92,6 +92,7 @@ protected void miss( HostedConnection source ) { * HostConnection. If there is no value at that attribute then * the miss() method is called. */ + @Override protected Object getSourceDelegate( HostedConnection source ) { Object result = source.getAttribute(attributeName); if( result == null ) { diff --git a/jme3-niftygui/build.gradle b/jme3-niftygui/build.gradle index d0e126207d..b11c3e5017 100644 --- a/jme3-niftygui/build.gradle +++ b/jme3-niftygui/build.gradle @@ -1,12 +1,6 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -def niftyVersion = '1.4.3' - dependencies { - compile project(':jme3-core') - compile "com.github.nifty-gui:nifty:${niftyVersion}" - compile "com.github.nifty-gui:nifty-default-controls:${niftyVersion}" - compile "com.github.nifty-gui:nifty-style-black:${niftyVersion}" + api project(':jme3-core') + api libs.nifty + api libs.nifty.default.controls + runtimeOnly libs.nifty.style.black } diff --git a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java index 5671432b62..ee86f1bb5c 100644 --- a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java +++ b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiEvent.java @@ -37,6 +37,7 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.screen.Screen; import java.io.IOException; import java.util.logging.Level; import java.util.logging.Logger; @@ -51,10 +52,10 @@ public class GuiEvent extends AbstractCinematicEvent { /** * message logger for this class */ - static final Logger log = Logger.getLogger(GuiEvent.class.getName()); + private static final Logger log = Logger.getLogger(GuiEvent.class.getName()); /** - * name of the associated Nifty screen(not null) + * name of the associated Nifty screen (not null) */ protected String screen; /** @@ -135,8 +136,9 @@ public void onPlay() { */ @Override public void onStop() { - if (nifty.getCurrentScreen() != null) { - nifty.getCurrentScreen().endScreen(null); + Screen currentScreen = nifty.getCurrentScreen(); + if (currentScreen != null) { + currentScreen.endScreen(null); } } diff --git a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java index a7bd123bca..06e25271a6 100644 --- a/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java +++ b/jme3-niftygui/src/main/java/com/jme3/cinematic/events/GuiTrack.java @@ -37,6 +37,7 @@ import com.jme3.export.JmeImporter; import com.jme3.export.OutputCapsule; import de.lessvoid.nifty.Nifty; +import de.lessvoid.nifty.screen.Screen; import java.io.IOException; /** @@ -84,8 +85,9 @@ public void onPlay() { @Override public void onStop() { - if (nifty.getCurrentScreen() != null) { - nifty.getCurrentScreen().endScreen(null); + Screen currentScreen = nifty.getCurrentScreen(); + if (currentScreen != null) { + currentScreen.endScreen(null); } } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java index 38c7427f15..407e89f848 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/InputSystemJme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,14 +46,15 @@ import de.lessvoid.nifty.spi.input.InputSystem; import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; import java.util.ArrayList; +import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; public class InputSystemJme implements InputSystem, RawInputListener { - private final ArrayList inputQueue = new ArrayList(); - private InputManager inputManager; - private boolean[] niftyOwnsDragging = new boolean[3]; + private final List inputQueue = new ArrayList<>(); + private final InputManager inputManager; + private final boolean[] niftyOwnsDragging = new boolean[3]; private int inputPointerId = -1; private int x, y; private int height; @@ -65,13 +66,14 @@ public InputSystemJme(InputManager inputManager) { this.inputManager = inputManager; } + @Override public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { } /** * Must be set in order for nifty events to be forwarded correctly. * - * @param nifty + * @param nifty the Nifty instance to use */ public void setNifty(Nifty nifty) { this.nifty = nifty; @@ -101,15 +103,18 @@ public void setHeight(int height) { this.height = height; } + @Override public void setMousePosition(int x, int y) { // TODO: When does nifty use this? } + @Override public void beginInput() { } + @Override public void endInput() { - boolean result = nifty.update(); + nifty.update(); } private void handleMouseEvent(int button, boolean value, NiftyInputConsumer nic, InputEvent evt) { @@ -147,12 +152,12 @@ private void handleMouseEvent(int button, boolean value, NiftyInputConsumer nic, // special case: // 1) You click on the jme scene (not Nifty) and Nifty will correctly return false (event not consumed) but // internally it remembers: "mouse button is now down". Note that the jme mouse cursor is now hidden. - // 2) You release the mouse button but the mouse down event will not be forwarded to Nifty because it did + // 2) You release the mouse button but the mouse down event will not be forwarded to Nifty because it // owned the mouse and the jme mouse cursor is not visible. // - // Nifty now still thinks that the mouse button is down although it's not. The result is that the next click + // Nifty now still thinks that the mouse button is down, but it's not. The result is that the next click // on any Nifty element will not be recognized as an initial click by Nifty. So you need an additional click - // on the Nifty element to activate it correctly. In case of drag and drop this additional click was quite + // on the Nifty element to activate it correctly. In case of drag and drop, this additional click was quite // irritating. // // To fix that we'll now forward the mouse button up event ALWAYS to Nifty regardless of it owning the mouse @@ -165,13 +170,13 @@ private void handleMouseEvent(int button, boolean value, NiftyInputConsumer nic, // which is good ;-) If that ever happens to someone there is an easy fix possible: // nifty.setIgnoreMouseEvents() to completely stop Nifty from processing events. - boolean consumed = nic.processMouseEvent(x, y, 0, button, false); + boolean consumed = nic.processMouseEvent(x, y, 0, button, false); - // Only consume event if it ORIGINATED in nifty! - if (niftyOwnsDragging[button] && consumed) { - evt.setConsumed(); - processSoftKeyboard(); - } + // Only consume event if it ORIGINATED in nifty! + if (niftyOwnsDragging[button] && consumed) { + evt.setConsumed(); + processSoftKeyboard(); + } niftyOwnsDragging[button] = false; //System.out.format("niftyMouse(%d, %d, %d, false) = %b\n", x, y, button, consumed); @@ -187,7 +192,7 @@ private void onTouchEventQueued(TouchEvent evt, NiftyInputConsumer nic) { y = (int) (height - evt.getY()); // Input manager will not convert touch events to mouse events, - // thus we must do it ourselves.. + // so we must do it ourselves. switch (evt.getType()) { case DOWN: if (inputPointerId != -1) { @@ -226,8 +231,8 @@ private void onMouseMotionEventQueued(MouseMotionEvent evt, NiftyInputConsumer n } private void onMouseButtonEventQueued(MouseButtonEvent evt, NiftyInputConsumer nic) { - x = (int) evt.getX(); - y = (int) (height - evt.getY()); + x = evt.getX(); + y = height - evt.getY(); handleMouseEvent(evt.getButtonIndex(), evt.isPressed(), nic, evt); } @@ -251,6 +256,7 @@ private void onKeyEventQueued(KeyInputEvent evt, NiftyInputConsumer nic) { } } + @Override public void onMouseMotionEvent(MouseMotionEvent evt) { // Only forward the event if there's actual motion involved. if (inputManager.isCursorVisible() && (evt.getDX() != 0 @@ -260,6 +266,7 @@ public void onMouseMotionEvent(MouseMotionEvent evt) { } } + @Override public void onMouseButtonEvent(MouseButtonEvent evt) { if (evt.getButtonIndex() >= 0 && evt.getButtonIndex() <= 2) { if (evt.isReleased() || inputManager.isCursorVisible()) { @@ -270,20 +277,25 @@ public void onMouseButtonEvent(MouseButtonEvent evt) { } } + @Override public void onJoyAxisEvent(JoyAxisEvent evt) { } + @Override public void onJoyButtonEvent(JoyButtonEvent evt) { } + @Override public void onKeyEvent(KeyInputEvent evt) { inputQueue.add(evt); } + @Override public void onTouchEvent(TouchEvent evt) { inputQueue.add(evt); } + @Override public void forwardEvents(NiftyInputConsumer nic) { int queueSize = inputQueue.size(); @@ -317,6 +329,8 @@ private void processSoftKeyboard() { } softTextDialogInput.requestDialog(SoftTextDialogInput.TEXT_ENTRY_DIALOG, "Enter Text", initialValue, new SoftTextDialogInputListener() { + + @Override public void onSoftText(int action, String text) { if (action == SoftTextDialogInputListener.COMPLETE) { textField.setText(text); diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java index 6cb1798bbc..4d08588759 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/JmeBatchRenderBackend.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -31,15 +31,6 @@ */ package com.jme3.niftygui; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.ShortBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - import com.jme3.asset.TextureKey; import com.jme3.material.Material; import com.jme3.material.RenderState; @@ -54,570 +45,598 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.scene.VertexBuffer.Usage; import com.jme3.texture.Image.Format; -import com.jme3.texture.image.ImageRaster; import com.jme3.texture.Texture.MagFilter; import com.jme3.texture.Texture.MinFilter; import com.jme3.texture.Texture2D; import com.jme3.texture.image.ColorSpace; +import com.jme3.texture.image.ImageRaster; import com.jme3.util.BufferUtils; - -import de.lessvoid.nifty.render.batch.spi.BatchRenderBackend; import de.lessvoid.nifty.render.BlendMode; +import de.lessvoid.nifty.render.batch.spi.BatchRenderBackend; import de.lessvoid.nifty.spi.render.MouseCursor; import de.lessvoid.nifty.tools.Color; import de.lessvoid.nifty.tools.Factory; import de.lessvoid.nifty.tools.ObjectPool; import de.lessvoid.nifty.tools.resourceloader.NiftyResourceLoader; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.FloatBuffer; +import java.nio.ShortBuffer; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Nifty GUI BatchRenderBackend Implementation for jMonkeyEngine. + * * @author void */ public class JmeBatchRenderBackend implements BatchRenderBackend { - private static Logger log = Logger.getLogger(JmeBatchRenderBackend.class.getName()); - - private final ObjectPool batchPool; - private final List batches = new ArrayList(); - - // a modify texture call needs a jme Renderer to execute. if we're called to modify a texture but don't - // have a Renderer yet - since it was not initialized on the jme side - we'll cache the modify texture calls - // in here and execute them later (at the next beginFrame() call). - private final List modifyTextureCalls = new ArrayList(); - - private RenderManager renderManager; - private NiftyJmeDisplay display; - private Map textures = new HashMap(); - private int textureAtlasId = 1; - private Batch currentBatch; - private Matrix4f tempMat = new Matrix4f(); - private ByteBuffer initialData; - - // this is only used for debugging purpose and will make the removed textures filled with a color - // please note: the old way to init this via a system property has been - // removed since it's now possible to configure it using the - // BatchRenderConfiguration class when you create the NiftyJmeDisplay instance - private boolean fillRemovedTexture = false; - - public JmeBatchRenderBackend(final NiftyJmeDisplay display) { - this.display = display; - this.batchPool = new ObjectPool(new Factory() { - @Override - public Batch createNew() { - return new Batch(); - } - }); - } - - public void setRenderManager(final RenderManager rm) { - this.renderManager = rm; - } - - @Override - public void setResourceLoader(final NiftyResourceLoader resourceLoader) { - } - - @Override - public int getWidth() { - return display.getWidth(); - } - - @Override - public int getHeight() { - return display.getHeight(); - } - - @Override - public void beginFrame() { - log.fine("beginFrame()"); - - for (int i=0; i batchPool; + private final List batches = new ArrayList<>(); + + // A modify texture call needs a jme Renderer to execute. If we're called to modify a texture but don't + // have a Renderer yet - since it was not initialized on the jme side - we'll cache the modify texture calls + // in here and execute them later (at the next beginFrame() call). + private final List modifyTextureCalls = new ArrayList<>(); + + private RenderManager renderManager; + private NiftyJmeDisplay display; + private Map textures = new HashMap<>(); + private int textureAtlasId = 1; + private Batch currentBatch; + private Matrix4f tempMat = new Matrix4f(); + private ByteBuffer initialData = null; + + // This is only used for debugging purposes and will fill the removed textures with a color. + // Please note: the old way to init this via a system property has been + // removed, since it's now possible to configure it using the + // BatchRenderConfiguration class when you create the NiftyJmeDisplay instance. + private boolean fillRemovedTexture = false; + + public JmeBatchRenderBackend(final NiftyJmeDisplay display) { + this.display = display; + this.batchPool = new ObjectPool<>(new Factory() { + + @Override + public Batch createNew() { + return new Batch(); + } + }); + } + + public void setRenderManager(final RenderManager rm) { + this.renderManager = rm; + } + + @Override + public void setResourceLoader(final NiftyResourceLoader resourceLoader) { + } + + @Override + public int getWidth() { + return display.getWidth(); + } + + @Override + public int getHeight() { + return display.getHeight(); + } + + @Override + public void beginFrame() { + log.fine("beginFrame()"); + + for (int i = 0; i < batches.size(); i++) { + batchPool.free(batches.get(i)); } + batches.clear(); - @Override - public void enable() { + // in case we have pending modifyTexture calls we'll need to execute them now + if (!modifyTextureCalls.isEmpty()) { + Renderer renderer = display.getRenderer(); + for (int i = 0; i < modifyTextureCalls.size(); i++) { + modifyTextureCalls.get(i).execute(renderer); + } + modifyTextureCalls.clear(); } + } - @Override - public void disable() { + @Override + public void endFrame() { + log.fine("endFrame"); + } + + @Override + public void clear() { + } + + // TODO: Cursor support + @Override + public MouseCursor createMouseCursor(final String filename, final int hotspotX, final int hotspotY) throws IOException { + return new MouseCursor() { + @Override + public void dispose() { + } + + @Override + public void enable() { + } + + @Override + public void disable() { + } + }; + } + + @Override + public void enableMouseCursor(final MouseCursor mouseCursor) { + } + + @Override + public void disableMouseCursor() { + } + + @Override + public int createTextureAtlas(final int width, final int height) { + try { + // we initialize a buffer here that will be used as base for all texture atlas images + if (initialData == null) { + initialData = BufferUtils.createByteBuffer(width * height * 4); + for (int i = 0; i < width * height; i++) { + initialData.put((byte) 0x00); + initialData.put((byte) 0x00); + initialData.put((byte) 0x00); + initialData.put((byte) 0xff); + } + } + + int atlasId = addTexture(createAtlasTextureInternal(width, height)); + + return atlasId; + } catch (Exception e) { + log.log(Level.WARNING, e.getMessage(), e); + return 0; // TODO Nifty always expects this call to be successful + // there currently is no way to return failure or something :/ } - }; - } - - @Override - public void enableMouseCursor(final MouseCursor mouseCursor) { - } - - @Override - public void disableMouseCursor() { - } - - @Override - public int createTextureAtlas(final int width, final int height) { - try { - int atlasId = addTexture(createAtlasTextureInternal(width, height)); - - // we just initialize a second buffer here that will replace the texture atlas image - initialData = BufferUtils.createByteBuffer(width*height*4); - for (int i=0; i key = new AssetKey(path); + AssetKey key = new AssetKey<>(path); AssetInfo info = assetManager.locateAsset(key); if (info != null) { return info.openStream(); @@ -86,6 +86,7 @@ public InputStream getResourceAsStream(String path) { } } + @Override public URL getResource(String path) { throw new UnsupportedOperationException(); } @@ -112,12 +113,13 @@ public NiftyJmeDisplay() { * @param inputManager jME InputManager * @param audioRenderer jME AudioRenderer * @param viewport Viewport to use + * @return new NiftyJmeDisplay instance */ public static NiftyJmeDisplay newNiftyJmeDisplay( - final AssetManager assetManager, - final InputManager inputManager, - final AudioRenderer audioRenderer, - final ViewPort viewport) { + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport) { return newNiftyJmeDisplay( assetManager, inputManager, @@ -142,13 +144,14 @@ public static NiftyJmeDisplay newNiftyJmeDisplay( * you can use to further configure batch rendering. If unsure you * can simply use new BatchRenderConfiguration() in here for the * default configuration which should give you good default values. + * @return new NiftyJmeDisplay instance */ public static NiftyJmeDisplay newNiftyJmeDisplay( - final AssetManager assetManager, - final InputManager inputManager, - final AudioRenderer audioRenderer, - final ViewPort viewport, - final BatchRenderConfiguration batchRenderConfiguration) { + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport, + final BatchRenderConfiguration batchRenderConfiguration) { return new NiftyJmeDisplay( assetManager, inputManager, @@ -171,7 +174,7 @@ public static NiftyJmeDisplay newNiftyJmeDisplay( * * A complete re-organisation of the texture atlas happens when a Nifty screen ends and another begins. Dynamically * adding images while a screen is running is supported as well. - * + * * @param assetManager jME AssetManager * @param inputManager jME InputManager * @param audioRenderer jME AudioRenderer @@ -184,79 +187,100 @@ public static NiftyJmeDisplay newNiftyJmeDisplay( * instead of this constructor. */ public NiftyJmeDisplay( - final AssetManager assetManager, - final InputManager inputManager, - final AudioRenderer audioRenderer, - final ViewPort viewport, - final int atlasWidth, - final int atlasHeight) { - // The code duplication in here really sucks - it's a copy of the - // private constructor below that takes a BatchRenderConfiguration as an - // additional parameter. This method should really be removed soon and - // users should simply call the new factory methods. - // - // For now I keep this constructor as-is but have marked it as deprecated - // to allow migration to the new way to instantiate this class. - initialize(assetManager, inputManager, audioRenderer, viewport); - - this.renderDev = null; - this.batchRendererBackend = new JmeBatchRenderBackend(this); - - BatchRenderConfiguration batchRenderConfiguration = new BatchRenderConfiguration(); - batchRenderConfiguration.atlasWidth = atlasWidth; - batchRenderConfiguration.atlasHeight = atlasHeight; - - nifty = new Nifty( - new BatchRenderDevice(batchRendererBackend, batchRenderConfiguration), - soundDev, - inputSys, - new AccurateTimeProvider()); - inputSys.setNifty(nifty); - - resourceLocation = new ResourceLocationJme(); - nifty.getResourceLoader().removeAllResourceLocations(); - nifty.getResourceLoader().addResourceLocation(resourceLocation); + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport, + final int atlasWidth, + final int atlasHeight) { + // The code duplication in here really sucks - it's a copy of the + // private constructor below that takes a BatchRenderConfiguration as an + // additional parameter. This method should really be removed soon and + // users should simply call the new factory methods. + // + // For now, I keep this constructor as-is, but have marked it as deprecated + // to allow migration to the new way to instantiate this class. + initialize(assetManager, inputManager, audioRenderer, viewport); + + this.renderDev = null; + this.batchRendererBackend = new JmeBatchRenderBackend(this); + + BatchRenderConfiguration batchRenderConfiguration = new BatchRenderConfiguration(); + batchRenderConfiguration.atlasWidth = atlasWidth; + batchRenderConfiguration.atlasHeight = atlasHeight; + + nifty = new Nifty( + new BatchRenderDevice(batchRendererBackend, batchRenderConfiguration), + soundDev, + inputSys, + new AccurateTimeProvider()); + inputSys.setNifty(nifty); + + resourceLocation = new ResourceLocationJme(); + nifty.getResourceLoader().removeAllResourceLocations(); + nifty.getResourceLoader().addResourceLocation(resourceLocation); } private NiftyJmeDisplay( - final AssetManager assetManager, - final InputManager inputManager, - final AudioRenderer audioRenderer, - final ViewPort viewport, - final BatchRenderConfiguration batchRenderConfiguration) { - initialize(assetManager, inputManager, audioRenderer, viewport); - - this.renderDev = null; - this.batchRendererBackend = new JmeBatchRenderBackend(this); - - nifty = new Nifty( - new BatchRenderDevice(batchRendererBackend, batchRenderConfiguration), - soundDev, - inputSys, - new AccurateTimeProvider()); - inputSys.setNifty(nifty); - - resourceLocation = new ResourceLocationJme(); - nifty.getResourceLoader().removeAllResourceLocations(); - nifty.getResourceLoader().addResourceLocation(resourceLocation); + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport, + final BatchRenderConfiguration batchRenderConfiguration) { + initialize(assetManager, inputManager, audioRenderer, viewport); + + this.renderDev = null; + this.batchRendererBackend = new JmeBatchRenderBackend(this); + + nifty = new Nifty( + new BatchRenderDevice(batchRendererBackend, batchRenderConfiguration), + soundDev, + inputSys, + new AccurateTimeProvider()); + inputSys.setNifty(nifty); + + resourceLocation = new ResourceLocationJme(); + nifty.getResourceLoader().removeAllResourceLocations(); + nifty.getResourceLoader().addResourceLocation(resourceLocation); } /** - * Create a standard NiftyJmeDisplay. This uses the old Nifty renderer. It's probably slower then the batched + * Create a standard NiftyJmeDisplay. This uses the old Nifty renderer. + * It's probably slower than the batched * renderer and is mainly here for backwards compatibility. + * Nifty colors are assumed to be in Linear colorspace (no gamma correction). * * @param assetManager jME AssetManager * @param inputManager jME InputManager * @param audioRenderer jME AudioRenderer * @param vp Viewport to use */ - public NiftyJmeDisplay(AssetManager assetManager, - InputManager inputManager, - AudioRenderer audioRenderer, - ViewPort vp){ + public NiftyJmeDisplay(AssetManager assetManager, + InputManager inputManager, + AudioRenderer audioRenderer, + ViewPort vp) { + this(assetManager, inputManager, audioRenderer, vp, ColorSpace.Linear); + } + + /** + * Create a standard NiftyJmeDisplay. This uses the old Nifty renderer. + * It's probably slower than the batched + * renderer and is mainly here for backwards compatibility. + * + * @param assetManager jME AssetManager + * @param inputManager jME InputManager + * @param audioRenderer jME AudioRenderer + * @param vp Viewport to use + * @param colorSpace the ColorSpace to use for Nifty colors (sRGB or Linear) + */ + public NiftyJmeDisplay(AssetManager assetManager, + InputManager inputManager, + AudioRenderer audioRenderer, + ViewPort vp, + ColorSpace colorSpace) { initialize(assetManager, inputManager, audioRenderer, vp); - this.renderDev = new RenderDeviceJme(this); + this.renderDev = new RenderDeviceJme(this, colorSpace); this.batchRendererBackend = null; nifty = new Nifty(renderDev, soundDev, inputSys, new AccurateTimeProvider()); @@ -268,24 +292,25 @@ public NiftyJmeDisplay(AssetManager assetManager, } private void initialize( - final AssetManager assetManager, - final InputManager inputManager, - final AudioRenderer audioRenderer, - final ViewPort viewport) { - this.assetManager = assetManager; - this.inputManager = inputManager; - this.w = viewport.getCamera().getWidth(); - this.h = viewport.getCamera().getHeight(); - this.soundDev = new SoundDeviceJme(assetManager, audioRenderer); - this.inputSys = new InputSystemJme(inputManager); + final AssetManager assetManager, + final InputManager inputManager, + final AudioRenderer audioRenderer, + final ViewPort viewport) { + this.assetManager = assetManager; + this.inputManager = inputManager; + this.w = viewport.getCamera().getWidth(); + this.h = viewport.getCamera().getHeight(); + this.soundDev = new SoundDeviceJme(assetManager, audioRenderer); + this.inputSys = new InputSystemJme(inputManager); } + @Override public void initialize(RenderManager rm, ViewPort vp) { this.renderManager = rm; if (renderDev != null) { - renderDev.setRenderManager(rm); + renderDev.setRenderManager(rm); } else { - batchRendererBackend.setRenderManager(rm); + batchRendererBackend.setRenderManager(rm); } if (inputManager != null) { @@ -295,17 +320,22 @@ public void initialize(RenderManager rm, ViewPort vp) { inited = true; this.vp = 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() { return nifty; } - public void simulateKeyEvent( KeyInputEvent event ) { - inputSys.onKeyEvent(event); + public void simulateKeyEvent(KeyInputEvent event) { + inputSys.onKeyEvent(event); } AssetManager getAssetManager() { @@ -324,10 +354,11 @@ int getWidth() { return w; } - Renderer getRenderer(){ + Renderer getRenderer() { return renderer; } + @Override public void reshape(ViewPort vp, int w, int h) { this.w = w; this.h = h; @@ -335,13 +366,16 @@ public void reshape(ViewPort vp, int w, int h) { nifty.resolutionChanged(); } + @Override public boolean isInitialized() { return inited; } + @Override public void preFrame(float tpf) { } + @Override public void postQueue(RenderQueue rq) { // render nifty before anything else renderManager.setCamera(vp.getCamera(), true); @@ -350,9 +384,11 @@ public void postQueue(RenderQueue rq) { renderManager.setCamera(vp.getCamera(), false); } + @Override public void postFrame(FrameBuffer out) { } + @Override public void cleanup() { inited = false; inputSys.reset(); @@ -363,7 +399,6 @@ public void cleanup() { @Override public void setProfiler(AppProfiler profiler) { - this.prof = profiler; + // not implemented } - } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java index 7390a32654..d3bf2d5fc1 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderDeviceJme.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 @@ -46,6 +46,7 @@ import com.jme3.scene.VertexBuffer.Usage; import com.jme3.scene.shape.Quad; import com.jme3.texture.Texture2D; +import com.jme3.texture.image.ColorSpace; import com.jme3.util.BufferUtils; import de.lessvoid.nifty.render.BlendMode; import de.lessvoid.nifty.spi.render.MouseCursor; @@ -57,46 +58,56 @@ import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.HashMap; +import java.util.Map; public class RenderDeviceJme implements RenderDevice { - - private NiftyJmeDisplay display; + private final ColorSpace colorSpace; + private final NiftyJmeDisplay display; private RenderManager rm; private Renderer r; - private HashMap textCacheLastFrame = new HashMap(); - private HashMap textCacheCurrentFrame = new HashMap(); + private Map textCacheLastFrame = new HashMap<>(); + private Map textCacheCurrentFrame = new HashMap<>(); private final Quad quad = new Quad(1, -1, true); private final Geometry quadGeom = new Geometry("nifty-quad", quad); private boolean clipWasSet = false; - private VertexBuffer quadDefaultTC = quad.getBuffer(Type.TexCoord); - private VertexBuffer quadModTC = quadDefaultTC.clone(); - private VertexBuffer quadColor; - private Matrix4f tempMat = new Matrix4f(); - private ColorRGBA tempColor = new ColorRGBA(); - private RenderState renderState = new RenderState(); - - private Material colorMaterial; - private Material textureColorMaterial; - private Material vertexColorMaterial; - + private final VertexBuffer quadDefaultTC = quad.getBuffer(Type.TexCoord); + private final VertexBuffer quadModTC = quadDefaultTC.clone(); + private final VertexBuffer quadColor; + private final Matrix4f tempMat = new Matrix4f(); + private final ColorRGBA tempColor = new ColorRGBA(); + private final RenderState renderState = new RenderState(); + + private final Material colorMaterial; + private final Material textureColorMaterial; + private final Material vertexColorMaterial; + private static class CachedTextKey { - + BitmapFont font; String text; // ColorRGBA color; - + public CachedTextKey(BitmapFont font, String text/*, ColorRGBA color*/) { this.font = font; this.text = text; // this.color = color; } - + @Override - public boolean equals(Object other) { - CachedTextKey otherKey = (CachedTextKey) other; - return font.equals(otherKey.font) && - text.equals(otherKey.text)/* && - color.equals(otherKey.color)*/; + public boolean equals(Object otherObject) { + boolean result; + if (otherObject == this) { + result = true; + } else if (otherObject != null + && otherObject.getClass() == getClass()) { + CachedTextKey otherKey = (CachedTextKey) otherObject; + result = font.equals(otherKey.font) + && text.equals(otherKey.text); + } else { + result = false; + } + + return result; } @Override @@ -108,47 +119,69 @@ public int hashCode() { return hash; } } - + + /** + * Instantiates a new RenderDevice, assuming Nifty colors are in linear + * colorspace (no gamma correction). + * + * @param display the SceneProcessor to render Nifty (not null, alias created) + */ public RenderDeviceJme(NiftyJmeDisplay display) { + this(display, ColorSpace.Linear); + } + + /** + * Instantiates a new RenderDevice using the specified ColorSpace for Nifty + * colors. + * + * @param display the SceneProcessor to render Nifty (not null, alias created) + * @param colorSpace the ColorSpace to use for Nifty colors (sRGB or Linear) + */ + public RenderDeviceJme(NiftyJmeDisplay display, ColorSpace colorSpace) { this.display = display; - + this.colorSpace = colorSpace; + quadColor = new VertexBuffer(Type.Color); quadColor.setNormalized(true); ByteBuffer bb = BufferUtils.createByteBuffer(4 * 4); quadColor.setupData(Usage.Stream, 4, Format.UnsignedByte, bb); quad.setBuffer(quadColor); - + quadModTC.setUsage(Usage.Stream); - + // Load the 3 material types separately to avoid // reloading the shader when the defines change. - + // Material with a single color (no texture or vertex color) colorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - + // Material with a texture and a color (no vertex color) textureColorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); - + // Material with vertex color, used for gradients (no texture) vertexColorMaterial = new Material(display.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); vertexColorMaterial.setBoolean("VertexColor", true); - + // Shared render state for all materials renderState.setDepthTest(false); renderState.setDepthWrite(false); } - + + @Override public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { } - + public void setRenderManager(RenderManager rm) { this.rm = rm; this.r = rm.getRenderer(); } // TODO: Cursor support + @Override public MouseCursor createMouseCursor(String str, int x, int y) { return new MouseCursor() { + + @Override public void dispose() { } @@ -161,60 +194,72 @@ public void disable() { } }; } - + + @Override public void enableMouseCursor(MouseCursor cursor) { } - + + @Override public void disableMouseCursor() { } - + + @Override public RenderImage createImage(String filename, boolean linear) { //System.out.println("createImage(" + filename + ", " + linear + ")"); return new RenderImageJme(filename, linear, display); } - + + @Override public RenderFont createFont(String filename) { return new RenderFontJme(filename, display); } - + + @Override public void beginFrame() { } - + + @Override public void endFrame() { - HashMap temp = textCacheLastFrame; + Map temp = textCacheLastFrame; textCacheLastFrame = textCacheCurrentFrame; textCacheCurrentFrame = temp; textCacheCurrentFrame.clear(); rm.setForcedRenderState(null); } - + + @Override public int getWidth() { return display.getWidth(); } - + + @Override public int getHeight() { return display.getHeight(); } - + + @Override public void clear() { } - + + @Override public void setBlendMode(BlendMode blendMode) { renderState.setBlendMode(convertBlend(blendMode)); } - + private RenderState.BlendMode convertBlend(BlendMode blendMode) { if (blendMode == null) { return RenderState.BlendMode.Off; - } else if (blendMode == BlendMode.BLEND) { - return RenderState.BlendMode.Alpha; - } else if (blendMode == BlendMode.MULIPLY) { - return RenderState.BlendMode.Alpha; - } else { - throw new UnsupportedOperationException(); + } else + switch (blendMode) { + case BLEND: + return RenderState.BlendMode.Alpha; + case MULIPLY: + return RenderState.BlendMode.Alpha; + default: + throw new UnsupportedOperationException(); } } - + private int convertColor(Color color) { int color2 = 0; color2 |= ((int) (255.0 * color.getAlpha())) << 24; @@ -223,19 +268,30 @@ private int convertColor(Color color) { color2 |= ((int) (255.0 * color.getRed())); return color2; } - + private ColorRGBA convertColor(Color inColor, ColorRGBA outColor) { - return outColor.set(inColor.getRed(), inColor.getGreen(), inColor.getBlue(), inColor.getAlpha()); + float red = inColor.getRed(); + float green = inColor.getGreen(); + float blue = inColor.getBlue(); + float alpha = inColor.getAlpha(); + + if (colorSpace == ColorSpace.sRGB) { + outColor.setAsSrgb(red, green, blue, alpha); + } else { + outColor.set(red, green, blue, alpha); + } + + return outColor; } @Override - public void renderFont(RenderFont font, String str, int x, int y, Color color, float sizeX, float sizeY) { + public void renderFont(RenderFont font, String str, int x, int y, Color color, float sizeX, float sizeY) { if (str.length() == 0) { return; } - + RenderFontJme jmeFont = (RenderFontJme) font; - + ColorRGBA colorRgba = convertColor(color, tempColor); CachedTextKey key = new CachedTextKey(jmeFont.getFont(), str/*, colorRgba*/); BitmapText text = textCacheLastFrame.get(key); @@ -245,12 +301,12 @@ public void renderFont(RenderFont font, String str, int x, int y, Color color, f text.updateLogicalState(0); } textCacheCurrentFrame.put(key, text); - + // float width = text.getLineWidth(); // float height = text.getLineHeight(); float x0 = x; //+ 0.5f * width * (1f - sizeX); float y0 = y; // + 0.5f * height * (1f - sizeY); - + tempMat.loadIdentity(); tempMat.setTranslation(x0, getHeight() - y0, 0); tempMat.setScale(sizeX, sizeY, 0); @@ -260,33 +316,34 @@ public void renderFont(RenderFont font, String str, int x, int y, Color color, f text.setColor(colorRgba); text.updateLogicalState(0); text.render(rm, colorRgba); - + // System.out.format("renderFont(%s, %s, %d, %d, %s, %f, %f)\n", jmeFont.getFont(), str, x, y, color.toString(), sizeX, sizeY); } - + + @Override public void renderImage(RenderImage image, int x, int y, int w, int h, int srcX, int srcY, int srcW, int srcH, Color color, float scale, int centerX, int centerY) { - + RenderImageJme jmeImage = (RenderImageJme) image; Texture2D texture = jmeImage.getTexture(); - + textureColorMaterial.setColor("Color", convertColor(color, tempColor)); - textureColorMaterial.setTexture("ColorMap", texture); - + textureColorMaterial.setTexture("ColorMap", texture); + float imageWidth = jmeImage.getWidth(); float imageHeight = jmeImage.getHeight(); FloatBuffer texCoords = (FloatBuffer) quadModTC.getData(); - + float startX = srcX / imageWidth; float startY = srcY / imageHeight; float endX = startX + (srcW / imageWidth); float endY = startY + (srcH / imageHeight); - + startY = 1f - startY; endY = 1f - endY; - + texCoords.rewind(); texCoords.put(startX).put(startY); texCoords.put(endX).put(startY); @@ -294,59 +351,61 @@ public void renderImage(RenderImage image, int x, int y, int w, int h, texCoords.put(startX).put(endY); texCoords.flip(); quadModTC.updateData(texCoords); - + quad.clearBuffer(Type.TexCoord); quad.setBuffer(quadModTC); - + float x0 = centerX + (x - centerX) * scale; float y0 = centerY + (y - centerY) * scale; - + tempMat.loadIdentity(); tempMat.setTranslation(x0, getHeight() - y0, 0); tempMat.setScale(w * scale, h * scale, 0); - + rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); textureColorMaterial.render(quadGeom, rm); - + //System.out.format("renderImage2(%s, %d, %d, %d, %d, %d, %d, %d, %d, %s, %f, %d, %d)\n", texture.getKey().toString(), // x, y, w, h, srcX, srcY, srcW, srcH, // color.toString(), scale, centerX, centerY); } - + + @Override public void renderImage(RenderImage image, int x, int y, int width, int height, Color color, float imageScale) { - + RenderImageJme jmeImage = (RenderImageJme) image; - + textureColorMaterial.setColor("Color", convertColor(color, tempColor)); textureColorMaterial.setTexture("ColorMap", jmeImage.getTexture()); - + quad.clearBuffer(Type.TexCoord); quad.setBuffer(quadDefaultTC); - + float x0 = x + 0.5f * width * (1f - imageScale); float y0 = y + 0.5f * height * (1f - imageScale); - + tempMat.loadIdentity(); tempMat.setTranslation(x0, getHeight() - y0, 0); tempMat.setScale(width * imageScale, height * imageScale, 0); - + rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); textureColorMaterial.render(quadGeom, rm); - + //System.out.format("renderImage1(%s, %d, %d, %d, %d, %s, %f)\n", jmeImage.getTexture().getKey().toString(), x, y, width, height, color.toString(), imageScale); } - + + @Override public void renderQuad(int x, int y, int width, int height, Color color) { //We test for alpha >0 as an optimization to prevent the render of completely transparent quads. - //Nifty use layers that are often used for logical positionning and not rendering. + //Nifty layers are often used for logical positioning and not rendering. //each layer is rendered as a quad, but that can bump up the number of geometry rendered by a lot. //Since we disable depth write, there is absolutely no point in rendering those quads - //This optimization can result in a huge increase of perfs on complex Nifty UIs. + //This optimization can result in a huge performance increase on complex Nifty UIs. if(color.getAlpha() >0){ - colorMaterial.setColor("Color", convertColor(color, tempColor)); + colorMaterial.setColor("Color", convertColor(color, tempColor)); tempMat.loadIdentity(); tempMat.setTranslation(x, getHeight() - y, 0); @@ -356,44 +415,47 @@ public void renderQuad(int x, int y, int width, int height, Color color) { rm.setForcedRenderState(renderState); colorMaterial.render(quadGeom, rm); } - + //System.out.format("renderQuad1(%d, %d, %d, %d, %s)\n", x, y, width, height, color.toString()); } - + + @Override public void renderQuad(int x, int y, int width, int height, Color topLeft, Color topRight, Color bottomRight, Color bottomLeft) { - + ByteBuffer buf = (ByteBuffer) quadColor.getData(); buf.rewind(); - + buf.putInt(convertColor(topRight)); buf.putInt(convertColor(topLeft)); - + buf.putInt(convertColor(bottomLeft)); buf.putInt(convertColor(bottomRight)); - + buf.flip(); quadColor.updateData(buf); - + tempMat.loadIdentity(); tempMat.setTranslation(x, getHeight() - y, 0); tempMat.setScale(width, height, 0); - + rm.setWorldMatrix(tempMat); rm.setForcedRenderState(renderState); vertexColorMaterial.render(quadGeom, rm); - + //System.out.format("renderQuad2(%d, %d, %d, %d, %s, %s, %s, %s)\n", x, y, width, height, topLeft.toString(), // topRight.toString(), // bottomRight.toString(), // bottomLeft.toString()); } - + + @Override public void enableClip(int x0, int y0, int x1, int y1) { clipWasSet = true; r.setClipRect(x0, getHeight() - y1, x1 - x0, y1 - y0); } - + + @Override public void disableClip() { if (clipWasSet) { r.clearClipRect(); diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java index 48a995e5be..af8e82e080 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderFontJme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,17 +37,16 @@ public class RenderFontJme implements RenderFont { - private NiftyJmeDisplay display; - private BitmapFont font; - private BitmapText text; - private float actualSize; + private final BitmapFont font; + private final BitmapText text; + private final float actualSize; /** * Initialize the font. * @param name font filename + * @param display (not null) */ public RenderFontJme(String name, NiftyJmeDisplay display) { - this.display = display; font = display.getAssetManager().loadFont(name); text = new BitmapText(font); actualSize = font.getPreferredSize(); @@ -55,14 +54,14 @@ public RenderFontJme(String name, NiftyJmeDisplay display) { } public BitmapText createText() { - return new BitmapText(font); + return new BitmapText(font); } public BitmapFont getFont() { return font; } - - public BitmapText getText(){ + + public BitmapText getText() { return text; } @@ -70,6 +69,7 @@ public BitmapText getText(){ * get font height. * @return height */ + @Override public int getHeight() { return (int) text.getLineHeight(); } @@ -79,17 +79,18 @@ public int getHeight() { * @param str text * @return width of the given text for the current font */ + @Override public int getWidth(final String str) { if (str.length() == 0) { return 0; } - + // Note: BitmapFont is now fixed to return the proper line width // at least for now. The older commented out (by someone else, not me) // code below is arguably 'more accurate' if BitmapFont gets // buggy again. The issue is that the BitmapText and BitmapFont // use a different algorithm for calculating size and both must - // be modified in sync. + // be modified in sync. int result = (int) font.getLineWidth(str); // text.setText(str); // text.updateLogicalState(0); @@ -98,12 +99,13 @@ public int getWidth(final String str) { return result; } + @Override public int getWidth(final String str, final float size) { - // Note: This is supposed to return the width of the String when scaled - // with the size factor. Since I don't know how to do that with - // the font rendering in jme this will only work correctly with - // a size value of 1.f and will return inaccurate values otherwise. - return getWidth(str); + // Note: This is supposed to return the width of the String when scaled + // with the size factor. Since I don't know how to do that with + // the font rendering in jme this will only work correctly with + // a size value of 1.f and will return inaccurate values otherwise. + return getWidth(str); } /** @@ -113,10 +115,12 @@ public int getWidth(final String str, final float size) { * @param size font size * @return width of the character or null when no information for the character is available */ + @Override public int getCharacterAdvance(final char currentCharacter, final char nextCharacter, final float size) { return Math.round(font.getCharacterAdvance(currentCharacter, nextCharacter, size)); } + @Override public void dispose() { } } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java index 584c9bbf91..2354f1c596 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/RenderImageJme.java @@ -45,12 +45,12 @@ public class RenderImageJme implements RenderImage { private int width; private int height; - public RenderImageJme(String filename, boolean linear, NiftyJmeDisplay display){ + public RenderImageJme(String filename, boolean linear, NiftyJmeDisplay display) { TextureKey key = new TextureKey(filename, true); key.setAnisotropy(0); key.setGenerateMips(false); - + texture = (Texture2D) display.getAssetManager().loadTexture(key); texture.setMagFilter(linear ? MagFilter.Bilinear : MagFilter.Nearest); texture.setMinFilter(linear ? MinFilter.BilinearNoMipMaps : MinFilter.NearestNoMipMaps); @@ -60,29 +60,32 @@ public RenderImageJme(String filename, boolean linear, NiftyJmeDisplay display){ height = image.getHeight(); } - public RenderImageJme(Texture2D texture){ + public RenderImageJme(Texture2D texture) { if (texture.getImage() == null) { throw new IllegalArgumentException("texture.getImage() cannot be null"); } - + this.texture = texture; this.image = texture.getImage(); width = image.getWidth(); height = image.getHeight(); } - public Texture2D getTexture(){ + public Texture2D getTexture() { return texture; } + @Override public int getWidth() { return width; } + @Override public int getHeight() { return height; } + @Override public void dispose() { } } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java index 7868468e93..b775934622 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundDeviceJme.java @@ -45,25 +45,28 @@ public class SoundDeviceJme implements SoundDevice { protected AssetManager assetManager; protected AudioRenderer ar; - public SoundDeviceJme(AssetManager assetManager, AudioRenderer ar){ + public SoundDeviceJme(AssetManager assetManager, AudioRenderer ar) { this.assetManager = assetManager; this.ar = ar; } + @Override public void setResourceLoader(NiftyResourceLoader niftyResourceLoader) { } + @Override public SoundHandle loadSound(SoundSystem soundSystem, String filename) { AudioNode an = new AudioNode(assetManager, filename, AudioData.DataType.Buffer); an.setPositional(false); return new SoundHandleJme(ar, an); } + @Override public SoundHandle loadMusic(SoundSystem soundSystem, String filename) { return new SoundHandleJme(ar, assetManager, filename); } + @Override public void update(int delta) { } - } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java index 545f3d2012..27be362c03 100644 --- a/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/SoundHandleJme.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,8 +34,8 @@ import com.jme3.asset.AssetManager; import com.jme3.audio.AudioData; import com.jme3.audio.AudioNode; -import com.jme3.audio.AudioSource.Status; import com.jme3.audio.AudioRenderer; +import com.jme3.audio.AudioSource.Status; import de.lessvoid.nifty.spi.sound.SoundHandle; public class SoundHandleJme implements SoundHandle { @@ -45,50 +45,58 @@ public class SoundHandleJme implements SoundHandle { private String fileName; private float volume = 1; - public SoundHandleJme(AudioRenderer ar, AudioNode node){ - if (ar == null || node == null) { - throw new NullPointerException(); + public SoundHandleJme(AudioRenderer ar, AudioNode node) { + if (ar == null) { + throw new IllegalArgumentException("AudioRenderer cannot be null"); + } + if (node == null) { + throw new IllegalArgumentException("AudioNode cannot be null"); } this.node = node; } /** - * For streaming music only. (May need to loop..) - * @param ar - * @param am - * @param fileName + * For streaming music only. (May need to loop.) + * + * @param ar for rendering audio (not null) + * @param am the AssetManager for loading assets (not null) + * @param fileName the path to the audio asset (not null) */ - public SoundHandleJme(AudioRenderer ar, AssetManager am, String fileName){ - if (ar == null || am == null) { - throw new NullPointerException(); + public SoundHandleJme(AudioRenderer ar, AssetManager am, String fileName) { + if (ar == null) { + throw new IllegalArgumentException("AudioRenderer cannot be null"); + } + if (am == null) { + throw new IllegalArgumentException("AssetManager cannot be null"); } - - this.am = am; if (fileName == null) { - throw new NullPointerException(); + throw new IllegalArgumentException("fileName cannot be null"); } - + + this.am = am; this.fileName = fileName; } + @Override public void play() { - if (fileName != null){ - if (node != null){ + if (fileName != null) { + if (node != null) { node.stop(); } - node = new AudioNode(am, fileName,AudioData.DataType.Stream); + node = new AudioNode(am, fileName, AudioData.DataType.Stream); node.setPositional(false); node.setVolume(volume); node.play(); - }else{ + } else { node.playInstance(); } } + @Override public void stop() { - if (node != null){ + if (node != null) { node.stop(); // Do not nullify the node for non-streaming nodes! if (fileName != null) { @@ -98,6 +106,7 @@ public void stop() { } } + @Override public void setVolume(float f) { if (node != null) { node.setVolume(f); @@ -105,14 +114,17 @@ public void setVolume(float f) { volume = f; } + @Override public float getVolume() { return volume; } + @Override public boolean isPlaying() { return node != null && node.getStatus() == Status.Playing; } + @Override public void dispose() { } } diff --git a/jme3-niftygui/src/main/java/com/jme3/niftygui/package-info.java b/jme3-niftygui/src/main/java/com/jme3/niftygui/package-info.java new file mode 100644 index 0000000000..e24acb473e --- /dev/null +++ b/jme3-niftygui/src/main/java/com/jme3/niftygui/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * implement interactive user interfaces using Nifty GUI + */ +package com.jme3.niftygui; diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag index 31f49d6e5b..8ffbceca20 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec4 m_Color; void main() { diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md index bea82f0da0..ad4b26a294 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.j3md @@ -5,8 +5,8 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100: Common/MatDefs/Nifty/NiftyQuad.vert - FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyQuad.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyQuad.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyQuad.frag WorldParameters { WorldViewProjectionMatrix diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert index 1eb1616c29..2e23fd25b3 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuad.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform mat4 g_WorldViewProjectionMatrix; attribute vec4 inPosition; diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag index 1f0a645e56..f9eb8baa0e 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" varying vec4 color; void main() { diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md index 9412ba3808..fb9b19fd97 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.j3md @@ -4,8 +4,8 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.vert - FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyQuadGrad.frag WorldParameters { WorldViewProjectionMatrix diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert index e62b2b4cb1..c514a2c70f 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyQuadGrad.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform mat4 g_WorldViewProjectionMatrix; attribute vec4 inPosition; diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag index b2b4b95d6a..e333114c65 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform sampler2D m_Texture; uniform vec4 m_Color; diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md index 8d3b919981..12f83cf652 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.j3md @@ -6,8 +6,8 @@ MaterialDef Default GUI { } Technique { - VertexShader GLSL100: Common/MatDefs/Nifty/NiftyTex.vert - FragmentShader GLSL100: Common/MatDefs/Nifty/NiftyTex.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyTex.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Nifty/NiftyTex.frag WorldParameters { WorldViewProjectionMatrix diff --git a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert index c5c3dc1b35..1a7f8499ba 100644 --- a/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert +++ b/jme3-niftygui/src/main/resources/Common/MatDefs/Nifty/NiftyTex.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform mat4 g_WorldViewProjectionMatrix; attribute vec4 inPosition; diff --git a/jme3-plugins-json-gson/build.gradle b/jme3-plugins-json-gson/build.gradle new file mode 100644 index 0000000000..1acc4d3830 --- /dev/null +++ b/jme3-plugins-json-gson/build.gradle @@ -0,0 +1,16 @@ +sourceSets { + main { + java { + + srcDir 'src/main/java' + + } + } +} + +dependencies { + + api project(':jme3-plugins-json') + api libs.gson + +} diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonArray.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonArray.java new file mode 100644 index 0000000000..8cd98f89d2 --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonArray.java @@ -0,0 +1,80 @@ +/* + * 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.plugins.gson; + +import java.util.Iterator; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; + +/** + * GSON implementation of {@link JsonArray}. + */ +class GsonArray extends GsonElement implements JsonArray { + + GsonArray(com.google.gson.JsonElement element) { + super(element); + } + + private com.google.gson.JsonArray arr() { + return element.getAsJsonArray(); + } + + @Override + public Iterator iterator() { + return new Iterator() { + Iterator it = arr().iterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public JsonElement next() { + return new GsonElement(it.next()).autoCast(); + } + }; + } + + @Override + public JsonElement get(int i) { + com.google.gson.JsonElement el = arr().get(i); + return isNull(el)?null:new GsonElement(el).autoCast(); + } + + @Override + public int size() { + return arr().size(); + } + +} diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java new file mode 100644 index 0000000000..102969c65f --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonElement.java @@ -0,0 +1,123 @@ +/* + * 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.plugins.gson; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; + +import java.util.Objects; + +/** + * GSON implementation of {@link JsonElement} + */ +class GsonElement implements JsonElement { + com.google.gson.JsonElement element; + + GsonElement(com.google.gson.JsonElement element) { + this.element = element; + } + + @Override + public int hashCode() { + return Objects.hashCode(this.element); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } + final GsonElement other = (GsonElement) obj; + return Objects.equals(this.element, other.element); + } + + protected boolean isNull(com.google.gson.JsonElement element) { + if (element == null) return true; + if (element.isJsonNull()) return true; + return false; + } + + @Override + public String getAsString() { + return element.getAsString(); + } + + @Override + public JsonObject getAsJsonObject() { + return new GsonObject(element.getAsJsonObject()); + } + + @Override + public float getAsFloat() { + return element.getAsFloat(); + } + + @Override + public int getAsInt() { + return element.getAsInt(); + } + + @Override + public Number getAsNumber() { + return element.getAsNumber(); + } + + @Override + public boolean getAsBoolean() { + return element.getAsBoolean(); + } + + @Override + public JsonArray getAsJsonArray() { + return new GsonArray(element.getAsJsonArray()); + } + + @Override + public JsonPrimitive getAsJsonPrimitive() { + return new GsonPrimitive(element.getAsJsonPrimitive()); + } + + @SuppressWarnings("unchecked") + public T autoCast() { + if(isNull(element)) return null; + if (element.isJsonArray()) return (T) new GsonArray(element.getAsJsonArray()); + if (element.isJsonObject()) return (T) new GsonObject(element.getAsJsonObject()); + if (element.isJsonPrimitive()) return (T) new GsonPrimitive(element.getAsJsonPrimitive()); + return (T) new GsonElement(element); + } + +} diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonObject.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonObject.java new file mode 100644 index 0000000000..8aadbfb182 --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonObject.java @@ -0,0 +1,113 @@ +/* + * 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.plugins.gson; + +import java.util.Set; +import java.util.Map.Entry; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; + +/** + * GSON implementation of {@link JsonObject} + */ +class GsonObject extends GsonElement implements JsonObject { + + GsonObject(com.google.gson.JsonObject gsonObject) { + super(gsonObject); + } + + private com.google.gson.JsonObject obj() { + return (com.google.gson.JsonObject) element; + } + + @Override + public JsonArray getAsJsonArray(String string) { + com.google.gson.JsonArray el = obj().getAsJsonArray(string); + return isNull(el) ? null : new GsonArray(el); + } + + @Override + public JsonObject getAsJsonObject(String string) { + com.google.gson.JsonObject el = obj().getAsJsonObject(string); + return isNull(el) ? null : new GsonObject(el); + } + + @Override + public boolean has(String string) { + return obj().has(string); + } + + @Override + public JsonElement get(String string) { + com.google.gson.JsonElement el = obj().get(string); + return isNull(el)?null:new GsonElement(el).autoCast(); + } + + @Override + public Entry[] entrySet() { + Set> entrySet = obj().entrySet(); + Entry[] entries = new Entry[entrySet.size()]; + int i = 0; + for (Entry entry : entrySet) { + + Entry e = new Entry() { + @Override + public String getKey() { + return entry.getKey(); + } + + @Override + public GsonElement getValue() { + return new GsonElement(entry.getValue()).autoCast(); + } + + @Override + public GsonElement setValue(JsonElement value) { + throw new UnsupportedOperationException("Unimplemented method 'setValue'"); + } + }; + + entries[i++] = e; + } + return entries; + + } + + @Override + public JsonPrimitive getAsJsonPrimitive(String string) { + com.google.gson.JsonPrimitive el= obj().getAsJsonPrimitive(string); + return isNull(el) ? null : new GsonPrimitive(el); + } +} \ No newline at end of file diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonParser.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonParser.java new file mode 100644 index 0000000000..fd7fd0462c --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonParser.java @@ -0,0 +1,48 @@ +/* + * 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.plugins.gson; + +import java.io.InputStream; +import java.io.InputStreamReader; + +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonParser; + +/** + * GSON implementation of {@link JsonParser} + */ +public class GsonParser implements JsonParser { + @Override + public JsonObject parse(InputStream stream) { + return new GsonObject(com.google.gson.JsonParser.parseReader(new com.google.gson.stream.JsonReader(new InputStreamReader(stream))).getAsJsonObject()); + } +} diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.java new file mode 100644 index 0000000000..91399bcebb --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/GsonPrimitive.java @@ -0,0 +1,64 @@ +/* + * 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.plugins.gson; + +import com.jme3.plugins.json.JsonPrimitive; + +/** + * GSON implementation of {@link JsonPrimitive} + */ +public class GsonPrimitive extends GsonElement implements JsonPrimitive { + + public GsonPrimitive(com.google.gson.JsonPrimitive element) { + super(element); + } + + private com.google.gson.JsonPrimitive prim() { + return (com.google.gson.JsonPrimitive) element; + } + + @Override + public boolean isNumber() { + return prim().isNumber(); + } + + @Override + public boolean isBoolean() { + return prim().isBoolean(); + } + + @Override + public boolean isString() { + return prim().isString(); + } + +} diff --git a/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java new file mode 100644 index 0000000000..df1e88063f --- /dev/null +++ b/jme3-plugins-json-gson/src/main/java/com/jme3/plugins/gson/package-info.java @@ -0,0 +1,37 @@ +/* + * 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. + */ + +/** + * A JSON parser that uses Gson internally + */ + +package com.jme3.plugins.gson; diff --git a/jme3-plugins-json/build.gradle b/jme3-plugins-json/build.gradle new file mode 100644 index 0000000000..9a0bb3e43b --- /dev/null +++ b/jme3-plugins-json/build.gradle @@ -0,0 +1,14 @@ +sourceSets { + main { + java { + + srcDir 'src/main/java' + + } + } +} + +dependencies { + + +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/Json.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/Json.java new file mode 100644 index 0000000000..19eb8490d6 --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/Json.java @@ -0,0 +1,111 @@ +/* + * 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.plugins.json; + +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * A json parser factory that allows you to set the parser to use. + * + * @author Riccardo Balbo + */ +public class Json { + /** + * The property name to set the parser to use. + * Should be set automatically by the JmeSystemDelegate. + * Note: changing this property after the first call to Json.create() will have no effect. + */ + public static final String PROPERTY_JSON_PARSER_IMPLEMENTATION = "com.jme3.JsonParserImplementation"; + private static final Logger LOGGER = Logger.getLogger(Json.class.getName()); + private static final String DEFAULT_JSON_PARSER_IMPLEMENTATION = "com.jme3.plugins.gson.GsonParser"; + + private static Class clazz = null; + + /** + * Set the parser to use. + * + * @param clazz + * a class that implements JsonParser + */ + public static void setParser(Class clazz) { + Json.clazz = clazz; + } + + @SuppressWarnings("unchecked") + private static Class findJsonParser(String className) { + Class clazz = null; + + try { + clazz = Class.forName(className); + } catch (final Throwable e) { + LOGGER.log(Level.WARNING, "Unable to access {0}", className); + } + + if (clazz != null && !JsonParser.class.isAssignableFrom(clazz)) { + LOGGER.log(Level.WARNING, "{0} does not implement {1}", new Object[] { className, JsonParser.class.getName() }); + clazz = null; + } + + return (Class) clazz; + } + + /** + * Create a new JsonParser instance. + * + * @return a new JsonParser instance + */ + + public static JsonParser create() { + if (Json.clazz == null) { + String userDefinedImpl = System.getProperty(PROPERTY_JSON_PARSER_IMPLEMENTATION, null); + if (userDefinedImpl != null) { + LOGGER.log(Level.FINE, "Loading user defined JsonParser implementation {0}", userDefinedImpl); + Json.clazz = findJsonParser(userDefinedImpl); + } + if (Json.clazz == null) { + LOGGER.log(Level.FINE, "No usable user defined JsonParser implementation found, using default implementation {0}", DEFAULT_JSON_PARSER_IMPLEMENTATION); + Json.clazz = findJsonParser(DEFAULT_JSON_PARSER_IMPLEMENTATION); + } + } + + if (Json.clazz == null) { + throw new RuntimeException("No JsonParser implementation found"); + } + + try { + return clazz.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new RuntimeException("Could not instantiate JsonParser class " + clazz.getName(), e); + } + } +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonArray.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonArray.java new file mode 100644 index 0000000000..3be3e0321c --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonArray.java @@ -0,0 +1,55 @@ +/* + * 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.plugins.json; + +/** + * Represents an array. + * + * @author Riccardo Balbo + */ +public interface JsonArray extends Iterable { + /** + * Get the element at index i + * + * @param i + * index + * @return the element + */ + public JsonElement get(int i); + + /** + * Get the size of the array + * + * @return the size + */ + public int size(); +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java new file mode 100644 index 0000000000..15894147a9 --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonElement.java @@ -0,0 +1,101 @@ +/* + * 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.plugins.json; + +/** + * A generic element + * + * @author Riccardo Balbo + */ +public interface JsonElement { + + /** + * Returns the object as a String + * + * @return the string + */ + public String getAsString(); + + /** + * Returns the object as a JsonObject + * + * @return the JsonObject + */ + public JsonObject getAsJsonObject(); + + /** + * Returns the object as a float + * + * @return the float + */ + public float getAsFloat(); + + /** + * Returns the object as an int + * + * @return the int + */ + public int getAsInt(); + + /** + * Returns the object as a boolean + * + * @return the boolean + */ + public boolean getAsBoolean(); + + /** + * Returns the object as a JsonArray + * + * @return the JsonArray + */ + public JsonArray getAsJsonArray(); + + /** + * Returns the object as a Number + * @return the Number + */ + public Number getAsNumber(); + + + /** + * Returns the object as a JsonPrimitive + * @return the json primitive + */ + public JsonPrimitive getAsJsonPrimitive(); + + /** + * Cast this JsonElement to a specific type + * @return the casted JsonElement + */ + public T autoCast(); +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonObject.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonObject.java new file mode 100644 index 0000000000..230cba0da1 --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonObject.java @@ -0,0 +1,95 @@ +/* + * 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.plugins.json; + +import java.util.Map.Entry; + +/** + * A generic object/map + * + * @author Riccardo Balbo + */ +public interface JsonObject extends JsonElement { + + /** + * Returns the object property as a String + * + * @param string + * name of the property + * @return the string + */ + public JsonArray getAsJsonArray(String string); + + /** + * Returns the object property as a JsonObject + * + * @param string + * name of the property + * @return the JsonObject + */ + public JsonObject getAsJsonObject(String string); + + /** + * Check if the object has a property + * + * @param string + * name of the property + * @return true if it exists, false otherwise + */ + public boolean has(String string); + + /** + * Returns the object property as generic element + * + * @param string + * name of the property + * @return the element + */ + public JsonElement get(String string); + + /** + * Returns the object's key-value pairs + * + * @return an array of key-value pairs + */ + public Entry[] entrySet(); + + /** + * Returns the object property as a wrapped primitive + * + * @param string + * name of the property + * @return the wrapped primitive + */ + public JsonPrimitive getAsJsonPrimitive(String string); + +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonParser.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonParser.java new file mode 100644 index 0000000000..7e87912c87 --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonParser.java @@ -0,0 +1,50 @@ +/* + * 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.plugins.json; + +import java.io.InputStream; + +/** + * A json parser + * + * @author Riccardo Balbo + */ +public interface JsonParser { + /** + * Parse a json input stream and returns a {@link JsonObject} + * + * @param stream + * the stream to parse + * @return the JsonObject + */ + public JsonObject parse(InputStream stream); +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonPrimitive.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonPrimitive.java new file mode 100644 index 0000000000..ed55a8db77 --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/JsonPrimitive.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 com.jme3.plugins.json; + +/** + * A wrapped primitive + * + * @author Riccardo Balbo + */ +public interface JsonPrimitive { + /** + * Returns the wrapped primitive as a float + * + * @return the float value + */ + public float getAsFloat(); + + /** + * Returns the wrapped primitive as an int + * + * @return the int value + */ + public int getAsInt(); + + /* + * Returns the wrapped primitive as a boolean + * + * @return the boolean value + */ + public boolean getAsBoolean(); + + /** + * Check if the wrapped primitive is a number + * @return true if it is a number + */ + public boolean isNumber(); + + /** + * Check if the wrapped primitive is a boolean + * @return true if it is a boolean + */ + public boolean isBoolean(); + + /** + * Check if the wrapped primitive is a string + * @return true if it is a string + */ + public boolean isString(); + + /** + * Returns the wrapped primitive as a string + * @return the string value + */ + public String getAsString(); + + /** + * Returns the wrapped primitive as a generic number + * @return the number value + */ + public Number getAsNumber(); +} diff --git a/jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java new file mode 100644 index 0000000000..55a9208e1c --- /dev/null +++ b/jme3-plugins-json/src/main/java/com/jme3/plugins/json/package-info.java @@ -0,0 +1,36 @@ +/* + * 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. + */ + +/** + * An abstraction layer to implement JSON parsers in jMonkeyEngine + */ +package com.jme3.plugins.json; diff --git a/jme3-plugins/build.gradle b/jme3-plugins/build.gradle index 0c2280b313..e84f234a68 100644 --- a/jme3-plugins/build.gradle +++ b/jme3-plugins/build.gradle @@ -1,7 +1,3 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - sourceSets { main { java { @@ -14,7 +10,9 @@ sourceSets { } dependencies { - compile project(':jme3-core') - compile 'com.google.code.gson:gson:2.8.1' - testCompile project(':jme3-desktop') + api project(':jme3-core') + + implementation project(':jme3-plugins-json') + implementation project(':jme3-plugins-json-gson') + testRuntimeOnly project(':jme3-desktop') } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java index 20f5c0fe87..cccac38727 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/AnimationList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,6 +33,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Defines animations set that will be created while loading FBX scene @@ -42,35 +43,71 @@ * Skeletal animations will be created if only scene contain skeletal bones

                  */ public class AnimationList { - - List list = new ArrayList(); - - /** - * Use in the case of multiple animation layers in FBX asset - * @param name - animation name to access via {@link com.jme3.animation.AnimControl} - */ - public void add(String name, int firstFrame, int lastFrame) { - add(name, null, firstFrame, lastFrame); - } - - /** - * Use in the case of multiple animation layers in FBX asset - * @param name - animation name to access via {@link com.jme3.animation.AnimControl} - * @param layerName - source layer - */ - public void add(String name, String layerName, int firstFrame, int lastFrame) { - AnimInverval cue = new AnimInverval(); - cue.name = name; - cue.layerName = layerName; - cue.firstFrame = firstFrame; - cue.lastFrame = lastFrame; - list.add(cue); - } - - static class AnimInverval { - String name; - String layerName; - int firstFrame; - int lastFrame; - } + + List list = new ArrayList<>(); + + /** + * Use in the case of multiple animation layers in FBX asset + * + * @param name - animation name to access via {@link com.jme3.animation.AnimControl} + * @param firstFrame the index of the first frame + * @param lastFrame the index of the last frame + */ + public void add(String name, int firstFrame, int lastFrame) { + add(name, null, firstFrame, lastFrame); + } + + /** + * Use in the case of multiple animation layers in FBX asset + * + * @param name - animation name to access via {@link com.jme3.animation.AnimControl} + * @param layerName - source layer + * @param firstFrame the index of the first frame + * @param lastFrame the index of the last frame + */ + public void add(String name, String layerName, int firstFrame, int lastFrame) { + AnimInverval cue = new AnimInverval(); + cue.name = name; + cue.layerName = layerName; + cue.firstFrame = firstFrame; + cue.lastFrame = lastFrame; + list.add(cue); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final AnimationList other = (AnimationList)obj; + return Objects.equals(this.list, other.list); + } + + @Override + public int hashCode() { + return 119 + Objects.hashCode(this.list); + } + + static class AnimInverval { + String name; + String layerName; + int firstFrame; + int lastFrame; + // hashCode generator, for good measure + @Override + public int hashCode() { + int hash = 7; + hash = 29 * hash + Objects.hashCode(this.name); + hash = 29 * hash + Objects.hashCode(this.layerName); + hash = 29 * hash + this.firstFrame; + hash = 29 * hash + this.lastFrame; + return hash; + } + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java index 2db439e401..f2e9277014 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureKey.java @@ -45,41 +45,41 @@ *

                  Filename is required to acquire proper type asset loader according to extension.

                  */ public class ContentTextureKey extends TextureKey { - - private byte[] content; - - public ContentTextureKey(String name, byte[] content) { - super(name); - this.content = content; - } - - public byte[] getContent() { - return content; - } - - @Override - public Class getCacheType(){ - // Need to override this so that textures embedded in FBX - // don't get cached by the asset manager. - return null; - } + + private byte[] content; + + public ContentTextureKey(String name, byte[] content) { + super(name); + this.content = content; + } + + public byte[] getContent() { + return content; + } + + @Override + public Class getCacheType(){ + // Need to override this so that textures embedded in FBX + // don't get cached by the asset manager. + return null; + } - @Override - public String toString() { - return super.toString() + " " + content.length + " bytes"; - } - - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); - oc.write(content, "content", new byte[0]); - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - content = ic.readByteArray("content", new byte[0]); - } + @Override + public String toString() { + return super.toString() + " " + content.length + " bytes"; + } + + @Override + public void write(JmeExporter ex) throws IOException { + super.write(ex); + OutputCapsule oc = ex.getCapsule(this); + oc.write(content, "content", new byte[0]); + } + + @Override + public void read(JmeImporter im) throws IOException { + super.read(im); + InputCapsule ic = im.getCapsule(this); + content = ic.readByteArray("content", new byte[0]); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java index 76902159c7..2dea41b4cd 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/ContentTextureLocator.java @@ -45,45 +45,45 @@ * Used to locate a resource based on a {@link ContentTextureKey}. */ public class ContentTextureLocator implements AssetLocator { - - private static final Logger logger = Logger.getLogger(ContentTextureLocator.class.getName()); - - @Override - public void setRootPath(String rootPath) { - } - - @SuppressWarnings("rawtypes") - @Override - public AssetInfo locate(AssetManager manager, AssetKey key) { - if(key instanceof ContentTextureKey) { - String name = key.getName(); - byte[] content = ((ContentTextureKey) key).getContent(); - if(content != null) { - return new ContentAssetInfo(manager, key, content); - } else { - logger.log(Level.WARNING, "No content for " + name); - return null; - } - } else { - logger.log(Level.SEVERE, "AssetKey should be TextureContentKey instance"); - return null; - } - } - - private class ContentAssetInfo extends AssetInfo { - - private InputStream stream; - - @SuppressWarnings("rawtypes") - public ContentAssetInfo(AssetManager assetManager, AssetKey key, byte[] content) { - super(assetManager, key); - this.stream = (content != null) ? new ByteArrayInputStream(content) : null; - } - - @Override - public InputStream openStream() { - return stream; - } - } - + + private static final Logger logger = Logger.getLogger(ContentTextureLocator.class.getName()); + + @Override + public void setRootPath(String rootPath) { + } + + @SuppressWarnings("rawtypes") + @Override + public AssetInfo locate(AssetManager manager, AssetKey key) { + if(key instanceof ContentTextureKey) { + String name = key.getName(); + byte[] content = ((ContentTextureKey) key).getContent(); + if(content != null) { + return new ContentAssetInfo(manager, key, content); + } else { + logger.log(Level.WARNING, "No content for " + name); + return null; + } + } else { + logger.log(Level.SEVERE, "AssetKey should be TextureContentKey instance"); + return null; + } + } + + private class ContentAssetInfo extends AssetInfo { + + private InputStream stream; + + @SuppressWarnings("rawtypes") + public ContentAssetInfo(AssetManager assetManager, AssetKey key, byte[] content) { + super(assetManager, key); + this.stream = (content != null) ? new ByteArrayInputStream(content) : null; + } + + @Override + public InputStream openStream() { + return stream; + } + } + } 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 da25bd93e8..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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,6 @@ import com.jme3.scene.plugins.fbx.anim.FbxAnimStack; import com.jme3.scene.plugins.fbx.anim.FbxBindPose; import com.jme3.scene.plugins.fbx.anim.FbxLimbNode; -import com.jme3.scene.plugins.fbx.file.FbxDump; import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.plugins.fbx.file.FbxFile; import com.jme3.scene.plugins.fbx.file.FbxReader; @@ -81,10 +80,10 @@ public class FbxLoader implements AssetLoader { private String sceneFilename; private String sceneFolderName; private FbxGlobalSettings globalSettings; - private final Map objectMap = new HashMap(); + private final Map objectMap = new HashMap<>(); - private final List animStacks = new ArrayList(); - private final List bindPoses = new ArrayList(); + private final List animStacks = new ArrayList<>(); + private final List bindPoses = new ArrayList<>(); @Override public Object load(AssetInfo assetInfo) throws IOException { @@ -116,7 +115,7 @@ public Object load(AssetInfo assetInfo) throws IOException { // Need world transforms for skeleton creation. updateWorldTransforms(); - // Need skeletons for meshs to be created in scene graph construction. + // Need skeletons for meshes to be created in scene graph construction. // Mesh bone indices require skeletons to determine bone index. constructSkeletons(); @@ -218,15 +217,6 @@ private void loadObjects(FbxElement element) { } } - private void removeUnconnectedObjects() { - for (FbxObject object : new ArrayList(objectMap.values())) { - if (!object.isJmeObjectCreated()) { - logger.log(Level.WARNING, "Purging orphan FBX object: {0}", object); - objectMap.remove(object.getId()); - } - } - } - private void connectObjects(FbxElement element) { if (objectMap.isEmpty()) { logger.log(Level.WARNING, "FBX file is missing object information"); @@ -267,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); } @@ -282,7 +287,9 @@ private void connectObjects(FbxElement element) { private void applyBindPoses() { for (FbxBindPose bindPose : bindPoses) { Map bindPoseData = bindPose.getJmeObject(); - logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); + if (logger.isLoggable(Level.INFO)) { + logger.log(Level.INFO, "Applying {0} bind poses", bindPoseData.size()); + } for (Map.Entry entry : bindPoseData.entrySet()) { FbxObject obj = objectMap.get(entry.getKey()); if (obj instanceof FbxNode) { @@ -309,7 +316,7 @@ private void constructAnimations() { // So, we need to use heuristics to find which node(s) // an animation is associated with, so we can create the AnimControl // in the appropriate location in the scene. - Map pairs = new HashMap(); + Map pairs = new HashMap<>(); for (FbxAnimStack stack : animStacks) { for (FbxAnimLayer layer : stack.getLayers()) { for (FbxAnimCurveNode curveNode : layer.getAnimationCurveNodes()) { @@ -336,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/RotationOrder.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java index 8a1e05b361..88a562351e 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/RotationOrder.java @@ -5,48 +5,48 @@ import com.jme3.math.Vector3f; public enum RotationOrder { - - EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; - - // Static values field for fast access by an oridinal without Enum.values() overhead - public static final RotationOrder[] values = values(); - - private RotationOrder() { - } - - public Quaternion rotate(Vector3f vec) { - return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this); - } - - public Quaternion rotate(float x, float y, float z) { - return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); - } - - private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) { - switch(order) { - case EULER_XYZ: - return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z); - case EULER_YXZ: - return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z); - case EULER_ZXY: - return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y); - case EULER_ZYX: - return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X); - case EULER_YZX: - return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X); - case EULER_XZY: - return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y); - case SPHERIC_XYZ: - default: - throw new IllegalArgumentException("Spheric rotation is unsupported in this importer"); - } - } - - private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { - // TODO It has some potential in optimization - Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); - Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); - Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); - return q1.multLocal(q2).multLocal(q3); - } + + EULER_XYZ, EULER_XZY, EULER_YZX, EULER_YXZ, EULER_ZXY, EULER_ZYX, SPHERIC_XYZ; + + // Static values field for fast access by ordinal without Enum.values() overhead + public static final RotationOrder[] values = values(); + + private RotationOrder() { + } + + public Quaternion rotate(Vector3f vec) { + return fromEuler(vec.x * FastMath.DEG_TO_RAD, vec.y * FastMath.DEG_TO_RAD, vec.z * FastMath.DEG_TO_RAD, this); + } + + public Quaternion rotate(float x, float y, float z) { + return fromEuler(x * FastMath.DEG_TO_RAD, y * FastMath.DEG_TO_RAD, z * FastMath.DEG_TO_RAD, this); + } + + private static Quaternion fromEuler(float x, float y, float z, RotationOrder order) { + switch(order) { + case EULER_XYZ: + return toQuat(x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z); + case EULER_YXZ: + return toQuat(y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z); + case EULER_ZXY: + return toQuat(z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X, y, Vector3f.UNIT_Y); + case EULER_ZYX: + return toQuat(z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y, x, Vector3f.UNIT_X); + case EULER_YZX: + return toQuat(y, Vector3f.UNIT_Y, z, Vector3f.UNIT_Z, x, Vector3f.UNIT_X); + case EULER_XZY: + return toQuat(x, Vector3f.UNIT_X, z, Vector3f.UNIT_Z, y, Vector3f.UNIT_Y); + case SPHERIC_XYZ: + default: + throw new IllegalArgumentException("Spherical rotation is unsupported in this importer"); + } + } + + private static Quaternion toQuat(float ax1v, Vector3f ax1, float ax2v, Vector3f ax2, float ax3v, Vector3f ax3) { + // TODO It has some potential in optimization + Quaternion q1 = new Quaternion().fromAngleNormalAxis(ax1v, ax1); + Quaternion q2 = new Quaternion().fromAngleNormalAxis(ax2v, ax2); + Quaternion q3 = new Quaternion().fromAngleNormalAxis(ax3v, ax3); + return q1.multLocal(q2).multLocal(q3); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java index a4362d46d3..67e184664c 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneKey.java @@ -32,23 +32,47 @@ package com.jme3.scene.plugins.fbx; import com.jme3.asset.ModelKey; +import java.util.Objects; public class SceneKey extends ModelKey { - - private final AnimationList animList; - - public SceneKey(String name) { - super(name); - this.animList = null; - } - - public SceneKey(String name, AnimationList animationList) { - super(name); - this.animList = animationList; - } - - public AnimationList getAnimations() { - return this.animList; - } - + + private final AnimationList animList; + + public SceneKey(String name) { + super(name); + this.animList = null; + } + + public SceneKey(String name, AnimationList animationList) { + super(name); + this.animList = animationList; + } + + public AnimationList getAnimations() { + return this.animList; + } + + @Override + public boolean equals(Object object) { + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + final SceneKey other = (SceneKey)object; + if (!super.equals(other)) { + return false; + } + if (!Objects.equals(animList, other.animList)) { + return false; + } + return true; + } + + @Override + public int hashCode() { + return 413 + Objects.hashCode(animList); + } + } \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java index 43565837ae..e68649f276 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneLoader.java @@ -48,428 +48,428 @@ * or loaded from different animation layer.

                  */ public class SceneLoader implements AssetLoader { - - private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); - private static final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor - public static final boolean WARN_IGNORED_ATTRIBUTES = false; - - // Scene loading data - private List warnings = new ArrayList<>(); - private AnimationList animList; - private String sceneName; - public String sceneFilename; - public String sceneFolderName; - public AssetManager assetManager; - public AssetInfo currentAssetInfo; - - // Scene global settings - private float animFrameRate; - public float unitSize; - public int xAxis = 1; - public int yAxis = 1; - public int zAxis = 1; - - // Scene objects - private Map allObjects = new HashMap<>(); // All supported FBX objects - private Map skinMap = new HashMap<>(); // Skin for bone clusters - private Map alayerMap = new HashMap<>(); // Animation layers - public Map modelMap = new HashMap<>(); // Nodes - private Map limbMap = new HashMap<>(); // Nodes that are actually bones - private Map bindMap = new HashMap<>(); // Node bind poses - private Map geomMap = new HashMap<>(); // Mesh geometries - private Skeleton skeleton; - private AnimControl animControl; - public Node sceneNode; - - public void warning(String warning) { - warnings.add(warning); - } - - @Override - public Object load(AssetInfo assetInfo) throws IOException { - this.currentAssetInfo = assetInfo; - this.assetManager = assetInfo.getManager(); - AssetKey assetKey = assetInfo.getKey(); - if(assetKey instanceof SceneKey) - animList = ((SceneKey) assetKey).getAnimations(); - InputStream stream = assetInfo.openStream(); - final Node sceneNode = this.sceneNode = new Node(sceneName + "-scene"); - try { - sceneFilename = assetKey.getName(); - sceneFolderName = assetKey.getFolder(); - String ext = assetKey.getExtension(); - sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); - if(sceneFolderName != null && sceneFolderName.length() > 0) - sceneName = sceneName.substring(sceneFolderName.length()); - loadScene(stream); - linkScene(); - if(warnings.size() > 0) - logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n")); - } finally { - releaseObjects(); - if(stream != null) - stream.close(); - } - return sceneNode; - } - - private void loadScene(InputStream stream) throws IOException { - logger.log(Level.FINE, "Loading scene {0}", sceneFilename); - long startTime = System.currentTimeMillis(); - FbxFile scene = FbxReader.readFBX(stream); - for(FbxElement e : scene.rootElements) { - // Is it possible for elements to be in wrong order? - switch(e.id) { - case "GlobalSettings": - loadGlobalSettings(e); - break; - case "Objects": - loadObjects(e); - break; - case "Connections": - loadConnections(e); - break; - } - } - long estimatedTime = System.currentTimeMillis() - startTime; - logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); - } - - private void loadGlobalSettings(FbxElement element) { - for(FbxElement e2 : element.getFbxProperties()) { - String propName = (String) e2.properties.get(0); - switch(propName) { - case "UnitScaleFactor": - this.unitSize = ((Double) e2.properties.get(4)).floatValue(); - break; - case "CustomFrameRate": - float framerate = ((Double) e2.properties.get(4)).floatValue(); - if(framerate != -1) - this.animFrameRate = framerate; - break; - case "UpAxisSign": - this.yAxis = ((Integer) e2.properties.get(4)).intValue(); - break; - case "FrontAxisSign": - this.zAxis = ((Integer) e2.properties.get(4)).intValue(); - break; - case "CoordAxisSign": - this.xAxis = ((Integer) e2.properties.get(4)).intValue(); - break; - } - } - } - - private void loadObjects(FbxElement element) throws IOException { - FbxObject obj = null; - for(FbxElement e : element.children) { - switch(e.id) { - case "Geometry": - FbxMesh mesh = new FbxMesh(this, e); - obj = mesh; - if(mesh.geometries != null) - geomMap.put(mesh.id, mesh); - break; - case "Material": - obj = new FbxMaterial(this, e); - break; - case "Model": - FbxNode node = new FbxNode(this, e); - obj = node; - modelMap.put(node.id, node); - if(node.isLimb()) - limbMap.put(node.id, node); - break; - case "Pose": - FbxBindPose pose = new FbxBindPose(this, e); - obj = pose; - bindMap.put(pose.id, pose); - break; - case "Texture": - obj = new FbxTexture(this, e); - break; - case "Video": - obj = new FbxImage(this, e); - break; - case "Deformer": - obj = loadDeformer(e); - break; - case "AnimationLayer": - FbxObject layer = new FbxObject(this, e); - obj = layer; - alayerMap.put(layer.id, layer); - break; - case "AnimationCurve": - obj = new FbxAnimCurve(this, e); - break; - case "AnimationCurveNode": - obj = new FbxAnimNode(this, e); - break; - default: - obj = null; - //warnings.add("Object with id '" + e.id + "' was ignored"); - } - if(obj != null) - allObjects.put(obj.id, obj); - } - } - - private FbxObject loadDeformer(FbxElement element) { - String type = (String) element.properties.get(2); - switch(type) { - case "Skin": - FbxSkin skinData = new FbxSkin(this, element); - skinMap.put(skinData.id, skinData); - return skinData; - case "Cluster": - FbxCluster clusterData = new FbxCluster(this, element); - return clusterData; - } - return null; - } - - private void loadConnections(FbxElement element) { - for(FbxElement e : element.children) { - if(e.id.equals("C")) { - String type = (String) e.properties.get(0); - long objId, refId; - FbxObject obj, ref; - switch(type) { - case "OO": - objId = (Long) e.properties.get(1); - refId = (Long) e.properties.get(2); - obj = allObjects.get(objId); - ref = allObjects.get(refId); - if(ref != null) { - ref.link(obj); - } else if(refId == 0) { - obj.linkToZero(); - } - break; - case "OP": - objId = (Long) e.properties.get(1); - refId = (Long) e.properties.get(2); - String propName = (String) e.properties.get(3); - obj = allObjects.get(objId); - ref = allObjects.get(refId); - if(ref != null) - ref.link(obj, propName); - break; - } - } - } - } - - private void linkScene() { - logger.log(Level.FINE, "Linking scene objects"); - long startTime = System.currentTimeMillis(); - applySkinning(); - buildAnimations(); - for(FbxMesh mesh : geomMap.values()) - mesh.clearMaterials(); - // Remove bones from node structures : JME creates attach node by itself - for(FbxNode limb : limbMap.values()) - limb.node.removeFromParent(); - long estimatedTime = System.currentTimeMillis() - startTime; - logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime); - } - - private void applySkinning() { - for(FbxBindPose pose : bindMap.values()) - pose.fillBindTransforms(); - if(limbMap == null) - return; - List bones = new ArrayList<>(); - for(FbxNode limb : limbMap.values()) { - if(limb.bone != null) { - bones.add(limb.bone); - limb.buildBindPoseBoneTransform(); - } - } - skeleton = new Skeleton(bones.toArray(new Bone[bones.size()])); - skeleton.setBindingPose(); - for(FbxNode limb : limbMap.values()) - limb.setSkeleton(skeleton); - for(FbxSkin skin : skinMap.values()) - skin.generateSkinning(); - // Attach controls - animControl = new AnimControl(skeleton); - sceneNode.addControl(animControl); - SkeletonControl control = new SkeletonControl(skeleton); - sceneNode.addControl(control); - } - - private void buildAnimations() { - if(skeleton == null) - return; - if(animList == null || animList.list.size() == 0) { - animList = new AnimationList(); - for(long layerId : alayerMap.keySet()) { - FbxObject layer = alayerMap.get(layerId); - animList.add(layer.name, layer.name, 0, -1); - } - } - // Extract animations - HashMap anims = new HashMap(); - for(AnimInverval animInfo : animList.list) { - float realLength = 0; - float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate; - float animStart = animInfo.firstFrame / this.animFrameRate; - float animStop = animInfo.lastFrame / this.animFrameRate; - Animation anim = new Animation(animInfo.name, length); - // Search source layer for animation nodes - long sourceLayerId = 0L; - for(long layerId : alayerMap.keySet()) { - FbxObject layer = alayerMap.get(layerId); - if(layer.name.equals(animInfo.layerName)) { - sourceLayerId = layerId; - break; - } - } - // Build bone tracks - for(FbxNode limb : limbMap.values()) { - // Animation channels may have different keyframes (non-baked animation). - // So we have to restore intermediate values for all channels cause of JME requires - // a bone track as a single channel with collective transformation for each keyframe - Set stamps = new TreeSet(); // Sorted unique timestamps - FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId); - FbxAnimNode animRotation = limb.animRotation(sourceLayerId); - FbxAnimNode animScale = limb.animScale(sourceLayerId); - boolean haveTranslation = haveAnyChannel(animTranslation); - boolean haveRotation = haveAnyChannel(animRotation); - boolean haveScale = haveAnyChannel(animScale); - // Collect keyframes stamps - if(haveTranslation) - animTranslation.exportTimes(stamps); - if(haveRotation) - animRotation.exportTimes(stamps); - if(haveScale) - animScale.exportTimes(stamps); - if(stamps.isEmpty()) - continue; - long[] keyTimes = new long[stamps.size()]; - int cnt = 0; - for(long t : stamps) - keyTimes[cnt++] = t; - // Calculate keys interval by animation time interval - int firstKeyIndex = 0; - int lastKeyIndex = keyTimes.length - 1; - for(int i = 0; i < keyTimes.length; ++i) { - float time = (float) (((double) keyTimes[i]) * secondsPerUnit); // Translate into seconds - if(time <= animStart) - firstKeyIndex = i; - if(time >= animStop && animStop >= 0) { - lastKeyIndex = i; - break; - } - } - int keysCount = lastKeyIndex - firstKeyIndex + 1; - if(keysCount <= 0) - continue; - float[] times = new float[keysCount]; - Vector3f[] translations = new Vector3f[keysCount]; - Quaternion[] rotations = new Quaternion[keysCount]; - Vector3f[] scales = null; - // Calculate keyframes times - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - float time = (float) (((double) keyTimes[keyIndex]) * secondsPerUnit); // Translate into seconds - times[i] = time - animStart; - realLength = Math.max(realLength, times[i]); - } - // Load keyframes from animation curves - if(haveTranslation) { - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animTranslation; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic? - translations[i] = tvec.divideLocal(unitSize); - } - } else { - for(int i = 0; i < keysCount; ++i) - translations[i] = Vector3f.ZERO; - } - RotationOrder ro = RotationOrder.EULER_XYZ; - if(haveRotation) { - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animRotation; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); - rotations[i] = ro.rotate(tvec); - } - } else { - for(int i = 0; i < keysCount; ++i) - rotations[i] = Quaternion.IDENTITY; - } - if(haveScale) { - scales = new Vector3f[keysCount]; - for(int i = 0; i < keysCount; ++i) { - int keyIndex = firstKeyIndex + i; - FbxAnimNode n = animScale; - Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); - scales[i] = tvec; - } - } - BoneTrack track = null; - if(haveScale) - track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales); - else - track = new BoneTrack(limb.boneIndex, times, translations, rotations); - anim.addTrack(track); - } - if(realLength != length && animInfo.lastFrame == -1) { - Track[] tracks = anim.getTracks(); - if(tracks == null || tracks.length == 0) - continue; - anim = new Animation(animInfo.name, realLength); - for(Track track : tracks) - anim.addTrack(track); - } - anims.put(anim.getName(), anim); - } - animControl.setAnimations(anims); - } - - private void releaseObjects() { - // Reset settings - unitSize = 1; - animFrameRate = 30; - xAxis = 1; - yAxis = 1; - zAxis = 1; - // Clear cache - warnings.clear(); - animList = null; - sceneName = null; - sceneFilename = null; - sceneFolderName = null; - assetManager = null; - currentAssetInfo = null; - // Clear objects - allObjects.clear(); - skinMap.clear(); - alayerMap.clear(); - modelMap.clear(); - limbMap.clear(); - bindMap.clear(); - geomMap.clear(); - skeleton = null; - animControl = null; - sceneNode = null; - } - - - private static boolean haveAnyChannel(FbxAnimNode anims) { - return anims != null && anims.haveAnyChannel(); - } - - private static String join(List list, String glue) { - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < list.size(); ++i) { - if(sb.length() != 0) - sb.append(glue); - sb.append(list.get(i)); - } - return sb.toString(); - } + + private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); + private static final double secondsPerUnit = 1 / 46186158000d; // Animation speed factor + public static final boolean WARN_IGNORED_ATTRIBUTES = false; + + // Scene loading data + private List warnings = new ArrayList<>(); + private AnimationList animList; + private String sceneName; + public String sceneFilename; + public String sceneFolderName; + public AssetManager assetManager; + public AssetInfo currentAssetInfo; + + // Scene global settings + private float animFrameRate; + public float unitSize; + public int xAxis = 1; + public int yAxis = 1; + public int zAxis = 1; + + // Scene objects + private Map allObjects = new HashMap<>(); // All supported FBX objects + private Map skinMap = new HashMap<>(); // Skin for bone clusters + private Map aLayerMap = new HashMap<>(); // Animation layers + public Map modelMap = new HashMap<>(); // Nodes + private Map limbMap = new HashMap<>(); // Nodes that are actually bones + private Map bindMap = new HashMap<>(); // Node bind poses + private Map geomMap = new HashMap<>(); // Mesh geometries + private Skeleton skeleton; + private AnimControl animControl; + public Node sceneNode; + + public void warning(String warning) { + warnings.add(warning); + } + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + this.currentAssetInfo = assetInfo; + this.assetManager = assetInfo.getManager(); + AssetKey assetKey = assetInfo.getKey(); + if(assetKey instanceof SceneKey) + animList = ((SceneKey) assetKey).getAnimations(); + InputStream stream = assetInfo.openStream(); + final Node sceneNode = this.sceneNode = new Node(sceneName + "-scene"); + try { + sceneFilename = assetKey.getName(); + sceneFolderName = assetKey.getFolder(); + String ext = assetKey.getExtension(); + sceneName = sceneFilename.substring(0, sceneFilename.length() - ext.length() - 1); + if(sceneFolderName != null && sceneFolderName.length() > 0) + sceneName = sceneName.substring(sceneFolderName.length()); + loadScene(stream); + linkScene(); + if(warnings.size() > 0) + logger.log(Level.WARNING, "Model load finished with warnings:\n" + join(warnings, "\n")); + } finally { + releaseObjects(); + if(stream != null) + stream.close(); + } + return sceneNode; + } + + private void loadScene(InputStream stream) throws IOException { + logger.log(Level.FINE, "Loading scene {0}", sceneFilename); + long startTime = System.currentTimeMillis(); + FbxFile scene = FbxReader.readFBX(stream); + for(FbxElement e : scene.rootElements) { + // Is it possible for elements to be in wrong order? + switch(e.id) { + case "GlobalSettings": + loadGlobalSettings(e); + break; + case "Objects": + loadObjects(e); + break; + case "Connections": + loadConnections(e); + break; + } + } + long estimatedTime = System.currentTimeMillis() - startTime; + logger.log(Level.FINE, "Loading done in {0} ms", estimatedTime); + } + + private void loadGlobalSettings(FbxElement element) { + for(FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + switch(propName) { + case "UnitScaleFactor": + this.unitSize = ((Double) e2.properties.get(4)).floatValue(); + break; + case "CustomFrameRate": + float framerate = ((Double) e2.properties.get(4)).floatValue(); + if(framerate != -1) + this.animFrameRate = framerate; + break; + case "UpAxisSign": + this.yAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + case "FrontAxisSign": + this.zAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + case "CoordAxisSign": + this.xAxis = ((Integer) e2.properties.get(4)).intValue(); + break; + } + } + } + + private void loadObjects(FbxElement element) throws IOException { + FbxObject obj = null; + for(FbxElement e : element.children) { + switch(e.id) { + case "Geometry": + FbxMesh mesh = new FbxMesh(this, e); + obj = mesh; + if(mesh.geometries != null) + geomMap.put(mesh.id, mesh); + break; + case "Material": + obj = new FbxMaterial(this, e); + break; + case "Model": + FbxNode node = new FbxNode(this, e); + obj = node; + modelMap.put(node.id, node); + if(node.isLimb()) + limbMap.put(node.id, node); + break; + case "Pose": + FbxBindPose pose = new FbxBindPose(this, e); + obj = pose; + bindMap.put(pose.id, pose); + break; + case "Texture": + obj = new FbxTexture(this, e); + break; + case "Video": + obj = new FbxImage(this, e); + break; + case "Deformer": + obj = loadDeformer(e); + break; + case "AnimationLayer": + FbxObject layer = new FbxObject(this, e); + obj = layer; + aLayerMap.put(layer.id, layer); + break; + case "AnimationCurve": + obj = new FbxAnimCurve(this, e); + break; + case "AnimationCurveNode": + obj = new FbxAnimNode(this, e); + break; + default: + obj = null; + //warnings.add("Object with id '" + e.id + "' was ignored"); + } + if(obj != null) + allObjects.put(obj.id, obj); + } + } + + private FbxObject loadDeformer(FbxElement element) { + String type = (String) element.properties.get(2); + switch(type) { + case "Skin": + FbxSkin skinData = new FbxSkin(this, element); + skinMap.put(skinData.id, skinData); + return skinData; + case "Cluster": + FbxCluster clusterData = new FbxCluster(this, element); + return clusterData; + } + return null; + } + + private void loadConnections(FbxElement element) { + for(FbxElement e : element.children) { + if(e.id.equals("C")) { + String type = (String) e.properties.get(0); + long objId, refId; + FbxObject obj, ref; + switch(type) { + case "OO": + objId = (Long) e.properties.get(1); + refId = (Long) e.properties.get(2); + obj = allObjects.get(objId); + ref = allObjects.get(refId); + if(ref != null) { + ref.link(obj); + } else if(refId == 0) { + obj.linkToZero(); + } + break; + case "OP": + objId = (Long) e.properties.get(1); + refId = (Long) e.properties.get(2); + String propName = (String) e.properties.get(3); + obj = allObjects.get(objId); + ref = allObjects.get(refId); + if(ref != null) + ref.link(obj, propName); + break; + } + } + } + } + + private void linkScene() { + logger.log(Level.FINE, "Linking scene objects"); + long startTime = System.currentTimeMillis(); + applySkinning(); + buildAnimations(); + for(FbxMesh mesh : geomMap.values()) + mesh.clearMaterials(); + // Remove bones from node structures : JME creates attach node by itself + for(FbxNode limb : limbMap.values()) + limb.node.removeFromParent(); + long estimatedTime = System.currentTimeMillis() - startTime; + logger.log(Level.FINE, "Linking done in {0} ms", estimatedTime); + } + + private void applySkinning() { + for(FbxBindPose pose : bindMap.values()) + pose.fillBindTransforms(); + if(limbMap == null) + return; + List bones = new ArrayList<>(); + for(FbxNode limb : limbMap.values()) { + if(limb.bone != null) { + bones.add(limb.bone); + limb.buildBindPoseBoneTransform(); + } + } + skeleton = new Skeleton(bones.toArray(new Bone[bones.size()])); + skeleton.setBindingPose(); + for(FbxNode limb : limbMap.values()) + limb.setSkeleton(skeleton); + for(FbxSkin skin : skinMap.values()) + skin.generateSkinning(); + // Attach controls + animControl = new AnimControl(skeleton); + sceneNode.addControl(animControl); + SkeletonControl control = new SkeletonControl(skeleton); + sceneNode.addControl(control); + } + + private void buildAnimations() { + if(skeleton == null) + return; + if(animList == null || animList.list.size() == 0) { + animList = new AnimationList(); + for(long layerId : aLayerMap.keySet()) { + FbxObject layer = aLayerMap.get(layerId); + animList.add(layer.name, layer.name, 0, -1); + } + } + // Extract animations + HashMap anims = new HashMap<>(); + for(AnimInverval animInfo : animList.list) { + float realLength = 0; + float length = (animInfo.lastFrame - animInfo.firstFrame) / this.animFrameRate; + float animStart = animInfo.firstFrame / this.animFrameRate; + float animStop = animInfo.lastFrame / this.animFrameRate; + Animation anim = new Animation(animInfo.name, length); + // Search source layer for animation nodes + long sourceLayerId = 0L; + for(long layerId : aLayerMap.keySet()) { + FbxObject layer = aLayerMap.get(layerId); + if(layer.name.equals(animInfo.layerName)) { + sourceLayerId = layerId; + break; + } + } + // Build bone tracks + for(FbxNode limb : limbMap.values()) { + // Animation channels may have different keyframes (non-baked animation). + // So we have to restore intermediate values for all channels cause of JME requires + // a bone track as a single channel with collective transformation for each keyframe + Set stamps = new TreeSet<>(); // Sorted unique timestamps + FbxAnimNode animTranslation = limb.animTranslation(sourceLayerId); + FbxAnimNode animRotation = limb.animRotation(sourceLayerId); + FbxAnimNode animScale = limb.animScale(sourceLayerId); + boolean haveTranslation = haveAnyChannel(animTranslation); + boolean haveRotation = haveAnyChannel(animRotation); + boolean haveScale = haveAnyChannel(animScale); + // Collect keyframes stamps + if(haveTranslation) + animTranslation.exportTimes(stamps); + if(haveRotation) + animRotation.exportTimes(stamps); + if(haveScale) + animScale.exportTimes(stamps); + if(stamps.isEmpty()) + continue; + long[] keyTimes = new long[stamps.size()]; + int cnt = 0; + for(long t : stamps) + keyTimes[cnt++] = t; + // Calculate keys interval by animation time interval + int firstKeyIndex = 0; + int lastKeyIndex = keyTimes.length - 1; + for(int i = 0; i < keyTimes.length; ++i) { + float time = (float) (keyTimes[i] * secondsPerUnit); // Translate into seconds + if(time <= animStart) + firstKeyIndex = i; + if(time >= animStop && animStop >= 0) { + lastKeyIndex = i; + break; + } + } + int keysCount = lastKeyIndex - firstKeyIndex + 1; + if(keysCount <= 0) + continue; + float[] times = new float[keysCount]; + Vector3f[] translations = new Vector3f[keysCount]; + Quaternion[] rotations = new Quaternion[keysCount]; + Vector3f[] scales = null; + // Calculate keyframes times + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + float time = (float) (keyTimes[keyIndex] * secondsPerUnit); // Translate into seconds + times[i] = time - animStart; + realLength = Math.max(realLength, times[i]); + } + // Load keyframes from animation curves + if(haveTranslation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animTranslation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value).subtractLocal(n.value); // Why do it here but not in other places? FBX magic? + translations[i] = tvec.divideLocal(unitSize); + } + } else { + for(int i = 0; i < keysCount; ++i) + translations[i] = Vector3f.ZERO; + } + RotationOrder ro = RotationOrder.EULER_XYZ; + if(haveRotation) { + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animRotation; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); + rotations[i] = ro.rotate(tvec); + } + } else { + for(int i = 0; i < keysCount; ++i) + rotations[i] = Quaternion.IDENTITY; + } + if(haveScale) { + scales = new Vector3f[keysCount]; + for(int i = 0; i < keysCount; ++i) { + int keyIndex = firstKeyIndex + i; + FbxAnimNode n = animScale; + Vector3f tvec = n.getValue(keyTimes[keyIndex], n.value); + scales[i] = tvec; + } + } + BoneTrack track = null; + if(haveScale) + track = new BoneTrack(limb.boneIndex, times, translations, rotations, scales); + else + track = new BoneTrack(limb.boneIndex, times, translations, rotations); + anim.addTrack(track); + } + if(realLength != length && animInfo.lastFrame == -1) { + Track[] tracks = anim.getTracks(); + if(tracks == null || tracks.length == 0) + continue; + anim = new Animation(animInfo.name, realLength); + for(Track track : tracks) + anim.addTrack(track); + } + anims.put(anim.getName(), anim); + } + animControl.setAnimations(anims); + } + + private void releaseObjects() { + // Reset settings + unitSize = 1; + animFrameRate = 30; + xAxis = 1; + yAxis = 1; + zAxis = 1; + // Clear cache + warnings.clear(); + animList = null; + sceneName = null; + sceneFilename = null; + sceneFolderName = null; + assetManager = null; + currentAssetInfo = null; + // Clear objects + allObjects.clear(); + skinMap.clear(); + aLayerMap.clear(); + modelMap.clear(); + limbMap.clear(); + bindMap.clear(); + geomMap.clear(); + skeleton = null; + animControl = null; + sceneNode = null; + } + + + private static boolean haveAnyChannel(FbxAnimNode anims) { + return anims != null && anims.haveAnyChannel(); + } + + private static String join(List list, String glue) { + StringBuilder sb = new StringBuilder(); + for(int i = 0; i < list.size(); ++i) { + if(sb.length() != 0) + sb.append(glue); + sb.append(list.get(i)); + } + return sb.toString(); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java index 349bbb478c..a2a3c68f86 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/SceneWithAnimationLoader.java @@ -15,53 +15,53 @@ import com.jme3.asset.ModelKey; public class SceneWithAnimationLoader implements AssetLoader { - - private Pattern splitStrings = Pattern.compile("([^\"]\\S*|\".+?\")\\s*"); - - @Override - public Object load(AssetInfo assetInfo) throws IOException { - AssetKey key = assetInfo.getKey(); - if(!(key instanceof ModelKey)) - throw new AssetLoadException("Invalid asset key"); - InputStream stream = assetInfo.openStream(); - Scanner scanner = new Scanner(stream); - AnimationList animList = new AnimationList(); - String modelName = null; - try { - while(scanner.hasNextLine()) { - String line = scanner.nextLine(); - if(line.startsWith("#")) - continue; - if(modelName == null) { - modelName = line; - continue; - } - String[] split = split(line); - if(split.length < 3) - throw new IOException("Unparseable string \"" + line + "\""); - int start; - int end; - try { - start = Integer.parseInt(split[0]); - end = Integer.parseInt(split[1]); - } catch(NumberFormatException e) { - throw new IOException("Unparseable string \"" + line + "\"", e); - } - animList.add(split[2], split.length > 3 ? split[3] : null, start, end); - } - } finally { - scanner.close(); - stream.close(); - } - return assetInfo.getManager().loadAsset(new SceneKey(key.getFolder() + modelName, animList)); - } - - private String[] split(String src) { - List list = new ArrayList(); - Matcher m = splitStrings.matcher(src); - while(m.find()) - list.add(m.group(1).replace("\"", "")); - return list.toArray(new String[list.size()]); - } - + + private Pattern splitStrings = Pattern.compile("([^\"]\\S*|\".+?\")\\s*"); + + @Override + public Object load(AssetInfo assetInfo) throws IOException { + AssetKey key = assetInfo.getKey(); + if(!(key instanceof ModelKey)) + throw new AssetLoadException("Invalid asset key"); + InputStream stream = assetInfo.openStream(); + Scanner scanner = new Scanner(stream); + AnimationList animList = new AnimationList(); + String modelName = null; + try { + while(scanner.hasNextLine()) { + String line = scanner.nextLine(); + if(line.startsWith("#")) + continue; + if(modelName == null) { + modelName = line; + continue; + } + String[] split = split(line); + if(split.length < 3) + throw new IOException("Unparseable string \"" + line + "\""); + int start; + int end; + try { + start = Integer.parseInt(split[0]); + end = Integer.parseInt(split[1]); + } catch(NumberFormatException e) { + throw new IOException("Unparseable string \"" + line + "\"", e); + } + animList.add(split[2], split.length > 3 ? split[3] : null, start, end); + } + } finally { + scanner.close(); + stream.close(); + } + return assetInfo.getManager().loadAsset(new SceneKey(key.getFolder() + modelName, animList)); + } + + private String[] split(String src) { + List list = new ArrayList<>(); + Matcher m = splitStrings.matcher(src); + while(m.find()) + list.add(m.group(1).replace("\"", "")); + return list.toArray(new String[list.size()]); + } + } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java index e8dbe7fc09..f3e929c0a9 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimCurveNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,9 +48,9 @@ public class FbxAnimCurveNode extends FbxObject { private static final Logger logger = Logger.getLogger(FbxAnimCurveNode.class.getName()); - private final Map influencedNodePropertiesMap = new HashMap(); - private final Map propertyToCurveMap = new HashMap(); - private final Map propertyToDefaultMap = new HashMap(); + private final Map influencedNodePropertiesMap = new HashMap<>(); + private final Map propertyToCurveMap = new HashMap<>(); + private final Map propertyToDefaultMap = new HashMap<>(); public FbxAnimCurveNode(AssetManager assetManager, String sceneFolderName) { super(assetManager, sceneFolderName); @@ -103,9 +103,9 @@ public Vector3f getVector3Value(long time) { */ public Quaternion getQuaternionValue(long time) { Vector3f eulerAngles = getVector3Value(time); - System.out.println("\tT: " + time + ". Rotation: " + - eulerAngles.x + ", " + - eulerAngles.y + ", " + eulerAngles.z); +// System.out.println("\tT: " + time + ". Rotation: " + +// eulerAngles.x + ", " + +// eulerAngles.y + ", " + eulerAngles.z); Quaternion q = new Quaternion(); q.fromAngles(eulerAngles.x * FastMath.DEG_TO_RAD, eulerAngles.y * FastMath.DEG_TO_RAD, diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java index 872626615e..541a9f3405 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -43,7 +43,7 @@ public class FbxAnimLayer extends FbxObject { private static final Logger logger = Logger.getLogger(FbxAnimLayer.class.getName()); - private final List animCurves = new ArrayList(); + private final List animCurves = new ArrayList<>(); public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) { super(assetManager, sceneFolderName); @@ -52,8 +52,8 @@ public FbxAnimLayer(AssetManager assetManager, String sceneFolderName) { @Override public void fromElement(FbxElement element) { super.fromElement(element); - // No known properties for layers.. - // Also jME3 doesn't support multiple layers anyway. + // No known properties for layers. + // Anyway, jME3 doesn't support multiple layers. } public List getAnimationCurveNodes() { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java index c376757de1..258ffb72cc 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxAnimUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,4 +41,10 @@ public class FbxAnimUtil { public static final String CURVE_NODE_PROPERTY_Y = "d|Y"; public static final String CURVE_NODE_PROPERTY_Z = "d|Z"; public static final String CURVE_NODE_PROPERTY_VISIBILITY = "d|Visibility"; + + /** + * A private constructor to inhibit instantiation of this class. + */ + private FbxAnimUtil() { + } } 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 78d37c74ea..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-2015 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ public class FbxBindPose extends FbxObject> { - private final Map bindPose = new HashMap(); + private final Map bindPose = new HashMap<>(); public FbxBindPose(AssetManager assetManager, String sceneFolderName) { super(assetManager, sceneFolderName); @@ -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/FbxLimbNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java index 4b41b4f473..2e8f501eac 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxLimbNode.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,7 +67,7 @@ public static Skeleton createSkeleton(FbxNode skeletonHolderNode) { throw new UnsupportedOperationException("Limb nodes cannot be skeleton holders"); } - List bones = new ArrayList(); + List bones = new ArrayList<>(); for (FbxNode child : skeletonHolderNode.getChildren()) { if (child instanceof FbxLimbNode) { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java index 7a831cf7fb..8769ae89cb 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/FbxSkinDeformer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,7 +38,7 @@ public class FbxSkinDeformer extends FbxObject> { - private final List clusters = new ArrayList(); + private final List clusters = new ArrayList<>(); public FbxSkinDeformer(AssetManager assetManager, String sceneFolderName) { super(assetManager, sceneFolderName); 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 7b7b5ee5b8..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-2015 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,7 @@ public final class FbxToJmeTrack { public transient final Map animCurves = new HashMap(); public long[] getKeyTimes() { - Set keyFrameTimesSet = new HashSet(); + Set keyFrameTimesSet = new HashSet<>(); for (FbxAnimCurveNode curveNode : animCurves.values()) { for (FbxAnimCurve curve : curveNode.getCurves()) { for (long keyTime : curve.getKeyTimes()) { @@ -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); @@ -140,7 +156,7 @@ private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) { if (time > duration) { // Expand animation duration to fit the curve. duration = time; - System.out.println("actual duration: " + duration); +// System.out.println("actual duration: " + duration); } times[i] = time; @@ -153,8 +169,8 @@ private Track toJmeTrackInternal(int boneIndex, Transform inverseBindPose) { rotations[i] = rotationCurve.getQuaternionValue(fbxTime); if (i > 0) { if (rotations[i - 1].dot(rotations[i]) < 0) { - System.out.println("rotation will go the long way, oh noes"); - rotations[i - 1].negate(); +// System.out.println("rotation will go the long way, oh noes"); + rotations[i - 1].negateLocal(); } } } else { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/package-info.java new file mode 100644 index 0000000000..ecaade7ab3 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/anim/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import animations from an FBX asset + */ +package com.jme3.scene.plugins.fbx.anim; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java index 00619dce7b..242ce39346 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxDump.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,7 +63,7 @@ private FbxDump() { } * @return The UID to object map. */ private static Map createUidToObjectMap(FbxFile file) { - Map uidToObjectMap = new HashMap(); + Map uidToObjectMap = new HashMap<>(); for (FbxElement rootElement : file.rootElements) { if (rootElement.id.equals("Objects")) { for (FbxElement fbxObj : rootElement.children) { @@ -148,9 +148,9 @@ protected static void dumpProperty(String id, char propertyType, case 'F': // Double, Float. if (property instanceof Double) { - ps.print(DECIMAL_FORMAT.format((Double)property)); + ps.print(DECIMAL_FORMAT.format(property)); } else if (property instanceof Float) { - ps.print(DECIMAL_FORMAT.format((Float)property)); + ps.print(DECIMAL_FORMAT.format(property)); } else { ps.print(property); } @@ -184,7 +184,7 @@ protected static void dumpProperty(String id, char propertyType, case 'i': case 'l': case 'f': - // Arrays of things.. + // Arrays of things. int length = Array.getLength(property); for (int j = 0; j < length; j++) { Object arrayEntry = Array.get(property, j); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java index 1a4d09d53a..e203866e0a 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/FbxElement.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2014 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,32 +35,32 @@ import java.util.List; public class FbxElement { - - public String id; - public List properties; - /* - * Y - signed short - * C - boolean - * I - signed integer - * F - float - * D - double - * L - long - * R - byte array - * S - string - * f - array of floats - * i - array of ints - * d - array of doubles - * l - array of longs - * b - array of boleans - * c - array of unsigned bytes (represented as array of ints) - */ - public char[] propertiesTypes; - public List children = new ArrayList(); - - public FbxElement(int propsCount) { - this.properties = new ArrayList(propsCount); - this.propertiesTypes = new char[propsCount]; - } + + public String id; + public List properties; + /* + * Y - signed short + * C - boolean + * I - signed integer + * F - float + * D - double + * L - long + * R - byte array + * S - string + * f - array of floats + * i - array of ints + * d - array of doubles + * l - array of longs + * b - array of booleans + * c - array of unsigned bytes (represented as array of ints) + */ + public char[] propertiesTypes; + public List children = new ArrayList<>(); + + public FbxElement(int propsCount) { + this.properties = new ArrayList(propsCount); + this.propertiesTypes = new char[propsCount]; + } public FbxElement getChildById(String name) { for (FbxElement element : children) { @@ -72,7 +72,7 @@ public FbxElement getChildById(String name) { } public List getFbxProperties() { - List props = new ArrayList(); + List props = new ArrayList<>(); FbxElement propsElement = null; boolean legacy = false; @@ -99,7 +99,7 @@ public List getFbxProperties() { types[1] = prop.propertiesTypes[0]; System.arraycopy(prop.propertiesTypes, 1, types, 2, types.length - 2); - List values = new ArrayList(prop.properties); + List values = new ArrayList<>(prop.properties); values.add(1, values.get(0)); FbxElement dummyProp = new FbxElement(types.length); 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 9435b4c4e2..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-2014 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,12 +35,41 @@ import java.util.List; public class FbxFile { - - public List rootElements = new ArrayList(); - public long version; - - @Override - public String toString() { - return "FBXFile[version=" + version + ",numElements=" + rootElements.size() + "]"; + + 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 22e93d8db0..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-2014 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,186 +42,236 @@ public class FbxReader { - public static final int BLOCK_SENTINEL_LENGTH = 13; - public static final byte[] BLOCK_SENTINEL_DATA = new byte[BLOCK_SENTINEL_LENGTH]; - /** - * Majic string at start: - * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" - */ - public static final byte[] HEAD_MAGIC = new byte[]{0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x00, 0x1a, 0x00}; - - public static FbxFile readFBX(InputStream stream) throws IOException { - FbxFile fbxFile = new FbxFile(); - // Read file to byte buffer so we can know current position in file - ByteBuffer byteBuffer = readToByteBuffer(stream); - try { - stream.close(); - } catch(IOException e) { - } - // Check majic header - byte[] majic = getBytes(byteBuffer, HEAD_MAGIC.length); - if(!Arrays.equals(HEAD_MAGIC, majic)) - throw new IOException("Either ASCII FBX or corrupt file. " + /** + * magic string at start: + * "Kaydara FBX Binary\x20\x20\x00\x1a\x00" + */ + public static final byte[] HEAD_MAGIC = new byte[]{0x4b, 0x61, 0x79, 0x64, 0x61, 0x72, 0x61, 0x20, 0x46, 0x42, 0x58, 0x20, 0x42, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x20, 0x20, 0x00, 0x1a, 0x00}; + + /** + * A private constructor to inhibit instantiation of this class. + */ + private FbxReader() { + } + + public static FbxFile readFBX(InputStream stream) throws IOException { + FbxFile fbxFile = new FbxFile(); + // Read the file to a ByteBuffer, so we can determine positions in the file. + ByteBuffer byteBuffer = readToByteBuffer(stream); + try { + stream.close(); + } catch(IOException e) { + } + // Check magic header + byte[] magic = getBytes(byteBuffer, HEAD_MAGIC.length); + if(!Arrays.equals(HEAD_MAGIC, magic)) + throw new IOException("Either ASCII FBX or corrupt file. " + "Only binary FBX files are supported"); - // Read version - fbxFile.version = getUInt(byteBuffer); - // Read root elements - while(true) { - FbxElement e = readFBXElement(byteBuffer); - if(e == null) - break; - fbxFile.rootElements.add(e); - } - return fbxFile; - } - - private static FbxElement readFBXElement(ByteBuffer byteBuffer) throws IOException { - long endOffset = getUInt(byteBuffer); - if(endOffset == 0) - return null; - long propCount = getUInt(byteBuffer); - getUInt(byteBuffer); // Properties length unused - - FbxElement element = new FbxElement((int) propCount); - element.id = new String(getBytes(byteBuffer, getUByte(byteBuffer))); - - for(int i = 0; i < propCount; ++i) { - char dataType = readDataType(byteBuffer); - element.properties.add(readData(byteBuffer, dataType)); - 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"); - } - if(byteBuffer.position() != endOffset) - throw new IOException("Data length not equal to expected"); - return element; - } - - private static Object readData(ByteBuffer byteBuffer, char dataType) throws IOException { - switch(dataType) { - case 'Y': - return byteBuffer.getShort(); - case 'C': - return byteBuffer.get() == 1; - case 'I': - return byteBuffer.getInt(); - case 'F': - return byteBuffer.getFloat(); - case 'D': - return byteBuffer.getDouble(); - case 'L': - return byteBuffer.getLong(); - case 'R': - return getBytes(byteBuffer, (int) getUInt(byteBuffer)); - case 'S': - return new String(getBytes(byteBuffer, (int) getUInt(byteBuffer))); - case 'f': - return readArray(byteBuffer, 'f', 4); - case 'i': - return readArray(byteBuffer, 'i', 4); - case 'd': - return readArray(byteBuffer, 'd', 8); - case 'l': - return readArray(byteBuffer, 'l', 8); - case 'b': - return readArray(byteBuffer, 'b', 1); - case 'c': - return readArray(byteBuffer, 'c', 1); - } - throw new IOException("Unknown data type: " + dataType); - } - - private static Object readArray(ByteBuffer byteBuffer, char type, int bytes) throws IOException { - int count = (int) getUInt(byteBuffer); - int encoding = (int) getUInt(byteBuffer); - int length = (int) getUInt(byteBuffer); - - byte[] data = getBytes(byteBuffer, length); - if(encoding == 1) - data = inflate(data); - if(data.length != count * bytes) - throw new IOException("Wrong data lenght. Expected: " + count * bytes + ", got: " + data.length); - ByteBuffer dis = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); - switch(type) { - case 'f': - float[] arr = new float[count]; - for(int i = 0; i < count; ++i) - arr[i] = dis.getFloat(); - return arr; - case 'i': - int[] arr2 = new int[count]; - for(int i = 0; i < count; ++i) - arr2[i] = dis.getInt(); - return arr2; - case 'd': - double[] arr3 = new double[count]; - for(int i = 0; i < count; ++i) - arr3[i] = dis.getDouble(); - return arr3; - case 'l': - long[] arr4 = new long[count]; - for(int i = 0; i < count; ++i) - arr4[i] = dis.getLong(); - return arr4; - case 'b': - boolean[] arr5 = new boolean[count]; - for(int i = 0; i < count; ++i) - arr5[i] = dis.get() == 1; - return arr5; - case 'c': - int[] arr6 = new int[count]; - for(int i = 0; i < count; ++i) - arr6[i] = dis.get() & 0xFF; - return arr6; - } - throw new IOException("Unknown array data type: " + type); - } - - private static byte[] inflate(byte[] input) throws IOException { - InflaterInputStream gzis = new InflaterInputStream(new ByteArrayInputStream(input)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - byte[] buffer = new byte[1024]; - while(gzis.available() > 0) { - int l = gzis.read(buffer); - if(l > 0) - out.write(buffer, 0, l); - } - return out.toByteArray(); - } - - private static char readDataType(ByteBuffer byteBuffer) { - return (char) byteBuffer.get(); - } - - private static long getUInt(ByteBuffer byteBuffer) { - return byteBuffer.getInt() & 0x00000000ffffffffL; - } - - private static int getUByte(ByteBuffer byteBuffer) { - return byteBuffer.get() & 0xFF; - } - - private static byte[] getBytes(ByteBuffer byteBuffer, int size) { - byte[] b = new byte[size]; - byteBuffer.get(b); - return b; - } - - private static ByteBuffer readToByteBuffer(InputStream input) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(2048); - byte[] tmp = new byte[2048]; - while(true) { - int r = input.read(tmp); - if(r == -1) - break; - out.write(tmp, 0, r); - } - return ByteBuffer.wrap(out.toByteArray()).order(ByteOrder.LITTLE_ENDIAN); - } + // Read version + fbxFile.version = getUInt(byteBuffer); + // Read root elements + while(true) { + FbxElement e = readFBXElement(byteBuffer, fbxFile); + if(e == null) + break; + fbxFile.rootElements.add(e); + } + return fbxFile; + } + + 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))); + + for(int i = 0; i < propCount; ++i) { + char dataType = readDataType(byteBuffer); + element.properties.add(readData(byteBuffer, dataType)); + element.propertiesTypes[i] = dataType; + } + if(byteBuffer.position() < endOffset) { + 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': + return byteBuffer.getShort(); + case 'C': + return byteBuffer.get() == 1; + case 'I': + return byteBuffer.getInt(); + case 'F': + return byteBuffer.getFloat(); + case 'D': + return byteBuffer.getDouble(); + case 'L': + return byteBuffer.getLong(); + case 'R': + return getBytes(byteBuffer, (int) getUInt(byteBuffer)); + case 'S': + return new String(getBytes(byteBuffer, (int) getUInt(byteBuffer))); + case 'f': + return readArray(byteBuffer, 'f', 4); + case 'i': + return readArray(byteBuffer, 'i', 4); + case 'd': + return readArray(byteBuffer, 'd', 8); + case 'l': + return readArray(byteBuffer, 'l', 8); + case 'b': + return readArray(byteBuffer, 'b', 1); + case 'c': + return readArray(byteBuffer, 'c', 1); + } + throw new IOException("Unknown data type: " + dataType); + } + + private static Object readArray(ByteBuffer byteBuffer, char type, int bytes) throws IOException { + int count = (int) getUInt(byteBuffer); + int encoding = (int) getUInt(byteBuffer); + int length = (int) getUInt(byteBuffer); + + byte[] data = getBytes(byteBuffer, length); + if(encoding == 1) + data = inflate(data); + if(data.length != count * bytes) + throw new IOException("Wrong data length. Expected: " + count * bytes + ", got: " + data.length); + ByteBuffer dis = ByteBuffer.wrap(data).order(ByteOrder.LITTLE_ENDIAN); + switch(type) { + case 'f': + float[] arr = new float[count]; + for(int i = 0; i < count; ++i) + arr[i] = dis.getFloat(); + return arr; + case 'i': + int[] arr2 = new int[count]; + for(int i = 0; i < count; ++i) + arr2[i] = dis.getInt(); + return arr2; + case 'd': + double[] arr3 = new double[count]; + for(int i = 0; i < count; ++i) + arr3[i] = dis.getDouble(); + return arr3; + case 'l': + long[] arr4 = new long[count]; + for(int i = 0; i < count; ++i) + arr4[i] = dis.getLong(); + return arr4; + case 'b': + boolean[] arr5 = new boolean[count]; + for(int i = 0; i < count; ++i) + arr5[i] = dis.get() == 1; + return arr5; + case 'c': + int[] arr6 = new int[count]; + for(int i = 0; i < count; ++i) + arr6[i] = dis.get() & 0xFF; + return arr6; + } + throw new IOException("Unknown array data type: " + type); + } + + private static byte[] inflate(byte[] input) throws IOException { + InflaterInputStream gzis = new InflaterInputStream(new ByteArrayInputStream(input)); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + while(gzis.available() > 0) { + int l = gzis.read(buffer); + if(l > 0) + out.write(buffer, 0, l); + } + return out.toByteArray(); + } + + private static char readDataType(ByteBuffer byteBuffer) { + return (char) byteBuffer.get(); + } + + private static long getUInt(ByteBuffer byteBuffer) { + return byteBuffer.getInt() & 0x00000000ffffffffL; + } + + private static int getUByte(ByteBuffer byteBuffer) { + return byteBuffer.get() & 0xFF; + } + + private static byte[] getBytes(ByteBuffer byteBuffer, int size) { + byte[] b = new byte[size]; + byteBuffer.get(b); + return b; + } + + private static ByteBuffer readToByteBuffer(InputStream input) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(2048); + byte[] tmp = new byte[2048]; + while(true) { + int r = input.read(tmp); + if(r == -1) + break; + out.write(tmp, 0, r); + } + return ByteBuffer.wrap(out.toByteArray()).order(ByteOrder.LITTLE_ENDIAN); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/package-info.java new file mode 100644 index 0000000000..cead945296 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/file/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * read and parse FBX files + */ +package com.jme3.scene.plugins.fbx.file; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java index 9be5bf70c5..8574bf0637 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterial.java @@ -260,13 +260,13 @@ protected Material toJmeObject() { assert transp == null || (transp.r == 1f && transp.g == 1f && transp.b == 1f); // If shininess is less than 1.0, the lighting shader won't be able - // to handle it. Gotta disable specularity then. + // to handle it. Must disable specularity in that case. if (shininess < 1f) { shininess = 1f; specular = ColorRGBA.Black; } - // Try to guess if we need to enable alpha blending. + // Guess whether we should enable alpha blending. // FBX does not specify this explicitly. boolean useAlphaBlend = false; @@ -305,7 +305,7 @@ protected Material toJmeObject() { mat.setReceivesShadows(true); if (useAlphaBlend) { - // No idea if this is a transparent or translucent model, gotta guess.. + // No clue whether this is a transparent or translucent model, so guess. mat.setTransparent(true); mat.setFloat("AlphaDiscardThreshold", 0.01f); mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java index 14d23900de..6eff610f09 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxMaterialProperties.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -44,7 +44,7 @@ public class FbxMaterialProperties { private static final Map propertyMetaMap = new HashMap(); - private final Map propertyValueMap = new HashMap(); + private final Map propertyValueMap = new HashMap<>(); private static enum Type { Color, @@ -68,37 +68,6 @@ public FBXMaterialProperty(String name, Type type) { } } - private static boolean isValueAcceptable(Type type, Object value) { - if (type == Type.Ignore) { - return true; - } - if (value instanceof FbxTexture) { - switch (type) { - case Texture2D: - case Texture2DOrAlpha: - case Texture2DOrColor: - case Texture2DOrFactor: - return true; - } - } else if (value instanceof ColorRGBA) { - switch (type) { - case Color: - case Texture2DOrColor: - return true; - } - } else if (value instanceof Float) { - switch (type) { - case Alpha: - case Factor: - case Texture2DOrAlpha: - case Texture2DOrFactor: - return true; - } - } - - return false; - } - private static void defineProp(String name, Type type) { propertyMetaMap.put(name, new FBXMaterialProperty(name, type)); } @@ -154,10 +123,10 @@ private static void defineAlias(String alias, String name) { // ShadingModel is no longer specified under Properties element. defineProp("ShadingModel", Type.Ignore); - // MultiLayer materials aren't supported anyway.. + // MultiLayer materials aren't supported. defineProp("MultiLayer", Type.Ignore); - // Not sure what this is.. NormalMap again?? + // Not sure what this is. NormalMap again?? defineProp("Bump", Type.Texture2DOrColor); defineProp("BumpFactor", Type.Factor); @@ -174,7 +143,7 @@ public void setPropertyTexture(String name, FbxTexture texture) { } if (propertyValueMap.get(name) instanceof FbxTexture) { - // Multiple / layered textures .. + // Multiple / layered textures // Just write into 2nd slot for now (maybe will use for lightmaps). name = name + "2"; } @@ -222,13 +191,4 @@ public void setPropertyFromElement(FbxElement propertyElement) { public Object getProperty(String name) { return propertyValueMap.get(name); } - - public static Type getPropertyType(String name) { - FBXMaterialProperty prop = propertyMetaMap.get(name); - if (prop == null) { - return null; - } else { - return prop.type; - } - } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java index 4569dad335..cb1cdd0d9e 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/FbxTexture.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,6 @@ import com.jme3.asset.AssetManager; import com.jme3.asset.TextureKey; -import com.jme3.math.Vector2f; import com.jme3.scene.plugins.fbx.file.FbxElement; import com.jme3.scene.plugins.fbx.obj.FbxObject; import com.jme3.texture.Image; @@ -53,15 +52,9 @@ private static enum AlphaSource { FromTextureIntensity; } - private String type; private FbxImage media; - - // TODO: not currently used. - private AlphaSource alphaSource = AlphaSource.FromTextureAlpha; private String uvSet; private int wrapModeU = 0, wrapModeV = 0; - private final Vector2f uvTranslation = new Vector2f(0, 0); - private final Vector2f uvScaling = new Vector2f(1, 1); public FbxTexture(AssetManager assetManager, String sceneFolderName) { super(assetManager, sceneFolderName); @@ -105,7 +98,7 @@ public void fromElement(FbxElement element) { if (getSubclassName().equals("")) { for (FbxElement e : element.children) { if (e.id.equals("Type")) { - type = (String) e.properties.get(0); + e.properties.get(0); } /*else if (e.id.equals("FileName")) { filename = (String) e.properties.get(0); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/package-info.java new file mode 100644 index 0000000000..589771505e --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/material/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import materials from an FBX asset + */ +package com.jme3.scene.plugins.fbx.material; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java index d1b9d3860d..84b5a68589 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxLayer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ public static class FbxLayerElementRef { int layer; final EnumMap references = - new EnumMap(FbxLayerElement.Type.class); + new EnumMap<>(FbxLayerElement.Type.class); private FbxLayer() { } 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 bffd94c658..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-2015 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -47,7 +47,7 @@ public class FbxLayerElement { public enum Type { Position, // Vector3f (isn't actually defined in FBX) BoneIndex, // List (isn't actually defined in FBX) - BoneWeight, // List isn't actually defined in FBX) + BoneWeight, // List (isn't actually defined in FBX) Normal, // Vector3f Binormal, // Vector3f Tangent, // Vector3f @@ -108,6 +108,7 @@ public enum TextureBlendMode { private FbxLayerElement() { } + @Override public String toString() { return "LayerElement[type=" + type + ", layer=" + index + ", mapInfoType=" + mapInfoType + ", refInfoType=" + refInfoType + "]"; @@ -195,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/mesh/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java index 5dd911bed6..7f48d3e201 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMesh.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -60,7 +60,6 @@ public final class FbxMesh extends FbxNodeAttribute> { private static final Logger logger = Logger.getLogger(FbxMesh.class.getName()); private FbxPolygon[] polygons; - private int[] edges; private FbxLayerElement[] layerElements; private Vector3f[] positions; private FbxLayer[] layers; @@ -78,8 +77,8 @@ public FbxMesh(AssetManager assetManager, String sceneFolderName) { public void fromElement(FbxElement element) { super.fromElement(element); - List layerElementsList = new ArrayList(); - List layersList = new ArrayList(); + List layerElementsList = new ArrayList<>(); + List layersList = new ArrayList<>(); for (FbxElement e : element.children) { if (e.id.equals("Vertices")) { @@ -110,6 +109,7 @@ public FbxSkinDeformer getSkinDeformer() { return skinDeformer; } + @SuppressWarnings("unchecked") public void applyCluster(FbxCluster cluster) { if (boneIndices == null) { boneIndices = new ArrayList[positions.length]; @@ -166,15 +166,14 @@ private void setPositions(double[] positions) { } private void setEdges(int[] edges) { - this.edges = edges; // TODO: ... } private void setPolygonVertexIndices(int[] polygonVertexIndices) { - List polygonList = new ArrayList(); + List polygonList = new ArrayList<>(); boolean finishPolygon = false; - List vertexIndices = new ArrayList(); + List vertexIndices = new ArrayList<>(); for (int i = 0; i < polygonVertexIndices.length; i++) { int vertexIndex = polygonVertexIndices[i]; @@ -229,7 +228,7 @@ protected IntMap toJmeObject() { IntMap irMeshes = IrUtils.splitByMaterial(irMesh); // Create a jME3 Mesh for each material index. - IntMap jmeMeshes = new IntMap(); + IntMap jmeMeshes = new IntMap<>(); for (IntMap.Entry irMeshEntry : irMeshes) { Mesh jmeMesh = IrUtils.convertIrMeshToJmeMesh(irMeshEntry.getValue()); jmeMeshes.put(irMeshEntry.getKey(), jmeMesh); @@ -253,6 +252,8 @@ protected IntMap toJmeObject() { /** * Convert FBXMesh to IRMesh. + * + * @return a new IrMesh */ public IrMesh toIRMesh() { IrMesh newMesh = new IrMesh(); diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java index 61fb001dd4..8aa333a2ff 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/FbxMeshUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,11 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxMeshUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private FbxMeshUtil() { + } public static double[] getDoubleArray(FbxElement el) { if (el.propertiesTypes[0] == 'd') { diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/package-info.java new file mode 100644 index 0000000000..0ba4720998 --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/mesh/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import meshes from an FBX asset + */ +package com.jme3.scene.plugins.fbx.mesh; 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 c0ce06d4b9..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-2015 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -98,10 +98,10 @@ private static enum InheritMode { private InheritMode inheritMode = InheritMode.ScaleAfterChildRotation; protected FbxNode parent; - protected List children = new ArrayList(); - protected List materials = new ArrayList(); - protected Map userData = new HashMap(); - protected Map> propertyToAnimCurveMap = new HashMap>(); + protected List children = new ArrayList<>(); + protected List materials = new ArrayList<>(); + protected Map userData = new HashMap<>(); + protected Map> propertyToAnimCurveMap = new HashMap<>(); protected FbxNodeAttribute nodeAttribute; protected double visibility = 1.0; @@ -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"); } } @@ -171,14 +171,14 @@ public void setWorldBindPose(Matrix4f worldBindPose) { this.jmeWorldBindPose.setRotation(worldBindPose.toRotationQuat()); this.jmeWorldBindPose.setScale(worldBindPose.toScaleVector()); - System.out.println("\tBind Pose for " + getName()); - System.out.println(jmeWorldBindPose); +// System.out.println("\tBind Pose for " + getName()); +// System.out.println(jmeWorldBindPose); float[] angles = new float[3]; jmeWorldBindPose.getRotation().toAngles(angles); - System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " + - angles[1] * FastMath.RAD_TO_DEG + ", " + - angles[2] * FastMath.RAD_TO_DEG); +// System.out.println("Angles: " + angles[0] * FastMath.RAD_TO_DEG + ", " + +// angles[1] * FastMath.RAD_TO_DEG + ", " + +// angles[2] * FastMath.RAD_TO_DEG); } public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform parentBindPose) { @@ -207,16 +207,16 @@ public void updateWorldTransforms(Transform jmeParentNodeTransform, Transform pa jmeLocalBindPose.set(jmeWorldBindPose); jmeLocalBindPose.combineWithParent(parentBindPose.invert()); - // Its somewhat odd for the transforms to differ ... - System.out.println("Bind Pose for: " + getName()); - if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) { - System.out.println("Local Bind: " + jmeLocalBindPose); - System.out.println("Local Trans: " + jmeLocalNodeTransform); - } - if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) { - System.out.println("World Bind: " + jmeWorldBindPose); - System.out.println("World Trans: " + jmeWorldNodeTransform); - } + // It's somewhat odd for the transforms to differ ... +// System.out.println("Bind Pose for: " + getName()); +// if (!jmeLocalBindPose.equals(jmeLocalNodeTransform)) { +// System.out.println("Local Bind: " + jmeLocalBindPose); +// System.out.println("Local Trans: " + jmeLocalNodeTransform); +// } +// if (!jmeWorldBindPose.equals(jmeWorldNodeTransform)) { +// System.out.println("World Bind: " + jmeWorldBindPose); +// System.out.println("World Trans: " + jmeWorldNodeTransform); +// } } else { // World pose derived from local transforms // (this is to be expected for FBX nodes) @@ -280,9 +280,9 @@ public void fromElement(FbxElement element) { Object userDataValue; if (userDataType.equals("KString")) { - userDataValue = (String) e2.properties.get(4); + userDataValue = e2.properties.get(4); } else if (userDataType.equals("int")) { - userDataValue = (Integer) e2.properties.get(4); + userDataValue = e2.properties.get(4); } else if (userDataType.equals("double")) { // NOTE: jME3 does not support doubles in UserData. // Need to convert to float. @@ -309,9 +309,9 @@ public void fromElement(FbxElement element) { if (element.getChildById("Vertices") != null) { // This is an old-style FBX 6.1 - // Meshes could be embedded inside the node.. - - // Inject the mesh into ourselves.. + // Meshes could be embedded inside the node. + + // Inject the mesh into ourselves. FbxMesh mesh = new FbxMesh(assetManager, sceneFolderName); mesh.fromElement(element); connectObject(mesh); @@ -480,7 +480,7 @@ public static void createSkeletons(FbxNode fbxNode) { throw new UnsupportedOperationException(); } fbxNode.skeleton = FbxLimbNode.createSkeleton(fbxNode); - System.out.println("created skeleton: " + fbxNode.skeleton); +// System.out.println("created skeleton: " + fbxNode.skeleton); } } @@ -505,7 +505,7 @@ public static Spatial createScene(FbxNode fbxNode) { FbxNode preferredParent = fbxChild.getPreferredParent(); Spatial jmeChild = fbxChild.getJmeObject(); if (preferredParent != null) { - System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent); +// System.out.println("Preferred parent for " + fbxChild + " is " + preferredParent); Node jmePreferredParent = (Node) preferredParent.getJmeObject(); relocateSpatial(jmeChild, fbxChild.jmeWorldNodeTransform, diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java index 8c35e45e90..ee2892a4f3 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/FbxNodeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -35,6 +35,12 @@ import com.jme3.math.Quaternion; public class FbxNodeUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private FbxNodeUtil() { + } + public static Quaternion quatFromBoneAngles(float xAngle, float yAngle, float zAngle) { float angle; float sinY, sinZ, sinX, cosY, cosZ, cosX; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/package-info.java new file mode 100644 index 0000000000..916ceaa30f --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/node/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import nodes from an FBX asset + */ +package com.jme3.scene.plugins.fbx.node; diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java index 111750df1e..68121a5883 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/obj/FbxObjectFactory.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -59,6 +59,12 @@ public final class FbxObjectFactory { private static final Logger logger = Logger.getLogger(FbxObjectFactory.class.getName()); + /** + * A private constructor to inhibit instantiation of this class. + */ + private FbxObjectFactory() { + } + private static Class getImplementingClass(String elementName, String subclassName) { if (elementName.equals("NodeAttribute")) { if (subclassName.equals("Root")) { @@ -194,16 +200,10 @@ public static FbxObject createObject(FbxElement element, AssetManager assetManag ") forgot to call super.fromElement() in their fromElement() implementation"); } return obj; - } catch (InvocationTargetException ex) { - // Programmer error. - throw new IllegalStateException(ex); - } catch (NoSuchMethodException ex) { - // Programmer error. - throw new IllegalStateException(ex); - } catch (InstantiationException ex) { - // Programmer error. - throw new IllegalStateException(ex); - } catch (IllegalAccessException ex) { + } catch (InvocationTargetException + | NoSuchMethodException + | InstantiationException + | IllegalAccessException ex) { // Programmer error. throw new IllegalStateException(ex); } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java index 3952161569..b59f216292 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimCurve.java @@ -4,46 +4,46 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxAnimCurve extends FbxObject { - - public long[] keyTimes; - public float[] keyValues; - public float defaultValue = 0.0f; - - public FbxAnimCurve(SceneLoader scene, FbxElement element) { - super(scene, element); - for(FbxElement e : element.children) { - switch(e.id) { - case "KeyTime": - keyTimes = (long[]) e.properties.get(0); - break; - case "KeyValueFloat": - keyValues = (float[]) e.properties.get(0); - break; - case "Default": - defaultValue = ((Number) e.properties.get(0)).floatValue(); - break; - } - } - } - - public float getValue(long time) { - // Search animation interval - for(int i = 0; i < keyTimes.length; ++i) { - if(keyTimes[i] == time) { // hit the keyframe - return keyValues[i]; - } else if(keyTimes[i] > time) { - if(i == 0) { // left from the whole range - return defaultValue;//keyValues[0]; - } else { - // Interpolate between two keyframes - float dt = (float) (keyTimes[i] - keyTimes[i - 1]); - float dtInt = (float) (time - keyTimes[i - 1]); - float dv = keyValues[i] - keyValues[i - 1]; - return keyValues[i - 1] + dv * (dtInt / dt); - } - } - } - // right from the whole range - return defaultValue;//keyValues[keyValues.length - 1]; - } + + public long[] keyTimes; + public float[] keyValues; + public float defaultValue = 0.0f; + + public FbxAnimCurve(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "KeyTime": + keyTimes = (long[]) e.properties.get(0); + break; + case "KeyValueFloat": + keyValues = (float[]) e.properties.get(0); + break; + case "Default": + defaultValue = ((Number) e.properties.get(0)).floatValue(); + break; + } + } + } + + public float getValue(long time) { + // Search animation interval + for(int i = 0; i < keyTimes.length; ++i) { + if(keyTimes[i] == time) { // hit the keyframe + return keyValues[i]; + } else if(keyTimes[i] > time) { + if(i == 0) { // left from the whole range + return defaultValue;//keyValues[0]; + } else { + // Interpolate between two keyframes + float dt = keyTimes[i] - keyTimes[i - 1]; + float dtInt = time - keyTimes[i - 1]; + float dv = keyValues[i] - keyValues[i - 1]; + return keyValues[i - 1] + dv * (dtInt / dt); + } + } + } + // right from the whole range + return defaultValue;//keyValues[keyValues.length - 1]; + } } \ No newline at end of file diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java index 7febc92d16..ba6f38d0b1 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxAnimNode.java @@ -7,80 +7,80 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxAnimNode extends FbxObject { - - public Vector3f value; - public FbxAnimCurve xCurve; - public FbxAnimCurve yCurve; - public FbxAnimCurve zCurve; - public long layerId; - - public FbxAnimNode(SceneLoader scene, FbxElement element) { - super(scene, element); - if(type.equals("")) { - Double x = null, y = null, z = null; - for(FbxElement e2 : element.getFbxProperties()) { - String propName = (String) e2.properties.get(0); - switch(propName) { - case "d|X": - x = (Double) e2.properties.get(4); - break; - case "d|Y": - y = (Double) e2.properties.get(4); - break; - case "d|Z": - z = (Double) e2.properties.get(4); - break; - } - } - // Load only T R S curve nodes - if(x != null && y != null && z != null) - value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue()); - } - } - - @Override - public void link(FbxObject otherObject, String propertyName) { - if(otherObject instanceof FbxAnimCurve) { - FbxAnimCurve curve = (FbxAnimCurve) otherObject; - switch(propertyName) { - case "d|X": - xCurve = curve; - break; - case "d|Y": - yCurve = curve; - break; - case "d|Z": - zCurve = curve; - break; - } - } - } - - @Override - public void link(FbxObject otherObject) { - layerId = otherObject.id; - } - - public boolean haveAnyChannel() { - return xCurve != null || yCurve != null || zCurve != null; - } - - public void exportTimes(Collection stamps) { - if(xCurve != null) - for(long t : xCurve.keyTimes) - stamps.add(t); - if(yCurve != null) - for(long t : yCurve.keyTimes) - stamps.add(t); - if(zCurve != null) - for(long t : zCurve.keyTimes) - stamps.add(t); - } - - public Vector3f getValue(long time, Vector3f defaultValue) { - float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x; - float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y; - float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z; - return new Vector3f(xValue, yValue, zValue); - } + + public Vector3f value; + public FbxAnimCurve xCurve; + public FbxAnimCurve yCurve; + public FbxAnimCurve zCurve; + public long layerId; + + public FbxAnimNode(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("")) { + Double x = null, y = null, z = null; + for(FbxElement e2 : element.getFbxProperties()) { + String propName = (String) e2.properties.get(0); + switch(propName) { + case "d|X": + x = (Double) e2.properties.get(4); + break; + case "d|Y": + y = (Double) e2.properties.get(4); + break; + case "d|Z": + z = (Double) e2.properties.get(4); + break; + } + } + // Load only T R S curve nodes + if(x != null && y != null && z != null) + value = new Vector3f(x.floatValue(), y.floatValue(), z.floatValue()); + } + } + + @Override + public void link(FbxObject otherObject, String propertyName) { + if(otherObject instanceof FbxAnimCurve) { + FbxAnimCurve curve = (FbxAnimCurve) otherObject; + switch(propertyName) { + case "d|X": + xCurve = curve; + break; + case "d|Y": + yCurve = curve; + break; + case "d|Z": + zCurve = curve; + break; + } + } + } + + @Override + public void link(FbxObject otherObject) { + layerId = otherObject.id; + } + + public boolean haveAnyChannel() { + return xCurve != null || yCurve != null || zCurve != null; + } + + public void exportTimes(Collection stamps) { + if(xCurve != null) + for(long t : xCurve.keyTimes) + stamps.add(t); + if(yCurve != null) + for(long t : yCurve.keyTimes) + stamps.add(t); + if(zCurve != null) + for(long t : zCurve.keyTimes) + stamps.add(t); + } + + public Vector3f getValue(long time, Vector3f defaultValue) { + float xValue = (xCurve != null) ? xCurve.getValue(time) : defaultValue.x; + float yValue = (yCurve != null) ? yCurve.getValue(time) : defaultValue.y; + float zValue = (zCurve != null) ? zCurve.getValue(time) : defaultValue.z; + return new Vector3f(xValue, yValue, zValue); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java index 1170f9718c..1d3207cd39 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxBindPose.java @@ -9,47 +9,47 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxBindPose extends FbxObject { - - public Map nodeTransforms = new HashMap<>(); - - public FbxBindPose(SceneLoader scene, FbxElement element) { - super(scene, element); - if(type.equals("BindPose")) { - for(FbxElement e : element.children) { - if(e.id.equals("PoseNode")) { - long nodeId = 0; - double[] transform = null; - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "Node": - nodeId = (Long) e2.properties.get(0); - break; - case "Matrix": - transform = (double[]) e2.properties.get(0); - break; - } - } - Matrix4f t = buildTransform(transform); - t.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); - nodeTransforms.put(nodeId, t); - } - } - } - } - - public void fillBindTransforms() { - for(long nodeId : nodeTransforms.keySet()) { - FbxNode node = scene.modelMap.get(nodeId); - node.bindTransform = nodeTransforms.get(nodeId).clone(); - } - } - - private static Matrix4f buildTransform(double[] transform) { - float[] m = new float[transform.length]; - for(int i = 0; i < transform.length; ++i) - m[i] = (float) transform[i]; - Matrix4f matrix = new Matrix4f(); - matrix.set(m, false); - return matrix; - } + + public Map nodeTransforms = new HashMap<>(); + + public FbxBindPose(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("BindPose")) { + for(FbxElement e : element.children) { + if(e.id.equals("PoseNode")) { + long nodeId = 0; + double[] transform = null; + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "Node": + nodeId = (Long) e2.properties.get(0); + break; + case "Matrix": + transform = (double[]) e2.properties.get(0); + break; + } + } + Matrix4f t = buildTransform(transform); + t.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); + nodeTransforms.put(nodeId, t); + } + } + } + } + + public void fillBindTransforms() { + for(long nodeId : nodeTransforms.keySet()) { + FbxNode node = scene.modelMap.get(nodeId); + node.bindTransform = nodeTransforms.get(nodeId).clone(); + } + } + + private static Matrix4f buildTransform(double[] transform) { + float[] m = new float[transform.length]; + for(int i = 0; i < transform.length; ++i) + m[i] = (float) transform[i]; + Matrix4f matrix = new Matrix4f(); + matrix.set(m, false); + return matrix; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java index 354354567e..fd23755a5b 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxCluster.java @@ -4,39 +4,39 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxCluster extends FbxObject { - - public int[] indexes; - public double[] weights; - public double[] transform; - public double[] transformLink; - public FbxSkin skin; - - public FbxCluster(SceneLoader scene, FbxElement element) { - super(scene, element); - for(FbxElement e : element.children) { - switch(e.id) { - case "Indexes": - indexes = (int[]) e.properties.get(0); - break; - case "Weights": - weights = (double[]) e.properties.get(0); - break; - case "Transform": - transform = (double[]) e.properties.get(0); - break; - case "TransformLink": - transformLink = (double[]) e.properties.get(0); - break; - } - } - } - - @Override - public void link(FbxObject child) { - if(child instanceof FbxNode) { - FbxNode limb = (FbxNode) child; - limb.skinToCluster.put(skin.id, this); - skin.bones.add(limb); - } - } + + public int[] indexes; + public double[] weights; + public double[] transform; + public double[] transformLink; + public FbxSkin skin; + + public FbxCluster(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "Indexes": + indexes = (int[]) e.properties.get(0); + break; + case "Weights": + weights = (double[]) e.properties.get(0); + break; + case "Transform": + transform = (double[]) e.properties.get(0); + break; + case "TransformLink": + transformLink = (double[]) e.properties.get(0); + break; + } + } + } + + @Override + public void link(FbxObject child) { + if(child instanceof FbxNode) { + FbxNode limb = (FbxNode) child; + limb.skinToCluster.put(skin.id, this); + skin.bones.add(limb); + } + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java index 0b50aa163e..7ef3299339 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxImage.java @@ -13,111 +13,111 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxImage extends FbxObject { - - String filename; - String relativeFilename; - byte[] content; - String imageType; - - public Image image; - - public FbxImage(SceneLoader scene, FbxElement element) { - super(scene, element); - if(type.equals("Clip")) { - for(FbxElement e : element.children) { - switch(e.id) { - case "Type": - imageType = (String) e.properties.get(0); - break; - case "Filename": - case "FileName": - filename = (String) e.properties.get(0); - break; - case "RelativeFilename": - relativeFilename = (String) e.properties.get(0); - break; - case "Content": - if(e.properties.size() > 0) - content = (byte[]) e.properties.get(0); - break; - } - } - image = createImage(); - } - } - - - private Image createImage() { - AssetManager assetManager = scene.assetManager; - Image image = null; - if(filename != null) { - // Try load by absolute path - File file = new File(filename); - if(file.exists() && file.isFile()) { - File dir = new File(file.getParent()); - String locatorPath = dir.getAbsolutePath(); - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - tex = assetManager.loadTexture(file.getName()); - } catch(Exception e) {} finally { - assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - } - if(image == null && relativeFilename != null) { - // Try load by relative path - File dir = new File(scene.sceneFolderName); - String locatorPath = dir.getAbsolutePath(); - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - tex = assetManager.loadTexture(relativeFilename); - } catch(Exception e) {} finally { - assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - if(image == null && content != null) { - // Try load from content - String filename = null; - if(this.filename != null) - filename = new File(this.filename).getName(); - if(filename != null && this.relativeFilename != null) - filename = this.relativeFilename; - // Filename is required to aquire asset loader by extension - if(filename != null) { - String locatorPath = scene.sceneFilename; - filename = scene.sceneFilename + File.separatorChar + filename; // Unique path - Texture tex = null; - try { - assetManager.registerLocator(locatorPath, ContentTextureLocator.class); - tex = assetManager.loadTexture(new ContentTextureKey(filename, content)); - } catch(Exception e) {} finally { - assetManager.unregisterLocator(locatorPath, ContentTextureLocator.class); - } - if(tex != null) - image = tex.getImage(); - } - } - if(image == null) { - // Try to load from files near - if(relativeFilename != null) { - String[] split = relativeFilename.split("[\\\\/]"); - String filename = split[split.length - 1]; - Texture tex = null; - try { - tex = assetManager.loadTexture(new ContentTextureKey(scene.currentAssetInfo.getKey().getFolder() + filename, content)); - } catch(Exception e) {} - if(tex != null) - image = tex.getImage(); - } - } - if(image == null) - return new Image(Image.Format.RGB8, 1, 1, BufferUtils.createByteBuffer((int) ((long) 1 * (long) 1 * (long) Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear); - return image; - } + + String filename; + String relativeFilename; + byte[] content; + String imageType; + + public Image image; + + public FbxImage(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("Clip")) { + for(FbxElement e : element.children) { + switch(e.id) { + case "Type": + imageType = (String) e.properties.get(0); + break; + case "Filename": + case "FileName": + filename = (String) e.properties.get(0); + break; + case "RelativeFilename": + relativeFilename = (String) e.properties.get(0); + break; + case "Content": + if(e.properties.size() > 0) + content = (byte[]) e.properties.get(0); + break; + } + } + image = createImage(); + } + } + + + private Image createImage() { + AssetManager assetManager = scene.assetManager; + Image image = null; + if(filename != null) { + // Try load by absolute path + File file = new File(filename); + if(file.exists() && file.isFile()) { + File dir = new File(file.getParent()); + String locatorPath = dir.getAbsolutePath(); + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + tex = assetManager.loadTexture(file.getName()); + } catch(Exception e) {} finally { + assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + } + if(image == null && relativeFilename != null) { + // Try load by relative path + File dir = new File(scene.sceneFolderName); + String locatorPath = dir.getAbsolutePath(); + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + tex = assetManager.loadTexture(relativeFilename); + } catch(Exception e) {} finally { + assetManager.unregisterLocator(locatorPath, com.jme3.asset.plugins.FileLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + if(image == null && content != null) { + // Try load from content + String filename = null; + if(this.filename != null) + filename = new File(this.filename).getName(); + if(filename != null && this.relativeFilename != null) + filename = this.relativeFilename; + // Filename is required to acquire asset loader by extension + if(filename != null) { + String locatorPath = scene.sceneFilename; + filename = scene.sceneFilename + File.separatorChar + filename; // Unique path + Texture tex = null; + try { + assetManager.registerLocator(locatorPath, ContentTextureLocator.class); + tex = assetManager.loadTexture(new ContentTextureKey(filename, content)); + } catch(Exception e) {} finally { + assetManager.unregisterLocator(locatorPath, ContentTextureLocator.class); + } + if(tex != null) + image = tex.getImage(); + } + } + if(image == null) { + // Try to load from files near + if(relativeFilename != null) { + String[] split = relativeFilename.split("[\\\\/]"); + String filename = split[split.length - 1]; + Texture tex = null; + try { + tex = assetManager.loadTexture(new ContentTextureKey(scene.currentAssetInfo.getKey().getFolder() + filename, content)); + } catch(Exception e) {} + if(tex != null) + image = tex.getImage(); + } + } + if(image == null) + return new Image(Image.Format.RGB8, 1, 1, BufferUtils.createByteBuffer((int) (Image.Format.RGB8.getBitsPerPixel() / 8L)), ColorSpace.Linear); + return image; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java index c16d253add..647fdc3753 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMaterial.java @@ -8,106 +8,106 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxMaterial extends FbxObject { - - public String shadingModel = "phong"; - public Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f); - public float ambientFactor = 1.0f; - public Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f); - public float diffuseFactor = 1.0f; - public Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f); - public float specularFactor = 1.0f; - public float shininessExponent = 1.0f; - - public Material material; - - public FbxMaterial(SceneLoader scene, FbxElement element) { - super(scene, element); - if(type.equals("")) { - for(FbxElement e : element.children) { - if(e.id.equals("ShadingModel")) { - shadingModel = (String) e.properties.get(0); - } else if(e.id.equals("Properties70")) { - for(FbxElement e2 : e.children) { - if(e2.id.equals("P")) { - double x, y, z; - String propName = (String) e2.properties.get(0); - switch(propName) { - case "AmbientColor": - x = (Double) e2.properties.get(4); - y = (Double) e2.properties.get(5); - z = (Double) e2.properties.get(6); - ambientColor.set((float) x, (float) y, (float) z); - break; - case "AmbientFactor": - x = (Double) e2.properties.get(4); - ambientFactor = (float) x; - break; - case "DiffuseColor": - x = (Double) e2.properties.get(4); - y = (Double) e2.properties.get(5); - z = (Double) e2.properties.get(6); - diffuseColor.set((float) x, (float) y, (float) z); - break; - case "DiffuseFactor": - x = (Double) e2.properties.get(4); - diffuseFactor = (float) x; - break; - case "SpecularColor": - x = (Double) e2.properties.get(4); - y = (Double) e2.properties.get(5); - z = (Double) e2.properties.get(6); - specularColor.set((float) x, (float) y, (float) z); - break; - case "Shininess": - case "ShininessExponent": - x = (Double) e2.properties.get(4); - shininessExponent = (float) x; - break; - } - } - } - } - } - material = createMaterial(); - } - } - - @Override - public void link(FbxObject otherObject, String propertyName) { - if(otherObject instanceof FbxTexture) { - FbxTexture tex = (FbxTexture) otherObject; - if(tex.texture == null || material == null) - return; - switch(propertyName) { - case "DiffuseColor": - material.setTexture("DiffuseMap", tex.texture); - material.setColor("Diffuse", ColorRGBA.White); - break; - case "SpecularColor": - material.setTexture("SpecularMap", tex.texture); - material.setColor("Specular", ColorRGBA.White); - break; - case "NormalMap": - material.setTexture("NormalMap", tex.texture); - break; - } - } - } - - private Material createMaterial() { - Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md"); - m.setName(name); - ambientColor.multLocal(ambientFactor); - diffuseColor.multLocal(diffuseFactor); - specularColor.multLocal(specularFactor); - m.setColor("Ambient", new ColorRGBA(ambientColor.x, ambientColor.y, ambientColor.z, 1)); - m.setColor("Diffuse", new ColorRGBA(diffuseColor.x, diffuseColor.y, diffuseColor.z, 1)); - m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1)); - m.setFloat("Shininess", shininessExponent); - m.setBoolean("UseMaterialColors", true); - m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Aplha Test" - m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - return m; - } - + + public String shadingModel = "phong"; + public Vector3f ambientColor = new Vector3f(0.2f, 0.2f, 0.2f); + public float ambientFactor = 1.0f; + public Vector3f diffuseColor = new Vector3f(0.8f, 0.8f, 0.8f); + public float diffuseFactor = 1.0f; + public Vector3f specularColor = new Vector3f(0.2f, 0.2f, 0.2f); + public float specularFactor = 1.0f; + public float shininessExponent = 1.0f; + + public Material material; + + public FbxMaterial(SceneLoader scene, FbxElement element) { + super(scene, element); + if(type.equals("")) { + for(FbxElement e : element.children) { + if(e.id.equals("ShadingModel")) { + shadingModel = (String) e.properties.get(0); + } else if(e.id.equals("Properties70")) { + for(FbxElement e2 : e.children) { + if(e2.id.equals("P")) { + double x, y, z; + String propName = (String) e2.properties.get(0); + switch(propName) { + case "AmbientColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + ambientColor.set((float) x, (float) y, (float) z); + break; + case "AmbientFactor": + x = (Double) e2.properties.get(4); + ambientFactor = (float) x; + break; + case "DiffuseColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + diffuseColor.set((float) x, (float) y, (float) z); + break; + case "DiffuseFactor": + x = (Double) e2.properties.get(4); + diffuseFactor = (float) x; + break; + case "SpecularColor": + x = (Double) e2.properties.get(4); + y = (Double) e2.properties.get(5); + z = (Double) e2.properties.get(6); + specularColor.set((float) x, (float) y, (float) z); + break; + case "Shininess": + case "ShininessExponent": + x = (Double) e2.properties.get(4); + shininessExponent = (float) x; + break; + } + } + } + } + } + material = createMaterial(); + } + } + + @Override + public void link(FbxObject otherObject, String propertyName) { + if(otherObject instanceof FbxTexture) { + FbxTexture tex = (FbxTexture) otherObject; + if(tex.texture == null || material == null) + return; + switch(propertyName) { + case "DiffuseColor": + material.setTexture("DiffuseMap", tex.texture); + material.setColor("Diffuse", ColorRGBA.White); + break; + case "SpecularColor": + material.setTexture("SpecularMap", tex.texture); + material.setColor("Specular", ColorRGBA.White); + break; + case "NormalMap": + material.setTexture("NormalMap", tex.texture); + break; + } + } + } + + private Material createMaterial() { + Material m = new Material(scene.assetManager, "Common/MatDefs/Light/Lighting.j3md"); + m.setName(name); + ambientColor.multLocal(ambientFactor); + diffuseColor.multLocal(diffuseFactor); + specularColor.multLocal(specularFactor); + m.setColor("Ambient", new ColorRGBA(ambientColor.x, ambientColor.y, ambientColor.z, 1)); + m.setColor("Diffuse", new ColorRGBA(diffuseColor.x, diffuseColor.y, diffuseColor.z, 1)); + m.setColor("Specular", new ColorRGBA(specularColor.x, specularColor.y, specularColor.z, 1)); + m.setFloat("Shininess", shininessExponent); + m.setBoolean("UseMaterialColors", true); + m.setFloat("AlphaDiscardThreshold", 0.5f); // TODO replace with right way in JME to set "Alpha Test" + m.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); + return m; + } + } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java index 930dd830d3..9cc40fe5b9 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxMesh.java @@ -19,519 +19,519 @@ import com.jme3.util.IntMap.Entry; public class FbxMesh extends FbxObject { - - public double[] vertices; - public int[] indices; - public int[] edges; - public String normalsMapping; - public String normalsReference; - public double[] normals; - public String tangentsMapping; - public String tangentsReference; - public double[] tangents; - public String binormalsMapping; - public String binormalsReference; - public double[] binormals; - public String uvMapping; - public String uvReference; - public double[] uv; - public int[] uvIndex; - public List uvIndexes = new ArrayList<>(); - public List uvs = new ArrayList<>(); - public String smoothingMapping; - public String smoothingReference; - public int[] smoothing; - public String materialsMapping; - public String materialsReference; - public int[] materials; - // Build helping data - public int iCount; - public int vCount; - public int srcVertexCount; - public List vertexMap; // Target vertex -> source vertex - public List> reverseVertexMap; // source vertex -> list of target vertices - public List indexMap; // Target vertex -> source index - - public List geometries; // One mesh can be split in two geometries in case of by-polygon material mapping - public FbxNode parent; - public int lastMaterialId = 0; - - public FbxMesh(SceneLoader scene, FbxElement element) throws IOException { - super(scene, element); - if(type.equals("Mesh")) { - data: for(FbxElement e : element.children) { - switch(e.id) { - case "Vertices": - vertices = (double[]) e.properties.get(0); - break; - case "PolygonVertexIndex": - indices = (int[]) e.properties.get(0); - break; - // TODO edges are not used now - /*case "Edges": - edges = (int[]) e.properties.get(0); - break;*/ - case "LayerElementNormal": - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - normalsMapping = (String) e2.properties.get(0); - if(!normalsMapping.equals("ByVertice") && !normalsMapping.equals("ByPolygonVertex")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementNormal.MappingInformationType attribute (" + normalsReference + ")"); - continue data; - } - break; - case "ReferenceInformationType": - normalsReference = (String) e2.properties.get(0); - if(!normalsReference.equals("Direct")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementNormal.ReferenceInformationType attribute (" + normalsReference + ")"); - continue data; - } - break; - case "Normals": - normals = (double[]) e2.properties.get(0); - break; - } - } - break; - case "LayerElementTangent": - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - tangentsMapping = (String) e2.properties.get(0); - if(!tangentsMapping.equals("ByVertice") && !tangentsMapping.equals("ByPolygonVertex")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementTangent.MappingInformationType attribute (" + tangentsMapping + ")"); - continue data; - } - break; - case "ReferenceInformationType": - tangentsReference = (String) e2.properties.get(0); - if(!tangentsReference.equals("Direct")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementTangent.ReferenceInformationType attribute (" + tangentsReference + ")"); - continue data; - } - break; - case "Tangents": - tangents = (double[]) e2.properties.get(0); - break; - } - } - break; - case "LayerElementBinormal": - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - binormalsMapping = (String) e2.properties.get(0); - if(!binormalsMapping.equals("ByVertice") && !binormalsMapping.equals("ByPolygonVertex")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementBinormal.MappingInformationType attribute (" + binormalsMapping + ")"); - continue data; - } - break; - case "ReferenceInformationType": - binormalsReference = (String) e2.properties.get(0); - if(!binormalsReference.equals("Direct")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementBinormal.ReferenceInformationType attribute (" + binormalsReference + ")"); - continue data; - } - break; - case "Tangents": - binormals = (double[]) e2.properties.get(0); - break; - } - } - break; - case "LayerElementUV": - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - uvMapping = (String) e2.properties.get(0); - if(!uvMapping.equals("ByPolygonVertex")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementUV.MappingInformationType attribute (" + uvMapping + ")"); - continue data; - } - break; - case "ReferenceInformationType": - uvReference = (String) e2.properties.get(0); - if(!uvReference.equals("IndexToDirect")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementUV.ReferenceInformationType attribute (" + uvReference + ")"); - continue data; - } - break; - case "UV": - uv = (double[]) e2.properties.get(0); - uvs.add(uv); - break; - case "UVIndex": - uvIndex = (int[]) e2.properties.get(0); - uvIndexes.add(uvIndex); - break; - } - } - break; - // TODO smoothing is not used now - /*case "LayerElementSmoothing": - for(FBXElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - smoothingMapping = (String) e2.properties.get(0); - if(!smoothingMapping.equals("ByEdge")) - throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + smoothingMapping); - break; - case "ReferenceInformationType": - smoothingReference = (String) e2.properties.get(0); - if(!smoothingReference.equals("Direct")) - throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + smoothingReference); - break; - case "Smoothing": - smoothing = (int[]) e2.properties.get(0); - break; - } - } - break;*/ - case "LayerElementMaterial": - for(FbxElement e2 : e.children) { - switch(e2.id) { - case "MappingInformationType": - materialsMapping = (String) e2.properties.get(0); - if(!materialsMapping.equals("AllSame") && !materialsMapping.equals("ByPolygon")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementMaterial.MappingInformationType attribute (" + materialsMapping + ")"); - continue data; - } - break; - case "ReferenceInformationType": - materialsReference = (String) e2.properties.get(0); - if(!materialsReference.equals("IndexToDirect")) { - if(SceneLoader.WARN_IGNORED_ATTRIBUTES) - scene.warning("Ignored LayerElementMaterial.ReferenceInformationType attribute (" + materialsReference + ")"); - continue data; - } - break; - case "Materials": - materials = (int[]) e2.properties.get(0); - break; - } - } - break; - } - } - geometries = createGeometries(); - } - } - - public void setParent(Node node) { - for(int i = 0; i < geometries.size(); ++i) { - Geometry geom = geometries.get(i); - geom.setName(node.getName() + (i > 0 ? "-" + i : "")); - geom.updateModelBound(); - node.attachChild(geom); - } - } - - @Override - public void linkToZero() { - setParent(scene.sceneNode); - } - - public void clearMaterials() { - for(Geometry g : geometries) { - if(g.getUserData("FBXMaterial") != null) - g.setUserData("FBXMaterial", null); - } - } - - @Override - public void link(FbxObject otherObject) { - if(otherObject instanceof FbxSkin) { - FbxSkin skin = (FbxSkin) otherObject; - skin.toSkin.add(this); - } - } - - private List createGeometries() throws IOException { - Mesh mesh = new Mesh(); - mesh.setMode(Mode.Triangles); - // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing - // So we don't use VertexBuffer.Type.Index for elements drawing - // Moreover quads should be triangulated (this increases number of vertices) - if(indices != null) { - iCount = indices.length; - srcVertexCount = vertices.length / 3; - // Indices contains negative numbers to define polygon last index - // Check indices strides to be sure we have triangles or quads - vCount = 0; - // Count number of vertices to be produced - int polyVertCount = 0; - for(int i = 0; i < iCount; ++i) { - int index = indices[i]; - polyVertCount++; - if(index < 0) { - if(polyVertCount == 3) { - vCount += 3; // A triangle - } else if(polyVertCount == 4) { - vCount += 6; // A quad produce two triangles - } else { - throw new AssetLoadException("Unsupported PolygonVertexIndex stride"); - } - polyVertCount = 0; - } - } - // Unroll index array into vertex mapping - vertexMap = new ArrayList<>(vCount); - indexMap = new ArrayList<>(vCount); - polyVertCount = 0; - for(int i = 0; i < iCount; ++i) { - int index = indices[i]; - polyVertCount++; - if(index < 0) { - int lastIndex = -(index + 1); - if(polyVertCount == 3) { - vertexMap.add(indices[i - 2]); - vertexMap.add(indices[i - 1]); - vertexMap.add(lastIndex); - indexMap.add(i - 2); - indexMap.add(i - 1); - indexMap.add(i - 0); - } else if(polyVertCount == 4) { - vertexMap.add(indices[i - 3]); - vertexMap.add(indices[i - 2]); - vertexMap.add(indices[i - 1]); - vertexMap.add(indices[i - 3]); - vertexMap.add(indices[i - 1]); - vertexMap.add(lastIndex); - indexMap.add(i - 3); - indexMap.add(i - 2); - indexMap.add(i - 1); - indexMap.add(i - 3); - indexMap.add(i - 1); - indexMap.add(i - 0); - } - polyVertCount = 0; - } - } - // Build reverse vertex mapping - reverseVertexMap = new ArrayList<>(srcVertexCount); - for(int i = 0; i < srcVertexCount; ++i) - reverseVertexMap.add(new ArrayList()); - for(int i = 0; i < vCount; ++i) { - int index = vertexMap.get(i); - reverseVertexMap.get(index).add(i); - } - } else { - // Stub for no vertex indexing (direct mapping) - iCount = vCount = srcVertexCount; - vertexMap = new ArrayList<>(vCount); - indexMap = new ArrayList<>(vCount); - reverseVertexMap = new ArrayList<>(vCount); - for(int i = 0; i < vCount; ++i) { - vertexMap.set(i, i); - indexMap.set(i, i); - List l = new ArrayList(1); - l.add(i); - reverseVertexMap.add(l); - } - } - if(vertices != null) { - // Unroll vertices data array - FloatBuffer posBuf = BufferUtils.createFloatBuffer(vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Position, 3, posBuf); - int srcCount = vertices.length / 3; - for(int i = 0; i < vCount; ++i) { - int index = vertexMap.get(i); - if(index > srcCount) - throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); - float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size? - float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis; - float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis; - posBuf.put(x).put(y).put(z); - } - } - if(normals != null) { - // Unroll normals data array - FloatBuffer normBuf = BufferUtils.createFloatBuffer(vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Normal, 3, normBuf); - List mapping = null; - if(normalsMapping.equals("ByVertice")) - mapping = vertexMap; - else if(normalsMapping.equals("ByPolygonVertex")) - mapping = indexMap; - else - throw new IOException("Unknown normals mapping type: " + normalsMapping); - int srcCount = normals.length / 3; - for(int i = 0; i < vCount; ++i) { - int index = mapping.get(i); - if(index > srcCount) - throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); - float x = (float) normals[3 * index + 0] * scene.xAxis; - float y = (float) normals[3 * index + 1] * scene.yAxis; - float z = (float) normals[3 * index + 2] * scene.zAxis; - normBuf.put(x).put(y).put(z); - } - } - if(tangents != null) { - // Unroll normals data array - FloatBuffer tanBuf = BufferUtils.createFloatBuffer(vCount * 4); - mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tanBuf); - List mapping = null; - if(tangentsMapping.equals("ByVertice")) - mapping = vertexMap; - else if(tangentsMapping.equals("ByPolygonVertex")) - mapping = indexMap; - else - throw new IOException("Unknown tangents mapping type: " + tangentsMapping); - int srcCount = tangents.length / 3; - for(int i = 0; i < vCount; ++i) { - int index = mapping.get(i); - if(index > srcCount) - throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); - float x = (float) tangents[3 * index + 0] * scene.xAxis; - float y = (float) tangents[3 * index + 1] * scene.yAxis; - float z = (float) tangents[3 * index + 2] * scene.zAxis; - tanBuf.put(x).put(y).put(z).put(-1.0f); - } - } - if(binormals != null) { - // Unroll normals data array - FloatBuffer binormBuf = BufferUtils.createFloatBuffer(vCount * 3); - mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormBuf); - List mapping = null; - if(binormalsMapping.equals("ByVertice")) - mapping = vertexMap; - else if(binormalsMapping.equals("ByPolygonVertex")) - mapping = indexMap; - else - throw new IOException("Unknown binormals mapping type: " + binormalsMapping); - int srcCount = binormals.length / 3; - for(int i = 0; i < vCount; ++i) { - int index = mapping.get(i); - if(index > srcCount) - throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); - float x = (float) binormals[3 * index + 0] * scene.xAxis; - float y = (float) binormals[3 * index + 1] * scene.yAxis; - float z = (float) binormals[3 * index + 2] * scene.zAxis; - binormBuf.put(x).put(y).put(z); - } - } - for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) { - double[] uv = uvs.get(uvLayer); - int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null; - List unIndexMap = vertexMap; - if(uvIndex != null) { - int uvIndexSrcCount = uvIndex.length; - if(uvIndexSrcCount != iCount) - throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount); - // Unroll UV index array - unIndexMap = new ArrayList<>(vCount); - int polyVertCount = 0; - for(int i = 0; i < iCount; ++i) { - int index = indices[i]; - polyVertCount++; - if(index < 0) { - if(polyVertCount == 3) { - unIndexMap.add(uvIndex[i - 2]); - unIndexMap.add(uvIndex[i - 1]); - unIndexMap.add(uvIndex[i - 0]); - } else if(polyVertCount == 4) { - unIndexMap.add(uvIndex[i - 3]); - unIndexMap.add(uvIndex[i - 2]); - unIndexMap.add(uvIndex[i - 1]); - unIndexMap.add(uvIndex[i - 3]); - unIndexMap.add(uvIndex[i - 1]); - unIndexMap.add(uvIndex[i - 0]); - } - polyVertCount = 0; - } - } - } - // Unroll UV data array - FloatBuffer tcBuf = BufferUtils.createFloatBuffer(vCount * 2); - VertexBuffer.Type type = VertexBuffer.Type.TexCoord; - switch(uvLayer) { - case 1: - type = VertexBuffer.Type.TexCoord2; - break; - case 2: - type = VertexBuffer.Type.TexCoord3; - break; - case 3: - type = VertexBuffer.Type.TexCoord4; - break; - case 4: - type = VertexBuffer.Type.TexCoord5; - break; - case 5: - type = VertexBuffer.Type.TexCoord6; - break; - case 6: - type = VertexBuffer.Type.TexCoord7; - break; - case 7: - type = VertexBuffer.Type.TexCoord8; - break; - } - mesh.setBuffer(type, 2, tcBuf); - int srcCount = uv.length / 2; - for(int i = 0; i < vCount; ++i) { - int index = unIndexMap.get(i); - if(index > srcCount) - throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); - float u = (index >= 0) ? (float) uv[2 * index + 0] : 0; - float v = (index >= 0) ? (float) uv[2 * index + 1] : 0; - tcBuf.put(u).put(v); - } - } - List geometries = new ArrayList(); - if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) { - IntMap> indexBuffers = new IntMap<>(); - for(int polygon = 0; polygon < materials.length; ++polygon) { - int material = materials[polygon]; - List list = indexBuffers.get(material); - if(list == null) { - list = new ArrayList<>(); - indexBuffers.put(material, list); - } - list.add(polygon * 3 + 0); - list.add(polygon * 3 + 1); - list.add(polygon * 3 + 2); - } - Iterator>> iterator = indexBuffers.iterator(); - while(iterator.hasNext()) { - Entry> e = iterator.next(); - int materialId = e.getKey(); - List indexes = e.getValue(); - Mesh newMesh = mesh.clone(); - newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()]))); - newMesh.setStatic(); - newMesh.updateBound(); - newMesh.updateCounts(); - Geometry geom = new Geometry(); - geom.setMesh(newMesh); - geometries.add(geom); - geom.setUserData("FBXMaterial", materialId); - } - } else { - mesh.setStatic(); - mesh.updateBound(); - mesh.updateCounts(); - Geometry geom = new Geometry(); - geom.setMesh(mesh); - geometries.add(geom); - } - return geometries; - } - - private static int[] toArray(Integer[] arr) { - int[] ret = new int[arr.length]; - for(int i = 0; i < arr.length; ++i) - ret[i] = arr[i].intValue(); - return ret; - } + + public double[] vertices; + public int[] indices; + public int[] edges; + public String normalsMapping; + public String normalsReference; + public double[] normals; + public String tangentsMapping; + public String tangentsReference; + public double[] tangents; + public String binormalsMapping; + public String binormalsReference; + public double[] binormals; + public String uvMapping; + public String uvReference; + public double[] uv; + public int[] uvIndex; + public List uvIndexes = new ArrayList<>(); + public List uvs = new ArrayList<>(); + public String smoothingMapping; + public String smoothingReference; + public int[] smoothing; + public String materialsMapping; + public String materialsReference; + public int[] materials; + // Build helping data + public int iCount; + public int vCount; + public int srcVertexCount; + public List vertexMap; // Target vertex -> source vertex + public List> reverseVertexMap; // source vertex -> list of target vertices + public List indexMap; // Target vertex -> source index + + public List geometries; // One mesh can be split in two geometries in case of by-polygon material mapping + public FbxNode parent; + public int lastMaterialId = 0; + + public FbxMesh(SceneLoader scene, FbxElement element) throws IOException { + super(scene, element); + if(type.equals("Mesh")) { + data: for(FbxElement e : element.children) { + switch(e.id) { + case "Vertices": + vertices = (double[]) e.properties.get(0); + break; + case "PolygonVertexIndex": + indices = (int[]) e.properties.get(0); + break; + // TODO edges are not used now + /*case "Edges": + edges = (int[]) e.properties.get(0); + break;*/ + case "LayerElementNormal": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + normalsMapping = (String) e2.properties.get(0); + if(!normalsMapping.equals("ByVertice") && !normalsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementNormal.MappingInformationType attribute (" + normalsReference + ")"); + continue data; + } + break; + case "ReferenceInformationType": + normalsReference = (String) e2.properties.get(0); + if(!normalsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementNormal.ReferenceInformationType attribute (" + normalsReference + ")"); + continue data; + } + break; + case "Normals": + normals = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementTangent": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + tangentsMapping = (String) e2.properties.get(0); + if(!tangentsMapping.equals("ByVertice") && !tangentsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementTangent.MappingInformationType attribute (" + tangentsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + tangentsReference = (String) e2.properties.get(0); + if(!tangentsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementTangent.ReferenceInformationType attribute (" + tangentsReference + ")"); + continue data; + } + break; + case "Tangents": + tangents = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementBinormal": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + binormalsMapping = (String) e2.properties.get(0); + if(!binormalsMapping.equals("ByVertice") && !binormalsMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementBinormal.MappingInformationType attribute (" + binormalsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + binormalsReference = (String) e2.properties.get(0); + if(!binormalsReference.equals("Direct")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementBinormal.ReferenceInformationType attribute (" + binormalsReference + ")"); + continue data; + } + break; + case "Tangents": + binormals = (double[]) e2.properties.get(0); + break; + } + } + break; + case "LayerElementUV": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + uvMapping = (String) e2.properties.get(0); + if(!uvMapping.equals("ByPolygonVertex")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementUV.MappingInformationType attribute (" + uvMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + uvReference = (String) e2.properties.get(0); + if(!uvReference.equals("IndexToDirect")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementUV.ReferenceInformationType attribute (" + uvReference + ")"); + continue data; + } + break; + case "UV": + uv = (double[]) e2.properties.get(0); + uvs.add(uv); + break; + case "UVIndex": + uvIndex = (int[]) e2.properties.get(0); + uvIndexes.add(uvIndex); + break; + } + } + break; + // TODO smoothing is not used now + /*case "LayerElementSmoothing": + for(FBXElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + smoothingMapping = (String) e2.properties.get(0); + if(!smoothingMapping.equals("ByEdge")) + throw new AssetLoadException("Not supported LayerElementSmoothing.MappingInformationType = " + smoothingMapping); + break; + case "ReferenceInformationType": + smoothingReference = (String) e2.properties.get(0); + if(!smoothingReference.equals("Direct")) + throw new AssetLoadException("Not supported LayerElementSmoothing.ReferenceInformationType = " + smoothingReference); + break; + case "Smoothing": + smoothing = (int[]) e2.properties.get(0); + break; + } + } + break;*/ + case "LayerElementMaterial": + for(FbxElement e2 : e.children) { + switch(e2.id) { + case "MappingInformationType": + materialsMapping = (String) e2.properties.get(0); + if(!materialsMapping.equals("AllSame") && !materialsMapping.equals("ByPolygon")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementMaterial.MappingInformationType attribute (" + materialsMapping + ")"); + continue data; + } + break; + case "ReferenceInformationType": + materialsReference = (String) e2.properties.get(0); + if(!materialsReference.equals("IndexToDirect")) { + if(SceneLoader.WARN_IGNORED_ATTRIBUTES) + scene.warning("Ignored LayerElementMaterial.ReferenceInformationType attribute (" + materialsReference + ")"); + continue data; + } + break; + case "Materials": + materials = (int[]) e2.properties.get(0); + break; + } + } + break; + } + } + geometries = createGeometries(); + } + } + + public void setParent(Node node) { + for(int i = 0; i < geometries.size(); ++i) { + Geometry geom = geometries.get(i); + geom.setName(node.getName() + (i > 0 ? "-" + i : "")); + geom.updateModelBound(); + node.attachChild(geom); + } + } + + @Override + public void linkToZero() { + setParent(scene.sceneNode); + } + + public void clearMaterials() { + for(Geometry g : geometries) { + if(g.getUserData("FBXMaterial") != null) + g.setUserData("FBXMaterial", null); + } + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxSkin) { + FbxSkin skin = (FbxSkin) otherObject; + skin.toSkin.add(this); + } + } + + private List createGeometries() throws IOException { + Mesh mesh = new Mesh(); + mesh.setMode(Mode.Triangles); + // Since each vertex should contain unique texcoord and normal we should unroll vertex indexing + // So we don't use VertexBuffer.Type.Index for elements drawing + // Moreover quads should be triangulated (this increases number of vertices) + if(indices != null) { + iCount = indices.length; + srcVertexCount = vertices.length / 3; + // Indices contain negative numbers to define polygon last index. + // Check index strides to be sure we have triangles or quads. + vCount = 0; + // Count number of vertices to be produced + int polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + if(polyVertCount == 3) { + vCount += 3; // A triangle + } else if(polyVertCount == 4) { + vCount += 6; // A quad produce two triangles + } else { + throw new AssetLoadException("Unsupported PolygonVertexIndex stride"); + } + polyVertCount = 0; + } + } + // Unroll index array into vertex mapping + vertexMap = new ArrayList<>(vCount); + indexMap = new ArrayList<>(vCount); + polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + int lastIndex = -(index + 1); + if(polyVertCount == 3) { + vertexMap.add(indices[i - 2]); + vertexMap.add(indices[i - 1]); + vertexMap.add(lastIndex); + indexMap.add(i - 2); + indexMap.add(i - 1); + indexMap.add(i - 0); + } else if(polyVertCount == 4) { + vertexMap.add(indices[i - 3]); + vertexMap.add(indices[i - 2]); + vertexMap.add(indices[i - 1]); + vertexMap.add(indices[i - 3]); + vertexMap.add(indices[i - 1]); + vertexMap.add(lastIndex); + indexMap.add(i - 3); + indexMap.add(i - 2); + indexMap.add(i - 1); + indexMap.add(i - 3); + indexMap.add(i - 1); + indexMap.add(i - 0); + } + polyVertCount = 0; + } + } + // Build reverse vertex mapping + reverseVertexMap = new ArrayList<>(srcVertexCount); + for(int i = 0; i < srcVertexCount; ++i) + reverseVertexMap.add(new ArrayList()); + for(int i = 0; i < vCount; ++i) { + int index = vertexMap.get(i); + reverseVertexMap.get(index).add(i); + } + } else { + // Stub for no vertex indexing (direct mapping) + iCount = vCount = srcVertexCount; + vertexMap = new ArrayList<>(vCount); + indexMap = new ArrayList<>(vCount); + reverseVertexMap = new ArrayList<>(vCount); + for(int i = 0; i < vCount; ++i) { + vertexMap.set(i, i); + indexMap.set(i, i); + List l = new ArrayList<>(1); + l.add(i); + reverseVertexMap.add(l); + } + } + if(vertices != null) { + // Unroll vertices data array + FloatBuffer positionBuffer = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Position, 3, positionBuffer); + int srcCount = vertices.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = vertexMap.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid vertex mapping. Unexpected lookup vertex " + index + " from " + srcCount); + float x = (float) vertices[3 * index + 0] / scene.unitSize * scene.xAxis; // XXX Why we should scale by unit size? + float y = (float) vertices[3 * index + 1] / scene.unitSize * scene.yAxis; + float z = (float) vertices[3 * index + 2] / scene.unitSize * scene.zAxis; + positionBuffer.put(x).put(y).put(z); + } + } + if(normals != null) { + // Unroll normals data array + FloatBuffer normalBuffer = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Normal, 3, normalBuffer); + List mapping = null; + if(normalsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(normalsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown normals mapping type: " + normalsMapping); + int srcCount = normals.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid normal mapping. Unexpected lookup normal " + index + " from " + srcCount); + float x = (float) normals[3 * index + 0] * scene.xAxis; + float y = (float) normals[3 * index + 1] * scene.yAxis; + float z = (float) normals[3 * index + 2] * scene.zAxis; + normalBuffer.put(x).put(y).put(z); + } + } + if(tangents != null) { + // Unroll normals data array + FloatBuffer tangentBuffer = BufferUtils.createFloatBuffer(vCount * 4); + mesh.setBuffer(VertexBuffer.Type.Tangent, 4, tangentBuffer); + List mapping = null; + if(tangentsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(tangentsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown tangents mapping type: " + tangentsMapping); + int srcCount = tangents.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid tangent mapping. Unexpected lookup tangent " + index + " from " + srcCount); + float x = (float) tangents[3 * index + 0] * scene.xAxis; + float y = (float) tangents[3 * index + 1] * scene.yAxis; + float z = (float) tangents[3 * index + 2] * scene.zAxis; + tangentBuffer.put(x).put(y).put(z).put(-1.0f); + } + } + if(binormals != null) { + // Unroll normals data array + FloatBuffer binormalBuffer = BufferUtils.createFloatBuffer(vCount * 3); + mesh.setBuffer(VertexBuffer.Type.Binormal, 3, binormalBuffer); + List mapping = null; + if(binormalsMapping.equals("ByVertice")) + mapping = vertexMap; + else if(binormalsMapping.equals("ByPolygonVertex")) + mapping = indexMap; + else + throw new IOException("Unknown binormals mapping type: " + binormalsMapping); + int srcCount = binormals.length / 3; + for(int i = 0; i < vCount; ++i) { + int index = mapping.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid binormal mapping. Unexpected lookup binormal " + index + " from " + srcCount); + float x = (float) binormals[3 * index + 0] * scene.xAxis; + float y = (float) binormals[3 * index + 1] * scene.yAxis; + float z = (float) binormals[3 * index + 2] * scene.zAxis; + binormalBuffer.put(x).put(y).put(z); + } + } + for(int uvLayer = 0; uvLayer < uvs.size(); ++uvLayer) { + double[] uv = uvs.get(uvLayer); + int[] uvIndex = uvIndexes.size() > uvLayer ? uvIndexes.get(uvLayer) : null; + List unIndexMap = vertexMap; + if(uvIndex != null) { + int uvIndexSrcCount = uvIndex.length; + if(uvIndexSrcCount != iCount) + throw new AssetLoadException("Invalid number of texcoord index data " + uvIndexSrcCount + " expected " + iCount); + // Unroll UV index array + unIndexMap = new ArrayList<>(vCount); + int polyVertCount = 0; + for(int i = 0; i < iCount; ++i) { + int index = indices[i]; + polyVertCount++; + if(index < 0) { + if(polyVertCount == 3) { + unIndexMap.add(uvIndex[i - 2]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 0]); + } else if(polyVertCount == 4) { + unIndexMap.add(uvIndex[i - 3]); + unIndexMap.add(uvIndex[i - 2]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 3]); + unIndexMap.add(uvIndex[i - 1]); + unIndexMap.add(uvIndex[i - 0]); + } + polyVertCount = 0; + } + } + } + // Unroll UV data array + FloatBuffer uvBuffer = BufferUtils.createFloatBuffer(vCount * 2); + VertexBuffer.Type type = VertexBuffer.Type.TexCoord; + switch(uvLayer) { + case 1: + type = VertexBuffer.Type.TexCoord2; + break; + case 2: + type = VertexBuffer.Type.TexCoord3; + break; + case 3: + type = VertexBuffer.Type.TexCoord4; + break; + case 4: + type = VertexBuffer.Type.TexCoord5; + break; + case 5: + type = VertexBuffer.Type.TexCoord6; + break; + case 6: + type = VertexBuffer.Type.TexCoord7; + break; + case 7: + type = VertexBuffer.Type.TexCoord8; + break; + } + mesh.setBuffer(type, 2, uvBuffer); + int srcCount = uv.length / 2; + for(int i = 0; i < vCount; ++i) { + int index = unIndexMap.get(i); + if(index > srcCount) + throw new AssetLoadException("Invalid texcoord mapping. Unexpected lookup texcoord " + index + " from " + srcCount); + float u = (index >= 0) ? (float) uv[2 * index + 0] : 0; + float v = (index >= 0) ? (float) uv[2 * index + 1] : 0; + uvBuffer.put(u).put(v); + } + } + List geometries = new ArrayList<>(); + if(materialsReference.equals("IndexToDirect") && materialsMapping.equals("ByPolygon")) { + IntMap> indexBuffers = new IntMap<>(); + for(int polygon = 0; polygon < materials.length; ++polygon) { + int material = materials[polygon]; + List list = indexBuffers.get(material); + if(list == null) { + list = new ArrayList<>(); + indexBuffers.put(material, list); + } + list.add(polygon * 3 + 0); + list.add(polygon * 3 + 1); + list.add(polygon * 3 + 2); + } + Iterator>> iterator = indexBuffers.iterator(); + while(iterator.hasNext()) { + Entry> e = iterator.next(); + int materialId = e.getKey(); + List indexes = e.getValue(); + Mesh newMesh = mesh.clone(); + newMesh.setBuffer(VertexBuffer.Type.Index, 3, toArray(indexes.toArray(new Integer[indexes.size()]))); + newMesh.setStatic(); + newMesh.updateBound(); + newMesh.updateCounts(); + Geometry geom = new Geometry(); + geom.setMesh(newMesh); + geometries.add(geom); + geom.setUserData("FBXMaterial", materialId); + } + } else { + mesh.setStatic(); + mesh.updateBound(); + mesh.updateCounts(); + Geometry geom = new Geometry(); + geom.setMesh(mesh); + geometries.add(geom); + } + return geometries; + } + + private static int[] toArray(Integer[] arr) { + int[] ret = new int[arr.length]; + for(int i = 0; i < arr.length; ++i) + ret[i] = arr[i].intValue(); + return ret; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java index ebb2094928..1554bef9b0 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxNode.java @@ -18,255 +18,255 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxNode extends FbxObject { - - public FaceCullMode cullMode = FaceCullMode.Back; - public Transform localTransform; - public Node node; - public FbxNode parentFbxNode; - - public boolean rotationActive = false; - public RotationOrder rotationOrder = RotationOrder.EULER_XYZ; - - - // For bones and animation, in world space - public Matrix4f bindTransform = null; - public int boneIndex; - public Map animTranslations = new HashMap<>(); - public Map animRotations = new HashMap<>(); - public Map animScales = new HashMap<>(); - public Bone bone; - private FbxAnimNode lastAnimTranslation; - private FbxAnimNode lastAnimRotation; - private FbxAnimNode lastAnimScale; - private FbxMesh mesh; - public Map skinToCluster = new HashMap<>(); - - public FbxNode(SceneLoader scene, FbxElement element) { - super(scene, element); - node = new Node(name); - Vector3f translationLocalRaw = new Vector3f(); - Vector3f rotationOffsetRaw = new Vector3f(); - Vector3f rotationPivotRaw = new Vector3f(); - Vector3f rotationPreRaw = new Vector3f(); - Vector3f rotationLocalRaw = new Vector3f(); - Vector3f rotationPostRaw = new Vector3f(); - Vector3f scaleOffsetRaw = new Vector3f(); - Vector3f scalePivotRaw = new Vector3f(); - Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); - for(FbxElement prop : element.getFbxProperties()) { - double x, y, z; - String propName = (String) prop.properties.get(0); - switch(propName) { - case "RotationOrder": - rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)]; - break; - case "Lcl Translation": - readVectorFromProp(translationLocalRaw, prop); - break; - case "Lcl Rotation": - readVectorFromProp(rotationLocalRaw, prop); - break; - case "Lcl Scaling": - readVectorFromProp(scaleLocalRaw, prop); - break; - case "PreRotation": - readVectorFromProp(rotationPreRaw, prop); - break; - case "RotationActive": - rotationActive = ((Number) prop.properties.get(4)).intValue() == 1; - break; - case "RotationPivot": - readVectorFromProp(rotationPivotRaw, prop); - break; - case "PostRotation": - readVectorFromProp(rotationPostRaw, prop); - break; - case "ScaleOffset": - readVectorFromProp(scaleOffsetRaw, prop); - break; - case "ScalePivot": - readVectorFromProp(scalePivotRaw, prop); - break; - case "U": - String userDataKey = (String) prop.properties.get(0); - String userDataType = (String) prop.properties.get(1); - Object userDataValue; - if(userDataType.equals("KString")) { - userDataValue = (String) prop.properties.get(4); - } else if(userDataType.equals("int")) { - userDataValue = (Integer) prop.properties.get(4); - } else if(userDataType.equals("double")) { - // NOTE: jME3 does not support doubles in UserData. - // Need to convert to float. - userDataValue = ((Double) prop.properties.get(4)).floatValue(); - } else if(userDataType.equals("Vector")) { - x = (Double) prop.properties.get(4); - y = (Double) prop.properties.get(5); - z = (Double) prop.properties.get(6); - userDataValue = new Vector3f((float) x, (float) y, (float) z); - } else { - scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); - continue; - } - node.setUserData(userDataKey, userDataValue); - break; - } - } - - FbxElement cullingElement = element.getChildById("Culling"); - if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff")) - cullMode = FaceCullMode.Off; // TODO Add other variants - - /*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/ - - Local Matrix = LclTranslation * RotationOffset * RotationPivot * - PreRotation * LclRotation * PostRotation * RotationPivotInverse * - ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse - - LocalTranslation : translate (xform -query -translation) - RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation) - RotationPivot: current rotate pivot position (xform -q -rotatePivot) - PreRotation : joint orientation(pre rotation) - LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder) - PostRotation : rotate axis (xform -q -rotateAxis) - RotationPivotInverse: inverse of RotationPivot - ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation) - ScalingPivot: current scale pivot position (xform -q -scalePivot) - LocalScaling: scale transform (xform -q -scale) - ScalingPivotInverse: inverse of ScalingPivot - */ - - RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ; - - Matrix4f transformMatrix = new Matrix4f(); - transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z); - - if(rotationActive) { - Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); - Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z); - Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); - //preRotation.multLocal(localRotation).multLocal(postRotation); - postRotation.multLocal(localRotation).multLocal(preRotation); - transformMatrix.multLocal(postRotation); - } else { - transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z)); - } - - Matrix4f mat = new Matrix4f(); - mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z); - transformMatrix.multLocal(mat); - - transformMatrix.scale(scaleLocalRaw); - transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); - - mat.setTranslation(scalePivotRaw.negate()); - transformMatrix.multLocal(mat); - - localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); - - node.setLocalTransform(localTransform); - } - - @Override - public void linkToZero() { - scene.sceneNode.attachChild(node); - } - - public void setSkeleton(Skeleton skeleton) { - if(bone != null) - boneIndex = skeleton.getBoneIndex(bone); - } - - public void buildBindPoseBoneTransform() { - if(bone != null) { - Matrix4f t = bindTransform; - if(t != null) { - Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY; - if(parentMatrix == null) - parentMatrix = node.getLocalToWorldMatrix(null); - t = parentMatrix.invert().multLocal(t); - bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector()); - } else { - bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale()); - } - } - } - - @Override - public void link(FbxObject child, String propertyName) { - if(child instanceof FbxAnimNode) { - FbxAnimNode anim = (FbxAnimNode) child; - switch(propertyName) { - case "Lcl Translation": - animTranslations.put(anim.layerId, anim); - lastAnimTranslation = anim; - break; - case "Lcl Rotation": - animRotations.put(anim.layerId, anim); - lastAnimRotation = anim; - break; - case "Lcl Scaling": - animScales.put(anim.layerId, anim); - lastAnimScale = anim; - break; - } - } - } - - public FbxAnimNode animTranslation(long layerId) { - if(layerId == 0) - return lastAnimTranslation; - return animTranslations.get(layerId); - } - - public FbxAnimNode animRotation(long layerId) { - if(layerId == 0) - return lastAnimRotation; - return animRotations.get(layerId); - } - - public FbxAnimNode animScale(long layerId) { - if(layerId == 0) - return lastAnimScale; - return animScales.get(layerId); - } - - @Override - public void link(FbxObject otherObject) { - if(otherObject instanceof FbxMaterial) { - FbxMaterial m = (FbxMaterial) otherObject; - Material mat = m.material; - if(cullMode != FaceCullMode.Back) - mat.getAdditionalRenderState().setFaceCullMode(cullMode); - for(Geometry g : mesh.geometries) { - if(g.getUserData("FBXMaterial") != null) { - if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId) - g.setMaterial(mat); - } else { - g.setMaterial(mat); - } - } - mesh.lastMaterialId++; - } else if(otherObject instanceof FbxNode) { - FbxNode n = (FbxNode) otherObject; - node.attachChild(n.node); - n.parentFbxNode = this; - if(isLimb() && n.isLimb()) { - if(bone == null) - bone = new Bone(name); - if(n.bone == null) - n.bone = new Bone(n.name); - bone.addChild(n.bone); - } - } else if(otherObject instanceof FbxMesh) { - FbxMesh m = (FbxMesh) otherObject; - m.setParent(node); - m.parent = this; - mesh = m; - } - } - - public boolean isLimb() { - return type.equals("LimbNode"); - } + + public FaceCullMode cullMode = FaceCullMode.Back; + public Transform localTransform; + public Node node; + public FbxNode parentFbxNode; + + public boolean rotationActive = false; + public RotationOrder rotationOrder = RotationOrder.EULER_XYZ; + + + // For bones and animation, in world space + public Matrix4f bindTransform = null; + public int boneIndex; + public Map animTranslations = new HashMap<>(); + public Map animRotations = new HashMap<>(); + public Map animScales = new HashMap<>(); + public Bone bone; + private FbxAnimNode lastAnimTranslation; + private FbxAnimNode lastAnimRotation; + private FbxAnimNode lastAnimScale; + private FbxMesh mesh; + public Map skinToCluster = new HashMap<>(); + + public FbxNode(SceneLoader scene, FbxElement element) { + super(scene, element); + node = new Node(name); + Vector3f translationLocalRaw = new Vector3f(); + Vector3f rotationOffsetRaw = new Vector3f(); + Vector3f rotationPivotRaw = new Vector3f(); + Vector3f rotationPreRaw = new Vector3f(); + Vector3f rotationLocalRaw = new Vector3f(); + Vector3f rotationPostRaw = new Vector3f(); + Vector3f scaleOffsetRaw = new Vector3f(); + Vector3f scalePivotRaw = new Vector3f(); + Vector3f scaleLocalRaw = new Vector3f(1, 1, 1); + for(FbxElement prop : element.getFbxProperties()) { + double x, y, z; + String propName = (String) prop.properties.get(0); + switch(propName) { + case "RotationOrder": + rotationOrder = RotationOrder.values[(Integer) prop.properties.get(4)]; + break; + case "Lcl Translation": + readVectorFromProp(translationLocalRaw, prop); + break; + case "Lcl Rotation": + readVectorFromProp(rotationLocalRaw, prop); + break; + case "Lcl Scaling": + readVectorFromProp(scaleLocalRaw, prop); + break; + case "PreRotation": + readVectorFromProp(rotationPreRaw, prop); + break; + case "RotationActive": + rotationActive = ((Number) prop.properties.get(4)).intValue() == 1; + break; + case "RotationPivot": + readVectorFromProp(rotationPivotRaw, prop); + break; + case "PostRotation": + readVectorFromProp(rotationPostRaw, prop); + break; + case "ScaleOffset": + readVectorFromProp(scaleOffsetRaw, prop); + break; + case "ScalePivot": + readVectorFromProp(scalePivotRaw, prop); + break; + case "U": + String userDataKey = (String) prop.properties.get(0); + String userDataType = (String) prop.properties.get(1); + Object userDataValue; + if(userDataType.equals("KString")) { + userDataValue = prop.properties.get(4); + } else if(userDataType.equals("int")) { + userDataValue = prop.properties.get(4); + } else if(userDataType.equals("double")) { + // NOTE: jME3 does not support doubles in UserData. + // Need to convert to float. + userDataValue = ((Double) prop.properties.get(4)).floatValue(); + } else if(userDataType.equals("Vector")) { + x = (Double) prop.properties.get(4); + y = (Double) prop.properties.get(5); + z = (Double) prop.properties.get(6); + userDataValue = new Vector3f((float) x, (float) y, (float) z); + } else { + scene.warning("Unsupported user data type: " + userDataType + ". Ignoring."); + continue; + } + node.setUserData(userDataKey, userDataValue); + break; + } + } + + FbxElement cullingElement = element.getChildById("Culling"); + if(cullingElement != null && cullingElement.properties.get(0).equals("CullingOff")) + cullMode = FaceCullMode.Off; // TODO Add other variants + + /*From http://area.autodesk.com/forum/autodesk-fbx/fbx-sdk/the-makeup-of-the-local-matrix-of-an-kfbxnode/ + + Local Matrix = LclTranslation * RotationOffset * RotationPivot * + PreRotation * LclRotation * PostRotation * RotationPivotInverse * + ScalingOffset * ScalingPivot * LclScaling * ScalingPivotInverse + + LocalTranslation : translate (xform -query -translation) + RotationOffset: translation compensates for the change in the rotate pivot point (xform -q -rotateTranslation) + RotationPivot: current rotate pivot position (xform -q -rotatePivot) + PreRotation : joint orientation(pre rotation) + LocalRotation: rotate transform (xform -q -rotation & xform -q -rotateOrder) + PostRotation : rotate axis (xform -q -rotateAxis) + RotationPivotInverse: inverse of RotationPivot + ScalingOffset: translation compensates for the change in the scale pivot point (xform -q -scaleTranslation) + ScalingPivot: current scale pivot position (xform -q -scalePivot) + LocalScaling: scale transform (xform -q -scale) + ScalingPivotInverse: inverse of ScalingPivot + */ + + RotationOrder rotOrder = rotationActive ? rotationOrder : RotationOrder.EULER_XYZ; + + Matrix4f transformMatrix = new Matrix4f(); + transformMatrix.setTranslation(translationLocalRaw.x + rotationOffsetRaw.x + rotationPivotRaw.x, translationLocalRaw.y + rotationOffsetRaw.y + rotationPivotRaw.y, translationLocalRaw.z + rotationOffsetRaw.z + rotationPivotRaw.z); + + if(rotationActive) { + Quaternion postRotation = rotOrder.rotate(rotationPostRaw.x, rotationPostRaw.y, rotationPostRaw.z); + Quaternion localRotation = rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z); + Quaternion preRotation = rotOrder.rotate(rotationPreRaw.x, rotationPreRaw.y, rotationPreRaw.z); + //preRotation.multLocal(localRotation).multLocal(postRotation); + postRotation.multLocal(localRotation).multLocal(preRotation); + transformMatrix.multLocal(postRotation); + } else { + transformMatrix.multLocal(rotOrder.rotate(rotationLocalRaw.x, rotationLocalRaw.y, rotationLocalRaw.z)); + } + + Matrix4f mat = new Matrix4f(); + mat.setTranslation(scaleOffsetRaw.x + scalePivotRaw.x - rotationPivotRaw.x, scaleOffsetRaw.y + scalePivotRaw.y - rotationPivotRaw.y, scaleOffsetRaw.z + scalePivotRaw.z - rotationPivotRaw.z); + transformMatrix.multLocal(mat); + + transformMatrix.scale(scaleLocalRaw); + transformMatrix.scale(new Vector3f(scene.unitSize, scene.unitSize, scene.unitSize)); + + mat.setTranslation(scalePivotRaw.negate()); + transformMatrix.multLocal(mat); + + localTransform = new Transform(transformMatrix.toTranslationVector(), transformMatrix.toRotationQuat(), transformMatrix.toScaleVector()); + + node.setLocalTransform(localTransform); + } + + @Override + public void linkToZero() { + scene.sceneNode.attachChild(node); + } + + public void setSkeleton(Skeleton skeleton) { + if(bone != null) + boneIndex = skeleton.getBoneIndex(bone); + } + + public void buildBindPoseBoneTransform() { + if(bone != null) { + Matrix4f t = bindTransform; + if(t != null) { + Matrix4f parentMatrix = parentFbxNode != null ? parentFbxNode.bindTransform : Matrix4f.IDENTITY; + if(parentMatrix == null) + parentMatrix = node.getLocalToWorldMatrix(null); + t = parentMatrix.invert().multLocal(t); + bone.setBindTransforms(t.toTranslationVector(), t.toRotationQuat(), t.toScaleVector()); + } else { + bone.setBindTransforms(node.getLocalTranslation(), node.getLocalRotation(), node.getLocalScale()); + } + } + } + + @Override + public void link(FbxObject child, String propertyName) { + if(child instanceof FbxAnimNode) { + FbxAnimNode anim = (FbxAnimNode) child; + switch(propertyName) { + case "Lcl Translation": + animTranslations.put(anim.layerId, anim); + lastAnimTranslation = anim; + break; + case "Lcl Rotation": + animRotations.put(anim.layerId, anim); + lastAnimRotation = anim; + break; + case "Lcl Scaling": + animScales.put(anim.layerId, anim); + lastAnimScale = anim; + break; + } + } + } + + public FbxAnimNode animTranslation(long layerId) { + if(layerId == 0) + return lastAnimTranslation; + return animTranslations.get(layerId); + } + + public FbxAnimNode animRotation(long layerId) { + if(layerId == 0) + return lastAnimRotation; + return animRotations.get(layerId); + } + + public FbxAnimNode animScale(long layerId) { + if(layerId == 0) + return lastAnimScale; + return animScales.get(layerId); + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxMaterial) { + FbxMaterial m = (FbxMaterial) otherObject; + Material mat = m.material; + if(cullMode != FaceCullMode.Back) + mat.getAdditionalRenderState().setFaceCullMode(cullMode); + for(Geometry g : mesh.geometries) { + if(g.getUserData("FBXMaterial") != null) { + if((Integer) g.getUserData("FBXMaterial") == mesh.lastMaterialId) + g.setMaterial(mat); + } else { + g.setMaterial(mat); + } + } + mesh.lastMaterialId++; + } else if(otherObject instanceof FbxNode) { + FbxNode n = (FbxNode) otherObject; + node.attachChild(n.node); + n.parentFbxNode = this; + if(isLimb() && n.isLimb()) { + if(bone == null) + bone = new Bone(name); + if(n.bone == null) + n.bone = new Bone(n.name); + bone.addChild(n.bone); + } + } else if(otherObject instanceof FbxMesh) { + FbxMesh m = (FbxMesh) otherObject; + m.setParent(node); + m.parent = this; + mesh = m; + } + } + + public boolean isLimb() { + return type.equals("LimbNode"); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java index 57611f0721..4f111545e1 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxObject.java @@ -5,36 +5,36 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxObject { - - protected final SceneLoader scene; - public final FbxElement element; - public final long id; - public final String name; - public final String type; - - public FbxObject(SceneLoader scene, FbxElement element) { - this.scene = scene; - this.element = element; - this.id = (Long) element.properties.get(0); - String name = (String) element.properties.get(1); - this.name = name.substring(0, name.indexOf(0)); - this.type = (String) element.properties.get(2); - } - - public void link(FbxObject child) { - } - - public void link(FbxObject child, String propertyName) { - } - - // Parent is 0 id - public void linkToZero() { - } - - protected static void readVectorFromProp(Vector3f store, FbxElement propElement) { - float x = ((Double) propElement.properties.get(4)).floatValue(); - float y = ((Double) propElement.properties.get(5)).floatValue(); - float z = ((Double) propElement.properties.get(6)).floatValue(); - store.set(x, y, z); - } + + protected final SceneLoader scene; + public final FbxElement element; + public final long id; + public final String name; + public final String type; + + public FbxObject(SceneLoader scene, FbxElement element) { + this.scene = scene; + this.element = element; + this.id = (Long) element.properties.get(0); + String name = (String) element.properties.get(1); + this.name = name.substring(0, name.indexOf(0)); + this.type = (String) element.properties.get(2); + } + + public void link(FbxObject child) { + } + + public void link(FbxObject child, String propertyName) { + } + + // Parent is 0 id + public void linkToZero() { + } + + protected static void readVectorFromProp(Vector3f store, FbxElement propElement) { + float x = ((Double) propElement.properties.get(4)).floatValue(); + float y = ((Double) propElement.properties.get(5)).floatValue(); + float z = ((Double) propElement.properties.get(6)).floatValue(); + store.set(x, y, z); + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java index 8968600770..8cb1a2e8a8 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxSkin.java @@ -15,136 +15,136 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxSkin extends FbxObject { - - public String skinningType; - public List toSkin = new ArrayList<>(); - public List bones = new ArrayList<>(); - - public FbxSkin(SceneLoader scene, FbxElement element) { - super(scene, element); - for(FbxElement e : element.children) { - switch(e.id) { - case "SkinningType": - skinningType = (String) e.properties.get(0); - break; - } - } - } - - @Override - public void link(FbxObject otherObject) { - if(otherObject instanceof FbxCluster) { - FbxCluster cluster = ((FbxCluster) otherObject); - cluster.skin = this; - } - } - - public void generateSkinning() { - for(FbxMesh fbxMesh : toSkin) { - if(fbxMesh.geometries == null) - continue; - Mesh firstMesh = fbxMesh.geometries.get(0).getMesh(); - int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh); - for(int i = 0; i < fbxMesh.geometries.size(); ++i) { - Mesh mesh = fbxMesh.geometries.get(i).getMesh(); - if(mesh != firstMesh) { - mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight)); - mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex)); - mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight)); - mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex)); - } - mesh.setMaxNumWeights(maxWeightsPerVert); - mesh.generateBindPose(true); - } - } - } - - private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) { - // Create bone buffers - FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4); - ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.vCount * 4); - mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData); - mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData); - mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly); - mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly); - VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight); - VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex); - indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU - weightsHW.setUsage(Usage.CpuOnly); - mesh.setBuffer(weightsHW); - mesh.setBuffer(indicesHW); - int bonesLimitExceeded = 0; - // Accumulate skin bones influence into mesh buffers - for(FbxNode limb : bones) { - FbxCluster cluster = limb.skinToCluster.get(id); - if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length) - continue; - if(limb.boneIndex > 255) - throw new AssetLoadException("Bone index can't be packed into byte"); - for(int i = 0; i < cluster.indexes.length; ++i) { - int vertexIndex = cluster.indexes[i]; - if(vertexIndex >= fbxMesh.reverseVertexMap.size()) - throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size()); - List dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex); - for(int j = 0; j < dstVertices.size(); ++j) { - int v = dstVertices.get(j); - // Append bone index and weight to vertex - int offset; - int smalestOffset = 0; - float w = 0; - float smalestW = Float.MAX_VALUE; - for(offset = v * 4; offset < v * 4 + 4; ++offset) { - w = boneWeightData.get(offset); - if(w == 0) - break; - if(w < smalestW) { - smalestW = w; - smalestOffset = offset; - } - } - if(w == 0) { - boneWeightData.put(offset, (float) cluster.weights[i]); - boneIndicesData.put(offset, (byte) limb.boneIndex); - } else { - if((float) cluster.weights[i] > smalestW) { // If current weight more than smallest, discard smallest - boneWeightData.put(smalestOffset, (float) cluster.weights[i]); - boneIndicesData.put(smalestOffset, (byte) limb.boneIndex); - } - bonesLimitExceeded++; - } - } - } - } - if(bonesLimitExceeded > 0) - scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded"); - // Postprocess bones weights - int maxWeightsPerVert = 0; - boneWeightData.rewind(); - for(int v = 0; v < fbxMesh.vCount; v++) { - float w0 = boneWeightData.get(); - float w1 = boneWeightData.get(); - float w2 = boneWeightData.get(); - float w3 = boneWeightData.get(); - if(w3 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); - } else if(w2 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); - } else if(w1 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); - } else if(w0 != 0) { - maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); - } - float sum = w0 + w1 + w2 + w3; - if(sum != 1f) { - // normalize weights - float mult = (sum != 0) ? (1f / sum) : 0; - boneWeightData.position(v * 4); - boneWeightData.put(w0 * mult); - boneWeightData.put(w1 * mult); - boneWeightData.put(w2 * mult); - boneWeightData.put(w3 * mult); - } - } - return maxWeightsPerVert; - } + + public String skinningType; + public List toSkin = new ArrayList<>(); + public List bones = new ArrayList<>(); + + public FbxSkin(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "SkinningType": + skinningType = (String) e.properties.get(0); + break; + } + } + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxCluster) { + FbxCluster cluster = ((FbxCluster) otherObject); + cluster.skin = this; + } + } + + public void generateSkinning() { + for(FbxMesh fbxMesh : toSkin) { + if(fbxMesh.geometries == null) + continue; + Mesh firstMesh = fbxMesh.geometries.get(0).getMesh(); + int maxWeightsPerVert = generateBoneData(firstMesh, fbxMesh); + for(int i = 0; i < fbxMesh.geometries.size(); ++i) { + Mesh mesh = fbxMesh.geometries.get(i).getMesh(); + if(mesh != firstMesh) { + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneWeight)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.BoneIndex)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneWeight)); + mesh.setBuffer(firstMesh.getBuffer(VertexBuffer.Type.HWBoneIndex)); + } + mesh.setMaxNumWeights(maxWeightsPerVert); + mesh.generateBindPose(true); + } + } + } + + private int generateBoneData(Mesh mesh, FbxMesh fbxMesh) { + // Create bone buffers + FloatBuffer boneWeightData = BufferUtils.createFloatBuffer(fbxMesh.vCount * 4); + ByteBuffer boneIndicesData = BufferUtils.createByteBuffer(fbxMesh.vCount * 4); + mesh.setBuffer(VertexBuffer.Type.BoneWeight, 4, boneWeightData); + mesh.setBuffer(VertexBuffer.Type.BoneIndex, 4, boneIndicesData); + mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(Usage.CpuOnly); + mesh.getBuffer(VertexBuffer.Type.BoneIndex).setUsage(Usage.CpuOnly); + VertexBuffer weightsHW = new VertexBuffer(Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(Type.HWBoneIndex); + indicesHW.setUsage(Usage.CpuOnly); // Setting usage to CpuOnly so that the buffer is not send empty to the GPU + weightsHW.setUsage(Usage.CpuOnly); + mesh.setBuffer(weightsHW); + mesh.setBuffer(indicesHW); + int bonesLimitExceeded = 0; + // Accumulate skin bones influence into mesh buffers + for(FbxNode limb : bones) { + FbxCluster cluster = limb.skinToCluster.get(id); + if(cluster == null || cluster.indexes == null || cluster.weights == null || cluster.indexes.length != cluster.weights.length) + continue; + if(limb.boneIndex > 255) + throw new AssetLoadException("Bone index can't be packed into byte"); + for(int i = 0; i < cluster.indexes.length; ++i) { + int vertexIndex = cluster.indexes[i]; + if(vertexIndex >= fbxMesh.reverseVertexMap.size()) + throw new AssetLoadException("Invalid skinning vertex index. Unexpected index lookup " + vertexIndex + " from " + fbxMesh.reverseVertexMap.size()); + List dstVertices = fbxMesh.reverseVertexMap.get(vertexIndex); + for(int j = 0; j < dstVertices.size(); ++j) { + int v = dstVertices.get(j); + // Append bone index and weight to vertex + int offset; + int smallestOffset = 0; + float w = 0; + float smallestW = Float.MAX_VALUE; + for(offset = v * 4; offset < v * 4 + 4; ++offset) { + w = boneWeightData.get(offset); + if(w == 0) + break; + if(w < smallestW) { + smallestW = w; + smallestOffset = offset; + } + } + if(w == 0) { + boneWeightData.put(offset, (float) cluster.weights[i]); + boneIndicesData.put(offset, (byte) limb.boneIndex); + } else { + if((float) cluster.weights[i] > smallestW) { // If current weight more than smallest, discard smallest + boneWeightData.put(smallestOffset, (float) cluster.weights[i]); + boneIndicesData.put(smallestOffset, (byte) limb.boneIndex); + } + bonesLimitExceeded++; + } + } + } + } + if(bonesLimitExceeded > 0) + scene.warning("Skinning support max 4 bone per vertex. Exceeding data of " + bonesLimitExceeded + " weights in mesh bones will be discarded"); + // Postprocess bones weights + int maxWeightsPerVert = 0; + boneWeightData.rewind(); + for(int v = 0; v < fbxMesh.vCount; v++) { + float w0 = boneWeightData.get(); + float w1 = boneWeightData.get(); + float w2 = boneWeightData.get(); + float w3 = boneWeightData.get(); + if(w3 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 4); + } else if(w2 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 3); + } else if(w1 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 2); + } else if(w0 != 0) { + maxWeightsPerVert = Math.max(maxWeightsPerVert, 1); + } + float sum = w0 + w1 + w2 + w3; + if(sum != 1f) { + // normalize weights + float mult = (sum != 0) ? (1f / sum) : 0; + boneWeightData.position(v * 4); + boneWeightData.put(w0 * mult); + boneWeightData.put(w1 * mult); + boneWeightData.put(w2 * mult); + boneWeightData.put(w3 * mult); + } + } + return maxWeightsPerVert; + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java index 6093d7228f..1ccc01141d 100644 --- a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/objects/FbxTexture.java @@ -7,36 +7,36 @@ import com.jme3.scene.plugins.fbx.file.FbxElement; public class FbxTexture extends FbxObject { - - String bindType; - String filename; - - public Texture texture; - - public FbxTexture(SceneLoader scene, FbxElement element) { - super(scene, element); - for(FbxElement e : element.children) { - switch(e.id) { - case "Type": - bindType = (String) e.properties.get(0); - break; - case "FileName": - filename = (String) e.properties.get(0); - break; - } - } - texture = new Texture2D(); - texture.setName(name); - texture.setWrap(WrapMode.Repeat); // Default FBX wrapping. TODO: Investigate where this is stored (probably, in material) - } - @Override - public void link(FbxObject otherObject) { - if(otherObject instanceof FbxImage) { - FbxImage img = (FbxImage) otherObject; - if(img.image == null) - return; - texture.setImage(img.image); - } - } + String bindType; + String filename; + + public Texture texture; + + public FbxTexture(SceneLoader scene, FbxElement element) { + super(scene, element); + for(FbxElement e : element.children) { + switch(e.id) { + case "Type": + bindType = (String) e.properties.get(0); + break; + case "FileName": + filename = (String) e.properties.get(0); + break; + } + } + texture = new Texture2D(); + texture.setName(name); + texture.setWrap(WrapMode.Repeat); // Default FBX wrapping. TODO: Investigate where this is stored (probably, in material) + } + + @Override + public void link(FbxObject otherObject) { + if(otherObject instanceof FbxImage) { + FbxImage img = (FbxImage) otherObject; + if(img.image == null) + return; + texture.setImage(img.image); + } + } } diff --git a/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/package-info.java b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/package-info.java new file mode 100644 index 0000000000..2537f2d67b --- /dev/null +++ b/jme3-plugins/src/fbx/java/com/jme3/scene/plugins/fbx/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import models in the FBX file format + */ +package com.jme3.scene.plugins.fbx; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java index 505ca29c31..520e7ce0ee 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinDataKey.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2020 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.jme3.asset.AssetKey; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinLoader.java index 17826d59c4..de966f4294 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/BinLoader.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2020 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.jme3.asset.*; 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 ab7a3bc745..20f2c5e141 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/CustomContentManager.java @@ -1,12 +1,44 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; import com.jme3.asset.AssetLoadException; - import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -14,16 +46,54 @@ * Created by Nehon on 20/08/2017. */ public class CustomContentManager { + static volatile Class defaultExtraLoaderClass = UserDataLoader.class; + private ExtrasLoader defaultExtraLoaderInstance; + private final static Logger logger = Logger.getLogger(CustomContentManager.class.getName()); private GltfModelKey key; private GltfLoader gltfLoader; - private static Map defaultExtensionLoaders = new HashMap<>(); - + + static final Map> defaultExtensionLoaders = new ConcurrentHashMap<>(); static { - defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", new PBRSpecGlossExtensionLoader()); + defaultExtensionLoaders.put("KHR_materials_pbrSpecularGlossiness", PBRSpecGlossExtensionLoader.class); + defaultExtensionLoaders.put("KHR_lights_punctual", LightsPunctualExtensionLoader.class); + defaultExtensionLoaders.put("KHR_materials_unlit", UnlitExtensionLoader.class); + defaultExtensionLoaders.put("KHR_texture_transform", TextureTransformExtensionLoader.class); + defaultExtensionLoaders.put("KHR_materials_emissive_strength", PBREmissiveStrengthExtensionLoader.class); + + } + + private final Map loadedExtensionLoaders = new HashMap<>(); + + public CustomContentManager() { + } + + /** + * Returns the default extras loader. + * @return the default extras loader. + */ + public ExtrasLoader getDefaultExtrasLoader() { + if (defaultExtraLoaderClass == null) { + defaultExtraLoaderInstance = null; // do not hold reference + return null; + } + + if (defaultExtraLoaderInstance != null + && defaultExtraLoaderInstance.getClass() != defaultExtraLoaderClass) { + defaultExtraLoaderInstance = null; // reset instance if class changed + } + + try { + defaultExtraLoaderInstance = defaultExtraLoaderClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + logger.log(Level.WARNING, "Could not instantiate default extras loader", e); + defaultExtraLoaderInstance = null; + } + + return defaultExtraLoaderInstance; } void init(GltfLoader gltfLoader) { @@ -63,6 +133,7 @@ public T readExtensionAndExtras(String name, JsonElement el, T input) throws return output; } + @SuppressWarnings("unchecked") private T readExtension(String name, JsonElement el, T input) throws AssetLoadException, IOException { JsonElement extensions = el.getAsJsonObject().getAsJsonObject("extensions"); if (extensions == null) { @@ -71,12 +142,29 @@ private T readExtension(String name, JsonElement el, T input) throws AssetLo for (Map.Entry ext : extensions.getAsJsonObject().entrySet()) { ExtensionLoader loader = null; + if (key != null) { loader = key.getExtensionLoader(ext.getKey()); } + if (loader == null) { - loader = defaultExtensionLoaders.get(ext.getKey()); + loader = loadedExtensionLoaders.get(ext.getKey()); + if (loader == null) { + try { + Class clz = defaultExtensionLoaders.get(ext.getKey()); + if (clz != null) { + loader = clz.getDeclaredConstructor().newInstance(); + } + } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { + logger.log(Level.WARNING, "Could not instantiate loader", e); + } + + if (loader != null) { + loadedExtensionLoaders.put(ext.getKey(), loader); + } + } } + if (loader == null) { logger.log(Level.WARNING, "Could not find loader for extension " + ext.getKey()); @@ -93,15 +181,22 @@ private T readExtension(String name, JsonElement el, T input) throws AssetLo return input; } + @SuppressWarnings("unchecked") private T readExtras(String name, JsonElement el, T input) throws AssetLoadException { - if (key == null) { - return input; + ExtrasLoader loader = null; + + if (key != null) { // try to get the extras loader from the model key if available + loader = key.getExtrasLoader(); + } + + if (loader == null) { // if no loader was found, use the default extras loader + loader = getDefaultExtrasLoader(); } - ExtrasLoader loader; - loader = key.getExtrasLoader(); - if (loader == null) { + + if (loader == null) { // if default loader is not set or failed to instantiate, skip extras return input; } + JsonElement extras = el.getAsJsonObject().getAsJsonObject("extras"); if (extras == null) { return input; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java index 3f32be4bd7..1778cece97 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtensionLoader.java @@ -1,25 +1,56 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; - +import com.jme3.plugins.json.JsonElement; import java.io.IOException; /** - * Base Interface for extension loading implementation. + * Interface to handle a glTF extension. * * Created by Nehon on 20/08/2017. */ public interface ExtensionLoader { /** - * Implement this methods to handle a gltf extension reading + * Handles a glTF extension. * - * @param loader the GltfLoader with all the data structure. + * @param loader the GltfLoader with all the data structures * @param parentName the name of the element being read * @param parent the element being read * @param extension the content of the extension found in the element being read - * @param input an object containing already loaded data from the element, this is most probably a JME object + * @param input an object containing already loaded data from the element, probably a JME object * @return An object of the same type as input, containing the data from the input object and the eventual additional data read from the extension + * @throws IOException for various error conditions */ Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java index 1f0118b577..1267cddf05 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/ExtrasLoader.java @@ -1,19 +1,50 @@ +/* + * Copyright (c) 2009-2020 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.JsonElement; +import com.jme3.plugins.json.JsonElement; /** - * Base Interface for extra loading implementation. + * Interface to handle a glTF extra. * Created by Nehon on 30/08/2017. */ public interface ExtrasLoader { /** - * Implement this methods to handle gltf extra reading - * Note that this method will be invoked every time an "extras" element will be found in the gltf file. - * You can check the parentName to know where the "extras" element has been found. + * Handles a glTF extra. + * This method will be invoked every time an "extras" element is found in the glTF file. + * The parentName indicates where the "extras" element was found. * - * @param loader the GltfLoader with all the data structure. + * @param loader the GltfLoader with all the data structures * @param parentName the name of the element being read * @param parent the element being read * @param extras the content of the extras found in the element being read diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java index 662e992cac..04456b0361 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GlbLoader.java @@ -1,27 +1,71 @@ +/* + * 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.scene.plugins.gltf; import com.jme3.asset.AssetInfo; +import com.jme3.util.BufferUtils; import com.jme3.util.LittleEndien; import java.io.*; +import java.nio.ByteBuffer; import java.util.ArrayList; +import java.util.logging.Level; +import java.util.logging.Logger; /** * Created by Nehon on 12/09/2017. */ public class GlbLoader extends GltfLoader { - private static final int GLTF_MAGIC = 0x46546C67; private static final int JSON_TYPE = 0x4E4F534A; - private static final int BIN_TYPE = 0x004E4942; - private ArrayList data = new ArrayList<>(); + /** + * log diagnostic messages from this class + */ + private static final Logger logger = Logger.getLogger(GlbLoader.class.getName()); + + private ArrayList data = new ArrayList<>(); @Override public Object load(AssetInfo assetInfo) throws IOException { data.clear(); - LittleEndien stream = new LittleEndien(new DataInputStream(assetInfo.openStream())); - int magic = stream.readInt(); + LittleEndien stream = new LittleEndien(new BufferedInputStream(assetInfo.openStream())); + /* magic */ stream.readInt(); + int version = stream.readInt(); + if (version != 2) { + logger.log(Level.SEVERE, "GlbLoader doesn''t support file version {0}.", version); + throw new IOException("GLB file version = " + version); + } + int length = stream.readInt(); byte[] json = null; @@ -34,21 +78,24 @@ public Object load(AssetInfo assetInfo) throws IOException { int chunkType = stream.readInt(); if (chunkType == JSON_TYPE) { json = new byte[chunkLength]; - stream.read(json); + GltfUtils.readToByteArray(stream, json, chunkLength); } else { - byte[] bin = new byte[chunkLength]; - stream.read(bin); - data.add(bin); + ByteBuffer buff = BufferUtils.createByteBuffer(chunkLength); + GltfUtils.readToByteBuffer(stream, buff, chunkLength); + data.add(buff); } //8 is the byte size of the 2 ints chunkLength and chunkType. length -= chunkLength + 8; } + if (json == null) { + throw new IOException("No JSON chunk found."); + } return loadFromStream(assetInfo, new ByteArrayInputStream(json)); } @Override - protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException { + protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException { return data.get(bufferIndex); } 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 cc9b54d454..0c8448e226 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,7 +1,40 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -import com.google.gson.*; -import com.google.gson.stream.JsonReader; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; +import com.jme3.plugins.json.JsonElement; import com.jme3.anim.*; import com.jme3.asset.*; import com.jme3.material.Material; @@ -12,19 +45,22 @@ import com.jme3.scene.*; import com.jme3.scene.control.CameraControl; import com.jme3.scene.mesh.MorphTarget; +import static com.jme3.scene.plugins.gltf.GltfUtils.*; import com.jme3.texture.Texture; import com.jme3.texture.Texture2D; +import com.jme3.util.BufferInputStream; +import com.jme3.util.BufferUtils; import com.jme3.util.IntMap; import com.jme3.util.mikktspace.MikktspaceTangentGenerator; import java.io.*; +import java.net.URLDecoder; import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; -import static com.jme3.scene.plugins.gltf.GltfUtils.*; - /** * GLTF 2.0 loader * Created by Nehon on 07/08/2017. @@ -33,8 +69,8 @@ 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<>(); + // Data cache for already parsed JME objects + private final Map dataCache = new HashMap<>(); private JsonArray scenes; private JsonArray nodes; private JsonArray meshes; @@ -54,22 +90,18 @@ 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 static 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; - private Quaternion tmpQuat = new Quaternion(); - private Transform tmpTransforms = new Transform(); - private Transform tmpTransforms2 = new Transform(); - private Matrix4f tmpMat = new Matrix4f(); Map> skinnedSpatials = new HashMap<>(); IntMap skinBuffers = new IntMap<>(); - static { + public GltfLoader() { defaultMaterialAdapters.put("pbrMetallicRoughness", new PBRMetalRoughMaterialAdapter()); } @@ -78,10 +110,8 @@ public Object load(AssetInfo assetInfo) throws IOException { return loadFromStream(assetInfo, assetInfo.openStream()); } - protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws IOException { try { - dataCache.clear(); info = assetInfo; skinnedSpatials.clear(); rootNode = new Node(); @@ -93,14 +123,14 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws defaultMat.setFloat("Roughness", 1f); } - docRoot = new JsonParser().parse(new JsonReader(new InputStreamReader(stream))).getAsJsonObject(); + docRoot = parse(stream); JsonObject asset = docRoot.getAsJsonObject().get("asset").getAsJsonObject(); - String generator = getAsString(asset, "generator"); + getAsString(asset, "generator"); String version = getAsString(asset, "version"); String minVersion = getAsString(asset, "minVersion"); if (!isSupported(version, minVersion)) { - logger.log(Level.SEVERE, "Gltf Loader doesn't support this gltf version: " + version + (minVersion != null ? ("/" + minVersion) : "")); + logger.log(Level.SEVERE, "Gltf Loader doesn''t support this gltf version: {0}{1}", new Object[]{version, minVersion != null ? ("/" + minVersion) : ""}); } scenes = docRoot.getAsJsonArray("scenes"); @@ -128,7 +158,7 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws rootNode = customContentManager.readExtensionAndExtras("root", docRoot, rootNode); - //Loading animations + // Loading animations if (animations != null) { for (int i = 0; i < animations.size(); i++) { readAnimation(i); @@ -137,11 +167,14 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws setupControls(); - //only one scene let's not return the root. + // only one scene, let's not return the root. if (rootNode.getChildren().size() == 1) { - rootNode = (Node) rootNode.getChild(0); + Node child = (Node) rootNode.getChild(0); + // Migrate lights that were in the parent to the child. + rootNode.getLocalLightList().forEach(child::addLight); + rootNode = child; } - //no name for the scene... let's set the file name. + // no name for the scene... let's set the file name. if (rootNode.getName() == null) { rootNode.setName(assetInfo.getKey().getName()); } @@ -150,6 +183,27 @@ protected Object loadFromStream(AssetInfo assetInfo, InputStream stream) throws throw new AssetLoadException("An error occurred loading " + assetInfo.getKey().getName(), e); } finally { stream.close(); + dataCache.clear(); + skinBuffers.clear(); + skinnedSpatials.clear(); + info = null; + docRoot = null; + rootNode = null; + defaultMat = null; + accessors = null; + bufferViews = null; + buffers = null; + scenes = null; + nodes = null; + meshes = null; + materials = null; + textures = null; + images = null; + samplers = null; + animations = null; + skins = null; + cameras = null; + useNormalsFlag = false; } } @@ -165,27 +219,30 @@ private boolean isSupported(String version, String minVersion) { public void readScenes(JsonPrimitive defaultScene, Node rootNode) throws IOException { if (scenes == null) { - //no scene... lets handle this later... + // no scene... let's handle this later... throw new AssetLoadException("Gltf files with no scene is not yet supported"); } for (JsonElement scene : scenes) { Node sceneNode = new Node(); - //specs says that only the default scene should be rendered, - // if there are several scenes, they are attached to the rootScene, but they are culled + // Specs say that only the default scene should be rendered. + // If there are several scenes, they are attached to the rootScene, but they are culled. sceneNode.setCullHint(Spatial.CullHint.Always); sceneNode.setName(getAsString(scene.getAsJsonObject(), "name")); + // If the scene is empty, ignore it. + if (!scene.getAsJsonObject().has("nodes")) { + continue; + } JsonArray sceneNodes = scene.getAsJsonObject().getAsJsonArray("nodes"); sceneNode = customContentManager.readExtensionAndExtras("scene", scene, sceneNode); rootNode.attachChild(sceneNode); for (JsonElement node : sceneNodes) { readChild(sceneNode, node); } - } - //Setting the default scene cul hint to inherit. + // Setting the default scene cul hint to inherit. int activeChild = 0; if (defaultScene != null) { activeChild = defaultScene.getAsInt(); @@ -197,10 +254,10 @@ public Object readNode(int nodeIndex) throws IOException { Object obj = fetchFromCache("nodes", nodeIndex, Object.class); if (obj != null) { if (obj instanceof JointWrapper) { - //the node can be a previously loaded bone let's return it + // the node can be a previously loaded bone, let's return it return obj; } else { - //If a spatial is referenced several times, it may be attached to different parents, + // If a spatial is referenced several times, it may be attached to different parents, // and it's not possible in JME, so we have to clone it. return ((Spatial) obj).clone(); } @@ -212,24 +269,23 @@ public Object readNode(int nodeIndex) throws IOException { if (meshIndex != null) { assertNotNull(meshes, "Can't find any mesh data, yet a node references a mesh"); - //there is a mesh in this node, however gltf can split meshes in primitives (some kind of sub meshes), - //We don't have this in JME so we have to make one mesh and one Geometry for each primitive. + // there is a mesh in this node, however gltf + // can split meshes in primitives (some kind of sub meshes), + // We don't have this in JME, so we have to make one Mesh and one Geometry for each primitive. Geometry[] primitives = readMeshPrimitives(meshIndex); - if (primitives.length == 1 && children == null) { - //only one geometry, let's not wrap it in another node unless the node has children. - spatial = primitives[0]; - } else { - //several geometries, let's make a parent Node and attach them to it - Node node = new Node(); - for (Geometry primitive : primitives) { - node.attachChild(primitive); - } - spatial = node; + + Node node = new Node(); + for (Geometry primitive : primitives) { + node.attachChild(primitive); } - spatial.setName(readMeshName(meshIndex)); + + node.setName(readMeshName(meshIndex)); + + spatial = new Node(); + ((Node)spatial).attachChild(node); } else { - //no mesh, we have a node. Can be a camera node or a regular node. + // no mesh, we have a node. Can be a camera node or a regular node. Integer camIndex = getAsInteger(nodeData, "camera"); if (camIndex != null) { Camera cam = fetchFromCache("cameras", camIndex, Camera.class); @@ -277,7 +333,7 @@ private void readChild(Spatial parent, JsonElement nodeIndex) throws IOException } } } else if (loaded instanceof JointWrapper) { - //parent is the Armature Node, we have to apply its transforms to the root bone's animation data + // parent is the Armature Node, we have to apply its transforms to the root bone's animation data JointWrapper bw = (JointWrapper) loaded; bw.isRoot = true; SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class); @@ -286,24 +342,23 @@ private void readChild(Spatial parent, JsonElement nodeIndex) throws IOException } skinData.parent = parent; } - } public Transform readTransforms(JsonObject nodeData) { Transform transform = new Transform(); JsonArray matrix = nodeData.getAsJsonArray("matrix"); if (matrix != null) { - //transforms are given as a mat4 + // transforms are given as a mat4 float[] tmpArray = new float[16]; for (int i = 0; i < tmpArray.length; i++) { tmpArray[i] = matrix.get(i).getAsFloat(); } - //creates a row major matrix from color major data + // creates a row-major matrix from column-major data Matrix4f mat = new Matrix4f(tmpArray); transform.fromTransformMatrix(mat); return transform; } - //no matrix transforms: no transforms or transforms givens as translation/rotation/scale + // no matrix transforms: no transforms or transforms given as translation/rotation/scale JsonArray translation = nodeData.getAsJsonArray("translation"); if (translation != null) { transform.setTranslation( @@ -332,138 +387,147 @@ public Transform readTransforms(JsonObject nodeData) { public Geometry[] readMeshPrimitives(int meshIndex) throws IOException { Geometry[] geomArray = (Geometry[]) fetchFromCache("meshes", meshIndex, Object.class); - if (geomArray != null) { - //cloning the geoms. - Geometry[] geoms = new Geometry[geomArray.length]; - for (int i = 0; i < geoms.length; i++) { - geoms[i] = geomArray[i].clone(false); - } - return geoms; - } - JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); - JsonArray primitives = meshData.getAsJsonArray("primitives"); - assertNotNull(primitives, "Can't find any primitives in mesh " + meshIndex); - - String name = getAsString(meshData, "name"); - - geomArray = new Geometry[primitives.size()]; - int index = 0; - for (JsonElement primitive : primitives) { - JsonObject meshObject = primitive.getAsJsonObject(); - Mesh mesh = new Mesh(); - Integer mode = getAsInteger(meshObject, "mode"); - mesh.setMode(getMeshMode(mode)); - Integer indices = getAsInteger(meshObject, "indices"); - if (indices != null) { - mesh.setBuffer(readAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index))); - } - JsonObject attributes = meshObject.getAsJsonObject("attributes"); - assertNotNull(attributes, "No attributes defined for mesh " + mesh); - - skinBuffers.clear(); - - for (Map.Entry entry : attributes.entrySet()) { - //special case for joints and weights buffer. If there are more than 4 bones per vertex, there might be several of them - //we need to read them all and to keep only the 4 that have the most weight on the vertex. - String bufferType = entry.getKey(); - if (bufferType.startsWith("JOINTS")) { - SkinBuffers buffs = getSkinBuffers(bufferType); - SkinBuffers buffer = readAccessorData(entry.getValue().getAsInt(), new JointArrayPopulator()); - buffs.joints = buffer.joints; - buffs.componentSize = buffer.componentSize; - } else if (bufferType.startsWith("WEIGHTS")) { - SkinBuffers buffs = getSkinBuffers(bufferType); - buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator()); - } else { - VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(getVertexBufferType(bufferType))); - if (vb != null) { - mesh.setBuffer(vb); + if (geomArray == null) { + JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); + JsonArray primitives = meshData.getAsJsonArray("primitives"); + assertNotNull(primitives, "Can't find any primitives in mesh " + meshIndex); + String name = getAsString(meshData, "name"); + + geomArray = new Geometry[primitives.size()]; + int index = 0; + 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"); + if (indices != null) { + mesh.setBuffer(readAccessorData(indices, new VertexBufferPopulator(VertexBuffer.Type.Index))); + } + JsonObject attributes = meshObject.getAsJsonObject("attributes"); + assertNotNull(attributes, "No attributes defined for mesh " + mesh); + + boolean useVertexColors = false; + + skinBuffers.clear(); + + for (Map.Entry entry : attributes.entrySet()) { + // special case for joints and weights buffer. + // If there are more than 4 bones per vertex, there might be several of them + // we need to read them all and to keep only the 4 that have the most weight on the vertex. + String bufferType = entry.getKey(); + if (bufferType.startsWith("JOINTS")) { + SkinBuffers buffs = getSkinBuffers(bufferType); + SkinBuffers buffer + = readAccessorData(entry.getValue().getAsInt(), new JointArrayPopulator()); + buffs.joints = buffer.joints; + buffs.componentSize = buffer.componentSize; + } else if (bufferType.startsWith("WEIGHTS")) { + SkinBuffers buffs = getSkinBuffers(bufferType); + buffs.weights = readAccessorData(entry.getValue().getAsInt(), new FloatArrayPopulator()); + } else { + VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), + new VertexBufferPopulator(getVertexBufferType(bufferType))); + if (vb != null) { + mesh.setBuffer(vb); + } + } + // if the color buffer is used, we will need to enable vertex colors on the material + if (bufferType.startsWith("COLOR")) { + useVertexColors = true; } } - } - handleSkinningBuffers(mesh, skinBuffers); - - if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) { - //the mesh has some skinning let's create needed buffers for HW skinning - //creating empty buffers for HW skinning - //the buffers will be setup if ever used. - VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); - VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); - //setting usage to cpuOnly so that the buffer is not sent empty to the GPU - indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); - weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); - mesh.setBuffer(weightsHW); - mesh.setBuffer(indicesHW); - mesh.generateBindPose(); - } - - //Read morph target names - LinkedList targetNames = new LinkedList<>(); - if (meshData.has("extras") && meshData.getAsJsonObject("extras").has("targetNames")) { - JsonArray targetNamesJson = meshData.getAsJsonObject("extras").getAsJsonArray("targetNames"); - for (JsonElement target : targetNamesJson) { - targetNames.add(target.getAsString()); + handleSkinningBuffers(mesh, skinBuffers); + + if (mesh.getBuffer(VertexBuffer.Type.BoneIndex) != null) { + // the mesh has some skinning, let's create needed buffers for HW skinning + // creating empty buffers for HW skinning + // the buffers will be set up if ever used. + VertexBuffer weightsHW = new VertexBuffer(VertexBuffer.Type.HWBoneWeight); + VertexBuffer indicesHW = new VertexBuffer(VertexBuffer.Type.HWBoneIndex); + // setting usage to cpuOnly so that the buffer is not sent empty to the GPU + indicesHW.setUsage(VertexBuffer.Usage.CpuOnly); + weightsHW.setUsage(VertexBuffer.Usage.CpuOnly); + mesh.setBuffer(weightsHW); + mesh.setBuffer(indicesHW); + mesh.generateBindPose(); } - } - - //Read morph targets - JsonArray targets = meshObject.getAsJsonArray("targets"); - if(targets != null){ - for (JsonElement target : targets) { - MorphTarget morphTarget = new MorphTarget(); - if (targetNames.size() > 0) { - morphTarget.setName(targetNames.pop()); + + // Read morph target names + LinkedList targetNames = new LinkedList<>(); + if (meshData.has("extras") && meshData.getAsJsonObject("extras").has("targetNames")) { + JsonArray targetNamesJson = meshData.getAsJsonObject("extras").getAsJsonArray("targetNames"); + for (JsonElement target : targetNamesJson) { + targetNames.add(target.getAsString()); } - for (Map.Entry entry : target.getAsJsonObject().entrySet()) { - String bufferType = entry.getKey(); - VertexBuffer.Type type = getVertexBufferType(bufferType); - VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), new VertexBufferPopulator(type)); - if (vb != null) { - morphTarget.setBuffer(type, (FloatBuffer)vb.getData()); + } + + // Read morph targets + JsonArray targets = meshObject.getAsJsonArray("targets"); + if (targets != null) { + for (JsonElement target : targets) { + MorphTarget morphTarget = new MorphTarget(); + if (targetNames.size() > 0) { + morphTarget.setName(targetNames.pop()); } + for (Map.Entry entry : target.getAsJsonObject().entrySet()) { + String bufferType = entry.getKey(); + VertexBuffer.Type type = getVertexBufferType(bufferType); + VertexBuffer vb = readAccessorData(entry.getValue().getAsInt(), + new VertexBufferPopulator(type)); + if (vb != null) { + morphTarget.setBuffer(type, (FloatBuffer) vb.getData()); + } + } + mesh.addMorphTarget(morphTarget); } - mesh.addMorphTarget(morphTarget); } - } - - //Read mesh extras - mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); - Geometry geom = new Geometry(null, mesh); + // Read mesh extras + mesh = customContentManager.readExtensionAndExtras("primitive", meshObject, mesh); + Geometry geom = new Geometry(null, mesh); - Integer materialIndex = getAsInteger(meshObject, "material"); - if (materialIndex == null) { - geom.setMaterial(defaultMat); - } else { - useNormalsFlag = false; - geom.setMaterial(readMaterial(materialIndex)); - if (geom.getMaterial().getAdditionalRenderState().getBlendMode() == RenderState.BlendMode.Alpha) { - //Alpha blending is on on this material let's place the geom in the transparent bucket - geom.setQueueBucket(RenderQueue.Bucket.Transparent); + Integer materialIndex = getAsInteger(meshObject, "material"); + if (materialIndex == null) { + geom.setMaterial(defaultMat); + } else { + useNormalsFlag = false; + geom.setMaterial(readMaterial(materialIndex)); + if (geom.getMaterial().getAdditionalRenderState().getBlendMode() + == RenderState.BlendMode.Alpha) { + // Alpha blending is enabled for this material. Let's place the geom in the transparent bucket. + geom.setQueueBucket(RenderQueue.Bucket.Transparent); + } + if (useNormalsFlag && mesh.getBuffer(VertexBuffer.Type.Tangent) == null) { + // No tangent buffer, but there is a normal map, we have to generate them using MikktSpace + MikktspaceTangentGenerator.generate(geom); + } } - if (useNormalsFlag && mesh.getBuffer(VertexBuffer.Type.Tangent) == null) { - //No tangent buffer, but there is a normal map, we have to generate them using MiiktSpace - MikktspaceTangentGenerator.generate(geom); + + if (useVertexColors) { + geom.getMaterial().setBoolean("UseVertexColor", useVertexColors); } - } - if (name != null) { - geom.setName(name + (primitives.size() > 1 ? ("_" + index) : "")); + geom.setName(name + "_" + index); + + geom.updateModelBound(); + geomArray[index] = geom; + index++; } - geom.updateModelBound(); - geomArray[index] = geom; - index++; + geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray); + addToCache("meshes", meshIndex, geomArray, meshes.size()); } - - geomArray = customContentManager.readExtensionAndExtras("mesh", meshData, geomArray); - - addToCache("meshes", meshIndex, geomArray, meshes.size()); - return geomArray; + // cloning the geoms. + Geometry[] geoms = new Geometry[geomArray.length]; + for (int i = 0; i < geoms.length; i++) { + geoms[i] = geomArray[i].clone(false); + } + return geoms; } - private SkinBuffers getSkinBuffers(String bufferType) { int bufIndex = getIndex(bufferType); SkinBuffers buffs = skinBuffers.get(bufIndex); @@ -474,8 +538,7 @@ private SkinBuffers getSkinBuffers(String bufferType) { return buffs; } - public R readAccessorData(int accessorIndex, Populator populator) throws IOException { - + private R readAccessorData(int accessorIndex, Populator populator) throws IOException { assertNotNull(accessors, "No accessor attribute in the gltf file"); JsonObject accessor = accessors.get(accessorIndex).getAsJsonObject(); @@ -490,16 +553,16 @@ public R readAccessorData(int accessorIndex, Populator populator) throws boolean normalized = getAsBoolean(accessor, "normalized", false); - //TODO min / max...don't know what to do about them. - //TODO sparse + // TODO min / max...don't know what to do about them. + // TODO sparse R data = populator.populate(bufferViewIndex, componentType, type, count, byteOffset, normalized); data = customContentManager.readExtensionAndExtras("accessor", accessor, data); return data; } - public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Object store, int numComponents, VertexBuffer.Format format) throws IOException { - + public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Object store, + int numComponents, VertexBuffer.Format format) throws IOException { JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject(); Integer bufferIndex = getAsInteger(bufferView, "buffer"); assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); @@ -508,16 +571,20 @@ public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Obj assertNotNull(byteLength, "No byte length defined for bufferView " + bufferViewIndex); int byteStride = getAsInteger(bufferView, "byteStride", 0); - //target defines ELEMENT_ARRAY_BUFFER or ARRAY_BUFFER, but we already know that since we know we load the indexbuffer or any other... - //not sure it's useful for us, but I guess it's useful when you map data directly to the GPU. - //int target = getAsInteger(bufferView, "target", 0); - - byte[] data = readData(bufferIndex); + // target defines ELEMENT_ARRAY_BUFFER or ARRAY_BUFFER, + // but we already know that since we know we load the index buffer or any other... + // Not sure it's useful for us, but I guess it's useful when you map data directly to the GPU. + // int target = getAsInteger(bufferView, "target", 0); + ByteBuffer data = readData(bufferIndex); data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data); + if(!(data instanceof ByteBuffer)){ + throw new IOException("Buffer data is not a NIO Buffer"); + } + if (store == null) { - store = new byte[byteLength]; + store = BufferUtils.createByteBuffer(byteLength); } if (count == -1) { @@ -529,15 +596,40 @@ public Object readBuffer(Integer bufferViewIndex, int byteOffset, int count, Obj return store; } - public byte[] readData(int bufferIndex) throws IOException { + public Buffer viewBuffer(Integer bufferViewIndex, int byteOffset, int count, + int numComponents, VertexBuffer.Format originalFormat, VertexBuffer.Format targetFormat) throws IOException { + JsonObject bufferView = bufferViews.get(bufferViewIndex).getAsJsonObject(); + Integer bufferIndex = getAsInteger(bufferView, "buffer"); + assertNotNull(bufferIndex, "No buffer defined for bufferView " + bufferViewIndex); + int bvByteOffset = getAsInteger(bufferView, "byteOffset", 0); + Integer byteLength = getAsInteger(bufferView, "byteLength"); + assertNotNull(byteLength, "No byte length defined for bufferView " + bufferViewIndex); + int byteStride = getAsInteger(bufferView, "byteStride", 0); + + ByteBuffer data = readData(bufferIndex); + data = customContentManager.readExtensionAndExtras("bufferView", bufferView, data); + + if(!(data instanceof ByteBuffer)){ + throw new IOException("Buffer data is not a NIO Buffer"); + } + + + if (count == -1) { + count = byteLength; + } + + return GltfUtils.getBufferView(data, byteOffset + bvByteOffset, count, byteStride, numComponents, originalFormat, targetFormat ); + + } + public ByteBuffer readData(int bufferIndex) throws IOException { assertNotNull(buffers, "No buffer defined"); JsonObject buffer = buffers.get(bufferIndex).getAsJsonObject(); String uri = getAsString(buffer, "uri"); Integer bufferLength = getAsInteger(buffer, "byteLength"); assertNotNull(bufferLength, "No byteLength defined for buffer " + bufferIndex); - byte[] data = (byte[]) fetchFromCache("buffers", bufferIndex, Object.class); + ByteBuffer data = (ByteBuffer) fetchFromCache("buffers", bufferIndex, Object.class); if (data != null) { return data; } @@ -547,30 +639,31 @@ public byte[] readData(int bufferIndex) throws IOException { addToCache("buffers", bufferIndex, data, buffers.size()); return data; - } - protected byte[] getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException { - byte[] data; + protected ByteBuffer getBytes(int bufferIndex, String uri, Integer bufferLength) throws IOException { + ByteBuffer data; if (uri != null) { if (uri.startsWith("data:")) { - //base 64 embed data - data = Base64.getDecoder().decode(uri.substring(uri.indexOf(",") + 1)); + // base 64 embed data + data = BufferUtils.createByteBuffer(Base64.getDecoder().decode(uri.substring(uri.indexOf(",") + 1))); } else { - //external file let's load it - if (!uri.endsWith(".bin")) { - throw new AssetLoadException("Cannot load " + uri + ", a .bin extension is required."); + // external file let's load it + String decoded = decodeUri(uri); + if (!decoded.endsWith(".bin")) { + throw new AssetLoadException( + "Cannot load " + decoded + ", a .bin extension is required."); } - BinDataKey key = new BinDataKey(info.getKey().getFolder() + uri); - InputStream input = (InputStream) info.getManager().loadAsset(key); - data = new byte[bufferLength]; - DataInputStream dataStream = new DataInputStream(input); - dataStream.readFully(data); - dataStream.close(); + BinDataKey key = new BinDataKey(info.getKey().getFolder() + decoded); + try(InputStream input = (InputStream) info.getManager().loadAsset(key)){ + data = BufferUtils.createByteBuffer(bufferLength); + GltfUtils.readToByteBuffer(input, data, bufferLength); + } + } } else { - //no URI this should not happen in a gltf file, only in glb files. + // no URI, this should not happen in a gltf file, only in glb files. throw new AssetLoadException("Buffer " + bufferIndex + " has no uri"); } return data; @@ -582,7 +675,6 @@ public Material readMaterial(int materialIndex) throws IOException { JsonObject matData = materials.get(materialIndex).getAsJsonObject(); JsonObject pbrMat = matData.getAsJsonObject("pbrMetallicRoughness"); - MaterialAdapter adapter = null; if (pbrMat != null) { @@ -596,18 +688,23 @@ public Material readMaterial(int materialIndex) throws IOException { adapter = customContentManager.readExtensionAndExtras("material", matData, adapter); if (adapter == null) { - logger.log(Level.WARNING, "Couldn't find any matching material definition for material " + materialIndex); + logger.log(Level.WARNING, + "Couldn't find any matching material definition for material " + materialIndex); adapter = defaultMaterialAdapters.get("pbrMetallicRoughness"); adapter.init(info.getManager()); setDefaultParams(adapter.getMaterial()); } + Integer metallicRoughnessIndex = null; if (pbrMat != null) { adapter.setParam("baseColorFactor", getAsColor(pbrMat, "baseColorFactor", ColorRGBA.White)); adapter.setParam("metallicFactor", getAsFloat(pbrMat, "metallicFactor", 1f)); adapter.setParam("roughnessFactor", getAsFloat(pbrMat, "roughnessFactor", 1f)); adapter.setParam("baseColorTexture", readTexture(pbrMat.getAsJsonObject("baseColorTexture"))); - adapter.setParam("metallicRoughnessTexture", readTexture(pbrMat.getAsJsonObject("metallicRoughnessTexture"))); + 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")); @@ -622,10 +719,27 @@ 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); + } + } + 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("occlusionTexture", readTexture(matData.getAsJsonObject("occlusionTexture"))); - adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); + adapter.setParam("emissiveTexture", readTexture(matData.getAsJsonObject("emissiveTexture"))); return adapter.getMaterial(); } @@ -635,9 +749,8 @@ public void readCameras() throws IOException { return; } for (int i = 0; i < cameras.size(); i++) { - - //Can't access resolution here... actually it's a shame we can't access settings from anywhere. - //users will have to call resize on the camera. + // Can't access resolution here... actually it's a shame we can't access settings from anywhere. + // users will have to call resize on the camera. Camera cam = new Camera(1, 1); JsonObject camObj = cameras.get(i).getAsJsonObject(); @@ -648,11 +761,11 @@ public void readCameras() throws IOException { float aspectRatio = getAsFloat(camData, "aspectRation", 1f); Float yfov = getAsFloat(camData, "yfov"); assertNotNull(yfov, "No yfov for perspective camera"); - Float znear = getAsFloat(camData, "znear"); - assertNotNull(znear, "No znear for perspective camere"); - Float zfar = getAsFloat(camData, "zfar", znear * 1000f); + Float zNear = getAsFloat(camData, "znear"); + assertNotNull(zNear, "No znear for perspective camera"); + Float zFar = getAsFloat(camData, "zfar", zNear * 1000f); - cam.setFrustumPerspective(yfov * FastMath.RAD_TO_DEG, aspectRatio, znear, zfar); + cam.setFrustumPerspective(yfov * FastMath.RAD_TO_DEG, aspectRatio, zNear, zFar); cam = customContentManager.readExtensionAndExtras("camera.perspective", camData, cam); } else { @@ -660,13 +773,13 @@ public void readCameras() throws IOException { assertNotNull(xmag, "No xmag for orthographic camera"); Float ymag = getAsFloat(camData, "ymag"); assertNotNull(ymag, "No ymag for orthographic camera"); - Float znear = getAsFloat(camData, "znear"); - assertNotNull(znear, "No znear for orthographic camere"); - Float zfar = getAsFloat(camData, "zfar", znear * 1000f); - assertNotNull(zfar, "No zfar for orthographic camera"); + Float zNear = getAsFloat(camData, "znear"); + assertNotNull(zNear, "No znear for orthographic camera"); + Float zFar = getAsFloat(camData, "zfar", zNear * 1000f); + assertNotNull(zFar, "No zfar for orthographic camera"); cam.setParallelProjection(true); - cam.setFrustum(znear, zfar, -xmag, xmag, ymag, -ymag); + cam.setFrustum(zNear, zFar, -xmag, xmag, ymag, -ymag); cam = customContentManager.readExtensionAndExtras("camera.orthographic", camData, cam); } @@ -677,7 +790,6 @@ public void readCameras() throws IOException { public Texture2D readTexture(JsonObject texture) throws IOException { return readTexture(texture, false); - } public Texture2D readTexture(JsonObject texture, boolean flip) throws IOException { @@ -688,11 +800,16 @@ public Texture2D readTexture(JsonObject texture, boolean flip) throws IOExceptio assertNotNull(textureIndex, "Texture has no index"); assertNotNull(textures, "There are no textures, yet one is referenced by a material"); + Texture2D texture2d = fetchFromCache("textures", textureIndex, Texture2D.class); + if (texture2d != null) { + return texture2d; + } + JsonObject textureData = textures.get(textureIndex).getAsJsonObject(); Integer sourceIndex = getAsInteger(textureData, "source"); Integer samplerIndex = getAsInteger(textureData, "sampler"); - Texture2D texture2d = readImage(sourceIndex, flip); + texture2d = readImage(sourceIndex, flip); if (samplerIndex != null) { texture2d = readSampler(samplerIndex, texture2d); @@ -702,6 +819,8 @@ public Texture2D readTexture(JsonObject texture, boolean flip) throws IOExceptio texture2d = customContentManager.readExtensionAndExtras("texture", texture, texture2d); + addToCache("textures", textureIndex, texture2d, textures.size()); + return texture2d; } @@ -718,22 +837,27 @@ public Texture2D readImage(int sourceIndex, boolean flip) throws IOException { if (uri == null) { assertNotNull(bufferView, "Image " + sourceIndex + " should either have an uri or a bufferView"); assertNotNull(mimeType, "Image " + sourceIndex + " should have a mimeType"); - byte[] data = (byte[]) readBuffer(bufferView, 0, -1, null, 1, VertexBuffer.Format.Byte); + ByteBuffer data = (ByteBuffer) viewBuffer(bufferView, 0, -1, 1, VertexBuffer.Format.Byte, VertexBuffer.Format.Byte); + String extension = mimeType.split("/")[1]; TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip); - result = (Texture2D) info.getManager().loadAssetFromStream(key, new ByteArrayInputStream(data)); - + try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){ + result = (Texture2D) info.getManager().loadAssetFromStream(key, bis); + } } else if (uri.startsWith("data:")) { - //base64 encoded image + // base64 encoded image String[] uriInfo = uri.split(","); - byte[] data = Base64.getDecoder().decode(uriInfo[1]); + ByteBuffer data = BufferUtils.createByteBuffer(Base64.getDecoder().decode(uriInfo[1])); String headerInfo = uriInfo[0].split(";")[0]; String extension = headerInfo.split("/")[1]; TextureKey key = new TextureKey("image" + sourceIndex + "." + extension, flip); - result = (Texture2D) info.getManager().loadAssetFromStream(key, new ByteArrayInputStream(data)); + try(BufferedInputStream bis = new BufferedInputStream(new BufferInputStream(data))){ + result = (Texture2D) info.getManager().loadAssetFromStream(key, bis); + } } else { - //external file image - TextureKey key = new TextureKey(info.getKey().getFolder() + uri, flip); + // external file image + String decoded = decodeUri(uri); + TextureKey key = new TextureKey(info.getKey().getFolder() + decoded, flip); Texture tex = info.getManager().loadTexture(key); result = (Texture2D) tex; } @@ -747,26 +871,26 @@ public void readAnimation(int animationIndex) throws IOException { String name = getAsString(animation, "name"); assertNotNull(channels, "No channels for animation " + name); assertNotNull(samplers, "No samplers for animation " + name); - - //temp data storage of track data + + // temp data storage of track data TrackData[] tracks = new TrackData[nodes.size()]; boolean hasMorphTrack = false; for (JsonElement channel : channels) { - JsonObject target = channel.getAsJsonObject().getAsJsonObject("target"); Integer targetNode = getAsInteger(target, "node"); String targetPath = getAsString(target, "path"); if (targetNode == null) { - //no target node for the channel, specs say to ignore the channel. + // no target node for the channel, specs say to ignore the channel. continue; } assertNotNull(targetPath, "No target path for channel"); // // if (targetPath.equals("weights")) { -// //Morph animation, not implemented in JME, let's warn the user and skip the channel -// logger.log(Level.WARNING, "Morph animation is not supported by JME yet, skipping animation track"); +// // Morph animation, not implemented in JME, let's warn the user and skip the channel +// logger.log(Level.WARNING, +// "Morph animation is not supported by JME yet, skipping animation track"); // continue; // } @@ -786,8 +910,9 @@ public void readAnimation(int animationIndex) throws IOException { String interpolation = getAsString(sampler, "interpolation"); if (interpolation == null || !interpolation.equals("LINEAR")) { - //JME anim system only supports Linear interpolation (will be possible with monkanim though) - //TODO rework this once monkanim is core, or allow a hook for animation loading to fit custom animation systems + // JME anim system only supports Linear interpolation (will be possible with monkanim though) + // TODO rework this once monkanim is core, + // or allow a hook for animation loading to fit custom animation systems logger.log(Level.WARNING, "JME only supports linear interpolation for animations"); } @@ -840,21 +965,22 @@ public void readAnimation(int animationIndex) throws IOException { if (node instanceof Spatial) { Spatial s = (Spatial) node; spatials.add(s); - if (trackData.rotations != null || trackData.translations != null || trackData.scales != null) { - TransformTrack track = new TransformTrack(s, trackData.times, trackData.translations, trackData.rotations, trackData.scales); + if (trackData.rotations != null || trackData.translations != null + || trackData.scales != null) { + TransformTrack track = new TransformTrack(s, trackData.times, + trackData.translations, trackData.rotations, trackData.scales); aTracks.add(track); } - if( trackData.weights != null && s instanceof Geometry){ - Geometry g = (Geometry)s; - int nbMorph = g.getMesh().getMorphTargets().length; -// for (int k = 0; k < trackData.weights.length; k++) { -// System.err.print(trackData.weights[k] + ","); -// if(k % nbMorph == 0 && k!=0){ -// System.err.println(" "); -// } -// } - MorphTrack track = new MorphTrack(g, trackData.times, trackData.weights, nbMorph); - aTracks.add(track); + if (trackData.weights != null) { + if (s instanceof Node) { + s.depthFirstTraversal((Spatial spatial) -> { + if (spatial instanceof Geometry) { + aTracks.add(toMorphTrack(trackData, spatial)); + } + }); + } else if (s instanceof Geometry) { + aTracks.add(toMorphTrack(trackData, s)); + } } } else if (node instanceof JointWrapper) { JointWrapper jw = (JointWrapper) node; @@ -863,26 +989,32 @@ public void readAnimation(int animationIndex) throws IOException { if (skinIndex == -1) { skinIndex = jw.skinIndex; } else { - //Check if all joints affected by this animation are from the same skin, the track will be skipped. + // Check if all joints affected by this animation are from the same skin, + // the track will be skipped. if (skinIndex != jw.skinIndex) { - logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + ") applies to joints that are not from the same skin: skin " + skinIndex + ", joint " + jw.joint.getName() + " from skin " + jw.skinIndex); + logger.log(Level.WARNING, "Animation " + animationIndex + " (" + name + + ") applies to joints that are not from the same skin: skin " + + skinIndex + ", joint " + jw.joint.getName() + + " from skin " + jw.skinIndex); continue; } } - TransformTrack track = new TransformTrack(jw.joint, trackData.times, trackData.translations, trackData.rotations, trackData.scales); + TransformTrack track = new TransformTrack(jw.joint, trackData.times, + trackData.translations, trackData.rotations, trackData.scales); aTracks.add(track); } } - // Check each bone to see if their local pose is different from their bind pose. - // If it is, we ensure that the bone has an animation track, else JME way of applying anim transforms will apply the bind pose to those bones, + // Check each bone to see if its local pose is different from its bind pose. + // If it is, we ensure that the bone has an animation track, + // else the JME way of applying anim transforms will apply the bind pose to those bones, // instead of the local pose that is supposed to be the default if (skinIndex != -1) { SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); for (Joint joint : skin.joints) { if (!usedJoints.contains(joint)) { - //create a track + // create a track float[] times = new float[]{0}; Vector3f[] translations = new Vector3f[]{joint.getLocalTranslation()}; @@ -893,29 +1025,29 @@ public void readAnimation(int animationIndex) throws IOException { } } } - + anim.setTracks(aTracks.toArray(new AnimTrack[aTracks.size()])); - anim = customContentManager.readExtensionAndExtras("animations", animation, anim); if (skinIndex != -1) { - //we have a armature animation. + // we have an armature animation. SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); skin.animComposer.addAnimClip(anim); } if (!spatials.isEmpty()) { if (skinIndex != -1) { - //there are some spatial or moph tracks in this bone animation... or the other way around. Let's add the spatials in the skinnedSpatials. + // there are some spatial or morph tracks in this bone animation... or the other way around. + // Let's add the spatials to the skinnedSpatials. SkinData skin = fetchFromCache("skins", skinIndex, SkinData.class); List spat = skinnedSpatials.get(skin); spat.addAll(spatials); - //the animControl will be added in the setupControls(); + // the animControl will be added in the setupControls(); if (hasMorphTrack && skin.morphControl == null) { skin.morphControl = new MorphControl(); } } else { - //Spatial animation + // Spatial animation Spatial spatial = null; if (spatials.size() == 1) { spatial = spatials.get(0); @@ -962,22 +1094,23 @@ public Texture2D readSampler(int samplerIndex, Texture2D texture) throws IOExcep public void readSkins() throws IOException { if (skins == null) { - //no skins, no bone animation. + // no skins, no bone animation. return; } List allJoints = new ArrayList<>(); for (int index = 0; index < skins.size(); index++) { JsonObject skin = skins.get(index).getAsJsonObject(); - //Note that the "skeleton" index is intentionally ignored. - //It's not mandatory and exporters tends to mix up how it should be used because the specs are not clear. - //Anyway we have other means to detect both armature structures and root bones. + // Note that the "skeleton" index is intentionally ignored. + // It's not mandatory and exporters tend to mix up how it should be used + // because the specs are not clear. + // Anyway we have other means to detect both armature structures and root bones. JsonArray jsonJoints = skin.getAsJsonArray("joints"); assertNotNull(jsonJoints, "No joints defined for skin"); int idx = allJoints.indexOf(jsonJoints); if (idx >= 0) { - //skin already exists let's just set it in the cache + // skin already exists let's just set it in the cache SkinData sd = fetchFromCache("skins", idx, SkinData.class); addToCache("skins", index, sd, nodes.size()); continue; @@ -985,8 +1118,9 @@ public void readSkins() throws IOException { allJoints.add(jsonJoints); } - //These inverse bind matrices, once inverted again, will give us the real bind pose of the bones (in model space), - //since the skeleton in not guaranteed to be exported in bind pose. + // These inverse bind matrices, once inverted again, + // will give us the real bind pose of the bones (in model space), + // since the skeleton is not guaranteed to be exported in bind pose. Integer matricesIndex = getAsInteger(skin, "inverseBindMatrices"); Matrix4f[] inverseBindMatrices = null; if (matricesIndex != null) { @@ -1010,7 +1144,6 @@ public void readSkins() throws IOException { } Armature armature = new Armature(joints); - armature = customContentManager.readExtensionAndExtras("skin", skin, armature); SkinData skinData = new SkinData(); skinData.joints = joints; @@ -1024,8 +1157,8 @@ public void readSkins() throws IOException { } } - public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix4f inverseModelBindMatrix) throws IOException { - + public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix4f inverseModelBindMatrix) + throws IOException { JointWrapper jointWrapper = fetchFromCache("nodes", nodeIndex, JointWrapper.class); if (jointWrapper != null) { return jointWrapper.joint; @@ -1048,6 +1181,12 @@ public Joint readNodeAsBone(int nodeIndex, int jointIndex, int skinIndex, Matrix private void findChildren(int nodeIndex) throws IOException { JointWrapper jw = fetchFromCache("nodes", nodeIndex, JointWrapper.class); + if (jw == null) { + logger.log(Level.WARNING, + "No JointWrapper found for nodeIndex={0}.", nodeIndex); + return; + } + JsonObject nodeData = nodes.get(nodeIndex).getAsJsonObject(); JsonArray children = nodeData.getAsJsonArray("children"); @@ -1055,7 +1194,7 @@ private void findChildren(int nodeIndex) throws IOException { for (JsonElement child : children) { int childIndex = child.getAsInt(); if (jw.children.contains(childIndex)) { - //bone already has the child in its children + // bone already has the child in its children continue; } JointWrapper cjw = fetchFromCache("nodes", childIndex, JointWrapper.class); @@ -1063,17 +1202,17 @@ private void findChildren(int nodeIndex) throws IOException { jw.joint.addChild(cjw.joint); jw.children.add(childIndex); } else { - //The child might be a Node - //Creating a dummy node to read the subgraph + // The child might be a Node + // Creating a dummy node to read the subgraph Node n = new Node(); readChild(n, child); Spatial s = n.getChild(0); - //removing the spatial from the dummy node, it will be attached to the attachment node of the bone + // removing the spatial from the dummy node, + // it will be attached to the attachment node of the bone s.removeFromParent(); jw.attachedSpatial = s; } } - } } @@ -1088,14 +1227,12 @@ private void setupControls() { if (spatials.size() >= 1) { spatial = findCommonAncestor(spatials); } - // if (spatial != skinData.parent) { // skinData.rootBoneTransformOffset = spatial.getWorldTransform().invert(); // if (skinData.parent != null) { // skinData.rootBoneTransformOffset.combineWithParent(skinData.parent.getWorldTransform()); // } // } - if (skinData.animComposer != null && skinData.animComposer.getSpatial() == null) { spatial.addControl(skinData.animComposer); } @@ -1110,8 +1247,16 @@ private void setupControls() { if (bw == null || bw.attachedSpatial == null) { continue; } + String jointName = bw.joint.getName(); SkinData skinData = fetchFromCache("skins", bw.skinIndex, SkinData.class); - skinData.skinningControl.getAttachmentsNode(bw.joint.getName()).attachChild(bw.attachedSpatial); + SkinningControl skinControl = skinData.skinningControl; + if (skinControl.getSpatial() == null) { + logger.log(Level.WARNING, + "No skinned Spatial for joint \"{0}\" -- will skin the model's root node!", + jointName); + rootNode.addControl(skinControl); + } + skinControl.getAttachmentsNode(jointName).attachChild(bw.attachedSpatial); } } @@ -1119,6 +1264,12 @@ private String readMeshName(int meshIndex) { JsonObject meshData = meshes.get(meshIndex).getAsJsonObject(); return getAsString(meshData, "name"); } + + private MorphTrack toMorphTrack(TrackData data, Spatial spatial) { + Geometry g = (Geometry) spatial; + int nbMorph = g.getMesh().getMorphTargets().length; + return new MorphTrack(g, data.times, data.weights, nbMorph); + } public T fetchFromCache(String name, int index, Class type) { Object[] data = dataCache.get(name); @@ -1131,7 +1282,6 @@ public T fetchFromCache(String name, int index, Class type) { } catch (ClassCastException e) { return null; } - } public void addToCache(String name, int index, Object object, int maxLength) { @@ -1155,6 +1305,14 @@ public Node getRootNode() { return rootNode; } + private String decodeUri(String uri) { + try { + return URLDecoder.decode(uri, "UTF-8"); + } catch (UnsupportedEncodingException e) { + return uri; // This would mean that UTF-8 is unsupported on the platform. + } + } + public static class WeightData { float value; short index; @@ -1167,7 +1325,6 @@ public WeightData(float value, short index, int componentSize) { } } - private class JointWrapper { Joint joint; int jointIndex; @@ -1204,12 +1361,12 @@ public SkinBuffers(short[] joints, int componentSize) { this.componentSize = componentSize; } - public SkinBuffers() { - } + public SkinBuffers() {} } private interface Populator { - T populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException; + T populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, + boolean normalized) throws IOException; } private class VertexBufferPopulator implements Populator { @@ -1220,31 +1377,32 @@ public VertexBufferPopulator(VertexBuffer.Type bufferType) { } @Override - public VertexBuffer populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + 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; } - VertexBuffer vb = new VertexBuffer(bufferType); VertexBuffer.Format format = getVertexBufferFormat(componentType); VertexBuffer.Format originalFormat = format; if (normalized) { - //Some float data can be packed into short buffers, "normalized" means they have to be unpacked. - //In that case the buffer is a FloatBuffer + // Some float data can be packed into short buffers, + // "normalized" means they have to be unpacked. + // In that case the buffer is a FloatBuffer format = VertexBuffer.Format.Float; } int numComponents = getNumberOfComponents(type); - Buffer buff = VertexBuffer.createBuffer(format, numComponents, count); int bufferSize = numComponents * count; + Buffer buff; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the buffer with zeros. + buff = VertexBuffer.createBuffer(format, numComponents, count); + // no referenced buffer, specs says to pad the buffer with zeros. padBuffer(buff, bufferSize); } else { - readBuffer(bufferViewIndex, byteOffset, count, buff, numComponents, originalFormat); + buff = (Buffer) viewBuffer(bufferViewIndex, byteOffset, count, numComponents, originalFormat, format); } if (bufferType == VertexBuffer.Type.Index) { @@ -1254,44 +1412,45 @@ public VertexBuffer populate(Integer bufferViewIndex, int componentType, String return vb; } - } private class FloatArrayPopulator implements Populator { @Override - public float[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + public float[] populate(Integer bufferViewIndex, int componentType, String type, int count, + int byteOffset, boolean normalized) throws IOException { int numComponents = getNumberOfComponents(type); int dataSize = numComponents * count; float[] data = new float[dataSize]; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the data with zeros. + // no referenced buffer, specs say to pad the data with zeros. padBuffer(data, dataSize); } else { - readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); + readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, + getVertexBufferFormat(componentType)); } return data; } - } // -// private class FloaGridPopulator implements Populator { +// private class FloatGridPopulator implements Populator { // // @Override -// public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { +// public float[][] populate(Integer bufferViewIndex, int componentType, String type, int count, +// int byteOffset, boolean normalized) throws IOException { // // int numComponents = getNumberOfComponents(type); // int dataSize = numComponents * count; // float[] data = new float[dataSize]; // // if (bufferViewIndex == null) { -// //no referenced buffer, specs says to pad the data with zeros. +// // no referenced buffer, specs say to pad the data with zeros. // padBuffer(data, dataSize); // } else { -// readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); +// readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, +// getVertexBufferFormat(componentType)); // } // // return data; @@ -1302,17 +1461,18 @@ public float[] populate(Integer bufferViewIndex, int componentType, String type, private class Vector3fArrayPopulator implements Populator { @Override - public Vector3f[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + public Vector3f[] populate(Integer bufferViewIndex, int componentType, String type, int count, + int byteOffset, boolean normalized) throws IOException { int numComponents = getNumberOfComponents(type); int dataSize = numComponents * count; Vector3f[] data = new Vector3f[count]; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the data with zeros. + // no referenced buffer, specs say to pad the data with zeros. padBuffer(data, dataSize); } else { - readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); + readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, + getVertexBufferFormat(componentType)); } return data; } @@ -1321,17 +1481,18 @@ public Vector3f[] populate(Integer bufferViewIndex, int componentType, String ty private class QuaternionArrayPopulator implements Populator { @Override - public Quaternion[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + public Quaternion[] populate(Integer bufferViewIndex, int componentType, String type, int count, + int byteOffset, boolean normalized) throws IOException { int numComponents = getNumberOfComponents(type); int dataSize = numComponents * count; Quaternion[] data = new Quaternion[count]; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the data with zeros. + // no referenced buffer, specs say to pad the data with zeros. padBuffer(data, dataSize); } else { - readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); + readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, + getVertexBufferFormat(componentType)); } return data; @@ -1341,17 +1502,18 @@ public Quaternion[] populate(Integer bufferViewIndex, int componentType, String private class Matrix4fArrayPopulator implements Populator { @Override - public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String type, int count, + int byteOffset, boolean normalized) throws IOException { int numComponents = getNumberOfComponents(type); int dataSize = numComponents * count; Matrix4f[] data = new Matrix4f[count]; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the data with zeros. + // no referenced buffer, specs says to pad the data with zeros. padBuffer(data, dataSize); } else { - readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, getVertexBufferFormat(componentType)); + readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, + getVertexBufferFormat(componentType)); } return data; @@ -1361,11 +1523,11 @@ public Matrix4f[] populate(Integer bufferViewIndex, int componentType, String ty private class JointArrayPopulator implements Populator { @Override - public SkinBuffers populate(Integer bufferViewIndex, int componentType, String type, int count, int byteOffset, boolean normalized) throws IOException { - + public SkinBuffers populate(Integer bufferViewIndex, int componentType, String type, int count, + int byteOffset, boolean normalized) throws IOException { int numComponents = getNumberOfComponents(type); - //can be bytes or shorts. + // can be bytes or shorts. VertexBuffer.Format format = VertexBuffer.Format.Byte; if (componentType == 5123) { format = VertexBuffer.Format.Short; @@ -1375,7 +1537,7 @@ public SkinBuffers populate(Integer bufferViewIndex, int componentType, String t short[] data = new short[dataSize]; if (bufferViewIndex == null) { - //no referenced buffer, specs says to pad the data with zeros. + // no referenced buffer, specs say to pad the data with zeros. padBuffer(data, dataSize); } else { readBuffer(bufferViewIndex, byteOffset, count, data, numComponents, format); @@ -1384,6 +1546,32 @@ public SkinBuffers populate(Integer bufferViewIndex, int componentType, String t return new SkinBuffers(data, format.getComponentSize()); } } + + + public static void registerExtension(String name, Class ext) { + CustomContentManager.defaultExtensionLoaders.put(name, ext); + } + -} + public static void unregisterExtension(String name) { + CustomContentManager.defaultExtensionLoaders.remove(name); + } + + /** + * Sets the default extras loader used when no loader is specified in the GltfModelKey. + * + * @param loader + * the default extras loader. + */ + public static void registerDefaultExtrasLoader(Class loader) { + CustomContentManager.defaultExtraLoaderClass = loader; + } + + /** + * Unregisters the default extras loader. + */ + public static void unregisterDefaultExtrasLoader() { + CustomContentManager.defaultExtraLoaderClass = UserDataLoader.class; + } +} \ No newline at end of file diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java index c8128c44f1..26da82bb3f 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfModelKey.java @@ -1,17 +1,49 @@ +/* + * Copyright (c) 2009-2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; import com.jme3.asset.ModelKey; import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * An optional key to use when loading a glTF file - * It allows to specify custom data loader replacing the default ones. + * It allows you to specify custom data loader, replacing the default ones. * - * MaterialAdapters: Allows to map glTF standard material model to a non stock material. - * ExtensionLoaders: Allows to provide or override a loader for a given gltf extension. - * ExtrasLoader: Allows to load any extras, application specific data of the gltf file. + * MaterialAdapters: Allows you to map glTF standard material model to a non-stock material. + * ExtensionLoaders: Allows you to provide or override a loader for a given glTF extension. + * ExtrasLoader: Allows you to load any extras, application specific data of the glTF file. * * For more information, please see glTF 2.0 specifications * https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md @@ -22,8 +54,8 @@ public class GltfModelKey extends ModelKey { private Map materialAdapters = new HashMap<>(); private static Map extensionLoaders = new HashMap<>(); - private ExtrasLoader extrasLoader; private boolean keepSkeletonPose = false; + private ExtrasLoader extrasLoader; public GltfModelKey(String name) { super(name); @@ -78,9 +110,38 @@ public ExtrasLoader getExtrasLoader() { /** * Sets the ExtrasLoader for reading any extra information from the gltf file. * - * @param extrasLoader + * @param extrasLoader the desired loader */ public void setExtrasLoader(ExtrasLoader extrasLoader) { this.extrasLoader = extrasLoader; } + + @Override + public boolean equals(Object object) { + if (object == null) { + return false; + } + if (getClass() != object.getClass()) { + return false; + } + final GltfModelKey other = (GltfModelKey)object; + if (!super.equals(other)) { + return false; + } + if (!Objects.equals(materialAdapters, other.materialAdapters) + || !Objects.equals(extrasLoader, other.extrasLoader)) { + return false; + } + return keepSkeletonPose == other.keepSkeletonPose; + } + + @Override + public int hashCode() { + int hash = 5; + hash = 37 * hash + materialAdapters.hashCode(); + hash = 37 * hash + Objects.hashCode(this.extrasLoader); + hash = 37 * hash + (this.keepSkeletonPose ? 1 : 0); + return hash; + } + } 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 f6200f533b..7c6ee853f1 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/GltfUtils.java @@ -1,15 +1,52 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -import com.google.gson.*; +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; import com.jme3.asset.AssetInfo; import com.jme3.asset.AssetLoadException; +import com.jme3.export.binary.ByteUtils; import com.jme3.math.*; +import com.jme3.plugins.json.Json; +import com.jme3.plugins.json.JsonParser; import com.jme3.scene.*; import com.jme3.texture.Texture; import com.jme3.util.*; - import java.io.*; import java.nio.*; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; @@ -21,6 +58,26 @@ public class GltfUtils { private static final Logger logger = Logger.getLogger(GltfUtils.class.getName()); + /** + * A private constructor to inhibit instantiation of this class. + */ + private GltfUtils() { + } + + public static ByteBuffer asReadableByteBuffer(ByteBuffer bbf){ + return bbf.slice().order(ByteOrder.LITTLE_ENDIAN); + } + + /** + * Parse a json input stream and returns a {@link JsonObject} + * @param stream the stream to parse + * @return the JsonObject + */ + public static JsonObject parse(InputStream stream) { + JsonParser parser = Json.create(); + return parser.parse(stream); + } + public static Mesh.Mode getMeshMode(Integer mode) { if (mode == null) { return Mesh.Mode.Triangles; @@ -177,62 +234,68 @@ public static Texture.WrapMode getWrapMode(Integer value) { } } - public static void padBuffer(Object store, int bufferSize) { - if (store instanceof Buffer) { - Buffer buffer = (Buffer) store; - buffer.clear(); - if (buffer instanceof IntBuffer) { - IntBuffer ib = (IntBuffer) buffer; - for (int i = 0; i < bufferSize; i++) { - ib.put(0); - } - } else if (buffer instanceof FloatBuffer) { - FloatBuffer fb = (FloatBuffer) buffer; - for (int i = 0; i < bufferSize; i++) { - fb.put(0); - } - } else if (buffer instanceof ShortBuffer) { - ShortBuffer sb = (ShortBuffer) buffer; - for (int i = 0; i < bufferSize; i++) { - sb.put((short) 0); - } - } else if (buffer instanceof ByteBuffer) { - ByteBuffer bb = (ByteBuffer) buffer; - for (int i = 0; i < bufferSize; i++) { - bb.put((byte) 0); - } - } - buffer.rewind(); - } - if (store instanceof short[]) { - short[] array = (short[]) store; - for (int i = 0; i < array.length; i++) { - array[i] = 0; - } - } else if (store instanceof float[]) { - float[] array = (float[]) store; - for (int i = 0; i < array.length; i++) { - array[i] = 0; + public static void padBuffer(Buffer buffer, int bufferSize) { + buffer.clear(); + if (buffer instanceof IntBuffer) { + IntBuffer ib = (IntBuffer) buffer; + for (int i = 0; i < bufferSize; i++) { + ib.put(0); } - } else if (store instanceof Vector3f[]) { - Vector3f[] array = (Vector3f[]) store; - for (int i = 0; i < array.length; i++) { - array[i] = new Vector3f(); + } else if (buffer instanceof FloatBuffer) { + FloatBuffer fb = (FloatBuffer) buffer; + for (int i = 0; i < bufferSize; i++) { + fb.put(0); } - } else if (store instanceof Quaternion[]) { - Quaternion[] array = (Quaternion[]) store; - for (int i = 0; i < array.length; i++) { - array[i] = new Quaternion(); + } else if (buffer instanceof ShortBuffer) { + ShortBuffer sb = (ShortBuffer) buffer; + for (int i = 0; i < bufferSize; i++) { + sb.put((short) 0); } - } else if (store instanceof Matrix4f[]) { - Matrix4f[] array = (Matrix4f[]) store; - for (int i = 0; i < array.length; i++) { - array[i] = new Matrix4f(); + } else if (buffer instanceof ByteBuffer) { + ByteBuffer bb = (ByteBuffer) buffer; + for (int i = 0; i < bufferSize; i++) { + bb.put((byte) 0); } } + buffer.rewind(); + } + + public static void padBuffer(float[] array, int bufferSize) { + for (int i = 0; i < bufferSize; i++) { + array[i] = 0; + } + } + + public static void padBuffer(short[] array, int bufferSize) { + for (int i = 0; i < bufferSize; i++) { + array[i] = 0; + } + } + + public static void padBuffer(Vector3f[] array, int bufferSize) { + for (int i = 0; i < bufferSize; i++) { + array[i] = new Vector3f(); + } + } + + public static void padBuffer(Quaternion[] array, int bufferSize) { + for (int i = 0; i < bufferSize; i++) { + array[i] = new Quaternion(); + } + } + + public static void padBuffer(Matrix4f[] array, int bufferSize) { + for (int i = 0; i < bufferSize; i++) { + array[i] = new Matrix4f(); + } } - public static void populateBuffer(Object store, byte[] source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + + + + + public static void populateBuffer(Object store, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + source = asReadableByteBuffer(source); if (store instanceof Buffer) { Buffer buffer = (Buffer) store; @@ -241,34 +304,37 @@ public static void populateBuffer(Object store, byte[] source, int count, int by populateByteBuffer((ByteBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format); return; } - LittleEndien stream = getStream(source); if (buffer instanceof ShortBuffer) { - populateShortBuffer((ShortBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format); + populateShortBuffer((ShortBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format); } else if (buffer instanceof IntBuffer) { - populateIntBuffer((IntBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format); + populateIntBuffer((IntBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format); } else if (buffer instanceof FloatBuffer) { - populateFloatBuffer((FloatBuffer) buffer, stream, count, byteOffset, byteStride, numComponents, format); + populateFloatBuffer((FloatBuffer) buffer, source, count, byteOffset, byteStride, numComponents, format); } buffer.rewind(); return; } - LittleEndien stream = getStream(source); + if (store instanceof byte[]) { - populateByteArray((byte[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateByteArray((byte[]) store, source, count, byteOffset, byteStride, numComponents, format); } else if (store instanceof short[]) { - populateShortArray((short[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateShortArray((short[]) store, source, count, byteOffset, byteStride, numComponents, format); } else if (store instanceof float[]) { - populateFloatArray((float[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateFloatArray((float[]) store, source, count, byteOffset, byteStride, numComponents, format); } else if (store instanceof Vector3f[]) { - populateVector3fArray((Vector3f[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateVector3fArray((Vector3f[]) store, source, count, byteOffset, byteStride, numComponents, format); } else if (store instanceof Quaternion[]) { - populateQuaternionArray((Quaternion[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateQuaternionArray((Quaternion[]) store, source, count, byteOffset, byteStride, numComponents, format); } else if (store instanceof Matrix4f[]) { - populateMatrix4fArray((Matrix4f[]) store, stream, count, byteOffset, byteStride, numComponents, format); + populateMatrix4fArray((Matrix4f[]) store, source, count, byteOffset, byteStride, numComponents, format); } } - private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) { + private static void skip(ByteBuffer buff, int n) { + buff.position(Math.min(buff.position() + n, buff.limit())); + } + + private static void populateByteBuffer(ByteBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; @@ -276,136 +342,155 @@ private static void populateByteBuffer(ByteBuffer buffer, byte[] source, int cou int end = count * stride + byteOffset; while (index < end) { for (int i = 0; i < numComponents; i++) { - buffer.put(source[index + i]); + buffer.put(source.get(index + i)); } index += stride; } } - private static void populateShortBuffer(ShortBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateShortBuffer(ShortBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); - int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + int end = count * stride + byteOffset; + source.position(source.position() + byteOffset); while (index < end) { for (int i = 0; i < numComponents; i++) { - buffer.put(stream.readShort()); + buffer.put(source.getShort()); } if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - private static void populateIntBuffer(IntBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateIntBuffer(IntBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); while (index < end) { for (int i = 0; i < numComponents; i++) { - buffer.put(stream.readInt()); + buffer.put(source.getInt()); } if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - private static void populateFloatBuffer(FloatBuffer buffer, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateFloatBuffer(FloatBuffer buffer, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); while (index < end) { for (int i = 0; i < numComponents; i++) { - buffer.put(readAsFloat(stream, format)); + buffer.put(readAsFloat(source, format)); } if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - public static float readAsFloat(LittleEndien stream, VertexBuffer.Format format) throws IOException { + public static float readAsFloat(ByteBuffer source, VertexBuffer.Format format) throws IOException { //We may have packed data so depending on the format, we need to read data differently and unpack it // Implementations must use following equations to get corresponding floating-point value f from a normalized integer c and vise-versa: - // accessor.componentType int-to-float float-to-int - // 5120 (BYTE) f = max(c / 127.0, -1.0) c = round(f * 127.0) - // 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; + // accessor.componentType int-to-float float-to-int + // 5120 (BYTE) f = max(c / 127.0, -1.0) c = round(f * 127.0) + // 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) + int c; switch (format) { case Byte: - b = stream.readByte(); - return Math.max((float) b / 127f, -1f); + c = source.get(); + return Math.max(c / 127f, -1f); case UnsignedByte: - b = stream.readByte(); - return (float) b / 255f; + c = source.get() & 0xFF; + return c / 255f; case Short: - b = stream.readByte(); - return Math.max((float) b / 32767f, -1f); - case UnsignedShort: - b = stream.readByte(); - return (float) b / 65535f; + c = source.getShort(); + return Math.max(c / 32767f, -1f); + case UnsignedShort: + c = source.get() & 0xff | (source.get() & 0xff) << 8; + return c / 65535f; default: //we have a regular float - return stream.readFloat(); + return source.getFloat(); } } - private static void populateByteArray(byte[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateByteArray(byte[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); + + if (dataLength == stride) { + read(source, array, end - index); + + return; + } + int arrayIndex = 0; + byte[] buffer = new byte[numComponents]; while (index < end) { - for (int i = 0; i < numComponents; i++) { - array[arrayIndex] = stream.readByte(); - arrayIndex++; - } + read(source, buffer, numComponents); + System.arraycopy(buffer, 0, array, arrayIndex, numComponents); + arrayIndex += numComponents; if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - private static void populateShortArray(short[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void read(ByteBuffer source, byte[] buffer, int length) throws IOException { + int n = 0; + while (n < length) { + int cnt = Math.min(source.remaining(), length - n); + source.get(buffer, n, cnt); + if (cnt < 0) { + throw new AssetLoadException("Data ended prematurely"); + } + n += cnt; + } + } + + private static void populateShortArray(short[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); int arrayIndex = 0; while (index < end) { for (int i = 0; i < numComponents; i++) { if (componentSize == 2) { - array[arrayIndex] = stream.readShort(); + array[arrayIndex] = source.getShort(); } else { - array[arrayIndex] = stream.readByte(); + array[arrayIndex] = source.get(); } arrayIndex++; } if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } @@ -465,7 +550,7 @@ public int compare(GltfLoader.WeightData o1, GltfLoader.WeightData o2) { } if (sum != 1f) { - // compute new vals based on sum + // compute new values based on sum float sumToB = sum == 0 ? 0 : 1f / sum; weightsArray[i] *= sumToB; weightsArray[i + 1] *= sumToB; @@ -489,107 +574,106 @@ public static void setSkinBuffers(Mesh mesh, short[] jointsArray, float[] weight mesh.getBuffer(VertexBuffer.Type.BoneWeight).setUsage(VertexBuffer.Usage.CpuOnly); } - private static void populateFloatArray(float[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateFloatArray(float[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); int arrayIndex = 0; while (index < end) { for (int i = 0; i < numComponents; i++) { - array[arrayIndex] = readAsFloat(stream, format); + array[arrayIndex] = readAsFloat(source, format); arrayIndex++; } if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - private static void populateVector3fArray(Vector3f[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateVector3fArray(Vector3f[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); int arrayIndex = 0; while (index < end) { array[arrayIndex] = new Vector3f( - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format) + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format) ); arrayIndex++; if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } - index += stride; } } - private static void populateQuaternionArray(Quaternion[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateQuaternionArray(Quaternion[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); int arrayIndex = 0; while (index < end) { array[arrayIndex] = new Quaternion( - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format) + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format) ); arrayIndex++; if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; } } - private static void populateMatrix4fArray(Matrix4f[] array, LittleEndien stream, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { + private static void populateMatrix4fArray(Matrix4f[] array, ByteBuffer source, int count, int byteOffset, int byteStride, int numComponents, VertexBuffer.Format format) throws IOException { int componentSize = format.getComponentSize(); int index = byteOffset; int dataLength = componentSize * numComponents; int stride = Math.max(dataLength, byteStride); int end = count * stride + byteOffset; - stream.skipBytes(byteOffset); + source.position(source.position() + byteOffset); int arrayIndex = 0; while (index < end) { array[arrayIndex] = toRowMajor( - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format), - readAsFloat(stream, format) + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format), + readAsFloat(source, format) ); //gltf matrix are column major, JME ones are row major. arrayIndex++; if (dataLength < stride) { - stream.skipBytes(stride - dataLength); + skip(source, stride - dataLength); } index += stride; @@ -686,30 +770,24 @@ public static void assertNotNull(Object o, String errorMessage) { } } -// public static boolean equalBindAndLocalTransforms(Joint b) { -// return equalsEpsilon(b.getBindPosition(), b.getLocalPosition()) -// && equalsEpsilon(b.getBindRotation(), b.getLocalRotation()) -// && equalsEpsilon(b.getBindScale(), b.getLocalScale()); -// } - - private static float epsilon = 0.0001f; + private static final float EPSILON = 0.0001f; public static boolean equalsEpsilon(Vector3f v1, Vector3f v2) { - return FastMath.abs(v1.x - v2.x) < epsilon - && FastMath.abs(v1.y - v2.y) < epsilon - && FastMath.abs(v1.z - v2.z) < epsilon; + return FastMath.abs(v1.x - v2.x) < EPSILON + && FastMath.abs(v1.y - v2.y) < EPSILON + && FastMath.abs(v1.z - v2.z) < EPSILON; } public static boolean equalsEpsilon(Quaternion q1, Quaternion q2) { - return (FastMath.abs(q1.getX() - q2.getX()) < epsilon - && FastMath.abs(q1.getY() - q2.getY()) < epsilon - && FastMath.abs(q1.getZ() - q2.getZ()) < epsilon - && FastMath.abs(q1.getW() - q2.getW()) < epsilon) + return (FastMath.abs(q1.getX() - q2.getX()) < EPSILON + && FastMath.abs(q1.getY() - q2.getY()) < EPSILON + && FastMath.abs(q1.getZ() - q2.getZ()) < EPSILON + && FastMath.abs(q1.getW() - q2.getW()) < EPSILON) || - (FastMath.abs(q1.getX() + q2.getX()) < epsilon - && FastMath.abs(q1.getY() + q2.getY()) < epsilon - && FastMath.abs(q1.getZ() + q2.getZ()) < epsilon - && FastMath.abs(q1.getW() + q2.getW()) < epsilon); + (FastMath.abs(q1.getX() + q2.getX()) < EPSILON + && FastMath.abs(q1.getY() + q2.getY()) < EPSILON + && FastMath.abs(q1.getZ() + q2.getZ()) < EPSILON + && FastMath.abs(q1.getW() + q2.getW()) < EPSILON); } @@ -833,4 +911,165 @@ public static void dumpMesh(Mesh m) { System.err.println("\n---------------------------"); } } + + public static void readToByteBuffer(InputStream input, ByteBuffer dst, int bytesToRead) throws IOException { + if (bytesToRead <= 0) throw new IOException("bytesToRead must be > 0"); + + int startPos = dst.position(); + int remaining = dst.limit() - startPos; + if (remaining < bytesToRead) { + throw new IOException("Destination ByteBuffer too small: remaining=" + remaining + " < bytesToRead=" + bytesToRead); + } + + ReadableByteChannel ch = Channels.newChannel(input); + int total = 0; + while (total < bytesToRead) { + int n = ch.read(dst); + if (n == -1) break; + total += n; + } + + if (total < bytesToRead) { + throw new IOException("Data ended prematurely " + total + " < " + bytesToRead); + } + + dst.flip(); + } + + public static void readToByteArray(InputStream input, byte[] dst, int bytesToRead) throws IOException { + if (bytesToRead < 0) throw new IllegalArgumentException("bytesToRead < 0"); + if (bytesToRead > dst.length) { + throw new IOException("Destination array too small: length=" + dst.length + " < bytesToRead=" + bytesToRead); + } + + int totalRead = 0; + while (totalRead < bytesToRead) { + int n = input.read(dst, totalRead, bytesToRead - totalRead); + if (n == -1) break; + totalRead += n; + } + + if (totalRead < bytesToRead) { + throw new IOException("Data ended prematurely " + totalRead + " < " + bytesToRead); + } + } + + + /** + * Try to expose a glTF buffer region as a typed NIO view without copying. + * Falls back to allocating a destination buffer and populating it when + * interleaving, normalization, or format mismatch prevents a pure view. + * + * @param source the original ByteBuffer (direct or heap) + * @param count number of elements + * @param byteOffset start offset within source (relative to beginning) + * @param byteStride stride in bytes (0 means tightly packed = element size) + * @param numComponents components per element (e.g. 3 for VEC3) + * @param originalFormat the source component type + * @param targetFormat the desired buffer view type to return + */ + public static Buffer getBufferView(ByteBuffer source, int byteOffset, int count, int byteStride, + int numComponents, VertexBuffer.Format originalFormat, + VertexBuffer.Format targetFormat) throws IOException { + // Work in little-endian as per glTF spec + source = asReadableByteBuffer(source); + + // Layout from source format + int srcCompSize = originalFormat.getComponentSize(); + int elemSize = srcCompSize * numComponents; + int stride = Math.max(elemSize, byteStride); + int start = byteOffset; + int bytes = stride * count; + + + boolean tightlyPacked = (stride == elemSize); + + if (tightlyPacked) { + ByteBuffer view = source.duplicate(); + view.position(start).limit(start + bytes); + view = view.slice().order(ByteOrder.LITTLE_ENDIAN); + + // Zero-copy returns only when source/target formats are compatible and aligned + switch (targetFormat) { + case Byte: + case UnsignedByte: + if (srcCompSize == 1 && + (originalFormat == VertexBuffer.Format.Byte || + originalFormat == VertexBuffer.Format.UnsignedByte)) { + return view; + } + break; + + case Short: + case UnsignedShort: + if (srcCompSize == 2 && + (originalFormat == VertexBuffer.Format.Short || + originalFormat == VertexBuffer.Format.UnsignedShort) && + (start & 1) == 0) { + return view.asShortBuffer(); + } + break; + + case Int: + case UnsignedInt: + if (srcCompSize == 4 && + (originalFormat == VertexBuffer.Format.Int || + originalFormat == VertexBuffer.Format.UnsignedInt) && + (start & 3) == 0) { + return view.asIntBuffer(); + } + break; + + case Float: + if (srcCompSize == 4 && + originalFormat == VertexBuffer.Format.Float && + (start & 3) == 0) { + return view.asFloatBuffer(); + } + break; + + case Double: + if (srcCompSize == 8 && + originalFormat == VertexBuffer.Format.Double && + (start & 7) == 0) { + return view.asDoubleBuffer(); + } + break; + } + } + + // Fallback: allocate destination buffer by desired targetFormat and populate from source + int elements = count * numComponents; + switch (targetFormat) { + case Byte: + case UnsignedByte: { + ByteBuffer out = BufferUtils.createByteBuffer(elements); + populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat); + return out; + } + case Short: + case UnsignedShort: { + ShortBuffer out = BufferUtils.createShortBuffer(elements); + populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat); + return out; + } + case Int: + case UnsignedInt: { + IntBuffer out = BufferUtils.createIntBuffer(elements); + populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat); + return out; + } + case Float: { + FloatBuffer out = BufferUtils.createFloatBuffer(elements); + populateBuffer(out, source, count, byteOffset, byteStride, numComponents, originalFormat); + return out; + } + case Double: + throw new IllegalArgumentException("Double conversion fallback not supported"); + default: + throw new IllegalArgumentException("Unsupported format " + targetFormat); + } + } + + } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java new file mode 100644 index 0000000000..112c6f3700 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/LightsPunctualExtensionLoader.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.asset.AssetLoadException; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Node; +import com.jme3.scene.control.LightControl; +import java.util.HashMap; +import java.util.HashSet; + +/** + * Extension loader for KHR_lights_punctual extension which allows + * for lights to be added to the node from the gltf model. + * + * Supports directional, point, and spot lights. + * + * Created by Trevor Flynn - 3/23/2021 + */ +public class LightsPunctualExtensionLoader implements ExtensionLoader { + + private final HashSet pendingNodes = new HashSet<>(); + private final HashMap lightDefinitions = new HashMap<>(); + + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) { + if (input instanceof Node) { //We are processing a node + JsonObject jsonObject = extension.getAsJsonObject(); + if (jsonObject.has("light")) { //These will get run first when loading the gltf file + //Add node to queue + JsonElement indexElement = jsonObject.get("light"); + int index = indexElement.getAsInt(); + if (!lightDefinitions.containsKey(index)) { + pendingNodes.add(new NodeNeedingLight((Node) input, index)); + } else { + addLight((Node) input, (Node) input, index); + } + } else if (jsonObject.has("lights")) { //This will get run last + //Process the light definitions + JsonArray lights = jsonObject.getAsJsonArray("lights"); + + for (int i = 0; i < lights.size(); i++) { + //Create light definition + JsonObject light = lights.get(i).getAsJsonObject(); + String type = light.get("type").getAsString(); + + Light lightNode; + switch (type) { + case "point": + lightNode = buildPointLight(light); + break; + case "directional": + lightNode = buildDirectionalLight(light); + break; + case "spot": + lightNode = buildSpotLight(light); + break; + default: + throw new AssetLoadException("KHR_lights_punctual unsupported light type: " + type); + } + + lightDefinitions.put(i, lightNode); + } + + //Build any lights that are pending now that we have definitions + for (NodeNeedingLight nodeInNeed : pendingNodes) { + addLight((Node) input, nodeInNeed.getNode(), nodeInNeed.getLightIndex()); + } + pendingNodes.clear(); + } else { + throw new AssetLoadException("KHR_lights_punctual extension malformed json"); + } + + return input; + } else { + throw new AssetLoadException("KHR_lights_punctual extension added on unsupported element"); + } + } + + /** + * Build a spot light from gltf json. + * @param obj The gltf json object for the spot light + * @return A spot light representation of the gltf object + */ + private SpotLight buildSpotLight(JsonObject obj) { + //Get properties + String name = obj.has("name") ? obj.get("name").getAsString() : ""; + + float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; + ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); + color = lumensToColor(color, intensity); + float range = obj.has("range") ? obj.get("range").getAsFloat() : Float.POSITIVE_INFINITY; + + //Spot specific + JsonObject spot = obj.getAsJsonObject("spot"); + float innerConeAngle = spot != null && spot.has("innerConeAngle") ? spot.get("innerConeAngle").getAsFloat() : 0f; + float outerConeAngle = spot != null && spot.has("outerConeAngle") ? spot.get("outerConeAngle").getAsFloat() : ((float) Math.PI) / 4f; + + /* + Correct floating point error on half PI, GLTF spec says that the outerConeAngle + can be less or equal to PI/2, but JME requires less than PI/2. + We will bring the angle within PI/2 if it is equal or larger than PI/2 + */ + if (outerConeAngle >= FastMath.HALF_PI) { + outerConeAngle = FastMath.HALF_PI - 0.000001f; + } + + SpotLight spotLight = new SpotLight(); + spotLight.setName(name); + spotLight.setColor(color); + spotLight.setSpotRange(range); + spotLight.setSpotInnerAngle(innerConeAngle); + spotLight.setSpotOuterAngle(outerConeAngle); + spotLight.setDirection(Vector3f.UNIT_Z.negate()); + + return spotLight; + } + + /** + * Build a directional light from gltf json. + * @param obj The gltf json object for the directional light + * @return A directional light representation of the gltf object + */ + private DirectionalLight buildDirectionalLight(JsonObject obj) { + //Get properties + String name = obj.has("name") ? obj.get("name").getAsString() : ""; + + float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; + ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); + color = lumensToColor(color, intensity); + + DirectionalLight directionalLight = new DirectionalLight(); + directionalLight.setName(name); + directionalLight.setColor(color); + directionalLight.setDirection(Vector3f.UNIT_Z.negate()); + + return directionalLight; + } + + /** + * Build a point light from gltf json. + * @param obj The gltf json object for the point light + * @return A point light representation of the gltf object + */ + private PointLight buildPointLight(JsonObject obj) { + //Get properties + String name = obj.has("name") ? obj.get("name").getAsString() : ""; + + float intensity = obj.has("intensity") ? obj.get("intensity").getAsFloat() : 1.0f; + ColorRGBA color = obj.has("color") ? GltfUtils.getAsColor(obj, "color") : new ColorRGBA(ColorRGBA.White); + color = lumensToColor(color, intensity); + float range = obj.has("range") ? obj.get("range").getAsFloat() : Float.POSITIVE_INFINITY; + + PointLight pointLight = new PointLight(); + pointLight.setName(name); + pointLight.setColor(color); + pointLight.setRadius(range); + + return pointLight; + } + + /** + * Attach a light at the given index to the given parent node, + * and the control for the light to the given node. + * @param parent The node to attach the light to + * @param node The node to attach the light control to + * @param lightIndex The index of the light + */ + private void addLight(Node parent, Node node, int lightIndex) { + if (lightDefinitions.containsKey(lightIndex)) { + Light light = lightDefinitions.get(lightIndex); + parent.addLight(light); + LightControl control = new LightControl(light); + control.setInvertAxisDirection(true); + node.addControl(control); + } else { + throw new AssetLoadException("KHR_lights_punctual extension accessed undefined light at index " + lightIndex); + } + } + + /** + * Convert a floating point lumens value into a color that + * represents both color and brightness of the light. + * + * @param color The base color of the light + * @param lumens The lumens value to convert to a color + * @return A color representing the intensity of the given lumens encoded into the given color + */ + private ColorRGBA lumensToColor(ColorRGBA color, float lumens) { + ColorRGBA brightnessModifier = lumensToColor(lumens); + return color.mult(brightnessModifier); + } + + /** + * Convert a floating point lumens value into a grayscale color that + * represents a brightness. + * + * @param lumens The lumens value to convert to a color + * @return A color representing the intensity of the given lumens + */ + private ColorRGBA lumensToColor(float lumens) { + /* + Taken from /Common/ShaderLib/Hdr.glsllib + vec4 HDR_EncodeLum(in float lum){ + float Le = 2.0 * log2(lum + epsilon) + 127.0; + vec4 result = vec4(0.0); + result.a = fract(Le); + result.rgb = vec3((Le - (floor(result.a * 255.0)) / 255.0) / 255.0); + return result; + */ + float epsilon = 0.0001f; + + double Le = 2f * Math.log(lumens * epsilon) / Math.log(2) + 127.0; + ColorRGBA color = new ColorRGBA(); + color.a = (float) (Le - Math.floor(Le)); //Get fractional part + float val = (float) ((Le - (Math.floor(color.a * 255.0)) / 255.0) / 255.0); + color.r = val; + color.g = val; + color.b = val; + + return color; + } + + /** + * A bean to contain the relation between a node and a light index + */ + private static class NodeNeedingLight { + private Node node; + private int lightIndex; + + private NodeNeedingLight(Node node, int lightIndex) { + this.node = node; + this.lightIndex = lightIndex; + } + + private Node getNode() { + return node; + } + + private void setNode(Node node) { + this.node = node; + } + + private int getLightIndex() { + return lightIndex; + } + + private void setLightIndex(int lightIndex) { + this.lightIndex = lightIndex; + } + } +} 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 759360e6af..b6b10f9c83 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,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.scene.plugins.gltf; @@ -19,7 +50,7 @@ */ public abstract class MaterialAdapter { - private Map paramsMapping = new HashMap<>(); + private final Map paramsMapping = new HashMap<>(); private Material mat; private AssetManager assetManager; @@ -31,6 +62,13 @@ public abstract class MaterialAdapter { protected abstract String getMaterialDefPath(); protected abstract MatParam adaptMatParam(MatParam param); + + /** + * Initializes material parameters to their default settings. + * + * @param material + */ + protected abstract void initDefaultMatParams(Material material); protected void init(AssetManager assetManager) { this.assetManager = assetManager; @@ -44,6 +82,7 @@ void reset() { protected Material getMaterial() { if (mat == null) { mat = new Material(assetManager, getMaterialDefPath()); + initDefaultMatParams(mat); } return mat; } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java new file mode 100644 index 0000000000..71ab1b84a4 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthExtensionLoader.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import com.jme3.asset.AssetKey; +import com.jme3.plugins.json.JsonElement; +import java.io.IOException; + +/** + * Extension loader for "KHR_materials_emissive_strength". + * + * @author codex + */ +public class PBREmissiveStrengthExtensionLoader implements ExtensionLoader { + + private PBREmissiveStrengthMaterialAdapter materialAdapter = new PBREmissiveStrengthMaterialAdapter(); + + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) throws IOException { + MaterialAdapter adapter = materialAdapter; + AssetKey key = loader.getInfo().getKey(); + //check for a custom adapter for emissive strength + if (key instanceof GltfModelKey) { + MaterialAdapter custom = ((GltfModelKey)key).getAdapterForMaterial("pbrEmissiveStrength"); + if (custom != null) { + adapter = custom; + } + } + adapter.init(loader.getInfo().getManager()); + adapter.setParam("emissiveStrength", GltfUtils.getAsFloat(extension.getAsJsonObject(), "emissiveStrength")); + return adapter; + } + +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java new file mode 100644 index 0000000000..5d078c97f7 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBREmissiveStrengthMaterialAdapter.java @@ -0,0 +1,46 @@ +/* + * 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.plugins.gltf; + +/** + * Adapter for converting GLTF emissive strength to JME emissive intensity. + * + * @author codex + */ +public class PBREmissiveStrengthMaterialAdapter extends PBRMaterialAdapter { + + public PBREmissiveStrengthMaterialAdapter() { + super(); + addParamMapping("emissiveStrength", "EmissiveIntensity"); + } + +} 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 de4bc98b3e..340cdb513b 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,16 +1,55 @@ +/* + * 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.scene.plugins.gltf; import com.jme3.material.*; /** - * Created by Nehon on 08/08/2017. + * Adapts GLTF PBR materials to JME PBR materials. + * + * @author Nehon */ public abstract class PBRMaterialAdapter extends MaterialAdapter { - - + + /** + * The default alpha discard threshold for "MASK" blend mode. + */ + public static final float MASK_ALPHA_DISCARD = 0.5f; + public PBRMaterialAdapter() { addParamMapping("normalTexture", "NormalMap"); + addParamMapping("normalScale", "NormalScale"); addParamMapping("occlusionTexture", "LightMap"); + addParamMapping("occlusionStrength", "AoStrength"); addParamMapping("emissiveTexture", "EmissiveMap"); addParamMapping("emissiveFactor", "Emissive"); addParamMapping("alphaMode", "alpha"); @@ -22,6 +61,11 @@ public PBRMaterialAdapter() { protected String getMaterialDefPath() { return "Common/MatDefs/Light/PBRLighting.j3md"; } + + @Override + protected void initDefaultMatParams(Material material) { + material.setFloat("EmissiveIntensity", 1); + } @Override protected MatParam adaptMatParam(MatParam param) { @@ -29,31 +73,34 @@ protected MatParam adaptMatParam(MatParam param) { String alphaMode = (String) param.getValue(); switch (alphaMode) { case "MASK": + // "MASK" -> BlendMode.Off + getMaterial().setFloat("AlphaDiscardThreshold", MASK_ALPHA_DISCARD); + break; case "BLEND": getMaterial().getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + break; } + + // Alpha mode is handled here, so return null return null; - } - if (param.getName().equals("doubleSided")) { + } else if (param.getName().equals("doubleSided")) { boolean doubleSided = (boolean) param.getValue(); if (doubleSided) { //Note that this is not completely right as normals on the back side will be in the wrong direction. getMaterial().getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); } + // FaceCulling is a RenderState not a Material Parameter, so return null return null; - } - if (param.getName().equals("NormalMap")) { + } else if (param.getName().equals("NormalMap")) { //Set the normal map type to OpenGl getMaterial().setFloat("NormalType", 1.0f); - } - if (param.getName().equals("LightMap")) { + } else if (param.getName().equals("LightMap")) { //Gltf only supports AO maps (gray scales and only the r channel must be read) getMaterial().setBoolean("LightMapAsAOMap", true); + } else if (param.getName().equals("alphaCutoff")) { + getMaterial().setFloat("AlphaDiscardThreshold", (float)param.getValue()); } - - - return param; } } diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java index a40e514d1c..fd744cffdd 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRMetalRoughMaterialAdapter.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2020 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; /** diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java index f9a9c68ee4..abeda10cd7 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossExtensionLoader.java @@ -1,10 +1,40 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ package com.jme3.scene.plugins.gltf; -import com.google.gson.JsonElement; import com.jme3.asset.AssetKey; import java.io.IOException; - +import com.jme3.plugins.json.JsonElement; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsColor; import static com.jme3.scene.plugins.gltf.GltfUtils.getAsFloat; diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java index 8edff41482..bd4c25c8af 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/PBRSpecGlossMaterialAdapter.java @@ -1,3 +1,34 @@ +/* + * Copyright (c) 2009-2020 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.jme3.material.MatParam; 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..aff6fbdcf1 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TextureTransformExtensionLoader.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.asset.AssetLoadException; +import com.jme3.math.Matrix3f; +import com.jme3.math.Vector3f; +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/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java index f5e0154f37..c453e5f7aa 100644 --- a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.java +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/TrackData.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.scene.plugins.gltf; import com.jme3.asset.AssetLoadException; @@ -59,7 +90,14 @@ public void update() { KeyFrame kf = keyFrames.get(j); if (Float.floatToIntBits(kf.time) != Float.floatToIntBits(time)) { if (time > kf.time) { - continue; + if (j < keyFrames.size() - 1) { + // Keep searching for the insertion point. + continue; + } + kf = new KeyFrame(); + kf.time = time; + // Add kf after the last keyframe in the list. + keyFrames.add(kf); } else { kf = new KeyFrame(); kf.time = time; @@ -134,12 +172,19 @@ public void update() { } } - checkTimesConsistantcy(); + checkTimesConsistency(); length = times[times.length - 1]; } - public void checkTimesConsistantcy() { + /** + * Verify that the + * {@link #times}, {@link #translations}, {@link #rotations}, and + * {@link #scales} vectors all have the same length, if present. + * + * @throws AssetLoadException if the lengths differ + */ + public void checkTimesConsistency() { if ((translations != null && times.length != translations.length) || (rotations != null && times.length != rotations.length) || (scales != null && times.length != scales.length)) { @@ -147,6 +192,11 @@ public void checkTimesConsistantcy() { } } + @Deprecated + public void checkTimesConsistantcy() { + checkTimesConsistency(); + } + private void populateTransform(Type type, int index, List keyFrames, KeyFrame currentKeyFrame, TransformIndices transformIndices) { Object transform = getTransform(type, currentKeyFrame); if (transform != null) { diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java new file mode 100644 index 0000000000..790d70b0cf --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitExtensionLoader.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.scene.plugins.gltf; + +import com.jme3.plugins.json.JsonElement; +import com.jme3.asset.AssetKey; + +/** + * Material adapter for the Unlit pipeline + * @author Markil 3 + */ +public class UnlitExtensionLoader implements ExtensionLoader { + + private final UnlitMaterialAdapter materialAdapter = new UnlitMaterialAdapter(); + + @Override + public Object handleExtension(GltfLoader loader, String parentName, JsonElement parent, JsonElement extension, Object input) { + MaterialAdapter adapter = materialAdapter; + AssetKey key = loader.getInfo().getKey(); + //check for a custom adapter for spec/gloss pipeline + if (key instanceof GltfModelKey) { + GltfModelKey gltfKey = (GltfModelKey) key; + MaterialAdapter ma = gltfKey.getAdapterForMaterial("unlit"); + if (ma != null) { + adapter = ma; + } + } + + adapter.init(loader.getInfo().getManager()); + + return adapter; + } +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java new file mode 100644 index 0000000000..65acdf7f2b --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UnlitMaterialAdapter.java @@ -0,0 +1,86 @@ +/* + * 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.plugins.gltf; + +import com.jme3.material.MatParam; +import com.jme3.material.Material; +import com.jme3.material.RenderState; + +/** + * @author Markil 3 + */ +public class UnlitMaterialAdapter extends MaterialAdapter { + + public UnlitMaterialAdapter() { + addParamMapping("baseColorFactor", "Color"); + addParamMapping("baseColorTexture", "ColorMap"); + addParamMapping("emissiveFactor", "GlowColor"); + addParamMapping("emissiveTexture", "GlowMap"); + addParamMapping("alphaMode", "alpha"); + addParamMapping("alphaCutoff", "AlphaDiscardThreshold"); + addParamMapping("doubleSided", "doubleSided"); + } + + @Override + protected String getMaterialDefPath() { + return "Common/MatDefs/Misc/Unshaded.j3md"; + } + + @Override + protected MatParam adaptMatParam(MatParam param) { + if (param.getName().equals("alpha")) { + String alphaMode = (String) param.getValue(); + switch (alphaMode) { + case "MASK": // fallthrough + case "BLEND": + getMaterial().getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); + break; + } + // Alpha is a RenderState not a Material Parameter, so return null + return null; + } + if (param.getName().equals("doubleSided")) { + boolean doubleSided = (boolean) param.getValue(); + if (doubleSided) { + //Note that this is not completely right as normals on the back side will be in the wrong direction. + getMaterial().getAdditionalRenderState().setFaceCullMode(RenderState.FaceCullMode.Off); + } + // FaceCulling is a RenderState not a Material Parameter, so return null + return null; + } + return param; + } + + @Override + protected void initDefaultMatParams(Material material) {} + +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java new file mode 100644 index 0000000000..b1276af904 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/UserDataLoader.java @@ -0,0 +1,175 @@ +/* + * Copyright (c) 2009-2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package com.jme3.scene.plugins.gltf; + +import com.jme3.plugins.json.JsonArray; +import com.jme3.plugins.json.JsonElement; +import com.jme3.plugins.json.JsonObject; +import com.jme3.plugins.json.JsonPrimitive; +import com.jme3.scene.Spatial; + +import java.lang.reflect.Array; +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * Import user data from glTF extras. + * + * Derived from Simsilica JmeConvert + * (https://github.com/Simsilica/JmeConvert/blob/master/src/main/java/com/simsilica/jmec/gltf/GltfExtrasLoader.java) + * by Paul Speed (Copyright (c) 2019, Simsilica, LLC) + * + */ + +public class UserDataLoader implements ExtrasLoader { + + private static final Logger log = Logger.getLogger(UserDataLoader.class.getName()); + + public UserDataLoader() { + } + + @Override + public Object handleExtras(GltfLoader loader, String parentName, JsonElement parent, JsonElement extras, + Object input) { + log.fine("handleExtras(" + loader + ", " + parentName + ", " + parent + ", " + extras + ", " + input + + ")"); + // Only interested in composite objects + if (!(extras instanceof JsonObject)) { + log.warning("Skipping extras:" + extras); + return input; + } + JsonObject jo = extras.getAsJsonObject(); + apply(input, jo); + return input; + } + + protected void apply(Object input, JsonObject extras) { + if (input == null) { + return; + } + if (input.getClass().isArray()) { + applyToArray(input, extras); + } else if (input instanceof Spatial) { + applyToSpatial((Spatial) input, extras); + } else { + log.warning("Unhandled input type:" + input.getClass()); + } + } + + protected void applyToArray(Object array, JsonObject extras) { + int size = Array.getLength(array); + for (int i = 0; i < size; i++) { + Object o = Array.get(array, i); + log.fine("processing array[" + i + "]:" + o); + apply(o, extras); + } + } + + protected void applyToSpatial(Spatial spatial, JsonObject extras) { + for (Map.Entry el : extras.entrySet()) { + log.fine(el.toString()); + Object val = toAttribute(el.getValue(), false); + + if (log.isLoggable(Level.FINE)) { + log.fine("setUserData(" + el.getKey() + ", " + val + ")"); + } + spatial.setUserData(el.getKey(), val); + } + } + + protected Object toAttribute(JsonElement el, boolean nested) { + if (el == null) { + return null; + } + el = el.autoCast(); + if (el instanceof JsonObject) { + return toAttribute(el.getAsJsonObject(), nested); + } else if (el instanceof JsonArray) { + return toAttribute(el.getAsJsonArray(), nested); + } else if (el instanceof JsonPrimitive) { + return toAttribute(el.getAsJsonPrimitive(), nested); + } + log.warning("Unhandled extras element:" + el); + return null; + } + + protected Object toAttribute(JsonObject jo, boolean nested) { + Map result = new HashMap<>(); + for (Map.Entry el : jo.entrySet()) { + result.put(el.getKey(), toAttribute(el.getValue(), true)); + } + return result; + } + + protected Object toAttribute(JsonArray ja, boolean nested) { + List result = new ArrayList<>(); + for (JsonElement el : ja) { + result.add(toAttribute(el, true)); + } + return result; + } + + protected Object toAttribute(JsonPrimitive jp, boolean nested) { + if (jp.isBoolean()) { + return jp.getAsBoolean(); + } else if (jp.isNumber()) { + // JME doesn't save Maps properly and treats them as two + // separate Lists... and it doesn't like saving Doubles + // in lists so we'll just return strings in the case where + // the value would end up in a map. If users someday really + // need properly typed map values and JME map storage hasn't + // been fixed then perhaps we give the users the option of + // flattening the nested properties into dot notation, ie: + // all directly on UserData with no Map children. + if (nested) { + return jp.getAsString(); + } + Number num = jp.getAsNumber(); + // JME doesn't like to save GSON's LazilyParsedNumber so we'll + // convert it into a real number. I don't think we can reliably + // guess what type of number the user intended. It would take + // some expirimentation to determine if things like 0.0 are + // preserved + // during export or just get exported as 0. + // Rather than randomly flip-flop between number types depending + // on the inclusion (or not) of a decimal point, we'll just always + // return Double. + return num.doubleValue(); + } else if (jp.isString()) { + return jp.getAsString(); + } + log.warning("Unhandled primitive:" + jp); + return null; + } +} diff --git a/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/package-info.java b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/package-info.java new file mode 100644 index 0000000000..f081102cd5 --- /dev/null +++ b/jme3-plugins/src/gltf/java/com/jme3/scene/plugins/gltf/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import 3-D models in Graphics Language Transmission Format (glTF) format + */ +package com.jme3.scene.plugins.gltf; diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java index 0535c45825..4affcd64c8 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MExporter.java @@ -1,8 +1,3 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package com.jme3.material.plugin.export.material; import com.jme3.export.JmeExporter; @@ -10,13 +5,13 @@ import com.jme3.export.Savable; import com.jme3.material.Material; import com.jme3.material.MaterialDef; - +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; -import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; /** * Saves a Material to a j3m file with proper formatting. @@ -50,19 +45,25 @@ public void save(Savable object, OutputStream f) throws IOException { throw new IllegalArgumentException("J3MExporter can only save com.jme3.material.Material class"); } - OutputStreamWriter out = new OutputStreamWriter(f, Charset.forName("UTF-8")); + try (OutputStreamWriter out = new OutputStreamWriter(f, StandardCharsets.UTF_8)) { - rootCapsule.clear(); - object.write(this); - rootCapsule.writeToStream(out); + rootCapsule.clear(); + object.write(this); + rootCapsule.writeToStream(out); - out.flush(); + } } @Override - public void save(Savable object, File f) throws IOException { - try (FileOutputStream fos = new FileOutputStream(f)) { - save(object, fos); + public void save(Savable object, File f, boolean createDirectories) throws IOException { + File parentDirectory = f.getParentFile(); + if (parentDirectory != null && !parentDirectory.exists() && createDirectories) { + parentDirectory.mkdirs(); + } + + try (FileOutputStream fos = new FileOutputStream(f); + BufferedOutputStream bos = new BufferedOutputStream(fos)) { + save(object, bos); } } 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 01ae2b915b..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,7 +1,33 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * 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; @@ -16,7 +42,7 @@ import com.jme3.texture.Texture.WrapMode; import com.jme3.util.IntMap; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.io.Writer; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; @@ -40,7 +66,7 @@ public J3MOutputCapsule(J3MExporter exporter) { parameters = new HashMap<>(); } - public void writeToStream(OutputStreamWriter out) throws IOException { + public void writeToStream(Writer out) throws IOException { for (String key : parameters.keySet()) { out.write(" "); writeParameter(out, key, parameters.get(key)); @@ -48,7 +74,7 @@ public void writeToStream(OutputStreamWriter out) throws IOException { } } - protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + protected void writeParameter(Writer out, String name, String value) throws IOException { out.write(name); out.write(" : "); out.write(value); @@ -145,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/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java index 02c8df02ca..e9ab1d9bc8 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRenderStateOutputCapsule.java @@ -1,24 +1,19 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package com.jme3.material.plugin.export.material; import com.jme3.export.OutputCapsule; import com.jme3.export.Savable; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.HashMap; /** * * @author tsr */ -public class J3MRenderStateOutputCapsule extends J3MOutputCapsule { +public class J3MRenderStateOutputCapsule extends J3MOutputCapsule { protected final static HashMap NAME_MAP; protected String offsetUnit; - + static { NAME_MAP = new HashMap<>(); NAME_MAP.put( "wireframe", "Wireframe"); @@ -41,7 +36,7 @@ public J3MRenderStateOutputCapsule(J3MExporter exporter) { public OutputCapsule getCapsule(Savable object) { throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates. } - + @Override public void clear() { super.clear(); @@ -49,34 +44,34 @@ public void clear() { } @Override - public void writeToStream(OutputStreamWriter out) throws IOException { + public void writeToStream(Writer out) throws IOException { out.write(" AdditionalRenderState {\n"); super.writeToStream(out); out.write(" }\n"); - } - + } + @Override - protected void writeParameter(OutputStreamWriter out, String name, String value) throws IOException { + protected void writeParameter(Writer out, String name, String value) throws IOException { out.write(name); - out.write(" "); + out.write(" "); out.write(value); - + if( "PolyOffset".equals(name) ) { out.write(" "); out.write(offsetUnit); - } + } } - + @Override protected void putParameter(String name, String value ) { if( "offsetUnits".equals(name) ) { offsetUnit = value; return; } - + if( !NAME_MAP.containsKey(name) ) return; - + super.putParameter(NAME_MAP.get(name), value); } } diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java index 40130b7050..40bca64c01 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/J3MRootOutputCapsule.java @@ -1,7 +1,33 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2025 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; @@ -9,52 +35,85 @@ import com.jme3.export.Savable; import java.io.IOException; -import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.HashMap; /** + * The `J3MRootOutputCapsule` class extends `J3MOutputCapsule` and serves as the + * root output capsule for exporting jME materials (`.j3m` files). + * * @author tsr */ public class J3MRootOutputCapsule extends J3MOutputCapsule { - private final HashMap outCapsules; + /** + * Stores a map of `Savable` objects to their corresponding `J3MOutputCapsule` instances. + * This allows for managing and exporting different components (e.g., render states) + * of a material. + */ + private final HashMap outCapsules = new HashMap<>(); + // The name of the material. private String name; - private String materialDefinition; + // The material definition string (e.g., "Common/MatDefs/Light.j3md"). + private String materialDef; + // Indicates whether the material is transparent private Boolean isTransparent; - + // Indicates whether the material receives shadows + private Boolean receivesShadows; + + /** + * Constructs a new `J3MRootOutputCapsule`. + * + * @param exporter The `J3MExporter` instance used for exporting savable objects. + */ public J3MRootOutputCapsule(J3MExporter exporter) { super(exporter); - outCapsules = new HashMap<>(); } + /** + * Clears all data within this capsule and its superclass. + * Resets material properties to their default or null values and clears + * the map of savable capsules. + */ @Override public void clear() { super.clear(); isTransparent = null; + receivesShadows = null; name = ""; - materialDefinition = ""; + materialDef = ""; outCapsules.clear(); - } + /** + * Retrieves an `OutputCapsule` for a given `Savable` object. + * If a capsule for the object does not exist, a new `J3MRenderStateOutputCapsule` + * is created and associated with the object. + * + * @param object The `Savable` object for which to retrieve or create a capsule. + * @return The `OutputCapsule` associated with the given savable object. + */ public OutputCapsule getCapsule(Savable object) { if (!outCapsules.containsKey(object)) { outCapsules.put(object, new J3MRenderStateOutputCapsule(exporter)); } - return outCapsules.get(object); } @Override - public void writeToStream(OutputStreamWriter out) throws IOException { - out.write("Material " + name + " : " + materialDefinition + " {\n\n"); + public void writeToStream(Writer out) throws IOException { + out.write("Material " + name + " : " + materialDef + " {\n\n"); + if (isTransparent != null) - out.write(" Transparent " + ((isTransparent) ? "On" : "Off") + "\n\n"); + out.write(" Transparent " + (isTransparent ? "On" : "Off") + "\n\n"); + if (receivesShadows != null) + out.write(" ReceivesShadows " + (receivesShadows ? "On" : "Off") + "\n\n"); out.write(" MaterialParameters {\n"); - super.writeToStream(out); + super.writeToStream(out); // Writes parameters from the superclass out.write(" }\n\n"); + // Write out encapsulated savable object data for (J3MOutputCapsule c : outCapsules.values()) { c.writeToStream(out); } @@ -65,7 +124,7 @@ public void writeToStream(OutputStreamWriter out) throws IOException { public void write(String value, String name, String defVal) throws IOException { switch (name) { case "material_def": - materialDefinition = value; + materialDef = value; break; case "name": this.name = value; @@ -77,13 +136,18 @@ public void write(String value, String name, String defVal) throws IOException { @Override public void write(boolean value, String name, boolean defVal) throws IOException { - if( value == defVal) + // No need to write if the value is the same as the default. + if (value == defVal) { return; + } switch (name) { case "is_transparent": isTransparent = value; break; + case "receives_shadows": + receivesShadows = value; + break; default: throw new UnsupportedOperationException(name + " boolean material parameter not supported yet"); } @@ -91,7 +155,7 @@ public void write(boolean value, String name, boolean defVal) throws IOException @Override public void write(Savable object, String name, Savable defVal) throws IOException { - if(object != null && !object.equals(defVal)) { + if (object != null && !object.equals(defVal)) { object.write(exporter); } } diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/package-info.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/package-info.java new file mode 100644 index 0000000000..9b6945fc97 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/material/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * export a Material using the J3M file format + */ +package com.jme3.material.plugin.export.material; diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdExporter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdExporter.java index affb1215c9..32cccd1d92 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdExporter.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdExporter.java @@ -1,12 +1,6 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ package com.jme3.material.plugin.export.materialdef; import com.jme3.material.*; - import java.io.*; import java.util.List; @@ -68,8 +62,9 @@ public void save(MaterialDef matDef, OutputStream f) throws IOException { public void save(MaterialDef matDef, File f) throws IOException { - try (FileOutputStream fos = new FileOutputStream(f)) { - save(matDef, fos); + try (FileOutputStream fos = new FileOutputStream(f); + BufferedOutputStream bos = new BufferedOutputStream(fos)) { + save(matDef, bos); } } diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdMatParamWriter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdMatParamWriter.java index 9d9cc3ef5d..fde5ab7f8c 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdMatParamWriter.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdMatParamWriter.java @@ -1,19 +1,43 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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.materialdef; import com.jme3.material.*; import com.jme3.math.*; -import com.jme3.texture.image.ColorSpace; - -import java.io.*; - import static com.jme3.shader.VarType.Vector2; import static com.jme3.shader.VarType.Vector3; import static com.jme3.shader.VarType.Vector4; +import com.jme3.texture.image.ColorSpace; +import java.io.*; /** * @author nehon @@ -23,7 +47,7 @@ public class J3mdMatParamWriter { public J3mdMatParamWriter() { } - public void write(MatParam param, OutputStreamWriter out) throws IOException { + public void write(MatParam param, Writer out) throws IOException { out.write(" "); out.write(param.getVarType().name()); out.write(" "); diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java index eabfda1c0f..48f861a86b 100644 --- a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/J3mdTechniqueDefWriter.java @@ -1,7 +1,33 @@ /* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. + * Copyright (c) 2009-2021 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.materialdef; @@ -9,9 +35,8 @@ import com.jme3.material.RenderState; import com.jme3.material.TechniqueDef; import com.jme3.shader.*; - import java.io.IOException; -import java.io.OutputStreamWriter; +import java.io.Writer; import java.util.Collection; import java.util.List; import java.util.regex.Matcher; @@ -25,7 +50,7 @@ public class J3mdTechniqueDefWriter { public J3mdTechniqueDefWriter() { } - public void write(TechniqueDef techniqueDef, Collection matParams, OutputStreamWriter out) throws IOException { + public void write(TechniqueDef techniqueDef, Collection matParams, Writer out) throws IOException { out.write(" Technique"); if(!techniqueDef.getName().equals("Default")) { out.write(" "); @@ -62,7 +87,7 @@ public void write(TechniqueDef techniqueDef, Collection matParams, Out writeShaderNodes(techniqueDef, matParams, out); } else { - //When we have ShaderNodes, Defines are handled differently so we don't have to write them. + // When we have ShaderNodes, defines are handled differently, so we don't have to write them. //Defines if (techniqueDef.getDefineNames().length != 0) { writeDefines(techniqueDef, matParams, out); @@ -93,7 +118,7 @@ public void write(TechniqueDef techniqueDef, Collection matParams, Out out.write(" }\n"); } - private void writeDefines(TechniqueDef techniqueDef, Collection matParams, OutputStreamWriter out) throws IOException { + private void writeDefines(TechniqueDef techniqueDef, Collection matParams, Writer out) throws IOException { out.write(" Defines {\n"); for (int i = 0; i < techniqueDef.getDefineNames().length; i++) { @@ -110,7 +135,7 @@ private void writeDefines(TechniqueDef techniqueDef, Collection matPar out.write(" }\n\n"); } - private void writeShaderNodes(TechniqueDef techniqueDef, Collection matParams, OutputStreamWriter out) throws IOException { + private void writeShaderNodes(TechniqueDef techniqueDef, Collection matParams, Writer out) throws IOException { out.write(" VertexShaderNodes {\n"); for (ShaderNode shaderNode : techniqueDef.getShaderNodes()) { if(shaderNode.getDefinition().getType() == Shader.ShaderType.Vertex){ @@ -128,7 +153,7 @@ private void writeShaderNodes(TechniqueDef techniqueDef, Collection ma out.write(" }\n\n"); } - private void writeWorldParams(TechniqueDef techniqueDef, OutputStreamWriter out) throws IOException { + private void writeWorldParams(TechniqueDef techniqueDef, Writer out) throws IOException { out.write(" WorldParameters {\n"); for (UniformBinding uniformBinding : techniqueDef.getWorldBindings()) { out.write(" "); @@ -138,7 +163,7 @@ private void writeWorldParams(TechniqueDef techniqueDef, OutputStreamWriter out) out.write(" }\n\n"); } - private void writeShaders(TechniqueDef techniqueDef, OutputStreamWriter out) throws IOException { + private void writeShaders(TechniqueDef techniqueDef, Writer out) throws IOException { if (techniqueDef.getShaderProgramNames().size() > 0) { for (Shader.ShaderType shaderType : techniqueDef.getShaderProgramNames().keySet()) { // System.err.println(shaderType + " " +techniqueDef.getShaderProgramNames().get(shaderType) + " " +techniqueDef.getShaderProgramLanguage(shaderType)) @@ -154,7 +179,7 @@ private void writeShaders(TechniqueDef techniqueDef, OutputStreamWriter out) thr } } - private void writeShaderNode( OutputStreamWriter out, ShaderNode shaderNode, Collection matParams) throws IOException { + private void writeShaderNode(Writer out, ShaderNode shaderNode, Collection matParams) throws IOException { out.write(" ShaderNode "); out.write(shaderNode.getName()); out.write(" {\n"); @@ -193,8 +218,7 @@ private void writeShaderNode( OutputStreamWriter out, ShaderNode shaderNode, Col out.write(" }\n"); } - private void writeVariableMapping(final OutputStreamWriter out, final ShaderNode shaderNode, - final VariableMapping mapping, final Collection matParams) + private void writeVariableMapping(final Writer out, final ShaderNode shaderNode, final VariableMapping mapping, final Collection matParams) throws IOException { final ShaderNodeVariable leftVar = mapping.getLeftVariable(); @@ -269,7 +293,7 @@ private String formatCondition(String condition, Collection matParams) return res; } - private void writeRenderStateAttribute(OutputStreamWriter out, String name, String value) throws IOException { + private void writeRenderStateAttribute(Writer out, String name, String value) throws IOException { out.write(" "); out.write(name); out.write(" "); @@ -277,7 +301,7 @@ private void writeRenderStateAttribute(OutputStreamWriter out, String name, Stri out.write("\n"); } - private void writeRenderState(RenderState rs, OutputStreamWriter out) throws IOException { + private void writeRenderState(RenderState rs, Writer out) throws IOException { RenderState defRs = RenderState.DEFAULT; if(rs.getBlendMode() != defRs.getBlendMode()) { writeRenderStateAttribute(out, "Blend", rs.getBlendMode().name()); diff --git a/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/package-info.java b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/package-info.java new file mode 100644 index 0000000000..b8ebfac651 --- /dev/null +++ b/jme3-plugins/src/main/java/com/jme3/material/plugin/export/materialdef/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * export a material definition using the J3MD file format + */ +package com.jme3.material.plugin.export.materialdef; diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java index 523993f51e..7a97de7ed7 100644 --- a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrBoneWeightIndex.java @@ -42,9 +42,9 @@ public IrBoneWeightIndex(int boneIndex, float boneWeight) { } @Override - public Object clone() { + public IrBoneWeightIndex clone() { try { - return super.clone(); + return (IrBoneWeightIndex)super.clone(); } catch (CloneNotSupportedException ex) { throw new AssertionError(ex); } diff --git a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java index 7b20e1670d..30338d27fd 100644 --- a/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java +++ b/jme3-plugins/src/main/java/com/jme3/scene/plugins/IrUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2015 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,7 +71,7 @@ private static IrPolygon[] quadToTri(IrPolygon quad) { IrVertex v2 = quad.vertices[2]; IrVertex v3 = quad.vertices[3]; - // find the pair of verticies that is closest to each over + // find the pair of vertices that is closest to each over // v0 and v2 // OR // v1 and v3 @@ -102,7 +102,10 @@ private static IrPolygon[] quadToTri(IrPolygon quad) { } /** - * Applies smoothing groups to vertex normals. + * Applies smoothing groups to vertex normals. XXX not implemented! + * + * @param mesh ignored + * @return null */ public static IrMesh applySmoothingGroups(IrMesh mesh) { return null; @@ -125,6 +128,11 @@ public static void toTangentsWithParity(IrMesh mesh) { } } + /** + * Removes low bone weights from mesh, leaving only 4 bone weights at max. + * + * @param vertex the IrVertex to modify (not null) + */ private static void trimBoneWeights(IrVertex vertex) { if (vertex.boneWeightsIndices == null) { return; @@ -163,6 +171,8 @@ private static void trimBoneWeights(IrVertex vertex) { /** * Removes low bone weights from mesh, leaving only 4 bone weights at max. + * + * @param mesh the IrMesh to modify (not null) */ public static void trimBoneWeights(IrMesh mesh) { for (IrPolygon polygon : mesh.polygons) { @@ -174,9 +184,11 @@ public static void trimBoneWeights(IrMesh mesh) { /** * Convert mesh from quads / triangles to triangles only. + * + * @param mesh the input IrMesh (not null) */ public static void triangulate(IrMesh mesh) { - List newPolygons = new ArrayList(mesh.polygons.length); + List newPolygons = new ArrayList<>(mesh.polygons.length); for (IrPolygon inputPoly : mesh.polygons) { if (inputPoly.vertices.length == 4) { IrPolygon[] tris = quadToTri(inputPoly); @@ -185,7 +197,7 @@ public static void triangulate(IrMesh mesh) { } else if (inputPoly.vertices.length == 3) { newPolygons.add(inputPoly); } else { - // N-gon. We have to ignore it.. + // N-gon. We have to ignore it. logger.log(Level.WARNING, "N-gon encountered, ignoring. " + "The mesh may not appear correctly. " + "Triangulate your model prior to export."); @@ -200,9 +212,12 @@ public static void triangulate(IrMesh mesh) { * one material each. * * Polygons without a material will be added to key = -1. + * + * @param mesh the input IrMesh (not null) + * @return a new IntMap containing the resulting meshes */ public static IntMap splitByMaterial(IrMesh mesh) { - IntMap> materialToPolyList = new IntMap>(); + IntMap> materialToPolyList = new IntMap<>(); for (IrPolygon polygon : mesh.polygons) { int materialIndex = -1; for (IrVertex vertex : polygon.vertices) { @@ -223,7 +238,7 @@ public static IntMap splitByMaterial(IrMesh mesh) { } polyList.add(polygon); } - IntMap materialToMesh = new IntMap(); + IntMap materialToMesh = new IntMap<>(); for (IntMap.Entry> entry : materialToPolyList) { int key = entry.getKey(); List polygons = entry.getValue(); @@ -239,11 +254,14 @@ public static IntMap splitByMaterial(IrMesh mesh) { /** * Convert IrMesh to jME3 mesh. + * + * @param mesh the input IrMesh (not null) + * @return a new Mesh */ public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) { - Map vertexToVertexIndex = new HashMap(); - List vertices = new ArrayList(); - List indexes = new ArrayList(); + Map vertexToVertexIndex = new HashMap<>(); + List vertices = new ArrayList<>(); + List indexes = new ArrayList<>(); int vertexIndex = 0; for (IrPolygon polygon : mesh.polygons) { @@ -326,7 +344,7 @@ public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) { jmeMesh.setBuffer(indicesHW); } if (vertices.size() >= 65536) { - // too many verticies: use intbuffer instead of shortbuffer + // too many vertices: use IntBuffer instead of ShortBuffer IntBuffer ib = BufferUtils.createIntBuffer(indexes.size()); jmeMesh.setBuffer(VertexBuffer.Type.Index, 3, ib); indexBuf = new IndexIntBuffer(ib); @@ -363,7 +381,7 @@ public static Mesh convertIrMeshToJmeMesh(IrMesh mesh) { if (vertex.boneWeightsIndices != null) { if (vertex.boneWeightsIndices.length > 4) { throw new UnsupportedOperationException("Mesh uses more than 4 weights per bone. " + - "Call trimBoneWeights() to allieviate this"); + "Call trimBoneWeights() to alleviate this"); } for (int i = 0; i < vertex.boneWeightsIndices.length; i++) { boneIndices.put((byte) (vertex.boneWeightsIndices[i].boneIndex & 0xFF)); diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java index ed11ddd253..5c0b33ed63 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MaterialLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -63,7 +63,7 @@ public class MaterialLoader implements AssetLoader { private Texture[] textures = new Texture[4]; private String texName; private String matName; - private float shinines; + private float shininess; private boolean vcolor = false; private boolean blend = false; private boolean twoSide = false; @@ -247,11 +247,11 @@ private void readPassStatement(Statement statement){ if (subsplit.length >= 5){ // using 5 float values specular.a = unknown; - shinines = Float.parseFloat(subsplit[4]); + shininess = Float.parseFloat(subsplit[4]); }else{ // using 4 float values specular.a = 1f; - shinines = unknown; + shininess = unknown; } }else if (keyword.equals("texture_unit")){ readTextureUnit(statement); @@ -363,8 +363,8 @@ private Material compileMaterial(){ } if (!noLight){ - if (shinines > 0f) { - mat.setFloat("Shininess", shinines); + if (shininess > 0f) { + mat.setFloat("Shininess", shininess); } else { mat.setFloat("Shininess", 16f); // set shininess to some value anyway.. } @@ -432,7 +432,7 @@ private Material compileMaterial(){ diffuse = null; specular = null; emissive = null; - shinines = 0f; + shininess = 0f; vcolor = false; blend = false; texUnit = 0; @@ -483,6 +483,7 @@ private MaterialList load(AssetManager assetManager, AssetKey key, InputStream i return list; } + @Override public Object load(AssetInfo info) throws IOException { InputStream in = null; try { diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java index 01474b5c46..4cb7967d94 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/MeshLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -98,9 +98,9 @@ public class MeshLoader extends DefaultHandler implements AssetLoader { private int meshIndex = 0; private int texCoordIndex = 0; private String ignoreUntilEnd = null; - private List geoms = new ArrayList(); - private ArrayList usesSharedMesh = new ArrayList(); - private IntMap> lodLevels = new IntMap>(); + private List geoms = new ArrayList<>(); + private ArrayList usesSharedMesh = new ArrayList<>(); + private IntMap> lodLevels = new IntMap<>(); private AnimData animData; public MeshLoader() { @@ -285,17 +285,17 @@ private void startSubMesh(String matName, String usesharedvertices, String use32 geoms.add(geom); } - private void startSharedGeom(String vertexcount) throws SAXException { + private void startSharedGeom(String vertexCount) throws SAXException { sharedMesh = new Mesh(); - vertCount = parseInt(vertexcount); + vertCount = parseInt(vertexCount); usesSharedVerts = false; geom = null; mesh = sharedMesh; } - private void startGeometry(String vertexcount) throws SAXException { - vertCount = parseInt(vertexcount); + private void startGeometry(String vertexCount) throws SAXException { + vertCount = parseInt(vertexCount); } /** @@ -341,7 +341,7 @@ private void endBoneAssigns() { float sum = w0 + w1 + w2 + w3; if (sum != 1f) { weightsFloatData.position(weightsFloatData.position() - 4); - // compute new vals based on sum + // Compute new weights based on sum. float sumToB = sum == 0 ? 0 : 1f / sum; weightsFloatData.put(w0 * sumToB); weightsFloatData.put(w1 * sumToB); @@ -519,10 +519,10 @@ private void pushColor(Attributes attribs) throws SAXException { buf.put(color.r).put(color.g).put(color.b).put(color.a); } - private void startLodFaceList(String submeshindex, String numfaces) { - int index = Integer.parseInt(submeshindex); + private void startLodFaceList(String submeshIndex, String numFaces) { + int index = Integer.parseInt(submeshIndex); mesh = geoms.get(index).getMesh(); - int faceCount = Integer.parseInt(numfaces); + int faceCount = Integer.parseInt(numFaces); VertexBuffer originalIndexBuffer = mesh.getBuffer(Type.Index); vb = new VertexBuffer(VertexBuffer.Type.Index); @@ -549,8 +549,8 @@ private void startLodFaceList(String submeshindex, String numfaces) { levels.add(vb); } - private void startLevelOfDetail(String numlevels) { -// numLevels = Integer.parseInt(numlevels); + private void startLevelOfDetail(String numLevels) { +// numLevels = Integer.parseInt(numLevels); } private void endLevelOfDetail() { @@ -595,9 +595,9 @@ private void pushBoneAssign(String vertIndex, String boneIndex, String weight) t } private void startSkeleton(String name) { - AssetKey assetKey = new AssetKey(folderName + name + ".xml"); + AssetKey assetKey = new AssetKey<>(folderName + name + ".xml"); try { - animData = (AnimData) assetManager.loadAsset(assetKey); + animData = assetManager.loadAsset(assetKey); } catch (AssetNotFoundException ex) { logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{assetKey, key}); animData = null; @@ -716,7 +716,7 @@ public void endElement(String uri, String name, String qName) { geom = null; mesh = null; } else if (qName.equals("submeshes") && !submeshNamesHack) { - // IMPORTANT: restore sharedmesh, for use with shared boneweights + // IMPORTANT: restore shared mesh, for use with shared bone weights geom = null; mesh = sharedMesh; usesSharedVerts = false; @@ -817,6 +817,7 @@ private Node compileModel() { return model; } + @Override public Object load(AssetInfo info) throws IOException { try { key = info.getKey(); @@ -840,7 +841,7 @@ public Object load(AssetInfo info) throws IOException { if (materialList == null && materialName != null) { OgreMaterialKey materialKey = new OgreMaterialKey(folderName + materialName + ".material"); try { - materialList = (MaterialList) assetManager.loadAsset(materialKey); + materialList = assetManager.loadAsset(materialKey); } catch (AssetNotFoundException e) { logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key}); } @@ -857,7 +858,7 @@ public Object load(AssetInfo info) throws IOException { if (materialList == null) { OgreMaterialKey materialKey = new OgreMaterialKey(folderName + meshName + ".material"); try { - materialList = (MaterialList) assetManager.loadAsset(materialKey); + materialList = assetManager.loadAsset(materialKey); } catch (AssetNotFoundException e) { logger.log(Level.WARNING, "Cannot locate {0} for model {1}", new Object[]{materialKey, key}); } @@ -886,11 +887,7 @@ public Object load(AssetInfo info) throws IOException { } return compileModel(); - } catch (SAXException ex) { - IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml"); - ioEx.initCause(ex); - throw ioEx; - } catch (ParserConfigurationException ex) { + } catch (SAXException | ParserConfigurationException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D mesh.xml"); ioEx.initCause(ex); throw ioEx; diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java index 4c26eb5f4b..01b2f52b98 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -71,7 +71,7 @@ public class SceneLoader extends DefaultHandler implements AssetLoader { private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); private SceneMaterialLoader materialLoader = new SceneMaterialLoader(); private SceneMeshLoader meshLoader=new SceneMeshLoader(); - private Stack elementStack = new Stack(); + private Stack elementStack = new Stack<>(); private AssetKey key; private String sceneName; private String folderName; @@ -99,7 +99,7 @@ public void endDocument() { } private void reset() { - meshLoader.reset(); + meshLoader.reset(); elementStack.clear(); nodeIdx = 0; @@ -262,7 +262,7 @@ private void parseCamera(Attributes attribs) throws SAXException { } float fov = SAXUtil.parseFloat(attribs.getValue("fov"), 45f); if (fov < FastMath.PI) { - // XXX: Most likely, it is in radians.. + // XXX: Most likely it is in radians fov = fov * FastMath.RAD_TO_DEG; } camera.setFrustumPerspective(fov, (float)DEFAULT_CAM_WIDTH / DEFAULT_CAM_HEIGHT, 1, 1000); @@ -300,12 +300,12 @@ private void parseEntity(Attributes attribs) throws SAXException { entityNode = new com.jme3.scene.Node(name); OgreMeshKey meshKey = new OgreMeshKey(meshFile, materialList); try { - try{ - Spatial ogreMesh=(Spatial)meshLoader.load(assetManager.locateAsset(meshKey)); - entityNode.attachChild(ogreMesh); - }catch(IOException e){ - throw new AssetNotFoundException(meshKey.toString()); - } + try{ + Spatial ogreMesh=(Spatial)meshLoader.load(assetManager.locateAsset(meshKey)); + entityNode.attachChild(ogreMesh); + }catch(IOException e){ + throw new AssetNotFoundException(meshKey.toString()); + } } catch (AssetNotFoundException ex) { if (ex.getMessage().equals(meshFile)) { logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{meshKey, key}); @@ -423,7 +423,7 @@ public void startElement(String uri, String localName, String qName, Attributes if (elementStack.peek().equals("environment")) { ColorRGBA color = parseColor(attribs); if (!color.equals(ColorRGBA.Black) && !color.equals(ColorRGBA.BlackNoAlpha)) { - // Lets add an ambient light to the scene. + // Let's add an ambient light to the scene. AmbientLight al = new AmbientLight(); al.setColor(color); root.addLight(al); @@ -454,7 +454,7 @@ public void endElement(String uri, String name, String qName) throws SAXExceptio node = cameraNode.getParent(); cameraNode = null; } else if (qName.equals("light")) { - // apply the node's world transform on the light.. + // Apply the node's world transform to the light. root.updateGeometricState(); if (light != null) { if (light instanceof DirectionalLight) { @@ -489,6 +489,7 @@ public void endElement(String uri, String name, String qName) throws SAXExceptio public void characters(char ch[], int start, int length) { } + @Override public Object load(AssetInfo info) throws IOException { try { key = info.getKey(); @@ -509,7 +510,7 @@ public Object load(AssetInfo info) throws IOException { // (Backward compatibility only!) OgreMaterialKey materialKey = new OgreMaterialKey(sceneName + ".material"); try { - materialList = (MaterialList) assetManager.loadAsset(materialKey); + materialList = assetManager.loadAsset(materialKey); } catch (AssetNotFoundException ex) { logger.log(Level.WARNING, "Cannot locate {0} for scene {1}", new Object[]{materialKey, key}); materialList = null; @@ -542,11 +543,7 @@ public Object load(AssetInfo info) throws IOException { } return root; - } catch (SAXException ex) { - IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); - ioEx.initCause(ex); - throw ioEx; - } catch (ParserConfigurationException ex) { + } catch (SAXException | ParserConfigurationException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); ioEx.initCause(ex); throw ioEx; diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java index 3507de2f7c..542e99c740 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMaterialLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ class SceneMaterialLoader extends DefaultHandler { private static final Logger logger = Logger.getLogger(SceneMaterialLoader.class.getName()); - private Stack elementStack = new Stack(); + private Stack elementStack = new Stack<>(); private String folderName; private MaterialList materialList; private AssetManager assetManager; @@ -100,7 +100,7 @@ public void startElement(String uri, String localName, String qName, Attributes String materialName = new File(materialPath).getName(); String matFile = folderName + materialName; try { - MaterialList loadedMaterialList = (MaterialList) assetManager.loadAsset(new OgreMaterialKey(matFile)); + MaterialList loadedMaterialList = assetManager.loadAsset(new OgreMaterialKey(matFile)); materialList.putAll(loadedMaterialList); } catch (AssetNotFoundException ex) { logger.log(Level.WARNING, "Cannot locate material file: {0}", matFile); @@ -145,11 +145,7 @@ public MaterialList load(AssetManager assetManager, String folderName, InputStre } return materialList; - } catch (SAXException ex) { - IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); - ioEx.initCause(ex); - throw ioEx; - } catch (ParserConfigurationException ex) { + } catch (SAXException | ParserConfigurationException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); ioEx.initCause(ex); throw ioEx; diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java index ecdc728eaf..5ea589d249 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SceneMeshLoader.java @@ -9,18 +9,18 @@ import com.jme3.scene.Spatial; public class SceneMeshLoader extends MeshLoader{ - private Map cache=new HashMap(); - @Override + private Map cache=new HashMap<>(); + @Override public Object load(AssetInfo info) throws IOException { - AssetKey key=info.getKey(); - Spatial output=cache.get(key); - if(output==null){ - output=(Spatial)super.load(info); - cache.put(key,output); - } - return output.clone(false); - } - public void reset(){ - cache.clear(); - } + AssetKey key=info.getKey(); + Spatial output=cache.get(key); + if(output==null){ + output=(Spatial)super.load(info); + cache.put(key,output); + } + return output.clone(false); + } + public void reset(){ + cache.clear(); + } } diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java index 59be5cf7d5..728580523a 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/SkeletonLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,7 +51,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { private static final Logger logger = Logger.getLogger(SceneLoader.class.getName()); //private AssetManager assetManager; - private Stack elementStack = new Stack(); + private Stack elementStack = new Stack<>(); private HashMap indexToJoint = new HashMap<>(); private HashMap nameToJoint = new HashMap<>(); private TransformTrack track; @@ -72,6 +72,7 @@ public class SkeletonLoader extends DefaultHandler implements AssetLoader { private Vector3f axis; private List unusedJoints = new ArrayList<>(); + @Override public void startElement(String uri, String localName, String qName, Attributes attribs) throws SAXException { if (qName.equals("position") || qName.equals("translate")) { position = SAXUtil.parseVector3(attribs); @@ -92,8 +93,7 @@ public void startElement(String uri, String localName, String qName, Attributes assert elementStack.peek().equals("tracks"); String jointName = SAXUtil.parseString(attribs.getValue("bone")); joint = nameToJoint.get(jointName); - track = new TransformTrack(); - track.setTarget(joint); + track = new TransformTrack(joint, null, null, null, null); } else if (qName.equals("boneparent")) { assert elementStack.peek().equals("bonehierarchy"); String jointName = attribs.getValue("bone"); @@ -132,6 +132,7 @@ public void startElement(String uri, String localName, String qName, Attributes elementStack.add(qName); } + @Override public void endElement(String uri, String name, String qName) { if (qName.equals("translate") || qName.equals("position") || qName.equals("scale")) { } else if (qName.equals("axis")) { @@ -162,6 +163,7 @@ public void endElement(String uri, String name, String qName) { indexToJoint.clear(); armature = new Armature(joints); armature.saveBindPose(); + armature.saveInitialPose(); } else if (qName.equals("animation")) { animClips.add(animClip); animClip = null; @@ -274,12 +276,7 @@ public Object load(InputStream in) throws IOException { armature = null; animClips = null; return data; - } catch (SAXException ex) { - IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); - ioEx.initCause(ex); - fullReset(); - throw ioEx; - } catch (ParserConfigurationException ex) { + } catch (SAXException | ParserConfigurationException ex) { IOException ioEx = new IOException("Error while parsing Ogre3D dotScene"); ioEx.initCause(ex); fullReset(); @@ -288,6 +285,7 @@ public Object load(InputStream in) throws IOException { } + @Override public Object load(AssetInfo info) throws IOException { //AssetManager assetManager = info.getManager(); InputStream in = null; diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java index 5c68f5a789..cf55eef2a3 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,7 +41,7 @@ public class MaterialExtension { private String baseMatName; private String jmeMatDefName; - private HashMap textureMappings = new HashMap(); + private HashMap textureMappings = new HashMap<>(); /** * Material extension defines a mapping from an Ogre3D "base" material diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java index b153da37ee..9033aed447 100644 --- a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/matext/MaterialExtensionSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -42,8 +42,8 @@ */ public class MaterialExtensionSet { - private HashMap extensions = new HashMap(); - private HashMap> nameMappings = new HashMap>(); + private HashMap extensions = new HashMap<>(); + private HashMap> nameMappings = new HashMap<>(); /** * Adds a new material extension to the set of extensions. diff --git a/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/package-info.java b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/package-info.java new file mode 100644 index 0000000000..4331630743 --- /dev/null +++ b/jme3-plugins/src/ogre/java/com/jme3/scene/plugins/ogre/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +/** + * import 3-D models in formats defined by the OGRE 3-D engine + */ +package com.jme3.scene.plugins.ogre; diff --git a/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java b/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java new file mode 100644 index 0000000000..74edd66d5a --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/export/InputOutputCapsuleTest.java @@ -0,0 +1,717 @@ +/* + * Copyright (c) 2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import java.util.List; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.BitSet; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.File; +import java.io.PrintWriter; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.nio.IntBuffer; +import java.nio.FloatBuffer; +import java.util.Iterator; + +import org.junit.Assert; +import org.junit.Test; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.AssetKey; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.export.xml.XMLImporter; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.util.BufferUtils; +import com.jme3.util.IntMap; +import com.jme3.math.Vector3f; +import com.jme3.math.Quaternion; +import com.jme3.math.Transform; +import com.jme3.math.Matrix4f; + +/** + * Test suite for implementations of the JmeExporter and JmeImporter interfaces. + * There are tests here for all write* and read* methods of the OutputCapsule and InputCapsule interfaces respectively. + */ +public class InputOutputCapsuleTest { + private static final List exporters = new ArrayList<>(); + private static final List importers = new ArrayList<>(); + static { + exporters.add(new BinaryExporter()); + importers.add(new BinaryImporter()); + + exporters.add(new XMLExporter()); + importers.add(new XMLImporter()); + + // add any future implementations here + } + + @Test + public void testPrimitives() { + saveAndLoad(new TestPrimitives()); + } + + @Test + public void testStrings() { + saveAndLoad(new TestStrings()); + } + + @Test + public void testEnums() { + saveAndLoad(new TestEnums()); + } + + @Test + public void testBitSets() { + saveAndLoad(new TestBitSets()); + } + + @Test + public void testSavables() { + saveAndLoad(new TestSavables()); + } + + @Test + public void testSavableReferences() { + saveAndLoad(new TestSavableReferences()); + } + + @Test + public void testArrays() { + saveAndLoad(new TestArrays()); + } + + @Test + public void testBuffers() { + saveAndLoad(new TestBuffers()); + } + + @Test + public void testLists() { + saveAndLoad(new TestLists()); + } + + @Test + public void testMaps() { + saveAndLoad(new TestMaps()); + } + + // attempts to save and load a Savable using the JmeExporter/JmeImporter implementations listed at the top of this class. + // the Savable inner classes in this file run assertions in their read() methods + // to ensure the data loaded is the same as what was written. more or less stole this from JmeExporterTest.java + private static void saveAndLoad(Savable savable) { + for (int i = 0; i < exporters.size(); i++) { + JmeExporter exporter = exporters.get(i); + JmeImporter importer = importers.get(i); + + // export + byte[] exportedBytes = null; + try (ByteArrayOutputStream outStream = new ByteArrayOutputStream()) { + exporter.save(savable, outStream); + exportedBytes = outStream.toByteArray(); + } catch (IOException e) { + Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString()); + } + + // write the xml into files for debugging. + // leave this commented out unless you need it since it makes a mess of the jme3-plugins directory. + /*if (exporter instanceof XMLExporter) { + try { + File outFile = new File(savable.getClass().getSimpleName() + ".xml"); + outFile.createNewFile(); + PrintWriter out = new PrintWriter(outFile); + out.print(new String(exportedBytes)); + out.close(); + } catch(IOException ioEx) { + + } + }*/ + + // import + try (ByteArrayInputStream inStream = new ByteArrayInputStream(exportedBytes)) { + AssetInfo info = new AssetInfo(null, null) { + @Override + public InputStream openStream() { + return inStream; + } + }; + importer.load(info); // this is where assertions will fail if loaded data does not match saved data. + } catch (IOException e) { + Assert.fail(exporter.getClass().getSimpleName() + ": " + e.toString()); + } + } + } + + // test data. I tried to include as many edge cases as I could think of. + private static final byte[] testByteArray = new byte[] {Byte.MIN_VALUE, Byte.MAX_VALUE}; + private static final short[] testShortArray = new short[] {Short.MIN_VALUE, Short.MAX_VALUE}; + private static final int[] testIntArray = new int[] {Integer.MIN_VALUE, Integer.MAX_VALUE}; + private static final long[] testLongArray = new long[] {Long.MIN_VALUE, Long.MAX_VALUE}; + private static final float[] testFloatArray = new float[] { + Float.MIN_VALUE, Float.MAX_VALUE, Float.MIN_NORMAL, + Float.POSITIVE_INFINITY, Float.NEGATIVE_INFINITY, Float.NaN + }; + private static final double[] testDoubleArray = new double[] { + Double.MIN_VALUE, Double.MAX_VALUE, Double.MIN_NORMAL, + Double.POSITIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NaN + }; + private static final boolean[] testBooleanArray = new boolean[] {false, true}; + private static final String[] testStringArray = new String[] { + "hello, world!", + null, + "", + " ", // blank string (whitespace) + "mind the gap", // multiple consecutive spaces (some xml processors would normalize this to a single space) + //new String(new char[10_000_000]).replace('\0', 'a'), // long string. kinda slows down the test too much so I'm leaving it out for now. + "\t", + "\n", + "\r", + "hello こんにちは 你好 Здравствуйте 안녕하세요 🙋", + "' " < > &", // xml entities + // xml metacharacters + "\'", + "\"", + "<", + ">", + "&", + "", + "]]>" // xml close cdata + }; + + private static final Savable[] testSavableArray = new Savable[] { + new Vector3f(0f, 1f, 2f), + null, + new Quaternion(0f, 1f, 2f, 3f), + new Transform(new Vector3f(1f, 2f, 3f), new Quaternion(1f, 2f, 3f, 4f), new Vector3f(1f, 2f, 3f)), + new Matrix4f() + }; + + private static final byte[][] testByteArray2D = new byte[][] { + testByteArray, + null, + new byte[0] + }; + + private static final short[][] testShortArray2D = new short[][] { + testShortArray, + null, + new short[0] + }; + + private static final int[][] testIntArray2D = new int[][] { + testIntArray, + null, + new int[0] + }; + + private static final long[][] testLongArray2D = new long[][] { + testLongArray, + null, + new long[0] + }; + + private static final float[][] testFloatArray2D = new float[][] { + testFloatArray, + null, + new float[0] + }; + + private static final double[][] testDoubleArray2D = new double[][] { + testDoubleArray, + null, + new double[0] + }; + + private static final boolean[][] testBooleanArray2D = new boolean[][] { + testBooleanArray, + null, + new boolean[0] + }; + + private static final String[][] testStringArray2D = new String[][] { + testStringArray, + null, + new String[0] + }; + + private static final CullHint[] testEnumArray = new CullHint[] { + CullHint.Never, + null, + CullHint.Always, + CullHint.Inherit + }; + + private static final BitSet[] testBitSetArray = new BitSet[] { + BitSet.valueOf("BitSet".getBytes()), + null, + new BitSet() + }; + + private static final Savable[][] testSavableArray2D = new Savable[][] { + testSavableArray, + null, + new Savable[0] + }; + + private static final ByteBuffer testByteBuffer = (ByteBuffer) BufferUtils.createByteBuffer(testByteArray).rewind(); + private static final ShortBuffer testShortBuffer = (ShortBuffer) BufferUtils.createShortBuffer(testShortArray).rewind(); + private static final IntBuffer testIntBuffer = (IntBuffer) BufferUtils.createIntBuffer(testIntArray).rewind(); + private static final FloatBuffer testFloatBuffer = (FloatBuffer) BufferUtils.createFloatBuffer(testFloatArray).rewind(); + + private static final ArrayList testByteBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createByteBuffer(testByteArray2D[0]), + null, + BufferUtils.createByteBuffer(testByteArray2D[2]) + )); + + private static final ArrayList testFloatBufferArrayList = new ArrayList<>(Arrays.asList( + BufferUtils.createFloatBuffer(testFloatArray2D[0]), + null, + BufferUtils.createFloatBuffer(testFloatArray2D[2]) + )); + + private static final ArrayList testSavableArrayList = new ArrayList<>(Arrays.asList(testSavableArray)); + + @SuppressWarnings("unchecked") + private static final ArrayList[] testSavableArrayListArray = new ArrayList[] { + testSavableArrayList, + null, + new ArrayList() + }; + + // "array" and "list" don't sound like real words anymore + @SuppressWarnings("unchecked") + private static final ArrayList[][] testSavableArrayListArray2D = new ArrayList[][] { + testSavableArrayListArray, + null, + {}, + }; + + private static final Map testSavableMap = new HashMap() {{ + put(Vector3f.UNIT_X, Vector3f.UNIT_X); + put(Vector3f.UNIT_Y, Quaternion.IDENTITY); + put(Vector3f.UNIT_Z, null); + }}; + + private static final Map testStringSavableMap = new HashMap() {{ + put("v", Vector3f.UNIT_X); + put("q", Quaternion.IDENTITY); + put("n", null); + }}; + + private static final IntMap testIntSavableMap = new IntMap(); + static { //IntMap is final so we gotta use a static block here. + testIntSavableMap.put(0, Vector3f.UNIT_X); + testIntSavableMap.put(1, Quaternion.IDENTITY); + testIntSavableMap.put(2, null); + } + + // the rest of this file is inner classes that implement Savable. + // these classes write the test data, then verify that it's the same data in their read() methods. + private static class TestPrimitives implements Savable { + TestPrimitives() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testByteArray.length; i++) + capsule.write(testByteArray[i], "test_byte_" + i, (byte) 0); + + for (int i = 0; i < testShortArray.length; i++) + capsule.write(testShortArray[i], "test_short_" + i, (short) 0); + + for (int i = 0; i < testIntArray.length; i++) + capsule.write(testIntArray[i], "test_int_" + i, 0); + + for (int i = 0; i < testLongArray.length; i++) + capsule.write(testLongArray[i], "test_long_" + i, 0l); + + for (int i = 0; i < testFloatArray.length; i++) + capsule.write(testFloatArray[i], "test_float_" + i, 0f); + + for (int i = 0; i < testDoubleArray.length; i++) + capsule.write(testDoubleArray[i], "test_double_" + i, 0d); + + for (int i = 0; i < testBooleanArray.length; i++) + capsule.write(testBooleanArray[i], "test_boolean_" + i, false); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testByteArray.length; i++) + Assert.assertEquals("readByte()", testByteArray[i], capsule.readByte("test_byte_" + i, (byte) 0)); + + for (int i = 0; i < testShortArray.length; i++) + Assert.assertEquals("readShort()", testShortArray[i], capsule.readShort("test_short_" + i, (short) 0)); + + for (int i = 0; i < testIntArray.length; i++) + Assert.assertEquals("readInt()", testIntArray[i], capsule.readInt("test_int_" + i, 0)); + + for (int i = 0; i < testLongArray.length; i++) + Assert.assertEquals("readLong()", testLongArray[i], capsule.readLong("test_long_" + i, 0l)); + + for (int i = 0; i < testFloatArray.length; i++) + Assert.assertEquals("readFloat()", testFloatArray[i], capsule.readFloat("test_float_" + i, 0f), 0f); + + for (int i = 0; i < testDoubleArray.length; i++) + Assert.assertEquals("readDouble()", testDoubleArray[i], capsule.readDouble("test_double_" + i, 0d), 0d); + + for (int i = 0; i < testBooleanArray.length; i++) + Assert.assertEquals("readBoolean()", testBooleanArray[i], capsule.readBoolean("test_boolean_" + i, false)); + } + } + + private static class TestStrings implements Savable { + TestStrings() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testStringArray.length; i++) { + capsule.write(testStringArray[i], "test_string_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testStringArray.length; i++) { + Assert.assertEquals("readString()", testStringArray[i], capsule.readString("test_string_" + i, null)); + } + } + } + + private static class TestEnums implements Savable { + TestEnums() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testEnumArray.length; i++) { + capsule.write(testEnumArray[i], "test_enum_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testEnumArray.length; i++) { + Assert.assertEquals("readEnum()", testEnumArray[i], capsule.readEnum("test_enum_" + i, CullHint.class, null)); + } + } + } + + private static class TestBitSets implements Savable { + TestBitSets() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for (int i = 0; i < testBitSetArray.length; i++) { + capsule.write(testBitSetArray[i], "test_bit_set_" + i, null); + } + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for (int i = 0; i < testBitSetArray.length; i++) { + Assert.assertEquals("readBitSet()", testBitSetArray[i], capsule.readBitSet("test_bit_set_" + i, null)); + } + } + } + + private static class TestSavables implements Savable { + TestSavables() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + for(int i = 0; i < testSavableArray.length; i++) + capsule.write(testSavableArray[i], "test_savable_" + i, null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + for(int i = 0; i < testSavableArray.length; i++) + Assert.assertEquals("readSavable()", testSavableArray[i], capsule.readSavable("test_savable_" + i, null)); + } + } + + private static class TestSavableReferences implements Savable { + TestSavableReferences() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + Vector3f v1 = new Vector3f(1f, 2f, 3f); + Vector3f notV1 = v1.clone(); + + capsule.write(v1, "v1", null); + capsule.write(v1, "also_v1", null); + capsule.write(notV1, "not_v1", null); + + // testing reference loop. this used to cause infinite recursion. + Node n1 = new Node("node_1"); + Node n2 = new Node("node_2"); + + n1.setUserData("node_2", n2); + n2.setUserData("node_1", n1); + + capsule.write(n1, "node_1", null); + capsule.write(n2, "node_2", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Vector3f v1 = (Vector3f) capsule.readSavable("v1", null); + Vector3f alsoV1 = (Vector3f) capsule.readSavable("also_v1", null); + Vector3f notV1 = (Vector3f) capsule.readSavable("not_v1", null); + + Assert.assertTrue("readSavable() savable duplicated, references not preserved.", v1 == alsoV1); + Assert.assertTrue("readSavable() unique savables merged, unexpected shared references.", v1 != notV1); + + Node n1 = (Node) capsule.readSavable("node_1", null); + Node n2 = (Node) capsule.readSavable("node_2", null); + + Assert.assertTrue("readSavable() reference loop not preserved.", n1.getUserData("node_2") == n2 && n2.getUserData("node_1") == n1); + } + } + + private static class TestArrays implements Savable { + TestArrays() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.write(testByteArray, "testByteArray", null); + capsule.write(testShortArray, "testShortArray", null); + capsule.write(testIntArray, "testIntArray", null); + capsule.write(testLongArray, "testLongArray", null); + capsule.write(testFloatArray, "testFloatArray", null); + capsule.write(testDoubleArray, "testDoubleArray", null); + capsule.write(testBooleanArray, "testBooleanArray", null); + capsule.write(testStringArray, "testStringArray", null); + capsule.write(testSavableArray, "testSavableArray", null); + + capsule.write(new byte[0], "emptyByteArray", null); + capsule.write(new short[0], "emptyShortArray", null); + capsule.write(new int[0], "emptyIntArray", null); + capsule.write(new long[0], "emptyLongArray", null); + capsule.write(new float[0], "emptyFloatArray", null); + capsule.write(new double[0], "emptyDoubleArray", null); + capsule.write(new boolean[0], "emptyBooleanArray", null); + capsule.write(new String[0], "emptyStringArray", null); + capsule.write(new Savable[0], "emptySavableArray", null); + + capsule.write(testByteArray2D, "testByteArray2D", null); + capsule.write(testShortArray2D, "testShortArray2D", null); + capsule.write(testIntArray2D, "testIntArray2D", null); + capsule.write(testLongArray2D, "testLongArray2D", null); + capsule.write(testFloatArray2D, "testFloatArray2D", null); + capsule.write(testDoubleArray2D, "testDoubleArray2D", null); + capsule.write(testBooleanArray2D, "testBooleanArray2D", null); + capsule.write(testStringArray2D, "testStringArray2D", null); + capsule.write(testSavableArray2D, "testSavableArray2D", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertArrayEquals("readByteArray()", testByteArray, capsule.readByteArray("testByteArray", null)); + Assert.assertArrayEquals("readShortArray()", testShortArray, capsule.readShortArray("testShortArray", null)); + Assert.assertArrayEquals("readIntArray()", testIntArray, capsule.readIntArray("testIntArray", null)); + Assert.assertArrayEquals("readLongArray()", testLongArray, capsule.readLongArray("testLongArray", null)); + Assert.assertArrayEquals("readFloatArray()", testFloatArray, capsule.readFloatArray("testFloatArray", null), 0f); + Assert.assertArrayEquals("readDoubleArray()", testDoubleArray, capsule.readDoubleArray("testDoubleArray", null), 0d); + Assert.assertArrayEquals("readBooleanArray()", testBooleanArray, capsule.readBooleanArray("testBooleanArray", null)); + Assert.assertArrayEquals("readStringArray()", testStringArray, capsule.readStringArray("testStringArray", null)); + Assert.assertArrayEquals("readSavableArray()", testSavableArray, capsule.readSavableArray("testSavableArray", null)); + + Assert.assertArrayEquals("readByteArray()", new byte[0], capsule.readByteArray("emptyByteArray", null)); + Assert.assertArrayEquals("readShortArray()", new short[0], capsule.readShortArray("emptyShortArray", null)); + Assert.assertArrayEquals("readIntArray()", new int[0], capsule.readIntArray("emptyIntArray", null)); + Assert.assertArrayEquals("readLongArray()", new long[0], capsule.readLongArray("emptyLongArray", null)); + Assert.assertArrayEquals("readFloatArray()", new float[0], capsule.readFloatArray("emptyFloatArray", null), 0f); + Assert.assertArrayEquals("readDoubleArray()", new double[0], capsule.readDoubleArray("emptyDoubleArray", null), 0d); + Assert.assertArrayEquals("readBooleanArray()", new boolean[0], capsule.readBooleanArray("emptyBooleanArray", null)); + Assert.assertArrayEquals("readStringArray()", new String[0], capsule.readStringArray("emptyStringArray", null)); + Assert.assertArrayEquals("readSavableArray()", new Savable[0], capsule.readSavableArray("emptySavableArray", null)); + + Assert.assertArrayEquals("readByteArray2D()", testByteArray2D, capsule.readByteArray2D("testByteArray2D", null)); + Assert.assertArrayEquals("readShortArray2D()", testShortArray2D, capsule.readShortArray2D("testShortArray2D", null)); + Assert.assertArrayEquals("readIntArray2D()", testIntArray2D, capsule.readIntArray2D("testIntArray2D", null)); + Assert.assertArrayEquals("readLongArray2D()", testLongArray2D, capsule.readLongArray2D("testLongArray2D", null)); + Assert.assertArrayEquals("readFloatArray2D()", testFloatArray2D, capsule.readFloatArray2D("testFloatArray2D", null)); + Assert.assertArrayEquals("readDoubleArray2D()", testDoubleArray2D, capsule.readDoubleArray2D("testDoubleArray2D", null)); + Assert.assertArrayEquals("readBooleanArray2D()", testBooleanArray2D, capsule.readBooleanArray2D("testBooleanArray2D", null)); + Assert.assertArrayEquals("readStringArray2D()", testStringArray2D, capsule.readStringArray2D("testStringArray2D", null)); + Assert.assertArrayEquals("readSavableArray2D()", testSavableArray2D, capsule.readSavableArray2D("testSavableArray2D", null)); + } + } + + private static class TestBuffers implements Savable { + TestBuffers() { + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.write(testByteBuffer, "testByteBuffer", null); + capsule.write(testShortBuffer, "testShortBuffer", null); + capsule.write(testIntBuffer, "testIntBuffer", null); + capsule.write(testFloatBuffer, "testFloatBuffer", null); + + capsule.write(BufferUtils.createByteBuffer(0), "emptyByteBuffer", null); + capsule.write(BufferUtils.createShortBuffer(0), "emptyShortBuffer", null); + capsule.write(BufferUtils.createIntBuffer(0), "emptyIntBuffer", null); + capsule.write(BufferUtils.createFloatBuffer(0), "emptyFloatBuffer", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readByteBuffer()", testByteBuffer, capsule.readByteBuffer("testByteBuffer", null)); + Assert.assertEquals("readShortBuffer()", testShortBuffer, capsule.readShortBuffer("testShortBuffer", null)); + Assert.assertEquals("readIntBuffer()", testIntBuffer, capsule.readIntBuffer("testIntBuffer", null)); + Assert.assertEquals("readFloatBuffer()", testFloatBuffer, capsule.readFloatBuffer("testFloatBuffer", null)); + + Assert.assertEquals("readByteBuffer()", BufferUtils.createByteBuffer(0), capsule.readByteBuffer("emptyByteBuffer", null)); + Assert.assertEquals("readShortBuffer()", BufferUtils.createShortBuffer(0), capsule.readShortBuffer("emptyShortBuffer", null)); + Assert.assertEquals("readIntBuffer()", BufferUtils.createIntBuffer(0), capsule.readIntBuffer("emptyIntBuffer", null)); + Assert.assertEquals("readFloatBuffer()", BufferUtils.createFloatBuffer(0), capsule.readFloatBuffer("emptyFloatBuffer", null)); + } + } + + private static class TestLists implements Savable { + TestLists() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.writeByteBufferArrayList(testByteBufferArrayList, "testByteBufferArrayList", null); + capsule.writeFloatBufferArrayList(testFloatBufferArrayList, "testFloatBufferArrayList", null); + capsule.writeSavableArrayList(testSavableArrayList, "testSavableArrayList", null); + capsule.writeSavableArrayListArray(testSavableArrayListArray, "testSavableArrayListArray", null); + capsule.writeSavableArrayListArray2D(testSavableArrayListArray2D, "testSavableArrayListArray2D", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readByteBufferArrayList()", testByteBufferArrayList, capsule.readByteBufferArrayList("testByteBufferArrayList", null)); + Assert.assertEquals("readFloatBufferArrayList()", testFloatBufferArrayList, capsule.readFloatBufferArrayList("testFloatBufferArrayList", null)); + Assert.assertEquals("readSavableArrayList()", testSavableArrayList, capsule.readSavableArrayList("testSavableArrayList", null)); + Assert.assertEquals("readSavableArrayListArray()", testSavableArrayListArray, capsule.readSavableArrayListArray("testSavableArrayListArray", null)); + Assert.assertEquals("readSavableArrayListArray2D()", testSavableArrayListArray2D, capsule.readSavableArrayListArray2D("testSavableArrayListArray2D", null)); + } + } + + private static class TestMaps implements Savable { + TestMaps() { + + } + + @Override + public void write(JmeExporter je) throws IOException { + OutputCapsule capsule = je.getCapsule(this); + + capsule.writeSavableMap(testSavableMap, "testSavableMap", null); + capsule.writeStringSavableMap(testStringSavableMap, "testStringSavableMap", null); + capsule.writeIntSavableMap(testIntSavableMap, "testIntSavableMap", null); + } + + @Override + public void read(JmeImporter ji) throws IOException { + InputCapsule capsule = ji.getCapsule(this); + + Assert.assertEquals("readSavableMap()", testSavableMap, capsule.readSavableMap("testSavableMap", null)); + Assert.assertEquals("readStringSavableMap()", testStringSavableMap, capsule.readStringSavableMap("testStringSavableMap", null)); + + // IntMap does not implement equals() so we have to do it manually + IntMap loadedIntMap = capsule.readIntSavableMap("testIntSavableMap", null); + Iterator iterator = testIntSavableMap.iterator(); + while(iterator.hasNext()) { + IntMap.Entry entry = (IntMap.Entry) iterator.next(); + Assert.assertEquals("readIntSavableMap()", entry.getValue(), loadedIntMap.get(entry.getKey())); + } + } + } +} diff --git a/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java new file mode 100644 index 0000000000..d11df242cc --- /dev/null +++ b/jme3-plugins/src/test/java/com/jme3/export/JmeExporterTest.java @@ -0,0 +1,267 @@ +/* + * Copyright (c) 2023 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import com.jme3.asset.AssetInfo; +import com.jme3.asset.AssetManager; +import com.jme3.asset.DesktopAssetManager; +import com.jme3.asset.ModelKey; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.export.xml.XMLExporter; +import com.jme3.export.xml.XMLImporter; +import com.jme3.material.Material; +import com.jme3.material.plugin.export.material.J3MExporter; +import com.jme3.scene.Node; + +/** + * Tests the methods on classes that implements the JmeExporter interface. + */ +@RunWith(Parameterized.class) +public class JmeExporterTest { + + // test saving with a material since the J3MExporter expects one + private static Material material; + + private final JmeExporter exporter; + + @Rule + public TemporaryFolder folder = new TemporaryFolder(); + + @BeforeClass + public static void beforeClass() { + AssetManager assetManager = new DesktopAssetManager(true); + material = new Material(assetManager, "Common/MatDefs/Gui/Gui.j3md"); + } + + public JmeExporterTest(JmeExporter exporter) { + this.exporter = exporter; + } + + @Parameterized.Parameters + public static Collection defineExporters() { + return Arrays.asList(new BinaryExporter(), new XMLExporter(), new J3MExporter()); + } + + private File fileWithMissingParent() { + File dir = new File(folder.getRoot(), "missingDir"); + return new File(dir, "afile.txt"); + } + + private File fileWithExistingParent() throws IOException { + File dir = folder.newFolder(); + return new File(dir, "afile.txt"); + } + + @Test + public void testSaveWhenPathDoesntExist() throws IOException { + File file = fileWithMissingParent(); + Assert.assertFalse(file.exists()); + exporter.save(material, file); + Assert.assertTrue(file.exists()); + } + + @Test + public void testSaveWhenPathDoesExist() throws IOException { + File file = fileWithExistingParent(); + exporter.save(material, file); + Assert.assertTrue(file.exists()); + } + + @Test(expected = FileNotFoundException.class) + public void testSaveWhenPathDoesntExistWithoutCreateDirs() throws IOException { + File file = fileWithMissingParent(); + exporter.save(material, file, false); + Assert.assertTrue(file.exists()); + } + + @Test + public void testSaveWithNullParent() throws IOException { + File file = new File("someFile.txt"); + try { + exporter.save(material, file); + Assert.assertTrue(file.exists()); + } finally { + file.delete(); + } + } + + @Test + public void testExporterConsistency() { + // + final boolean testXML = true; + final boolean testLists = false; + final boolean testMaps = true; + final boolean printXML = false; + + // initialize data + AssetManager assetManager = new DesktopAssetManager(true); + ArrayList exporters = new ArrayList(); + ArrayList importers = new ArrayList(); + + BinaryExporter be = new BinaryExporter(); + BinaryImporter bi = new BinaryImporter(); + exporters.add(be); + importers.add(bi); + + if (testXML) { + XMLExporter xe = new XMLExporter(); + XMLImporter xi = new XMLImporter(); + exporters.add(xe); + importers.add(xi); + } + + Node origin = new Node("origin"); + + origin.setUserData("testInt", 10); + origin.setUserData("testString", "ABC"); + origin.setUserData("testBoolean", true); + origin.setUserData("testFloat", 1.5f); + origin.setUserData("1", "test"); + if (testLists) { + origin.setUserData("string-list", Arrays.asList("abc")); + origin.setUserData("int-list", Arrays.asList(1, 2, 3)); + origin.setUserData("float-list", Arrays.asList(1f, 2f, 3f)); + } + + if (testMaps) { + Map map = new HashMap<>(); + map.put("int", 1); + map.put("string", "abc"); + map.put("float", 1f); + origin.setUserData("map", map); + } + + // export + ByteArrayOutputStream outs[] = new ByteArrayOutputStream[exporters.size()]; + for (int i = 0; i < exporters.size(); i++) { + JmeExporter exporter = exporters.get(i); + outs[i] = new ByteArrayOutputStream(); + try { + exporter.save(origin, outs[i]); + } catch (IOException ex) { + Assert.fail(ex.getMessage()); + } + } + + // print + if (printXML) { + for (int i = 0; i < exporters.size(); i++) { + ByteArrayOutputStream out = outs[i]; + if (exporters.get(i) instanceof XMLExporter) { + System.out.println("XML: \n" + new String(out.toByteArray()) + "\n\n"); + } else if (exporters.get(i) instanceof BinaryExporter) { + System.out.println("Binary: " + out.size() + " bytes"); + } else { + System.out.println("Unknown exporter: " + exporters.get(i).getClass().getName()); + } + } + } + + // import + Node nodes[] = new Node[importers.size() + 1]; + nodes[0] = origin; + for (int i = 0; i < importers.size(); i++) { + JmeImporter importer = importers.get(i); + ByteArrayOutputStream out = outs[i]; + try { + AssetInfo info = new AssetInfo(assetManager, new ModelKey("origin")) { + @Override + public InputStream openStream() { + return new ByteArrayInputStream(out.toByteArray()); + } + }; + nodes[i + 1] = (Node) importer.load(info); + } catch (IOException ex) { + Assert.fail(ex.getMessage()); + } + } + + // compare + Map userData[] = new Map[nodes.length]; + for (int i = 0; i < nodes.length; i++) { + Node n = nodes[i]; + userData[i] = new HashMap(); + for (String k : n.getUserDataKeys()) { + userData[i].put(k, n.getUserData(k)); + } + } + compareMaps(userData); + } + + private static final void compareMaps(Map[] maps) { + String[] keys = maps[0].keySet().toArray(new String[0]); + // check if all maps have the same keys and values for those keys + for (int i = 1; i < maps.length; i++) { + Map map = maps[i]; + Assert.assertEquals("Map " + i + " keys do not match", keys.length, map.size()); + for (String key : keys) { + Assert.assertTrue("Missing key " + key + " in map " + i, map.containsKey(key)); + Object v1 = maps[0].get(key); + Object v2 = map.get(key); + if (v1.getClass().isArray()) { + boolean c = Arrays.equals((Object[]) v1, (Object[]) v2); + if (c) System.out.println(key + " match"); + Assert.assertTrue("Value does not match in map " + i + " for key " + key + " expected " + + Arrays.deepToString((Object[]) v1) + " but got " + + Arrays.deepToString((Object[]) v2), c); + } else { + boolean c = v1.equals(v2); + if (c) System.out.println(key + " match"); + Assert.assertTrue("Value does not match in map " + i + " for key " + key + " expected " + + v1 + " but got " + v2, c); + } + } + } + + } +} 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..1007a2f764 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-2025 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; @@ -48,6 +49,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.URL; import static org.junit.Assert.assertTrue; @@ -57,56 +59,58 @@ public class TestMaterialWrite { @Before public void init() { - assetManager = JmeSystem.newAssetManager( - TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg")); - - + URL configFile = TestMaterialWrite.class.getResource("/com/jme3/asset/Desktop.cfg"); + assetManager = JmeSystem.newAssetManager(configFile); } - @Test public void testWriteMat() throws Exception { - - Material mat = new Material(assetManager,"Common/MatDefs/Light/Lighting.j3md"); - + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setName("TestMaterial"); mat.setBoolean("UseMaterialColors", true); mat.setColor("Diffuse", ColorRGBA.White); mat.setColor("Ambient", ColorRGBA.DarkGray); mat.setFloat("AlphaDiscardThreshold", 0.5f); - mat.setFloat("Shininess", 2.5f); + mat.setTransparent(true); + mat.setReceivesShadows(true); - 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); tex.setWrap(Texture.WrapAxis.T, Texture.WrapMode.MirroredRepeat); - mat.setTexture("DiffuseMap", tex); + mat.getAdditionalRenderState().setDepthWrite(false); mat.getAdditionalRenderState().setDepthTest(false); + mat.getAdditionalRenderState().setColorWrite(false); + mat.getAdditionalRenderState().setWireframe(true); mat.getAdditionalRenderState().setLineWidth(5); + mat.getAdditionalRenderState().setPolyOffset(-1, 1); mat.getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha); - final ByteArrayOutputStream stream = new ByteArrayOutputStream(); + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); J3MExporter exporter = new J3MExporter(); try { - exporter.save(mat, stream); + exporter.save(mat, baos); } catch (IOException e) { e.printStackTrace(); } - System.err.println(stream.toString()); + System.err.println(baos); J3MLoader loader = new J3MLoader(); AssetInfo info = new AssetInfo(assetManager, new AssetKey("test")) { @Override public InputStream openStream() { - return new ByteArrayInputStream(stream.toByteArray()); + return new ByteArrayInputStream(baos.toByteArray()); } }; - Material mat2 = (Material)loader.load(info); + Material mat2 = (Material) loader.load(info); + assertTrue(mat2.isReceivesShadows()); + assertTrue(mat2.isTransparent()); assertTrue(mat.contentEquals(mat2)); } diff --git a/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java b/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java index 06fda2718b..d9b873afa9 100644 --- a/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java +++ b/jme3-plugins/src/test/java/com/jme3/scene/plugins/gltf/GltfLoaderTest.java @@ -1,10 +1,50 @@ +/* + * Copyright (c) 2017-2021 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.jme3.asset.AssetLoadException; import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.light.Light; +import com.jme3.light.PointLight; +import com.jme3.light.SpotLight; import com.jme3.material.plugin.TestMaterialWrite; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; import com.jme3.system.JmeSystem; + +import org.junit.Assert; import org.junit.Before; import org.junit.Test; @@ -33,12 +73,54 @@ public void testLoad() { // dumpScene(scene, 0); } + @Test + public void testLoadEmptyScene() { + try { + Spatial scene = assetManager.loadModel("gltf/box/boxWithEmptyScene.gltf"); + dumpScene(scene, 0); + } catch (AssetLoadException ex) { + ex.printStackTrace(); + Assert.fail("Failed to import gltf model with empty scene"); + } + } + + @Test + public void testLightsPunctualExtension() { + try { + Spatial scene = assetManager.loadModel("gltf/lights/lights.gltf"); + dumpScene(scene, 0); + } catch (AssetLoadException ex) { + ex.printStackTrace(); + Assert.fail("Failed to import gltf model with lights punctual extension"); + } + } private void dumpScene(Spatial s, int indent) { - System.err.println(indentString.substring(0, indent) + s.getName() + " (" + s.getClass().getSimpleName() + ") / " + + System.err.print(indentString.substring(0, indent) + s.getName() + " (" + s.getClass().getSimpleName() + ") / " + s.getLocalTransform().getTranslation().toString() + ", " + s.getLocalTransform().getRotation().toString() + ", " + s.getLocalTransform().getScale().toString()); + if (s instanceof Geometry) { + System.err.print(" / " + ((Geometry) s).getMaterial()); + } + System.err.println(); + for (Light light : s.getLocalLightList()) { + System.err.print(indentString.substring(0, indent + 1) + " (" + light.getClass().getSimpleName() + ")"); + if (light instanceof SpotLight) { + Vector3f pos = ((SpotLight) light).getPosition(); + Vector3f dir = ((SpotLight) light).getDirection(); + System.err.println(" " + pos.toString() + ", " + dir.toString()); + } else if (light instanceof PointLight) { + Vector3f pos = ((PointLight) light).getPosition(); + System.err.println(" " + pos.toString()); + } else if (light instanceof DirectionalLight) { + Vector3f dir = ((DirectionalLight) light).getDirection(); + System.err.println(" " + dir.toString()); + } else { + System.err.println(); + } + } + if (s instanceof Node) { Node n = (Node) s; for (Spatial spatial : n.getChildren()) { diff --git a/jme3-plugins/src/test/resources/gltf/box/boxWithEmptyScene.gltf b/jme3-plugins/src/test/resources/gltf/box/boxWithEmptyScene.gltf new file mode 100644 index 0000000000..4f96e8e7b1 --- /dev/null +++ b/jme3-plugins/src/test/resources/gltf/box/boxWithEmptyScene.gltf @@ -0,0 +1,146 @@ +{ + "asset": { + "generator": "COLLADA2GLTF", + "version": "2.0" + }, + "scene": 0, + "scenes": [ + { + "name":"box", + "nodes": [ + 0 + ] + }, + { + "name":"scene" + } + ], + "nodes": [ + { + "children": [ + 1 + ], + "matrix": [ + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.0, + 0.0, + 0.0, + 1.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 1.0 + ] + }, + { + "mesh": 0 + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 2 + }, + "indices": 0, + "mode": 4, + "material": 0 + } + ], + "name": "Mesh" + } + ], + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5123, + "count": 36, + "max": [ + 23 + ], + "min": [ + 0 + ], + "type": "SCALAR" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 24, + "max": [ + 1.0, + 1.0, + 1.0 + ], + "min": [ + -1.0, + -1.0, + -1.0 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 288, + "componentType": 5126, + "count": 24, + "max": [ + 0.5, + 0.5, + 0.5 + ], + "min": [ + -0.5, + -0.5, + -0.5 + ], + "type": "VEC3" + } + ], + "materials": [ + { + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.800000011920929, + 0.0, + 0.0, + 1.0 + ], + "metallicFactor": 0.0 + }, + "name": "Red" + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 576, + "byteLength": 72, + "target": 34963 + }, + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 576, + "byteStride": 12, + "target": 34962 + } + ], + "buffers": [ + { + "byteLength": 648, + "uri": "Box0.bin" + } + ] +} \ No newline at end of file diff --git a/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/indices.bin b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/indices.bin new file mode 100644 index 0000000000..f178d8db9c Binary files /dev/null and b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/indices.bin differ diff --git a/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/normals.bin b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/normals.bin new file mode 100644 index 0000000000..bdbda393f8 --- /dev/null +++ b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/normals.bin @@ -0,0 +1,385 @@ +~;Ϡ,e};Ϡ,e}~Ϡ,e};vu6v;vu6vϠ,e}vu6v;ʋԾhk;ʋԾhkvu6vʋԾhk;\ +bWSM;\ +bWSMʋԾhk\ +bWSM;'B{AL?;'B{AL?\ +bWSM'B{AL?;B{A'L?;B{A'L?'B{AL?B{A'L?;bW\ +SM;bW\ +SMB{A'L?bW\ +SM;hԾk;hԾkbW\ +SMhԾk;uv6v;uv6vhԾkuv6v;,e}Ϡ;,e}Ϡuv6v,e}Ϡ;~;~,e}Ϡ~?Ϡ,e}?;Ϡ,e}?;~?Ϡ,e}?6vvu?6v;vu?;Ϡ,e}?6vvu?kԾh?k;Ծh?6v;vu?kԾh?SM\ +bW?SM;\ +bW?k;Ծh?SM\ +bW?L?'B{A?L?;'B{A?SM;\ +bW?L?'B{A?L?B{A'?L?;B{A'?L?;'B{A?L?B{A'?SMbW\ +?SM;bW\ +?L?;B{A'?SMbW\ +?kh>k;h>SM;bW\ +?kh>6vuv>6v;uv>k;h>6vuv>,e}Ϡ>;,e}Ϡ>6v;uv>,e}Ϡ>~;;~;;,e}Ϡ>~;,e}Ϡ>,e};Ϡ>~;;,e}Ϡ>u6vv>u6v;v>,e};Ϡ>u6vv>hk>hk;>u6v;v>hk>bWSM\ +?bWSM;\ +?hk;>bWSM\ +?B{AL?'?B{AL?;'?bWSM;\ +?B{AL?'?'L?B{A?'L?;B{A?B{AL?;'?'L?B{A?\ +SMbW?\ +SM;bW?'L?;B{A?\ +SMbW?Ծkh?Ծk;h?\ +SM;bW?Ծkh?v6vu?v6v;u?Ծk;h?v6vu?,e}?;,e}?v6v;u?,e}?~?;~?;,e}?~?;,e}?Ϡ;,e}?Ϡ~?,e}?Ϡ;u?v6v;u?v6v,e}?Ϡu?v6v;h?Ծk;h?Ծku?v6vh?Ծk;bW?\ +SM;bW?\ +SMh?ԾkbW?\ +SM;B{A?'L?;B{A?'L?bW?\ +SMB{A?'L?;'?B{AL?;'?B{AL?B{A?'L?'?B{AL?;\ +?bWSM;\ +?bWSM'?B{AL?\ +?bWSM;ʋ>hk;ʋ>hk\ +?bWSMʋ>hk;v>u6v;v>u6vʋ>hkv>u6v;Ϡ>,e};Ϡ>,e}v>u6vϠ>,e};;~;;~Ϡ>,e};~?>,e}?>;,e}?;;~?>,e}?v>6vu?v>6v;u?>;,e}?v>6vu?>kh?>k;h?v>6v;u?>kh?\ +?SMbW?\ +?SM;bW?>k;h?\ +?SMbW?'?L?B{A?'?L?;B{A?\ +?SM;bW?'?L?B{A?B{A?L?'?B{A?L?;'?'?L?;B{A?B{A?L?'?bW?SM\ +?bW?SM;\ +?B{A?L?;'?bW?SM\ +?h?k>h?k;>bW?SM;\ +?h?k>u?6vv>u?6v;v>h?k;>u?6vv>,e}?Ϡ>,e}?;Ϡ>u?6v;v>,e}?Ϡ>~?;~?;;,e}?;Ϡ>~;;,e}Ϡ>;,e}Ϡ>~;,e}Ϡ>;uv>6v;uv>6v,e}Ϡ>uv>6v;h>k;h>kuv>6vh>k;bW\ +?SM;bW\ +?SMh>kbW\ +?SM;B{A'?L?;B{A'?L?bW\ +?SMB{A'?L?;¡'1{A?L?;¡'1{A?L?B{A'?L?¡'1{A?L?;\ +bW?SM;\ +bW?SM¡'1{A?L?\ +bW?SM;Ծh?k;Ծh?k\ +bW?SMԾh?k;Uu?6v;Uu?6vԾh?kUu?6v;Ϡ,e}?;Ϡ,e}?Uu?6vϠ,e}?;~?;~?Ϡ,e}?~?;,e}?Ϡ>;,e}?Ϡ>;~?;,e}?Ϡ>6vu?U>6v;u?U>;,e}?Ϡ>6vu?U>kh?>k;h?>6v;u?U>kh?>SMbW?\ +?SM;bW?\ +?k;h?>SMbW?\ +?L?1{A?¡'?L?;1{A?¡'?SM;bW?\ +?L?1{A?¡'?L?'?B{A?L?;'?B{A?L?;1{A?¡'?L?'?B{A?SM\ +?bW?SM;\ +?bW?L?;'?B{A?SM\ +?bW?k>h?k;>h?SM;\ +?bW?k>h?6vv>u?6v;v>u?k;>h?6vv>u?Ϡ>,e}?;Ϡ>,e}?6v;v>u?Ϡ>,e}?;~?;;~?;Ϡ>,e}?;~?;Ϡ>,e}?;Ϡ>,e}?;~?Ϡ>,e}?;U>u?6v;U>u?6vϠ>,e}?U>u?6v;>h?k;>h?kU>u?6v>h?k;\ +?bW?SM;\ +?bW?SM>h?k\ +?bW?SM;¡'?1{A?L?;¡'?1{A?L?\ +?bW?SM¡'?1{A?L?;B{A?'?L?;B{A?'?L?¡'?1{A?L?B{A?'?L?;bW?\ +?SM;bW?\ +?SMB{A?'?L?bW?\ +?SM;h?>k;h?>kbW?\ +?SMh?>k;u?v>6v;u?v>6vh?>ku?v>6v;,e}?Ϡ>;,e}?Ϡ>u?v>6v,e}?Ϡ>;~?;;~?;,e}?Ϡ>~;,e};Ϡ,e}Ϡ~,e};Ϡu6v;vu6vv,e}Ϡu6v;vhk;ԾhkԾu6vvhk;ԾbWSM;\ +bWSM\ +hkԾbWSM;\ +B{AL?;'B{AL?'bWSM\ +B{AL?;''L?;B{A'L?B{AB{AL?''L?;B{A\ +SM;bW\ +SMbW'L?B{A\ +SM;bWԾk;hԾkh\ +SMbWԾk;hv6v;uv6vuԾkhv6v;u;,e},e}v6vu;,e};~~,e};~Ϡ>,e};Ϡ>,e};;~Ϡ>,e}6vv>u6v;v>u;Ϡ>,e}6vv>uk>hk;>h6v;v>uk>hSM\ +?bWSM;\ +?bWk;>hSM\ +?bWL?'?B{AL?;'?B{ASM;\ +?bWL?'?B{AL?1{A?¡'L?;1{A?¡'L?;'?B{AL?1{A?¡'SMbW?\ +SM;bW?\ +L?;1{A?¡'SMbW?\ +kh?Ծk;h?ԾSM;bW?\ +kh?Ծ6vu?U6v;u?Uk;h?Ծ6vu?U,e}?Ϡ;,e}?Ϡ6v;u?U,e}?Ϡ~?;~?;,e}?Ϡ;;~>;,e}>,e};~>;,e}v>6v;uv>6vu>,e}v>6v;u>k;h>khv>6vu>k;h\ +?SM;bW\ +?SMbW>kh\ +?SM;bW'?L?;B{A'?L?B{A\ +?SMbW'?L?;B{AB{A?L?;'B{A?L?''?L?B{AB{A?L?;'bW?SM;\ +bW?SM\ +B{A?L?'bW?SM;\ +h?k;Ծh?kԾbW?SM\ +h?k;Ծu?6v;vu?6vvh?kԾu?6v;v,e}?;Ϡ,e}?Ϡu?6vv,e}?;Ϡ~?;~?,e}?Ϡ~,e}Ϡ;,e}Ϡ;~,e}Ϡ6vuv6v;uv;,e}Ϡ6vuvkhԾk;hԾ6v;uvkhԾSMbW\ +SM;bW\ +k;hԾSMbW\ +L?B{A'L?;B{A'SM;bW\ +L?B{A'L?'B{AL?;'B{AL?;B{A'L?'B{ASM\ +bWSM;\ +bWL?;'B{ASM\ +bWkԾhk;ԾhSM;\ +bWkԾh6vvu6v;vuk;Ծh6vvuϠ,e};Ϡ,e}6v;vuϠ,e}~;~;Ϡ,e}~?;~?;;~?;~?~?;;~?;;~?~?;~;;~;~~~;~;~;~;~?;~?~?;~?;;~~;~;;~;,e}Ϡ>~;,e}Ϡ;zd;;>zd;;>,e}Ϡ;uv6v;sy >sy >uv6v;hԾk;g{оJ=g{оJ=hԾk;bW\ +SM;V?;B"=V?;B"=bW\ +SM;B{A'L?;U@$&;=U@$&;=B{A'L?;'B{AL?;$&U@;=$&U@;='B{AL?;\ +bWSM;.;VB"=.;VB"=\ +bWSM;ʋԾhk;оgJ=оgJ=ʋԾhk;vu6v;zs >zs >vu6v;Ϡ,e};;zd;>Ϡ,e};~;,e}Ϡ>;zd;>;zd;>,e}Ϡ>6vuv> sz> sz>6vuv>kh>Jg{>Jg{>kh>SMbW\ +?B"V.;?B"V.;?SMbW\ +?L?B{A'?;U@$&?;U@$&?L?B{A'?L?'B{A?;$&U@?;$&U@?L?'B{A?SM\ +bW?B".;V?B".;V?SM\ +bW?kԾh?Jоg?Jоg?kԾh?6vvu? zs? zs?6vvu?Ϡ,e}?d;;z?Ϡ,e}?~?,e}?d;;z?d;;z?,e}?v6vu?z s?z s?v6vu?Ծkh?оJg?оJg?Ծkh?\ +SMbW?.;B"V?.;B"V?\ +SMbW?'L?B{A?$&;U@?$&;U@?'L?B{A?B{AL?'?U@;$&?U@;$&?B{AL?'?bWSM\ +?VB".;?VB".;?bWSM\ +?hk>gJ>gJ>hk>u6vv>s z>s z>u6vv>,e}Ϡ>zd;;>s z>zd;;>sy >OYmr>OYmr>sy >g{оJ=#bshɾ^|>#bshɾ^|>g{оJ=V?;B"=lR l>lR l>V?;B"=U@$&;=Na="3b>Na="3b>U@$&;=$&U@;="Na=3b>"Na=3b>$&U@;=.;VB"=lR l>lR l>.;VB"=оgJ=shɾ#b^|>shɾ#b^|>оgJ=zs >OYmr>zs >;zd;> sz>OYmr>OYmr> sz>Jg{>^|#bsh>^|#bsh>Jg{>B"V.;? llR? llR?B"V.;?;U@$&?3bNa="?3bNa="?;U@$&?;$&U@?3b"Na=?3b"Na=?;$&U@?B".;V? llR? llR?B".;V?Jоg?^|shɾ#b?^|shɾ#b?Jоg? zs?r>Ym? zs?d;;z?z s?r>Ym?r>Ym?z s?оJg?shɾ^|#b?shɾ^|#b?оJg?.;B"V? llR? llR?.;B"V?$&;U@?"3bNa=?"3bNa=?$&;U@?U@;$&?Na=3b"?Na=3b"?U@;$&?VB".;?lR l?lR l?VB".;?gJ>#b^|sh>#b^|sh>gJ>s z>OYmr>#b^|sh>OYmr>#bshɾ^|>Y==>Y==>#bshɾ^|>lR l>rLƯ>rLƯ>lR l>Na="3b>8W>8W>Na="3b>"Na=3b>85>85>"Na=3b>lR l>rLƯ>rLƯ>lR l>shɾ#b^|>=Y=>shɾ#b^|>OYmr>^|#bsh>=Y=>=Y=>^|#bsh> llR?ƯrL>ƯrL> llR?3bNa="?W짾8?W짾8?3bNa="?3b"Na=?5짾8?5짾8?3b"Na=? llR?ǯrL?ǯrL? llR?^|shɾ#b?==Y?^|shɾ#b?r>Ym?shɾ^|#b?==Y?==Y?shɾ^|#b? llR?ƯrL?ƯrL? llR?"3bNa=?W짾8?W짾8?"3bNa=?Na=3b"?85짾?85짾?Na=3b"?lR l?rLƯ>rLƯ>lR l?#b^|sh>Y==>rLƯ>Y==>rLƯ>"B뾋>"B뾋>rLƯ>8W>-z/x>-z/x>8W>85>x-z/>x-z/>85>rLƯ>뾒"B>rLƯ>=Y=>ƯrL>뾒"B>뾒"B>ƯrL>W짾8?޾-z/x?޾-z/x?W짾8?5짾8?޾x-z/?޾x-z/?5짾8?ǯrL?뾋뾒"B?ǯrL?==Y?ƯrL?뾋뾒"B?뾋뾒"B?ƯrL?W짾8?x޾-z/?x޾-z/?W짾8?85짾?-z/޾-x?-z/޾-x?85짾?rLƯ>"B뾋>-z/޾-x?"B뾋>-z/x>% + +?% + +?-z/x>x-z/> + % +?x-z/>뾒"B>޾-z/x? + % +? + % +?޾-z/x?޾x-z/? + +%?޾x-z/?뾋뾒"B?x޾-z/? + +%? + +%?x޾-z/?-z/޾-x?% + +?% + +? + % +? + +%?;,e}Ϡ>;~;Ϡ>,e};;>zd;>;>zd;>Ϡ>,e};v>u6v;z>s >z>s >v>u6v;ʋ>hk;>gJ=>gJ=ʋ>hk;\ +?bWSM;.;?VB"=.;?VB"=\ +?bWSM;'?B{AL?;$&?U@;=$&?U@;='?B{AL?;B{A?'L?;U@?$&;=U@?$&;=B{A?'L?;bW?\ +SM;V?.;B"=V?.;B"=bW?\ +SM;h?Ծk;g?{оJ=g?{оJ=h?Ծk;u?v6v;s?z >s?z >u?v6v;,e}?Ϡ;z?d;d;>,e}?Ϡ;~?;,e}?Ϡ>z?d;d;>z?d;d;>,e}?Ϡ>u?6vv>s? z>s? z>u?6vv>h?k>g?J>g?J>h?k>bW?SM\ +?V?B".;?V?B".;?bW?SM\ +?B{A?L?'?U@?;$&?U@?;$&?B{A?L?'?'?L?B{A?$&?;U@?$&?;U@?'?L?B{A?\ +?SMbW?.;?B"V?.;?B"V?\ +?SMbW?>kh?>Jg?>Jg?>kh?v>6vu?z> s?z> s?v>6vu?>,e}?d;>;z?>,e}?;~?;Ϡ,e}?d;>;z?d;>;z?;Ϡ,e}?6v;vu? >zs? >zs?6v;vu?k;Ծh?J=оg?J=оg?k;Ծh?SM;\ +bW?B"=.;V?B"=.;V?SM;\ +bW?L?;'B{A?;=$&U@?;=$&U@?L?;'B{A?L?;B{A'?;=U@$&?;=U@$&?L?;B{A'?SM;bW\ +?B"=V.;?B"=V.;?SM;bW\ +?k;h>J=g{>J=g{>k;h>6v;uv> >sz> >sz>6v;uv>;,e}Ϡ>;>zd;> >sz>;>zd;>z>s >>OYmr>>OYmr>z>s >>gJ=sh>#b^|>sh>#b^|>>gJ=.;?VB"=?lR l>?lR l>.;?VB"=$&?U@;="?Na=3b>"?Na=3b>$&?U@;=U@?$&;=Na=?"3b>Na=?"3b>U@?$&;=V?.;B"=lR? l>lR? l>V?.;B"=g?{оJ=#b?shɾ^|>#b?shɾ^|>g?{оJ=s?z >OYm?r>s?z >z?d;d;>s? z>OYm?r>OYm?r>s? z>g?J>#b?^|sh>#b?^|sh>g?J>V?B".;?lR? l?lR? l?V?B".;?U@?;$&?Na=?3b"?Na=?3b"?U@?;$&?$&?;U@?"?3bNa=?"?3bNa=?$&?;U@?.;?B"V?? llR?? llR?.;?B"V?>Jg?sh>^|#b?sh>^|#b?>Jg?z> s?r>OYm?z> s?d;>;z? >zs?r>OYm?r>OYm? >zs?J=оg?^|>shɾ#b?^|>shɾ#b?J=оg?B"=.;V? l>lR? l>lR?B"=.;V?;=$&U@?3b>"Na=?3b>"Na=?;=$&U@?;=U@$&?3b>Na="?3b>Na="?;=U@$&?B"=V.;? l>lR? l>lR?B"=V.;?J=g{>^|>#bsh>^|>#bsh>J=g{> >sz>>OYmr>^|>#bsh>>OYmr>sh>#b^|>=>Y=>=>Y=>sh>#b^|>?lR l>>rLƯ>>rLƯ>?lR l>"?Na=3b>?85>?85>"?Na=3b>Na=?"3b>8?W>8?W>Na=?"3b>lR? l>rL?ǯ>rL?ǯ>lR? l>#b?shɾ^|>Y?==>#b?shɾ^|>OYm?r>#b?^|sh>Y?==>Y?==>#b?^|sh>lR? l?rL?ǯ>rL?ǯ>lR? l?Na=?3b"?8?5짾?8?5짾?Na=?3b"?"?3bNa=??W짾8??W짾8?"?3bNa=?? llR?>ǯrL?>ǯrL?? llR?sh>^|#b?=>=Y?sh>^|#b?r>OYm?^|>shɾ#b?=>=Y?=>=Y?^|>shɾ#b? l>lR?ǯ>rL?ǯ>rL? l>lR?3b>"Na=?5>8?5>8?3b>"Na=?3b>Na="?W>8?W>8?3b>Na="? l>lR?ǯ>rL>ǯ>rL> l>lR?^|>#bsh>=>Y=>ǯ>rL>=>Y=>>rLƯ>>"B>>"B>>rLƯ>?85>-x?-z/>-x?-z/>?85>8?W>-z/?x>-z/?x>8?W>rL?ǯ>"B?뾋>rL?ǯ>Y?==>rL?ǯ>"B?뾋>"B?뾋>rL?ǯ>8?5짾?-z/?޾-x?-z/?޾-x?8?5짾??W짾8?x?޾-z/?x?޾-z/??W짾8?>ǯrL?>뾒"B?>ǯrL?=>=Y?ǯ>rL?>뾒"B?>뾒"B?ǯ>rL?5>8?>-x-z/?>-x-z/?5>8?W>8?>-z/x?>-z/x?W>8?ǯ>rL>>"B>>-z/x?>"B>-x?-z/> +?%- +? +?%- +?-x?-z/>-z/?x>%?- + +?-z/?x>"B?뾋>-z/?޾-x?%?- + +?%?- + +?-z/?޾-x?x?޾-z/?- +? +%?x?޾-z/?>뾒"B?>-x-z/?- +? +%?- +? +%?>-x-z/?>-z/x? +?%- +? +?%- +?%?- + +?- +? +%?,e}?Ϡ>~?;Ϡ,e}?;d;z?d;>d;z?d;>Ϡ,e}?;Uu?6v;zs? >zs? >Uu?6v;Ծh?k;{оg?J={оg?J=Ծh?k;\ +bW?SM;.;V?"=.;V?"=\ +bW?SM;¡'1{A?L?;$&D@?;=$&D@?;=¡'1{A?L?;B{A'?L?;U@$&?;=U@$&?;=B{A'?L?;bW\ +?SM;V.;?B"=V.;?B"=bW\ +?SM;h>k;g{>J=g{>J=h>k;uv>6v;sz> >sz> >uv>6v;,e}Ϡ>;zd;>;>,e}Ϡ>;~;;,e};Ϡ>zd;>;>zd;>;>,e};Ϡ>u6v;v>s >z>s >z>u6v;v>hk;>gJ=>gJ=>hk;>bWSM;\ +?VB"=.;?VB"=.;?bWSM;\ +?B{AL?;'?U@;=$&?U@;=$&?B{AL?;'?'L?;B{A?$&;=U@?$&;=U@?'L?;B{A?\ +SM;bW??;B"=V??;B"=V?\ +SM;bW?Ծk;h?оJ=g?оJ=g?Ծk;h?v6v;u?y >s?y >s?v6v;u?;,e}?d;;>z?;,e}?;~?Ϡ>,e}?d;;>z?d;;>z?Ϡ>,e}?6vv>u? z>s? z>s?6vv>u?k>h?J{>g?J{>g?k>h?SM\ +?bW?!.;?V?!.;?V?SM\ +?bW?L?'?B{A?;$&?U@?;$&?U@?L?'?B{A?L?1{A?¡'?;U@?$&?;U@?$&?L?1{A?¡'?SMbW?\ +?B"V?.;?B"V?.;?SMbW?\ +?kh?>g?{>g?{>kh?>6vu?U> s?z> s?z>6vu?U>,e}?Ϡ>d;z?d;> s?z>d;z?d;>zs? >>Ym?r>>Ym?r>zs? >{оg?J=shɾ#b?^|>shɾ#b?^|>{оg?J=.;V?"=lR? l>lR? l>.;V?"=$&D@?;="Na=?3b>"Na=?3b>$&D@?;=U@$&?;=Na="?3b>Na="?3b>U@$&?;=V.;?B"=lR? l>lR? l>V.;?B"=g{>J=#bsh>^|>#bsh>^|>g{>J=sz> >OYmr>>sz> >zd;>;>s >z>OYmr>>OYmr>>s >z>gJ=>#b^|>sh>#b^|>sh>gJ=>VB"=.;?lR l>?lR l>?VB"=.;?U@;=$&?Na=3b>"?Na=3b>"?U@;=$&?$&;=U@?"3b>Na=?"3b>Na=?$&;=U@??;B"=V? l>lR? l>lR??;B"=V?оJ=g?shɾ^|>#b?shɾ^|>#b?оJ=g?y >s?r>OYm?y >s?d;;>z? z>s?r>OYm?r>OYm? z>s?J{>g?^|sh>#b?^|sh>#b?J{>g?!.;?V? l?lR? l?lR?!.;?V?;$&?U@?3b"?Na=?3b"?Na=?;$&?U@?;U@?$&?3bNa=?"?3bNa=?"?;U@?$&?B"V?.;? llR?? llR??B"V?.;?g?{>^|#b?sh>^|#b?sh>g?{> s?z>>Ym?r>^|#b?sh>>Ym?r>shɾ#b?^|>=Y?=>=Y?=>shɾ#b?^|>lR? l>rL?Ư>rL?Ư>lR? l>"Na=?3b>8?5>8?5>"Na=?3b>Na="?3b>8?W>8?W>Na="?3b>lR? l>rL>ǯ>rL>ǯ>lR? l>#bsh>^|>Y=>=>#bsh>^|>OYmr>>#b^|>sh>Y=>=>Y=>=>#b^|>sh>lR l>?rLǯ>>rLǯ>>lR l>?Na=3b>"?85>?85>?Na=3b>"?"3b>Na=?5>8?5>8?"3b>Na=? l>lR?ǯ>rL?ǯ>rL? l>lR?shɾ^|>#b?==>Y?shɾ^|>#b?r>OYm?^|sh>#b?==>Y?==>Y?^|sh>#b? l?lR?ǯ>rL?ǯ>rL? l?lR?3b"?Na=?5짾?8?5짾?8?3b"?Na=?3bNa=?"?W짾8??W짾8??3bNa=?"? llR??ǯrL?>ǯrL?> llR??^|#b?sh>=Y?=>ǯrL?>=Y?=>rL?Ư>뾒"B?>뾒"B?>rL?Ư>8?5>-x-z/?>-x-z/?>8?5>8?W>-z/x?>-z/x?>8?W>rL>ǯ>"B>>rL>ǯ>Y=>=>rLǯ>>"B>>"B>>rLǯ>>85>?-z/>x?-z/>x?85>?5>8?x>-z/?x>-z/?5>8?ǯ>rL?뾋>"B?ǯ>rL?==>Y?ǯ>rL?뾋>"B?뾋>"B?ǯ>rL?5짾?8?޾-x?-z/?޾-x?-z/?5짾?8?W짾8??޾-z/?x?޾-z/?x?W짾8??ǯrL?>뾒"B?>޾-z/?x?뾒"B?>-x-z/?> +%?- +? +%?- +?-x-z/?>-z/x?>%- +? +?-z/x?>"B>>-z/>x?%- +? +?%- +? +?-z/>x?x>-z/?- +- +?%?x>-z/?뾋>"B?޾-x?-z/?- +- +?%?- +- +?%?޾-x?-z/?޾-z/?x? +%?- +? +%?- +?%- +? +?- +- +?%?,e}?;Ϡ>~?;;,e}?Ϡ>;z?d;>;>z?d;>;>,e}?Ϡ>;u?v>6v;s?y> >s?y> >u?v>6v;h?>k;g?{>J=g?{>J=h?>k;bW?\ +?SM;V?.;?B"=V?.;?B"=bW?\ +?SM;B{A?'?L?;U@?$&?;=U@?$&?;=B{A?'?L?;¡'?1{A?L?;$&?D@?;=$&?D@?;=¡'?1{A?L?;\ +?bW?SM;.;?V?"=.;?V?"=\ +?bW?SM;>h?k;{>g?={>g?=>h?k;U>u?6v;z>s? >z>s? >U>u?6v;Ϡ>,e}?;d;>z?d;>Ϡ>,e}?;;~?;;,e}?Ϡ>d;>z?d;>d;>z?d;>;,e}?Ϡ>6v;u?U> >s?z> >s?z>6v;u?U>k;h?>=g?{>=g?{>k;h?>SM;bW?\ +?B"=V?.;?B"=V?.;?SM;bW?\ +?L?;1{A?¡'?;=D@?$&?;=D@?$&?L?;1{A?¡'?L?;'?B{A?;=$&?U@?;=$&?U@?L?;'?B{A?SM;\ +?bW?!=.;?V?!=.;?V?SM;\ +?bW?k;>h?J=>g?J=>g?k;>h?6v;v>u? >z>s? >z>s?6v;v>u?;Ϡ>,e}?d;>;>z?;Ϡ>,e}?;;~?>;,e}?d;>;>z?d;>;>z?>;,e}?v>6v;u?z> >s?z> >s?v>6v;u?>k;h?>J=g?>J=g?>k;h?\ +?SM;bW?.;?B"=V?.;?B"=V?\ +?SM;bW?'?L?;B{A?$&?;=U@?$&?;=U@?'?L?;B{A?B{A?L?;'?U@?;=$&?U@?;=$&?B{A?L?;'?bW?SM;\ +?V?B"=.;?V?B"=.;?bW?SM;\ +?h?k;>g?J=>g?J=>h?k;>u?6v;v>s? >z>s? >z>u?6v;v>,e}?;Ϡ>z?d;>;>s? >z>z?d;>;>s?y> >OYm?r>>OYm?r>>s?y> >g?{>J=#b?sh>^|>#b?sh>^|>g?{>J=V?.;?B"=lR?? l>lR?? l>V?.;?B"=U@?$&?;=Na=?"?3b>Na=?"?3b>U@?$&?;=$&?D@?;="?Na=?3b>"?Na=?3b>$&?D@?;=.;?V?"=?lR? l>?lR? l>.;?V?"={>g?=sh>#b?^|>sh>#b?^|>{>g?=z>s? >>OYm?r>z>s? >d;>z?d;> >s?z>>OYm?r>>OYm?r> >s?z>=g?{>^|>#b?sh>^|>#b?sh>=g?{>B"=V?.;? l>lR?? l>lR??B"=V?.;?;=D@?$&?3b>Na=?"?3b>Na=?"?;=D@?$&?;=$&?U@?3b>"?Na=?3b>"?Na=?;=$&?U@?!=.;?V? l>?lR? l>?lR?!=.;?V?J=>g?^|>sh>#b?^|>sh>#b?J=>g? >z>s?r>>OYm? >z>s?d;>;>z?z> >s?r>>OYm?r>>OYm?z> >s?>J=g?sh>^|>#b?sh>^|>#b?>J=g?.;?B"=V?? l>lR?? l>lR?.;?B"=V?$&?;=U@?"?3b>Na=?"?3b>Na=?$&?;=U@?U@?;=$&?Na=?3b>"?Na=?3b>"?U@?;=$&?V?B"=.;?lR? l>?lR? l>?V?B"=.;?g?J=>#b?^|>sh>#b?^|>sh>g?J=>s? >z>OYm?r>>#b?^|>sh>OYm?r>>#b?sh>^|>Y?=>=>Y?=>=>#b?sh>^|>lR?? l>rL?>ǯ>rL?>ǯ>lR?? l>Na=?"?3b>8??W>8??W>Na=?"?3b>"?Na=?3b>?8?5>?8?5>"?Na=?3b>?lR? l>>rL?Ư>>rL?Ư>?lR? l>sh>#b?^|>=>Y?=>sh>#b?^|>>OYm?r>^|>#b?sh>=>Y?=>=>Y?=>^|>#b?sh> l>lR??Ư>rL?>Ư>rL?> l>lR??3b>Na=?"?W>8??W>8??3b>Na=?"?3b>"?Na=?5>?8?5>?8?3b>"?Na=? l>?lR?Ư>>rL?Ư>>rL? l>?lR?^|>sh>#b?=>=>Y?^|>sh>#b?r>>OYm?sh>^|>#b?=>=>Y?=>=>Y?sh>^|>#b?? l>lR?>Ư>*rL?>Ư>*rL?? l>lR?"?3b>Na=??5>8??5>8?"?3b>Na=?Na=?3b>"?8?5>?8?5>?Na=?3b>"?lR? l>?rL?Ư>>rL?Ư>>lR? l>?#b?^|>sh>Y?=>=>rL?Ư>>Y?=>=>rL?>ǯ>"B?>>"B?>>rL?>ǯ>8??W>-z/?x?>-z/?x?>8??W>?8?5>-x?-z/?>-x?-z/?>?8?5>>rL?Ư>>"B?>>rL?Ư>=>Y?=>Ư>rL?>>"B?>>"B?>Ư>rL?>W>8??>-z/?x?>-z/?x?W>8??5>?8?>x?-z/?>x?-z/?5>?8?Ư>>rL?>>"B?Ư>>rL?=>=>Y?>Ư>*rL?>>"B?>>"B?>Ư>*rL??5>8?x?>-z/?x?>-z/??5>8?8?5>?-z/?>x?-z/?>x?8?5>?rL?Ư>>"B?>>-z/?>x?"B?>>-z/?x?>%? +? +?%? +? +?-z/?x?>-x?-z/?>- +?%? +?-x?-z/?>>"B?>>-z/?x?- +?%? +?- +?%? +?>-z/?x?>x?-z/?- +? +?%?>x?-z/?>>"B?x?>-z/?- +? +?%?- +? +?%?x?>-z/?-z/?>x?%? +? +?%? +? +?- +?%? +?- +? +?%?Ϡ>,e};~;,e}d;;>zd;;>z;,e}v6v;uy >sy >sv6v;uԾk;hоJ=gоJ=gԾk;h\ +SM;bW.;B"=V.;B"=V\ +SM;bW'L?;B{A$&;=U@$&;=U@'L?;B{AB{AL?;'U@;=$&U@;=$&B{AL?;'bWSM;\ +VB"=.;VB"=.;bWSM;\ +hk;ԾgJ=оgJ=оhk;Ծu6v;vs >zs >zu6v;v,e};Ϡzd;>;,e};Ϡ~;,e}Ϡ>zd;>;zd;>;,e}Ϡ>uv>6vsy> sy> uv>6vh>kg{>Jg{>Jh>kbW\ +?SMV.;?B"V.;?B"bW\ +?SMB{A'?L?U@$&?;U@$&?;B{A'?L?¡'1{A?L?$&D@?;$&D@?;¡'1{A?L?\ +bW?SM.;V?".;V?"\ +bW?SMԾh?k{оg?{оg?Ծh?kUu?6vzs? zs? Uu?6vϠ,e}?d;z?d;Ϡ,e}?~?,e}?Ϡd;z?d;d;z?d;,e}?Ϡ6vu?U s?z s?z6vu?Ukh?Ծg?{оg?{оkh?ԾSMbW?\ +B"V?.;B"V?.;SMbW?\ +L?1{A?¡';D@?$&;D@?$&L?1{A?¡'L?'?B{A;$&?U@;$&?U@L?'?B{ASM\ +?bW!.;?V!.;?VSM\ +?bWk>hJ>gJ>gk>h6vv>u z>s z>s6vv>uϠ>,e}d;;>z z>sd;;>zy >sr>OYmr>OYmy >sоJ=gshɾ^|>#bshɾ^|>#bоJ=g.;B"=V l>lR l>lR.;B"=V$&;=U@"3b>Na="3b>Na=$&;=U@U@;=$&Na=3b>"Na=3b>"U@;=$&VB"=.;lR l>lR l>VB"=.;gJ=о#b^|>shɾ#b^|>shɾgJ=оs >zOYmr>s >zzd;>;sy> OYmr>OYmr>sy> g{>J#bsh>^|#bsh>^|g{>JV.;?B"lR? llR? lV.;?B"U@$&?;Na="?3bNa="?3bU@$&?;$&D@?;彷"Na=?3b"Na=?3b$&D@?;.;V?"lR? llR? l.;V?"{оg?shɾ#b?^|shɾ#b?^|{оg?zs? OYm?rzs? d;z?d; s?zOYm?rOYm?r s?zg?{о^|#b?shɾ^|#b?shɾg?{оB"V?.; llR? llR?B"V?.;;D@?$&3bNa=?"3bNa=?";D@?$&;$&?U@3b"?Na=3b"?Na=;$&?U@!.;?V l?lR l?lR!.;?VJ>g^|sh>#b^|sh>#bJ>g z>sr>OYm^|sh>#br>OYmshɾ^|>#b==>Y==>Yshɾ^|>#b l>lRƯ>rLƯ>rL l>lR"3b>Na=W>8W>8"3b>Na=Na=3b>"85>85>Na=3b>"lR l>*rLƯ>*rLƯ>lR l>#b^|>shɾY=>=#b^|>shɾOYmr>#bsh>^|Y=>=Y=>=#bsh>^|lR? lrL>ǯrL>ǯlR? lNa="?3b8?W짾8?W짾Na="?3b"Na=?3b8?5짾8?5짾"Na=?3blR? lrL?ƯrL?ƯlR? lshɾ#b?^|=Y?=shɾ#b?^|OYm?r^|#b?shɾ=Y?==Y?=^|#b?shɾ llR?ƯrL?ƯrL? llR?3bNa=?"W짾8?W짾8?3bNa=?"3b"?Na=5짾?85짾?83b"?Na= l?lRƯ>*rLƯ>*rL l?lR^|sh>#b==>YƯ>*rL==>YƯ>rL뾋>"B뾋>"BƯ>rLW>8x>-z/x>-z/W>885>-z/>x-z/>x85>*rLƯ>"B>*rLƯ>Y=>=rL>ǯ"B>뾒"B>rL>ǯ8?W짾-z/x?޾-z/x?޾8?W짾8?5짾-x-z/?޾-x-z/?޾8?5짾rL?Ư뾒"B?뾹rL?Ư=Y?=ƯrL?뾒"B?뾋뾒"B?ƯrL?W짾8?޾-z/?x޾-z/?xW짾8?5짾?8޾x?-z/޾x?-z/5짾?8Ư>*rL뾋>"B޾x?-z/뾋>"Bx>-z/- + +?%- + +?%x>-z/-z/>x%- +?- +-z/>x"B>-z/x?޾%- +?- +%- +?- +-z/x?޾-x-z/?޾- +%? +-x-z/?޾뾒"B?޾-z/?x- +%? +- +%? +޾-z/?x޾x?-z/- + +?%- + +?%%- +?- +- +%? +,e}?Ϡ>~?;,e}?;Ϡz?d;>;z?d;>;,e}?;Ϡu?6v;vs? >zs? >zu?6v;vh?k;Ծg?J=оg?J=оh?k;ԾbW?SM;\ +V?B"=.;V?B"=.;bW?SM;\ +B{A?L?;'U@?;=$&U@?;=$&B{A?L?;''?L?;B{A$&?;=U@$&?;=U@'?L?;B{A\ +?SM;bW?;?B"=V?;?B"=V\ +?SM;bW>k;h>J=g>J=g>k;hv>6v;uy> >sy> >sv>6v;u>;,e}d;>;>z>;,e};;~;Ϡ>,e}d;>;>zd;>;>z;Ϡ>,e}6v;v>u >z>s >z>s6v;v>uk;>hJ={>gJ={>gk;>hSM;\ +?bW!=.;?V!=.;?VSM;\ +?bWL?;'?B{A;=$&?U@;=$&?U@L?;'?B{AL?;1{A?¡';=U@?$&;=U@?$&L?;1{A?¡'SM;bW?\ +B"=V?.;B"=V?.;SM;bW?\ +k;h?Ծ=g?{о=g?{оk;h?Ծ6v;u?U >s?z >s?z6v;u?U;,e}?Ϡd;>z?d;;,e}?Ϡ;~?Ϡ>,e}?d;>z?d;d;>z?d;Ϡ>,e}?U>u?6vz>s? z>s? U>u?6v>h?k{>g?J{>g?J>h?k\ +?bW?SM.;?V?".;?V?"\ +?bW?SM¡'?1{A?L?$&?D@?;$&?D@?;¡'?1{A?L?B{A?'?L?U@?$&?;U@?$&?;B{A?'?L?bW?\ +?SMV?.;?B"V?.;?B"bW?\ +?SMh?>kg?{>Jg?{>Jh?>ku?v>6vs?z> s?z> u?v>6v,e}?Ϡ>z?d;>;s?z> z?d;>;s? >zOYm?r>OYm?r>s? >zg?J=о#b?^|>shɾ#b?^|>shɾg?J=оV?B"=.;lR? l>lR? l>V?B"=.;U@?;=$&Na=?3b>"Na=?3b>"U@?;=$&$&?;=U@"?3b>Na="?3b>Na=$&?;=U@?;?B"=V? l>lR? l>lR?;?B"=V>J=gsh>^|>#bsh>^|>#b>J=gy> >sr>>OYmy> >sd;>;>z >z>sr>>OYmr>>OYm >z>sJ={>g^|>sh>#b^|>sh>#bJ={>g!=.;?V l>?lR l>?lR!=.;?V;=$&?U@3b>"?Na=3b>"?Na=;=$&?U@;=U@?$&3b>Na=?"3b>Na=?";=U@?$&B"=V?.; l>lR? l>lR?B"=V?.;=g?{о^|>#b?shɾ^|>#b?shɾ=g?{о >s?z>OYm?r >s?zd;>z?d;z>s? >OYm?r>OYm?rz>s? {>g?Jsh>#b?^|sh>#b?^|{>g?J.;?V?"?lR? l?lR? l.;?V?"$&?D@?;彷"?Na=?3b"?Na=?3b$&?D@?;U@?$&?;Na=?"?3bNa=?"?3bU@?$&?;彖V?.;?B"lR?? llR?? lV?.;?B"g?{>J#b?sh>^|#b?sh>^|g?{>Js?z> OYm?r>#b?sh>^|OYm?r>#b?^|>shɾY?=>=Y?=>=#b?^|>shɾlR? l>rL?Ư>rL?Ư>lR? l>Na=?3b>"8?5>8?5>Na=?3b>""?3b>Na=?5>8?5>8"?3b>Na=? l>lR>ǯ>rL>ǯ>rL? l>lRsh>^|>#b=>=>Ysh>^|>#br>>OYm^|>sh>#b=>=>Y=>=>Y^|>sh>#b l>?lRǯ>>rLǯ>>rL l>?lR3b>"?Na=5>?85>?83b>"?Na=3b>Na=?"W>8?W>8?3b>Na=?" l>lR?ǯ>rL?ǯ>rL? l>lR?^|>#b?shɾ=>Y?=^|>#b?shɾ>OYm?rsh>#b?^|=>Y?==>Y?=sh>#b?^|?lR? l>rL?ǯ>rL?ǯ?lR? l"?Na=?3b?8?5짾?8?5짾"?Na=?3bNa=?"?3b8??W짾8??W짾Na=?"?3blR?? lrL?>ǯrL?>ǯlR?? l#b?sh>^|Y?=>=rL?>ǯY?=>=rL?Ư>"B?>뾒"B?>rL?Ư>8?5>-z/?>-x-z/?>-x8?5>?5>8x?>-z/x?>-z/?5>8>ǯ>rL>>"B>ǯ>rL=>=>Yǯ>>rL>>"B>>"Bǯ>>rL5>?8>-x?-z/>-x?-z/5>?8W>8?>-z/?x>-z/?xW>8?ǯ>rL?>"B?ǯ>rL?=>Y?=>rL?ǯ>"B?뾋>"B?뾹>rL?ǯ?8?5짾-x?-z/?޾-x?-z/?޾?8?5짾8??W짾-z/?x?޾-z/?x?޾8??W짾rL?>ǯ"B?>-z/?x?޾"B?>-z/?>-x%?- +?- +%?- +?- +-z/?>-xx?>-z/- +?- +?%x?>-z/>>"B>-x?-z/- +?- +?%- +?- +?%>-x?-z/>-z/?x +?%?- +>-z/?x>"B?-x?-z/?޾ +?%?- + +?%?- +-x?-z/?޾-z/?x?޾%?- +?- +%?- +?- +- +?- +?% +?%?- +,e}Ϡ~Ϡ,e};zd;;zd;Ϡ,e}vu6vzs zs vu6vʋԾhkоgJоgJʋԾhk\ +bWSM.;VB".;VB"\ +bWSM'B{AL?$&U@;$&U@;彲'B{AL?B{A'L?U@$&;U@$&;B{A'L?bW\ +SMV.;B"V.;B"bW\ +SMhԾkg{оJg{оJhԾkuv6vsz sz uv6v,e}Ϡzd;d;,e}Ϡ~,e}Ϡzd;d;zd;d;,e}Ϡu6vvs zs zu6vvhkԾgJоgJоhkԾbWSM\ +VB".;VB".;bWSM\ +B{AL?'U@;$&U@;$&B{AL?''L?B{A$&;U@$&;U@'L?B{A\ +SMbW.;B"V.;B"V\ +SMbWԾkhоJgоJgԾkhv6vuz sz sv6vu,e}d;;z,e}~Ϡ,e}d;;zd;;zϠ,e}6vvu zs zs6vvukԾhJоgJоgkԾhSM\ +bWB".;VB".;VSM\ +bWL?'B{A;$&U@;$&U@L?'B{AL?B{A';U@$&;U@$&L?B{A'SMbW\ +B"V.;B"V.;SMbW\ +khԾJg{оJg{оkhԾ6vuv sz sz6vuv,e}Ϡ;zd; sz;zd;zs OYmrOYmrzs оgJshɾ#b^|shɾ#b^|оgJ.;VB"lR llR l.;VB"$&U@;彷"Na=3b"Na=3b$&U@;U@$&;Na="3bNa="3bU@$&;彖V.;B"lR llR lV.;B"g{оJ#bshɾ^|#bshɾ^|g{оJsz OYmrsz zd;d;s zOYmrOYmrs zgJо#b^|shɾ#b^|shɾgJоVB".;lR llR lVB".;U@;$&Na=3b"Na=3b"U@;$&$&;U@"3bNa="3bNa=$&;U@.;B"V llR llR.;B"VоJgshɾ^|#bshɾ^|#bоJgz srOYmz sd;;z zsrOYmrOYm zsJоg^|shɾ#b^|shɾ#bJоgB".;V llR llRB".;V;$&U@3b"Na=3b"Na=;$&U@;U@$&3bNa="3bNa=";U@$&B"V.; llR llRB"V.;Jg{о^|#bshɾ^|#bshɾJg{о szOYmr^|#bshɾOYmrshɾ#b^|=Y==Y=shɾ#b^|lR lrLƯrLƯlR l"Na=3b85짾85짾"Na=3bNa="3b8W짾8W짾Na="3blR lrLǯrLǯlR l#bshɾ^|Y==#bshɾ^|OYmr#b^|shɾY==Y==#b^|shɾlR lrLǯrLǯlR lNa=3b"85짾85짾Na=3b""3bNa=W짾8W짾8"3bNa= llRǯrLǯrL llRshɾ^|#b==Yshɾ^|#brOYm^|shɾ#b==Y==Y^|shɾ#b llRǯrLǯrL llR3b"Na=5짾85짾83b"Na=3bNa="W짾8W짾83bNa=" llRǯrLǯrL llR^|#bshɾ=Y=ǯrL=Y=rLƯ뾒"B뾋뾒"B뾹rLƯ85짾-x-z/޾-x-z/޾85짾8W짾-z/x޾-z/x޾8W짾rLǯ"B뾋rLǯY==rLǯ"B뾋뾒"B뾋rLǯ85짾-z/޾-x-z/޾-x85짾W짾8x޾-z/x޾-z/W짾8ǯrL뾋뾒"BǯrL==YǯrL뾋뾒"B뾋뾒"BǯrL5짾8޾-x-z/޾-x-z/5짾8W짾8޾-z/x޾-z/xW짾8ǯrL뾒"B޾-z/x뾒"B-x-z/޾ +%- + +%- +-x-z/޾-z/x޾%- + +-z/x޾"B뾋-z/޾-x%- + +%- + +-z/޾-xx޾-z/- + +%x޾-z/뾋뾒"B޾-x-z/- + +%- + +%޾-x-z/޾-z/x +%- + +%- +%- + +- + +%,e}?Ϡ~?,e}?Ϡz?d;;z?d;;,e}?Ϡu?v6vs?y s?y u?v6vh?Ծkg?{оJg?{оJh?ԾkbW?\ +SMV??;B"V??;B"bW?\ +SMB{A?'L?U@?$&;U@?$&;B{A?'L?'?B{AL?$&?U@;$&?U@;彲'?B{AL?\ +?bWSM.;?VB".;?VB"\ +?bWSMʋ>hk>gJ>gJʋ>hkv>u6vz>s z>s v>u6vϠ>,e};>zd;Ϡ>,e};~;,e}Ϡ;>zd;;>zd;;,e}Ϡ6v;uv >sz >sz6v;uvk;hԾJ=g{оJ=g{оk;hԾSM;bW\ +B"=V.;B"=V.;SM;bW\ +L?;B{A';=U@$&;=U@$&L?;B{A'L?;'B{A;=$&U@;=$&U@L?;'B{ASM;\ +bWB"=.;VB"=.;VSM;\ +bWk;ԾhJ=оgJ=оgk;Ծh6v;vu >zs >zs6v;vu;Ϡ,e}d;>;z;Ϡ,e};~>,e}d;>;zd;>;z>,e}v>6vuz> sz> sv>6vu>kh>Jg>Jg>kh\ +?SMbW.;?B"V.;?B"V\ +?SMbW'?L?B{A$&?;U@$&?;U@'?L?B{AB{A?L?'U@?;$&U@?;$&B{A?L?'bW?SM\ +V?B".;V?B".;bW?SM\ +h?kԾg?Jоg?Jоh?kԾu?6vvs? zs? zu?6vv,e}?Ϡz?d;;s? zz?d;;s?y OYm?rOYm?rs?y g?{оJ#b?shɾ^|#b?shɾ^|g?{оJV??;B"lR? llR? lV??;B"U@?$&;Na=?"3bNa=?"3bU@?$&;$&?U@;彷"?Na=3b"?Na=3b$&?U@;.;?VB"?lR l?lR l.;?VB">gJsh>#b^|sh>#b^|>gJz>s >OYmrz>s ;>zd; >sz>OYmr>OYmr >szJ=g{о^|>#bshɾ^|>#bshɾJ=g{оB"=V.; l>lR l>lRB"=V.;;=U@$&3b>Na="3b>Na=";=U@$&;=$&U@3b>"Na=3b>"Na=;=$&U@B"=.;V l>lR l>lRB"=.;VJ=оg^|>shɾ#b^|>shɾ#bJ=оg >zsr>>Ym >zsd;>;zz> sr>>Ymr>>Ymz> s>Jgsh>^|#bsh>^|#b>Jg.;?B"V? llR? llR.;?B"V$&?;U@"?3bNa="?3bNa=$&?;U@U@?;$&Na=?3b"Na=?3b"U@?;$&V?B".;lR? llR? lV?B".;g?Jо#b?^|shɾ#b?^|shɾg?Jоs? zOYm?r#b?^|shɾOYm?r#b?shɾ^|Y?==Y?==#b?shɾ^|lR? lrL?ƯrL?ƯlR? lNa=?"3b8?W짾8?W짾Na=?"3b"?Na=3b?85짾?85짾"?Na=3b?lR l>rLƯ>rLƯ?lR lsh>#b^|=>Y=sh>#b^|>OYmr^|>#bshɾ=>Y==>Y=^|>#bshɾ l>lRƯ>rLƯ>rL l>lR3b>Na="W>8W>83b>Na="3b>"Na=5>85>83b>"Na= l>lRǯ>rLǯ>rL l>lR^|>shɾ#b=>=Y^|>shɾ#br>>Ymsh>^|#b=>=Y=>=Ysh>^|#b? llR>ƯrL>ƯrL? llR"?3bNa=?W짾8?W짾8"?3bNa=Na=?3b"8?5짾8?5짾Na=?3b"lR? lrL?ƯrL?ƯlR? l#b?^|shɾY?==rL?ƯY?==rL?Ư"B?뾋뾒"B?뾋rL?Ư8?W짾-z/?x޾-z/?x޾8?W짾?85짾x?-z/޾x?-z/޾?85짾>rLƯ>"B뾹>rLƯ=>Y=Ư>rL>"B뾋>"BƯ>rLW>8>-z/x>-z/xW>85>8>x-z/>x-z/5>8ǯ>rL>뾒"Bǯ>rL=>=Y>ƯrL>뾒"B>뾒"B>ƯrL?W짾8x?޾-z/x?޾-z/?W짾88?5짾-z/?޾-x-z/?޾-x8?5짾rL?Ư"B?뾋-z/?޾-x"B?뾋-z/?x޾%? + +%? + +-z/?x޾x?-z/޾ +? % +x?-z/޾>"B>-z/x +? % + +? % +>-z/x>x-z/ +? +%>x-z/>뾒"Bx?޾-z/ +? +% +? +%x?޾-z/-z/?޾-x%? + +%? + + +? % + +? +% \ No newline at end of file diff --git a/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/positions.bin b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/positions.bin new file mode 100644 index 0000000000..96c2a4e1b9 Binary files /dev/null and b/jme3-plugins/src/test/resources/gltf/lights/MODEL_ROUNDED_CUBE_PART_1/positions.bin differ diff --git a/jme3-plugins/src/test/resources/gltf/lights/lights.gltf b/jme3-plugins/src/test/resources/gltf/lights/lights.gltf new file mode 100644 index 0000000000..1fb7ee2ba8 --- /dev/null +++ b/jme3-plugins/src/test/resources/gltf/lights/lights.gltf @@ -0,0 +1,226 @@ +{ + "extensionsUsed": [ + "KHR_lights_punctual" + ], + "extensions": { + "KHR_lights_punctual": { + "lights": [ + { + "color": [ + 1.0, + 0.9, + 0.7 + ], + "name": "Directional", + "intensity": 3.0, + "type": "directional" + }, + { + "color": [ + 1.0, + 0.0, + 0.0 + ], + "name": "Point", + "intensity": 20.0, + "type": "point" + }, + { + "color": [ + 0.3, + 0.7, + 1.0 + ], + "name": "Spot", + "intensity": 40.0, + "type": "spot", + "spot": { + "innerConeAngle": 0.785398163397448, + "outerConeAngle": 1.57079632679 + } + } + ] + } + }, + "accessors": [ + { + "bufferView": 0, + "byteOffset": 0, + "componentType": 5126, + "count": 3456, + "max": [ + 10, + 19.949111938476562, + 10 + ], + "min": [ + -10, + -0.050886999815702438, + -10 + ], + "type": "VEC3" + }, + { + "bufferView": 1, + "byteOffset": 0, + "componentType": 5126, + "count": 3456, + "type": "VEC3" + }, + { + "bufferView": 2, + "byteOffset": 0, + "componentType": 5125, + "count": 5172, + "max": [ + 3455 + ], + "min": [ + 0 + ], + "type": "SCALAR" + } + ], + "asset": { + "copyright": "2017 (c) Adobe Corp", + "minVersion": "2.0", + "version": "2.0" + }, + "bufferViews": [ + { + "buffer": 0, + "byteLength": 41472, + "byteOffset": 0 + }, + { + "buffer": 1, + "byteLength": 41472, + "byteOffset": 0 + }, + { + "buffer": 2, + "byteLength": 20688, + "byteOffset": 0, + "target": 34963 + } + ], + "buffers": [ + { + "byteLength": 41472, + "uri": "MODEL_ROUNDED_CUBE_PART_1/positions.bin" + }, + { + "byteLength": 41472, + "uri": "MODEL_ROUNDED_CUBE_PART_1/normals.bin" + }, + { + "byteLength": 20688, + "uri": "MODEL_ROUNDED_CUBE_PART_1/indices.bin" + } + ], + "cameras": [ + { + "name": "render_camera", + "perspective": { + "aspectRatio": 1.3333333730697632, + "yfov": 0.58904862403869629, + "zfar": 100, + "znear": 9.9999997473787516e-05 + }, + "type": "perspective" + }, + { + "name": "render_camera2", + "perspective": { + "aspectRatio": 1.3333333730697632, + "yfov": 0.58904862403869629, + "zfar": 100, + "znear": 9.9999997473787516e-05 + }, + "type": "perspective" + } + ], + "materials": [ + { + "doubleSided": true, + "name": "Rounded Cube Material", + "pbrMetallicRoughness": { + "baseColorFactor": [ + 0.63075679540634155, + 0.63075679540634155, + 0.63075679540634155, + 1 + ], + "metallicFactor": 0, + "roughnessFactor": 0.50300002098083496 + } + } + ], + "meshes": [ + { + "name": "MODEL_ROUNDED_CUBE_PART_1", + "primitives": [ + { + "attributes": { + "NORMAL": 1, + "POSITION": 0 + }, + "indices": 2, + "material": 0, + "mode": 4 + } + ] + } + ], + "nodes": [ + { + "extensions": { + "KHR_lights_punctual": { + "light": 1 + } + }, + "matrix": [ + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 0, + 0, + 1, + 0, + 0, + 22, + 0, + 1 + ], + "name": "point_light" + }, + { + "name": "directional_light", + "extensions": { + "KHR_lights_punctual": { + "light": 0 + } + } + }, + { + "mesh": 0, + "name": "MODEL_ROUNDED_CUBE_PART_1model_N3D" + } + ], + "scene": 0, + "scenes": [ + { + "name": "scene", + "nodes": [ + 0, + 1, + 2 + ] + } + ] +} \ No newline at end of file diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java index 22779cf254..1d91ca903b 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMInputCapsule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -48,21 +48,20 @@ import org.w3c.dom.*; /** - * Part of the jME XML IO system as introduced in the google code jmexml project. + * Part of the jME XML IO system as introduced in the Google Code jmexml project. * * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 * @author blaine */ public class DOMInputCapsule implements InputCapsule { - private static final Logger logger = - Logger.getLogger(DOMInputCapsule.class .getName()); + private static final Logger logger = Logger.getLogger(DOMInputCapsule.class .getName()); private Document doc; - private Element currentElem; + private Element currentElement; private XMLImporter importer; private boolean isAtRoot = true; - private Map referencedSavables = new HashMap(); + private Map referencedSavables = new HashMap<>(); private int[] classHierarchyVersions; private Savable savable; @@ -70,1437 +69,863 @@ public class DOMInputCapsule implements InputCapsule { public DOMInputCapsule(Document doc, XMLImporter importer) { this.doc = doc; this.importer = importer; - currentElem = doc.getDocumentElement(); + currentElement = doc.getDocumentElement(); - String version = currentElem.getAttribute("format_version"); + // file version is always unprefixed for backwards compatibility + String version = currentElement.getAttribute("format_version"); importer.formatVersion = version.equals("") ? 0 : Integer.parseInt(version); } + @Override public int getSavableVersion(Class desiredClass) { if (classHierarchyVersions != null){ - return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, - classHierarchyVersions, importer.getFormatVersion()); + return SavableClassUtil.getSavedSavableVersion(savable, desiredClass, classHierarchyVersions, importer.getFormatVersion()); }else{ return 0; } } - - private static String decodeString(String s) { - if (s == null) { - return null; - } - s = s.replaceAll("\\"", "\"").replaceAll("\\<", "<").replaceAll("\\&", "&"); - return s; - } - - private Element findFirstChildElement(Element parent) { - Node ret = parent.getFirstChild(); - while (ret != null && (!(ret instanceof Element))) { - ret = ret.getNextSibling(); - } - return (Element) ret; - } - private Element findChildElement(Element parent, String name) { - if (parent == null) { + private Element findChildElement(String name) { + if (currentElement == null) { return null; } - Node ret = parent.getFirstChild(); + Node ret = currentElement.getFirstChild(); while (ret != null && (!(ret instanceof Element) || !ret.getNodeName().equals(name))) { ret = ret.getNextSibling(); } return (Element) ret; } - private Element findNextSiblingElement(Element current) { - Node ret = current.getNextSibling(); - while (ret != null) { - if (ret instanceof Element) { - return (Element) ret; - } - ret = ret.getNextSibling(); + // helper method to reduce duplicate code. checks that number of tokens in the "data" attribute matches the "size" attribute + // and returns an array of parsed primitives. + private Object readPrimitiveArrayHelper(Element element, String primType) throws IOException { + if (element == null) { + return null; } - return null; - } - public byte readByte(String name, byte defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Byte.parseByte(tmpString); - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); + + if (sizeString.isEmpty()) { + return null; } - } - public byte[] readByteArray(String name, byte[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { + String[] tokens = parseTokens(XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_DATA)); + + if(!sizeString.isEmpty()) { + try { int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bytes for '" + name + if (tokens.length != requiredSize) { + throw new IOException("Wrong token count for '" + element.getTagName() + "'. size says " + requiredSize + ", data contains " - + strings.length); - } - byte[] tmp = new byte[strings.length]; - for (int i = 0; i < strings.length; i++) { - tmp[i] = Byte.parseByte(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + + tokens.length); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); + } + } + + try { + switch (primType) { + case "byte": + byte[] byteArray = new byte[tokens.length]; + for (int i = 0; i < tokens.length; i++) byteArray[i] = Byte.parseByte(tokens[i]); + return byteArray; + case "short": + short[] shortArray = new short[tokens.length]; + for (int i = 0; i < tokens.length; i++) shortArray[i] = Short.parseShort(tokens[i]); + return shortArray; + case "int": + int[] intArray = new int[tokens.length]; + for (int i = 0; i < tokens.length; i++) intArray[i] = Integer.parseInt(tokens[i]); + return intArray; + case "long": + long[] longArray = new long[tokens.length]; + for (int i = 0; i < tokens.length; i++) longArray[i] = Long.parseLong(tokens[i]); + return longArray; + case "float": + float[] floatArray = new float[tokens.length]; + for (int i = 0; i < tokens.length; i++) floatArray[i] = Float.parseFloat(tokens[i]); + return floatArray; + case "double": + double[] doubleArray = new double[tokens.length]; + for (int i = 0; i < tokens.length; i++) doubleArray[i] = Double.parseDouble(tokens[i]); + return doubleArray; + case "boolean": + boolean[] booleanArray = new boolean[tokens.length]; + for (int i = 0; i < tokens.length; i++) booleanArray[i] = Boolean.parseBoolean(tokens[i]); + return booleanArray; + default: + throw new IOException(); // will never happen + } + } catch(NumberFormatException nfe) { + throw new IOException(nfe); + } + } + + // helper method to reduce duplicate code. checks that number of child elements matches the "size" attribute + // and returns a convenient list of child elements. + private List getObjectArrayElements(Element element) throws IOException { + if (element == null) { + return null; } - } - public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + String sizeString = XMLUtils.getAttribute(importer.getFormatVersion(), element, XMLExporter.ATTRIBUTE_SIZE); - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List byteArrays = new ArrayList(); + if (sizeString.isEmpty()) { + return null; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - byteArrays.add(readByteArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (byteArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + byteArrays.size()); + NodeList nodes = element.getChildNodes(); + + List elements = new ArrayList<>(); + for (int i = 0; i < nodes.getLength(); i++) { + Node node = nodes.item(i); + if (node instanceof Element) { + elements.add((Element) node); } - currentElem = (Element) currentElem.getParentNode(); - return byteArrays.toArray(new byte[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; } - } - public int readInt(String name, int defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; try { - return Integer.parseInt(tmpString); - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + int requiredSize = Integer.parseInt(sizeString); + if (elements.size() != requiredSize) { + throw new IOException("DOMInputCapsule.getObjectArrayElements(): Wrong element count for '" + element.getTagName() + + "'. size says " + requiredSize + + ", data contains " + + elements.size()); + } + } catch (NumberFormatException ex) { + throw new IOException("Invalid size for '" + element.getTagName() + "': " + sizeString); } + + return elements; } - public int[] readIntArray(String name, int[] defVal) throws IOException { + @Override + public byte readByte(String name, byte defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of ints for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - int[] tmp = new int[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Integer.parseInt(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; + return Byte.parseByte(attribute); } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + throw new IOException(nfe); } } - public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); + @Override + public byte[] readByteArray(String name, byte[] defVal) throws IOException { + byte[] array = (byte[]) readPrimitiveArrayHelper(findChildElement(name), "byte"); + return array != null ? array : defVal; + } + @Override + public byte[][] readByteArray2D(String name, byte[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + if (arrayEntryElements == null) { + return defVal; + } + byte[][] arrays = new byte[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (byte[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "byte"); + } - NodeList nodes = currentElem.getChildNodes(); - List intArrays = new ArrayList(); + return arrays; + } - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - intArrays.add(readIntArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (intArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + intArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return intArrays.toArray(new int[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + @Override + public short readShort(String name, short defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } - } - public float readFloat(String name, float defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; try { - return Float.parseFloat(tmpString); + return Short.parseShort(attribute); } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + throw new IOException(nfe); } } - public float[] readFloatArray(String name, float[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of floats for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - float[] tmp = new float[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Float.parseFloat(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + @Override + public short[] readShortArray(String name, short[] defVal) throws IOException { + short[] array = (short[]) readPrimitiveArrayHelper(findChildElement(name), "short"); + return array != null ? array : defVal; } - public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { - /* Why does this one method ignore the 'size attr.? */ - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); - int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + @Override + public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - float[][] tmp = new float[size_outer][size_inner]; + if (arrayEntryElements == null) { + return defVal; + } - String[] strings = parseTokens(tmpEl.getAttribute("data")); - for (int i = 0; i < size_outer; i++) { - tmp[i] = new float[size_inner]; - for (int k = 0; k < size_inner; k++) { - tmp[i][k] = Float.parseFloat(strings[i]); - } - } - return tmp; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + short[][] arrays = new short[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (short[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "short"); } + + return arrays; } - public double readDouble(String name, double defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Double.parseDouble(tmpString); - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + @Override + public int readInt(String name, int defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } - } - public double[] readDoubleArray(String name, double[] defVal) throws IOException { try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of doubles for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - double[] tmp = new double[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Double.parseDouble(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; + return Integer.parseInt(attribute); } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + throw new IOException(nfe); } } - public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List doubleArrays = new ArrayList(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - doubleArrays.add(readDoubleArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (doubleArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + doubleArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return doubleArrays.toArray(new double[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + @Override + public int[] readIntArray(String name, int[] defVal) throws IOException { + int[] array = (int[]) readPrimitiveArrayHelper(findChildElement(name), "int"); + return array != null ? array : defVal; + } + + @Override + public int[][] readIntArray2D(String name, int[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + int[][] arrays = new int[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (int[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "int"); } + + return arrays; } + @Override public long readLong(String name, long defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Long.parseLong(tmpString); + return Long.parseLong(attribute); } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + throw new IOException(nfe); } } + @Override public long[] readLongArray(String name, long[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of longs for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - long[] tmp = new long[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Long.parseLong(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + long[] array = (long[]) readPrimitiveArrayHelper(findChildElement(name), "long"); + return array != null ? array : defVal; } + @Override public long[][] readLongArray2D(String name, long[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List longArrays = new ArrayList(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - longArrays.add(readLongArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (longArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + longArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return longArrays.toArray(new long[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + long[][] arrays = new long[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (long[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "long"); } + + return arrays; } - public short readShort(String name, short defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + @Override + public float readFloat(String name, float defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - return Short.parseShort(tmpString); + return Float.parseFloat(attribute); } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + throw new IOException(nfe); } } - public short[] readShortArray(String name, short[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of shorts for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - short[] tmp = new short[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Short.parseShort(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + @Override + public float[] readFloatArray(String name, float[] defVal) throws IOException { + float[] array = (float[]) readPrimitiveArrayHelper(findChildElement(name), "float"); + return array != null ? array : defVal; + } + + @Override + public float[][] readFloatArray2D(String name, float[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + float[][] arrays = new float[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (float[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "float"); } + + return arrays; } - public short[][] readShortArray2D(String name, short[][] defVal) throws IOException { + @Override + public double readDouble(String name, double defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } + return Double.parseDouble(attribute); + } catch (NumberFormatException nfe) { + throw new IOException(nfe); + } + } + + @Override + public double[] readDoubleArray(String name, double[] defVal) throws IOException { + double[] array = (double[]) readPrimitiveArrayHelper(findChildElement(name), "double"); + return array != null ? array : defVal; + } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List shortArrays = new ArrayList(); + @Override + public double[][] readDoubleArray2D(String name, double[][] defVal) throws IOException { + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - shortArrays.add(readShortArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (shortArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + shortArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return shortArrays.toArray(new short[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + if (arrayEntryElements == null) { + return defVal; + } + + double[][] arrays = new double[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (double[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "double"); } + + return arrays; } + @Override public boolean readBoolean(String name, boolean defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return Boolean.parseBoolean(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; } + + //parseBoolean doesn't throw anything, just returns false if the string isn't "true", ignoring case. + return Boolean.parseBoolean(attribute); } + @Override public boolean[] readBooleanArray(String name, boolean[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of bools for '" + name - + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - boolean[] tmp = new boolean[strings.length]; - for (int i = 0; i < tmp.length; i++) { - tmp[i] = Boolean.parseBoolean(strings[i]); - } - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; - } + boolean[] array = (boolean[]) readPrimitiveArrayHelper(findChildElement(name), "boolean"); + return array != null ? array : defVal; } + @Override public boolean[][] readBooleanArray2D(String name, boolean[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List booleanArrays = new ArrayList(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - booleanArrays.add(readBooleanArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (booleanArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + booleanArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return booleanArrays.toArray(new boolean[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + List arrayEntryElements = getObjectArrayElements(findChildElement(name)); + + if (arrayEntryElements == null) { + return defVal; + } + + boolean[][] arrays = new boolean[arrayEntryElements.size()][]; + for (int i = 0; i < arrayEntryElements.size(); i++) { + arrays[i] = (boolean[]) readPrimitiveArrayHelper(arrayEntryElements.get(i), "boolean"); } + + return arrays; } + @Override public String readString(String name, String defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; - try { - return decodeString(tmpString); - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + String attribute = null; + + // Element.getAttribute() returns an empty string if the specified attribute does not exist. + // see https://www.w3.org/2003/01/dom2-javadoc/org/w3c/dom/Element.html#getAttribute_java.lang.String_ + // somewhat confusing since the w3c JS api equivalent returns null as one would expect. + // https://www.w3schools.com/jsref/met_element_getattribute.asp + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, name)) { + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); } + + if (attribute == null) { + return defVal; + } + + return attribute; } + @Override public String[] readStringArray(String name, String[] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = tmpEl.getChildNodes(); - List strings = new ArrayList(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("String")) { - // Very unsafe assumption - strings.add(((Element) n).getAttributeNode("value").getValue()); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + strings.size()); - } - return strings.toArray(new String[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + List arrayElements = getObjectArrayElements(findChildElement(name)); + + if (arrayElements == null) { + return defVal; } + + Element oldElement = currentElement; + + String[] array = new String[arrayElements.size()]; + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + array[i] = readString("value", null); + } + + currentElement = oldElement; + + return array; } + @Override public String[][] readStringArray2D(String name, String[][] defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; - } - String sizeString = tmpEl.getAttribute("size"); - NodeList nodes = currentElem.getChildNodes(); - List stringArrays = new ArrayList(); - - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().contains("array")) { - // Very unsafe assumption - stringArrays.add(readStringArray(n.getNodeName(), null)); - } - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (stringArrays.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + stringArrays.size()); - } - currentElem = (Element) currentElem.getParentNode(); - return stringArrays.toArray(new String[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + Element outerArrayElement = findChildElement(name); + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + if (innerArrayElements == null) { + return defVal; } + + currentElement = outerArrayElement; + + String[][] arrays = new String[innerArrayElements.size()][]; + for (int i = 0; i < innerArrayElements.size(); i++) { + arrays[i] = readStringArray(innerArrayElements.get(i).getTagName(), null); + } + + currentElement = (Element) currentElement.getParentNode(); + + return arrays; } - public BitSet readBitSet(String name, BitSet defVal) throws IOException { - String tmpString = currentElem.getAttribute(name); - if (tmpString == null || tmpString.length() < 1) return defVal; + @Override + public > T readEnum(String name, Class enumType, T defVal) throws IOException { + String attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute.isEmpty()) { + return defVal; + } + try { - BitSet set = new BitSet(); - String[] strings = parseTokens(tmpString); - for (int i = 0; i < strings.length; i++) { - int isSet = Integer.parseInt(strings[i]); - if (isSet == 1) { - set.set(i); - } - } - return set; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + return Enum.valueOf(enumType, attribute); + } catch (IllegalArgumentException | NullPointerException e) { + throw new IOException(e); } } - public Savable readSavable(String name, Savable defVal) throws IOException { - Savable ret = defVal; - if (name != null && name.equals("")) - logger.warning("Reading Savable String with name \"\"?"); - try { - Element tmpEl = null; - if (name != null) { - tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - } else if (isAtRoot) { - tmpEl = doc.getDocumentElement(); - isAtRoot = false; - } else { - tmpEl = findFirstChildElement(currentElem); - } - currentElem = tmpEl; - ret = readSavableFromCurrentElem(defVal); - if (currentElem.getParentNode() instanceof Element) { - currentElem = (Element) currentElem.getParentNode(); - } else { - currentElem = null; + @Override + public BitSet readBitSet(String name, BitSet defVal) throws IOException { + String attribute = null; + + attribute = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, name); + + if (attribute == null || attribute.isEmpty()) { + return defVal; + } + + String[] strings = parseTokens(attribute); + BitSet bitSet = new BitSet(); + for (int i = 0; i < strings.length; i++) { + if (strings[i].equals("1")) { + bitSet.set(i); } - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } - return ret; - } - private Savable readSavableFromCurrentElem(Savable defVal) throws - InstantiationException, ClassNotFoundException, - NoSuchMethodException, InvocationTargetException, - IOException, IllegalAccessException { - Savable ret = defVal; - Savable tmp = null; + return bitSet; + } - if (currentElem == null || currentElem.getNodeName().equals("null")) { - return null; + private Savable readSavableFromCurrentElement(Savable defVal) throws IOException { + if (currentElement == null || !currentElement.hasAttributes()) { + return defVal; } - String reference = currentElem.getAttribute("ref"); - if (reference.length() > 0) { - ret = referencedSavables.get(reference); + + String reference = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE); + if (!reference.isEmpty()) { + return referencedSavables.get(reference); } else { - String className = currentElem.getNodeName(); - if (currentElem.hasAttribute("class")) { - className = currentElem.getAttribute("class"); + // for backwards compatibility with old XML files. previous version of DOMOutputCapsule would only write the class attribute + // if the element name wasn't the class name. + String className = currentElement.getNodeName(); + if (XMLUtils.hasAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS)) { + className = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_CLASS); } else if (defVal != null) { className = defVal.getClass().getName(); } - tmp = SavableClassUtil.fromName(className, null); + + Savable tmp = null; + try { + tmp = SavableClassUtil.fromName(className); + } catch (ClassNotFoundException | IllegalAccessException | InstantiationException | InvocationTargetException e) { + throw new IOException(e); + } - String versionsStr = currentElem.getAttribute("savable_versions"); + String versionsStr = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS); if (versionsStr != null && !versionsStr.equals("")){ String[] versionStr = versionsStr.split(","); classHierarchyVersions = new int[versionStr.length]; - for (int i = 0; i < classHierarchyVersions.length; i++){ - classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + try { + for (int i = 0; i < classHierarchyVersions.length; i++) { + classHierarchyVersions[i] = Integer.parseInt(versionStr[i].trim()); + } + } catch (NumberFormatException nfe) { + throw new IOException(nfe); } }else{ classHierarchyVersions = null; } - String refID = currentElem.getAttribute("reference_ID"); - if (refID.length() < 1) refID = currentElem.getAttribute("id"); - if (refID.length() > 0) referencedSavables.put(refID, tmp); + String refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, XMLExporter.ATTRIBUTE_REFERENCE_ID); + + // what does this line do? guessing backwards compatibility? + if (refID.isEmpty()) refID = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "id"); + + if (!refID.isEmpty()) referencedSavables.put(refID, tmp); + if (tmp != null) { // Allows reading versions from this savable savable = tmp; + tmp.read(importer); - ret = tmp; + + return tmp; + } else { + return defVal; } } + } + + @Override + public Savable readSavable(String name, Savable defVal) throws IOException { + Savable ret = defVal; + + if (name != null && name.equals("")) + logger.warning("Reading Savable String with name \"\"?"); + + Element old = currentElement; + + if (isAtRoot) { + currentElement = doc.getDocumentElement(); + isAtRoot = false; + } else { + currentElement = findChildElement(name); + } + + ret = readSavableFromCurrentElement(defVal); + + currentElement = old; + return ret; } + @Override public Savable[] readSavableArray(String name, Savable[] defVal) throws IOException { - Savable[] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + Element arrayElement = findChildElement(name); - String sizeString = tmpEl.getAttribute("size"); - List savables = new ArrayList(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException("Wrong number of Savables for '" - + name + "'. size says " + requiredSize - + ", data contains " + savables.size()); - } - ret = savables.toArray(new Savable[0]); - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + if (arrayElement == null || !arrayElement.hasAttributes()) { + return defVal; } - } - public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { - Savable[][] ret = defVal; - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + List arrayElements = getObjectArrayElements(arrayElement); - int size_outer = Integer.parseInt(tmpEl.getAttribute("size_outer")); - int size_inner = Integer.parseInt(tmpEl.getAttribute("size_outer")); + Savable[] savableArray = new Savable[arrayElements.size()]; - Savable[][] tmp = new Savable[size_outer][size_inner]; - currentElem = findFirstChildElement(tmpEl); - for (int i = 0; i < size_outer; i++) { - for (int j = 0; j < size_inner; j++) { - tmp[i][j] = (readSavableFromCurrentElem(null)); - if (i == size_outer - 1 && j == size_inner - 1) { - break; - } - currentElem = findNextSiblingElement(currentElem); - } - } - ret = tmp; - currentElem = (Element) tmpEl.getParentNode(); - return ret; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + Element old = currentElement; + + for (int i = 0; i < arrayElements.size(); i++) { + currentElement = arrayElements.get(i); + savableArray[i] = readSavableFromCurrentElement(null); } + + currentElement = old; + + return savableArray; } - public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + public Savable[][] readSavableArray2D(String name, Savable[][] defVal) throws IOException { + Element outerArrayElement = findChildElement(name); + + if (outerArrayElement == null || !outerArrayElement.hasAttributes()) { + return defVal; + } + + List innerArrayElements = getObjectArrayElements(outerArrayElement); - String sizeString = tmpEl.getAttribute("size"); - ArrayList savables = new ArrayList(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - savables.add(readSavableFromCurrentElem(null)); + Savable[][] savableArray2D = new Savable[innerArrayElements.size()][]; + + Element old = currentElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + List savableElements = getObjectArrayElements(innerArrayElements.get(i)); + + if (savableElements == null) { + continue; } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (savables.size() != requiredSize) - throw new IOException( - "Wrong number of Savable arrays for '" + name - + "'. size says " + requiredSize - + ", data contains " + savables.size()); + + savableArray2D[i] = new Savable[savableElements.size()]; + for (int j = 0; j < savableElements.size(); j++) { + currentElement = savableElements.get(j); + savableArray2D[i][j] = readSavableFromCurrentElement(null); } - currentElem = (Element) tmpEl.getParentNode(); - return savables; - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = old; + + return savableArray2D; } - public ArrayList[] readSavableArrayListArray( - String name, ArrayList[] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - - String sizeString = tmpEl.getAttribute("size"); - int requiredSize = (sizeString.length() > 0) - ? Integer.parseInt(sizeString) - : -1; - - ArrayList sal; - List> savableArrayLists = - new ArrayList>(); - int i = -1; - while (true) { - sal = readSavableArrayList("SavableArrayList_" + ++i, null); - if (sal == null && savableArrayLists.size() >= requiredSize) - break; - savableArrayLists.add(sal); - } + @Override + public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { + Element element = findChildElement(name); - if (requiredSize > -1 && savableArrayLists.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + savableArrayLists.size()); - currentElem = (Element) tmpEl.getParentNode(); - return savableArrayLists.toArray(new ArrayList[0]); - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + byte[] array = (byte[]) readPrimitiveArrayHelper(element, "byte"); + + if (array == null) { + return defVal; } + + return (ByteBuffer) BufferUtils.createByteBuffer(array.length).put(array).rewind(); } - public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } - currentElem = tmpEl; - String sizeString = tmpEl.getAttribute("size"); - - ArrayList[] arr; - List[]> sall = new ArrayList[]>(); - int i = -1; - while ((arr = readSavableArrayListArray( - "SavableArrayListArray_" + ++i, null)) != null) sall.add(arr); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (sall.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + sall.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return sall.toArray(new ArrayList[0][]); - } catch (IOException ioe) { - throw ioe; - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; + @Override + public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { + Element element = findChildElement(name); + + short[] array = (short[]) readPrimitiveArrayHelper(element, "short"); + + if (array == null) { + return defVal; } + + return (ShortBuffer) BufferUtils.createShortBuffer(array.length).put(array).rewind(); } - public ArrayList readFloatBufferArrayList( - String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { + Element element = findChildElement(name); - String sizeString = tmpEl.getAttribute("size"); - ArrayList tmp = new ArrayList(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readFloatBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException( - "String array contains wrong element count. " - + "Specified size " + requiredSize - + ", data contains " + tmp.size()); - } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + int[] array = (int[]) readPrimitiveArrayHelper(element, "int"); + + if (array == null) { + return defVal; } + + return (IntBuffer) BufferUtils.createIntBuffer(array.length).put(array).rewind(); } - public Map readSavableMap(String name, Map defVal) throws IOException { - Map ret; - Element tempEl; + @Override + public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { + Element element = findChildElement(name); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - ret = new HashMap(); + float[] array = (float[]) readPrimitiveArrayHelper(element, "float"); - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); - Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); - ret.put(key, val); - } + if (array == null) { + return defVal; } - currentElem = (Element) tempEl.getParentNode(); - return ret; + + return (FloatBuffer) BufferUtils.createFloatBuffer(array.length).put(array).rewind(); } - public Map readStringSavableMap(String name, Map defVal) throws IOException { - Map ret = null; - Element tempEl; + @Override + public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { + byte[][] byteArray2D = readByteArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new HashMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - String key = currentElem.getAttribute("key"); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; + if (byteArray2D == null) { + return defVal; + } + + ArrayList byteBufferList = new ArrayList<>(byteArray2D.length); + for (byte[] byteArray : byteArray2D) { + if (byteArray == null) { + byteBufferList.add(null); + } else { + byteBufferList.add((ByteBuffer) BufferUtils.createByteBuffer(byteArray.length).put(byteArray).rewind()); } - currentElem = (Element) tempEl.getParentNode(); - return ret; + } + + return byteBufferList; } - public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { - IntMap ret = null; - Element tempEl; + @Override + public ArrayList readFloatBufferArrayList(String name, ArrayList defVal) throws IOException { + float[][] floatArray2D = readFloatArray2D(name, null); - if (name != null) { - tempEl = findChildElement(currentElem, name); - } else { - tempEl = currentElem; - } - if (tempEl != null) { - ret = new IntMap(); - - NodeList nodes = tempEl.getChildNodes(); - for (int i = 0; i < nodes.getLength(); i++) { - Node n = nodes.item(i); - if (n instanceof Element && n.getNodeName().equals("MapEntry")) { - Element elem = (Element) n; - currentElem = elem; - int key = Integer.parseInt(currentElem.getAttribute("key")); - Savable val = readSavable("Savable", null); - ret.put(key, val); - } - } - } else { - return defVal; - } - currentElem = (Element) tempEl.getParentNode(); - return ret; - } + if (floatArray2D == null) { + return defVal; + } - /** - * reads from currentElem if name is null - */ - public FloatBuffer readFloatBuffer(String name, FloatBuffer defVal) throws IOException { - try { - Element tmpEl; - if (name != null) { - tmpEl = findChildElement(currentElem, name); + ArrayList floatBufferList = new ArrayList<>(floatArray2D.length); + for (float[] floatArray : floatArray2D) { + if (floatArray == null) { + floatBufferList.add(null); } else { - tmpEl = currentElem; - } - if (tmpEl == null) { - return defVal; + floatBufferList.add((FloatBuffer) BufferUtils.createFloatBuffer(floatArray.length).put(floatArray).rewind()); } - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of float buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - FloatBuffer tmp = BufferUtils.createFloatBuffer(strings.length); - for (String s : strings) tmp.put(Float.parseFloat(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; } + + return floatBufferList; } - public IntBuffer readIntBuffer(String name, IntBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + @SuppressWarnings("unchecked") + public ArrayList readSavableArrayList(String name, ArrayList defVal) throws IOException { + Savable[] savableArray = readSavableArray(name, null); - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of int buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); - } - IntBuffer tmp = BufferUtils.createIntBuffer(strings.length); - for (String s : strings) tmp.put(Integer.parseInt(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; + if (savableArray == null) { + return defVal; } + + return new ArrayList(Arrays.asList(savableArray)); } - public ByteBuffer readByteBuffer(String name, ByteBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + @SuppressWarnings("unchecked") + public ArrayList[] readSavableArrayListArray(String name, ArrayList[] defVal) throws IOException { + Savable[][] savableArray2D = readSavableArray2D(name, null); - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of byte buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (savableArray2D == null) { + return defVal; + } + + ArrayList[] savableArrayListArray = new ArrayList[savableArray2D.length]; + for (int i = 0; i < savableArray2D.length; i++) { + if (savableArray2D[i] != null) { + savableArrayListArray[i] = new ArrayList(Arrays.asList(savableArray2D[i])); } - ByteBuffer tmp = BufferUtils.createByteBuffer(strings.length); - for (String s : strings) tmp.put(Byte.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; } + + return savableArrayListArray; } - public ShortBuffer readShortBuffer(String name, ShortBuffer defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + @SuppressWarnings("unchecked") + public ArrayList[][] readSavableArrayListArray2D(String name, ArrayList[][] defVal) throws IOException { + Element outerArrayElement = findChildElement(name); - String sizeString = tmpEl.getAttribute("size"); - String[] strings = parseTokens(tmpEl.getAttribute("data")); - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (strings.length != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + strings.length); + if (outerArrayElement == null) { + return defVal; + } + + List innerArrayElements = getObjectArrayElements(outerArrayElement); + + ArrayList[][] savableArrayListArray2D = new ArrayList[innerArrayElements.size()][]; + + Element old = currentElement; + currentElement = outerArrayElement; + + for (int i = 0; i < innerArrayElements.size(); i++) { + if (innerArrayElements.get(i) != null) { + + savableArrayListArray2D[i] = readSavableArrayListArray(innerArrayElements.get(i).getTagName(), null); } - ShortBuffer tmp = BufferUtils.createShortBuffer(strings.length); - for (String s : strings) tmp.put(Short.valueOf(s)); - tmp.flip(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; } + + currentElement = old; + + return savableArrayListArray2D; } - public ArrayList readByteBufferArrayList(String name, ArrayList defVal) throws IOException { - try { - Element tmpEl = findChildElement(currentElem, name); - if (tmpEl == null) { - return defVal; - } + @Override + public Map readSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); - String sizeString = tmpEl.getAttribute("size"); - ArrayList tmp = new ArrayList(); - for (currentElem = findFirstChildElement(tmpEl); - currentElem != null; - currentElem = findNextSiblingElement(currentElem)) { - tmp.add(readByteBuffer(null, null)); - } - if (sizeString.length() > 0) { - int requiredSize = Integer.parseInt(sizeString); - if (tmp.size() != requiredSize) - throw new IOException("Wrong number of short buffers for '" - + name + "'. size says " + requiredSize - + ", data contains " + tmp.size()); + if (mapElement == null) { + return defVal; + } + + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + Savable key = readSavable(XMLExporter.ELEMENT_KEY, null); + Savable val = readSavable(XMLExporter.ELEMENT_VALUE, null); + ret.put(key, val); } - currentElem = (Element) tmpEl.getParentNode(); - return tmp; - } catch (IOException ioe) { - throw ioe; - } catch (NumberFormatException nfe) { - IOException io = new IOException(nfe.toString()); - io.initCause(nfe); - throw io; - } catch (DOMException de) { - IOException io = new IOException(de.toString()); - io.initCause(de); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; + } + + @Override + public Map readStringSavableMap(String name, Map defVal) throws IOException { + Element mapElement = findChildElement(name); + + if (mapElement == null) { + return defVal; } - public > T readEnum(String name, Class enumType, - T defVal) throws IOException { - T ret = defVal; - try { - String eVal = currentElem.getAttribute(name); - if (eVal != null && eVal.length() > 0) { - ret = Enum.valueOf(enumType, eVal); + Map ret = new HashMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + String key = XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key"); + Savable val = readSavable("Savable", null); + ret.put(key, val); } - } catch (Exception e) { - IOException io = new IOException(e.toString()); - io.initCause(e); - throw io; } + + currentElement = (Element) mapElement.getParentNode(); + return ret; + } + + @Override + public IntMap readIntSavableMap(String name, IntMap defVal) throws IOException { + Element mapElement = findChildElement(name); + + if (mapElement == null) { + return defVal; } + IntMap ret = new IntMap(); + + NodeList nodes = mapElement.getChildNodes(); + for (int i = 0; i < nodes.getLength(); i++) { + Node n = nodes.item(i); + if (n instanceof Element && n.getNodeName().equals("MapEntry")) { + Element elem = (Element) n; + currentElement = elem; + int key = Integer.parseInt(XMLUtils.getAttribute(importer.getFormatVersion(), currentElement, "key")); + Savable val = readSavable("Savable", null); + ret.put(key, val); + } + } + + currentElement = (Element) mapElement.getParentNode(); + + return ret; + } + private static final String[] zeroStrings = new String[0]; protected String[] parseTokens(String inString) { diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java index 0c5db7e231..58d1713de9 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMOutputCapsule.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2016 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -39,29 +39,27 @@ import com.jme3.export.SavableClassUtil; import com.jme3.util.IntMap; import com.jme3.util.IntMap.Entry; +import java.lang.reflect.Array; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; import java.util.*; -import org.w3c.dom.DOMException; import org.w3c.dom.Document; import org.w3c.dom.Element; /** - * Part of the jME XML IO system as introduced in the google code jmexml project. + * Part of the jME XML IO system as introduced in the Google Code jmexml project. * * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 */ public class DOMOutputCapsule implements OutputCapsule { - - private static final String dataAttributeName = "data"; private Document doc; private Element currentElement; private JmeExporter exporter; - private Map writtenSavables = new IdentityHashMap(); + private Map writtenSavables = new IdentityHashMap<>(); public DOMOutputCapsule(Document doc, JmeExporter exporter) { this.doc = doc; @@ -80,6 +78,7 @@ public Document getDoc() { private Element appendElement(String name) { Element ret = doc.createElement(name); if (currentElement == null) { + // file version is always unprefixed for backwards compatibility ret.setAttribute("format_version", Integer.toString(FormatVersion.VERSION)); doc.appendChild(ret); } else { @@ -89,397 +88,258 @@ private Element appendElement(String name) { return ret; } - private static String encodeString(String s) { - if (s == null) { - return null; - } - s = s.replaceAll("\\&", "&") - .replaceAll("\\\"", """) - .replaceAll("\\<", "<"); - return s; - } + // helper function to reduce duplicate code. uses reflection to write an array of any primitive type. + // also has optional position argument for writing buffers. + private void writePrimitiveArrayHelper(Object value, String name) throws IOException { + StringBuilder sb = new StringBuilder(); - @Override - public void write(byte value, String name, byte defVal) throws IOException { - if (value == defVal) { - return; + for(int i = 0; i < Array.getLength(value); i++) { + sb.append(Array.get(value, i)); + sb.append(" "); } - currentElement.setAttribute(name, String.valueOf(value)); - } - @Override - public void write(byte[] value, String name, byte[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (byte b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); - } + // remove last space + sb.setLength(Math.max(0, sb.length() - 1)); + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(Array.getLength(value))); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_DATA, sb.toString()); - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); currentElement = (Element) currentElement.getParentNode(); } - @Override - public void write(byte[][] value, String name, byte[][] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (byte[] bs : value) { - for (byte b : bs) { - buf.append(b); - buf.append(" "); + // helper function to reduce duplicate code. uses the above helper to write a 2d array of any primitive type. + private void writePrimitiveArray2DHelper(Object[] value, String name) throws IOException { + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + + // tag names are not used for anything by DOMInputCapsule, but for the sake of readability, it's nice to know what type of array this is. + String childNamePrefix = value.getClass().getComponentType().getSimpleName().toLowerCase(); + childNamePrefix = childNamePrefix.replace("[]", "_array_"); + + for (int i = 0; i < value.length; i++) { + String childName = childNamePrefix + i; + + if (value[i] != null) { + writePrimitiveArrayHelper(value[i], childName); + } else { + // empty tag + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); } - buf.append(" "); - } - - if (buf.length() > 1) { - //remove last spaces - buf.setLength(buf.length() - 2); - } else if (buf.length() > 0) { - buf.setLength(buf.length() - 1); } - Element el = appendElement(name); - el.setAttribute("size_outer", String.valueOf(value.length)); - el.setAttribute("size_inner", String.valueOf(value[0].length)); - el.setAttribute(dataAttributeName, buf.toString()); currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(int value, String name, int defVal) throws IOException { - if (value == defVal) { - return; + public void write(byte value, String name, byte defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } - currentElement.setAttribute(name, String.valueOf(value)); } @Override - public void write(int[] value, String name, int[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { return; } - if (Arrays.equals(value, defVal)) { return; } - - for (int b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(byte[] value, String name, byte[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(int[][] value, String name, int[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); - } + public void write(short[] value, String name, short[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size", value == null ? "0" : String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(float[][] value, String name, float[][] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - for (float[] bs : value) { - for(float b : bs){ - buf.append(b); - buf.append(" "); - } - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(short[][] value, String name, short[][] defVal) throws IOException { + if (!Arrays.deepEquals(value, defVal)) { + writePrimitiveArray2DHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size_outer", String.valueOf(value.length)); - el.setAttribute("size_inner", String.valueOf(value[0].length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(double value, String name, double defVal) throws IOException { - if (value == defVal) { - return; + public void write(int value, String name, int defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); } - currentElement.setAttribute(name, String.valueOf(value)); } @Override - public void write(double[] value, String name, double[] defVal) throws IOException { - StringBuilder buf = new StringBuilder(); - if (value == null) { - value = defVal; - } - for (double b : value) { - buf.append(b); - buf.append(" "); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(int[] value, String name, int[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(double[][] value, String name, double[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override public void write(long[][] value, String name, long[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + public void write(float[] value, String name, float[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } + } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); + @Override + public void write(float[][] value, String name, float[][] defVal) throws IOException { + if (!Arrays.deepEquals(value, defVal)) { + writePrimitiveArray2DHelper(value, name); + } } @Override - public void write(short[][] value, String name, short[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; + public void write(double value, String name, double defVal) throws IOException { + if (value != defVal) { + XMLUtils.setAttribute(currentElement, name, String.valueOf(value)); + } + } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); + @Override + public void write(double[] value, String name, double[] defVal) throws IOException { + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); + } + } - for (int i=0; i 0) { - //remove last space - buf.setLength(buf.length() - 1); + if (!Arrays.equals(value, defVal)) { + writePrimitiveArrayHelper(value, name); } - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) currentElement.getParentNode(); } @Override public void write(boolean[][] value, String name, boolean[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.length)); - - for (int i=0; i= 0; i = value.nextSetBit(i + 1)) { - buf.append(i); - buf.append(" "); + for (int i = 0; i < value.size(); i++) { + buf.append(value.get(i) ? "1 " : "0 "); } if (buf.length() > 0) { @@ -498,50 +358,42 @@ public void write(BitSet value, String name, BitSet defVal) throws IOException { buf.setLength(buf.length() - 1); } - currentElement.setAttribute(name, buf.toString()); - + XMLUtils.setAttribute(currentElement, name, buf.toString()); } @Override - public void write(Savable object, String name, Savable defVal) throws IOException { - if (object == null) { - return; - } - if (object.equals(defVal)) { + public void write(Savable value, String name, Savable defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } Element old = currentElement; - Element el = writtenSavables.get(object); - - String className = null; - if(!object.getClass().getName().equals(name)){ - className = object.getClass().getName(); - } - try { - doc.createElement(name); - } catch (DOMException e) { - // Ridiculous fallback behavior. - // Would be far better to throw than to totally disregard the - // specified "name" and write a class name instead! - // (Besides the fact we are clobbering the managed .getClassTag()). - name = "Object"; - className = object.getClass().getName(); - } - - if (el != null) { - String refID = el.getAttribute("reference_ID"); - if (refID.length() == 0) { - refID = object.getClass().getName() + "@" + object.hashCode(); - el.setAttribute("reference_ID", refID); + + // no longer tries to use class name as element name. that makes things unnecessarily complicated. + + Element refElement = writtenSavables.get(value); + // this object has already been written, so make an element that refers to the existing one. + if (refElement != null) { + String refID = XMLUtils.getAttribute(FormatVersion.VERSION, refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID); + + // add the reference_ID to the referenced element if it didn't already have it + if (refID.isEmpty()) { + refID = value.getClass().getName() + "@" + value.hashCode(); + XMLUtils.setAttribute(refElement, XMLExporter.ATTRIBUTE_REFERENCE_ID, refID); } - el = appendElement(name); - el.setAttribute("ref", refID); + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_REFERENCE, refID); } else { - el = appendElement(name); + appendElement(name); + + // this now always writes the class attribute even if the class name is also the element name. + // for backwards compatibility, DOMInputCapsule will still try to get the class name from the element name if the + // attribute isn't found. + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_CLASS, value.getClass().getName()); // jME3 NEW: Append version number(s) - int[] versions = SavableClassUtil.getSavableVersions(object.getClass()); + int[] versions = SavableClassUtil.getSavableVersions(value.getClass()); StringBuilder sb = new StringBuilder(); for (int i = 0; i < versions.length; i++){ sb.append(versions[i]); @@ -549,375 +401,299 @@ public void write(Savable object, String name, Savable defVal) throws IOExceptio sb.append(", "); } } - el.setAttribute("savable_versions", sb.toString()); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SAVABLE_VERSIONS, sb.toString()); - writtenSavables.put(object, el); - object.write(exporter); - } - if(className != null){ - el.setAttribute("class", className); + writtenSavables.put(value, currentElement); + + value.write(exporter); } currentElement = old; } @Override - public void write(Savable[] objects, String name, Savable[] defVal) throws IOException { - if (objects == null) { - return; - } - if (Arrays.equals(objects, defVal)) { + public void write(Savable[] value, String name, Savable[] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { return; } Element old = currentElement; - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(objects.length)); - for (int i = 0; i < objects.length; i++) { - Savable o = objects[i]; + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + Savable o = value[i]; + String elementName = "savable_" + i; if(o == null){ - //renderStateList has special loading code, so we can leave out the null values + // renderStateList has special loading code, so we can leave out the null values if(!name.equals("renderStateList")){ - Element before = currentElement; - appendElement("null"); - currentElement = before; + Element before = currentElement; + appendElement(elementName); + currentElement = before; } }else{ - write(o, o.getClass().getName(), null); + write(o, elementName, null); } } + currentElement = old; } @Override public void write(Savable[][] value, String name, Savable[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size_outer", String.valueOf(value.length)); - el.setAttribute("size_inner", String.valueOf(value[0].length)); - for (Savable[] bs : value) { - for(Savable b : bs){ - write(b, b.getClass().getSimpleName(), null); - } - } - currentElement = (Element) currentElement.getParentNode(); - } - - @Override - public void writeSavableArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + if (value == null || Arrays.deepEquals(value, defVal)) { return; } + Element old = currentElement; - Element el = appendElement(name); - currentElement = el; - el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); - for (Object o : array) { - if(o == null) { - continue; - } - else if (o instanceof Savable) { - Savable s = (Savable) o; - write(s, s.getClass().getName(), null); + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_array_" + i; + if (value[i] != null) { + write(value[i], childName, null); } else { - throw new ClassCastException("Not a Savable instance: " + o); + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); } } + currentElement = old; } @Override - public void writeSavableArrayListArray(ArrayList[] objects, String name, ArrayList[] defVal) throws IOException { - if (objects == null) {return;} - if (Arrays.equals(objects, defVal)) {return;} - - Element old = currentElement; - Element el = appendElement(name); - el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(objects.length)); - for (int i = 0; i < objects.length; i++) { - ArrayList o = objects[i]; - if(o == null){ - Element before = currentElement; - appendElement("null"); - currentElement = before; - }else{ - StringBuilder buf = new StringBuilder("SavableArrayList_"); - buf.append(i); - writeSavableArrayList(o, buf.toString(), null); - } + public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; } - currentElement = old; + + int position = value.position(); + value.rewind(); + byte[] array = new byte[value.remaining()]; + value.get(array); + value.position(position); + + writePrimitiveArrayHelper(array, name); } @Override - public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException { - if (value == null) return; - if(Arrays.deepEquals(value, defVal)) return; + public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { + return; + } - Element el = appendElement(name); - int size = value.length; - el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(size)); + int position = value.position(); + value.rewind(); + short[] array = new short[value.remaining()]; + value.get(array); + value.position(position); - for (int i=0; i< size; i++) { - ArrayList[] vi = value[i]; - writeSavableArrayListArray(vi, "SavableArrayListArray_"+i, null); - } - currentElement = (Element) el.getParentNode(); + writePrimitiveArrayHelper(array, name); } @Override - public void writeFloatBufferArrayList(ArrayList array, String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element el = appendElement(name); - el.setAttribute(XMLExporter.ATTRIBUTE_SIZE, String.valueOf(array.size())); - for (FloatBuffer o : array) { - write(o, XMLExporter.ELEMENT_FLOATBUFFER, null); - } - currentElement = (Element) el.getParentNode(); + + int position = value.position(); + value.rewind(); + int[] array = new int[value.remaining()]; + value.get(array); + value.position(position); + + writePrimitiveArrayHelper(array, name); } @Override - public void writeSavableMap(Map map, String name, Map defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - Iterator keyIterator = map.keySet().iterator(); - while(keyIterator.hasNext()) { - Savable key = keyIterator.next(); - Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY); - write(key, XMLExporter.ELEMENT_KEY, null); - Savable value = map.get(key); - write(value, XMLExporter.ELEMENT_VALUE, null); - currentElement = stringMap; - } + int position = value.position(); + value.rewind(); + float[] array = new float[value.remaining()]; + value.get(array); + value.position(position); - currentElement = (Element) stringMap.getParentNode(); + writePrimitiveArrayHelper(array, name); } @Override - public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeByteBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - Iterator keyIterator = map.keySet().iterator(); - while(keyIterator.hasNext()) { - String key = keyIterator.next(); - Element mapEntry = appendElement("MapEntry"); - mapEntry.setAttribute("key", key); - Savable s = map.get(key); - write(s, "Savable", null); - currentElement = stringMap; - } + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size())); + for (int i = 0; i < value.size(); i++) { + String childName = "byte_buffer_" + i; + if (value.get(i) != null) { + write(value.get(i), childName, null); + } else { + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } + } - currentElement = (Element) stringMap.getParentNode(); + currentElement = (Element) currentElement.getParentNode(); } @Override - public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { - if (map == null) { - return; - } - if (map.equals(defVal)) { + public void writeFloatBufferArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element stringMap = appendElement(name); - for(Entry entry : map) { - int key = entry.getKey(); - Element mapEntry = appendElement("MapEntry"); - mapEntry.setAttribute("key", Integer.toString(key)); - Savable s = entry.getValue(); - write(s, "Savable", null); - currentElement = stringMap; - } + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.size())); + for (int i = 0; i < value.size(); i++) { + String childName = "float_buffer_" + i; + if (value.get(i) != null) { + write(value.get(i), childName, null); + } else { + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } + } - currentElement = (Element) stringMap.getParentNode(); + currentElement = (Element) currentElement.getParentNode(); } @Override - public void write(FloatBuffer value, String name, FloatBuffer defVal) throws IOException { - if (value == null) { + public void writeSavableArrayList(ArrayList value, String name, ArrayList defVal) throws IOException { + if (value == null || value.equals(defVal)) { return; } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Savable[] savableArray = new Savable[value.size()]; + for (int i = 0; i < value.size(); i++) { + Object o = value.get(i); + + if (o != null && !(o instanceof Savable)) { + throw new IOException(new ClassCastException("Not a Savable instance: " + o)); + } + + savableArray[i] = (Savable) o; } - - value.position(pos); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + write(savableArray, name, null); } @Override - public void write(IntBuffer value, String name, IntBuffer defVal) throws IOException { - if (value == null) { - return; - } - if (value.equals(defVal)) { + public void writeSavableArrayListArray(ArrayList[] value, String name, ArrayList[] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { return; } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Element old = currentElement; + + appendElement(name); + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_list_" + i; + if(value[i] != null){ + writeSavableArrayList(value[i], childName, null); + }else{ + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } } - value.position(pos); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = old; } @Override - public void write(ByteBuffer value, String name, ByteBuffer defVal) throws IOException { - if (value == null) return; - if (value.equals(defVal)) return; - - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); + public void writeSavableArrayListArray2D(ArrayList[][] value, String name, ArrayList[][] defVal) throws IOException { + if (value == null || Arrays.equals(value, defVal)) { + return; } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + + Element old = currentElement; + + appendElement(name); + + XMLUtils.setAttribute(currentElement, XMLExporter.ATTRIBUTE_SIZE, String.valueOf(value.length)); + for (int i = 0; i < value.length; i++) { + String childName = "savable_list_array_" + i; + if(value[i] != null){ + writeSavableArrayListArray(value[i], childName, null); + }else{ + appendElement(childName); + currentElement = (Element) currentElement.getParentNode(); + } } - - value.position(pos); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = old; } @Override - public void write(ShortBuffer value, String name, ShortBuffer defVal) throws IOException { - if (value == null) { - return; - } - if (value.equals(defVal)) { + public void writeSavableMap(Map map, String name, Map defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(value.limit())); - StringBuilder buf = new StringBuilder(); - int pos = value.position(); - value.rewind(); - int ctr = 0; - while (value.hasRemaining()) { - ctr++; - buf.append(value.get()); - buf.append(" "); - } - if (ctr != value.limit()) { - throw new IOException("'" + name - + "' buffer contention resulted in write data consistency. " - + ctr + " values written when should have written " - + value.limit()); - } - - if (buf.length() > 0) { - //remove last space - buf.setLength(buf.length() - 1); + Element stringMap = appendElement(name); + + Iterator keyIterator = map.keySet().iterator(); + while(keyIterator.hasNext()) { + Savable key = keyIterator.next(); + Element mapEntry = appendElement(XMLExporter.ELEMENT_MAPENTRY); + write(key, XMLExporter.ELEMENT_KEY, null); + Savable value = map.get(key); + write(value, XMLExporter.ELEMENT_VALUE, null); + currentElement = stringMap; } - - value.position(pos); - el.setAttribute(dataAttributeName, buf.toString()); - currentElement = (Element) el.getParentNode(); + + currentElement = (Element) stringMap.getParentNode(); } @Override - public void write(Enum value, String name, Enum defVal) throws IOException { - if (value == defVal) { + public void writeStringSavableMap(Map map, String name, Map defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - currentElement.setAttribute(name, String.valueOf(value)); + Element stringMap = appendElement(name); + + Iterator keyIterator = map.keySet().iterator(); + while(keyIterator.hasNext()) { + String key = keyIterator.next(); + Element mapEntry = appendElement("MapEntry"); + XMLUtils.setAttribute(mapEntry, "key", key); + Savable s = map.get(key); + write(s, "Savable", null); + currentElement = stringMap; } + currentElement = (Element) stringMap.getParentNode(); + } + @Override - public void writeByteBufferArrayList(ArrayList array, - String name, ArrayList defVal) throws IOException { - if (array == null) { - return; - } - if (array.equals(defVal)) { + public void writeIntSavableMap(IntMap map, String name, IntMap defVal) throws IOException { + if (map == null || map.equals(defVal)) { return; } - Element el = appendElement(name); - el.setAttribute("size", String.valueOf(array.size())); - for (ByteBuffer o : array) { - write(o, "ByteBuffer", null); - } - currentElement = (Element) el.getParentNode(); + Element stringMap = appendElement(name); + + for(Entry entry : map) { + int key = entry.getKey(); + Element mapEntry = appendElement("MapEntry"); + XMLUtils.setAttribute(mapEntry, "key", Integer.toString(key)); + Savable s = entry.getValue(); + write(s, "Savable", null); + currentElement = stringMap; } + + currentElement = (Element) stringMap.getParentNode(); + } } \ No newline at end of file diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java index 3aa431c9a6..af5f898bf8 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/DOMSerializer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +34,8 @@ import java.io.*; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + import org.w3c.dom.*; /** @@ -44,11 +46,13 @@ * @author Brett McLaughlin, Justin Edelson - Original creation for "Java and XML" book. * @author Doug Daniels (dougnukem) - adjustments for XML formatting * @version $Revision: 4207 $, $Date: 2009-03-29 11:19:16 -0400 (Sun, 29 Mar 2009) $ + * @deprecated This class was only used in XMLExporter and has been replaced by javax.xml.transform.Transformer */ +@Deprecated public class DOMSerializer { /** The encoding to use for output (default is UTF-8) */ - private Charset encoding = Charset.forName("utf-8"); + private Charset encoding = StandardCharsets.UTF_8; /** The amount of indentation to use (default is 4 spaces). */ private int indent = 4; @@ -75,6 +79,7 @@ private void escape(Writer writer, String s) throws IOException { break; default: writer.write(c); + break; } } } @@ -84,7 +89,7 @@ private void escape(Writer writer, String s) throws IOException { * * @param doc the document to serialize. * @param file the file to serialize to. - * @throws IOException + * @throws IOException for various error conditions */ public void serialize(Document doc, File file) throws IOException { serialize(doc, new FileOutputStream(file)); @@ -95,7 +100,7 @@ public void serialize(Document doc, File file) throws IOException { * * @param doc the document to serialize. * @param out the stream to serialize to. - * @throws IOException + * @throws IOException for various error conditions */ public void serialize(Document doc, OutputStream out) throws IOException { Writer writer = new OutputStreamWriter(out, encoding); diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java index 0ede52884c..bc4ef918b0 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLExporter.java @@ -39,45 +39,84 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; +import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; /** - * Part of the jME XML IO system as introduced in the google code jmexml project. + * Part of the jME XML IO system as introduced in the Google Code jmexml project. * * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 */ public class XMLExporter implements JmeExporter { - public static final String ELEMENT_MAPENTRY = "MapEntry"; - public static final String ELEMENT_KEY = "Key"; + public static final String ELEMENT_MAPENTRY = "MapEntry"; + public static final String ELEMENT_KEY = "Key"; public static final String ELEMENT_VALUE = "Value"; - public static final String ELEMENT_FLOATBUFFER = "FloatBuffer"; - public static final String ATTRIBUTE_SIZE = "size"; + public static final String ATTRIBUTE_SIZE = "size"; + public static final String ATTRIBUTE_DATA = "data"; + public static final String ATTRIBUTE_CLASS = "class"; + public static final String ATTRIBUTE_REFERENCE_ID = "reference_ID"; + public static final String ATTRIBUTE_REFERENCE = "ref"; + public static final String ATTRIBUTE_SAVABLE_VERSIONS = "savable_versions"; private DOMOutputCapsule domOut; + + private int indentSpaces = 4; public XMLExporter() { } @Override - public void save(Savable object, OutputStream f) throws IOException { + public void save(Savable object, OutputStream outputStream) throws IOException { + Document document = null; try { - //Initialize Document when saving so we don't retain state of previous exports - this.domOut = new DOMOutputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(), this); - domOut.write(object, object.getClass().getName(), null); - DOMSerializer serializer = new DOMSerializer(); - serializer.serialize(domOut.getDoc(), f); - f.flush(); + document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); } catch (ParserConfigurationException ex) { throw new IOException(ex); } + document.setXmlStandalone(true); // for some reason the transformer output property below doesn't work unless the document is set to standalone + + // Initialize the DOMOutputCapsule when saving, so we don't retain state of previous exports. + domOut = new DOMOutputCapsule(document, this); + + domOut.write(object, "savable", null); + + DOMSource source = new DOMSource(domOut.getDoc()); + StreamResult result = new StreamResult(outputStream); + + try { + TransformerFactory tfFactory = TransformerFactory.newInstance(); + tfFactory.setAttribute("indent-number", indentSpaces); + + Transformer transformer = tfFactory.newTransformer(); + transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); + + if (indentSpaces > 0) { + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + } + + transformer.transform(source, result); + } catch (TransformerException ex) { + throw new IOException(ex); + } } @Override - public void save(Savable object, File f) throws IOException { + public void save(Savable object, File f, boolean createDirectories) throws IOException { + File parentDirectory = f.getParentFile(); + if (parentDirectory != null && !parentDirectory.exists() && createDirectories) { + parentDirectory.mkdirs(); + } + FileOutputStream fos = new FileOutputStream(f); try { save(object, fos); @@ -91,8 +130,16 @@ public OutputCapsule getCapsule(Savable object) { return domOut; } + /** + * Sets the number of spaces used to indent nested tags. + * @param indentSpaces The number of spaces to indent for each level of nesting. Default is 4. + * Pass 0 to disable pretty printing and write document without adding any whitespace. + */ + public void setIndentSpaces(int indentSpaces) { + this.indentSpaces = indentSpaces; + } + public static XMLExporter getInstance() { - return new XMLExporter(); + return new XMLExporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java index d531a0840c..90d9e6e057 100644 --- a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLImporter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -46,7 +46,7 @@ import org.xml.sax.SAXException; /** - * Part of the jME XML IO system as introduced in the google code jmexml project. + * Part of the jME XML IO system as introduced in the Google Code jmexml project. * @author Kai Rabien (hevee) - original author of the code.google.com jmexml project * @author Doug Daniels (dougnukem) - adjustments for jME 2.0 and Java 1.5 */ @@ -59,10 +59,12 @@ public class XMLImporter implements JmeImporter { public XMLImporter() { } + @Override public int getFormatVersion() { return formatVersion; } + @Override public AssetManager getAssetManager(){ return assetManager; } @@ -71,14 +73,16 @@ public void setAssetManager(AssetManager assetManager){ this.assetManager = assetManager; } + @Override public Object load(AssetInfo info) throws IOException { assetManager = info.getManager(); InputStream in = info.openStream(); try { return load(in); } finally { - if (in != null) + if (in != null) { in.close(); + } } } @@ -97,17 +101,14 @@ public Savable load(InputStream f) throws IOException { try { domIn = new DOMInputCapsule(DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(f), this); return domIn.readSavable(null, null); - } catch (SAXException e) { - IOException ex = new IOException(); - ex.initCause(e); - throw ex; - } catch (ParserConfigurationException e) { + } catch (SAXException | ParserConfigurationException e) { IOException ex = new IOException(); ex.initCause(e); throw ex; } } + @Override public InputCapsule getCapsule(Savable id) { return domIn; } @@ -115,5 +116,4 @@ public InputCapsule getCapsule(Savable id) { public static XMLImporter getInstance() { return new XMLImporter(); } - } diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java new file mode 100644 index 0000000000..fcec673553 --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/XMLUtils.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2009-2021 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package com.jme3.export.xml; + +import java.io.IOException; +import org.w3c.dom.Element; +import org.w3c.dom.DOMException; + +/** + * Utilities for reading and writing XML files. + * + * @author codex + */ +public class XMLUtils { + + /** + * Prefix for every jme xml attribute for format versions 3 and up. + *

                  + * This prefix should be appended at the beginning of every xml + * attribute name. For format versions 3 and up, every name to + * access an attribute must append this prefix first. + */ + public static final String PREFIX = "jme-"; + + /** + * Sets the attribute in the element under the name. + *

                  + * Automatically appends {@link #PREFIX} to the beginning of the name + * before assigning the attribute to the element. + * + * @param element element to set the attribute in + * @param name name of the attribute (without prefix) + * @param attribute attribute to save + * + * @throws java.io.IOException wraps DOMException in IOException for convenience since everywhere this method + * is used is expected to throw only IOException. + */ + public static void setAttribute(Element element, String name, String attribute) throws IOException { + try { + element.setAttribute(PREFIX+name, attribute); + } catch (DOMException domEx) { + throw new IOException(domEx); + } + } + + /** + * Fetches the named attribute from the element. + *

                  + * Automatically appends {@link #PREFIX} to the beginning + * of the name before looking up the attribute for format versions 3 and up. + * + * @param version format version of the xml + * @param element XML element to get the attribute from + * @param name name of the attribute (without prefix) + * @return named attribute + */ + public static String getAttribute(int version, Element element, String name) { + if (version >= 3) { + return element.getAttribute(PREFIX+name); + } else { + return element.getAttribute(name); + } + } + + /** + * Tests if the element contains the named attribute. + *

                  + * Automatically appends {@link #PREFIX} to the beginning + * of the name before looking up the attribute for format versions 3 and up. + * + * @param version format version of the xml + * @param element element to test + * @param name name of the attribute (without prefix) + * @return true if the element has the named attribute + */ + public static boolean hasAttribute(int version, Element element, String name) { + if (version >= 3) { + return element.hasAttribute(PREFIX+name); + } else { + return element.hasAttribute(name); + } + } + + /** + * Denies instantiation of this class. + */ + private XMLUtils() { + } +} diff --git a/jme3-plugins/src/xml/java/com/jme3/export/xml/package-info.java b/jme3-plugins/src/xml/java/com/jme3/export/xml/package-info.java new file mode 100644 index 0000000000..c23c8d97e6 --- /dev/null +++ b/jme3-plugins/src/xml/java/com/jme3/export/xml/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * load and save assets (such as models) in XML format + */ +package com.jme3.export.xml; diff --git a/jme3-screenshot-tests/README.md b/jme3-screenshot-tests/README.md new file mode 100644 index 0000000000..9215123bb9 --- /dev/null +++ b/jme3-screenshot-tests/README.md @@ -0,0 +1,51 @@ +# jme3-screenshot-tests + +This module contains tests that compare screenshots of the JME3 test applications to reference images. Think of these like visual unit tests + +The tests are run using the following command: + +``` + ./gradlew :jme3-screenshot-test:screenshotTest +``` + +This will create a report in `jme3-screenshot-test/build/reports/ScreenshotDiffReport.html` that shows the differences between the reference images and the screenshots taken during the test run. Note that this is an ExtentReport. + +This is most reliable when run on the CI server. The report can be downloaded from the artifacts section of the pipeline (once the full pipeline has completed). If you go into +the Actions tab (on GitHub) and find your pipeline you can download the report from the Artifacts section. It will be called screenshot-test-report. + +## Machine variability + +It is important to be aware that the tests are sensitive to machine variability. Different GPUs may produce subtly different pixel outputs +(that look identical to a human user). The tests are run on a specific machine and the reference images are generated on that machine. If the tests are run on a different machine, the images may not match the reference images and this is "fine". If you run these on your local machine compare the differences by eye in the report, don't wory about failing tests. + +## Parameterised tests + +By default, the tests use the class and method name to produce the screenshot image name. E.g. org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f15.png is the testExplosionEffect test at frame 15. If you are using parameterised tests this won't work (as all the tests have the same function name). In this case you should specify the image name (including whatever parameterised information to make it unique). E.g. + +``` + screenshotTest( + .... + ).setFramesToTakeScreenshotsOn(45) + .setBaseImageFileName("some_unique_name_" + theParameterGivenToTest) + .run(); +) +``` + +## Non-deterministic (and known bad) tests + +By default, screenshot variability will cause the pipeline to fail. If a test is non-deterministic (e.g. includes randomness) or +is a known accepted failure (that will be fixed "at some point" but not now) that can be non-desirable. In that case you can +change the behaviour of the test such that these are marked as warnings in the generated report but don't fail the test + +``` + screenshotTest( + .... + ).setFramesToTakeScreenshotsOn(45) + .setTestType(TestType.NON_DETERMINISTIC) + .run(); +) +``` + +## Accepting new images + +It may be the case that a change makes an improvement to the library (or the test is entirely new) and the new image should be accepted as the new reference image. To do this, copy the new image to the `src/test/resources` directory. The new image can be found in the `build/changed-images` directory, however it is very important that the image come from the reference machine. This can be obtained from the CI server. The job runs only if there is an active pull request (to one of the mainline branches; e.g. master or 3.7). If you go into the Actions tab and find your pipeline you can download the report and changed images from the Artifacts section. diff --git a/jme3-screenshot-tests/build.gradle b/jme3-screenshot-tests/build.gradle new file mode 100644 index 0000000000..b0f7bd5c1d --- /dev/null +++ b/jme3-screenshot-tests/build.gradle @@ -0,0 +1,38 @@ +plugins { + id 'java' +} + +repositories { + mavenCentral() +} + +dependencies { + implementation project(':jme3-desktop') + implementation project(':jme3-core') + implementation project(':jme3-effects') + implementation project(':jme3-terrain') + implementation project(':jme3-lwjgl3') + implementation project(':jme3-plugins') + + implementation 'com.aventstack:extentreports:5.1.1' + implementation platform('org.junit:junit-bom:5.9.1') + implementation 'org.junit.jupiter:junit-jupiter' + testRuntimeOnly project(':jme3-testdata') +} + +tasks.register("screenshotTest", Test) { + useJUnitPlatform{ + filter{ + includeTags 'integration' + } + } +} + + +test { + useJUnitPlatform{ + filter{ + excludeTags 'integration' + } + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/App.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/App.java new file mode 100644 index 0000000000..892e1f88a2 --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/App.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppState; +import com.jme3.app.state.VideoRecorderAppState; +import com.jme3.math.ColorRGBA; + +import java.util.function.Consumer; + +/** + * The app used for the tests. AppState(s) are used to inject the actual test code. + * @author Richard Tingle (aka richtea) + */ +public class App extends SimpleApplication { + + public App(AppState... initialStates){ + super(initialStates); + } + + Consumer onError = (onError) -> {}; + + @Override + public void simpleInitApp(){ + getViewPort().setBackgroundColor(ColorRGBA.Black); + setTimer(new VideoRecorderAppState.IsoTimer(60)); + } + + @Override + public void handleError(String errMsg, Throwable t) { + super.handleError(errMsg, t); + onError.accept(t); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportExtension.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportExtension.java new file mode 100644 index 0000000000..d3ad17bd0f --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportExtension.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.aventstack.extentreports.ExtentReports; +import com.aventstack.extentreports.ExtentTest; +import com.aventstack.extentreports.reporter.ExtentSparkReporter; +import com.aventstack.extentreports.reporter.configuration.Theme; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.TestWatcher; + +import java.util.Optional; + +/** + * This creates the Extent report and manages the test lifecycle + * + * @author Richard Tingle (aka richtea) + */ +public class ExtentReportExtension implements BeforeAllCallback, AfterAllCallback, TestWatcher, BeforeTestExecutionCallback{ + private static ExtentReports extent; + private static ExtentTest currentTest; + + @Override + public void beforeAll(ExtensionContext context) { + if(extent==null){ + ExtentSparkReporter spark = new ExtentSparkReporter("build/reports/ScreenshotDiffReport.html"); + spark.config().setTheme(Theme.STANDARD); + spark.config().setDocumentTitle("Screenshot Test Report"); + spark.config().setReportName("Screenshot Test Report"); + extent = new ExtentReports(); + extent.attachReporter(spark); + } + // Initialize log capture to redirect console output to the report + ExtentReportLogCapture.initialize(); + } + + @Override + public void afterAll(ExtensionContext context) { + /* + * this writes the entire report after each test class. This sucks but I don't think there is + * anywhere else I can hook into the lifecycle of the end of all tests to write the report. + */ + extent.flush(); + + // Restore the original System.out + ExtentReportLogCapture.restore(); + } + + @Override + public void testSuccessful(ExtensionContext context) { + getCurrentTest().pass("Test passed"); + } + + @Override + public void testFailed(ExtensionContext context, Throwable cause) { + getCurrentTest().fail(cause); + } + + @Override + public void testAborted(ExtensionContext context, Throwable cause) { + getCurrentTest().skip("Test aborted " + cause.toString()); + } + + @Override + public void testDisabled(ExtensionContext context, Optional reason) { + getCurrentTest().skip("Test disabled: " + reason.orElse("No reason")); + } + + @Override + public void beforeTestExecution(ExtensionContext context) { + String testName = context.getDisplayName(); + String className = context.getRequiredTestClass().getSimpleName(); + currentTest = extent.createTest(className + "." + testName); + } + + public static ExtentTest getCurrentTest() { + return currentTest; + } +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportLogCapture.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportLogCapture.java new file mode 100644 index 0000000000..3d4f2e925f --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ExtentReportLogCapture.java @@ -0,0 +1,117 @@ +/* + * Copyright (c) 2025 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 org.jmonkeyengine.screenshottests.testframework; + +import com.aventstack.extentreports.ExtentTest; + +import java.io.OutputStream; +import java.io.PrintStream; + +/** + * This class captures console logs and adds them to the ExtentReport. + * It redirects System.out to both the original console and the ExtentReport. + * + * @author Richard Tingle (aka richtea) + */ +public class ExtentReportLogCapture { + + private static final PrintStream originalOut = System.out; + private static final PrintStream originalErr = System.err; + private static boolean initialized = false; + + /** + * Initializes the log capture system. This should be called once at the start of the test suite. + */ + public static void initialize() { + if (!initialized) { + // Redirect System.out and System.err + System.setOut(new ExtentReportPrintStream(originalOut)); + System.setErr(new ExtentReportPrintStream(originalErr)); + + initialized = true; + } + } + + /** + * Restores the original System.out. This should be called at the end of the test suite. + */ + public static void restore() { + if(initialized) { + // Restore System.out and System.err + System.setOut(originalOut); + System.setErr(originalErr); + initialized = false; + } + } + + /** + * A custom PrintStream that redirects output to both the original console and the ExtentReport. + */ + private static class ExtentReportPrintStream extends PrintStream { + private StringBuilder buffer = new StringBuilder(); + + public ExtentReportPrintStream(OutputStream out) { + super(out, true); + } + + @Override + public void write(byte[] buf, int off, int len) { + super.write(buf, off, len); + + // Convert the byte array to a string and add to buffer + String s = new String(buf, off, len); + buffer.append(s); + + // If we have a complete line (ends with newline), process it + if (s.endsWith("\n") || s.endsWith("\r\n")) { + String line = buffer.toString().trim(); + if (!line.isEmpty()) { + addToExtentReport(line); + } + buffer.setLength(0); // Clear the buffer + } + } + + private void addToExtentReport(String s) { + try { + ExtentTest currentTest = ExtentReportExtension.getCurrentTest(); + if (currentTest != null) { + currentTest.info(s); + } + } catch (Exception e) { + // If there's an error adding to the report, just continue + // This ensures that console logs are still displayed even if there's an issue with the report + } + } + } + +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/PixelSamenessDegree.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/PixelSamenessDegree.java new file mode 100644 index 0000000000..3fcc2abaed --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/PixelSamenessDegree.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.jme3.math.ColorRGBA; + +/** + * @author Richard Tingle (aka richtea) + */ +public enum PixelSamenessDegree{ + SAME(1, null), + NEGLIGIBLY_DIFFERENT(1, ColorRGBA.Green), + SUBTLY_DIFFERENT(10, ColorRGBA.Blue), + + MEDIUMLY_DIFFERENT(20, ColorRGBA.Yellow), + + VERY_DIFFERENT(60,ColorRGBA.Orange), + + EXTREMELY_DIFFERENT(100,ColorRGBA.Red); + + private final int maximumAllowedDifference; + + private final ColorRGBA colorInDebugImage; + + PixelSamenessDegree(int maximumAllowedDifference, ColorRGBA colorInDebugImage){ + this.colorInDebugImage = colorInDebugImage; + this.maximumAllowedDifference = maximumAllowedDifference; + } + + public ColorRGBA getColorInDebugImage(){ + return colorInDebugImage; + } + + public int getMaximumAllowedDifference(){ + return maximumAllowedDifference; + } +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotNoInputAppState.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotNoInputAppState.java new file mode 100644 index 0000000000..2817bb0e73 --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotNoInputAppState.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.jme3.app.Application; +import com.jme3.app.state.AbstractAppState; +import com.jme3.app.state.AppStateManager; +import com.jme3.input.controls.ActionListener; +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; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.system.JmeSystem; +import com.jme3.texture.FrameBuffer; +import com.jme3.util.BufferUtils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.util.List; +import java.util.logging.Level; +import java.util.logging.Logger; + +/** + * This is more or less the same as ScreenshotAppState but without the keyboard input + * (because in a headless environment, there is no keyboard and trying to configure it caused + * errors). + * + * @author Richard Tingle (aka richtea) + * + */ +public class ScreenshotNoInputAppState extends AbstractAppState implements ActionListener, SceneProcessor { + + private static final Logger logger = Logger.getLogger(ScreenshotNoInputAppState.class.getName()); + private String filePath; + private boolean capture = false; + private boolean numbered = true; + private Renderer renderer; + private RenderManager rm; + private ByteBuffer outBuf; + private String shotName; + private long shotIndex = 0; + private int width, height; + + /** + * ViewPort to which the SceneProcessor is attached + */ + private ViewPort last; + + /** + * Using this constructor, the screenshot files will be written sequentially to the system + * default storage folder. + */ + public ScreenshotNoInputAppState() { + this(null); + } + + /** + * This constructor allows you to specify the output file path of the screenshot. + * Include the separator at the end of the path. + * Use an empty string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the separator at the end of the path. + */ + public ScreenshotNoInputAppState(String filePath) { + this.filePath = filePath; + } + + /** + * This constructor allows you to specify the output file path of the screenshot. + * Include the separator at the end of the path. + * Use an empty string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the separator at the end of the path. + * @param fileName The name of the file to save the screenshot as. + */ + public ScreenshotNoInputAppState(String filePath, String fileName) { + this.filePath = filePath; + this.shotName = fileName; + } + + /** + * This constructor allows you to specify the output file path of the screenshot and + * a base index for the shot index. + * Include the separator at the end of the path. + * Use an empty string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the separator at the end of the path. + * @param shotIndex The base index for screenshots. The first screenshot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. + */ + public ScreenshotNoInputAppState(String filePath, long shotIndex) { + this.filePath = filePath; + this.shotIndex = shotIndex; + } + + /** + * This constructor allows you to specify the output file path of the screenshot and + * a base index for the shot index. + * Include the separator at the end of the path. + * Use an empty string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath The screenshot file path to use. Include the separator at the end of the path. + * @param fileName The name of the file to save the screenshot as. + * @param shotIndex The base index for screenshots. The first screenshot will have + * shotIndex + 1 appended, the next shotIndex + 2, and so on. + */ + public ScreenshotNoInputAppState(String filePath, String fileName, long shotIndex) { + this.filePath = filePath; + this.shotName = fileName; + this.shotIndex = shotIndex; + } + + /** + * Set the file path to store the screenshot. + * Include the separator at the end of the path. + * Use an empty string to use the application folder. Use NULL to use the system + * default storage folder. + * @param filePath File path to use to store the screenshot. Include the separator at the end of the path. + */ + public void setFilePath(String filePath) { + this.filePath = filePath; + } + + /** + * Set the file name of the screenshot. + * @param fileName File name to save the screenshot as. + */ + public void setFileName(String fileName) { + this.shotName = fileName; + } + + /** + * Sets the base index that will used for subsequent screenshots. + * + * @param index the desired base index + */ + public void setShotIndex(long index) { + this.shotIndex = index; + } + + /** + * Sets if the filename should be appended with a number representing the + * current sequence. + * @param numberedWanted If numbering is wanted. + */ + public void setIsNumbered(boolean numberedWanted) { + this.numbered = numberedWanted; + } + + @Override + public void initialize(AppStateManager stateManager, Application app) { + if (!super.isInitialized()) { + List vps = app.getRenderManager().getPostViews(); + last = vps.get(vps.size() - 1); + last.addProcessor(this); + + if (shotName == null) { + shotName = app.getClass().getSimpleName(); + } + } + + super.initialize(stateManager, app); + } + + /** + * Clean up this AppState during the first update after it gets detached. + *

                  + * Because each ScreenshotAppState is also a SceneProcessor (in addition to + * being an AppState) this method is also invoked when the SceneProcessor + * get removed from its ViewPort, leading to an indirect recursion: + *

                  1. AppStateManager invokes ScreenshotAppState.cleanup()
                  2. + *
                  3. cleanup() invokes ViewPort.removeProcessor()
                  4. + *
                  5. removeProcessor() invokes ScreenshotAppState.cleanup()
                  6. + *
                  7. ... and so on.
                  8. + *
                  + *

                  + * In order to break this recursion, this method only removes the + * SceneProcessor if it has not previously been removed. + *

                  + * A better design would have the AppState and SceneProcessor be 2 distinct + * objects, but doing so now might break applications that rely on them + * being a single object. + */ + @Override + public void cleanup() { + ViewPort viewPort = last; + if (viewPort != null) { + last = null; + viewPort.removeProcessor(this); // XXX indirect recursion! + } + + super.cleanup(); + } + + @Override + public void onAction(String name, boolean value, float tpf) { + if (value) { + capture = true; + } + } + + public void takeScreenshot() { + capture = true; + } + + @Override + public void initialize(RenderManager rm, ViewPort vp) { + renderer = rm.getRenderer(); + this.rm = rm; + reshape(vp, vp.getCamera().getWidth(), vp.getCamera().getHeight()); + } + + @Override + public boolean isInitialized() { + return super.isInitialized() && renderer != null; + } + + @Override + public void reshape(ViewPort vp, int w, int h) { + outBuf = BufferUtils.createByteBuffer(w * h * 4); + width = w; + height = h; + } + + @Override + public void preFrame(float tpf) { + // do nothing + } + + @Override + public void postQueue(RenderQueue rq) { + // do nothing + } + + @Override + public void postFrame(FrameBuffer out) { + if (capture) { + capture = false; + + Camera curCamera = rm.getCurrentCamera(); + int viewX = (int) (curCamera.getViewPortLeft() * curCamera.getWidth()); + int viewY = (int) (curCamera.getViewPortBottom() * curCamera.getHeight()); + int viewWidth = (int) ((curCamera.getViewPortRight() - curCamera.getViewPortLeft()) * curCamera.getWidth()); + int viewHeight = (int) ((curCamera.getViewPortTop() - curCamera.getViewPortBottom()) * curCamera.getHeight()); + + renderer.setViewPort(0, 0, width, height); + renderer.readFrameBuffer(out, outBuf); + renderer.setViewPort(viewX, viewY, viewWidth, viewHeight); + + File file; + String filename; + if (numbered) { + shotIndex++; + filename = shotName + shotIndex; + } else { + filename = shotName; + } + + if (filePath == null) { + file = new File(JmeSystem.getStorageFolder() + File.separator + filename + ".png").getAbsoluteFile(); + } else { + file = new File(filePath + filename + ".png").getAbsoluteFile(); + } + + if (logger.isLoggable(Level.FINE)) { + logger.log(Level.FINE, "Saving ScreenShot to: {0}", file.getAbsolutePath()); + } + + try { + writeImageFile(file); + } catch (IOException ex) { + logger.log(Level.SEVERE, "Error while saving screenshot", ex); + } + } + } + + @Override + public void setProfiler(AppProfiler profiler) { + // not implemented + } + + /** + * Called by postFrame() once the screen has been captured to outBuf. + * + * @param file the output file + * @throws IOException if an I/O error occurs + */ + protected void writeImageFile(File file) throws IOException { + OutputStream outStream = new FileOutputStream(file); + try { + JmeSystem.writeImageFile(outStream, "png", outBuf, width, height); + } finally { + outStream.close(); + } + } +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTest.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTest.java new file mode 100644 index 0000000000..5bff5d9960 --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTest.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.jme3.app.state.AppState; +import com.jme3.system.AppSettings; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * This is how a test is configured and started. It uses a fluent API. + * + * @author Richard Tingle (aka richtea) + */ +public class ScreenshotTest{ + + TestType testType = TestType.MUST_PASS; + + AppState[] states; + + List framesToTakeScreenshotsOn = new ArrayList<>(); + + TestResolution resolution = new TestResolution(500, 400); + + String baseImageFileName = null; + + public ScreenshotTest(AppState... initialStates){ + states = initialStates; + framesToTakeScreenshotsOn.add(1); //default behaviour is to take a screenshot on the first frame + } + + /** + * Sets the frames to take screenshots on. Frames are at a hard coded 60 FPS (from JME's perspective, clock time may vary). + */ + public ScreenshotTest setFramesToTakeScreenshotsOn(Integer... frames){ + framesToTakeScreenshotsOn.clear(); + framesToTakeScreenshotsOn.addAll(Arrays.asList(frames)); + return this; + } + + /** + * Sets the test type (i.e. what the pass/fail rules are for the test + */ + public ScreenshotTest setTestType(TestType testType){ + this.testType = testType; + return this; + } + + public ScreenshotTest setTestResolution(TestResolution resolution){ + this.resolution = resolution; + return this; + } + + /** + * Sets the file name to be used (as the first part) of saved images in both the resources directory and + * the generated image. Note that you only have to call this if you want to override the default behaviour which is + * to use the calling class and method name, like org.jmonkeyengine.screenshottests.water.TestPostWater.testPostWater + */ + public ScreenshotTest setBaseImageFileName(String baseImageFileName){ + this.baseImageFileName = baseImageFileName; + return this; + } + + public void run(){ + AppSettings settings = new AppSettings(true); + settings.setResolution(resolution.getWidth(), resolution.getHeight()); + settings.setAudioRenderer(null); // Disable audio (for headless) + settings.setUseInput(false); //while it will run with inputs on it causes non-fatal errors. + + String imageFilePrefix = baseImageFileName == null ? calculateImageFilePrefix() : baseImageFileName; + + TestDriver.bootAppForTest(testType,settings,imageFilePrefix, framesToTakeScreenshotsOn, states); + } + + + private String calculateImageFilePrefix(){ + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + + // The element at index 2 is the caller of this method, so at 3 should be the test class + if (stackTrace.length > 3) { + StackTraceElement caller = stackTrace[3]; + return caller.getClassName() + "." + caller.getMethodName(); + } else { + throw new RuntimeException("Caller information is not available."); + } + } + + +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java new file mode 100644 index 0000000000..81553e614e --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/ScreenshotTestBase.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.jme3.app.state.AppState; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.extension.ExtendWith; + +/** + * This is the base class for all screenshot tests + * + * @author Richard Tingle (aka richtea) + */ +@ExtendWith(ExtentReportExtension.class) +@Tag("integration") +public abstract class ScreenshotTestBase{ + + /** + * Initialises a screenshot test. The resulting object should be configured (if neccessary) and then started + * by calling {@link ScreenshotTest#run()}. + * @param initialStates + * @return + */ + public ScreenshotTest screenshotTest(AppState... initialStates){ + return new ScreenshotTest(initialStates); + } +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java new file mode 100644 index 0000000000..ca0cfa98a9 --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestDriver.java @@ -0,0 +1,474 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +import com.aventstack.extentreports.ExtentTest; +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.AppState; +import com.jme3.app.state.BaseAppState; +import com.jme3.math.FastMath; +import com.jme3.system.AppSettings; +import com.jme3.system.JmeContext; + +import javax.imageio.IIOImage; +import javax.imageio.ImageIO; +import javax.imageio.ImageWriteParam; +import javax.imageio.ImageWriter; +import javax.imageio.stream.ImageOutputStream; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Stream; + +import static org.junit.jupiter.api.Assertions.fail; + +/** + * The test driver injects the screenshot taking into the application lifecycle, pauses the main test thread until the + * screenshots have been taken and then compares the screenshots to the expected images. + * + * @author Richard Tingle (aka richtea) + * + */ +public class TestDriver extends BaseAppState{ + + private static final Logger logger = Logger.getLogger(TestDriver.class.getName()); + + public static final String IMAGES_ARE_DIFFERENT = "Images are different. (If you are running the test locally this is expected, images only reproducible on github CI infrastructure)"; + + public static final String IMAGES_ARE_DIFFERENT_SIZES = "Images are different sizes."; + + public static final String KNOWN_BAD_TEST_IMAGES_DIFFERENT = "Images are different. This is a known broken test."; + + public static final String KNOWN_BAD_TEST_IMAGES_SAME = "This is (or was?) a known broken test but it is now passing, please change the test type to MUST_PASS."; + + public static final String NON_DETERMINISTIC_TEST = "This is a non deterministic test, please manually review the expected and actual images to make sure they are approximately the same."; + + private static final Executor executor = Executors.newSingleThreadExecutor( (r) -> { + Thread thread = new Thread(r); + thread.setDaemon(true); + return thread; + }); + + int tick = 0; + + Collection framesToTakeScreenshotsOn; + + ScreenshotNoInputAppState screenshotAppState; + + private CountDownLatch waitLatch; + + private final int tickToTerminateApp; + + public TestDriver(ScreenshotNoInputAppState screenshotAppState, Collection framesToTakeScreenshotsOn){ + this.screenshotAppState = screenshotAppState; + this.framesToTakeScreenshotsOn = framesToTakeScreenshotsOn; + this.tickToTerminateApp = framesToTakeScreenshotsOn.stream().mapToInt(i -> i).max().orElse(0) + 1; + } + + @Override + public void update(float tpf){ + super.update(tpf); + + if(framesToTakeScreenshotsOn.contains(tick)){ + screenshotAppState.takeScreenshot(); + } + if(tick >= tickToTerminateApp){ + getApplication().stop(true); + waitLatch.countDown(); + } + + tick++; + } + + @Override protected void initialize(Application app){ + ((App)app).onError = error -> { + logger.log(Level.WARNING, "Error in test application", error); + waitLatch.countDown(); + }; + + } + + @Override protected void cleanup(Application app){} + + @Override protected void onEnable(){} + + @Override protected void onDisable(){} + + /** + * Boots up the application on a separate thread (blocks this thread) and then does the following: + * - Takes screenshots on the requested frames + * - After all the frames have been taken it stops the application + * - Compares the screenshot to the expected screenshot (if any). Fails the test if they are different + */ + public static void bootAppForTest(TestType testType, AppSettings appSettings, String baseImageFileName, List framesToTakeScreenshotsOn, AppState... initialStates){ + FastMath.rand.setSeed(0); //try to make things deterministic by setting the random seed + Collections.sort(framesToTakeScreenshotsOn); + + Path imageTempDir; + + try{ + imageTempDir = Files.createTempDirectory("jmeSnapshotTest"); + } catch(IOException e){ + throw new RuntimeException(e); + } + + ScreenshotNoInputAppState screenshotAppState = new ScreenshotNoInputAppState(imageTempDir.toString() + "/"); + String screenshotAppFileNamePrefix = "Screenshot-"; + screenshotAppState.setFileName(screenshotAppFileNamePrefix); + + List states = new ArrayList<>(Arrays.asList(initialStates)); + TestDriver testDriver = new TestDriver(screenshotAppState, framesToTakeScreenshotsOn); + states.add(screenshotAppState); + states.add(testDriver); + + SimpleApplication app = new App(states.toArray(new AppState[0])); + app.setSettings(appSettings); + app.setShowSettings(false); + + testDriver.waitLatch = new CountDownLatch(1); + executor.execute(() -> app.start(JmeContext.Type.Display)); + + int maxWaitTimeMilliseconds = 45000; + + try { + boolean exitedProperly = testDriver.waitLatch.await(maxWaitTimeMilliseconds, TimeUnit.MILLISECONDS); + + if(!exitedProperly){ + logger.warning("Test driver did not exit in " + maxWaitTimeMilliseconds + "ms. Timed out"); + app.stop(true); + } + + Thread.sleep(1000); //give time for openGL is fully released before starting a new test (get random JVM crashes without this) + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + + //search the imageTempDir + List imageFiles = new ArrayList<>(); + try(Stream paths = Files.list(imageTempDir)){ + paths.forEach(imageFiles::add); + } catch(IOException e){ + throw new RuntimeException(e); + } + + //this resorts with natural numeric ordering (so App10.png comes after App9.png) + imageFiles.sort(new Comparator(){ + @Override + public int compare(Path p1, Path p2){ + return extractNumber(p1).compareTo(extractNumber(p2)); + } + + private Integer extractNumber(Path path){ + String name = path.getFileName().toString(); + int numStart = screenshotAppFileNamePrefix.length(); + int numEnd = name.lastIndexOf(".png"); + return Integer.parseInt(name.substring(numStart, numEnd)); + } + }); + + if(imageFiles.isEmpty()){ + fail("No screenshot found in the temporary directory. Did the application crash?"); + } + if(imageFiles.size() != framesToTakeScreenshotsOn.size()){ + fail("Not all screenshots were taken, expected " + framesToTakeScreenshotsOn.size() + " but got " + imageFiles.size()); + } + + String failureMessage = null; + + try { + for(int screenshotIndex=0;screenshotIndex paths = Files.walk(temporaryFolder)) { + paths.sorted((a, b) -> b.getNameCount() - a.getNameCount()) + .forEach(path -> { + try { + Files.delete(path); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + /** + * Saves the image with the exact file name it needs to go into the resources directory to be a new reference image + * if the instigator of the change wants to accept this as the new "correct" state. + */ + private static Path saveGeneratedImageToChangedImages(Path generatedImage, String imageFileName) throws IOException{ + Path savedImage = Paths.get("build/changed-images/" + imageFileName + ".png"); + Files.createDirectories(savedImage.getParent()); + Files.copy(generatedImage, savedImage, StandardCopyOption.REPLACE_EXISTING); + aggressivelyCompressImage(savedImage); + return savedImage; + } + + /** + * This remains lossless but makes the maximum effort to compress the image. As these images + * may be committed to the repository it is important to keep them as small as possible and worth the extra CPU time + * to do so + */ + private static void aggressivelyCompressImage(Path path) throws IOException { + // Load your image + BufferedImage image = ImageIO.read(path.toFile()); + + // Get a PNG writer + ImageWriter writer = ImageIO.getImageWritersByFormatName("png").next(); + ImageWriteParam writeParam = writer.getDefaultWriteParam(); + + // Increase compression effort + writeParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT); + writeParam.setCompressionQuality(0.0f); // 0.0 means maximum compression + + // Save the image with increased compression + try (ImageOutputStream outputStream = ImageIO.createImageOutputStream(path.toFile())) { + writer.setOutput(outputStream); + writer.write(null, new IIOImage(image, null, null), writeParam); + } + + // Clean up + writer.dispose(); + } + + /** + * Attaches the image to the report. A copy of the image is made in the report directory + */ + private static void attachImage(String title, String fileName, Path originalImage) throws IOException{ + ExtentTest test = ExtentReportExtension.getCurrentTest(); + Files.copy(originalImage.toAbsolutePath(), Paths.get("build/reports/" + fileName), StandardCopyOption.REPLACE_EXISTING); + test.addScreenCaptureFromPath(fileName, title); + } + + /** + * Attaches the image to the report. The image is written to the report directory + */ + private static void attachImage(String title, String fileName, BufferedImage originalImage) throws IOException{ + ExtentTest test = ExtentReportExtension.getCurrentTest(); + ImageIO.write(originalImage, "png", Paths.get("build/reports/" + fileName).toFile()); + test.addScreenCaptureFromPath(fileName, title); + } + + /** + * Tests that the images are the same. If they are not the same it will return false (which may fail the test + * depending on the test type). Different sizes are so fatal that they will immediately fail the test. + */ + private static boolean imagesAreTheSame(BufferedImage img1, BufferedImage img2) { + if (img1.getWidth() != img2.getWidth() || img1.getHeight() != img2.getHeight()) { + ExtentReportExtension.getCurrentTest().createNode("Image 1 size : " + img1.getWidth() + "x" + img1.getHeight()); + ExtentReportExtension.getCurrentTest().createNode("Image 2 size : " + img2.getWidth() + "x" + img2.getHeight()); + fail(IMAGES_ARE_DIFFERENT_SIZES); + } + + for (int y = 0; y < img1.getHeight(); y++) { + for (int x = 0; x < img1.getWidth(); x++) { + if (img1.getRGB(x, y) != img2.getRGB(x, y)){ + return false; + } + } + } + return true; + } + + /** + * Creates an image that highlights the differences between the two images. The reference image is shown + * dully in grey with blue, yellow, orange and red showing where pixels are different. + */ + private static BufferedImage createComparisonImage(BufferedImage img1, BufferedImage img2) { + BufferedImage comparisonImage = new BufferedImage(img1.getWidth(), img1.getHeight(), BufferedImage.TYPE_INT_ARGB); + + for (int y = 0; y < img1.getHeight(); y++) { + for (int x = 0; x < img1.getWidth(); x++) { + PixelSamenessDegree pixelSameness = categorisePixelDifference(img1.getRGB(x, y),img2.getRGB(x, y)); + + if(pixelSameness == PixelSamenessDegree.SAME){ + int washedOutPixel = getWashedOutPixel(img1, x, y, 0.9f); + //Color rawColor = new Color(img1.getRGB(x, y), true); + comparisonImage.setRGB(x, y, washedOutPixel); + }else{ + comparisonImage.setRGB(x, y, pixelSameness.getColorInDebugImage().asIntARGB()); + } + } + } + return comparisonImage; + } + + /** + * This produces the almost grey ghost of the original image, used when the differences are being highlighted + */ + public static int getWashedOutPixel(BufferedImage img, int x, int y, float alpha) { + // Get the raw pixel value + int rgb = img.getRGB(x, y); + + // Extract the color components + int a = (rgb >> 24) & 0xFF; + int r = (rgb >> 16) & 0xFF; + int g = (rgb >> 8) & 0xFF; + int b = rgb & 0xFF; + + // Define the overlay gray color (same value for r, g, b) + int gray = 128; + + // Blend the original color with the gray color + r = (int) ((1 - alpha) * r + alpha * gray); + g = (int) ((1 - alpha) * g + alpha * gray); + b = (int) ((1 - alpha) * b + alpha * gray); + + // Clamp the values to the range [0, 255] + r = Math.min(255, r); + g = Math.min(255, g); + b = Math.min(255, b); + + // Combine the components back into a single int + + return (a << 24) | (r << 16) | (g << 8) | b; + } + + private static PixelSamenessDegree categorisePixelDifference(int pixel1, int pixel2){ + if(pixel1 == pixel2){ + return PixelSamenessDegree.SAME; + } + + int pixelDifference = getMaximumComponentDifference(pixel1, pixel2); + + if(pixelDifference<= PixelSamenessDegree.NEGLIGIBLY_DIFFERENT.getMaximumAllowedDifference()){ + return PixelSamenessDegree.NEGLIGIBLY_DIFFERENT; + } + if(pixelDifference<= PixelSamenessDegree.SUBTLY_DIFFERENT.getMaximumAllowedDifference()){ + return PixelSamenessDegree.SUBTLY_DIFFERENT; + } + if(pixelDifference<= PixelSamenessDegree.MEDIUMLY_DIFFERENT.getMaximumAllowedDifference()){ + return PixelSamenessDegree.MEDIUMLY_DIFFERENT; + } + if(pixelDifference<= PixelSamenessDegree.VERY_DIFFERENT.getMaximumAllowedDifference()){ + return PixelSamenessDegree.VERY_DIFFERENT; + } + return PixelSamenessDegree.EXTREMELY_DIFFERENT; + } + + private static int getMaximumComponentDifference(int pixel1, int pixel2){ + int r1 = (pixel1 >> 16) & 0xFF; + int g1 = (pixel1 >> 8) & 0xFF; + int b1 = pixel1 & 0xFF; + int a1 = (pixel1 >> 24) & 0xFF; + + int r2 = (pixel2 >> 16) & 0xFF; + int g2 = (pixel2 >> 8) & 0xFF; + int b2 = pixel2 & 0xFF; + int a2 = (pixel2 >> 24) & 0xFF; + + return Math.max(Math.abs(r1 - r2), Math.max(Math.abs(g1 - g2), Math.max(Math.abs(b1 - b2), Math.abs(a1 - a2)))); + } + +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestResolution.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestResolution.java new file mode 100644 index 0000000000..5bc9e31ace --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestResolution.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +/** + * The size the test should be run at. Try to keep it small to reduce the file size of the screenshots saved into the + * repository. + * + * @author Richard Tingle (aka richtea) + */ +public class TestResolution{ + int width; + int height; + + public TestResolution(int width, int height){ + this.width = width; + this.height = height; + } + + public int getWidth(){ + return width; + } + + public int getHeight(){ + return height; + } +} diff --git a/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestType.java b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestType.java new file mode 100644 index 0000000000..b32f2dc23b --- /dev/null +++ b/jme3-screenshot-tests/src/main/java/org/jmonkeyengine/screenshottests/testframework/TestType.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.testframework; + +/** + * Controls pass/fail behaviour of a test. Default behaviour is MUST_PASS. + */ +public enum TestType{ + /** + * Normal test, if it fails it will fail the step + */ + MUST_PASS, + /** + * Test is likely to fail because it is testing something non-deterministic (e.g. uses random numbers). + * This will be marked as a warning in the report but the test will not fail. + */ + NON_DETERMINISTIC, + /** + * Test is known to fail, this will be marked as a warning in the report but the test will not fail. + * It will be marked as a warning whether it passes or fails, this is because if it has started to pass again it should + * be returned to a normal test with type MUST_PASS. + */ + KNOWN_TO_FAIL, +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java new file mode 100644 index 0000000000..50435b1218 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestIssue2076.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.animation; + +import com.jme3.anim.SkinningControl; +import com.jme3.anim.util.AnimMigrationUtils; +import com.jme3.animation.SkeletonControl; +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.AmbientLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.VertexBuffer; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for JMonkeyEngine issue #2076: software skinning requires vertex + * normals. + * + *

                  If the issue is resolved, 2 copies of the Jaime model will be rendered in the screenshot. + * + *

                  If the issue is present, then the application will immediately crash, + * typically with a {@code NullPointerException}. + * + * @author Stephen Gold (original test) + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestIssue2076 extends ScreenshotTestBase { + + /** + * This test creates a scene with two Jaime models, one using the old animation system + * and one using the new animation system, both with software skinning and no vertex normals. + */ + @Test + public void testIssue2076() { + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Add ambient light + AmbientLight ambientLight = new AmbientLight(); + ambientLight.setColor(new ColorRGBA(1f, 1f, 1f, 1f)); + rootNode.addLight(ambientLight); + + /* + * The original Jaime model was chosen for testing because it includes + * tangent buffers (needed to trigger issue #2076) and uses the old + * animation system (so it can be easily used to test both systems). + */ + String assetPath = "Models/Jaime/Jaime.j3o"; + + // Test old animation system + Node oldJaime = (Node) assetManager.loadModel(assetPath); + rootNode.attachChild(oldJaime); + oldJaime.setLocalTranslation(-1f, 0f, 0f); + + // Enable software skinning + SkeletonControl skeletonControl = oldJaime.getControl(SkeletonControl.class); + skeletonControl.setHardwareSkinningPreferred(false); + + // Remove its vertex normals + Geometry oldGeometry = (Geometry) oldJaime.getChild(0); + Mesh oldMesh = oldGeometry.getMesh(); + oldMesh.clearBuffer(VertexBuffer.Type.Normal); + oldMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + + // Test new animation system + Node newJaime = (Node) assetManager.loadModel(assetPath); + AnimMigrationUtils.migrate(newJaime); + rootNode.attachChild(newJaime); + newJaime.setLocalTranslation(1f, 0f, 0f); + + // Enable software skinning + SkinningControl skinningControl = newJaime.getControl(SkinningControl.class); + skinningControl.setHardwareSkinningPreferred(false); + + // Remove its vertex normals + Geometry newGeometry = (Geometry) newJaime.getChild(0); + Mesh newMesh = newGeometry.getMesh(); + newMesh.clearBuffer(VertexBuffer.Type.Normal); + newMesh.clearBuffer(VertexBuffer.Type.BindPoseNormal); + + // Position the camera to see both models + simpleApplication.getCamera().setLocation(new Vector3f(0f, 0f, 5f)); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + + @Override + public void update(float tpf) { + super.update(tpf); + } + }).run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java new file mode 100644 index 0000000000..28a5e042d2 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/animation/TestMotionPath.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.animation; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.MotionPathListener; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.font.BitmapText; +import com.jme3.input.ChaseCamera; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Box; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the MotionPath functionality. + * + *

                  This test creates a teapot model that follows a predefined path with several waypoints. + * The animation is automatically started and screenshots are taken at frames 10 and 60 + * to capture the teapot at different positions along the path. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestMotionPath extends ScreenshotTestBase { + + /** + * This test creates a scene with a teapot following a motion path. + */ + @Test + public void testMotionPath() { + screenshotTest(new BaseAppState() { + private Spatial teapot; + private MotionPath path; + private MotionEvent motionControl; + private BitmapText wayPointsText; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + Node guiNode = simpleApplication.getGuiNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Set camera position + app.getCamera().setLocation(new Vector3f(8.4399185f, 11.189463f, 14.267577f)); + + // Create the scene + createScene(rootNode, assetManager); + + // Create the motion path + path = new MotionPath(); + path.addWayPoint(new Vector3f(10, 3, 0)); + path.addWayPoint(new Vector3f(10, 3, 10)); + path.addWayPoint(new Vector3f(-40, 3, 10)); + path.addWayPoint(new Vector3f(-40, 3, 0)); + path.addWayPoint(new Vector3f(-40, 8, 0)); + path.addWayPoint(new Vector3f(10, 8, 0)); + path.addWayPoint(new Vector3f(10, 8, 10)); + path.addWayPoint(new Vector3f(15, 8, 10)); + path.enableDebugShape(assetManager, rootNode); + + // Create the motion event + motionControl = new MotionEvent(teapot, path); + motionControl.setDirectionType(MotionEvent.Direction.PathAndRotation); + motionControl.setRotation(new Quaternion().fromAngleNormalAxis(-FastMath.HALF_PI, Vector3f.UNIT_Y)); + motionControl.setInitialDuration(10f); + motionControl.setSpeed(2f); + + // Create text for waypoint notifications + wayPointsText = new BitmapText(assetManager.loadFont("Interface/Fonts/Default.fnt")); + wayPointsText.setSize(wayPointsText.getFont().getCharSet().getRenderedSize()); + guiNode.attachChild(wayPointsText); + + // Add listener for waypoint events + path.addListener(new MotionPathListener() { + @Override + public void onWayPointReach(MotionEvent control, int wayPointIndex) { + if (path.getNbWayPoints() == wayPointIndex + 1) { + wayPointsText.setText(control.getSpatial().getName() + " Finished!!! "); + } else { + wayPointsText.setText(control.getSpatial().getName() + " Reached way point " + wayPointIndex); + } + wayPointsText.setLocalTranslation( + (app.getCamera().getWidth() - wayPointsText.getLineWidth()) / 2, + app.getCamera().getHeight(), + 0); + } + }); + + // note that the ChaseCamera is self-initialising, so just creating this object attaches it + new ChaseCamera(getApplication().getCamera(), teapot); + + // Start the animation automatically + motionControl.play(); + } + + private void createScene(Node rootNode, AssetManager assetManager) { + // Create materials + Material mat = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + mat.setFloat("Shininess", 1f); + mat.setBoolean("UseMaterialColors", true); + mat.setColor("Ambient", ColorRGBA.Black); + mat.setColor("Diffuse", ColorRGBA.DarkGray); + mat.setColor("Specular", ColorRGBA.White.mult(0.6f)); + + Material matSoil = new Material(assetManager, "Common/MatDefs/Light/Lighting.j3md"); + matSoil.setBoolean("UseMaterialColors", true); + matSoil.setColor("Ambient", ColorRGBA.Black); + matSoil.setColor("Diffuse", ColorRGBA.Black); + matSoil.setColor("Specular", ColorRGBA.Black); + + // Create teapot + teapot = assetManager.loadModel("Models/Teapot/Teapot.obj"); + teapot.setName("Teapot"); + teapot.setLocalScale(3); + teapot.setMaterial(mat); + rootNode.attachChild(teapot); + + // Create ground + Geometry soil = new Geometry("soil", new Box(50, 1, 50)); + soil.setLocalTranslation(0, -1, 0); + soil.setMaterial(matSoil); + rootNode.attachChild(soil); + + // Add light + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(0, -1, 0).normalizeLocal()); + light.setColor(ColorRGBA.White.mult(1.5f)); + rootNode.addLight(light); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + }) + .setFramesToTakeScreenshotsOn(10, 60) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestExplosionEffect.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestExplosionEffect.java new file mode 100644 index 0000000000..6501c1057b --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestExplosionEffect.java @@ -0,0 +1,290 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.effects; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.effect.shapes.EmitterSphereShape; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Node; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * @author Richard Tingle (aka richtea) + */ +public class TestExplosionEffect extends ScreenshotTestBase{ + + /** + * This test's particle effects (using an explosion) + */ + @Test + public void testExplosionEffect(){ + screenshotTest(new BaseAppState(){ + private ParticleEmitter flame, flash, spark, roundspark, smoketrail, debris, + shockwave; + + private int state = 0; + + final private Node explosionEffect = new Node("explosionFX"); + + @Override + protected void initialize(Application app){ + createFlame(); + createFlash(); + createSpark(); + createRoundSpark(); + createSmokeTrail(); + createDebris(); + createShockwave(); + explosionEffect.setLocalScale(0.5f); + app.getRenderManager().preloadScene(explosionEffect); + + Camera camera = app.getCamera(); + camera.setLocation(new Vector3f(0, 3.5135868f, 10)); + camera.setRotation(new Quaternion(1.5714673E-4f, 0.98696727f, -0.16091813f, 9.6381607E-4f)); + Node rootNode = ((SimpleApplication)app).getRootNode(); + rootNode.attachChild(explosionEffect); + } + + @Override + protected void cleanup(Application app){} + + @Override + protected void onEnable(){} + + @Override + protected void onDisable(){} + + private void createFlame(){ + flame = new ParticleEmitter("Flame", ParticleMesh.Type.Point, 32); + flame.setSelectRandomImage(true); + flame.setStartColor(new ColorRGBA(1f, 0.4f, 0.05f, 1f )); + flame.setEndColor(new ColorRGBA(.4f, .22f, .12f, 0f)); + flame.setStartSize(1.3f); + flame.setEndSize(2f); + flame.setShape(new EmitterSphereShape(Vector3f.ZERO, 1f)); + flame.setParticlesPerSec(0); + flame.setGravity(0, -5, 0); + flame.setLowLife(.4f); + flame.setHighLife(.5f); + flame.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 7, 0)); + flame.getParticleInfluencer().setVelocityVariation(1f); + flame.setImagesX(2); + flame.setImagesY(2); + + AssetManager assetManager = getApplication().getAssetManager(); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png")); + mat.setBoolean("PointSprite", true); + flame.setMaterial(mat); + explosionEffect.attachChild(flame); + } + + private void createFlash(){ + AssetManager assetManager = getApplication().getAssetManager(); + flash = new ParticleEmitter("Flash", ParticleMesh.Type.Point, 24 ); + flash.setSelectRandomImage(true); + flash.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1f )); + flash.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + flash.setStartSize(.1f); + flash.setEndSize(3.0f); + flash.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); + flash.setParticlesPerSec(0); + flash.setGravity(0, 0, 0); + flash.setLowLife(.2f); + flash.setHighLife(.2f); + flash.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 5f, 0)); + flash.getParticleInfluencer().setVelocityVariation(1); + flash.setImagesX(2); + flash.setImagesY(2); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flash.png")); + mat.setBoolean("PointSprite", true); + flash.setMaterial(mat); + explosionEffect.attachChild(flash); + } + + private void createRoundSpark(){ + AssetManager assetManager = getApplication().getAssetManager(); + roundspark = new ParticleEmitter("RoundSpark", ParticleMesh.Type.Point, 20 ); + roundspark.setStartColor(new ColorRGBA(1f, 0.29f, 0.34f, (float) (1.0 ))); + roundspark.setEndColor(new ColorRGBA(0, 0, 0, 0.5f )); + roundspark.setStartSize(1.2f); + roundspark.setEndSize(1.8f); + roundspark.setShape(new EmitterSphereShape(Vector3f.ZERO, 2f)); + roundspark.setParticlesPerSec(0); + roundspark.setGravity(0, -.5f, 0); + roundspark.setLowLife(1.8f); + roundspark.setHighLife(2f); + roundspark.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 3, 0)); + roundspark.getParticleInfluencer().setVelocityVariation(.5f); + roundspark.setImagesX(1); + roundspark.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/roundspark.png")); + mat.setBoolean("PointSprite", true); + roundspark.setMaterial(mat); + explosionEffect.attachChild(roundspark); + } + + private void createSpark(){ + AssetManager assetManager = getApplication().getAssetManager(); + spark = new ParticleEmitter("Spark", ParticleMesh.Type.Triangle, 30 ); + spark.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f)); + spark.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + spark.setStartSize(.5f); + spark.setEndSize(.5f); + spark.setFacingVelocity(true); + spark.setParticlesPerSec(0); + spark.setGravity(0, 5, 0); + spark.setLowLife(1.1f); + spark.setHighLife(1.5f); + spark.getParticleInfluencer().setInitialVelocity(new Vector3f(0, 20, 0)); + spark.getParticleInfluencer().setVelocityVariation(1); + spark.setImagesX(1); + spark.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/spark.png")); + spark.setMaterial(mat); + explosionEffect.attachChild(spark); + } + + private void createSmokeTrail(){ + AssetManager assetManager = getApplication().getAssetManager(); + smoketrail = new ParticleEmitter("SmokeTrail", ParticleMesh.Type.Triangle, 22 ); + smoketrail.setStartColor(new ColorRGBA(1f, 0.8f, 0.36f, 1.0f )); + smoketrail.setEndColor(new ColorRGBA(1f, 0.8f, 0.36f, 0f)); + smoketrail.setStartSize(.2f); + smoketrail.setEndSize(1f); + + smoketrail.setFacingVelocity(true); + smoketrail.setParticlesPerSec(0); + smoketrail.setGravity(0, 1, 0); + smoketrail.setLowLife(.4f); + smoketrail.setHighLife(.5f); + smoketrail.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 12, 0)); + smoketrail.getParticleInfluencer().setVelocityVariation(1); + smoketrail.setImagesX(1); + smoketrail.setImagesY(3); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/smoketrail.png")); + smoketrail.setMaterial(mat); + explosionEffect.attachChild(smoketrail); + } + + private void createDebris(){ + AssetManager assetManager = getApplication().getAssetManager(); + debris = new ParticleEmitter("Debris", ParticleMesh.Type.Triangle, 15 ); + debris.setSelectRandomImage(true); + debris.setRandomAngle(true); + debris.setRotateSpeed(FastMath.TWO_PI * 4); + debris.setStartColor(new ColorRGBA(1f, 0.59f, 0.28f, 1.0f )); + debris.setEndColor(new ColorRGBA(.5f, 0.5f, 0.5f, 0f)); + debris.setStartSize(.2f); + debris.setEndSize(.2f); + +// debris.setShape(new EmitterSphereShape(Vector3f.ZERO, .05f)); + debris.setParticlesPerSec(0); + debris.setGravity(0, 12f, 0); + debris.setLowLife(1.4f); + debris.setHighLife(1.5f); + debris.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 15, 0)); + debris.getParticleInfluencer().setVelocityVariation(.60f); + debris.setImagesX(3); + debris.setImagesY(3); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/Debris.png")); + debris.setMaterial(mat); + explosionEffect.attachChild(debris); + } + + private void createShockwave(){ + AssetManager assetManager = getApplication().getAssetManager(); + shockwave = new ParticleEmitter("Shockwave", ParticleMesh.Type.Triangle, 1); +// shockwave.setRandomAngle(true); + shockwave.setFaceNormal(Vector3f.UNIT_Y); + shockwave.setStartColor(new ColorRGBA(.48f, 0.17f, 0.01f, .8f)); + shockwave.setEndColor(new ColorRGBA(.48f, 0.17f, 0.01f, 0f)); + + shockwave.setStartSize(0f); + shockwave.setEndSize(7f); + + shockwave.setParticlesPerSec(0); + shockwave.setGravity(0, 0, 0); + shockwave.setLowLife(0.5f); + shockwave.setHighLife(0.5f); + shockwave.getParticleInfluencer() + .setInitialVelocity(new Vector3f(0, 0, 0)); + shockwave.getParticleInfluencer().setVelocityVariation(0f); + shockwave.setImagesX(1); + shockwave.setImagesY(1); + Material mat = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md"); + mat.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/shockwave.png")); + shockwave.setMaterial(mat); + explosionEffect.attachChild(shockwave); + } + @Override + public void update(float tpf){ + if (state == 0){ + flash.emitAllParticles(); + spark.emitAllParticles(); + smoketrail.emitAllParticles(); + debris.emitAllParticles(); + shockwave.emitAllParticles(); + state++; + } + if (state == 1){ + flame.emitAllParticles(); + roundspark.emitAllParticles(); + state++; + } + } + + }).setFramesToTakeScreenshotsOn(2,15) + .run(); + } + +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestIssue1773.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestIssue1773.java new file mode 100644 index 0000000000..05eb097ee7 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/effects/TestIssue1773.java @@ -0,0 +1,266 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.effects; + +import com.jme3.animation.LoopMode; +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.cinematic.MotionPath; +import com.jme3.cinematic.events.MotionEvent; +import com.jme3.effect.ParticleEmitter; +import com.jme3.effect.ParticleMesh; +import com.jme3.effect.shapes.EmitterMeshVertexShape; +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.Camera; +import com.jme3.renderer.ViewPort; +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.texture.Texture; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.util.Arrays; + +/** + * @author Richard Tingle (aka richtea) + */ +public class TestIssue1773 extends ScreenshotTestBase{ + + /** + * 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. + * + */ + @ParameterizedTest(name = "Test Issue 1773 (emit in worldSpace = {0})") + @ValueSource(booleans = {true, false}) + public void testIssue1773(boolean worldSpace, TestInfo testInfo){ + + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + (worldSpace ? "_worldSpace" : "_localSpace"); + + screenshotTest(new BaseAppState(){ + private ParticleEmitter emit; + private Node myModel; + + AssetManager assetManager; + + Node rootNode; + + Camera cam; + + ViewPort viewPort; + + @Override + public void initialize(Application app) { + assetManager = app.getAssetManager(); + rootNode = ((SimpleApplication)app).getRootNode(); + cam = app.getCamera(); + viewPort = app.getViewPort(); + configCamera(); + setupLights(); + setupGround(); + setupCircle(); + createMotionControl(); + } + + @Override + protected void cleanup(Application app){} + + @Override + protected void onEnable(){} + + @Override + protected void onDisable(){} + + /** + * 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) { + ParticleMesh.Type type = pointSprite ? ParticleMesh.Type.Point : ParticleMesh.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.setInWorldSpace(worldSpace); + //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)); + } + MotionEvent motionControl = new MotionEvent(myModel, path); + motionControl.setLoopMode(LoopMode.Loop); + motionControl.setDirectionType(MotionEvent.Direction.Path); + motionControl.play(); + } + + private void configCamera() { + 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); + } + + + }).setFramesToTakeScreenshotsOn(45) + .setBaseImageFileName(imageName) + .run(); + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/export/TestOgreConvert.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/export/TestOgreConvert.java new file mode 100644 index 0000000000..f076902f2d --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/export/TestOgreConvert.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.export; + +import com.jme3.anim.AnimComposer; +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.export.binary.BinaryExporter; +import com.jme3.export.binary.BinaryImporter; +import com.jme3.light.DirectionalLight; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; + +/** + * @author Richard Tingle (aka richtea) + */ +public class TestOgreConvert extends ScreenshotTestBase{ + + /** + * This tests loads an Ogre model, converts it to binary, and then reloads it. + *

                  + * Note that the model is animated and the animation is played back. That is why + * two screenshots are taken + *

                  + */ + @Test + public void testOgreConvert(){ + + screenshotTest( + new BaseAppState(){ + @Override + protected void initialize(Application app){ + AssetManager assetManager = app.getAssetManager(); + Node rootNode = ((SimpleApplication)app).getRootNode(); + Camera cam = app.getCamera(); + Spatial ogreModel = assetManager.loadModel("Models/Oto/Oto.mesh.xml"); + + DirectionalLight dl = new DirectionalLight(); + dl.setColor(ColorRGBA.White); + dl.setDirection(new Vector3f(0,-1,-1).normalizeLocal()); + rootNode.addLight(dl); + + cam.setLocation(new Vector3f(0, 0, 15)); + + try { + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + BinaryExporter exp = new BinaryExporter(); + exp.save(ogreModel, baos); + + ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); + BinaryImporter imp = new BinaryImporter(); + imp.setAssetManager(assetManager); + Node ogreModelReloaded = (Node) imp.load(bais, null, null); + + AnimComposer composer = ogreModelReloaded.getControl(AnimComposer.class); + composer.setCurrentAction("Walk"); + + rootNode.attachChild(ogreModelReloaded); + } catch (IOException ex){ + throw new RuntimeException(ex); + } + } + + @Override + protected void cleanup(Application app){} + + @Override + protected void onEnable(){} + + @Override + protected void onDisable(){} + } + ) + .setFramesToTakeScreenshotsOn(1, 5) + .run(); + + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/gui/TestBitmapText3D.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/gui/TestBitmapText3D.java new file mode 100644 index 0000000000..b68e4589df --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/gui/TestBitmapText3D.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.gui; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.font.BitmapFont; +import com.jme3.font.BitmapText; +import com.jme3.font.Rectangle; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.shape.Quad; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * @author Richard Tingle (aka richtea) + */ +public class TestBitmapText3D extends ScreenshotTestBase{ + + /** + * This tests both that bitmap text is rendered correctly and that it is + * wrapped correctly. + */ + @Test + public void testBitmapText3D(){ + + screenshotTest( + new BaseAppState(){ + @Override + protected void initialize(Application app){ + String txtB = "ABCDEFGHIKLMNOPQRSTUVWXYZ1234567890`~!@#$%^&*()-=_+[]\\;',./{}|:<>?"; + + AssetManager assetManager = app.getAssetManager(); + Node rootNode = ((SimpleApplication)app).getRootNode(); + + Quad q = new Quad(6, 3); + Geometry g = new Geometry("quad", q); + g.setLocalTranslation(-1.5f, -3, -0.0001f); + g.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(g); + + BitmapFont fnt = assetManager.loadFont("Interface/Fonts/Default.fnt"); + BitmapText txt = new BitmapText(fnt); + txt.setBox(new Rectangle(0, 0, 6, 3)); + txt.setQueueBucket(RenderQueue.Bucket.Transparent); + txt.setSize( 0.5f ); + txt.setText(txtB); + txt.setLocalTranslation(-1.5f,0,0); + rootNode.attachChild(txt); + } + + @Override + protected void cleanup(Application app){} + + @Override + protected void onEnable(){} + + @Override + protected void onDisable(){} + } + ).run(); + + + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java new file mode 100644 index 0000000000..0cbd19da24 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRLighting.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.light.pbr; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.environment.EnvironmentCamera; +import com.jme3.environment.FastLightProbeFactory; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.ToneMapFilter; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.texture.plugins.ktx.KTXLoader; +import com.jme3.util.SkyFactory; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * Screenshot tests for PBR lighting. + * + * @author nehon - original test + * @author Richard Tingle (aka richtea) - screenshot test adaptation + * + */ +public class TestPBRLighting extends ScreenshotTestBase { + + private static Stream testParameters() { + return Stream.of( + Arguments.of("LowRoughness", 0.1f, false), + Arguments.of("HighRoughness", 1.0f, false), + Arguments.of("DefaultDirectionalLight", 0.5f, false), + Arguments.of("UpdatedDirectionalLight", 0.5f, true) + ); + } + + /** + * Test PBR lighting with different parameters + * + * @param testName The name of the test (used for screenshot filename) + * @param roughness The roughness value to use + * @param updateLight Whether to update the directional light to match camera direction + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testParameters") + public void testPBRLighting(String testName, float roughness, boolean updateLight, TestInfo testInfo) { + + if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) { + throw new RuntimeException("Test preconditions not met"); + } + + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName; + + screenshotTest(new BaseAppState() { + private static final int RESOLUTION = 256; + + private Node modelNode; + private int frame = 0; + + @Override + protected void initialize(Application app) { + Camera cam = app.getCamera(); + cam.setLocation(new Vector3f(18, 10, 0)); + cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y); + + AssetManager assetManager = app.getAssetManager(); + assetManager.registerLoader(KTXLoader.class, "ktx"); + + app.getViewPort().setBackgroundColor(ColorRGBA.White); + + modelNode = new Node("modelNode"); + Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); + MikktspaceTangentGenerator.generate(model); + modelNode.attachChild(model); + + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, -1).normalizeLocal()); + SimpleApplication simpleApp = (SimpleApplication) app; + simpleApp.getRootNode().addLight(dl); + dl.setColor(ColorRGBA.White); + + // If we need to update the light direction to match camera + if (updateLight) { + dl.setDirection(app.getCamera().getDirection().normalize()); + } + + simpleApp.getRootNode().attachChild(modelNode); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + int numSamples = app.getContext().getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + + fpp.addFilter(new ToneMapFilter(Vector3f.UNIT_XYZ.mult(4.0f))); + app.getViewPort().addProcessor(fpp); + + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + simpleApp.getRootNode().attachChild(sky); + + Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); + pbrMat.setFloat("Roughness", roughness); + model.setMaterial(pbrMat); + + // Set up environment camera + EnvironmentCamera envCam = new EnvironmentCamera(RESOLUTION, new Vector3f(0, 3f, 0)); + app.getStateManager().attach(envCam); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() {} + + @Override + public void update(float tpf) { + frame++; + + if (frame == 2) { + modelNode.removeFromParent(); + LightProbe probe; + + SimpleApplication simpleApp = (SimpleApplication) getApplication(); + probe = FastLightProbeFactory.makeProbe(simpleApp.getRenderManager(), + simpleApp.getAssetManager(), + RESOLUTION, + Vector3f.ZERO, + 1f, + 1000f, + simpleApp.getRootNode()); + + probe.getArea().setRadius(100); + simpleApp.getRootNode().addLight(probe); + } + + if (frame > 10 && modelNode.getParent() == null) { + SimpleApplication simpleApp = (SimpleApplication) getApplication(); + simpleApp.getRootNode().attachChild(modelNode); + } + } + }).setBaseImageFileName(imageName) + .setFramesToTakeScreenshotsOn(12) + .run(); + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java new file mode 100644 index 0000000000..70220f7eef --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/light/pbr/TestPBRSimple.java @@ -0,0 +1,138 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.light.pbr; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.environment.EnvironmentProbeControl; +import com.jme3.material.Material; +import com.jme3.math.Vector3f; +import com.jme3.renderer.Camera; +import com.jme3.scene.Geometry; +import com.jme3.scene.Spatial; +import com.jme3.util.SkyFactory; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * A simpler PBR example that uses EnvironmentProbeControl to bake the environment + * + * @author Richard Tingle (aka richtea) - screenshot test adaptation + */ +public class TestPBRSimple extends ScreenshotTestBase { + + private static Stream testParameters() { + return Stream.of( + Arguments.of("WithRealtimeBaking", true), + Arguments.of("WithoutRealtimeBaking", false) + ); + } + + /** + * Test PBR simple with different parameters + * + * @param testName The name of the test (used for screenshot filename) + * @param realtimeBaking Whether to use realtime baking + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testParameters") + public void testPBRSimple(String testName, boolean realtimeBaking, TestInfo testInfo) { + if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) { + throw new RuntimeException("Test preconditions not met"); + } + + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName; + + screenshotTest(new BaseAppState() { + private int frame = 0; + + @Override + protected void initialize(Application app) { + Camera cam = app.getCamera(); + cam.setLocation(new Vector3f(18, 10, 0)); + cam.lookAt(new Vector3f(0, 0, 0), Vector3f.UNIT_Y); + + AssetManager assetManager = app.getAssetManager(); + SimpleApplication simpleApp = (SimpleApplication) app; + + // Create the tank model + Geometry model = (Geometry) assetManager.loadModel("Models/Tank/tank.j3o"); + MikktspaceTangentGenerator.generate(model); + + Material pbrMat = assetManager.loadMaterial("Models/Tank/tank.j3m"); + model.setMaterial(pbrMat); + simpleApp.getRootNode().attachChild(model); + + // Create sky + Spatial sky = SkyFactory.createSky(assetManager, "Textures/Sky/Path.hdr", SkyFactory.EnvMapType.EquirectMap); + simpleApp.getRootNode().attachChild(sky); + + // Create baker control + EnvironmentProbeControl envProbe = new EnvironmentProbeControl(assetManager, 256); + simpleApp.getRootNode().addControl(envProbe); + + // Tag the sky, only the tagged spatials will be rendered in the env map + envProbe.tag(sky); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() {} + + @Override + public void update(float tpf) { + if (realtimeBaking) { + frame++; + if (frame == 2) { + SimpleApplication simpleApp = (SimpleApplication) getApplication(); + simpleApp.getRootNode().getControl(EnvironmentProbeControl.class).rebake(); + } + } + } + }).setBaseImageFileName(imageName) + .setFramesToTakeScreenshotsOn(10) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java new file mode 100644 index 0000000000..0e4e53df54 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/material/TestSimpleBumps.java @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.material; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.PointLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.shape.Quad; +import com.jme3.scene.shape.Sphere; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the SimpleBumps material test. + * + *

                  This test creates a quad with a bump map material and a point light that orbits around it. + * The light's position is represented by a small red sphere. Screenshots are taken at frames 10 and 60 + * to capture the light at different positions in its orbit. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestSimpleBumps extends ScreenshotTestBase { + + /** + * This test creates a scene with a bump-mapped quad and an orbiting light. + */ + @Test + public void testSimpleBumps() { + screenshotTest(new BaseAppState() { + private float angle; + private PointLight pl; + private Spatial lightMdl; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + // Create quad with bump map material + Quad quadMesh = new Quad(1, 1); + Geometry sphere = new Geometry("Rock Ball", quadMesh); + Material mat = assetManager.loadMaterial("Textures/BumpMapTest/SimpleBump.j3m"); + sphere.setMaterial(mat); + MikktspaceTangentGenerator.generate(sphere); + rootNode.attachChild(sphere); + + // Create light representation + lightMdl = new Geometry("Light", new Sphere(10, 10, 0.1f)); + lightMdl.setMaterial(assetManager.loadMaterial("Common/Materials/RedColor.j3m")); + rootNode.attachChild(lightMdl); + + // Create point light + pl = new PointLight(); + pl.setColor(ColorRGBA.White); + pl.setPosition(new Vector3f(0f, 0f, 4f)); + rootNode.addLight(pl); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + + @Override + public void update(float tpf) { + super.update(tpf); + + angle += tpf * 2f; + angle %= FastMath.TWO_PI; + + pl.setPosition(new Vector3f(FastMath.cos(angle) * 4f, 0.5f, FastMath.sin(angle) * 4f)); + lightMdl.setLocalTranslation(pl.getPosition()); + } + }) + .setFramesToTakeScreenshotsOn(10, 60) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/model/shape/TestBillboard.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/model/shape/TestBillboard.java new file mode 100644 index 0000000000..096662503b --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/model/shape/TestBillboard.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2025 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 org.jmonkeyengine.screenshottests.model.shape; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.Mesh; +import com.jme3.scene.Node; +import com.jme3.scene.control.BillboardControl; +import com.jme3.scene.debug.Arrow; +import com.jme3.scene.debug.Grid; +import com.jme3.scene.shape.Quad; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * Screenshot test for the Billboard test. + * + *

                  This test creates three different billboard alignments (Screen, Camera, AxialY) + * with different colored quads. Each billboard is positioned at a different x-coordinate + * and has a blue Z-axis arrow attached to it. Screenshots are taken from three different angles: + * front, above, and right. + * + * @author Richard Tingle (screenshot test adaptation) + */ +@SuppressWarnings("OptionalGetWithoutIsPresent") +public class TestBillboard extends ScreenshotTestBase { + + private static Stream testParameters() { + return Stream.of( + Arguments.of("fromFront", new Vector3f(0, 1, 15)), + Arguments.of("fromAbove", new Vector3f(0, 15, 6)), + Arguments.of("fromRight", new Vector3f(-15, 10, 5)) + ); + } + + /** + * A billboard test with the specified camera parameters. + * + * @param cameraPosition The position of the camera + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testParameters") + public void testBillboard(String testName, Vector3f cameraPosition, TestInfo testInfo) { + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName; + + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + + // Set up the camera + simpleApplication.getCamera().setLocation(cameraPosition); + simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + // Set background color + simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.DarkGray); + + // Create grid + Geometry grid = makeShape(simpleApplication, "DebugGrid", new Grid(21, 21, 2), ColorRGBA.Gray); + grid.center().move(0, 0, 0); + rootNode.attachChild(grid); + + // Create billboards with different alignments + Node node = createBillboard(simpleApplication, BillboardControl.Alignment.Screen, ColorRGBA.Red); + node.setLocalTranslation(-6f, 0, 0); + rootNode.attachChild(node); + + node = createBillboard(simpleApplication, BillboardControl.Alignment.Camera, ColorRGBA.Green); + node.setLocalTranslation(-2f, 0, 0); + rootNode.attachChild(node); + + node = createBillboard(simpleApplication, BillboardControl.Alignment.AxialY, ColorRGBA.Blue); + node.setLocalTranslation(2f, 0, 0); + rootNode.attachChild(node); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() {} + + private Node createBillboard(SimpleApplication app, BillboardControl.Alignment alignment, ColorRGBA color) { + Node node = new Node("Parent"); + Quad quad = new Quad(2, 2); + Geometry g = makeShape(app, alignment.name(), quad, color); + BillboardControl bc = new BillboardControl(); + bc.setAlignment(alignment); + g.addControl(bc); + node.attachChild(g); + node.attachChild(makeShape(app, "ZAxis", new Arrow(Vector3f.UNIT_Z), ColorRGBA.Blue)); + return node; + } + + private Geometry makeShape(SimpleApplication app, String name, Mesh shape, ColorRGBA color) { + Geometry geo = new Geometry(name, shape); + Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md"); + mat.setColor("Color", color); + geo.setMaterial(mat); + return geo; + } + }) + .setBaseImageFileName(imageName) + .setFramesToTakeScreenshotsOn(1) + .run(); + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestCartoonEdge.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestCartoonEdge.java new file mode 100644 index 0000000000..3f40859c18 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestCartoonEdge.java @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2025 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 org.jmonkeyengine.screenshottests.post; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.FastMath; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.CartoonEdgeFilter; +import com.jme3.renderer.Caps; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.scene.Spatial.CullHint; +import com.jme3.texture.Texture; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the CartoonEdge filter. + * + *

                  This test creates a scene with a monkey head model that has a cartoon/cel-shaded effect + * applied to it. The CartoonEdgeFilter is used to create yellow outlines around the edges + * of the model, and a toon shader is applied to the model's material to create the cel-shaded look. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestCartoonEdge extends ScreenshotTestBase { + + /** + * This test creates a scene with a cartoon-shaded monkey head model. + */ + @Test + public void testCartoonEdge() { + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + + simpleApplication.getViewPort().setBackgroundColor(ColorRGBA.Gray); + + simpleApplication.getCamera().setLocation(new Vector3f(-1, 2, -5)); + simpleApplication.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + simpleApplication.getCamera().setFrustumFar(300); + + rootNode.setCullHint(CullHint.Never); + + setupLighting(rootNode); + + setupModel(simpleApplication, rootNode); + + setupFilters(simpleApplication); + } + + private void setupFilters(SimpleApplication app) { + if (app.getRenderer().getCaps().contains(Caps.GLSL100)) { + FilterPostProcessor fpp = new FilterPostProcessor(app.getAssetManager()); + + CartoonEdgeFilter toon = new CartoonEdgeFilter(); + toon.setEdgeColor(ColorRGBA.Yellow); + fpp.addFilter(toon); + app.getViewPort().addProcessor(fpp); + } + } + + private void setupLighting(Node rootNode) { + DirectionalLight dl = new DirectionalLight(); + dl.setDirection(new Vector3f(-1, -1, 1).normalizeLocal()); + dl.setColor(new ColorRGBA(2, 2, 2, 1)); + rootNode.addLight(dl); + } + + private void setupModel(SimpleApplication app, Node rootNode) { + Spatial model = app.getAssetManager().loadModel("Models/MonkeyHead/MonkeyHead.mesh.xml"); + makeToonish(app, model); + model.rotate(0, FastMath.PI, 0); + rootNode.attachChild(model); + } + + private void makeToonish(SimpleApplication app, Spatial spatial) { + if (spatial instanceof Node) { + Node n = (Node) spatial; + for (Spatial child : n.getChildren()) { + makeToonish(app, child); + } + } else if (spatial instanceof Geometry) { + Geometry g = (Geometry) spatial; + Material m = g.getMaterial(); + if (m.getMaterialDef().getMaterialParam("UseMaterialColors") != null) { + Texture t = app.getAssetManager().loadTexture("Textures/ColorRamp/toon.png"); + m.setTexture("ColorRamp", t); + m.setBoolean("UseMaterialColors", true); + m.setColor("Specular", ColorRGBA.Black); + m.setColor("Diffuse", ColorRGBA.White); + m.setBoolean("VertexLighting", true); + } + } + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + }) + .setFramesToTakeScreenshotsOn(1) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestFog.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestFog.java new file mode 100644 index 0000000000..796feb0edb --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestFog.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2025 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 org.jmonkeyengine.screenshottests.post; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.FogFilter; +import com.jme3.renderer.queue.RenderQueue; +import com.jme3.scene.Node; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.util.SkyFactory; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the Fog filter. + * + *

                  This test creates a scene with a terrain and sky, with a fog effect applied. + * The fog is a light gray color and has a specific density and distance setting. + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestFog extends ScreenshotTestBase { + + /** + * This test creates a scene with a fog effect. + */ + @Test + public void testFog() { + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + + simpleApplication.getCamera().setLocation(new Vector3f(-34.74095f, 95.21318f, -287.4945f)); + simpleApplication.getCamera().setRotation(new Quaternion(0.023536969f, 0.9361278f, -0.016098259f, -0.35050195f)); + + Node mainScene = new Node(); + + mainScene.attachChild(SkyFactory.createSky(simpleApplication.getAssetManager(), + "Textures/Sky/Bright/BrightSky.dds", + SkyFactory.EnvMapType.CubeMap)); + + createTerrain(mainScene, app.getAssetManager()); + + DirectionalLight sun = new DirectionalLight(); + Vector3f lightDir = new Vector3f(-0.37352666f, -0.50444174f, -0.7784704f); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + mainScene.addLight(sun); + + rootNode.attachChild(mainScene); + + FilterPostProcessor fpp = new FilterPostProcessor(simpleApplication.getAssetManager()); + + FogFilter fog = new FogFilter(); + fog.setFogColor(new ColorRGBA(0.9f, 0.9f, 0.9f, 1.0f)); + fog.setFogDistance(155); + fog.setFogDensity(1.0f); + fpp.addFilter(fog); + simpleApplication.getViewPort().addProcessor(fpp); + } + + + private void createTerrain(Node rootNode, AssetManager assetManager) { + Material matRock = new Material(assetManager, "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(Texture.WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(Texture.WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(Texture.WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(Texture.WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(Texture.WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(Texture.WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap1); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + + TerrainQuad terrain = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setLocked(false); // unlock it so we can edit the height + + terrain.setShadowMode(RenderQueue.ShadowMode.Receive); + rootNode.attachChild(terrain); + + } + + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + + @Override + public void update(float tpf) { + super.update(tpf); + System.out.println(getApplication().getCamera().getLocation()); + } + + }) + .setFramesToTakeScreenshotsOn(1) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestLightScattering.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestLightScattering.java new file mode 100644 index 0000000000..5353226f90 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/post/TestLightScattering.java @@ -0,0 +1,126 @@ +/* + * Copyright (c) 2025 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 org.jmonkeyengine.screenshottests.post; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.LightScatteringFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Geometry; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.util.SkyFactory; +import com.jme3.util.mikktspace.MikktspaceTangentGenerator; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * Screenshot test for the LightScattering filter. + * + *

                  This test creates a scene with a terrain model and a sky, with a light scattering + * (god rays) effect applied. The effect simulates light rays scattering through the atmosphere + * from a bright light source (like the sun). + * + * @author Richard Tingle (screenshot test adaptation) + */ +public class TestLightScattering extends ScreenshotTestBase { + + /** + * This test creates a scene with a light scattering effect. + */ + @Test + public void testLightScattering() { + screenshotTest(new BaseAppState() { + @Override + protected void initialize(Application app) { + SimpleApplication simpleApplication = (SimpleApplication) app; + Node rootNode = simpleApplication.getRootNode(); + + simpleApplication.getCamera().setLocation(new Vector3f(55.35316f, -0.27061665f, 27.092093f)); + simpleApplication.getCamera().setRotation(new Quaternion(0.010414706f, 0.9874893f, 0.13880467f, -0.07409228f)); + + Material mat = simpleApplication.getAssetManager().loadMaterial("Textures/Terrain/Rocky/Rocky.j3m"); + Spatial scene = simpleApplication.getAssetManager().loadModel("Models/Terrain/Terrain.mesh.xml"); + MikktspaceTangentGenerator.generate(((Geometry) ((Node) scene).getChild(0)).getMesh()); + scene.setMaterial(mat); + scene.setShadowMode(ShadowMode.CastAndReceive); + scene.setLocalScale(400); + scene.setLocalTranslation(0, -10, -120); + rootNode.attachChild(scene); + + rootNode.attachChild(SkyFactory.createSky(simpleApplication.getAssetManager(), + "Textures/Sky/Bright/FullskiesBlueClear03.dds", + SkyFactory.EnvMapType.CubeMap)); + + DirectionalLight sun = new DirectionalLight(); + Vector3f lightDir = new Vector3f(-0.12f, -0.3729129f, 0.74847335f); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(2)); + scene.addLight(sun); + + FilterPostProcessor fpp = new FilterPostProcessor(simpleApplication.getAssetManager()); + + Vector3f lightPos = lightDir.normalize().negate().multLocal(3000); + LightScatteringFilter filter = new LightScatteringFilter(lightPos); + + filter.setLightDensity(1.0f); + filter.setBlurStart(0.02f); + filter.setBlurWidth(0.9f); + filter.setLightPosition(lightPos); + + fpp.addFilter(filter); + simpleApplication.getViewPort().addProcessor(fpp); + } + + @Override + protected void cleanup(Application app) { + } + + @Override + protected void onEnable() { + } + + @Override + protected void onDisable() { + } + }) + .setFramesToTakeScreenshotsOn(1) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java new file mode 100644 index 0000000000..9069fb4464 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/scene/instancing/TestInstanceNodeWithPbr.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.scene.instancing; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.font.BitmapText; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.scene.Geometry; +import com.jme3.scene.instancing.InstancedNode; +import com.jme3.scene.shape.Box; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +/** + * This test specifically validates the corrected PBR rendering when combined + * with instancing, as addressed in issue #2435. + * + *

                  + * It creates an InstancedNode with a PBR-materialized Box to ensure the fix in + * PBRLighting.vert correctly handles world position calculations for instanced geometry. + *

                  + * + * @author Ryan McDonough - original test + * @author Richard Tingle (aka richtea) - screenshot test adaptation + */ +public class TestInstanceNodeWithPbr extends ScreenshotTestBase { + + @Test + public void testInstanceNodeWithPbr() { + screenshotTest( + new BaseAppState() { + private Geometry box; + private float pos = -5; + private float vel = 50; + private BitmapText bmp; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApp = (SimpleApplication) app; + + app.getCamera().setLocation(Vector3f.UNIT_XYZ.mult(12)); + app.getCamera().lookAt(Vector3f.ZERO, Vector3f.UNIT_Y); + + bmp = new BitmapText(app.getAssetManager().loadFont("Interface/Fonts/Default.fnt")); + bmp.setText(""); + bmp.setLocalTranslation(10, app.getContext().getSettings().getHeight() - 20, 0); + bmp.setColor(ColorRGBA.Red); + simpleApp.getGuiNode().attachChild(bmp); + + InstancedNode instancedNode = new InstancedNode("InstancedNode"); + simpleApp.getRootNode().attachChild(instancedNode); + + Box mesh = new Box(0.5f, 0.5f, 0.5f); + box = new Geometry("Box", mesh); + Material pbrMaterial = createPbrMaterial(app, ColorRGBA.Red); + box.setMaterial(pbrMaterial); + + instancedNode.attachChild(box); + instancedNode.instance(); + + DirectionalLight light = new DirectionalLight(); + light.setDirection(new Vector3f(-1, -2, -3).normalizeLocal()); + simpleApp.getRootNode().addLight(light); + } + + private Material createPbrMaterial(Application app, ColorRGBA color) { + Material mat = new Material(app.getAssetManager(), "Common/MatDefs/Light/PBRLighting.j3md"); + mat.setColor("BaseColor", color); + mat.setFloat("Roughness", 0.8f); + mat.setFloat("Metallic", 0.1f); + mat.setBoolean("UseInstancing", true); + return mat; + } + + @Override + public void update(float tpf) { + pos += tpf * vel; + box.setLocalTranslation(pos, 0f, 0f); + + bmp.setText(String.format(Locale.ENGLISH, "BoxPosition: (%.2f, %.1f, %.1f)", pos, 0f, 0f)); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() { } + } + ) + .setFramesToTakeScreenshotsOn(1, 10) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java new file mode 100644 index 0000000000..37f8063300 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrain.java @@ -0,0 +1,291 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.terrain; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.stream.Stream; + +/** + * This test uses 'PBRTerrain.j3md' to create a terrain Material for PBR. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + * @author yaRnMcDonuts (Original manual test) + * @author Richard Tingle (aka richtea) - screenshot test adaptation + */ +@SuppressWarnings("FieldCanBeLocal") +public class TestPBRTerrain extends ScreenshotTestBase { + + private static Stream testParameters() { + return Stream.of( + Arguments.of("FinalRender", 0), + Arguments.of("NormalMap", 1), + Arguments.of("RoughnessMap", 2), + Arguments.of("MetallicMap", 3), + Arguments.of("GeometryNormals", 8) + ); + } + + /** + * Test PBR terrain with different debug modes + * + * @param testName The name of the test (used for screenshot filename) + * @param debugMode The debug mode to use + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testParameters") + public void testPBRTerrain(String testName, int debugMode, TestInfo testInfo) { + + if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) { + throw new RuntimeException("Test preconditions not met"); + } + + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName; + + screenshotTest(new BaseAppState() { + private TerrainQuad terrain; + private Material matTerrain; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApp = (SimpleApplication) app; + AssetManager assetManager = app.getAssetManager(); + + setUpTerrain(simpleApp, assetManager); + setUpTerrainMaterial(assetManager); + setUpLights(simpleApp, assetManager); + setUpCamera(app); + + // Set debug mode + matTerrain.setInt("DebugValuesMode", debugMode); + } + + private void setUpTerrainMaterial(AssetManager assetManager) { + // PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/PBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + + // DIRT texture + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + dirt.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_0", dirt); + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0); + + // DARK ROCK texture + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + darkRock.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_1", darkRock); + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 0.92f); + matTerrain.setFloat("Metallic_1", 0.02f); + + // SNOW texture + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + snow.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_2", snow); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.55f); + matTerrain.setFloat("Metallic_2", 0.12f); + + // TILES texture + Texture tiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + tiles.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_3", tiles); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 0.87f); + matTerrain.setFloat("Metallic_3", 0.08f); + + // GRASS texture + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + grass.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_4", grass); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + // MARBLE texture + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + marble.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_5", marble); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 0.06f); + matTerrain.setFloat("Metallic_5", 0.8f); + + // Gravel texture + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + gravel.setWrap(WrapMode.Repeat); + matTerrain.setTexture("AlbedoMap_6", gravel); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 0.9f); + matTerrain.setFloat("Metallic_6", 0.07f); + + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + normalMapDirt.setWrap(WrapMode.Repeat); + + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + normalMapDarkRock.setWrap(WrapMode.Repeat); + + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + normalMapSnow.setWrap(WrapMode.Repeat); + + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + normalMapGravel.setWrap(WrapMode.Repeat); + + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + normalMapGrass.setWrap(WrapMode.Repeat); + + Texture normalMapTiles = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + normalMapTiles.setWrap(WrapMode.Repeat); + + matTerrain.setTexture("NormalMap_0", normalMapDirt); + matTerrain.setTexture("NormalMap_1", normalMapDarkRock); + matTerrain.setTexture("NormalMap_2", normalMapSnow); + matTerrain.setTexture("NormalMap_3", normalMapTiles); + matTerrain.setTexture("NormalMap_4", normalMapGrass); + matTerrain.setTexture("NormalMap_6", normalMapGravel); + + terrain.setMaterial(matTerrain); + } + + private void setUpTerrain(SimpleApplication simpleApp, AssetManager assetManager) { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getApplication().getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + simpleApp.getRootNode().attachChild(terrain); + } + + private void setUpLights(SimpleApplication simpleApp, AssetManager assetManager) { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + simpleApp.getRootNode().addLight(probe); + + DirectionalLight directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + simpleApp.getRootNode().addLight(directionalLight); + + AmbientLight ambientLight = new AmbientLight(); + ambientLight.setColor(ColorRGBA.White); + simpleApp.getRootNode().addLight(ambientLight); + } + + private void setUpCamera(Application app) { + app.getCamera().setLocation(new Vector3f(0, 10, -10)); + app.getCamera().lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() {} + + }).setBaseImageFileName(imageName) + .setFramesToTakeScreenshotsOn(5) + .run(); + } +} diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java new file mode 100644 index 0000000000..a1f5830896 --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/terrain/TestPBRTerrainAdvanced.java @@ -0,0 +1,368 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.terrain; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.asset.TextureKey; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.light.LightProbe; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Vector3f; +import com.jme3.shader.VarType; +import com.jme3.terrain.geomipmap.TerrainLodControl; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.geomipmap.lodcalc.DistanceLodCalculator; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Image; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture.MagFilter; +import com.jme3.texture.Texture.MinFilter; +import com.jme3.texture.TextureArray; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.TestInfo; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + + +/** + * This test uses 'AdvancedPBRTerrain.j3md' to create a terrain Material with + * more textures than 'PBRTerrain.j3md' can handle. + * + * Upon running the app, the user should see a mountainous, terrain-based + * landscape with some grassy areas, some snowy areas, and some tiled roads and + * gravel paths weaving between the valleys. Snow should be slightly + * shiny/reflective, and marble texture should be even shinier. If you would + * like to know what each texture is supposed to look like, you can find the + * textures used for this test case located in jme3-testdata. + + * The MetallicRoughness map stores: + *
                    + *
                  • AmbientOcclusion in the Red channel
                  • + *
                  • Roughness in the Green channel
                  • + *
                  • Metallic in the Blue channel
                  • + *
                  • EmissiveIntensity in the Alpha channel
                  • + *
                  + * + * The shaders are still subject to the GLSL max limit of 16 textures, however + * each TextureArray counts as a single texture, and each TextureArray can store + * multiple images. For more information on texture arrays see: + * https://www.khronos.org/opengl/wiki/Array_Texture + * + * Uses assets from CC0Textures.com, licensed under CC0 1.0 Universal. For more + * information on the textures this test case uses, view the license.txt file + * located in the jme3-testdata directory where these textures are located: + * jme3-testdata/src/main/resources/Textures/Terrain/PBR + * + * @author yaRnMcDonuts - original test + * @author Richard Tingle (aka richtea) - screenshot test adaptation + */ +@SuppressWarnings("FieldCanBeLocal") +public class TestPBRTerrainAdvanced extends ScreenshotTestBase { + + private static Stream testParameters() { + return Stream.of( + Arguments.of("FinalRender", 0), + Arguments.of("AmbientOcclusion", 4), + Arguments.of("Emissive", 5) + ); + } + + /** + * Test advanced PBR terrain with different debug modes + * + * @param testName The name of the test (used for screenshot filename) + * @param debugMode The debug mode to use + */ + @ParameterizedTest(name = "{0}") + @MethodSource("testParameters") + public void testPBRTerrainAdvanced(String testName, int debugMode, TestInfo testInfo) { + if(!testInfo.getTestClass().isPresent() || !testInfo.getTestMethod().isPresent()) { + throw new RuntimeException("Test preconditions not met"); + } + + String imageName = testInfo.getTestClass().get().getName() + "." + testInfo.getTestMethod().get().getName() + "_" + testName; + + screenshotTest(new BaseAppState() { + private TerrainQuad terrain; + private Material matTerrain; + + private final int terrainSize = 512; + private final int patchSize = 256; + private final float dirtScale = 24; + private final float darkRockScale = 24; + private final float snowScale = 64; + private final float tileRoadScale = 64; + private final float grassScale = 24; + private final float marbleScale = 64; + private final float gravelScale = 64; + + private final ColorRGBA tilesEmissiveColor = new ColorRGBA(0.12f, 0.02f, 0.23f, 0.85f); //dim magenta emission + private final ColorRGBA marbleEmissiveColor = new ColorRGBA(0.0f, 0.0f, 1.0f, 1.0f); //fully saturated blue emission + + @Override + protected void initialize(Application app) { + SimpleApplication simpleApp = (SimpleApplication) app; + AssetManager assetManager = app.getAssetManager(); + + setUpTerrain(simpleApp, assetManager); + setUpTerrainMaterial(assetManager); + setUpLights(simpleApp, assetManager); + setUpCamera(app); + + // Set debug mode + matTerrain.setInt("DebugValuesMode", debugMode); + } + + private void setUpTerrainMaterial(AssetManager assetManager) { + // advanced PBR terrain matdef + matTerrain = new Material(assetManager, "Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md"); + + matTerrain.setBoolean("useTriPlanarMapping", false); + + // ALPHA map (for splat textures) + matTerrain.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alpha1.png")); + matTerrain.setTexture("AlphaMap_1", assetManager.loadTexture("Textures/Terrain/splat/alpha2.png")); + + // load textures for texture arrays + // These MUST all have the same dimensions and format in order to be put into a texture array. + //ALBEDO MAPS + Texture dirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture darkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Color.png"); + Texture snow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Color.png"); + Texture tileRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Color.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Color.png"); + Texture marble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Color.png"); + Texture gravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Color.png"); + + // NORMAL MAPS + Texture normalMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_1K_Normal.png"); + Texture normalMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_1K_Normal.png"); + Texture normalMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_1K_Normal.png"); + Texture normalMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel015_1K_Normal.png"); + Texture normalMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_1K_Normal.png"); + Texture normalMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_1K_Normal.png"); + Texture normalMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_1K_Normal.png"); + + //PACKED METALLIC/ROUGHNESS / AMBIENT OCCLUSION / EMISSIVE INTENSITY MAPS + Texture metallicRoughnessAoEiMapDirt = assetManager.loadTexture("Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapDarkRock = assetManager.loadTexture("Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapSnow = assetManager.loadTexture("Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGravel = assetManager.loadTexture("Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapGrass = assetManager.loadTexture("Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapMarble = assetManager.loadTexture("Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png"); + Texture metallicRoughnessAoEiMapRoad = assetManager.loadTexture("Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png"); + + // put all images into lists to create texture arrays. + List albedoImages = new ArrayList<>(); + List normalMapImages = new ArrayList<>(); + List metallicRoughnessAoEiMapImages = new ArrayList<>(); + + albedoImages.add(dirt.getImage()); //0 + albedoImages.add(darkRock.getImage()); //1 + albedoImages.add(snow.getImage()); //2 + albedoImages.add(tileRoad.getImage()); //3 + albedoImages.add(grass.getImage()); //4 + albedoImages.add(marble.getImage()); //5 + albedoImages.add(gravel.getImage()); //6 + + normalMapImages.add(normalMapDirt.getImage()); //0 + normalMapImages.add(normalMapDarkRock.getImage()); //1 + normalMapImages.add(normalMapSnow.getImage()); //2 + normalMapImages.add(normalMapRoad.getImage()); //3 + normalMapImages.add(normalMapGrass.getImage()); //4 + normalMapImages.add(normalMapMarble.getImage()); //5 + normalMapImages.add(normalMapGravel.getImage()); //6 + + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDirt.getImage()); //0 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapDarkRock.getImage()); //1 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapSnow.getImage()); //2 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapRoad.getImage()); //3 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGrass.getImage()); //4 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapMarble.getImage()); //5 + metallicRoughnessAoEiMapImages.add(metallicRoughnessAoEiMapGravel.getImage()); //6 + + //initiate texture arrays + TextureArray albedoTextureArray = new TextureArray(albedoImages); + TextureArray normalParallaxTextureArray = new TextureArray(normalMapImages); // parallax is not used currently + TextureArray metallicRoughnessAoEiTextureArray = new TextureArray(metallicRoughnessAoEiMapImages); + + //apply wrapMode to the whole texture array, rather than each individual texture in the array + setWrapAndMipMaps(albedoTextureArray); + setWrapAndMipMaps(normalParallaxTextureArray); + setWrapAndMipMaps(metallicRoughnessAoEiTextureArray); + + //assign texture array to materials + matTerrain.setParam("AlbedoTextureArray", VarType.TextureArray, albedoTextureArray); + matTerrain.setParam("NormalParallaxTextureArray", VarType.TextureArray, normalParallaxTextureArray); + matTerrain.setParam("MetallicRoughnessAoEiTextureArray", VarType.TextureArray, metallicRoughnessAoEiTextureArray); + + //set up texture slots: + matTerrain.setInt("AlbedoMap_0", 0); // dirt is index 0 in the albedo image list + matTerrain.setFloat("AlbedoMap_0_scale", dirtScale); + matTerrain.setFloat("Roughness_0", 1); + matTerrain.setFloat("Metallic_0", 0.02f); + + matTerrain.setInt("AlbedoMap_1", 1); // darkRock is index 1 in the albedo image list + matTerrain.setFloat("AlbedoMap_1_scale", darkRockScale); + matTerrain.setFloat("Roughness_1", 1); + matTerrain.setFloat("Metallic_1", 0.04f); + + matTerrain.setInt("AlbedoMap_2", 2); + matTerrain.setFloat("AlbedoMap_2_scale", snowScale); + matTerrain.setFloat("Roughness_2", 0.72f); + matTerrain.setFloat("Metallic_2", 0.12f); + + matTerrain.setInt("AlbedoMap_3", 3); + matTerrain.setFloat("AlbedoMap_3_scale", tileRoadScale); + matTerrain.setFloat("Roughness_3", 1); + matTerrain.setFloat("Metallic_3", 0.04f); + + matTerrain.setInt("AlbedoMap_4", 4); + matTerrain.setFloat("AlbedoMap_4_scale", grassScale); + matTerrain.setFloat("Roughness_4", 1); + matTerrain.setFloat("Metallic_4", 0); + + matTerrain.setInt("AlbedoMap_5", 5); + matTerrain.setFloat("AlbedoMap_5_scale", marbleScale); + matTerrain.setFloat("Roughness_5", 1); + matTerrain.setFloat("Metallic_5", 0.2f); + + matTerrain.setInt("AlbedoMap_6", 6); + matTerrain.setFloat("AlbedoMap_6_scale", gravelScale); + matTerrain.setFloat("Roughness_6", 1); + matTerrain.setFloat("Metallic_6", 0.01f); + + // NORMAL MAPS + matTerrain.setInt("NormalMap_0", 0); + matTerrain.setInt("NormalMap_1", 1); + matTerrain.setInt("NormalMap_2", 2); + matTerrain.setInt("NormalMap_3", 3); + matTerrain.setInt("NormalMap_4", 4); + matTerrain.setInt("NormalMap_5", 5); + matTerrain.setInt("NormalMap_6", 6); + + //METALLIC/ROUGHNESS/AO/EI MAPS + matTerrain.setInt("MetallicRoughnessMap_0", 0); + matTerrain.setInt("MetallicRoughnessMap_1", 1); + matTerrain.setInt("MetallicRoughnessMap_2", 2); + matTerrain.setInt("MetallicRoughnessMap_3", 3); + matTerrain.setInt("MetallicRoughnessMap_4", 4); + matTerrain.setInt("MetallicRoughnessMap_5", 5); + matTerrain.setInt("MetallicRoughnessMap_6", 6); + + //EMISSIVE + matTerrain.setColor("EmissiveColor_5", marbleEmissiveColor); + matTerrain.setColor("EmissiveColor_3", tilesEmissiveColor); + + terrain.setMaterial(matTerrain); + } + + private void setWrapAndMipMaps(Texture texture) { + texture.setWrap(WrapMode.Repeat); + texture.setMinFilter(MinFilter.Trilinear); + texture.setMagFilter(MagFilter.Bilinear); + } + + private void setUpTerrain(SimpleApplication simpleApp, AssetManager assetManager) { + // HEIGHTMAP image (for the terrain heightmap) + TextureKey hmKey = new TextureKey("Textures/Terrain/splat/mountains512.png", false); + Texture heightMapImage = assetManager.loadTexture(hmKey); + + // CREATE HEIGHTMAP + AbstractHeightMap heightmap; + try { + heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.3f); + heightmap.load(); + heightmap.smooth(0.9f, 1); + } catch (Exception e) { + throw new RuntimeException(e); + } + + terrain = new TerrainQuad("terrain", patchSize + 1, terrainSize + 1, heightmap.getHeightMap()); + TerrainLodControl control = new TerrainLodControl(terrain, getApplication().getCamera()); + control.setLodCalculator(new DistanceLodCalculator(patchSize + 1, 2.7f)); // patch size, and a multiplier + terrain.addControl(control); + terrain.setMaterial(matTerrain); + terrain.setLocalTranslation(0, -100, 0); + terrain.setLocalScale(1f, 1f, 1f); + simpleApp.getRootNode().attachChild(terrain); + } + + private void setUpLights(SimpleApplication simpleApp, AssetManager assetManager) { + LightProbe probe = (LightProbe) assetManager.loadAsset("Scenes/LightProbes/quarry_Probe.j3o"); + + probe.setAreaType(LightProbe.AreaType.Spherical); + probe.getArea().setRadius(2000); + probe.getArea().setCenter(new Vector3f(0, 0, 0)); + simpleApp.getRootNode().addLight(probe); + + DirectionalLight directionalLight = new DirectionalLight(); + directionalLight.setDirection((new Vector3f(-0.3f, -0.5f, -0.3f)).normalize()); + directionalLight.setColor(ColorRGBA.White); + simpleApp.getRootNode().addLight(directionalLight); + + AmbientLight ambientLight = new AmbientLight(); + ambientLight.setColor(ColorRGBA.White); + simpleApp.getRootNode().addLight(ambientLight); + } + + private void setUpCamera(Application app) { + app.getCamera().setLocation(new Vector3f(0, 10, -10)); + app.getCamera().lookAtDirection(new Vector3f(0, -1.5f, -1).normalizeLocal(), Vector3f.UNIT_Y); + } + + @Override + protected void cleanup(Application app) {} + + @Override + protected void onEnable() {} + + @Override + protected void onDisable() {} + + }).setBaseImageFileName(imageName) + .setFramesToTakeScreenshotsOn(5) + .run(); + } +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/water/TestPostWater.java b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/water/TestPostWater.java new file mode 100644 index 0000000000..7868ecd71e --- /dev/null +++ b/jme3-screenshot-tests/src/test/java/org/jmonkeyengine/screenshottests/water/TestPostWater.java @@ -0,0 +1,212 @@ +/* + * Copyright (c) 2024 jMonkeyEngine + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'jMonkeyEngine' nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.jmonkeyengine.screenshottests.water; + +import com.jme3.app.Application; +import com.jme3.app.SimpleApplication; +import com.jme3.app.state.BaseAppState; +import com.jme3.asset.AssetManager; +import com.jme3.light.AmbientLight; +import com.jme3.light.DirectionalLight; +import com.jme3.material.Material; +import com.jme3.math.ColorRGBA; +import com.jme3.math.Quaternion; +import com.jme3.math.Vector3f; +import com.jme3.post.FilterPostProcessor; +import com.jme3.post.filters.BloomFilter; +import com.jme3.post.filters.DepthOfFieldFilter; +import com.jme3.post.filters.FXAAFilter; +import com.jme3.post.filters.LightScatteringFilter; +import com.jme3.renderer.queue.RenderQueue.ShadowMode; +import com.jme3.scene.Node; +import com.jme3.scene.Spatial; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import com.jme3.texture.Texture.WrapMode; +import com.jme3.texture.Texture2D; +import com.jme3.util.SkyFactory; +import com.jme3.util.SkyFactory.EnvMapType; +import com.jme3.water.WaterFilter; +import org.jmonkeyengine.screenshottests.testframework.ScreenshotTestBase; +import org.junit.jupiter.api.Test; + +/** + * @author Richard Tingle (aka richtea) + */ +public class TestPostWater extends ScreenshotTestBase{ + + /** + * This test creates a scene with a terrain and post process water filter. + */ + @Test + public void testPostWater(){ + screenshotTest(new BaseAppState(){ + @Override + protected void initialize(Application app){ + Vector3f lightDir = new Vector3f(-4.9236743f, -1.27054665f, 5.896916f); + SimpleApplication simpleApplication = ((SimpleApplication)app); + Node rootNode = simpleApplication.getRootNode(); + AssetManager assetManager = simpleApplication.getAssetManager(); + + Node mainScene = new Node("Main Scene"); + rootNode.attachChild(mainScene); + + createTerrain(mainScene, assetManager); + DirectionalLight sun = new DirectionalLight(); + sun.setDirection(lightDir); + sun.setColor(ColorRGBA.White.clone().multLocal(1f)); + mainScene.addLight(sun); + + AmbientLight al = new AmbientLight(); + al.setColor(new ColorRGBA(0.1f, 0.1f, 0.1f, 1.0f)); + mainScene.addLight(al); + + + simpleApplication.getCamera().setLocation(new Vector3f(-370.31592f, 182.04016f, 196.81192f)); + simpleApplication.getCamera().setRotation(new Quaternion(0.10058216f, 0.51807004f, -0.061508257f, 0.8471738f)); + + Spatial sky = SkyFactory.createSky(assetManager, + "Scenes/Beach/FullskiesSunset0068.dds", EnvMapType.CubeMap); + sky.setLocalScale(350); + + mainScene.attachChild(sky); + simpleApplication.getCamera().setFrustumFar(4000); + + //Water Filter + WaterFilter water = new WaterFilter(rootNode, lightDir); + water.setWaterColor(new ColorRGBA().setAsSrgb(0.0078f, 0.3176f, 0.5f, 1.0f)); + water.setDeepWaterColor(new ColorRGBA().setAsSrgb(0.0039f, 0.00196f, 0.145f, 1.0f)); + water.setUnderWaterFogDistance(80); + water.setWaterTransparency(0.12f); + water.setFoamIntensity(0.4f); + water.setFoamHardness(0.3f); + water.setFoamExistence(new Vector3f(0.8f, 8f, 1f)); + water.setReflectionDisplace(50); + water.setRefractionConstant(0.25f); + water.setColorExtinction(new Vector3f(30, 50, 70)); + water.setCausticsIntensity(0.4f); + water.setWaveScale(0.003f); + water.setMaxAmplitude(2f); + water.setFoamTexture((Texture2D) assetManager.loadTexture("Common/MatDefs/Water/Textures/foam2.jpg")); + water.setRefractionStrength(0.2f); + //0.8f; + float initialWaterHeight = 90f; + water.setWaterHeight(initialWaterHeight); + + //Bloom Filter + BloomFilter bloom = new BloomFilter(); + bloom.setExposurePower(55); + bloom.setBloomIntensity(1.0f); + + //Light Scattering Filter + LightScatteringFilter lsf = new LightScatteringFilter(lightDir.mult(-300)); + lsf.setLightDensity(0.5f); + + //Depth of field Filter + DepthOfFieldFilter dof = new DepthOfFieldFilter(); + dof.setFocusDistance(0); + dof.setFocusRange(100); + + FilterPostProcessor fpp = new FilterPostProcessor(assetManager); + + fpp.addFilter(water); + fpp.addFilter(bloom); + fpp.addFilter(dof); + fpp.addFilter(lsf); + fpp.addFilter(new FXAAFilter()); + + int numSamples = simpleApplication.getContext().getSettings().getSamples(); + if (numSamples > 0) { + fpp.setNumSamples(numSamples); + } + simpleApplication.getViewPort().addProcessor(fpp); + } + + @Override protected void cleanup(Application app){} + + @Override protected void onEnable(){} + + @Override protected void onDisable(){} + + @Override + public void update(float tpf){ + super.update(tpf); + } + + private void createTerrain(Node rootNode, AssetManager assetManager) { + Material matRock = new Material(assetManager, + "Common/MatDefs/Terrain/TerrainLighting.j3md"); + matRock.setBoolean("useTriPlanarMapping", false); + matRock.setBoolean("WardIso", true); + matRock.setTexture("AlphaMap", assetManager.loadTexture("Textures/Terrain/splat/alphamap.png")); + Texture heightMapImage = assetManager.loadTexture("Textures/Terrain/splat/mountains512.png"); + Texture grass = assetManager.loadTexture("Textures/Terrain/splat/grass.jpg"); + grass.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap", grass); + matRock.setFloat("DiffuseMap_0_scale", 64); + Texture dirt = assetManager.loadTexture("Textures/Terrain/splat/dirt.jpg"); + dirt.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_1", dirt); + matRock.setFloat("DiffuseMap_1_scale", 16); + Texture rock = assetManager.loadTexture("Textures/Terrain/splat/road.jpg"); + rock.setWrap(WrapMode.Repeat); + matRock.setTexture("DiffuseMap_2", rock); + matRock.setFloat("DiffuseMap_2_scale", 128); + Texture normalMap0 = assetManager.loadTexture("Textures/Terrain/splat/grass_normal.jpg"); + normalMap0.setWrap(WrapMode.Repeat); + Texture normalMap1 = assetManager.loadTexture("Textures/Terrain/splat/dirt_normal.png"); + normalMap1.setWrap(WrapMode.Repeat); + Texture normalMap2 = assetManager.loadTexture("Textures/Terrain/splat/road_normal.png"); + normalMap2.setWrap(WrapMode.Repeat); + matRock.setTexture("NormalMap", normalMap0); + matRock.setTexture("NormalMap_1", normalMap1); + matRock.setTexture("NormalMap_2", normalMap2); + + AbstractHeightMap heightmap = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + heightmap.load(); + + TerrainQuad terrain + = new TerrainQuad("terrain", 65, 513, heightmap.getHeightMap()); + terrain.setMaterial(matRock); + terrain.setLocalScale(new Vector3f(5, 5, 5)); + terrain.setLocalTranslation(new Vector3f(0, -30, 0)); + terrain.setLocked(false); // unlock it so we can edit the height + + terrain.setShadowMode(ShadowMode.Receive); + rootNode.attachChild(terrain); + } + }).run(); + } + +} \ No newline at end of file diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png new file mode 100644 index 0000000000..f4cbc42002 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestIssue2076.testIssue2076_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png new file mode 100644 index 0000000000..028530e9c8 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f10.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png new file mode 100644 index 0000000000..cca8ae963f Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.animation.TestMotionPath.testMotionPath_f60.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f15.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f15.png new file mode 100644 index 0000000000..b22da40fc7 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f15.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f2.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f2.png new file mode 100644 index 0000000000..b7f4833cbc Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestExplosionEffect.testExplosionEffect_f2.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_localSpace_f45.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_localSpace_f45.png new file mode 100644 index 0000000000..1d1c733b3b Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_localSpace_f45.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_worldSpace_f45.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_worldSpace_f45.png new file mode 100644 index 0000000000..cf72a001e5 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.effects.TestIssue1773.testIssue1773_worldSpace_f45.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f1.png new file mode 100644 index 0000000000..61bd29c0b4 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f5.png new file mode 100644 index 0000000000..603680a81d Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.export.TestOgreConvert.testOgreConvert_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.gui.TestBitmapText3D.testBitmapText3D_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.gui.TestBitmapText3D.testBitmapText3D_f1.png new file mode 100644 index 0000000000..40ea199165 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.gui.TestBitmapText3D.testBitmapText3D_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png new file mode 100644 index 0000000000..1589f38f1e Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_DefaultDirectionalLight_f12.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png new file mode 100644 index 0000000000..ab61568039 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_HighRoughness_f12.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png new file mode 100644 index 0000000000..564abc0e5d Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_LowRoughness_f12.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png new file mode 100644 index 0000000000..4c764f9223 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRLighting.testPBRLighting_UpdatedDirectionalLight_f12.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png new file mode 100644 index 0000000000..ac5bb874f5 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithRealtimeBaking_f10.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png new file mode 100644 index 0000000000..ac5bb874f5 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.light.pbr.TestPBRSimple.testPBRSimple_WithoutRealtimeBaking_f10.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png new file mode 100644 index 0000000000..4608681de3 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f10.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png new file mode 100644 index 0000000000..11ac17390f Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.material.TestSimpleBumps.testSimpleBumps_f60.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromAbove_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromAbove_f1.png new file mode 100644 index 0000000000..4a3e1f2af0 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromAbove_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromFront_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromFront_f1.png new file mode 100644 index 0000000000..a798a16d34 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromFront_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromRight_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromRight_f1.png new file mode 100644 index 0000000000..8be63bb6fa Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.model.shape.TestBillboard.testBillboard_fromRight_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestCartoonEdge.testCartoonEdge_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestCartoonEdge.testCartoonEdge_f1.png new file mode 100644 index 0000000000..453219b949 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestCartoonEdge.testCartoonEdge_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestFog.testFog_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestFog.testFog_f1.png new file mode 100644 index 0000000000..da77df67cc Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestFog.testFog_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestLightScattering.testLightScattering_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestLightScattering.testLightScattering_f1.png new file mode 100644 index 0000000000..bf84144b5a Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.post.TestLightScattering.testLightScattering_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png new file mode 100644 index 0000000000..c761934d2d Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f1.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png new file mode 100644 index 0000000000..2ddf12bb33 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.scene.instancing.TestInstanceNodeWithPbr.testInstanceNodeWithPbr_f10.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png new file mode 100644 index 0000000000..aa8c62dcd4 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_FinalRender_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png new file mode 100644 index 0000000000..215274f23d Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_GeometryNormals_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png new file mode 100644 index 0000000000..dbbe6cf3a5 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_MetallicMap_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png new file mode 100644 index 0000000000..791339d4c3 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_NormalMap_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png new file mode 100644 index 0000000000..167ec2eeb7 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrain.testPBRTerrain_RoughnessMap_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png new file mode 100644 index 0000000000..5c515e1e26 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_AmbientOcclusion_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png new file mode 100644 index 0000000000..f1304956c3 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_Emissive_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png new file mode 100644 index 0000000000..aa8c62dcd4 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.terrain.TestPBRTerrainAdvanced.testPBRTerrainAdvanced_FinalRender_f5.png differ diff --git a/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.water.TestPostWater.testPostWater_f1.png b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.water.TestPostWater.testPostWater_f1.png new file mode 100644 index 0000000000..eaeb6f7c02 Binary files /dev/null and b/jme3-screenshot-tests/src/test/resources/org.jmonkeyengine.screenshottests.water.TestPostWater.testPostWater_f1.png differ diff --git a/jme3-terrain/build.gradle b/jme3-terrain/build.gradle index a782045c6a..d96200a187 100644 --- a/jme3-terrain/build.gradle +++ b/jme3-terrain/build.gradle @@ -1,11 +1,14 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' +dependencies { + api project(':jme3-core') + testImplementation project(':jme3-core') + testImplementation project(':jme3-core').sourceSets.test.output + testRuntimeOnly project(':jme3-desktop') + testRuntimeOnly project(':jme3-testdata') } -dependencies { - compile project(':jme3-core') - testCompile project(':jme3-core') - testCompile project(':jme3-desktop') - testCompile project(':jme3-core').sourceSets.test.output - testCompile project(':jme3-testdata') +javadoc { + // Disable doclint for JDK8+. + if (JavaVersion.current().isJava8Compatible()){ + options.addStringOption('Xdoclint:none', '-quiet') + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java index a6774400ab..4a68a9cc18 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/GeoMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -67,7 +67,7 @@ public float[] getHeightArray(){ /** * @return The maximum possible value that getValue() can - * return. Mostly depends on the source data format (byte, short, int, etc). + * return. Mostly depends on the source data format (byte, short, int, etcetera). */ public int getMaximumValue(){ return maxval; @@ -239,9 +239,9 @@ public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean c int i = 0; for (int z = 0; z < height; z++){ for (int x = 0; x < width; x++){ - store.put( (float)x*scale.x + offset.x ); - store.put( (float)hdata[i++]*scale.y ); - store.put( (float)z*scale.z + offset.z ); + store.put( x*scale.x + offset.x ); + store.put( hdata[i++]*scale.y ); + store.put( z*scale.z + offset.z ); } } @@ -249,8 +249,8 @@ public FloatBuffer writeVertexArray(FloatBuffer store, Vector3f scale, boolean c } public Vector2f getUV(int x, int y, Vector2f store){ - store.set( (float)x / (float)getWidth(), - (float)y / (float)getHeight() ); + store.set( x / (float)getWidth(), + y / (float)getHeight() ); return store; } @@ -323,6 +323,7 @@ public Mesh createMesh(Vector3f scale, Vector2f tcScale, boolean center){ return m; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(hdata, "hdataarray", null); @@ -331,6 +332,7 @@ public void write(JmeExporter ex) throws IOException { oc.write(maxval, "maxval", 0); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); hdata = ic.readFloatArray("hdataarray", null); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java b/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java index fefba99bcc..5b3256f6f3 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/Terrain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -37,7 +37,7 @@ import java.util.List; /** - * Terrain can be one or many meshes comprising of a, probably large, piece of land. + * Terrain can be one or many meshes comprising a (probably large) piece of land. * Terrain is Y-up in the grid axis, meaning gravity acts in the -Y direction. * Level of Detail (LOD) is supported and expected as terrains can get very large. LOD can * also be disabled if you so desire, however some terrain implementations can choose to ignore @@ -49,7 +49,7 @@ public interface Terrain { /** - * Get the real-world height of the terrain at the specified X-Z coorindate. + * Get the real-world height of the terrain at the specified X-Z coordinate. * @param xz the X-Z world coordinate * @return the height at the given point */ @@ -166,7 +166,7 @@ public interface Terrain { /** * Used for painting to get the number of vertices along the edge of the * terrain. - * This is an un-scaled size, and should represent the vertex count (ie. the + * This is an un-scaled size, and should represent the vertex count (i.e. the * texture coord count) along an edge of a square terrain. * * In the standard TerrainQuad default implementation, this will return @@ -174,7 +174,7 @@ public interface Terrain { */ public int getTerrainSize(); - /** + /* * Get the scale of the texture coordinates. Normally if the texture is * laid on the terrain and not scaled so that the texture does not repeat, * then each texture coordinate (on a vertex) will be 1/(terrain size). diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/executor/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/executor/package-info.java new file mode 100644 index 0000000000..12f9cd63cd --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/executor/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * execute background tasks connected with terrain + */ +package com.jme3.terrain.executor; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java index 0f18af88ef..2dae19b458 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/LODGeomap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -57,7 +57,7 @@ * mesh, minus one outer edge around it. Then it builds the edges in counter-clockwise order, * starting at the bottom right and working up, then left across the top, then down across the * left, then right across the bottom. - * It needs to know what its neighbour's LOD's are so it can stitch the edges. + * It needs to know what its neighbour's LODs are, so it can stitch the edges. * It creates degenerate polygons in order to keep the winding order of the polygons and to move * the strip to a new position while still maintaining the continuity of the overall mesh. These * degenerates are removed quickly by the video card. @@ -141,8 +141,8 @@ public Vector2f getUV(int x, int y, Vector2f store, Vector2f offset, float offse float offsetX = offset.x + (offsetAmount * 1.0f); float offsetY = -offset.y + (offsetAmount * 1.0f);//note the -, we flip the tex coords - store.set((((float) x) + offsetX) / (float) (totalSize - 1), // calculates percentage of texture here - (((float) y) + offsetY) / (float) (totalSize - 1)); + store.set((x + offsetX) / (totalSize - 1), // calculates percentage of texture here + (y + offsetY) / (totalSize - 1)); return store; } @@ -158,17 +158,14 @@ public Vector2f getUV(int x, int y, Vector2f store, Vector2f offset, float offse * @return the LOD-ified index buffer */ public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean topLod, boolean leftLod, boolean bottomLod, int totalSize) { - - + int numVertices = getWidth() * getHeight(); int numIndexes = calculateNumIndexesLodDiff(lod); - - IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes); + IndexBuffer ib = IndexBuffer.createIndexBuffer(numVertices, numIndexes); VerboseBuffer buffer = new VerboseBuffer(ib); - // generate center squares minus the edges //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")"); - //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); + //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row int rowIdx = r * getWidth(); int nextRowIdx = (r + 1 * lod) * getWidth(); @@ -198,7 +195,7 @@ public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean top int br = getWidth() * (getWidth() - lod) - 1 - lod; buffer.put(br); // bottom right -1 int corner = getWidth() * getWidth() - 1; - buffer.put(corner); // bottom right corner + buffer.put(corner); // bottom right corner if (rightLod) { // if lower LOD for (int row = getWidth() - lod; row >= 1 + lod; row -= 2 * lod) { int idx = (row) * getWidth() - 1 - lod; @@ -232,7 +229,7 @@ public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean top //System.out.println("\ntop:"); - // top (the order gets reversed here so the diagonals line up) + // top (the order gets reversed here so the diagonals line up) if (topLod) { // if lower LOD if (rightLod) { buffer.put(getWidth() - 1); @@ -360,16 +357,14 @@ public IndexBuffer writeIndexArrayLodDiff(int lod, boolean rightLod, boolean top } public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int leftLod, int bottomLod, int totalSize) { - + int numVertices = getWidth() * getHeight(); int numIndexes = calculateNumIndexesLodDiff(lod); - - IndexBuffer ib = IndexBuffer.createIndexBuffer(numIndexes, numIndexes); + IndexBuffer ib = IndexBuffer.createIndexBuffer(numVertices, numIndexes); VerboseBuffer buffer = new VerboseBuffer(ib); - // generate center squares minus the edges //System.out.println("for (x="+lod+"; x<"+(getWidth()-(2*lod))+"; x+="+lod+")"); - //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); + //System.out.println(" for (z="+lod+"; z<"+(getWidth()-(1*lod))+"; z+="+lod+")"); for (int r = lod; r < getWidth() - (2 * lod); r += lod) { // row int rowIdx = r * getWidth(); int nextRowIdx = (r + 1 * lod) * getWidth(); @@ -399,7 +394,7 @@ public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, int br = getWidth() * (getWidth() - lod) - 1 - lod; buffer.put(br); // bottom right -1 int corner = getWidth() * getWidth() - 1; - buffer.put(corner); // bottom right corner + buffer.put(corner); // bottom right corner if (rightLod > lod) { // if lower LOD int idx = corner; int it = (getWidth() - 1) / rightLod; // iterations @@ -441,7 +436,7 @@ public IndexBuffer writeIndexArrayLodVariable(int lod, int rightLod, int topLod, //System.out.println("\ntop:"); - // top (the order gets reversed here so the diagonals line up) + // top (the order gets reversed here so the diagonals line up) if (topLod > lod) { // if lower LOD if (rightLod > lod) { // need to flip winding order @@ -613,7 +608,7 @@ private int calculateNumIndexesLodDiff(int lod) { //System.out.println("side: "+side); int num = side * side * 2; //System.out.println("num: "+num); - num -= 2 * side; // remove one first row and one last row (they are only hit once each) + num -= 2 * side; // remove one first row and one last row (they are only hit once each) //System.out.println("num2: "+num); // now get the degenerate indexes that exist between strip rows int degenerates = 2 * (side - (2)); // every row except the first and last @@ -632,7 +627,7 @@ private int calculateNumIndexesLodDiff(int lod) { public FloatBuffer[] writeTangentArray(FloatBuffer normalBuffer, FloatBuffer tangentStore, FloatBuffer binormalStore, FloatBuffer textureBuffer, Vector3f scale) { if (!isLoaded()) { - throw new NullPointerException(); + throw new IllegalStateException("The Geomap data is not loaded."); } if (tangentStore != null) { @@ -774,7 +769,7 @@ public static Vector3f calculateTangent(Vector3f[] v, Vector2f[] t, Vector3f tan @Override public FloatBuffer writeNormalArray(FloatBuffer store, Vector3f scale) { if (!isLoaded()) { - throw new NullPointerException(); + throw new IllegalStateException("The Geomap data is not loaded."); } if (store != null) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java index fe754242ef..f7d5e018a0 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGrid.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ /** *

                  - * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles.

                  + * TerrainGrid itself is an actual TerrainQuad. Its four children are the visible four tiles. *

                  * The grid is indexed by cells. Each cell has an integer XZ coordinate originating at 0,0. * TerrainGrid will piggyback on the TerrainLodControl so it can use the camera for its @@ -92,7 +92,7 @@ * an empty (non-loaded) area, it will trigger the system to load in the next tiles. *

                  * The tile loading is done on a background thread, and once the tile is loaded, then it is - * attached to the qrid quad tree, back on the OGL thread. It will grab the terrain quad from + * attached to the grid quad tree, back on the OGL thread. It will grab the terrain quad from * the LRU cache if it exists. If it does not exist, it will load in the new TerrainQuad tile. *

                  * The loading of new tiles triggers events for any TerrainGridListeners. The events are: @@ -114,10 +114,10 @@ public class TerrainGrid extends TerrainQuad { protected int quadSize; private TerrainGridTileLoader gridTileLoader; protected Vector3f[] quadIndex; - protected Set listeners = new HashSet(); + protected Set listeners = new HashSet<>(); protected Material material; //cache needs to be 1 row (4 cells) larger than what we care is cached - protected LRUCache cache = new LRUCache(20); + protected LRUCache cache = new LRUCache<>(20); protected int cellsLoaded = 0; protected int[] gridOffset; protected boolean runOnce = false; @@ -140,6 +140,7 @@ public UpdateQuadCache(Vector3f location) { * attachQuadAt() method. It also resets any cached values in TerrainQuad (such as * neighbours). */ + @Override public void run() { for (int i = 0; i < 4; i++) { for (int j = 0; j < 4; j++) { @@ -162,8 +163,9 @@ public void run() { if (isCenter(quadIdx)) { // if it should be attached as a child right now, attach it - getControl(UpdateControl.class).enqueue(new Callable() { + getControl(UpdateControl.class).enqueue(new Callable() { // back on the OpenGL thread: + @Override public Object call() throws Exception { if (newQuad.getParent() != null) { attachQuadAt(newQuad, quadrant, quadCell, true); @@ -175,7 +177,8 @@ public Object call() throws Exception { } }); } else { - getControl(UpdateControl.class).enqueue(new Callable() { + getControl(UpdateControl.class).enqueue(new Callable() { + @Override public Object call() throws Exception { removeQuad(newQuad); return null; @@ -185,8 +188,9 @@ public Object call() throws Exception { } } - getControl(UpdateControl.class).enqueue(new Callable() { + getControl(UpdateControl.class).enqueue(new Callable() { // back on the OpenGL thread: + @Override public Object call() throws Exception { for (Spatial s : getChildren()) { if (s instanceof TerrainQuad) { @@ -274,7 +278,7 @@ private void initData() { /** * Get the location in cell-coordinates of the specified location. - * Cell coordinates are integer corrdinates, usually with y=0, each + * Cell coordinates are integer coordinates, usually with y=0, each * representing a cell in the world. * For example, moving right in the +X direction: * (0,0,0) (1,0,0) (2,0,0), (3,0,0) @@ -345,7 +349,7 @@ protected void removeQuad(TerrainQuad q) { } q.setQuadrant((short)0); this.detachChild(q); - cellsLoaded++; // For gridoffset calc., maybe the run() method is a better location for this. + cellsLoaded++; // For gridOffset calculation, maybe the run() method is a better location for this. } } @@ -489,6 +493,7 @@ public Material getMaterial(Vector3f worldLocation) { */ protected ExecutorService createExecutorService() { final ThreadFactory threadFactory = new ThreadFactory() { + @Override public Thread newThread(Runnable r) { Thread th = new Thread(r); th.setName("jME TerrainGrid Thread"); @@ -500,6 +505,7 @@ public Thread newThread(Runnable r) { 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), threadFactory) { + @Override protected void afterExecute(Runnable r, Throwable t) { super.afterExecute(r, t); if (t == null && r instanceof Future) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java index 20a0889011..63602c745b 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainGridLodControl.java @@ -58,8 +58,8 @@ protected void updateLOD(SafeArrayList locations, LodCalculator lodCal // 1: every camera has an associated grid, then the location is not enough to identify which camera location has changed // 2: grids are associated with locations, and no incremental update is done, we load new grids for new locations, and unload those that are not needed anymore Vector3f cam = locations.isEmpty() ? Vector3f.ZERO.clone() : locations.get(0); - Vector3f camCell = terrainGrid.getCamCell(cam); // get the grid index value of where the camera is (ie. 2,1) - if (terrainGrid.cellsLoaded > 1) { // Check if cells are updated before updating gridoffset. + Vector3f camCell = terrainGrid.getCamCell(cam); // get the grid index where the camera is (i.e. 2,1) + if (terrainGrid.cellsLoaded > 1) { // Check if cells are updated before updating gridOffset. terrainGrid.gridOffset[0] = Math.round(camCell.x * (terrainGrid.size / 2)); terrainGrid.gridOffset[1] = Math.round(camCell.z * (terrainGrid.size / 2)); terrainGrid.cellsLoaded = 0; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java index 7bab7f2072..94827bd4ed 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainLodControl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -157,14 +157,14 @@ public TerrainLodControl(final Terrain terrain, final List cameras) { } /** - * @param useRenderCamera true if need to use a camera from render view port. + * @param useRenderCamera true to use camera from the render viewport */ public void setUseRenderCamera(final boolean useRenderCamera) { this.useRenderCamera = useRenderCamera; } /** - * @return true if need to use a camera from render view port. + * @return true to use camera from the render viewport */ public boolean isUseRenderCamera() { return useRenderCamera; @@ -236,7 +236,7 @@ public void detachAndCleanUpControl() { getSpatial().removeControl(this); } - // do all of the LOD calculations + // Do all the LOD calculations. protected void updateLOD(final LodCalculator lodCalculator) { if (getSpatial() == null || camera == null) { @@ -270,7 +270,7 @@ protected void updateLOD(final LodCalculator lodCalculator) { indexer = executorService.submit(createLodUpdateTask(singletonList(currentLocation), lodCalculator)); } - // do all of the LOD calculations + // Do all the LOD calculations. protected void updateLOD(final SafeArrayList locations, final LodCalculator lodCalculator) { if (getSpatial() == null || locations.isEmpty()) { @@ -462,6 +462,7 @@ protected UpdateLOD(final List camLocations, final LodCalculator lodCa this.lodCalculator = lodCalculator; } + @Override public HashMap call() throws Exception { TerrainQuad terrainQuad = (TerrainQuad) getSpatial(); 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 36c44ea462..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-2019 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -54,10 +54,10 @@ import java.io.IOException; import java.nio.Buffer; import java.nio.FloatBuffer; -import java.nio.IntBuffer; -import java.nio.ShortBuffer; import java.util.HashMap; import java.util.List; +import java.util.logging.Logger; +import java.util.logging.Level; /** @@ -71,7 +71,7 @@ * LOD. If this doesn't happen, you will see gaps. * * The LOD value is most detailed at zero. It gets less detailed the higher the LOD value until you reach maxLod, which - * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However there is a -1 to that + * is a mathematical limit on the number of times the 'size' of the patch can be divided by two. However, there is a -1 to that * for now until I add in a custom index buffer calculation for that max level, the current algorithm does not go that far. * * You can supply a LodThresholdCalculator for use in determining when the LOD should change. Its API will no doubt change @@ -81,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 @@ -135,15 +137,15 @@ public TerrainPatch(String name, int size) { * TriMesh object for rendering. * * @param name - * the name of the terrain patch. + * the name of the terrain patch. * @param size - * the size of the heightmap. + * the size of the heightmap. * @param stepScale - * the scale for the axes. + * the scale for the axes. * @param heightMap - * the height data. + * the height data. * @param origin - * the origin offset of the patch. + * the origin offset of the patch. */ public TerrainPatch(String name, int size, Vector3f stepScale, float[] heightMap, Vector3f origin) { @@ -153,25 +155,25 @@ public TerrainPatch(String name, int size, Vector3f stepScale, /** * Constructor instantiates a new TerrainPatch object. The * parameters and heightmap data are then processed to generate a - * TriMesh object for renderering. + * TriMesh object for rendering. * * @param name - * the name of the terrain patch. + * the name of the terrain patch. * @param size - * the size of the patch. + * the size of the patch. * @param stepScale - * the scale for the axes. + * the scale for the axes. * @param heightMap - * the height data. + * the height data. * @param origin - * the origin offset of the patch. + * the origin offset of the patch. * @param totalSize - * the total size of the terrain. (Higher if the patch is part of - * a TerrainQuad tree. + * the total size of the terrain. (Higher if the patch is part of + * a TerrainQuad tree.) * @param offset - * the offset for texture coordinates. + * the offset for texture coordinates. * @param offsetAmount - * the total offset amount. Used for texture coordinates. + * the total offset amount. Used for texture coordinates. */ public TerrainPatch(String name, int size, Vector3f stepScale, float[] heightMap, Vector3f origin, int totalSize, @@ -200,11 +202,7 @@ public void generateLodEntropies() { for (int i = 0; i <= getMaxLod(); i++){ int curLod = (int) Math.pow(2, i); IndexBuffer idxB = geomap.writeIndexArrayLodDiff(curLod, false, false, false, false, totalSize); - Buffer ib; - if (idxB.getBuffer() instanceof IntBuffer) - ib = (IntBuffer)idxB.getBuffer(); - else - ib = (ShortBuffer)idxB.getBuffer(); + Buffer ib = idxB.getBuffer(); entropies[i] = EntropyComputeUtil.computeLodEntropy(mesh, ib); } @@ -253,11 +251,7 @@ protected void reIndexGeometry(HashMap updated, bool else idxB = geomap.writeIndexArrayLodDiff(pow, right, top, left, bottom, totalSize); - Buffer b; - if (idxB.getBuffer() instanceof IntBuffer) - b = (IntBuffer)idxB.getBuffer(); - else - b = (ShortBuffer)idxB.getBuffer(); + Buffer b = idxB.getBuffer(); utp.setNewIndexBuffer(b); } @@ -329,7 +323,7 @@ protected void setHeight(List locationHeights, boolean overrideH } /** - * recalculate all of the normal vectors in this terrain patch + * Recalculates all normal vectors in this terrain patch. */ protected void updateNormals() { FloatBuffer newNormalBuffer = geomap.writeNormalArray(null, getWorldScale()); @@ -360,7 +354,7 @@ private void setInBuffer(Mesh mesh, int index, Vector3f normal, Vector3f tangent * Computes the normals for the right, bottom, left, and top edges of the * patch, and saves those normals in the neighbour's edges too. * - * Takes 4 points (if has neighbour on that side) for each + * Takes 4 points (if it has a neighbour on that side) for each * point on the edge of the patch: * * * | @@ -615,7 +609,7 @@ protected float getHeight(int x, int z, float xm, float zm) { /** * Locks the mesh (sets it static) to improve performance. - * But it it not editable then. Set unlock to make it editable. + * If it is not editable, then unlock to make it editable. */ public void lockMesh() { getMesh().setStatic(); @@ -682,7 +676,7 @@ public Vector2f getOffset() { * This is mostly used for outside constructors of terrain patches. * * @param offset - * The new texture offset. + * The new texture offset. */ public void setOffset(Vector2f offset) { this.offset = offset; @@ -694,7 +688,7 @@ public void setOffset(Vector2f offset) { * of terrain patches. * * @param size - * The new size. + * The new size. */ public void setSize(int size) { this.size = size; @@ -708,7 +702,7 @@ public void setSize(int size) { * of terrain patches. * * @param totalSize - * The new total size. + * The new total size. */ public void setTotalSize(int totalSize) { this.totalSize = totalSize; @@ -720,7 +714,7 @@ public void setTotalSize(int totalSize) { * outside constructors of terrain patches. * * @param stepScale - * The new step scale. + * The new step scale. */ public void setStepScale(Vector3f stepScale) { this.stepScale = stepScale; @@ -732,7 +726,7 @@ public void setStepScale(Vector3f stepScale) { * constructors of terrain patches. * * @param offsetAmount - * The new texture offset. + * The new texture offset. */ public void setOffsetAmount(float offsetAmount) { this.offsetAmount = offsetAmount; @@ -747,7 +741,7 @@ public short getQuadrant() { /** * @param quadrant - * The quadrant to set. + * The quadrant to set. */ public void setQuadrant(short quadrant) { this.quadrant = quadrant; @@ -808,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()); } } @@ -939,6 +936,9 @@ public void read(JmeImporter im) throws IOException { @Override public TerrainPatch clone() { + // Note: this method should probably be using JmeCloner instead of manually + // cloning. -pspeed:2020-08-21 + TerrainPatch clone = new TerrainPatch(); clone.name = name.toString(); clone.size = size; @@ -954,7 +954,7 @@ public TerrainPatch clone() { clone.setLocalTranslation(getLocalTranslation().clone()); Mesh m = clone.geomap.createMesh(clone.stepScale, Vector2f.UNIT_XY, clone.offset, clone.offsetAmount, clone.totalSize, false); clone.setMesh(m); - clone.setMaterial(material.clone()); + clone.setMaterial(material == null ? null : material.clone()); return clone; } @@ -973,17 +973,19 @@ public void cloneFields( Cloner cloner, Object original ) { this.rightNeighbour = null; this.bottomNeighbour = null; - // Don't feel like making geomap cloneable tonight + // Don't feel like making geomap cloneable tonight, // so I'll copy the old logic. this.geomap = new LODGeomap(size, geomap.getHeightArray()); Mesh m = geomap.createMesh(stepScale, Vector2f.UNIT_XY, offset, offsetAmount, totalSize, false); this.setMesh(m); - // In this case, we always clone material even if the cloner is setup - // not to clone it. Terrain uses mutable textures and stuff so it's important + // In this case, we always clone material even if the cloner is set up + // not to clone it. Terrain uses mutable textures and stuff, so it's important // to clone it. (At least that's my understanding and is evidenced by the old // clone code specifically cloning material.) -pspeed - this.material = material.clone(); + // Also note that Geometry will have potentially already cloned this but the pre-JmeCloner + // code didn't care about that extra garbage, either. -pspeed:2020-08-21 + this.material = material == null ? null : material.clone(); } protected void ensurePositiveVolumeBBox() { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java index f89198f251..3e3b6a84ee 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/TerrainQuad.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 @@ -32,6 +32,7 @@ package com.jme3.terrain.geomipmap; import com.jme3.bounding.BoundingBox; +import com.jme3.bounding.BoundingSphere; import com.jme3.bounding.BoundingVolume; import com.jme3.collision.Collidable; import com.jme3.collision.CollisionResults; @@ -44,6 +45,7 @@ import com.jme3.math.Ray; import com.jme3.math.Vector2f; import com.jme3.math.Vector3f; +import com.jme3.math.Quaternion; import com.jme3.scene.Geometry; import com.jme3.scene.Node; import com.jme3.scene.Spatial; @@ -54,7 +56,7 @@ import com.jme3.terrain.geomipmap.picking.BresenhamTerrainPicker; import com.jme3.terrain.geomipmap.picking.TerrainPickData; import com.jme3.terrain.geomipmap.picking.TerrainPicker; -import com.jme3.util.TangentBinormalGenerator; +import com.jme3.util.TangentUtils; import com.jme3.util.clone.Cloner; import java.io.IOException; import java.util.ArrayList; @@ -146,9 +148,9 @@ public TerrainQuad() { * @param name the name of the scene element. This is required for * identification and comparison purposes. * @param patchSize size of the individual patches (geometry). Power of 2 plus 1, - * must be smaller than totalSize. (eg. 33, 65...) + * must be smaller than totalSize. (e.g. 33, 65...) * @param totalSize the size of this entire terrain (on one side). Power of 2 plus 1 - * (eg. 513, 1025, 2049...) + * (e.g. 513, 1025, 2049...) * @param heightMap The height map to generate the terrain from (a flat * height map will be generated if this is null). The size of one side of the heightmap * must match the totalSize. So a 513x513 heightmap is needed for a terrain with totalSize of 513. @@ -282,6 +284,7 @@ private int collideWithRay(Ray ray, CollisionResults results) { * calculator. This routine can take a long time to run! * @param progressMonitor optional */ + @Override public void generateEntropy(ProgressMonitor progressMonitor) { // only check this on the root quad if (isRootQuad()) @@ -313,10 +316,12 @@ protected boolean isRootQuad() { return (getParent() != null && !(getParent() instanceof TerrainQuad) ); } + @Override public Material getMaterial() { return getMaterial(null); } + @Override public Material getMaterial(Vector3f worldLocation) { // get the material from one of the children. They all share the same material if (children != null) { @@ -332,6 +337,7 @@ public Material getMaterial(Vector3f worldLocation) { return null; } + @Override public int getNumMajorSubdivisions() { return 1; } @@ -545,9 +551,9 @@ protected synchronized void reIndexPages(HashMap upd * size, then patches are created, otherwise, quads are created. * * @param blockSize - * the blocks size to test against. + * the blocks size to test against. * @param heightMap - * the height data. + * the height data. */ protected void split(int blockSize, float[] heightMap) { if ((size >> 1) + 1 <= blockSize) { @@ -671,7 +677,7 @@ public void generateDebugTangents(Material mat) { ((TerrainQuad)child).generateDebugTangents(mat); } else if (child instanceof TerrainPatch) { Geometry debug = new Geometry( "Debug " + name, - TangentBinormalGenerator.genTbnLines( ((TerrainPatch)child).getMesh(), 0.8f)); + TangentUtils.genTbnLines(((TerrainPatch) child).getMesh(), 0.8f)); attachChild(debug); debug.setLocalTranslation(child.getLocalTranslation()); debug.setCullHint(CullHint.Never); @@ -846,7 +852,26 @@ protected void setNormalRecalcNeeded(Vector2f changedPoint) { affectedAreaBBox = null; return; } - + + Vector2f worldLocVec2 = changedPoint.clone(); + worldLocVec2.multLocal(new Vector2f(getWorldScale().x, getWorldScale().z)); + worldLocVec2.addLocal(getWorldTranslation().x, getWorldTranslation().z); + changedPoint = worldLocVec2; + + Quaternion wr = getWorldRotation(); + if (wr.getX() != 0 || wr.getY() != 0 || wr.getZ() != 0) { + BoundingVolume bv = getWorldBound(); + if (bv instanceof BoundingSphere) { + BoundingSphere bs = (BoundingSphere) bv; + float r = bs.getRadius(); + Vector3f center = bs.getCenter(); + affectedAreaBBox = new BoundingBox(center, r, r, r); + } else { + affectedAreaBBox = (BoundingBox) bv.clone(); + } + return; + } + if (affectedAreaBBox == null) { affectedAreaBBox = new BoundingBox(new Vector3f(changedPoint.x, 0, changedPoint.y), 1f, Float.MAX_VALUE, 1f); // unit length } else { @@ -873,6 +898,7 @@ protected void setNeedToRecalculateNormals() { affectedAreaBBox = new BoundingBox(getWorldTranslation(), Float.MAX_VALUE, Float.MAX_VALUE, Float.MAX_VALUE); } + @Override public float getHeightmapHeight(Vector2f xz) { // offset int halfSize = totalSize / 2; @@ -898,7 +924,7 @@ protected float getHeightmapHeight(int x, int z) { int row = z; boolean match = false; - // get the childs quadrant + // get the child's quadrant int childQuadrant = 0; if (spat instanceof TerrainQuad) { childQuadrant = ((TerrainQuad) spat).getQuadrant(); @@ -943,7 +969,7 @@ protected Vector3f getMeshNormal(int x, int z) { int row = z; boolean match = false; - // get the childs quadrant + // get the child's quadrant int childQuadrant = 0; if (spat instanceof TerrainQuad) { childQuadrant = ((TerrainQuad) spat).getQuadrant(); @@ -1015,7 +1041,7 @@ private QuadrantChild findMatchingChild(int x, int z) { int row = z; boolean match = false; - // get the childs quadrant + // get the child's quadrant int childQuadrant = 0; if (spat instanceof TerrainQuad) { childQuadrant = ((TerrainQuad) spat).getQuadrant(); @@ -1046,12 +1072,13 @@ private QuadrantChild findMatchingChild(int x, int z) { /** * Get the interpolated height of the terrain at the specified point. * @param xz the location to get the height for - * @return Float.NAN if the value does not exist, or the coordinates are outside of the terrain + * @return Float.NAN if the value does not exist, or the coordinates lie outside the terrain */ + @Override public float getHeight(Vector2f xz) { // offset - float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); - float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); + float x = ((xz.x - getWorldTranslation().x) / getWorldScale().x) + (totalSize-1) / 2f; + float z = ((xz.y - getWorldTranslation().z) / getWorldScale().z) + (totalSize-1) / 2f; if (!isInside((int)x, (int)z)) return Float.NaN; float height = getHeight((int)x, (int)z, (x%1f), (z%1f)); @@ -1059,7 +1086,7 @@ public float getHeight(Vector2f xz) { return height; } - /* + /** * gets an interpolated value at the specified point */ protected float getHeight(int x, int z, float xm, float zm) { @@ -1075,10 +1102,11 @@ protected float getHeight(int x, int z, float xm, float zm) { return Float.NaN; } + @Override public Vector3f getNormal(Vector2f xz) { // offset - float x = (float)(((xz.x - getWorldTranslation().x) / getWorldScale().x) + (float)(totalSize-1) / 2f); - float z = (float)(((xz.y - getWorldTranslation().z) / getWorldScale().z) + (float)(totalSize-1) / 2f); + float x = ((xz.x - getWorldTranslation().x) / getWorldScale().x) + (totalSize-1) / 2f; + float z = ((xz.y - getWorldTranslation().z) / getWorldScale().z) + (totalSize-1) / 2f; Vector3f normal = getNormal(x, z, xz); return normal; @@ -1107,28 +1135,32 @@ protected Vector3f getNormal(float x, float z, Vector2f xz) { return n1.add(n2).add(n3).add(n4).normalize(); } + @Override public void setHeight(Vector2f xz, float height) { - List coord = new ArrayList(); + List coord = new ArrayList<>(); coord.add(xz); - List h = new ArrayList(); + List h = new ArrayList<>(); h.add(height); setHeight(coord, h); } + @Override public void adjustHeight(Vector2f xz, float delta) { - List coord = new ArrayList(); + List coord = new ArrayList<>(); coord.add(xz); - List h = new ArrayList(); + List h = new ArrayList<>(); h.add(delta); adjustHeight(coord, h); } + @Override public void setHeight(List xz, List height) { setHeight(xz, height, true); } + @Override public void adjustHeight(List xz, List height) { setHeight(xz, height, false); } @@ -1139,7 +1171,7 @@ protected void setHeight(List xz, List height, boolean override int halfSize = totalSize / 2; - List locations = new ArrayList(); + List locations = new ArrayList<>(); // offset for (int i=0; i locations, boolean overrideHeight) if (children == null) return; - List quadLH1 = new ArrayList(); - List quadLH2 = new ArrayList(); - List quadLH3 = new ArrayList(); - List quadLH4 = new ArrayList(); + List quadLH1 = new ArrayList<>(); + List quadLH2 = new ArrayList<>(); + List quadLH3 = new ArrayList<>(); + List quadLH4 = new ArrayList<>(); Spatial quad1 = null; Spatial quad2 = null; Spatial quad3 = null; @@ -1265,12 +1297,13 @@ protected boolean isPointOnTerrain(int x, int z) { } + @Override public int getTerrainSize() { return totalSize; } - // a position can be in multiple quadrants, so use a bit anded value. + // a position can be in multiple quadrants, so return a bitmask. private int findQuadrant(int x, int y) { int split = (size + 1) >> 1; int quads = 0; @@ -1290,6 +1323,7 @@ private int findQuadrant(int x, int y) { * Locked meshes are uneditable but have better performance. * @param locked or unlocked */ + @Override public void setLocked(boolean locked) { for (int i = 0; i < this.getQuantity(); i++) { if (this.getChild(i) instanceof TerrainQuad) { @@ -1567,7 +1601,7 @@ else if (quadrant == 1) { * Find what terrain patches need normal recalculations and update * their normals; */ - protected void fixNormals(BoundingBox affectedArea) { + public void fixNormals(BoundingBox affectedArea) { if (children == null) return; @@ -1576,10 +1610,10 @@ protected void fixNormals(BoundingBox affectedArea) { for (int x = children.size(); --x >= 0;) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { - if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects( child.getWorldBound()) ) ((TerrainQuad) child).fixNormals(affectedArea); } else if (child instanceof TerrainPatch) { - if (affectedArea != null && affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects(child.getWorldBound()) ) ((TerrainPatch) child).updateNormals(); // recalculate the patch's normals } } @@ -1588,17 +1622,17 @@ protected void fixNormals(BoundingBox affectedArea) { /** * fix the normals on the edge of the terrain patches. */ - protected void fixNormalEdges(BoundingBox affectedArea) { + public void fixNormalEdges(BoundingBox affectedArea) { if (children == null) return; for (int x = children.size(); --x >= 0;) { Spatial child = children.get(x); if (child instanceof TerrainQuad) { - if (affectedArea != null && affectedArea.intersects(((TerrainQuad) child).getWorldBound()) ) + if (affectedArea != null && affectedArea.intersects(child.getWorldBound()) ) ((TerrainQuad) child).fixNormalEdges(affectedArea); } else if (child instanceof TerrainPatch) { - if (affectedArea != null && !affectedArea.intersects(((TerrainPatch) child).getWorldBound()) ) // if doesn't intersect, continue + if (affectedArea != null && !affectedArea.intersects(child.getWorldBound()) ) // if doesn't intersect, continue continue; TerrainPatch tp = (TerrainPatch) child; @@ -1664,7 +1698,7 @@ public void findPick(Ray toTest, List results) { if (tp.getWorldBound().intersects(toTest)) { CollisionResults cr = new CollisionResults(); toTest.collideWith(tp.getWorldBound(), cr); - if (cr != null && cr.getClosestCollision() != null) { + if (cr.getClosestCollision() != null) { cr.getClosestCollision().getDistance(); results.add(new TerrainPickData(tp, cr.getClosestCollision())); } @@ -1712,15 +1746,16 @@ public void getAllTerrainPatchesWithTranslation(Map holde } @Override - public void read(JmeImporter e) throws IOException { - super.read(e); - InputCapsule c = e.getCapsule(this); + public void read(JmeImporter importer) throws IOException { + super.read(importer); + InputCapsule c = importer.getCapsule(this); size = c.readInt("size", 0); stepScale = (Vector3f) c.readSavable("stepScale", null); offset = (Vector2f) c.readSavable("offset", new Vector2f(0,0)); offsetAmount = c.readFloat("offsetAmount", 0); quadrant = c.readInt("quadrant", 0); totalSize = c.readInt("totalSize", 0); + patchSize = c.readInt("patchSize", 0); //lodCalculator = (LodCalculator) c.readSavable("lodCalculator", createDefaultLodCalculator()); //lodCalculatorFactory = (LodCalculatorFactory) c.readSavable("lodCalculatorFactory", null); @@ -1737,6 +1772,7 @@ public void write(JmeExporter e) throws IOException { OutputCapsule c = e.getCapsule(this); c.write(size, "size", 0); c.write(totalSize, "totalSize", 0); + c.write(patchSize, "patchSize", 0); c.write(stepScale, "stepScale", null); c.write(offset, "offset", new Vector2f(0,0)); c.write(offsetAmount, "offsetAmount", 0); @@ -1750,7 +1786,7 @@ public TerrainQuad clone() { return this.clone(true); } - @Override + @Override public TerrainQuad clone(boolean cloneMaterials) { TerrainQuad quadClone = (TerrainQuad) super.clone(cloneMaterials); quadClone.name = name.toString(); @@ -1793,9 +1829,8 @@ public void cloneFields( Cloner cloner, Object original ) { // This was not cloned before... I think that's a mistake. this.affectedAreaBBox = cloner.clone(affectedAreaBBox); - // picker is not cloneable and not cloned. This also seems like - // a mistake if you ever load the same terrain twice. - // this.picker = cloner.clone(picker); + // Otherwise, picker would be cloned by reference and thus "this" would be wrong + this.picker = new BresenhamTerrainPicker(this); // neighbourFinder is also not cloned. Maybe that's ok. } @@ -1828,6 +1863,7 @@ public void clearCaches() { } } + @Override public int getMaxLod() { if (maxLod < 0) maxLod = Math.max(1, (int) (FastMath.log(size-1)/FastMath.log(2)) -1); // -1 forces our minimum of 4 triangles wide @@ -1843,6 +1879,7 @@ public int getTotalSize() { return totalSize; } + @Override public float[] getHeightMap() { float[] hm = null; @@ -1912,8 +1949,7 @@ else if(((TerrainPatch) s).getQuadrant() == 4) */ public void setSupportMultipleCollisions(boolean set) { if (picker == null) { - // This is so that it doesn't fail at the IllegalStateException because null !instanceof Anything - throw new NullPointerException("TerrainPicker is null"); + throw new IllegalStateException("The TerrainPicker is null."); } else if (picker instanceof BresenhamTerrainPicker) { ((BresenhamTerrainPicker)picker).setSupportMultipleCollisions(set); } else { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java index f6d370b238..697e81b2d5 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/UpdatedTerrainPatch.java @@ -33,6 +33,7 @@ import com.jme3.scene.VertexBuffer.Type; import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -180,6 +181,8 @@ public void updateAll() { updatedPatch.getMesh().setBuffer(Type.Index, 3, (IntBuffer)newIndexBuffer); else if (newIndexBuffer instanceof ShortBuffer) updatedPatch.getMesh().setBuffer(Type.Index, 3, (ShortBuffer)newIndexBuffer); + else + updatedPatch.getMesh().setBuffer(Type.Index, 3, (ByteBuffer)newIndexBuffer); } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java index 54cd15c003..7b11274835 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/AssetTileLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -52,7 +52,6 @@ public class AssetTileLoader implements TerrainGridTileLoader { private AssetManager manager; private String assetPath; private String name; - private int size; private int patchSize; private int quadSize; @@ -65,6 +64,7 @@ public AssetTileLoader(AssetManager manager, String name, String assetPath) { this.assetPath = assetPath; } + @Override public TerrainQuad getTerrainQuadAt(Vector3f location) { String modelName = assetPath + "/" + name + "_" + Math.round(location.x) + "_" + Math.round(location.y) + "_" + Math.round(location.z) + ".j3o"; Logger.getLogger(this.getClass().getName()).log(Level.FINE, "Load terrain grid tile: {0}", modelName); @@ -91,10 +91,12 @@ public String getName() { return name; } + @Override public void setPatchSize(int patchSize) { this.patchSize = patchSize; } + @Override public void setQuadSize(int quadSize) { this.quadSize = quadSize; } @@ -104,12 +106,14 @@ private TerrainQuad createNewQuad(Vector3f location) { return q; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule c = ex.getCapsule(this); c.write(assetPath, "assetPath", null); c.write(name, "name", null); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule c = im.getCapsule(this); manager = im.getAssetManager(); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java index 60f01afcd5..da0b66621e 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/FractalTileLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -89,24 +89,29 @@ private HeightMap getHeightMapAt(Vector3f location) { return heightmap; } + @Override public TerrainQuad getTerrainQuadAt(Vector3f location) { HeightMap heightMapAt = getHeightMapAt(location); TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap()); return q; } + @Override public void setPatchSize(int patchSize) { this.patchSize = patchSize; } + @Override public void setQuadSize(int quadSize) { this.quadSize = quadSize; } + @Override public void write(JmeExporter ex) throws IOException { //TODO: serialization } + @Override public void read(JmeImporter im) throws IOException { //TODO: serialization } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java index ffd77c0c7e..d6692c25ca 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/grid/ImageTileLoader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -55,13 +55,11 @@ public class ImageTileLoader implements TerrainGridTileLoader{ private final Namer namer; private int patchSize; private int quadSize; - private float heightScale = 1; - //private int imageType = BufferedImage.TYPE_USHORT_GRAY; // 16 bit grayscale - //private ImageHeightmap customImageHeightmap; public ImageTileLoader(final String textureBase, final String textureExt, AssetManager assetManager) { this(assetManager, new Namer() { + @Override public String getName(int x, int y) { return textureBase + "_" + x + "_" + y + "." + textureExt; } @@ -77,11 +75,11 @@ public ImageTileLoader(AssetManager assetManager, Namer namer) { * Affects the vertical scale of the terrain when loaded. */ public void setHeightScale(float heightScale) { - this.heightScale = heightScale; + // not implemented } - /** + /* * Lets you specify the type of images that are being loaded. All images * must be the same type. * @param imageType eg. BufferedImage.TYPE_USHORT_GRAY @@ -90,7 +88,7 @@ public void setHeightScale(float heightScale) { this.imageType = imageType; }*/ - /** + /* * The ImageHeightmap that will parse the image type that you * specify with setImageType(). * @param customImageHeightmap must extend AbstractHeightmap @@ -156,24 +154,29 @@ public void setSize(int size) { this.patchSize = size - 1; } + @Override public TerrainQuad getTerrainQuadAt(Vector3f location) { HeightMap heightMapAt = getHeightMapAt(location); TerrainQuad q = new TerrainQuad("Quad" + location, patchSize, quadSize, heightMapAt == null ? null : heightMapAt.getHeightMap()); return q; } + @Override public void setPatchSize(int patchSize) { this.patchSize = patchSize; } + @Override public void setQuadSize(int quadSize) { this.quadSize = quadSize; } + @Override public void write(JmeExporter ex) throws IOException { //TODO: serialization } + @Override public void read(JmeImporter im) throws IOException { //TODO: serialization } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java index e60d80b568..8815a534ff 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/DistanceLodCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -62,6 +62,7 @@ public DistanceLodCalculator(int patchSize, float multiplier) { this.lodMultiplier = multiplier; } + @Override public boolean calculateLod(TerrainPatch terrainPatch, List locations, HashMap updates) { if (locations == null || locations.isEmpty()) return false;// no camera yet @@ -114,20 +115,22 @@ protected Vector3f getCenterLocation(TerrainPatch terrainPatch) { return loc; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(size, "patchSize", 32); oc.write(lodMultiplier, "lodMultiplier", 32); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); size = ic.readInt("patchSize", 32); - lodMultiplier = ic.readFloat("lodMultiplier", 2.7f); + lodMultiplier = ic.readFloat("lodMultiplier", 32f); } @Override - public LodCalculator clone() { + public DistanceLodCalculator clone() { DistanceLodCalculator clone = new DistanceLodCalculator(size, lodMultiplier); return clone; } @@ -148,6 +151,7 @@ protected float getLodDistanceThreshold() { * Does this calculator require the terrain to have the difference of * LOD levels of neighbours to be more than 1. */ + @Override public boolean usesVariableLod() { return false; } @@ -168,14 +172,17 @@ public void setSize(int size) { this.size = size; } + @Override public void turnOffLod() { turnOffLod = true; } + @Override public boolean isLodOff() { return turnOffLod; } + @Override public void turnOnLod() { turnOffLod = false; } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java index b266996317..018ad962f4 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/PerspectiveLodCalculator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -72,6 +72,7 @@ private float getCameraConstant(Camera cam, float pixelLimit){ return A / T; } + @Override public boolean calculateLod(TerrainPatch patch, List locations, HashMap updates) { if (turnOffLod) { // set to full detail @@ -131,21 +132,24 @@ public Vector3f getCenterLocation(TerrainPatch patch) { } @Override - public LodCalculator clone() { + public PerspectiveLodCalculator clone() { try { - return (LodCalculator) super.clone(); + return (PerspectiveLodCalculator) super.clone(); } catch (CloneNotSupportedException ex) { throw new AssertionError(); } } + @Override public void write(JmeExporter ex) throws IOException { } + @Override public void read(JmeImporter im) throws IOException { } + @Override public boolean usesVariableLod() { return true; } @@ -162,14 +166,17 @@ public void setCam(Camera cam) { this.cam = cam; } + @Override public void turnOffLod() { turnOffLod = true; } + @Override public boolean isLodOff() { return turnOffLod; } + @Override public void turnOnLod() { turnOffLod = false; } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java index 6f7ea57dca..93270d4225 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/SimpleLodThreshold.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -49,7 +49,7 @@ * @author bowens */ public class SimpleLodThreshold implements LodThreshold { - + private int size; // size of a terrain patch private float lodMultiplier = 2; @@ -81,18 +81,20 @@ public int getSize() { public void setSize(int size) { this.size = size; } - + @Override public float getLodDistanceThreshold() { return size*lodMultiplier; } + @Override public void write(JmeExporter ex) throws IOException { OutputCapsule oc = ex.getCapsule(this); oc.write(size, "size", 16); oc.write(lodMultiplier, "lodMultiplier", 2); } + @Override public void read(JmeImporter im) throws IOException { InputCapsule ic = im.getCapsule(this); size = ic.readInt("size", 16); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java index 0f638830e3..9057664d12 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/lodcalc/util/EntropyComputeUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -41,6 +41,7 @@ import com.jme3.scene.VertexBuffer.Type; import com.jme3.util.BufferUtils; import java.nio.Buffer; +import java.nio.ByteBuffer; import java.nio.FloatBuffer; import java.nio.IntBuffer; import java.nio.ShortBuffer; @@ -54,6 +55,11 @@ * @author Kirill Vainer */ public class EntropyComputeUtil { + /** + * A private constructor to inhibit instantiation of this class. + */ + private EntropyComputeUtil() { + } public static float computeLodEntropy(Mesh terrainBlock, Buffer lodIndices){ // Bounding box for the terrain block @@ -78,6 +84,8 @@ public static float computeLodEntropy(Mesh terrainBlock, Buffer lodIndices){ terrainBlock.setBuffer(Type.Index, 3, (IntBuffer)lodIndices); else if (lodIndices instanceof ShortBuffer) { terrainBlock.setBuffer(Type.Index, 3, (ShortBuffer) lodIndices); + } else { + terrainBlock.setBuffer(Type.Index, 3, (ByteBuffer) lodIndices); } // Recalculate collision mesh diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java index 5adf4f2bd6..cb2fc2fe23 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/BresenhamTerrainPicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -82,6 +82,8 @@ public boolean isSupportingMultipleCollisions() { return multipleCollisions; } + @Override + @SuppressWarnings("unchecked") public int getTerrainIntersection(Ray worldPick, CollisionResults results) { int numCollisions = 0; worldPickRay.set(worldPick); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java index 576dd15ca4..9edcf11384 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPickData.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -53,6 +53,7 @@ public TerrainPickData(TerrainPatch patch, CollisionResult cr) { this.cr = cr; } + @Override public int compareTo(Object o) { if (o instanceof TerrainPickData) { TerrainPickData tpd = (TerrainPickData) o; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java index bd45f3a084..ea9ed74f9b 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/TerrainPicker.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -33,7 +33,6 @@ import com.jme3.collision.CollisionResults; import com.jme3.math.Ray; -import com.jme3.math.Vector3f; /** * Pick the location on the terrain from a given ray. diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/package-info.java new file mode 100644 index 0000000000..f8d0669600 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/geomipmap/picking/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * picking algorithms for use with terrain + */ +package com.jme3.terrain.geomipmap.picking; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java index c6466c17b8..136114b2a6 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/AbstractHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -69,6 +69,7 @@ public abstract class AbstractHeightMap implements HeightMap { * unloadHeightMap clears the data of the height map. This * insures it is ready for reloading. */ + @Override public void unloadHeightMap() { heightData = null; } @@ -81,6 +82,7 @@ public void unloadHeightMap() { * @param scale * the scale to multiply height values by. */ + @Override public void setHeightScale(float scale) { heightScale = scale; } @@ -97,6 +99,7 @@ public void setHeightScale(float scale) { * @param z * the z (north/south) coordinate. */ + @Override public void setHeightAtPoint(float height, int x, int z) { heightData[x + (z * size)] = height; } @@ -109,9 +112,10 @@ public void setHeightAtPoint(float height, int x, int z) { * the new size of the terrain. * @throws Exception * - * @throws JmeException + * @throws Exception * if the size is less than or equal to zero. */ + @Override public void setSize(int size) throws Exception { if (size <= 0) { throw new Exception("size must be greater than zero."); @@ -127,10 +131,10 @@ public void setSize(int size) throws Exception { * * @param filter * the erosion value. - * @throws Exception - * @throws JmeException + * @throws Exception * if filter is less than 0 or greater than 1. */ + @Override public void setMagnificationFilter(float filter) throws Exception { if (filter < 0 || filter >= 1) { throw new Exception("filter must be between 0 and 1"); @@ -148,6 +152,7 @@ public void setMagnificationFilter(float filter) throws Exception { * the z (north/south) coordinate. * @return the value at (x,z). */ + @Override public float getTrueHeightAtPoint(int x, int z) { //logger.fine( heightData[x + (z*size)]); return heightData[x + (z * size)]; @@ -163,6 +168,7 @@ public float getTrueHeightAtPoint(int x, int z) { * the z (north/south) coordinate. * @return the scaled value at (x, z). */ + @Override public float getScaledHeightAtPoint(int x, int z) { return ((heightData[x + (z * size)]) * heightScale); } @@ -177,6 +183,7 @@ public float getScaledHeightAtPoint(int x, int z) { * the y coordinate of the point. * @return the interpolated height at this point. */ + @Override public float getInterpolatedHeight(float x, float z) { float low, highX, highZ; float intX, intZ; @@ -210,6 +217,7 @@ public float getInterpolatedHeight(float x, float z) { * * @return the grid of height data. */ + @Override public float[] getHeightMap() { return heightData; } @@ -218,6 +226,7 @@ public float[] getHeightMap() { * Build a new array of height data with the scaled values. * @return a new array */ + @Override public float[] getScaledHeightMap() { float[] hm = new float[heightData.length]; for (int i=0; i currentMax) { @@ -318,7 +326,7 @@ public void normalizeTerrain(float value) { height = currentMax - currentMin; - //scale the values to a range of 0-255 + //scale the values to a range of 0-value for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { heightData[i + j * size] = ((heightData[i + j * size] - currentMin) / height) * value; @@ -428,21 +436,21 @@ public void flatten(byte flattening) { } } - // re-normalize back to its oraginal height range + // re-normalize back to its original height range float height = minmax[1] - minmax[0]; normalizeTerrain(height); } /** - * Smooth the terrain. For each node, its 8 neighbors heights - * are averaged and will participate in the node new height - * by a factor np between 0 and 1 + * Smooth the terrain. For each node, its 8 neighbors' heights + * are averaged and will influence node's new height + * to the extent specified by np. * * You must first load() the heightmap data before this will have any effect. * * @param np - * The factor to what extend the neighbors average has an influence. - * Value of 0 will ignore neighbors (no smoothing) + * To what extent neighbors influence the new height: + * Value of 0 will ignore neighbors (no smoothing). * Value of 1 will ignore the node old height. */ public void smooth(float np) { @@ -450,15 +458,15 @@ public void smooth(float np) { } /** - * Smooth the terrain. For each node, its X(determined by radius) neighbors heights - * are averaged and will participate in the node new height - * by a factor np between 0 and 1 + * Smooth the terrain. For each node, its X (determined by radius) neighbors' heights + * are averaged and will influence node's new height + * to the extent specified by np. * * You must first load() the heightmap data before this will have any effect. * * @param np - * The factor to what extend the neighbors average has an influence. - * Value of 0 will ignore neighbors (no smoothing) + * To what extent neighbors influence the new height: + * Value of 0 will ignore neighbors (no smoothing). * Value of 1 will ignore the node old height. */ public void smooth(float np, int radius) { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java index e36dd03a6c..7e253f49cd 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/CombinerHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2018 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,13 +34,12 @@ import java.util.logging.Logger; /** - * CombinerHeightMap generates a new height map based on - * two provided height maps. These had maps can either be added together + * Generates a new height map based on + * two provided height maps. These maps can either be added together * or subtracted from each other. Each heightmap has a weight to - * determine how much one will affect the other. By default it is set to - * 0.5, 0.5 and meaning the two heightmaps are averaged evenly. This - * value can be adjusted at will, as long as the two factors are equal - * to 1.0. + * determine how much one will affect the other. By default, it is set to + * 0.5, 0.5, meaning the two heightmaps have equal weight. This + * value can be adjusted at will, as long as the two factors sum to 1. * * @author Mark Powell * @version $Id$ @@ -74,7 +73,7 @@ public class CombinerHeightMap extends AbstractHeightMap { * @param map2 the second heightmap to combine. * @param mode denotes whether to add or subtract the heightmaps, may * be either ADDITION or SUBTRACTION. - * @throws JmeException if either map is null, their size + * @throws Exception if either map is null, their size * do not match or the mode is invalid. */ public CombinerHeightMap( @@ -122,7 +121,7 @@ public CombinerHeightMap( * @param factor2 the factor for map2. * @param mode denotes whether to add or subtract the heightmaps, may * be either ADDITION or SUBTRACTION. - * @throws JmeException if either map is null, their size + * @throws Exception if either map is null, their size * do not match, the mode is invalid, or the factors do not add * to 1.0. */ @@ -173,7 +172,7 @@ public CombinerHeightMap( * to 1.0. * @param factor1 the factor for map1. * @param factor2 the factor for map2. - * @throws JmeException if the factors do not add to 1.0. + * @throws Exception if the factors do not add to 1.0. */ public void setFactors(float factor1, float factor2) throws Exception { if ((factor1 + factor2) != 1.0f) { @@ -190,7 +189,7 @@ public void setFactors(float factor1, float factor2) throws Exception { * The size of the height maps must be the same. * @param map1 the first height map. * @param map2 the second height map. - * @throws JmeException if the either heightmap is null, or their + * @throws Exception if either height map is null, or their * sizes do not match. */ public void setHeightMaps(AbstractHeightMap map1, AbstractHeightMap map2) throws Exception { @@ -213,7 +212,7 @@ public void setHeightMaps(AbstractHeightMap map1, AbstractHeightMap map2) throws * setMode sets the mode of the combiner. This may either * be ADDITION or SUBTRACTION. * @param mode the mode of the combiner. - * @throws JmeException if mode is not ADDITION or SUBTRACTION. + * @throws Exception if mode is not ADDITION or SUBTRACTION. */ public void setMode(int mode) throws Exception { if (mode != ADDITION && mode != SUBTRACTION) { @@ -229,6 +228,7 @@ public void setMode(int mode) throws Exception { * * @return boolean if the heightmap was successfully created. */ + @Override public boolean load() { if (null != heightData) { unloadHeightMap(); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java index ea64c570c3..bf91835f34 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FaultHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -51,8 +51,8 @@ public class FaultHeightMap extends AbstractHeightMap { */ public static final int FAULTTYPE_STEP = 0; /** - * Values on one side are lowered, then increase lineary while crossing - * the fault line to the other side. The fault line will be a inclined + * Values on one side are lowered, then increase linearly while crossing + * the fault line to the other side. The fault line will be an inclined * plane */ public static final int FAULTTYPE_LINEAR = 1; @@ -86,8 +86,8 @@ public class FaultHeightMap extends AbstractHeightMap { private int faultShape; // The type of fault /** - * Constructor creates the fault. For faulttype other than STEP, a range can - * be provided. For faultshape circle, min and max radii can be provided. + * Constructor creates the fault. For faultType other than STEP, a range can + * be provided. For faultShape circle, min and max radii can be provided. * Don't forget to reload the map if you have set parameters after the constructor * call. * @param size The size of the heightmap @@ -187,7 +187,7 @@ protected void addLineFault(float[][] tempBuffer, Random random, float faultHeig protected void addCircleFault(float[][] tempBuffer, Random random, float faultHeight, float range) { float radius = random.nextFloat() * (maxRadius - minRadius) + minRadius; int intRadius = (int) FastMath.floor(radius); - // Allox circle center to be out of map if not by more than radius. + // Allow circle center to be out of map if not by more than radius. // Unlucky cases will put them in the far corner, with the circle // entirely outside heightmap int x = random.nextInt(size + 2 * intRadius) - intRadius; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java index c532681d6a..a1d01e0c67 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/FluidSimHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -81,8 +81,8 @@ public class FluidSimHeightMap extends AbstractHeightMap { * the distance between each node of the heightmap * @param seed * the seed to generate the same heightmap again - * @throws JmeException - * if size of the terrain is not greater that zero, or number of + * @throws Exception + * if size of the terrain is not greater than zero, or number of * iterations is not greater that zero, or the minimum initial height * is greater than the maximum (or the other way around) */ @@ -116,15 +116,15 @@ public FluidSimHeightMap(int size, int iterations, float minInitialHeight, float * size the size of the terrain to be generated * @param iterations * the number of iterations to do - * @throws JmeException - * if size of the terrain is not greater that zero, or number of - * iterations is not greater that zero + * @throws Exception + * if size of the terrain is not greater than zero, or number of + * iterations is not greater than zero */ public FluidSimHeightMap(int size, int iterations) throws Exception { if (size <= 0 || iterations <= 0) { throw new Exception( - "Either size of the terrain is not greater that zero, " - + "or number of iterations is not greater that zero"); + "Either size of the terrain is not greater than zero, " + + "or number of iterations is not greater than zero"); } this.size = size; @@ -134,10 +134,11 @@ public FluidSimHeightMap(int size, int iterations) throws Exception { } - /* + /** * Generates a heightmap using fluid simulation and the attributes set by * the constructor or the setters. */ + @Override public boolean load() { // Clean up data if needed. if (null != heightData) { @@ -211,7 +212,7 @@ public boolean load() { // put the normalized heightmap into the range [0...255] and into the heightmap for (int y = 0; y < size; y++) { for (int x = 0; x < size; x++) { - heightData[x + y * size] = (float) (tempBuffer[curBuf][x + y * size]); + heightData[x + y * size] = tempBuffer[curBuf][x + y * size]; } } normalizeTerrain(NORMALIZE_RANGE); @@ -227,13 +228,13 @@ private float randomRange(Random random, float min, float max) { /** * Sets the number of times the fluid simulation should be iterated over - * the heightmap. The more often this is, the less features (hills, etc) + * the heightmap. The more iterations, the fewer features (hills, etcetera) * the terrain will have, and the smoother it will be. * * @param iterations - * the number of iterations to do - * @throws JmeException - * if iterations if not greater than zero + * the number of iterations to perform + * @throws Exception + * if iterations is not greater than zero */ public void setIterations(int iterations) throws Exception { if (iterations <= 0) { @@ -277,7 +278,7 @@ public void setNodeDistance(float nodeDistance) { /** * Sets the time-speed between each iteration of the fluid - * simulation algortithm. + * simulation algorithm. * * @param timeStep * the time-step between each iteration @@ -287,7 +288,7 @@ public void setTimeStep(float timeStep) { } /** - * Sets the viscosity of the simulated fuid. + * Sets the viscosity of the simulated fluid. * * @param viscosity * the viscosity of the fluid @@ -297,7 +298,7 @@ public void setViscosity(float viscosity) { } /** - * Sets the speed at which the waves trave. + * Sets the speed at which the waves travel. * * @param waveSpeed * the speed at which the waves travel diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java index 65731b1623..de29427741 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2020 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -130,7 +130,6 @@ public interface HeightMap { * @param filter * the erosion value. * @throws Exception - * @throws JmeException * if filter is less than 0 or greater than 1. */ void setMagnificationFilter(float filter) throws Exception; @@ -142,8 +141,6 @@ public interface HeightMap { * @param size * the new size of the terrain. * @throws Exception - * - * @throws JmeException * if the size is less than or equal to zero. */ void setSize(int size) throws Exception; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java index ca129236b2..e78ead1071 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HeightMapGrid.java @@ -36,11 +36,9 @@ /** * * @author Anthyon + * @deprecated in favor of TerrainGridTileLoader */ @Deprecated -/** - * @Deprecated in favor of TerrainGridTileLoader - */ public interface HeightMapGrid { public HeightMap getHeightMapAt(Vector3f location); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java index 5a284d8579..da3178f463 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/HillHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -36,7 +36,7 @@ /** * HillHeightMap generates a height map base on the Hill - * Algorithm. Terrain is generatd by growing hills of random size and height at + * Algorithm. Terrain is generated by growing hills of random size and height at * random points in the heightmap. The terrain is then normalized and valleys * can be flattened. * @@ -66,22 +66,21 @@ public class HillHeightMap extends AbstractHeightMap { * @param seed * the seed to generate the same heightmap again * @throws Exception - * @throws JmeException - * if size of the terrain is not greater that zero, or number of - * iterations is not greater that zero + * if size of the terrain is not greater than zero, or the number of + * iterations is not greater than zero */ public HillHeightMap(int size, int iterations, float minRadius, float maxRadius, long seed) throws Exception { if (size <= 0 || iterations <= 0 || minRadius <= 0 || maxRadius <= 0 || minRadius >= maxRadius) { throw new Exception( - "Either size of the terrain is not greater that zero, " - + "or number of iterations is not greater that zero, " + "Either size of the terrain is not greater than zero, " + + "or number of iterations is not greater than zero, " + "or minimum or maximum radius are not greater than zero, " + "or minimum radius is greater than maximum radius, " + "or power of flattening is below one"); } - logger.fine("Contructing hill heightmap using seed: " + seed); + logger.fine("Constructing hill heightmap using seed: " + seed); this.size = size; this.seed = seed; this.iterations = iterations; @@ -104,19 +103,19 @@ public HillHeightMap(int size, int iterations, float minRadius, * @param maxRadius * the maximum radius of a hill * @throws Exception - * @throws JmeException - * if size of the terrain is not greater that zero, or number of - * iterations is not greater that zero + * if size of the terrain is not greater than zero, or number of + * iterations is not greater than zero */ public HillHeightMap(int size, int iterations, float minRadius, float maxRadius) throws Exception { this(size, iterations, minRadius, maxRadius, new Random().nextLong()); } - /* + /** * Generates a heightmap using the Hill Algorithm and the attributes set by * the constructor or the setters. */ + @Override public boolean load() { // clean up data if needed. if (null != heightData) { @@ -134,7 +133,7 @@ public boolean load() { // transfer temporary buffer to final heightmap for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { - setHeightAtPoint((float) tempBuffer[i][j], j, i); + setHeightAtPoint(tempBuffer[i][j], j, i); } } @@ -213,8 +212,7 @@ private float randomRange(Random random, float min, float max) { * @param iterations * the number of hills to grow * @throws Exception - * @throws JmeException - * if iterations if not greater than zero + * if iterations is not greater than zero */ public void setIterations(int iterations) throws Exception { if (iterations <= 0) { @@ -230,8 +228,7 @@ public void setIterations(int iterations) throws Exception { * @param maxRadius * the maximum radius of a hill * @throws Exception - * @throws JmeException - * if the maximum radius if not greater than zero or not greater + * if the maximum radius is not greater than zero or not greater * than the minimum radius */ public void setMaxRadius(float maxRadius) throws Exception { @@ -247,8 +244,7 @@ public void setMaxRadius(float maxRadius) throws Exception { * * @param minRadius * the minimum radius of a hill - * @throws Exception - * @throws JmeException if the minimum radius is not greater than zero or not + * @throws Exception if the minimum radius is not greater than zero or not * lower than the maximum radius */ public void setMinRadius(float minRadius) throws Exception { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java index 0373c80ebd..459beffb63 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/ImageBasedHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -58,7 +58,7 @@ public void setImage(Image image) { /** * Creates a HeightMap from an Image. The image will be converted to * grayscale, and the grayscale values will be used to generate the height - * map. White is highest point while black is lowest point. + * map. White is the highest point, and black is the lowest point. * * Currently, the Image used must be square (width == height), but future * work could rescale the image. @@ -71,13 +71,14 @@ public ImageBasedHeightMap(Image colorImage) { } public ImageBasedHeightMap(Image colorImage, float heightScale) { - this.colorImage = colorImage; + this.colorImage = colorImage; this.heightScale = heightScale; } /** * Loads the image data from top left to bottom right */ + @Override public boolean load() { return load(false, false); } @@ -119,13 +120,13 @@ public boolean load(boolean flipX, boolean flipY) { if (flipX) { for (int w = imageWidth - 1; w >= 0; --w) { //int baseIndex = (h * imageWidth)+ w; - //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + //heightData[index++] = getHeightAtPosition(raster, baseIndex, colorStore)*heightScale; heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; } } else { for (int w = 0; w < imageWidth; ++w) { //int baseIndex = (h * imageWidth)+ w; - //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + //heightData[index++] = getHeightAtPosition(raster, baseIndex, colorStore)*heightScale; heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; } } @@ -135,13 +136,13 @@ public boolean load(boolean flipX, boolean flipY) { if (flipX) { for (int w = imageWidth - 1; w >= 0; --w) { //int baseIndex = (h * imageWidth)+ w; - //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + //heightData[index++] = getHeightAtPosition(raster, baseIndex, colorStore)*heightScale; heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; } } else { for (int w = 0; w < imageWidth; ++w) { //int baseIndex = (h * imageWidth)+ w; - //heightData[index++] = getHeightAtPostion(raster, baseIndex, colorStore)*heightScale; + //heightData[index++] = getHeightAtPosition(raster, baseIndex, colorStore)*heightScale; heightData[index++] = calculateHeight(raster.getPixel(w, h, colorStore))*heightScale*backwardsCompScale; } } @@ -151,7 +152,7 @@ public boolean load(boolean flipX, boolean flipY) { return true; } - /*protected float getHeightAtPostion(ImageRaster image, int position, ColorRGBA store) { + /*protected float getHeightAtPosition(ImageRaster image, int position, ColorRGBA store) { switch (image.getFormat()){ case RGBA8: buf.position( position * 4 ); diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java index acfbc9881d..c4f14d27d4 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/MidpointDisplacementHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2019 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -50,8 +50,8 @@ public class MidpointDisplacementHeightMap extends AbstractHeightMap { /** * The constructor generates the heightmap. After the first 4 corners are - * randomly given a height, the center will be heighted to the average of - * the 4 corners to which a random value is added. Then other passes fill + * randomly given a height, the center height will be set to the average of + * the 4 corners plus a random value. Then other passes fill * the heightmap by the same principle. * The random value is generated between the values -range * and range. The range parameter is multiplied by @@ -87,8 +87,8 @@ public MidpointDisplacementHeightMap(int size, float range, float persistence, l /** * The constructor generates the heightmap. After the first 4 corners are - * randomly given a height, the center will be heighted to the average of - * the 4 corners to which a random value is added. Then other passes fill + * randomly given a height, the center height will be set to the average of + * the 4 corners plus a random value. Then other passes fill * the heightmap by the same principle. * The random value is generated between the values -range * and range. The range parameter is multiplied by @@ -102,7 +102,7 @@ public MidpointDisplacementHeightMap(int size, float range, float persistence, l * The factor by which the range will evolve at each iteration. * A value of 0.5f will halve the range at each iteration and is * typically a good choice - * @throws JMException if size is not a power of two plus one. + * @throws IllegalArgumentException if size is not a power of two plus one. */ public MidpointDisplacementHeightMap(int size, float range, float persistence) throws Exception { this(size, range, persistence, new Random().nextLong()); @@ -144,7 +144,7 @@ public boolean load() { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { - setHeightAtPoint((float) tempBuffer[i][j], j, i); + setHeightAtPoint(tempBuffer[i][j], j, i); } } 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 c684aee20c..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-2012 jMonkeyEngine + * Copyright (c) 2009-2023 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -76,7 +76,7 @@ public class ParticleDepositionHeightMap extends AbstractHeightMap { * represented as a percentage, where 0.0 will not invert * anything, and 1.0 will invert all. * - * @throws JmeException if any value is less than zero, and + * @throws Exception if any value is less than zero, and * if caldera is not between 0 and 1. If minParticles is greater than * max particles as well. */ @@ -124,6 +124,7 @@ public ParticleDepositionHeightMap( * to load is recommended if attributes have changed using * the set methods. */ + @Override public boolean load() { int x, y; int calderaX, calderaY; @@ -172,7 +173,7 @@ public boolean load() { + minParticles)); //drop particles. for (int j = 0; j < numberParticles; j++) { - //check to see if we should aggitate the drop point. + //check to see if we should agitate the drop point. if (peakWalk != 0 && j % peakWalk == 0) { m = (int) (Math.rint(Math.random() * 7)); x = (x + dx[m] + size) % size; @@ -180,7 +181,7 @@ public boolean load() { } - //add the particle to the piont. + //add the particle to the point. tempBuffer[x][y] += 1; @@ -312,7 +313,7 @@ public boolean load() { //transfer the new terrain into the height map. for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { - setHeightAtPoint((float) tempBuffer[i][j], j, i); + setHeightAtPoint(tempBuffer[i][j], j, i); } } erodeTerrain(); @@ -320,15 +321,14 @@ public boolean load() { logger.fine("Created heightmap using Particle Deposition"); - - return false; + return true; // success } /** * setJumps sets the number of jumps or peaks that will * be created during the next call to load. * @param jumps the number of jumps to use for next load. - * @throws JmeException if jumps is less than zero. + * @throws Exception if jumps is less than zero. */ public void setJumps(int jumps) throws Exception { if (jumps < 0) { @@ -339,11 +339,11 @@ public void setJumps(int jumps) throws Exception { /** * setPeakWalk sets how often the jump point will be - * aggitated. The lower the peakWalk, the more often the point will - * be aggitated. + * agitated. The lower the peakWalk, the more often the point will + * be agitated. * - * @param peakWalk the amount to aggitate the jump point. - * @throws JmeException if peakWalk is negative or zero. + * @param peakWalk the amount to agitate the jump point. + * @throws Exception if peakWalk is negative or zero. */ public void setPeakWalk(int peakWalk) throws Exception { if (peakWalk <= 0) { @@ -359,7 +359,7 @@ public void setPeakWalk(int peakWalk) throws Exception { * * @param caldera the level at which a peak will be inverted. This must be * between 0 and 1, as it is represented as a percentage. - * @throws JmeException if caldera is not between 0 and 1. + * @throws Exception if caldera is not between 0 and 1. */ public void setCaldera(float caldera) throws Exception { if (caldera < 0.0f || caldera > 1.0f) { @@ -373,8 +373,6 @@ public void setCaldera(float caldera) throws Exception { * setMaxParticles sets the maximum number of particles * for a single jump. * @param maxParticles the maximum number of particles for a single jump. - * @throws JmeException if maxParticles is negative or less than - * the current number of minParticles. */ public void setMaxParticles(int maxParticles) { this.maxParticles = maxParticles; @@ -384,7 +382,7 @@ public void setMaxParticles(int maxParticles) { * setMinParticles sets the minimum number of particles * for a single jump. * @param minParticles the minimum number of particles for a single jump. - * @throws JmeException if minParticles are greater than + * @throws Exception if minParticles are greater than * the current maxParticles; */ public void setMinParticles(int minParticles) throws Exception { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java index 6cba90654b..a545eee362 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/RawHeightMap.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2009-2012 jMonkeyEngine + * Copyright (c) 2009-2021 jMonkeyEngine * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -61,7 +61,7 @@ public class RawHeightMap extends AbstractHeightMap { */ public static final int FORMAT_16BITBE = 2; private int format; - private boolean swapxy; + private boolean swapXy; private InputStream stream; /** @@ -74,7 +74,7 @@ public class RawHeightMap extends AbstractHeightMap { * the RAW file to use as the heightmap. * @param size * the size of the RAW (must be square). - * @throws JmeException + * @throws Exception * if the filename is null or not RAW, and if the size is 0 or * less. */ @@ -88,14 +88,14 @@ public RawHeightMap(float heightData[]) { this.format = FORMAT_8BIT; } - public RawHeightMap(String filename, int size, int format, boolean swapxy) throws Exception { - // varify that filename and size are valid. + public RawHeightMap(String filename, int size, int format, boolean swapXy) throws Exception { + // Verify that filename and size are valid. if (null == filename || size <= 0) { throw new Exception("Must supply valid filename and " + "size (> 0)"); } try { - setup(new FileInputStream(filename), size, format, swapxy); + setup(new FileInputStream(filename), size, format, swapXy); } catch (FileNotFoundException e) { throw new Exception("height file not found: " + filename); } @@ -106,7 +106,7 @@ public RawHeightMap(InputStream stream, int size, int format, boolean swapxy) th } public RawHeightMap(URL resource, int size, int format, boolean swapxy) throws Exception { - // varify that resource and size are valid. + // Verify that resource and size are valid. if (null == resource || size <= 0) { throw new Exception("Must supply valid resource and " + "size (> 0)"); @@ -121,7 +121,7 @@ public RawHeightMap(URL resource, int size, int format, boolean swapxy) throws E } private void setup(InputStream stream, int size, int format, boolean swapxy) throws Exception { - // varify that filename and size are valid. + // Verify that filename and size are valid. if (null == stream || size <= 0) { throw new Exception("Must supply valid stream and " + "size (> 0)"); @@ -131,16 +131,16 @@ private void setup(InputStream stream, int size, int format, boolean swapxy) thr this.stream = stream; this.size = size; this.format = format; - this.swapxy = swapxy; + this.swapXy = swapxy; load(); } /** * load fills the height data array with the appropriate data - * from the set RAW image. If the RAW image has not been set a JmeException - * will be thrown. + * from the set RAW image. * - * @return true if the load is successfull, false otherwise. + * @return true if the load is successful, false otherwise. + * @throws RuntimeException if the RAW image has not been set */ @Override public boolean load() { @@ -173,7 +173,7 @@ public boolean load() { // read the raw file for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { - if (swapxy) { + if (swapXy) { index = i + j * size; } else { index = (i * size) + j; @@ -188,7 +188,7 @@ public boolean load() { for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { int index; - if (swapxy) { + if (swapXy) { index = i + j * size; } else { index = (i * size) + j; @@ -216,7 +216,7 @@ public boolean load() { * * @param filename * the new file to use for the height data. - * @throws JmeException + * @throws Exception * if the file is null or not RAW. */ public void setFilename(String filename) throws Exception { @@ -236,7 +236,7 @@ public void setFilename(String filename) throws Exception { * * @param stream * the new stream to use for the height data. - * @throws JmeException + * @throws Exception * if the stream is null or not RAW. */ public void setHeightStream(InputStream stream) throws Exception { diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/package-info.java new file mode 100644 index 0000000000..2dc75a7565 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/heightmap/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * heightmaps for use with terrain + */ +package com.jme3.terrain.heightmap; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java index 7f48aa21f5..072ce9d1f1 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Basis.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -42,14 +42,14 @@ * * * float value(float x, float y, float z) { - * return 0; // a flat noise with 0 value everywhere + * return 0; // a flat noise with 0 value everywhere * } * * * or a more complex perlin noise ({@link ImprovedNoise} * * Fractals use these functions to generate a more complex result based on some - * frequency, roughness, etc values. + * frequency, roughness, etcetera values. * * Fractals themselves are implementing the Basis interface as well, opening * an infinite range of results. @@ -61,16 +61,16 @@ */ public interface Basis { - public void init(); + public void init(); - public Basis setScale(float scale); + public Basis setScale(float scale); - public float getScale(); + public float getScale(); - public Basis addModulator(Modulator modulator); + public Basis addModulator(Modulator modulator); - public float value(float x, float y, float z); + public float value(float x, float y, float z); - public FloatBuffer getBuffer(float sx, float sy, float base, int size); + public FloatBuffer getBuffer(float sx, float sy, float base, int size); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java index 719c0ffee1..2af7bad3b8 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Color.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -37,98 +37,98 @@ */ public class Color { - private final float[] rgba = new float[4]; + private final float[] rgba = new float[4]; - public Color() {} + public Color() {} - public Color(final int r, final int g, final int b) { - this(r, g, b, 255); - } + public Color(final int r, final int g, final int b) { + this(r, g, b, 255); + } - public Color(final int r, final int g, final int b, final int a) { - this.rgba[0] = (r & 255) / 256f; - this.rgba[1] = (g & 255) / 256f; - this.rgba[2] = (b & 255) / 256f; - this.rgba[3] = (a & 255) / 256f; - } + public Color(final int r, final int g, final int b, final int a) { + this.rgba[0] = (r & 255) / 256f; + this.rgba[1] = (g & 255) / 256f; + this.rgba[2] = (b & 255) / 256f; + this.rgba[3] = (a & 255) / 256f; + } - public Color(final float r, final float g, final float b) { - this(r, g, b, 1); - } + public Color(final float r, final float g, final float b) { + this(r, g, b, 1); + } - public Color(final float r, final float g, final float b, final float a) { - this.rgba[0] = ShaderUtils.clamp(r, 0, 1); - this.rgba[1] = ShaderUtils.clamp(g, 0, 1); - this.rgba[2] = ShaderUtils.clamp(b, 0, 1); - this.rgba[3] = ShaderUtils.clamp(a, 0, 1); - } + public Color(final float r, final float g, final float b, final float a) { + this.rgba[0] = ShaderUtils.clamp(r, 0, 1); + this.rgba[1] = ShaderUtils.clamp(g, 0, 1); + this.rgba[2] = ShaderUtils.clamp(b, 0, 1); + this.rgba[3] = ShaderUtils.clamp(a, 0, 1); + } - public Color(final int h, final float s, final float b) { - this(h, s, b, 1); - } + public Color(final int h, final float s, final float b) { + this(h, s, b, 1); + } - public Color(final int h, final float s, final float b, final float a) { - this.rgba[3] = a; - if (s == 0) { - // achromatic ( grey ) - this.rgba[0] = b; - this.rgba[1] = b; - this.rgba[2] = b; - return; - } + public Color(final int h, final float s, final float b, final float a) { + this.rgba[3] = a; + if (s == 0) { + // achromatic ( grey ) + this.rgba[0] = b; + this.rgba[1] = b; + this.rgba[2] = b; + return; + } - float hh = h / 60.0f; - int i = ShaderUtils.floor(hh); - float f = hh - i; - float p = b * (1 - s); - float q = b * (1 - s * f); - float t = b * (1 - s * (1 - f)); + float hh = h / 60.0f; + int i = ShaderUtils.floor(hh); + float f = hh - i; + float p = b * (1 - s); + float q = b * (1 - s * f); + float t = b * (1 - s * (1 - f)); - if (i == 0) { - this.rgba[0] = b; - this.rgba[1] = t; - this.rgba[2] = p; - } else if (i == 1) { - this.rgba[0] = q; - this.rgba[1] = b; - this.rgba[2] = p; - } else if (i == 2) { - this.rgba[0] = p; - this.rgba[1] = b; - this.rgba[2] = t; - } else if (i == 3) { - this.rgba[0] = p; - this.rgba[1] = q; - this.rgba[2] = b; - } else if (i == 4) { - this.rgba[0] = t; - this.rgba[1] = p; - this.rgba[2] = b; - } else { - this.rgba[0] = b; - this.rgba[1] = p; - this.rgba[2] = q; - } - } + if (i == 0) { + this.rgba[0] = b; + this.rgba[1] = t; + this.rgba[2] = p; + } else if (i == 1) { + this.rgba[0] = q; + this.rgba[1] = b; + this.rgba[2] = p; + } else if (i == 2) { + this.rgba[0] = p; + this.rgba[1] = b; + this.rgba[2] = t; + } else if (i == 3) { + this.rgba[0] = p; + this.rgba[1] = q; + this.rgba[2] = b; + } else if (i == 4) { + this.rgba[0] = t; + this.rgba[1] = p; + this.rgba[2] = b; + } else { + this.rgba[0] = b; + this.rgba[1] = p; + this.rgba[2] = q; + } + } - public int toInteger() { - return 0x00000000 | (int) (this.rgba[3] * 256) << 24 | (int) (this.rgba[0] * 256) << 16 | (int) (this.rgba[1] * 256) << 8 - | (int) (this.rgba[2] * 256); - } + public int toInteger() { + return 0x00000000 | (int) (this.rgba[3] * 256) << 24 | (int) (this.rgba[0] * 256) << 16 | (int) (this.rgba[1] * 256) << 8 + | (int) (this.rgba[2] * 256); + } - public String toWeb() { - return Integer.toHexString(this.toInteger()); - } + public String toWeb() { + return Integer.toHexString(this.toInteger()); + } - public Color toGrayscale() { - float v = (this.rgba[0] + this.rgba[1] + this.rgba[2]) / 3f; - return new Color(v, v, v, this.rgba[3]); - } + public Color toGrayscale() { + float v = (this.rgba[0] + this.rgba[1] + this.rgba[2]) / 3f; + return new Color(v, v, v, this.rgba[3]); + } - public Color toSepia() { - float r = ShaderUtils.clamp(this.rgba[0] * 0.393f + this.rgba[1] * 0.769f + this.rgba[2] * 0.189f, 0, 1); - float g = ShaderUtils.clamp(this.rgba[0] * 0.349f + this.rgba[1] * 0.686f + this.rgba[2] * 0.168f, 0, 1); - float b = ShaderUtils.clamp(this.rgba[0] * 0.272f + this.rgba[1] * 0.534f + this.rgba[2] * 0.131f, 0, 1); - return new Color(r, g, b, this.rgba[3]); - } + public Color toSepia() { + float r = ShaderUtils.clamp(this.rgba[0] * 0.393f + this.rgba[1] * 0.769f + this.rgba[2] * 0.189f, 0, 1); + float g = ShaderUtils.clamp(this.rgba[0] * 0.349f + this.rgba[1] * 0.686f + this.rgba[2] * 0.168f, 0, 1); + float b = ShaderUtils.clamp(this.rgba[0] * 0.272f + this.rgba[1] * 0.534f + this.rgba[2] * 0.131f, 0, 1); + return new Color(r, g, b, this.rgba[3]); + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java index 1dca8bfbfc..32f0a310de 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/Filter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -32,13 +32,13 @@ import java.nio.FloatBuffer; public interface Filter { - public Filter addPreFilter(Filter filter); + public Filter addPreFilter(Filter filter); - public Filter addPostFilter(Filter filter); + public Filter addPostFilter(Filter filter); - public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size); + public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size); - public int getMargin(int size, int margin); + public int getMargin(int size, int margin); - public boolean isEnabled(); + public boolean isEnabled(); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java index 360baec373..84aa59a4ee 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/ShaderUtils.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -46,243 +46,248 @@ * */ public class ShaderUtils { - - public static final float[] i2c(final int color) { - return new float[] { (color & 0x00ff0000) / 256f, (color & 0x0000ff00) / 256f, (color & 0x000000ff) / 256f, - (color & 0xff000000) / 256f }; - } - - public static final int c2i(final float[] color) { - return (color.length == 4 ? (int) (color[3] * 256) : 0xff000000) | ((int) (color[0] * 256) << 16) | ((int) (color[1] * 256) << 8) - | (int) (color[2] * 256); - } - - public static final float mix(final float a, final float b, final float f) { - return (1 - f) * a + f * b; - } - - public static final Color mix(final Color a, final Color b, final float f) { - return new Color((int) ShaderUtils.clamp(ShaderUtils.mix(a.getRed(), b.getRed(), f), 0, 255), (int) ShaderUtils.clamp( - ShaderUtils.mix(a.getGreen(), b.getGreen(), f), 0, 255), (int) ShaderUtils.clamp( - ShaderUtils.mix(a.getBlue(), b.getBlue(), f), 0, 255)); - } - - public static final int mix(final int a, final int b, final float f) { - return (int) ((1 - f) * a + f * b); - } - - public static final float[] mix(final float[] c1, final float[] c2, final float f) { - return new float[] { ShaderUtils.mix(c1[0], c2[0], f), ShaderUtils.mix(c1[1], c2[1], f), ShaderUtils.mix(c1[2], c2[2], f) }; - } - - public static final float step(final float a, final float x) { - return x < a ? 0 : 1; - } - - public static final float boxstep(final float a, final float b, final float x) { - return ShaderUtils.clamp((x - a) / (b - a), 0, 1); - } - - public static final float pulse(final float a, final float b, final float x) { - return ShaderUtils.step(a, x) - ShaderUtils.step(b, x); - } - - public static final float clamp(final float x, final float a, final float b) { - return x < a ? a : x > b ? b : x; - } - - public static final float min(final float a, final float b) { - return a < b ? a : b; - } - - public static final float max(final float a, final float b) { - return a > b ? a : b; - } - - public static final float abs(final float x) { - return x < 0 ? -x : x; - } - - public static final float smoothstep(final float a, final float b, final float x) { - if (x < a) { - return 0; - } else if (x > b) { - return 1; - } - float xx = (x - a) / (b - a); - return xx * xx * (3 - 2 * xx); - } - - public static final float mod(final float a, final float b) { - int n = (int) (a / b); - float aa = a - n * b; - if (aa < 0) { - aa += b; - } - return aa; - } - - public static final int floor(final float x) { - return x > 0 ? (int) x : (int) x - 1; - } - - public static final float ceil(final float x) { - return (int) x + (x > 0 && x != (int) x ? 1 : 0); - } - - public static final float spline(float x, final float[] knot) { - float CR00 = -0.5f; - float CR01 = 1.5f; - float CR02 = -1.5f; - float CR03 = 0.5f; - float CR10 = 1.0f; - float CR11 = -2.5f; - float CR12 = 2.0f; - float CR13 = -0.5f; - float CR20 = -0.5f; - float CR21 = 0.0f; - float CR22 = 0.5f; - float CR23 = 0.0f; - float CR30 = 0.0f; - float CR31 = 1.0f; - float CR32 = 0.0f; - float CR33 = 0.0f; - - int span; - int nspans = knot.length - 3; - float c0, c1, c2, c3; /* coefficients of the cubic. */ - if (nspans < 1) {/* illegal */ - throw new RuntimeException("Spline has too few knots."); - } - /* Find the appropriate 4-point span of the spline. */ - x = ShaderUtils.clamp(x, 0, 1) * nspans; - span = (int) x; - if (span >= knot.length - 3) { - span = knot.length - 3; - } - x -= span; - /* Evaluate the span cubic at x using Horner’s rule. */ - c3 = CR00 * knot[span + 0] + CR01 * knot[span + 1] + CR02 * knot[span + 2] + CR03 * knot[span + 3]; - c2 = CR10 * knot[span + 0] + CR11 * knot[span + 1] + CR12 * knot[span + 2] + CR13 * knot[span + 3]; - c1 = CR20 * knot[span + 0] + CR21 * knot[span + 1] + CR22 * knot[span + 2] + CR23 * knot[span + 3]; - c0 = CR30 * knot[span + 0] + CR31 * knot[span + 1] + CR32 * knot[span + 2] + CR33 * knot[span + 3]; - return ((c3 * x + c2) * x + c1) * x + c0; - } - - public static final float[] spline(final float x, final float[][] knots) { - float[] retval = new float[knots.length]; - for (int i = 0; i < knots.length; i++) { - retval[i] = ShaderUtils.spline(x, knots[i]); - } - return retval; - } - - public static final float gammaCorrection(final float gamma, final float x) { - return (float) Math.pow(x, 1 / gamma); - } - - public static final float bias(final float b, final float x) { - return (float) Math.pow(x, Math.log(b) / Math.log(0.5)); - } - - public static final float gain(final float g, final float x) { - return x < 0.5 ? ShaderUtils.bias(1 - g, 2 * x) / 2 : 1 - ShaderUtils.bias(1 - g, 2 - 2 * x) / 2; - } - - public static final float sinValue(final float s, final float minFreq, final float maxFreq, final float swidth) { - float value = 0; - float cutoff = ShaderUtils.clamp(0.5f / swidth, 0, maxFreq); - float f; - for (f = minFreq; f < 0.5 * cutoff; f *= 2) { - value += Math.sin(2 * Math.PI * f * s) / f; - } - float fade = ShaderUtils.clamp(2 * (cutoff - f) / cutoff, 0, 1); - value += fade * Math.sin(2 * Math.PI * f * s) / f; - return value; - } - - public static final float length(final float x, final float y, final float z) { - return (float) Math.sqrt(x * x + y * y + z * z); - } - - public static final float[] rotate(final float[] v, final float[][] m) { - float x = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2]; - float y = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2]; - float z = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2]; - return new float[] { x, y, z }; - } - - public static final float[][] calcRotationMatrix(final float ax, final float ay, final float az) { - float[][] retval = new float[3][3]; - float cax = (float) Math.cos(ax); - float sax = (float) Math.sin(ax); - float cay = (float) Math.cos(ay); - float say = (float) Math.sin(ay); - float caz = (float) Math.cos(az); - float saz = (float) Math.sin(az); - - retval[0][0] = cay * caz; - retval[0][1] = -cay * saz; - retval[0][2] = say; - retval[1][0] = sax * say * caz + cax * saz; - retval[1][1] = -sax * say * saz + cax * caz; - retval[1][2] = -sax * cay; - retval[2][0] = -cax * say * caz + sax * saz; - retval[2][1] = cax * say * saz + sax * caz; - retval[2][2] = cax * cay; - - return retval; - } - - public static final float[] normalize(final float[] v) { - float l = ShaderUtils.length(v); - float[] r = new float[v.length]; - int i = 0; - for (float vv : v) { - r[i++] = vv / l; - } - return r; - } - - public static final float length(final float[] v) { - float s = 0; - for (float vv : v) { - s += vv * vv; - } - return (float) Math.sqrt(s); - } - - public static final ByteBuffer getImageDataFromImage(BufferedImage bufferedImage) { - WritableRaster wr; - DataBuffer db; - - BufferedImage bi = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); - Graphics2D g = bi.createGraphics(); - g.drawImage(bufferedImage, null, null); - bufferedImage = bi; - wr = bi.getRaster(); - db = wr.getDataBuffer(); - - DataBufferInt dbi = (DataBufferInt) db; - int[] data = dbi.getData(); - - ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); - byteBuffer.order(ByteOrder.LITTLE_ENDIAN); - byteBuffer.asIntBuffer().put(data); - byteBuffer.flip(); - - return byteBuffer; - } - - public static float frac(float f) { - return f - ShaderUtils.floor(f); - } - - public static float[] floor(float[] fs) { - float[] retval = new float[fs.length]; - for (int i = 0; i < fs.length; i++) { - retval[i] = ShaderUtils.floor(fs[i]); - } - return retval; - } + /** + * A private constructor to inhibit instantiation of this class. + */ + private ShaderUtils() { + } + + public static final float[] i2c(final int color) { + return new float[] { (color & 0x00ff0000) / 256f, (color & 0x0000ff00) / 256f, (color & 0x000000ff) / 256f, + (color & 0xff000000) / 256f }; + } + + public static final int c2i(final float[] color) { + return (color.length == 4 ? (int) (color[3] * 256) : 0xff000000) | ((int) (color[0] * 256) << 16) | ((int) (color[1] * 256) << 8) + | (int) (color[2] * 256); + } + + public static final float mix(final float a, final float b, final float f) { + return (1 - f) * a + f * b; + } + + public static final Color mix(final Color a, final Color b, final float f) { + return new Color((int) ShaderUtils.clamp(ShaderUtils.mix(a.getRed(), b.getRed(), f), 0, 255), (int) ShaderUtils.clamp( + ShaderUtils.mix(a.getGreen(), b.getGreen(), f), 0, 255), (int) ShaderUtils.clamp( + ShaderUtils.mix(a.getBlue(), b.getBlue(), f), 0, 255)); + } + + public static final int mix(final int a, final int b, final float f) { + return (int) ((1 - f) * a + f * b); + } + + public static final float[] mix(final float[] c1, final float[] c2, final float f) { + return new float[] { ShaderUtils.mix(c1[0], c2[0], f), ShaderUtils.mix(c1[1], c2[1], f), ShaderUtils.mix(c1[2], c2[2], f) }; + } + + public static final float step(final float a, final float x) { + return x < a ? 0 : 1; + } + + public static final float boxstep(final float a, final float b, final float x) { + return ShaderUtils.clamp((x - a) / (b - a), 0, 1); + } + + public static final float pulse(final float a, final float b, final float x) { + return ShaderUtils.step(a, x) - ShaderUtils.step(b, x); + } + + public static final float clamp(final float x, final float a, final float b) { + return x < a ? a : x > b ? b : x; + } + + public static final float min(final float a, final float b) { + return a < b ? a : b; + } + + public static final float max(final float a, final float b) { + return a > b ? a : b; + } + + public static final float abs(final float x) { + return x < 0 ? -x : x; + } + + public static final float smoothstep(final float a, final float b, final float x) { + if (x < a) { + return 0; + } else if (x > b) { + return 1; + } + float xx = (x - a) / (b - a); + return xx * xx * (3 - 2 * xx); + } + + public static final float mod(final float a, final float b) { + int n = (int) (a / b); + float aa = a - n * b; + if (aa < 0) { + aa += b; + } + return aa; + } + + public static final int floor(final float x) { + return x > 0 ? (int) x : (int) x - 1; + } + + public static final float ceil(final float x) { + return (int) x + (x > 0 && x != (int) x ? 1 : 0); + } + + public static final float spline(float x, final float[] knot) { + float CR00 = -0.5f; + float CR01 = 1.5f; + float CR02 = -1.5f; + float CR03 = 0.5f; + float CR10 = 1.0f; + float CR11 = -2.5f; + float CR12 = 2.0f; + float CR13 = -0.5f; + float CR20 = -0.5f; + float CR21 = 0.0f; + float CR22 = 0.5f; + float CR23 = 0.0f; + float CR30 = 0.0f; + float CR31 = 1.0f; + float CR32 = 0.0f; + float CR33 = 0.0f; + + int span; + int nspans = knot.length - 3; + float c0, c1, c2, c3; /* coefficients of the cubic. */ + if (nspans < 1) {/* illegal */ + throw new RuntimeException("Spline has too few knots."); + } + /* Find the appropriate 4-point span of the spline. */ + x = ShaderUtils.clamp(x, 0, 1) * nspans; + span = (int) x; + if (span >= knot.length - 3) { + span = knot.length - 3; + } + x -= span; + /* Evaluate the span cubic at x using Horner’s rule. */ + c3 = CR00 * knot[span + 0] + CR01 * knot[span + 1] + CR02 * knot[span + 2] + CR03 * knot[span + 3]; + c2 = CR10 * knot[span + 0] + CR11 * knot[span + 1] + CR12 * knot[span + 2] + CR13 * knot[span + 3]; + c1 = CR20 * knot[span + 0] + CR21 * knot[span + 1] + CR22 * knot[span + 2] + CR23 * knot[span + 3]; + c0 = CR30 * knot[span + 0] + CR31 * knot[span + 1] + CR32 * knot[span + 2] + CR33 * knot[span + 3]; + return ((c3 * x + c2) * x + c1) * x + c0; + } + + public static final float[] spline(final float x, final float[][] knots) { + float[] retval = new float[knots.length]; + for (int i = 0; i < knots.length; i++) { + retval[i] = ShaderUtils.spline(x, knots[i]); + } + return retval; + } + + public static final float gammaCorrection(final float gamma, final float x) { + return (float) Math.pow(x, 1 / gamma); + } + + public static final float bias(final float b, final float x) { + return (float) Math.pow(x, Math.log(b) / Math.log(0.5)); + } + + public static final float gain(final float g, final float x) { + return x < 0.5 ? ShaderUtils.bias(1 - g, 2 * x) / 2 : 1 - ShaderUtils.bias(1 - g, 2 - 2 * x) / 2; + } + + public static final float sinValue(final float s, final float minFreq, final float maxFreq, final float sWidth) { + float value = 0; + float cutoff = ShaderUtils.clamp(0.5f / sWidth, 0, maxFreq); + float f; + for (f = minFreq; f < 0.5 * cutoff; f *= 2) { + value += Math.sin(2 * Math.PI * f * s) / f; + } + float fade = ShaderUtils.clamp(2 * (cutoff - f) / cutoff, 0, 1); + value += fade * Math.sin(2 * Math.PI * f * s) / f; + return value; + } + + public static final float length(final float x, final float y, final float z) { + return (float) Math.sqrt(x * x + y * y + z * z); + } + + public static final float[] rotate(final float[] v, final float[][] m) { + float x = v[0] * m[0][0] + v[1] * m[0][1] + v[2] * m[0][2]; + float y = v[0] * m[1][0] + v[1] * m[1][1] + v[2] * m[1][2]; + float z = v[0] * m[2][0] + v[1] * m[2][1] + v[2] * m[2][2]; + return new float[] { x, y, z }; + } + + public static final float[][] calcRotationMatrix(final float ax, final float ay, final float az) { + float[][] retval = new float[3][3]; + float cax = (float) Math.cos(ax); + float sax = (float) Math.sin(ax); + float cay = (float) Math.cos(ay); + float say = (float) Math.sin(ay); + float caz = (float) Math.cos(az); + float saz = (float) Math.sin(az); + + retval[0][0] = cay * caz; + retval[0][1] = -cay * saz; + retval[0][2] = say; + retval[1][0] = sax * say * caz + cax * saz; + retval[1][1] = -sax * say * saz + cax * caz; + retval[1][2] = -sax * cay; + retval[2][0] = -cax * say * caz + sax * saz; + retval[2][1] = cax * say * saz + sax * caz; + retval[2][2] = cax * cay; + + return retval; + } + + public static final float[] normalize(final float[] v) { + float l = ShaderUtils.length(v); + float[] r = new float[v.length]; + int i = 0; + for (float vv : v) { + r[i++] = vv / l; + } + return r; + } + + public static final float length(final float[] v) { + float s = 0; + for (float vv : v) { + s += vv * vv; + } + return (float) Math.sqrt(s); + } + + public static final ByteBuffer getImageDataFromImage(BufferedImage bufferedImage) { + WritableRaster wr; + DataBuffer db; + + BufferedImage bi = new BufferedImage(128, 64, BufferedImage.TYPE_INT_ARGB); + Graphics2D g = bi.createGraphics(); + g.drawImage(bufferedImage, null, null); + bufferedImage = bi; + wr = bi.getRaster(); + db = wr.getDataBuffer(); + + DataBufferInt dbi = (DataBufferInt) db; + int[] data = dbi.getData(); + + ByteBuffer byteBuffer = ByteBuffer.allocateDirect(data.length * 4); + byteBuffer.order(ByteOrder.LITTLE_ENDIAN); + byteBuffer.asIntBuffer().put(data); + byteBuffer.flip(); + + return byteBuffer; + } + + public static float frac(float f) { + return f - ShaderUtils.floor(f); + } + + public static float[] floor(float[] fs) { + float[] retval = new float[fs.length]; + for (int i = 0; i < fs.length; i++) { + retval[i] = ShaderUtils.floor(fs[i]); + } + return retval; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java index 8fec02ef5f..8fee842442 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/FilteredBasis.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -38,73 +38,73 @@ public class FilteredBasis extends AbstractFilter implements Basis { - private Basis basis; - private List modulators = new ArrayList(); - private float scale; - - public FilteredBasis() {} - - public FilteredBasis(Basis basis) { - this.basis = basis; - } - - public Basis getBasis() { - return this.basis; - } - - public void setBasis(Basis basis) { - this.basis = basis; - } - - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { - return data; - } - - @Override - public void init() { - this.basis.init(); - } - - @Override - public Basis setScale(float scale) { - this.scale = scale; - return this; - } - - @Override - public float getScale() { - return this.scale; - } - - @Override - public Basis addModulator(Modulator modulator) { - this.modulators.add(modulator); - return this; - } - - @Override - public float value(float x, float y, float z) { - throw new UnsupportedOperationException( - "Method value cannot be called on FilteredBasis and its descendants. Use getBuffer instead!"); - } - - @Override - public FloatBuffer getBuffer(float sx, float sy, float base, int size) { - int margin = this.getMargin(size, 0); - int workSize = size + 2 * margin; - FloatBuffer retval = this.basis.getBuffer(sx - margin, sy - margin, base, workSize); - return this.clip(this.doFilter(sx, sy, base, retval, workSize), workSize, size, margin); - } - - public FloatBuffer clip(FloatBuffer buf, int origSize, int newSize, int offset) { - FloatBuffer result = FloatBuffer.allocate(newSize * newSize); - - float[] orig = buf.array(); - for (int i = offset; i < offset + newSize; i++) { - result.put(orig, i * origSize + offset, newSize); - } - - return result; - } + private Basis basis; + private List modulators = new ArrayList<>(); + private float scale; + + public FilteredBasis() {} + + public FilteredBasis(Basis basis) { + this.basis = basis; + } + + public Basis getBasis() { + return this.basis; + } + + public void setBasis(Basis basis) { + this.basis = basis; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { + return data; + } + + @Override + public void init() { + this.basis.init(); + } + + @Override + public Basis setScale(float scale) { + this.scale = scale; + return this; + } + + @Override + public float getScale() { + return this.scale; + } + + @Override + public Basis addModulator(Modulator modulator) { + this.modulators.add(modulator); + return this; + } + + @Override + public float value(float x, float y, float z) { + throw new UnsupportedOperationException( + "Method value cannot be called on FilteredBasis and its descendants. Use getBuffer instead!"); + } + + @Override + public FloatBuffer getBuffer(float sx, float sy, float base, int size) { + int margin = this.getMargin(size, 0); + int workSize = size + 2 * margin; + FloatBuffer retval = this.basis.getBuffer(sx - margin, sy - margin, base, workSize); + return this.clip(this.doFilter(sx, sy, base, retval, workSize), workSize, size, margin); + } + + public FloatBuffer clip(FloatBuffer buf, int origSize, int newSize, int offset) { + FloatBuffer result = FloatBuffer.allocate(newSize * newSize); + + float[] orig = buf.array(); + for (int i = offset; i < offset + newSize; i++) { + result.put(orig, i * origSize + offset, newSize); + } + + return result; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java index 9233ef87af..404c6613c4 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/ImprovedNoise.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -38,90 +38,90 @@ */ public final class ImprovedNoise extends Noise { - @Override - public void init() { + @Override + public void init() { - } + } - static public float noise(float x, float y, float z) { - int X = ShaderUtils.floor(x), // FIND UNIT CUBE THAT - Y = ShaderUtils.floor(y), // CONTAINS POINT. - Z = ShaderUtils.floor(z); - x -= X; // FIND RELATIVE X,Y,Z - y -= Y; // OF POINT IN CUBE. - z -= Z; - X = X & 255; - Y = Y & 255; - Z = Z & 255; - float u = ImprovedNoise.fade(x), // COMPUTE FADE CURVES - v = ImprovedNoise.fade(y), // FOR EACH OF X,Y,Z. - w = ImprovedNoise.fade(z); - int A = ImprovedNoise.p[X] + Y; - int AA = ImprovedNoise.p[A] + Z; - int AB = ImprovedNoise.p[A + 1] + Z; - int B = ImprovedNoise.p[X + 1] + Y; - int BA = ImprovedNoise.p[B] + Z; - int BB = ImprovedNoise.p[B + 1] + Z; + static public float noise(float x, float y, float z) { + int X = ShaderUtils.floor(x), // FIND UNIT CUBE THAT + Y = ShaderUtils.floor(y), // CONTAINS POINT. + Z = ShaderUtils.floor(z); + x -= X; // FIND RELATIVE X,Y,Z + y -= Y; // OF POINT IN CUBE. + z -= Z; + X = X & 255; + Y = Y & 255; + Z = Z & 255; + float u = ImprovedNoise.fade(x), // COMPUTE FADE CURVES + v = ImprovedNoise.fade(y), // FOR EACH OF X,Y,Z. + w = ImprovedNoise.fade(z); + int A = ImprovedNoise.p[X] + Y; + int AA = ImprovedNoise.p[A] + Z; + int AB = ImprovedNoise.p[A + 1] + Z; + int B = ImprovedNoise.p[X + 1] + Y; + int BA = ImprovedNoise.p[B] + Z; + int BB = ImprovedNoise.p[B + 1] + Z; - return ImprovedNoise.lerp( - w, - ImprovedNoise.lerp( - v, - ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA], x, y, z), - ImprovedNoise.grad3(ImprovedNoise.p[BA], x - 1, y, z)), // BLENDED - ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB], x, y - 1, z), // RESULTS - ImprovedNoise.grad3(ImprovedNoise.p[BB], x - 1, y - 1, z))),// FROM - ImprovedNoise.lerp(v, - ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA + 1], x, y, z - 1), // CORNERS - ImprovedNoise.grad3(ImprovedNoise.p[BA + 1], x - 1, y, z - 1)), // OF - ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB + 1], x, y - 1, z - 1), - ImprovedNoise.grad3(ImprovedNoise.p[BB + 1], x - 1, y - 1, z - 1)))); - } + return ImprovedNoise.lerp( + w, + ImprovedNoise.lerp( + v, + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA], x, y, z), + ImprovedNoise.grad3(ImprovedNoise.p[BA], x - 1, y, z)), // BLENDED + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB], x, y - 1, z), // RESULTS + ImprovedNoise.grad3(ImprovedNoise.p[BB], x - 1, y - 1, z))),// FROM + ImprovedNoise.lerp(v, + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AA + 1], x, y, z - 1), // CORNERS + ImprovedNoise.grad3(ImprovedNoise.p[BA + 1], x - 1, y, z - 1)), // OF + ImprovedNoise.lerp(u, ImprovedNoise.grad3(ImprovedNoise.p[AB + 1], x, y - 1, z - 1), + ImprovedNoise.grad3(ImprovedNoise.p[BB + 1], x - 1, y - 1, z - 1)))); + } - static final float fade(final float t) { - return t * t * t * (t * (t * 6 - 15) + 10); - } + static final float fade(final float t) { + return t * t * t * (t * (t * 6 - 15) + 10); + } - static final float lerp(final float t, final float a, final float b) { - return a + t * (b - a); - } + static final float lerp(final float t, final float a, final float b) { + return a + t * (b - a); + } - static float grad(final int hash, final float x, final float y, final float z) { - int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE - float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. - v = h < 4 ? y : h == 12 || h == 14 ? x : z; - return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); - } + static float grad(final int hash, final float x, final float y, final float z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + float u = h < 8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h < 4 ? y : h == 12 || h == 14 ? x : z; + return ((h & 1) == 0 ? u : -u) + ((h & 2) == 0 ? v : -v); + } - static final float grad3(final int hash, final float x, final float y, final float z) { - int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE - return x * ImprovedNoise.GRAD3[h][0] + y * ImprovedNoise.GRAD3[h][1] + z * ImprovedNoise.GRAD3[h][2]; - } + static final float grad3(final int hash, final float x, final float y, final float z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + return x * ImprovedNoise.GRAD3[h][0] + y * ImprovedNoise.GRAD3[h][1] + z * ImprovedNoise.GRAD3[h][2]; + } - static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, - 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, - 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, - 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, - 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, - 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, - 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, - 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, - 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, - 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; + static final int p[] = new int[512], permutation[] = { 151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, + 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, + 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175, 74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, + 231, 83, 111, 229, 122, 60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54, 65, 25, 63, 161, 1, 216, 80, + 73, 209, 76, 132, 187, 208, 89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64, 52, 217, + 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, + 170, 213, 119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9, 129, 22, 39, 253, 19, 98, 108, 110, 79, 113, + 224, 232, 178, 185, 112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241, 81, 51, 145, 235, + 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157, 184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, + 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180 }; - private static float[][] GRAD3 = new float[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, - { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, -1, 1 }, - { 0, 1, 1 } }; + private static float[][] GRAD3 = new float[][] { { 1, 1, 0 }, { -1, 1, 0 }, { 1, -1, 0 }, { -1, -1, 0 }, { 1, 0, 1 }, { -1, 0, 1 }, + { 1, 0, -1 }, { -1, 0, -1 }, { 0, 1, 1 }, { 0, -1, 1 }, { 0, 1, -1 }, { 0, -1, -1 }, { 1, 0, -1 }, { -1, 0, -1 }, { 0, -1, 1 }, + { 0, 1, 1 } }; - static { - for (int i = 0; i < 256; i++) { - ImprovedNoise.p[256 + i] = ImprovedNoise.p[i] = ImprovedNoise.permutation[i]; - } - } + static { + for (int i = 0; i < 256; i++) { + ImprovedNoise.p[256 + i] = ImprovedNoise.p[i] = ImprovedNoise.permutation[i]; + } + } - @Override - public float value(final float x, final float y, final float z) { - return ImprovedNoise.noise(this.scale * x, this.scale * y, this.scale * z); - } + @Override + public float value(final float x, final float y, final float z) { + return ImprovedNoise.noise(this.scale * x, this.scale * y, this.scale * z); + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java index 0ceaa24ac9..ffbf66473a 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/Noise.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -44,50 +44,50 @@ */ public abstract class Noise implements Basis { - protected List modulators = new ArrayList(); + protected List modulators = new ArrayList<>(); - protected float scale = 1.0f; + protected float scale = 1.0f; - @Override - public String toString() { - return this.getClass().getSimpleName(); - } + @Override + public String toString() { + return this.getClass().getSimpleName(); + } - @Override - public FloatBuffer getBuffer(float sx, float sy, float base, int size) { - FloatBuffer retval = FloatBuffer.allocate(size * size); - for (int y = 0; y < size; y++) { - for (int x = 0; x < size; x++) { - retval.put(this.modulate((sx + x) / size, (sy + y) / size, base)); - } - } - return retval; - } + @Override + public FloatBuffer getBuffer(float sx, float sy, float base, int size) { + FloatBuffer retval = FloatBuffer.allocate(size * size); + for (int y = 0; y < size; y++) { + for (int x = 0; x < size; x++) { + retval.put(this.modulate((sx + x) / size, (sy + y) / size, base)); + } + } + return retval; + } - public float modulate(float x, float y, float z) { - float retval = this.value(x, y, z); - for (Modulator m : this.modulators) { - if (m instanceof NoiseModulator) { - retval = m.value(retval); - } - } - return retval; - } + public float modulate(float x, float y, float z) { + float retval = this.value(x, y, z); + for (Modulator m : this.modulators) { + if (m instanceof NoiseModulator) { + retval = m.value(retval); + } + } + return retval; + } - @Override - public Basis addModulator(Modulator modulator) { - this.modulators.add(modulator); - return this; - } + @Override + public Basis addModulator(Modulator modulator) { + this.modulators.add(modulator); + return this; + } - @Override - public Basis setScale(float scale) { - this.scale = scale; - return this; - } + @Override + public Basis setScale(float scale) { + this.scale = scale; + return this; + } - @Override - public float getScale() { - return this.scale; - } + @Override + public float getScale() { + return this.scale; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java index 8a547a3b9a..7f9afd3f4a 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/basis/NoiseAggregator.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -40,25 +40,25 @@ */ public class NoiseAggregator extends Noise { - private final float rate; - private final Basis a; - private final Basis b; + private final float rate; + private final Basis a; + private final Basis b; - public NoiseAggregator(final Basis a, final Basis b, final float rate) { - this.a = a; - this.b = b; - this.rate = rate; - } + public NoiseAggregator(final Basis a, final Basis b, final float rate) { + this.a = a; + this.b = b; + this.rate = rate; + } - @Override - public void init() { - this.a.init(); - this.b.init(); - } + @Override + public void init() { + this.a.init(); + this.b.init(); + } - @Override - public float value(final float x, final float y, final float z) { - return this.a.value(x, y, z) * (1 - this.rate) + this.rate * this.b.value(x, y, z); - } + @Override + public float value(final float x, final float y, final float z) { + return this.a.value(x, y, z) * (1 - this.rate) + this.rate * this.b.value(x, y, z); + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java index 02715d5b0a..bdc1b3b363 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/AbstractFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -36,64 +36,64 @@ public abstract class AbstractFilter implements Filter { - protected List preFilters = new ArrayList(); - protected List postFilters = new ArrayList(); + protected List preFilters = new ArrayList<>(); + protected List postFilters = new ArrayList<>(); - private boolean enabled = true; + private boolean enabled = true; - @Override - public Filter addPreFilter(Filter filter) { - this.preFilters.add(filter); - return this; - } + @Override + public Filter addPreFilter(Filter filter) { + this.preFilters.add(filter); + return this; + } - @Override - public Filter addPostFilter(Filter filter) { - this.postFilters.add(filter); - return this; - } + @Override + public Filter addPostFilter(Filter filter) { + this.postFilters.add(filter); + return this; + } - @Override - public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size) { - if (!this.isEnabled()) { - return data; - } - FloatBuffer retval = data; - for (Filter f : this.preFilters) { - retval = f.doFilter(sx, sy, base, retval, size); - } - retval = this.filter(sx, sy, base, retval, size); - for (Filter f : this.postFilters) { - retval = f.doFilter(sx, sy, base, retval, size); - } - return retval; - } + @Override + public FloatBuffer doFilter(float sx, float sy, float base, FloatBuffer data, int size) { + if (!this.isEnabled()) { + return data; + } + FloatBuffer retval = data; + for (Filter f : this.preFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + retval = this.filter(sx, sy, base, retval, size); + for (Filter f : this.postFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + return retval; + } - public abstract FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size); + public abstract FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size); - @Override - public int getMargin(int size, int margin) { - // TODO sums up all the margins from filters... maybe there's a more - // efficient algorithm - if (!this.isEnabled()) { - return margin; - } - for (Filter f : this.preFilters) { - margin = f.getMargin(size, margin); - } - for (Filter f : this.postFilters) { - margin = f.getMargin(size, margin); - } - return margin; - } + @Override + public int getMargin(int size, int margin) { + // TODO sums up all the margins from filters... maybe there's a more + // efficient algorithm + if (!this.isEnabled()) { + return margin; + } + for (Filter f : this.preFilters) { + margin = f.getMargin(size, margin); + } + for (Filter f : this.postFilters) { + margin = f.getMargin(size, margin); + } + return margin; + } - @Override - public boolean isEnabled() { - return this.enabled; - } + @Override + public boolean isEnabled() { + return this.enabled; + } - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java index ec23d21eb9..d4b3ccc584 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/HydraulicErodeFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011-2018, Novyon Events * * All rights reserved. @@ -34,120 +34,118 @@ public class HydraulicErodeFilter extends AbstractFilter { - private Basis waterMap; - private Basis sedimentMap; - private float Kr; - private float Ks; - private float Ke; - private float Kc; - private float T; - - public void setKc(float kc) { - this.Kc = kc; - } - - public void setKe(float ke) { - this.Ke = ke; - } - - public void setKr(float kr) { - this.Kr = kr; - } - - public void setKs(float ks) { - this.Ks = ks; - } - - public void setSedimentMap(Basis sedimentMap) { - this.sedimentMap = sedimentMap; - } - - public void setT(float t) { - this.T = t; - } - - public void setWaterMap(Basis waterMap) { - this.waterMap = waterMap; - } - - @Override - public int getMargin(int size, int margin) { - return super.getMargin(size, margin) + 1; - } - - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { - float[] ga = buffer.array(); - // float[] wa = this.waterMap.getBuffer(sx, sy, base, workSize).array(); - // float[] sa = this.sedimentMap.getBuffer(sx, sy, base, - // workSize).array(); - float[] wt = new float[workSize * workSize]; - float[] st = new float[workSize * workSize]; - - int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; - - // step 1. water arrives and step 2. captures material - for (int y = 0; y < workSize; y++) { - for (int x = 0; x < workSize; x++) { - int idx = y * workSize + x; - float wtemp = this.Kr; // * wa[idx]; - float stemp = this.Ks; // * sa[idx]; - if (wtemp > 0) { - wt[idx] += wtemp; - if (stemp > 0) { - ga[idx] -= stemp * wt[idx]; - st[idx] += stemp * wt[idx]; - } - } - - // step 3. water is transported to its neighbours - float a = ga[idx] + wt[idx]; - // float[] aj = new float[idxrel.length]; - float amax = 0; - int amaxidx = -1; - float ac = 0; - float dtotal = 0; - - for (int j = 0; j < idxrel.length; j++) { - if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize) { - float at = ga[idx + idxrel[j]] + wt[idx + idxrel[j]]; - if (a - at > a - amax) { - dtotal += at; - amax = at; - amaxidx = j; - ac++; - } - } - } - - float aa = (dtotal + a) / (ac + 1); - // for (int j = 0; j < idxrel.length; j++) { - // if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize && a - - // aj[j] > 0) { - if (amaxidx > -1) { - float dwj = Math.min(wt[idx], a - aa) * (a - amax) / dtotal; - float dsj = st[idx] * dwj / wt[idx]; - wt[idx] -= dwj; - st[idx] -= dsj; - wt[idx + idxrel[amaxidx]] += dwj; - st[idx + idxrel[amaxidx]] += dsj; - } - // } - - // step 4. water evaporates and deposits material - wt[idx] = wt[idx] * (1 - this.Ke); - if (wt[idx] < this.T) { - wt[idx] = 0; - } - float smax = this.Kc * wt[idx]; - if (st[idx] > smax) { - ga[idx] += st[idx] - smax; - st[idx] -= st[idx] - smax; - } - } - } - - return buffer; - } + private float Kr; + private float Ks; + private float Ke; + private float Kc; + private float T; + + public void setKc(float kc) { + this.Kc = kc; + } + + public void setKe(float ke) { + this.Ke = ke; + } + + public void setKr(float kr) { + this.Kr = kr; + } + + public void setKs(float ks) { + this.Ks = ks; + } + + public void setSedimentMap(Basis sedimentMap) { + // not implemented + } + + public void setT(float t) { + this.T = t; + } + + public void setWaterMap(Basis waterMap) { + // not implemented + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + 1; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { + float[] ga = buffer.array(); + // float[] wa = this.waterMap.getBuffer(sx, sy, base, workSize).array(); + // float[] sa = this.sedimentMap.getBuffer(sx, sy, base, + // workSize).array(); + float[] wt = new float[workSize * workSize]; + float[] st = new float[workSize * workSize]; + + int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; + + // step 1. water arrives and step 2. captures material + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + int idx = y * workSize + x; + float wtemp = this.Kr; // * wa[idx]; + float stemp = this.Ks; // * sa[idx]; + if (wtemp > 0) { + wt[idx] += wtemp; + if (stemp > 0) { + ga[idx] -= stemp * wt[idx]; + st[idx] += stemp * wt[idx]; + } + } + + // step 3. water is transported to its neighbours + float a = ga[idx] + wt[idx]; + // float[] aj = new float[idxrel.length]; + float amax = 0; + int amaxidx = -1; + float ac = 0; + float dtotal = 0; + + for (int j = 0; j < idxrel.length; j++) { + if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize) { + float at = ga[idx + idxrel[j]] + wt[idx + idxrel[j]]; + if (a - at > a - amax) { + dtotal += at; + amax = at; + amaxidx = j; + ac++; + } + } + } + + float aa = (dtotal + a) / (ac + 1); + // for (int j = 0; j < idxrel.length; j++) { + // if (idx + idxrel[j] > 0 && idx + idxrel[j] < workSize && a - + // aj[j] > 0) { + if (amaxidx > -1) { + float dwj = Math.min(wt[idx], a - aa) * (a - amax) / dtotal; + float dsj = st[idx] * dwj / wt[idx]; + wt[idx] -= dwj; + st[idx] -= dsj; + wt[idx + idxrel[amaxidx]] += dwj; + st[idx + idxrel[amaxidx]] += dsj; + } + // } + + // step 4. water evaporates and deposits material + wt[idx] = wt[idx] * (1 - this.Ke); + if (wt[idx] < this.T) { + wt[idx] = 0; + } + float smax = this.Kc * wt[idx]; + if (st[idx] > smax) { + ga[idx] += st[idx] - smax; + st[idx] -= st[idx] - smax; + } + } + } + + return buffer; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java index 60bc512497..57de380e44 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/IterativeFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -36,66 +36,66 @@ public class IterativeFilter extends AbstractFilter { - private int iterations; + private int iterations; - private List preIterateFilters = new ArrayList(); - private List postIterateFilters = new ArrayList(); - private Filter filter; + private List preIterateFilters = new ArrayList<>(); + private List postIterateFilters = new ArrayList<>(); + private Filter filter; - @Override - public int getMargin(int size, int margin) { - if (!this.isEnabled()) { - return margin; - } - for (Filter f : this.preIterateFilters) { - margin = f.getMargin(size, margin); - } - margin = this.filter.getMargin(size, margin); - for (Filter f : this.postIterateFilters) { - margin = f.getMargin(size, margin); - } - return this.iterations * margin + super.getMargin(size, margin); - } + @Override + public int getMargin(int size, int margin) { + if (!this.isEnabled()) { + return margin; + } + for (Filter f : this.preIterateFilters) { + margin = f.getMargin(size, margin); + } + margin = this.filter.getMargin(size, margin); + for (Filter f : this.postIterateFilters) { + margin = f.getMargin(size, margin); + } + return this.iterations * margin + super.getMargin(size, margin); + } - public void setIterations(int iterations) { - this.iterations = iterations; - } + public void setIterations(int iterations) { + this.iterations = iterations; + } - public int getIterations() { - return this.iterations; - } + public int getIterations() { + return this.iterations; + } - public IterativeFilter addPostIterateFilter(Filter filter) { - this.postIterateFilters.add(filter); - return this; - } + public IterativeFilter addPostIterateFilter(Filter filter) { + this.postIterateFilters.add(filter); + return this; + } - public IterativeFilter addPreIterateFilter(Filter filter) { - this.preIterateFilters.add(filter); - return this; - } + public IterativeFilter addPreIterateFilter(Filter filter) { + this.preIterateFilters.add(filter); + return this; + } - public void setFilter(Filter filter) { - this.filter = filter; - } + public void setFilter(Filter filter) { + this.filter = filter; + } - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { - if (!this.isEnabled()) { - return data; - } - FloatBuffer retval = data; + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int size) { + if (!this.isEnabled()) { + return data; + } + FloatBuffer retval = data; - for (int i = 0; i < this.iterations; i++) { - for (Filter f : this.preIterateFilters) { - retval = f.doFilter(sx, sy, base, retval, size); - } - retval = this.filter.doFilter(sx, sy, base, retval, size); - for (Filter f : this.postIterateFilters) { - retval = f.doFilter(sx, sy, base, retval, size); - } - } + for (int i = 0; i < this.iterations; i++) { + for (Filter f : this.preIterateFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + retval = this.filter.doFilter(sx, sy, base, retval, size); + for (Filter f : this.postIterateFilters) { + retval = f.doFilter(sx, sy, base, retval, size); + } + } - return retval; - } + return retval; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java index fc20da6018..2173646461 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/OptimizedErode.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -33,81 +33,81 @@ public class OptimizedErode extends AbstractFilter { - private float talus; - private int radius; - - public OptimizedErode setRadius(int radius) { - this.radius = radius; - return this; - } - - public int getRadius() { - return this.radius; - } - - public OptimizedErode setTalus(float talus) { - this.talus = talus; - return this; - } - - public float getTalus() { - return this.talus; - } - - @Override - public int getMargin(int size, int margin) { - return super.getMargin(size, margin) + this.radius; - } - - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { - float[] tmp = buffer.array(); - float[] retval = new float[tmp.length]; - - for (int y = this.radius + 1; y < size - this.radius; y++) { - for (int x = this.radius + 1; x < size - this.radius; x++) { - int idx = y * size + x; - float h = tmp[idx]; - - float horizAvg = 0; - int horizCount = 0; - float vertAvg = 0; - int vertCount = 0; - - boolean horizT = false; - boolean vertT = false; - - for (int i = 0; i >= -this.radius; i--) { - int idxV = (y + i) * size + x; - int idxVL = (y + i - 1) * size + x; - int idxH = y * size + x + i; - int idxHL = y * size + x + i - 1; - float hV = tmp[idxV]; - float hH = tmp[idxH]; - - if (Math.abs(h - hV) > this.talus && Math.abs(h - tmp[idxVL]) > this.talus || vertT) { - vertT = true; - } else { - if (Math.abs(h - hV) <= this.talus) { - vertAvg += hV; - vertCount++; - } - } - - if (Math.abs(h - hH) > this.talus && Math.abs(h - tmp[idxHL]) > this.talus || horizT) { - horizT = true; - } else { - if (Math.abs(h - hH) <= this.talus) { - horizAvg += hH; - horizCount++; - } - } - } - - retval[idx] = 0.5f * (vertAvg / (vertCount > 0 ? vertCount : 1) + horizAvg / (horizCount > 0 ? horizCount : 1)); - } - } - return FloatBuffer.wrap(retval); - } + private float talus; + private int radius; + + public OptimizedErode setRadius(int radius) { + this.radius = radius; + return this; + } + + public int getRadius() { + return this.radius; + } + + public OptimizedErode setTalus(float talus) { + this.talus = talus; + return this; + } + + public float getTalus() { + return this.talus; + } + + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + this.radius; + } + + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { + float[] tmp = buffer.array(); + float[] retval = new float[tmp.length]; + + for (int y = this.radius + 1; y < size - this.radius; y++) { + for (int x = this.radius + 1; x < size - this.radius; x++) { + int idx = y * size + x; + float h = tmp[idx]; + + float horizAvg = 0; + int horizCount = 0; + float vertAvg = 0; + int vertCount = 0; + + boolean horizT = false; + boolean vertT = false; + + for (int i = 0; i >= -this.radius; i--) { + int idxV = (y + i) * size + x; + int idxVL = (y + i - 1) * size + x; + int idxH = y * size + x + i; + int idxHL = y * size + x + i - 1; + float hV = tmp[idxV]; + float hH = tmp[idxH]; + + if (Math.abs(h - hV) > this.talus && Math.abs(h - tmp[idxVL]) > this.talus || vertT) { + vertT = true; + } else { + if (Math.abs(h - hV) <= this.talus) { + vertAvg += hV; + vertCount++; + } + } + + if (Math.abs(h - hH) > this.talus && Math.abs(h - tmp[idxHL]) > this.talus || horizT) { + horizT = true; + } else { + if (Math.abs(h - hH) <= this.talus) { + horizAvg += hH; + horizCount++; + } + } + } + + retval[idx] = 0.5f * (vertAvg / (vertCount > 0 ? vertCount : 1) + horizAvg / (horizCount > 0 ? horizCount : 1)); + } + } + return FloatBuffer.wrap(retval); + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java index 782f384aa0..ac2d47044b 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/PerturbFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -36,62 +36,62 @@ public class PerturbFilter extends AbstractFilter { - private float magnitude; + private float magnitude; - @Override - public int getMargin(int size, int margin) { - margin = super.getMargin(size, margin); - return (int) Math.floor(this.magnitude * (margin + size) + margin); - } + @Override + public int getMargin(int size, int margin) { + margin = super.getMargin(size, margin); + return (int) Math.floor(this.magnitude * (margin + size) + margin); + } - public void setMagnitude(float magnitude) { - this.magnitude = magnitude; - } + public void setMagnitude(float magnitude) { + this.magnitude = magnitude; + } - public float getMagnitude() { - return this.magnitude; - } + public float getMagnitude() { + return this.magnitude; + } - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int workSize) { - float[] arr = data.array(); - int origSize = (int) Math.ceil(workSize / (2 * this.magnitude + 1)); - int offset = (workSize - origSize) / 2; - Logger.getLogger(PerturbFilter.class.getCanonicalName()).info( - "Found origSize : " + origSize + " and offset: " + offset + " for workSize : " + workSize + " and magnitude : " - + this.magnitude); - float[] retval = new float[workSize * workSize]; - float[] perturbx = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base, workSize).array(); - float[] perturby = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base + 1, workSize).array(); - for (int y = 0; y < workSize; y++) { - for (int x = 0; x < workSize; x++) { - // Perturb our coordinates - float noisex = perturbx[y * workSize + x]; - float noisey = perturby[y * workSize + x]; + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer data, int workSize) { + float[] arr = data.array(); + int origSize = (int) Math.ceil(workSize / (2 * this.magnitude + 1)); + int offset = (workSize - origSize) / 2; + Logger.getLogger(PerturbFilter.class.getCanonicalName()).info( + "Found origSize : " + origSize + " and offset: " + offset + " for workSize : " + workSize + " and magnitude : " + + this.magnitude); + float[] retval = new float[workSize * workSize]; + float[] perturbX = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base, workSize).array(); + float[] perturbY = new FractalSum().setOctaves(8).setScale(5f).getBuffer(sx, sy, base + 1, workSize).array(); + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + // Perturb our coordinates + float noiseX = perturbX[y * workSize + x]; + float noiseY = perturbY[y * workSize + x]; - int px = (int) (origSize * noisex * this.magnitude); - int py = (int) (origSize * noisey * this.magnitude); + int px = (int) (origSize * noiseX * this.magnitude); + int py = (int) (origSize * noiseY * this.magnitude); - float c00 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x - px, workSize)]; - float c01 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x + px, workSize)]; - float c10 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x - px, workSize)]; - float c11 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x + px, workSize)]; + float c00 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x - px, workSize)]; + float c01 = arr[this.wrap(y - py, workSize) * workSize + this.wrap(x + px, workSize)]; + float c10 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x - px, workSize)]; + float c11 = arr[this.wrap(y + py, workSize) * workSize + this.wrap(x + px, workSize)]; - float c0 = ShaderUtils.mix(c00, c01, noisex); - float c1 = ShaderUtils.mix(c10, c11, noisex); - retval[y * workSize + x] = ShaderUtils.mix(c0, c1, noisey); - } - } - return FloatBuffer.wrap(retval); - } + float c0 = ShaderUtils.mix(c00, c01, noiseX); + float c1 = ShaderUtils.mix(c10, c11, noiseX); + retval[y * workSize + x] = ShaderUtils.mix(c0, c1, noiseY); + } + } + return FloatBuffer.wrap(retval); + } - private int wrap(int v, int size) { - if (v < 0) { - return v + size - 1; - } else if (v >= size) { - return v - size; - } else { - return v; - } - } + private int wrap(int v, int size) { + if (v < 0) { + return v + size - 1; + } else if (v >= size) { + return v - size; + } else { + return v; + } + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java index 1c20c2451a..558a9eb50f 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/SmoothFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -33,48 +33,48 @@ public class SmoothFilter extends AbstractFilter { - private int radius; - private float effect; + private int radius; + private float effect; - public void setRadius(int radius) { - this.radius = radius; - } + public void setRadius(int radius) { + this.radius = radius; + } - public int getRadius() { - return this.radius; - } + public int getRadius() { + return this.radius; + } - public void setEffect(float effect) { - this.effect = effect; - } + public void setEffect(float effect) { + this.effect = effect; + } - public float getEffect() { - return this.effect; - } + public float getEffect() { + return this.effect; + } - @Override - public int getMargin(int size, int margin) { - return super.getMargin(size, margin) + this.radius; - } + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + this.radius; + } - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { - float[] data = buffer.array(); - float[] retval = new float[data.length]; + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int size) { + float[] data = buffer.array(); + float[] retval = new float[data.length]; - for (int y = this.radius; y < size - this.radius; y++) { - for (int x = this.radius; x < size - this.radius; x++) { - int idx = y * size + x; - float n = 0; - for (int i = -this.radius; i < this.radius + 1; i++) { - for (int j = -this.radius; j < this.radius + 1; j++) { - n += data[(y + i) * size + x + j]; - } - } - retval[idx] = this.effect * n / (4 * this.radius * (this.radius + 1) + 1) + (1 - this.effect) * data[idx]; - } - } + for (int y = this.radius; y < size - this.radius; y++) { + for (int x = this.radius; x < size - this.radius; x++) { + int idx = y * size + x; + float n = 0; + for (int i = -this.radius; i < this.radius + 1; i++) { + for (int j = -this.radius; j < this.radius + 1; j++) { + n += data[(y + i) * size + x + j]; + } + } + retval[idx] = this.effect * n / (4 * this.radius * (this.radius + 1) + 1) + (1 - this.effect) * data[idx]; + } + } - return FloatBuffer.wrap(retval); - } + return FloatBuffer.wrap(retval); + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java index 2e496799ea..c9f04b77e4 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/ThermalErodeFilter.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -33,69 +33,69 @@ public class ThermalErodeFilter extends AbstractFilter { - private float talus; - private float c; + private float talus; + private float c; - public ThermalErodeFilter setC(float c) { - this.c = c; - return this; - } + public ThermalErodeFilter setC(float c) { + this.c = c; + return this; + } - public ThermalErodeFilter setTalus(float talus) { - this.talus = talus; - return this; - } + public ThermalErodeFilter setTalus(float talus) { + this.talus = talus; + return this; + } - @Override - public int getMargin(int size, int margin) { - return super.getMargin(size, margin) + 1; - } + @Override + public int getMargin(int size, int margin) { + return super.getMargin(size, margin) + 1; + } - @Override - public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { - float[] ga = buffer.array(); - float[] sa = new float[workSize * workSize]; + @Override + public FloatBuffer filter(float sx, float sy, float base, FloatBuffer buffer, int workSize) { + float[] ga = buffer.array(); + float[] sa = new float[workSize * workSize]; - int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; + int[] idxrel = { -workSize - 1, -workSize + 1, workSize - 1, workSize + 1 }; - for (int y = 0; y < workSize; y++) { - for (int x = 0; x < workSize; x++) { - int idx = y * workSize + x; - ga[idx] += sa[idx]; - sa[idx] = 0; + for (int y = 0; y < workSize; y++) { + for (int x = 0; x < workSize; x++) { + int idx = y * workSize + x; + ga[idx] += sa[idx]; + sa[idx] = 0; - float[] deltas = new float[idxrel.length]; - float deltaMax = this.talus; - float deltaTotal = 0; + float[] deltas = new float[idxrel.length]; + float deltaMax = this.talus; + float deltaTotal = 0; - for (int j = 0; j < idxrel.length; j++) { - if (idx + idxrel[j] > 0 && idx + idxrel[j] < ga.length) { - float dj = ga[idx] - ga[idx + idxrel[j]]; - if (dj > this.talus) { - deltas[j] = dj; - deltaTotal += dj; - if (dj > deltaMax) { - deltaMax = dj; - } - } - } - } + for (int j = 0; j < idxrel.length; j++) { + if (idx + idxrel[j] > 0 && idx + idxrel[j] < ga.length) { + float dj = ga[idx] - ga[idx + idxrel[j]]; + if (dj > this.talus) { + deltas[j] = dj; + deltaTotal += dj; + if (dj > deltaMax) { + deltaMax = dj; + } + } + } + } - for (int j = 0; j < idxrel.length; j++) { - if (deltas[j] != 0) { - float d = this.c * (deltaMax - this.talus) * deltas[j] / deltaTotal; - if (d > ga[idx] + sa[idx]) { - d = ga[idx] + sa[idx]; - } - sa[idx] -= d; - sa[idx + idxrel[j]] += d; - } - deltas[j] = 0; - } - } - } + for (int j = 0; j < idxrel.length; j++) { + if (deltas[j] != 0) { + float d = this.c * (deltaMax - this.talus) * deltas[j] / deltaTotal; + if (d > ga[idx] + sa[idx]) { + d = ga[idx] + sa[idx]; + } + sa[idx] -= d; + sa[idx + idxrel[j]] += d; + } + deltas[j] = 0; + } + } + } - return buffer; - } + return buffer; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/package-info.java new file mode 100644 index 0000000000..2e0320a23e --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/filter/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * filters to modify terrain + */ +package com.jme3.terrain.noise.filter; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java index 9b53447171..99cb0930a3 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/Fractal.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -34,7 +34,7 @@ /** * Interface for a general fractal basis. * - * Takes any number of basis funcions to work with and a few common parameters + * Takes any number of basis functions to work with and a few common parameters * for noise fractals * * @author Anthyon @@ -42,16 +42,16 @@ */ public interface Fractal extends Basis { - public Fractal setOctaves(final float octaves); + public Fractal setOctaves(final float octaves); - public Fractal setFrequency(final float frequency); + public Fractal setFrequency(final float frequency); - public Fractal setRoughness(final float roughness); + public Fractal setRoughness(final float roughness); - public Fractal setAmplitude(final float amplitude); + public Fractal setAmplitude(final float amplitude); - public Fractal setLacunarity(final float lacunarity); + public Fractal setLacunarity(final float lacunarity); - public Fractal addBasis(Basis basis); + public Fractal addBasis(Basis basis); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java index df2fb235af..a31d03376d 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/FractalSum.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -49,94 +49,94 @@ */ public class FractalSum extends Noise implements Fractal { - private Basis basis; - private float lacunarity; - private float amplitude; - private float roughness; - private float frequency; - private float octaves; - private int maxFreq; - - public FractalSum() { - this.basis = new ImprovedNoise(); - this.lacunarity = 2.124367f; - this.amplitude = 1.0f; - this.roughness = 0.6f; - this.frequency = 1f; - this.setOctaves(1); - } - - @Override - public float value(final float x, final float y, final float z) { - float total = 0; - - for (float f = this.frequency, a = this.amplitude; f < this.maxFreq; f *= this.lacunarity, a *= this.roughness) { - total += this.basis.value(this.scale * x * f, this.scale * y * f, this.scale * z * f) * a; - } - - return ShaderUtils.clamp(total, -1, 1); - } - - @Override - public Fractal addBasis(final Basis basis) { - this.basis = basis; - return this; - } - - public float getOctaves() { - return this.octaves; - } - - @Override - public Fractal setOctaves(final float octaves) { - this.octaves = octaves; - this.maxFreq = 1 << (int) octaves; - return this; - } - - public float getFrequency() { - return this.frequency; - } - - @Override - public Fractal setFrequency(final float frequency) { - this.frequency = frequency; - return this; - } - - public float getRoughness() { - return this.roughness; - } - - @Override - public Fractal setRoughness(final float roughness) { - this.roughness = roughness; - return this; - } - - public float getAmplitude() { - return this.amplitude; - } - - @Override - public Fractal setAmplitude(final float amplitude) { - this.amplitude = amplitude; - return this; - } - - public float getLacunarity() { - return this.lacunarity; - } - - @Override - public Fractal setLacunarity(final float lacunarity) { - this.lacunarity = lacunarity; - return this; - } - - @Override - public void init() { - - } + private Basis basis; + private float lacunarity; + private float amplitude; + private float roughness; + private float frequency; + private float octaves; + private int maxFreq; + + public FractalSum() { + this.basis = new ImprovedNoise(); + this.lacunarity = 2.124367f; + this.amplitude = 1.0f; + this.roughness = 0.6f; + this.frequency = 1f; + this.setOctaves(1); + } + + @Override + public float value(final float x, final float y, final float z) { + float total = 0; + + for (float f = this.frequency, a = this.amplitude; f < this.maxFreq; f *= this.lacunarity, a *= this.roughness) { + total += this.basis.value(this.scale * x * f, this.scale * y * f, this.scale * z * f) * a; + } + + return ShaderUtils.clamp(total, -1, 1); + } + + @Override + public Fractal addBasis(final Basis basis) { + this.basis = basis; + return this; + } + + public float getOctaves() { + return this.octaves; + } + + @Override + public Fractal setOctaves(final float octaves) { + this.octaves = octaves; + this.maxFreq = 1 << (int) octaves; + return this; + } + + public float getFrequency() { + return this.frequency; + } + + @Override + public Fractal setFrequency(final float frequency) { + this.frequency = frequency; + return this; + } + + public float getRoughness() { + return this.roughness; + } + + @Override + public Fractal setRoughness(final float roughness) { + this.roughness = roughness; + return this; + } + + public float getAmplitude() { + return this.amplitude; + } + + @Override + public Fractal setAmplitude(final float amplitude) { + this.amplitude = amplitude; + return this; + } + + public float getLacunarity() { + return this.lacunarity; + } + + @Override + public Fractal setLacunarity(final float lacunarity) { + this.lacunarity = lacunarity; + return this; + } + + @Override + public void init() { + + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/package-info.java new file mode 100644 index 0000000000..b8b4093261 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/fractal/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * use fractals to generate terrain + */ +package com.jme3.terrain.noise.fractal; diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java index ce65cd6f90..1393f08f13 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/CatRom2.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -35,43 +35,43 @@ public class CatRom2 implements Modulator { - private int sampleRate = 100; + private int sampleRate = 100; - private final float[] table; + private final float[] table; - private static Map instances = new HashMap(); + private static Map instances = new HashMap(); - public CatRom2(final int sampleRate) { - this.sampleRate = sampleRate; - this.table = new float[4 * sampleRate + 1]; - for (int i = 0; i < 4 * sampleRate + 1; i++) { - float x = i / (float) sampleRate; - x = (float) Math.sqrt(x); - if (x < 1) { - this.table[i] = 0.5f * (2 + x * x * (-5 + x * 3)); - } else { - this.table[i] = 0.5f * (4 + x * (-8 + x * (5 - x))); - } - } - } + public CatRom2(final int sampleRate) { + this.sampleRate = sampleRate; + this.table = new float[4 * sampleRate + 1]; + for (int i = 0; i < 4 * sampleRate + 1; i++) { + float x = i / (float) sampleRate; + x = (float) Math.sqrt(x); + if (x < 1) { + this.table[i] = 0.5f * (2 + x * x * (-5 + x * 3)); + } else { + this.table[i] = 0.5f * (4 + x * (-8 + x * (5 - x))); + } + } + } - public static CatRom2 getInstance(final int sampleRate) { - if (!CatRom2.instances.containsKey(sampleRate)) { - CatRom2.instances.put(sampleRate, new CatRom2(sampleRate)); - } - return CatRom2.instances.get(sampleRate); - } + public static CatRom2 getInstance(final int sampleRate) { + if (!CatRom2.instances.containsKey(sampleRate)) { + CatRom2.instances.put(sampleRate, new CatRom2(sampleRate)); + } + return CatRom2.instances.get(sampleRate); + } - @Override - public float value(final float... in) { - if (in[0] >= 4) { - return 0; - } - in[0] = in[0] * this.sampleRate + 0.5f; - int i = ShaderUtils.floor(in[0]); - if (i >= 4 * this.sampleRate + 1) { - return 0; - } - return this.table[i]; - } + @Override + public float value(final float... in) { + if (in[0] >= 4) { + return 0; + } + in[0] = in[0] * this.sampleRate + 0.5f; + int i = ShaderUtils.floor(in[0]); + if (i >= 4 * this.sampleRate + 1) { + return 0; + } + return this.table[i]; + } } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java index 28bfd1c926..9355938ad6 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/Modulator.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. @@ -31,6 +31,6 @@ public interface Modulator { - public float value(float... in); + public float value(float... in); } diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java index 38a3bd6749..50cc44b205 100644 --- a/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java +++ b/jme3-terrain/src/main/java/com/jme3/terrain/noise/modulator/NoiseModulator.java @@ -1,4 +1,4 @@ -/** +/* * Copyright (c) 2011, Novyon Events * * All rights reserved. diff --git a/jme3-terrain/src/main/java/com/jme3/terrain/package-info.java b/jme3-terrain/src/main/java/com/jme3/terrain/package-info.java new file mode 100644 index 0000000000..33ea9bd484 --- /dev/null +++ b/jme3-terrain/src/main/java/com/jme3/terrain/package-info.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2021 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. + */ +/** + * the terrain system + */ +package com.jme3.terrain; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag new file mode 100644 index 0000000000..9cad93f886 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.frag @@ -0,0 +1,164 @@ +#extension GL_EXT_texture_array : enable +#import "Common/ShaderLib/GLSLCompat.glsllib" + +#define ENABLE_PBRLightingUtils_getWorldPosition 1 +#define ENABLE_PBRLightingUtils_getLocalPosition 1 +#define ENABLE_PBRLightingUtils_getWorldNormal 1 +#define ENABLE_PBRLightingUtils_getTexCoord 1 +#define ENABLE_PBRLightingUtils_computeDirectLightContribution 1 +#define ENABLE_PBRLightingUtils_computeProbesContribution 1 + +#define ENABLE_PBRTerrainUtils_readPBRTerrainLayers 1 + +#import "Common/ShaderLib/module/pbrlighting/PBRLightingUtils.glsllib" +#import "Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib" +#ifdef AFFLICTIONTEXTURE + #import "Common/MatDefs/Terrain/Modular/AfflictionLib.glsllib" +#endif + +//declare PBR Lighting vars +uniform vec4 g_LightData[NB_LIGHTS]; +uniform vec3 g_CameraPosition; + +#ifdef DEBUG_VALUES_MODE + uniform int m_DebugValuesMode; +#endif + +#ifdef USE_FOG + #import "Common/ShaderLib/MaterialFog.glsllib" +#endif + +void main(){ + vec3 wpos = PBRLightingUtils_getWorldPosition(); + vec3 worldViewDir = normalize(g_CameraPosition - wpos); + + // Create a blank PBRSurface. + PBRSurface surface = PBRLightingUtils_createPBRSurface(worldViewDir); + + //pre-calculate necessary values for tri-planar blending + TriPlanarUtils_calculateBlending(surface.geometryNormal); + + //reads terrain alphaMaps + PBRTerrainUtils_readAlphaMaps(); + + //CUSTOM LIB EXAMPLE: + #ifdef AFFLICTIONTEXTURE + AfflictionLib_readAfflictionVector(); + #endif + + // read and blend up to 12 texture layers + #for i=0..12 (#ifdef ALBEDOMAP_$i $0 #endif) + + PBRTerrainTextureLayer terrainTextureLayer_$i = PBRTerrainUtils_createAdvancedPBRTerrainLayer($i, surface.geometryNormal); + + #ifdef USE_FIRST_LAYER_AS_TRANSPARENCY + if($i == 0){ + if(terrainTextureLayer_$i.blendValue > 0.01f){ + discard; + } + } + #endif + + terrainTextureLayer_$i.roughness = m_Roughness_$i; + terrainTextureLayer_$i.metallic = m_Metallic_$i; + terrainTextureLayer_$i.emission = m_EmissiveColor_$i; + + #ifdef USE_TEXTURE_ARRAYS + #if defined(TRI_PLANAR_MAPPING) || defined(TRI_PLANAR_MAPPING_$i) + //triplanar for texture arrays: + PBRTerrainUtils_readTriPlanarAlbedoTexArray(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i); + #ifdef NORMALMAP_$i + PBRTerrainUtils_readTriPlanarNormalTexArray(m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i); + #endif + #ifdef METALLICROUGHNESSMAP_$i + PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i); + #endif + #else + //non tri-planar for texture arrays: + PBRTerrainUtils_readAlbedoTexArray(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, m_AlbedoTextureArray, terrainTextureLayer_$i); + #ifdef NORMALMAP_$i + PBRTerrainUtils_readNormalTexArray(m_NormalMap_$i, m_AlbedoMap_$i_scale, m_NormalParallaxTextureArray, terrainTextureLayer_$i); + #endif + #ifdef METALLICROUGHNESSMAP_$i + PBRTerrainUtils_readMetallicRoughnessAoEiTexArray(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, m_MetallicRoughnessAoEiTextureArray, terrainTextureLayer_$i); + #endif + #endif + #else + #if defined(TRI_PLANAR_MAPPING) || defined(TRI_PLANAR_MAPPING_$i) + //triplanar texture reads: + PBRTerrainUtils_readTriPlanarAlbedoTexture(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #ifdef NORMALMAP_$i + PBRTerrainUtils_readTriPlanarNormalTexture(m_NormalMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #endif + #ifdef METALLICROUGHNESSMAP_$i + PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexture(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #endif + #else + //non tri-planar texture reads: + PBRTerrainUtils_readAlbedoTexture(m_AlbedoMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #ifdef NORMALMAP_$i + PBRTerrainUtils_readNormalTexture(m_NormalMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #endif + #ifdef METALLICROUGHNESSMAP_$i + PBRTerrainUtils_readMetallicRoughnessAoEiTexture(m_MetallicRoughnessMap_$i, m_AlbedoMap_$i_scale, terrainTextureLayer_$i); + #endif + #endif + #endif + + //CUSTOM LIB EXAMPLE: uses a custom alpha map to desaturate albedo color for a color-removal effect + #ifdef AFFLICTIONTEXTURE + afflictionMode = m_AfflictionMode_$i; + terrainTextureLayer_$i.albedo.rgb = alterLiveliness(terrainTextureLayer_$i.albedo.rgb, livelinessValue, afflictionMode); //changes saturation of albedo for this layer; does nothing if not using AfflictionAlphaMap for affliction splatting + #endif + + //blends this layer + PBRTerrainUtils_blendPBRTerrainLayer(surface, terrainTextureLayer_$i); + #endfor + + #ifdef DISCARD_ALPHA + if(surface.alpha < m_AlphaDiscardThreshold){ + discard; + } + #endif + + PBRLightingUtils_readSunLightExposureParams(surface); + + //CUSTOM LIB EXAMPLE: uses a custom alpha map and noise to blend an extra splat layer overtop of all other layers + #ifdef AFFLICTIONTEXTURE + AfflictionLib_blendSplatLayers(surface); + #endif + + //Calculate necessary variables in pbr surface prior to applying lighting. Ensure all texture/param reading and blending occurrs prior to this being called! + PBRLightingUtils_calculatePreLightingValues(surface); + + // Calculate direct lights + for(int i = 0;i < NB_LIGHTS; i+=3){ + vec4 lightData0 = g_LightData[i]; + vec4 lightData1 = g_LightData[i+1]; + vec4 lightData2 = g_LightData[i+2]; + PBRLightingUtils_computeDirectLightContribution( + lightData0, lightData1, lightData2, + surface + ); + } + + // Calculate env probes + PBRLightingUtils_computeProbesContribution(surface); + + // Put it all together + gl_FragColor.rgb = vec3(0.0); + gl_FragColor.rgb += surface.bakedLightContribution; + gl_FragColor.rgb += surface.directLightContribution; + gl_FragColor.rgb += surface.envLightContribution; + gl_FragColor.rgb += surface.emission; + gl_FragColor.a = surface.alpha; + + #ifdef USE_FOG + gl_FragColor = MaterialFog_calculateFogColor(vec4(gl_FragColor)); + #endif + + //outputs the final value of the selected layer as a color for debug purposes. + #ifdef DEBUG_VALUES_MODE + gl_FragColor = PBRLightingUtils_getColorOutputForDebugMode(m_DebugValuesMode, vec4(gl_FragColor.rgba), surface); + #endif +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md new file mode 100644 index 0000000000..2098bd77f3 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/AdvancedPBRTerrain.j3md @@ -0,0 +1,443 @@ +MaterialDef AdvancedPBRTerrain { + + MaterialParameters { + Int BoundDrawBuffer + + Texture2D SunLightExposureMap + Boolean UseVertexColorsAsSunExposure //set true to make the vertex color's R channel how exposed a vertex is to the sun + Float StaticSunExposure //used for setting the sun exposure value for a whole material + //these are usually generated at run time or setup in a level editor per-geometry, so that models indoors can have the DirectionalLight dimmed accordingly. + + Boolean BrightenIndoorShadows //set true if shadows are enabled and indoor areas without full sun exposure are too dark compared to when shadows are turned off in settings + + Boolean UseFirstLayerAsTransparency + + TextureArray AlbedoTextureArray + TextureArray NormalParallaxTextureArray -LINEAR + TextureArray MetallicRoughnessAoEiTextureArray -LINEAR + + //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl) + Float NormalType : -1.0 + + // Specular-AA + Boolean UseSpecularAA : true + // screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing. + Float SpecularAASigma + // clamping threshold,Use the slider to set a maximum value for the offset that HDRP subtracts from the smoothness value to reduce artifacts. + Float SpecularAAKappa + + Int AfflictionSplatScale : 8 + Float AfflictionRoughnessValue : 1.0 + Float AfflictionMetallicValue : 0.0 + Float AfflictionEmissiveValue : 0.0 //note that this is simplified into one value, rather than 2 with power and intensity like the regular pbr values. + + // affliction texture splatting & desaturation functionality + Boolean UseTriplanarAfflictionMapping + + Texture2D AfflictionAlphaMap + + Texture2D SplatAlbedoMap -LINEAR + Texture2D SplatNormalMap -LINEAR + Texture2D SplatRoughnessMetallicMap -LINEAR + Texture2D SplatEmissiveMap -LINEAR + + Color AfflictionEmissiveColor : 0.0 0.0 0.0 0.0 + + Float SplatNoiseVar + + Int AfflictionMode_0 : 1 + Int AfflictionMode_1 : 1 + Int AfflictionMode_2 : 1 + Int AfflictionMode_3 : 1 + Int AfflictionMode_4 : 1 + Int AfflictionMode_5 : 1 + Int AfflictionMode_6 : 1 + Int AfflictionMode_7 : 1 + Int AfflictionMode_8 : 1 + Int AfflictionMode_9 : 1 + Int AfflictionMode_10 : 1 + Int AfflictionMode_11 : 1 + + Color EmissiveColor_0 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_1 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_2 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_3 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_4 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_5 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_6 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_7 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_8 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_9 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_10 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_11 : 0.0 0.0 0.0 0.0 + + Float Roughness_0 : 0.0 + Float Roughness_1 : 0.0 + Float Roughness_2 : 0.0 + Float Roughness_3 : 0.0 + Float Roughness_4 : 0.0 + Float Roughness_5 : 0.0 + Float Roughness_6 : 0.0 + Float Roughness_7 : 0.0 + Float Roughness_8 : 0.0 + Float Roughness_9 : 0.0 + Float Roughness_10 : 0.0 + Float Roughness_11 : 0.0 + + Float Metallic_0 : 0.0 + Float Metallic_1 : 0.0 + Float Metallic_2 : 0.0 + Float Metallic_3 : 0.0 + Float Metallic_4 : 0.0 + Float Metallic_5 : 0.0 + Float Metallic_6 : 0.0 + Float Metallic_7 : 0.0 + Float Metallic_8 : 0.0 + Float Metallic_9 : 0.0 + Float Metallic_10 : 0.0 + Float Metallic_11 : 0.0 + + Int AlbedoMap_0 + Int AlbedoMap_1 + Int AlbedoMap_2 + Int AlbedoMap_3 + Int AlbedoMap_4 + Int AlbedoMap_5 + Int AlbedoMap_6 + Int AlbedoMap_7 + Int AlbedoMap_8 + Int AlbedoMap_9 + Int AlbedoMap_10 + Int AlbedoMap_11 + + Float AlbedoMap_0_scale : 1 + Float AlbedoMap_1_scale : 1 + Float AlbedoMap_2_scale : 1 + Float AlbedoMap_3_scale : 1 + Float AlbedoMap_4_scale : 1 + Float AlbedoMap_5_scale : 1 + Float AlbedoMap_6_scale : 1 + Float AlbedoMap_7_scale : 1 + Float AlbedoMap_8_scale : 1 + Float AlbedoMap_9_scale : 1 + Float AlbedoMap_10_scale : 1 + Float AlbedoMap_11_scale : 1 + + Boolean UseTriPlanarMapping_0 + Boolean UseTriPlanarMapping_1 + Boolean UseTriPlanarMapping_2 + Boolean UseTriPlanarMapping_3 + Boolean UseTriPlanarMapping_4 + Boolean UseTriPlanarMapping_5 + Boolean UseTriPlanarMapping_6 + Boolean UseTriPlanarMapping_7 + Boolean UseTriPlanarMapping_8 + Boolean UseTriPlanarMapping_9 + Boolean UseTriPlanarMapping_10 + Boolean UseTriPlanarMapping_11 + + Int NormalMap_0 + Int NormalMap_1 + Int NormalMap_2 + Int NormalMap_3 + Int NormalMap_4 + Int NormalMap_5 + Int NormalMap_6 + Int NormalMap_7 + Int NormalMap_8 + Int NormalMap_9 + Int NormalMap_10 + Int NormalMap_11 + + Int MetallicRoughnessMap_0 + Int MetallicRoughnessMap_1 + Int MetallicRoughnessMap_2 + Int MetallicRoughnessMap_3 + Int MetallicRoughnessMap_4 + Int MetallicRoughnessMap_5 + Int MetallicRoughnessMap_6 + Int MetallicRoughnessMap_7 + Int MetallicRoughnessMap_8 + Int MetallicRoughnessMap_9 + Int MetallicRoughnessMap_10 + Int MetallicRoughnessMap_11 + + Float ParallaxHeight_0 + Float ParallaxHeight_1 + Float ParallaxHeight_2 + Float ParallaxHeight_3 + Float ParallaxHeight_4 + Float ParallaxHeight_5 + Float ParallaxHeight_6 + Float ParallaxHeight_7 + Float ParallaxHeight_8 + Float ParallaxHeight_9 + Float ParallaxHeight_10 + Float ParallaxHeight_11 + + //used in order to convert world coords to tex coords so afflictionTexture accurately represents the world in cases where terrain is not scaled at a 1,1,1 value + Float TileWidth : 0 + Vector3 TileLocation + + // debug the final value of the selected layer as a color output + Int DebugValuesMode + // Layers: + // 0 - albedo (unshaded) + // 1 - normals + // 2 - roughness + // 3 - metallic + // 4 - ao + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals + + // use tri-planar mapping + Boolean useTriPlanarMapping + + // Texture that specifies alpha values + Texture2D AlphaMap -LINEAR + Texture2D AlphaMap_1 -LINEAR + Texture2D AlphaMap_2 -LINEAR + + Vector4 ProbeData + + // Prefiltered Env Map for indirect specular lighting + TextureCubeMap PrefEnvMap -LINEAR + + // Irradiance map for indirect diffuse lighting + TextureCubeMap IrradianceMap -LINEAR + + //integrate BRDF map for indirect Lighting + Texture2D IntegrateBRDF -LINEAR + + //shadows + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + Vector3 LightDir + + Float PCFEdge + Float ShadowMapSize + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + + //For instancing + Boolean UseInstancing + + //For Vertex Color + Boolean UseVertexColor + + Boolean BackfaceShadows : false + + Boolean UseFog + Color FogColor + Vector2 LinearFog + Float ExpFog + Float ExpSqFog + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + } + + Technique { + + LightMode SinglePassAndImageBased + + VertexShader GLSL300 GLSL150 : Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 : Common/MatDefs/Terrain/AdvancedPBRTerrain.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + Time + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + + EXPOSUREMAP : SunLightExposureMap + USE_VERTEX_COLORS_AS_SUN_EXPOSURE : UseVertexColorsAsSunExposure + STATIC_SUN_EXPOSURE : StaticSunExposure + BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows + + NORMAL_TYPE: NormalType + + USE_FIRST_LAYER_AS_TRANSPARENCY : UseFirstLayerAsTransparency + + SPECULAR_AA : UseSpecularAA + SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma + SPECULAR_AA_THRESHOLD : SpecularAAKappa + + DISCARD_ALPHA : AlphaDiscardThreshold + + TILELOCATION : TileLocation + AFFLICTIONTEXTURE : AfflictionAlphaMap + + AFFLICTIONTEXTURE : AfflictionAlphaMap + AFFLICTIONALBEDOMAP: SplatAlbedoMap + AFFLICTIONNORMALMAP : SplatNormalMap + AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap + AFFLICTIONEMISSIVEMAP : SplatEmissiveMap + USE_SPLAT_NOISE : SplatNoiseVar + + USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping + + TRI_PLANAR_MAPPING : useTriPlanarMapping + + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + + ALBEDOMAP_0 : AlbedoMap_0 + ALBEDOMAP_1 : AlbedoMap_1 + ALBEDOMAP_2 : AlbedoMap_2 + ALBEDOMAP_3 : AlbedoMap_3 + ALBEDOMAP_4 : AlbedoMap_4 + ALBEDOMAP_5 : AlbedoMap_5 + ALBEDOMAP_6 : AlbedoMap_6 + ALBEDOMAP_7 : AlbedoMap_7 + ALBEDOMAP_8 : AlbedoMap_8 + ALBEDOMAP_9 : AlbedoMap_9 + ALBEDOMAP_10 : AlbedoMap_10 + ALBEDOMAP_11 : AlbedoMap_11 + + NORMALMAP_0 : NormalMap_0 + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + + METALLICROUGHNESSMAP_0 : MetallicRoughnessMap_0 + METALLICROUGHNESSMAP_1 : MetallicRoughnessMap_1 + METALLICROUGHNESSMAP_2 : MetallicRoughnessMap_2 + METALLICROUGHNESSMAP_3 : MetallicRoughnessMap_3 + METALLICROUGHNESSMAP_4 : MetallicRoughnessMap_4 + METALLICROUGHNESSMAP_5 : MetallicRoughnessMap_5 + METALLICROUGHNESSMAP_6 : MetallicRoughnessMap_6 + METALLICROUGHNESSMAP_7 : MetallicRoughnessMap_7 + METALLICROUGHNESSMAP_8 : MetallicRoughnessMap_8 + METALLICROUGHNESSMAP_9 : MetallicRoughnessMap_9 + METALLICROUGHNESSMAP_10 : MetallicRoughnessMap_10 + METALLICROUGHNESSMAP_11 : MetallicRoughnessMap_11 + + TRI_PLANAR_MAPPING_0 : UseTriPlanarMapping_0 + TRI_PLANAR_MAPPING_1 : UseTriPlanarMapping_1 + TRI_PLANAR_MAPPING_2 : UseTriPlanarMapping_2 + TRI_PLANAR_MAPPING_3 : UseTriPlanarMapping_3 + TRI_PLANAR_MAPPING_4 : UseTriPlanarMapping_4 + TRI_PLANAR_MAPPING_5 : UseTriPlanarMapping_5 + TRI_PLANAR_MAPPING_6 : UseTriPlanarMapping_6 + TRI_PLANAR_MAPPING_7 : UseTriPlanarMapping_7 + TRI_PLANAR_MAPPING_8 : UseTriPlanarMapping_8 + TRI_PLANAR_MAPPING_9 : UseTriPlanarMapping_9 + TRI_PLANAR_MAPPING_10 : UseTriPlanarMapping_10 + TRI_PLANAR_MAPPING_11 : UseTriPlanarMapping_11 + + DEBUG_VALUES_MODE : DebugValuesMode + + USE_TEXTURE_ARRAYS : AlbedoTextureArray + } + } + + Technique PreShadow { + + VertexShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100 : Common/MatDefs/Shadow/PreShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + } + + ForcedRenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 3 + ColorWrite Off + } + + } + + + Technique PostShadow{ + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag index b0b594c624..d19d8aec99 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform vec3 m_region1; uniform vec3 m_region2; uniform vec3 m_region3; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md index b57aaa4ca9..b112644e2d 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.j3md @@ -1,40 +1,44 @@ MaterialDef Terrain { - // Parameters to material: - // regionXColorMap: X = 1..4 the texture that should be appliad to state X - // regionX: a Vector3f containing the following information: - // regionX.x: the start height of the region - // regionX.y: the end height of the region - // regionX.z: the texture scale for the region - // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) - // slopeColorMap: the texture to be used for cliffs, and steep mountain sites - // slopeTileFactor: the texture scale for slopes - // terrainSize: the total size of the terrain (used for scaling the texture) - MaterialParameters { - Texture2D region1ColorMap - Texture2D region2ColorMap - Texture2D region3ColorMap - Texture2D region4ColorMap - Texture2D slopeColorMap - Float slopeTileFactor - Float terrainSize - Vector3 region1 - Vector3 region2 - Vector3 region3 - Vector3 region4 - } + // Parameters to material: + // regionXColorMap: X = 1..4 the texture that should be applied to state X + // regionX: a Vector3f containing the following information: + // regionX.x: the start height of the region + // regionX.y: the end height of the region + // regionX.z: the texture scale for the region + // it might not be the most elegant way for storing these 3 values, but it packs the data nicely :) + // slopeColorMap: the texture to be used for cliffs, and steep mountain sites + // slopeTileFactor: the texture scale for slopes + // terrainSize: the total size of the terrain (used for scaling the texture) - Technique { - VertexShader GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.vert - FragmentShader GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.frag - - WorldParameters { - WorldViewProjectionMatrix - WorldMatrix - NormalMatrix - } - } + MaterialParameters { + Int BoundDrawBuffer + Texture2D region1ColorMap + Texture2D region2ColorMap + Texture2D region3ColorMap + Texture2D region4ColorMap + Texture2D slopeColorMap + Float slopeTileFactor + Float terrainSize + Vector3 region1 + Vector3 region2 + Vector3 region3 + Vector3 region4 + } Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/HeightBasedTerrain.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + NormalMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + } } -} \ No newline at end of file + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert index 8260d8f72d..60f57f0782 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/HeightBasedTerrain.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform float m_tilingFactor; uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_WorldMatrix; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/AfflictionLib.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/AfflictionLib.glsllib new file mode 100644 index 0000000000..b8b2457af6 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/AfflictionLib.glsllib @@ -0,0 +1,462 @@ +#ifndef __AFFLICTION_LIB__ + #define __AFFLICTION_LIB__ + + #import "Common/ShaderLib/NoiseLib.glsllib" + #import "Common/ShaderLib/TangentUtils.glsllib" + + #import "Common/ShaderLib/TriPlanarUtils.glsllib" + + #ifdef AFFLICTIONTEXTURE + uniform sampler2D m_AfflictionAlphaMap; + #endif + #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 + #ifdef USE_SPLAT_NOISE + uniform float m_SplatNoiseVar; + #endif + + #ifdef TILELOCATION + uniform float m_TileWidth; + uniform vec3 m_TileLocation; + #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; + + vec4 desaturate(vec4 albedo, float deathVar){ + vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), albedo.rgb)); + albedo = vec4(mix(albedo.rgb, gray, deathVar), 0.0); + + return albedo; + } + vec3 alterLiveliness(vec3 color, float liveVal, int mode){ + + float deathVar = (1.0 - (liveVal)); + + //0 means dont scale to be desaturated (bricks, stones, etc) + if(mode > 0){ //1 means almost fully desaturated.. 1 is less alive, and 2 is slightly less, etc etc + deathVar -= mode * 0.033; + + deathVar = max(0.0, deathVar); + deathVar = min(0.99, deathVar); + + float hueVar = (deathVar) * 0.34; + color.r += color.r*hueVar * 1.8; + color.g -= color.g*hueVar; + color.b -= color.b*hueVar * 5.0 ; + + color = desaturate(vec4(color, 1.0), deathVar).rgb; + + } + + return color; + + } + + + vec3 alterLiveliness(vec3 color, float livelinessValue){ + + float deathVar = (1.0 - (livelinessValue)); + + float hueVar = (deathVar) * 0.34; + color.r += color.r*hueVar * 1.8; + color.g -= color.g*hueVar; + color.b -= color.b*hueVar*5.0 ; + + color = desaturate(vec4(color, 1.0), deathVar).rgb; + + return color; + } + + + //methods for death and afflictionness applied to all other types of affliction shaders + + + vec4 alterBarkLiveliness(vec4 albedo, float livelinessValue){ + float deathVar = 1.0 - livelinessValue; + + float hueVar = (deathVar) * 0.97; + albedo.r += albedo.r*hueVar * 0.21; + albedo.g += albedo.g*hueVar*0.84; + albedo.b -= albedo.b*hueVar*1.9; + + albedo *= 0.1 + (0.9 * livelinessValue); + + return albedo; + } + + vec4 alterPlantLiveliness(vec4 albedo, float livelinessValue){ + float deathVar = 1.0 - livelinessValue; + + float hueVar = (deathVar) * 0.77; + albedo.r += albedo.r*hueVar * 1.8; + albedo.g -= albedo.g*hueVar; + albedo.b -= albedo.b*hueVar*5.0 ; + + return albedo; + } + + vec4 alterStoneLiveliness(vec4 albedo, float livelinessValue){ + livelinessValue = 0.56 + (0.44 * livelinessValue); //stone and rock has an 80% minimum, and scales up from there + + float deathVar = 1.0 - livelinessValue; + + float hueVar = (deathVar); + albedo.r += albedo.r*hueVar * 1.2; + albedo.g += albedo.g*hueVar; + albedo.b -= albedo.b*hueVar*3.14 ; + + albedo = desaturate(albedo, deathVar * 1.7); + + return albedo; + } + + vec3 desaturateVec(vec3 albedo, float deathVar){ + vec3 gray = vec3(dot(vec3(0.2126,0.7152,0.0722), albedo.rgb)); + albedo = mix(albedo, gray, deathVar); + + return albedo; + } + vec3 alterStoneLivelinessVar(vec3 albedo, float livelinessValue){ + livelinessValue = 0.56 + (0.44 * livelinessValue); //stone and rock has an 80% minimum, and scales up from there + + float deathVar = 1.0 - livelinessValue; + + float hueVar = (deathVar); + albedo.r += albedo.r*hueVar * 1.2; + albedo.g += albedo.g*hueVar; + albedo.b -= albedo.b*hueVar*3.14 ; + + albedo = desaturateVec(albedo, deathVar * 1.7); + + return albedo; + } + + //AFFLICTION METHODS + + //adjusts the affliction value for the best visual representation (since 0.0 - 1.0 is not as visually linear as it is numerically) + float getAdjustedAfflictionVar(float afflictionVar){ + float adjustedVar = afflictionVar; + if(afflictionVar > 0.02){ + adjustedVar = mix(0.02, 0.53, afflictionVar); + } + else{ + adjustedVar = 0; + } + + return adjustedVar; + } + + float getAfflictionEdgeTaper(float noiseVar, float afflictionVar){ + float amt = noiseVar - (0.4 * afflictionVar) - .04; + + if(amt <= 0.05){ + amt = 0.05; + } + return amt; + } + + vec4 alterAfflictionColor(float afflictionVar, vec4 albedo, vec4 afflictionAlbedo, float noiseVar){ + float originalAlpha = albedo.a; + + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + + if(afflictionVar >= noiseVar){ + float albedoOpacity = min((afflictionVar * 0.2) + 0.8 , 1.0); + albedo.rgba = mix(albedo.rgba, afflictionAlbedo.rgba, albedoOpacity); + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + + albedo.rgba = mix(afflictionAlbedo.rgba, albedo.rgba, edgeDiff); + } + else{ + albedo.rgba = mix(albedo.rgba, afflictionAlbedo.rgba, afflictionVar); + } + + albedo.a = albedo.a * originalAlpha; //ensures alpha blending is always done based on original texture so + //avoid artifacts on edge of transparent leaves and similar materials + + return albedo; + } + vec4 alterAfflictionGlow(float afflictionVar, vec4 emissive, vec4 afflictionGlowColor, float noiseVar){ + emissive = mix(emissive, afflictionGlowColor, afflictionVar); + + return emissive; + } + + float alterAfflictionEmissiveIntensity(float afflictionVar, float emissiveIntensity, float afflictionEmissiveIntensity, float noiseVar){ + emissiveIntensity = mix(emissiveIntensity, afflictionEmissiveIntensity, afflictionVar); + + return emissiveIntensity; + } + + vec3 alterAfflictionNormals(float afflictionVar, vec3 normal, vec3 afflictionNormal, float noiseVar){ + vec3 originalNorm = normal; + + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + + if(afflictionVar >= noiseVar){ + normal = afflictionNormal; + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + normal = mix(afflictionNormal, normal, edgeDiff); + } + else{ + normal = mix(normal, afflictionNormal, afflictionVar); + } + + + return normalize(normal); + } + + vec3 alterAfflictionNormalsForTerrain(float afflictionVar, vec3 normal, vec3 afflictionNormal, float noiseVar, vec3 worldNorm){ + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + vec3 blendedNormal = normal; + + float blendValue = afflictionVar; + + if(afflictionVar >= noiseVar){ + blendValue = 1.0; + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + + blendValue = edgeDiff; + + } + else{ + float blendAmt = noiseVar * afflictionVar; + blendAmt = max(0.0, blendAmt); + blendAmt = min(1.0, blendAmt); + + blendValue = blendAmt; + } + + afflictionNormal = calculateTangentsAndApplyToNormals(afflictionNormal, worldNorm); + blendedNormal = mix(normal, afflictionNormal, blendValue); + + return blendedNormal; + } + + vec3 alterAfflictionAo(float afflictionVar, vec3 ao, vec3 afflictionAo, float noiseVar){ + + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + + if(afflictionVar >= noiseVar){ + ao = afflictionAo; + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + + ao = mix(afflictionAo, ao, edgeDiff); + } + else{ + ao = mix(ao, afflictionAo, afflictionVar); + } + + return ao; + } + + float alterAfflictionRoughness(float afflictionVar, float originalRoughness, float afflictionRoughness, float noiseVar){ + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + if(afflictionVar >= noiseVar){ + originalRoughness = afflictionRoughness; + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + + originalRoughness = mix(afflictionRoughness, originalRoughness, edgeDiff); + } + + + originalRoughness = min(originalRoughness, 1.0); + + + return originalRoughness; + } + + float alterAfflictionMetallic(float afflictionVar, float originalMetallic, float afflictionMetallic, float noiseVar){ + float edgeTaper = getAfflictionEdgeTaper(noiseVar, afflictionVar); + if(afflictionVar >= noiseVar){ + originalMetallic = afflictionMetallic; + } + else if(afflictionVar > edgeTaper){ + float edgeDiff = noiseVar - afflictionVar; + edgeDiff = edgeDiff / afflictionVar; + + originalMetallic = mix(afflictionMetallic, originalMetallic, edgeDiff); + } + + + originalMetallic = min(originalMetallic, 1.0); + return originalMetallic; + } + + #ifndef __SURFACE_MODULE__ + #import "Common/ShaderLib/module/PBRSurface.glsl" + #endif + + void AfflictionLib_readAfflictionVector(){ + #ifdef AFFLICTIONTEXTURE + + 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) + + #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 dimesnions that the AfflictionAlphaMap represents).. + vec2 tileCoords; + float xPos, zPos; + + vec3 locInTile = (wPosition - m_TileLocation); + + locInTile += vec3(m_TileWidth/2, 0, m_TileWidth/2); + + xPos = (locInTile.x / m_TileWidth); + zPos = 1 - (locInTile.z / m_TileWidth); + + tileCoords = vec2(xPos, zPos); + + afflictionVector = texture2D(m_AfflictionAlphaMap, tileCoords).rgba; + + #else + // ..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 + + livelinessValue = afflictionVector.r; + afflictionValue = afflictionVector.g; + #endif + + } + + void AfflictionLib_blendSplatLayers(inout PBRSurface surface){ + + TriPlanarUtils_calculateBlending(surface.geometryNormal); + + #ifdef AFFLICTIONTEXTURE + vec4 afflictionAlbedo; + + float newAfflictionScale = m_AfflictionSplatScale; + vec2 newScaledCoords; + + #ifdef AFFLICTIONALBEDOMAP + #ifdef TRI_PLANAR_MAPPING + newAfflictionScale = newAfflictionScale / 256; + afflictionAlbedo = getTriPlanarBlend(lPosition, m_SplatAlbedoMap , newAfflictionScale); + #else + newScaledCoords = mod(wPosition.xz / m_AfflictionSplatScale, 0.985); + afflictionAlbedo = texture2D(m_SplatAlbedoMap , newScaledCoords); + #endif + + #else + afflictionAlbedo = vec4(1.0, 1.0, 1.0, 1.0); + #endif + + vec3 afflictionNormal; + #ifdef AFFLICTIONNORMALMAP + #ifdef TRI_PLANAR_MAPPING + + afflictionNormal = getTriPlanarBlend(lPosition, m_SplatNormalMap , newAfflictionScale).rgb; + + #else + afflictionNormal = texture2D(m_SplatNormalMap , newScaledCoords).rgb; + #endif + afflictionNormal = normalize((afflictionNormal * vec3(2.0,2.0, 2.0) - vec3(1.0, 1.0, 1.0))); + + if(surface.hasTangents == true){ + afflictionNormal = normalize(surface.tbnMat * afflictionNormal); + } + #else + afflictionNormal = surface.geometryNormal; + + #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(lPosition, m_SplatRoughnessMetallicMap, newAfflictionScale); + #endif + + 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 + + #ifdef AFFLICTIONEMISSIVEMAP + vec4 emissiveMapColor; + #ifdef TRI_PLANAR_MAPPING + emissiveMapColor = texture2D(m_SplatEmissiveMap, newScaledCoords); + #else + emissiveMapColor = getTriPlanarBlend(lPosition, m_SplatEmissiveMap, newAfflictionScale); + #endif + afflictionEmissive *= emissiveMapColor; + #endif + + float adjustedAfflictionValue = afflictionValue; + #ifdef USE_SPLAT_NOISE + 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 + + surface.roughness = alterAfflictionRoughness(afflictionValue, surface.roughness, afflictionRoughness, noiseHash); + surface.metallic = alterAfflictionMetallic(afflictionValue, surface.metallic, afflictionMetallic, noiseHash); + surface.albedo = alterAfflictionColor(afflictionValue, vec4(surface.albedo, 1.0), afflictionAlbedo, noiseHash).rgb; + surface.emission = alterAfflictionGlow(afflictionValue, vec4(surface.emission, 1.0), afflictionEmissive, noiseHash).rgb; + surface.ao = alterAfflictionAo(afflictionValue, surface.ao, vec3(afflictionAo), noiseHash); + + if(surface.hasTangents == true){ + surface.normal = alterAfflictionNormals(afflictionValue, surface.normal, afflictionNormal, noiseHash); + } + else{ + surface.normal = alterAfflictionNormalsForTerrain(afflictionValue, surface.normal, afflictionNormal, noiseHash, surface.geometryNormal); + } + + #endif + } +#endif + + + + + diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainTextureLayer.glsl b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainTextureLayer.glsl new file mode 100644 index 0000000000..0e05b1231a --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainTextureLayer.glsl @@ -0,0 +1,21 @@ +#ifndef __TERRAIN_LAYER_MODULE__ + #define __TERRAIN_LAYER_MODULE__ + + #ifndef PBRTerrainTextureLayer + #struct StdPBRTerrainTextureLayer + + float blendValue; + + vec4 albedo; + float alpha; + vec3 normal; + float height; //parallax unused currently + float metallic; + float roughness; + float ao; + vec4 emission; + + #endstruct + #define PBRTerrainTextureLayer StdPBRTerrainTextureLayer + #endif +#endif \ No newline at end of file diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib new file mode 100644 index 0000000000..e731ad2e27 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Modular/PBRTerrainUtils.glsllib @@ -0,0 +1,227 @@ +#ifndef __PBR_TERRAIN_UTILS_MODULE__ + #define __PBR_TERRAIN_UTILS_MODULE__ + + #import "Common/MatDefs/Terrain/Modular/PBRTerrainTextureLayer.glsl" + + #import "Common/ShaderLib/TangentUtils.glsllib" + #import "Common/ShaderLib/TriPlanarUtils.glsllib" + + #ifdef ENABLE_PBRTerrainUtils_readPBRTerrainLayers + + #ifndef NORMAL_TYPE + #define NORMAL_TYPE -1.0 + #endif + + #ifdef USE_TEXTURE_ARRAYS + //texture arrays: + uniform sampler2DArray m_AlbedoTextureArray; + uniform sampler2DArray m_NormalParallaxTextureArray; + uniform sampler2DArray m_MetallicRoughnessAoEiTextureArray; + #endif + + //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 (#ifdef ALBEDOMAP_$i $0 #endif) + 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 USE_TEXTURE_ARRAYS + uniform int m_AlbedoMap_$i; + #ifdef NORMALMAP_$i + uniform int m_NormalMap_$i; + #endif + #ifdef METALLICROUGHNESSMAP_$i + uniform int m_MetallicRoughnessMap_$i; + #endif + #else + uniform sampler2D m_AlbedoMap_$i; + #ifdef NORMALMAP_$i + uniform sampler2D m_NormalMap_$i; + #endif + #ifdef METALLICROUGHNESSMAP_$i + uniform sampler2D m_MetallicRoughnessMap_$i; + #endif + #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 + + vec4 alphaBlend_0, alphaBlend_1, alphaBlend_2; + + void PBRTerrainUtils_readAlphaMaps(){ + + #ifdef ALPHAMAP + alphaBlend_0 = texture2D( m_AlphaMap, texCoord.xy ); + #endif + #ifdef ALPHAMAP_1 + alphaBlend_1 = texture2D( m_AlphaMap_1, texCoord.xy ); + #endif + #ifdef ALPHAMAP_2 + alphaBlend_2 = texture2D( m_AlphaMap_2, texCoord.xy ); + #endif + } + + float PBRTerrainUtils_getAlphaBlendFromChannel(int layer){ + float finalAlphaBlendForLayer = 0.0; + vec4 alphaBlend; + if(layer <= 3.0){ + alphaBlend = alphaBlend_0; + }else if(layer <= 7.0){ + alphaBlend = alphaBlend_1; + }else if(layer <= 11.0){ + alphaBlend = alphaBlend_2; + } + int texChannelForAlphaBlending = int(mod(float(layer), 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; + } + + finalAlphaBlendForLayer = clamp(finalAlphaBlendForLayer, 0.0, 1.0); + + return finalAlphaBlendForLayer; + } + + PBRTerrainTextureLayer PBRTerrainUtils_createAdvancedPBRTerrainLayer(int layerNum, vec3 geometryNormal){ + + PBRTerrainTextureLayer terrainTextureLayer; + terrainTextureLayer.blendValue = PBRTerrainUtils_getAlphaBlendFromChannel(layerNum); + terrainTextureLayer.albedo = vec4(1.0); + terrainTextureLayer.emission = vec4(0.0); + terrainTextureLayer.normal = geometryNormal; + terrainTextureLayer.alpha = 1.0; + terrainTextureLayer.ao = 1.0; + terrainTextureLayer.roughness = 1.0; + terrainTextureLayer.metallic = 0.0; + terrainTextureLayer.height = 0.0; + + return terrainTextureLayer; + } + + //3 functions to update layers from respective packed data vecs: + void updateLayerFromPackedAlbedoMap(inout vec4 packedAlbedoVec, inout PBRTerrainTextureLayer layer){ + layer.albedo = packedAlbedoVec; + layer.alpha = packedAlbedoVec.a; + } + void updateLayerFromPackedNormalParallaxVec(inout vec4 packedNormalParallaxVec, inout PBRTerrainTextureLayer layer){ + layer.normal = normalize(calculateTangentsAndApplyToNormals(packedNormalParallaxVec.rgb, PBRLightingUtils_getWorldNormal())); + layer.height = packedNormalParallaxVec.a; + } + void updateLayerFromPackedMRAoEiVec(inout vec4 packedMRAoEiVec, inout PBRTerrainTextureLayer layer){ + layer.ao = packedMRAoEiVec.r; //ao only comes from texture (no float scalars) so no *= is done here + layer.roughness *= packedMRAoEiVec.g; + layer.metallic *= packedMRAoEiVec.b; + layer.emission *= packedMRAoEiVec.a * layer.emission.a; + } + //________________________________ + // Basic Texture Reads: + + // Albedo: + void PBRTerrainUtils_readAlbedoTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedAlbedoVec = texture2D(tex, texCoord * scale); + updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer); + } + // normal: + void PBRTerrainUtils_readNormalTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedNormalParallaxVec = texture2D(tex, texCoord * scale); + packedNormalParallaxVec.xyz = normalize(packedNormalParallaxVec.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)); + updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer); + } + // metallicRoughnessAoEi: + void PBRTerrainUtils_readMetallicRoughnessAoEiTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedMRAoEi = texture2D(tex, texCoord * scale); + updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer); + } + //________________________________ + // Basic Triplanar Reads: + + // Triplanar Albedo: + void PBRTerrainUtils_readTriPlanarAlbedoTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedAlbedoVec = getTriPlanarBlend(lPosition, tex, scale); + updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer); + } + // Triplanar normal: + void PBRTerrainUtils_readTriPlanarNormalTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedNormalParallaxVec = getTriPlanarNormalBlend(lPosition, tex, scale); + updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer); + } + // TriPlanar metallicRoughnessAoEi: + void PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexture(in sampler2D tex, in float scale, inout PBRTerrainTextureLayer layer){ + vec4 packedMRAoEi = getTriPlanarBlend(lPosition, tex, scale); + updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer); + } + //________________________________ + // Basic TexArray reads: + + // Albedo TextureArray: + void PBRTerrainUtils_readAlbedoTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedAlbedoVec = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray)); + updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer); + } + // Normal TextureArray: + void PBRTerrainUtils_readNormalTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedNormalParallaxVec = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray)); + packedNormalParallaxVec.xyz = normalize(packedNormalParallaxVec.xyz * vec3(2.0, NORMAL_TYPE * 2.0, 2.0) - vec3(1.0, NORMAL_TYPE * 1.0, 1.0)); + updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer); + } + // metallicRoughnessAoEi TextureArray: + void PBRTerrainUtils_readMetallicRoughnessAoEiTexArray(in int indexInTexArray, float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedMRAoEi = texture2DArray(texArray, vec3(texCoord * scale, indexInTexArray)); + updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer); + } + //________________________________ + // Triplanar TexArray reads: + + // Triplana Albedo TextureArray: + void PBRTerrainUtils_readTriPlanarAlbedoTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedAlbedoVec = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray); + updateLayerFromPackedAlbedoMap(packedAlbedoVec, layer); + } + // Triplanar normal TextureArray: + void PBRTerrainUtils_readTriPlanarNormalTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedNormalParallaxVec = getTriPlanarNormalBlendFromTexArray(lPosition, indexInTexArray, scale, texArray); + updateLayerFromPackedNormalParallaxVec(packedNormalParallaxVec, layer); + } + // TriPlanar metallicRoughnessAoEi TextureArray: + void PBRTerrainUtils_readTriPlanarMetallicRoughnessAoEiTexArray(in int indexInTexArray, in float scale, in sampler2DArray texArray, inout PBRTerrainTextureLayer layer){ + vec4 packedMRAoEi = getTriPlanarBlendFromTexArray(lPosition, indexInTexArray, scale, texArray); + updateLayerFromPackedMRAoEiVec(packedMRAoEi, layer); + } + //_______________________________ + + //blend layer function. This mixes each layer's pbr vars over top of the current surface values based on the layer's blendValue + void PBRTerrainUtils_blendPBRTerrainLayer(inout PBRSurface surface, inout PBRTerrainTextureLayer layer){ + layer.ao = clamp(layer.ao, 0.0, 1.0); + + surface.albedo = mix(surface.albedo, layer.albedo.rgb, layer.blendValue); + surface.normal = normalize(mix(surface.normal.rgb, layer.normal, layer.blendValue)); + surface.metallic = mix(surface.metallic, layer.metallic, layer.blendValue); + surface.roughness = mix(surface.roughness, layer.roughness, layer.blendValue); + surface.ao = mix(surface.ao, vec3(layer.ao), layer.blendValue); + surface.emission = mix(surface.emission, layer.emission.rgb, layer.blendValue); + } + + #endif +#endif diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md new file mode 100644 index 0000000000..92e601c1c1 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.j3md @@ -0,0 +1,432 @@ +// NOTE: Doesn't support OpenGL1 +MaterialDef PBR Terrain { + + MaterialParameters { + Int BoundDrawBuffer + + Texture2D SunLightExposureMap + Boolean UseVertexColorsAsSunExposure //set true to make the vertex color's R channel how exposed a vertex is to the sun + Float StaticSunExposure //used for setting the sun exposure value for a whole material + //these are usually generated at run time or setup in a level editor per-geometry, so that models indoors can have the DirectionalLight dimmed accordingly. + + Boolean BrightenIndoorShadows //set true if shadows are enabled and indoor areas without full sun exposure are too dark compared to when shadows are turned off in settings + + Boolean UseFirstLayerAsTransparency + + Boolean UseTriplanarAfflictionMapping + Int AfflictionSplatScale : 8 + + Float AfflictionRoughnessValue : 1.0 + Float AfflictionMetallicValue : 0.0 + Float AfflictionEmissiveValue : 0.0 //note that this is simplified into one value, rather than 2 with power and intensity like the regular pbr values. + + Texture2D AfflictionAlphaMap + Texture2D SplatAlbedoMap -LINEAR + Texture2D SplatNormalMap -LINEAR + Texture2D SplatRoughnessMetallicMap -LINEAR + Texture2D SplatEmissiveMap -LINEAR + + //The type of normal map: -1.0 (DirectX), 1.0 (OpenGl) + Float NormalType : -1.0 + + // Specular-AA + Boolean UseSpecularAA : true + // screen space variance,Use the slider to set the strength of the geometric specular anti-aliasing effect between 0 and 1. Higher values produce a blurrier result with less aliasing. + Float SpecularAASigma + // clamping threshold,Use the slider to set a maximum value for the offset that HDRP subtracts from the smoothness value to reduce artifacts. + Float SpecularAAKappa + + Color AfflictionEmissiveColor : 0.0 0.0 0.0 0.0 + + Float SplatNoiseVar + + //used for decal mapping with afflictionMap, in order to convert world coords to tex coords so afflictionTexture accurately represents the world + Float TileWidth : 0 + Vector3 TileLocation + + Int AfflictionMode_0 : 1 + Int AfflictionMode_1 : 1 + Int AfflictionMode_2 : 1 + Int AfflictionMode_3 : 1 + Int AfflictionMode_4 : 1 + Int AfflictionMode_5 : 1 + Int AfflictionMode_6 : 1 + Int AfflictionMode_7 : 1 + Int AfflictionMode_8 : 1 + Int AfflictionMode_9 : 1 + Int AfflictionMode_10 : 1 + Int AfflictionMode_11 : 1 + + Float Roughness_0 : 0.0 + Float Roughness_1 : 0.0 + Float Roughness_2 : 0.0 + Float Roughness_3 : 0.0 + Float Roughness_4 : 0.0 + Float Roughness_5 : 0.0 + Float Roughness_6 : 0.0 + Float Roughness_7 : 0.0 + Float Roughness_8 : 0.0 + Float Roughness_9 : 0.0 + Float Roughness_10 : 0.0 + Float Roughness_11 : 0.0 + + Float Metallic_0 : 0.0 + Float Metallic_1 : 0.0 + Float Metallic_2 : 0.0 + Float Metallic_3 : 0.0 + Float Metallic_4 : 0.0 + Float Metallic_5 : 0.0 + Float Metallic_6 : 0.0 + Float Metallic_7 : 0.0 + Float Metallic_8 : 0.0 + Float Metallic_9 : 0.0 + Float Metallic_10 : 0.0 + Float Metallic_11 : 0.0 + + Color EmissiveColor_0 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_1 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_2 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_3 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_4 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_5 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_6 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_7 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_8 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_9 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_10 : 0.0 0.0 0.0 0.0 + Color EmissiveColor_11 : 0.0 0.0 0.0 0.0 + + Boolean UseTriPlanarMapping_0 + Boolean UseTriPlanarMapping_1 + Boolean UseTriPlanarMapping_2 + Boolean UseTriPlanarMapping_3 + Boolean UseTriPlanarMapping_4 + Boolean UseTriPlanarMapping_5 + Boolean UseTriPlanarMapping_6 + Boolean UseTriPlanarMapping_7 + Boolean UseTriPlanarMapping_8 + Boolean UseTriPlanarMapping_9 + Boolean UseTriPlanarMapping_10 + Boolean UseTriPlanarMapping_11 + + // debug the final value of the selected layer as a color output + Int DebugValuesMode + // Layers: + // 0 - albedo (unshaded) + // 1 - normals + // 2 - roughness + // 3 - metallic + // 4 - ao + // 5 - emissive + // 6 - exposure + // 7 - alpha + // 8 - geometryNormals + + // use tri-planar mapping + Boolean useTriPlanarMapping + + // Texture map #0 + Texture2D AlbedoMap_0 + Float AlbedoMap_0_scale + Texture2D NormalMap_0 -LINEAR + + // Texture map #1 + Texture2D AlbedoMap_1 + Float AlbedoMap_1_scale + Texture2D NormalMap_1 -LINEAR + + // Texture map #2 + Texture2D AlbedoMap_2 + Float AlbedoMap_2_scale + Texture2D NormalMap_2 -LINEAR + + // Texture map #3 + Texture2D AlbedoMap_3 + Float AlbedoMap_3_scale + Texture2D NormalMap_3 -LINEAR + + // Texture map #4 + Texture2D AlbedoMap_4 + Float AlbedoMap_4_scale + Texture2D NormalMap_4 -LINEAR + + // Texture map #5 + Texture2D AlbedoMap_5 + Float AlbedoMap_5_scale + Texture2D NormalMap_5 -LINEAR + + // Texture map #6 + Texture2D AlbedoMap_6 + Float AlbedoMap_6_scale + Texture2D NormalMap_6 -LINEAR + + // Texture map #7 + Texture2D AlbedoMap_7 + Float AlbedoMap_7_scale + Texture2D NormalMap_7 -LINEAR + + // Texture map #8 + Texture2D AlbedoMap_8 + Float AlbedoMap_8_scale + Texture2D NormalMap_8 -LINEAR + + // Texture map #9 + Texture2D AlbedoMap_9 + Float AlbedoMap_9_scale + Texture2D NormalMap_9 -LINEAR + + // Texture map #10 + Texture2D AlbedoMap_10 + Float AlbedoMap_10_scale + Texture2D NormalMap_10 -LINEAR + + // Texture map #11 + Texture2D AlbedoMap_11 + Float AlbedoMap_11_scale + Texture2D NormalMap_11 -LINEAR + + // Texture that specifies alpha values + Texture2D AlphaMap -LINEAR + Texture2D AlphaMap_1 -LINEAR + Texture2D AlphaMap_2 -LINEAR + + Vector4 ProbeData + + // Prefiltered Env Map for indirect specular lighting + TextureCubeMap PrefEnvMap -LINEAR + + // Irradiance map for indirect diffuse lighting + TextureCubeMap IrradianceMap -LINEAR + + //integrate BRDF map for indirect Lighting + Texture2D IntegrateBRDF -LINEAR + + //shadows + Int FilterMode + Boolean HardwareShadows + + Texture2D ShadowMap0 + Texture2D ShadowMap1 + Texture2D ShadowMap2 + Texture2D ShadowMap3 + //pointLights + Texture2D ShadowMap4 + Texture2D ShadowMap5 + + Float ShadowIntensity + Vector4 Splits + Vector2 FadeInfo + + Matrix4 LightViewProjectionMatrix0 + Matrix4 LightViewProjectionMatrix1 + Matrix4 LightViewProjectionMatrix2 + Matrix4 LightViewProjectionMatrix3 + //pointLight + Matrix4 LightViewProjectionMatrix4 + Matrix4 LightViewProjectionMatrix5 + Vector3 LightPos + Vector3 LightDir + + Float PCFEdge + Float ShadowMapSize + + // For hardware skinning + Int NumberOfBones + Matrix4Array BoneMatrices + + //For instancing + Boolean UseInstancing + + //For Vertex Color + Boolean UseVertexColor + + Boolean BackfaceShadows : false + + Boolean UseFog + Color FogColor + Vector2 LinearFog + Float ExpFog + Float ExpSqFog + + // Alpha threshold for fragment discarding + Float AlphaDiscardThreshold (AlphaTestFallOff) + } + + Technique { + + LightMode SinglePassAndImageBased + + VertexShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/PBRTerrain.vert + FragmentShader GLSL300 GLSL150 GLSL130 GLSL100: Common/MatDefs/Terrain/AdvancedPBRTerrain.frag + + WorldParameters { + WorldViewProjectionMatrix + CameraPosition + WorldMatrix + WorldNormalMatrix + ViewProjectionMatrix + ViewMatrix + Time + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + + TILELOCATION : TileLocation + AFFLICTIONTEXTURE : AfflictionAlphaMap + + USE_TRIPLANAR_AFFLICTION_MAPPING : UseTriplanarAfflictionMapping + AFFLICTIONALBEDOMAP: SplatAlbedoMap + AFFLICTIONNORMALMAP : SplatNormalMap + AFFLICTIONROUGHNESSMETALLICMAP : SplatRoughnessMetallicMap + AFFLICTIONEMISSIVEMAP : SplatEmissiveMap + + USE_SPLAT_NOISE : SplatNoiseVar + + SPECULAR_AA : UseSpecularAA + SPECULAR_AA_SCREEN_SPACE_VARIANCE : SpecularAASigma + SPECULAR_AA_THRESHOLD : SpecularAAKappa + + EXPOSUREMAP : SunLightExposureMap + USE_VERTEX_COLORS_AS_SUN_EXPOSURE : UseVertexColorsAsSunExposure + STATIC_SUN_EXPOSURE : StaticSunExposure + BRIGHTEN_INDOOR_SHADOWS : BrightenIndoorShadows + + NORMAL_TYPE: NormalType + + USE_FIRST_LAYER_AS_TRANSPARENCY : UseFirstLayerAsTransparency + + DISCARD_ALPHA : AlphaDiscardThreshold + + USE_FOG : UseFog + FOG_LINEAR : LinearFog + FOG_EXP : ExpFog + FOG_EXPSQ : ExpSqFog + + TRI_PLANAR_MAPPING : useTriPlanarMapping + + ALBEDOMAP_0 : AlbedoMap_0 + ALBEDOMAP_1 : AlbedoMap_1 + ALBEDOMAP_2 : AlbedoMap_2 + ALBEDOMAP_3 : AlbedoMap_3 + ALBEDOMAP_4 : AlbedoMap_4 + ALBEDOMAP_5 : AlbedoMap_5 + ALBEDOMAP_6 : AlbedoMap_6 + ALBEDOMAP_7 : AlbedoMap_7 + ALBEDOMAP_8 : AlbedoMap_8 + ALBEDOMAP_9 : AlbedoMap_9 + ALBEDOMAP_10 : AlbedoMap_10 + ALBEDOMAP_11 : AlbedoMap_11 + + NORMALMAP_0 : NormalMap_0 + NORMALMAP_1 : NormalMap_1 + NORMALMAP_2 : NormalMap_2 + NORMALMAP_3 : NormalMap_3 + NORMALMAP_4 : NormalMap_4 + NORMALMAP_5 : NormalMap_5 + NORMALMAP_6 : NormalMap_6 + NORMALMAP_7 : NormalMap_7 + NORMALMAP_8 : NormalMap_8 + NORMALMAP_9 : NormalMap_9 + NORMALMAP_10 : NormalMap_10 + NORMALMAP_11 : NormalMap_11 + + ALPHAMAP : AlphaMap + ALPHAMAP_1 : AlphaMap_1 + ALPHAMAP_2 : AlphaMap_2 + ALBEDOMAP_0_SCALE : AlbedoMap_0_scale + ALBEDOMAP_1_SCALE : AlbedoMap_1_scale + ALBEDOMAP_2_SCALE : AlbedoMap_2_scale + ALBEDOMAP_3_SCALE : AlbedoMap_3_scale + ALBEDOMAP_4_SCALE : AlbedoMap_4_scale + ALBEDOMAP_5_SCALE : AlbedoMap_5_scale + ALBEDOMAP_6_SCALE : AlbedoMap_6_scale + ALBEDOMAP_7_SCALE : AlbedoMap_7_scale + ALBEDOMAP_8_SCALE : AlbedoMap_8_scale + ALBEDOMAP_9_SCALE : AlbedoMap_9_scale + ALBEDOMAP_10_SCALE : AlbedoMap_10_scale + ALBEDOMAP_11_SCALE : AlbedoMap_11_scale + + TRI_PLANAR_MAPPING_0 : UseTriPlanarMapping_0 + TRI_PLANAR_MAPPING_1 : UseTriPlanarMapping_1 + TRI_PLANAR_MAPPING_2 : UseTriPlanarMapping_2 + TRI_PLANAR_MAPPING_3 : UseTriPlanarMapping_3 + TRI_PLANAR_MAPPING_4 : UseTriPlanarMapping_4 + TRI_PLANAR_MAPPING_5 : UseTriPlanarMapping_5 + TRI_PLANAR_MAPPING_6 : UseTriPlanarMapping_6 + TRI_PLANAR_MAPPING_7 : UseTriPlanarMapping_7 + TRI_PLANAR_MAPPING_8 : UseTriPlanarMapping_8 + TRI_PLANAR_MAPPING_9 : UseTriPlanarMapping_9 + TRI_PLANAR_MAPPING_10 : UseTriPlanarMapping_10 + TRI_PLANAR_MAPPING_11 : UseTriPlanarMapping_11 + + DEBUG_VALUES_MODE : DebugValuesMode + + } + } + + + Technique PreShadow { + + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldViewMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + DISCARD_ALPHA : AlphaDiscardThreshold + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + } + + ForcedRenderState { + FaceCull Off + DepthTest On + DepthWrite On + PolyOffset 5 3 + ColorWrite Off + } + + } + + + Technique PostShadow{ + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PostShadow.frag + + WorldParameters { + WorldViewProjectionMatrix + WorldMatrix + ViewProjectionMatrix + ViewMatrix + } + + Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer + HARDWARE_SHADOWS : HardwareShadows + FILTER_MODE : FilterMode + PCFEDGE : PCFEdge + DISCARD_ALPHA : AlphaDiscardThreshold + SHADOWMAP_SIZE : ShadowMapSize + FADE : FadeInfo + PSSM : Splits + POINTLIGHT : LightViewProjectionMatrix5 + NUM_BONES : NumberOfBones + INSTANCING : UseInstancing + BACKFACE_SHADOWS: BackfaceShadows + } + + ForcedRenderState { + Blend Modulate + DepthWrite Off + PolyOffset -0.1 0 + } + } + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert new file mode 100644 index 0000000000..8096ca9724 --- /dev/null +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/PBRTerrain.vert @@ -0,0 +1,37 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" +#import "Common/ShaderLib/Instancing.glsllib" + +attribute vec3 inPosition; +attribute vec3 inNormal; +attribute vec2 inTexCoord; + +varying vec2 texCoord; +varying vec3 wPosition; +varying vec3 wNormal; +varying vec3 lPosition; + +uniform vec4 g_AmbientLightColor; + +#ifdef USE_FOG + varying float fogDistance; + uniform vec3 g_CameraPosition; +#endif + +void main(){ + vec4 modelSpacePos = vec4(inPosition, 1.0); + + gl_Position = TransformWorldViewProjection(modelSpacePos); + + texCoord = inTexCoord; + + wPosition = (g_WorldMatrix * vec4(inPosition, 1.0)).xyz; + + wNormal = normalize(TransformWorldNormal(inNormal)); + + lPosition = modelSpacePos.xyz; + + #ifdef USE_FOG + fogDistance = distance(g_CameraPosition, (g_WorldMatrix * modelSpacePos).xyz); + #endif + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag index ac59e19cf3..426b665e94 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/BlinnPhongLighting.glsllib" #import "Common/ShaderLib/Lighting.glsllib" diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert index 78036c93cc..497f06b90c 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/SPTerrainLighting.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform mat4 g_WorldViewProjectionMatrix; uniform mat4 g_WorldViewMatrix; uniform mat3 g_NormalMatrix; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag index 7ae56cb4fd..ba7fac48f0 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.frag @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform sampler2D m_Alpha; uniform sampler2D m_Tex1; uniform sampler2D m_Tex2; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md index 0f12dac703..cda4487cb2 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.j3md @@ -1,32 +1,32 @@ MaterialDef Terrain { - MaterialParameters { + MaterialParameters { + Int BoundDrawBuffer // use tri-planar mapping Boolean useTriPlanarMapping - Texture2D Alpha -LINEAR - Texture2D Tex1 - Texture2D Tex2 - Texture2D Tex3 - Float Tex1Scale - Float Tex2Scale - Float Tex3Scale - } + Texture2D Alpha -LINEAR + Texture2D Tex1 + Texture2D Tex2 + Texture2D Tex3 + Float Tex1Scale + Float Tex2Scale + Float Tex3Scale + } + + Technique { + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/Terrain.frag - Technique { - VertexShader GLSL100: Common/MatDefs/Terrain/Terrain.vert - FragmentShader GLSL100: Common/MatDefs/Terrain/Terrain.frag - - WorldParameters { - WorldViewProjectionMatrix - } + WorldParameters { + WorldViewProjectionMatrix + } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TRI_PLANAR_MAPPING : useTriPlanarMapping } - } - - Technique { } -} \ No newline at end of file + +} diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert index ddb40a9f4d..4ac2736bf4 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/Terrain.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" uniform mat4 g_WorldViewProjectionMatrix; attribute vec3 inPosition; diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag index 9ce272fb73..8125982303 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.frag @@ -1,7 +1,12 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/BlinnPhongLighting.glsllib" #import "Common/ShaderLib/Lighting.glsllib" uniform float m_Shininess; +#ifdef SPECULARMAP + uniform sampler2D m_SpecularMap; +#endif + uniform vec4 g_LightDirection; varying vec4 AmbientSum; @@ -633,6 +638,19 @@ void main(){ vec3 normal = vNormal; #endif + //----------------------- + // read shininess or specularColor from specularMap (possibly want to create a new texture called ShininessMap if there is ever a need to have both a specularMap and reflectivityMap) + //----------------------- + vec4 specularColor = vec4(1.0); + float finalShininessValue = m_Shininess; + #ifdef SPECULARMAP + vec4 specularMapColor = texture2D(m_SpecularMap, texCoord); + #ifdef USE_SPECULARMAP_AS_SHININESS + finalShininessValue = specularMapColor.r; //assumes that specularMap is a gray-scale reflectivity/shininess map) + #else + specularColor = specularMapColor; + #endif + #endif //----------------------- // lighting calculations @@ -640,9 +658,7 @@ void main(){ vec4 lightDir = vLightDir; lightDir.xyz = normalize(lightDir.xyz); - vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,m_Shininess); - - vec4 specularColor = vec4(1.0); + vec2 light = computeLighting(normal, vViewDir.xyz, lightDir.xyz,lightDir.w*spotFallOff,finalShininessValue); //-------------------------- // final color calculations diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md index 0d62d901fb..4eb9c8d7a2 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.j3md @@ -2,6 +2,7 @@ MaterialDef Terrain Lighting { MaterialParameters { + Int BoundDrawBuffer // use tri-planar mapping Boolean useTriPlanarMapping @@ -102,14 +103,18 @@ MaterialDef Terrain Lighting { // The glow color of the object Color GlowColor + + // Use diffuse alpha when mixing + Boolean useSpecularMapAsShininess + } Technique { LightMode MultiPass - VertexShader GLSL100: Common/MatDefs/Terrain/TerrainLighting.vert - FragmentShader GLSL100: Common/MatDefs/Terrain/TerrainLighting.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/TerrainLighting.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/TerrainLighting.frag WorldParameters { WorldViewProjectionMatrix @@ -119,6 +124,7 @@ MaterialDef Terrain Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TRI_PLANAR_MAPPING : useTriPlanarMapping TERRAIN_GRID : isTerrainGrid WARDISO : WardIso @@ -165,6 +171,7 @@ MaterialDef Terrain Lighting { DIFFUSEMAP_11_SCALE : DiffuseMap_11_scale USE_ALPHA : useDiffuseAlpha + USE_SPECULARMAP_AS_SHININESS : useSpecularMapAsShininess } } @@ -173,8 +180,8 @@ MaterialDef Terrain Lighting { LightMode SinglePass - VertexShader GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.vert - FragmentShader GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Terrain/SPTerrainLighting.frag WorldParameters { WorldViewProjectionMatrix @@ -184,6 +191,7 @@ MaterialDef Terrain Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer TRI_PLANAR_MAPPING : useTriPlanarMapping TERRAIN_GRID : isTerrainGrid WARDISO : WardIso @@ -236,8 +244,8 @@ MaterialDef Terrain Lighting { Technique PreShadow { - VertexShader GLSL100 : Common/MatDefs/Shadow/PreShadow.vert - FragmentShader GLSL100 : Common/MatDefs/Shadow/PreShadow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Shadow/PreShadow.frag WorldParameters { WorldViewProjectionMatrix @@ -245,6 +253,7 @@ MaterialDef Terrain Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DIFFUSEMAP_ALPHA : DiffuseMap } @@ -260,8 +269,8 @@ MaterialDef Terrain Lighting { Technique PreNormalPass { - VertexShader GLSL100 : Common/MatDefs/SSAO/normal.vert - FragmentShader GLSL100 : Common/MatDefs/SSAO/normal.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/SSAO/normal.frag WorldParameters { WorldViewProjectionMatrix @@ -270,6 +279,7 @@ MaterialDef Terrain Lighting { } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer DIFFUSEMAP_ALPHA : DiffuseMap } @@ -283,14 +293,15 @@ MaterialDef Terrain Lighting { Technique Glow { - VertexShader GLSL100: Common/MatDefs/Misc/Unshaded.vert - FragmentShader GLSL100: Common/MatDefs/Light/Glow.frag + VertexShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL300 GLSL150 GLSL100: Common/MatDefs/Light/Glow.frag WorldParameters { WorldViewProjectionMatrix } Defines { + BOUND_DRAW_BUFFER: BoundDrawBuffer HAS_GLOWMAP : GlowMap HAS_GLOWCOLOR : GlowColor } diff --git a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert index e03f03f7ff..04ddbe5a20 100644 --- a/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert +++ b/jme3-terrain/src/main/resources/Common/MatDefs/Terrain/TerrainLighting.vert @@ -1,3 +1,4 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" #import "Common/ShaderLib/Lighting.glsllib" uniform mat4 g_WorldViewProjectionMatrix; diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/TestTerrainExporting.java b/jme3-terrain/src/test/java/com/jme3/terrain/TestTerrainExporting.java new file mode 100644 index 0000000000..7dceb63b0e --- /dev/null +++ b/jme3-terrain/src/test/java/com/jme3/terrain/TestTerrainExporting.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2021 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.terrain; + +import com.jme3.export.binary.BinaryExporter; +import com.jme3.math.FastMath; +import com.jme3.terrain.collision.BaseAWTTest; +import com.jme3.terrain.geomipmap.TerrainQuad; +import com.jme3.terrain.heightmap.AbstractHeightMap; +import com.jme3.terrain.heightmap.ImageBasedHeightMap; +import com.jme3.texture.Texture; +import org.junit.Assert; +import org.junit.Test; + +/** + * Test saving/loading terrain. + * + * @author Ali-RS + */ +public class TestTerrainExporting extends BaseAWTTest { + + /** + * Test saving/loading a TerrainQuad. + */ + @Test + public void testTerrainExporting() { + + Texture heightMapImage = getAssetManager().loadTexture("Textures/Terrain/splat/mountains512.png"); + AbstractHeightMap map = new ImageBasedHeightMap(heightMapImage.getImage(), 0.25f); + map.load(); + + TerrainQuad terrain = new TerrainQuad("Terrain", 65, 513, map.getHeightMap()); + + TerrainQuad saveAndLoad = BinaryExporter.saveAndLoad(getAssetManager(), terrain); + + Assert.assertEquals(513, saveAndLoad.getTotalSize()); + Assert.assertEquals(65, saveAndLoad.getPatchSize()); + Assert.assertArrayEquals(terrain.getHeightMap(), saveAndLoad.getHeightMap(), FastMath.ZERO_TOLERANCE); + } + +} diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseAWTTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseAWTTest.java index 4ff66b4bff..5a70a7e6c5 100644 --- a/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseAWTTest.java +++ b/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseAWTTest.java @@ -1,12 +1,11 @@ package com.jme3.terrain.collision; import com.jme3.asset.AssetManager; -import com.jme3.system.JmeDesktopSystem; import com.jme3.system.JmeSystem; /** * This class provides some utility functions to properly test the jMonkeyEngine.
                  - * Thus it contains simple methods to get and create a headless assetManager amongst other things.
                  + * It contains simple methods to get and create a headless assetManager amongst other things.
                  * In comparison to {@link BaseTest} it provides a DesktopAssetManager capable of loading image formats using AWT, which * however makes those tests unsuitable for headless ci testing. This requires jme3-desktop to be a testRuntime dependency. * diff --git a/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseTest.java b/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseTest.java index 15f7518558..e5544f32c8 100644 --- a/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseTest.java +++ b/jme3-terrain/src/test/java/com/jme3/terrain/collision/BaseTest.java @@ -5,7 +5,7 @@ /** * This class provides some utility functions to properly test the jMonkeyEngine.
                  - * Thus it contains simple methods to get and create a headless assetManager amongst other things.
                  + * It contains simple methods to get and create a headless assetManager amongst other things.
                  * If you need support for image/texture formats (png, tga, jpg, ...) see {@link BaseAWTTest} * * @author MeFisto94 diff --git a/jme3-testdata/build.gradle b/jme3-testdata/build.gradle index d025b5775d..7d82dc72fb 100644 --- a/jme3-testdata/build.gradle +++ b/jme3-testdata/build.gradle @@ -1,10 +1,2 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -def niftyVersion = '1.4.3' - dependencies { - runtime "com.github.nifty-gui:nifty-examples:$niftyVersion" - runtime "com.github.nifty-gui:nifty-style-black:$niftyVersion" } diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend b/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend deleted file mode 100644 index 617711c51b..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/BaseMesh_249.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend b/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend deleted file mode 100644 index f4149af044..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/BaseScene.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend b/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend deleted file mode 100644 index f2f794f14d..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/MountainValley_Track.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend b/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend deleted file mode 100644 index ea0404c87d..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/ObjectAnimation.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend b/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend deleted file mode 100644 index 8ca887d09c..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/SimpleAnimation.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend b/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend deleted file mode 100644 index 3737d0085b..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/Sinbad.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png deleted file mode 100644 index cfe461db2a..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - (Inverted Normal Map).png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png deleted file mode 100644 index 20caf50075..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter - Height Map.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png b/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png deleted file mode 100644 index 29d6867ab6..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/WoodCrate_lighter.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend b/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend deleted file mode 100644 index eabd593dce..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/animtest.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend b/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend deleted file mode 100644 index 53976ad014..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/constraints.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend b/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend deleted file mode 100644 index 9e9b73b1a2..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/curves.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend deleted file mode 100644 index 51b4266ebc..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png b/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png deleted file mode 100644 index 3776ea0179..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/kerrigan_diffuse.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend b/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend deleted file mode 100644 index e38be7355d..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/materials.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend b/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend deleted file mode 100644 index d722d851fc..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/modifiers.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend b/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend deleted file mode 100644 index 84b291b73b..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/nurbs.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend b/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend deleted file mode 100644 index ba7ee53ee4..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/particles.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend b/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend deleted file mode 100644 index d22e89796c..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/positions.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga deleted file mode 100644 index 0074ecd4c1..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_body.tga and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga deleted file mode 100644 index 51bf211bdb..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_clothes.tga and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga b/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga deleted file mode 100644 index 2cabb79e86..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/sinbad_sword.tga and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend b/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend deleted file mode 100644 index da8128d6b0..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/texturedPlaneTest.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend b/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend deleted file mode 100644 index 183fc88d1f..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG deleted file mode 100644 index b6713622e9..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Concrete_Wall.PNG and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png deleted file mode 100644 index 4b6cd8466a..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Grass_256.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png deleted file mode 100644 index 7b2e6ddd27..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SandDesert_StartTower.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png deleted file mode 100644 index ac0821d56a..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/SkyBox-Mountain.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png deleted file mode 100644 index 27ba25f320..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/Tar_Cracked.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png b/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png deleted file mode 100644 index 05366a7afa..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.4x/textures/WarningStrip.png and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend b/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend deleted file mode 100644 index 2d058d196d..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.5x/BaseMesh_256.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend b/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend deleted file mode 100644 index a4f743c17c..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/2.5x/textures.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Blender/test.conf b/jme3-testdata/src/main/resources/Blender/test.conf deleted file mode 100644 index 40b630423b..0000000000 Binary files a/jme3-testdata/src/main/resources/Blender/test.conf and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml index c945de6e2f..fc16771ff9 100644 --- a/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml +++ b/jme3-testdata/src/main/resources/Interface/Nifty/HelloJme.xml @@ -1,6 +1,6 @@ - + diff --git a/jme3-testdata/src/main/resources/Materials/Geom/SimpleGeom.geom b/jme3-testdata/src/main/resources/Materials/Geom/SimpleGeom.geom index 7acd056e8f..46d34e809f 100644 --- a/jme3-testdata/src/main/resources/Materials/Geom/SimpleGeom.geom +++ b/jme3-testdata/src/main/resources/Materials/Geom/SimpleGeom.geom @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + layout (points) in; layout (line_strip) out; layout (max_vertices = 11) out; diff --git a/jme3-testdata/src/main/resources/Materials/ImageTest.frag b/jme3-testdata/src/main/resources/Materials/ImageTest.frag new file mode 100644 index 0000000000..5e2339f06a --- /dev/null +++ b/jme3-testdata/src/main/resources/Materials/ImageTest.frag @@ -0,0 +1,11 @@ + +#import "Common/ShaderLib/GLSLCompat.glsllib" + +layout(RGBA8) uniform image2D m_TargetImage; + +void main() { + + gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); + imageStore(m_TargetImage, ivec2(gl_FragCoord.xy), vec4(0.0, 1.0, 0.0, 1.0)); + +} diff --git a/jme3-testdata/src/main/resources/Materials/ImageTest.j3md b/jme3-testdata/src/main/resources/Materials/ImageTest.j3md new file mode 100644 index 0000000000..0e11e66319 --- /dev/null +++ b/jme3-testdata/src/main/resources/Materials/ImageTest.j3md @@ -0,0 +1,20 @@ +MaterialDef ImageTest { + + MaterialParameters { + + Image2D TargetImage + + } + + Technique { + + VertexShader GLSL400 GLSL320 GLSL150 : Common/MatDefs/Misc/Unshaded.vert + FragmentShader GLSL400 GLSL320 GLSL150 : Materials/ImageTest.frag + + WorldParameters { + WorldViewProjectionMatrix + } + + } + +} diff --git a/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tsctrl b/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tsctrl index 81b4e291a5..2732b59f1f 100644 --- a/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tsctrl +++ b/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tsctrl @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + layout(vertices=4) out; out gl_PerVertex{ vec4 gl_Position; diff --git a/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tseval b/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tseval index 4dbf976f31..1bc159a663 100644 --- a/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tseval +++ b/jme3-testdata/src/main/resources/Materials/Tess/SimpleTess.tseval @@ -1,3 +1,5 @@ +#import "Common/ShaderLib/GLSLCompat.glsllib" + layout (quads,equal_spacing,cw) in; uniform mat4 g_WorldViewProjectionMatrix; diff --git a/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend b/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend deleted file mode 100644 index ee44ffb9ce..0000000000 Binary files a/jme3-testdata/src/main/resources/Models/HoverTank/tankFinalExport.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend b/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend deleted file mode 100644 index 51890a2387..0000000000 Binary files a/jme3-testdata/src/main/resources/Models/TangentBugs/test.blend and /dev/null differ diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.mtl b/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.mtl new file mode 100644 index 0000000000..c85a0ddaa4 --- /dev/null +++ b/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.mtl @@ -0,0 +1,20 @@ +newmtl dot_purple +map_Kd -o 0 0 -s 1 1 dot_purple.png +Kd 1 1 1 +d 1 + +newmtl dot_red +map_Kd -o 0 0 -s 1 1 dot_red.png +Kd 1 1 1 +d 1 + +newmtl dot_blue +map_Kd -o 0 0 -s 1 1 dot_blue.png +Kd 1 1 1 +d 1 + +newmtl dot_green +map_Kd -o 0 0 -s 1 1 dot_green.png +Kd 1 1 1 +d 1 + diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.obj b/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.obj new file mode 100644 index 0000000000..727ec46190 --- /dev/null +++ b/jme3-testdata/src/main/resources/OBJLoaderTest/TwoChairs.obj @@ -0,0 +1,219 @@ +# ProBuilder 4.2.3 +# https://unity3d.com/unity/features/worldbuilding/probuilder +# 5/10/2020 4:30:31 PM + +mtllib ./TwoChairs.mtl +o TwoChairs + +v -4 1 0 +v -5 1 0 +v -4 2 0 +v -5 2 0 +v -5 1 -1 +v -5 2 -1 +v -5 1 -1.25 +v -4 1 -1.25 + +s 1 +v -5 2 -1.25 +v -4 2 -1.25 +v -4 1 -1 +v -4 2 -1 +v -4 3 -1 +v -5 3 -1 +v -4 3 -1.25 +v -5 3 -1.25 +s off + +vt 0 0 +vt -1 0 +vt 0 1 +vt -1 1 +vt 1 0 +vt 1 1 +vt -1 -1 +vt 0 -1 +vt -1 -1.25 +vt 0 -1.25 +vt -1.25 1 +vt -1.25 0 +vt 1.25 0 +vt 1.25 1 +vt 1 -1 +vt 1 -1.25 +vt 0 2 +vt -1 2 +vt 1.25 2 +vt 1 2 +vt -1.25 2 + +vn 0 0 1 +vn -1 0 0 +vn 0 0 -1 +vn 1 0 0 +vn 0 -1 0 +vn 0 1 0 + +usemtl dot_purple +f 3/3/1 4/4/1 2/2/1 1/1/1 +f 4/3/2 6/4/2 5/2/2 2/1/2 +f 9/6/3 10/3/3 8/1/3 7/5/3 +f 12/6/4 3/3/4 1/1/4 11/5/4 +f 7/9/5 8/10/5 11/8/5 5/7/5 +f 9/11/2 7/12/2 5/2/2 6/4/2 +f 8/13/4 10/14/4 12/6/4 11/5/4 +f 15/10/6 16/16/6 14/15/6 13/8/6 +f 13/17/1 14/18/1 6/4/1 12/3/1 +f 15/19/4 13/20/4 12/6/4 10/14/4 +f 14/18/2 16/21/2 9/11/2 6/4/2 +f 16/20/3 15/17/3 10/3/3 9/6/3 + +g Chair 2 +v -2 1 0 +v -3 1 0 +v -2 2 0 +v -3 2 0 +v -3 1 -1 +v -3 2 -1 +v -3 1 -1.25 +v -2 1 -1.25 +v -3 2 -1.25 +v -2 2 -1.25 +v -2 1 -1 +v -2 2 -1 +v -2 3 -1 +v -3 3 -1 +v -2 3 -1.25 +v -3 3 -1.25 + +vt 0 0 +vt -1 0 +vt 0 1 +vt -1 1 +vt 1 0 +vt 1 1 +vt -1 -1 +vt 0 -1 +vt -1 -1.25 +vt 0 -1.25 +vt -1.25 1 +vt -1.25 0 +vt 1.25 0 +vt 1.25 1 +vt 1 -1 +vt 1 -1.25 +vt 0 2 +vt -1 2 +vt 1.25 2 +vt 1 2 +vt -1.25 2 + +vn 0 0 1 +vn -1 0 0 +vn 0 0 -1 +vn 1 0 0 +vn 0 -1 0 +vn 0 1 0 + +usemtl dot_purple +f 19/24/7 20/25/7 18/23/7 17/22/7 +f 20/24/8 22/25/8 21/23/8 18/22/8 +f 25/27/9 26/24/9 24/22/9 23/26/9 +f 28/27/10 19/24/10 17/22/10 27/26/10 +f 23/30/11 24/31/11 27/29/11 21/28/11 +f 25/32/8 23/33/8 21/23/8 22/25/8 +f 24/34/10 26/35/10 28/27/10 27/26/10 +f 31/31/12 32/37/12 30/36/12 29/29/12 +f 29/38/7 30/39/7 22/25/7 28/24/7 +f 31/40/10 29/41/10 28/27/10 26/35/10 +f 30/39/8 32/42/8 25/32/8 22/25/8 +f 32/41/9 31/38/9 26/24/9 25/27/9 + +g Pillow 2 +v -2 2 0 +v -3 2 0 +v -2 2 -1 +v -3 2 -1 + +vt 0 0 +vt 1 0 +vt 0 -1 +vt 1 -1 + +vn 0 1 0 + +usemtl dot_red +f 35/45/13 36/46/13 34/44/13 33/43/13 + +g Podium +v -1 0 1.5 +v -6 0 1.5 +v -1 1 1.5 +v -6 1 1.5 +v -6 0 -2 +v -6 1 -2 +v -1 0 -2 +v -1 1 -2 + +vt -1 0 +vt -6 0 +vt -1 1 +vt -6 1 +vt 1.5 0 +vt -2 0 +vt 1.5 1 +vt -2 1 +vt 6 0 +vt 1 0 +vt 6 1 +vt 1 1 + +s 1 +vt 2 0 +vt -1.5 0 +vt 2 1 +vt -1.5 1 +vt 1 1.5 +vt 6 1.5 +vt 1 -2 +vt 6 -2 +vt -1 -2 +vt -6 -2 +vt -1 1.5 +vt -6 1.5 +s off + +vn 0 0 1 +vn 0 0.7071068 0.7071068 +vn -1 0 0 +vn 0 0 -1 +vn 1 0 0 +vn 0 1 0 +vn 0 -1 0 + +usemtl dot_red +f 39/49/15 40/50/15 38/48/14 37/47/14 +f 40/53/16 42/54/16 41/52/16 38/51/16 +f 42/57/17 44/58/17 43/56/17 41/55/17 +f 44/61/18 39/62/18 37/60/18 43/59/18 +f 37/69/20 38/70/20 41/68/20 43/67/20 + +usemtl dot_blue +f 44/65/19 42/66/19 40/64/15 39/63/15 + +g Pillow 1 +v -4 2 0 +v -5 2 0 +v -4 2 -1 +v -5 2 -1 + +vt 0 0 +vt 1 0 +vt 0 -1 +vt 1 -1 + +vn 0 1 0 + +usemtl dot_green +f 47/73/21 48/74/21 46/72/21 45/71/21 + diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/dot_blue.png b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_blue.png new file mode 100644 index 0000000000..0a908db378 Binary files /dev/null and b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_blue.png differ diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/dot_green.png b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_green.png new file mode 100644 index 0000000000..74f4a1707e Binary files /dev/null and b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_green.png differ diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/dot_purple.png b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_purple.png new file mode 100644 index 0000000000..4af4209e98 Binary files /dev/null and b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_purple.png differ diff --git a/jme3-testdata/src/main/resources/OBJLoaderTest/dot_red.png b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_red.png new file mode 100644 index 0000000000..c6f2ae931f Binary files /dev/null and b/jme3-testdata/src/main/resources/OBJLoaderTest/dot_red.png differ diff --git a/jme3-testdata/src/main/resources/Scenes/LightProbes/license.txt b/jme3-testdata/src/main/resources/Scenes/LightProbes/license.txt new file mode 100644 index 0000000000..1ecf9e0610 --- /dev/null +++ b/jme3-testdata/src/main/resources/Scenes/LightProbes/license.txt @@ -0,0 +1,15 @@ +Licensing history for files in "src/main/resources/Scenes/LightProbes" + +Based on "Quarry 03": +* download file: quarry_03.jpg +* downloaded from: https://hdrihaven.com/hdri/?h=quarry_03 +* author: Sergej Majboroda +* author URL: https://hdrihaven.com/hdris/?a=Sergej%20Majboroda +* license type: CC0 +* license URL: https://creativecommons.org/publicdomain/zero/1.0/ + +Derived files (also licensed CC0): +* quarry_Probe.j3o + +Added files (also licensed CC0): +* license.txt diff --git a/jme3-testdata/src/main/resources/Scenes/LightProbes/quarry_Probe.j3o b/jme3-testdata/src/main/resources/Scenes/LightProbes/quarry_Probe.j3o new file mode 100644 index 0000000000..e3091de5f6 Binary files /dev/null and b/jme3-testdata/src/main/resources/Scenes/LightProbes/quarry_Probe.j3o differ diff --git a/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene b/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene index eb260fbc35..fa1f15f054 100644 --- a/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene +++ b/jme3-testdata/src/main/resources/Scenes/ManyLights/Main.scene @@ -177,7 +177,7 @@ - + diff --git a/jme3-testdata/src/main/resources/Textures/Sky/Earth/Earth.jpg b/jme3-testdata/src/main/resources/Textures/Sky/Earth/Earth.jpg new file mode 100644 index 0000000000..6cdcffea63 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Sky/Earth/Earth.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..a45da77a01 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Color.png new file mode 100644 index 0000000000..313fd04eba Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Displacement.png new file mode 100644 index 0000000000..95f46e60ba Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Normal.png new file mode 100644 index 0000000000..024b2eb09e Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Roughness.png new file mode 100644 index 0000000000..1847ce1e45 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel015_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..0f52760080 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Gravel_015_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..367adfacb3 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Color.png new file mode 100644 index 0000000000..07679c2c2a Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Displacement.png new file mode 100644 index 0000000000..ec1143d8ee Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Normal.png new file mode 100644 index 0000000000..d4f2e27d65 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Roughness.png new file mode 100644 index 0000000000..cd2bde7c71 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..2beede4711 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground036_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..de831810ef Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Color.png new file mode 100644 index 0000000000..930ed0a84d Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Displacement.png new file mode 100644 index 0000000000..7dbefe48ad Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Normal.png new file mode 100644 index 0000000000..a8172fc43b Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Roughness.png new file mode 100644 index 0000000000..e203ae5f62 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..e9ffa3941a Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Ground037_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Color.png new file mode 100644 index 0000000000..25caf7a9b7 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Displacement.png new file mode 100644 index 0000000000..352c9bff38 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Normal.png new file mode 100644 index 0000000000..fee0636254 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Roughness.png new file mode 100644 index 0000000000..6956a7f5cf Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..ba48ec15c2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Marble013_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..03a376f94a Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.jpg b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.jpg new file mode 100644 index 0000000000..13fbd8ff75 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.jpg differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.png new file mode 100644 index 0000000000..041d698b9b Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Displacement.png new file mode 100644 index 0000000000..ec65b697f6 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Normal.png new file mode 100644 index 0000000000..9659eb3459 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Roughness.png new file mode 100644 index 0000000000..df75e4b5c2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..13e9a214da Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Rock035_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..84a5150cdd Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Color.png new file mode 100644 index 0000000000..fed65cf206 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Displacement.png new file mode 100644 index 0000000000..ea31a4ee49 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Normal.png new file mode 100644 index 0000000000..0f259d0993 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Roughness.png new file mode 100644 index 0000000000..1ac43267c2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..8584462b65 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Snow006_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_AmbientOcclusion.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_AmbientOcclusion.png new file mode 100644 index 0000000000..8a946e0e02 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_AmbientOcclusion.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Color.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Color.png new file mode 100644 index 0000000000..ac8af44b5f Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Color.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Displacement.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Displacement.png new file mode 100644 index 0000000000..549b7e0e13 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Displacement.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Normal.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Normal.png new file mode 100644 index 0000000000..114231cb15 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Normal.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Roughness.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Roughness.png new file mode 100644 index 0000000000..12d50429b2 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_1K_Roughness.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png new file mode 100644 index 0000000000..d629d175d5 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/Tiles083_PackedMetallicRoughnessMap.png differ diff --git a/jme3-testdata/src/main/resources/Textures/Terrain/PBR/license.txt b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/license.txt new file mode 100644 index 0000000000..757008a8dc --- /dev/null +++ b/jme3-testdata/src/main/resources/Textures/Terrain/PBR/license.txt @@ -0,0 +1,58 @@ +Licensing history for files in "src/main/resources/Textures/Terrain/PBR" + +All textures were downloaded and derived from: +* downloaded from: https://cc0textures.com/ +* license type: CC0 +* license URL: https://docs.cc0textures.com/licensing.html + +* Files Downloaded: +Gravel015_1K_Color.png +Gravel015_1K_Normal.png +Ground036_1K_Color.png +Ground036_1K_Normal.png +Ground037_1K_Color.png +Ground037_1K_Normal.png +Marble013_1K_Color.png +Marble013_1K_Normal.png +Rock035_1K_Color.png +Rock035_1K_Normal.png +Snow006_1K_Color.png +Snow006_1K_Normal.png +Tiles083_1K_Color.png +Tiles083_1K_Normal.png +Gravel015_1K_Displacement.png +Gravel015_1K_AmbientOcclusion.png +Ground036_1K_Displacement.png +Ground036_1K_AmbientOcclusion.png +Ground037_1K_Displacement.png +Ground037_1K_AmbientOcclusion.png +Marble013_1K_Displacement.png +Marble013_1K_AmbientOcclusion.png +Rock035_1K_Displacement.png +Rock035_1K_AmbientOcclusion.png +Snow006_1K_Displacement.png +Snow006_1K_AmbientOcclusion.png +Tiles083_1K_Displacement.png +Tiles083_1K_AmbientOcclusion.png +Gravel015_1K_Roughness.png +Ground036_1K_Roughness.png +Ground037_1K_Roughness.png +Marble013_1K_Roughness.png +Rock035_1K_Roughness.png +Snow006_1K_Roughness.png +Tiles083_1K_Roughness.png + + + +*Files Derived: +Gravel_015_PackedMetallicRoughnessMap.png +Ground036_PackedMetallicRoughnessMap.png +Ground037_PackedMetallicRoughnessMap.png +Marble013_PackedMetallicRoughnessMap.png +Rock035_PackedMetallicRoughnessMap.png +Snow006_PackedMetallicRoughnessMap.png +Tiles083_PackedMetallicRoughnessMap.png + + + +Contains assets from CC0Textures.com, licensed under CC0 1.0 Universal. diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC1_12.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC1_12.DDS new file mode 100644 index 0000000000..4e0873d44b Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC1_12.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC2_11.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC2_11.DDS new file mode 100644 index 0000000000..dddb744259 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC2_11.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC3_10.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC3_10.DDS new file mode 100644 index 0000000000..991a5d56c4 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC3_10.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_9.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_9.DDS new file mode 100644 index 0000000000..3b57a9174c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_9.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_S_8.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_S_8.DDS new file mode 100644 index 0000000000..aac2afd909 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC4_S_8.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_7.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_7.DDS new file mode 100644 index 0000000000..62f05c6c2d Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_7.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_S_6.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_S_6.DDS new file mode 100644 index 0000000000..3cd3a96100 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC5_S_6.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_3.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_3.DDS new file mode 100644 index 0000000000..df0cb2a665 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_3.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_SF_2.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_SF_2.DDS new file mode 100644 index 0000000000..d900e45f2c Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC6H_SF_2.DDS differ diff --git a/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC7_1.DDS b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC7_1.DDS new file mode 100644 index 0000000000..f7d177f842 Binary files /dev/null and b/jme3-testdata/src/main/resources/Textures/dds/Monkey_PNG_BC7_1.DDS differ diff --git a/jme3-vr/build.gradle b/jme3-vr/build.gradle deleted file mode 100644 index e3db2d6ead..0000000000 --- a/jme3-vr/build.gradle +++ /dev/null @@ -1,29 +0,0 @@ -if (!hasProperty('mainClass')) { - ext.mainClass = '' -} - -def lwjglVersion = '3.2.3' - -sourceCompatibility = '1.8' - -dependencies { - compile project(':jme3-core') - compile project(':jme3-lwjgl3') - compile project(':jme3-effects') - - // https://mvnrepository.com/artifact/net.java.dev.jna/jna - compile group: 'net.java.dev.jna', name: 'jna', version: '4.3.0' - compile 'com.nativelibs4java:jnaerator-runtime:0.12' - - // Native LibOVR/Oculus support - compile "org.lwjgl:lwjgl-ovr:${lwjglVersion}" - runtime "org.lwjgl:lwjgl-ovr:${lwjglVersion}:natives-windows" - - // Native OpenVR/LWJGL support - compile "org.lwjgl:lwjgl-openvr:${lwjglVersion}" - compile "org.lwjgl:lwjgl-openvr:${lwjglVersion}:natives-linux" - compile "org.lwjgl:lwjgl-openvr:${lwjglVersion}:natives-macos" - runtime "org.lwjgl:lwjgl-openvr:${lwjglVersion}:natives-windows" - runtime "org.lwjgl:lwjgl-openvr:${lwjglVersion}:natives-linux" - runtime "org.lwjgl:lwjgl-openvr:${lwjglVersion}:natives-macos" -} diff --git a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java b/jme3-vr/src/main/java/com/jme3/app/VRAppState.java deleted file mode 100644 index e4b3f18253..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRAppState.java +++ /dev/null @@ -1,635 +0,0 @@ -package com.jme3.app; - -/* - * Copyright (c) 2009-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -import com.jme3.app.Application; -import com.jme3.app.state.AbstractAppState; -import com.jme3.app.state.AppStateManager; -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRMouseManager; -import com.jme3.input.vr.VRViewManager; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.post.PreNormalCaching; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import com.jme3.util.VRGUIPositioningMode; -import com.jme3.util.VRGuiManager; - -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A JMonkey app state dedicated to Virtual Reality. - * An application that want to use VR devices (HTC vive, ...) has to use this app state.
                  - * As this app state and the main {@link Application application} have to share {@link AppSettings application settings}, - * the common way to use this app state is:
                  - *
                    - *
                  • To create {@link AppSettings application settings} and set the VR related settings (see {@link VRConstants}). - *
                  • To instantiate this app state with the created settings. - *
                  • To instantiate the main {@link Application application} and to attach it to the created settings (with {@link Application#setSettings(AppSettings) setSettings(AppSettings)}). - *
                  • To start the main {@link Application application}. - *
                  - * Attaching an instance of this app state to an already started application may cause crashes. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class VRAppState extends AbstractAppState { - - private static final Logger logger = Logger.getLogger(VRAppState.class.getName()); - - /** - * Is the application has not to start within VR mode (default is false). - */ - public boolean DISABLE_VR = false; - - - - private float fFar = 1000f; - private float fNear = 0.1f; - private int xWin = 1920; - private int yWin = 1080; - - private float resMult = 1f; - - /* - where is the headset pointing, after all rotations are combined? - depends on observer rotation, if any - */ - private Quaternion tempq = new Quaternion(); - - private Application application = null; - private AppStateManager stateManager = null; - private AppSettings settings = null; - - private VREnvironment environment = null; - - /** - * Create a new default VR app state that relies on the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} that this app state is using. - */ - public VRAppState(VREnvironment environment) { - super(); - - this.environment = environment; - - this.setSettings(environment.getSettings()); - } - - /** - * Create a new VR app state with given settings. The app state relies on the given {@link VREnvironment VR environment}. - * @param settings the settings to use. - * @param environment the {@link VREnvironment VR environment} that this app state is using. - */ - public VRAppState(AppSettings settings, VREnvironment environment){ - this(environment); - this.settings = settings; - processSettings(settings); - } - - - /** - * Simple update of the app state, this method should contains any spatial updates. - * This method is called by the {@link #update(float) update()} method and should not be called manually. - * @param tpf the application time. - */ - public void simpleUpdate(float tpf) { - return; - } - - /** - * Rendering callback of the app state. This method is called by the {@link #update(float) update()} method and should not be called manually. - * @param renderManager the {@link RenderManager render manager}. - */ - public void simpleRender(RenderManager renderManager) { - PreNormalCaching.resetCache(environment.isInVR()); - } - - /** - * Set the frustum values for the application. - * @param near the frustum near value. - * @param far the frustum far value. - */ - public void setFrustrumNearFar(float near, float far) { - fNear = near; - fFar = far; - } - - /** - * Set the mirror window size in pixel. - * @param width the width of the mirror window in pixel. - * @param height the height of the mirror window in pixel. - */ - public void setMirrorWindowSize(int width, int height) { - xWin = width; - yWin = height; - } - - /** - * Set the resolution multiplier. - * @param val the resolution multiplier. - */ - public void setResolutionMultiplier(float val) { - resMult = val; - if( environment.getVRViewManager() != null ){ - environment.getVRViewManager().setResolutionMultiplier(resMult); - } - } - - - /** - * Move filters from the main scene into the eye's. - * This removes filters from the main scene. - */ - public void moveScreenProcessingToVR() { - environment.getVRViewManager().moveScreenProcessingToEyes(); - } - - /** - * Get the observer final rotation within the scene. - * @return the observer final rotation within the scene. - * @see #getFinalObserverPosition() - */ - public Quaternion getFinalObserverRotation() { - if( environment.getVRViewManager() == null ) { - if( environment.getObserver() == null ) { - return environment.getCamera().getRotation(); - } else { - return ((Spatial)environment.getObserver()).getWorldRotation(); - } - } - - if( environment.getObserver() == null ) { - tempq.set(environment.getDummyCamera().getRotation()); - } else { - tempq.set(((Spatial)environment.getObserver()).getWorldRotation()); - } - return tempq.multLocal(environment.getVRHardware().getOrientation()); - } - - /** - * Get the observer final position within the scene. - * @return the observer position. - * @see #getFinalObserverRotation() - */ - public Vector3f getFinalObserverPosition() { - if( environment.getVRViewManager() == null ) { - if( environment.getObserver() == null ) { - return environment.getCamera().getLocation(); - } else{ - return ((Spatial)environment.getObserver()).getWorldTranslation(); - } - } - - Vector3f pos = environment.getVRHardware().getPosition(); - if( environment.getObserver() == null ) { - environment.getDummyCamera().getRotation().mult(pos, pos); - return pos.addLocal(environment.getDummyCamera().getLocation()); - } else { - ((Spatial)environment.getObserver()).getWorldRotation().mult(pos, pos); - return pos.addLocal(((Spatial)environment.getObserver()).getWorldTranslation()); - } - } - - /** - * Get the VR headset left viewport. - * @return the VR headset left viewport. - * @see #getRightViewPort() - */ - public ViewPort getLeftViewPort() { - if( environment.getVRViewManager() == null ){ - return application.getViewPort(); - } - - return environment.getVRViewManager().getLeftViewPort(); - } - - /** - * Get the VR headset right viewport. - * @return the VR headset right viewport. - * @see #getLeftViewPort() - */ - public ViewPort getRightViewPort() { - if( environment.getVRViewManager() == null ){ - return application.getViewPort(); - } - return environment.getVRViewManager().getRightViewPort(); - } - - /** - * Set the background color for both left and right view ports. - * @param clr the background color. - */ - public void setBackgroundColors(ColorRGBA clr) { - if( environment.getVRViewManager() == null ) { - application.getViewPort().setBackgroundColor(clr); - } else if( environment.getVRViewManager().getLeftViewPort() != null ) { - - environment.getVRViewManager().getLeftViewPort().setBackgroundColor(clr); - - if( environment.getVRViewManager().getRightViewPort() != null ){ - environment.getVRViewManager().getRightViewPort().setBackgroundColor(clr); - } - } - } - - /** - * Get the {@link Application} to which this app state is attached. - * @return the {@link Application} to which this app state is attached. - * @see #getStateManager() - */ - public Application getApplication(){ - return application; - } - - /** - * Get the {@link AppStateManager state manager} to which this app state is attached. - * @return the {@link AppStateManager state manager} to which this app state is attached. - * @see #getApplication() - */ - public AppStateManager getStateManager(){ - return stateManager; - } - - /** - * Get the scene observer. If no observer has been set, this method returns the application camera. - * @return the scene observer. - * @see #setObserver(Spatial) - */ - public Object getObserver() { - return environment.getObserver(); - } - - /** - * Set the scene observer. The VR headset will be linked to it. If no observer is set, the VR headset is linked to the application camera. - * @param observer the scene observer. - */ - public void setObserver(Spatial observer) { - environment.setObserver(observer); - } - - /** - * Check if the rendering is instanced (see Geometry instancing). - * @return true if the rendering is instanced and false otherwise. - */ - public boolean isInstanceRendering() { - return environment.isInstanceRendering(); - } - - /** - * Return the {@link VREnvironment VR environment} on which this app state relies. - * @return the {@link VREnvironment VR environment} on which this app state relies. - */ - public VREnvironment getVREnvironment(){ - return environment; - } - - /** - * Get the VR underlying hardware. - * @return the VR underlying hardware. - */ - public VRAPI getVRHardware() { - return getVREnvironment().getVRHardware(); - } - - /** - * Get the VR dedicated input. - * @return the VR dedicated input. - */ - public VRInputAPI getVRinput() { - if( getVREnvironment().getVRHardware() == null ){ - return null; - } - - return getVREnvironment().getVRHardware().getVRinput(); - } - - /** - * Get the VR view manager. - * @return the VR view manager. - */ - public VRViewManager getVRViewManager() { - return getVREnvironment().getVRViewManager(); - } - - /** - * Get the GUI manager attached to this app state. - * @return the GUI manager attached to this app state. - */ - public VRGuiManager getVRGUIManager(){ - return getVREnvironment().getVRGUIManager(); - } - - /** - * Get the VR mouse manager attached to this app state. - * @return the VR mouse manager attached to this application. - */ - public VRMouseManager getVRMouseManager(){ - return getVREnvironment().getVRMouseManager(); - } - - /** - * Get the {@link AppSettings settings} attached to this app state. - * @return the {@link AppSettings settings} attached to this app state. - * @see #setSettings(AppSettings) - */ - public AppSettings getSettings(){ - return settings; - } - - /** - * Set the {@link AppSettings settings} attached to this app state. - * @param settings the {@link AppSettings settings} attached to this app state. - * @see #getSettings() - */ - public void setSettings(AppSettings settings){ - this.settings = settings; - processSettings(settings); - } - - @Override - public void update(float tpf) { - - // update VR pose & cameras - if( environment.getVRViewManager() != null ) { - environment.getVRViewManager().update(tpf); - } else if( environment.getObserver() != null ) { - environment.getCamera().setFrame(((Spatial)environment.getObserver()).getWorldTranslation(), ((Spatial)environment.getObserver()).getWorldRotation()); - } - - if( environment.isInVR() == false || environment.getVRGUIManager().getPositioningMode() == VRGUIPositioningMode.MANUAL ) { - // only update geometric state here if GUI is in manual mode, or not in VR - // it will get updated automatically in the viewmanager update otherwise - // TODO isn't this done by SimpleApplication? - for (Spatial spatial : application.getGuiViewPort().getScenes()) { - //spatial.updateLogicalState(tpf); - spatial.updateGeometricState(); - } - } - - // use the analog control on the first tracked controller to push around the mouse - environment.getVRMouseManager().updateAnalogAsMouse(0, null, null, null, tpf); - } - - @Override - public void render(RenderManager rm) { - super.render(rm); - - // update compositor - if( environment.getVRViewManager() != null ) { - environment.getVRViewManager().render(); - } - } - - @Override - public void postRender() { - super.postRender(); - - // update compositor - if( environment.getVRViewManager() != null ) { - environment.getVRViewManager().postRender(); - } - } - - @Override - public void initialize(AppStateManager stateManager, Application app) { - super.initialize(stateManager, app); - - this.application = app; - this.stateManager = stateManager; - - // disable annoying warnings about GUI stuff being updated, which is normal behavior - // for late GUI placement for VR purposes - Logger.getLogger("com.jme3").setLevel(Level.SEVERE); - - app.getCamera().setFrustumFar(fFar); - app.getCamera().setFrustumNear(fNear); - - if( environment.isInVR() ) { - - logger.config("VR mode enabled."); - - if( environment.getVRHardware() != null ) { - environment.getVRHardware().initVRCompositor(environment.compositorAllowed()); - } else { - logger.warning("No VR system found."); - } - - - environment.getVRViewManager().setResolutionMultiplier(resMult); - //inputManager.addMapping(RESET_HMD, new KeyTrigger(KeyInput.KEY_F9)); - //setLostFocusBehavior(LostFocusBehavior.Disabled); - } else { - logger.config("VR mode disabled."); - //viewPort.attachScene(rootNode); - //guiViewPort.attachScene(guiNode); - } - - if( environment.getVRViewManager() != null ) { - environment.getVRViewManager().initialize(); - } - } - - @Override - public void stateAttached(AppStateManager stateManager) { - super.stateAttached(stateManager); //To change body of generated methods, choose Tools | Templates. - - if (settings == null) { - settings = new AppSettings(true); - logger.config("Using default settings."); - } else { - logger.config("Using given settings."); - } - - // Attach VR environment to the application - if (!environment.isInitialized()){ - environment.initialize(); - } - - if (environment.isInitialized()){ - environment.atttach(this, stateManager.getApplication()); - } else { - logger.severe("Cannot attach VR environment to the VR app state as its not initialized."); - } - - GraphicsDevice defDev = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice(); - - if( environment.isInVR() && !environment.compositorAllowed() ) { - // "easy extended" mode - // setup experimental JFrame on external device - // first, find the VR device - GraphicsDevice VRdev = null; - GraphicsDevice[] devs = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); - // pick the display that isn't the default one - for(GraphicsDevice gd : devs) { - if( gd != defDev ) { - VRdev = gd; - break; - } - } - - // did we get the VR device? - if( VRdev != null ) { - // set properties for VR acceleration - try { - java.awt.DisplayMode useDM = null; - int max = 0; - for(java.awt.DisplayMode dm : VRdev.getDisplayModes()) { - int check = dm.getHeight() + dm.getWidth() + dm.getRefreshRate() + dm.getBitDepth(); - if( check > max ) { - max = check; - useDM = dm; - } - } - - // create a window for the VR device - settings.setWidth(useDM.getWidth()); - settings.setHeight(useDM.getHeight()); - settings.setBitsPerPixel(useDM.getBitDepth()); - settings.setFrequency(useDM.getRefreshRate()); - settings.setSwapBuffers(true); - settings.setVSync(true); // allow vsync on this display - stateManager.getApplication().setSettings(settings); - logger.config("Updated underlying application settings."); - - //VRdev.setFullScreenWindow(VRwindow); - // make sure we are in the right display mode - if( VRdev.getDisplayMode().equals(useDM) == false ) { - VRdev.setDisplayMode(useDM); - } - - return; - } catch(Exception e) { - logger.log(Level.SEVERE, e.getMessage(), e); - } - } else { - logger.config("Cannot access to external screen."); - } - } else { - if (!environment.isInVR()){ - logger.config("Cannot switch to VR mode (VR disabled by user)."); - } else if (!environment.compositorAllowed()){ - logger.warning("Cannot switch to VR mode (VR not supported)."); - } - } - - if( !environment.isInVR() ) { - - //FIXME: Handling GLFW workaround on MacOS - boolean macOs = false; - if (macOs) { - // 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); - } - settings.setSwapBuffers(true); - } else { - // use basic mirroring window, skip settings window - settings.setSamples(1); - settings.setWidth(xWin); - settings.setHeight(yWin); - settings.setBitsPerPixel(32); - settings.setFrameRate(0); - settings.setFrequency(environment.getVRHardware().getDisplayFrequency()); - settings.setFullscreen(false); - settings.setVSync(false); // stop vsyncing on primary monitor! - settings.setSwapBuffers(environment.isSwapBuffers()); - } - - // Updating application settings - stateManager.getApplication().setSettings(settings); - logger.config("Updated underlying application settings."); - - } - - @Override - public void cleanup() { - if( environment.getVRHardware() != null ) { - environment.getVRHardware().destroy(); - } - - this.application = null; - this.stateManager = null; - } - - @Override - public void stateDetached(AppStateManager stateManager) { - super.stateDetached(stateManager); - } - - /** - * Process the attached settings and apply changes to this app state. - * @param settings the app settings to process. - */ - protected void processSettings(AppSettings settings){ - if (settings != null){ - - if (settings.get(VRConstants.SETTING_DISABLE_VR) != null){ - DISABLE_VR = settings.getBoolean(VRConstants.SETTING_DISABLE_VR); - } - } - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java b/jme3-vr/src/main/java/com/jme3/app/VRApplication.java deleted file mode 100644 index c81d9b8825..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRApplication.java +++ /dev/null @@ -1,1535 +0,0 @@ -package com.jme3.app; - -import com.jme3.app.AppTask; -import com.jme3.app.Application; -import com.jme3.app.LegacyApplication; -import com.jme3.app.LostFocusBehavior; -import com.jme3.app.ResetStatsState; -import com.jme3.app.SimpleApplication; -import com.jme3.app.state.AppState; -import com.jme3.app.state.AppStateManager; -import com.jme3.asset.AssetManager; -import com.jme3.audio.AudioContext; -import com.jme3.audio.AudioRenderer; -import com.jme3.audio.Listener; -import com.jme3.input.InputManager; -import com.jme3.input.JoyInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.controls.KeyTrigger; -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.openvr.OpenVR; -import com.jme3.input.vr.openvr.OpenVRMouseManager; -import com.jme3.input.vr.openvr.OpenVRViewManager; -import com.jme3.input.vr.osvr.OSVR; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; -import com.jme3.post.PreNormalCaching; -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; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.Spatial.CullHint; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeContext; -import com.jme3.system.JmeContext.Type; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.JmeSystem; -import com.jme3.system.NanoTimer; -import com.jme3.system.SystemListener; -import com.jme3.system.Timer; -import com.jme3.system.lwjgl.LwjglDisplayVR; -import com.jme3.system.lwjgl.LwjglOffscreenBufferVR; -import com.jme3.util.VRGUIPositioningMode; -import com.jme3.util.VRGuiManager; - -import java.awt.GraphicsDevice; -import java.awt.GraphicsEnvironment; -import java.io.BufferedReader; -import java.io.File; -import java.io.FileReader; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Locale; -import java.util.concurrent.Callable; -import java.util.concurrent.ConcurrentLinkedQueue; -import java.util.concurrent.Future; -import java.util.logging.Level; -import java.util.logging.Logger; - -import org.lwjgl.system.Platform; - - -/** - * A JMonkey application dedicated to Virtual Reality. An application that use VR devices (HTC vive, ...) has to extends this one.
                  - *

                  - * This class is no more functional and is deprecated. Please use {@link VRAppState VRAppState} instead. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @deprecated use {@link VRAppState VRAppState} instead. - */ -public abstract class VRApplication implements Application, SystemListener { - - - private static final Logger logger = Logger.getLogger(LegacyApplication.class.getName()); - - /** - * The default FOV. - */ - public float DEFAULT_FOV = 108f; - - - /** - * The default aspect ratio. - */ - public float DEFAULT_ASPECT = 1f; - - /** - * Is the application is based on OSVR (default is false). - */ - public boolean CONSTRUCT_WITH_OSVR = false; - - /** - * Is the application has not to start within VR mode (default is false). - */ - public boolean DISABLE_VR = false; - - /** - * VR application configuration parameters. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - * - */ - public static enum PreconfigParameter { - /** - * Is the SteamVR compositor is used (kinda needed at the moment) - */ - USE_VR_COMPOSITOR, - - /** - * Render two eyes, regardless of VR API detection. - */ - FORCE_VR_MODE, - - /** - * Invert the eyes. - */ - FLIP_EYES, - - /** - * Show GUI even if it is behind objects. - */ - SET_GUI_OVERDRAW, - - /** - * - */ - SET_GUI_CURVED_SURFACE, - - /** - * Display a mirror rendering on the screen. Runs faster when set to false. - */ - ENABLE_MIRROR_WINDOW, - - /** - * - */ - PREFER_OPENGL3, - - /** - * Disable VR rendering, regardless VR API and devices are presents. - */ - DISABLE_VR, - - /** - * - */ - SEATED_EXPERIENCE, - - /** - * Remove GUI node from the application. - */ - NO_GUI, - - /** - * Faster VR rendering, requires some vertex shader changes (see Common/MatDefs/VR/Unshaded.j3md) - */ - INSTANCE_VR_RENDERING, - - /** - * - */ - FORCE_DISABLE_MSAA - } - - private VRAPI VRhardware = null; - private VRGuiManager guiManager = null; - private OpenVRMouseManager mouseManager = null; - private OpenVRViewManager viewmanager = null; - - private String OS; - - private Camera dummyCam; - private Spatial observer; - private boolean VRSupportedOS; - private boolean forceVR; - private boolean disableSwapBuffers = true; - private boolean tryOpenGL3 = true; - private boolean seated; - private boolean nogui; - private boolean instanceVR; - private boolean forceDisableMSAA; - - // things taken from LegacyApplication - private AppStateManager stateManager; - private Camera cam; - private AppSettings settings; - private JmeContext context; - private float speed = 1f; - private AudioRenderer audioRenderer; - private LostFocusBehavior lostFocusBehavior = LostFocusBehavior.ThrottleOnLostFocus; - private final ConcurrentLinkedQueue> taskQueue = new ConcurrentLinkedQueue>(); - private Timer timer = new NanoTimer(); - private boolean paused = false, inputEnabled = true; - private InputManager inputManager; - private RenderManager renderManager; - private ViewPort viewPort; - private ViewPort guiViewPort; - private AssetManager assetManager; - private Renderer renderer; - private Listener listener; - private MouseInput mouseInput; - private KeyInput keyInput; - private JoyInput joyInput; - private TouchInput touchInput; - - protected Node guiNode, rootNode; - - private float fFar = 1000f, fNear = 1f; - private int xWin = 1280, yWin = 720; - - private float resMult = 1f; - - private boolean useCompositor = true, compositorOS; - private final String RESET_HMD = "ResetHMD"; - - /** - * Create a new VR application and attach the given {@link AppState app states}.
                  - * The application scene is made of a {@link #getRootNode() root node} that holds the scene spatials - * and a {@link #getGuiNode() GUI node} that is the root of the Graphical user interface. - * @param initialStates the {@link AppState app states} to attach to the application. - */ - public VRApplication(AppState... initialStates) { - this(); - - if (initialStates != null) { - for (AppState a : initialStates) { - if (a != null) { - stateManager.attach(a); - } - } - } - } - - /** - * Create a new VR application.
                  - * The application scene is made of a {@link #getRootNode() root node} that holds the scene spatials - * and a {@link #getGuiNode() GUI node} that is the root of the Graphical user interface. - */ - public VRApplication() { - super(); - - rootNode = new Node("root"); - guiNode = new Node("guiNode"); - - guiNode.setQueueBucket(Bucket.Gui); - guiNode.setCullHint(CullHint.Never); - dummyCam = new Camera(); - - initStateManager(); - - // Create the GUI manager. - guiManager = new VRGuiManager(null); - - // Create a new view manager. - viewmanager = new OpenVRViewManager(null); - - // Create a new mouse manager. - mouseManager = new OpenVRMouseManager(null); - - // we are going to use OpenVR now, not the Oculus Rift - // OpenVR does support the Rift - OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - VRSupportedOS = !OS.contains("nux") && System.getProperty("sun.arch.data.model").equalsIgnoreCase("64"); //for the moment, linux/unix causes crashes, 64-bit only - compositorOS = OS.contains("indows"); - - if( !VRSupportedOS ) { - logger.warning("Non-supported OS: " + OS + ", architecture: " + System.getProperty("sun.arch.data.model")); - } else if( DISABLE_VR ) { - logger.warning("VR disabled via code."); - } else if( VRSupportedOS && DISABLE_VR == false ) { - if( CONSTRUCT_WITH_OSVR ) { - //FIXME: WARNING !! - VRhardware = new OSVR(null); - logger.config("Creating OSVR wrapper [SUCCESS]"); - } else { - //FIXME: WARNING !! - VRhardware = new OpenVR(null); - logger.config("Creating OpenVR wrapper [SUCCESS]"); - } - if( VRhardware.initialize() ) { - setPauseOnLostFocus(false); - } - } - } - - /** - * Get the VR underlying hardware. - * @return the VR underlying hardware. - */ - public VRAPI getVRHardware() { - return VRhardware; - } - - /** - * Get the VR dedicated input. - * @return the VR dedicated input. - */ - public VRInputAPI getVRinput() { - if( VRhardware == null ) return null; - return VRhardware.getVRinput(); - } - - /** - * Get the VR view manager. - * @return the VR view manager. - */ - public OpenVRViewManager getVRViewManager() { - return viewmanager; - } - - /** - * Get the GUI manager attached to this application. - * @return the GUI manager attached to this application. - */ - public VRGuiManager getVRGUIManager(){ - return guiManager; - } - - /** - * Get the VR mouse manager attached to this application. - * @return the VR mouse manager attached to this application. - */ - public OpenVRMouseManager getVRMouseManager(){ - return mouseManager; - } - - /** - * Set the frustum values for the application. - * @param near the frustum near value. - * @param far the frustum far value. - */ - public void setFrustrumNearFar(float near, float far) { - fNear = near; - fFar = far; - } - - /** - * Set the mirror window size in pixel. - * @param width the width of the mirror window in pixel. - * @param height the height of the mirror window in pixel. - */ - public void setMirrorWindowSize(int width, int height) { - xWin = width; - yWin = height; - } - - /** - * Set the resolution multiplier. - * @param val the resolution multiplier. - */ - public void setResolutionMultiplier(float val) { - resMult = val; - if( viewmanager != null ) viewmanager.setResolutionMultiplier(resMult); - } - - - /** - * Is the SteamVR compositor is active. - * @return true if the SteamVR compositor is active and false otherwise. - */ - public boolean compositorAllowed() { - return useCompositor && compositorOS; - } - - /** - * Get if the system currently support VR. - * @return true if the system currently support VR and false otherwise. - */ - public boolean isOSVRSupported() { - return VRSupportedOS; - } - - /** - * Simple update of the application, this method should contains {@link #getRootNode() root node} updates. - * This method is called by the {@link #update() update()} method and should not be called manually. - * @param tpf the application time. - */ - public void simpleUpdate(float tpf) { } - - /** - * Rendering callback of the application. This method is called by the {@link #update() update()} method and should not be called manually. - * @param renderManager the {@link RenderManager render manager}. - */ - public void simpleRender(RenderManager renderManager) { - PreNormalCaching.resetCache(isInVR()); - } - - - /* - we do NOT want to get & modify the distortion scene camera, so - return the left viewport camera instead if we are in VR mode - */ - @Override - public Camera getCamera() { - if( isInVR() && viewmanager != null && viewmanager.getLeftCamera() != null ) { - return dummyCam; - } - return cam; - } - - /** - * Get the application internal camera. - * @return the application internal camera. - * @see #getCamera() - */ - public Camera getBaseCamera() { - return cam; - } - - - @Override - public JmeContext getContext(){ - return context; - } - - @Override - public AssetManager getAssetManager(){ - return assetManager; - } - - @Override - public InputManager getInputManager(){ - return inputManager; - } - - @Override - public AppStateManager getStateManager() { - return stateManager; - } - - @Override - public RenderManager getRenderManager() { - return renderManager; - } - - @Override - public Renderer getRenderer(){ - return renderer; - } - - @Override - public AudioRenderer getAudioRenderer() { - return audioRenderer; - } - - @Override - public Listener getListener() { - return listener; - } - - @Override - public Timer getTimer(){ - return timer; - } - - /** - * Handle the error given in parameters by creating a log entry and a dialog window. Internal use only. - */ - public void handleError(String errMsg, Throwable t){ - // Print error to log. - logger.log(Level.SEVERE, errMsg, 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() + - (t.getMessage() != null ? ": " + t.getMessage() : "")); - } else { - JmeSystem.showErrorDialog(errMsg); - } - } - - stop(); // stop the application - } - - - /** - * Force the focus gain for the application. Internal use only. - */ - public void gainFocus(){ - if (lostFocusBehavior != LostFocusBehavior.Disabled) { - if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) { - paused = false; - } - context.setAutoFlushFrames(true); - if (inputManager != null) { - inputManager.reset(); - } - } - } - - /** - * Force the focus lost for the application. Internal use only. - */ - public void loseFocus(){ - if (lostFocusBehavior != LostFocusBehavior.Disabled){ - if (lostFocusBehavior == LostFocusBehavior.PauseOnLostFocus) { - paused = true; - } - context.setAutoFlushFrames(false); - } - } - - /** - * Reshape the display window. Internal use only. - */ - public void reshape(int w, int h){ - if (renderManager != null) { - renderManager.notifyReshape(w, h); - } - } - - /** - * Request the application to close. Internal use only. - */ - public void requestClose(boolean esc){ - context.destroy(false); - } - - /** - * Set the {@link AppSettings display settings} to define the display created. - *

                  - * Examples of display parameters include display frame {@link AppSettings#getWidth() width} and {@link AppSettings#getHeight() height}, - * pixel {@link AppSettings#getBitsPerPixel() color bit depth}, {@link AppSettings#getDepthBits() z-buffer bits}, {@link AppSettings#getSamples() anti-aliasing samples}, {@link AppSettings#getFrequency() update frequency}, ... - *

                  If this method is called while the application is already running, then - * {@link #restart() } must be called to apply the settings to the display. - * - * @param settings The settings to set. - */ - public void setSettings(AppSettings settings){ - this.settings = settings; - if (context != null && settings.useInput() != inputEnabled){ - // may need to create or destroy input based - // on settings change - inputEnabled = !inputEnabled; - if (inputEnabled){ - initInput(); - }else{ - destroyInput(); - } - }else{ - inputEnabled = settings.useInput(); - } - } - - /** - * Sets the {@link Timer} implementation that will be used for calculating - * frame times.

                  - * By default, Application will use the Timer as returned by the current {@link JmeContext} implementation. - * @param timer the timer to use. - */ - public void setTimer(Timer timer){ - this.timer = timer; - - if (timer != null) { - timer.reset(); - } - - if (renderManager != null) { - renderManager.setTimer(timer); - } - } - - - /** - * Determine the application's behavior when unfocused. - * @return The lost focus behavior of the application. - */ - public LostFocusBehavior getLostFocusBehavior() { - return lostFocusBehavior; - } - - /** - * Change the application's behavior when unfocused. By default, the application will - * {@link LostFocusBehavior#ThrottleOnLostFocus throttle the update loop} - * so as to not take 100% CPU usage when it is not in focus, e.g. - * alt-tabbed, minimized, or obstructed by another window. - * - * @param lostFocusBehavior The new {@link LostFocusBehavior lost focus behavior} to use. - */ - public void setLostFocusBehavior(LostFocusBehavior lostFocusBehavior) { - this.lostFocusBehavior = lostFocusBehavior; - } - - /** - * Get if the application has to pause then it lost the focus. - * @return true if pause on lost focus is enabled, false otherwise. - * @see #getLostFocusBehavior() - */ - public boolean isPauseOnLostFocus() { - return getLostFocusBehavior() == LostFocusBehavior.PauseOnLostFocus; - } - - /** - * Enable or disable pause on lost focus. - *

                  - * By default, pause on lost focus is enabled. - * If enabled, the application will stop updating - * when it loses focus or becomes inactive (e.g. alt-tab). - * For online or real-time applications, this might not be preferable, - * so this feature should be set to disabled. For other applications, - * it is best to keep it on so that CPU usage is not used when - * not necessary. - * - * @param pauseOnLostFocus true to enable pause on lost focus, false - * otherwise. - * - * @see #setLostFocusBehavior(com.jme3.app.LostFocusBehavior) - */ - public void setPauseOnLostFocus(boolean pauseOnLostFocus) { - if (pauseOnLostFocus) { - setLostFocusBehavior(LostFocusBehavior.PauseOnLostFocus); - } else { - setLostFocusBehavior(LostFocusBehavior.Disabled); - } - } - - @Override - public void start() { - - logger.config("Starting application..."); - - // set some default settings in-case - // settings dialog is not shown - boolean loadSettings = false; - if (settings == null) { - setSettings(new AppSettings(true)); - loadSettings = true; - } - - GraphicsDevice defDev = null; - try { - GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); - defDev = ge.getDefaultScreenDevice(); - } catch (Throwable e1) { - logger.log(Level.SEVERE, "Cannot access default screen device: "+e1.getMessage(), e1); - } - - if( isInVR() && !compositorAllowed() ) { - logger.warning("VR Composition is not allowed."); - // "easy extended" mode - // TO-DO: JFrame was removed in LWJGL 3, need to use new GLFW library to pick "monitor" display of VR device - // first, find the VR device - GraphicsDevice VRdev = null; - GraphicsDevice[] devs = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices(); - // pick the display that isn't the default one - for(GraphicsDevice gd : devs) { - if( gd != defDev ) { - VRdev = gd; - break; - } - } - // did we get the VR device? - if( VRdev != null ) { - // set properties for VR acceleration - try { - java.awt.DisplayMode useDM = null; - int max = 0; - for(java.awt.DisplayMode dm : VRdev.getDisplayModes()) { - int check = dm.getHeight() + dm.getWidth() + dm.getRefreshRate() + dm.getBitDepth(); - if( check > max ) { - max = check; - useDM = dm; - } - } - // create a window for the VR device - settings.setWidth(useDM.getWidth()); - settings.setHeight(useDM.getHeight()); - settings.setBitsPerPixel(useDM.getBitDepth()); - settings.setFrequency(useDM.getRefreshRate()); - settings.setSwapBuffers(true); - settings.setVSync(true); // allow vsync on this display - setSettings(settings); - //VRdev.setFullScreenWindow(VRwindow); - // make sure we are in the right display mode - if( VRdev.getDisplayMode().equals(useDM) == false ) { - VRdev.setDisplayMode(useDM); - } - // make a blank cursor to hide it - //BufferedImage cursorImg = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); - //Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(cursorImg, new Point(0, 0), "blank cursor"); - //VRwindow.setCursor(blankCursor); - //jmeCanvas.getCanvas().setCursor(blankCursor); - //VRwindow.pack(); - //VRwindow.setVisible(true); - //startCanvas(); - logger.config("Starting application [SUCCESS]"); - return; - } catch(Exception e) { - logger.log(Level.SEVERE, "Error during application start: "+e.getMessage(), e); - } - } - } - - if( !isInVR() ) { - - 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); - } - settings.setSwapBuffers(true); - } else { - - logger.config("VR mode enabled."); - - // use basic mirroring window, skip settings window - settings.setWidth(xWin); - settings.setHeight(yWin); - settings.setBitsPerPixel(24); - settings.setFrameRate(0); // never sleep in main loop - settings.setFrequency(VRhardware.getDisplayFrequency()); - settings.setFullscreen(false); - settings.setVSync(false); // stop vsyncing on primary monitor! - settings.setSwapBuffers(!disableSwapBuffers || VRhardware instanceof OSVR); - settings.setTitle("Put Headset On Now: " + settings.getTitle()); - settings.setResizable(true); - } - - if( forceDisableMSAA ) { - logger.config("Disabling multisampling."); - // disable multisampling, which is more likely to break things than be useful - settings.setSamples(1); - } - - // set opengl mode - if( tryOpenGL3 ) { - logger.config("Using LWJGL OpenGL 3.2 renderer."); - settings.setRenderer(AppSettings.LWJGL_OPENGL32); - } else { - logger.config("Using LWJGL OpenGL 2 renderer."); - settings.setRenderer(AppSettings.LWJGL_OPENGL2); - } - - - setSettings(settings); - start(JmeContext.Type.Display, false); - - // disable annoying warnings about GUI stuff being updated, which is normal behavior - // for late GUI placement for VR purposes - Logger.getLogger("com.jme3").setLevel(Level.SEVERE); - } - - /** - * Starts the application in {@link com.jme3.system.JmeContext.Type#Display display} mode. - * @param waitFor if true, the method will wait until the application is started. - * @see #start(com.jme3.system.JmeContext.Type, boolean) - */ - public void start(boolean waitFor){ - start(JmeContext.Type.Display, waitFor); - } - - /** - * Starts the application. - * Creating a rendering context and executing the main loop in a separate thread. - * @param contextType the {@link com.jme3.system.JmeContext.Type type} of the context to create. - * @param waitFor if true, the method will wait until the application is started. - * @throws IllegalArgumentException if the context type is not supported. - */ - public void start(JmeContext.Type contextType, boolean waitFor){ - if (context != null && context.isCreated()){ - logger.warning("start() called when application already created!"); - return; - } - - if (settings == null){ - settings = new AppSettings(true); - } - - logger.log(Level.FINE, "Starting application: {0}", getClass().getName()); - - // Create VR decicated context - if (contextType == Type.Display){ - context = new LwjglDisplayVR(); - context.setSettings(settings); - } else if (contextType == Type.OffscreenSurface){ - context = new LwjglOffscreenBufferVR(); - context.setSettings(settings); - } else { - logger.severe("Unsupported context type \""+contextType+"\". Supported are \"Display\" and \"OffscreenSurface\""); - throw new IllegalArgumentException("Unsupported context type \""+contextType+"\". Supported are \"Display\" and \"OffscreenSurface\""); - } - - context.setSystemListener(this); - context.create(waitFor); - } - - /** - * Move filters from the main scene into the eye's. - * This removes filters from the main scene. - */ - public void moveScreenProcessingToVR() { - if( isInVR() ) { - viewmanager.moveScreenProcessingToEyes(); - } - } - - /** - * Set VR application {@link PreconfigParameter specific parameter}. - * If making changes to default values, this must be called before the VRApplication starts - * @param parm the parameter to set. - * @param value the value of the parameter. - */ - public void preconfigureVRApp(PreconfigParameter parm, boolean value) { - switch( parm ) { - case SET_GUI_OVERDRAW: - guiManager.setGuiOverdraw(value); - break; - case SET_GUI_CURVED_SURFACE: - guiManager.setCurvedSurface(value); - break; - case FORCE_VR_MODE: - forceVR = value; - break; - //case USE_CUSTOM_DISTORTION: //deprecated, always using a render manager - // VRViewManager._setCustomDistortion(value); - // break; - case USE_VR_COMPOSITOR: - useCompositor = value; - if( value == false ) disableSwapBuffers = false; - break; - case FLIP_EYES: - if( VRhardware == null ) return; - VRhardware.setFlipEyes(value); - break; - case INSTANCE_VR_RENDERING: - instanceVR = value; - break; - case ENABLE_MIRROR_WINDOW: - if( useCompositor == false ) { - disableSwapBuffers = false; - } else disableSwapBuffers = !value; - break; - case PREFER_OPENGL3: - tryOpenGL3 = value; - break; - case DISABLE_VR: - DISABLE_VR = value; - break; - case NO_GUI: - nogui = value; - break; - case SEATED_EXPERIENCE: - seated = value; - break; - case FORCE_DISABLE_MSAA: - forceDisableMSAA = value; - break; - } - } - - /** - * Can be used to change seated experience during runtime. - * @param isSeated true if designed for sitting, false for standing/roomscale - * @see #isSeatedExperience() - */ - public void setSeatedExperience(boolean isSeated) { - seated = isSeated; - if( VRhardware instanceof OpenVR ) { - if( VRhardware.getCompositor() == null ) return; - if( seated ) { - ((OpenVR)VRhardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated); - } else { - ((OpenVR)VRhardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding); - } - } - } - - /** - * Check if the application is configured as a seated experience. - * @return true if the application is configured as a seated experience and false otherwise. - * @see #setSeatedExperience(boolean) - */ - public boolean isSeatedExperience() { - return seated; - } - - /** - * Reset headset pose if seating experience. - */ - public void resetSeatedPose(){ - if( VRSupportedOS == false || isSeatedExperience() == false ) return; - VRhardware.reset(); - } - - /** - * Check if the rendering is instanced (see Geometry instancing). - * @return true if the rendering is instanced and false otherwise. - */ - public boolean isInstanceVRRendering() { - return instanceVR && isInVR(); - } - - /** - * Check if the VR mode is enabled. - * @return true if the VR mode is enabled and false otherwise. - */ - public boolean isInVR() { - return DISABLE_VR == false && (forceVR || VRSupportedOS && VRhardware != null && VRhardware.isInitialized()); - } - - - /** - * Get the GUI node from the application. - * @return the GUI node from the application. - * @see #setGuiNode(Node) - */ - public Node getGuiNode(){ - return guiNode; - } - - /** - * Set the GUI node that is displayed within the GUI viewport. - * Calling this method involve clearing all the scenes previously attached to the gui viewport. - * @param node the GUI node to attach. - * @see #getGuiNode() - */ - public void setGuiNode(Node node){ - if (node != null){ - if (guiViewPort != null){ - - enqueue(new Callable(){ - - @Override - public Object call() throws Exception { - guiViewPort.clearScenes(); - guiViewPort.attachScene(node); - guiNode = node; - return null; - } - - }); - - } else { - throw new IllegalArgumentException("GUI view port is not initialized."); - } - } - - } - - /** - * Get the root node of the application. - * @return the root node of the application. - */ - public Node getRootNode() { - return rootNode; - } - - /** - * Check if the application has a GUI overlay attached. - * @return true if the application has a GUI overlay attached and false otherwise. - */ - public boolean hasTraditionalGUIOverlay() { - return !nogui; - } - - - /** - * Get the scene observer. If no observer has been set, this method return the application {@link #getCamera() camera}. - * @return the scene observer. - * @see #setObserver(Spatial) - */ - public Object getObserver() { - if( observer == null ) { - return getCamera(); - } - return observer; - } - - /** - * Set the scene observer. The VR headset will be linked to it. If no observer is set, the VR headset is linked to the application {@link #getCamera() camera}. - * @param observer the scene observer. - */ - public void setObserver(Spatial observer) { - this.observer = observer; - } - - /* - where is the headset pointing, after all rotations are combined? - depends on observer rotation, if any - */ - private static Quaternion tempq = new Quaternion(); - - /** - * Get the observer final rotation within the scene. - * @return the observer final rotation within the scene. - * @see #getFinalObserverPosition() - */ - public Quaternion getFinalObserverRotation() { - if( viewmanager == null ) { - if( observer == null ) { - return getCamera().getRotation(); - } else return observer.getWorldRotation(); - } - if( observer == null ) { - tempq.set(dummyCam.getRotation()); - } else { - tempq.set(observer.getWorldRotation()); - } - return tempq.multLocal(VRhardware.getOrientation()); - } - - /** - * Get the observer final position within the scene. - * @return the observer position. - * @see #getFinalObserverRotation() - */ - public Vector3f getFinalObserverPosition() { - if( viewmanager == null ) { - if( observer == null ) { - return getCamera().getLocation(); - } else return observer.getWorldTranslation(); - } - Vector3f pos = VRhardware.getPosition(); - if( observer == null ) { - dummyCam.getRotation().mult(pos, pos); - return pos.addLocal(dummyCam.getLocation()); - } else { - observer.getWorldRotation().mult(pos, pos); - return pos.addLocal(observer.getWorldTranslation()); - } - } - - /** - * Set the VR headset height from the ground. - * @param amount the VR headset height from the ground. - * @see #getVRHeightAdjustment() - */ - public void setVRHeightAdjustment(float amount) { - if( viewmanager != null ) viewmanager.setHeightAdjustment(amount); - } - - /** - * Get the VR headset height from the ground. - * @return the VR headset height from the ground. - * @see #setVRHeightAdjustment(float) - */ - public float getVRHeightAdjustment() { - if( viewmanager != null ) return viewmanager.getHeightAdjustment(); - return 0f; - } - - /** - * Get the VR headset left viewport. - * @return the VR headset left viewport. - * @see #getRightViewPort() - */ - public ViewPort getLeftViewPort() { - if( viewmanager == null ) return getViewPort(); - return viewmanager.getLeftViewPort(); - } - - /** - * Get the VR headset right viewport. - * @return the VR headset right viewport. - * @see #getLeftViewPort() - */ - public ViewPort getRightViewPort() { - if( viewmanager == null ) return getViewPort(); - return viewmanager.getRightViewPort(); - } - - - /** - * Set the background color for both left and right view ports. - * @param clr the background color. - */ - public void setBackgroundColors(ColorRGBA clr) { - if( viewmanager == null ) { - getViewPort().setBackgroundColor(clr); - } else if( viewmanager.getLeftViewPort() != null ) { - viewmanager.getLeftViewPort().setBackgroundColor(clr); - if( viewmanager.getRightViewPort() != null ) viewmanager.getRightViewPort().setBackgroundColor(clr); - } - } - - - /** - * Runs tasks enqueued via {@link #enqueue(Callable)} - */ - protected void runQueuedTasks() { - AppTask task; - while( (task = taskQueue.poll()) != null ) { - if (!task.isCancelled()) { - task.invoke(); - } - } - } - - @Override - public void update() { - // Make sure the audio renderer is available to callables - AudioContext.setAudioRenderer(audioRenderer); - - runQueuedTasks(); - - if (speed != 0 && !paused) { - - timer.update(); - - if (inputEnabled){ - inputManager.update(timer.getTimePerFrame()); - } - - if (audioRenderer != null){ - audioRenderer.update(timer.getTimePerFrame()); - } - } - - if (speed == 0 || paused) { - try { - Thread.sleep(50); // throttle the CPU when paused - } catch (InterruptedException ex) { - Logger.getLogger(SimpleApplication.class.getName()).log(Level.SEVERE, null, ex); - } - return; - } - - float tpf = timer.getTimePerFrame() * speed; - - // update states - stateManager.update(tpf); - - // simple update and root node - simpleUpdate(tpf); - - - // render states - stateManager.render(renderManager); - - // update VR pose & cameras - if( viewmanager != null ) { - viewmanager.update(tpf); - } else if( observer != null ) { - getCamera().setFrame(observer.getWorldTranslation(), observer.getWorldRotation()); - } - - //FIXME: check if this code is necessary. - // Updates scene and gui states. - rootNode.updateLogicalState(tpf); - guiNode.updateLogicalState(tpf); - - rootNode.updateGeometricState(); - - if( isInVR() == false || guiManager.getPositioningMode() == VRGUIPositioningMode.MANUAL ) { - // only update geometric state here if GUI is in manual mode, or not in VR - // it will get updated automatically in the viewmanager update otherwise - guiNode.updateGeometricState(); - } - - renderManager.render(tpf, context.isRenderable()); - simpleRender(renderManager); - stateManager.postRender(); - - // update compositor? - if( viewmanager != null ) { - viewmanager.postRender(); - } - } - - private void initAssetManager(){ - URL assetCfgUrl = null; - - if (settings != null){ - String assetCfg = settings.getString("AssetConfigURL"); - if (assetCfg != null){ - try { - assetCfgUrl = new URL(assetCfg); - } catch (MalformedURLException ex) { - } - if (assetCfgUrl == null) { - assetCfgUrl = LegacyApplication.class.getClassLoader().getResource(assetCfg); - if (assetCfgUrl == null) { - logger.log(Level.SEVERE, "Unable to access AssetConfigURL in asset config:{0}", assetCfg); - return; - } - } - } - } - if (assetCfgUrl == null) { - assetCfgUrl = JmeSystem.getPlatformAssetConfigURL(); - } - if (assetManager == null){ - assetManager = JmeSystem.newAssetManager(assetCfgUrl); - logger.config("Created asset manager from "+assetCfgUrl); - } - } - - - private void initDisplay(){ - // aquire important objects - // from the context - settings = context.getSettings(); - - // Only reset the timer if a user has not already provided one - if (timer == null) { - timer = context.getTimer(); - } - - renderer = context.getRenderer(); - } - - private void initAudio(){ - if (settings.getAudioRenderer() != null && context.getType() != JmeContext.Type.Headless){ - audioRenderer = JmeSystem.newAudioRenderer(settings); - audioRenderer.initialize(); - AudioContext.setAudioRenderer(audioRenderer); - - listener = new Listener(); - audioRenderer.setListener(listener); - } - } - - /** - * Creates the camera to use for rendering. Default values are perspective - * projection with 45° field of view, with near and far values 1 and 1000 - * units respectively. - */ - private void initCamera(){ - cam = new Camera(settings.getWidth(), settings.getHeight()); - - cam.setFrustumPerspective(45f, (float)cam.getWidth() / cam.getHeight(), 1f, 1000f); - cam.setLocation(new Vector3f(0f, 0f, 10f)); - cam.lookAt(new Vector3f(0f, 0f, 0f), Vector3f.UNIT_Y); - - renderManager = new RenderManager(renderer); - //Remy - 09/14/2010 setted the timer in the renderManager - renderManager.setTimer(timer); - - viewPort = renderManager.createMainView("Default", cam); - viewPort.setClearFlags(true, true, true); - - // Create a new cam for the gui - Camera guiCam = new Camera(settings.getWidth(), settings.getHeight()); - guiViewPort = renderManager.createPostView("Gui Default", guiCam); - guiViewPort.setClearFlags(false, false, false); - } - - /** - * Initializes mouse and keyboard input. Also - * initializes joystick input if joysticks are enabled in the - * AppSettings. - */ - private void initInput(){ - mouseInput = context.getMouseInput(); - if (mouseInput != null) - mouseInput.initialize(); - - keyInput = context.getKeyInput(); - if (keyInput != null) - keyInput.initialize(); - - touchInput = context.getTouchInput(); - if (touchInput != null) - touchInput.initialize(); - - if (!settings.getBoolean("DisableJoysticks")){ - joyInput = context.getJoyInput(); - if (joyInput != null) - joyInput.initialize(); - } - - inputManager = new InputManager(mouseInput, keyInput, joyInput, touchInput); - } - - private void initStateManager(){ - stateManager = new AppStateManager(this); - - // Always register a ResetStatsState to make sure - // that the stats are cleared every frame - stateManager.attach(new ResetStatsState()); - } - - /** - * Do not call manually. - * Callback from ContextListener. - *

                  - * Initializes the Application, by creating a display and - * default camera. If display settings are not specified, a default - * 640x480 display is created. Default values are used for the camera; - * perspective projection with 45° field of view, with near - * and far values 1 and 1000 units respectively. - */ - private void initialize_internal(){ - if (assetManager == null){ - initAssetManager(); - } - - initDisplay(); - initCamera(); - - if (inputEnabled){ - initInput(); - } - initAudio(); - - // update timer so that the next delta is not too large -// timer.update(); - timer.reset(); - - // user code here.. - } - - @Override - public void initialize() { - - logger.config("Initialize VR application..."); - - initialize_internal(); - cam.setFrustumFar(fFar); - cam.setFrustumNear(fNear); - dummyCam = cam.clone(); - if( isInVR() ) { - - logger.config("VR mode enabled."); - - if( VRhardware != null ) { - VRhardware.initVRCompositor(compositorAllowed()); - } else { - logger.warning("No VR system found."); - } - - //FIXME: WARNING !! - viewmanager = new OpenVRViewManager(null); - viewmanager.setResolutionMultiplier(resMult); - inputManager.addMapping(RESET_HMD, new KeyTrigger(KeyInput.KEY_F9)); - setLostFocusBehavior(LostFocusBehavior.Disabled); - } else { - logger.config("VR mode disabled."); - viewPort.attachScene(rootNode); - guiViewPort.attachScene(guiNode); - } - - if( viewmanager != null ) { - viewmanager.initialize(); - } - - simpleInitApp(); - - // any filters created, move them now - if( viewmanager != null ) { - viewmanager.moveScreenProcessingToEyes(); - - // print out camera information - if( isInVR() ) { - logger.info("VR Initialization Information"); - if( viewmanager.getLeftCamera() != null ){ - logger.info("camLeft: " + viewmanager.getLeftCamera().toString()); - } - - if( viewmanager.getRightCamera() != null ){ - logger.info("camRight: " + viewmanager.getRightCamera().toString()); - } - } - } - } - - /** - * Initialize the application. This method has to be overridden by implementations. - */ - public abstract void simpleInitApp(); - - /** - * Destroy the application (release all resources). - */ - public void destroy() { - if( VRhardware != null ) { - VRhardware.destroy(); - VRhardware = null; - } - DISABLE_VR = true; - stateManager.cleanup(); - - destroyInput(); - if (audioRenderer != null) - audioRenderer.cleanup(); - - timer.reset(); - Runtime.getRuntime().exit(0); - } - - protected void destroyInput(){ - if (mouseInput != null) - mouseInput.destroy(); - - if (keyInput != null) - keyInput.destroy(); - - if (joyInput != null) - joyInput.destroy(); - - if (touchInput != null) - touchInput.destroy(); - - inputManager = null; - } - - @Override - public ViewPort getGuiViewPort() { - return guiViewPort; - } - - @Override - public ViewPort getViewPort() { - return viewPort; - } - - @Override - public Future enqueue(Callable callable) { - AppTask task = new AppTask(callable); - taskQueue.add(task); - return task; - } - - /** - * Enqueues a runnable object to execute in the jME3 - * rendering thread. - *

                  - * Runnables are executed right at the beginning of the main loop. - * They are executed even if the application is currently paused - * or out of focus. - * - * @param runnable The runnable to run in the main jME3 thread - */ - public void enqueue(Runnable runnable){ - enqueue(new RunnableWrapper(runnable)); - } - - private class RunnableWrapper implements Callable{ - private final Runnable runnable; - - public RunnableWrapper(Runnable runnable){ - this.runnable = runnable; - } - - @Override - public Object call(){ - runnable.run(); - return null; - } - - } - - /** - * Requests the context to close, shutting down the main loop - * and making necessary cleanup operations. - * - * Same as calling stop(false) - * - * @see #stop(boolean) - */ - @Override - public void stop(){ - stop(false); - } - - /** - * Requests the context to close, shutting down the main loop - * and making necessary cleanup operations. - * After the application has stopped, it cannot be used anymore. - */ - @Override - public void stop(boolean waitFor){ - logger.log(Level.FINE, "Closing application: {0}", getClass().getName()); - context.destroy(waitFor); - } - - /** - * Restarts the context, applying any changed settings. - *

                  - * Changes to the {@link AppSettings} of this Application are not - * applied immediately; calling this method forces the context - * to restart, applying the new settings. - */ - @Override - public void restart(){ - context.setSettings(settings); - context.restart(); - } - - /** - * Sets an AppProfiler hook that will be called back for - * specific steps within a single update frame. Value defaults - * to null. - */ - - public void setAppProfiler(AppProfiler prof) { - return; - } - - /** - * Returns the current AppProfiler hook, or null if none is set. - */ - public AppProfiler getAppProfiler() { - return null; - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/app/VRConstants.java b/jme3-vr/src/main/java/com/jme3/app/VRConstants.java deleted file mode 100644 index 969f6cb809..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VRConstants.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.jme3.app; - -import java.util.HashMap; - -import com.jme3.system.AppSettings; - -/** - * Some constants dedicated to the VR module. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @since 3.1.0 - */ -public class VRConstants { - - /** - * An AppSettings parameter that set if the VR compositor has to be used. - *

                  - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_USE_COMPOSITOR, value) - */ - public static final String SETTING_USE_COMPOSITOR = "VRUseCompositor"; - - /** - * An AppSettings parameter that set if the rendering has to use two eyes, - * regardless of VR API detection (turning this setting on without a VR system should lead to errors). - *

                  - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_VR_FORCE, value) - - */ - public static final String SETTING_VR_FORCE = "VRForce"; - - /** - * An AppSettings parameter that set to invert the eyes of the HMD. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_FLIP_EYES, value) - */ - public static final String SETTING_FLIP_EYES = "VRFlipEyes"; - - /** - * An AppSettings parameter that set if the GUI has to be displayed even if it is behind objects. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_GUI_OVERDRAW, value) - * - */ - public static final String SETTING_GUI_OVERDRAW = "VRGUIOverdraw"; - - /** - * An AppSettings parameter that set if the GUI surface has to be curved. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_GUI_CURVED_SURFACE, value) - */ - public static final String SETTING_GUI_CURVED_SURFACE = "VRGUICurvedSurface"; - - /** - * An AppSettings parameter that set if a mirror rendering has to be displayed on the screen. - * Runs faster when set to false. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_ENABLE_MIRROR_WINDOW, value) - */ - public static final String SETTING_ENABLE_MIRROR_WINDOW = "VREnableMirrorWindow"; - - /** - * An AppSettings parameter that set if the VR rendering has to be disabled, - * regardless VR API and devices are presents. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DISABLE_VR, value) - */ - public static final String SETTING_DISABLE_VR = "VRDisable"; - - - /** - * An AppSettings parameter that set if the VR user is seated. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_SEATED_EXPERIENCE, value) - */ - public static final String SETTING_SEATED_EXPERIENCE = "VRSeatedExperience"; - - /** - * An AppSettings parameter that set if the GUI has to be ignored. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_NO_GUI, value) - */ - public static final String SETTING_NO_GUI = "VRNoGUI"; - - /** - * An AppSettings parameter that set if instance rendering has to be used. - * This setting requires some vertex shader changes (see Common/MatDefs/VR/Unshaded.j3md). - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_INSTANCE_RENDERING, value) - */ - public static final String SETTING_INSTANCE_RENDERING = "VRInstanceRendering"; - - /** - * An AppSettings parameter that set if Multi Sample Anti Aliasing has to be enabled. - * Type: boolean
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DISABLE_MSAA, value) - */ - public static final String SETTING_DISABLE_MSAA = "VRDisableMSAA"; - - /** - * An AppSettings parameter that set the default field of view (FOV) value. - * Type: float
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DEFAULT_FOV, value) - */ - public static final String SETTING_DEFAULT_FOV = "VRDefaultFOV"; - - /** - * An AppSettings parameter that set the default aspect ratio. - * Type: float
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_DEFAULT_ASPECT_RATIO, value) - */ - public static final String SETTING_DEFAULT_ASPECT_RATIO = "VRDefaultAspectRatio"; - - /** - * An AppSettings parameter that specifies the underlying VR API. Possible values are:
                  - *

                    - *
                  • {@link VRConstants#SETTING_VRAPI_OPENVR_VALUE SETTING_VRAPI_OPENVR_VALUE}: Use OpenVR binding. - *
                  • {@link VRConstants#SETTING_VRAPI_OSVR_VALUE SETTING_VRAPI_OSVR_VALUE}: Use OSVR binding. - *
                  • {@link VRConstants#SETTING_VRAPI_OPENVR_LWJGL_VALUE SETTING_VRAPI_OPENVR_LWJGL_VALUE}: Use OpenVR binding from LWJGL. - *
                  • {@link VRConstants#SETTING_VRAPI_OCULUSVR_VALUE SETTING_VRAPI_OCULUSVR_VALUE}: Use Occulus Rift binding binding. - *
                  - * Type: int
                  - * Usage: {@link AppSettings appSettings}.{@link HashMap#put(Object, Object) put}(VRConstants.SETTING_VRAPI, value) - - */ - public static final String SETTING_VRAPI = "VRAPI"; - - /** - * The identifier of the OpenVR system. - * @see #SETTING_VRAPI_OSVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - * @see #SETTING_VRAPI_OCULUSVR_VALUE - */ - public static final int SETTING_VRAPI_OPENVR_VALUE = 1; - - /** - * The identifier of the OSVR system. - * @see #SETTING_VRAPI_OPENVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - * @see #SETTING_VRAPI_OCULUSVR_VALUE - */ - public static final int SETTING_VRAPI_OSVR_VALUE = 2; - - /** - * The identifier of the OpenVR from LWJGL system. - * @see #SETTING_VRAPI_OPENVR_VALUE - * @see #SETTING_VRAPI_OSVR_VALUE - * @see #SETTING_VRAPI_OCULUSVR_VALUE - */ - public static final int SETTING_VRAPI_OPENVR_LWJGL_VALUE = 3; - - /** - * The identifier of the Oculus Rift system. - * @see #SETTING_VRAPI_OPENVR_VALUE - * @see #SETTING_VRAPI_OSVR_VALUE - * @see #SETTING_VRAPI_OPENVR_LWJGL_VALUE - */ - public static final int SETTING_VRAPI_OCULUSVR_VALUE = 4; - -} diff --git a/jme3-vr/src/main/java/com/jme3/app/VREnvironment.java b/jme3-vr/src/main/java/com/jme3/app/VREnvironment.java deleted file mode 100644 index 3ed87b6960..0000000000 --- a/jme3-vr/src/main/java/com/jme3/app/VREnvironment.java +++ /dev/null @@ -1,555 +0,0 @@ -package com.jme3.app; - -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.app.state.AppState; -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.VRBounds; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRMouseManager; -import com.jme3.input.vr.VRViewManager; -import com.jme3.input.vr.lwjgl_openvr.LWJGLOpenVR; -import com.jme3.input.vr.lwjgl_openvr.LWJGLOpenVRMouseManager; -import com.jme3.input.vr.lwjgl_openvr.LWJGLOpenVRViewManager; -import com.jme3.input.vr.oculus.OculusMouseManager; -import com.jme3.input.vr.oculus.OculusVR; -import com.jme3.input.vr.oculus.OculusViewManager; -import com.jme3.input.vr.openvr.OpenVR; -import com.jme3.input.vr.openvr.OpenVRMouseManager; -import com.jme3.input.vr.openvr.OpenVRViewManager; -import com.jme3.input.vr.osvr.OSVR; -import com.jme3.input.vr.osvr.OSVRViewManager; -import com.jme3.renderer.Camera; -import com.jme3.scene.Spatial; -import com.jme3.system.AppSettings; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.util.VRGuiManager; - -/** - * - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class VREnvironment { - - private static final Logger logger = Logger.getLogger(VREnvironment.class.getName()); - - private VRAPI hardware = null; - private VRGuiManager guiManager = null; - private VRMouseManager mouseManager = null; - private VRViewManager viewmanager = null; - - private VRBounds bounds = null; - - /** - * The underlying system VR API. By default set to {@link VRConstants#SETTING_VRAPI_OPENVR_VALUE}. - */ - public int vrBinding = VRConstants.SETTING_VRAPI_OPENVR_VALUE; - - private boolean seated = false; - - private Spatial observer = null; - - private boolean forceVR = false; - - private boolean vrSupportedOS = false; - - private boolean nogui = false; - - private boolean compositorOS; - - private boolean useCompositor = true; - - private boolean instanceRendering = false; - - private boolean disableSwapBuffers = true; - - private float defaultFOV = 108f; - - private float defaultAspect = 1f; - - private AppSettings settings = null; - - private Application application = null; - - private Camera dummyCam = null; - - private AppState app = null; - - private boolean initialized = false; - - - public VREnvironment(AppSettings settings){ - - this.settings = settings; - - bounds = null; - - processSettings(); - } - - /** - * Get the VR underlying hardware. - * @return the VR underlying hardware. - */ - public VRAPI getVRHardware() { - return hardware; - } - - /** - * Set the VR bounds. - * @see #getVRBounds() - */ - public void setVRBounds(VRBounds bounds){ - this.bounds = bounds; - } - - /** - * Get the VR bounds. - * @return the VR bounds. - * @see #setVRBounds(VRBounds) - */ - public VRBounds getVRBounds(){ - return bounds; - } - - /** - * Get the VR dedicated input. - * @return the VR dedicated input. - */ - public VRInputAPI getVRinput() { - if( hardware == null ){ - return null; - } - - return hardware.getVRinput(); - } - - /** - * Get the VR view manager. - * @return the VR view manager. - */ - public VRViewManager getVRViewManager() { - return viewmanager; - } - - /** - * Get the GUI manager attached to this environment. - * @return the GUI manager attached to this environment. - */ - public VRGuiManager getVRGUIManager(){ - return guiManager; - } - - /** - * Get the VR mouse manager attached to this environment. - * @return the VR mouse manager attached to this environment. - */ - public VRMouseManager getVRMouseManager(){ - return mouseManager; - } - - /** - * Can be used to change seated experience during runtime. - * @param isSeated true if designed for sitting, false for standing/roomscale - * @see #isSeatedExperience() - */ - public void setSeatedExperience(boolean isSeated) { - seated = isSeated; - if( hardware instanceof OpenVR ) { - if( hardware.getCompositor() == null ) { - return; - } - - if( seated ) { - ((OpenVR)hardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated); - } else { - ((OpenVR)hardware).getCompositor().SetTrackingSpace.apply(JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding); - } - } else if (hardware instanceof LWJGLOpenVR) { - if( ((LWJGLOpenVR)hardware).isInitialized() ) { - ((LWJGLOpenVR)hardware).setTrackingSpace(seated); - } - } - } - - /** - * Check if the application is configured as a seated experience. - * @return true if the application is configured as a seated experience and false otherwise. - * @see #setSeatedExperience(boolean) - */ - public boolean isSeatedExperience() { - return seated; - } - - /** - * Set the VR headset height from the ground. - * @param amount the VR headset height from the ground. - * @see #getVRHeightAdjustment() - */ - public void setVRHeightAdjustment(float amount) { - if( viewmanager != null ){ - viewmanager.setHeightAdjustment(amount); - } - } - - /** - * Get the VR headset height from the ground. - * @return the VR headset height from the ground. - * @see #setVRHeightAdjustment(float) - */ - public float getVRHeightAdjustment() { - if( viewmanager != null ){ - return viewmanager.getHeightAdjustment(); - } - return 0f; - } - - /** - * Get the scene observer. If no observer has been set, this method return the application {@link #getCamera() camera}. - * @return the scene observer. - * @see #setObserver(Spatial) - */ - public Object getObserver() { - if( observer == null ) { - - if (application != null){ - return application.getCamera(); - } else { - throw new IllegalStateException("VR environment is not attached to any application."); - } - } - return observer; - } - - /** - * Set the scene observer. The VR headset will be linked to it. If no observer is set, the VR headset is linked to the application {@link #getCamera() camera}. - * @param observer the scene observer. - */ - public void setObserver(Spatial observer) { - this.observer = observer; - } - - /** - * Get the default Field Of View (FOV) value. - * @return the default Field Of View (FOV) value. - * @see #setDefaultFOV(float) - */ - public float getDefaultFOV() { - return defaultFOV; - } - - /** - * Set the default Field Of View (FOV) value. - * @param defaultFOV the default Field Of View (FOV) value. - * @see #getDefaultFOV() - */ - public void setDefaultFOV(float defaultFOV) { - this.defaultFOV = defaultFOV; - } - - /** - * Get the default aspect ratio. - * @return the default aspect ratio. - * @see #setDefaultAspect(float) - */ - public float getDefaultAspect() { - return defaultAspect; - } - - /** - * Set the default aspect ratio. - * @param defaultAspect the default aspect ratio. - * @see #getDefaultAspect() - */ - public void setDefaultAspect(float defaultAspect) { - this.defaultAspect = defaultAspect; - } - - /** - * Get the {@link AppSettings settings} attached to this environment. - * @return the {@link AppSettings settings} attached to this environment. - * @see #setSettings(AppSettings) - */ - public AppSettings getSettings(){ - return settings; - } - - /** - * Set the {@link AppSettings settings} attached to this environment. - * @param settings the {@link AppSettings settings} attached to this environment. - * @see #getSettings() - */ - public void setSettings(AppSettings settings){ - this.settings = settings; - processSettings(); - } - - /** - * Get if the system currently support VR. - * @return true if the system currently support VR and false otherwise. - */ - public boolean isVRSupported() { - return vrSupportedOS; - } - - /** - * Check if the VR mode is enabled. - * @return true if the VR mode is enabled and false otherwise. - */ - public boolean isInVR() { - return (forceVR || vrSupportedOS && hardware != null && hardware.isInitialized() && isInitialized()); - } - - /** - * Check if the rendering is instanced (see Geometry instancing). - * @return true if the rendering is instanced and false otherwise. - */ - public boolean isInstanceRendering() { - return instanceRendering; - } - - public boolean isSwapBuffers(){ - return disableSwapBuffers; - } - - /** - * Check if the application has a GUI overlay attached. - * @return true if the application has a GUI overlay attached and false otherwise. - */ - public boolean hasTraditionalGUIOverlay() { - return !nogui; - } - - /** - * Check if the VR environment is initialized. A call to the {@link #initialize() initialize()} method should set this value to true - * @return true if the VR environment is initialized and false otherwise. - */ - public boolean isInitialized(){ - return initialized; - } - - /** - * Is the VR compositor is active. - * @return true if the VR compositor is active and false otherwise. - */ - public boolean compositorAllowed() { - return useCompositor && compositorOS; - } - - /** - * Reset headset pose if seating experience. - */ - public void resetSeatedPose(){ - if( vrSupportedOS == false || isSeatedExperience() == false ){ - return; - } - getVRHardware().reset(); - } - - public AppState getAppState(){ - return app; - } - - public Application getApplication(){ - return application; - } - - /** - * Get the {@link Camera camera} used for rendering. - * If the VR mode is {@link #isInVR() active}, this method return a dummy camera, otherwise, - * this method return the camera of the attached application. - * @return the camera attached used for rendering. - */ - public Camera getCamera() { - if( isInVR() && getVRViewManager() != null && getVRViewManager().getLeftCamera() != null ) { - return getDummyCamera(); - } - - return application.getCamera(); - } - - public Camera getDummyCamera(){ - - if (dummyCam == null){ - - if (application != null){ - - if (application.getCamera() != null){ - dummyCam = application.getCamera().clone(); - } else { - - if ((settings != null) && (settings.getWidth() != 0) && (settings.getHeight() != 0)){ - dummyCam = new Camera(settings.getWidth(), settings.getHeight()); - } else { - dummyCam = new Camera(); - } - } - } else { - throw new IllegalStateException("VR environment is not attached to any application."); - } - } - return dummyCam; - } - - /** - * Attach the VR environment to the given app state and application. - * This method should be called within the {@link AppState#stateAttached(com.jme3.app.state.AppStateManager) stateAttached(com.jme3.app.state.AppStateManager)} method - * from the app state. - * @param appState the app state to attach. - * @param application the application to attach. - */ - public void atttach(AppState appState, Application application){ - this.application = application; - this.app = appState; - - // Instanciate view manager - if (vrBinding == VRConstants.SETTING_VRAPI_OPENVR_VALUE){ - viewmanager = new OpenVRViewManager(this); - } else if (vrBinding == VRConstants.SETTING_VRAPI_OSVR_VALUE){ - viewmanager = new OSVRViewManager(this); - } else if (vrBinding == VRConstants.SETTING_VRAPI_OCULUSVR_VALUE) { - viewmanager = new OculusViewManager(this); - } else if (vrBinding == VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE) { - viewmanager = new LWJGLOpenVRViewManager(this); - } else { - logger.severe("Cannot instanciate view manager, unknown VRAPI type: "+vrBinding); - } - } - - /** - * Initialize this VR environment. This method enable the system bindings and configure all the VR system modules. - * A call to this method has to be made before any use of VR capabilities. - * @return true if the VR environment is successfully initialized and false otherwise. - */ - public boolean initialize(){ - - logger.config("Initializing VR environment."); - - initialized = false; - - // we are going to use OpenVR now, not the Oculus Rift - // OpenVR does support the Rift - String OS = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH); - vrSupportedOS = System.getProperty("sun.arch.data.model").equalsIgnoreCase("64"); //64-bit only - compositorOS = OS.contains("indows") || OS.contains("nux"); - - if (OS.contains("nux") && vrBinding != VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE){ - logger.severe("Only LWJGL VR backend is currently (partially) supported on Linux."); - vrSupportedOS = false; - } - - if( vrSupportedOS) { - if( vrBinding == VRConstants.SETTING_VRAPI_OSVR_VALUE ) { - - guiManager = new VRGuiManager(this); - mouseManager = new OpenVRMouseManager(this); - - hardware = new OSVR(this); - initialized = true; - logger.config("Creating OSVR wrapper [SUCCESS]"); - } else if( vrBinding == VRConstants.SETTING_VRAPI_OPENVR_VALUE ) { - - guiManager = new VRGuiManager(this); - mouseManager = new OpenVRMouseManager(this); - - hardware = new OpenVR(this); - initialized = true; - logger.config("Creating OpenVR wrapper [SUCCESS]"); - } else if (vrBinding == VRConstants.SETTING_VRAPI_OCULUSVR_VALUE) { - - guiManager = new VRGuiManager(this); - mouseManager = new OculusMouseManager(this); - - hardware = new OculusVR(this); - initialized = true; - logger.config("Creating Occulus Rift wrapper [SUCCESS]"); - } else if (vrBinding == VRConstants.SETTING_VRAPI_OPENVR_LWJGL_VALUE) { - - guiManager = new VRGuiManager(this); - mouseManager = new LWJGLOpenVRMouseManager(this); - - hardware = new LWJGLOpenVR(this); - initialized = true; - logger.config("Creating OpenVR/LWJGL wrapper [SUCCESS]"); - } else { - logger.config("Cannot create VR binding: "+vrBinding+" [FAILED]"); - logger.log(Level.SEVERE, "Cannot initialize VR environment [FAILED]"); - } - - if( hardware.initialize() ) { - initialized &= true; - logger.config("VR native wrapper initialized [SUCCESS]"); - } else { - initialized &= false; - logger.warning("VR native wrapper initialized [FAILED]"); - logger.log(Level.SEVERE, "Cannot initialize VR environment [FAILED]"); - } - } else { - logger.log(Level.SEVERE, "System does not support VR capabilities."); - logger.log(Level.SEVERE, "Cannot initialize VR environment [FAILED]"); - } - - return initialized; - } - - private void processSettings(){ - if (settings != null){ - - if (settings.get(VRConstants.SETTING_USE_COMPOSITOR) != null){ - useCompositor = settings.getBoolean(VRConstants.SETTING_USE_COMPOSITOR); - if( useCompositor == false ){ - disableSwapBuffers = false; - } - } - - if (settings.get(VRConstants.SETTING_ENABLE_MIRROR_WINDOW) != null){ - if( useCompositor == false ) { - disableSwapBuffers = false; - } else { - disableSwapBuffers = !settings.getBoolean(VRConstants.SETTING_ENABLE_MIRROR_WINDOW); - } - } - - if (settings.get(VRConstants.SETTING_GUI_OVERDRAW) != null){ - getVRGUIManager().setGuiOverdraw(settings.getBoolean(VRConstants.SETTING_GUI_OVERDRAW)); - } - - if (settings.get(VRConstants.SETTING_GUI_CURVED_SURFACE) != null){ - getVRGUIManager().setCurvedSurface(settings.getBoolean(VRConstants.SETTING_GUI_CURVED_SURFACE)); - } - - if (settings.get(VRConstants.SETTING_NO_GUI) != null){ - nogui = settings.getBoolean(VRConstants.SETTING_NO_GUI); - } - - if (settings.get(VRConstants.SETTING_VRAPI) != null){ - vrBinding = settings.getInteger(VRConstants.SETTING_VRAPI); - } - - if (settings.get(VRConstants.SETTING_SEATED_EXPERIENCE) != null){ - seated = settings.getBoolean(VRConstants.SETTING_SEATED_EXPERIENCE); - } - - if (settings.get(VRConstants.SETTING_INSTANCE_RENDERING) != null){ - instanceRendering = settings.getBoolean(VRConstants.SETTING_INSTANCE_RENDERING); - } - - if (settings.get(VRConstants.SETTING_DEFAULT_FOV) != null){ - defaultFOV = settings.getFloat(VRConstants.SETTING_DEFAULT_FOV); - } - - if (settings.get(VRConstants.SETTING_DEFAULT_ASPECT_RATIO) != null){ - defaultAspect = settings.getFloat(VRConstants.SETTING_DEFAULT_ASPECT_RATIO); - } - - if (settings.get(VRConstants.SETTING_FLIP_EYES) != null){ - if( getVRHardware() != null ){ - getVRHardware().setFlipEyes(settings.getBoolean(VRConstants.SETTING_FLIP_EYES)); - } - } - } - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwKeyInputVR.java b/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwKeyInputVR.java deleted file mode 100644 index e551f0fdc2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwKeyInputVR.java +++ /dev/null @@ -1,140 +0,0 @@ -package com.jme3.input.lwjgl; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.input.KeyInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.KeyInputEvent; -import com.jme3.system.lwjgl.LwjglWindowVR; - -import org.lwjgl.glfw.GLFWKeyCallback; - -import java.util.LinkedList; -import java.util.Queue; -import java.util.logging.Logger; - -import static org.lwjgl.glfw.GLFW.*; - -/** - * A key input that wraps GLFW underlying components. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class GlfwKeyInputVR implements KeyInput { - - private static final Logger logger = Logger.getLogger(GlfwKeyInput.class.getName()); - - private LwjglWindowVR context; - private RawInputListener listener; - private boolean initialized, shift_pressed; - private GLFWKeyCallback keyCallback; - private Queue keyInputEvents = new LinkedList(); - - /** - * Create a new input attached to the given {@link LwjglWindowVR context}. - * @param context the context to attach to this input. - */ - public GlfwKeyInputVR(LwjglWindowVR context) { - this.context = context; - } - - public void initialize() { - if (!context.isRenderable()) { - return; - } - - glfwSetKeyCallback(context.getWindowHandle(), keyCallback = new GLFWKeyCallback() { - @Override - public void invoke(long window, int key, int scancode, int action, int mods) { - scancode = GlfwKeyMap.toJmeKeyCode(key); - if( key == GLFW_KEY_LEFT_SHIFT || key == GLFW_KEY_RIGHT_SHIFT ) { - shift_pressed = (action == GLFW_PRESS); - } else if( key >= 'A' && key <= 'Z' && !shift_pressed ) { - key += 32; // make lowercase - } else if( key >= 'a' && key <= 'z' && shift_pressed ) { - key -= 32; // make uppercase - } - final KeyInputEvent evt = new KeyInputEvent(scancode, (char) key, GLFW_PRESS == action, GLFW_REPEAT == action); - evt.setTime(getInputTimeNanos()); - keyInputEvents.add(evt); - } - }); - - glfwSetInputMode(context.getWindowHandle(), GLFW_STICKY_KEYS, 1); - - initialized = true; - logger.fine("Keyboard created."); - } - - /** - * Get the key count. - * @return the key count. - */ - public int getKeyCount() { - // This might not be correct - return GLFW_KEY_LAST - GLFW_KEY_SPACE; - } - - public void update() { - if (!context.isRenderable()) { - return; - } - - while (!keyInputEvents.isEmpty()) { - listener.onKeyEvent(keyInputEvents.poll()); - } - } - - public void destroy() { - if (!context.isRenderable()) { - return; - } - - keyCallback.free(); - - logger.fine("Keyboard destroyed."); - } - - public boolean isInitialized() { - return initialized; - } - - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - public long getInputTimeNanos() { - return (long) (glfwGetTime() * 1000000000); - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwMouseInputVR.java b/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwMouseInputVR.java deleted file mode 100644 index 5dbb958fb7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/lwjgl/GlfwMouseInputVR.java +++ /dev/null @@ -1,329 +0,0 @@ -package com.jme3.input.lwjgl; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.cursors.plugins.JmeCursor; -import com.jme3.input.MouseInput; -import com.jme3.input.RawInputListener; -import com.jme3.input.event.MouseButtonEvent; -import com.jme3.input.event.MouseMotionEvent; -import com.jme3.system.lwjgl.LwjglWindowVR; -import com.jme3.util.BufferUtils; - -import org.lwjgl.glfw.GLFWCursorPosCallback; -import org.lwjgl.glfw.GLFWMouseButtonCallback; -import org.lwjgl.glfw.GLFWScrollCallback; - -import java.nio.ByteBuffer; -import java.nio.IntBuffer; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.Map; -import java.util.Queue; -import java.util.logging.Logger; - -import static org.lwjgl.glfw.GLFW.*; -import org.lwjgl.glfw.GLFWImage; -import org.lwjgl.system.MemoryUtil; - -/** - * Captures mouse input using GLFW callbacks. It then temporarily stores these in event queues which are processed in the - * {@link #update()} method. Due to some of the GLFW button id's there is a conversion method in this class which will - * convert the GLFW left, middle and right mouse button to JME3 left, middle and right button codes.

                  - * This class support modifications dedicated to VR rendering. - * @author Daniel Johansson (dannyjo) - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public class GlfwMouseInputVR implements MouseInput { - - private static final Logger logger = Logger.getLogger(GlfwMouseInputVR.class.getName()); - - private static final int WHEEL_SCALE = 120; - - private LwjglWindowVR context; - private RawInputListener listener; - private boolean cursorVisible = true; - private int mouseX, xDelta; - private int mouseY, yDelta; - private int mouseWheel; - private boolean initialized; - private GLFWCursorPosCallback cursorPosCallback; - private GLFWScrollCallback scrollCallback; - private GLFWMouseButtonCallback mouseButtonCallback; - private Queue mouseMotionEvents = new LinkedList(); - private Queue mouseButtonEvents = new LinkedList(); - - private Map jmeToGlfwCursorMap = new HashMap(); - - /** - * Create a new mouse input attached to the given {@link LwjglWindowVR context}. - * @param context the context to which to attach the input. - */ - public GlfwMouseInputVR(LwjglWindowVR context) { - this.context = context; - } - - private void onCursorPos(long window, double xpos, double ypos) { - int x = (int) Math.round(xpos); - int y = context.getSettings().getHeight() - (int) Math.round(ypos); - - if (mouseX == 0) { - mouseX = x; - } - - if (mouseY == 0) { - mouseY = y; - } - - xDelta = x - mouseX; - yDelta = y - mouseY; - mouseX = x; - mouseY = y; - - if (xDelta != 0 || yDelta != 0) { - final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(x, y, xDelta, yDelta, mouseWheel, 0); - mouseMotionEvent.setTime(getInputTimeNanos()); - mouseMotionEvents.add(mouseMotionEvent); - } - } - - private void onWheelScroll(long window, double xOffset, double yOffset) { - mouseWheel += yOffset; - - final MouseMotionEvent mouseMotionEvent = new MouseMotionEvent(mouseX, mouseY, 0, 0, mouseWheel, (int) Math.round(yOffset)); - mouseMotionEvent.setTime(getInputTimeNanos()); - mouseMotionEvents.add(mouseMotionEvent); - } - private void onMouseButton(final long window, final int button, final int action, final int mods) { - final MouseButtonEvent mouseButtonEvent = new MouseButtonEvent(convertButton(button), action == GLFW_PRESS, mouseX, mouseY); - mouseButtonEvent.setTime(getInputTimeNanos()); - mouseButtonEvents.add(mouseButtonEvent); - } - - public void initialize() { - glfwSetCursorPosCallback(context.getWindowHandle(), cursorPosCallback = new GLFWCursorPosCallback() { - @Override - public void invoke(long window, double xpos, double ypos) { - onCursorPos(window, xpos, ypos); - } - }); - - glfwSetScrollCallback(context.getWindowHandle(), scrollCallback = new GLFWScrollCallback() { - @Override - public void invoke(final long window, final double xOffset, final double yOffset) { - onWheelScroll(window, xOffset, yOffset * WHEEL_SCALE); - } - }); - - glfwSetMouseButtonCallback(context.getWindowHandle(), mouseButtonCallback = new GLFWMouseButtonCallback() { - @Override - public void invoke(final long window, final int button, final int action, final int mods) { - onMouseButton(window, button, action, mods); - } - }); - - setCursorVisible(cursorVisible); - logger.fine("Mouse created."); - initialized = true; - } - - /** - * Set the position of the cursor on the display. - * @param x the x position of the cursor (pixel). - * @param y the y position of the cursor (pixel). - */ - public void setCursorPosition(int x, int y) { - if (!context.isRenderable()) { - return; - } - - glfwSetCursorPos(context.getWindowHandle(), x, y); - } - - /** - * Hide the active cursor within the display. - */ - public void hideActiveCursor() { - if (!context.isRenderable()) { - return; - } - - if (cursorVisible) { - glfwSetInputMode(context.getWindowHandle(), GLFW_CURSOR, GLFW_CURSOR_HIDDEN); - } - } - - /** - * Get the last delta in x (pixel). - * @return the last delta in x (pixel). - * @see #getLastDeltaY() - */ - public int getLastDeltaX() { - return xDelta; - } - - /** - * Get the last delta in y (pixel). - * @return the last delta in y (pixel). - * @see #getLastDeltaX() - */ - public int getLastDeltaY() { - return yDelta; - } - - /** - * Clear the last x and y deltas. - * @see #getLastDeltaX() - * @see #getLastDeltaY() - */ - public void clearDeltas() { - xDelta = 0; - yDelta = 0; - } - - /** - * Check if the input is initialized. - * @return true if the input is initialized and false otherwise. - */ - public boolean isInitialized() { - return initialized; - } - - @Override - public int getButtonCount() { - return GLFW_MOUSE_BUTTON_LAST + 1; - } - - @Override - public void update() { - while (!mouseMotionEvents.isEmpty()) { - listener.onMouseMotionEvent(mouseMotionEvents.poll()); - } - - while (!mouseButtonEvents.isEmpty()) { - listener.onMouseButtonEvent(mouseButtonEvents.poll()); - } - } - - @Override - public void destroy() { - if (!context.isRenderable()) { - return; - } - - cursorPosCallback.free(); - scrollCallback.free(); - mouseButtonCallback.free(); - - for (long glfwCursor : jmeToGlfwCursorMap.values()) { - glfwDestroyCursor(glfwCursor); - } - - logger.fine("Mouse destroyed."); - } - - @Override - public void setCursorVisible(boolean visible) { - cursorVisible = visible; - - if (!context.isRenderable()) { - return; - } - - if (cursorVisible) { - glfwSetInputMode(context.getWindowHandle(), GLFW_CURSOR, GLFW_CURSOR_NORMAL); - } else { - glfwSetInputMode(context.getWindowHandle(), GLFW_CURSOR, GLFW_CURSOR_DISABLED); - } - } - - @Override - public void setInputListener(RawInputListener listener) { - this.listener = listener; - } - - @Override - public long getInputTimeNanos() { - return (long) (glfwGetTime() * 1000000000); - } - - private long createGlfwCursor(JmeCursor jmeCursor) { - GLFWImage glfwImage = new GLFWImage(BufferUtils.createByteBuffer(GLFWImage.SIZEOF)); - - // TODO: currently animated cursors are not supported - IntBuffer imageData = jmeCursor.getImagesData(); - ByteBuffer buf = BufferUtils.createByteBuffer(imageData.capacity() * 4); - buf.asIntBuffer().put(imageData); - - glfwImage.set(jmeCursor.getWidth(), jmeCursor.getHeight(), buf); - - return glfwCreateCursor(glfwImage, jmeCursor.getXHotSpot(), jmeCursor.getYHotSpot()); - } - - public void setNativeCursor(JmeCursor jmeCursor) { - if (jmeCursor != null) { - Long glfwCursor = jmeToGlfwCursorMap.get(jmeCursor); - - if (glfwCursor == null) { - glfwCursor = createGlfwCursor(jmeCursor); - jmeToGlfwCursorMap.put(jmeCursor, glfwCursor); - } - - glfwSetCursor(context.getWindowHandle(), glfwCursor); - } else { - glfwSetCursor(context.getWindowHandle(), MemoryUtil.NULL); - } - } - - /** - * Simply converts the GLFW button code to a JME button code. If there is no - * match it just returns the GLFW button code. Bear in mind GLFW supports 8 - * different mouse buttons. - * - * @param glfwButton the raw GLFW button index. - * @return the mapped {@link MouseInput} button id. - */ - private int convertButton(final int glfwButton) { - switch (glfwButton) { - case GLFW_MOUSE_BUTTON_LEFT: - return MouseInput.BUTTON_LEFT; - case GLFW_MOUSE_BUTTON_MIDDLE: - return MouseInput.BUTTON_MIDDLE; - case GLFW_MOUSE_BUTTON_RIGHT: - return MouseInput.BUTTON_RIGHT; - default: - return glfwButton; - } - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRMouseManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRMouseManager.java deleted file mode 100644 index 97c0c2a3a0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRMouseManager.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.jme3.input.vr; - -import java.util.logging.Logger; - -import org.lwjgl.glfw.GLFW; - -import com.jme3.app.VREnvironment; -import com.jme3.input.MouseInput; -import com.jme3.input.lwjgl.GlfwMouseInputVR; -import com.jme3.material.RenderState.BlendMode; -import com.jme3.math.Vector2f; -import com.jme3.system.AppSettings; -import com.jme3.system.lwjgl.LwjglWindow; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; - -/** - * An abstract implementation of a {@link VRMouseManager}. This class should be overrided by specific hardware implementation of VR devices. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public abstract class AbstractVRMouseManager implements VRMouseManager { - - private static final Logger logger = Logger.getLogger(AbstractVRMouseManager.class.getName()); - - private VREnvironment environment = null; - - private boolean vrMouseEnabled = true; - private boolean mouseAttached = false; - private Picture mouseImage; - private int recentCenterCount = 0; - - protected final Vector2f cursorPos = new Vector2f(); - - private float ySize, sensitivity = 8f, acceleration = 2f; - - private boolean thumbstickMode; - private float moveScale = 1f; - - /** - * Create a new AbstractVRMouseManager attached to the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} that this manager is attached to. - */ - public AbstractVRMouseManager(VREnvironment environment) { - this.environment = environment; - } - - @Override - public void initialize() { - - logger.config("Initializing VR mouse manager."); - - // load default mouseimage - mouseImage = new Picture("mouse"); - setImage("Common/Util/mouse.png"); - // hide default cursor by making it invisible - - MouseInput mi = environment.getApplication().getContext().getMouseInput(); - if( mi instanceof GlfwMouseInputVR ){ - ((GlfwMouseInputVR)mi).hideActiveCursor(); - } - centerMouse(); - - logger.config("Initialized VR mouse manager [SUCCESS]"); - } - - @Override - public VREnvironment getVREnvironment() { - return environment; - } - - @Override - public void setVRMouseEnabled(boolean enabled) { - vrMouseEnabled = enabled; - } - - @Override - public void setThumbstickMode(boolean set) { - thumbstickMode = set; - } - - @Override - public boolean isThumbstickMode() { - return thumbstickMode; - } - - @Override - public void setSpeed(float sensitivity, float acceleration) { - this.sensitivity = sensitivity; - this.acceleration = acceleration; - } - - @Override - public float getSpeedSensitivity() { - return sensitivity; - } - - @Override - public float getSpeedAcceleration() { - return acceleration; - } - - @Override - public float getMouseMoveScale() { - return moveScale; - } - - @Override - public void setMouseMoveScale(float set) { - moveScale = set; - } - - @Override - public void setImage(String texture) { - - if (environment != null){ - - if (environment.getApplication() != null){ - if( environment.isInVR() == false ){ - Texture tex = environment.getApplication().getAssetManager().loadTexture(texture); - mouseImage.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, true); - ySize = tex.getImage().getHeight(); - mouseImage.setHeight(ySize); - mouseImage.setWidth(tex.getImage().getWidth()); - mouseImage.getMaterial().getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - mouseImage.getMaterial().getAdditionalRenderState().setDepthWrite(false); - } else { - Texture tex = environment.getApplication().getAssetManager().loadTexture(texture); - mouseImage.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, true); - ySize = tex.getImage().getHeight(); - mouseImage.setHeight(ySize); - mouseImage.setWidth(tex.getImage().getWidth()); - mouseImage.getMaterial().getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - mouseImage.getMaterial().getAdditionalRenderState().setDepthWrite(false); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - - @Override - public Vector2f getCursorPosition() { - - if (environment != null){ - if (environment.getApplication() != null){ - if( environment.isInVR() ) { - return cursorPos; - } - - return environment.getApplication().getInputManager().getCursorPosition(); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - @Override - public void centerMouse() { - - if (environment != null){ - if (environment.getApplication() != null){ - // set mouse in center of the screen if newly added - Vector2f size = environment.getVRGUIManager().getCanvasSize(); - MouseInput mi = environment.getApplication().getContext().getMouseInput(); - AppSettings as = environment.getApplication().getContext().getSettings(); - if( mi instanceof GlfwMouseInputVR ) ((GlfwMouseInputVR)mi).setCursorPosition((int)(as.getWidth() / 2f), (int)(as.getHeight() / 2f)); - if( environment.isInVR() ) { - cursorPos.x = size.x / 2f; - cursorPos.y = size.y / 2f; - recentCenterCount = 2; - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - } - - @Override - public void update(float tpf) { - // if we are showing the cursor, add our picture as it - if( vrMouseEnabled && environment.getApplication().getInputManager().isCursorVisible() ) { - if(!mouseAttached) { - mouseAttached = true; - environment.getApplication().getGuiViewPort().attachScene(mouseImage); - centerMouse(); - // the "real" mouse pointer should stay hidden - if (environment.getApplication().getContext() instanceof LwjglWindow){ - GLFW.glfwSetInputMode(((LwjglWindow)environment.getApplication().getContext()).getWindowHandle(), GLFW.GLFW_CURSOR, GLFW.GLFW_CURSOR_DISABLED); - } - } - // handle mouse movements, which may be in addition to (or exclusive from) tracked movement - MouseInput mi = environment.getApplication().getContext().getMouseInput(); - if( mi instanceof GlfwMouseInputVR ) { - if( recentCenterCount <= 0 ) { - //Vector2f winratio = VRGuiManager.getCanvasToWindowRatio(); - cursorPos.x += ((GlfwMouseInputVR)mi).getLastDeltaX();// * winratio.x; - cursorPos.y += ((GlfwMouseInputVR)mi).getLastDeltaY();// * winratio.y; - if( cursorPos.x < 0f ) cursorPos.x = 0f; - if( cursorPos.y < 0f ) cursorPos.y = 0f; - if( cursorPos.x > environment.getVRGUIManager().getCanvasSize().x ) cursorPos.x = environment.getVRGUIManager().getCanvasSize().x; - if( cursorPos.y > environment.getVRGUIManager().getCanvasSize().y ) cursorPos.y = environment.getVRGUIManager().getCanvasSize().y; - } else recentCenterCount--; - ((GlfwMouseInputVR)mi).clearDeltas(); - } - // ok, update the cursor graphic position - Vector2f currentPos = getCursorPosition(); - mouseImage.setLocalTranslation(currentPos.x, currentPos.y - ySize, environment.getVRGUIManager().getGuiDistance() + 1f); - - mouseImage.updateGeometricState(); - - } else if(mouseAttached) { - mouseAttached = false; - environment.getApplication().getGuiViewPort().detachScene(mouseImage); - - // Use the setCursorVisible implementation to show the cursor again, depending on the state of cursorVisible - boolean cursorVisible = environment.getApplication().getInputManager().isCursorVisible(); - environment.getApplication().getContext().getMouseInput().setCursorVisible(cursorVisible); - } - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRViewManager.java deleted file mode 100644 index 54251fadf3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/AbstractVRViewManager.java +++ /dev/null @@ -1,234 +0,0 @@ -package com.jme3.input.vr; - -import com.jme3.app.VREnvironment; -import com.jme3.post.CartoonSSAO; -import com.jme3.post.Filter; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.FilterUtil; -import com.jme3.post.SceneProcessor; -import com.jme3.post.filters.FogFilter; -import com.jme3.post.filters.TranslucentBucketFilter; -import com.jme3.post.ssao.SSAOFilter; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.shadow.DirectionalLightShadowFilter; -import com.jme3.shadow.VRDirectionalLightShadowRenderer; -import com.jme3.texture.Texture2D; - -/** - * A VR view manager. This class holds methods that enable to submit 3D views to the VR compositor. - * System dependent classes should extends from this one. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public abstract class AbstractVRViewManager implements VRViewManager { - - - //private static final Logger logger = Logger.getLogger(AbstractVRViewManager.class.getName()); - - protected VREnvironment environment = null; - - protected Camera leftCamera; - protected ViewPort leftViewPort; - protected FilterPostProcessor leftPostProcessor; - protected Texture2D leftEyeTexture; - protected Texture2D leftEyeDepth; - - protected Camera rightCamera; - protected ViewPort rightViewPort; - protected FilterPostProcessor rightPostProcessor; - protected Texture2D rightEyeTexture; - protected Texture2D rightEyeDepth; - - protected ViewPort mirrorViewPort; - - private float resMult = 1f; - - private float heightAdjustment; - - @Override - public Camera getLeftCamera() { - return leftCamera; - } - - @Override - public Camera getRightCamera() { - return rightCamera; - } - - @Override - public ViewPort getLeftViewPort() { - return leftViewPort; - } - - @Override - public ViewPort getRightViewPort() { - return rightViewPort; - } - - /** - * Get the {@link ViewPort view port} attached to the mirror display. - * @return the view port attached to the mirror display. - */ - public ViewPort getMirrorViewPort() { - return mirrorViewPort; - } - - - @Override - public Texture2D getLeftTexture(){ - return leftEyeTexture; - } - - @Override - public Texture2D getRightTexture(){ - return rightEyeTexture; - } - - @Override - public Texture2D getLeftDepth(){ - return leftEyeDepth; - } - - @Override - public Texture2D getRightDepth(){ - return rightEyeDepth; - } - - @Override - public FilterPostProcessor getLeftPostProcessor(){ - return leftPostProcessor; - } - - @Override - public FilterPostProcessor getRightPostProcessor(){ - return rightPostProcessor; - } - - @Override - public float getResolutionMuliplier() { - return resMult; - } - - @Override - public void setResolutionMultiplier(float resMult) { - this.resMult = resMult; - } - - @Override - public float getHeightAdjustment() { - return heightAdjustment; - } - - @Override - public void setHeightAdjustment(float amount) { - heightAdjustment = amount; - } - - @Override - public VREnvironment getVREnvironment(){ - return environment; - } - - /** - * Handles moving filters from the main view to each eye - */ - public void moveScreenProcessingToEyes() { - - if (environment != null){ - if( getRightViewPort() == null ){ - return; - } - - if (environment.getApplication() != null){ - syncScreenProcessing(environment.getApplication().getViewPort()); - environment.getApplication().getViewPort().clearProcessors(); - } else { - throw new IllegalStateException("The VR environment is not attached to any application."); - } - - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - } - - /** - * Sets the two views to use the list of {@link SceneProcessor processors}. - * @param sourceViewport the {@link ViewPort viewport} that contains the processors to use. - */ - public void syncScreenProcessing(ViewPort sourceViewport) { - - if (environment != null){ - if( getRightViewPort() == null ){ - return; - } - - if (environment.getApplication() != null){ - // setup post processing filters - if( getRightPostProcessor() == null ) { - rightPostProcessor = new FilterPostProcessor(environment.getApplication().getAssetManager()); - leftPostProcessor = new FilterPostProcessor(environment.getApplication().getAssetManager()); - } - // clear out all filters & processors, to start from scratch - getRightPostProcessor().removeAllFilters(); - getLeftPostProcessor().removeAllFilters(); - getLeftViewPort().clearProcessors(); - getRightViewPort().clearProcessors(); - // if we have no processors to sync, don't add the FilterPostProcessor - if( sourceViewport.getProcessors().isEmpty() ) return; - // add post processors we just made, which are empty - getLeftViewPort().addProcessor(getLeftPostProcessor()); - getRightViewPort().addProcessor(getRightPostProcessor()); - // go through all of the filters in the processors list - // add them to the left viewport processor & clone them to the right - for(SceneProcessor sceneProcessor : sourceViewport.getProcessors()) { - if (sceneProcessor instanceof FilterPostProcessor) { - for(Filter f : ((FilterPostProcessor)sceneProcessor).getFilterList() ) { - if( f instanceof TranslucentBucketFilter ) { - // just remove this filter, we will add it at the end manually - ((FilterPostProcessor)sceneProcessor).removeFilter(f); - } else { - getLeftPostProcessor().addFilter(f); - // clone to the right - Filter f2; - if(f instanceof FogFilter){ - f2 = FilterUtil.cloneFogFilter((FogFilter)f); - } else if (f instanceof CartoonSSAO ) { - f2 = new CartoonSSAO((CartoonSSAO)f); - } else if (f instanceof SSAOFilter){ - f2 = FilterUtil.cloneSSAOFilter((SSAOFilter)f); - } else if (f instanceof DirectionalLightShadowFilter){ - f2 = FilterUtil.cloneDirectionalLightShadowFilter(environment.getApplication().getAssetManager(), (DirectionalLightShadowFilter)f); - } else { - f2 = f; // dof, bloom, lightscattering etc. - } - getRightPostProcessor().addFilter(f2); - } - } - } else if (sceneProcessor instanceof VRDirectionalLightShadowRenderer) { - // shadow processing - // TODO: make right shadow processor use same left shadow maps for performance - VRDirectionalLightShadowRenderer dlsr = (VRDirectionalLightShadowRenderer) sceneProcessor; - VRDirectionalLightShadowRenderer dlsrRight = dlsr.clone(); - dlsrRight.setLight(dlsr.getLight()); - getRightViewPort().getProcessors().add(0, dlsrRight); - getLeftViewPort().getProcessors().add(0, sceneProcessor); - } - } - // make sure each has a translucent filter renderer - getLeftPostProcessor().addFilter(new TranslucentBucketFilter()); - getRightPostProcessor().addFilter(new TranslucentBucketFilter()); - } else { - throw new IllegalStateException("The VR environment is not attached to any application."); - } - - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/HmdType.java b/jme3-vr/src/main/java/com/jme3/input/vr/HmdType.java deleted file mode 100644 index cffc1b35d4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/HmdType.java +++ /dev/null @@ -1,69 +0,0 @@ -package com.jme3.input.vr; - -/** - * The type of VR Head Mounted Device (HMD) - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public enum HmdType { - - /** - * HTC vive Head Mounted Device (HMD). - */ - HTC_VIVE, - - /** - * Valve Index Head Mounted Device (HMD). - */ - VALVE_INDEX, - - /** - * Occulus Rift Head Mounted Device (HMD). - */ - OCULUS_RIFT, - - /** - * OSVR generic Head Mounted Device (HMD). - */ - OSVR, - - /** - * FOVE Head Mounted Device (HMD). - */ - FOVE, - - /** - * STARVR Head Mounted Device (HMD). - */ - STARVR, - - /** - * GameFace Head Mounted Device (HMD). - */ - GAMEFACE, - - /** - * PlayStation VR (formely Morpheus) Head Mounted Device (HMD). - */ - MORPHEUS, - - /** - * Samsung GearVR Head Mounted Device (HMD). - */ - GEARVR, - - /** - * a null Head Mounted Device (HMD). - */ - NULL, - - /** - * a none Head Mounted Device (HMD). - */ - NONE, - - /** - * a not referenced Head Mounted Device (HMD). - */ - OTHER -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRAPI.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRAPI.java deleted file mode 100644 index b1d0fc46ab..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRAPI.java +++ /dev/null @@ -1,190 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr; - -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; - -/** - * An interface that represents a VR system. This interface has to be implemented in order to wrap underlying VR system (OpenVR, OSVR, ...) - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public interface VRAPI { - - /** - * Initialize this object from a VR system. All the native bindings to underlying VR system should be done within this method. - * @return true if the initialization is a success and false otherwise. - */ - public boolean initialize(); - - /** - * Initialize the VR compositor that will be used for rendering. - * @param allowed true if the use of VR compositor is allowed and false otherwise. - * @return true if the initialization is a success and false otherwise. - */ - public boolean initVRCompositor(boolean allowed); - - /** - * Get the object that wraps natively the VR system. - * @return the object that wraps natively the VR system. - */ - public Object getVRSystem(); - - /** - * Get the object that wraps natively the VR compositor. - * @return the object that wraps natively the VR system. - */ - public Object getCompositor(); - - /** - * Get the name of the underlying VR system. - * @return the name of the underlying VR system. - */ - public String getName(); - - /** - * Get the input provided by the underlying VR system. - * @return the input provided by the underlying VR system. - */ - public VRInputAPI getVRinput(); - - /** - * Flip the left and right eye.. - * @param set true if the eyes has to be flipped and false otherwise. - */ - public void setFlipEyes(boolean set); - - /** - * Set if latency information has to be logged. - * @param set true if latency information has to be logged and false otherwise. - */ - public void printLatencyInfoToConsole(boolean set); - - /** - * Get the Head Mounted Device (HMD) display frequency. - * @return the Head Mounted DEvice (HMD) display frequency. - */ - public int getDisplayFrequency(); - - /** - * Close the link with underlying VR system and free all attached resources. - */ - public void destroy(); - - /** - * Check if the VR API is initialized. - * @return true if the VR API is initialized and false otherwise. - * @see #initialize() - */ - public boolean isInitialized(); - - /** - * Reset the VR system. After a call to this method, the current position of the HMD should be - * the origin (i-e the observer without any combined transformation). - */ - public void reset(); - - /** - * Get the size of a Head Mounted Device (HMD) rendering area in pixels. - * @param store the size of a Head Mounted Device (HMD) rendering area in pixels (modified). - */ - public void getRenderSize(Vector2f store); - - //public float getFOV(int dir); - - /** - * Get the Head Mounted Device (HMD) interpupilar distance in meters. - * @return the Head Mounted Device (HMD) interpupilar distance in meters. - */ - public float getInterpupillaryDistance(); - - /** - * Get the Head Mounted Device (HMD) orientation. - * @return the Head Mounted Device (HMD) orientation. - */ - public Quaternion getOrientation(); - - /** - * Get the Head Mounted Device (HMD) position. - * @return the Head Mounted Device (HMD) orientation. - */ - public Vector3f getPosition(); - - /** - * Get the Head Mounted Device (HMD) position and orientation. - * @param storePos the Head Mounted Device (HMD) position (modified). - * @param storeRot the Head Mounted Device (HMD) rotation (modified). - */ - public void getPositionAndOrientation(Vector3f storePos, Quaternion storeRot); - - /** - * Update Head Mounted Device (HMD) pose internal storage. This method should be called before other calls to HMD position/orientation access. - */ - public void updatePose(); - - /** - * Get the Head Mounted Device (HMD) left eye projection matrix. - * @param cam the camera attached to the left eye. - * @return the Head Mounted Device (HMD) left eye projection matrix. - */ - public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam); - - /** - * Get the Head Mounted Device (HMD) right eye projection matrix. - * @param cam the camera attached to the right eye. - * @return the Head Mounted Device (HMD) right eye projection matrix. - */ - public Matrix4f getHMDMatrixProjectionRightEye(Camera cam); - - /** - * Get the Head Mounted Device (HMD) left eye pose (position of the eye from the head) as a {@link Vector3f vector}. - * @return the Head Mounted Device (HMD) left eye pose as a {@link Vector3f vector}. - */ - public Vector3f getHMDVectorPoseLeftEye(); - - /** - * Get the Head Mounted Device (HMD) right eye pose (position of the eye from the head) as a {@link Vector3f vector}. - * @return the Head Mounted Device (HMD) right eye pose as a {@link Vector3f vector}. - */ - public Vector3f getHMDVectorPoseRightEye(); - - /** - * Returns the transform between the view space and left eye space. - * Eye space is the per-eye flavor of view space that provides stereo disparity. - * Instead of Model * View * Projection the model is Model * View * Eye * Projection. - * Normally View and Eye will be multiplied together and treated as View. - * This matrix incorporates the user's interpupillary distance (IPD). - * @return the transform between the view space and eye space. - */ - public Matrix4f getHMDMatrixPoseLeftEye(); - - /** - * Returns the transform between the view space and right eye space. - * Eye space is the per-eye flavor of view space that provides stereo disparity. - * Instead of Model * View * Projection the model is Model * View * Eye * Projection. - * Normally View and Eye will be multiplied together and treated as View. - * This matrix incorporates the user's interpupillary distance (IPD). - * @return the transform between the view space and eye space. - */ - public Matrix4f getHMDMatrixPoseRightEye(); - - /** - * Get the Head Mounted Device (HMD) type. - * @return the Head Mounted Device (HMD) type. - */ - public HmdType getType(); - - /** - * Get the seated to absolute position. - * @return the seated to absolute position. - */ - public Vector3f getSeatedToAbsolutePosition(); - -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRBounds.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRBounds.java deleted file mode 100644 index 288812c68f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRBounds.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.jme3.input.vr; - -import com.jme3.math.Vector2f; - -/** - * This interface describe the VR playground bounds. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public interface VRBounds { - - /** - * Get the size of the VR playground. - * @return the size of the VR playground. - */ - public Vector2f getPlaySize(); -} 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 deleted file mode 100644 index 0a2443a686..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputAPI.java +++ /dev/null @@ -1,198 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr; - -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; - -/** - * An interface that represents a VR input (typically a VR device such as a controller). - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public interface VRInputAPI { - - /** - * Check if the given button is down (more generally if the given input type is activated). - * @param controllerIndex the index of the controller to check. - * @param checkButton the button / input to check. - * @return true if the button / input is down / activated and false otherwise. - */ - public boolean isButtonDown(int controllerIndex, VRInputType checkButton); - - /** - * Check if the given button / input from the given controller has been just pressed / activated. - * @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. - */ - 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. - * @see #wasButtonPressedSinceLastCall(int, VRInputType) - */ - public void resetInputSinceLastCall(); - - /** - * Get the controller axis delta from the last value. - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the controller axis delta from the last call. - */ - public Vector2f getAxisDeltaSinceLastCall(int controllerIndex, VRInputType forAxis); - - /** - * Get the controller velocity on all axes. - * @param controllerIndex the index of the controller. - * @return the controller velocity on all axes. - * @see #getAngularVelocity(int) - */ - public Vector3f getVelocity(int controllerIndex); - - /** - * Get the controller angular velocity on all axes. - * @param controllerIndex the index of the controller. - * @return the controller angular velocity on all axes. - * @see #getVelocity(int) - */ - public Vector3f getAngularVelocity(int controllerIndex); - - /** - * 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}. - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the axis value for the given input on the given controller. - * @see #getAxisRaw(int, VRInputType) - * @see #getAxisMultiplier() - */ - public Vector2f getAxis(int controllerIndex, VRInputType forAxis); - - /** - * Get the axis value for the given input on the given controller. - * @param controllerIndex the index of the controller. - * @param forAxis the axis. - * @return the axis value for the given input on the given controller. - * @see #getAxis(int, VRInputType) - */ - public Vector2f getAxisRaw(int controllerIndex, VRInputType forAxis); - - /** - * Initialize the input. - * @return true if the initialization is successful and false otherwise. - */ - public boolean init(); - - /** - * Get the number of tracked controllers (for example, hand controllers) attached to the VR system. - * @return the number of controllers attached to the VR system. - * @see #getTrackedController(int) - */ - public int getTrackedControllerCount(); - - /** - * Get a tracked controller (for example, a hand controller) that is attached to the VR system. - * @param index the index of the controller. - * @return the tracked controller (for example, a hand controller) that is attached to the VR system. - * @see #getTrackedControllerCount() - */ - public VRTrackedController getTrackedController(int index); - - /** - * Update the connected controllers. - * This method should be used just after the initialization of the input. - */ - public void updateConnectedControllers(); - - /** - * Update the controller states. - * This method should be called before accessing any controller data. - */ - public void updateControllerStates(); - - /** - * Get the native wrapping of a controller state. - * @param index the index of the controller. - * @return the native wrapping of a controller state. - */ - public Object getRawControllerState(int index); - - /** - * Swap the two hands (exchange the hands controller 1 and 2 indices). - */ - public void swapHands(); - - /** - * Get the controller axis multiplier. - * The controller axis raw data (trackpad, trigger, ...) value is multiplied by the one given in parameter. - * @return the controller axis multiplier. - * @see #setAxisMultiplier(float) - */ - public float getAxisMultiplier(); - - /** - * Set the controller axis multiplier. - * The controller axis raw data (trackpad, trigger, ...) value is multiplied by the one given in parameter. - * @param set the controller axis multiplier. - * @see #getAxisMultiplier() - */ - public void setAxisMultiplier(float set); - - //public Matrix4f getPoseForInputDevice(int index); - - /** - * Check if the VR system has the focus and if it's not used by other process. - * @return true if the VR system has the focus and false otherwise. - */ - public boolean isInputFocused(); - - /** - * Check if the input device is actually tracked (i-e if we can obtain a pose from the input). - * @param index the index of the controller. - * @return true if the input device is actually tracked and false otherwise. - */ - public boolean isInputDeviceTracking(int index); - - /** - * Get the orientation of the input. - * @param index the index of the controller. - * @return the orientation of the input. - */ - public Quaternion getOrientation(int index); - - /** - * Get the position of the input. - * @param index the index of the controller. - * @return the position of the input. - */ - public Vector3f getPosition(int index); - - /** - * Get where is the controller pointing, after all rotations are combined. - * This position should include includes observer rotation from the VR application. - * @param index the index of the controller. - * @return the rotation of the input after all positional tracking is complete. - */ - public Quaternion getFinalObserverRotation(int index); - - /** - * Get the position of the input after all positional tracking is complete. - * This position should include includes observer position from the VR application. - * @param index the index of the controller. - * @return the position of the input after all positional tracking is complete. - */ - public Vector3f getFinalObserverPosition(int index); - - /** - * Trigger a haptic pulse on the selected controller for the duration given in parameters (in seconds). - * @param controllerIndex the index of the controller. - * @param seconds the duration of the pulse in seconds. - */ - public void triggerHapticPulse(int controllerIndex, float seconds); - -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java deleted file mode 100644 index c82e2b518c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRInputType.java +++ /dev/null @@ -1,147 +0,0 @@ -package com.jme3.input.vr; - -/** - * The type of a VR input. This enumeration enables to determine which part of the VR device is involved within input callback. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public enum VRInputType { - - /** - * an HTC vive trigger axis (about Vive controller). - */ - ViveTriggerAxis(0), - - /** - * an HTC vive trackpad axis (about Vive controller). - */ - ViveTrackpadAxis(1), - - /** - * an HTC vive grip button (about Vive controller). - */ - ViveGripButton(2), - - /** - * an HTC vive menu button (about Vive controller). - */ - ViveMenuButton(3), - - /** - * The thumbstick on the Oculus Touch controllers. - * - * Unlike the Vive controllers where the touchpad is commonly used - * as a virtual DPad, you should avoid using the thumbstick for purposes - * that do not require analog input. - */ - OculusThumbstickAxis(0), - - /** - * The trigger button on the Oculus Touch controllers. - * - * This is the button under the user's index finger, and should not be used to - * pick up objects. See the - * Oculus Developer documentation. - */ - OculusTriggerAxis(0), - - /** - * The 'grab' button on the Oculus Touch controllers. - * - * This button should only (unless you have a compelling reason otherwise) be used to pick up objects. - */ - OculusGripAxis(0), - - /** - * The upper buttons on the Oculus Touch controllers - B on the right controller, and Y on the left. - */ - OculusTopButton(org.lwjgl.ovr.OVR.ovrButton_B | org.lwjgl.ovr.OVR.ovrButton_Y), - - /** - * The lower (not counting menu) buttons on the Oculus Touch - * controllers - A on the right controller, and X on the left. - */ - OculusBottomButton(org.lwjgl.ovr.OVR.ovrButton_A | org.lwjgl.ovr.OVR.ovrButton_X), - - /** - * The 'click' button on the Oculus Touch thumbsticks. - */ - OculusThumbstickButton(org.lwjgl.ovr.OVR.ovrButton_LThumb | org.lwjgl.ovr.OVR.ovrButton_RThumb), - - /** - * The game-usable menu button, under and to the left of the 'X' button on the left controller. - * - * Most games use this to pause - it preferably should be used for at least that purpose, and is - * uncomfortable to rest your thumb on (in games where you suddenly have to pause/open a menu). - */ - OculusMenuButton(org.lwjgl.ovr.OVR.ovrButton_Enter), - - /** - * The capacitive touch sensors on the top buttons (Y and B) of the Oculus Touch. - */ - OculusTopTouch(org.lwjgl.ovr.OVR.ovrTouch_B | org.lwjgl.ovr.OVR.ovrTouch_Y), - - /** - * The capacitive touch sensors on the lower buttons (X and A) of the Oculus Touch. - */ - OculusBottomTouch(org.lwjgl.ovr.OVR.ovrTouch_A | org.lwjgl.ovr.OVR.ovrTouch_X), - - /** - * The capacitive touch sensors on the thumbsticks of the Oculus Touch. - */ - OculusThumbstickTouch(org.lwjgl.ovr.OVR.ovrTouch_LThumb | org.lwjgl.ovr.OVR.ovrTouch_RThumb), - - /** - * The capacitive touch sensors on the thumbrests of the Oculus Touch - this is a textured pad - * on the Oculus Touch controller next to the ABXY buttons for users to reset their thumbs on. - * - * While it probably goes without saying, only use this for gesture support and do not bind game - * elements to it. - */ - OculusThumbrestTouch(org.lwjgl.ovr.OVR.ovrTouch_LThumbRest | org.lwjgl.ovr.OVR.ovrTouch_RThumbRest), - - /** - * The state of a software calculation based on the capacitive touch sensor values that determine if - * the user has lifted their thumb off the controller, and can be used for gesture support. - * - * This should be used instead of calculating this yourself based on the touch results of all the other - * parts of the controller. - */ - OculusThumbUp(org.lwjgl.ovr.OVR.ovrTouch_LThumbUp | org.lwjgl.ovr.OVR.ovrTouch_RThumbUp), - - /** - * Is the user resting their finger on the trigger of an Oculus Touch controller? - */ - OculusIndexTouch(org.lwjgl.ovr.OVR.ovrTouch_LIndexPointing | org.lwjgl.ovr.OVR.ovrTouch_RIndexPointing), - - /** - * Is the user pointing their finger forwards, as if to press a button? - * - * This is internally calculated from proximity and filtering is applied - it should be used rather - * than !OculusIndexTouch, as it will probably lead to better results. - */ - OculusIndexPointing(org.lwjgl.ovr.OVR.ovrTouch_LIndexPointing | org.lwjgl.ovr.OVR.ovrTouch_RIndexPointing); - - /** - * The value that codes the input type. - */ - private final int value; - - /** - * Construct a new input type with the given code. - * @param value the code of the input type. - */ - private VRInputType(int value) { - this.value = value; - } - - /** - * Get the value (code) of the input type. - * @return the value (code) of the input type. - */ - public int getValue() { - return value; - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRMouseManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRMouseManager.java deleted file mode 100644 index aa6b100569..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRMouseManager.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.jme3.input.vr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.controls.AnalogListener; -import com.jme3.math.Vector2f; - -/** - * A class dedicated to the handling of the mouse within VR environment. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public interface VRMouseManager { - - - /** - * Initialize the VR mouse manager. - */ - public void initialize(); - - /** - * Get the {@link VREnvironment VR Environment} to which this manager is attached. - * @return the {@link VREnvironment VR Environment} to which this manager is attached. - */ - public VREnvironment getVREnvironment(); - - /** - * Set if the mouse cursor should be used in the VR view. - * @param enabled true if the mouse cursor should be displayed in VR and false otherwise. - */ - public void setVRMouseEnabled(boolean enabled); - - /** - * Set if the VR device controller is used within thumb stick mode. - * @param set true if the VR device controller is used within thumb stick mode and false otherwise. - */ - public void setThumbstickMode(boolean set); - - /** - * Get if the VR device controller is used within thumb stick mode. - * @return true if the VR device controller is used within thumb stick mode and false otherwise. - */ - public boolean isThumbstickMode(); - - /** - * Set the speed of the mouse. - * @param sensitivity the sensitivity of the mouse. - * @param acceleration the acceleration of the mouse. - * @see #getSpeedAcceleration() - * @see #getSpeedSensitivity() - */ - public void setSpeed(float sensitivity, float acceleration); - - /** - * Get the sensitivity of the mouse. - * @return the sensitivity of the mouse. - * @see #setSpeed(float, float) - */ - public float getSpeedSensitivity(); - - /** - * Get the acceleration of the mouse. - * @return the acceleration of the mouse. - * @see #setSpeed(float, float) - */ - public float getSpeedAcceleration(); - - /** - * Get the move scale. - * return the move scale. - * @see #setMouseMoveScale(float) - */ - public float getMouseMoveScale(); - - /** - * Set the mouse move scale. - * @param set the mouse move scale. - * @see #getMouseMoveScale() - */ - public void setMouseMoveScale(float set); - - /** - * Set the image to use as mouse cursor. The given string describe an asset that the underlying application asset manager has to load. - * @param texture the image to use as mouse cursor. - */ - public void setImage(String texture); - - /** - * Update analog controller as it was a mouse controller. - * @param inputIndex the index of the controller attached to the VR system. - * @param mouseListener the JMonkey mouse listener to trigger. - * @param mouseXName the mouseX identifier. - * @param mouseYName the mouseY identifier - * @param tpf the time per frame. - */ - public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf); - - /** - * Get the actual cursor position. - * @return the actual cursor position. - */ - public Vector2f getCursorPosition(); - - /** - * Center the mouse on the display. - */ - public void centerMouse(); - - /** - * Update the mouse manager. This method should not be called manually. - * The standard behavior for this method is to be called from the {@link VRViewManager#update(float) update method} of the attached {@link VRViewManager VR view manager}. - * @param tpf the time per frame. - */ - public void update(float tpf); -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRTrackedController.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRTrackedController.java deleted file mode 100644 index f47e36aa8b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRTrackedController.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jme3.input.vr; - -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; - -/** - * TODO - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public interface VRTrackedController { - - /** - * Get the controller name. - * @return the controller name. - */ - public String getControllerName(); - - /** - * Get the controller manufacturer. - * @return the controller manufacturer. - */ - public String getControllerManufacturer(); - - /** - * Get the position of the tracked device. This value is the translation component of the device {@link #getPose() pose}. - * @return the position of the tracked device. - * @see #getOrientation() - * @see #getPose() - */ - public Vector3f getPosition(); - - /** - * Get the orientation of the tracked device. This value is the rotation component of the device {@link #getPose() pose}. - * @return the orientation of the tracked device. - * @see #getPosition() - * @see #getPose() - */ - public Quaternion getOrientation(); - - /** - * Get the pose of the tracked device. - * The pose is a 4x4 matrix than combine the {@link #getPosition() position} and the {@link #getOrientation() orientation} of the device. - * @return the pose of the tracked device. - * @see #getPosition() - * @see #getOrientation() - */ - public Matrix4f getPose(); -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/VRViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/VRViewManager.java deleted file mode 100644 index 87aaadff90..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/VRViewManager.java +++ /dev/null @@ -1,172 +0,0 @@ -package com.jme3.input.vr; - -import com.jme3.app.VRAppState; -import com.jme3.app.VREnvironment; -import com.jme3.app.state.AppState; -import com.jme3.post.FilterPostProcessor; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.texture.Texture2D; - -/** - * A VR view manager. This interface describes methods that enable to submit 3D views to the VR compositor. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public interface VRViewManager { - - /** - * The name of the left view. - */ - public final static String LEFT_VIEW_NAME = "Left View"; - - /** - * The name of the right view. - */ - public final static String RIGHT_VIEW_NAME = "Right View"; - - /** - * Get the {@link Camera camera} attached to the left eye. - * @return the {@link Camera camera} attached to the left eye. - * @see #getRightCamera() - */ - public Camera getLeftCamera(); - - /** - * Get the {@link Camera camera} attached to the right eye. - * @return the {@link Camera camera} attached to the right eye. - * @see #getLeftCamera() - */ - public Camera getRightCamera(); - - /** - * Get the {@link ViewPort viewport} attached to the left eye. - * @return the {@link ViewPort viewport} attached to the left eye. - * @see #getRightViewPort() - */ - public ViewPort getLeftViewPort(); - - - /** - * Get the {@link ViewPort viewport} attached to the right eye. - * @return the {@link ViewPort viewport} attached to the right eye. - * @see #getLeftViewPort() - */ - public ViewPort getRightViewPort(); - - /** - * Get the {@link ViewPort view port} attached to the mirror display. - * @return the view port attached to the mirror display. - * @see #getLeftViewPort() - * @see #getRightViewPort() - */ - public ViewPort getMirrorViewPort(); - - /** - * Get the texture attached to the left eye. - * @return the texture attached to the left eye. - * @see #getRightTexture() - */ - public Texture2D getLeftTexture(); - - /** - * Get the texture attached to the right eye. - * @return the texture attached to the right eye. - * @see #getLeftTexture() - */ - public Texture2D getRightTexture(); - - /** - * Get the depth texture attached to the left eye. - * @return the texture attached to the left eye. - * @see #getRightTexture() - */ - public Texture2D getLeftDepth(); - - /** - * Get the depth texture attached to the right eye. - * @return the texture attached to the right eye. - * @see #getLeftTexture() - */ - public Texture2D getRightDepth(); - - /** - * Get the {@link FilterPostProcessor filter post processor} attached to the left eye. - * @return the {@link FilterPostProcessor filter post processor} attached to the left eye. - * @see #getRightPostProcessor() - */ - public FilterPostProcessor getLeftPostProcessor(); - - /** - * Get the {@link FilterPostProcessor filter post processor} attached to the right eye. - * @return the {@link FilterPostProcessor filter post processor} attached to the right eye. - * @see #getLeftPostProcessor() - */ - public FilterPostProcessor getRightPostProcessor(); - - /** - * Get the resolution multiplier. - * @return the resolution multiplier. - * @see #setResolutionMultiplier(float) - */ - public float getResolutionMuliplier(); - - /** - * Set the resolution multiplier. - * @param resMult the resolution multiplier. - * @see #getResolutionMuliplier() - */ - public void setResolutionMultiplier(float resMult); - - /** - * Get the height adjustment to apply to the cameras before rendering. - * @return the height adjustment to apply to the cameras before rendering. - * @see #setHeightAdjustment(float) - */ - public float getHeightAdjustment(); - - /** - * Set the height adjustment to apply to the cameras before rendering. - * @param amount the height adjustment to apply to the cameras before rendering. - * @see #getHeightAdjustment() - */ - public void setHeightAdjustment(float amount); - - /** - * Get the {@link VREnvironment VR environment} to which the view manager is attached. - * @return the {@link VREnvironment VR environment} to which the view manager is attached. - */ - public VREnvironment getVREnvironment(); - - /** - * Initialize the VR view manager. This method should be called after the attachment of a {@link VREnvironment VR environment} to an application. - */ - public void initialize(); - - /** - * Update the VR view manager. - * This method is called by the attached {@link VRAppState app state} and should not be called manually. - * @param tpf the time per frame. - */ - public void update(float tpf); - - /** - * This method contains action to be done during the rendering phase. - * This method should be called for example from the {@link com.jme3.app.state.AppState#render(com.jme3.renderer.RenderManager) render} method of an {@link com.jme3.app.state.AppState app state}. - * @see #postRender() - */ - public void render(); - - /** - * Send the rendering result as textures to the two eyes. - * This method should be called after all the rendering operations - * (for example at the end of the {@link AppState#postRender() postRender()} method of the attached app state.) - * @see #render() - */ - public void postRender(); - - /** - * Handles moving filters from the main view to each eye. - */ - public void moveScreenProcessingToEyes(); -} 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 deleted file mode 100644 index 7615ae8449..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVR.java +++ /dev/null @@ -1,461 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.lwjgl_openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.HmdType; -import com.jme3.input.vr.VRAPI; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.util.VRUtil; - -import java.nio.IntBuffer; -import java.util.Locale; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.BufferUtils; -import org.lwjgl.openvr.HmdMatrix34; -import org.lwjgl.openvr.HmdMatrix44; -import org.lwjgl.openvr.TrackedDevicePose; -import org.lwjgl.openvr.VR; -import org.lwjgl.openvr.VRCompositor; -import org.lwjgl.openvr.VRSystem; - -/** - * A class that wraps an OpenVR system. - * @author reden - phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @author Rickard Edén - */ -public class LWJGLOpenVR implements VRAPI { - - private static final Logger logger = Logger.getLogger(LWJGLOpenVR.class.getName()); - - private static boolean initSuccess = false; - private static boolean flipEyes = false; - - private IntBuffer hmdDisplayFrequency; - private TrackedDevicePose.Buffer trackedDevicePose; - protected TrackedDevicePose[] hmdTrackedDevicePoses; - - protected IntBuffer hmdErrorStore = BufferUtils.createIntBuffer(1); - - private final Quaternion rotStore = new Quaternion(); - private final Vector3f posStore = new Vector3f(); - - // for debugging latency - private int frames = 0; - - protected Matrix4f[] poseMatrices; - - private final Matrix4f hmdPose = Matrix4f.IDENTITY.clone(); - private Matrix4f hmdProjectionLeftEye; - private Matrix4f hmdProjectionRightEye; - private Matrix4f hmdPoseLeftEye; - private Matrix4f hmdPoseRightEye; - - private Vector3f hmdPoseLeftEyeVec, hmdPoseRightEyeVec, hmdSeatToStand; - - private float vsyncToPhotons; - private double timePerFrame, frameCountRun; - private long frameCount; - private LWJGLOpenVRInput VRinput; - - - private VREnvironment environment = null; - - - /** - * Convert specific OpenVR {@link org.lwjgl.openvr.HmdMatrix34 HmdMatrix34} into JME {@link Matrix4f Matrix4f} - * @param hmdMatrix the input matrix - * @param mat the converted matrix - * @return the converted matrix - */ - public static Matrix4f convertSteamVRMatrix3ToMatrix4f(org.lwjgl.openvr.HmdMatrix34 hmdMatrix, Matrix4f mat){ - mat.set(hmdMatrix.m(0), hmdMatrix.m(1), hmdMatrix.m(2), hmdMatrix.m(3), - hmdMatrix.m(4), hmdMatrix.m(5), hmdMatrix.m(6), hmdMatrix.m(7), - hmdMatrix.m(8), hmdMatrix.m(9), hmdMatrix.m(10), hmdMatrix.m(11), - 0f, 0f, 0f, 1f); - return mat; - } - - /** - * Convert specific OpenVR {@link org.lwjgl.openvr.HmdMatrix34 HmdMatrix34_t} into JME {@link Matrix4f Matrix4f} - * @param hmdMatrix the input matrix - * @param mat the converted matrix - * @return the converted matrix - */ - public static Matrix4f convertSteamVRMatrix4ToMatrix4f(org.lwjgl.openvr.HmdMatrix44 hmdMatrix, Matrix4f mat){ - mat.set(hmdMatrix.m(0), hmdMatrix.m(1), hmdMatrix.m(2), hmdMatrix.m(3), - hmdMatrix.m(4), hmdMatrix.m(5), hmdMatrix.m(6), hmdMatrix.m(7), - hmdMatrix.m(8), hmdMatrix.m(9), hmdMatrix.m(10), hmdMatrix.m(11), - hmdMatrix.m(12), hmdMatrix.m(13), hmdMatrix.m(14), hmdMatrix.m(15)); - return mat; - } - - /** - * Create a new OpenVR system - * attached to the given {@link VREnvironment VR environment}. - * @param environment the VR environment to which this API is attached. - */ - public LWJGLOpenVR(VREnvironment environment){ - this.environment = environment; - } - - @Override - public LWJGLOpenVRInput getVRinput() { - return VRinput; - } - - @Override - public Object getVRSystem() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - public Object getCompositor() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - public String getName() { - return "OpenVR/LWJGL"; - } - - private static long latencyWaitTime = 0; - - @Override - public void setFlipEyes(boolean set) { - flipEyes = set; - } - - private boolean enableDebugLatency = false; - - @Override - public void printLatencyInfoToConsole(boolean set) { - enableDebugLatency = set; - } - - @Override - public int getDisplayFrequency() { - if( hmdDisplayFrequency == null ) return 0; - return hmdDisplayFrequency.get(0); - } - - @Override - public boolean initialize() { - - logger.config("Initializing OpenVR system..."); - - // Init the native linking to the OpenVR library. - - int result = VR.VR_InitInternal(hmdErrorStore, VR.EVRApplicationType_VRApplication_Scene); - - if(hmdErrorStore.get(0) != VR.EVRInitError_VRInitError_None) { - logger.severe("OpenVR Initialize Result: " + VR.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0))); - logger.severe("Initializing OpenVR system [FAILED]"); - return false; - } else { - logger.config("OpenVR initialized & VR connected."); - org.lwjgl.openvr.OpenVR.create(result); - logger.info("Model Number : " + VRSystem.VRSystem_GetStringTrackedDeviceProperty( - VR.k_unTrackedDeviceIndex_Hmd, VR.ETrackedDeviceProperty_Prop_ModelNumber_String, hmdErrorStore)); - logger.info("Serial Number: " + VRSystem.VRSystem_GetStringTrackedDeviceProperty( - VR.k_unTrackedDeviceIndex_Hmd, VR.ETrackedDeviceProperty_Prop_SerialNumber_String, hmdErrorStore)); - - hmdDisplayFrequency = BufferUtils.createIntBuffer(1); - hmdDisplayFrequency.put( (int) VR.ETrackedDeviceProperty_Prop_DisplayFrequency_Float); - - trackedDevicePose = TrackedDevicePose.create(VR.k_unMaxTrackedDeviceCount); - hmdTrackedDevicePoses = new TrackedDevicePose[VR.k_unMaxTrackedDeviceCount]; - poseMatrices = new Matrix4f[VR.k_unMaxTrackedDeviceCount]; - for(int i=0;i 0){ - if(hmdErrorStore.get(0) == VR.EVRInitError_VRInitError_None){ - setTrackingSpace(environment.isSeatedExperience() ); - logger.config("OpenVR Compositor initialized"); - } else { - logger.severe("OpenVR Compositor error: " + hmdErrorStore.get(0)); - } - } else { - logger.log(Level.SEVERE, "Cannot get generic interface for \""+VR.IVRCompositor_Version+"\", "+VR.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.get(0))+" ("+hmdErrorStore.get(0)+")"); - } - } - return true; - } - - /** - * Initialize the headset camera. - * @param allowed true is the use of the headset camera is allowed and false otherwise. - * @return token for camera - */ - public long initCamera(boolean allowed) { - hmdErrorStore.put(0, VR.EVRInitError_VRInitError_None); // clear the error store - if( allowed) { - - long result = VR.VR_GetGenericInterface(VR.IVRTrackedCamera_Version, hmdErrorStore); - if (result > 0){ - if(hmdErrorStore.get(0) == VR.EVRInitError_VRInitError_None ){ - logger.config("OpenVR Camera initialized"); - } - return result; - } else { - logger.severe("Failed to initialize camera"); - } - } - return 0; - } - - @Override - public void destroy() { - VR.VR_ShutdownInternal(); - } - - @Override - public boolean isInitialized() { - return initSuccess; - } - - @Override - public void reset() { - VRSystem.VRSystem_ResetSeatedZeroPose(); - hmdSeatToStand = null; - } - - @Override - public void getRenderSize(Vector2f store) { - IntBuffer w = BufferUtils.createIntBuffer(1); - IntBuffer h = BufferUtils.createIntBuffer(1); - VRSystem.VRSystem_GetRecommendedRenderTargetSize(w, h); - logger.config("Recommended render width : " + w.get(0)); - logger.config("Recommended render height: " + h.get(0)); - store.x = w.get(0); - store.y = h.get(0); - } - - @Override - public float getInterpupillaryDistance() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - @Override - public Quaternion getOrientation() { - VRUtil.convertMatrix4toQuat(hmdPose, rotStore); - return rotStore; - } - - @Override - public Vector3f getPosition() { - // the hmdPose comes in rotated funny, fix that here - hmdPose.toTranslationVector(posStore); - posStore.x = -posStore.x; - posStore.z = -posStore.z; - return posStore; - } - - @Override - public void getPositionAndOrientation(Vector3f storePos, Quaternion storeRot) { - hmdPose.toTranslationVector(storePos); - storePos.x = -storePos.x; - storePos.z = -storePos.z; - storeRot.set(getOrientation()); - } - - @Override - public void updatePose(){ - int result = VRCompositor.nVRCompositor_WaitGetPoses(trackedDevicePose.address(), trackedDevicePose.remaining(), 0, 0); - // NPE when calling without a gamePoseArray. Issue filed with lwjgl #418 -// int result = VRCompositor.VRCompositor_WaitGetPoses(trackedDevicePose, null); - environment.getVRinput().updateControllerStates(); - - // read pose data from native - for (int nDevice = 0; nDevice < VR.k_unMaxTrackedDeviceCount; ++nDevice ){ - if( hmdTrackedDevicePoses[nDevice].bPoseIsValid() ){ - convertSteamVRMatrix3ToMatrix4f(hmdTrackedDevicePoses[nDevice].mDeviceToAbsoluteTracking(), poseMatrices[nDevice]); - } - } - - if ( hmdTrackedDevicePoses[VR.k_unTrackedDeviceIndex_Hmd].bPoseIsValid()){ - hmdPose.set(poseMatrices[VR.k_unTrackedDeviceIndex_Hmd]); - } else { - hmdPose.set(Matrix4f.IDENTITY); - } - } - - @Override - public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam){ - if( hmdProjectionLeftEye != null ) { - return hmdProjectionLeftEye; - } else { - HmdMatrix44 mat = HmdMatrix44.create(); - mat = VRSystem.VRSystem_GetProjectionMatrix(VR.EVREye_Eye_Left, cam.getFrustumNear(), cam.getFrustumFar(), mat); - hmdProjectionLeftEye = new Matrix4f(); - convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionLeftEye); - return hmdProjectionLeftEye; - } - } - - @Override - public Matrix4f getHMDMatrixProjectionRightEye(Camera cam){ - if( hmdProjectionRightEye != null ) { - return hmdProjectionRightEye; - } else { - HmdMatrix44 mat = HmdMatrix44.create(); - mat = VRSystem.VRSystem_GetProjectionMatrix(VR.EVREye_Eye_Right, cam.getFrustumNear(), cam.getFrustumFar(), mat); - hmdProjectionRightEye = new Matrix4f(); - convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionRightEye); - return hmdProjectionRightEye; - } - } - - @Override - public Vector3f getHMDVectorPoseLeftEye() { - if( hmdPoseLeftEyeVec == null ) { - hmdPoseLeftEyeVec = getHMDMatrixPoseLeftEye().toTranslationVector(); - // set default IPD if none or broken - if( hmdPoseLeftEyeVec.x <= 0.080f * -0.5f || hmdPoseLeftEyeVec.x >= 0.040f * -0.5f ) { - hmdPoseLeftEyeVec.x = 0.065f * -0.5f; - } - if( flipEyes == false ) hmdPoseLeftEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseLeftEyeVec; - } - - @Override - public Vector3f getHMDVectorPoseRightEye() { - if( hmdPoseRightEyeVec == null ) { - hmdPoseRightEyeVec = getHMDMatrixPoseRightEye().toTranslationVector(); - // set default IPD if none or broken - if( hmdPoseRightEyeVec.x >= 0.080f * 0.5f || hmdPoseRightEyeVec.x <= 0.040f * 0.5f ) { - hmdPoseRightEyeVec.x = 0.065f * 0.5f; - } - if( flipEyes == false ) hmdPoseRightEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseRightEyeVec; - } - - @Override - public Vector3f getSeatedToAbsolutePosition() { - if( environment.isSeatedExperience() == false ) return Vector3f.ZERO; - if( hmdSeatToStand == null ) { - hmdSeatToStand = new Vector3f(); - - HmdMatrix34 mat = HmdMatrix34.create(); - VRSystem.VRSystem_GetSeatedZeroPoseToStandingAbsoluteTrackingPose(mat); - Matrix4f tempmat = new Matrix4f(); - convertSteamVRMatrix3ToMatrix4f(mat, tempmat); - tempmat.toTranslationVector(hmdSeatToStand); - } - return hmdSeatToStand; - } - - @Override - public Matrix4f getHMDMatrixPoseLeftEye(){ - if( hmdPoseLeftEye != null ) { - return hmdPoseLeftEye; - } else { - HmdMatrix34 mat = HmdMatrix34.create(); - VRSystem.VRSystem_GetEyeToHeadTransform(VR.EVREye_Eye_Left, mat); - hmdPoseLeftEye = new Matrix4f(); - return convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseLeftEye); - } - } - - - @Override - public Matrix4f getHMDMatrixPoseRightEye(){ - if( hmdPoseRightEye != null ) { - return hmdPoseRightEye; - } else { - HmdMatrix34 mat = HmdMatrix34.create(); - VRSystem.VRSystem_GetEyeToHeadTransform(VR.EVREye_Eye_Right, mat); - hmdPoseRightEye = new Matrix4f(); - return convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseRightEye); - } - } - - @Override - public HmdType getType() { - String completeName = ""; - String name = VRSystem.VRSystem_GetStringTrackedDeviceProperty(VR.k_unTrackedDeviceIndex_Hmd, - VR.ETrackedDeviceProperty_Prop_ManufacturerName_String, - 128, hmdErrorStore); - if( hmdErrorStore.get(0) == 0 ) completeName += name; - String number = VRSystem.VRSystem_GetStringTrackedDeviceProperty(VR.k_unTrackedDeviceIndex_Hmd, - VR.ETrackedDeviceProperty_Prop_ModelNumber_String, - 128, hmdErrorStore); - if( hmdErrorStore.get(0) == 0 ) completeName += " " + number; - if( completeName.length() > 0 ) { - completeName = completeName.toLowerCase(Locale.ENGLISH).trim(); - if( completeName.contains("htc") || completeName.contains("vive") ) { - return HmdType.HTC_VIVE; - } else if ( completeName.contains("index") ) { - return HmdType.VALVE_INDEX; - } else if( completeName.contains("osvr") ) { - return HmdType.OSVR; - } else if( completeName.contains("oculus") || completeName.contains("rift") || - completeName.contains("dk1") || completeName.contains("dk2") || completeName.contains("cv1") ) { - return HmdType.OCULUS_RIFT; - } else if( completeName.contains("fove") ) { - return HmdType.FOVE; - } else if( completeName.contains("game") && completeName.contains("face") ) { - return HmdType.GAMEFACE; - } else if( completeName.contains("morpheus") ) { - return HmdType.MORPHEUS; - } else if( completeName.contains("gear") ) { - return HmdType.GEARVR; - } else if( completeName.contains("star") ) { - return HmdType.STARVR; - } else if( completeName.contains("null") ) { - return HmdType.NULL; - } - } - return HmdType.OTHER; - } - - public void setTrackingSpace(boolean isSeated){ - if( isSeated) { - VRCompositor.VRCompositor_SetTrackingSpace(VR.ETrackingUniverseOrigin_TrackingUniverseSeated); - } else { - VRCompositor.VRCompositor_SetTrackingSpace(VR.ETrackingUniverseOrigin_TrackingUniverseStanding); - } - } - - - public Matrix4f[] getPoseMatrices() { - return poseMatrices; - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRBounds.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRBounds.java deleted file mode 100644 index 2d10f88888..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRBounds.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jme3.input.vr.lwjgl_openvr; - -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.VRBounds; -import com.jme3.math.Vector2f; -import com.jme3.util.BufferUtils; -import java.nio.FloatBuffer; - -import java.util.logging.Logger; - -/** - * A class that represents VR world bounds. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @author Rickard Edén - */ -public class LWJGLOpenVRBounds implements VRBounds { - - private static Logger logger = Logger.getLogger(LWJGLOpenVRBounds.class.getName()); - - private Vector2f playSize; - private boolean setup = false; - - /** - * Initialize the VR bounds. - * @return true if the initialization is a success and false otherwise. - */ - public boolean init(VRAPI api) { - - logger.config("Initialize VR bounds..."); - - if( !setup ) { -// vrChaperone = new VR_IVRChaperone_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRChaperone_Version, api.hmdErrorStore).getPointer()); - FloatBuffer fbX = BufferUtils.createFloatBuffer(1); - FloatBuffer fbZ = BufferUtils.createFloatBuffer(1); - org.lwjgl.openvr.VRChaperone.VRChaperone_GetPlayAreaSize(fbX, fbZ); - - playSize = new Vector2f(fbX.get(0), fbZ.get(0)); - setup = true; - logger.config("Initialize VR bounds [SUCCESS]"); - return true; // init success - } - - logger.config("Initialize VR bounds already done."); - return true; // already initialized - } - - @Override - public Vector2f getPlaySize() { - return playSize; - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRInput.java deleted file mode 100644 index d0f49594d5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRInput.java +++ /dev/null @@ -1,498 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.lwjgl_openvr; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Logger; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRInputType; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.input.vr.VRViewManager; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.scene.Spatial; -import com.jme3.util.VRUtil; -import java.nio.IntBuffer; -import org.lwjgl.BufferUtils; -import org.lwjgl.openvr.HmdVector3; -import org.lwjgl.openvr.VR; -import org.lwjgl.openvr.VRControllerState; -import org.lwjgl.openvr.VRSystem; - -/* -make helper functions to pull the following easily from raw data (DONE) -trigger: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 1.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 8589934592 (when full), touch: 8589934592 -touchpad (upper left): -Controller#1, Axis#0 X: -0.6059755, Y: 0.2301706 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 4294967296 (when pressed in), touch: 4294967296 -grip: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 4, touch: 4 -thumb: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 2, touch: 2 - */ -/** - * A class that wraps an - * OpenVR - * input.
                  - * null values will be returned if no valid pose exists, or that - * input device isn't available user code should check for null - * values. - * - * @author reden - phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @author Rickard Edén - */ -public class LWJGLOpenVRInput implements VRInputAPI { - - private static final Logger logger = Logger.getLogger(LWJGLOpenVRInput.class.getName()); - - private final VRControllerState[] cStates = new VRControllerState[VR.k_unMaxTrackedDeviceCount]; - - private final Quaternion[] rotStore = new Quaternion[VR.k_unMaxTrackedDeviceCount]; - - private final Vector3f[] posStore = new Vector3f[VR.k_unMaxTrackedDeviceCount]; - - private static final int[] controllerIndex = new int[VR.k_unMaxTrackedDeviceCount]; - - private int controllerCount = 0; - - private final Vector2f tempAxis = new Vector2f(), temp2Axis = new Vector2f(); - - private final Vector2f lastCallAxis[] = new Vector2f[VR.k_unMaxTrackedDeviceCount]; - - private final boolean buttonDown[][] = new boolean[VR.k_unMaxTrackedDeviceCount][16]; - - private float axisMultiplier = 1f; - - private final Vector3f tempVel = new Vector3f(); - - private final Quaternion tempq = new Quaternion(); - - private final VREnvironment environment; - - private List trackedControllers = null; - - /** - * Create a new - * OpenVR - * input attached to the given VR environment. - * - * @param environment the VR environment to which the input is attached. - */ - public LWJGLOpenVRInput(VREnvironment environment) { - this.environment = environment; - } - - @Override - public float getAxisMultiplier() { - return axisMultiplier; - } - - @Override - public void setAxisMultiplier(float set) { - axisMultiplier = set; - } - - @Override - public void swapHands() { - if (controllerCount != 2) { - return; - } - int temp = controllerIndex[0]; - controllerIndex[0] = controllerIndex[1]; - controllerIndex[1] = temp; - } - - @Override - public boolean isButtonDown(int controllerIndex, VRInputType checkButton) { - VRControllerState cs = cStates[LWJGLOpenVRInput.controllerIndex[controllerIndex]]; - switch (checkButton) { - default: - return false; - case ViveGripButton: - return (cs.ulButtonPressed() & 4) != 0; - case ViveMenuButton: - return (cs.ulButtonPressed() & 2) != 0; - case ViveTrackpadAxis: - return (cs.ulButtonPressed() & 4294967296l) != 0; - case ViveTriggerAxis: - return (cs.ulButtonPressed() & 8589934592l) != 0; - } - } - - @Override - public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton) { - boolean buttonDownNow = isButtonDown(controllerIndex, checkButton); - int checkButtonValue = checkButton.getValue(); - int cIndex = LWJGLOpenVRInput.controllerIndex[controllerIndex]; - boolean retval = buttonDownNow == true && buttonDown[cIndex][checkButtonValue] == false; - buttonDown[cIndex][checkButtonValue] = buttonDownNow; - return retval; - } - - @Override - public void resetInputSinceLastCall() { - for (int i = 0; i < lastCallAxis.length; i++) { - lastCallAxis[i].x = 0f; - lastCallAxis[i].y = 0f; - } - for (int i = 0; i < VR.k_unMaxTrackedDeviceCount; i++) { - for (int j = 0; j < 16; j++) { - buttonDown[i][j] = false; - } - } - } - - @Override - public Vector2f getAxisDeltaSinceLastCall(int controllerIndex, VRInputType forAxis) { - int axisIndex = forAxis.getValue(); - temp2Axis.set(lastCallAxis[axisIndex]); - lastCallAxis[axisIndex].set(getAxis(controllerIndex, forAxis)); - if ((temp2Axis.x != 0f || temp2Axis.y != 0f) && (lastCallAxis[axisIndex].x != 0f || lastCallAxis[axisIndex].y != 0f)) { - temp2Axis.subtractLocal(lastCallAxis[axisIndex]); - } else { - // move made from rest, don't count as a delta move - temp2Axis.x = 0f; - temp2Axis.y = 0f; - } - return temp2Axis; - } - - @Override - public Vector3f getVelocity(int controllerIndex) { - - if (environment != null) { - - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - int index = LWJGLOpenVRInput.controllerIndex[controllerIndex]; -// if( needsNewVelocity[index] ) { - HmdVector3 tempVec = ((LWJGLOpenVR) environment.getVRHardware()).hmdTrackedDevicePoses[index].vVelocity(); -// needsNewVelocity[index] = false; -// } - tempVel.x = tempVec.v(0); - tempVel.y = tempVec.v(1); - tempVel.z = tempVec.v(2); - return tempVel; - } else { - throw new IllegalStateException("VR hardware " + environment.getVRHardware().getClass().getSimpleName() + " is not a subclass of " + LWJGLOpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Vector3f getAngularVelocity(int controllerIndex) { - - if (environment != null) { - - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - - int index = LWJGLOpenVRInput.controllerIndex[controllerIndex]; - HmdVector3 tempVec = ((LWJGLOpenVR) environment.getVRHardware()).hmdTrackedDevicePoses[index].vAngularVelocity(); -// needsNewVelocity[index] = false; -// } - tempVel.x = tempVec.v(0); - tempVel.y = tempVec.v(1); - tempVel.z = tempVec.v(2); - return tempVel; - } else { - throw new IllegalStateException("VR hardware " + environment.getVRHardware().getClass().getSimpleName() + " is not a subclass of " + LWJGLOpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - - } - - @Override - public Vector2f getAxisRaw(int controllerIndex, VRInputType forAxis) { - VRControllerState cs = cStates[LWJGLOpenVRInput.controllerIndex[controllerIndex]]; - switch (forAxis) { - default: - return null; - case ViveTriggerAxis: - tempAxis.x = cs.rAxis(1).x(); - tempAxis.y = tempAxis.x; - break; - case ViveTrackpadAxis: - tempAxis.x = cs.rAxis(0).x(); - tempAxis.y = cs.rAxis(0).y(); - break; - } - return tempAxis; - } - - @Override - public Vector2f getAxis(int controllerIndex, VRInputType forAxis) { - getAxisRaw(controllerIndex, forAxis); - tempAxis.x *= axisMultiplier; - tempAxis.y *= axisMultiplier; - return tempAxis; - } - - @Override - public boolean init() { - - logger.config("Initialize OpenVR input."); - - for (int i = 0; i < VR.k_unMaxTrackedDeviceCount; i++) { - rotStore[i] = new Quaternion(); - posStore[i] = new Vector3f(); - cStates[i] = VRControllerState.create(); - lastCallAxis[i] = new Vector2f(); - logger.config(" Input " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " binded."); - } - - return true; - } - - @Override - public VRTrackedController getTrackedController(int index) { - if (trackedControllers != null) { - if ((trackedControllers.size() > 0) && (index < trackedControllers.size())) { - return trackedControllers.get(index); - } - } - - return null; - } - - @Override - public int getTrackedControllerCount() { - return controllerCount; - } - - @Override - public VRControllerState getRawControllerState(int index) { - if (isInputDeviceTracking(index) == false) { - return null; - } - return cStates[controllerIndex[index]]; - } - - @Override - public boolean isInputFocused() { - if (environment != null){ - // not a 100% match, but the closest i can find in lwjgl. Doc seems to confirm this too. - return VRSystem.VRSystem_IsInputAvailable(); - //return ((VR_IVRSystem_FnTable)environment.getVRHardware().getVRSystem()).IsInputFocusCapturedByAnotherProcess.apply() == 0; - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public boolean isInputDeviceTracking(int index) { - if (index < 0 || index >= controllerCount) { - return false; - } - - if (environment != null) { - - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - return ((LWJGLOpenVR) environment.getVRHardware()).hmdTrackedDevicePoses[controllerIndex[index]].bPoseIsValid(); - } else { - throw new IllegalStateException("VR hardware " + environment.getVRHardware().getClass().getSimpleName() + " is not a subclass of " + LWJGLOpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Quaternion getOrientation(int index) { - if (isInputDeviceTracking(index) == false) { - return null; - } - - if (environment != null) { - - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - index = controllerIndex[index]; - VRUtil.convertMatrix4toQuat(((LWJGLOpenVR) environment.getVRHardware()).poseMatrices[index], rotStore[index]); - return rotStore[index]; - } else { - throw new IllegalStateException("VR hardware " + environment.getVRHardware().getClass().getSimpleName() + " is not a subclass of " + LWJGLOpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Vector3f getPosition(int index) { - if (isInputDeviceTracking(index) == false) { - return null; - } - - if (environment != null) { - - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - // the hmdPose comes in rotated funny, fix that here - index = controllerIndex[index]; - ((LWJGLOpenVR) environment.getVRHardware()).poseMatrices[index].toTranslationVector(posStore[index]); - posStore[index].x = -posStore[index].x; - posStore[index].z = -posStore[index].z; - return posStore[index]; - } else { - throw new IllegalStateException("VR hardware " + environment.getVRHardware().getClass().getSimpleName() + " is not a subclass of " + LWJGLOpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - - } - - @Override - public Quaternion getFinalObserverRotation(int index) { - - if (environment != null) { - VRViewManager vrvm = environment.getVRViewManager(); - - if (vrvm != null) { - if (isInputDeviceTracking(index) == false) { - return null; - } - - Object obs = environment.getObserver(); - if (obs instanceof Camera) { - tempq.set(((Camera) obs).getRotation()); - } else { - tempq.set(((Spatial) obs).getWorldRotation()); - } - - return tempq.multLocal(getOrientation(index)); - } else { - throw new IllegalStateException("VR environment has no valid view manager."); - } - - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Vector3f getFinalObserverPosition(int index) { - - if (environment != null) { - VRViewManager vrvm = (VRViewManager) environment.getVRViewManager(); - - if (vrvm != null) { - if (isInputDeviceTracking(index) == false) { - return null; - } - Object obs = environment.getObserver(); - Vector3f pos = getPosition(index); - if (obs instanceof Camera) { - ((Camera) obs).getRotation().mult(pos, pos); - return pos.addLocal(((Camera) obs).getLocation()); - } else { - ((Spatial) obs).getWorldRotation().mult(pos, pos); - return pos.addLocal(((Spatial) obs).getWorldTranslation()); - } - } else { - throw new IllegalStateException("VR environment has no valid view manager."); - } - - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public void triggerHapticPulse(int controllerIndex, float seconds) { - if (environment.isInVR() == false || isInputDeviceTracking(controllerIndex) == false) { - return; - } - - // apparently only axis ID of 0 works - VRSystem.VRSystem_TriggerHapticPulse(LWJGLOpenVRInput.controllerIndex[controllerIndex], - 0, (short) Math.round(3f * seconds / 1e-3f)); - } - - @Override - public void updateConnectedControllers() { - logger.config("Updating connected controllers."); - - if (environment != null) { - controllerCount = 0; - for (int i = 0; i < VR.k_unMaxTrackedDeviceCount; i++) { - int classCallback = VRSystem.VRSystem_GetTrackedDeviceClass(i); - if (classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_Controller || classCallback == VR.ETrackedDeviceClass_TrackedDeviceClass_GenericTracker) { - IntBuffer error = BufferUtils.createIntBuffer(1); - String controllerName = "Unknown"; - String manufacturerName = "Unknown"; - controllerName = VRSystem.VRSystem_GetStringTrackedDeviceProperty(i, VR.ETrackedDeviceProperty_Prop_TrackingSystemName_String, error); - manufacturerName = VRSystem.VRSystem_GetStringTrackedDeviceProperty(i, VR.ETrackedDeviceProperty_Prop_ManufacturerName_String, error); - - if (error.get(0) != 0) { - logger.warning("Error getting controller information " + controllerName + " " + manufacturerName + "Code (" + error.get(0) + ")"); - } - controllerIndex[controllerCount] = i; - - // Adding tracked controller to control. - if (trackedControllers == null) { - trackedControllers = new ArrayList(VR.k_unMaxTrackedDeviceCount); - } - trackedControllers.add(new LWJGLOpenVRTrackedController(i, this, controllerName, manufacturerName, environment)); - - // Send a Haptic pulse to the controller - triggerHapticPulse(controllerCount, 1.0f); - - controllerCount++; - logger.config(" Tracked controller " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " " + controllerName + " (" + manufacturerName + ") attached."); - } else { - logger.config(" Controller " + (i + 1) + "/" + VR.k_unMaxTrackedDeviceCount + " ignored."); - } - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public void updateControllerStates() { - - if (environment != null) { - for (int i = 0; i < controllerCount; i++) { - int index = controllerIndex[i]; - VRSystem.VRSystem_GetControllerState(index, cStates[index], 64); - cStates[index].ulButtonPressed(); - cStates[index].rAxis(); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRMouseManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRMouseManager.java deleted file mode 100644 index 6ae3b85ce4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRMouseManager.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.lwjgl_openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.controls.AnalogListener; -import com.jme3.input.vr.AbstractVRMouseManager; -import com.jme3.input.vr.VRInputType; -import com.jme3.math.Vector2f; - - -/** - * A class dedicated to the handling of the mouse within VR environment. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class LWJGLOpenVRMouseManager extends AbstractVRMouseManager { - - private final int AVERAGE_AMNT = 4; - - private int avgCounter; - - private final float[] lastXmv = new float[AVERAGE_AMNT]; - - private final float[] lastYmv = new float[AVERAGE_AMNT]; - - /** - * Create a new VR mouse manager within the given {@link VREnvironment VR environment}. - * @param environment the VR environment of the mouse manager. - */ - public LWJGLOpenVRMouseManager(VREnvironment environment){ - super(environment); - } - - - @Override - public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) { - - if (getVREnvironment() != null){ - if (getVREnvironment().getApplication() != null){ - // got a tracked controller to use as the "mouse" - if( getVREnvironment().isInVR() == false || - getVREnvironment().getVRinput() == null || - getVREnvironment().getVRinput().isInputDeviceTracking(inputIndex) == false ){ - return; - } - - Vector2f tpDelta; - // TODO option to use Touch joysticks - if( isThumbstickMode() ) { - tpDelta = getVREnvironment().getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis); - } else { - tpDelta = getVREnvironment().getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis); - } - - float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * getSpeedSensitivity(), getSpeedAcceleration()); - float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * getSpeedSensitivity(), getSpeedAcceleration()); - - if( tpDelta.x < 0f ){ - Xamount = -Xamount; - } - - if( tpDelta.y < 0f ){ - Yamount = -Yamount; - } - - Xamount *= getMouseMoveScale(); - Yamount *= getMouseMoveScale(); - - if( mouseListener != null ) { - if( tpDelta.x != 0f && mouseXName != null ) mouseListener.onAnalog(mouseXName, Xamount * 0.2f, tpf); - if( tpDelta.y != 0f && mouseYName != null ) mouseListener.onAnalog(mouseYName, Yamount * 0.2f, tpf); - } - - if( getVREnvironment().getApplication().getInputManager().isCursorVisible() ) { - int index = (avgCounter+1) % AVERAGE_AMNT; - lastXmv[index] = Xamount * 133f; - lastYmv[index] = Yamount * 133f; - cursorPos.x -= avg(lastXmv); - cursorPos.y -= avg(lastYmv); - Vector2f maxsize = getVREnvironment().getVRGUIManager().getCanvasSize(); - - if( cursorPos.x > maxsize.x ){ - cursorPos.x = maxsize.x; - } - - if( cursorPos.x < 0f ){ - cursorPos.x = 0f; - } - - if( cursorPos.y > maxsize.y ){ - cursorPos.y = maxsize.y; - } - - if( cursorPos.y < 0f ){ - cursorPos.y = 0f; - } - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private float avg(float[] arr) { - float amt = 0f; - for(float f : arr) amt += f; - return amt / arr.length; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRTrackedController.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRTrackedController.java deleted file mode 100644 index c87cea2532..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRTrackedController.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.jme3.input.vr.lwjgl_openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; - -/** - * A controller that is tracked within the VR environment. Such a controller can provide its position within the VR space. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @author Rickard Edén - */ -public class LWJGLOpenVRTrackedController implements VRTrackedController{ - - /** - * The index of the controller within the unserlying VR API. - */ - private int controllerIndex = -1; - - /** - * The underlying VRAPI. - */ - private VRInputAPI hardware = null; - - /** - * The name of the controller. - */ - private String name; - - private VREnvironment environment; - - /** - * Wrap a new VR tracked controller on an OpenVR system. - * @param controllerIndex the index of the controller within the underlying VR system. - * @param hardware the underlying VR system. - * @param name the name of the controller. - * @param manufacturer the manufacturer of the controller. - * @param environment the VR environment. - */ - public LWJGLOpenVRTrackedController(int controllerIndex, VRInputAPI hardware, String name, String manufacturer, VREnvironment environment){ - this.controllerIndex = controllerIndex; - this.hardware = hardware; - - this.name = name; - this.manufacturer = manufacturer; - - this.environment = environment; - } - - /** - * The manufacturer of the controller. - */ - private String manufacturer; - - @Override - public Vector3f getPosition() { - if (hardware != null){ - return hardware.getPosition(controllerIndex); - } else { - throw new IllegalStateException("No underlying VR API."); - } - } - - @Override - public Quaternion getOrientation() { - if (hardware != null){ - return hardware.getOrientation(controllerIndex); - } else { - throw new IllegalStateException("No underlying VR API."); - } - } - - @Override - public Matrix4f getPose(){ - - if (environment != null){ - if (hardware != null){ - return ((LWJGLOpenVR)environment.getVRHardware()).getPoseMatrices()[controllerIndex]; - } else { - throw new IllegalStateException("No underlying VR API."); - } - } else { - throw new IllegalStateException("VR tracked device is not attached to any environment."); - } - - - } - - @Override - public String getControllerName() { - return name; - } - - @Override - public String getControllerManufacturer() { - return manufacturer; - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRViewManager.java deleted file mode 100644 index 78be2565fd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/lwjgl_openvr/LWJGLOpenVRViewManager.java +++ /dev/null @@ -1,573 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.lwjgl_openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.AbstractVRViewManager; -import com.jme3.input.vr.VRAPI; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Spatial; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; -import com.jme3.util.VRGUIPositioningMode; - -import java.util.Iterator; -import java.util.logging.Logger; -import org.lwjgl.openvr.VRTextureBounds; -import org.lwjgl.openvr.Texture; -import org.lwjgl.openvr.VR; -import org.lwjgl.openvr.VRCompositor; - -/** - * A VR view manager based on OpenVR. This class enable to submit 3D views to - * the VR compositor. - * - * @author reden - phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @author Rickard Edén - */ -public class LWJGLOpenVRViewManager extends AbstractVRViewManager { - - private static final Logger logger = Logger.getLogger(LWJGLOpenVRViewManager.class.getName()); - - // OpenVR values - private VRTextureBounds leftTextureBounds; - private Texture leftTextureType; - - private VRTextureBounds rightTextureBounds; - private Texture rightTextureType; - - private Texture2D dualEyeTex; - - //final & temp values for camera calculations - private final Vector3f finalPosition = new Vector3f(); - private final Quaternion finalRotation = new Quaternion(); - private final Vector3f hmdPos = new Vector3f(); - private final Quaternion hmdRot = new Quaternion(); - - /** - * Create a new VR view manager attached to the given - * {@link VREnvironment VR environment}. - * - * @param environment the {@link VREnvironment VR environment} to which this - * view manager is attached. - */ - public LWJGLOpenVRViewManager(VREnvironment environment) { - this.environment = environment; - } - - /** - * Get the identifier of the left eye texture. - * - * @return the identifier of the left eye texture. - * @see #getRightTexId() - * @see #getFullTexId() - */ - protected int getLeftTexId() { - return (int) getLeftTexture().getImage().getId(); - } - - /** - * Get the identifier of the right eye texture. - * - * @return the identifier of the right eye texture. - * @see #getLeftTexId() - * @see #getFullTexId() - */ - protected int getRightTexId() { - return (int) getRightTexture().getImage().getId(); - } - - /** - * Get the identifier of the full (dual eye) texture. - * - * @return the identifier of the full (dual eye) texture. - * @see #getLeftTexId() - * @see #getRightTexId() - */ - private int getFullTexId() { - return (int) dualEyeTex.getImage().getId(); - } - - /** - * Initialize the system binds of the textures. - */ - private void initTextureSubmitStructs() { - leftTextureType = Texture.create(); - rightTextureType = Texture.create(); - - if (environment != null) { - if (environment.getVRHardware() instanceof LWJGLOpenVR) { - leftTextureBounds = VRTextureBounds.create(); - rightTextureBounds = VRTextureBounds.create(); - // left eye - leftTextureBounds.set(0f, 0f, 0.5f, 1f); - // right eye - rightTextureBounds.set(0.5f, 0f, 1f, 1f); - // texture type - leftTextureType.set(-1, VR.ETextureType_TextureType_OpenGL, VR.EColorSpace_ColorSpace_Gamma); - rightTextureType.set(-1, VR.ETextureType_TextureType_OpenGL, VR.EColorSpace_ColorSpace_Gamma); - - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - @Override - /** - * updatePose can be called here because appstates are always called before the main renderer. This way we get the latest pose close to when it's supposed to render - */ - public void render() { - if (environment != null) { - // grab the observer - Object obs = environment.getObserver(); - Quaternion objRot; - Vector3f objPos; - if (obs instanceof Camera) { - objRot = ((Camera) obs).getRotation(); - objPos = ((Camera) obs).getLocation(); - } else { - objRot = ((Spatial) obs).getWorldRotation(); - objPos = ((Spatial) obs).getWorldTranslation(); - } - // grab the hardware handle - VRAPI dev = environment.getVRHardware(); - if (dev != null) { - - // update the HMD's position & orientation - dev.updatePose(); - dev.getPositionAndOrientation(hmdPos, hmdRot); - - if (obs != null) { - // update hmdPos based on obs rotation - finalRotation.set(objRot); - finalRotation.mult(hmdPos, hmdPos); - finalRotation.multLocal(hmdRot); - } - - finalizeCamera(dev.getHMDVectorPoseLeftEye(), objPos, getLeftCamera()); - finalizeCamera(dev.getHMDVectorPoseRightEye(), objPos, getRightCamera()); - } else { - getLeftCamera().setFrame(objPos, objRot); - getRightCamera().setFrame(objPos, objRot); - } - } - } - - @Override - public void postRender() { - - if (environment != null) { - if (environment.isInVR()) { - VRAPI api = environment.getVRHardware(); - // using the compositor... - int errl = 0, errr = 0; - if (environment.isInstanceRendering()) { - if (leftTextureType.handle() == -1 || leftTextureType.handle() != getFullTexId()) { - leftTextureType.set(getFullTexId(), leftTextureType.eType(), leftTextureType.eColorSpace()); - } else { - if (api instanceof LWJGLOpenVR) { - int submitFlag = VR.EVRSubmitFlags_Submit_Default; - errr = VRCompositor.VRCompositor_Submit(VR.EVREye_Eye_Right, rightTextureType, rightTextureBounds, submitFlag); - errl = VRCompositor.VRCompositor_Submit(VR.EVREye_Eye_Left, leftTextureType, leftTextureBounds, submitFlag); - } - } - } else if (leftTextureType.handle() == -1 || rightTextureType.handle() == -1 - || leftTextureType.handle() != getLeftTexId() || rightTextureType.handle() != getRightTexId()) { - leftTextureType.set(getLeftTexId(), leftTextureType.eType(), leftTextureType.eColorSpace()); - rightTextureType.set(getRightTexId(), leftTextureType.eType(), leftTextureType.eColorSpace()); - } else { - if (api instanceof LWJGLOpenVR) { - int submitFlag = VR.EVRSubmitFlags_Submit_Default; - errr = VRCompositor.VRCompositor_Submit(VR.EVREye_Eye_Right, rightTextureType, null, submitFlag); - errl = VRCompositor.VRCompositor_Submit(VR.EVREye_Eye_Left, leftTextureType, null, submitFlag); - } else { - - } - } - - if (errl != 0) { - logger.severe("Submit to left compositor error: " + " (" + Integer.toString(errl) + ")"); - logger.severe(" Texture handle: " + leftTextureType.handle()); - - logger.severe(" Left eye texture " + leftEyeTexture.getName() + " (" + leftEyeTexture.getImage().getId() + ")"); - logger.severe(" Type: " + leftEyeTexture.getType()); - logger.severe(" Size: " + leftEyeTexture.getImage().getWidth() + "x" + leftEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: " + leftEyeTexture.getImage().getDepth()); - logger.severe(" Image format: " + leftEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: " + leftEyeTexture.getImage().getColorSpace()); - - } - - if (errr != 0) { - logger.severe("Submit to right compositor error: " + " (" + Integer.toString(errl) + ")"); -// logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); -// logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.severe(" Texture handle: " + rightTextureType.handle()); - - logger.severe(" Right eye texture " + rightEyeTexture.getName() + " (" + rightEyeTexture.getImage().getId() + ")"); - logger.severe(" Type: " + rightEyeTexture.getType()); - logger.severe(" Size: " + rightEyeTexture.getImage().getWidth() + "x" + rightEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: " + rightEyeTexture.getImage().getDepth()); - logger.severe(" Image format: " + rightEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: " + rightEyeTexture.getImage().getColorSpace()); - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - VRCompositor.VRCompositor_PostPresentHandoff(); - - } - - @Override - public void initialize() { - - logger.config("Initializing VR view manager."); - - if (environment != null) { - - initTextureSubmitStructs(); - setupCamerasAndViews(); - setupVRScene(); - moveScreenProcessingToEyes(); - - if (environment.hasTraditionalGUIOverlay()) { - - environment.getVRMouseManager().initialize(); - - // update the pose to position the gui correctly on start - update(0f); - environment.getVRGUIManager().positionGui(); - } - - logger.config("Initialized VR view manager [SUCCESS]"); - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Prepare the size of the given {@link Camera camera} to adapt it to the - * underlying rendering context. - * - * @param cam the {@link Camera camera} to prepare. - * @param xMult the camera width multiplier. - */ - private void prepareCameraSize(Camera cam, float xMult) { - - if (environment != null) { - - if (environment.getApplication() != null) { - Vector2f size = new Vector2f(); - VRAPI vrhmd = environment.getVRHardware(); - - if (vrhmd == null) { - size.x = 1280f; - size.y = 720f; - } else { - vrhmd.getRenderSize(size); - } - - if (size.x < environment.getApplication().getContext().getSettings().getWidth()) { - size.x = environment.getApplication().getContext().getSettings().getWidth(); - } - if (size.y < environment.getApplication().getContext().getSettings().getHeight()) { - size.y = environment.getApplication().getContext().getSettings().getHeight(); - } - - if (environment.isInstanceRendering()) { - size.x *= 2f; - } - - // other adjustments - size.x *= xMult; - size.x *= getResolutionMuliplier(); - size.y *= getResolutionMuliplier(); - - if (cam.getWidth() != size.x || cam.getHeight() != size.y) { - cam.resize((int) size.x, (int) size.y, false); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - } - - /** - * Replaces rootNode as the main cameras scene with the distortion mesh - */ - private void setupVRScene() { - - if (environment != null) { - if (environment.getApplication() != null) { - // no special scene to setup if we are doing instancing - if (environment.isInstanceRendering()) { - // distortion has to be done with compositor here... we want only one pass on our end! - if (environment.getApplication().getContext().getSettings().isSwapBuffers()) { - setupMirrorBuffers(environment.getCamera(), dualEyeTex, true); - } - return; - } - - leftEyeTexture = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - rightEyeTexture = (Texture2D) getRightViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - leftEyeDepth = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - rightEyeDepth = (Texture2D) getRightViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - - // main viewport is either going to be a distortion scene or nothing - // mirroring is handled by copying framebuffers - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while (spatialIter.hasNext()) { - environment.getApplication().getViewPort().detachScene(spatialIter.next()); - } - - spatialIter = environment.getApplication().getGuiViewPort().getScenes().iterator(); - while (spatialIter.hasNext()) { - environment.getApplication().getGuiViewPort().detachScene(spatialIter.next()); - } - - // only setup distortion scene if compositor isn't running (or using custom mesh distortion option) - if (environment.getApplication().getContext().getSettings().isSwapBuffers()) { - setupMirrorBuffers(environment.getCamera(), leftEyeTexture, false); - - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - @Override - public void update(float tpf) { - - if (environment != null) { - - if (environment.hasTraditionalGUIOverlay()) { - // update the mouse? - environment.getVRMouseManager().update(tpf); - - // update GUI position? - if (environment.getVRGUIManager().isWantsReposition() || environment.getVRGUIManager().getPositioningMode() != VRGUIPositioningMode.MANUAL) { - environment.getVRGUIManager().positionGuiNow(tpf); - environment.getVRGUIManager().updateGuiQuadGeometricState(); - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Place the camera within the scene. - * - * @param eyePos the eye position. - * @param obsPosition the observer position. - * @param cam the camera to place. - */ - private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { - finalRotation.mult(eyePos, finalPosition); - finalPosition.addLocal(hmdPos); - if (obsPosition != null) { - finalPosition.addLocal(obsPosition); - } - finalPosition.y += getHeightAdjustment(); - cam.setFrame(finalPosition, finalRotation); - } - - private void setupCamerasAndViews() { - - if (environment != null) { - // get desired frustum from original camera - Camera origCam = environment.getCamera(); - float fFar = origCam.getFrustumFar(); - float fNear = origCam.getFrustumNear(); - - // restore frustum on distortion scene cam, if needed - if (environment.isInstanceRendering()) { - leftCamera = origCam; - } else if (environment.compositorAllowed() == false) { - origCam.setFrustumFar(100f); - origCam.setFrustumNear(1f); - leftCamera = origCam.clone(); - prepareCameraSize(origCam, 2f); - } else { - leftCamera = origCam.clone(); - } - - getLeftCamera().setFrustumPerspective(environment.getDefaultFOV(), environment.getDefaultAspect(), fNear, fFar); - - prepareCameraSize(getLeftCamera(), 1f); - if (environment.getVRHardware() != null) { - getLeftCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionLeftEye(getLeftCamera())); - } - //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); - - if (!environment.isInstanceRendering()) { - leftViewPort = setupViewBuffers(getLeftCamera(), LEFT_VIEW_NAME); - rightCamera = getLeftCamera().clone(); - if (environment.getVRHardware() != null) { - getRightCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(getRightCamera())); - } - rightViewPort = setupViewBuffers(getRightCamera(), RIGHT_VIEW_NAME); - } else { - - if (environment.getApplication() != null) { - - logger.severe("THIS CODE NEED CHANGES !!!"); - leftViewPort = environment.getApplication().getViewPort(); - //leftViewport.attachScene(app.getRootNode()); - rightCamera = getLeftCamera().clone(); - if (environment.getVRHardware() != null) { - getRightCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(getRightCamera())); - } - - org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_CLIP_DISTANCE0); - - //FIXME: [jme-vr] Fix with JMonkey next release - //RenderManager._VRInstancing_RightCamProjection = camRight.getViewProjectionMatrix(); - setupFinalFullTexture(environment.getApplication().getViewPort().getCamera()); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - } - - // setup gui - environment.getVRGUIManager().setupGui(getLeftCamera(), getRightCamera(), getLeftViewPort(), getRightViewPort()); - - if (environment.getVRHardware() != null) { - // call these to cache the results internally - environment.getVRHardware().getHMDMatrixPoseLeftEye(); - environment.getVRHardware().getHMDMatrixPoseRightEye(); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupMirrorBuffers(Camera cam, Texture2D tex, boolean expand) { - - if (environment != null) { - if (environment.getApplication() != null) { - Camera clonecam = cam.clone(); - ViewPort viewPort = environment.getApplication().getRenderManager().createPostView("MirrorView", clonecam); - clonecam.setParallelProjection(true); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - Picture pic = new Picture("fullscene"); - pic.setLocalTranslation(-0.75f, -0.5f, 0f); - if (expand) { - pic.setLocalScale(3f, 1f, 1f); - } else { - pic.setLocalScale(1.5f, 1f, 1f); - } - pic.setQueueBucket(Bucket.Opaque); - pic.setTexture(environment.getApplication().getAssetManager(), (Texture2D) tex, false); - viewPort.attachScene(pic); - viewPort.setOutputFrameBuffer(null); - - pic.updateGeometricState(); - - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private void setupFinalFullTexture(Camera cam) { - - if (environment != null) { - if (environment.getApplication() != null) { - // create offscreen framebuffer - FrameBuffer out = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBuffer.setSrgb(true); - - //setup framebuffer's texture - dualEyeTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - dualEyeTex.setMinFilter(Texture2D.MinFilter.BilinearNoMipMaps); - dualEyeTex.setMagFilter(Texture2D.MagFilter.Bilinear); - - logger.config("Dual eye texture " + dualEyeTex.getName() + " (" + dualEyeTex.getImage().getId() + ")"); - logger.config(" Type: " + dualEyeTex.getType()); - logger.config(" Size: " + dualEyeTex.getImage().getWidth() + "x" + dualEyeTex.getImage().getHeight()); - logger.config(" Image depth: " + dualEyeTex.getImage().getDepth()); - logger.config(" Image format: " + dualEyeTex.getImage().getFormat()); - logger.config(" Image color space: " + dualEyeTex.getImage().getColorSpace()); - - //setup framebuffer to use texture - out.setDepthBuffer(Image.Format.Depth); - out.setColorTexture(dualEyeTex); - - ViewPort viewPort = environment.getApplication().getViewPort(); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - viewPort.setOutputFrameBuffer(out); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupViewBuffers(Camera cam, String viewName) { - - if (environment != null) { - if (environment.getApplication() != null) { - // create offscreen framebuffer - FrameBuffer offBufferLeft = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBufferLeft.setSrgb(true); - - //setup framebuffer's texture - Texture2D offTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - offTex.setMinFilter(Texture2D.MinFilter.BilinearNoMipMaps); - offTex.setMagFilter(Texture2D.MagFilter.Bilinear); - - //setup framebuffer to use texture - offBufferLeft.setDepthBuffer(Image.Format.Depth); - offBufferLeft.setColorTexture(offTex); - - ViewPort viewPort = environment.getApplication().getRenderManager().createPreView(viewName, cam); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while (spatialIter.hasNext()) { - viewPort.attachScene(spatialIter.next()); - } - - //set viewport to render to offscreen framebuffer - viewPort.setOutputFrameBuffer(offBufferLeft); - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusMouseManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusMouseManager.java deleted file mode 100644 index 52fbaf7b0e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusMouseManager.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.jme3.input.vr.oculus; - -import com.jme3.app.VREnvironment; -import com.jme3.input.controls.AnalogListener; -import com.jme3.input.vr.AbstractVRMouseManager; -import com.jme3.input.vr.VRInputType; -import com.jme3.math.Vector2f; - -/** - * A class dedicated to the mouse handling within Oculus Rift based VR experience. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class OculusMouseManager extends AbstractVRMouseManager { - - private final int AVERAGE_AMNT = 4; - - private int avgCounter; - - private final float[] lastXmv = new float[AVERAGE_AMNT]; - - private final float[] lastYmv = new float[AVERAGE_AMNT]; - - /** - * Create a new VR mouse manager within the given {@link VREnvironment VR environment}. - * @param environment the VR environment of the mouse manager. - */ - public OculusMouseManager(VREnvironment environment){ - super(environment); - } - - @Override - public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) { - - if (getVREnvironment() != null){ - if (getVREnvironment().getApplication() != null){ - // got a tracked controller to use as the "mouse" - if( getVREnvironment().isInVR() == false || - getVREnvironment().getVRinput() == null || - getVREnvironment().getVRinput().isInputDeviceTracking(inputIndex) == false ){ - return; - } - - Vector2f tpDelta; - // TODO option to use Touch joysticks - if( isThumbstickMode() ) { - tpDelta = getVREnvironment().getVRinput().getAxis(inputIndex, VRInputType.OculusThumbstickAxis); - } else { - tpDelta = getVREnvironment().getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.OculusThumbstickAxis); - } - - float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * getSpeedSensitivity(), getSpeedAcceleration()); - float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * getSpeedSensitivity(), getSpeedAcceleration()); - - if( tpDelta.x < 0f ){ - Xamount = -Xamount; - } - - if( tpDelta.y < 0f ){ - Yamount = -Yamount; - } - - Xamount *= getMouseMoveScale(); - Yamount *= getMouseMoveScale(); - - if( mouseListener != null ) { - if( tpDelta.x != 0f && mouseXName != null ) mouseListener.onAnalog(mouseXName, Xamount * 0.2f, tpf); - if( tpDelta.y != 0f && mouseYName != null ) mouseListener.onAnalog(mouseYName, Yamount * 0.2f, tpf); - } - - if( getVREnvironment().getApplication().getInputManager().isCursorVisible() ) { - int index = (avgCounter+1) % AVERAGE_AMNT; - lastXmv[index] = Xamount * 133f; - lastYmv[index] = Yamount * 133f; - cursorPos.x -= avg(lastXmv); - cursorPos.y -= avg(lastYmv); - Vector2f maxsize = getVREnvironment().getVRGUIManager().getCanvasSize(); - - if( cursorPos.x > maxsize.x ){ - cursorPos.x = maxsize.x; - } - - if( cursorPos.x < 0f ){ - cursorPos.x = 0f; - } - - if( cursorPos.y > maxsize.y ){ - cursorPos.y = maxsize.y; - } - - if( cursorPos.y < 0f ){ - cursorPos.y = 0f; - } - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private float avg(float[] arr) { - float amt = 0f; - for(float f : arr) amt += f; - return amt / arr.length; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVR.java deleted file mode 100644 index a3a506d5c1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVR.java +++ /dev/null @@ -1,663 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.oculus; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.HmdType; -import com.jme3.input.vr.VRAPI; -import com.jme3.math.*; -import com.jme3.renderer.Camera; -import com.jme3.texture.*; -import org.lwjgl.*; -import org.lwjgl.ovr.*; - -import java.nio.IntBuffer; -import java.util.logging.Logger; - -import static org.lwjgl.BufferUtils.createPointerBuffer; -import static org.lwjgl.ovr.OVR.*; -import static org.lwjgl.ovr.OVRErrorCode.ovrSuccess; -import static org.lwjgl.ovr.OVRUtil.ovr_Detect; -import static org.lwjgl.system.MemoryUtil.*; - -/** - * Oculus VR (LibOVR 1.3.0) Native support. - *

                  - * A few notes about the Oculus coordinate system: - *

                    - *
                  • Matrices should be transposed
                  • - *
                  • Quaternions should be inverted
                  • - *
                  • Vectors should have their X and Z axes flipped, but apparently not Y.
                  • - *
                  - * - * @author Campbell Suter (znix@znix.xyz) - */ -public class OculusVR implements VRAPI { - - private static final Logger LOGGER = Logger.getLogger(OculusVR.class.getName()); - - private final VREnvironment environment; - private boolean initialized; - - /** - * Pointer to the HMD object - */ - private long session; - - /** - * Information about the VR session (should the app quit, is - * it visible or is the universal menu open, etc) - */ - private OVRSessionStatus sessionStatus; - - /** - * HMD information, such as product name and manufacturer. - */ - private OVRHmdDesc hmdDesc; - - /** - * The horizontal resolution of the HMD - */ - private int resolutionW; - - /** - * The vertical resolution of the HMD - */ - private int resolutionH; - - /** - * Field-of-view data for each eye (how many degrees from the - * center can the user see). - */ - private final OVRFovPort fovPorts[] = new OVRFovPort[2]; - - /** - * Data about each eye to be rendered - in particular, the - * offset from the center of the HMD to the eye. - */ - private final OVREyeRenderDesc eyeRenderDesc[] = new OVREyeRenderDesc[2]; - - /** - * Store the projections for each eye, so we don't have to malloc - * and recalculate them each frame. - */ - private final OVRMatrix4f[] projections = new OVRMatrix4f[2]; - - /** - * Store the poses for each eye, relative to the HMD. - * - * @see #getHMDMatrixPoseLeftEye() - */ - private final Matrix4f[] hmdRelativeEyePoses = new Matrix4f[2]; - - /** - * Store the positions for each eye, relative to the HMD. - * - * @see #getHMDVectorPoseLeftEye() - */ - private final Vector3f[] hmdRelativeEyePositions = new Vector3f[2]; - - /** - * The current state of the tracked components (HMD, touch) - */ - private OVRTrackingState trackingState; - - /** - * The position and orientation of the user's head. - */ - private OVRPosef headPose; - - /** - * The state of the Touch controllers. - */ - private OculusVRInput input; - - // The size of the texture drawn onto the HMD - private int textureW; - private int textureH; - - // Layers to render into - private PointerBuffer layers; - private OVRLayerEyeFov layer0; - - /** - * Chain texture set thing. - */ - private long chains[]; - - /** - * Frame buffers we can draw into. - */ - private FrameBuffer framebuffers[][]; - - public OculusVR(VREnvironment environment) { - this.environment = environment; - } - - @Override - public OculusVRInput getVRinput() { - return input; - } - - @Override - public String getName() { - return "OVR"; - } - - @Override - public int getDisplayFrequency() { - // TODO find correct frequency. I'm not sure - // if LibOVR has a way to do that, though. - return 60; - } - - @Override - public boolean initialize() { - // Check to make sure the HMD is connected - OVRDetectResult detect = OVRDetectResult.calloc(); - ovr_Detect(0, detect); - boolean connected = detect.IsOculusHMDConnected(); - LOGGER.config("OVRDetectResult.IsOculusHMDConnected = " + connected); - LOGGER.config("OVRDetectResult.IsOculusServiceRunning = " + detect.IsOculusServiceRunning()); - detect.free(); - - if (!connected) { - LOGGER.info("Oculus Rift not connected"); - return false; - } - - initialized = true; - - // Set up the HMD - OVRLogCallback callback = new OVRLogCallback() { - @Override - public void invoke(long userData, int level, long message) { - LOGGER.fine("LibOVR [" + userData + "] [" + level + "] " + memASCII(message)); - } - }; - OVRInitParams initParams = OVRInitParams.calloc(); - initParams.LogCallback(callback); - if (ovr_Initialize(initParams) != ovrSuccess) { - LOGGER.severe("LibOVR Init Failed"); - return false; // TODO fix memory leak - destroy() is not called - } - LOGGER.config("LibOVR Version " + ovr_GetVersionString()); - initParams.free(); - - // Get access to the HMD - LOGGER.info("Initialize HMD Session"); - PointerBuffer pHmd = memAllocPointer(1); - OVRGraphicsLuid luid = OVRGraphicsLuid.calloc(); - if (ovr_Create(pHmd, luid) != ovrSuccess) { - LOGGER.severe("Failed to create HMD"); - return false; // TODO fix memory leak - destroy() is not called - } - session = pHmd.get(0); - memFree(pHmd); - luid.free(); - sessionStatus = OVRSessionStatus.calloc(); - - // Get the information about the HMD - LOGGER.fine("Get HMD properties"); - hmdDesc = OVRHmdDesc.malloc(); - ovr_GetHmdDesc(session, hmdDesc); - if (hmdDesc.Type() == ovrHmd_None) { - LOGGER.warning("No HMD connected"); - return false; // TODO fix memory leak - destroy() is not called - } - - resolutionW = hmdDesc.Resolution().w(); - resolutionH = hmdDesc.Resolution().h(); - - LOGGER.config("HMD Properties: " - + "\t Manufacturer: " + hmdDesc.ManufacturerString() - + "\t Product: " + hmdDesc.ProductNameString() - + "\t Serial: " // + hmdDesc.SerialNumberString() // Hidden for privacy reasons - + "\t Type: " + hmdDesc.Type() - + "\t Resolution (total): " + resolutionW + "," + resolutionH); - - if (resolutionW == 0) { - LOGGER.severe("HMD witdth=0 : aborting"); - return false; // TODO fix memory leak - destroy() is not called - } - - // Find the FOV for each eye - for (int eye = 0; eye < 2; eye++) { - fovPorts[eye] = hmdDesc.DefaultEyeFov(eye); - } - - // Get the pose for each eye, and cache it for later. - for (int eye = 0; eye < 2; eye++) { - // Create the projection objects - projections[eye] = OVRMatrix4f.malloc(); - hmdRelativeEyePoses[eye] = new Matrix4f(); - hmdRelativeEyePositions[eye] = new Vector3f(); - - // Find the eye render information - we use this in the - // view manager for giving LibOVR its timewarp information. - eyeRenderDesc[eye] = OVREyeRenderDesc.malloc(); - ovr_GetRenderDesc(session, eye, fovPorts[eye], eyeRenderDesc[eye]); - - // Get the pose of the eye - OVRPosef pose = eyeRenderDesc[eye].HmdToEyePose(); - - // Get the position and rotation of the eye - vecO2J(pose.Position(), hmdRelativeEyePositions[eye]); - Quaternion rotation = quatO2J(pose.Orientation(), new Quaternion()); - - // Put it into a matrix for the get eye pose functions - hmdRelativeEyePoses[eye].loadIdentity(); - hmdRelativeEyePoses[eye].setTranslation(hmdRelativeEyePositions[eye]); - hmdRelativeEyePoses[eye].setRotationQuaternion(rotation); - } - - // Recenter the HMD. The game itself should do this too, but just in case / before they do. - reset(); - - // Do this so others relying on our texture size (the GUI in particular) get it correct. - findHMDTextureSize(); - - // Allocate the memory for the tracking state - we actually - // set it up later, but Input uses it so calloc it now. - trackingState = OVRTrackingState.calloc(); - - // Set up the input - input = new OculusVRInput(this, session, sessionStatus, trackingState); - - // TODO find some way to get in ovrTrackingOrigin_FloorLevel - - // throw new UnsupportedOperationException("Not yet implemented!"); - return true; - } - - @Override - public void updatePose() { - double ftiming = ovr_GetPredictedDisplayTime(session, 0); - ovr_GetTrackingState(session, ftiming, true, trackingState); - ovr_GetSessionStatus(session, sessionStatus); - - input.updateControllerStates(); - - headPose = trackingState.HeadPose().ThePose(); - } - - @Override - public boolean isInitialized() { - return initialized; - } - - @Override - public void destroy() { - // fovPorts: contents are managed by LibOVR, no need to do anything. - - // Clean up the input - input.dispose(); - - // Check if we've set up rendering - if so, clean that up. - if (chains != null) { - // Destroy our set of huge buffer images. - for (long chain : chains) { - ovr_DestroyTextureSwapChain(session, chain); - } - - // Free up the layer - layer0.free(); - - // The layers array apparently takes care of itself (and crashes if we try to free it) - } - - for (OVREyeRenderDesc eye : eyeRenderDesc) { - eye.free(); - } - for (OVRMatrix4f projection : projections) { - projection.free(); - } - - hmdDesc.free(); - trackingState.free(); - sessionStatus.free(); - - // Wrap everything up - ovr_Destroy(session); - ovr_Shutdown(); - } - - @Override - public void reset() { - // Reset the coordinate system - where the user's head is now is facing forwards from [0,0,0] - ovr_RecenterTrackingOrigin(session); - } - - @Override - public void getRenderSize(Vector2f store) { - if (!isInitialized()) { - throw new IllegalStateException("Cannot call getRenderSize() before initialized!"); - } - store.x = textureW; - store.y = textureH; - } - - @Override - public float getInterpupillaryDistance() { - return 0.065f; // TODO - } - - @Override - public Quaternion getOrientation() { - return quatO2J(headPose.Orientation(), new Quaternion()); - } - - @Override - public Vector3f getPosition() { - return vecO2J(headPose.Position(), new Vector3f()); - } - - @Override - public void getPositionAndOrientation(Vector3f storePos, Quaternion storeRot) { - storePos.set(getPosition()); - storeRot.set(getOrientation()); - } - - private Matrix4f calculateProjection(int eye, Camera cam) { - Matrix4f mat = new Matrix4f(); - - // Get LibOVR to find the correct projection - OVRUtil.ovrMatrix4f_Projection(fovPorts[eye], cam.getFrustumNear(), cam.getFrustumFar(), OVRUtil.ovrProjection_None, projections[eye]); - - matrixO2J(projections[eye], mat); - - return mat; - } - - @Override - public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam) { - return calculateProjection(ovrEye_Left, cam); - } - - @Override - public Matrix4f getHMDMatrixProjectionRightEye(Camera cam) { - return calculateProjection(ovrEye_Right, cam); - } - - @Override - public Vector3f getHMDVectorPoseLeftEye() { - return hmdRelativeEyePositions[ovrEye_Left]; - } - - @Override - public Vector3f getHMDVectorPoseRightEye() { - return hmdRelativeEyePositions[ovrEye_Right]; - } - - @Override - public Vector3f getSeatedToAbsolutePosition() { - throw new UnsupportedOperationException(); - } - - @Override - public Matrix4f getHMDMatrixPoseLeftEye() { - return hmdRelativeEyePoses[ovrEye_Left]; - } - - @Override - public Matrix4f getHMDMatrixPoseRightEye() { - return hmdRelativeEyePoses[ovrEye_Left]; - } - - @Override - public HmdType getType() { - return HmdType.OCULUS_RIFT; - } - - public boolean initVRCompositor(boolean set) { - if (!set) { - throw new UnsupportedOperationException("Cannot use LibOVR without compositor!"); - } - - setupLayers(); - - framebuffers = new FrameBuffer[2][]; - for (int eye = 0; eye < 2; eye++) - setupFramebuffers(eye); - - // TODO move initialization code here from VRViewManagerOculus - return true; - } - - public void printLatencyInfoToConsole(boolean set) { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - public void setFlipEyes(boolean set) { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - public Void getCompositor() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - public Void getVRSystem() { - throw new UnsupportedOperationException("Not yet implemented!"); - } - - // Rendering-type stuff - - public void findHMDTextureSize() { - // Texture sizes - float pixelScaling = 1.0f; // pixelsPerDisplayPixel - - OVRSizei leftTextureSize = OVRSizei.malloc(); - ovr_GetFovTextureSize(session, ovrEye_Left, fovPorts[ovrEye_Left], pixelScaling, leftTextureSize); - - OVRSizei rightTextureSize = OVRSizei.malloc(); - ovr_GetFovTextureSize(session, ovrEye_Right, fovPorts[ovrEye_Right], pixelScaling, rightTextureSize); - - if (leftTextureSize.w() != rightTextureSize.w()) { - throw new IllegalStateException("Texture sizes do not match [horizontal]"); - } - if (leftTextureSize.h() != rightTextureSize.h()) { - throw new IllegalStateException("Texture sizes do not match [vertical]"); - } - - textureW = leftTextureSize.w(); - textureH = leftTextureSize.h(); - - leftTextureSize.free(); - rightTextureSize.free(); - } - - private long setupTextureChain() { - // Set up the information for the texture buffer chain thing - OVRTextureSwapChainDesc swapChainDesc = OVRTextureSwapChainDesc.calloc() - .Type(ovrTexture_2D) - .ArraySize(1) - .Format(OVR_FORMAT_R8G8B8A8_UNORM_SRGB) - .Width(textureW) - .Height(textureH) - .MipLevels(1) - .SampleCount(1) - .StaticImage(false); // ovrFalse - - // Create the chain - PointerBuffer textureSetPB = createPointerBuffer(1); - if (OVRGL.ovr_CreateTextureSwapChainGL(session, swapChainDesc, textureSetPB) != ovrSuccess) { - throw new RuntimeException("Failed to create Swap Texture Set"); - } - swapChainDesc.free(); - - return textureSetPB.get(); // TODO is this a memory leak? - } - - public void setupLayers() { - //Layers - layer0 = OVRLayerEyeFov.calloc(); - layer0.Header().Type(ovrLayerType_EyeFov); - layer0.Header().Flags(ovrLayerFlag_TextureOriginAtBottomLeft); - - chains = new long[2]; - for (int eye = 0; eye < 2; eye++) { - long eyeChain = setupTextureChain(); - chains[eye] = eyeChain; - - OVRRecti viewport = OVRRecti.calloc(); - viewport.Pos().x(0); - viewport.Pos().y(0); - viewport.Size().w(textureW); - viewport.Size().h(textureH); - - layer0.ColorTexture(eye, eyeChain); - layer0.Viewport(eye, viewport); - layer0.Fov(eye, fovPorts[eye]); - - viewport.free(); - // we update pose only when we have it in the render loop - } - - layers = createPointerBuffer(1); - layers.put(0, layer0); - } - - /** - * Create a framebuffer for an eye. - */ - public void setupFramebuffers(int eye) { - // Find the chain length - IntBuffer length = BufferUtils.createIntBuffer(1); - ovr_GetTextureSwapChainLength(session, chains[eye], length); - int chainLength = length.get(); - - LOGGER.fine("HMD Eye #" + eye + " texture chain length: " + chainLength); - - // Create the frame buffers - framebuffers[eye] = new FrameBuffer[chainLength]; - for (int i = 0; i < chainLength; i++) { - // find the GL texture ID for this texture - IntBuffer textureIdB = BufferUtils.createIntBuffer(1); - OVRGL.ovr_GetTextureSwapChainBufferGL(session, chains[eye], i, textureIdB); - int textureId = textureIdB.get(); - - // TODO less hacky way of getting our texture into JMonkeyEngine - Image img = new Image(); - img.setId(textureId); - img.setFormat(Image.Format.RGBA8); - img.setWidth(textureW); - img.setHeight(textureH); - - Texture2D tex = new Texture2D(img); - - FrameBuffer buffer = new FrameBuffer(textureW, textureH, 1); - buffer.setDepthBuffer(Image.Format.Depth); - buffer.setColorTexture(tex); - - framebuffers[eye][i] = buffer; - } - } - - // UTILITIES - // TODO move to helper class - - /** - * Copy the values from a LibOVR matrix into a jMonkeyEngine matrix. - * - * @param from The matrix to copy from. - * @param to The matrix to copy to. - * @return The {@code to} argument. - */ - public static Matrix4f matrixO2J(OVRMatrix4f from, Matrix4f to) { - to.loadIdentity(); // For the additional columns (unless I'm badly misunderstanding matricies) - - for (int x = 0; x < 4; x++) { - for (int y = 0; y < 4; y++) { - float val = from.M(x + y * 4); // TODO verify this - to.set(x, y, val); - } - } - - to.transposeLocal(); // jME vs LibOVR coordinate spaces - Yay! - - return to; - } - - /** - * Copy the values from a LibOVR quaternion into a jMonkeyEngine quaternion. - * - * @param from The quaternion to copy from. - * @param to The quaternion to copy to. - * @return The {@code to} argument. - */ - public static Quaternion quatO2J(OVRQuatf from, Quaternion to) { - // jME and LibOVR do their coordinate spaces differently for rotations, so flip Y and W (thanks, jMonkeyVR). - to.set( - from.x(), - -from.y(), - from.z(), - -from.w() - ); - - to.normalizeLocal(); - - return to; - } - - /** - * Copy the values from a LibOVR vector into a jMonkeyEngine vector. - * - * @param from The vector to copy from. - * @param to The vector to copy to. - * @return The {@code to} argument. - */ - public static Vector3f vecO2J(OVRVector3f from, Vector3f to) { - // jME and LibOVR disagree on which way X and Z are, too. - to.set( - -from.x(), - from.y(), - -from.z() - ); - - return to; - } - - // Getters, intended for VRViewManager. - - public long getSessionPointer() { - return session; - } - - public long getChain(int eye) { - return chains[eye]; - } - - public FrameBuffer[] getFramebuffers(int eye) { - return framebuffers[eye]; - } - - public PointerBuffer getLayers() { - return layers; - } - - public OVRLayerEyeFov getLayer0() { - return layer0; - } - - public OVRFovPort getFovPort() { - return fovPorts[ovrEye_Left]; // TODO checking the left and right eyes match - } - - public OVRPosef getHeadPose() { - return headPose; - } - - public OVRPosef getEyePose(int eye) { - return eyeRenderDesc[eye].HmdToEyePose(); - } - - public VREnvironment getEnvironment() { - return environment; - } -} - -/* vim: set ts=4 softtabstop=0 sw=4 expandtab: */ - diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVRInput.java deleted file mode 100644 index e3631e72a1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusVRInput.java +++ /dev/null @@ -1,370 +0,0 @@ -package com.jme3.input.vr.oculus; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRInputType; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.math.*; -import com.jme3.renderer.Camera; -import com.jme3.scene.Spatial; - -import org.lwjgl.ovr.*; - -import static org.lwjgl.ovr.OVR.*; - -public class OculusVRInput implements VRInputAPI { - // State control - private final OVRInputState inputState; - private final OVRSessionStatus sessionStatus; - private final OVRTrackingState trackingState; - private final OculusVR hardware; - private long session; - - // Setup values - private float axisMultiplier = 1; - - // Cached stuff - private int buttons, touch; - - // Used to calculate sinceLastCall stuff - private int lastButtons, lastTouch; - private final Vector2f[][] lastAxises; - - /** - * The state data (linear and angular velocity and acceleration) for each hand - */ - private OVRPoseStatef[] handStates; - - /** - * The position and orientation of the Touch controllers. - */ - private OVRPosef[] handPoses; - - /** - * The object forms of the tracked controllers. - */ - private final OculusController[] controllers = { - new OculusController(0), - new OculusController(1) - }; - - public OculusVRInput(OculusVR hardware, long session, - OVRSessionStatus sessionStatus, OVRTrackingState trackingState) { - this.hardware = hardware; - this.session = session; - this.sessionStatus = sessionStatus; - this.trackingState = trackingState; - - inputState = OVRInputState.calloc(); - - handStates = new OVRPoseStatef[ovrHand_Count]; - handPoses = new OVRPosef[handStates.length]; - lastAxises = new Vector2f[handStates.length][3]; // trigger+grab+thumbstick for each hand. - } - - public void dispose() { - inputState.free(); - session = 0; // Crashing > undefined behaviour if this object is incorrectly accessed again. - } - - @Override - public void updateControllerStates() { - // Handle buttons, axies - ovr_GetInputState(session, ovrControllerType_Touch, inputState); - buttons = inputState.Buttons(); - touch = inputState.Touches(); - - // Get the touch controller poses - // TODO what if no touch controllers are available? - for (int hand = 0; hand < handPoses.length; hand++) { - handStates[hand] = trackingState.HandPoses(hand); - handPoses[hand] = handStates[hand].ThePose(); - } - } - - private Vector3f cv(OVRVector3f in) { - // TODO do we want to reuse vectors rather than making new ones? - // TODO OpenVRInput does this, but it will probably cause some bugs. - return OculusVR.vecO2J(in, new Vector3f()); // This also fixes the coordinate space transform issues. - } - - private Vector2f cv(OVRVector2f in) { - // TODO do we want to reuse vectors rather than making new ones? - // TODO OpenVRInput does this, but it will probably cause some bugs. - return new Vector2f(in.x(), in.y()); - } - - private Quaternion cq(OVRQuatf in) { - // TODO do we want to reuse quaternions rather than making new ones? - // TODO OpenVRInput does this, but it will probably cause some bugs. - return OculusVR.quatO2J(in, new Quaternion()); // This also fixes the coordinate space transform issues. - } - - private Vector2f axis(float input) { - // See above comments about reusing vectors - return new Vector2f(input, input); - } - - // Tracking (position, rotation, velocity, status) - - @Override - public Vector3f getPosition(int index) { - return cv(handPoses[index].Position()); - } - - @Override - public Vector3f getVelocity(int controllerIndex) { - return cv(handStates[controllerIndex].LinearVelocity()); - } - - @Override - public Quaternion getOrientation(int index) { - return cq(handPoses[index].Orientation()); - } - - @Override - public Vector3f getAngularVelocity(int controllerIndex) { - return cv(handStates[controllerIndex].AngularVelocity()); - } - - @Override - public Quaternion getFinalObserverRotation(int index) { - // Copied from OpenVRInput - - VREnvironment env = hardware.getEnvironment(); - OculusViewManager vrvm = (OculusViewManager) hardware.getEnvironment().getVRViewManager(); - - Object obs = env.getObserver(); - Quaternion tempq = new Quaternion(); // TODO move to class scope? - if (obs instanceof Camera) { - tempq.set(((Camera) obs).getRotation()); - } else { - tempq.set(((Spatial) obs).getWorldRotation()); - } - - return tempq.multLocal(getOrientation(index)); - } - - @Override - public Vector3f getFinalObserverPosition(int index) { - // Copied from OpenVRInput - - VREnvironment env = hardware.getEnvironment(); - OculusViewManager vrvm = (OculusViewManager) hardware.getEnvironment().getVRViewManager(); - - Object obs = env.getObserver(); - Vector3f pos = getPosition(index); - if (obs instanceof Camera) { - ((Camera) obs).getRotation().mult(pos, pos); - return pos.addLocal(((Camera) obs).getLocation()); - } else { - ((Spatial) obs).getWorldRotation().mult(pos, pos); - return pos.addLocal(((Spatial) obs).getWorldTranslation()); - } - } - - @Override - public boolean isInputDeviceTracking(int index) { - int flags = trackingState.HandStatusFlags(index); - return (flags & ovrStatus_PositionTracked) != 0; // TODO do we require orientation as well? - } - - // Input Getters - - @Override - public Vector2f getAxis(int controllerIndex, VRInputType forAxis) { - Vector2f result = getAxisRaw(controllerIndex, forAxis); - return result == null ? null : result.multLocal(axisMultiplier); - } - - @Override - public Vector2f getAxisRaw(int controllerIndex, VRInputType forAxis) { - switch (forAxis) { - case OculusThumbstickAxis: - return cv(inputState.Thumbstick(controllerIndex)); - case OculusTriggerAxis: - return axis(inputState.IndexTrigger(controllerIndex)); - case OculusGripAxis: - return axis(inputState.HandTrigger(controllerIndex)); - default: - return null; - } - } - - @Override - public boolean isButtonDown(int controllerIndex, VRInputType checkButton) { - return isButtonDownForStatus(controllerIndex, checkButton, buttons, touch); - } - - public boolean isButtonDownForStatus(int controllerIndex, VRInputType checkButton, int buttons, int touch) { - int buttonMask = (controllerIndex == ovrHand_Left) ? ovrButton_LMask : ovrButton_RMask; - int touchMask = (controllerIndex == ovrHand_Left) ? - (ovrTouch_LButtonMask + ovrTouch_LPoseMask) : - (ovrTouch_RButtonMask + ovrTouch_RPoseMask); - - switch (checkButton) { - default: - return false; - - case OculusTopButton: // Physical buttons - case OculusBottomButton: - case OculusThumbstickButton: - case OculusMenuButton: - return (buttons & buttonMask & checkButton.getValue()) != 0; - - case OculusTopTouch: // Standard capacitive buttons - case OculusBottomTouch: - case OculusThumbstickTouch: - case OculusThumbrestTouch: - case OculusIndexTouch: - case OculusThumbUp: // Calculated/virtual capacitive buttons - case OculusIndexPointing: - return (touch & touchMask & checkButton.getValue()) != 0; - } - } - - // Since-last-call stuff - - @Override - public void resetInputSinceLastCall() { - lastButtons = 0; - lastTouch = 0; - } - - @Override - public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton) { - boolean wasPressed = isButtonDownForStatus(controllerIndex, checkButton, lastButtons, lastTouch); - lastButtons = buttons; - lastTouch = touch; - return !wasPressed && isButtonDown(controllerIndex, checkButton); - } - - @Override - public Vector2f getAxisDeltaSinceLastCall(int controllerIndex, VRInputType forAxis) { - int index; - switch (forAxis) { - case OculusTriggerAxis: - index = 0; - break; - case OculusGripAxis: - index = 1; - break; - case OculusThumbstickAxis: - index = 2; - break; - default: - return null; - } - - Vector2f last = lastAxises[controllerIndex][index]; - if (last == null) { - last = lastAxises[controllerIndex][index] = new Vector2f(); - } - - Vector2f current = getAxis(controllerIndex, forAxis); - - // TODO could this lead to accuracy problems? - current.subtractLocal(last); - last.addLocal(current); - - return current; - } - - // Misc - - @Override - public boolean init() { - throw new UnsupportedOperationException("Input initialized at creation time"); - } - - @Override - public void updateConnectedControllers() { - throw new UnsupportedOperationException("Automatically done by LibOVR (I think?)"); - } - - @Override - public float getAxisMultiplier() { - return axisMultiplier; - } - - @Override - public void setAxisMultiplier(float axisMultiplier) { - this.axisMultiplier = axisMultiplier; - } - - @Override - public void triggerHapticPulse(int controllerIndex, float seconds) { - // TODO: How do we time so we can turn the feedback off? - } - - @Override - public boolean isInputFocused() { - return sessionStatus.IsVisible(); // TODO do we need HmdMounted, or is it counted in IsVisible - } - - @Override - public Object getRawControllerState(int index) { - throw new UnsupportedOperationException("Cannot get raw controller state!"); - } - - @Override - public void swapHands() { - // Do nothing. - // TODO although OSVR and OpenVR if it has more than two controllers both do nothing, shouldn't we be - // TODO throwing an exception or something? - } - - @Override - public int getTrackedControllerCount() { - // TODO: Shouldn't we be seeing if the user has the touch controllers first? - return 2; - } - - @Override - public VRTrackedController getTrackedController(int index) { - return controllers[index]; - } - - /** - * The object form representation of a controller. - */ - public class OculusController implements VRTrackedController { - - /** - * The ID of the hand to track - */ - private int hand; - - public OculusController(int hand) { - this.hand = hand; - } - - @Override - public String getControllerName() { - return "Touch"; // TODO - } - - @Override - public String getControllerManufacturer() { - return "Oculus"; // TODO - } - - @Override - public Vector3f getPosition() { - return OculusVRInput.this.getPosition(hand); - } - - @Override - public Quaternion getOrientation() { - return OculusVRInput.this.getOrientation(hand); - } - - @Override - public Matrix4f getPose() { - Matrix4f mat = new Matrix4f(); - mat.setRotationQuaternion(getOrientation()); - mat.setTranslation(getPosition()); - return mat; - } - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusViewManager.java deleted file mode 100644 index 1d7baee09c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/oculus/OculusViewManager.java +++ /dev/null @@ -1,340 +0,0 @@ -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.input.vr.oculus; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.AbstractVRViewManager; -import com.jme3.input.vr.VRAPI; -import com.jme3.math.*; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.scene.Spatial; -import com.jme3.util.BufferUtils; -import com.jme3.util.VRGUIPositioningMode; - -import java.nio.IntBuffer; -import java.util.Iterator; -import java.util.Objects; - -import org.lwjgl.ovr.OVRFovPort; -import org.lwjgl.ovr.OVRPosef; -import org.lwjgl.ovr.OVRUtil; - -import static org.lwjgl.ovr.OVR.*; -import static org.lwjgl.ovr.OVRErrorCode.*; - -/** - * A rendering system for Oculus's LibOVR API. - * - * @author Campbell Suter (znix@znix.xyz) - */ -public class OculusViewManager extends AbstractVRViewManager { - - private final VREnvironment environment; - private final OculusVR hardware; - - // Copied from OSVR - //final & temp values for camera calculations - private final Vector3f finalPosition = new Vector3f(); - private final Quaternion finalRotation = new Quaternion(); - private final Vector3f hmdPos = new Vector3f(); - private final Quaternion hmdRot = new Quaternion(); - - public OculusViewManager(VREnvironment environment) { - this.environment = environment; - - VRAPI hardware = environment.getVRHardware(); - Objects.requireNonNull(hardware, "Attached VR Hardware cannot be null"); - if (!(hardware instanceof OculusVR)) { - throw new IllegalStateException("Cannot use Oculus VR view manager on non-Oculus hardware state!"); - } - - this.hardware = (OculusVR) hardware; - - if (!environment.compositorAllowed()) { - throw new UnsupportedOperationException("Cannot render without compositor on LibOVR"); - } - } - - @Override - public void initialize() { - setupCamerasAndViews(); - - if (environment.hasTraditionalGUIOverlay()) { - - environment.getVRMouseManager().initialize(); - - // update the pose to position the gui correctly on start - update(0f); - environment.getVRGUIManager().positionGui(); - } - } - - private long session() { - return hardware.getSessionPointer(); - } - - @Override - public void update(float tpf) { - // TODO - - hardware.updatePose(); - - // TODO deduplicate - if (environment == null) { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - // grab the observer - Object obs = environment.getObserver(); - Quaternion objRot; - Vector3f objPos; - if (obs instanceof Camera) { - objRot = ((Camera) obs).getRotation(); - objPos = ((Camera) obs).getLocation(); - } else { - objRot = ((Spatial) obs).getWorldRotation(); - objPos = ((Spatial) obs).getWorldTranslation(); - } - - // update the HMD's position & orientation - hardware.getPositionAndOrientation(hmdPos, hmdRot); - if (obs != null) { - // update hmdPos based on obs rotation - finalRotation.set(objRot); - finalRotation.mult(hmdPos, hmdPos); - finalRotation.multLocal(hmdRot); - } - - // Update both eye cameras - finalizeCamera(hardware.getHMDVectorPoseLeftEye(), objPos, leftCamera); - finalizeCamera(hardware.getHMDVectorPoseRightEye(), objPos, rightCamera); - - // Update the main camera, so it shows the same basic view the HMD is getting - // TODO: Do this in VRAppState, so it works on all HMDs. - // I only have a Rift, so I can't test it on anything else. - if(!environment.isInstanceRendering()) { // We use the app camera as the left camera here - // TODO: Double up on rendering and use one eye, to reduce GPU load rendering the scene again. - // TODO: Snip at the image to remove the distorted corners from a very high FOV. - finalizeCamera(Vector3f.ZERO, objPos, environment.getApplication().getCamera()); - } - - if (environment.hasTraditionalGUIOverlay()) { - // update the mouse? - environment.getVRMouseManager().update(tpf); - - // update GUI position? - if (environment.getVRGUIManager().isWantsReposition() || environment.getVRGUIManager().getPositioningMode() != VRGUIPositioningMode.MANUAL) { - environment.getVRGUIManager().positionGuiNow(tpf); - environment.getVRGUIManager().updateGuiQuadGeometricState(); - } - } - } - - /** - * Place the camera within the scene. - * - * @param eyePos the eye position. - * @param obsPosition the observer position. - * @param cam the camera to place. - */ - private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { - finalRotation.mult(eyePos, finalPosition); - finalPosition.addLocal(hmdPos); - if (obsPosition != null) { - finalPosition.addLocal(obsPosition); - } - finalPosition.y += getHeightAdjustment(); - cam.setFrame(finalPosition, finalRotation); - } - - @Override - public void render() { - - // Calculate the render pose (translation/rotation) for each eye. - // LibOVR takes the difference between this and the real position of each eye at display time - // to apply AZW (timewarp). - - OVRPosef.Buffer hmdToEyeOffsets = OVRPosef.calloc(2); - hmdToEyeOffsets.put(0, hardware.getEyePose(ovrEye_Left)); - hmdToEyeOffsets.put(1, hardware.getEyePose(ovrEye_Right)); - - //calculate eye poses - OVRUtil.ovr_CalcEyePoses(hardware.getHeadPose(), hmdToEyeOffsets, hardware.getLayer0().RenderPose()); - hmdToEyeOffsets.free(); - - for (int eye = 0; eye < 2; eye++) { - IntBuffer currentIndexB = BufferUtils.createIntBuffer(1); - ovr_GetTextureSwapChainCurrentIndex(session(), hardware.getChain(eye), currentIndexB); - int index = currentIndexB.get(); - - // Constantly (each frame) rotating through a series of - // frame buffers, so make sure we write into the correct one. - (eye == ovrEye_Left ? leftViewPort : rightViewPort).setOutputFrameBuffer(hardware.getFramebuffers(eye)[index]); - } - - // Now the game will render into the buffers given to us by LibOVR - } - - @Override - public void postRender() { - // We're done with our textures now - the game is done drawing into them. - for (int eye = 0; eye < 2; eye++) { - ovr_CommitTextureSwapChain(session(), hardware.getChain(eye)); - } - - // Send the result to the HMD - int result = ovr_SubmitFrame(session(), 0, null, hardware.getLayers()); - if (result != ovrSuccess) { - throw new IllegalStateException("Failed to submit frame!"); - } - } - - /* - ********************************************************* - * Show's over, now it's just boring camera stuff etc. * - ********************************************************* - */ - - /** - * Set up the cameras and views for each eye and the mirror display. - */ - private void setupCamerasAndViews() { - // TODO: Use LobOVR IPD etc - if (environment != null) { - // get desired frustum from original camera - Camera origCam = environment.getCamera(); - float fFar = origCam.getFrustumFar(); - float fNear = origCam.getFrustumNear(); - - // restore frustum on distortion scene cam, if needed - if (environment.isInstanceRendering()) { - leftCamera = origCam; - } else { - leftCamera = origCam.clone(); - } - - OVRFovPort fp = hardware.getFovPort(); - float hFov = fp.LeftTan() + fp.RightTan(); - float vFov = fp.UpTan() + fp.DownTan(); - getLeftCamera().setFrustumPerspective(hFov / FastMath.TWO_PI * 360, vFov / hFov, fNear, fFar); - - prepareCameraSize(getLeftCamera(), 1f); - if (environment.getVRHardware() != null) { - getLeftCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionLeftEye(getLeftCamera())); - } - //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); - - if (!environment.isInstanceRendering()) { - leftViewPort = setupViewBuffers(getLeftCamera(), LEFT_VIEW_NAME); - rightCamera = getLeftCamera().clone(); - if (environment.getVRHardware() != null) { - getRightCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(getRightCamera())); - } - rightViewPort = setupViewBuffers(getRightCamera(), RIGHT_VIEW_NAME); - } else if (environment.getApplication() != null) { - throw new UnsupportedOperationException("Not yet implemented!"); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - // setup gui - environment.getVRGUIManager().setupGui(getLeftCamera(), getRightCamera(), getLeftViewPort(), getRightViewPort()); - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private void prepareCameraSize(Camera cam, float xMult) { - // TODO this function is identical to that in VRViewManagerOpenVR; merge the two. - if (environment != null) { - if (environment.getApplication() != null) { - Vector2f size = new Vector2f(); - VRAPI vrhmd = environment.getVRHardware(); - - if (vrhmd == null) { - size.x = 1280f; - size.y = 720f; - } else { - vrhmd.getRenderSize(size); - } - - if (size.x < environment.getApplication().getContext().getSettings().getWidth()) { - size.x = environment.getApplication().getContext().getSettings().getWidth(); - } - if (size.y < environment.getApplication().getContext().getSettings().getHeight()) { - size.y = environment.getApplication().getContext().getSettings().getHeight(); - } - - if (environment.isInstanceRendering()) { - size.x *= 2f; - } - - // other adjustments - size.x *= xMult; - size.x *= getResolutionMuliplier(); - size.y *= getResolutionMuliplier(); - - if (cam.getWidth() != size.x || cam.getHeight() != size.y) { - cam.resize((int) size.x, (int) size.y, false); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupViewBuffers(Camera cam, String viewName) { - // TODO this function is identical to that in VRViewManagerOpenVR; merge the two. - if (environment != null) { - if (environment.getApplication() != null) { - ViewPort viewPort = environment.getApplication().getRenderManager().createPreView(viewName, cam); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while (spatialIter.hasNext()) { - viewPort.attachScene(spatialIter.next()); - } - - // The viewbuffer to render into will be set during prerender. - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVR.java deleted file mode 100644 index a4d76018be..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVR.java +++ /dev/null @@ -1,597 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.HmdType; -import com.jme3.input.vr.VRAPI; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.system.jopenvr.HmdMatrix34_t; -import com.jme3.system.jopenvr.HmdMatrix44_t; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.OpenVRUtil; -import com.jme3.system.jopenvr.TrackedDevicePose_t; -import com.jme3.system.jopenvr.VR_IVRCompositor_FnTable; -import com.jme3.system.jopenvr.VR_IVRSystem_FnTable; -import com.jme3.system.jopenvr.VR_IVRTrackedCamera_FnTable; -import com.jme3.util.VRUtil; -import com.sun.jna.Memory; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; - -import java.nio.IntBuffer; -import java.util.Locale; -import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * A class that wraps an OpenVR system. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OpenVR implements VRAPI { - - private static final Logger logger = Logger.getLogger(OpenVR.class.getName()); - - private static VR_IVRCompositor_FnTable compositorFunctions; - private static VR_IVRSystem_FnTable vrsystemFunctions; - private static VR_IVRTrackedCamera_FnTable cameraFunctions; - - private static boolean initSuccess = false; - private static boolean flipEyes = false; - - private IntBuffer hmdDisplayFrequency; - private TrackedDevicePose_t.ByReference hmdTrackedDevicePoseReference; - protected TrackedDevicePose_t[] hmdTrackedDevicePoses; - - protected IntByReference hmdErrorStore; - - private final Quaternion rotStore = new Quaternion(); - private final Vector3f posStore = new Vector3f(); - - private static FloatByReference tlastVsync; - - /** - * The actual frame count. - */ - public static LongByReference _tframeCount; - - // for debugging latency - private int frames = 0; - - protected Matrix4f[] poseMatrices; - - private final Matrix4f hmdPose = Matrix4f.IDENTITY.clone(); - private Matrix4f hmdProjectionLeftEye; - private Matrix4f hmdProjectionRightEye; - private Matrix4f hmdPoseLeftEye; - private Matrix4f hmdPoseRightEye; - - private Vector3f hmdPoseLeftEyeVec, hmdPoseRightEyeVec, hmdSeatToStand; - - private float vsyncToPhotons; - private double timePerFrame, frameCountRun; - private long frameCount; - private OpenVRInput VRinput; - - - private VREnvironment environment = null; - - /** - * Convert specific OpenVR {@link com.jme3.system.jopenvr.HmdMatrix34_t HmdMatrix34_t} into JME {@link Matrix4f Matrix4f} - * @param hmdMatrix the input matrix - * @param mat the converted matrix - * @return the converted matrix - */ - public static Matrix4f convertSteamVRMatrix3ToMatrix4f(com.jme3.system.jopenvr.HmdMatrix34_t hmdMatrix, Matrix4f mat){ - mat.set(hmdMatrix.m[0], hmdMatrix.m[1], hmdMatrix.m[2], hmdMatrix.m[3], - hmdMatrix.m[4], hmdMatrix.m[5], hmdMatrix.m[6], hmdMatrix.m[7], - hmdMatrix.m[8], hmdMatrix.m[9], hmdMatrix.m[10], hmdMatrix.m[11], - 0f, 0f, 0f, 1f); - return mat; - } - - /** - * Convert specific OpenVR {@link com.jme3.system.jopenvr.HmdMatrix44_t HmdMatrix34_t} into JME {@link Matrix4f Matrix4f} - * @param hmdMatrix the input matrix - * @param mat the converted matrix - * @return the converted matrix - */ - public static Matrix4f convertSteamVRMatrix4ToMatrix4f(com.jme3.system.jopenvr.HmdMatrix44_t hmdMatrix, Matrix4f mat){ - mat.set(hmdMatrix.m[0], hmdMatrix.m[1], hmdMatrix.m[2], hmdMatrix.m[3], - hmdMatrix.m[4], hmdMatrix.m[5], hmdMatrix.m[6], hmdMatrix.m[7], - hmdMatrix.m[8], hmdMatrix.m[9], hmdMatrix.m[10], hmdMatrix.m[11], - hmdMatrix.m[12], hmdMatrix.m[13], hmdMatrix.m[14], hmdMatrix.m[15]); - return mat; - } - - - /** - * Create a new OpenVR system - * attached to the given {@link VREnvironment VR environment}. - * @param environment the VR environment to which this API is attached. - */ - public OpenVR(VREnvironment environment){ - this.environment = environment; - } - - @Override - public OpenVRInput getVRinput() { - return VRinput; - } - - @Override - public VR_IVRSystem_FnTable getVRSystem() { - return vrsystemFunctions; - } - - @Override - public VR_IVRCompositor_FnTable getCompositor() { - return compositorFunctions; - } - - public VR_IVRTrackedCamera_FnTable getTrackedCamera(){ - return cameraFunctions; - } - - @Override - public String getName() { - return "OpenVR"; - } - - private static long latencyWaitTime = 0; - - @Override - public void setFlipEyes(boolean set) { - flipEyes = set; - } - - private boolean enableDebugLatency = false; - - @Override - public void printLatencyInfoToConsole(boolean set) { - enableDebugLatency = set; - } - - @Override - public int getDisplayFrequency() { - if( hmdDisplayFrequency == null ) return 0; - return hmdDisplayFrequency.get(0); - } - - @Override - public boolean initialize() { - - logger.config("Initializing OpenVR system..."); - - hmdErrorStore = new IntByReference(); - vrsystemFunctions = null; - - // Init the native linking to the OpenVR library. - try{ - JOpenVRLibrary.init(); - } catch(Throwable t){ - logger.log(Level.SEVERE, "Cannot link to OpenVR system library: "+t.getMessage(), t); - return false; - } - - JOpenVRLibrary.VR_InitInternal(hmdErrorStore, JOpenVRLibrary.EVRApplicationType.EVRApplicationType_VRApplication_Scene); - - if( hmdErrorStore.getValue() == 0 ) { - vrsystemFunctions = new VR_IVRSystem_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRSystem_Version, hmdErrorStore).getPointer()); - } - - if( vrsystemFunctions == null || hmdErrorStore.getValue() != 0 ) { - logger.severe("OpenVR Initialize Result: " + JOpenVRLibrary.VR_GetVRInitErrorAsEnglishDescription(hmdErrorStore.getValue()).getString(0)); - logger.severe("Initializing OpenVR system [FAILED]"); - return false; - } else { - logger.config("OpenVR initialized & VR connected."); - - vrsystemFunctions.setAutoSynch(false); - vrsystemFunctions.read(); - - - tlastVsync = new FloatByReference(); - _tframeCount = new LongByReference(); - - hmdDisplayFrequency = IntBuffer.allocate(1); - hmdDisplayFrequency.put( (int) JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayFrequency_Float); - hmdTrackedDevicePoseReference = new TrackedDevicePose_t.ByReference(); - hmdTrackedDevicePoses = (TrackedDevicePose_t[])hmdTrackedDevicePoseReference.toArray(JOpenVRLibrary.k_unMaxTrackedDeviceCount); - poseMatrices = new Matrix4f[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - for(int i=0;itrue is the use of the headset camera is allowed and false otherwise. - */ - public void initCamera(boolean allowed) { - hmdErrorStore.setValue(0); // clear the error store - - if( allowed && vrsystemFunctions != null ) { - IntByReference intptr = JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRTrackedCamera_Version, hmdErrorStore); - if (intptr != null){ - cameraFunctions = new VR_IVRTrackedCamera_FnTable(intptr.getPointer()); - if(cameraFunctions != null && hmdErrorStore.getValue() == 0 ){ - cameraFunctions.setAutoSynch(false); - cameraFunctions.read(); - logger.config("OpenVR Camera initialized"); - } - } - } - } - - @Override - public void destroy() { - JOpenVRLibrary.VR_ShutdownInternal(); - } - - @Override - public boolean isInitialized() { - return initSuccess; - } - - @Override - public void reset() { - if( vrsystemFunctions == null ) return; - vrsystemFunctions.ResetSeatedZeroPose.apply(); - hmdSeatToStand = null; - } - - @Override - public void getRenderSize(Vector2f store) { - if( vrsystemFunctions == null ) { - // 1344x1512 - store.x = 1344f; - store.y = 1512f; - } else { - IntByReference x = new IntByReference(); - IntByReference y = new IntByReference(); - vrsystemFunctions.GetRecommendedRenderTargetSize.apply(x, y); - store.x = x.getValue(); - store.y = y.getValue(); - } - } - /* - @Override - public float getFOV(int dir) { - float val = 0f; - if( vrsystemFunctions != null ) { - val = vrsystemFunctions.GetFloatTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, dir, hmdErrorStore); - } - // verification of number - if( val == 0f ) { - return 55f; - } else if( val <= 10f ) { - // most likely a radian number - return val * 57.2957795f; - } - return val; - } - */ - - @Override - public float getInterpupillaryDistance() { - if( vrsystemFunctions == null ) return 0.065f; - return vrsystemFunctions.GetFloatTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_UserIpdMeters_Float, hmdErrorStore); - } - - @Override - public Quaternion getOrientation() { - VRUtil.convertMatrix4toQuat(hmdPose, rotStore); - return rotStore; - } - - @Override - public Vector3f getPosition() { - // the hmdPose comes in rotated funny, fix that here - hmdPose.toTranslationVector(posStore); - posStore.x = -posStore.x; - posStore.z = -posStore.z; - return posStore; - } - - @Override - public void getPositionAndOrientation(Vector3f storePos, Quaternion storeRot) { - hmdPose.toTranslationVector(storePos); - storePos.x = -storePos.x; - storePos.z = -storePos.z; - storeRot.set(getOrientation()); - } - - @Override - public void updatePose(){ - if(vrsystemFunctions == null) return; - if(compositorFunctions != null) { - compositorFunctions.WaitGetPoses.apply(hmdTrackedDevicePoseReference, JOpenVRLibrary.k_unMaxTrackedDeviceCount, null, 0); - } else { - // wait - if( latencyWaitTime > 0 ) VRUtil.sleepNanos(latencyWaitTime); - - vrsystemFunctions.GetTimeSinceLastVsync.apply(tlastVsync, _tframeCount); - float fSecondsUntilPhotons = (float)timePerFrame - tlastVsync.getValue() + vsyncToPhotons; - - if( enableDebugLatency ) { - if( frames == 10 ) { - System.out.println("Waited (nanos): " + Long.toString(latencyWaitTime)); - System.out.println("Predict ahead time: " + Float.toString(fSecondsUntilPhotons)); - } - frames = (frames + 1) % 60; - } - - // handle skipping frame stuff - long nowCount = _tframeCount.getValue(); - if( nowCount - frameCount > 1 ) { - // skipped a frame! - if( enableDebugLatency ) System.out.println("Frame skipped!"); - frameCountRun = 0; - if( latencyWaitTime > 0 ) { - latencyWaitTime -= TimeUnit.MILLISECONDS.toNanos(1); - if( latencyWaitTime < 0 ) latencyWaitTime = 0; - } - } else if( latencyWaitTime < timePerFrame * 1000000000.0 ) { - // didn't skip a frame, lets try waiting longer to improve latency - frameCountRun++; - latencyWaitTime += Math.round(Math.pow(frameCountRun / 10.0, 2.0)); - } - - frameCount = nowCount; - - vrsystemFunctions.GetDeviceToAbsoluteTrackingPose.apply( - environment.isSeatedExperience()?JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseSeated: - JOpenVRLibrary.ETrackingUniverseOrigin.ETrackingUniverseOrigin_TrackingUniverseStanding, - fSecondsUntilPhotons, hmdTrackedDevicePoseReference, JOpenVRLibrary.k_unMaxTrackedDeviceCount); - } - - // deal with controllers being plugged in and out - // causing an invalid memory crash... skipping for now - /*boolean hasEvent = false; - while( JOpenVRLibrary.VR_IVRSystem_PollNextEvent(OpenVR.getVRSystemInstance(), tempEvent) != 0 ) { - // wait until the events are clear.. - hasEvent = true; - } - if( hasEvent ) { - // an event probably changed controller state - VRInput._updateConnectedControllers(); - }*/ - //update controllers pose information - environment.getVRinput().updateControllerStates(); - - // read pose data from native - for (int nDevice = 0; nDevice < JOpenVRLibrary.k_unMaxTrackedDeviceCount; ++nDevice ){ - hmdTrackedDevicePoses[nDevice].readField("bPoseIsValid"); - if( hmdTrackedDevicePoses[nDevice].bPoseIsValid != 0 ){ - hmdTrackedDevicePoses[nDevice].readField("mDeviceToAbsoluteTracking"); - convertSteamVRMatrix3ToMatrix4f(hmdTrackedDevicePoses[nDevice].mDeviceToAbsoluteTracking, poseMatrices[nDevice]); - } - } - - if ( hmdTrackedDevicePoses[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd].bPoseIsValid != 0 ){ - hmdPose.set(poseMatrices[JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd]); - } else { - hmdPose.set(Matrix4f.IDENTITY); - } - } - - @Override - public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam){ - if( hmdProjectionLeftEye != null ) { - return hmdProjectionLeftEye; - } else if(vrsystemFunctions == null){ - return cam.getProjectionMatrix(); - } else { - HmdMatrix44_t mat = vrsystemFunctions.GetProjectionMatrix.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, cam.getFrustumNear(), cam.getFrustumFar()); - hmdProjectionLeftEye = new Matrix4f(); - convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionLeftEye); - return hmdProjectionLeftEye; - } - } - - @Override - public Matrix4f getHMDMatrixProjectionRightEye(Camera cam){ - if( hmdProjectionRightEye != null ) { - return hmdProjectionRightEye; - } else if(vrsystemFunctions == null){ - return cam.getProjectionMatrix(); - } else { - HmdMatrix44_t mat = vrsystemFunctions.GetProjectionMatrix.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, cam.getFrustumNear(), cam.getFrustumFar()); - hmdProjectionRightEye = new Matrix4f(); - convertSteamVRMatrix4ToMatrix4f(mat, hmdProjectionRightEye); - return hmdProjectionRightEye; - } - } - - @Override - public Vector3f getHMDVectorPoseLeftEye() { - if( hmdPoseLeftEyeVec == null ) { - hmdPoseLeftEyeVec = getHMDMatrixPoseLeftEye().toTranslationVector(); - // set default IPD if none or broken - if( hmdPoseLeftEyeVec.x <= 0.080f * -0.5f || hmdPoseLeftEyeVec.x >= 0.040f * -0.5f ) { - hmdPoseLeftEyeVec.x = 0.065f * -0.5f; - } - if( flipEyes == false ) hmdPoseLeftEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseLeftEyeVec; - } - - @Override - public Vector3f getHMDVectorPoseRightEye() { - if( hmdPoseRightEyeVec == null ) { - hmdPoseRightEyeVec = getHMDMatrixPoseRightEye().toTranslationVector(); - // set default IPD if none or broken - if( hmdPoseRightEyeVec.x >= 0.080f * 0.5f || hmdPoseRightEyeVec.x <= 0.040f * 0.5f ) { - hmdPoseRightEyeVec.x = 0.065f * 0.5f; - } - if( flipEyes == false ) hmdPoseRightEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseRightEyeVec; - } - - @Override - public Vector3f getSeatedToAbsolutePosition() { - if( environment.isSeatedExperience() == false ) return Vector3f.ZERO; - if( hmdSeatToStand == null ) { - hmdSeatToStand = new Vector3f(); - HmdMatrix34_t mat = vrsystemFunctions.GetSeatedZeroPoseToStandingAbsoluteTrackingPose.apply(); - Matrix4f tempmat = new Matrix4f(); - convertSteamVRMatrix3ToMatrix4f(mat, tempmat); - tempmat.toTranslationVector(hmdSeatToStand); - } - return hmdSeatToStand; - } - - @Override - public Matrix4f getHMDMatrixPoseLeftEye(){ - if( hmdPoseLeftEye != null ) { - return hmdPoseLeftEye; - } else if(vrsystemFunctions == null) { - return Matrix4f.IDENTITY; - } else { - HmdMatrix34_t mat = vrsystemFunctions.GetEyeToHeadTransform.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left); - hmdPoseLeftEye = new Matrix4f(); - return convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseLeftEye); - } - } - - @Override - public HmdType getType() { - if( vrsystemFunctions != null ) { - Pointer str1 = new Memory(128); - Pointer str2 = new Memory(128); - String completeName = ""; - vrsystemFunctions.GetStringTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, - JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ManufacturerName_String, - str1, 128, hmdErrorStore); - if( hmdErrorStore.getValue() == 0 ) completeName += str1.getString(0); - vrsystemFunctions.GetStringTrackedDeviceProperty.apply(JOpenVRLibrary.k_unTrackedDeviceIndex_Hmd, - JOpenVRLibrary.ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ModelNumber_String, - str2, 128, hmdErrorStore); - if( hmdErrorStore.getValue() == 0 ) completeName += " " + str2.getString(0); - if( completeName.length() > 0 ) { - completeName = completeName.toLowerCase(Locale.ENGLISH).trim(); - if( completeName.contains("htc") || completeName.contains("vive") ) { - return HmdType.HTC_VIVE; - } else if ( completeName.contains("index") ) { - return HmdType.VALVE_INDEX; - } else if( completeName.contains("osvr") ) { - return HmdType.OSVR; - } else if( completeName.contains("oculus") || completeName.contains("rift") || - completeName.contains("dk1") || completeName.contains("dk2") || completeName.contains("cv1") ) { - return HmdType.OCULUS_RIFT; - } else if( completeName.contains("fove") ) { - return HmdType.FOVE; - } else if( completeName.contains("game") && completeName.contains("face") ) { - return HmdType.GAMEFACE; - } else if( completeName.contains("morpheus") ) { - return HmdType.MORPHEUS; - } else if( completeName.contains("gear") ) { - return HmdType.GEARVR; - } else if( completeName.contains("star") ) { - return HmdType.STARVR; - } else if( completeName.contains("null") ) { - return HmdType.NULL; - } - } - } else return HmdType.NONE; - return HmdType.OTHER; - } - - @Override - public Matrix4f getHMDMatrixPoseRightEye(){ - if( hmdPoseRightEye != null ) { - return hmdPoseRightEye; - } else if(vrsystemFunctions == null) { - return Matrix4f.IDENTITY; - } else { - HmdMatrix34_t mat = vrsystemFunctions.GetEyeToHeadTransform.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right); - hmdPoseRightEye = new Matrix4f(); - return convertSteamVRMatrix3ToMatrix4f(mat, hmdPoseRightEye); - } - } - -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRBounds.java b/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRBounds.java deleted file mode 100644 index b487a5f22f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRBounds.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jme3.input.vr.openvr; - -import com.jme3.input.vr.VRBounds; -import com.jme3.math.Vector2f; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.VR_IVRChaperone_FnTable; -import com.sun.jna.ptr.FloatByReference; - -import java.util.logging.Logger; - -/** - * A class that represents VR world bounds. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OpenVRBounds implements VRBounds { - - private static Logger logger = Logger.getLogger(OpenVRBounds.class.getName()); - - private VR_IVRChaperone_FnTable vrChaperone; - private Vector2f playSize; - - /** - * Initialize the VR bounds. - * @return true if the initialization is a success and false otherwise. - */ - public boolean init(OpenVR api) { - - logger.config("Initialize VR bounds..."); - - if( vrChaperone == null ) { - vrChaperone = new VR_IVRChaperone_FnTable(JOpenVRLibrary.VR_GetGenericInterface(JOpenVRLibrary.IVRChaperone_Version, api.hmdErrorStore).getPointer()); - if( vrChaperone != null ) { - vrChaperone.setAutoSynch(false); - vrChaperone.read(); - FloatByReference fbX = new FloatByReference(); - FloatByReference fbZ = new FloatByReference(); - vrChaperone.GetPlayAreaSize.apply(fbX, fbZ); - playSize = new Vector2f(fbX.getValue(), fbZ.getValue()); - - logger.config("Initialize VR bounds [SUCCESS]"); - return true; // init success - } - - logger.warning("Initialize VR bounds [FAILED]."); - return false; // failed to init - } - - logger.config("Initialize VR bounds already done."); - return true; // already initialized - } - - @Override - public Vector2f getPlaySize() { - return playSize; - } - -} - diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRInput.java deleted file mode 100644 index b1e2f27933..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRInput.java +++ /dev/null @@ -1,521 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.openvr; - -import java.util.ArrayList; -import java.util.List; -import java.util.logging.Level; -import java.util.logging.Logger; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRInputType; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.scene.Spatial; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.OpenVRUtil; -import com.jme3.system.jopenvr.VRControllerState_t; -import com.jme3.system.jopenvr.VR_IVRSystem_FnTable; -import com.jme3.util.VRUtil; - -/* -make helper functions to pull the following easily from raw data (DONE) - -trigger: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 1.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 8589934592 (when full), touch: 8589934592 - -touchpad (upper left): -Controller#1, Axis#0 X: -0.6059755, Y: 0.2301706 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 4294967296 (when pressed in), touch: 4294967296 - -grip: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 4, touch: 4 - -thumb: -Controller#1, Axis#0 X: 0.0, Y: 0.0 -Controller#1, Axis#1 X: 0.0, Y: 0.0 -Controller#1, Axis#2 X: 0.0, Y: 0.0 -Controller#1, Axis#3 X: 0.0, Y: 0.0 -Controller#1, Axis#4 X: 0.0, Y: 0.0 -Button press: 2, touch: 2 - -*/ - -/** - * A class that wraps an OpenVR input.
                  - * null values will be returned if no valid pose exists, or that input device isn't available - * user code should check for null values. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OpenVRInput implements VRInputAPI { - - private static final Logger logger = Logger.getLogger(OpenVRInput.class.getName()); - - private final VRControllerState_t[] cStates = new VRControllerState_t[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private final Quaternion[] rotStore = new Quaternion[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private final Vector3f[] posStore = new Vector3f[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private static final int[] controllerIndex = new int[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private int controllerCount = 0; - - private final Vector2f tempAxis = new Vector2f(), temp2Axis = new Vector2f(); - - private final Vector2f lastCallAxis[] = new Vector2f[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private final boolean needsNewVelocity[] = new boolean[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private final boolean needsNewAngVelocity[] = new boolean[JOpenVRLibrary.k_unMaxTrackedDeviceCount]; - - private final boolean buttonDown[][] = new boolean[JOpenVRLibrary.k_unMaxTrackedDeviceCount][16]; - - private float axisMultiplier = 1f; - - private final Vector3f tempVel = new Vector3f(); - - private final Quaternion tempq = new Quaternion(); - - private VREnvironment environment; - - private List trackedControllers = null; - - /** - * Create a new OpenVR input attached to the given VR environment. - * @param environment the VR environment to which the input is attached. - */ - public OpenVRInput(VREnvironment environment){ - this.environment = environment; - } - - @Override - public float getAxisMultiplier() { - return axisMultiplier; - } - - @Override - public void setAxisMultiplier(float set) { - axisMultiplier = set; - } - - @Override - public void swapHands() { - if( controllerCount != 2 ) return; - int temp = controllerIndex[0]; - controllerIndex[0] = controllerIndex[1]; - controllerIndex[1] = temp; - } - - @Override - public boolean isButtonDown(int controllerIndex, VRInputType checkButton) { - VRControllerState_t cs = cStates[OpenVRInput.controllerIndex[controllerIndex]]; - switch( checkButton ) { - default: - return false; - case ViveGripButton: - return (cs.ulButtonPressed & 4) != 0; - case ViveMenuButton: - return (cs.ulButtonPressed & 2) != 0; - case ViveTrackpadAxis: - return (cs.ulButtonPressed & 4294967296l) != 0; - case ViveTriggerAxis: - return (cs.ulButtonPressed & 8589934592l) != 0; - } - } - - @Override - public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton) { - boolean buttonDownNow = isButtonDown(controllerIndex, checkButton); - int checkButtonValue = checkButton.getValue(); - int cIndex = OpenVRInput.controllerIndex[controllerIndex]; - boolean retval = buttonDownNow == true && buttonDown[cIndex][checkButtonValue] == false; - buttonDown[cIndex][checkButtonValue] = buttonDownNow; - return retval; - } - - @Override - public void resetInputSinceLastCall() { - for(int i=0;i 0) && (index < trackedControllers.size())){ - return trackedControllers.get(index); - } - } - - return null; - } - - @Override - public int getTrackedControllerCount() { - return controllerCount; - } - - @Override - public VRControllerState_t getRawControllerState(int index) { - if( isInputDeviceTracking(index) == false ) return null; - return cStates[controllerIndex[index]]; - } - - //public Matrix4f getPoseForInputDevice(int index) { - // if( isInputDeviceTracking(index) == false ) return null; - // return OpenVR.poseMatrices[controllerIndex[index]]; - //} - - @Override - public boolean isInputFocused() { - - if (environment != null){ - return ((VR_IVRSystem_FnTable)environment.getVRHardware().getVRSystem()).IsInputAvailable.apply() == 0; - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public boolean isInputDeviceTracking(int index) { - if( index < 0 || index >= controllerCount ){ - return false; - } - - if (environment != null){ - - if (environment.getVRHardware() instanceof OpenVR){ - return ((OpenVR)environment.getVRHardware()).hmdTrackedDevicePoses[controllerIndex[index]].bPoseIsValid != 0; - } else { - throw new IllegalStateException("VR hardware "+environment.getVRHardware().getClass().getSimpleName()+" is not a subclass of "+OpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Quaternion getOrientation(int index) { - if( isInputDeviceTracking(index) == false ){ - return null; - } - - if (environment != null){ - - if (environment.getVRHardware() instanceof OpenVR){ - index = controllerIndex[index]; - VRUtil.convertMatrix4toQuat(((OpenVR)environment.getVRHardware()).poseMatrices[index], rotStore[index]); - return rotStore[index]; - } else { - throw new IllegalStateException("VR hardware "+environment.getVRHardware().getClass().getSimpleName()+" is not a subclass of "+OpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Vector3f getPosition(int index) { - if( isInputDeviceTracking(index) == false ){ - return null; - } - - if (environment != null){ - - if (environment.getVRHardware() instanceof OpenVR){ - // the hmdPose comes in rotated funny, fix that here - index = controllerIndex[index]; - ((OpenVR)environment.getVRHardware()).poseMatrices[index].toTranslationVector(posStore[index]); - posStore[index].x = -posStore[index].x; - posStore[index].z = -posStore[index].z; - return posStore[index]; - } else { - throw new IllegalStateException("VR hardware "+environment.getVRHardware().getClass().getSimpleName()+" is not a subclass of "+OpenVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - - - } - - @Override - public Quaternion getFinalObserverRotation(int index) { - - if (environment != null){ - OpenVRViewManager vrvm = (OpenVRViewManager)environment.getVRViewManager(); - - if (vrvm != null){ - if(isInputDeviceTracking(index) == false ){ - return null; - } - - Object obs = environment.getObserver(); - if( obs instanceof Camera ) { - tempq.set(((Camera)obs).getRotation()); - } else { - tempq.set(((Spatial)obs).getWorldRotation()); - } - - return tempq.multLocal(getOrientation(index)); - } else { - throw new IllegalStateException("VR environment has no valid view manager."); - } - - - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public Vector3f getFinalObserverPosition(int index) { - - if (environment != null){ - OpenVRViewManager vrvm = (OpenVRViewManager)environment.getVRViewManager(); - - if (vrvm != null){ - if(isInputDeviceTracking(index) == false ){ - return null; - } - Object obs = environment.getObserver(); - Vector3f pos = getPosition(index); - if( obs instanceof Camera ) { - ((Camera)obs).getRotation().mult(pos, pos); - return pos.addLocal(((Camera)obs).getLocation()); - } else { - ((Spatial)obs).getWorldRotation().mult(pos, pos); - return pos.addLocal(((Spatial)obs).getWorldTranslation()); - } - } else { - throw new IllegalStateException("VR environment has no valid view manager."); - } - - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public void triggerHapticPulse(int controllerIndex, float seconds) { - if( environment.isInVR() == false || isInputDeviceTracking(controllerIndex) == false ){ - return; - } - - // apparently only axis ID of 0 works - ((VR_IVRSystem_FnTable)environment.getVRHardware().getVRSystem()).TriggerHapticPulse.apply(OpenVRInput.controllerIndex[controllerIndex], - 0, (short)Math.round(3f * seconds / 1e-3f)); - } - - @Override - public void updateConnectedControllers() { - logger.config("Updating connected controllers."); - - if (environment != null){ - controllerCount = 0; - for(int i=0;i(JOpenVRLibrary.k_unMaxTrackedDeviceCount); - } - trackedControllers.add(new OpenVRTrackedController(i, this, controllerName, manufacturerName, environment)); - - // Send a Haptic pulse to the controller - triggerHapticPulse(controllerCount, 1.0f); - - controllerCount++; - logger.config(" Tracked controller "+(i+1)+"/"+JOpenVRLibrary.k_unMaxTrackedDeviceCount+" "+controllerName+" ("+manufacturerName+") attached."); - } else { - logger.config(" Controller "+(i+1)+"/"+JOpenVRLibrary.k_unMaxTrackedDeviceCount+" ignored."); - } - } - } else { - throw new IllegalStateException("VR input is not attached to a VR environment."); - } - } - - @Override - public void updateControllerStates() { - - if (environment != null){ - for(int i=0;ihttp://www.seinturier.fr - */ -public class OpenVRMouseManager extends AbstractVRMouseManager { - - private final int AVERAGE_AMNT = 4; - - private int avgCounter; - - private final float[] lastXmv = new float[AVERAGE_AMNT]; - - private final float[] lastYmv = new float[AVERAGE_AMNT]; - - /** - * Create a new VR mouse manager within the given {@link VREnvironment VR environment}. - * @param environment the VR environment of the mouse manager. - */ - public OpenVRMouseManager(VREnvironment environment){ - super(environment); - } - - - @Override - public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) { - - if (getVREnvironment() != null){ - if (getVREnvironment().getApplication() != null){ - // got a tracked controller to use as the "mouse" - if( getVREnvironment().isInVR() == false || - getVREnvironment().getVRinput() == null || - getVREnvironment().getVRinput().isInputDeviceTracking(inputIndex) == false ){ - return; - } - - Vector2f tpDelta; - // TODO option to use Touch joysticks - if( isThumbstickMode() ) { - tpDelta = getVREnvironment().getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis); - } else { - tpDelta = getVREnvironment().getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis); - } - - float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * getSpeedSensitivity(), getSpeedAcceleration()); - float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * getSpeedSensitivity(), getSpeedAcceleration()); - - if( tpDelta.x < 0f ){ - Xamount = -Xamount; - } - - if( tpDelta.y < 0f ){ - Yamount = -Yamount; - } - - Xamount *= getMouseMoveScale(); - Yamount *= getMouseMoveScale(); - - if( mouseListener != null ) { - if( tpDelta.x != 0f && mouseXName != null ) mouseListener.onAnalog(mouseXName, Xamount * 0.2f, tpf); - if( tpDelta.y != 0f && mouseYName != null ) mouseListener.onAnalog(mouseYName, Yamount * 0.2f, tpf); - } - - if( getVREnvironment().getApplication().getInputManager().isCursorVisible() ) { - int index = (avgCounter+1) % AVERAGE_AMNT; - lastXmv[index] = Xamount * 133f; - lastYmv[index] = Yamount * 133f; - cursorPos.x -= avg(lastXmv); - cursorPos.y -= avg(lastYmv); - Vector2f maxsize = getVREnvironment().getVRGUIManager().getCanvasSize(); - - if( cursorPos.x > maxsize.x ){ - cursorPos.x = maxsize.x; - } - - if( cursorPos.x < 0f ){ - cursorPos.x = 0f; - } - - if( cursorPos.y > maxsize.y ){ - cursorPos.y = maxsize.y; - } - - if( cursorPos.y < 0f ){ - cursorPos.y = 0f; - } - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private float avg(float[] arr) { - float amt = 0f; - for(float f : arr) amt += f; - return amt / arr.length; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRTrackedController.java b/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRTrackedController.java deleted file mode 100644 index ba9d06546e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRTrackedController.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jme3.input.vr.openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector3f; - -/** - * A controller that is tracked within the VR environment. Such a controller can provide its position within the VR space. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class OpenVRTrackedController implements VRTrackedController{ - - /** - * The index of the controller within the unserlying VR API. - */ - private int controllerIndex = -1; - - /** - * The underlying VRAPI. - */ - private OpenVRInput hardware = null; - - /** - * The name of the controller. - */ - private String name; - - private VREnvironment environment; - - /** - * Wrap a new VR tracked controller on an OpenVR system. - * @param controllerIndex the index of the controller within the underlying VR system. - * @param hardware the underlying VR system. - * @param name the name of the controller. - * @param manufacturer the manufacturer of the controller. - * @param environment the VR environment. - */ - public OpenVRTrackedController(int controllerIndex, OpenVRInput hardware, String name, String manufacturer, VREnvironment environment){ - this.controllerIndex = controllerIndex; - this.hardware = hardware; - - this.name = name; - this.manufacturer = manufacturer; - - this.environment = environment; - } - - /** - * The manufacturer of the controller. - */ - private String manufacturer; - - @Override - public Vector3f getPosition() { - if (hardware != null){ - return hardware.getPosition(controllerIndex); - } else { - throw new IllegalStateException("No underlying VR API."); - } - } - - @Override - public Quaternion getOrientation() { - if (hardware != null){ - return hardware.getOrientation(controllerIndex); - } else { - throw new IllegalStateException("No underlying VR API."); - } - } - - @Override - public Matrix4f getPose(){ - - if (environment != null){ - if (hardware != null){ - return ((OpenVR)environment.getVRHardware()).poseMatrices[controllerIndex]; - } else { - throw new IllegalStateException("No underlying VR API."); - } - } else { - throw new IllegalStateException("VR tracked device is not attached to any environment."); - } - - - } - - @Override - public String getControllerName() { - return name; - } - - @Override - public String getControllerManufacturer() { - return manufacturer; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRViewManager.java deleted file mode 100644 index f4bdc920ba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/openvr/OpenVRViewManager.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.openvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.AbstractVRViewManager; -import com.jme3.input.vr.VRAPI; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; -import com.jme3.system.jopenvr.DistortionCoordinates_t; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.OpenVRUtil; -import com.jme3.system.jopenvr.Texture_t; -import com.jme3.system.jopenvr.VRTextureBounds_t; -import com.jme3.system.jopenvr.VR_IVRSystem_FnTable; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; -import com.jme3.util.VRGUIPositioningMode; - -import java.util.Iterator; -import java.util.logging.Logger; - -/** - * A VR view manager based on OpenVR. This class enable to submit 3D views to the VR compositor. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OpenVRViewManager extends AbstractVRViewManager { - - private static final Logger logger = Logger.getLogger(OpenVRViewManager.class.getName()); - - // OpenVR values - private VRTextureBounds_t leftTextureBounds; - private Texture_t leftTextureType; - - private VRTextureBounds_t rightTextureBounds; - private Texture_t rightTextureType; - - private Texture2D dualEyeTex; - - //final & temp values for camera calculations - private final Vector3f finalPosition = new Vector3f(); - private final Quaternion finalRotation = new Quaternion(); - private final Vector3f hmdPos = new Vector3f(); - private final Quaternion hmdRot = new Quaternion(); - - /** - * Create a new VR view manager attached to the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} to which this view manager is attached. - */ - public OpenVRViewManager(VREnvironment environment){ - this.environment = environment; - } - - /** - * Get the identifier of the left eye texture. - * @return the identifier of the left eye texture. - * @see #getRightTexId() - * @see #getFullTexId() - */ - protected int getLeftTexId() { - return (int)getLeftTexture().getImage().getId(); - } - - /** - * Get the identifier of the right eye texture. - * @return the identifier of the right eye texture. - * @see #getLeftTexId() - * @see #getFullTexId() - */ - protected int getRightTexId() { - return (int)getRightTexture().getImage().getId(); - } - - /** - * Get the identifier of the full (dual eye) texture. - * @return the identifier of the full (dual eye) texture. - * @see #getLeftTexId() - * @see #getRightTexId() - */ - private int getFullTexId() { - return (int)dualEyeTex.getImage().getId(); - } - - /** - * Initialize the system binds of the textures. - */ - private void initTextureSubmitStructs() { - leftTextureType = new Texture_t(); - rightTextureType = new Texture_t(); - - if (environment != null){ - if( environment.getVRHardware() instanceof OpenVR ) { - leftTextureBounds = new VRTextureBounds_t(); - rightTextureBounds = new VRTextureBounds_t(); - // left eye - leftTextureBounds.uMax = 0.5f; - leftTextureBounds.uMin = 0f; - leftTextureBounds.vMax = 1f; - leftTextureBounds.vMin = 0f; - leftTextureBounds.setAutoSynch(false); - leftTextureBounds.setAutoRead(false); - leftTextureBounds.setAutoWrite(false); - leftTextureBounds.write(); - // right eye - rightTextureBounds.uMax = 1f; - rightTextureBounds.uMin = 0.5f; - rightTextureBounds.vMax = 1f; - rightTextureBounds.vMin = 0f; - rightTextureBounds.setAutoSynch(false); - rightTextureBounds.setAutoRead(false); - rightTextureBounds.setAutoWrite(false); - rightTextureBounds.write(); - // texture type - leftTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; - leftTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; - leftTextureType.setAutoSynch(false); - leftTextureType.setAutoRead(false); - leftTextureType.setAutoWrite(false); - leftTextureType.handle = -1; - rightTextureType.eColorSpace = JOpenVRLibrary.EColorSpace.EColorSpace_ColorSpace_Gamma; - rightTextureType.eType = JOpenVRLibrary.ETextureType.ETextureType_TextureType_OpenGL; - rightTextureType.setAutoSynch(false); - rightTextureType.setAutoRead(false); - rightTextureType.setAutoWrite(false); - rightTextureType.handle = -1; - - - logger.config("Init eyes native texture binds"); - logger.config(" Left eye texture"); - logger.config(" address: "+leftTextureType.getPointer()); - logger.config(" size: "+leftTextureType.size()+" bytes"); - logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); - logger.config(" type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); - logger.config(" auto read: "+leftTextureType.getAutoRead()); - logger.config(" auto write: "+leftTextureType.getAutoWrite()); - logger.config(" handle address: "+leftTextureType.handle); - logger.config(" handle value: "+leftTextureType.handle); - logger.config(""); - logger.config(" Right eye texture"); - logger.config(" address: "+rightTextureType.getPointer()); - logger.config(" size: "+rightTextureType.size()+" bytes"); - logger.config(" color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); - logger.config(" type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.config(" auto read: "+rightTextureType.getAutoRead()); - logger.config(" auto write: "+rightTextureType.getAutoWrite()); - logger.config(" handle address: "+rightTextureType.handle); - logger.config(" handle value: "+rightTextureType.handle); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - @Override - public void render() { - - } - - @Override - public void postRender() { - - if (environment != null){ - if( environment.isInVR() ) { - VRAPI api = environment.getVRHardware(); - if( api.getCompositor() != null ) { - // using the compositor... - int errl = 0, errr = 0; - if( environment.isInstanceRendering() ) { - if( leftTextureType.handle == -1 || leftTextureType.handle != getFullTexId() ) { - leftTextureType.handle = getFullTexId(); - if( leftTextureType.handle != -1 ) { - leftTextureType.write(); - } - } else { - if( api instanceof OpenVR ) { - int submitFlag = JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default; - errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, leftTextureType, rightTextureBounds, submitFlag); - errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, leftTextureBounds, submitFlag); - } - } - } else if( leftTextureType.handle == -1 || rightTextureType.handle == -1 || - leftTextureType.handle != getLeftTexId() || rightTextureType.handle != getRightTexId() ) { - leftTextureType.handle = getLeftTexId(); - if( leftTextureType.handle != -1 ) { - logger.fine("Writing Left texture to native memory at " + leftTextureType.getPointer()); - leftTextureType.write(); - } - rightTextureType.handle = getRightTexId(); - if( rightTextureType.handle != -1 ) { - logger.fine("Writing Right texture to native memory at " + leftTextureType.getPointer()); - rightTextureType.write(); - } - } else { - if( api instanceof OpenVR ) { - errl = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Left, leftTextureType, null, - JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); - errr = ((OpenVR)api).getCompositor().Submit.apply(JOpenVRLibrary.EVREye.EVREye_Eye_Right, rightTextureType, null, - JOpenVRLibrary.EVRSubmitFlags.EVRSubmitFlags_Submit_Default); - } else { - - } - } - - if( errl != 0 ){ - logger.severe("Submit to left compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); - logger.severe(" Texture handle: "+leftTextureType.handle); - - logger.severe(" Left eye texture "+leftEyeTexture.getName()+" ("+leftEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+leftEyeTexture.getType()); - logger.severe(" Size: "+leftEyeTexture.getImage().getWidth()+"x"+leftEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+leftEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+leftEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+leftEyeTexture.getImage().getColorSpace()); - - } - - if( errr != 0 ){ - logger.severe("Submit to right compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.severe(" Texture handle: "+rightTextureType.handle); - - logger.severe(" Right eye texture "+rightEyeTexture.getName()+" ("+rightEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+rightEyeTexture.getType()); - logger.severe(" Size: "+rightEyeTexture.getImage().getWidth()+"x"+rightEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+rightEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+rightEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+rightEyeTexture.getImage().getColorSpace()); - } - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - - } - - - @Override - public void initialize() { - - logger.config("Initializing VR view manager."); - - if (environment != null){ - - initTextureSubmitStructs(); - setupCamerasAndViews(); - setupVRScene(); - moveScreenProcessingToEyes(); - - if( environment.hasTraditionalGUIOverlay() ) { - - environment.getVRMouseManager().initialize(); - - // update the pose to position the gui correctly on start - update(0f); - environment.getVRGUIManager().positionGui(); - } - - logger.config("Initialized VR view manager [SUCCESS]"); - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Prepare the size of the given {@link Camera camera} to adapt it to the underlying rendering context. - * @param cam the {@link Camera camera} to prepare. - * @param xMult the camera width multiplier. - */ - private void prepareCameraSize(Camera cam, float xMult) { - - if (environment != null){ - - if (environment.getApplication() != null){ - Vector2f size = new Vector2f(); - VRAPI vrhmd = environment.getVRHardware(); - - if( vrhmd == null ) { - size.x = 1280f; - size.y = 720f; - } else { - vrhmd.getRenderSize(size); - } - - if( size.x < environment.getApplication().getContext().getSettings().getWidth() ) { - size.x = environment.getApplication().getContext().getSettings().getWidth(); - } - if( size.y < environment.getApplication().getContext().getSettings().getHeight() ) { - size.y = environment.getApplication().getContext().getSettings().getHeight(); - } - - if( environment.isInstanceRendering() ){ - size.x *= 2f; - } - - // other adjustments - size.x *= xMult; - size.x *= getResolutionMuliplier(); - size.y *= getResolutionMuliplier(); - - if( cam.getWidth() != size.x || cam.getHeight() != size.y ){ - cam.resize((int)size.x, (int)size.y, false); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - } - - /** - * Replaces rootNode as the main cameras scene with the distortion mesh - */ - private void setupVRScene(){ - - - if (environment != null){ - if (environment.getApplication() != null){ - // no special scene to setup if we are doing instancing - if( environment.isInstanceRendering() ) { - // distortion has to be done with compositor here... we want only one pass on our end! - if( environment.getApplication().getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(environment.getCamera(), dualEyeTex, true); - } - return; - } - - leftEyeTexture = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - rightEyeTexture = (Texture2D)getRightViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - leftEyeDepth = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - rightEyeDepth = (Texture2D)getRightViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - - // main viewport is either going to be a distortion scene or nothing - // mirroring is handled by copying framebuffers - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - environment.getApplication().getViewPort().detachScene(spatialIter.next()); - } - - spatialIter = environment.getApplication().getGuiViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - environment.getApplication().getGuiViewPort().detachScene(spatialIter.next()); - } - - // only setup distortion scene if compositor isn't running (or using custom mesh distortion option) - if( environment.getVRHardware().getCompositor() == null ) { - Node distortionScene = new Node(); - Material leftMat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - leftMat.setTexture("Texture", leftEyeTexture); - Geometry leftEye = new Geometry("box", setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Left, environment.getVRHardware())); - leftEye.setMaterial(leftMat); - distortionScene.attachChild(leftEye); - - Material rightMat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - rightMat.setTexture("Texture", rightEyeTexture); - Geometry rightEye = new Geometry("box", setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Right, environment.getVRHardware())); - rightEye.setMaterial(rightMat); - distortionScene.attachChild(rightEye); - - distortionScene.updateGeometricState(); - - environment.getApplication().getViewPort().attachScene(distortionScene); - - //if( useCustomDistortion ) setupFinalFullTexture(app.getViewPort().getCamera()); - } - - if( environment.getApplication().getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(environment.getCamera(), leftEyeTexture, false); - - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - @Override - public void update(float tpf) { - - if (environment != null){ - // grab the observer - Object obs = environment.getObserver(); - Quaternion objRot; - Vector3f objPos; - if( obs instanceof Camera ) { - objRot = ((Camera)obs).getRotation(); - objPos = ((Camera)obs).getLocation(); - } else { - objRot = ((Spatial)obs).getWorldRotation(); - objPos = ((Spatial)obs).getWorldTranslation(); - } - // grab the hardware handle - VRAPI dev = environment.getVRHardware(); - if( dev != null ) { - - - // update the HMD's position & orientation - dev.updatePose(); - dev.getPositionAndOrientation(hmdPos, hmdRot); -/* - // TOREMOVE - Vector3f v = dev.getVRinput().getTrackedController(0).getPosition(); - Quaternion q = dev.getVRinput().getTrackedController(0).getOrientation(); - if ((v != null)&&(q != null)){ - hmdPos.set(v); - hmdRot.set(q); - } - - logger.severe("HMD controller "); - logger.severe(" Position "+hmdPos); - logger.severe(" Orientation "+hmdRot); - - VRTrackedController tc = null; - for(int i = 0; i < dev.getVRinput().getTrackedControllerCount(); i++){ - tc = dev.getVRinput().getTrackedController(i); - logger.severe("Tracked controller "+i+": "+tc.getControllerName()); - logger.severe(" Position "+tc.getPosition()); - logger.severe(" Orientation "+tc.getOrientation()); - logger.severe(""); - } -*/ - // TOREMOVE - - if( obs != null ) { - // update hmdPos based on obs rotation - finalRotation.set(objRot); - finalRotation.mult(hmdPos, hmdPos); - finalRotation.multLocal(hmdRot); - } - - finalizeCamera(dev.getHMDVectorPoseLeftEye(), objPos, getLeftCamera()); - finalizeCamera(dev.getHMDVectorPoseRightEye(), objPos, getRightCamera()); - } else { - getLeftCamera().setFrame(objPos, objRot); - getRightCamera().setFrame(objPos, objRot); - } - - if( environment.hasTraditionalGUIOverlay() ) { - // update the mouse? - environment.getVRMouseManager().update(tpf); - - // update GUI position? - if( environment.getVRGUIManager().isWantsReposition() || environment.getVRGUIManager().getPositioningMode() != VRGUIPositioningMode.MANUAL ) { - environment.getVRGUIManager().positionGuiNow(tpf); - environment.getVRGUIManager().updateGuiQuadGeometricState(); - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Place the camera within the scene. - * @param eyePos the eye position. - * @param obsPosition the observer position. - * @param cam the camera to place. - */ - private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { - finalRotation.mult(eyePos, finalPosition); - finalPosition.addLocal(hmdPos); - if( obsPosition != null ) finalPosition.addLocal(obsPosition); - finalPosition.y += getHeightAdjustment(); - cam.setFrame(finalPosition, finalRotation); - } - - - private void setupCamerasAndViews() { - - if (environment != null){ - // get desired frustum from original camera - Camera origCam = environment.getCamera(); - float fFar = origCam.getFrustumFar(); - float fNear = origCam.getFrustumNear(); - - // restore frustum on distortion scene cam, if needed - if( environment.isInstanceRendering() ) { - leftCamera = origCam; - } else if( environment.compositorAllowed() == false ) { - origCam.setFrustumFar(100f); - origCam.setFrustumNear(1f); - leftCamera = origCam.clone(); - prepareCameraSize(origCam, 2f); - } else { - leftCamera = origCam.clone(); - } - - getLeftCamera().setFrustumPerspective(environment.getDefaultFOV(), environment.getDefaultAspect(), fNear, fFar); - - prepareCameraSize(getLeftCamera(), 1f); - if( environment.getVRHardware() != null ) { - getLeftCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionLeftEye(getLeftCamera())); - } - //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); - - if( !environment.isInstanceRendering()) { - leftViewPort = setupViewBuffers(getLeftCamera(), LEFT_VIEW_NAME); - rightCamera = getLeftCamera().clone(); - if( environment.getVRHardware() != null ){ - getRightCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(getRightCamera())); - } - rightViewPort = setupViewBuffers(getRightCamera(), RIGHT_VIEW_NAME); - } else { - - if (environment.getApplication() != null){ - - logger.severe("THIS CODE NEED CHANGES !!!"); - leftViewPort = environment.getApplication().getViewPort(); - //leftViewport.attachScene(app.getRootNode()); - rightCamera = getLeftCamera().clone(); - if( environment.getVRHardware() != null ){ - getRightCamera().setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(getRightCamera())); - } - - org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_CLIP_DISTANCE0); - - //FIXME: [jme-vr] Fix with JMonkey next release - //RenderManager._VRInstancing_RightCamProjection = camRight.getViewProjectionMatrix(); - setupFinalFullTexture(environment.getApplication().getViewPort().getCamera()); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - } - - // setup gui - environment.getVRGUIManager().setupGui(getLeftCamera(), getRightCamera(), getLeftViewPort(), getRightViewPort()); - - if( environment.getVRHardware() != null ) { - // call these to cache the results internally - environment.getVRHardware().getHMDMatrixPoseLeftEye(); - environment.getVRHardware().getHMDMatrixPoseRightEye(); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupMirrorBuffers(Camera cam, Texture tex, boolean expand) { - - if (environment != null){ - if (environment.getApplication() != null){ - Camera clonecam = cam.clone(); - ViewPort viewPort = environment.getApplication().getRenderManager().createPostView("MirrorView", clonecam); - clonecam.setParallelProjection(true); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - Picture pic = new Picture("fullscene"); - pic.setLocalTranslation(-0.75f, -0.5f, 0f); - if( expand ) { - pic.setLocalScale(3f, 1f, 1f); - } else { - pic.setLocalScale(1.5f, 1f, 1f); - } - pic.setQueueBucket(Bucket.Opaque); - pic.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, false); - viewPort.attachScene(pic); - viewPort.setOutputFrameBuffer(null); - - pic.updateGeometricState(); - - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private void setupFinalFullTexture(Camera cam) { - - if (environment != null){ - if (environment.getApplication() != null){ - // create offscreen framebuffer - FrameBuffer out = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBuffer.setSrgb(true); - - //setup framebuffer's texture - dualEyeTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - dualEyeTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - dualEyeTex.setMagFilter(Texture.MagFilter.Bilinear); - - logger.config("Dual eye texture "+dualEyeTex.getName()+" ("+dualEyeTex.getImage().getId()+")"); - logger.config(" Type: "+dualEyeTex.getType()); - logger.config(" Size: "+dualEyeTex.getImage().getWidth()+"x"+dualEyeTex.getImage().getHeight()); - logger.config(" Image depth: "+dualEyeTex.getImage().getDepth()); - logger.config(" Image format: "+dualEyeTex.getImage().getFormat()); - logger.config(" Image color space: "+dualEyeTex.getImage().getColorSpace()); - - //setup framebuffer to use texture - out.setDepthBuffer(Image.Format.Depth); - out.setColorTexture(dualEyeTex); - - ViewPort viewPort = environment.getApplication().getViewPort(); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - viewPort.setOutputFrameBuffer(out); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupViewBuffers(Camera cam, String viewName){ - - if (environment != null){ - if (environment.getApplication() != null){ - // create offscreen framebuffer - FrameBuffer offBufferLeft = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBufferLeft.setSrgb(true); - - //setup framebuffer's texture - Texture2D offTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - offTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - offTex.setMagFilter(Texture.MagFilter.Bilinear); - - //setup framebuffer to use texture - offBufferLeft.setDepthBuffer(Image.Format.Depth); - offBufferLeft.setColorTexture(offTex); - - ViewPort viewPort = environment.getApplication().getRenderManager().createPreView(viewName, cam); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - viewPort.attachScene(spatialIter.next()); - } - - //set viewport to render to offscreen framebuffer - viewPort.setOutputFrameBuffer(offBufferLeft); - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Setup a distortion mesh for the stereo view. - * @param eye the eye to apply. - * @param api the underlying VR api - * @return the distorted mesh. - */ - public static Mesh setupDistortionMesh(int eye, VRAPI api) { - Mesh distortionMesh = new Mesh(); - float m_iLensGridSegmentCountH = 43, m_iLensGridSegmentCountV = 43; - - float w = 1f / (m_iLensGridSegmentCountH - 1f); - float h = 1f / (m_iLensGridSegmentCountV - 1f); - - float u, v; - - float verts[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 3]; - - float texcoordR[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - float texcoordG[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - float texcoordB[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - - int vertPos = 0, coordPos = 0; - - float Xoffset = eye == JOpenVRLibrary.EVREye.EVREye_Eye_Left ? -1f : 0; - for (int y = 0; y < m_iLensGridSegmentCountV; y++) { - for (int x = 0; x < m_iLensGridSegmentCountH; x++) { - u = x * w; - v = 1 - y * h; - verts[vertPos] = Xoffset + u; // x - verts[vertPos + 1] = -1 + 2 * y * h; // y - verts[vertPos + 2] = 0f; // z - vertPos += 3; - - DistortionCoordinates_t dc0 = new DistortionCoordinates_t(); - if( api.getVRSystem() == null ) { - // default to no distortion - texcoordR[coordPos] = u; - texcoordR[coordPos + 1] = 1 - v; - texcoordG[coordPos] = u; - texcoordG[coordPos + 1] = 1 - v; - texcoordB[coordPos] = u; - texcoordB[coordPos + 1] = 1 - v; - } else { - ((VR_IVRSystem_FnTable)api.getVRSystem()).ComputeDistortion.apply(eye, u, v, dc0); - - texcoordR[coordPos] = dc0.rfRed[0]; - texcoordR[coordPos + 1] = 1 - dc0.rfRed[1]; - texcoordG[coordPos] = dc0.rfGreen[0]; - texcoordG[coordPos + 1] = 1 - dc0.rfGreen[1]; - texcoordB[coordPos] = dc0.rfBlue[0]; - texcoordB[coordPos + 1] = 1 - dc0.rfBlue[1]; - } - - coordPos += 2; - } - } - - // have UV coordinates & positions, now to setup indices - - int[] indices = new int[(int) ((m_iLensGridSegmentCountV - 1) * (m_iLensGridSegmentCountH - 1)) * 6]; - int indexPos = 0; - int a, b, c, d; - - int offset = 0; - for (int y = 0; y < m_iLensGridSegmentCountV - 1; y++) { - for (int x = 0; x < m_iLensGridSegmentCountH - 1; x++) { - a = (int) (m_iLensGridSegmentCountH * y + x + offset); - b = (int) (m_iLensGridSegmentCountH * y + x + 1 + offset); - c = (int) ((y + 1) * m_iLensGridSegmentCountH + x + 1 + offset); - d = (int) ((y + 1) * m_iLensGridSegmentCountH + x + offset); - - indices[indexPos] = a; - indices[indexPos + 1] = b; - indices[indexPos + 2] = c; - - indices[indexPos + 3] = a; - indices[indexPos + 4] = c; - indices[indexPos + 5] = d; - - indexPos += 6; - } - } - - // OK, create the mesh - distortionMesh.setBuffer(VertexBuffer.Type.Position, 3, verts); - distortionMesh.setBuffer(VertexBuffer.Type.Index, 1, indices); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, texcoordR); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, texcoordG); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord3, 2, texcoordB); - distortionMesh.setStatic(); - return distortionMesh; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVR.java b/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVR.java deleted file mode 100644 index 28d7aac069..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVR.java +++ /dev/null @@ -1,464 +0,0 @@ -/* - -https://github.com/sensics/OSVR-RenderManager/blob/master/examples/RenderManagerOpenGLCAPIExample.cpp - -- JVM crashes often.. placing breakpoints during initialization clears it up most of the time (WHY!?) - - OSVR is just unstable.. any way to improve things? -- render manager looks good, but left eye seems stretched - - */ -package com.jme3.input.vr.osvr; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.HmdType; -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary; -import com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary; -import com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig; -import com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_OpenResultsOpenGL; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderBufferOpenGL; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderInfoOpenGL; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderParams; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ViewportDescription; -import com.jme3.system.osvr.osvrrendermanageropengl.OsvrRenderManagerOpenGLLibrary; -import com.ochafik.lang.jnaerator.runtime.NativeSize; -import com.ochafik.lang.jnaerator.runtime.NativeSizeByReference; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.PointerByReference; -import java.nio.FloatBuffer; -import java.util.logging.Logger; - -/** - * A class that wraps an OSVR system. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OSVR implements VRAPI { - - private static final Logger logger = Logger.getLogger(OSVR.class.getName()); - - /** - * The first viewer index. - */ - public static final int FIRST_VIEWER = 0; - - /** - * The left eye index. - */ - public static final int EYE_LEFT = 0; - - /** - * The right eye index. - */ - public static final int EYE_RIGHT = 1; - - /** - * The size of the left eye. - */ - public static final NativeSize EYE_LEFT_SIZE = new NativeSize(EYE_LEFT); - - /** - * The size of the right eye. - */ - public static final NativeSize EYE_RIGHT_SIZE = new NativeSize(EYE_RIGHT); - - /** - * The default J String. - */ - public static byte[] defaultJString = { 'j', (byte)0 }; - - /** - * The default OpenGL String. - */ - public static byte[] OpenGLString = { 'O', 'p', 'e', 'n', 'G', 'L', (byte)0 }; - - private final Matrix4f[] eyeMatrix = new Matrix4f[2]; - - private PointerByReference grabRM; - private PointerByReference grabRMOGL; - private PointerByReference grabRIC; - - OSVR_RenderParams.ByValue renderParams; - OsvrClientKitLibrary.OSVR_ClientContext context; - com.jme3.system.osvr.osvrrendermanageropengl.OSVR_GraphicsLibraryOpenGL.ByValue graphicsLibrary; - Pointer renderManager, renderManagerOpenGL, renderInfoCollection, registerBufferState; - OSVRInput VRinput; - NativeSize numRenderInfo; - NativeSizeByReference grabNumInfo = new NativeSizeByReference(); - OSVR_RenderInfoOpenGL.ByValue eyeLeftInfo, eyeRightInfo; - Matrix4f hmdPoseLeftEye; - Matrix4f hmdPoseRightEye; - Vector3f hmdPoseLeftEyeVec, hmdPoseRightEyeVec, hmdSeatToStand; - OSVR_DisplayConfig displayConfig; - OSVR_Pose3 hmdPose = new OSVR_Pose3(); - Vector3f storePos = new Vector3f(); - Quaternion storeRot = new Quaternion(); - PointerByReference presentState = new PointerByReference(); - OSVR_OpenResultsOpenGL openResults = new OSVR_OpenResultsOpenGL(); - - long glfwContext; - long renderManagerContext; - long wglGLFW; - long wglRM; - - boolean initSuccess = false; - boolean flipEyes = false; - - private VREnvironment environment = null; - - /** - * Create a new OSVR system attached to the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} to which the input is attached. - */ - public OSVR(VREnvironment environment){ - this.environment = environment; - } - - /** - * Access to the underlying OSVR structures. - * @param leftView the left viewport. - * @param rightView the right viewport. - * @param leftBuffer the left buffer. - * @param rightBuffer the right buffer. - * @return true if the structure are accessible and false otherwise. - */ - public boolean handleRenderBufferPresent(OSVR_ViewportDescription.ByValue leftView, OSVR_ViewportDescription.ByValue rightView, - OSVR_RenderBufferOpenGL.ByValue leftBuffer, OSVR_RenderBufferOpenGL.ByValue rightBuffer) { - if( eyeLeftInfo == null || eyeRightInfo == null ) return false; - byte retval; - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerStartPresentRenderBuffers(presentState); - getEyeInfo(); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerPresentRenderBufferOpenGL(presentState.getValue(), leftBuffer, eyeLeftInfo, leftView); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerPresentRenderBufferOpenGL(presentState.getValue(), rightBuffer, eyeRightInfo, rightView); - retval = OsvrRenderManagerOpenGLLibrary.osvrRenderManagerFinishPresentRenderBuffers(renderManager, presentState.getValue(), renderParams, (byte)0); - return retval == 0; // only check the last error, since if something errored above, the last call won't work & all calls will log to syserr - } - - - - @Override - public boolean initialize() { - - logger.config("Initialize OSVR system."); - - hmdPose.setAutoSynch(false); - context = OsvrClientKitLibrary.osvrClientInit(defaultJString, 0); - VRinput = new OSVRInput(environment); - initSuccess = context != null && VRinput.init(); - if( initSuccess ) { - PointerByReference grabDisplay = new PointerByReference(); - byte retval = OsvrDisplayLibrary.osvrClientGetDisplay(context, grabDisplay); - if( retval != 0 ) { - System.out.println("OSVR Get Display Error: " + retval); - initSuccess = false; - return false; - } - displayConfig = new OSVR_DisplayConfig(grabDisplay.getValue()); - System.out.println("Waiting for the display to fully start up, including receiving initial pose update..."); - int i = 400; - while (OsvrDisplayLibrary.osvrClientCheckDisplayStartup(displayConfig) != 0) { - if( i-- < 0 ) { - System.out.println("Couldn't get display startup update in time, continuing anyway..."); - break; - } - OsvrClientKitLibrary.osvrClientUpdate(context); - try { - Thread.sleep(5); - } catch(Exception e) { } - } - System.out.println("OK, display startup status is good!"); - } - return initSuccess; - } - - - /** - * Grab the current GLFW context. - */ - public void grabGLFWContext() { - // get current conext - wglGLFW = org.lwjgl.opengl.WGL.wglGetCurrentContext(); - glfwContext = org.lwjgl.glfw.GLFW.glfwGetCurrentContext(); - } - - /** - * Enable context sharing. - * @return true if the context is successfully shared and false otherwise. - */ - public boolean shareContext() { - if( org.lwjgl.opengl.WGL.wglShareLists(wglRM, wglGLFW)) { - System.out.println("Context sharing success!"); - return true; - } else { - System.out.println("Context sharing problem..."); - return false; - } - } - - @Override - public boolean initVRCompositor(boolean allowed) { - if( !allowed || renderManager != null ) return false; - grabGLFWContext(); - graphicsLibrary = new com.jme3.system.osvr.osvrrendermanageropengl.OSVR_GraphicsLibraryOpenGL.ByValue(); - graphicsLibrary.toolkit = null; - graphicsLibrary.setAutoSynch(false); - grabRM = new PointerByReference(); grabRMOGL = new PointerByReference(); - byte retval = OsvrRenderManagerOpenGLLibrary.osvrCreateRenderManagerOpenGL(context, OpenGLString, graphicsLibrary, grabRM, grabRMOGL); - if( retval == 0 ) { - renderManager = grabRM.getValue(); renderManagerOpenGL = grabRMOGL.getValue(); - if( renderManager == null || renderManagerOpenGL == null ) { - System.out.println("Render Manager Created NULL, error!"); - return false; - } - openResults.setAutoSynch(false); - retval = OsvrRenderManagerOpenGLLibrary.osvrRenderManagerOpenDisplayOpenGL(renderManager, openResults); - if( retval == 0 ) { - wglRM = org.lwjgl.opengl.WGL.wglGetCurrentContext(); - renderManagerContext = org.lwjgl.glfw.GLFW.glfwGetCurrentContext(); - shareContext(); - OsvrClientKitLibrary.osvrClientUpdate(context); - renderParams = new OSVR_RenderParams.ByValue(); - renderParams.setAutoSynch(false); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerGetDefaultRenderParams(renderParams); - grabRIC = new PointerByReference(); - retval = OsvrRenderManagerOpenGLLibrary.osvrRenderManagerGetRenderInfoCollection(renderManager, renderParams, grabRIC); - if( retval == 0 ) { - renderInfoCollection = grabRIC.getValue(); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerGetNumRenderInfoInCollection(renderInfoCollection, grabNumInfo); - numRenderInfo = grabNumInfo.getValue(); - eyeLeftInfo = new OSVR_RenderInfoOpenGL.ByValue(); - eyeRightInfo = new OSVR_RenderInfoOpenGL.ByValue(); - eyeLeftInfo.setAutoSynch(false); - eyeRightInfo.setAutoSynch(false); - return true; - } - OsvrRenderManagerOpenGLLibrary.osvrDestroyRenderManager(renderManager); - System.out.println("OSVR Render Manager Info Collection Error: " + retval); - return false; - } - OsvrRenderManagerOpenGLLibrary.osvrDestroyRenderManager(renderManager); - System.out.println("OSVR Open Render Manager Display Error: " + retval); - return false; - } - System.out.println("OSVR Create Render Manager Error: " + retval); - return false; - } - - @Override - public OsvrClientKitLibrary.OSVR_ClientContext getVRSystem() { - return context; - } - - @Override - public Pointer getCompositor() { - return renderManager; - } - - @Override - public String getName() { - return "OSVR"; - } - - @Override - public VRInputAPI getVRinput() { - return VRinput; - } - - @Override - public void setFlipEyes(boolean set) { - flipEyes = set; - } - - @Override - public void printLatencyInfoToConsole(boolean set) { - - } - - @Override - public int getDisplayFrequency() { - return 60; //debug display frequency - } - - @Override - public void destroy() { - if( renderManager != null ) OsvrRenderManagerOpenGLLibrary.osvrDestroyRenderManager(renderManager); - if( displayConfig != null ) OsvrDisplayLibrary.osvrClientFreeDisplay(displayConfig); - } - - @Override - public boolean isInitialized() { - return initSuccess; - } - - @Override - public void reset() { - // TODO: no native OSVR reset function - // may need to take current position and negate it from future values - } - - @Override - public void getRenderSize(Vector2f store) { - if( eyeLeftInfo == null || eyeLeftInfo.viewport.width == 0.0 ) { - store.x = 1280f; store.y = 720f; - } else { - store.x = (float)eyeLeftInfo.viewport.width; - store.y = (float)eyeLeftInfo.viewport.height; - } - } - - /** - * Read and update the eye info from the underlying OSVR system. - */ - public void getEyeInfo() { - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerGetRenderInfoFromCollectionOpenGL(renderInfoCollection, EYE_LEFT_SIZE, eyeLeftInfo); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerGetRenderInfoFromCollectionOpenGL(renderInfoCollection, EYE_RIGHT_SIZE, eyeRightInfo); - eyeLeftInfo.read(); eyeRightInfo.read(); - } -/* - @Override - public float getFOV(int dir) { - return 105f; //default FOV - } -*/ - @Override - public float getInterpupillaryDistance() { - return 0.065f; //default IPD - } - - @Override - public Quaternion getOrientation() { - storeRot.set((float)-hmdPose.rotation.data[1], - (float)hmdPose.rotation.data[2], - (float)-hmdPose.rotation.data[3], - (float)hmdPose.rotation.data[0]); - if( storeRot.equals(Quaternion.ZERO) ) storeRot.set(Quaternion.DIRECTION_Z); - return storeRot; - } - - @Override - public Vector3f getPosition() { - storePos.x = (float)-hmdPose.translation.data[0]; - storePos.y = (float)hmdPose.translation.data[1]; - storePos.z = (float)-hmdPose.translation.data[2]; - return storePos; - } - - @Override - public void getPositionAndOrientation(Vector3f storePos, Quaternion storeRot) { - storePos.x = (float)-hmdPose.translation.data[0]; - storePos.y = (float)hmdPose.translation.data[1]; - storePos.z = (float)-hmdPose.translation.data[2]; - storeRot.set((float)-hmdPose.rotation.data[1], - (float)hmdPose.rotation.data[2], - (float)-hmdPose.rotation.data[3], - (float)hmdPose.rotation.data[0]); - if( storeRot.equals(Quaternion.ZERO) ) storeRot.set(Quaternion.DIRECTION_Z); - } - - @Override - public void updatePose() { - if( context == null || displayConfig == null ) return; - OsvrClientKitLibrary.osvrClientUpdate(context); - OsvrDisplayLibrary.osvrClientGetViewerPose(displayConfig, FIRST_VIEWER, hmdPose.getPointer()); - VRinput.updateControllerStates(); - hmdPose.read(); - } - - @Override - public Matrix4f getHMDMatrixProjectionLeftEye(Camera cam) { - if( eyeLeftInfo == null ) return cam.getProjectionMatrix(); - if( eyeMatrix[EYE_LEFT] == null ) { - FloatBuffer tfb = FloatBuffer.allocate(16); - com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.osvrClientGetViewerEyeSurfaceProjectionMatrixf(displayConfig, 0, (byte)EYE_LEFT, 0, cam.getFrustumNear(), cam.getFrustumFar(), (short)0, tfb); - eyeMatrix[EYE_LEFT] = new Matrix4f(); - eyeMatrix[EYE_LEFT].set(tfb.get(0), tfb.get(4), tfb.get(8), tfb.get(12), - tfb.get(1), tfb.get(5), tfb.get(9), tfb.get(13), - tfb.get(2), tfb.get(6), tfb.get(10), tfb.get(14), - tfb.get(3), tfb.get(7), tfb.get(11), tfb.get(15)); - } - return eyeMatrix[EYE_LEFT]; - } - - @Override - public Matrix4f getHMDMatrixProjectionRightEye(Camera cam) { - if( eyeRightInfo == null ) return cam.getProjectionMatrix(); - if( eyeMatrix[EYE_RIGHT] == null ) { - FloatBuffer tfb = FloatBuffer.allocate(16); - com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.osvrClientGetViewerEyeSurfaceProjectionMatrixf(displayConfig, 0, (byte)EYE_RIGHT, 0, cam.getFrustumNear(), cam.getFrustumFar(), (short)0, tfb); - eyeMatrix[EYE_RIGHT] = new Matrix4f(); - eyeMatrix[EYE_RIGHT].set(tfb.get(0), tfb.get(4), tfb.get(8), tfb.get(12), - tfb.get(1), tfb.get(5), tfb.get(9), tfb.get(13), - tfb.get(2), tfb.get(6), tfb.get(10), tfb.get(14), - tfb.get(3), tfb.get(7), tfb.get(11), tfb.get(15)); - } - return eyeMatrix[EYE_RIGHT]; - } - - @Override - public Vector3f getHMDVectorPoseLeftEye() { - if( hmdPoseLeftEyeVec == null ) { - hmdPoseLeftEyeVec = new Vector3f(); - hmdPoseLeftEyeVec.x = 0.065f * -0.5f; - if( flipEyes == false ) hmdPoseLeftEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseLeftEyeVec; - } - - @Override - public Vector3f getHMDVectorPoseRightEye() { - if( hmdPoseRightEyeVec == null ) { - hmdPoseRightEyeVec = new Vector3f(); - hmdPoseRightEyeVec.x = 0.065f * 0.5f; - if( flipEyes == false ) hmdPoseRightEyeVec.x *= -1f; // it seems these need flipping - } - return hmdPoseRightEyeVec; - } - - @Override - public Vector3f getSeatedToAbsolutePosition() { - return Vector3f.ZERO; - } - - @Override - public Matrix4f getHMDMatrixPoseLeftEye() { - // not actually used internally... - /*if( hmdPoseLeftEye != null ) { - return hmdPoseLeftEye; - } else { - FloatBuffer mat = FloatBuffer.allocate(16); - OsvrDisplayLibrary.osvrClientGetViewerEyeViewMatrixf(displayConfig, FIRST_VIEWER, (byte)EYE_LEFT, - (short)(OsvrMatrixConventionsLibrary.OSVR_MatrixVectorFlags.OSVR_MATRIX_COLVECTORS | - OsvrMatrixConventionsLibrary.OSVR_MatrixOrderingFlags.OSVR_MATRIX_COLMAJOR), tempfb); - hmdPoseLeftEye = new Matrix4f(tempfb.array()); - return hmdPoseLeftEye; - }*/ - return null; - } - - @Override - public Matrix4f getHMDMatrixPoseRightEye() { - // not actually used internally... - /*if( hmdPoseRightEye != null ) { - return hmdPoseRightEye; - } else { - OsvrDisplayLibrary.osvrClientGetViewerEyeViewMatrixf(displayConfig, FIRST_VIEWER, (byte)EYE_RIGHT, - (short)(OsvrMatrixConventionsLibrary.OSVR_MatrixVectorFlags.OSVR_MATRIX_COLVECTORS | - OsvrMatrixConventionsLibrary.OSVR_MatrixOrderingFlags.OSVR_MATRIX_COLMAJOR), tempfb); - hmdPoseRightEye = new Matrix4f(tempfb.array()); - return hmdPoseRightEye; - }*/ - return null; - } - - @Override - public HmdType getType() { - return HmdType.OSVR; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRInput.java b/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRInput.java deleted file mode 100644 index ab18f62888..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRInput.java +++ /dev/null @@ -1,358 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.input.vr.osvr; - -import java.util.logging.Logger; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.VRInputAPI; -import com.jme3.input.vr.VRInputType; -import com.jme3.input.vr.VRTrackedController; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.scene.Spatial; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientInterface; -import com.jme3.system.osvr.osvrclientreporttypes.OSVR_AnalogReport; -import com.jme3.system.osvr.osvrclientreporttypes.OSVR_ButtonReport; -import com.jme3.system.osvr.osvrclientreporttypes.OSVR_Pose3; -import com.jme3.system.osvr.osvrinterface.OsvrInterfaceLibrary; -import com.jme3.system.osvr.osvrtimevalue.OSVR_TimeValue; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.PointerByReference; - - -/** - * A class that wraps an OSVR input. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class OSVRInput implements VRInputAPI { - - private static final Logger logger = Logger.getLogger(OSVRInput.class.getName()); - - // position example: https://github.com/OSVR/OSVR-Core/blob/master/examples/clients/TrackerState.c - // button example: https://github.com/OSVR/OSVR-Core/blob/master/examples/clients/ButtonCallback.c - // analog example: https://github.com/OSVR/OSVR-Core/blob/master/examples/clients/AnalogCallback.c - - private static final int ANALOG_COUNT = 3, BUTTON_COUNT = 7, CHANNEL_COUNT = 3; - - OSVR_ClientInterface[][] buttons; - OSVR_ClientInterface[][][] analogs; - OSVR_ClientInterface[] hands; - - OSVR_Pose3[] handState; - Callback buttonHandler, analogHandler; - OSVR_TimeValue tv = new OSVR_TimeValue(); - boolean[] isHandTracked = new boolean[2]; - - private float[][][] analogState; - private float[][] buttonState; - - private final Quaternion tempq = new Quaternion(); - private final Vector3f tempv = new Vector3f(); - private final Vector2f temp2 = new Vector2f(); - private final boolean[][] buttonDown = new boolean[16][16]; - - private static final Vector2f temp2Axis = new Vector2f(); - private static final Vector2f lastCallAxis[] = new Vector2f[16]; - private static float axisMultiplier = 1f; - - private VREnvironment environment = null; - - /** - * Get the system String that identifies a controller. - * @param left is the controller is the left one (false if the right controller is needed). - * @param index the index of the controller. - * @return the system String that identifies the controller. - */ - public static byte[] getButtonString(boolean left, byte index) { - if( left ) { - return new byte[] { '/', 'c', 'o', 'n', 't', 'r', 'o', 'l', 'l', 'e', 'r', '/', 'l', 'e', 'f', 't', '/', index, (byte)0 }; - } - return new byte[] { '/', 'c', 'o', 'n', 't', 'r', 'o', 'l', 'l', 'e', 'r', '/', 'r', 'i', 'g', 'h', 't', '/', index, (byte)0 }; - } - - /** - * The left hand system String. - */ - public static byte[] leftHand = { '/', 'm', 'e', '/', 'h', 'a', 'n', 'd', 's', '/', 'l', 'e', 'f', 't', (byte)0 }; - - /** - * The right hand system String. - */ - public static byte[] rightHand = { '/', 'm', 'e', '/', 'h', 'a', 'n', 'd', 's', '/', 'r', 'i', 'g', 'h', 't', (byte)0 }; - - - /** - * Create a new OSVR input attached to the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} to which the input is attached. - */ - public OSVRInput(VREnvironment environment){ - this.environment = environment; - } - - - @Override - public boolean isButtonDown(int controllerIndex, VRInputType checkButton) { - return buttonState[controllerIndex][checkButton.getValue()] != 0f; - } - - @Override - public boolean wasButtonPressedSinceLastCall(int controllerIndex, VRInputType checkButton) { - boolean buttonDownNow = isButtonDown(controllerIndex, checkButton); - int checkButtonValue = checkButton.getValue(); - boolean retval = buttonDownNow == true && buttonDown[controllerIndex][checkButtonValue] == false; - buttonDown[controllerIndex][checkButtonValue] = buttonDownNow; - return retval; - } - - @Override - public void resetInputSinceLastCall() { - for(int i=0;ihttp://www.seinturier.fr - */ -public class OSVRMouseManager extends AbstractVRMouseManager { - - private final int AVERAGE_AMNT = 4; - - private int avgCounter; - - private final float[] lastXmv = new float[AVERAGE_AMNT]; - - private final float[] lastYmv = new float[AVERAGE_AMNT]; - - /** - * Create a new VR mouse manager within the given {@link VREnvironment VR environment}. - * @param environment the VR environment of the mouse manager. - */ - public OSVRMouseManager(VREnvironment environment){ - super(environment); - } - - - @Override - public void updateAnalogAsMouse(int inputIndex, AnalogListener mouseListener, String mouseXName, String mouseYName, float tpf) { - - if (getVREnvironment() != null){ - if (getVREnvironment().getApplication() != null){ - // got a tracked controller to use as the "mouse" - if( getVREnvironment().isInVR() == false || - getVREnvironment().getVRinput() == null || - getVREnvironment().getVRinput().isInputDeviceTracking(inputIndex) == false ){ - return; - } - - Vector2f tpDelta; - // TODO option to use Touch joysticks - if( isThumbstickMode() ) { - tpDelta = getVREnvironment().getVRinput().getAxis(inputIndex, VRInputType.ViveTrackpadAxis); - } else { - tpDelta = getVREnvironment().getVRinput().getAxisDeltaSinceLastCall(inputIndex, VRInputType.ViveTrackpadAxis); - } - - float Xamount = (float)Math.pow(Math.abs(tpDelta.x) * getSpeedSensitivity(), getSpeedAcceleration()); - float Yamount = (float)Math.pow(Math.abs(tpDelta.y) * getSpeedSensitivity(), getSpeedAcceleration()); - - if( tpDelta.x < 0f ){ - Xamount = -Xamount; - } - - if( tpDelta.y < 0f ){ - Yamount = -Yamount; - } - - Xamount *= getMouseMoveScale(); - Yamount *= getMouseMoveScale(); - - if( mouseListener != null ) { - if( tpDelta.x != 0f && mouseXName != null ) mouseListener.onAnalog(mouseXName, Xamount * 0.2f, tpf); - if( tpDelta.y != 0f && mouseYName != null ) mouseListener.onAnalog(mouseYName, Yamount * 0.2f, tpf); - } - - if( getVREnvironment().getApplication().getInputManager().isCursorVisible() ) { - int index = (avgCounter+1) % AVERAGE_AMNT; - lastXmv[index] = Xamount * 133f; - lastYmv[index] = Yamount * 133f; - cursorPos.x -= avg(lastXmv); - cursorPos.y -= avg(lastYmv); - Vector2f maxsize = getVREnvironment().getVRGUIManager().getCanvasSize(); - - if( cursorPos.x > maxsize.x ){ - cursorPos.x = maxsize.x; - } - - if( cursorPos.x < 0f ){ - cursorPos.x = 0f; - } - - if( cursorPos.y > maxsize.y ){ - cursorPos.y = maxsize.y; - } - - if( cursorPos.y < 0f ){ - cursorPos.y = 0f; - } - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private float avg(float[] arr) { - float amt = 0f; - for(float f : arr) amt += f; - return amt / arr.length; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRViewManager.java b/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRViewManager.java deleted file mode 100644 index 12eef2ca91..0000000000 --- a/jme3-vr/src/main/java/com/jme3/input/vr/osvr/OSVRViewManager.java +++ /dev/null @@ -1,881 +0,0 @@ -package com.jme3.input.vr.osvr; - -import java.awt.GraphicsEnvironment; -import java.util.Iterator; -import java.util.logging.Logger; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.AbstractVRViewManager; -import com.jme3.input.vr.VRAPI; -import com.jme3.input.vr.openvr.OpenVRViewManager; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.post.CartoonSSAO; -import com.jme3.post.Filter; -import com.jme3.post.FilterPostProcessor; -import com.jme3.post.FilterUtil; -import com.jme3.post.SceneProcessor; -import com.jme3.post.filters.FogFilter; -import com.jme3.post.filters.TranslucentBucketFilter; -import com.jme3.post.ssao.SSAOFilter; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Geometry; -import com.jme3.scene.Mesh; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; -import com.jme3.scene.VertexBuffer; -import com.jme3.shadow.DirectionalLightShadowFilter; -import com.jme3.shadow.VRDirectionalLightShadowRenderer; -import com.jme3.system.jopenvr.DistortionCoordinates_t; -import com.jme3.system.jopenvr.JOpenVRLibrary; -import com.jme3.system.jopenvr.OpenVRUtil; -import com.jme3.system.jopenvr.Texture_t; -import com.jme3.system.jopenvr.VR_IVRSystem_FnTable; -import com.jme3.system.lwjgl.LwjglWindow; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderBufferOpenGL; -import com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ViewportDescription; -import com.jme3.system.osvr.osvrrendermanageropengl.OsvrRenderManagerOpenGLLibrary; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; -import com.jme3.util.VRGUIPositioningMode; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.PointerByReference; - -/** - * - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class OSVRViewManager extends AbstractVRViewManager{ - private static final Logger logger = Logger.getLogger(OpenVRViewManager.class.getName()); - - // OpenVR values - private Texture_t leftTextureType; - private Texture_t rightTextureType; - - // OSVR values - OSVR_RenderBufferOpenGL.ByValue[] osvr_renderBuffer; - OSVR_ViewportDescription.ByValue osvr_viewDescFull; - OSVR_ViewportDescription.ByValue osvr_viewDescLeft; - OSVR_ViewportDescription.ByValue osvr_viewDescRight; - Pointer osvr_rmBufferState; - - private Texture2D dualEyeTex; - - private final PointerByReference grabRBS = new PointerByReference(); - - //final & temp values for camera calculations - private final Vector3f finalPosition = new Vector3f(); - private final Quaternion finalRotation = new Quaternion(); - private final Vector3f hmdPos = new Vector3f(); - private final Quaternion hmdRot = new Quaternion(); - - /** - * Create a new VR view manager attached to the given {@link VREnvironment VR environment}. - * @param environment the {@link VREnvironment VR environment} to which this view manager is attached. - */ - public OSVRViewManager(VREnvironment environment){ - this.environment = environment; - } - - /** - * Get the identifier of the left eye texture. - * @return the identifier of the left eye texture. - * @see #getRightTexId() - * @see #getFullTexId() - */ - protected int getLeftTexId() { - return (int)leftEyeTexture.getImage().getId(); - } - - /** - * Get the identifier of the right eye texture. - * @return the identifier of the right eye texture. - * @see #getLeftTexId() - * @see #getFullTexId() - */ - protected int getRightTexId() { - return (int)rightEyeTexture.getImage().getId(); - } - - /** - * Get the identifier of the full (dual eye) texture. - * @return the identifier of the full (dual eye) texture. - * @see #getLeftTexId() - * @see #getRightTexId() - */ - private int getFullTexId() { - return (int)dualEyeTex.getImage().getId(); - } - - /** - * Initialize the system binds of the textures. - */ - private void initTextureSubmitStructs() { - leftTextureType = new Texture_t(); - rightTextureType = new Texture_t(); - - // must be OSVR - osvr_renderBuffer = new OSVR_RenderBufferOpenGL.ByValue[2]; - osvr_renderBuffer[OSVR.EYE_LEFT] = new OSVR_RenderBufferOpenGL.ByValue(); - osvr_renderBuffer[OSVR.EYE_RIGHT] = new OSVR_RenderBufferOpenGL.ByValue(); - osvr_renderBuffer[OSVR.EYE_LEFT].setAutoSynch(false); - osvr_renderBuffer[OSVR.EYE_RIGHT].setAutoSynch(false); - osvr_viewDescFull = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescFull.setAutoSynch(false); - osvr_viewDescFull.left = osvr_viewDescFull.lower = 0.0; - osvr_viewDescFull.width = osvr_viewDescFull.height = 1.0; - osvr_viewDescLeft = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescLeft.setAutoSynch(false); - osvr_viewDescLeft.left = osvr_viewDescLeft.lower = 0.0; - osvr_viewDescLeft.width = 0.5; - osvr_viewDescLeft.height = 1.0; - osvr_viewDescRight = new OSVR_ViewportDescription.ByValue(); - osvr_viewDescRight.setAutoSynch(false); - osvr_viewDescRight.left = 0.5; - osvr_viewDescRight.lower = 0.0; - osvr_viewDescRight.width = 0.5; - osvr_viewDescRight.height = 1.0; - osvr_viewDescRight.write(); - osvr_viewDescLeft.write(); - osvr_viewDescFull.write(); - osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = -1; - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = -1; - osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = -1; - osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = -1; - } - - /** - * Register the OSVR OpenGL buffer. - * @param buf the OSVR OpenGL buffer. - */ - private void registerOSVRBuffer(OSVR_RenderBufferOpenGL.ByValue buf) { - - if (environment != null){ - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerStartRegisterRenderBuffers(grabRBS); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerRegisterRenderBufferOpenGL(grabRBS.getValue(), buf); - OsvrRenderManagerOpenGLLibrary.osvrRenderManagerFinishRegisterRenderBuffers(((OSVR)environment.getVRHardware()).getCompositor(), grabRBS.getValue(), (byte)0); - - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Send the textures to the two eyes. - */ - public void postRender() { - - if (environment != null){ - if( environment.isInVR() ) { - VRAPI api = environment.getVRHardware(); - if( api.getCompositor() != null ) { - // using the compositor... - int errl = 0, errr = 0; - if( environment.isInstanceRendering() ) { - if( leftTextureType.handle == -1 || leftTextureType.handle != getFullTexId() ) { - leftTextureType.handle = getFullTexId(); - if( leftTextureType.handle != -1 ) { - leftTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; - osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = dualEyeTex.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_LEFT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - } else { - if( api instanceof OSVR ) { - ((OSVR)api).handleRenderBufferPresent(osvr_viewDescLeft, osvr_viewDescRight, - osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - } else if( leftTextureType.handle == -1 || rightTextureType.handle == -1 || - leftTextureType.handle != getLeftTexId() || rightTextureType.handle != getRightTexId() ) { - leftTextureType.handle = getLeftTexId(); - if( leftTextureType.handle != -1 ) { - logger.fine("Writing Left texture to native memory at " + leftTextureType.getPointer()); - leftTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_LEFT].colorBufferName = leftTextureType.handle; - if( leftEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_LEFT].depthStencilBufferName = leftEyeDepth.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_LEFT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_LEFT]); - } - } - rightTextureType.handle = getRightTexId(); - if( rightTextureType.handle != -1 ) { - logger.fine("Writing Right texture to native memory at " + leftTextureType.getPointer()); - rightTextureType.write(); - if( api instanceof OSVR ) { - osvr_renderBuffer[OSVR.EYE_RIGHT].colorBufferName = rightTextureType.handle; - if( rightEyeDepth != null ) osvr_renderBuffer[OSVR.EYE_RIGHT].depthStencilBufferName = rightEyeDepth.getImage().getId(); - osvr_renderBuffer[OSVR.EYE_RIGHT].write(); - registerOSVRBuffer(osvr_renderBuffer[OSVR.EYE_RIGHT]); - } - } - } else { - if( api instanceof OSVR ) { - ((OSVR)api).handleRenderBufferPresent(osvr_viewDescFull, osvr_viewDescFull, - osvr_renderBuffer[OSVR.EYE_LEFT], osvr_renderBuffer[OSVR.EYE_RIGHT]); - } - } - - if( errl != 0 ){ - logger.severe("Submit to left compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(leftTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(leftTextureType.eType)); - logger.severe(" Texture handle: "+leftTextureType.handle); - - logger.severe(" Left eye texture "+leftEyeTexture.getName()+" ("+leftEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+leftEyeTexture.getType()); - logger.severe(" Size: "+leftEyeTexture.getImage().getWidth()+"x"+leftEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+leftEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+leftEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+leftEyeTexture.getImage().getColorSpace()); - - } - - if( errr != 0 ){ - logger.severe("Submit to right compositor error: " + OpenVRUtil.getEVRCompositorErrorString(errl)+" ("+Integer.toString(errl)+")"); - logger.severe(" Texture color space: "+OpenVRUtil.getEColorSpaceString(rightTextureType.eColorSpace)); - logger.severe(" Texture type: "+OpenVRUtil.getETextureTypeString(rightTextureType.eType)); - logger.severe(" Texture handle: "+rightTextureType.handle); - - logger.severe(" Right eye texture "+rightEyeTexture.getName()+" ("+rightEyeTexture.getImage().getId()+")"); - logger.severe(" Type: "+rightEyeTexture.getType()); - logger.severe(" Size: "+rightEyeTexture.getImage().getWidth()+"x"+rightEyeTexture.getImage().getHeight()); - logger.severe(" Image depth: "+rightEyeTexture.getImage().getDepth()); - logger.severe(" Image format: "+rightEyeTexture.getImage().getFormat()); - logger.severe(" Image color space: "+rightEyeTexture.getImage().getColorSpace()); - } - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - - /** - * Initialize the VR view manager. - */ - public void initialize() { - - logger.config("Initializing VR view manager."); - - if (environment != null){ - initTextureSubmitStructs(); - setupCamerasAndViews(); - setupVRScene(); - moveScreenProcessingToEyes(); - if( environment.hasTraditionalGUIOverlay() ) { - - environment.getVRMouseManager().initialize(); - - // update the pose to position the gui correctly on start - update(0f); - environment.getVRGUIManager().positionGui(); - } - - if (environment.getApplication() != null){ - // if we are OSVR, our primary mirror window needs to be the same size as the render manager's output... - if( environment.getVRHardware() instanceof OSVR ) { - int origWidth = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth(); - int origHeight = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight(); - long window = ((LwjglWindow)environment.getApplication().getContext()).getWindowHandle(); - Vector2f windowSize = new Vector2f(); - ((OSVR)environment.getVRHardware()).getRenderSize(windowSize); - windowSize.x = Math.max(windowSize.x * 2f, leftCamera.getWidth()); - org.lwjgl.glfw.GLFW.glfwSetWindowSize(window, (int)windowSize.x, (int)windowSize.y); - environment.getApplication().getContext().getSettings().setResolution((int)windowSize.x, (int)windowSize.y); - - if (environment.getApplication().getRenderManager() != null) { - environment.getApplication().getRenderManager().notifyReshape((int)windowSize.x, (int)windowSize.y); - } - - org.lwjgl.glfw.GLFW.glfwSetWindowPos(window, origWidth - (int)windowSize.x, 32); - - org.lwjgl.glfw.GLFW.glfwFocusWindow(window); - - org.lwjgl.glfw.GLFW.glfwSetCursorPos(window, origWidth / 2.0, origHeight / 2.0); - - logger.config("Initialized VR view manager [SUCCESS]"); - } else { - throw new IllegalStateException("Underlying VR hardware should be "+OSVR.class.getSimpleName()); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - } - - /** - * Prepare the size of the given {@link Camera camera} to adapt it to the underlying rendering context. - * @param cam the {@link Camera camera} to prepare. - * @param xMult the camera width multiplier. - */ - private void prepareCameraSize(Camera cam, float xMult) { - - if (environment != null){ - - if (environment.getApplication() != null){ - - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - - Vector2f size = new Vector2f(); - VRAPI vrhmd = environment.getVRHardware(); - - if( vrhmd == null ) { - size.x = 1280f; - size.y = 720f; - } else { - vrhmd.getRenderSize(size); - } - - if( size.x < environment.getApplication().getContext().getSettings().getWidth() ) { - size.x = environment.getApplication().getContext().getSettings().getWidth(); - } - if( size.y < environment.getApplication().getContext().getSettings().getHeight() ) { - size.y = environment.getApplication().getContext().getSettings().getHeight(); - } - - if( environment.isInstanceRendering() ){ - size.x *= 2f; - } - - // other adjustments - size.x *= xMult; - size.x *= getResolutionMuliplier(); - size.y *= getResolutionMuliplier(); - - if( cam.getWidth() != size.x || cam.getHeight() != size.y ){ - cam.resize((int)size.x, (int)size.y, false); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Replaces rootNode as the main cameras scene with the distortion mesh - */ - private void setupVRScene(){ - - if (environment != null){ - if (environment.getApplication() != null){ - // no special scene to setup if we are doing instancing - if( environment.isInstanceRendering() ) { - // distortion has to be done with compositor here... we want only one pass on our end! - if( environment.getApplication().getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(environment.getCamera(), dualEyeTex, true); - } - return; - } - - leftEyeTexture = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - rightEyeTexture = (Texture2D)getRightViewPort().getOutputFrameBuffer().getColorBuffer().getTexture(); - leftEyeDepth = (Texture2D) getLeftViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - rightEyeDepth = (Texture2D)getRightViewPort().getOutputFrameBuffer().getDepthBuffer().getTexture(); - - // main viewport is either going to be a distortion scene or nothing - // mirroring is handled by copying framebuffers - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - environment.getApplication().getViewPort().detachScene(spatialIter.next()); - } - - spatialIter = environment.getApplication().getGuiViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - environment.getApplication().getGuiViewPort().detachScene(spatialIter.next()); - } - - // only setup distortion scene if compositor isn't running (or using custom mesh distortion option) - if( environment.getVRHardware().getCompositor() == null ) { - Node distortionScene = new Node(); - Material leftMat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - leftMat.setTexture("Texture", leftEyeTexture); - Geometry leftEye = new Geometry("box", setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Left, environment.getVRHardware())); - leftEye.setMaterial(leftMat); - distortionScene.attachChild(leftEye); - - Material rightMat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/OpenVR.j3md"); - rightMat.setTexture("Texture", rightEyeTexture); - Geometry rightEye = new Geometry("box", setupDistortionMesh(JOpenVRLibrary.EVREye.EVREye_Eye_Right, environment.getVRHardware())); - rightEye.setMaterial(rightMat); - distortionScene.attachChild(rightEye); - - distortionScene.updateGeometricState(); - - environment.getApplication().getViewPort().attachScene(distortionScene); - - //if( useCustomDistortion ) setupFinalFullTexture(app.getViewPort().getCamera()); - } - - if( environment.getApplication().getContext().getSettings().isSwapBuffers() ) { - setupMirrorBuffers(environment.getCamera(), leftEyeTexture, false); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - - - } - - /** - * Update the VR view manager. - * This method is called by the attached VR application and should not be called manually. - * @param tpf the time per frame. - */ - public void update(float tpf) { - - if (environment != null){ - // grab the observer - Object obs = environment.getObserver(); - Quaternion objRot; - Vector3f objPos; - if( obs instanceof Camera ) { - objRot = ((Camera)obs).getRotation(); - objPos = ((Camera)obs).getLocation(); - } else { - objRot = ((Spatial)obs).getWorldRotation(); - objPos = ((Spatial)obs).getWorldTranslation(); - } - // grab the hardware handle - VRAPI dev = environment.getVRHardware(); - if( dev != null ) { - // update the HMD's position & orientation - dev.updatePose(); - dev.getPositionAndOrientation(hmdPos, hmdRot); - if( obs != null ) { - // update hmdPos based on obs rotation - finalRotation.set(objRot); - finalRotation.mult(hmdPos, hmdPos); - finalRotation.multLocal(hmdRot); - } - - finalizeCamera(dev.getHMDVectorPoseLeftEye(), objPos, leftCamera); - finalizeCamera(dev.getHMDVectorPoseRightEye(), objPos, rightCamera); - } else { - leftCamera.setFrame(objPos, objRot); - rightCamera.setFrame(objPos, objRot); - } - - if( environment.hasTraditionalGUIOverlay() ) { - // update the mouse? - environment.getVRMouseManager().update(tpf); - - // update GUI position? - if( environment.getVRGUIManager().isWantsReposition() || environment.getVRGUIManager().getPositioningMode() != VRGUIPositioningMode.MANUAL ) { - environment.getVRGUIManager().positionGuiNow(tpf); - environment.getVRGUIManager().updateGuiQuadGeometricState(); - } - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Place the camera within the scene. - * @param eyePos the eye position. - * @param obsPosition the observer position. - * @param cam the camera to place. - */ - private void finalizeCamera(Vector3f eyePos, Vector3f obsPosition, Camera cam) { - finalRotation.mult(eyePos, finalPosition); - finalPosition.addLocal(hmdPos); - if( obsPosition != null ){ - finalPosition.addLocal(obsPosition); - } - finalPosition.y += getHeightAdjustment(); - cam.setFrame(finalPosition, finalRotation); - } - - /** - * Handles moving filters from the main view to each eye - */ - public void moveScreenProcessingToEyes() { - if( getRightViewPort() == null ){ - return; - } - - if (environment != null){ - if (environment.getApplication() != null){ - - syncScreenProcessing(environment.getApplication().getViewPort()); - environment.getApplication().getViewPort().clearProcessors(); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Sets the two views to use the list of {@link SceneProcessor processors}. - * @param sourceViewport the {@link ViewPort viewport} that contains the processors to use. - */ - public void syncScreenProcessing(ViewPort sourceViewport) { - if( getRightViewPort() == null ){ - return; - } - - if (environment != null){ - if (environment.getApplication() != null){ - // setup post processing filters - if( rightPostProcessor == null ) { - rightPostProcessor = new FilterPostProcessor(environment.getApplication().getAssetManager()); - leftPostProcessor = new FilterPostProcessor(environment.getApplication().getAssetManager()); - } - // clear out all filters & processors, to start from scratch - rightPostProcessor.removeAllFilters(); - leftPostProcessor.removeAllFilters(); - getLeftViewPort().clearProcessors(); - getRightViewPort().clearProcessors(); - // if we have no processors to sync, don't add the FilterPostProcessor - if( sourceViewport.getProcessors().isEmpty() ) return; - // add post processors we just made, which are empty - getLeftViewPort().addProcessor(leftPostProcessor); - getRightViewPort().addProcessor(rightPostProcessor); - // go through all of the filters in the processors list - // add them to the left viewport processor & clone them to the right - for(SceneProcessor sceneProcessor : sourceViewport.getProcessors()) { - if (sceneProcessor instanceof FilterPostProcessor) { - for(Filter f : ((FilterPostProcessor)sceneProcessor).getFilterList() ) { - if( f instanceof TranslucentBucketFilter ) { - // just remove this filter, we will add it at the end manually - ((FilterPostProcessor)sceneProcessor).removeFilter(f); - } else { - leftPostProcessor.addFilter(f); - // clone to the right - Filter f2; - if(f instanceof FogFilter){ - f2 = FilterUtil.cloneFogFilter((FogFilter)f); - } else if (f instanceof CartoonSSAO ) { - f2 = new CartoonSSAO((CartoonSSAO)f); - } else if (f instanceof SSAOFilter){ - f2 = FilterUtil.cloneSSAOFilter((SSAOFilter)f); - } else if (f instanceof DirectionalLightShadowFilter){ - f2 = FilterUtil.cloneDirectionalLightShadowFilter(environment.getApplication().getAssetManager(), (DirectionalLightShadowFilter)f); - } else { - f2 = f; // dof, bloom, lightscattering etc. - } - rightPostProcessor.addFilter(f2); - } - } - } else if (sceneProcessor instanceof VRDirectionalLightShadowRenderer) { - // shadow processing - // TODO: make right shadow processor use same left shadow maps for performance - VRDirectionalLightShadowRenderer dlsr = (VRDirectionalLightShadowRenderer) sceneProcessor; - VRDirectionalLightShadowRenderer dlsrRight = dlsr.clone(); - dlsrRight.setLight(dlsr.getLight()); - getRightViewPort().getProcessors().add(0, dlsrRight); - getLeftViewPort().getProcessors().add(0, sceneProcessor); - } - } - // make sure each has a translucent filter renderer - leftPostProcessor.addFilter(new TranslucentBucketFilter()); - rightPostProcessor.addFilter(new TranslucentBucketFilter()); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private void setupCamerasAndViews() { - - if (environment != null){ - if (environment.getApplication() != null){ - // get desired frustum from original camera - Camera origCam = environment.getCamera(); - float fFar = origCam.getFrustumFar(); - float fNear = origCam.getFrustumNear(); - - // if we are using OSVR get the eye info here - if( environment.getVRHardware() instanceof OSVR ) { - ((OSVR)environment.getVRHardware()).getEyeInfo(); - } - - // restore frustum on distortion scene cam, if needed - if( environment.isInstanceRendering() ) { - leftCamera = origCam; - } else if( environment.compositorAllowed() == false ) { - origCam.setFrustumFar(100f); - origCam.setFrustumNear(1f); - leftCamera = origCam.clone(); - prepareCameraSize(origCam, 2f); - } else { - leftCamera = origCam.clone(); - } - - leftCamera.setFrustumPerspective(environment.getDefaultFOV(), environment.getDefaultAspect(), fNear, fFar); - - prepareCameraSize(leftCamera, 1f); - if( environment.getVRHardware() != null ) leftCamera.setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionLeftEye(leftCamera)); - //org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_FRAMEBUFFER_SRGB); - - if( !environment.isInstanceRendering()) { - leftViewPort = setupViewBuffers(leftCamera, LEFT_VIEW_NAME); - rightCamera = leftCamera.clone(); - if( environment.getVRHardware() != null ){ - rightCamera.setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); - } - rightViewPort = setupViewBuffers(rightCamera, RIGHT_VIEW_NAME); - } else { - - System.err.println("[VRViewManager] THIS CODE NEED CHANGES !!!"); - leftViewPort = environment.getApplication().getViewPort(); - //leftViewport.attachScene(app.getRootNode()); - rightCamera = leftCamera.clone(); - if( environment.getVRHardware() != null ){ - rightCamera.setProjectionMatrix(environment.getVRHardware().getHMDMatrixProjectionRightEye(rightCamera)); - } - - org.lwjgl.opengl.GL11.glEnable(org.lwjgl.opengl.GL30.GL_CLIP_DISTANCE0); - - //FIXME: [jme-vr] Fix with JMonkey next release - //RenderManager._VRInstancing_RightCamProjection = camRight.getViewProjectionMatrix(); - setupFinalFullTexture(environment.getApplication().getViewPort().getCamera()); - } - - // setup gui - environment.getVRGUIManager().setupGui(leftCamera, rightCamera, getLeftViewPort(), getRightViewPort()); - - if( environment.getVRHardware() != null ) { - // call these to cache the results internally - environment.getVRHardware().getHMDMatrixPoseLeftEye(); - environment.getVRHardware().getHMDMatrixPoseRightEye(); - } - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupMirrorBuffers(Camera cam, Texture tex, boolean expand) { - - if (environment != null){ - if (environment.getApplication() != null){ - Camera clonecam = cam.clone(); - ViewPort viewPort = environment.getApplication().getRenderManager().createPostView("MirrorView", clonecam); - clonecam.setParallelProjection(true); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - Picture pic = new Picture("fullscene"); - pic.setLocalTranslation(-0.75f, -0.5f, 0f); - if( expand ) { - pic.setLocalScale(3f, 1f, 1f); - } else { - pic.setLocalScale(1.5f, 1f, 1f); - } - pic.setQueueBucket(Bucket.Opaque); - pic.setTexture(environment.getApplication().getAssetManager(), (Texture2D)tex, false); - viewPort.attachScene(pic); - viewPort.setOutputFrameBuffer(null); - - pic.updateGeometricState(); - - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private void setupFinalFullTexture(Camera cam) { - - if (environment != null){ - if (environment.getApplication() != null){ - // create offscreen framebuffer - FrameBuffer out = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBuffer.setSrgb(true); - - //setup framebuffer's texture - dualEyeTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - dualEyeTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - dualEyeTex.setMagFilter(Texture.MagFilter.Bilinear); - - logger.config("Dual eye texture "+dualEyeTex.getName()+" ("+dualEyeTex.getImage().getId()+")"); - logger.config(" Type: "+dualEyeTex.getType()); - logger.config(" Size: "+dualEyeTex.getImage().getWidth()+"x"+dualEyeTex.getImage().getHeight()); - logger.config(" Image depth: "+dualEyeTex.getImage().getDepth()); - logger.config(" Image format: "+dualEyeTex.getImage().getFormat()); - logger.config(" Image color space: "+dualEyeTex.getImage().getColorSpace()); - - //setup framebuffer to use texture - out.setDepthBuffer(Image.Format.Depth); - out.setColorTexture(dualEyeTex); - - ViewPort viewPort = environment.getApplication().getViewPort(); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - viewPort.setOutputFrameBuffer(out); - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - private ViewPort setupViewBuffers(Camera cam, String viewName){ - - if (environment != null){ - if (environment.getApplication() != null){ - // create offscreen framebuffer - FrameBuffer offBufferLeft = new FrameBuffer(cam.getWidth(), cam.getHeight(), 1); - //offBufferLeft.setSrgb(true); - - //setup framebuffer's texture - Texture2D offTex = new Texture2D(cam.getWidth(), cam.getHeight(), Image.Format.RGBA8); - offTex.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - offTex.setMagFilter(Texture.MagFilter.Bilinear); - - //setup framebuffer to use texture - offBufferLeft.setDepthBuffer(Image.Format.Depth); - offBufferLeft.setColorTexture(offTex); - - ViewPort viewPort = environment.getApplication().getRenderManager().createPreView(viewName, cam); - viewPort.setClearFlags(true, true, true); - viewPort.setBackgroundColor(ColorRGBA.Black); - - Iterator spatialIter = environment.getApplication().getViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - viewPort.attachScene(spatialIter.next()); - } - - //set viewport to render to offscreen framebuffer - viewPort.setOutputFrameBuffer(offBufferLeft); - return viewPort; - } else { - throw new IllegalStateException("This VR environment is not attached to any application."); - } - } else { - throw new IllegalStateException("This VR view manager is not attached to any VR environment."); - } - } - - /** - * Setup a distortion mesh for the stereo view. - * @param eye the eye to apply. - * @param api the underlying VR api - * @return the distorted mesh. - */ - public static Mesh setupDistortionMesh(int eye, VRAPI api) { - Mesh distortionMesh = new Mesh(); - float m_iLensGridSegmentCountH = 43, m_iLensGridSegmentCountV = 43; - - float w = 1f / (m_iLensGridSegmentCountH - 1f); - float h = 1f / (m_iLensGridSegmentCountV - 1f); - - float u, v; - - float verts[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 3]; - - float texcoordR[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - float texcoordG[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - float texcoordB[] = new float[(int) (m_iLensGridSegmentCountV * m_iLensGridSegmentCountH) * 2]; - - int vertPos = 0, coordPos = 0; - - float Xoffset = eye == JOpenVRLibrary.EVREye.EVREye_Eye_Left ? -1f : 0; - for (int y = 0; y < m_iLensGridSegmentCountV; y++) { - for (int x = 0; x < m_iLensGridSegmentCountH; x++) { - u = x * w; - v = 1 - y * h; - verts[vertPos] = Xoffset + u; // x - verts[vertPos + 1] = -1 + 2 * y * h; // y - verts[vertPos + 2] = 0f; // z - vertPos += 3; - - DistortionCoordinates_t dc0 = new DistortionCoordinates_t(); - if( api.getVRSystem() == null ) { - // default to no distortion - texcoordR[coordPos] = u; - texcoordR[coordPos + 1] = 1 - v; - texcoordG[coordPos] = u; - texcoordG[coordPos + 1] = 1 - v; - texcoordB[coordPos] = u; - texcoordB[coordPos + 1] = 1 - v; - } else { - ((VR_IVRSystem_FnTable)api.getVRSystem()).ComputeDistortion.apply(eye, u, v, dc0); - - texcoordR[coordPos] = dc0.rfRed[0]; - texcoordR[coordPos + 1] = 1 - dc0.rfRed[1]; - texcoordG[coordPos] = dc0.rfGreen[0]; - texcoordG[coordPos + 1] = 1 - dc0.rfGreen[1]; - texcoordB[coordPos] = dc0.rfBlue[0]; - texcoordB[coordPos + 1] = 1 - dc0.rfBlue[1]; - } - - coordPos += 2; - } - } - - // have UV coordinates & positions, now to setup indices - - int[] indices = new int[(int) ((m_iLensGridSegmentCountV - 1) * (m_iLensGridSegmentCountH - 1)) * 6]; - int indexPos = 0; - int a, b, c, d; - - int offset = 0; - for (int y = 0; y < m_iLensGridSegmentCountV - 1; y++) { - for (int x = 0; x < m_iLensGridSegmentCountH - 1; x++) { - a = (int) (m_iLensGridSegmentCountH * y + x + offset); - b = (int) (m_iLensGridSegmentCountH * y + x + 1 + offset); - c = (int) ((y + 1) * m_iLensGridSegmentCountH + x + 1 + offset); - d = (int) ((y + 1) * m_iLensGridSegmentCountH + x + offset); - - indices[indexPos] = a; - indices[indexPos + 1] = b; - indices[indexPos + 2] = c; - - indices[indexPos + 3] = a; - indices[indexPos + 4] = c; - indices[indexPos + 5] = d; - - indexPos += 6; - } - } - - // OK, create the mesh - distortionMesh.setBuffer(VertexBuffer.Type.Position, 3, verts); - distortionMesh.setBuffer(VertexBuffer.Type.Index, 1, indices); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord, 2, texcoordR); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord2, 2, texcoordG); - distortionMesh.setBuffer(VertexBuffer.Type.TexCoord3, 2, texcoordB); - distortionMesh.setStatic(); - return distortionMesh; - } - - @Override - public void render() { - // TODO Auto-generated method stub - - } -} diff --git a/jme3-vr/src/main/java/com/jme3/post/CartoonSSAO.java b/jme3-vr/src/main/java/com/jme3/post/CartoonSSAO.java deleted file mode 100644 index 35b2cd95b6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/CartoonSSAO.java +++ /dev/null @@ -1,159 +0,0 @@ -/* -* To change this template, choose Tools | Templates -* and open the template in the editor. -*/ -package com.jme3.post; - -import com.jme3.asset.AssetManager; -import com.jme3.material.Material; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.post.Filter; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.texture.Image.Format; - -/** - * A Cartoon Screen Space Ambient Occlusion filter with instance rendering capabilities. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class CartoonSSAO extends Filter{ - private Pass normalPass; - private Vector3f frustumCorner; - private Vector2f frustumNearFar; - private boolean useOutline = true; - private float downsample = 1f, applyDistance = 0.0005f; - - private boolean instancedRendering = false; - - RenderManager renderManager; - ViewPort viewPort; - - /** - * Create a Screen Space Ambient Occlusion Filter. - * @param instancedRendering true if this filter has to use instance rendering and false (default) otherwise. - */ - public CartoonSSAO(boolean instancedRendering) { - super("CartoonSSAO"); - this.instancedRendering = instancedRendering; - } - - /** - * Create a Screen Space Ambient Occlusion Filter. - * @param downsample factor to divide resolution by for filter, >1 increases speed but degrades quality. - * @param instancedRendering true if this filter has to use instance rendering and false (default) otherwise. - */ - public CartoonSSAO(float downsample, boolean instancedRendering) { - this(instancedRendering); - this.downsample = downsample; - } - - /** - * Create a Screen Space Ambient Occlusion Filter from the given one (by copy). - * @param cloneFrom the original filter. - */ - public CartoonSSAO(CartoonSSAO cloneFrom) { - this(cloneFrom.downsample, cloneFrom.instancedRendering); - } - - @Override - protected boolean isRequiresDepthTexture() { - return true; - } - - @Override - protected void postQueue(RenderQueue renderQueue) { - PreNormalCaching.getPreNormals(renderManager, normalPass, viewPort); - } - - /** - * Set if outline has to be enabled. - * @param set true if the outline has to be enabled and false otherwise. - * @see #isOutlineEnabled() - */ - public void setOutlineEnabled(boolean set) { - useOutline = set; - if( material != null ) { - if( useOutline ) { - material.clearParam("disableOutline"); - } else { - material.setBoolean("disableOutline", true); - } - } - } - - /** - * Is outline rendering is enabled. - * @return true if the outline is enabled and false otherwise. - * @see #setOutlineEnabled(boolean) - */ - public boolean isOutlineEnabled() { - return useOutline; - } - - /** - * Set the down sampling value. - * @param downsample the down sampling value. - * @see #getDownsampling() - */ - public void setDownsampling(float downsample) { - this.downsample = downsample; - } - - /** - * Get the down sampling value. - * @return the down sampling value. - * @see #setDownsampling(float) - */ - public float getDownsampling() { - return this.downsample; - } - - @Override - protected Material getMaterial() { - return material; - } - - /** - * Set the distance of the material. - * @param dist the distance of the material. - */ - public void setDistance(float dist) { - applyDistance = dist; - if( material != null ) material.setFloat("Distance", dist); - } - - @Override - protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { - this.renderManager = renderManager; - this.viewPort = vp; - - int screenWidth = Math.round(w / downsample); - int screenHeight = Math.round(h / downsample); - - normalPass = new Pass(); - normalPass.init(renderManager.getRenderer(), screenWidth, screenHeight, Format.RGBA8, Format.Depth); - - frustumNearFar = new Vector2f(); - - float farY = (vp.getCamera().getFrustumTop() / vp.getCamera().getFrustumNear()) * vp.getCamera().getFrustumFar(); - float farX = farY * ((float) screenWidth / (float) screenHeight); - frustumCorner = new Vector3f(farX, farY, vp.getCamera().getFrustumFar()); - frustumNearFar.x = vp.getCamera().getFrustumNear(); - frustumNearFar.y = vp.getCamera().getFrustumFar(); - - //ssao Pass - material = new Material(manager, "Common/MatDefs/VR/CartoonSSAO.j3md"); - material.setTexture("Normals", normalPass.getRenderedTexture()); - - material.setVector3("FrustumCorner", frustumCorner); - material.setVector2("FrustumNearFar", frustumNearFar); - material.setFloat("Distance", applyDistance); - if( useOutline == false ) material.setBoolean("disableOutline", true); - if( instancedRendering ) material.setBoolean("useInstancing", true); - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/java/com/jme3/post/FilterUtil.java b/jme3-vr/src/main/java/com/jme3/post/FilterUtil.java deleted file mode 100644 index c29592d10f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/FilterUtil.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.post; - -import com.jme3.asset.AssetManager; -import com.jme3.post.filters.FogFilter; -import com.jme3.post.ssao.SSAOFilter; -import com.jme3.shadow.DirectionalLightShadowFilter; - -/** - * - * @author Rickard - */ -public class FilterUtil { - - public static FogFilter cloneFogFilter(FogFilter fogFilter){ - FogFilter filterClone = new FogFilter(); - filterClone.setFogColor(fogFilter.getFogColor()); - filterClone.setFogDensity(fogFilter.getFogDensity()); - filterClone.setFogDistance(fogFilter.getFogDistance()); - filterClone.setName(fogFilter.getName() + " Clone"); - - return filterClone; - } - - public static SSAOFilter cloneSSAOFilter(SSAOFilter filter){ - SSAOFilter clone = new SSAOFilter(); - clone.setSampleRadius(filter.getSampleRadius()); - clone.setIntensity(filter.getIntensity()); - clone.setScale(filter.getScale()); - clone.setBias(filter.getBias()); - return clone; - } - - public static DirectionalLightShadowFilter cloneDirectionalLightShadowFilter(AssetManager assetManager, DirectionalLightShadowFilter filter){ - DirectionalLightShadowFilter clone = new DirectionalLightShadowFilter(assetManager, 512, 3); - clone.setLight(filter.getLight()); - clone.setLambda(filter.getLambda()); - clone.setShadowIntensity(filter.getShadowIntensity()); - clone.setEdgeFilteringMode(filter.getEdgeFilteringMode()); -// clone.setEnabled(filter.isEnabled()); - return clone; - } - -} diff --git a/jme3-vr/src/main/java/com/jme3/post/PreNormalCaching.java b/jme3-vr/src/main/java/com/jme3/post/PreNormalCaching.java deleted file mode 100644 index 83159f134b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/post/PreNormalCaching.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.post; - -import com.jme3.post.Filter.Pass; -import com.jme3.renderer.Caps; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.Renderer; -import com.jme3.renderer.ViewPort; -import com.jme3.texture.FrameBuffer; - -/** - * Pre normal caching class. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class PreNormalCaching { - - private static FrameBuffer cachedPreNormals; - private static int lastNormalPassesCount, curCount; - - /** - * Get pre-normals from the given rendering. - * @param renderManager the render manager. - * @param normalPass the normal pass. - * @param viewPort the viewport. - */ - public static void getPreNormals(RenderManager renderManager, Pass normalPass, ViewPort viewPort) { - curCount++; - // do we already have a valid cache to set the framebuffer to? - Renderer r = renderManager.getRenderer(); - if( cachedPreNormals != null ) { - r.copyFrameBuffer(cachedPreNormals, normalPass.getRenderFrameBuffer(), false); - } else { - // lets make the prenormals - r.setFrameBuffer(normalPass.getRenderFrameBuffer()); - renderManager.getRenderer().clearBuffers(true, true, true); - if( renderManager.getRenderer().getCaps().contains(Caps.GLSL150) ) { - renderManager.setForcedTechnique("PreNormalPass15"); - } else { - renderManager.setForcedTechnique("PreNormalPass"); - } - renderManager.renderViewPortQueues(viewPort, false); - renderManager.setForcedTechnique(null); - // if we should cache this, do it now - if( lastNormalPassesCount > 1 ) { - cachedPreNormals = normalPass.getRenderFrameBuffer(); - } - } - renderManager.getRenderer().setFrameBuffer(viewPort.getOutputFrameBuffer()); - } - - /** - * Reset the cache - * @param stereo true if the rendering is stereo based and false otherwise. - */ - public static void resetCache(boolean stereo) { - if( stereo == false ) { - // only use this feature if we are NOT in VR - // we can't use the same normal information for another eye, - // because it will be different! - lastNormalPassesCount = curCount; - } - cachedPreNormals = null; - curCount = 0; - } - -} diff --git a/jme3-vr/src/main/java/com/jme3/scene/CenterQuad.java b/jme3-vr/src/main/java/com/jme3/scene/CenterQuad.java deleted file mode 100644 index 87aeda14b5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/scene/CenterQuad.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2009-2010 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.Mesh; -import com.jme3.scene.VertexBuffer.Type; - -/** - * Quad represents a rectangular plane in space - * defined by 4 vertices. The quad's lower-left side is contained - * at the local space origin (0, 0, 0), while the upper-right - * side is located at the width/height coordinates (width, height, 0). - * - * @author Kirill Vainer - */ -public class CenterQuad extends Mesh { - - public static CenterQuad UnitQuad = new CenterQuad(0.5f, 0.5f); - public static Mesh CenterSplitQuad; - - private float width; - private float height; - - /** - * Create a quad with the given width and height. The quad - * is always created in the XY plane. - * - * @param width The X extent or width - * @param height The Y extent or width - */ - public CenterQuad(float width, float height){ - updateGeometry(width, height); - } - - /** - * Create a quad with the given width and height. The quad - * is always created in the XY plane. - * - * @param width The X extent or width - * @param height The Y extent or width - * @param flipCoords If true, the texture coordinates will be flipped - * along the Y axis. - */ - public CenterQuad(float width, float height, boolean flipCoords){ - updateGeometry(width, height, flipCoords); - this.setStatic(); - } - - public float getHeight() { - return height; - } - - public float getWidth() { - return width; - } - - public void updateGeometry(float width, float height){ - updateGeometry(width, height, false); - } - - public void updateGeometry(float width, float height, boolean flipCoords) { - this.width = width; - this.height = height; - setBuffer(Type.Position, 3, new float[]{-width/2, -height/2, 0, - width/2, -height/2, 0, - width/2, height/2, 0, - -width/2, height/2, 0 - }); - - - if (flipCoords){ - setBuffer(Type.TexCoord, 2, new float[]{0, 1, - 1, 1, - 1, 0, - 0, 0}); - }else{ - setBuffer(Type.TexCoord, 2, new float[]{0, 0, - 1, 0, - 1, 1, - 0, 1}); - } - setBuffer(Type.Normal, 3, new float[]{0, 0, 1, - 0, 0, 1, - 0, 0, 1, - 0, 0, 1}); - if (height < 0){ - setBuffer(Type.Index, 3, new short[]{0, 2, 1, - 0, 3, 2}); - }else{ - setBuffer(Type.Index, 3, new short[]{0, 1, 2, - 0, 2, 3}); - } - - updateBound(); - } - - -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowFilterVR.java b/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowFilterVR.java deleted file mode 100644 index aa9401f3ea..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowFilterVR.java +++ /dev/null @@ -1,330 +0,0 @@ -package com.jme3.shadow; - -/* - * Copyright (c) 2009-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.asset.AssetManager; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector4f; -import com.jme3.post.Filter; -import com.jme3.renderer.RenderManager; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.shadow.CompareMode; -import com.jme3.shadow.EdgeFilteringMode; -import com.jme3.texture.FrameBuffer; - -import java.io.IOException; - -/** - * Generic abstract filter that holds common implementations for the different - * shadow filters. - * - * @author Rémy Bouquet aka Nehon - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * @param the type of the underlying renderer (subclass of {@link AbstractShadowRendererVR}). - */ -public abstract class AbstractShadowFilterVR extends Filter { - - protected T shadowRenderer; - protected ViewPort viewPort; - - /** - * Abstract class constructor - * - * @param manager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param shadowRenderer the shadowRenderer to use for this Filter - */ - @SuppressWarnings("all") - protected AbstractShadowFilterVR(AssetManager manager, int shadowMapSize, T shadowRenderer) { - super("Post Shadow"); - material = new Material(manager, "Common/MatDefs/Shadow/PostShadowFilter.j3md"); - this.shadowRenderer = shadowRenderer; - this.shadowRenderer.setPostShadowMaterial(material); - - //this is legacy setting for shadows with backface shadows - this.shadowRenderer.setRenderBackFacesShadows(true); - } - - @SuppressWarnings("all") - protected AbstractShadowFilterVR(AssetManager manager, int shadowMapSize, T shadowRenderer, String useMatDef) { - super("Post Shadow"); - material = new Material(manager, useMatDef); - this.shadowRenderer = shadowRenderer; - this.shadowRenderer.setPostShadowMaterial(material); - } - - @Override - protected Material getMaterial() { - return material; - } - - @Override - protected boolean isRequiresDepthTexture() { - return true; - } - - /** - * Get the {@link Material material} used by this filter. - * @return the {@link Material material} used by this filter. - */ - public Material getShadowMaterial() { - return material; - } - - Vector4f tmpv = new Vector4f(); - - @Override - protected void preFrame(float tpf) { - shadowRenderer.preFrame(tpf); - material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert()); - Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); - material.setVector4("ViewProjectionMatrixRow2", tmpv.set(m.m20, m.m21, m.m22, m.m23)); - - } - - @Override - protected void postQueue(RenderQueue queue) { - shadowRenderer.postQueue(queue); - if(shadowRenderer.skipPostPass){ - //removing the shadow map so that the post pass is skipped - material.setTexture("ShadowMap0", null); - } - } - - @Override - protected void postFrame(RenderManager renderManager, ViewPort viewPort, FrameBuffer prevFilterBuffer, FrameBuffer sceneBuffer) { - if(!shadowRenderer.skipPostPass){ - shadowRenderer.setPostShadowParams(); - } - } - - @Override - protected void initFilter(AssetManager manager, RenderManager renderManager, ViewPort vp, int w, int h) { - shadowRenderer.needsfallBackMaterial = true; - shadowRenderer.initialize(renderManager, vp); - this.viewPort = vp; - } - - /** - * How far the shadows are rendered in the view - * - * @see #setShadowZExtend(float zFar) - * @return shadowZExtend - */ - public float getShadowZExtend() { - return shadowRenderer.getShadowZExtend(); - } - - /** - * Set the distance from the eye where the shadows will be rendered default - * value is dynamically computed to the shadow casters/receivers union bound - * zFar, capped to view frustum far value. - * - * @param zFar the zFar values that override the computed one - */ - public void setShadowZExtend(float zFar) { - shadowRenderer.setShadowZExtend(zFar); - } - - /** - * Define the length over which the shadow will fade out when using a - * shadowZextend - * - * @param length the fade length in world units - */ - public void setShadowZFadeLength(float length) { - shadowRenderer.setShadowZFadeLength(length); - } - - /** - * get the length over which the shadow will fade out when using a - * shadowZextend - * - * @return the fade length in world units - */ - public float getShadowZFadeLength() { - return shadowRenderer.getShadowZFadeLength(); - } - - /** - * returns the shadow intensity - * - * @see #setShadowIntensity(float shadowIntensity) - * @return shadowIntensity - */ - public float getShadowIntensity() { - return shadowRenderer.getShadowIntensity(); - } - - /** - * Set the shadowIntensity, the value should be between 0 and 1, a 0 value - * gives a bright and invisible shadow, a 1 value gives a pitch black - * shadow, default is 0.7 - * - * @param shadowIntensity the darkness of the shadow - */ - final public void setShadowIntensity(float shadowIntensity) { - shadowRenderer.setShadowIntensity(shadowIntensity); - } - - /** - * returns the edges thickness
                  - * - * @see #setEdgesThickness(int edgesThickness) - * @return edgesThickness - */ - public int getEdgesThickness() { - return shadowRenderer.getEdgesThickness(); - } - - /** - * Sets the shadow edges thickness. default is 1, setting it to lower values - * can help to reduce the jagged effect of the shadow edges - * @param edgesThickness the edge thickness. - */ - public void setEdgesThickness(int edgesThickness) { - shadowRenderer.setEdgesThickness(edgesThickness); - } - - /** - * isFlushQueues does nothing and is kept only for backward compatibility. - * @return false - * @deprecated does nothing and is kept only for backward compatibility. - */ - @Deprecated - public boolean isFlushQueues() { - return shadowRenderer.isFlushQueues(); - } - - /** - * setFlushQueues does nothing now and is kept only for backward compatibility. - * @param flushQueues can be true or false. - * @deprecated does nothing now and is kept only for backward compatibility. - */ - @Deprecated - public void setFlushQueues(boolean flushQueues) {} - - /** - * Sets the shadow compare mode (see {@link CompareMode} for more info). - * @param compareMode the compare mode. - */ - final public void setShadowCompareMode(CompareMode compareMode) { - shadowRenderer.setShadowCompareMode(compareMode); - } - - /** - * Get the shadow compare mode. - * - * @return the shadow compare mode. - * @see CompareMode - */ - public CompareMode getShadowCompareMode() { - return shadowRenderer.getShadowCompareMode(); - } - - /** - * Sets the filtering mode for shadow edges see {@link EdgeFilteringMode} for more info - * @param filterMode the filtering mode for shadow edges. - */ - final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { - shadowRenderer.setEdgeFilteringMode(filterMode); - } - - /** - * - * WARNING this parameter is defaulted to true for the shadow filter. Setting it to true, may produce edges artifacts on shadows.
                  - *
                  - * Set to true if you want back faces shadows on geometries. - * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting.
                  - *
                  - * Setting this parameter will override this parameter for ALL materials in the scene. - * This also will automatically adjust the face cull mode and the PolyOffset of the pre shadow pass. - * You can modify them by using {@link #getPreShadowForcedRenderState()}.
                  - *
                  - * If you want to set it differently for each material in the scene you have to use the ShadowRenderer instead - * of the shadow filter. - * - * @param renderBackFacesShadows true if back faces shadows on geometries have to be rendered and false otherwise. - */ - public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { - shadowRenderer.setRenderBackFacesShadows(renderBackFacesShadows); - } - - /** - * Is this filter renders back faces shadows. - * @return true if this filter renders back faces shadows and false otherwise. - */ - public boolean isRenderBackFacesShadows() { - return shadowRenderer.isRenderBackFacesShadows(); - } - - /** - * Get the pre-shadows pass render state. - * use it to adjust the RenderState parameters of the pre shadow pass. - * Note that this will be overridden if the preShadow technique in the material has a ForcedRenderState - * @return the pre shadow render state. - */ - public RenderState getPreShadowForcedRenderState() { - return shadowRenderer.getPreShadowForcedRenderState(); - } - - - /** - * Get the edge filtering mode. - * @return the edge filtering mode. - */ - public EdgeFilteringMode getEdgeFilteringMode() { - return shadowRenderer.getEdgeFilteringMode(); - } - - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - //OutputCapsule oc = ex.getCapsule(this); - - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - //InputCapsule ic = im.getCapsule(this); - - } -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java b/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java deleted file mode 100644 index 6b30a2812a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/AbstractShadowRendererVR.java +++ /dev/null @@ -1,835 +0,0 @@ -package com.jme3.shadow; - -/* - * Copyright (c) 2009-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.asset.AssetManager; -import com.jme3.export.*; -import com.jme3.material.Material; -import com.jme3.material.RenderState; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -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; -import com.jme3.renderer.queue.GeometryList; -import com.jme3.renderer.queue.OpaqueComparator; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.renderer.queue.RenderQueue.ShadowMode; -import com.jme3.scene.Geometry; -import com.jme3.scene.Spatial; -import com.jme3.scene.debug.WireFrustum; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture.MagFilter; -import com.jme3.texture.Texture.MinFilter; -import com.jme3.texture.Texture.ShadowCompareMode; -import com.jme3.texture.Texture2D; -import com.jme3.ui.Picture; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; - -/** - * Abstract shadow renderer that holds commons feature to have for a shadow - * renderer. - * - * @author Rémy Bouquet aka Nehon - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public abstract class AbstractShadowRendererVR implements SceneProcessor, Savable { - - protected int nbShadowMaps = 1; - protected float shadowMapSize; - protected float shadowIntensity = 0.7f; - protected RenderManager renderManager; - protected ViewPort viewPort; - protected FrameBuffer[] shadowFB; - protected Texture2D[] shadowMaps; - protected Texture2D dummyTex; - protected Material preshadowMat; - protected Material postshadowMat; - protected Matrix4f[] lightViewProjectionsMatrices; - protected AssetManager assetManager; - protected boolean debug = false; - protected float edgesThickness = 1.0f; - protected EdgeFilteringMode edgeFilteringMode = EdgeFilteringMode.Bilinear; - protected CompareMode shadowCompareMode = CompareMode.Hardware; - protected Picture[] dispPic; - protected RenderState forcedRenderState = new RenderState(); - protected boolean renderBackFacesShadows; - - protected AppProfiler profiler = null; - - /** - * true if the fallback material should be used, otherwise false - */ - protected boolean needsfallBackMaterial = false; - /** - * name of the post material technique - */ - protected String postTechniqueName = "PostShadow"; - /** - * list of materials for post shadow queue geometries - */ - protected List matCache = new ArrayList(); - protected GeometryList lightReceivers = new GeometryList(new OpaqueComparator()); - protected GeometryList shadowMapOccluders = new GeometryList(new OpaqueComparator()); - private String[] shadowMapStringCache; - private String[] lightViewStringCache; - /** - * fade shadows at distance - */ - protected float zFarOverride = 0; - protected Vector2f fadeInfo; - protected float fadeLength; - protected Camera frustumCam; - /** - * true to skip the post pass when there are no shadow casters - */ - protected boolean skipPostPass; - - /** - * used for serialization - */ - protected AbstractShadowRendererVR(){ - } - - /** - * Create an abstract shadow renderer. Subclasses invoke this constructor. - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadow maps (512,1024,2048, - * etc...) - * @param nbShadowMaps the number of shadow maps rendered (the more shadow - * maps the more quality, the fewer fps). - */ - protected AbstractShadowRendererVR(AssetManager assetManager, int shadowMapSize, int nbShadowMaps) { - - this.assetManager = assetManager; - this.nbShadowMaps = nbShadowMaps; - this.shadowMapSize = shadowMapSize; - init(assetManager, nbShadowMaps, shadowMapSize); - - } - - private void init(AssetManager assetManager, int nbShadowMaps, int shadowMapSize) { - this.postshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PostShadow.j3md"); - shadowFB = new FrameBuffer[nbShadowMaps]; - shadowMaps = new Texture2D[nbShadowMaps]; - dispPic = new Picture[nbShadowMaps]; - lightViewProjectionsMatrices = new Matrix4f[nbShadowMaps]; - shadowMapStringCache = new String[nbShadowMaps]; - lightViewStringCache = new String[nbShadowMaps]; - - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) - dummyTex = new Texture2D(shadowMapSize, shadowMapSize, Format.RGBA8); - - preshadowMat = new Material(assetManager, "Common/MatDefs/Shadow/PreShadow.j3md"); - postshadowMat.setFloat("ShadowMapSize", shadowMapSize); - - for (int i = 0; i < nbShadowMaps; i++) { - lightViewProjectionsMatrices[i] = new Matrix4f(); - shadowFB[i] = new FrameBuffer(shadowMapSize, shadowMapSize, 1); - shadowMaps[i] = new Texture2D(shadowMapSize, shadowMapSize, Format.Depth); - - shadowFB[i].setDepthTexture(shadowMaps[i]); - - //DO NOT COMMENT THIS (it prevent the OSX incomplete read buffer crash) - shadowFB[i].setColorTexture(dummyTex); - shadowMapStringCache[i] = "ShadowMap" + i; - lightViewStringCache[i] = "LightViewProjectionMatrix" + i; - - postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); - - //quads for debuging purpose - dispPic[i] = new Picture("Picture" + i); - dispPic[i].setTexture(assetManager, shadowMaps[i], false); - } - - setShadowCompareMode(shadowCompareMode); - setEdgeFilteringMode(edgeFilteringMode); - setShadowIntensity(shadowIntensity); - initForcedRenderState(); - setRenderBackFacesShadows(isRenderBackFacesShadows()); - } - - protected void initForcedRenderState() { - forcedRenderState.setFaceCullMode(RenderState.FaceCullMode.Front); - forcedRenderState.setColorWrite(false); - forcedRenderState.setDepthWrite(true); - forcedRenderState.setDepthTest(true); - } - - /** - * set the post shadow material for this renderer - * - * @param postShadowMat - */ - protected final void setPostShadowMaterial(Material postShadowMat) { - this.postshadowMat = postShadowMat; - postshadowMat.setFloat("ShadowMapSize", shadowMapSize); - for (int i = 0; i < nbShadowMaps; i++) { - postshadowMat.setTexture(shadowMapStringCache[i], shadowMaps[i]); - } - setShadowCompareMode(shadowCompareMode); - setEdgeFilteringMode(edgeFilteringMode); - setShadowIntensity(shadowIntensity); - } - - /** - * Sets the filtering mode for shadow edges. See {@link EdgeFilteringMode} - * for more info. - * - * @param filterMode the desired filter mode (not null) - */ - final public void setEdgeFilteringMode(EdgeFilteringMode filterMode) { - if (filterMode == null) { - throw new NullPointerException(); - } - - this.edgeFilteringMode = filterMode; - postshadowMat.setInt("FilterMode", filterMode.getMaterialParamValue()); - postshadowMat.setFloat("PCFEdge", edgesThickness); - if (shadowCompareMode == CompareMode.Hardware) { - for (Texture2D shadowMap : shadowMaps) { - if (filterMode == EdgeFilteringMode.Bilinear) { - shadowMap.setMagFilter(MagFilter.Bilinear); - shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); - } else { - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } - } - } - - /** - * Get the edge filtering mode. - * @return the edge filtering mode. - */ - public EdgeFilteringMode getEdgeFilteringMode() { - return edgeFilteringMode; - } - - /** - * Sets the shadow compare mode. See {@link CompareMode} for more info. - * - * @param compareMode the desired compare mode (not null) - */ - final public void setShadowCompareMode(CompareMode compareMode) { - if (compareMode == null) { - throw new IllegalArgumentException("Shadow compare mode cannot be null"); - } - - this.shadowCompareMode = compareMode; - for (Texture2D shadowMap : shadowMaps) { - if (compareMode == CompareMode.Hardware) { - shadowMap.setShadowCompareMode(ShadowCompareMode.LessOrEqual); - if (edgeFilteringMode == EdgeFilteringMode.Bilinear) { - shadowMap.setMagFilter(MagFilter.Bilinear); - shadowMap.setMinFilter(MinFilter.BilinearNoMipMaps); - } else { - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } else { - shadowMap.setShadowCompareMode(ShadowCompareMode.Off); - shadowMap.setMagFilter(MagFilter.Nearest); - shadowMap.setMinFilter(MinFilter.NearestNoMipMaps); - } - } - postshadowMat.setBoolean("HardwareShadows", compareMode == CompareMode.Hardware); - } - - /** - * returns the shadow compare mode - * - * @see CompareMode - * @return the shadowCompareMode - */ - public CompareMode getShadowCompareMode() { - return shadowCompareMode; - } - - /** - * debug function to create a visible frustum - */ - protected Geometry createFrustum(Vector3f[] pts, int i) { - WireFrustum frustum = new WireFrustum(pts); - Geometry frustumMdl = new Geometry("f", frustum); - frustumMdl.setCullHint(Spatial.CullHint.Never); - frustumMdl.setShadowMode(ShadowMode.Off); - Material mat = new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"); - mat.getAdditionalRenderState().setWireframe(true); - frustumMdl.setMaterial(mat); - switch (i) { - case 0: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Pink); - break; - case 1: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Red); - break; - case 2: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Green); - break; - case 3: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.Blue); - break; - default: - frustumMdl.getMaterial().setColor("Color", ColorRGBA.White); - break; - } - - frustumMdl.updateGeometricState(); - return frustumMdl; - } - - /** - * Initialize this shadow renderer prior to its first update. - * - * @param rm the render manager - * @param vp the viewport - */ - public void initialize(RenderManager rm, ViewPort vp) { - renderManager = rm; - viewPort = vp; - postTechniqueName = "PostShadow"; - if(zFarOverride>0 && frustumCam == null){ - initFrustumCam(); - } - } - - /** - * delegates the initialization of the frustum cam to child renderers - */ - protected abstract void initFrustumCam(); - - /** - * Test whether this shadow renderer has been initialized. - * - * @return true if initialized, otherwise false - */ - public boolean isInitialized() { - return viewPort != null; - } - - /** - * Invoked once per frame to update the shadow cams according to the light - * view. - * - * @param viewCam the scene cam - */ - protected abstract void updateShadowCams(Camera viewCam); - - /** - * Returns a subclass-specific geometryList containing the occluders to be - * rendered in the shadow map - * - * @param shadowMapIndex the index of the shadow map being rendered - * @param shadowMapOccluders the list of occluders - * @return a list of occluders - */ - protected abstract GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders); - - /** - * return the shadow camera to use for rendering the shadow map according - * the given index - * - * @param shadowMapIndex the index of the shadow map being rendered - * @return the shadowCam - */ - protected abstract Camera getShadowCam(int shadowMapIndex); - - @Override - public void setProfiler(AppProfiler profiler) { - this.profiler = profiler; - } - - /** - * responsible for displaying the frustum of the shadow cam for debug - * purpose - * - * @param shadowMapIndex - */ - protected void doDisplayFrustumDebug(int shadowMapIndex) { - } - - @SuppressWarnings("fallthrough") - public void postQueue(RenderQueue rq) { - lightReceivers.clear(); - skipPostPass = false; - if ( !checkCulling(viewPort.getCamera()) ) { - skipPostPass = true; - return; - } - - updateShadowCams(viewPort.getCamera()); - - Renderer r = renderManager.getRenderer(); - renderManager.setForcedMaterial(preshadowMat); - renderManager.setForcedTechnique("PreShadow"); - - for (int shadowMapIndex = 0; shadowMapIndex < nbShadowMaps; shadowMapIndex++) { - - if (debugfrustums) { - doDisplayFrustumDebug(shadowMapIndex); - } - renderShadowMap(shadowMapIndex); - - } - - debugfrustums = false; - - //restore setting for future rendering - r.setFrameBuffer(viewPort.getOutputFrameBuffer()); - renderManager.setForcedMaterial(null); - renderManager.setForcedTechnique(null); - renderManager.setCamera(viewPort.getCamera(), false); - - } - - protected void renderShadowMap(int shadowMapIndex) { - shadowMapOccluders = getOccludersToRender(shadowMapIndex, shadowMapOccluders); - Camera shadowCam = getShadowCam(shadowMapIndex); - - //saving light view projection matrix for this split - lightViewProjectionsMatrices[shadowMapIndex].set(shadowCam.getViewProjectionMatrix()); - renderManager.setCamera(shadowCam, false); - - renderManager.getRenderer().setFrameBuffer(shadowFB[shadowMapIndex]); - renderManager.getRenderer().clearBuffers(true, true, true); - renderManager.setForcedRenderState(forcedRenderState); - - // render shadow casters to shadow map - viewPort.getQueue().renderShadowQueue(shadowMapOccluders, renderManager, shadowCam, true); - renderManager.setForcedRenderState(null); - } - boolean debugfrustums = false; - - /** - * Force the frustum to be displayed. - */ - public void displayFrustum() { - debugfrustums = true; - } - - /** - * For debugging purposes, display depth shadow maps. - */ - protected void displayShadowMap(Renderer r) { - Camera cam = viewPort.getCamera(); - renderManager.setCamera(cam, true); - int h = cam.getHeight(); - for (int i = 0; i < dispPic.length; i++) { - dispPic[i].setPosition((128 * i) + (150 + 64 * (i + 1)), h / 20f); - dispPic[i].setWidth(128); - dispPic[i].setHeight(128); - dispPic[i].updateGeometricState(); - renderManager.renderGeometry(dispPic[i]); - } - renderManager.setCamera(cam, false); - } - - /** - * For debugging purposes, "snapshot" the current frustum to the scene. - */ - public void displayDebug() { - debug = true; - } - - protected abstract void getReceivers(GeometryList lightReceivers); - - public void postFrame(FrameBuffer out) { - if (skipPostPass) { - return; - } - if (debug) { - displayShadowMap(renderManager.getRenderer()); - } - - getReceivers(lightReceivers); - - if (lightReceivers.size() != 0) { - //setting params to receiving geometry list - setMatParams(lightReceivers); - - Camera cam = viewPort.getCamera(); - //some materials in the scene does not have a post shadow technique so we're using the fall back material - if (needsfallBackMaterial) { - renderManager.setForcedMaterial(postshadowMat); - } - - //forcing the post shadow technique and render state - renderManager.setForcedTechnique(postTechniqueName); - - //rendering the post shadow pass - viewPort.getQueue().renderShadowQueue(lightReceivers, renderManager, cam, false); - - //resetting renderManager settings - renderManager.setForcedTechnique(null); - renderManager.setForcedMaterial(null); - renderManager.setCamera(cam, false); - - //clearing the params in case there are some other shadow renderers - clearMatParams(); - } - } - - /** - * This method is called once per frame and is responsible for clearing any - * material parameters that subclasses may need to clear on the post material. - * - * @param material the material that was used for the post shadow pass - */ - protected abstract void clearMaterialParameters(Material material); - - private void clearMatParams(){ - for (Material mat : matCache) { - - //clearing only necessary params, the others may be set by other - //renderers - //Note that j start at 1 because other shadow renderers will have - //at least 1 shadow map and will set it on each frame anyway. - for (int j = 1; j < nbShadowMaps; j++) { - mat.clearParam(lightViewStringCache[j]); - } - for (int j = 1; j < nbShadowMaps; j++) { - mat.clearParam(shadowMapStringCache[j]); - } - mat.clearParam("FadeInfo"); - clearMaterialParameters(mat); - } - //No need to clear the postShadowMat params as the instance is locale to each renderer - } - - /** - * This method is called once per frame and is responsible for setting any - * material parameters that subclasses may need to set on the post material. - * - * @param material the material to use for the post shadow pass - */ - protected abstract void setMaterialParameters(Material material); - - private void setMatParams(GeometryList l) { - //iteration throught all the geometries of the list to gather the materials - - buildMatCache(l); - - //iterating through the mat cache and setting the parameters - for (Material mat : matCache) { - - mat.setFloat("ShadowMapSize", shadowMapSize); - - for (int j = 0; j < nbShadowMaps; j++) { - mat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); - } - for (int j = 0; j < nbShadowMaps; j++) { - mat.setTexture(shadowMapStringCache[j], shadowMaps[j]); - } - mat.setBoolean("HardwareShadows", shadowCompareMode == CompareMode.Hardware); - mat.setInt("FilterMode", edgeFilteringMode.getMaterialParamValue()); - mat.setFloat("PCFEdge", edgesThickness); - mat.setFloat("ShadowIntensity", shadowIntensity); - mat.setBoolean("BackfaceShadows", renderBackFacesShadows); - - if (fadeInfo != null) { - mat.setVector2("FadeInfo", fadeInfo); - } - - setMaterialParameters(mat); - } - - //At least one material of the receiving geoms does not support the post shadow techniques - //so we fall back to the forced material solution (transparent shadows won't be supported for these objects) - if (needsfallBackMaterial) { - setPostShadowParams(); - } - - } - - private void buildMatCache(GeometryList l) { - matCache.clear(); - for (int i = 0; i < l.size(); i++) { - Material mat = l.get(i).getMaterial(); - //checking if the material has the post technique and adding it to the material cache - if (mat.getMaterialDef().getTechniqueDefs(postTechniqueName) != null) { - if (!matCache.contains(mat)) { - matCache.add(mat); - } - } else { - needsfallBackMaterial = true; - } - } - } - - /** - * for internal use only - */ - protected void setPostShadowParams() { - setMaterialParameters(postshadowMat); - for (int j = 0; j < nbShadowMaps; j++) { - postshadowMat.setMatrix4(lightViewStringCache[j], lightViewProjectionsMatrices[j]); - postshadowMat.setTexture(shadowMapStringCache[j], shadowMaps[j]); - } - if (fadeInfo != null) { - postshadowMat.setVector2("FadeInfo", fadeInfo); - } - postshadowMat.setBoolean("BackfaceShadows", renderBackFacesShadows); - } - - /** - * How far the shadows are rendered in the view - * - * @see #setShadowZExtend(float zFar) - * @return shadowZExtend - */ - public float getShadowZExtend() { - return zFarOverride; - } - - /** - * Set the distance from the eye where the shadows will be rendered default - * value is dynamically computed to the shadow casters/receivers union bound - * zFar, capped to view frustum far value. - * - * @param zFar the zFar values that override the computed one - */ - public void setShadowZExtend(float zFar) { - this.zFarOverride = zFar; - if(zFarOverride == 0){ - fadeInfo = null; - frustumCam = null; - }else{ - if (fadeInfo != null) { - fadeInfo.set(zFarOverride - fadeLength, 1f / fadeLength); - } - if(frustumCam == null && viewPort != null){ - initFrustumCam(); - } - } - } - - /** - * Define the length over which the shadow will fade out when using a - * shadowZextend This is useful to make dynamic shadows fade into baked - * shadows in the distance. - * - * @param length the fade length in world units - */ - public void setShadowZFadeLength(float length) { - if (length == 0) { - fadeInfo = null; - fadeLength = 0; - postshadowMat.clearParam("FadeInfo"); - } else { - if (zFarOverride == 0) { - fadeInfo = new Vector2f(0, 0); - } else { - fadeInfo = new Vector2f(zFarOverride - length, 1.0f / length); - } - fadeLength = length; - postshadowMat.setVector2("FadeInfo", fadeInfo); - } - } - - /** - * get the length over which the shadow will fade out when using a - * shadowZextend - * - * @return the fade length in world units - */ - public float getShadowZFadeLength() { - if (fadeInfo != null) { - return zFarOverride - fadeInfo.x; - } - return 0f; - } - - /** - * returns true if the light source bounding box is in the view frustum - * @return true if box in frustum - */ - protected abstract boolean checkCulling(Camera viewCam); - - public void preFrame(float tpf) { - } - - public void cleanup() { - } - - public void reshape(ViewPort vp, int w, int h) { - } - - /** - * Returns the shadow intensity. - * - * @see #setShadowIntensity(float shadowIntensity) - * @return shadowIntensity - */ - public float getShadowIntensity() { - return shadowIntensity; - } - - /** - * Set the shadowIntensity. The value should be between 0 and 1. A 0 value - * gives a bright and invisible shadow, a 1 value gives a pitch black - * shadow. The default is 0.7 - * - * @param shadowIntensity the darkness of the shadow - */ - final public void setShadowIntensity(float shadowIntensity) { - this.shadowIntensity = shadowIntensity; - postshadowMat.setFloat("ShadowIntensity", shadowIntensity); - } - - /** - * returns the edges thickness - * - * @see #setEdgesThickness(int edgesThickness) - * @return edgesThickness - */ - public int getEdgesThickness() { - return (int) (edgesThickness * 10); - } - - /** - * Sets the shadow edges thickness. default is 1, setting it to lower values - * can help to reduce the jagged effect of the shadow edges - * @param edgesThickness the shadow edges thickness. - */ - public void setEdgesThickness(int edgesThickness) { - this.edgesThickness = Math.max(1, Math.min(edgesThickness, 10)); - this.edgesThickness *= 0.1f; - postshadowMat.setFloat("PCFEdge", edgesThickness); - } - - /** - * This method does nothing now and is kept only for backward compatibility. - * @return false - * @deprecated This method does nothing now and is kept only for backward compatibility. - */ - @Deprecated - public boolean isFlushQueues() { return false; } - - /** - * This method does nothing now and is kept only for backward compatibility. - * @param flushQueues any boolean. - * @deprecated This method does nothing now and is kept only for backward compatibility. - */ - @Deprecated - public void setFlushQueues(boolean flushQueues) {} - - - /** - * Returns the pre shadows pass render state. - * use it to adjust the RenderState parameters of the pre shadow pass. - * Note that this will be overridden if the preShadow technique in the material has a ForcedRenderState - * @return the pre shadow render state. - */ - public RenderState getPreShadowForcedRenderState() { - return forcedRenderState; - } - - /** - * Set to true if you want back faces shadows on geometries. - * Note that back faces shadows will be blended over dark lighten areas and may produce overly dark lighting. - * - * Also note that setting this parameter will override this parameter for ALL materials in the scene. - * You can alternatively change this parameter on a single material using {@link Material#setBoolean(String, boolean)} - * - * This also will automatically adjust the faceCullMode and the PolyOffset of the pre shadow pass. - * You can modify them by using {@link #getPreShadowForcedRenderState()} - * - * @param renderBackFacesShadows true or false. - */ - public void setRenderBackFacesShadows(Boolean renderBackFacesShadows) { - this.renderBackFacesShadows = renderBackFacesShadows; - if(renderBackFacesShadows) { - getPreShadowForcedRenderState().setPolyOffset(5, 3); - getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Back); - }else{ - getPreShadowForcedRenderState().setPolyOffset(0, 0); - getPreShadowForcedRenderState().setFaceCullMode(RenderState.FaceCullMode.Front); - } - } - - /** - * if this processor renders back faces shadows - * - * @return true if this processor renders back faces shadows - */ - public boolean isRenderBackFacesShadows() { - return renderBackFacesShadows; - } - - /** - * De-serialize this instance, for example when loading from a J3O file. - * - * @param im importer (not null) - */ - public void read(JmeImporter im) throws IOException { - InputCapsule ic = (InputCapsule) im.getCapsule(this); - assetManager = im.getAssetManager(); - nbShadowMaps = ic.readInt("nbShadowMaps", 1); - shadowMapSize = ic.readFloat("shadowMapSize", 0f); - shadowIntensity = ic.readFloat("shadowIntensity", 0.7f); - edgeFilteringMode = ic.readEnum("edgeFilteringMode", EdgeFilteringMode.class, EdgeFilteringMode.Bilinear); - shadowCompareMode = ic.readEnum("shadowCompareMode", CompareMode.class, CompareMode.Hardware); - init(assetManager, nbShadowMaps, (int) shadowMapSize); - edgesThickness = ic.readFloat("edgesThickness", 1.0f); - postshadowMat.setFloat("PCFEdge", edgesThickness); - - } - - /** - * Serialize this instance, for example when saving to a J3O file. - * - * @param ex exporter (not null) - */ - public void write(JmeExporter ex) throws IOException { - OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); - oc.write(nbShadowMaps, "nbShadowMaps", 1); - oc.write(shadowMapSize, "shadowMapSize", 0); - oc.write(shadowIntensity, "shadowIntensity", 0.7f); - oc.write(edgeFilteringMode, "edgeFilteringMode", EdgeFilteringMode.Bilinear); - oc.write(shadowCompareMode, "shadowCompareMode", CompareMode.Hardware); - oc.write(edgesThickness, "edgesThickness", 1.0f); - } -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowFilterVR.java b/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowFilterVR.java deleted file mode 100644 index 5cc149ea3d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowFilterVR.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.jme3.shadow; - -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.asset.AssetManager; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.light.DirectionalLight; -import java.io.IOException; - -/** - * - * This Filter does basically the same as a DirectionalLightShadowRenderer - * except it renders the post shadow pass as a fullscreen quad pass instead of a - * geometry pass. It's mostly faster than PssmShadowRenderer as long as you have - * more than a about ten shadow receiving objects. The expense is the draw back - * that the shadow Receive mode set on spatial is ignored. So basically all and - * only objects that render depth in the scene receive shadows. See this post - * for more details - * http://jmonkeyengine.org/groups/general-2/forum/topic/silly-question-about-shadow-rendering/#post-191599 - * - * API is basically the same as the PssmShadowRenderer; - * - * @author Rémy Bouquet aka Nehon - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class DirectionalLightShadowFilterVR extends AbstractShadowFilterVR { - - /** - * Creates a DirectionalLightShadowFilter Shadow Filter More info on the - * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps - * the more quality, the less fps). - */ - public DirectionalLightShadowFilterVR(AssetManager assetManager, int shadowMapSize, int nbSplits) { - super(assetManager, shadowMapSize, new DirectionalLightShadowRendererVR(assetManager, shadowMapSize, nbSplits)); - } - - /** - * Creates a DirectionalLightShadowFilter Shadow Filter More info on the - * technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html. - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512, 1024, 2048, etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). - * @param useMatDef the material to attach to this filter. - */ - public DirectionalLightShadowFilterVR(AssetManager assetManager, int shadowMapSize, int nbSplits, String useMatDef) { - super(assetManager, shadowMapSize, new DirectionalLightShadowRendererVR(assetManager, shadowMapSize, nbSplits), useMatDef); - } - - /** - * return the light used to cast shadows - * - * @return the DirectionalLight - */ - public DirectionalLight getLight() { - return shadowRenderer.getLight(); - } - - /** - * Sets the light to use to cast shadows - * - * @param light a DirectionalLight - */ - public void setLight(DirectionalLight light) { - shadowRenderer.setLight(light); - } - - /** - * returns the lambda parameter - * - * @see #setLambda(float lambda) - * @return lambda - */ - public float getLambda() { - return shadowRenderer.getLambda(); - } - - /** - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 a low value give a more linear repartition - * resulting in a constant quality in the shadow over the extends, but near - * shadows could look very jagged a high value give a more logarithmic - * repartition resulting in a high quality for near shadows, but the quality - * quickly decrease over the extend. the default value is set to 0.65f - * (theoretic optimal value). - * - * @param lambda the lambda value. - */ - public void setLambda(float lambda) { - shadowRenderer.setLambda(lambda); - } - - /** - * Check if stabilization is enabled. - * @return true if the stabilization is enabled and false otherwise. - * @see #setEnabledStabilization(boolean) - */ - public boolean isEnabledStabilization() { - return shadowRenderer.isEnabledStabilization(); - } - - /** - * Enables the stabilization of the shadow's edges. (default is true) - * This prevents shadow edges from flickering when the camera moves. - * However it can lead to some shadow quality loss in some particular scenes. - * @param stabilize true if the stabilization has to be enabled and false otherwise. - * @see #isEnabledStabilization() - */ - public void setEnabledStabilization(boolean stabilize) { - shadowRenderer.setEnabledStabilization(stabilize); - } - - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = ex.getCapsule(this); - oc.write(shadowRenderer, "shadowRenderer", null); - - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = im.getCapsule(this); - shadowRenderer = (DirectionalLightShadowRendererVR) ic.readSavable("shadowRenderer", null); - } -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowRendererVR.java b/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowRendererVR.java deleted file mode 100644 index c4a1336d35..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/DirectionalLightShadowRendererVR.java +++ /dev/null @@ -1,302 +0,0 @@ -package com.jme3.shadow; - -/* - * Copyright (c) 2009-2019 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.asset.AssetManager; -import com.jme3.export.InputCapsule; -import com.jme3.export.JmeExporter; -import com.jme3.export.JmeImporter; -import com.jme3.export.OutputCapsule; -import com.jme3.light.DirectionalLight; -import com.jme3.material.Material; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.queue.GeometryList; -import com.jme3.renderer.queue.RenderQueue; -import com.jme3.scene.Node; -import com.jme3.scene.Spatial; - -import java.io.IOException; - -/** - * DirectionalLightShadowRenderer renderer use Parallel Split Shadow Mapping - * technique (pssm)
                  It splits the view frustum in several parts and compute - * a shadow map for each one.
                  splits are distributed so that the closer they - * are from the camera, the smaller they are to maximize the resolution used of - * the shadow map.
                  This results in a better quality shadow than standard - * shadow mapping.
                  for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
                  - *

                  - * @author Rémy Bouquet aka Nehon - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class DirectionalLightShadowRendererVR extends AbstractShadowRendererVR { - - protected float lambda = 0.65f; - protected Camera shadowCam; - protected ColorRGBA splits; - protected float[] splitsArray; - protected DirectionalLight light; - protected Vector3f[] points = new Vector3f[8]; - //Holding the info for fading shadows in the far distance - private boolean stabilize = true; - - /** - * Used for serialization use - * DirectionalLightShadowRenderer#DirectionalLightShadowRenderer(AssetManager - * assetManager, int shadowMapSize, int nbSplits) - */ - public DirectionalLightShadowRendererVR() { - super(); - } - - /** - * Create a DirectionalLightShadowRenderer More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps - * the more quality, the less fps). - */ - public DirectionalLightShadowRendererVR(AssetManager assetManager, int shadowMapSize, int nbSplits) { - super(assetManager, shadowMapSize, nbSplits); - init(nbSplits, shadowMapSize); - } - - private void init(int nbSplits, int shadowMapSize) { - nbShadowMaps = Math.max(Math.min(nbSplits, 4), 1); - if (nbShadowMaps != nbSplits) { - throw new IllegalArgumentException("Number of splits must be between 1 and 4. Given value : " + nbSplits); - } - splits = new ColorRGBA(); - splitsArray = new float[nbSplits + 1]; - shadowCam = new Camera(shadowMapSize, shadowMapSize); - shadowCam.setParallelProjection(true); - for (int i = 0; i < points.length; i++) { - points[i] = new Vector3f(); - } - } - - @Override - protected void initFrustumCam() { - //nothing to do - } - - /** - * return the light used to cast shadows - * @return the DirectionalLight - */ - public DirectionalLight getLight() { - return light; - } - - /** - * Sets the light to use to cast shadows - * @param light a DirectionalLight - */ - public void setLight(DirectionalLight light) { - this.light = light; - } - - @Override - protected void updateShadowCams(Camera viewCam) { - - float zFar = zFarOverride; - if (zFar == 0) { - zFar = viewCam.getFrustumFar(); - } - - //We prevent computing the frustum points and splits with zeroed or negative near clip value - float frustumNear = Math.max(viewCam.getFrustumNear(), 0.001f); - ShadowUtil.updateFrustumPoints(viewCam, frustumNear, zFar, 1.0f, points); - - //shadowCam.setDirection(direction); - shadowCam.getRotation().lookAt(light.getDirection(), shadowCam.getUp()); - shadowCam.update(); - shadowCam.updateViewProjection(); - - PssmShadowUtil.updateFrustumSplits(splitsArray, frustumNear, zFar, lambda); - - // in parallel projection shadow position goe from 0 to 1 - if(viewCam.isParallelProjection()){ - for (int i = 0; i < nbShadowMaps; i++) { - splitsArray[i] = splitsArray[i]/(zFar- frustumNear); - } - } - - switch (splitsArray.length) { - case 5: - splits.a = splitsArray[4]; - case 4: - splits.b = splitsArray[3]; - case 3: - splits.g = splitsArray[2]; - case 2: - case 1: - splits.r = splitsArray[1]; - break; - } - - } - - @Override - protected GeometryList getOccludersToRender(int shadowMapIndex, GeometryList shadowMapOccluders) { - - // update frustum points based on current camera and split - ShadowUtil.updateFrustumPoints(viewPort.getCamera(), splitsArray[shadowMapIndex], splitsArray[shadowMapIndex + 1], 1.0f, points); - - //Updating shadow cam with current split frusta - if (lightReceivers.size()==0) { - for (Spatial scene : viewPort.getScenes()) { - ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), RenderQueue.ShadowMode.Receive, lightReceivers); - } - } - ShadowUtil.updateShadowCamera(viewPort, lightReceivers, shadowCam, points, shadowMapOccluders, stabilize?shadowMapSize:0); - - return shadowMapOccluders; - } - - @Override - protected void getReceivers(GeometryList lightReceivers) { - if (lightReceivers.size()==0) { - for (Spatial scene : viewPort.getScenes()) { - ShadowUtil.getGeometriesInCamFrustum(scene, viewPort.getCamera(), RenderQueue.ShadowMode.Receive, lightReceivers); - } - } - } - - @Override - protected Camera getShadowCam(int shadowMapIndex) { - return shadowCam; - } - - @Override - protected void doDisplayFrustumDebug(int shadowMapIndex) { - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); - ShadowUtil.updateFrustumPoints2(shadowCam, points); - ((Node) viewPort.getScenes().get(0)).attachChild(createFrustum(points, shadowMapIndex)); - } - - @Override - protected void setMaterialParameters(Material material) { - material.setColor("Splits", splits); - material.setVector3("LightDir", light.getDirection()); - if (fadeInfo != null) { - material.setVector2("FadeInfo", fadeInfo); - } - } - - @Override - protected void clearMaterialParameters(Material material) { - material.clearParam("Splits"); - material.clearParam("FadeInfo"); - material.clearParam("LightDir"); - } - - /** - * returns the lambda parameter see #setLambda(float lambda) - * - * @return lambda - */ - public float getLambda() { - return lambda; - } - - /** - * Adjust the repartition of the different shadow maps in the shadow extend - * usually goes from 0.0 to 1.0 - * a low value give a more linear repartition resulting in a constant quality in the shadow over the extends, but near shadows could look very jagged - * a high value give a more logarithmic repartition resulting in a high quality for near shadows, but the quality quickly decrease over the extend. - * the default value is set to 0.65f (theoretic optimal value). - * @param lambda the lambda value. - */ - public void setLambda(float lambda) { - this.lambda = lambda; - } - - /** - * Check if the stabilization is enabled. - * @return true if stabilization is enabled and false otherwise. - */ - public boolean isEnabledStabilization() { - return stabilize; - } - - /** - * Enables the stabilization of the shadow's edges. (default is true) - * This prevents shadow edges from flickering when the camera moves. - * However it can lead to some shadow quality loss in some particular scenes. - * @param stabilize true if stabilization has to be enabled and false otherwise. - */ - public void setEnabledStabilization(boolean stabilize) { - this.stabilize = stabilize; - } - - @Override - public void read(JmeImporter im) throws IOException { - super.read(im); - InputCapsule ic = (InputCapsule) im.getCapsule(this); - lambda = ic.readFloat("lambda", 0.65f); - zFarOverride = ic.readInt("zFarOverride", 0); - light = (DirectionalLight) ic.readSavable("light", null); - fadeInfo = (Vector2f) ic.readSavable("fadeInfo", null); - fadeLength = ic.readFloat("fadeLength", 0f); - init(nbShadowMaps, (int) shadowMapSize); - } - - @Override - public void write(JmeExporter ex) throws IOException { - super.write(ex); - OutputCapsule oc = (OutputCapsule) ex.getCapsule(this); - oc.write(lambda, "lambda", 0.65f); - oc.write(zFarOverride, "zFarOverride", 0); - oc.write(light, "light", null); - oc.write(fadeInfo, "fadeInfo", null); - oc.write(fadeLength, "fadeLength", 0f); - } - - /** - * Directional light is always in the view frustum - * @param viewCam - * @return true - */ - @Override - protected boolean checkCulling(Camera viewCam) { - return true; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/InstancedDirectionalShadowFilter.java b/jme3-vr/src/main/java/com/jme3/shadow/InstancedDirectionalShadowFilter.java deleted file mode 100644 index d3f0798127..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/InstancedDirectionalShadowFilter.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * To change this license header, choose License Headers in Project Properties. - * To change this template file, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.shadow; - -import com.jme3.app.Application; -import com.jme3.math.Matrix4f; -import com.jme3.math.Vector4f; -import com.jme3.renderer.Camera; - -/** - * An instanced version of the {@link DirectionalLightShadowFilterVR directional light shadow filter} dedi. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - */ -public class InstancedDirectionalShadowFilter extends DirectionalLightShadowFilterVR { - - private final Vector4f temp4f = new Vector4f(), temp4f2 = new Vector4f(); - - private boolean instanceRendering = false; - - private Camera rightCamera = null; - - /** - * Create a new instanced version of the {@link DirectionalLightShadowFilterVR directional light shadow filter}. - * @param application the application that this filter is attached to. - * @param camera - * @param shadowMapSize the size of the rendered shadowmaps (512, 1024, 2048, etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps the more quality, the less fps). - * @param instancedRendering true if this filter has to use instance rendering and false otherwise. - * @param rightCamera the camera used as right eye in stereo rendering mode. - */ - public InstancedDirectionalShadowFilter(Application application, Camera camera, int shadowMapSize, int nbSplits, boolean instancedRendering, Camera rightCamera) { - super(application.getAssetManager(), shadowMapSize, nbSplits, "Common/MatDefs/VR/PostShadowFilter.j3md"); - this.instanceRendering = instancedRendering; - this.rightCamera = rightCamera; - } - - @Override - protected void preFrame(float tpf) { - shadowRenderer.preFrame(tpf); - if( instanceRendering ) { - material.setMatrix4("ViewProjectionMatrixInverseRight", rightCamera.getViewProjectionMatrix().invert()); - Matrix4f m = rightCamera.getViewProjectionMatrix(); - material.setVector4("ViewProjectionMatrixRow2Right", temp4f2.set(m.m20, m.m21, m.m22, m.m23)); - } - material.setMatrix4("ViewProjectionMatrixInverse", viewPort.getCamera().getViewProjectionMatrix().invert()); - Matrix4f m = viewPort.getCamera().getViewProjectionMatrix(); - material.setVector4("ViewProjectionMatrixRow2", temp4f.set(m.m20, m.m21, m.m22, m.m23)); - } - - /** - * Get if this filter is using instance rendering. - * @return true if this filter is using instance rendering and false otherwise. - * @see #setInstanceRendering(boolean) - */ - public boolean isInstanceRendering() { - return instanceRendering; - } - - /** - * Set if this filter has to use instance rendering. - * @param instanceRendering true if this filter has to use instance rendering and false otherwise. - * @see #isInstanceRendering() - */ - public void setInstanceRendering(boolean instanceRendering) { - this.instanceRendering = instanceRendering; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/shadow/VRDirectionalLightShadowRenderer.java b/jme3-vr/src/main/java/com/jme3/shadow/VRDirectionalLightShadowRenderer.java deleted file mode 100644 index 759fdc42b6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/shadow/VRDirectionalLightShadowRenderer.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (c) 2009-2018 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.shadow; - -import com.jme3.asset.AssetManager; -import com.jme3.shadow.DirectionalLightShadowRenderer; - -/** - * DirectionalLightShadowRenderer renderer use Parallel Split Shadow Mapping - * technique (pssm)
                  It splits the view frustum in several parts and compute - * a shadow map for each one.
                  splits are distributed so that the closer they - * are from the camera, the smaller they are to maximize the resolution used of - * the shadow map.
                  This results in a better quality shadow than standard - * shadow mapping.
                  for more informations on this read this http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html
                  - *

                  - * @author Rémy Bouquet aka Nehon - */ -public class VRDirectionalLightShadowRenderer extends DirectionalLightShadowRenderer { - - /** - * Create a OculusDirectionalLightShadowRenderer More info on the technique at http://http.developer.nvidia.com/GPUGems3/gpugems3_ch10.html - * - * @param assetManager the application asset manager - * @param shadowMapSize the size of the rendered shadowmaps (512,1024,2048, - * etc...) - * @param nbSplits the number of shadow maps rendered (the more shadow maps - * the more quality, the less fps). - */ - public VRDirectionalLightShadowRenderer(AssetManager assetManager, int shadowMapSize, int nbSplits) { - super(assetManager, shadowMapSize, nbSplits); - } - - @Override - public VRDirectionalLightShadowRenderer clone() { - VRDirectionalLightShadowRenderer clone = new VRDirectionalLightShadowRenderer(assetManager, (int)shadowMapSize, nbShadowMaps); - clone.setEdgeFilteringMode(getEdgeFilteringMode()); - clone.setEdgesThickness(getEdgesThickness()); - clone.setEnabledStabilization(isEnabledStabilization()); - clone.setLambda(getLambda()); - clone.setLight(getLight()); - clone.setShadowCompareMode(getShadowCompareMode()); - clone.setShadowIntensity(getShadowIntensity()); - clone.setShadowZExtend(getShadowZExtend()); - clone.setShadowZFadeLength(getShadowZFadeLength()); - return clone; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/AppOverrideKeys_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/AppOverrideKeys_t.java deleted file mode 100644 index add3d5d9ac..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/AppOverrideKeys_t.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1485
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class AppOverrideKeys_t extends Structure { - /** - * const char *
                  - * C type : char* - */ - public Pointer pchKey; - /** - * const char *
                  - * C type : char* - */ - public Pointer pchValue; - public AppOverrideKeys_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("pchKey", "pchValue"); - } - /** - * @param pchKey const char *
                  - * C type : char*
                  - * @param pchValue const char *
                  - * C type : char* - */ - public AppOverrideKeys_t(Pointer pchKey, Pointer pchValue) { - super(); - this.pchKey = pchKey; - this.pchValue = pchValue; - } - public AppOverrideKeys_t(Pointer peer) { - super(peer); - } - public static class ByReference extends AppOverrideKeys_t implements Structure.ByReference { - - }; - public static class ByValue extends AppOverrideKeys_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/COpenVRContext.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/COpenVRContext.java deleted file mode 100644 index b57646031b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/COpenVRContext.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1670
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class COpenVRContext extends Structure { - /** - * class vr::IVRSystem *
                  - * C type : intptr_t - */ - public IntByReference m_pVRSystem; - /** - * class vr::IVRChaperone *
                  - * C type : intptr_t - */ - public IntByReference m_pVRChaperone; - /** - * class vr::IVRChaperoneSetup *
                  - * C type : intptr_t - */ - public IntByReference m_pVRChaperoneSetup; - /** - * class vr::IVRCompositor *
                  - * C type : intptr_t - */ - public IntByReference m_pVRCompositor; - /** - * class vr::IVROverlay *
                  - * C type : intptr_t - */ - public IntByReference m_pVROverlay; - /** - * class vr::IVRResources *
                  - * C type : intptr_t - */ - public IntByReference m_pVRResources; - /** - * class vr::IVRRenderModels *
                  - * C type : intptr_t - */ - public IntByReference m_pVRRenderModels; - /** - * class vr::IVRExtendedDisplay *
                  - * C type : intptr_t - */ - public IntByReference m_pVRExtendedDisplay; - /** - * class vr::IVRSettings *
                  - * C type : intptr_t - */ - public IntByReference m_pVRSettings; - /** - * class vr::IVRApplications *
                  - * C type : intptr_t - */ - public IntByReference m_pVRApplications; - /** - * class vr::IVRTrackedCamera *
                  - * C type : intptr_t - */ - public IntByReference m_pVRTrackedCamera; - /** - * class vr::IVRScreenshots *
                  - * C type : intptr_t - */ - public IntByReference m_pVRScreenshots; - /** - * class vr::IVRDriverManager *
                  - * C type : intptr_t - */ - public IntByReference m_pVRDriverManager; - /** - * class vr::IVRInput *
                  - * C type : intptr_t - */ - public IntByReference m_pVRInput; - /** - * class vr::IVRIOBuffer *
                  - * C type : intptr_t - */ - public IntByReference m_pVRIOBuffer; - /** - * class vr::IVRSpatialAnchors *
                  - * C type : intptr_t - */ - public IntByReference m_pVRSpatialAnchors; - public COpenVRContext() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_pVRSystem", "m_pVRChaperone", "m_pVRChaperoneSetup", "m_pVRCompositor", "m_pVROverlay", "m_pVRResources", "m_pVRRenderModels", "m_pVRExtendedDisplay", "m_pVRSettings", "m_pVRApplications", "m_pVRTrackedCamera", "m_pVRScreenshots", "m_pVRDriverManager", "m_pVRInput", "m_pVRIOBuffer", "m_pVRSpatialAnchors"); - } - public COpenVRContext(Pointer peer) { - super(peer); - } - public static class ByReference extends COpenVRContext implements Structure.ByReference { - - }; - public static class ByValue extends COpenVRContext implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CVRSettingHelper.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/CVRSettingHelper.java deleted file mode 100644 index d2c37f9df1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CVRSettingHelper.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1592
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class CVRSettingHelper extends Structure { - /** - * class vr::IVRSettings *
                  - * C type : intptr_t - */ - public IntByReference m_pSettings; - public CVRSettingHelper() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_pSettings"); - } - /** - * @param m_pSettings class vr::IVRSettings *
                  - * C type : intptr_t - */ - public CVRSettingHelper(IntByReference m_pSettings) { - super(); - this.m_pSettings = m_pSettings; - } - public CVRSettingHelper(Pointer peer) { - super(peer); - } - public static class ByReference extends CVRSettingHelper implements Structure.ByReference { - - }; - public static class ByValue extends CVRSettingHelper implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CameraVideoStreamFrameHeader_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/CameraVideoStreamFrameHeader_t.java deleted file mode 100644 index d8476324dd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/CameraVideoStreamFrameHeader_t.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1466
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class CameraVideoStreamFrameHeader_t extends Structure { - /** - * C type : EVRTrackedCameraFrameType - */ - public int eFrameType; - public int nWidth; - public int nHeight; - public int nBytesPerPixel; - public int nFrameSequence; - /** C type : TrackedDevicePose_t */ - public TrackedDevicePose_t standingTrackedDevicePose; - public CameraVideoStreamFrameHeader_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("eFrameType", "nWidth", "nHeight", "nBytesPerPixel", "nFrameSequence", "standingTrackedDevicePose"); - } - /** - * @param eFrameType @see JOpenVRLibrary.EVRTrackedCameraFrameType
                  - * C type : EVRTrackedCameraFrameType
                  - * @param standingTrackedDevicePose C type : TrackedDevicePose_t - */ - public CameraVideoStreamFrameHeader_t(int eFrameType, int nWidth, int nHeight, int nBytesPerPixel, int nFrameSequence, TrackedDevicePose_t standingTrackedDevicePose) { - super(); - this.eFrameType = eFrameType; - this.nWidth = nWidth; - this.nHeight = nHeight; - this.nBytesPerPixel = nBytesPerPixel; - this.nFrameSequence = nFrameSequence; - this.standingTrackedDevicePose = standingTrackedDevicePose; - } - public CameraVideoStreamFrameHeader_t(Pointer peer) { - super(peer); - } - public static class ByReference extends CameraVideoStreamFrameHeader_t implements Structure.ByReference { - - }; - public static class ByValue extends CameraVideoStreamFrameHeader_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_CumulativeStats.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_CumulativeStats.java deleted file mode 100644 index c2e741fa42..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_CumulativeStats.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1528
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class Compositor_CumulativeStats extends Structure { - public int m_nPid; - public int m_nNumFramePresents; - public int m_nNumDroppedFrames; - public int m_nNumReprojectedFrames; - public int m_nNumFramePresentsOnStartup; - public int m_nNumDroppedFramesOnStartup; - public int m_nNumReprojectedFramesOnStartup; - public int m_nNumLoading; - public int m_nNumFramePresentsLoading; - public int m_nNumDroppedFramesLoading; - public int m_nNumReprojectedFramesLoading; - public int m_nNumTimedOut; - public int m_nNumFramePresentsTimedOut; - public int m_nNumDroppedFramesTimedOut; - public int m_nNumReprojectedFramesTimedOut; - public Compositor_CumulativeStats() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nPid", "m_nNumFramePresents", "m_nNumDroppedFrames", "m_nNumReprojectedFrames", "m_nNumFramePresentsOnStartup", "m_nNumDroppedFramesOnStartup", "m_nNumReprojectedFramesOnStartup", "m_nNumLoading", "m_nNumFramePresentsLoading", "m_nNumDroppedFramesLoading", "m_nNumReprojectedFramesLoading", "m_nNumTimedOut", "m_nNumFramePresentsTimedOut", "m_nNumDroppedFramesTimedOut", "m_nNumReprojectedFramesTimedOut"); - } - public Compositor_CumulativeStats(Pointer peer) { - super(peer); - } - public static class ByReference extends Compositor_CumulativeStats implements Structure.ByReference { - - }; - public static class ByValue extends Compositor_CumulativeStats implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_FrameTiming.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_FrameTiming.java deleted file mode 100644 index 8157254c5a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_FrameTiming.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1511
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class Compositor_FrameTiming extends Structure { - public int m_nSize; - public int m_nFrameIndex; - public int m_nNumFramePresents; - public int m_nNumMisPresented; - public int m_nNumDroppedFrames; - public int m_nReprojectionFlags; - public double m_flSystemTimeInSeconds; - public float m_flPreSubmitGpuMs; - public float m_flPostSubmitGpuMs; - public float m_flTotalRenderGpuMs; - public float m_flCompositorRenderGpuMs; - public float m_flCompositorRenderCpuMs; - public float m_flCompositorIdleCpuMs; - public float m_flClientFrameIntervalMs; - public float m_flPresentCallCpuMs; - public float m_flWaitForPresentCpuMs; - public float m_flSubmitFrameMs; - public float m_flWaitGetPosesCalledMs; - public float m_flNewPosesReadyMs; - public float m_flNewFrameReadyMs; - public float m_flCompositorUpdateStartMs; - public float m_flCompositorUpdateEndMs; - public float m_flCompositorRenderStartMs; - /** C type : TrackedDevicePose_t */ - public TrackedDevicePose_t m_HmdPose; - public Compositor_FrameTiming() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nSize", "m_nFrameIndex", "m_nNumFramePresents", "m_nNumMisPresented", "m_nNumDroppedFrames", "m_nReprojectionFlags", "m_flSystemTimeInSeconds", "m_flPreSubmitGpuMs", "m_flPostSubmitGpuMs", "m_flTotalRenderGpuMs", "m_flCompositorRenderGpuMs", "m_flCompositorRenderCpuMs", "m_flCompositorIdleCpuMs", "m_flClientFrameIntervalMs", "m_flPresentCallCpuMs", "m_flWaitForPresentCpuMs", "m_flSubmitFrameMs", "m_flWaitGetPosesCalledMs", "m_flNewPosesReadyMs", "m_flNewFrameReadyMs", "m_flCompositorUpdateStartMs", "m_flCompositorUpdateEndMs", "m_flCompositorRenderStartMs", "m_HmdPose"); - } - public Compositor_FrameTiming(Pointer peer) { - super(peer); - } - public static class ByReference extends Compositor_FrameTiming implements Structure.ByReference { - - }; - public static class ByValue extends Compositor_FrameTiming implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_OverlaySettings.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_OverlaySettings.java deleted file mode 100644 index ebc1e88b7e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Compositor_OverlaySettings.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1452
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class Compositor_OverlaySettings extends Structure { - public int size; - public byte curved; - public byte antialias; - public float scale; - public float distance; - public float alpha; - public float uOffset; - public float vOffset; - public float uScale; - public float vScale; - public float gridDivs; - public float gridWidth; - public float gridScale; - /** C type : HmdMatrix44_t */ - public HmdMatrix44_t transform; - public Compositor_OverlaySettings() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("size", "curved", "antialias", "scale", "distance", "alpha", "uOffset", "vOffset", "uScale", "vScale", "gridDivs", "gridWidth", "gridScale", "transform"); - } - public Compositor_OverlaySettings(Pointer peer) { - super(peer); - } - public static class ByReference extends Compositor_OverlaySettings implements Structure.ByReference { - - }; - public static class ByValue extends Compositor_OverlaySettings implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/D3D12TextureData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/D3D12TextureData_t.java deleted file mode 100644 index f6769e3e17..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/D3D12TextureData_t.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jme3.system.jopenvr; -import com.jme3.system.jopenvr.JOpenVRLibrary.ID3D12CommandQueue; -import com.jme3.system.jopenvr.JOpenVRLibrary.ID3D12Resource; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1301
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class D3D12TextureData_t extends Structure { - /** - * struct ID3D12Resource *
                  - * C type : ID3D12Resource* - */ - public ID3D12Resource m_pResource; - /** - * struct ID3D12CommandQueue *
                  - * C type : ID3D12CommandQueue* - */ - public ID3D12CommandQueue m_pCommandQueue; - public int m_nNodeMask; - public D3D12TextureData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_pResource", "m_pCommandQueue", "m_nNodeMask"); - } - /** - * @param m_pResource struct ID3D12Resource *
                  - * C type : ID3D12Resource*
                  - * @param m_pCommandQueue struct ID3D12CommandQueue *
                  - * C type : ID3D12CommandQueue* - */ - public D3D12TextureData_t(ID3D12Resource m_pResource, ID3D12CommandQueue m_pCommandQueue, int m_nNodeMask) { - super(); - this.m_pResource = m_pResource; - this.m_pCommandQueue = m_pCommandQueue; - this.m_nNodeMask = m_nNodeMask; - } - public D3D12TextureData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends D3D12TextureData_t implements Structure.ByReference { - - }; - public static class ByValue extends D3D12TextureData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DistortionCoordinates_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/DistortionCoordinates_t.java deleted file mode 100644 index e2f61e38d0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DistortionCoordinates_t.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1237
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class DistortionCoordinates_t extends Structure { - /** - * float[2]
                  - * C type : float[2] - */ - public float[] rfRed = new float[2]; - /** - * float[2]
                  - * C type : float[2] - */ - public float[] rfGreen = new float[2]; - /** - * float[2]
                  - * C type : float[2] - */ - public float[] rfBlue = new float[2]; - public DistortionCoordinates_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("rfRed", "rfGreen", "rfBlue"); - } - /** - * @param rfRed float[2]
                  - * C type : float[2]
                  - * @param rfGreen float[2]
                  - * C type : float[2]
                  - * @param rfBlue float[2]
                  - * C type : float[2] - */ - public DistortionCoordinates_t(float rfRed[], float rfGreen[], float rfBlue[]) { - super(); - if ((rfRed.length != this.rfRed.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rfRed = rfRed; - if ((rfGreen.length != this.rfGreen.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rfGreen = rfGreen; - if ((rfBlue.length != this.rfBlue.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rfBlue = rfBlue; - } - public DistortionCoordinates_t(Pointer peer) { - super(peer); - } - public static class ByReference extends DistortionCoordinates_t implements Structure.ByReference { - - }; - public static class ByValue extends DistortionCoordinates_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DriverDirectMode_FrameTiming.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/DriverDirectMode_FrameTiming.java deleted file mode 100644 index ca52264aba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/DriverDirectMode_FrameTiming.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1473
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class DriverDirectMode_FrameTiming extends Structure { - public int m_nSize; - public int m_nNumFramePresents; - public int m_nNumMisPresented; - public int m_nNumDroppedFrames; - public int m_nReprojectionFlags; - public DriverDirectMode_FrameTiming() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nSize", "m_nNumFramePresents", "m_nNumMisPresented", "m_nNumDroppedFrames", "m_nReprojectionFlags"); - } - public DriverDirectMode_FrameTiming(int m_nSize, int m_nNumFramePresents, int m_nNumMisPresented, int m_nNumDroppedFrames, int m_nReprojectionFlags) { - super(); - this.m_nSize = m_nSize; - this.m_nNumFramePresents = m_nNumFramePresents; - this.m_nNumMisPresented = m_nNumMisPresented; - this.m_nNumDroppedFrames = m_nNumDroppedFrames; - this.m_nReprojectionFlags = m_nReprojectionFlags; - } - public DriverDirectMode_FrameTiming(Pointer peer) { - super(peer); - } - public static class ByReference extends DriverDirectMode_FrameTiming implements Structure.ByReference { - - }; - public static class ByValue extends DriverDirectMode_FrameTiming implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HiddenAreaMesh_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HiddenAreaMesh_t.java deleted file mode 100644 index a1850de739..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HiddenAreaMesh_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1425
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HiddenAreaMesh_t extends Structure { - /** - * const struct vr::HmdVector2_t *
                  - * C type : HmdVector2_t* - */ - public com.jme3.system.jopenvr.HmdVector2_t.ByReference pVertexData; - public int unTriangleCount; - public HiddenAreaMesh_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("pVertexData", "unTriangleCount"); - } - /** - * @param pVertexData const struct vr::HmdVector2_t *
                  - * C type : HmdVector2_t* - */ - public HiddenAreaMesh_t(com.jme3.system.jopenvr.HmdVector2_t.ByReference pVertexData, int unTriangleCount) { - super(); - this.pVertexData = pVertexData; - this.unTriangleCount = unTriangleCount; - } - public HiddenAreaMesh_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HiddenAreaMesh_t implements Structure.ByReference { - - }; - public static class ByValue extends HiddenAreaMesh_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdColor_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdColor_t.java deleted file mode 100644 index 78bedfbcf5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdColor_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1221
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdColor_t extends Structure { - public float r; - public float g; - public float b; - public float a; - public HmdColor_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("r", "g", "b", "a"); - } - public HmdColor_t(float r, float g, float b, float a) { - super(); - this.r = r; - this.g = g; - this.b = b; - this.a = a; - } - public HmdColor_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdColor_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdColor_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix33_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix33_t.java deleted file mode 100644 index df334358bd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix33_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1183
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdMatrix33_t extends Structure { - /** - * float[3][3]
                  - * C type : float[3][3] - */ - public float[] m = new float[((3) * (3))]; - public HmdMatrix33_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m"); - } - /** - * @param m float[3][3]
                  - * C type : float[3][3] - */ - public HmdMatrix33_t(float m[]) { - super(); - if ((m.length != this.m.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.m = m; - } - public HmdMatrix33_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdMatrix33_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdMatrix33_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix34_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix34_t.java deleted file mode 100644 index 6593938053..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix34_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1179
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdMatrix34_t extends Structure { - /** - * float[3][4]
                  - * C type : float[3][4] - */ - public float[] m = new float[((3) * (4))]; - public HmdMatrix34_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m"); - } - /** - * @param m float[3][4]
                  - * C type : float[3][4] - */ - public HmdMatrix34_t(float m[]) { - super(); - if ((m.length != this.m.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.m = m; - } - public HmdMatrix34_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdMatrix34_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdMatrix34_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix44_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix44_t.java deleted file mode 100644 index 216aa73410..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdMatrix44_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1187
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdMatrix44_t extends Structure { - /** - * float[4][4]
                  - * C type : float[4][4] - */ - public float[] m = new float[((4) * (4))]; - public HmdMatrix44_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m"); - } - /** - * @param m float[4][4]
                  - * C type : float[4][4] - */ - public HmdMatrix44_t(float m[]) { - super(); - if ((m.length != this.m.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.m = m; - } - public HmdMatrix44_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdMatrix44_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdMatrix44_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuad_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuad_t.java deleted file mode 100644 index 0142d8ef13..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuad_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1225
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdQuad_t extends Structure { - /** - * struct vr::HmdVector3_t[4]
                  - * C type : HmdVector3_t[4] - */ - public HmdVector3_t[] vCorners = new HmdVector3_t[4]; - public HmdQuad_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("vCorners"); - } - /** - * @param vCorners struct vr::HmdVector3_t[4]
                  - * C type : HmdVector3_t[4] - */ - public HmdQuad_t(HmdVector3_t vCorners[]) { - super(); - if ((vCorners.length != this.vCorners.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.vCorners = vCorners; - } - public HmdQuad_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdQuad_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdQuad_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternion_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternion_t.java deleted file mode 100644 index fb0073ee79..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternion_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1209
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdQuaternion_t extends Structure { - public double w; - public double x; - public double y; - public double z; - public HmdQuaternion_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("w", "x", "y", "z"); - } - public HmdQuaternion_t(double w, double x, double y, double z) { - super(); - this.w = w; - this.x = x; - this.y = y; - this.z = z; - } - public HmdQuaternion_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdQuaternion_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdQuaternion_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternionf_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternionf_t.java deleted file mode 100644 index 6a7b963d6b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdQuaternionf_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1215
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdQuaternionf_t extends Structure { - public float w; - public float x; - public float y; - public float z; - public HmdQuaternionf_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("w", "x", "y", "z"); - } - public HmdQuaternionf_t(float w, float x, float y, float z) { - super(); - this.w = w; - this.x = x; - this.y = y; - this.z = z; - } - public HmdQuaternionf_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdQuaternionf_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdQuaternionf_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdRect2_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdRect2_t.java deleted file mode 100644 index 210843deb6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdRect2_t.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1229
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdRect2_t extends Structure { - /** C type : HmdVector2_t */ - public HmdVector2_t vTopLeft; - /** C type : HmdVector2_t */ - public HmdVector2_t vBottomRight; - public HmdRect2_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("vTopLeft", "vBottomRight"); - } - /** - * @param vTopLeft C type : HmdVector2_t
                  - * @param vBottomRight C type : HmdVector2_t - */ - public HmdRect2_t(HmdVector2_t vTopLeft, HmdVector2_t vBottomRight) { - super(); - this.vTopLeft = vTopLeft; - this.vBottomRight = vBottomRight; - } - public HmdRect2_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdRect2_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdRect2_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector2_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector2_t.java deleted file mode 100644 index 7a885d4c7d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector2_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1203
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdVector2_t extends Structure { - /** - * float[2]
                  - * C type : float[2] - */ - public float[] v = new float[2]; - public HmdVector2_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("v"); - } - /** - * @param v float[2]
                  - * C type : float[2] - */ - public HmdVector2_t(float v[]) { - super(); - if ((v.length != this.v.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.v = v; - } - public HmdVector2_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdVector2_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdVector2_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3_t.java deleted file mode 100644 index ed87e69916..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1191
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdVector3_t extends Structure { - /** - * float[3]
                  - * C type : float[3] - */ - public float[] v = new float[3]; - public HmdVector3_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("v"); - } - /** - * @param v float[3]
                  - * C type : float[3] - */ - public HmdVector3_t(float v[]) { - super(); - if ((v.length != this.v.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.v = v; - } - public HmdVector3_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdVector3_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdVector3_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3d_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3d_t.java deleted file mode 100644 index 1b1bfe2b9c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector3d_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1199
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdVector3d_t extends Structure { - /** - * double[3]
                  - * C type : double[3] - */ - public double[] v = new double[3]; - public HmdVector3d_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("v"); - } - /** - * @param v double[3]
                  - * C type : double[3] - */ - public HmdVector3d_t(double v[]) { - super(); - if ((v.length != this.v.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.v = v; - } - public HmdVector3d_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdVector3d_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdVector3d_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector4_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector4_t.java deleted file mode 100644 index b022e05d61..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/HmdVector4_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1195
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class HmdVector4_t extends Structure { - /** - * float[4]
                  - * C type : float[4] - */ - public float[] v = new float[4]; - public HmdVector4_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("v"); - } - /** - * @param v float[4]
                  - * C type : float[4] - */ - public HmdVector4_t(float v[]) { - super(); - if ((v.length != this.v.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.v = v; - } - public HmdVector4_t(Pointer peer) { - super(peer); - } - public static class ByReference extends HmdVector4_t implements Structure.ByReference { - - }; - public static class ByValue extends HmdVector4_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/ImuSample_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/ImuSample_t.java deleted file mode 100644 index 799893d2eb..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/ImuSample_t.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1479
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class ImuSample_t extends Structure { - public double fSampleTime; - /** C type : HmdVector3d_t */ - public HmdVector3d_t vAccel; - /** C type : HmdVector3d_t */ - public HmdVector3d_t vGyro; - public int unOffScaleFlags; - public ImuSample_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("fSampleTime", "vAccel", "vGyro", "unOffScaleFlags"); - } - /** - * @param vAccel C type : HmdVector3d_t
                  - * @param vGyro C type : HmdVector3d_t - */ - public ImuSample_t(double fSampleTime, HmdVector3d_t vAccel, HmdVector3d_t vGyro, int unOffScaleFlags) { - super(); - this.fSampleTime = fSampleTime; - this.vAccel = vAccel; - this.vGyro = vGyro; - this.unOffScaleFlags = unOffScaleFlags; - } - public ImuSample_t(Pointer peer) { - super(peer); - } - public static class ByReference extends ImuSample_t implements Structure.ByReference { - - }; - public static class ByValue extends ImuSample_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputAnalogActionData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputAnalogActionData_t.java deleted file mode 100644 index 61fee6243c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputAnalogActionData_t.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1603
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class InputAnalogActionData_t extends Structure { - public byte bActive; - /** C type : VRInputValueHandle_t */ - public long activeOrigin; - public float x; - public float y; - public float z; - public float deltaX; - public float deltaY; - public float deltaZ; - public float fUpdateTime; - public InputAnalogActionData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bActive", "activeOrigin", "x", "y", "z", "deltaX", "deltaY", "deltaZ", "fUpdateTime"); - } - /** @param activeOrigin C type : VRInputValueHandle_t */ - public InputAnalogActionData_t(byte bActive, long activeOrigin, float x, float y, float z, float deltaX, float deltaY, float deltaZ, float fUpdateTime) { - super(); - this.bActive = bActive; - this.activeOrigin = activeOrigin; - this.x = x; - this.y = y; - this.z = z; - this.deltaX = deltaX; - this.deltaY = deltaY; - this.deltaZ = deltaZ; - this.fUpdateTime = fUpdateTime; - } - public InputAnalogActionData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends InputAnalogActionData_t implements Structure.ByReference { - - }; - public static class ByValue extends InputAnalogActionData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputDigitalActionData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputDigitalActionData_t.java deleted file mode 100644 index 7325022eea..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputDigitalActionData_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1610
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class InputDigitalActionData_t extends Structure { - public byte bActive; - /** C type : VRInputValueHandle_t */ - public long activeOrigin; - public byte bState; - public byte bChanged; - public float fUpdateTime; - public InputDigitalActionData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bActive", "activeOrigin", "bState", "bChanged", "fUpdateTime"); - } - /** @param activeOrigin C type : VRInputValueHandle_t */ - public InputDigitalActionData_t(byte bActive, long activeOrigin, byte bState, byte bChanged, float fUpdateTime) { - super(); - this.bActive = bActive; - this.activeOrigin = activeOrigin; - this.bState = bState; - this.bChanged = bChanged; - this.fUpdateTime = fUpdateTime; - } - public InputDigitalActionData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends InputDigitalActionData_t implements Structure.ByReference { - - }; - public static class ByValue extends InputDigitalActionData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputOriginInfo_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputOriginInfo_t.java deleted file mode 100644 index f63555b8cc..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputOriginInfo_t.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1626
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class InputOriginInfo_t extends Structure { - /** C type : VRInputValueHandle_t */ - public long devicePath; - /** C type : TrackedDeviceIndex_t */ - public int trackedDeviceIndex; - /** - * char[128]
                  - * C type : char*[128] - */ - public Pointer[] rchRenderModelComponentName = new Pointer[128]; - public InputOriginInfo_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("devicePath", "trackedDeviceIndex", "rchRenderModelComponentName"); - } - /** - * @param devicePath C type : VRInputValueHandle_t
                  - * @param trackedDeviceIndex C type : TrackedDeviceIndex_t
                  - * @param rchRenderModelComponentName char[128]
                  - * C type : char*[128] - */ - public InputOriginInfo_t(long devicePath, int trackedDeviceIndex, Pointer rchRenderModelComponentName[]) { - super(); - this.devicePath = devicePath; - this.trackedDeviceIndex = trackedDeviceIndex; - if ((rchRenderModelComponentName.length != this.rchRenderModelComponentName.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rchRenderModelComponentName = rchRenderModelComponentName; - } - public InputOriginInfo_t(Pointer peer) { - super(peer); - } - public static class ByReference extends InputOriginInfo_t implements Structure.ByReference { - - }; - public static class ByValue extends InputOriginInfo_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputPoseActionData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputPoseActionData_t.java deleted file mode 100644 index 747493078a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputPoseActionData_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1615
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class InputPoseActionData_t extends Structure { - public byte bActive; - /** C type : VRInputValueHandle_t */ - public long activeOrigin; - /** C type : TrackedDevicePose_t */ - public TrackedDevicePose_t pose; - public InputPoseActionData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bActive", "activeOrigin", "pose"); - } - /** - * @param activeOrigin C type : VRInputValueHandle_t
                  - * @param pose C type : TrackedDevicePose_t - */ - public InputPoseActionData_t(byte bActive, long activeOrigin, TrackedDevicePose_t pose) { - super(); - this.bActive = bActive; - this.activeOrigin = activeOrigin; - this.pose = pose; - } - public InputPoseActionData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends InputPoseActionData_t implements Structure.ByReference { - - }; - public static class ByValue extends InputPoseActionData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputSkeletalActionData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputSkeletalActionData_t.java deleted file mode 100644 index d9d32b9516..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/InputSkeletalActionData_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1620
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class InputSkeletalActionData_t extends Structure { - public byte bActive; - /** C type : VRInputValueHandle_t */ - public long activeOrigin; - public int boneCount; - public InputSkeletalActionData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bActive", "activeOrigin", "boneCount"); - } - /** @param activeOrigin C type : VRInputValueHandle_t */ - public InputSkeletalActionData_t(byte bActive, long activeOrigin, int boneCount) { - super(); - this.bActive = bActive; - this.activeOrigin = activeOrigin; - this.boneCount = boneCount; - } - public InputSkeletalActionData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends InputSkeletalActionData_t implements Structure.ByReference { - - }; - public static class ByValue extends InputSkeletalActionData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskCircle_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskCircle_t.java deleted file mode 100644 index 631a127a95..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskCircle_t.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1552
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class IntersectionMaskCircle_t extends Structure { - public float m_flCenterX; - public float m_flCenterY; - public float m_flRadius; - public IntersectionMaskCircle_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_flCenterX", "m_flCenterY", "m_flRadius"); - } - public IntersectionMaskCircle_t(float m_flCenterX, float m_flCenterY, float m_flRadius) { - super(); - this.m_flCenterX = m_flCenterX; - this.m_flCenterY = m_flCenterY; - this.m_flRadius = m_flRadius; - } - public IntersectionMaskCircle_t(Pointer peer) { - super(peer); - } - public static class ByReference extends IntersectionMaskCircle_t implements Structure.ByReference { - - }; - public static class ByValue extends IntersectionMaskCircle_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskRectangle_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskRectangle_t.java deleted file mode 100644 index bbb4c0f578..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/IntersectionMaskRectangle_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1547
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class IntersectionMaskRectangle_t extends Structure { - public float m_flTopLeftX; - public float m_flTopLeftY; - public float m_flWidth; - public float m_flHeight; - public IntersectionMaskRectangle_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_flTopLeftX", "m_flTopLeftY", "m_flWidth", "m_flHeight"); - } - public IntersectionMaskRectangle_t(float m_flTopLeftX, float m_flTopLeftY, float m_flWidth, float m_flHeight) { - super(); - this.m_flTopLeftX = m_flTopLeftX; - this.m_flTopLeftY = m_flTopLeftY; - this.m_flWidth = m_flWidth; - this.m_flHeight = m_flHeight; - } - public IntersectionMaskRectangle_t(Pointer peer) { - super(peer); - } - public static class ByReference extends IntersectionMaskRectangle_t implements Structure.ByReference { - - }; - public static class ByValue extends IntersectionMaskRectangle_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/JOpenVRLibrary.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/JOpenVRLibrary.java deleted file mode 100644 index 4a00707fed..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/JOpenVRLibrary.java +++ /dev/null @@ -1,2332 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -import com.sun.jna.ptr.IntByReference; - -import java.nio.IntBuffer; -import java.util.logging.Logger; -/** - * JNA Wrapper for library JOpenVR
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class JOpenVRLibrary implements Library { - private static final Logger logger = Logger.getLogger(JOpenVRLibrary.class.getName()); - - private static String JNA_LIBRARY_NAME; - private static NativeLibrary JNA_NATIVE_LIB; - - /** - * The system property that specifies an implementation of OpenVR (openvr_api file) to use. - * This property has to be set at the program launch using -D connector. - * If this property is not set, the embedded library is loaded. - */ - public static final String JNA_OPENVR_LIBRARY_PATH = "openvr.library.path"; - - /** - * Init the native binding to the underlying system library. - */ - public static void init() throws UnsatisfiedLinkError { - Native.unregister(JOpenVRLibrary.class); - - String path = System.getProperty(JNA_OPENVR_LIBRARY_PATH); - if (path != null){ - - JNA_LIBRARY_NAME = path; - - logger.config("Using OpenVR implementation located at "+JNA_LIBRARY_NAME); - - JNA_NATIVE_LIB = NativeLibrary.getInstance(JOpenVRLibrary.JNA_LIBRARY_NAME); - Native.register(JOpenVRLibrary.class, JOpenVRLibrary.JNA_NATIVE_LIB); - } else { - - JNA_LIBRARY_NAME = "openvr_api"; - - logger.config("Using embedded OpenVR implementation "+JOpenVRLibrary.JNA_LIBRARY_NAME); - - JNA_NATIVE_LIB = NativeLibrary.getInstance(JOpenVRLibrary.JNA_LIBRARY_NAME); - Native.register(JOpenVRLibrary.class, JOpenVRLibrary.JNA_NATIVE_LIB); - } - } - - /** - * Get the name of the underlying system library. - * @return the name of the underlying system library. - */ - public static String getSystemLibraryName(){ - return ""+JNA_LIBRARY_NAME; - } - - - /** - * native declaration : headers\openvr_capi.h:229
                  - * enum values - */ - public static interface EVREye { - /** native declaration : headers\openvr_capi.h:227 */ - public static final int EVREye_Eye_Left = 0; - /** native declaration : headers\openvr_capi.h:228 */ - public static final int EVREye_Eye_Right = 1; - }; - /** - * native declaration : headers\openvr_capi.h:239
                  - * enum values - */ - public static interface ETextureType { - /** native declaration : headers\openvr_capi.h:231 */ - public static final int ETextureType_TextureType_Invalid = -1; - /** native declaration : headers\openvr_capi.h:232 */ - public static final int ETextureType_TextureType_DirectX = 0; - /** native declaration : headers\openvr_capi.h:233 */ - public static final int ETextureType_TextureType_OpenGL = 1; - /** native declaration : headers\openvr_capi.h:234 */ - public static final int ETextureType_TextureType_Vulkan = 2; - /** native declaration : headers\openvr_capi.h:235 */ - public static final int ETextureType_TextureType_IOSurface = 3; - /** native declaration : headers\openvr_capi.h:236 */ - public static final int ETextureType_TextureType_DirectX12 = 4; - /** native declaration : headers\openvr_capi.h:237 */ - public static final int ETextureType_TextureType_DXGISharedHandle = 5; - /** native declaration : headers\openvr_capi.h:238 */ - public static final int ETextureType_TextureType_Metal = 6; - }; - /** - * native declaration : headers\openvr_capi.h:244
                  - * enum values - */ - public static interface EColorSpace { - /** native declaration : headers\openvr_capi.h:241 */ - public static final int EColorSpace_ColorSpace_Auto = 0; - /** native declaration : headers\openvr_capi.h:242 */ - public static final int EColorSpace_ColorSpace_Gamma = 1; - /** native declaration : headers\openvr_capi.h:243 */ - public static final int EColorSpace_ColorSpace_Linear = 2; - }; - /** - * native declaration : headers\openvr_capi.h:251
                  - * enum values - */ - public static interface ETrackingResult { - /** native declaration : headers\openvr_capi.h:246 */ - public static final int ETrackingResult_TrackingResult_Uninitialized = 1; - /** native declaration : headers\openvr_capi.h:247 */ - public static final int ETrackingResult_TrackingResult_Calibrating_InProgress = 100; - /** native declaration : headers\openvr_capi.h:248 */ - public static final int ETrackingResult_TrackingResult_Calibrating_OutOfRange = 101; - /** native declaration : headers\openvr_capi.h:249 */ - public static final int ETrackingResult_TrackingResult_Running_OK = 200; - /** native declaration : headers\openvr_capi.h:250 */ - public static final int ETrackingResult_TrackingResult_Running_OutOfRange = 201; - }; - /** - * native declaration : headers\openvr_capi.h:260
                  - * enum values - */ - public static interface ETrackedDeviceClass { - /** native declaration : headers\openvr_capi.h:253 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_Invalid = 0; - /** native declaration : headers\openvr_capi.h:254 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_HMD = 1; - /** native declaration : headers\openvr_capi.h:255 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_Controller = 2; - /** native declaration : headers\openvr_capi.h:256 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_GenericTracker = 3; - /** native declaration : headers\openvr_capi.h:257 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_TrackingReference = 4; - /** native declaration : headers\openvr_capi.h:258 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_DisplayRedirect = 5; - /** native declaration : headers\openvr_capi.h:259 */ - public static final int ETrackedDeviceClass_TrackedDeviceClass_Max = 6; - }; - /** - * native declaration : headers\openvr_capi.h:267
                  - * enum values - */ - public static interface ETrackedControllerRole { - /** native declaration : headers\openvr_capi.h:262 */ - public static final int ETrackedControllerRole_TrackedControllerRole_Invalid = 0; - /** native declaration : headers\openvr_capi.h:263 */ - public static final int ETrackedControllerRole_TrackedControllerRole_LeftHand = 1; - /** native declaration : headers\openvr_capi.h:264 */ - public static final int ETrackedControllerRole_TrackedControllerRole_RightHand = 2; - /** native declaration : headers\openvr_capi.h:265 */ - public static final int ETrackedControllerRole_TrackedControllerRole_OptOut = 3; - /** native declaration : headers\openvr_capi.h:266 */ - public static final int ETrackedControllerRole_TrackedControllerRole_Max = 4; - }; - /** - * native declaration : headers\openvr_capi.h:272
                  - * enum values - */ - public static interface ETrackingUniverseOrigin { - /** native declaration : headers\openvr_capi.h:269 */ - public static final int ETrackingUniverseOrigin_TrackingUniverseSeated = 0; - /** native declaration : headers\openvr_capi.h:270 */ - public static final int ETrackingUniverseOrigin_TrackingUniverseStanding = 1; - /** native declaration : headers\openvr_capi.h:271 */ - public static final int ETrackingUniverseOrigin_TrackingUniverseRawAndUncalibrated = 2; - }; - /** - * native declaration : headers\openvr_capi.h:432
                  - * enum values - */ - public static interface ETrackedDeviceProperty { - /** native declaration : headers\openvr_capi.h:274 */ - public static final int ETrackedDeviceProperty_Prop_Invalid = 0; - /** native declaration : headers\openvr_capi.h:275 */ - public static final int ETrackedDeviceProperty_Prop_TrackingSystemName_String = 1000; - /** native declaration : headers\openvr_capi.h:276 */ - public static final int ETrackedDeviceProperty_Prop_ModelNumber_String = 1001; - /** native declaration : headers\openvr_capi.h:277 */ - public static final int ETrackedDeviceProperty_Prop_SerialNumber_String = 1002; - /** native declaration : headers\openvr_capi.h:278 */ - public static final int ETrackedDeviceProperty_Prop_RenderModelName_String = 1003; - /** native declaration : headers\openvr_capi.h:279 */ - public static final int ETrackedDeviceProperty_Prop_WillDriftInYaw_Bool = 1004; - /** native declaration : headers\openvr_capi.h:280 */ - public static final int ETrackedDeviceProperty_Prop_ManufacturerName_String = 1005; - /** native declaration : headers\openvr_capi.h:281 */ - public static final int ETrackedDeviceProperty_Prop_TrackingFirmwareVersion_String = 1006; - /** native declaration : headers\openvr_capi.h:282 */ - public static final int ETrackedDeviceProperty_Prop_HardwareRevision_String = 1007; - /** native declaration : headers\openvr_capi.h:283 */ - public static final int ETrackedDeviceProperty_Prop_AllWirelessDongleDescriptions_String = 1008; - /** native declaration : headers\openvr_capi.h:284 */ - public static final int ETrackedDeviceProperty_Prop_ConnectedWirelessDongle_String = 1009; - /** native declaration : headers\openvr_capi.h:285 */ - public static final int ETrackedDeviceProperty_Prop_DeviceIsWireless_Bool = 1010; - /** native declaration : headers\openvr_capi.h:286 */ - public static final int ETrackedDeviceProperty_Prop_DeviceIsCharging_Bool = 1011; - /** native declaration : headers\openvr_capi.h:287 */ - public static final int ETrackedDeviceProperty_Prop_DeviceBatteryPercentage_Float = 1012; - /** native declaration : headers\openvr_capi.h:288 */ - public static final int ETrackedDeviceProperty_Prop_StatusDisplayTransform_Matrix34 = 1013; - /** native declaration : headers\openvr_capi.h:289 */ - public static final int ETrackedDeviceProperty_Prop_Firmware_UpdateAvailable_Bool = 1014; - /** native declaration : headers\openvr_capi.h:290 */ - public static final int ETrackedDeviceProperty_Prop_Firmware_ManualUpdate_Bool = 1015; - /** native declaration : headers\openvr_capi.h:291 */ - public static final int ETrackedDeviceProperty_Prop_Firmware_ManualUpdateURL_String = 1016; - /** native declaration : headers\openvr_capi.h:292 */ - public static final int ETrackedDeviceProperty_Prop_HardwareRevision_Uint64 = 1017; - /** native declaration : headers\openvr_capi.h:293 */ - public static final int ETrackedDeviceProperty_Prop_FirmwareVersion_Uint64 = 1018; - /** native declaration : headers\openvr_capi.h:294 */ - public static final int ETrackedDeviceProperty_Prop_FPGAVersion_Uint64 = 1019; - /** native declaration : headers\openvr_capi.h:295 */ - public static final int ETrackedDeviceProperty_Prop_VRCVersion_Uint64 = 1020; - /** native declaration : headers\openvr_capi.h:296 */ - public static final int ETrackedDeviceProperty_Prop_RadioVersion_Uint64 = 1021; - /** native declaration : headers\openvr_capi.h:297 */ - public static final int ETrackedDeviceProperty_Prop_DongleVersion_Uint64 = 1022; - /** native declaration : headers\openvr_capi.h:298 */ - public static final int ETrackedDeviceProperty_Prop_BlockServerShutdown_Bool = 1023; - /** native declaration : headers\openvr_capi.h:299 */ - public static final int ETrackedDeviceProperty_Prop_CanUnifyCoordinateSystemWithHmd_Bool = 1024; - /** native declaration : headers\openvr_capi.h:300 */ - public static final int ETrackedDeviceProperty_Prop_ContainsProximitySensor_Bool = 1025; - /** native declaration : headers\openvr_capi.h:301 */ - public static final int ETrackedDeviceProperty_Prop_DeviceProvidesBatteryStatus_Bool = 1026; - /** native declaration : headers\openvr_capi.h:302 */ - public static final int ETrackedDeviceProperty_Prop_DeviceCanPowerOff_Bool = 1027; - /** native declaration : headers\openvr_capi.h:303 */ - public static final int ETrackedDeviceProperty_Prop_Firmware_ProgrammingTarget_String = 1028; - /** native declaration : headers\openvr_capi.h:304 */ - public static final int ETrackedDeviceProperty_Prop_DeviceClass_Int32 = 1029; - /** native declaration : headers\openvr_capi.h:305 */ - public static final int ETrackedDeviceProperty_Prop_HasCamera_Bool = 1030; - /** native declaration : headers\openvr_capi.h:306 */ - public static final int ETrackedDeviceProperty_Prop_DriverVersion_String = 1031; - /** native declaration : headers\openvr_capi.h:307 */ - public static final int ETrackedDeviceProperty_Prop_Firmware_ForceUpdateRequired_Bool = 1032; - /** native declaration : headers\openvr_capi.h:308 */ - public static final int ETrackedDeviceProperty_Prop_ViveSystemButtonFixRequired_Bool = 1033; - /** native declaration : headers\openvr_capi.h:309 */ - public static final int ETrackedDeviceProperty_Prop_ParentDriver_Uint64 = 1034; - /** native declaration : headers\openvr_capi.h:310 */ - public static final int ETrackedDeviceProperty_Prop_ResourceRoot_String = 1035; - /** native declaration : headers\openvr_capi.h:311 */ - public static final int ETrackedDeviceProperty_Prop_RegisteredDeviceType_String = 1036; - /** native declaration : headers\openvr_capi.h:312 */ - public static final int ETrackedDeviceProperty_Prop_InputProfilePath_String = 1037; - /** native declaration : headers\openvr_capi.h:313 */ - public static final int ETrackedDeviceProperty_Prop_NeverTracked_Bool = 1038; - /** native declaration : headers\openvr_capi.h:314 */ - public static final int ETrackedDeviceProperty_Prop_NumCameras_Int32 = 1039; - /** native declaration : headers\openvr_capi.h:315 */ - public static final int ETrackedDeviceProperty_Prop_CameraFrameLayout_Int32 = 1040; - /** native declaration : headers\openvr_capi.h:316 */ - public static final int ETrackedDeviceProperty_Prop_ReportsTimeSinceVSync_Bool = 2000; - /** native declaration : headers\openvr_capi.h:317 */ - public static final int ETrackedDeviceProperty_Prop_SecondsFromVsyncToPhotons_Float = 2001; - /** native declaration : headers\openvr_capi.h:318 */ - public static final int ETrackedDeviceProperty_Prop_DisplayFrequency_Float = 2002; - /** native declaration : headers\openvr_capi.h:319 */ - public static final int ETrackedDeviceProperty_Prop_UserIpdMeters_Float = 2003; - /** native declaration : headers\openvr_capi.h:320 */ - public static final int ETrackedDeviceProperty_Prop_CurrentUniverseId_Uint64 = 2004; - /** native declaration : headers\openvr_capi.h:321 */ - public static final int ETrackedDeviceProperty_Prop_PreviousUniverseId_Uint64 = 2005; - /** native declaration : headers\openvr_capi.h:322 */ - public static final int ETrackedDeviceProperty_Prop_DisplayFirmwareVersion_Uint64 = 2006; - /** native declaration : headers\openvr_capi.h:323 */ - public static final int ETrackedDeviceProperty_Prop_IsOnDesktop_Bool = 2007; - /** native declaration : headers\openvr_capi.h:324 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCType_Int32 = 2008; - /** native declaration : headers\openvr_capi.h:325 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCOffset_Float = 2009; - /** native declaration : headers\openvr_capi.h:326 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCScale_Float = 2010; - /** native declaration : headers\openvr_capi.h:327 */ - public static final int ETrackedDeviceProperty_Prop_EdidVendorID_Int32 = 2011; - /** native declaration : headers\openvr_capi.h:328 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageLeft_String = 2012; - /** native declaration : headers\openvr_capi.h:329 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageRight_String = 2013; - /** native declaration : headers\openvr_capi.h:330 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCBlackClamp_Float = 2014; - /** native declaration : headers\openvr_capi.h:331 */ - public static final int ETrackedDeviceProperty_Prop_EdidProductID_Int32 = 2015; - /** native declaration : headers\openvr_capi.h:332 */ - public static final int ETrackedDeviceProperty_Prop_CameraToHeadTransform_Matrix34 = 2016; - /** native declaration : headers\openvr_capi.h:333 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCType_Int32 = 2017; - /** native declaration : headers\openvr_capi.h:334 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCOffset_Float = 2018; - /** native declaration : headers\openvr_capi.h:335 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCScale_Float = 2019; - /** native declaration : headers\openvr_capi.h:336 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCPrescale_Float = 2020; - /** native declaration : headers\openvr_capi.h:337 */ - public static final int ETrackedDeviceProperty_Prop_DisplayGCImage_String = 2021; - /** native declaration : headers\openvr_capi.h:338 */ - public static final int ETrackedDeviceProperty_Prop_LensCenterLeftU_Float = 2022; - /** native declaration : headers\openvr_capi.h:339 */ - public static final int ETrackedDeviceProperty_Prop_LensCenterLeftV_Float = 2023; - /** native declaration : headers\openvr_capi.h:340 */ - public static final int ETrackedDeviceProperty_Prop_LensCenterRightU_Float = 2024; - /** native declaration : headers\openvr_capi.h:341 */ - public static final int ETrackedDeviceProperty_Prop_LensCenterRightV_Float = 2025; - /** native declaration : headers\openvr_capi.h:342 */ - public static final int ETrackedDeviceProperty_Prop_UserHeadToEyeDepthMeters_Float = 2026; - /** native declaration : headers\openvr_capi.h:343 */ - public static final int ETrackedDeviceProperty_Prop_CameraFirmwareVersion_Uint64 = 2027; - /** native declaration : headers\openvr_capi.h:344 */ - public static final int ETrackedDeviceProperty_Prop_CameraFirmwareDescription_String = 2028; - /** native declaration : headers\openvr_capi.h:345 */ - public static final int ETrackedDeviceProperty_Prop_DisplayFPGAVersion_Uint64 = 2029; - /** native declaration : headers\openvr_capi.h:346 */ - public static final int ETrackedDeviceProperty_Prop_DisplayBootloaderVersion_Uint64 = 2030; - /** native declaration : headers\openvr_capi.h:347 */ - public static final int ETrackedDeviceProperty_Prop_DisplayHardwareVersion_Uint64 = 2031; - /** native declaration : headers\openvr_capi.h:348 */ - public static final int ETrackedDeviceProperty_Prop_AudioFirmwareVersion_Uint64 = 2032; - /** native declaration : headers\openvr_capi.h:349 */ - public static final int ETrackedDeviceProperty_Prop_CameraCompatibilityMode_Int32 = 2033; - /** native declaration : headers\openvr_capi.h:350 */ - public static final int ETrackedDeviceProperty_Prop_ScreenshotHorizontalFieldOfViewDegrees_Float = 2034; - /** native declaration : headers\openvr_capi.h:351 */ - public static final int ETrackedDeviceProperty_Prop_ScreenshotVerticalFieldOfViewDegrees_Float = 2035; - /** native declaration : headers\openvr_capi.h:352 */ - public static final int ETrackedDeviceProperty_Prop_DisplaySuppressed_Bool = 2036; - /** native declaration : headers\openvr_capi.h:353 */ - public static final int ETrackedDeviceProperty_Prop_DisplayAllowNightMode_Bool = 2037; - /** native declaration : headers\openvr_capi.h:354 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageWidth_Int32 = 2038; - /** native declaration : headers\openvr_capi.h:355 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageHeight_Int32 = 2039; - /** native declaration : headers\openvr_capi.h:356 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageNumChannels_Int32 = 2040; - /** native declaration : headers\openvr_capi.h:357 */ - public static final int ETrackedDeviceProperty_Prop_DisplayMCImageData_Binary = 2041; - /** native declaration : headers\openvr_capi.h:358 */ - public static final int ETrackedDeviceProperty_Prop_SecondsFromPhotonsToVblank_Float = 2042; - /** native declaration : headers\openvr_capi.h:359 */ - public static final int ETrackedDeviceProperty_Prop_DriverDirectModeSendsVsyncEvents_Bool = 2043; - /** native declaration : headers\openvr_capi.h:360 */ - public static final int ETrackedDeviceProperty_Prop_DisplayDebugMode_Bool = 2044; - /** native declaration : headers\openvr_capi.h:361 */ - public static final int ETrackedDeviceProperty_Prop_GraphicsAdapterLuid_Uint64 = 2045; - /** native declaration : headers\openvr_capi.h:362 */ - public static final int ETrackedDeviceProperty_Prop_DriverProvidedChaperonePath_String = 2048; - /** native declaration : headers\openvr_capi.h:363 */ - public static final int ETrackedDeviceProperty_Prop_ExpectedTrackingReferenceCount_Int32 = 2049; - /** native declaration : headers\openvr_capi.h:364 */ - public static final int ETrackedDeviceProperty_Prop_ExpectedControllerCount_Int32 = 2050; - /** native declaration : headers\openvr_capi.h:365 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathControllerLeftDeviceOff_String = 2051; - /** native declaration : headers\openvr_capi.h:366 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathControllerRightDeviceOff_String = 2052; - /** native declaration : headers\openvr_capi.h:367 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathTrackingReferenceDeviceOff_String = 2053; - /** native declaration : headers\openvr_capi.h:368 */ - public static final int ETrackedDeviceProperty_Prop_DoNotApplyPrediction_Bool = 2054; - /** native declaration : headers\openvr_capi.h:369 */ - public static final int ETrackedDeviceProperty_Prop_CameraToHeadTransforms_Matrix34_Array = 2055; - /** native declaration : headers\openvr_capi.h:370 */ - public static final int ETrackedDeviceProperty_Prop_DistortionMeshResolution_Int32 = 2056; - /** native declaration : headers\openvr_capi.h:371 */ - public static final int ETrackedDeviceProperty_Prop_DriverIsDrawingControllers_Bool = 2057; - /** native declaration : headers\openvr_capi.h:372 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestsApplicationPause_Bool = 2058; - /** native declaration : headers\openvr_capi.h:373 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestsReducedRendering_Bool = 2059; - /** native declaration : headers\openvr_capi.h:374 */ - public static final int ETrackedDeviceProperty_Prop_MinimumIpdStepMeters_Float = 2060; - /** native declaration : headers\openvr_capi.h:375 */ - public static final int ETrackedDeviceProperty_Prop_AudioBridgeFirmwareVersion_Uint64 = 2061; - /** native declaration : headers\openvr_capi.h:376 */ - public static final int ETrackedDeviceProperty_Prop_ImageBridgeFirmwareVersion_Uint64 = 2062; - /** native declaration : headers\openvr_capi.h:377 */ - public static final int ETrackedDeviceProperty_Prop_ImuToHeadTransform_Matrix34 = 2063; - /** native declaration : headers\openvr_capi.h:378 */ - public static final int ETrackedDeviceProperty_Prop_ImuFactoryGyroBias_Vector3 = 2064; - /** native declaration : headers\openvr_capi.h:379 */ - public static final int ETrackedDeviceProperty_Prop_ImuFactoryGyroScale_Vector3 = 2065; - /** native declaration : headers\openvr_capi.h:380 */ - public static final int ETrackedDeviceProperty_Prop_ImuFactoryAccelerometerBias_Vector3 = 2066; - /** native declaration : headers\openvr_capi.h:381 */ - public static final int ETrackedDeviceProperty_Prop_ImuFactoryAccelerometerScale_Vector3 = 2067; - /** native declaration : headers\openvr_capi.h:382 */ - public static final int ETrackedDeviceProperty_Prop_ConfigurationIncludesLighthouse20Features_Bool = 2069; - /** native declaration : headers\openvr_capi.h:383 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraCorrectionMode_Int32 = 2200; - /** native declaration : headers\openvr_capi.h:384 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_InnerLeft_Int32 = 2201; - /** native declaration : headers\openvr_capi.h:385 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_InnerRight_Int32 = 2202; - /** native declaration : headers\openvr_capi.h:386 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_InnerTop_Int32 = 2203; - /** native declaration : headers\openvr_capi.h:387 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_InnerBottom_Int32 = 2204; - /** native declaration : headers\openvr_capi.h:388 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_OuterLeft_Int32 = 2205; - /** native declaration : headers\openvr_capi.h:389 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_OuterRight_Int32 = 2206; - /** native declaration : headers\openvr_capi.h:390 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_OuterTop_Int32 = 2207; - /** native declaration : headers\openvr_capi.h:391 */ - public static final int ETrackedDeviceProperty_Prop_DriverRequestedMuraFeather_OuterBottom_Int32 = 2208; - /** native declaration : headers\openvr_capi.h:392 */ - public static final int ETrackedDeviceProperty_Prop_AttachedDeviceId_String = 3000; - /** native declaration : headers\openvr_capi.h:393 */ - public static final int ETrackedDeviceProperty_Prop_SupportedButtons_Uint64 = 3001; - /** native declaration : headers\openvr_capi.h:394 */ - public static final int ETrackedDeviceProperty_Prop_Axis0Type_Int32 = 3002; - /** native declaration : headers\openvr_capi.h:395 */ - public static final int ETrackedDeviceProperty_Prop_Axis1Type_Int32 = 3003; - /** native declaration : headers\openvr_capi.h:396 */ - public static final int ETrackedDeviceProperty_Prop_Axis2Type_Int32 = 3004; - /** native declaration : headers\openvr_capi.h:397 */ - public static final int ETrackedDeviceProperty_Prop_Axis3Type_Int32 = 3005; - /** native declaration : headers\openvr_capi.h:398 */ - public static final int ETrackedDeviceProperty_Prop_Axis4Type_Int32 = 3006; - /** native declaration : headers\openvr_capi.h:399 */ - public static final int ETrackedDeviceProperty_Prop_ControllerRoleHint_Int32 = 3007; - /** native declaration : headers\openvr_capi.h:400 */ - public static final int ETrackedDeviceProperty_Prop_FieldOfViewLeftDegrees_Float = 4000; - /** native declaration : headers\openvr_capi.h:401 */ - public static final int ETrackedDeviceProperty_Prop_FieldOfViewRightDegrees_Float = 4001; - /** native declaration : headers\openvr_capi.h:402 */ - public static final int ETrackedDeviceProperty_Prop_FieldOfViewTopDegrees_Float = 4002; - /** native declaration : headers\openvr_capi.h:403 */ - public static final int ETrackedDeviceProperty_Prop_FieldOfViewBottomDegrees_Float = 4003; - /** native declaration : headers\openvr_capi.h:404 */ - public static final int ETrackedDeviceProperty_Prop_TrackingRangeMinimumMeters_Float = 4004; - /** native declaration : headers\openvr_capi.h:405 */ - public static final int ETrackedDeviceProperty_Prop_TrackingRangeMaximumMeters_Float = 4005; - /** native declaration : headers\openvr_capi.h:406 */ - public static final int ETrackedDeviceProperty_Prop_ModeLabel_String = 4006; - /** native declaration : headers\openvr_capi.h:407 */ - public static final int ETrackedDeviceProperty_Prop_IconPathName_String = 5000; - /** native declaration : headers\openvr_capi.h:408 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceOff_String = 5001; - /** native declaration : headers\openvr_capi.h:409 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceSearching_String = 5002; - /** native declaration : headers\openvr_capi.h:410 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceSearchingAlert_String = 5003; - /** native declaration : headers\openvr_capi.h:411 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceReady_String = 5004; - /** native declaration : headers\openvr_capi.h:412 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceReadyAlert_String = 5005; - /** native declaration : headers\openvr_capi.h:413 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceNotReady_String = 5006; - /** native declaration : headers\openvr_capi.h:414 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceStandby_String = 5007; - /** native declaration : headers\openvr_capi.h:415 */ - public static final int ETrackedDeviceProperty_Prop_NamedIconPathDeviceAlertLow_String = 5008; - /** native declaration : headers\openvr_capi.h:416 */ - public static final int ETrackedDeviceProperty_Prop_DisplayHiddenArea_Binary_Start = 5100; - /** native declaration : headers\openvr_capi.h:417 */ - public static final int ETrackedDeviceProperty_Prop_DisplayHiddenArea_Binary_End = 5150; - /** native declaration : headers\openvr_capi.h:418 */ - public static final int ETrackedDeviceProperty_Prop_ParentContainer = 5151; - /** native declaration : headers\openvr_capi.h:419 */ - public static final int ETrackedDeviceProperty_Prop_UserConfigPath_String = 6000; - /** native declaration : headers\openvr_capi.h:420 */ - public static final int ETrackedDeviceProperty_Prop_InstallPath_String = 6001; - /** native declaration : headers\openvr_capi.h:421 */ - public static final int ETrackedDeviceProperty_Prop_HasDisplayComponent_Bool = 6002; - /** native declaration : headers\openvr_capi.h:422 */ - public static final int ETrackedDeviceProperty_Prop_HasControllerComponent_Bool = 6003; - /** native declaration : headers\openvr_capi.h:423 */ - public static final int ETrackedDeviceProperty_Prop_HasCameraComponent_Bool = 6004; - /** native declaration : headers\openvr_capi.h:424 */ - public static final int ETrackedDeviceProperty_Prop_HasDriverDirectModeComponent_Bool = 6005; - /** native declaration : headers\openvr_capi.h:425 */ - public static final int ETrackedDeviceProperty_Prop_HasVirtualDisplayComponent_Bool = 6006; - /** native declaration : headers\openvr_capi.h:426 */ - public static final int ETrackedDeviceProperty_Prop_HasSpatialAnchorsSupport_Bool = 6007; - /** native declaration : headers\openvr_capi.h:427 */ - public static final int ETrackedDeviceProperty_Prop_ControllerType_String = 7000; - /** native declaration : headers\openvr_capi.h:428 */ - public static final int ETrackedDeviceProperty_Prop_LegacyInputProfile_String = 7001; - /** native declaration : headers\openvr_capi.h:429 */ - public static final int ETrackedDeviceProperty_Prop_VendorSpecific_Reserved_Start = 10000; - /** native declaration : headers\openvr_capi.h:430 */ - public static final int ETrackedDeviceProperty_Prop_VendorSpecific_Reserved_End = 10999; - /** native declaration : headers\openvr_capi.h:431 */ - public static final int ETrackedDeviceProperty_Prop_TrackedDeviceProperty_Max = 1000000; - }; - /** - * native declaration : headers\openvr_capi.h:447
                  - * enum values - */ - public static interface ETrackedPropertyError { - /** native declaration : headers\openvr_capi.h:434 */ - public static final int ETrackedPropertyError_TrackedProp_Success = 0; - /** native declaration : headers\openvr_capi.h:435 */ - public static final int ETrackedPropertyError_TrackedProp_WrongDataType = 1; - /** native declaration : headers\openvr_capi.h:436 */ - public static final int ETrackedPropertyError_TrackedProp_WrongDeviceClass = 2; - /** native declaration : headers\openvr_capi.h:437 */ - public static final int ETrackedPropertyError_TrackedProp_BufferTooSmall = 3; - /** native declaration : headers\openvr_capi.h:438 */ - public static final int ETrackedPropertyError_TrackedProp_UnknownProperty = 4; - /** native declaration : headers\openvr_capi.h:439 */ - public static final int ETrackedPropertyError_TrackedProp_InvalidDevice = 5; - /** native declaration : headers\openvr_capi.h:440 */ - public static final int ETrackedPropertyError_TrackedProp_CouldNotContactServer = 6; - /** native declaration : headers\openvr_capi.h:441 */ - public static final int ETrackedPropertyError_TrackedProp_ValueNotProvidedByDevice = 7; - /** native declaration : headers\openvr_capi.h:442 */ - public static final int ETrackedPropertyError_TrackedProp_StringExceedsMaximumLength = 8; - /** native declaration : headers\openvr_capi.h:443 */ - public static final int ETrackedPropertyError_TrackedProp_NotYetAvailable = 9; - /** native declaration : headers\openvr_capi.h:444 */ - public static final int ETrackedPropertyError_TrackedProp_PermissionDenied = 10; - /** native declaration : headers\openvr_capi.h:445 */ - public static final int ETrackedPropertyError_TrackedProp_InvalidOperation = 11; - /** native declaration : headers\openvr_capi.h:446 */ - public static final int ETrackedPropertyError_TrackedProp_CannotWriteToWildcards = 12; - }; - /** - * native declaration : headers\openvr_capi.h:455
                  - * enum values - */ - public static interface EVRSubmitFlags { - /** native declaration : headers\openvr_capi.h:449 */ - public static final int EVRSubmitFlags_Submit_Default = 0; - /** native declaration : headers\openvr_capi.h:450 */ - public static final int EVRSubmitFlags_Submit_LensDistortionAlreadyApplied = 1; - /** native declaration : headers\openvr_capi.h:451 */ - public static final int EVRSubmitFlags_Submit_GlRenderBuffer = 2; - /** native declaration : headers\openvr_capi.h:452 */ - public static final int EVRSubmitFlags_Submit_Reserved = 4; - /** native declaration : headers\openvr_capi.h:453 */ - public static final int EVRSubmitFlags_Submit_TextureWithPose = 8; - /** native declaration : headers\openvr_capi.h:454 */ - public static final int EVRSubmitFlags_Submit_TextureWithDepth = 16; - }; - /** - * native declaration : headers\openvr_capi.h:466
                  - * enum values - */ - public static interface EVRState { - /** native declaration : headers\openvr_capi.h:457 */ - public static final int EVRState_VRState_Undefined = -1; - /** native declaration : headers\openvr_capi.h:458 */ - public static final int EVRState_VRState_Off = 0; - /** native declaration : headers\openvr_capi.h:459 */ - public static final int EVRState_VRState_Searching = 1; - /** native declaration : headers\openvr_capi.h:460 */ - public static final int EVRState_VRState_Searching_Alert = 2; - /** native declaration : headers\openvr_capi.h:461 */ - public static final int EVRState_VRState_Ready = 3; - /** native declaration : headers\openvr_capi.h:462 */ - public static final int EVRState_VRState_Ready_Alert = 4; - /** native declaration : headers\openvr_capi.h:463 */ - public static final int EVRState_VRState_NotReady = 5; - /** native declaration : headers\openvr_capi.h:464 */ - public static final int EVRState_VRState_Standby = 6; - /** native declaration : headers\openvr_capi.h:465 */ - public static final int EVRState_VRState_Ready_Alert_Low = 7; - }; - /** - * native declaration : headers\openvr_capi.h:616
                  - * enum values - */ - public static interface EVREventType { - /** native declaration : headers\openvr_capi.h:468 */ - public static final int EVREventType_VREvent_None = 0; - /** native declaration : headers\openvr_capi.h:469 */ - public static final int EVREventType_VREvent_TrackedDeviceActivated = 100; - /** native declaration : headers\openvr_capi.h:470 */ - public static final int EVREventType_VREvent_TrackedDeviceDeactivated = 101; - /** native declaration : headers\openvr_capi.h:471 */ - public static final int EVREventType_VREvent_TrackedDeviceUpdated = 102; - /** native declaration : headers\openvr_capi.h:472 */ - public static final int EVREventType_VREvent_TrackedDeviceUserInteractionStarted = 103; - /** native declaration : headers\openvr_capi.h:473 */ - public static final int EVREventType_VREvent_TrackedDeviceUserInteractionEnded = 104; - /** native declaration : headers\openvr_capi.h:474 */ - public static final int EVREventType_VREvent_IpdChanged = 105; - /** native declaration : headers\openvr_capi.h:475 */ - public static final int EVREventType_VREvent_EnterStandbyMode = 106; - /** native declaration : headers\openvr_capi.h:476 */ - public static final int EVREventType_VREvent_LeaveStandbyMode = 107; - /** native declaration : headers\openvr_capi.h:477 */ - public static final int EVREventType_VREvent_TrackedDeviceRoleChanged = 108; - /** native declaration : headers\openvr_capi.h:478 */ - public static final int EVREventType_VREvent_WatchdogWakeUpRequested = 109; - /** native declaration : headers\openvr_capi.h:479 */ - public static final int EVREventType_VREvent_LensDistortionChanged = 110; - /** native declaration : headers\openvr_capi.h:480 */ - public static final int EVREventType_VREvent_PropertyChanged = 111; - /** native declaration : headers\openvr_capi.h:481 */ - public static final int EVREventType_VREvent_WirelessDisconnect = 112; - /** native declaration : headers\openvr_capi.h:482 */ - public static final int EVREventType_VREvent_WirelessReconnect = 113; - /** native declaration : headers\openvr_capi.h:483 */ - public static final int EVREventType_VREvent_ButtonPress = 200; - /** native declaration : headers\openvr_capi.h:484 */ - public static final int EVREventType_VREvent_ButtonUnpress = 201; - /** native declaration : headers\openvr_capi.h:485 */ - public static final int EVREventType_VREvent_ButtonTouch = 202; - /** native declaration : headers\openvr_capi.h:486 */ - public static final int EVREventType_VREvent_ButtonUntouch = 203; - /** native declaration : headers\openvr_capi.h:487 */ - public static final int EVREventType_VREvent_DualAnalog_Press = 250; - /** native declaration : headers\openvr_capi.h:488 */ - public static final int EVREventType_VREvent_DualAnalog_Unpress = 251; - /** native declaration : headers\openvr_capi.h:489 */ - public static final int EVREventType_VREvent_DualAnalog_Touch = 252; - /** native declaration : headers\openvr_capi.h:490 */ - public static final int EVREventType_VREvent_DualAnalog_Untouch = 253; - /** native declaration : headers\openvr_capi.h:491 */ - public static final int EVREventType_VREvent_DualAnalog_Move = 254; - /** native declaration : headers\openvr_capi.h:492 */ - public static final int EVREventType_VREvent_DualAnalog_ModeSwitch1 = 255; - /** native declaration : headers\openvr_capi.h:493 */ - public static final int EVREventType_VREvent_DualAnalog_ModeSwitch2 = 256; - /** native declaration : headers\openvr_capi.h:494 */ - public static final int EVREventType_VREvent_DualAnalog_Cancel = 257; - /** native declaration : headers\openvr_capi.h:495 */ - public static final int EVREventType_VREvent_MouseMove = 300; - /** native declaration : headers\openvr_capi.h:496 */ - public static final int EVREventType_VREvent_MouseButtonDown = 301; - /** native declaration : headers\openvr_capi.h:497 */ - public static final int EVREventType_VREvent_MouseButtonUp = 302; - /** native declaration : headers\openvr_capi.h:498 */ - public static final int EVREventType_VREvent_FocusEnter = 303; - /** native declaration : headers\openvr_capi.h:499 */ - public static final int EVREventType_VREvent_FocusLeave = 304; - /** native declaration : headers\openvr_capi.h:500 */ - public static final int EVREventType_VREvent_Scroll = 305; - /** native declaration : headers\openvr_capi.h:501 */ - public static final int EVREventType_VREvent_TouchPadMove = 306; - /** native declaration : headers\openvr_capi.h:502 */ - public static final int EVREventType_VREvent_OverlayFocusChanged = 307; - /** native declaration : headers\openvr_capi.h:503 */ - public static final int EVREventType_VREvent_InputFocusCaptured = 400; - /** native declaration : headers\openvr_capi.h:504 */ - public static final int EVREventType_VREvent_InputFocusReleased = 401; - /** native declaration : headers\openvr_capi.h:505 */ - public static final int EVREventType_VREvent_SceneFocusLost = 402; - /** native declaration : headers\openvr_capi.h:506 */ - public static final int EVREventType_VREvent_SceneFocusGained = 403; - /** native declaration : headers\openvr_capi.h:507 */ - public static final int EVREventType_VREvent_SceneApplicationChanged = 404; - /** native declaration : headers\openvr_capi.h:508 */ - public static final int EVREventType_VREvent_SceneFocusChanged = 405; - /** native declaration : headers\openvr_capi.h:509 */ - public static final int EVREventType_VREvent_InputFocusChanged = 406; - /** native declaration : headers\openvr_capi.h:510 */ - public static final int EVREventType_VREvent_SceneApplicationSecondaryRenderingStarted = 407; - /** native declaration : headers\openvr_capi.h:511 */ - public static final int EVREventType_VREvent_SceneApplicationUsingWrongGraphicsAdapter = 408; - /** native declaration : headers\openvr_capi.h:512 */ - public static final int EVREventType_VREvent_ActionBindingReloaded = 409; - /** native declaration : headers\openvr_capi.h:513 */ - public static final int EVREventType_VREvent_HideRenderModels = 410; - /** native declaration : headers\openvr_capi.h:514 */ - public static final int EVREventType_VREvent_ShowRenderModels = 411; - /** native declaration : headers\openvr_capi.h:515 */ - public static final int EVREventType_VREvent_ConsoleOpened = 420; - /** native declaration : headers\openvr_capi.h:516 */ - public static final int EVREventType_VREvent_ConsoleClosed = 421; - /** native declaration : headers\openvr_capi.h:517 */ - public static final int EVREventType_VREvent_OverlayShown = 500; - /** native declaration : headers\openvr_capi.h:518 */ - public static final int EVREventType_VREvent_OverlayHidden = 501; - /** native declaration : headers\openvr_capi.h:519 */ - public static final int EVREventType_VREvent_DashboardActivated = 502; - /** native declaration : headers\openvr_capi.h:520 */ - public static final int EVREventType_VREvent_DashboardDeactivated = 503; - /** native declaration : headers\openvr_capi.h:521 */ - public static final int EVREventType_VREvent_DashboardThumbSelected = 504; - /** native declaration : headers\openvr_capi.h:522 */ - public static final int EVREventType_VREvent_DashboardRequested = 505; - /** native declaration : headers\openvr_capi.h:523 */ - public static final int EVREventType_VREvent_ResetDashboard = 506; - /** native declaration : headers\openvr_capi.h:524 */ - public static final int EVREventType_VREvent_RenderToast = 507; - /** native declaration : headers\openvr_capi.h:525 */ - public static final int EVREventType_VREvent_ImageLoaded = 508; - /** native declaration : headers\openvr_capi.h:526 */ - public static final int EVREventType_VREvent_ShowKeyboard = 509; - /** native declaration : headers\openvr_capi.h:527 */ - public static final int EVREventType_VREvent_HideKeyboard = 510; - /** native declaration : headers\openvr_capi.h:528 */ - public static final int EVREventType_VREvent_OverlayGamepadFocusGained = 511; - /** native declaration : headers\openvr_capi.h:529 */ - public static final int EVREventType_VREvent_OverlayGamepadFocusLost = 512; - /** native declaration : headers\openvr_capi.h:530 */ - public static final int EVREventType_VREvent_OverlaySharedTextureChanged = 513; - /** native declaration : headers\openvr_capi.h:531 */ - public static final int EVREventType_VREvent_ScreenshotTriggered = 516; - /** native declaration : headers\openvr_capi.h:532 */ - public static final int EVREventType_VREvent_ImageFailed = 517; - /** native declaration : headers\openvr_capi.h:533 */ - public static final int EVREventType_VREvent_DashboardOverlayCreated = 518; - /** native declaration : headers\openvr_capi.h:534 */ - public static final int EVREventType_VREvent_SwitchGamepadFocus = 519; - /** native declaration : headers\openvr_capi.h:535 */ - public static final int EVREventType_VREvent_RequestScreenshot = 520; - /** native declaration : headers\openvr_capi.h:536 */ - public static final int EVREventType_VREvent_ScreenshotTaken = 521; - /** native declaration : headers\openvr_capi.h:537 */ - public static final int EVREventType_VREvent_ScreenshotFailed = 522; - /** native declaration : headers\openvr_capi.h:538 */ - public static final int EVREventType_VREvent_SubmitScreenshotToDashboard = 523; - /** native declaration : headers\openvr_capi.h:539 */ - public static final int EVREventType_VREvent_ScreenshotProgressToDashboard = 524; - /** native declaration : headers\openvr_capi.h:540 */ - public static final int EVREventType_VREvent_PrimaryDashboardDeviceChanged = 525; - /** native declaration : headers\openvr_capi.h:541 */ - public static final int EVREventType_VREvent_RoomViewShown = 526; - /** native declaration : headers\openvr_capi.h:542 */ - public static final int EVREventType_VREvent_RoomViewHidden = 527; - /** native declaration : headers\openvr_capi.h:543 */ - public static final int EVREventType_VREvent_Notification_Shown = 600; - /** native declaration : headers\openvr_capi.h:544 */ - public static final int EVREventType_VREvent_Notification_Hidden = 601; - /** native declaration : headers\openvr_capi.h:545 */ - public static final int EVREventType_VREvent_Notification_BeginInteraction = 602; - /** native declaration : headers\openvr_capi.h:546 */ - public static final int EVREventType_VREvent_Notification_Destroyed = 603; - /** native declaration : headers\openvr_capi.h:547 */ - public static final int EVREventType_VREvent_Quit = 700; - /** native declaration : headers\openvr_capi.h:548 */ - public static final int EVREventType_VREvent_ProcessQuit = 701; - /** native declaration : headers\openvr_capi.h:549 */ - public static final int EVREventType_VREvent_QuitAborted_UserPrompt = 702; - /** native declaration : headers\openvr_capi.h:550 */ - public static final int EVREventType_VREvent_QuitAcknowledged = 703; - /** native declaration : headers\openvr_capi.h:551 */ - public static final int EVREventType_VREvent_DriverRequestedQuit = 704; - /** native declaration : headers\openvr_capi.h:552 */ - public static final int EVREventType_VREvent_ChaperoneDataHasChanged = 800; - /** native declaration : headers\openvr_capi.h:553 */ - public static final int EVREventType_VREvent_ChaperoneUniverseHasChanged = 801; - /** native declaration : headers\openvr_capi.h:554 */ - public static final int EVREventType_VREvent_ChaperoneTempDataHasChanged = 802; - /** native declaration : headers\openvr_capi.h:555 */ - public static final int EVREventType_VREvent_ChaperoneSettingsHaveChanged = 803; - /** native declaration : headers\openvr_capi.h:556 */ - public static final int EVREventType_VREvent_SeatedZeroPoseReset = 804; - /** native declaration : headers\openvr_capi.h:557 */ - public static final int EVREventType_VREvent_AudioSettingsHaveChanged = 820; - /** native declaration : headers\openvr_capi.h:558 */ - public static final int EVREventType_VREvent_BackgroundSettingHasChanged = 850; - /** native declaration : headers\openvr_capi.h:559 */ - public static final int EVREventType_VREvent_CameraSettingsHaveChanged = 851; - /** native declaration : headers\openvr_capi.h:560 */ - public static final int EVREventType_VREvent_ReprojectionSettingHasChanged = 852; - /** native declaration : headers\openvr_capi.h:561 */ - public static final int EVREventType_VREvent_ModelSkinSettingsHaveChanged = 853; - /** native declaration : headers\openvr_capi.h:562 */ - public static final int EVREventType_VREvent_EnvironmentSettingsHaveChanged = 854; - /** native declaration : headers\openvr_capi.h:563 */ - public static final int EVREventType_VREvent_PowerSettingsHaveChanged = 855; - /** native declaration : headers\openvr_capi.h:564 */ - public static final int EVREventType_VREvent_EnableHomeAppSettingsHaveChanged = 856; - /** native declaration : headers\openvr_capi.h:565 */ - public static final int EVREventType_VREvent_SteamVRSectionSettingChanged = 857; - /** native declaration : headers\openvr_capi.h:566 */ - public static final int EVREventType_VREvent_LighthouseSectionSettingChanged = 858; - /** native declaration : headers\openvr_capi.h:567 */ - public static final int EVREventType_VREvent_NullSectionSettingChanged = 859; - /** native declaration : headers\openvr_capi.h:568 */ - public static final int EVREventType_VREvent_UserInterfaceSectionSettingChanged = 860; - /** native declaration : headers\openvr_capi.h:569 */ - public static final int EVREventType_VREvent_NotificationsSectionSettingChanged = 861; - /** native declaration : headers\openvr_capi.h:570 */ - public static final int EVREventType_VREvent_KeyboardSectionSettingChanged = 862; - /** native declaration : headers\openvr_capi.h:571 */ - public static final int EVREventType_VREvent_PerfSectionSettingChanged = 863; - /** native declaration : headers\openvr_capi.h:572 */ - public static final int EVREventType_VREvent_DashboardSectionSettingChanged = 864; - /** native declaration : headers\openvr_capi.h:573 */ - public static final int EVREventType_VREvent_WebInterfaceSectionSettingChanged = 865; - /** native declaration : headers\openvr_capi.h:574 */ - public static final int EVREventType_VREvent_TrackersSectionSettingChanged = 866; - /** native declaration : headers\openvr_capi.h:575 */ - public static final int EVREventType_VREvent_StatusUpdate = 900; - /** native declaration : headers\openvr_capi.h:576 */ - public static final int EVREventType_VREvent_WebInterface_InstallDriverCompleted = 950; - /** native declaration : headers\openvr_capi.h:577 */ - public static final int EVREventType_VREvent_MCImageUpdated = 1000; - /** native declaration : headers\openvr_capi.h:578 */ - public static final int EVREventType_VREvent_FirmwareUpdateStarted = 1100; - /** native declaration : headers\openvr_capi.h:579 */ - public static final int EVREventType_VREvent_FirmwareUpdateFinished = 1101; - /** native declaration : headers\openvr_capi.h:580 */ - public static final int EVREventType_VREvent_KeyboardClosed = 1200; - /** native declaration : headers\openvr_capi.h:581 */ - public static final int EVREventType_VREvent_KeyboardCharInput = 1201; - /** native declaration : headers\openvr_capi.h:582 */ - public static final int EVREventType_VREvent_KeyboardDone = 1202; - /** native declaration : headers\openvr_capi.h:583 */ - public static final int EVREventType_VREvent_ApplicationTransitionStarted = 1300; - /** native declaration : headers\openvr_capi.h:584 */ - public static final int EVREventType_VREvent_ApplicationTransitionAborted = 1301; - /** native declaration : headers\openvr_capi.h:585 */ - public static final int EVREventType_VREvent_ApplicationTransitionNewAppStarted = 1302; - /** native declaration : headers\openvr_capi.h:586 */ - public static final int EVREventType_VREvent_ApplicationListUpdated = 1303; - /** native declaration : headers\openvr_capi.h:587 */ - public static final int EVREventType_VREvent_ApplicationMimeTypeLoad = 1304; - /** native declaration : headers\openvr_capi.h:588 */ - public static final int EVREventType_VREvent_ApplicationTransitionNewAppLaunchComplete = 1305; - /** native declaration : headers\openvr_capi.h:589 */ - public static final int EVREventType_VREvent_ProcessConnected = 1306; - /** native declaration : headers\openvr_capi.h:590 */ - public static final int EVREventType_VREvent_ProcessDisconnected = 1307; - /** native declaration : headers\openvr_capi.h:591 */ - public static final int EVREventType_VREvent_Compositor_MirrorWindowShown = 1400; - /** native declaration : headers\openvr_capi.h:592 */ - public static final int EVREventType_VREvent_Compositor_MirrorWindowHidden = 1401; - /** native declaration : headers\openvr_capi.h:593 */ - public static final int EVREventType_VREvent_Compositor_ChaperoneBoundsShown = 1410; - /** native declaration : headers\openvr_capi.h:594 */ - public static final int EVREventType_VREvent_Compositor_ChaperoneBoundsHidden = 1411; - /** native declaration : headers\openvr_capi.h:595 */ - public static final int EVREventType_VREvent_TrackedCamera_StartVideoStream = 1500; - /** native declaration : headers\openvr_capi.h:596 */ - public static final int EVREventType_VREvent_TrackedCamera_StopVideoStream = 1501; - /** native declaration : headers\openvr_capi.h:597 */ - public static final int EVREventType_VREvent_TrackedCamera_PauseVideoStream = 1502; - /** native declaration : headers\openvr_capi.h:598 */ - public static final int EVREventType_VREvent_TrackedCamera_ResumeVideoStream = 1503; - /** native declaration : headers\openvr_capi.h:599 */ - public static final int EVREventType_VREvent_TrackedCamera_EditingSurface = 1550; - /** native declaration : headers\openvr_capi.h:600 */ - public static final int EVREventType_VREvent_PerformanceTest_EnableCapture = 1600; - /** native declaration : headers\openvr_capi.h:601 */ - public static final int EVREventType_VREvent_PerformanceTest_DisableCapture = 1601; - /** native declaration : headers\openvr_capi.h:602 */ - public static final int EVREventType_VREvent_PerformanceTest_FidelityLevel = 1602; - /** native declaration : headers\openvr_capi.h:603 */ - public static final int EVREventType_VREvent_MessageOverlay_Closed = 1650; - /** native declaration : headers\openvr_capi.h:604 */ - public static final int EVREventType_VREvent_MessageOverlayCloseRequested = 1651; - /** native declaration : headers\openvr_capi.h:605 */ - public static final int EVREventType_VREvent_Input_HapticVibration = 1700; - /** native declaration : headers\openvr_capi.h:606 */ - public static final int EVREventType_VREvent_Input_BindingLoadFailed = 1701; - /** native declaration : headers\openvr_capi.h:607 */ - public static final int EVREventType_VREvent_Input_BindingLoadSuccessful = 1702; - /** native declaration : headers\openvr_capi.h:608 */ - public static final int EVREventType_VREvent_Input_ActionManifestReloaded = 1703; - /** native declaration : headers\openvr_capi.h:609 */ - public static final int EVREventType_VREvent_Input_ActionManifestLoadFailed = 1704; - /** native declaration : headers\openvr_capi.h:610 */ - public static final int EVREventType_VREvent_SpatialAnchors_PoseUpdated = 1800; - /** native declaration : headers\openvr_capi.h:611 */ - public static final int EVREventType_VREvent_SpatialAnchors_DescriptorUpdated = 1801; - /** native declaration : headers\openvr_capi.h:612 */ - public static final int EVREventType_VREvent_SpatialAnchors_RequestPoseUpdate = 1802; - /** native declaration : headers\openvr_capi.h:613 */ - public static final int EVREventType_VREvent_SpatialAnchors_RequestDescriptorUpdate = 1803; - /** native declaration : headers\openvr_capi.h:614 */ - public static final int EVREventType_VREvent_VendorSpecific_Reserved_Start = 10000; - /** native declaration : headers\openvr_capi.h:615 */ - public static final int EVREventType_VREvent_VendorSpecific_Reserved_End = 19999; - }; - /** - * native declaration : headers\openvr_capi.h:623
                  - * enum values - */ - public static interface EDeviceActivityLevel { - /** native declaration : headers\openvr_capi.h:618 */ - public static final int EDeviceActivityLevel_k_EDeviceActivityLevel_Unknown = -1; - /** native declaration : headers\openvr_capi.h:619 */ - public static final int EDeviceActivityLevel_k_EDeviceActivityLevel_Idle = 0; - /** native declaration : headers\openvr_capi.h:620 */ - public static final int EDeviceActivityLevel_k_EDeviceActivityLevel_UserInteraction = 1; - /** native declaration : headers\openvr_capi.h:621 */ - public static final int EDeviceActivityLevel_k_EDeviceActivityLevel_UserInteraction_Timeout = 2; - /** native declaration : headers\openvr_capi.h:622 */ - public static final int EDeviceActivityLevel_k_EDeviceActivityLevel_Standby = 3; - }; - /** - * native declaration : headers\openvr_capi.h:646
                  - * enum values - */ - public static interface EVRButtonId { - /** native declaration : headers\openvr_capi.h:625 */ - public static final int EVRButtonId_k_EButton_System = 0; - /** native declaration : headers\openvr_capi.h:626 */ - public static final int EVRButtonId_k_EButton_ApplicationMenu = 1; - /** native declaration : headers\openvr_capi.h:627 */ - public static final int EVRButtonId_k_EButton_Grip = 2; - /** native declaration : headers\openvr_capi.h:628 */ - public static final int EVRButtonId_k_EButton_DPad_Left = 3; - /** native declaration : headers\openvr_capi.h:629 */ - public static final int EVRButtonId_k_EButton_DPad_Up = 4; - /** native declaration : headers\openvr_capi.h:630 */ - public static final int EVRButtonId_k_EButton_DPad_Right = 5; - /** native declaration : headers\openvr_capi.h:631 */ - public static final int EVRButtonId_k_EButton_DPad_Down = 6; - /** native declaration : headers\openvr_capi.h:632 */ - public static final int EVRButtonId_k_EButton_A = 7; - /** native declaration : headers\openvr_capi.h:633 */ - public static final int EVRButtonId_k_EButton_ProximitySensor = 31; - /** native declaration : headers\openvr_capi.h:634 */ - public static final int EVRButtonId_k_EButton_Axis0 = 32; - /** native declaration : headers\openvr_capi.h:635 */ - public static final int EVRButtonId_k_EButton_Axis1 = 33; - /** native declaration : headers\openvr_capi.h:636 */ - public static final int EVRButtonId_k_EButton_Axis2 = 34; - /** native declaration : headers\openvr_capi.h:637 */ - public static final int EVRButtonId_k_EButton_Axis3 = 35; - /** native declaration : headers\openvr_capi.h:638 */ - public static final int EVRButtonId_k_EButton_Axis4 = 36; - /** native declaration : headers\openvr_capi.h:639 */ - public static final int EVRButtonId_k_EButton_SteamVR_Touchpad = 32; - /** native declaration : headers\openvr_capi.h:640 */ - public static final int EVRButtonId_k_EButton_SteamVR_Trigger = 33; - /** native declaration : headers\openvr_capi.h:641 */ - public static final int EVRButtonId_k_EButton_Dashboard_Back = 2; - /** native declaration : headers\openvr_capi.h:642 */ - public static final int EVRButtonId_k_EButton_Knuckles_A = 2; - /** native declaration : headers\openvr_capi.h:643 */ - public static final int EVRButtonId_k_EButton_Knuckles_B = 1; - /** native declaration : headers\openvr_capi.h:644 */ - public static final int EVRButtonId_k_EButton_Knuckles_JoyStick = 35; - /** native declaration : headers\openvr_capi.h:645 */ - public static final int EVRButtonId_k_EButton_Max = 64; - }; - /** - * native declaration : headers\openvr_capi.h:651
                  - * enum values - */ - public static interface EVRMouseButton { - /** native declaration : headers\openvr_capi.h:648 */ - public static final int EVRMouseButton_VRMouseButton_Left = 1; - /** native declaration : headers\openvr_capi.h:649 */ - public static final int EVRMouseButton_VRMouseButton_Right = 2; - /** native declaration : headers\openvr_capi.h:650 */ - public static final int EVRMouseButton_VRMouseButton_Middle = 4; - }; - /** - * native declaration : headers\openvr_capi.h:655
                  - * enum values - */ - public static interface EDualAnalogWhich { - /** native declaration : headers\openvr_capi.h:653 */ - public static final int EDualAnalogWhich_k_EDualAnalog_Left = 0; - /** native declaration : headers\openvr_capi.h:654 */ - public static final int EDualAnalogWhich_k_EDualAnalog_Right = 1; - }; - /** - * native declaration : headers\openvr_capi.h:674
                  - * enum values - */ - public static interface EVRInputError { - /** native declaration : headers\openvr_capi.h:657 */ - public static final int EVRInputError_VRInputError_None = 0; - /** native declaration : headers\openvr_capi.h:658 */ - public static final int EVRInputError_VRInputError_NameNotFound = 1; - /** native declaration : headers\openvr_capi.h:659 */ - public static final int EVRInputError_VRInputError_WrongType = 2; - /** native declaration : headers\openvr_capi.h:660 */ - public static final int EVRInputError_VRInputError_InvalidHandle = 3; - /** native declaration : headers\openvr_capi.h:661 */ - public static final int EVRInputError_VRInputError_InvalidParam = 4; - /** native declaration : headers\openvr_capi.h:662 */ - public static final int EVRInputError_VRInputError_NoSteam = 5; - /** native declaration : headers\openvr_capi.h:663 */ - public static final int EVRInputError_VRInputError_MaxCapacityReached = 6; - /** native declaration : headers\openvr_capi.h:664 */ - public static final int EVRInputError_VRInputError_IPCError = 7; - /** native declaration : headers\openvr_capi.h:665 */ - public static final int EVRInputError_VRInputError_NoActiveActionSet = 8; - /** native declaration : headers\openvr_capi.h:666 */ - public static final int EVRInputError_VRInputError_InvalidDevice = 9; - /** native declaration : headers\openvr_capi.h:667 */ - public static final int EVRInputError_VRInputError_InvalidSkeleton = 10; - /** native declaration : headers\openvr_capi.h:668 */ - public static final int EVRInputError_VRInputError_InvalidBoneCount = 11; - /** native declaration : headers\openvr_capi.h:669 */ - public static final int EVRInputError_VRInputError_InvalidCompressedData = 12; - /** native declaration : headers\openvr_capi.h:670 */ - public static final int EVRInputError_VRInputError_NoData = 13; - /** native declaration : headers\openvr_capi.h:671 */ - public static final int EVRInputError_VRInputError_BufferTooSmall = 14; - /** native declaration : headers\openvr_capi.h:672 */ - public static final int EVRInputError_VRInputError_MismatchedActionManifest = 15; - /** native declaration : headers\openvr_capi.h:673 */ - public static final int EVRInputError_VRInputError_MissingSkeletonData = 16; - }; - /** - * native declaration : headers\openvr_capi.h:690
                  - * enum values - */ - public static interface EVRSpatialAnchorError { - /** native declaration : headers\openvr_capi.h:676 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_Success = 0; - /** native declaration : headers\openvr_capi.h:677 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_Internal = 1; - /** native declaration : headers\openvr_capi.h:678 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_UnknownHandle = 2; - /** native declaration : headers\openvr_capi.h:679 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_ArrayTooSmall = 3; - /** native declaration : headers\openvr_capi.h:680 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_InvalidDescriptorChar = 4; - /** native declaration : headers\openvr_capi.h:681 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_NotYetAvailable = 5; - /** native declaration : headers\openvr_capi.h:682 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_NotAvailableInThisUniverse = 6; - /** native declaration : headers\openvr_capi.h:683 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_PermanentlyUnavailable = 7; - /** native declaration : headers\openvr_capi.h:684 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_WrongDriver = 8; - /** native declaration : headers\openvr_capi.h:685 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_DescriptorTooLong = 9; - /** native declaration : headers\openvr_capi.h:686 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_Unknown = 10; - /** native declaration : headers\openvr_capi.h:687 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_NoRoomCalibration = 11; - /** native declaration : headers\openvr_capi.h:688 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_InvalidArgument = 12; - /** native declaration : headers\openvr_capi.h:689 */ - public static final int EVRSpatialAnchorError_VRSpatialAnchorError_UnknownDriver = 13; - }; - /** - * native declaration : headers\openvr_capi.h:696
                  - * enum values - */ - public static interface EHiddenAreaMeshType { - /** native declaration : headers\openvr_capi.h:692 */ - public static final int EHiddenAreaMeshType_k_eHiddenAreaMesh_Standard = 0; - /** native declaration : headers\openvr_capi.h:693 */ - public static final int EHiddenAreaMeshType_k_eHiddenAreaMesh_Inverse = 1; - /** native declaration : headers\openvr_capi.h:694 */ - public static final int EHiddenAreaMeshType_k_eHiddenAreaMesh_LineLoop = 2; - /** native declaration : headers\openvr_capi.h:695 */ - public static final int EHiddenAreaMeshType_k_eHiddenAreaMesh_Max = 3; - }; - /** - * native declaration : headers\openvr_capi.h:702
                  - * enum values - */ - public static interface EVRControllerAxisType { - /** native declaration : headers\openvr_capi.h:698 */ - public static final int EVRControllerAxisType_k_eControllerAxis_None = 0; - /** native declaration : headers\openvr_capi.h:699 */ - public static final int EVRControllerAxisType_k_eControllerAxis_TrackPad = 1; - /** native declaration : headers\openvr_capi.h:700 */ - public static final int EVRControllerAxisType_k_eControllerAxis_Joystick = 2; - /** native declaration : headers\openvr_capi.h:701 */ - public static final int EVRControllerAxisType_k_eControllerAxis_Trigger = 3; - }; - /** - * native declaration : headers\openvr_capi.h:706
                  - * enum values - */ - public static interface EVRControllerEventOutputType { - /** native declaration : headers\openvr_capi.h:704 */ - public static final int EVRControllerEventOutputType_ControllerEventOutput_OSEvents = 0; - /** native declaration : headers\openvr_capi.h:705 */ - public static final int EVRControllerEventOutputType_ControllerEventOutput_VREvents = 1; - }; - /** - * native declaration : headers\openvr_capi.h:714
                  - * enum values - */ - public static interface ECollisionBoundsStyle { - /** native declaration : headers\openvr_capi.h:708 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_BEGINNER = 0; - /** native declaration : headers\openvr_capi.h:709 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_INTERMEDIATE = 1; - /** native declaration : headers\openvr_capi.h:710 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_SQUARES = 2; - /** native declaration : headers\openvr_capi.h:711 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_ADVANCED = 3; - /** native declaration : headers\openvr_capi.h:712 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_NONE = 4; - /** native declaration : headers\openvr_capi.h:713 */ - public static final int ECollisionBoundsStyle_COLLISION_BOUNDS_STYLE_COUNT = 5; - }; - /** - * native declaration : headers\openvr_capi.h:740
                  - * enum values - */ - public static interface EVROverlayError { - /** native declaration : headers\openvr_capi.h:716 */ - public static final int EVROverlayError_VROverlayError_None = 0; - /** native declaration : headers\openvr_capi.h:717 */ - public static final int EVROverlayError_VROverlayError_UnknownOverlay = 10; - /** native declaration : headers\openvr_capi.h:718 */ - public static final int EVROverlayError_VROverlayError_InvalidHandle = 11; - /** native declaration : headers\openvr_capi.h:719 */ - public static final int EVROverlayError_VROverlayError_PermissionDenied = 12; - /** native declaration : headers\openvr_capi.h:720 */ - public static final int EVROverlayError_VROverlayError_OverlayLimitExceeded = 13; - /** native declaration : headers\openvr_capi.h:721 */ - public static final int EVROverlayError_VROverlayError_WrongVisibilityType = 14; - /** native declaration : headers\openvr_capi.h:722 */ - public static final int EVROverlayError_VROverlayError_KeyTooLong = 15; - /** native declaration : headers\openvr_capi.h:723 */ - public static final int EVROverlayError_VROverlayError_NameTooLong = 16; - /** native declaration : headers\openvr_capi.h:724 */ - public static final int EVROverlayError_VROverlayError_KeyInUse = 17; - /** native declaration : headers\openvr_capi.h:725 */ - public static final int EVROverlayError_VROverlayError_WrongTransformType = 18; - /** native declaration : headers\openvr_capi.h:726 */ - public static final int EVROverlayError_VROverlayError_InvalidTrackedDevice = 19; - /** native declaration : headers\openvr_capi.h:727 */ - public static final int EVROverlayError_VROverlayError_InvalidParameter = 20; - /** native declaration : headers\openvr_capi.h:728 */ - public static final int EVROverlayError_VROverlayError_ThumbnailCantBeDestroyed = 21; - /** native declaration : headers\openvr_capi.h:729 */ - public static final int EVROverlayError_VROverlayError_ArrayTooSmall = 22; - /** native declaration : headers\openvr_capi.h:730 */ - public static final int EVROverlayError_VROverlayError_RequestFailed = 23; - /** native declaration : headers\openvr_capi.h:731 */ - public static final int EVROverlayError_VROverlayError_InvalidTexture = 24; - /** native declaration : headers\openvr_capi.h:732 */ - public static final int EVROverlayError_VROverlayError_UnableToLoadFile = 25; - /** native declaration : headers\openvr_capi.h:733 */ - public static final int EVROverlayError_VROverlayError_KeyboardAlreadyInUse = 26; - /** native declaration : headers\openvr_capi.h:734 */ - public static final int EVROverlayError_VROverlayError_NoNeighbor = 27; - /** native declaration : headers\openvr_capi.h:735 */ - public static final int EVROverlayError_VROverlayError_TooManyMaskPrimitives = 29; - /** native declaration : headers\openvr_capi.h:736 */ - public static final int EVROverlayError_VROverlayError_BadMaskPrimitive = 30; - /** native declaration : headers\openvr_capi.h:737 */ - public static final int EVROverlayError_VROverlayError_TextureAlreadyLocked = 31; - /** native declaration : headers\openvr_capi.h:738 */ - public static final int EVROverlayError_VROverlayError_TextureLockCapacityReached = 32; - /** native declaration : headers\openvr_capi.h:739 */ - public static final int EVROverlayError_VROverlayError_TextureNotLocked = 33; - }; - /** - * native declaration : headers\openvr_capi.h:751
                  - * enum values - */ - public static interface EVRApplicationType { - /** native declaration : headers\openvr_capi.h:742 */ - public static final int EVRApplicationType_VRApplication_Other = 0; - /** native declaration : headers\openvr_capi.h:743 */ - public static final int EVRApplicationType_VRApplication_Scene = 1; - /** native declaration : headers\openvr_capi.h:744 */ - public static final int EVRApplicationType_VRApplication_Overlay = 2; - /** native declaration : headers\openvr_capi.h:745 */ - public static final int EVRApplicationType_VRApplication_Background = 3; - /** native declaration : headers\openvr_capi.h:746 */ - public static final int EVRApplicationType_VRApplication_Utility = 4; - /** native declaration : headers\openvr_capi.h:747 */ - public static final int EVRApplicationType_VRApplication_VRMonitor = 5; - /** native declaration : headers\openvr_capi.h:748 */ - public static final int EVRApplicationType_VRApplication_SteamWatchdog = 6; - /** native declaration : headers\openvr_capi.h:749 */ - public static final int EVRApplicationType_VRApplication_Bootstrapper = 7; - /** native declaration : headers\openvr_capi.h:750 */ - public static final int EVRApplicationType_VRApplication_Max = 8; - }; - /** - * native declaration : headers\openvr_capi.h:756
                  - * enum values - */ - public static interface EVRFirmwareError { - /** native declaration : headers\openvr_capi.h:753 */ - public static final int EVRFirmwareError_VRFirmwareError_None = 0; - /** native declaration : headers\openvr_capi.h:754 */ - public static final int EVRFirmwareError_VRFirmwareError_Success = 1; - /** native declaration : headers\openvr_capi.h:755 */ - public static final int EVRFirmwareError_VRFirmwareError_Fail = 2; - }; - /** - * native declaration : headers\openvr_capi.h:763
                  - * enum values - */ - public static interface EVRNotificationError { - /** native declaration : headers\openvr_capi.h:758 */ - public static final int EVRNotificationError_VRNotificationError_OK = 0; - /** native declaration : headers\openvr_capi.h:759 */ - public static final int EVRNotificationError_VRNotificationError_InvalidNotificationId = 100; - /** native declaration : headers\openvr_capi.h:760 */ - public static final int EVRNotificationError_VRNotificationError_NotificationQueueFull = 101; - /** native declaration : headers\openvr_capi.h:761 */ - public static final int EVRNotificationError_VRNotificationError_InvalidOverlayHandle = 102; - /** native declaration : headers\openvr_capi.h:762 */ - public static final int EVRNotificationError_VRNotificationError_SystemWithUserValueAlreadyExists = 103; - }; - /** - * native declaration : headers\openvr_capi.h:767
                  - * enum values - */ - public static interface EVRSkeletalMotionRange { - /** native declaration : headers\openvr_capi.h:765 */ - public static final int EVRSkeletalMotionRange_VRSkeletalMotionRange_WithController = 0; - /** native declaration : headers\openvr_capi.h:766 */ - public static final int EVRSkeletalMotionRange_VRSkeletalMotionRange_WithoutController = 1; - }; - /** - * native declaration : headers\openvr_capi.h:857
                  - * enum values - */ - public static interface EVRInitError { - /** native declaration : headers\openvr_capi.h:769 */ - public static final int EVRInitError_VRInitError_None = 0; - /** native declaration : headers\openvr_capi.h:770 */ - public static final int EVRInitError_VRInitError_Unknown = 1; - /** native declaration : headers\openvr_capi.h:771 */ - public static final int EVRInitError_VRInitError_Init_InstallationNotFound = 100; - /** native declaration : headers\openvr_capi.h:772 */ - public static final int EVRInitError_VRInitError_Init_InstallationCorrupt = 101; - /** native declaration : headers\openvr_capi.h:773 */ - public static final int EVRInitError_VRInitError_Init_VRClientDLLNotFound = 102; - /** native declaration : headers\openvr_capi.h:774 */ - public static final int EVRInitError_VRInitError_Init_FileNotFound = 103; - /** native declaration : headers\openvr_capi.h:775 */ - public static final int EVRInitError_VRInitError_Init_FactoryNotFound = 104; - /** native declaration : headers\openvr_capi.h:776 */ - public static final int EVRInitError_VRInitError_Init_InterfaceNotFound = 105; - /** native declaration : headers\openvr_capi.h:777 */ - public static final int EVRInitError_VRInitError_Init_InvalidInterface = 106; - /** native declaration : headers\openvr_capi.h:778 */ - public static final int EVRInitError_VRInitError_Init_UserConfigDirectoryInvalid = 107; - /** native declaration : headers\openvr_capi.h:779 */ - public static final int EVRInitError_VRInitError_Init_HmdNotFound = 108; - /** native declaration : headers\openvr_capi.h:780 */ - public static final int EVRInitError_VRInitError_Init_NotInitialized = 109; - /** native declaration : headers\openvr_capi.h:781 */ - public static final int EVRInitError_VRInitError_Init_PathRegistryNotFound = 110; - /** native declaration : headers\openvr_capi.h:782 */ - public static final int EVRInitError_VRInitError_Init_NoConfigPath = 111; - /** native declaration : headers\openvr_capi.h:783 */ - public static final int EVRInitError_VRInitError_Init_NoLogPath = 112; - /** native declaration : headers\openvr_capi.h:784 */ - public static final int EVRInitError_VRInitError_Init_PathRegistryNotWritable = 113; - /** native declaration : headers\openvr_capi.h:785 */ - public static final int EVRInitError_VRInitError_Init_AppInfoInitFailed = 114; - /** native declaration : headers\openvr_capi.h:786 */ - public static final int EVRInitError_VRInitError_Init_Retry = 115; - /** native declaration : headers\openvr_capi.h:787 */ - public static final int EVRInitError_VRInitError_Init_InitCanceledByUser = 116; - /** native declaration : headers\openvr_capi.h:788 */ - public static final int EVRInitError_VRInitError_Init_AnotherAppLaunching = 117; - /** native declaration : headers\openvr_capi.h:789 */ - public static final int EVRInitError_VRInitError_Init_SettingsInitFailed = 118; - /** native declaration : headers\openvr_capi.h:790 */ - public static final int EVRInitError_VRInitError_Init_ShuttingDown = 119; - /** native declaration : headers\openvr_capi.h:791 */ - public static final int EVRInitError_VRInitError_Init_TooManyObjects = 120; - /** native declaration : headers\openvr_capi.h:792 */ - public static final int EVRInitError_VRInitError_Init_NoServerForBackgroundApp = 121; - /** native declaration : headers\openvr_capi.h:793 */ - public static final int EVRInitError_VRInitError_Init_NotSupportedWithCompositor = 122; - /** native declaration : headers\openvr_capi.h:794 */ - public static final int EVRInitError_VRInitError_Init_NotAvailableToUtilityApps = 123; - /** native declaration : headers\openvr_capi.h:795 */ - public static final int EVRInitError_VRInitError_Init_Internal = 124; - /** native declaration : headers\openvr_capi.h:796 */ - public static final int EVRInitError_VRInitError_Init_HmdDriverIdIsNone = 125; - /** native declaration : headers\openvr_capi.h:797 */ - public static final int EVRInitError_VRInitError_Init_HmdNotFoundPresenceFailed = 126; - /** native declaration : headers\openvr_capi.h:798 */ - public static final int EVRInitError_VRInitError_Init_VRMonitorNotFound = 127; - /** native declaration : headers\openvr_capi.h:799 */ - public static final int EVRInitError_VRInitError_Init_VRMonitorStartupFailed = 128; - /** native declaration : headers\openvr_capi.h:800 */ - public static final int EVRInitError_VRInitError_Init_LowPowerWatchdogNotSupported = 129; - /** native declaration : headers\openvr_capi.h:801 */ - public static final int EVRInitError_VRInitError_Init_InvalidApplicationType = 130; - /** native declaration : headers\openvr_capi.h:802 */ - public static final int EVRInitError_VRInitError_Init_NotAvailableToWatchdogApps = 131; - /** native declaration : headers\openvr_capi.h:803 */ - public static final int EVRInitError_VRInitError_Init_WatchdogDisabledInSettings = 132; - /** native declaration : headers\openvr_capi.h:804 */ - public static final int EVRInitError_VRInitError_Init_VRDashboardNotFound = 133; - /** native declaration : headers\openvr_capi.h:805 */ - public static final int EVRInitError_VRInitError_Init_VRDashboardStartupFailed = 134; - /** native declaration : headers\openvr_capi.h:806 */ - public static final int EVRInitError_VRInitError_Init_VRHomeNotFound = 135; - /** native declaration : headers\openvr_capi.h:807 */ - public static final int EVRInitError_VRInitError_Init_VRHomeStartupFailed = 136; - /** native declaration : headers\openvr_capi.h:808 */ - public static final int EVRInitError_VRInitError_Init_RebootingBusy = 137; - /** native declaration : headers\openvr_capi.h:809 */ - public static final int EVRInitError_VRInitError_Init_FirmwareUpdateBusy = 138; - /** native declaration : headers\openvr_capi.h:810 */ - public static final int EVRInitError_VRInitError_Init_FirmwareRecoveryBusy = 139; - /** native declaration : headers\openvr_capi.h:811 */ - public static final int EVRInitError_VRInitError_Init_USBServiceBusy = 140; - /** native declaration : headers\openvr_capi.h:812 */ - public static final int EVRInitError_VRInitError_Init_VRWebHelperStartupFailed = 141; - /** native declaration : headers\openvr_capi.h:813 */ - public static final int EVRInitError_VRInitError_Init_TrackerManagerInitFailed = 142; - /** native declaration : headers\openvr_capi.h:814 */ - public static final int EVRInitError_VRInitError_Driver_Failed = 200; - /** native declaration : headers\openvr_capi.h:815 */ - public static final int EVRInitError_VRInitError_Driver_Unknown = 201; - /** native declaration : headers\openvr_capi.h:816 */ - public static final int EVRInitError_VRInitError_Driver_HmdUnknown = 202; - /** native declaration : headers\openvr_capi.h:817 */ - public static final int EVRInitError_VRInitError_Driver_NotLoaded = 203; - /** native declaration : headers\openvr_capi.h:818 */ - public static final int EVRInitError_VRInitError_Driver_RuntimeOutOfDate = 204; - /** native declaration : headers\openvr_capi.h:819 */ - public static final int EVRInitError_VRInitError_Driver_HmdInUse = 205; - /** native declaration : headers\openvr_capi.h:820 */ - public static final int EVRInitError_VRInitError_Driver_NotCalibrated = 206; - /** native declaration : headers\openvr_capi.h:821 */ - public static final int EVRInitError_VRInitError_Driver_CalibrationInvalid = 207; - /** native declaration : headers\openvr_capi.h:822 */ - public static final int EVRInitError_VRInitError_Driver_HmdDisplayNotFound = 208; - /** native declaration : headers\openvr_capi.h:823 */ - public static final int EVRInitError_VRInitError_Driver_TrackedDeviceInterfaceUnknown = 209; - /** native declaration : headers\openvr_capi.h:824 */ - public static final int EVRInitError_VRInitError_Driver_HmdDriverIdOutOfBounds = 211; - /** native declaration : headers\openvr_capi.h:825 */ - public static final int EVRInitError_VRInitError_Driver_HmdDisplayMirrored = 212; - /** native declaration : headers\openvr_capi.h:826 */ - public static final int EVRInitError_VRInitError_IPC_ServerInitFailed = 300; - /** native declaration : headers\openvr_capi.h:827 */ - public static final int EVRInitError_VRInitError_IPC_ConnectFailed = 301; - /** native declaration : headers\openvr_capi.h:828 */ - public static final int EVRInitError_VRInitError_IPC_SharedStateInitFailed = 302; - /** native declaration : headers\openvr_capi.h:829 */ - public static final int EVRInitError_VRInitError_IPC_CompositorInitFailed = 303; - /** native declaration : headers\openvr_capi.h:830 */ - public static final int EVRInitError_VRInitError_IPC_MutexInitFailed = 304; - /** native declaration : headers\openvr_capi.h:831 */ - public static final int EVRInitError_VRInitError_IPC_Failed = 305; - /** native declaration : headers\openvr_capi.h:832 */ - public static final int EVRInitError_VRInitError_IPC_CompositorConnectFailed = 306; - /** native declaration : headers\openvr_capi.h:833 */ - public static final int EVRInitError_VRInitError_IPC_CompositorInvalidConnectResponse = 307; - /** native declaration : headers\openvr_capi.h:834 */ - public static final int EVRInitError_VRInitError_IPC_ConnectFailedAfterMultipleAttempts = 308; - /** native declaration : headers\openvr_capi.h:835 */ - public static final int EVRInitError_VRInitError_Compositor_Failed = 400; - /** native declaration : headers\openvr_capi.h:836 */ - public static final int EVRInitError_VRInitError_Compositor_D3D11HardwareRequired = 401; - /** native declaration : headers\openvr_capi.h:837 */ - public static final int EVRInitError_VRInitError_Compositor_FirmwareRequiresUpdate = 402; - /** native declaration : headers\openvr_capi.h:838 */ - public static final int EVRInitError_VRInitError_Compositor_OverlayInitFailed = 403; - /** native declaration : headers\openvr_capi.h:839 */ - public static final int EVRInitError_VRInitError_Compositor_ScreenshotsInitFailed = 404; - /** native declaration : headers\openvr_capi.h:840 */ - public static final int EVRInitError_VRInitError_Compositor_UnableToCreateDevice = 405; - /** native declaration : headers\openvr_capi.h:841 */ - public static final int EVRInitError_VRInitError_VendorSpecific_UnableToConnectToOculusRuntime = 1000; - /** native declaration : headers\openvr_capi.h:842 */ - public static final int EVRInitError_VRInitError_VendorSpecific_WindowsNotInDevMode = 1001; - /** native declaration : headers\openvr_capi.h:843 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_CantOpenDevice = 1101; - /** native declaration : headers\openvr_capi.h:844 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart = 1102; - /** native declaration : headers\openvr_capi.h:845 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_NoStoredConfig = 1103; - /** native declaration : headers\openvr_capi.h:846 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigTooBig = 1104; - /** native declaration : headers\openvr_capi.h:847 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigTooSmall = 1105; - /** native declaration : headers\openvr_capi.h:848 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToInitZLib = 1106; - /** native declaration : headers\openvr_capi.h:849 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion = 1107; - /** native declaration : headers\openvr_capi.h:850 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart = 1108; - /** native declaration : headers\openvr_capi.h:851 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart = 1109; - /** native declaration : headers\openvr_capi.h:852 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext = 1110; - /** native declaration : headers\openvr_capi.h:853 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UserDataAddressRange = 1111; - /** native declaration : headers\openvr_capi.h:854 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_UserDataError = 1112; - /** native declaration : headers\openvr_capi.h:855 */ - public static final int EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck = 1113; - /** native declaration : headers\openvr_capi.h:856 */ - public static final int EVRInitError_VRInitError_Steam_SteamInstallationNotFound = 2000; - }; - /** - * native declaration : headers\openvr_capi.h:865
                  - * enum values - */ - public static interface EVRScreenshotType { - /** native declaration : headers\openvr_capi.h:859 */ - public static final int EVRScreenshotType_VRScreenshotType_None = 0; - /** native declaration : headers\openvr_capi.h:860 */ - public static final int EVRScreenshotType_VRScreenshotType_Mono = 1; - /** native declaration : headers\openvr_capi.h:861 */ - public static final int EVRScreenshotType_VRScreenshotType_Stereo = 2; - /** native declaration : headers\openvr_capi.h:862 */ - public static final int EVRScreenshotType_VRScreenshotType_Cubemap = 3; - /** native declaration : headers\openvr_capi.h:863 */ - public static final int EVRScreenshotType_VRScreenshotType_MonoPanorama = 4; - /** native declaration : headers\openvr_capi.h:864 */ - public static final int EVRScreenshotType_VRScreenshotType_StereoPanorama = 5; - }; - /** - * native declaration : headers\openvr_capi.h:869
                  - * enum values - */ - public static interface EVRScreenshotPropertyFilenames { - /** native declaration : headers\openvr_capi.h:867 */ - public static final int EVRScreenshotPropertyFilenames_VRScreenshotPropertyFilenames_Preview = 0; - /** native declaration : headers\openvr_capi.h:868 */ - public static final int EVRScreenshotPropertyFilenames_VRScreenshotPropertyFilenames_VR = 1; - }; - /** - * native declaration : headers\openvr_capi.h:888
                  - * enum values - */ - public static interface EVRTrackedCameraError { - /** native declaration : headers\openvr_capi.h:871 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_None = 0; - /** native declaration : headers\openvr_capi.h:872 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_OperationFailed = 100; - /** native declaration : headers\openvr_capi.h:873 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidHandle = 101; - /** native declaration : headers\openvr_capi.h:874 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidFrameHeaderVersion = 102; - /** native declaration : headers\openvr_capi.h:875 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_OutOfHandles = 103; - /** native declaration : headers\openvr_capi.h:876 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_IPCFailure = 104; - /** native declaration : headers\openvr_capi.h:877 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_NotSupportedForThisDevice = 105; - /** native declaration : headers\openvr_capi.h:878 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_SharedMemoryFailure = 106; - /** native declaration : headers\openvr_capi.h:879 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_FrameBufferingFailure = 107; - /** native declaration : headers\openvr_capi.h:880 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_StreamSetupFailure = 108; - /** native declaration : headers\openvr_capi.h:881 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidGLTextureId = 109; - /** native declaration : headers\openvr_capi.h:882 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidSharedTextureHandle = 110; - /** native declaration : headers\openvr_capi.h:883 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_FailedToGetGLTextureId = 111; - /** native declaration : headers\openvr_capi.h:884 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_SharedTextureFailure = 112; - /** native declaration : headers\openvr_capi.h:885 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_NoFrameAvailable = 113; - /** native declaration : headers\openvr_capi.h:886 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidArgument = 114; - /** native declaration : headers\openvr_capi.h:887 */ - public static final int EVRTrackedCameraError_VRTrackedCameraError_InvalidFrameBufferSize = 115; - }; - /** - * native declaration : headers\openvr_capi.h:894
                  - * enum values - */ - public static interface EVRTrackedCameraFrameLayout { - /** native declaration : headers\openvr_capi.h:890 */ - public static final int EVRTrackedCameraFrameLayout_Mono = 1; - /** native declaration : headers\openvr_capi.h:891 */ - public static final int EVRTrackedCameraFrameLayout_Stereo = 2; - /** native declaration : headers\openvr_capi.h:892 */ - public static final int EVRTrackedCameraFrameLayout_VerticalLayout = 16; - /** native declaration : headers\openvr_capi.h:893 */ - public static final int EVRTrackedCameraFrameLayout_HorizontalLayout = 32; - }; - /** - * native declaration : headers\openvr_capi.h:900
                  - * enum values - */ - public static interface EVRTrackedCameraFrameType { - /** native declaration : headers\openvr_capi.h:896 */ - public static final int EVRTrackedCameraFrameType_VRTrackedCameraFrameType_Distorted = 0; - /** native declaration : headers\openvr_capi.h:897 */ - public static final int EVRTrackedCameraFrameType_VRTrackedCameraFrameType_Undistorted = 1; - /** native declaration : headers\openvr_capi.h:898 */ - public static final int EVRTrackedCameraFrameType_VRTrackedCameraFrameType_MaximumUndistorted = 2; - /** native declaration : headers\openvr_capi.h:899 */ - public static final int EVRTrackedCameraFrameType_MAX_CAMERA_FRAME_TYPES = 3; - }; - /** - * native declaration : headers\openvr_capi.h:905
                  - * enum values - */ - public static interface EVSync { - /** native declaration : headers\openvr_capi.h:902 */ - public static final int EVSync_VSync_None = 0; - /** native declaration : headers\openvr_capi.h:903 */ - public static final int EVSync_VSync_WaitRender = 1; - /** native declaration : headers\openvr_capi.h:904 */ - public static final int EVSync_VSync_NoWaitRender = 2; - }; - /** - * native declaration : headers\openvr_capi.h:909
                  - * enum values - */ - public static interface EVRMuraCorrectionMode { - /** native declaration : headers\openvr_capi.h:907 */ - public static final int EVRMuraCorrectionMode_Default = 0; - /** native declaration : headers\openvr_capi.h:908 */ - public static final int EVRMuraCorrectionMode_NoCorrection = 1; - }; - /** - * native declaration : headers\openvr_capi.h:917
                  - * enum values - */ - public static interface Imu_OffScaleFlags { - /** native declaration : headers\openvr_capi.h:911 */ - public static final int Imu_OffScaleFlags_OffScale_AccelX = 1; - /** native declaration : headers\openvr_capi.h:912 */ - public static final int Imu_OffScaleFlags_OffScale_AccelY = 2; - /** native declaration : headers\openvr_capi.h:913 */ - public static final int Imu_OffScaleFlags_OffScale_AccelZ = 4; - /** native declaration : headers\openvr_capi.h:914 */ - public static final int Imu_OffScaleFlags_OffScale_GyroX = 8; - /** native declaration : headers\openvr_capi.h:915 */ - public static final int Imu_OffScaleFlags_OffScale_GyroY = 16; - /** native declaration : headers\openvr_capi.h:916 */ - public static final int Imu_OffScaleFlags_OffScale_GyroZ = 32; - }; - /** - * native declaration : headers\openvr_capi.h:940
                  - * enum values - */ - public static interface EVRApplicationError { - /** native declaration : headers\openvr_capi.h:919 */ - public static final int EVRApplicationError_VRApplicationError_None = 0; - /** native declaration : headers\openvr_capi.h:920 */ - public static final int EVRApplicationError_VRApplicationError_AppKeyAlreadyExists = 100; - /** native declaration : headers\openvr_capi.h:921 */ - public static final int EVRApplicationError_VRApplicationError_NoManifest = 101; - /** native declaration : headers\openvr_capi.h:922 */ - public static final int EVRApplicationError_VRApplicationError_NoApplication = 102; - /** native declaration : headers\openvr_capi.h:923 */ - public static final int EVRApplicationError_VRApplicationError_InvalidIndex = 103; - /** native declaration : headers\openvr_capi.h:924 */ - public static final int EVRApplicationError_VRApplicationError_UnknownApplication = 104; - /** native declaration : headers\openvr_capi.h:925 */ - public static final int EVRApplicationError_VRApplicationError_IPCFailed = 105; - /** native declaration : headers\openvr_capi.h:926 */ - public static final int EVRApplicationError_VRApplicationError_ApplicationAlreadyRunning = 106; - /** native declaration : headers\openvr_capi.h:927 */ - public static final int EVRApplicationError_VRApplicationError_InvalidManifest = 107; - /** native declaration : headers\openvr_capi.h:928 */ - public static final int EVRApplicationError_VRApplicationError_InvalidApplication = 108; - /** native declaration : headers\openvr_capi.h:929 */ - public static final int EVRApplicationError_VRApplicationError_LaunchFailed = 109; - /** native declaration : headers\openvr_capi.h:930 */ - public static final int EVRApplicationError_VRApplicationError_ApplicationAlreadyStarting = 110; - /** native declaration : headers\openvr_capi.h:931 */ - public static final int EVRApplicationError_VRApplicationError_LaunchInProgress = 111; - /** native declaration : headers\openvr_capi.h:932 */ - public static final int EVRApplicationError_VRApplicationError_OldApplicationQuitting = 112; - /** native declaration : headers\openvr_capi.h:933 */ - public static final int EVRApplicationError_VRApplicationError_TransitionAborted = 113; - /** native declaration : headers\openvr_capi.h:934 */ - public static final int EVRApplicationError_VRApplicationError_IsTemplate = 114; - /** native declaration : headers\openvr_capi.h:935 */ - public static final int EVRApplicationError_VRApplicationError_SteamVRIsExiting = 115; - /** native declaration : headers\openvr_capi.h:936 */ - public static final int EVRApplicationError_VRApplicationError_BufferTooSmall = 200; - /** native declaration : headers\openvr_capi.h:937 */ - public static final int EVRApplicationError_VRApplicationError_PropertyNotSet = 201; - /** native declaration : headers\openvr_capi.h:938 */ - public static final int EVRApplicationError_VRApplicationError_UnknownProperty = 202; - /** native declaration : headers\openvr_capi.h:939 */ - public static final int EVRApplicationError_VRApplicationError_InvalidParameter = 203; - }; - /** - * native declaration : headers\openvr_capi.h:959
                  - * enum values - */ - public static interface EVRApplicationProperty { - /** native declaration : headers\openvr_capi.h:942 */ - public static final int EVRApplicationProperty_VRApplicationProperty_Name_String = 0; - /** native declaration : headers\openvr_capi.h:943 */ - public static final int EVRApplicationProperty_VRApplicationProperty_LaunchType_String = 11; - /** native declaration : headers\openvr_capi.h:944 */ - public static final int EVRApplicationProperty_VRApplicationProperty_WorkingDirectory_String = 12; - /** native declaration : headers\openvr_capi.h:945 */ - public static final int EVRApplicationProperty_VRApplicationProperty_BinaryPath_String = 13; - /** native declaration : headers\openvr_capi.h:946 */ - public static final int EVRApplicationProperty_VRApplicationProperty_Arguments_String = 14; - /** native declaration : headers\openvr_capi.h:947 */ - public static final int EVRApplicationProperty_VRApplicationProperty_URL_String = 15; - /** native declaration : headers\openvr_capi.h:948 */ - public static final int EVRApplicationProperty_VRApplicationProperty_Description_String = 50; - /** native declaration : headers\openvr_capi.h:949 */ - public static final int EVRApplicationProperty_VRApplicationProperty_NewsURL_String = 51; - /** native declaration : headers\openvr_capi.h:950 */ - public static final int EVRApplicationProperty_VRApplicationProperty_ImagePath_String = 52; - /** native declaration : headers\openvr_capi.h:951 */ - public static final int EVRApplicationProperty_VRApplicationProperty_Source_String = 53; - /** native declaration : headers\openvr_capi.h:952 */ - public static final int EVRApplicationProperty_VRApplicationProperty_ActionManifestURL_String = 54; - /** native declaration : headers\openvr_capi.h:953 */ - public static final int EVRApplicationProperty_VRApplicationProperty_IsDashboardOverlay_Bool = 60; - /** native declaration : headers\openvr_capi.h:954 */ - public static final int EVRApplicationProperty_VRApplicationProperty_IsTemplate_Bool = 61; - /** native declaration : headers\openvr_capi.h:955 */ - public static final int EVRApplicationProperty_VRApplicationProperty_IsInstanced_Bool = 62; - /** native declaration : headers\openvr_capi.h:956 */ - public static final int EVRApplicationProperty_VRApplicationProperty_IsInternal_Bool = 63; - /** native declaration : headers\openvr_capi.h:957 */ - public static final int EVRApplicationProperty_VRApplicationProperty_WantsCompositorPauseInStandby_Bool = 64; - /** native declaration : headers\openvr_capi.h:958 */ - public static final int EVRApplicationProperty_VRApplicationProperty_LastLaunchTime_Uint64 = 70; - }; - /** - * native declaration : headers\openvr_capi.h:965
                  - * enum values - */ - public static interface EVRApplicationTransitionState { - /** native declaration : headers\openvr_capi.h:961 */ - public static final int EVRApplicationTransitionState_VRApplicationTransition_None = 0; - /** native declaration : headers\openvr_capi.h:962 */ - public static final int EVRApplicationTransitionState_VRApplicationTransition_OldAppQuitSent = 10; - /** native declaration : headers\openvr_capi.h:963 */ - public static final int EVRApplicationTransitionState_VRApplicationTransition_WaitingForExternalLaunch = 11; - /** native declaration : headers\openvr_capi.h:964 */ - public static final int EVRApplicationTransitionState_VRApplicationTransition_NewAppLaunched = 20; - }; - /** - * native declaration : headers\openvr_capi.h:977
                  - * enum values - */ - public static interface ChaperoneCalibrationState { - /** native declaration : headers\openvr_capi.h:967 */ - public static final int ChaperoneCalibrationState_OK = 1; - /** native declaration : headers\openvr_capi.h:968 */ - public static final int ChaperoneCalibrationState_Warning = 100; - /** native declaration : headers\openvr_capi.h:969 */ - public static final int ChaperoneCalibrationState_Warning_BaseStationMayHaveMoved = 101; - /** native declaration : headers\openvr_capi.h:970 */ - public static final int ChaperoneCalibrationState_Warning_BaseStationRemoved = 102; - /** native declaration : headers\openvr_capi.h:971 */ - public static final int ChaperoneCalibrationState_Warning_SeatedBoundsInvalid = 103; - /** native declaration : headers\openvr_capi.h:972 */ - public static final int ChaperoneCalibrationState_Error = 200; - /** native declaration : headers\openvr_capi.h:973 */ - public static final int ChaperoneCalibrationState_Error_BaseStationUninitialized = 201; - /** native declaration : headers\openvr_capi.h:974 */ - public static final int ChaperoneCalibrationState_Error_BaseStationConflict = 202; - /** native declaration : headers\openvr_capi.h:975 */ - public static final int ChaperoneCalibrationState_Error_PlayAreaInvalid = 203; - /** native declaration : headers\openvr_capi.h:976 */ - public static final int ChaperoneCalibrationState_Error_CollisionBoundsInvalid = 204; - }; - /** - * native declaration : headers\openvr_capi.h:981
                  - * enum values - */ - public static interface EChaperoneConfigFile { - /** native declaration : headers\openvr_capi.h:979 */ - public static final int EChaperoneConfigFile_Live = 1; - /** native declaration : headers\openvr_capi.h:980 */ - public static final int EChaperoneConfigFile_Temp = 2; - }; - /** - * native declaration : headers\openvr_capi.h:984
                  - * enum values - */ - public static interface EChaperoneImportFlags { - /** native declaration : headers\openvr_capi.h:983 */ - public static final int EChaperoneImportFlags_EChaperoneImport_BoundsOnly = 1; - }; - /** - * native declaration : headers\openvr_capi.h:998
                  - * enum values - */ - public static interface EVRCompositorError { - /** native declaration : headers\openvr_capi.h:986 */ - public static final int EVRCompositorError_VRCompositorError_None = 0; - /** native declaration : headers\openvr_capi.h:987 */ - public static final int EVRCompositorError_VRCompositorError_RequestFailed = 1; - /** native declaration : headers\openvr_capi.h:988 */ - public static final int EVRCompositorError_VRCompositorError_IncompatibleVersion = 100; - /** native declaration : headers\openvr_capi.h:989 */ - public static final int EVRCompositorError_VRCompositorError_DoNotHaveFocus = 101; - /** native declaration : headers\openvr_capi.h:990 */ - public static final int EVRCompositorError_VRCompositorError_InvalidTexture = 102; - /** native declaration : headers\openvr_capi.h:991 */ - public static final int EVRCompositorError_VRCompositorError_IsNotSceneApplication = 103; - /** native declaration : headers\openvr_capi.h:992 */ - public static final int EVRCompositorError_VRCompositorError_TextureIsOnWrongDevice = 104; - /** native declaration : headers\openvr_capi.h:993 */ - public static final int EVRCompositorError_VRCompositorError_TextureUsesUnsupportedFormat = 105; - /** native declaration : headers\openvr_capi.h:994 */ - public static final int EVRCompositorError_VRCompositorError_SharedTexturesNotSupported = 106; - /** native declaration : headers\openvr_capi.h:995 */ - public static final int EVRCompositorError_VRCompositorError_IndexOutOfRange = 107; - /** native declaration : headers\openvr_capi.h:996 */ - public static final int EVRCompositorError_VRCompositorError_AlreadySubmitted = 108; - /** native declaration : headers\openvr_capi.h:997 */ - public static final int EVRCompositorError_VRCompositorError_InvalidBounds = 109; - }; - /** - * native declaration : headers\openvr_capi.h:1003
                  - * enum values - */ - public static interface EVRCompositorTimingMode { - /** native declaration : headers\openvr_capi.h:1000 */ - public static final int EVRCompositorTimingMode_VRCompositorTimingMode_Implicit = 0; - /** native declaration : headers\openvr_capi.h:1001 */ - public static final int EVRCompositorTimingMode_VRCompositorTimingMode_Explicit_RuntimePerformsPostPresentHandoff = 1; - /** native declaration : headers\openvr_capi.h:1002 */ - public static final int EVRCompositorTimingMode_VRCompositorTimingMode_Explicit_ApplicationPerformsPostPresentHandoff = 2; - }; - /** - * native declaration : headers\openvr_capi.h:1008
                  - * enum values - */ - public static interface VROverlayInputMethod { - /** native declaration : headers\openvr_capi.h:1005 */ - public static final int VROverlayInputMethod_None = 0; - /** native declaration : headers\openvr_capi.h:1006 */ - public static final int VROverlayInputMethod_Mouse = 1; - /** native declaration : headers\openvr_capi.h:1007 */ - public static final int VROverlayInputMethod_DualAnalog = 2; - }; - /** - * native declaration : headers\openvr_capi.h:1014
                  - * enum values - */ - public static interface VROverlayTransformType { - /** native declaration : headers\openvr_capi.h:1010 */ - public static final int VROverlayTransformType_VROverlayTransform_Absolute = 0; - /** native declaration : headers\openvr_capi.h:1011 */ - public static final int VROverlayTransformType_VROverlayTransform_TrackedDeviceRelative = 1; - /** native declaration : headers\openvr_capi.h:1012 */ - public static final int VROverlayTransformType_VROverlayTransform_SystemOverlay = 2; - /** native declaration : headers\openvr_capi.h:1013 */ - public static final int VROverlayTransformType_VROverlayTransform_TrackedComponent = 3; - }; - /** - * native declaration : headers\openvr_capi.h:1032
                  - * enum values - */ - public static interface VROverlayFlags { - /** native declaration : headers\openvr_capi.h:1016 */ - public static final int VROverlayFlags_None = 0; - /** native declaration : headers\openvr_capi.h:1017 */ - public static final int VROverlayFlags_Curved = 1; - /** native declaration : headers\openvr_capi.h:1018 */ - public static final int VROverlayFlags_RGSS4X = 2; - /** native declaration : headers\openvr_capi.h:1019 */ - public static final int VROverlayFlags_NoDashboardTab = 3; - /** native declaration : headers\openvr_capi.h:1020 */ - public static final int VROverlayFlags_AcceptsGamepadEvents = 4; - /** native declaration : headers\openvr_capi.h:1021 */ - public static final int VROverlayFlags_ShowGamepadFocus = 5; - /** native declaration : headers\openvr_capi.h:1022 */ - public static final int VROverlayFlags_SendVRScrollEvents = 6; - /** native declaration : headers\openvr_capi.h:1023 */ - public static final int VROverlayFlags_SendVRTouchpadEvents = 7; - /** native declaration : headers\openvr_capi.h:1024 */ - public static final int VROverlayFlags_ShowTouchPadScrollWheel = 8; - /** native declaration : headers\openvr_capi.h:1025 */ - public static final int VROverlayFlags_TransferOwnershipToInternalProcess = 9; - /** native declaration : headers\openvr_capi.h:1026 */ - public static final int VROverlayFlags_SideBySide_Parallel = 10; - /** native declaration : headers\openvr_capi.h:1027 */ - public static final int VROverlayFlags_SideBySide_Crossed = 11; - /** native declaration : headers\openvr_capi.h:1028 */ - public static final int VROverlayFlags_Panorama = 12; - /** native declaration : headers\openvr_capi.h:1029 */ - public static final int VROverlayFlags_StereoPanorama = 13; - /** native declaration : headers\openvr_capi.h:1030 */ - public static final int VROverlayFlags_SortWithNonSceneOverlays = 14; - /** native declaration : headers\openvr_capi.h:1031 */ - public static final int VROverlayFlags_VisibleInDashboard = 15; - }; - /** - * native declaration : headers\openvr_capi.h:1041
                  - * enum values - */ - public static interface VRMessageOverlayResponse { - /** native declaration : headers\openvr_capi.h:1034 */ - public static final int VRMessageOverlayResponse_ButtonPress_0 = 0; - /** native declaration : headers\openvr_capi.h:1035 */ - public static final int VRMessageOverlayResponse_ButtonPress_1 = 1; - /** native declaration : headers\openvr_capi.h:1036 */ - public static final int VRMessageOverlayResponse_ButtonPress_2 = 2; - /** native declaration : headers\openvr_capi.h:1037 */ - public static final int VRMessageOverlayResponse_ButtonPress_3 = 3; - /** native declaration : headers\openvr_capi.h:1038 */ - public static final int VRMessageOverlayResponse_CouldntFindSystemOverlay = 4; - /** native declaration : headers\openvr_capi.h:1039 */ - public static final int VRMessageOverlayResponse_CouldntFindOrCreateClientOverlay = 5; - /** native declaration : headers\openvr_capi.h:1040 */ - public static final int VRMessageOverlayResponse_ApplicationQuit = 6; - }; - /** - * native declaration : headers\openvr_capi.h:1046
                  - * enum values - */ - public static interface EGamepadTextInputMode { - /** native declaration : headers\openvr_capi.h:1043 */ - public static final int EGamepadTextInputMode_k_EGamepadTextInputModeNormal = 0; - /** native declaration : headers\openvr_capi.h:1044 */ - public static final int EGamepadTextInputMode_k_EGamepadTextInputModePassword = 1; - /** native declaration : headers\openvr_capi.h:1045 */ - public static final int EGamepadTextInputMode_k_EGamepadTextInputModeSubmit = 2; - }; - /** - * native declaration : headers\openvr_capi.h:1050
                  - * enum values - */ - public static interface EGamepadTextInputLineMode { - /** native declaration : headers\openvr_capi.h:1048 */ - public static final int EGamepadTextInputLineMode_k_EGamepadTextInputLineModeSingleLine = 0; - /** native declaration : headers\openvr_capi.h:1049 */ - public static final int EGamepadTextInputLineMode_k_EGamepadTextInputLineModeMultipleLines = 1; - }; - /** - * native declaration : headers\openvr_capi.h:1057
                  - * enum values - */ - public static interface EOverlayDirection { - /** native declaration : headers\openvr_capi.h:1052 */ - public static final int EOverlayDirection_OverlayDirection_Up = 0; - /** native declaration : headers\openvr_capi.h:1053 */ - public static final int EOverlayDirection_OverlayDirection_Down = 1; - /** native declaration : headers\openvr_capi.h:1054 */ - public static final int EOverlayDirection_OverlayDirection_Left = 2; - /** native declaration : headers\openvr_capi.h:1055 */ - public static final int EOverlayDirection_OverlayDirection_Right = 3; - /** native declaration : headers\openvr_capi.h:1056 */ - public static final int EOverlayDirection_OverlayDirection_Count = 4; - }; - /** - * native declaration : headers\openvr_capi.h:1061
                  - * enum values - */ - public static interface EVROverlayIntersectionMaskPrimitiveType { - /** native declaration : headers\openvr_capi.h:1059 */ - public static final int EVROverlayIntersectionMaskPrimitiveType_OverlayIntersectionPrimitiveType_Rectangle = 0; - /** native declaration : headers\openvr_capi.h:1060 */ - public static final int EVROverlayIntersectionMaskPrimitiveType_OverlayIntersectionPrimitiveType_Circle = 1; - }; - /** - * native declaration : headers\openvr_capi.h:1076
                  - * enum values - */ - public static interface EVRRenderModelError { - /** native declaration : headers\openvr_capi.h:1063 */ - public static final int EVRRenderModelError_VRRenderModelError_None = 0; - /** native declaration : headers\openvr_capi.h:1064 */ - public static final int EVRRenderModelError_VRRenderModelError_Loading = 100; - /** native declaration : headers\openvr_capi.h:1065 */ - public static final int EVRRenderModelError_VRRenderModelError_NotSupported = 200; - /** native declaration : headers\openvr_capi.h:1066 */ - public static final int EVRRenderModelError_VRRenderModelError_InvalidArg = 300; - /** native declaration : headers\openvr_capi.h:1067 */ - public static final int EVRRenderModelError_VRRenderModelError_InvalidModel = 301; - /** native declaration : headers\openvr_capi.h:1068 */ - public static final int EVRRenderModelError_VRRenderModelError_NoShapes = 302; - /** native declaration : headers\openvr_capi.h:1069 */ - public static final int EVRRenderModelError_VRRenderModelError_MultipleShapes = 303; - /** native declaration : headers\openvr_capi.h:1070 */ - public static final int EVRRenderModelError_VRRenderModelError_TooManyVertices = 304; - /** native declaration : headers\openvr_capi.h:1071 */ - public static final int EVRRenderModelError_VRRenderModelError_MultipleTextures = 305; - /** native declaration : headers\openvr_capi.h:1072 */ - public static final int EVRRenderModelError_VRRenderModelError_BufferTooSmall = 306; - /** native declaration : headers\openvr_capi.h:1073 */ - public static final int EVRRenderModelError_VRRenderModelError_NotEnoughNormals = 307; - /** native declaration : headers\openvr_capi.h:1074 */ - public static final int EVRRenderModelError_VRRenderModelError_NotEnoughTexCoords = 308; - /** native declaration : headers\openvr_capi.h:1075 */ - public static final int EVRRenderModelError_VRRenderModelError_InvalidTexture = 400; - }; - /** - * native declaration : headers\openvr_capi.h:1083
                  - * enum values - */ - public static interface EVRComponentProperty { - /** native declaration : headers\openvr_capi.h:1078 */ - public static final int EVRComponentProperty_VRComponentProperty_IsStatic = 1; - /** native declaration : headers\openvr_capi.h:1079 */ - public static final int EVRComponentProperty_VRComponentProperty_IsVisible = 2; - /** native declaration : headers\openvr_capi.h:1080 */ - public static final int EVRComponentProperty_VRComponentProperty_IsTouched = 4; - /** native declaration : headers\openvr_capi.h:1081 */ - public static final int EVRComponentProperty_VRComponentProperty_IsPressed = 8; - /** native declaration : headers\openvr_capi.h:1082 */ - public static final int EVRComponentProperty_VRComponentProperty_IsScrolled = 16; - }; - /** - * native declaration : headers\openvr_capi.h:1088
                  - * enum values - */ - public static interface EVRNotificationType { - /** native declaration : headers\openvr_capi.h:1085 */ - public static final int EVRNotificationType_Transient = 0; - /** native declaration : headers\openvr_capi.h:1086 */ - public static final int EVRNotificationType_Persistent = 1; - /** native declaration : headers\openvr_capi.h:1087 */ - public static final int EVRNotificationType_Transient_SystemWithUserValue = 2; - }; - /** - * native declaration : headers\openvr_capi.h:1095
                  - * enum values - */ - public static interface EVRNotificationStyle { - /** native declaration : headers\openvr_capi.h:1090 */ - public static final int EVRNotificationStyle_None = 0; - /** native declaration : headers\openvr_capi.h:1091 */ - public static final int EVRNotificationStyle_Application = 100; - /** native declaration : headers\openvr_capi.h:1092 */ - public static final int EVRNotificationStyle_Contact_Disabled = 200; - /** native declaration : headers\openvr_capi.h:1093 */ - public static final int EVRNotificationStyle_Contact_Enabled = 201; - /** native declaration : headers\openvr_capi.h:1094 */ - public static final int EVRNotificationStyle_Contact_Active = 202; - }; - /** - * native declaration : headers\openvr_capi.h:1103
                  - * enum values - */ - public static interface EVRSettingsError { - /** native declaration : headers\openvr_capi.h:1097 */ - public static final int EVRSettingsError_VRSettingsError_None = 0; - /** native declaration : headers\openvr_capi.h:1098 */ - public static final int EVRSettingsError_VRSettingsError_IPCFailed = 1; - /** native declaration : headers\openvr_capi.h:1099 */ - public static final int EVRSettingsError_VRSettingsError_WriteFailed = 2; - /** native declaration : headers\openvr_capi.h:1100 */ - public static final int EVRSettingsError_VRSettingsError_ReadFailed = 3; - /** native declaration : headers\openvr_capi.h:1101 */ - public static final int EVRSettingsError_VRSettingsError_JsonParseFailed = 4; - /** native declaration : headers\openvr_capi.h:1102 */ - public static final int EVRSettingsError_VRSettingsError_UnsetSettingHasNoDefault = 5; - }; - /** - * native declaration : headers\openvr_capi.h:1111
                  - * enum values - */ - public static interface EVRScreenshotError { - /** native declaration : headers\openvr_capi.h:1105 */ - public static final int EVRScreenshotError_VRScreenshotError_None = 0; - /** native declaration : headers\openvr_capi.h:1106 */ - public static final int EVRScreenshotError_VRScreenshotError_RequestFailed = 1; - /** native declaration : headers\openvr_capi.h:1107 */ - public static final int EVRScreenshotError_VRScreenshotError_IncompatibleVersion = 100; - /** native declaration : headers\openvr_capi.h:1108 */ - public static final int EVRScreenshotError_VRScreenshotError_NotFound = 101; - /** native declaration : headers\openvr_capi.h:1109 */ - public static final int EVRScreenshotError_VRScreenshotError_BufferTooSmall = 102; - /** native declaration : headers\openvr_capi.h:1110 */ - public static final int EVRScreenshotError_VRScreenshotError_ScreenshotAlreadyInProgress = 108; - }; - /** - * native declaration : headers\openvr_capi.h:1116
                  - * enum values - */ - public static interface EVRSkeletalTransformSpace { - /** native declaration : headers\openvr_capi.h:1113 */ - public static final int EVRSkeletalTransformSpace_VRSkeletalTransformSpace_Model = 0; - /** native declaration : headers\openvr_capi.h:1114 */ - public static final int EVRSkeletalTransformSpace_VRSkeletalTransformSpace_Parent = 1; - /** native declaration : headers\openvr_capi.h:1115 */ - public static final int EVRSkeletalTransformSpace_VRSkeletalTransformSpace_Additive = 2; - }; - /** - * native declaration : headers\openvr_capi.h:1120
                  - * enum values - */ - public static interface EVRInputFilterCancelType { - /** native declaration : headers\openvr_capi.h:1118 */ - public static final int EVRInputFilterCancelType_VRInputFilterCancel_Timers = 0; - /** native declaration : headers\openvr_capi.h:1119 */ - public static final int EVRInputFilterCancelType_VRInputFilterCancel_Momentum = 1; - }; - /** - * native declaration : headers\openvr_capi.h:1129
                  - * enum values - */ - public static interface EIOBufferError { - /** native declaration : headers\openvr_capi.h:1122 */ - public static final int EIOBufferError_IOBuffer_Success = 0; - /** native declaration : headers\openvr_capi.h:1123 */ - public static final int EIOBufferError_IOBuffer_OperationFailed = 100; - /** native declaration : headers\openvr_capi.h:1124 */ - public static final int EIOBufferError_IOBuffer_InvalidHandle = 101; - /** native declaration : headers\openvr_capi.h:1125 */ - public static final int EIOBufferError_IOBuffer_InvalidArgument = 102; - /** native declaration : headers\openvr_capi.h:1126 */ - public static final int EIOBufferError_IOBuffer_PathExists = 103; - /** native declaration : headers\openvr_capi.h:1127 */ - public static final int EIOBufferError_IOBuffer_PathDoesNotExist = 104; - /** native declaration : headers\openvr_capi.h:1128 */ - public static final int EIOBufferError_IOBuffer_Permission = 105; - }; - /** - * native declaration : headers\openvr_capi.h:1134
                  - * enum values - */ - public static interface EIOBufferMode { - /** native declaration : headers\openvr_capi.h:1131 */ - public static final int EIOBufferMode_IOBufferMode_Read = 1; - /** native declaration : headers\openvr_capi.h:1132 */ - public static final int EIOBufferMode_IOBufferMode_Write = 2; - /** native declaration : headers\openvr_capi.h:1133 */ - public static final int EIOBufferMode_IOBufferMode_Create = 512; - }; - /** OpenVR Constants */ - public static final long k_nDriverNone = 4294967295L; - public static final int k_unMaxDriverDebugResponseSize = 32768; - public static final int k_unTrackedDeviceIndex_Hmd = 0; - public static final int k_unMaxTrackedDeviceCount = 64; - public static final int k_unTrackedDeviceIndexOther = -2; - public static final long k_unTrackedDeviceIndexInvalid = 4294967295L; - public static final long k_ulInvalidPropertyContainer = 0; - public static final int k_unInvalidPropertyTag = 0; - public static final long k_ulInvalidDriverHandle = 0; - public static final int k_unFloatPropertyTag = 1; - public static final int k_unInt32PropertyTag = 2; - public static final int k_unUint64PropertyTag = 3; - public static final int k_unBoolPropertyTag = 4; - public static final int k_unStringPropertyTag = 5; - public static final int k_unHmdMatrix34PropertyTag = 20; - public static final int k_unHmdMatrix44PropertyTag = 21; - public static final int k_unHmdVector3PropertyTag = 22; - public static final int k_unHmdVector4PropertyTag = 23; - public static final int k_unHiddenAreaPropertyTag = 30; - public static final int k_unPathHandleInfoTag = 31; - public static final int k_unActionPropertyTag = 32; - public static final int k_unInputValuePropertyTag = 33; - public static final int k_unWildcardPropertyTag = 34; - public static final int k_unHapticVibrationPropertyTag = 35; - public static final int k_unSkeletonPropertyTag = 36; - public static final int k_unSpatialAnchorPosePropertyTag = 40; - public static final int k_unOpenVRInternalReserved_Start = 1000; - public static final int k_unOpenVRInternalReserved_End = 10000; - public static final int k_unMaxPropertyStringSize = 32768; - public static final long k_ulInvalidActionHandle = 0; - public static final long k_ulInvalidActionSetHandle = 0; - public static final long k_ulInvalidInputValueHandle = 0; - public static final int k_unControllerStateAxisCount = 5; - public static final long k_ulOverlayHandleInvalid = 0; - public static final int k_unScreenshotHandleInvalid = 0; - public static final int k_unMaxApplicationKeyLength = 128; - public static final int k_unVROverlayMaxKeyLength = 128; - public static final int k_unVROverlayMaxNameLength = 128; - public static final int k_unMaxOverlayCount = 64; - public static final int k_unMaxOverlayIntersectionMaskPrimitivesCount = 32; - public static final int k_unNotificationTextMaxSize = 256; - public static final int k_unMaxSettingsKeyLength = 128; - public static final int k_unMaxActionNameLength = 64; - public static final int k_unMaxActionSetNameLength = 64; - public static final int k_unMaxActionOriginCount = 16; - public static final long k_ulInvalidIOBufferHandle = 0; - public static final int k_ulInvalidSpatialAnchorHandle = 0; - - - public static final String IVRSystem_Version = "IVRSystem_019"; - public static final String IVRExtendedDisplay_Version = "IVRExtendedDisplay_001"; - public static final String IVRTrackedCamera_Version = "IVRTrackedCamera_003"; - public static final String k_pch_MimeType_HomeApp = "vr/home"; - public static final String k_pch_MimeType_GameTheater = "vr/game_theater"; - public static final String IVRApplications_Version = "IVRApplications_006"; - public static final String IVRChaperone_Version = "IVRChaperone_003"; - public static final String IVRChaperoneSetup_Version = "IVRChaperoneSetup_005"; - public static final String IVRCompositor_Version = "IVRCompositor_022"; - public static final String IVROverlay_Version = "IVROverlay_018"; - public static final String k_pch_Controller_Component_GDC2015 = "gdc2015"; - public static final String k_pch_Controller_Component_Base = "base"; - public static final String k_pch_Controller_Component_Tip = "tip"; - public static final String k_pch_Controller_Component_HandGrip = "handgrip"; - public static final String k_pch_Controller_Component_Status = "status"; - public static final String IVRRenderModels_Version = "IVRRenderModels_006"; - public static final String IVRNotifications_Version = "IVRNotifications_002"; - public static final String IVRSettings_Version = "IVRSettings_002"; - public static final String k_pch_SteamVR_Section = "steamvr"; - public static final String k_pch_SteamVR_RequireHmd_String = "requireHmd"; - public static final String k_pch_SteamVR_ForcedDriverKey_String = "forcedDriver"; - public static final String k_pch_SteamVR_ForcedHmdKey_String = "forcedHmd"; - public static final String k_pch_SteamVR_DisplayDebug_Bool = "displayDebug"; - public static final String k_pch_SteamVR_DebugProcessPipe_String = "debugProcessPipe"; - public static final String k_pch_SteamVR_DisplayDebugX_Int32 = "displayDebugX"; - public static final String k_pch_SteamVR_DisplayDebugY_Int32 = "displayDebugY"; - public static final String k_pch_SteamVR_SendSystemButtonToAllApps_Bool = "sendSystemButtonToAllApps"; - public static final String k_pch_SteamVR_LogLevel_Int32 = "loglevel"; - public static final String k_pch_SteamVR_IPD_Float = "ipd"; - public static final String k_pch_SteamVR_Background_String = "background"; - public static final String k_pch_SteamVR_BackgroundUseDomeProjection_Bool = "backgroundUseDomeProjection"; - public static final String k_pch_SteamVR_BackgroundCameraHeight_Float = "backgroundCameraHeight"; - public static final String k_pch_SteamVR_BackgroundDomeRadius_Float = "backgroundDomeRadius"; - public static final String k_pch_SteamVR_GridColor_String = "gridColor"; - public static final String k_pch_SteamVR_PlayAreaColor_String = "playAreaColor"; - public static final String k_pch_SteamVR_ShowStage_Bool = "showStage"; - public static final String k_pch_SteamVR_ActivateMultipleDrivers_Bool = "activateMultipleDrivers"; - public static final String k_pch_SteamVR_DirectMode_Bool = "directMode"; - public static final String k_pch_SteamVR_DirectModeEdidVid_Int32 = "directModeEdidVid"; - public static final String k_pch_SteamVR_DirectModeEdidPid_Int32 = "directModeEdidPid"; - public static final String k_pch_SteamVR_UsingSpeakers_Bool = "usingSpeakers"; - public static final String k_pch_SteamVR_SpeakersForwardYawOffsetDegrees_Float = "speakersForwardYawOffsetDegrees"; - public static final String k_pch_SteamVR_BaseStationPowerManagement_Bool = "basestationPowerManagement"; - public static final String k_pch_SteamVR_NeverKillProcesses_Bool = "neverKillProcesses"; - public static final String k_pch_SteamVR_SupersampleScale_Float = "supersampleScale"; - public static final String k_pch_SteamVR_AllowAsyncReprojection_Bool = "allowAsyncReprojection"; - public static final String k_pch_SteamVR_AllowReprojection_Bool = "allowInterleavedReprojection"; - public static final String k_pch_SteamVR_ForceReprojection_Bool = "forceReprojection"; - public static final String k_pch_SteamVR_ForceFadeOnBadTracking_Bool = "forceFadeOnBadTracking"; - public static final String k_pch_SteamVR_DefaultMirrorView_Int32 = "defaultMirrorView"; - public static final String k_pch_SteamVR_ShowMirrorView_Bool = "showMirrorView"; - public static final String k_pch_SteamVR_MirrorViewGeometry_String = "mirrorViewGeometry"; - public static final String k_pch_SteamVR_StartMonitorFromAppLaunch = "startMonitorFromAppLaunch"; - public static final String k_pch_SteamVR_StartCompositorFromAppLaunch_Bool = "startCompositorFromAppLaunch"; - public static final String k_pch_SteamVR_StartDashboardFromAppLaunch_Bool = "startDashboardFromAppLaunch"; - public static final String k_pch_SteamVR_StartOverlayAppsFromDashboard_Bool = "startOverlayAppsFromDashboard"; - public static final String k_pch_SteamVR_EnableHomeApp = "enableHomeApp"; - public static final String k_pch_SteamVR_CycleBackgroundImageTimeSec_Int32 = "CycleBackgroundImageTimeSec"; - public static final String k_pch_SteamVR_RetailDemo_Bool = "retailDemo"; - public static final String k_pch_SteamVR_IpdOffset_Float = "ipdOffset"; - public static final String k_pch_SteamVR_AllowSupersampleFiltering_Bool = "allowSupersampleFiltering"; - public static final String k_pch_SteamVR_SupersampleManualOverride_Bool = "supersampleManualOverride"; - public static final String k_pch_SteamVR_EnableLinuxVulkanAsync_Bool = "enableLinuxVulkanAsync"; - public static final String k_pch_SteamVR_AllowDisplayLockedMode_Bool = "allowDisplayLockedMode"; - public static final String k_pch_SteamVR_HaveStartedTutorialForNativeChaperoneDriver_Bool = "haveStartedTutorialForNativeChaperoneDriver"; - public static final String k_pch_SteamVR_ForceWindows32bitVRMonitor = "forceWindows32BitVRMonitor"; - public static final String k_pch_SteamVR_DebugInput = "debugInput"; - public static final String k_pch_SteamVR_LegacyInputRebinding = "legacyInputRebinding"; - public static final String k_pch_SteamVR_DebugInputBinding = "debugInputBinding"; - public static final String k_pch_SteamVR_InputBindingUIBlock = "inputBindingUI"; - public static final String k_pch_SteamVR_RenderCameraMode = "renderCameraMode"; - public static final String k_pch_Lighthouse_Section = "driver_lighthouse"; - public static final String k_pch_Lighthouse_DisableIMU_Bool = "disableimu"; - public static final String k_pch_Lighthouse_DisableIMUExceptHMD_Bool = "disableimuexcepthmd"; - public static final String k_pch_Lighthouse_UseDisambiguation_String = "usedisambiguation"; - public static final String k_pch_Lighthouse_DisambiguationDebug_Int32 = "disambiguationdebug"; - public static final String k_pch_Lighthouse_PrimaryBasestation_Int32 = "primarybasestation"; - public static final String k_pch_Lighthouse_DBHistory_Bool = "dbhistory"; - public static final String k_pch_Lighthouse_EnableBluetooth_Bool = "enableBluetooth"; - public static final String k_pch_Lighthouse_PowerManagedBaseStations_String = "PowerManagedBaseStations"; - public static final String k_pch_Null_Section = "driver_null"; - public static final String k_pch_Null_SerialNumber_String = "serialNumber"; - public static final String k_pch_Null_ModelNumber_String = "modelNumber"; - public static final String k_pch_Null_WindowX_Int32 = "windowX"; - public static final String k_pch_Null_WindowY_Int32 = "windowY"; - public static final String k_pch_Null_WindowWidth_Int32 = "windowWidth"; - public static final String k_pch_Null_WindowHeight_Int32 = "windowHeight"; - public static final String k_pch_Null_RenderWidth_Int32 = "renderWidth"; - public static final String k_pch_Null_RenderHeight_Int32 = "renderHeight"; - public static final String k_pch_Null_SecondsFromVsyncToPhotons_Float = "secondsFromVsyncToPhotons"; - public static final String k_pch_Null_DisplayFrequency_Float = "displayFrequency"; - public static final String k_pch_UserInterface_Section = "userinterface"; - public static final String k_pch_UserInterface_StatusAlwaysOnTop_Bool = "StatusAlwaysOnTop"; - public static final String k_pch_UserInterface_MinimizeToTray_Bool = "MinimizeToTray"; - public static final String k_pch_UserInterface_Screenshots_Bool = "screenshots"; - public static final String k_pch_UserInterface_ScreenshotType_Int = "screenshotType"; - public static final String k_pch_Notifications_Section = "notifications"; - public static final String k_pch_Notifications_DoNotDisturb_Bool = "DoNotDisturb"; - public static final String k_pch_Keyboard_Section = "keyboard"; - public static final String k_pch_Keyboard_TutorialCompletions = "TutorialCompletions"; - public static final String k_pch_Keyboard_ScaleX = "ScaleX"; - public static final String k_pch_Keyboard_ScaleY = "ScaleY"; - public static final String k_pch_Keyboard_OffsetLeftX = "OffsetLeftX"; - public static final String k_pch_Keyboard_OffsetRightX = "OffsetRightX"; - public static final String k_pch_Keyboard_OffsetY = "OffsetY"; - public static final String k_pch_Keyboard_Smoothing = "Smoothing"; - public static final String k_pch_Perf_Section = "perfcheck"; - public static final String k_pch_Perf_HeuristicActive_Bool = "heuristicActive"; - public static final String k_pch_Perf_NotifyInHMD_Bool = "warnInHMD"; - public static final String k_pch_Perf_NotifyOnlyOnce_Bool = "warnOnlyOnce"; - public static final String k_pch_Perf_AllowTimingStore_Bool = "allowTimingStore"; - public static final String k_pch_Perf_SaveTimingsOnExit_Bool = "saveTimingsOnExit"; - public static final String k_pch_Perf_TestData_Float = "perfTestData"; - public static final String k_pch_Perf_LinuxGPUProfiling_Bool = "linuxGPUProfiling"; - public static final String k_pch_CollisionBounds_Section = "collisionBounds"; - public static final String k_pch_CollisionBounds_Style_Int32 = "CollisionBoundsStyle"; - public static final String k_pch_CollisionBounds_GroundPerimeterOn_Bool = "CollisionBoundsGroundPerimeterOn"; - public static final String k_pch_CollisionBounds_CenterMarkerOn_Bool = "CollisionBoundsCenterMarkerOn"; - public static final String k_pch_CollisionBounds_PlaySpaceOn_Bool = "CollisionBoundsPlaySpaceOn"; - public static final String k_pch_CollisionBounds_FadeDistance_Float = "CollisionBoundsFadeDistance"; - public static final String k_pch_CollisionBounds_ColorGammaR_Int32 = "CollisionBoundsColorGammaR"; - public static final String k_pch_CollisionBounds_ColorGammaG_Int32 = "CollisionBoundsColorGammaG"; - public static final String k_pch_CollisionBounds_ColorGammaB_Int32 = "CollisionBoundsColorGammaB"; - public static final String k_pch_CollisionBounds_ColorGammaA_Int32 = "CollisionBoundsColorGammaA"; - public static final String k_pch_Camera_Section = "camera"; - public static final String k_pch_Camera_EnableCamera_Bool = "enableCamera"; - public static final String k_pch_Camera_EnableCameraInDashboard_Bool = "enableCameraInDashboard"; - public static final String k_pch_Camera_EnableCameraForCollisionBounds_Bool = "enableCameraForCollisionBounds"; - public static final String k_pch_Camera_EnableCameraForRoomView_Bool = "enableCameraForRoomView"; - public static final String k_pch_Camera_BoundsColorGammaR_Int32 = "cameraBoundsColorGammaR"; - public static final String k_pch_Camera_BoundsColorGammaG_Int32 = "cameraBoundsColorGammaG"; - public static final String k_pch_Camera_BoundsColorGammaB_Int32 = "cameraBoundsColorGammaB"; - public static final String k_pch_Camera_BoundsColorGammaA_Int32 = "cameraBoundsColorGammaA"; - public static final String k_pch_Camera_BoundsStrength_Int32 = "cameraBoundsStrength"; - public static final String k_pch_Camera_RoomViewMode_Int32 = "cameraRoomViewMode"; - public static final String k_pch_audio_Section = "audio"; - public static final String k_pch_audio_OnPlaybackDevice_String = "onPlaybackDevice"; - public static final String k_pch_audio_OnRecordDevice_String = "onRecordDevice"; - public static final String k_pch_audio_OnPlaybackMirrorDevice_String = "onPlaybackMirrorDevice"; - public static final String k_pch_audio_OffPlaybackDevice_String = "offPlaybackDevice"; - public static final String k_pch_audio_OffRecordDevice_String = "offRecordDevice"; - public static final String k_pch_audio_VIVEHDMIGain = "viveHDMIGain"; - public static final String k_pch_Power_Section = "power"; - public static final String k_pch_Power_PowerOffOnExit_Bool = "powerOffOnExit"; - public static final String k_pch_Power_TurnOffScreensTimeout_Float = "turnOffScreensTimeout"; - public static final String k_pch_Power_TurnOffControllersTimeout_Float = "turnOffControllersTimeout"; - public static final String k_pch_Power_ReturnToWatchdogTimeout_Float = "returnToWatchdogTimeout"; - public static final String k_pch_Power_AutoLaunchSteamVROnButtonPress = "autoLaunchSteamVROnButtonPress"; - public static final String k_pch_Power_PauseCompositorOnStandby_Bool = "pauseCompositorOnStandby"; - public static final String k_pch_Dashboard_Section = "dashboard"; - public static final String k_pch_Dashboard_EnableDashboard_Bool = "enableDashboard"; - public static final String k_pch_Dashboard_ArcadeMode_Bool = "arcadeMode"; - public static final String k_pch_Dashboard_EnableWebUI = "webUI"; - public static final String k_pch_Dashboard_EnableWebUIDevTools = "webUIDevTools"; - public static final String k_pch_Dashboard_EnableWebUIDashboardReplacement = "webUIDashboard"; - public static final String k_pch_modelskin_Section = "modelskins"; - public static final String k_pch_Driver_Enable_Bool = "enable"; - public static final String k_pch_WebInterface_Section = "WebInterface"; - public static final String k_pch_WebInterface_WebEnable_Bool = "WebEnable"; - public static final String k_pch_WebInterface_WebPort_String = "WebPort"; - public static final String k_pch_TrackingOverride_Section = "TrackingOverrides"; - public static final String k_pch_App_BindingAutosaveURLSuffix_String = "AutosaveURL"; - public static final String k_pch_App_BindingCurrentURLSuffix_String = "CurrentURL"; - public static final String k_pch_App_NeedToUpdateAutosaveSuffix_Bool = "NeedToUpdateAutosave"; - public static final String k_pch_App_ActionManifestURL_String = "ActionManifestURL"; - public static final String k_pch_Trackers_Section = "trackers"; - public static final String IVRScreenshots_Version = "IVRScreenshots_001"; - public static final String IVRResources_Version = "IVRResources_001"; - public static final String IVRDriverManager_Version = "IVRDriverManager_001"; - public static final String IVRInput_Version = "IVRInput_004"; - public static final String IVRIOBuffer_Version = "IVRIOBuffer_001"; - public static final String IVRSpatialAnchors_Version = "IVRSpatialAnchors_001"; - - - /** - * Global entry points
                  - * Original signature : intptr_t VR_InitInternal(EVRInitError*, EVRApplicationType)
                  - * native declaration : headers\openvr_capi.h:2378
                  - * @deprecated use the safer methods {@link #VR_InitInternal(java.nio.IntBuffer, int)} and {@link #VR_InitInternal(com.sun.jna.ptr.IntByReference, int)} instead - */ - @Deprecated - public static native IntByReference VR_InitInternal(IntByReference peError, int eType); - /** - * Global entry points
                  - * Original signature : intptr_t VR_InitInternal(EVRInitError*, EVRApplicationType)
                  - * native declaration : headers\openvr_capi.h:2378 - */ - public static native IntByReference VR_InitInternal(IntBuffer peError, int eType); - /** - * Original signature : void VR_ShutdownInternal()
                  - * native declaration : headers\openvr_capi.h:2380 - */ - public static native void VR_ShutdownInternal(); - /** - * Original signature : bool VR_IsHmdPresent()
                  - * native declaration : headers\openvr_capi.h:2382 - */ - public static native byte VR_IsHmdPresent(); - /** - * Original signature : intptr_t VR_GetGenericInterface(const char*, EVRInitError*)
                  - * native declaration : headers\openvr_capi.h:2384
                  - * @deprecated use the safer method - * {@link #VR_GetGenericInterface(java.lang.String, com.sun.jna.ptr.IntByReference)} - * instead - */ - @Deprecated - public static native IntByReference VR_GetGenericInterface(Pointer pchInterfaceVersion, IntByReference peError); - /** - * Original signature : intptr_t VR_GetGenericInterface(const char*, EVRInitError*)
                  - * native declaration : headers\openvr_capi.h:2384 - */ - public static native IntByReference VR_GetGenericInterface(String pchInterfaceVersion, IntByReference peError); - /** - * Original signature : bool VR_IsRuntimeInstalled()
                  - * native declaration : headers\openvr_capi.h:2386 - */ - public static native byte VR_IsRuntimeInstalled(); - /** - * Original signature : char* VR_GetVRInitErrorAsSymbol(EVRInitError)
                  - * native declaration : headers\openvr_capi.h:2388 - */ - public static native Pointer VR_GetVRInitErrorAsSymbol(int error); - /** - * Original signature : char* VR_GetVRInitErrorAsEnglishDescription(EVRInitError)
                  - * native declaration : headers\openvr_capi.h:2390 - */ - public static native Pointer VR_GetVRInitErrorAsEnglishDescription(int error); - public static class VkQueue_T extends PointerType { - public VkQueue_T(Pointer address) { - super(address); - } - public VkQueue_T() { - super(); - } - }; - public static class VkPhysicalDevice_T extends PointerType { - public VkPhysicalDevice_T(Pointer address) { - super(address); - } - public VkPhysicalDevice_T() { - super(); - } - }; - public static class VkInstance_T extends PointerType { - public VkInstance_T(Pointer address) { - super(address); - } - public VkInstance_T() { - super(); - } - }; - public static class ID3D12CommandQueue extends PointerType { - public ID3D12CommandQueue(Pointer address) { - super(address); - } - public ID3D12CommandQueue() { - super(); - } - }; - public static class ID3D12Resource extends PointerType { - public ID3D12Resource(Pointer address) { - super(address); - } - public ID3D12Resource() { - super(); - } - }; - public static class VkDevice_T extends PointerType { - public VkDevice_T(Pointer address) { - super(address); - } - public VkDevice_T() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/NotificationBitmap_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/NotificationBitmap_t.java deleted file mode 100644 index 217eedb2c5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/NotificationBitmap_t.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1588
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class NotificationBitmap_t extends Structure { - /** - * void *
                  - * C type : void* - */ - public Pointer m_pImageData; - public int m_nWidth; - public int m_nHeight; - public int m_nBytesPerPixel; - public NotificationBitmap_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_pImageData", "m_nWidth", "m_nHeight", "m_nBytesPerPixel"); - } - /** - * @param m_pImageData void *
                  - * C type : void* - */ - public NotificationBitmap_t(Pointer m_pImageData, int m_nWidth, int m_nHeight, int m_nBytesPerPixel) { - super(); - this.m_pImageData = m_pImageData; - this.m_nWidth = m_nWidth; - this.m_nHeight = m_nHeight; - this.m_nBytesPerPixel = m_nBytesPerPixel; - } - public NotificationBitmap_t(Pointer peer) { - super(peer); - } - public static class ByReference extends NotificationBitmap_t implements Structure.ByReference { - - }; - public static class ByValue extends NotificationBitmap_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/OpenVRUtil.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/OpenVRUtil.java deleted file mode 100644 index 78b96e9620..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/OpenVRUtil.java +++ /dev/null @@ -1,774 +0,0 @@ -package com.jme3.system.jopenvr; - -import com.jme3.input.vr.openvr.OpenVRInput; -import com.jme3.system.jopenvr.JOpenVRLibrary.EColorSpace; -import com.jme3.system.jopenvr.JOpenVRLibrary.ETextureType; -import com.jme3.system.jopenvr.JOpenVRLibrary.ETrackedDeviceProperty; -import com.jme3.system.jopenvr.JOpenVRLibrary.ETrackedPropertyError; -import com.jme3.system.jopenvr.JOpenVRLibrary.EVRCompositorError; -import com.jme3.system.jopenvr.JOpenVRLibrary.EVRInitError; -import com.sun.jna.Memory; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.IntByReference; - -/** - * A utility class that provide helper methods for OpenVR system. - * @author Julien Seinturier - 2017 - http://www.seinturier.fr - */ -public class OpenVRUtil { - - /** - * Get the value of the given string {@link JOpenVRLibrary.ETrackedDeviceProperty property} attached to the given device. - * @param system the underlying OpenVR system. - * @param deviceIndex the index of the device to query. - * @param property the property to query. - * @param bufferSize the size of the buffer to use for storing native string. - * @return the value of the given string property attached to the given device. - * @see OpenVRInput#getTrackedControllerCount() - * @see JOpenVRLibrary.ETrackedDeviceProperty - * @see #getTrackedDeviceStringProperty(VR_IVRSystem_FnTable, int, int) - */ - public static String getTrackedDeviceStringProperty(VR_IVRSystem_FnTable system, int deviceIndex, int property, int bufferSize){ - String str =""; - - int unBufferSize = 256; - Pointer pchValue = new Memory(unBufferSize); - IntByReference pError = new IntByReference(); - - system.GetStringTrackedDeviceProperty.apply(deviceIndex, property, pchValue, unBufferSize, pError); - - if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_Success){ - str = pchValue.getString(0); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_BufferTooSmall){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_CouldNotContactServer){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_InvalidDevice){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_InvalidOperation){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_NotYetAvailable){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_PermissionDenied){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_StringExceedsMaximumLength){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_UnknownProperty){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_ValueNotProvidedByDevice){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_WrongDataType){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else if (pError.getValue() == ETrackedPropertyError.ETrackedPropertyError_TrackedProp_WrongDeviceClass){ - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } else { - throw new IllegalArgumentException("Cannot access property \""+getETrackedDevicePropertyString(property)+"\" ("+property+") for device "+deviceIndex+": "+getETrackedPropertyErrorString(pError.getValue())+" ("+pError.getValue()+")"); - } - - return str; - } - - /** - * Get the value of the given string {@link JOpenVRLibrary.ETrackedDeviceProperty property} attached to the given device. - * @param system the underlying OpenVR system. - * @param deviceIndex the index of the device to query. - * @param property the property to query. - * @return the value of the given string property attached to the given device. - * @see OpenVRInput#getTrackedControllerCount() - * @see JOpenVRLibrary.ETrackedDeviceProperty - * @see #getTrackedDeviceStringProperty(VR_IVRSystem_FnTable, int, int, int) - */ - public static String getTrackedDeviceStringProperty(VR_IVRSystem_FnTable system, int deviceIndex, int property){ - return getTrackedDeviceStringProperty(system, deviceIndex, property, 256); - } - - /** - * Get the String description of the given {@link ETrackedPropertyError string tracked property error}. - * @param error the string tracked property error. - * @return the String description of the given string tracked property error. - */ - public static String getETrackedPropertyErrorString(int error){ - String str =""; - - switch(error){ - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_Success: - str = "Success"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_WrongDataType: - str = "Wrong data type"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_WrongDeviceClass: - str = "Wrong device class"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_BufferTooSmall: - str = "Buffer too small"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_UnknownProperty: - str = "Unknown property"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_InvalidDevice: - str = "Invalid device"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_CouldNotContactServer: - str = "Could not contact server"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_ValueNotProvidedByDevice: - str = "Value not provided by device"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_StringExceedsMaximumLength: - str = "String exceed maximum length"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_NotYetAvailable: - str = "Not yet available"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_PermissionDenied: - str = "Permission denied"; - break; - case ETrackedPropertyError.ETrackedPropertyError_TrackedProp_InvalidOperation: - str = "Invalid operation"; - break; - default: - str = "Not handled error"; - } - - return str; - } - - /** - * Get the description of the given {@link EColorSpace color space}. - * @param eColorSpace the color space. - * @return the description of the given color space. - */ - public static String getEColorSpaceString(int eColorSpace){ - String str = ""; - - switch(eColorSpace){ - case EColorSpace.EColorSpace_ColorSpace_Auto: - str = "Auto"; - break; - case EColorSpace.EColorSpace_ColorSpace_Gamma: - str = "Gamma"; - break; - case EColorSpace.EColorSpace_ColorSpace_Linear: - str = "Linear"; - break; - default: - str = "Unknown ("+eColorSpace+")"; - } - - return str; - } - - /** - * Get the description of the given {@link ETextureType texture type}. - * @param type the texture type - * @return the description of the given texture type. - */ - public static String getETextureTypeString(int type){ - - String str = ""; - - switch(type){ - case ETextureType.ETextureType_TextureType_DirectX: - str = "DirectX"; - break; - case ETextureType.ETextureType_TextureType_OpenGL: - str = "OpenGL"; - break; - case ETextureType.ETextureType_TextureType_Vulkan: - str = "Vulkan"; - break; - case ETextureType.ETextureType_TextureType_IOSurface: - str = "IOSurface"; - break; - case ETextureType.ETextureType_TextureType_DirectX12: - str = "DirectX12"; - break; - default: - str = "Unknown ("+type+")"; - } - - return str; - } - - /** - * Get the description of the given {@link EVRCompositorError EVR compositor error}. - * @param error the EVR compositor error. - * @return the description of the given EVR compositor error. - */ - public static String getEVRCompositorErrorString(int error){ - String str =""; - - switch(error){ - case EVRCompositorError.EVRCompositorError_VRCompositorError_None: - str = "None"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_RequestFailed: - str = "Request failed"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_IncompatibleVersion: - str = "Incompatible version"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_DoNotHaveFocus: - str = "Do not have focus"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_InvalidTexture: - str = "Invalid texture"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_IsNotSceneApplication: - str = "Is not scene application"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_TextureIsOnWrongDevice: - str = "Texture is on wrong device"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_TextureUsesUnsupportedFormat: - str = "Texture uses unsupported format"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_SharedTexturesNotSupported: - str = "Shared textures not supported"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_IndexOutOfRange: - str = "Index out of range"; - break; - case EVRCompositorError.EVRCompositorError_VRCompositorError_AlreadySubmitted: - str = "Already submitted"; - break; - } - return str; - } - - /** - * Get the description of the given {@link EVRInitError EVR init error}. - * @param error the EVR init error. - * @return the description of the given EVR init error. - */ - public static String getEVRInitErrorString(int error){ - String str = ""; - - switch(error){ - - - case EVRInitError.EVRInitError_VRInitError_None: - str="None"; - break; - case EVRInitError.EVRInitError_VRInitError_Unknown: - str="Unknown"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InstallationNotFound: - str="Installation not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InstallationCorrupt: - str="Installation corrupt"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_VRClientDLLNotFound: - str="VR Client DLL not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_FileNotFound: - str="File not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_FactoryNotFound: - str="Factory not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InterfaceNotFound: - str="Interface not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InvalidInterface: - str="Invalid interface"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_UserConfigDirectoryInvalid: - str="User config directory invalid"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_HmdNotFound: - str="HMD not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NotInitialized: - str="Not initialized"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_PathRegistryNotFound: - str="Path registry not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NoConfigPath: - str="No config path"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NoLogPath: - str="No log path"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_PathRegistryNotWritable: - str="Path registry not writable"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_AppInfoInitFailed: - str="AppInfo init failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_Retry: - str="Init retry"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InitCanceledByUser: - str="Init canceled by user"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_AnotherAppLaunching: - str="Another app launching"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_SettingsInitFailed: - str="Setting init failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_ShuttingDown: - str="Shutting down"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_TooManyObjects: - str="Too many objects"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NoServerForBackgroundApp: - str="No server background app"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NotSupportedWithCompositor: - str="Not supported with compositor"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NotAvailableToUtilityApps: - str="Not available to utility apps"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_Internal: - str="Internal"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_HmdDriverIdIsNone: - str="Driver Id is None"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_HmdNotFoundPresenceFailed: - str="HMD not found presence failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_VRMonitorNotFound: - str="VR monitor not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_VRMonitorStartupFailed: - str="VR monitor startup failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_LowPowerWatchdogNotSupported: - str="Low power watchdog not supported"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_InvalidApplicationType: - str="Invalid application type"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_NotAvailableToWatchdogApps: - str="Not available to watchdog apps"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_WatchdogDisabledInSettings: - str="Watchdog disabled in settings"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_VRDashboardNotFound: - str="VR dashboard not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Init_VRDashboardStartupFailed: - str="VR dashboard setup failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_Failed: - str="Driver failed"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_Unknown: - str="Driver unknown"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_HmdUnknown: - str="HMD unknown"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_NotLoaded: - str="Driver not loaded"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_RuntimeOutOfDate: - str="Driver runtime out of date"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_HmdInUse: - str="HMD in use"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_NotCalibrated: - str="Not calibrated"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_CalibrationInvalid: - str="Calibration invalid"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_HmdDisplayNotFound: - str="HMD display not found"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_TrackedDeviceInterfaceUnknown: - str="Tracked device interface unknown"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_HmdDriverIdOutOfBounds: - str="HMD driver Id out of bounds"; - break; - case EVRInitError.EVRInitError_VRInitError_Driver_HmdDisplayMirrored: - str="HMD display mirrored"; - break; - case EVRInitError.EVRInitError_VRInitError_IPC_ServerInitFailed: - str=""; - break; - case EVRInitError.EVRInitError_VRInitError_IPC_ConnectFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_SharedStateInitFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_CompositorInitFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_MutexInitFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_Failed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_CompositorConnectFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_CompositorInvalidConnectResponse: str=""; break; - case EVRInitError.EVRInitError_VRInitError_IPC_ConnectFailedAfterMultipleAttempts: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Compositor_Failed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Compositor_D3D11HardwareRequired: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Compositor_FirmwareRequiresUpdate: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Compositor_OverlayInitFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Compositor_ScreenshotsInitFailed: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_UnableToConnectToOculusRuntime: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_CantOpenDevice: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToRequestConfigStart: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_NoStoredConfig: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigTooBig: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigTooSmall: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToInitZLib: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_CantReadFirmwareVersion: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToSendUserDataStart: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataStart: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UnableToGetUserDataNext: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UserDataAddressRange: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_UserDataError: str=""; break; - case EVRInitError.EVRInitError_VRInitError_VendorSpecific_HmdFound_ConfigFailedSanityCheck: str=""; break; - case EVRInitError.EVRInitError_VRInitError_Steam_SteamInstallationNotFound: str=""; break; - default: - } - - return str; - } - - /** - * Get the description of the given tracked device property. - * @param property the tracked device property. - * @return the description of the given tracked device property. - */ - public static String getETrackedDevicePropertyString(int property){ - String str = ""; - - switch(property){ - - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Invalid: - str = "Invalid"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_TrackingSystemName_String: - str = "Tracking system name"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ModelNumber_String: - str = "Model number"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_SerialNumber_String: - str = "Serial number"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_RenderModelName_String: - str = "Render model name"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_WillDriftInYaw_Bool: - str = "Will drift in yaw"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ManufacturerName_String: - str = "Manufacturer name"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_TrackingFirmwareVersion_String: - str = "Tracking firmware version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_HardwareRevision_String: - str = "Hardware revision"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_AllWirelessDongleDescriptions_String: - str = "All wireless dongle descriptions"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ConnectedWirelessDongle_String: - str = "Connect wireless dongle"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceIsWireless_Bool: - str = "Device is wireless"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceIsCharging_Bool: - str = "Device is charging"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceBatteryPercentage_Float: - str = "Device battery percentage"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_StatusDisplayTransform_Matrix34: - str = "Status display transform"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Firmware_UpdateAvailable_Bool: - str = "Update available"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Firmware_ManualUpdate_Bool: - str = "Firmware manual update"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Firmware_ManualUpdateURL_String: - str = "Firmware manual update URL"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_HardwareRevision_Uint64: - str = "Hardware revision"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FirmwareVersion_Uint64: - str = "Firmware version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FPGAVersion_Uint64: - str = "FPGA version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_VRCVersion_Uint64: - str = "VRC version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_RadioVersion_Uint64: - str = "Radio version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DongleVersion_Uint64: - str = "Dongle version"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_BlockServerShutdown_Bool: - str = "Block server shutdown"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CanUnifyCoordinateSystemWithHmd_Bool: - str = "Can unify coordinate system with HMD"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ContainsProximitySensor_Bool: - str = "Contains proximity sensor"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceProvidesBatteryStatus_Bool: - str = "Device provides battery status"; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceCanPowerOff_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Firmware_ProgrammingTarget_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DeviceClass_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_HasCamera_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DriverVersion_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Firmware_ForceUpdateRequired_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ViveSystemButtonFixRequired_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ParentDriver_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ReportsTimeSinceVSync_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_SecondsFromVsyncToPhotons_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayFrequency_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_UserIpdMeters_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CurrentUniverseId_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_PreviousUniverseId_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayFirmwareVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_IsOnDesktop_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCType_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCOffset_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCScale_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_EdidVendorID_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageLeft_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageRight_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCBlackClamp_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_EdidProductID_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CameraToHeadTransform_Matrix34: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCType_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCOffset_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCScale_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCPrescale_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayGCImage_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_LensCenterLeftU_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_LensCenterLeftV_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_LensCenterRightU_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_LensCenterRightV_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_UserHeadToEyeDepthMeters_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CameraFirmwareVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CameraFirmwareDescription_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayFPGAVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayBootloaderVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayHardwareVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_AudioFirmwareVersion_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_CameraCompatibilityMode_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ScreenshotHorizontalFieldOfViewDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ScreenshotVerticalFieldOfViewDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplaySuppressed_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayAllowNightMode_Bool: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageWidth_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageHeight_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageNumChannels_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayMCImageData_Binary: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_AttachedDeviceId_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_SupportedButtons_Uint64: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Axis0Type_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Axis1Type_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Axis2Type_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Axis3Type_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_Axis4Type_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ControllerRoleHint_Int32: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FieldOfViewLeftDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FieldOfViewRightDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FieldOfViewTopDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_FieldOfViewBottomDegrees_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_TrackingRangeMinimumMeters_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_TrackingRangeMaximumMeters_Float: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_ModeLabel_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_IconPathName_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceOff_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceSearching_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceSearchingAlert_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceReady_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceReadyAlert_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceNotReady_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceStandby_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_NamedIconPathDeviceAlertLow_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayHiddenArea_Binary_Start: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_DisplayHiddenArea_Binary_End: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_UserConfigPath_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_InstallPath_String: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_VendorSpecific_Reserved_Start: - str = ""; - break; - case ETrackedDeviceProperty.ETrackedDeviceProperty_Prop_VendorSpecific_Reserved_End: - str = ""; - break; - } - - - return str; - } -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ComponentState_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ComponentState_t.java deleted file mode 100644 index a55f51b933..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ComponentState_t.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1557
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class RenderModel_ComponentState_t extends Structure { - /** C type : HmdMatrix34_t */ - public HmdMatrix34_t mTrackingToComponentRenderModel; - /** C type : HmdMatrix34_t */ - public HmdMatrix34_t mTrackingToComponentLocal; - /** C type : VRComponentProperties */ - public int uProperties; - public RenderModel_ComponentState_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("mTrackingToComponentRenderModel", "mTrackingToComponentLocal", "uProperties"); - } - /** - * @param mTrackingToComponentRenderModel C type : HmdMatrix34_t
                  - * @param mTrackingToComponentLocal C type : HmdMatrix34_t
                  - * @param uProperties C type : VRComponentProperties - */ - public RenderModel_ComponentState_t(HmdMatrix34_t mTrackingToComponentRenderModel, HmdMatrix34_t mTrackingToComponentLocal, int uProperties) { - super(); - this.mTrackingToComponentRenderModel = mTrackingToComponentRenderModel; - this.mTrackingToComponentLocal = mTrackingToComponentLocal; - this.uProperties = uProperties; - } - public RenderModel_ComponentState_t(Pointer peer) { - super(peer); - } - public static class ByReference extends RenderModel_ComponentState_t implements Structure.ByReference { - - }; - public static class ByValue extends RenderModel_ComponentState_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ControllerMode_State_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ControllerMode_State_t.java deleted file mode 100644 index 5d5e45d57d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_ControllerMode_State_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1581
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class RenderModel_ControllerMode_State_t extends Structure { - public byte bScrollWheelVisible; - public RenderModel_ControllerMode_State_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bScrollWheelVisible"); - } - public RenderModel_ControllerMode_State_t(byte bScrollWheelVisible) { - super(); - this.bScrollWheelVisible = bScrollWheelVisible; - } - public RenderModel_ControllerMode_State_t(Pointer peer) { - super(peer); - } - public static class ByReference extends RenderModel_ControllerMode_State_t implements Structure.ByReference { - - }; - public static class ByValue extends RenderModel_ControllerMode_State_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_TextureMap_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_TextureMap_t.java deleted file mode 100644 index c6baff6fef..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_TextureMap_t.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1569
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class RenderModel_TextureMap_t extends Structure { - public short unWidth; - public short unHeight; - /** - * const uint8_t *
                  - * C type : uint8_t* - */ - public Pointer rubTextureMapData; - public RenderModel_TextureMap_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("unWidth", "unHeight", "rubTextureMapData"); - } - /** - * @param rubTextureMapData const uint8_t *
                  - * C type : uint8_t* - */ - public RenderModel_TextureMap_t(short unWidth, short unHeight, Pointer rubTextureMapData) { - super(); - this.unWidth = unWidth; - this.unHeight = unHeight; - this.rubTextureMapData = rubTextureMapData; - } - public RenderModel_TextureMap_t(Pointer peer) { - super(peer); - } - public static class ByReference extends RenderModel_TextureMap_t implements Structure.ByReference { - - }; - public static class ByValue extends RenderModel_TextureMap_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_Vertex_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_Vertex_t.java deleted file mode 100644 index 12e9217d82..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_Vertex_t.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1563
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class RenderModel_Vertex_t extends Structure { - /** C type : HmdVector3_t */ - public HmdVector3_t vPosition; - /** C type : HmdVector3_t */ - public HmdVector3_t vNormal; - /** - * float[2]
                  - * C type : float[2] - */ - public float[] rfTextureCoord = new float[2]; - public RenderModel_Vertex_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("vPosition", "vNormal", "rfTextureCoord"); - } - /** - * @param vPosition C type : HmdVector3_t
                  - * @param vNormal C type : HmdVector3_t
                  - * @param rfTextureCoord float[2]
                  - * C type : float[2] - */ - public RenderModel_Vertex_t(HmdVector3_t vPosition, HmdVector3_t vNormal, float rfTextureCoord[]) { - super(); - this.vPosition = vPosition; - this.vNormal = vNormal; - if ((rfTextureCoord.length != this.rfTextureCoord.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rfTextureCoord = rfTextureCoord; - } - public RenderModel_Vertex_t(Pointer peer) { - super(peer); - } - public static class ByReference extends RenderModel_Vertex_t implements Structure.ByReference { - - }; - public static class ByValue extends RenderModel_Vertex_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_t.java deleted file mode 100644 index 697c79cdf9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/RenderModel_t.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.ShortByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1578
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class RenderModel_t extends Structure { - /** - * const struct vr::RenderModel_Vertex_t *
                  - * C type : RenderModel_Vertex_t* - */ - public com.jme3.system.jopenvr.RenderModel_Vertex_t.ByReference rVertexData; - public int unVertexCount; - /** - * const uint16_t *
                  - * C type : uint16_t* - */ - public ShortByReference rIndexData; - public int unTriangleCount; - /** C type : TextureID_t */ - public int diffuseTextureId; - public RenderModel_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("rVertexData", "unVertexCount", "rIndexData", "unTriangleCount", "diffuseTextureId"); - } - /** - * @param rVertexData const struct vr::RenderModel_Vertex_t *
                  - * C type : RenderModel_Vertex_t*
                  - * @param rIndexData const uint16_t *
                  - * C type : uint16_t*
                  - * @param diffuseTextureId C type : TextureID_t - */ - public RenderModel_t(com.jme3.system.jopenvr.RenderModel_Vertex_t.ByReference rVertexData, int unVertexCount, ShortByReference rIndexData, int unTriangleCount, int diffuseTextureId) { - super(); - this.rVertexData = rVertexData; - this.unVertexCount = unVertexCount; - this.rIndexData = rIndexData; - this.unTriangleCount = unTriangleCount; - this.diffuseTextureId = diffuseTextureId; - } - public RenderModel_t(Pointer peer) { - super(peer); - } - public static class ByReference extends RenderModel_t implements Structure.ByReference { - - }; - public static class ByValue extends RenderModel_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/SpatialAnchorPose_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/SpatialAnchorPose_t.java deleted file mode 100644 index 3bc24a3ad5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/SpatialAnchorPose_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1636
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class SpatialAnchorPose_t extends Structure { - /** C type : HmdMatrix34_t */ - public HmdMatrix34_t mAnchorToAbsoluteTracking; - public SpatialAnchorPose_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("mAnchorToAbsoluteTracking"); - } - /** @param mAnchorToAbsoluteTracking C type : HmdMatrix34_t */ - public SpatialAnchorPose_t(HmdMatrix34_t mAnchorToAbsoluteTracking) { - super(); - this.mAnchorToAbsoluteTracking = mAnchorToAbsoluteTracking; - } - public SpatialAnchorPose_t(Pointer peer) { - super(peer); - } - public static class ByReference extends SpatialAnchorPose_t implements Structure.ByReference { - - }; - public static class ByValue extends SpatialAnchorPose_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Texture_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/Texture_t.java deleted file mode 100644 index ad0cc5c139..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/Texture_t.java +++ /dev/null @@ -1,55 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1247
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class Texture_t extends Structure { - /** - * void *
                  - * C type : void* - */ - public int handle; - /** - * C type : ETextureType - */ - public int eType; - /** - * C type : EColorSpace - */ - public int eColorSpace; - public Texture_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("handle", "eType", "eColorSpace"); - } - /** - * @param handle void *
                  - * C type : void*
                  - * @param eType @see JOpenVRLibrary.ETextureType
                  - * C type : ETextureType
                  - * @param eColorSpace @see JOpenVRLibrary.EColorSpace
                  - * C type : EColorSpace - */ - public Texture_t(int handle, int eType, int eColorSpace) { - super(); - this.handle = handle; - this.eType = eType; - this.eColorSpace = eColorSpace; - } - public Texture_t(Pointer peer) { - super(peer); - } - public static class ByReference extends Texture_t implements Structure.ByReference { - - }; - public static class ByValue extends Texture_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/TrackedDevicePose_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/TrackedDevicePose_t.java deleted file mode 100644 index 81eaf41517..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/TrackedDevicePose_t.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1257
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class TrackedDevicePose_t extends Structure { - /** C type : HmdMatrix34_t */ - public HmdMatrix34_t mDeviceToAbsoluteTracking; - /** C type : HmdVector3_t */ - public HmdVector3_t vVelocity; - /** C type : HmdVector3_t */ - public HmdVector3_t vAngularVelocity; - /** - * C type : ETrackingResult - */ - public int eTrackingResult; - public byte bPoseIsValid; - public byte bDeviceIsConnected; - public TrackedDevicePose_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("mDeviceToAbsoluteTracking", "vVelocity", "vAngularVelocity", "eTrackingResult", "bPoseIsValid", "bDeviceIsConnected"); - } - /** - * @param mDeviceToAbsoluteTracking C type : HmdMatrix34_t
                  - * @param vVelocity C type : HmdVector3_t
                  - * @param vAngularVelocity C type : HmdVector3_t
                  - * @param eTrackingResult @see JOpenVRLibrary.ETrackingResult
                  - * C type : ETrackingResult - */ - public TrackedDevicePose_t(HmdMatrix34_t mDeviceToAbsoluteTracking, HmdVector3_t vVelocity, HmdVector3_t vAngularVelocity, int eTrackingResult, byte bPoseIsValid, byte bDeviceIsConnected) { - super(); - this.mDeviceToAbsoluteTracking = mDeviceToAbsoluteTracking; - this.vVelocity = vVelocity; - this.vAngularVelocity = vAngularVelocity; - this.eTrackingResult = eTrackingResult; - this.bPoseIsValid = bPoseIsValid; - this.bDeviceIsConnected = bDeviceIsConnected; - } - public TrackedDevicePose_t(Pointer peer) { - super(peer); - } - public static class ByReference extends TrackedDevicePose_t implements Structure.ByReference { - - }; - public static class ByValue extends TrackedDevicePose_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRActiveActionSet_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRActiveActionSet_t.java deleted file mode 100644 index 3a643b893f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRActiveActionSet_t.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1633
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRActiveActionSet_t extends Structure { - /** C type : VRActionSetHandle_t */ - public long ulActionSet; - /** C type : VRInputValueHandle_t */ - public long ulRestrictedToDevice; - /** C type : VRActionSetHandle_t */ - public long ulSecondaryActionSet; - public int unPadding; - public int nPriority; - public VRActiveActionSet_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("ulActionSet", "ulRestrictedToDevice", "ulSecondaryActionSet", "unPadding", "nPriority"); - } - /** - * @param ulActionSet C type : VRActionSetHandle_t
                  - * @param ulRestrictedToDevice C type : VRInputValueHandle_t
                  - * @param ulSecondaryActionSet C type : VRActionSetHandle_t - */ - public VRActiveActionSet_t(long ulActionSet, long ulRestrictedToDevice, long ulSecondaryActionSet, int unPadding, int nPriority) { - super(); - this.ulActionSet = ulActionSet; - this.ulRestrictedToDevice = ulRestrictedToDevice; - this.ulSecondaryActionSet = ulSecondaryActionSet; - this.unPadding = unPadding; - this.nPriority = nPriority; - } - public VRActiveActionSet_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRActiveActionSet_t implements Structure.ByReference { - - }; - public static class ByValue extends VRActiveActionSet_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRBoneTransform_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRBoneTransform_t.java deleted file mode 100644 index 671e95d9bf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRBoneTransform_t.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1456
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRBoneTransform_t extends Structure { - /** C type : HmdVector4_t */ - public HmdVector4_t position; - /** C type : HmdQuaternionf_t */ - public HmdQuaternionf_t orientation; - public VRBoneTransform_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("position", "orientation"); - } - /** - * @param position C type : HmdVector4_t
                  - * @param orientation C type : HmdQuaternionf_t - */ - public VRBoneTransform_t(HmdVector4_t position, HmdQuaternionf_t orientation) { - super(); - this.position = position; - this.orientation = orientation; - } - public VRBoneTransform_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRBoneTransform_t implements Structure.ByReference { - - }; - public static class ByValue extends VRBoneTransform_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerAxis_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerAxis_t.java deleted file mode 100644 index 8114fdf94b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerAxis_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1429
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRControllerAxis_t extends Structure { - public float x; - public float y; - public VRControllerAxis_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("x", "y"); - } - public VRControllerAxis_t(float x, float y) { - super(); - this.x = x; - this.y = y; - } - public VRControllerAxis_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRControllerAxis_t implements Structure.ByReference { - - }; - public static class ByValue extends VRControllerAxis_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerState_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerState_t.java deleted file mode 100644 index f1becb6fd7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRControllerState_t.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1436
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRControllerState_t extends Structure { - public int unPacketNum; - public long ulButtonPressed; - public long ulButtonTouched; - /** - * struct vr::VRControllerAxis_t[5]
                  - * C type : VRControllerAxis_t[5] - */ - public VRControllerAxis_t[] rAxis = new VRControllerAxis_t[5]; - public VRControllerState_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("unPacketNum", "ulButtonPressed", "ulButtonTouched", "rAxis"); - } - /** - * @param rAxis struct vr::VRControllerAxis_t[5]
                  - * C type : VRControllerAxis_t[5] - */ - public VRControllerState_t(int unPacketNum, long ulButtonPressed, long ulButtonTouched, VRControllerAxis_t rAxis[]) { - super(); - this.unPacketNum = unPacketNum; - this.ulButtonPressed = ulButtonPressed; - this.ulButtonTouched = ulButtonTouched; - if ((rAxis.length != this.rAxis.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.rAxis = rAxis; - } - public VRControllerState_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRControllerState_t implements Structure.ByReference { - - }; - public static class ByValue extends VRControllerState_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ApplicationLaunch_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ApplicationLaunch_t.java deleted file mode 100644 index 675e0ac07f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ApplicationLaunch_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1373
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_ApplicationLaunch_t extends Structure { - public int pid; - public int unArgsHandle; - public VREvent_ApplicationLaunch_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("pid", "unArgsHandle"); - } - public VREvent_ApplicationLaunch_t(int pid, int unArgsHandle) { - super(); - this.pid = pid; - this.unArgsHandle = unArgsHandle; - } - public VREvent_ApplicationLaunch_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_ApplicationLaunch_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_ApplicationLaunch_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Chaperone_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Chaperone_t.java deleted file mode 100644 index 65287c0d73..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Chaperone_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1350
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Chaperone_t extends Structure { - public long m_nPreviousUniverse; - public long m_nCurrentUniverse; - public VREvent_Chaperone_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nPreviousUniverse", "m_nCurrentUniverse"); - } - public VREvent_Chaperone_t(long m_nPreviousUniverse, long m_nCurrentUniverse) { - super(); - this.m_nPreviousUniverse = m_nPreviousUniverse; - this.m_nCurrentUniverse = m_nCurrentUniverse; - } - public VREvent_Chaperone_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Chaperone_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Chaperone_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Controller_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Controller_t.java deleted file mode 100644 index c2a632c695..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Controller_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1304
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Controller_t extends Structure { - public int button; - public VREvent_Controller_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("button"); - } - public VREvent_Controller_t(int button) { - super(); - this.button = button; - } - public VREvent_Controller_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Controller_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Controller_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Data_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Data_t.java deleted file mode 100644 index 983a7da823..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Data_t.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Union; -/** - * native declaration : headers\openvr_capi.h:1686
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Data_t extends Union { - /** C type : VREvent_Reserved_t */ - public VREvent_Reserved_t reserved; - /** C type : VREvent_Controller_t */ - public VREvent_Controller_t controller; - /** C type : VREvent_Mouse_t */ - public VREvent_Mouse_t mouse; - /** C type : VREvent_Scroll_t */ - public VREvent_Scroll_t scroll; - /** C type : VREvent_Process_t */ - public VREvent_Process_t process; - /** C type : VREvent_Notification_t */ - public VREvent_Notification_t notification; - /** C type : VREvent_Overlay_t */ - public VREvent_Overlay_t overlay; - /** C type : VREvent_Status_t */ - public VREvent_Status_t status; - /** C type : VREvent_Keyboard_t */ - public VREvent_Keyboard_t keyboard; - /** C type : VREvent_Ipd_t */ - public VREvent_Ipd_t ipd; - /** C type : VREvent_Chaperone_t */ - public VREvent_Chaperone_t chaperone; - /** C type : VREvent_PerformanceTest_t */ - public VREvent_PerformanceTest_t performanceTest; - /** C type : VREvent_TouchPadMove_t */ - public VREvent_TouchPadMove_t touchPadMove; - /** C type : VREvent_SeatedZeroPoseReset_t */ - public VREvent_SeatedZeroPoseReset_t seatedZeroPoseReset; - public VREvent_Data_t() { - super(); - } - /** @param reserved C type : VREvent_Reserved_t */ - public VREvent_Data_t(VREvent_Reserved_t reserved) { - super(); - this.reserved = reserved; - setType(VREvent_Reserved_t.class); - } - /** @param controller C type : VREvent_Controller_t */ - public VREvent_Data_t(VREvent_Controller_t controller) { - super(); - this.controller = controller; - setType(VREvent_Controller_t.class); - } - /** @param mouse C type : VREvent_Mouse_t */ - public VREvent_Data_t(VREvent_Mouse_t mouse) { - super(); - this.mouse = mouse; - setType(VREvent_Mouse_t.class); - } - /** @param scroll C type : VREvent_Scroll_t */ - public VREvent_Data_t(VREvent_Scroll_t scroll) { - super(); - this.scroll = scroll; - setType(VREvent_Scroll_t.class); - } - /** @param process C type : VREvent_Process_t */ - public VREvent_Data_t(VREvent_Process_t process) { - super(); - this.process = process; - setType(VREvent_Process_t.class); - } - /** @param notification C type : VREvent_Notification_t */ - public VREvent_Data_t(VREvent_Notification_t notification) { - super(); - this.notification = notification; - setType(VREvent_Notification_t.class); - } - /** @param overlay C type : VREvent_Overlay_t */ - public VREvent_Data_t(VREvent_Overlay_t overlay) { - super(); - this.overlay = overlay; - setType(VREvent_Overlay_t.class); - } - /** @param status C type : VREvent_Status_t */ - public VREvent_Data_t(VREvent_Status_t status) { - super(); - this.status = status; - setType(VREvent_Status_t.class); - } - /** @param keyboard C type : VREvent_Keyboard_t */ - public VREvent_Data_t(VREvent_Keyboard_t keyboard) { - super(); - this.keyboard = keyboard; - setType(VREvent_Keyboard_t.class); - } - /** @param ipd C type : VREvent_Ipd_t */ - public VREvent_Data_t(VREvent_Ipd_t ipd) { - super(); - this.ipd = ipd; - setType(VREvent_Ipd_t.class); - } - /** @param chaperone C type : VREvent_Chaperone_t */ - public VREvent_Data_t(VREvent_Chaperone_t chaperone) { - super(); - this.chaperone = chaperone; - setType(VREvent_Chaperone_t.class); - } - /** @param performanceTest C type : VREvent_PerformanceTest_t */ - public VREvent_Data_t(VREvent_PerformanceTest_t performanceTest) { - super(); - this.performanceTest = performanceTest; - setType(VREvent_PerformanceTest_t.class); - } - /** @param touchPadMove C type : VREvent_TouchPadMove_t */ - public VREvent_Data_t(VREvent_TouchPadMove_t touchPadMove) { - super(); - this.touchPadMove = touchPadMove; - setType(VREvent_TouchPadMove_t.class); - } - /** @param seatedZeroPoseReset C type : VREvent_SeatedZeroPoseReset_t */ - public VREvent_Data_t(VREvent_SeatedZeroPoseReset_t seatedZeroPoseReset) { - super(); - this.seatedZeroPoseReset = seatedZeroPoseReset; - setType(VREvent_SeatedZeroPoseReset_t.class); - } - public VREvent_Data_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Data_t implements com.sun.jna.Structure.ByReference { - - }; - public static class ByValue extends VREvent_Data_t implements com.sun.jna.Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_DualAnalog_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_DualAnalog_t.java deleted file mode 100644 index a1fb2a523f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_DualAnalog_t.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1395
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_DualAnalog_t extends Structure { - public float x; - public float y; - public float transformedX; - public float transformedY; - /** - * C type : EDualAnalogWhich - */ - public int which; - public VREvent_DualAnalog_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("x", "y", "transformedX", "transformedY", "which"); - } - /** - * @param which @see JOpenVRLibrary.EDualAnalogWhich
                  - * C type : EDualAnalogWhich - */ - public VREvent_DualAnalog_t(float x, float y, float transformedX, float transformedY, int which) { - super(); - this.x = x; - this.y = y; - this.transformedX = transformedX; - this.transformedY = transformedY; - this.which = which; - } - public VREvent_DualAnalog_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_DualAnalog_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_DualAnalog_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_EditingCameraSurface_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_EditingCameraSurface_t.java deleted file mode 100644 index 6a8f5c8c35..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_EditingCameraSurface_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1377
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_EditingCameraSurface_t extends Structure { - public long overlayHandle; - public int nVisualMode; - public VREvent_EditingCameraSurface_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("overlayHandle", "nVisualMode"); - } - public VREvent_EditingCameraSurface_t(long overlayHandle, int nVisualMode) { - super(); - this.overlayHandle = overlayHandle; - this.nVisualMode = nVisualMode; - } - public VREvent_EditingCameraSurface_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_EditingCameraSurface_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_EditingCameraSurface_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_HapticVibration_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_HapticVibration_t.java deleted file mode 100644 index 1501bcbf43..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_HapticVibration_t.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1402
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_HapticVibration_t extends Structure { - public long containerHandle; - public long componentHandle; - public float fDurationSeconds; - public float fFrequency; - public float fAmplitude; - public VREvent_HapticVibration_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("containerHandle", "componentHandle", "fDurationSeconds", "fFrequency", "fAmplitude"); - } - public VREvent_HapticVibration_t(long containerHandle, long componentHandle, float fDurationSeconds, float fFrequency, float fAmplitude) { - super(); - this.containerHandle = containerHandle; - this.componentHandle = componentHandle; - this.fDurationSeconds = fDurationSeconds; - this.fFrequency = fFrequency; - this.fAmplitude = fAmplitude; - } - public VREvent_HapticVibration_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_HapticVibration_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_HapticVibration_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputActionManifestLoad_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputActionManifestLoad_t.java deleted file mode 100644 index 955ad7abdd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputActionManifestLoad_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1417
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_InputActionManifestLoad_t extends Structure { - public long pathAppKey; - public long pathMessage; - public long pathMessageParam; - public long pathManifestPath; - public VREvent_InputActionManifestLoad_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("pathAppKey", "pathMessage", "pathMessageParam", "pathManifestPath"); - } - public VREvent_InputActionManifestLoad_t(long pathAppKey, long pathMessage, long pathMessageParam, long pathManifestPath) { - super(); - this.pathAppKey = pathAppKey; - this.pathMessage = pathMessage; - this.pathMessageParam = pathMessageParam; - this.pathManifestPath = pathManifestPath; - } - public VREvent_InputActionManifestLoad_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_InputActionManifestLoad_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_InputActionManifestLoad_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputBindingLoad_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputBindingLoad_t.java deleted file mode 100644 index f7a02b4bf5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_InputBindingLoad_t.java +++ /dev/null @@ -1,41 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1411
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_InputBindingLoad_t extends Structure { - /** C type : PropertyContainerHandle_t */ - public long ulAppContainer; - public long pathMessage; - public long pathUrl; - public long pathControllerType; - public VREvent_InputBindingLoad_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("ulAppContainer", "pathMessage", "pathUrl", "pathControllerType"); - } - /** @param ulAppContainer C type : PropertyContainerHandle_t */ - public VREvent_InputBindingLoad_t(long ulAppContainer, long pathMessage, long pathUrl, long pathControllerType) { - super(); - this.ulAppContainer = ulAppContainer; - this.pathMessage = pathMessage; - this.pathUrl = pathUrl; - this.pathControllerType = pathControllerType; - } - public VREvent_InputBindingLoad_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_InputBindingLoad_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_InputBindingLoad_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Ipd_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Ipd_t.java deleted file mode 100644 index 02b100e350..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Ipd_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1346
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Ipd_t extends Structure { - public float ipdMeters; - public VREvent_Ipd_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("ipdMeters"); - } - public VREvent_Ipd_t(float ipdMeters) { - super(); - this.ipdMeters = ipdMeters; - } - public VREvent_Ipd_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Ipd_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Ipd_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Keyboard_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Keyboard_t.java deleted file mode 100644 index a6fb1e6e9b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Keyboard_t.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1343
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Keyboard_t extends Structure { - /** - * char[8]
                  - * C type : char*[8] - */ - public Pointer[] cNewInput = new Pointer[8]; - public long uUserValue; - public VREvent_Keyboard_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("cNewInput", "uUserValue"); - } - /** - * @param cNewInput char[8]
                  - * C type : char*[8] - */ - public VREvent_Keyboard_t(Pointer cNewInput[], long uUserValue) { - super(); - if ((cNewInput.length != this.cNewInput.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.cNewInput = cNewInput; - this.uUserValue = uUserValue; - } - public VREvent_Keyboard_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Keyboard_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Keyboard_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_MessageOverlay_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_MessageOverlay_t.java deleted file mode 100644 index fc9e82c9bf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_MessageOverlay_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1380
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_MessageOverlay_t extends Structure { - public int unVRMessageOverlayResponse; - public VREvent_MessageOverlay_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("unVRMessageOverlayResponse"); - } - public VREvent_MessageOverlay_t(int unVRMessageOverlayResponse) { - super(); - this.unVRMessageOverlayResponse = unVRMessageOverlayResponse; - } - public VREvent_MessageOverlay_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_MessageOverlay_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_MessageOverlay_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Mouse_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Mouse_t.java deleted file mode 100644 index b55c523c7c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Mouse_t.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1309
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Mouse_t extends Structure { - public float x; - public float y; - public int button; - public VREvent_Mouse_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("x", "y", "button"); - } - public VREvent_Mouse_t(float x, float y, int button) { - super(); - this.x = x; - this.y = y; - this.button = button; - } - public VREvent_Mouse_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Mouse_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Mouse_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Notification_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Notification_t.java deleted file mode 100644 index 0f65ecfc38..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Notification_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1326
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Notification_t extends Structure { - public long ulUserValue; - public int notificationId; - public VREvent_Notification_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("ulUserValue", "notificationId"); - } - public VREvent_Notification_t(long ulUserValue, int notificationId) { - super(); - this.ulUserValue = ulUserValue; - this.notificationId = notificationId; - } - public VREvent_Notification_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Notification_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Notification_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Overlay_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Overlay_t.java deleted file mode 100644 index d112d92e2e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Overlay_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1335
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Overlay_t extends Structure { - public long overlayHandle; - public long devicePath; - public VREvent_Overlay_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("overlayHandle", "devicePath"); - } - public VREvent_Overlay_t(long overlayHandle, long devicePath) { - super(); - this.overlayHandle = overlayHandle; - this.devicePath = devicePath; - } - public VREvent_Overlay_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Overlay_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Overlay_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_PerformanceTest_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_PerformanceTest_t.java deleted file mode 100644 index 37e357bef1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_PerformanceTest_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1359
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_PerformanceTest_t extends Structure { - public int m_nFidelityLevel; - public VREvent_PerformanceTest_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nFidelityLevel"); - } - public VREvent_PerformanceTest_t(int m_nFidelityLevel) { - super(); - this.m_nFidelityLevel = m_nFidelityLevel; - } - public VREvent_PerformanceTest_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_PerformanceTest_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_PerformanceTest_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Process_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Process_t.java deleted file mode 100644 index ad89d03c95..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Process_t.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1331
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Process_t extends Structure { - public int pid; - public int oldPid; - public byte bForced; - public VREvent_Process_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("pid", "oldPid", "bForced"); - } - public VREvent_Process_t(int pid, int oldPid, byte bForced) { - super(); - this.pid = pid; - this.oldPid = oldPid; - this.bForced = bForced; - } - public VREvent_Process_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Process_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Process_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Property_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Property_t.java deleted file mode 100644 index 945d766cf9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Property_t.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1386
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Property_t extends Structure { - /** C type : PropertyContainerHandle_t */ - public long container; - /** - * C type : ETrackedDeviceProperty - */ - public int prop; - public VREvent_Property_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("container", "prop"); - } - /** - * @param container C type : PropertyContainerHandle_t
                  - * @param prop @see JOpenVRLibrary.ETrackedDeviceProperty
                  - * C type : ETrackedDeviceProperty - */ - public VREvent_Property_t(long container, int prop) { - super(); - this.container = container; - this.prop = prop; - } - public VREvent_Property_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Property_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Property_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Reserved_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Reserved_t.java deleted file mode 100644 index 11dd3f849e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Reserved_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1356
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Reserved_t extends Structure { - public long reserved0; - public long reserved1; - public long reserved2; - public long reserved3; - public VREvent_Reserved_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("reserved0", "reserved1", "reserved2", "reserved3"); - } - public VREvent_Reserved_t(long reserved0, long reserved1, long reserved2, long reserved3) { - super(); - this.reserved0 = reserved0; - this.reserved1 = reserved1; - this.reserved2 = reserved2; - this.reserved3 = reserved3; - } - public VREvent_Reserved_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Reserved_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Reserved_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ScreenshotProgress_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ScreenshotProgress_t.java deleted file mode 100644 index 7d616dec60..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_ScreenshotProgress_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1369
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_ScreenshotProgress_t extends Structure { - public float progress; - public VREvent_ScreenshotProgress_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("progress"); - } - public VREvent_ScreenshotProgress_t(float progress) { - super(); - this.progress = progress; - } - public VREvent_ScreenshotProgress_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_ScreenshotProgress_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_ScreenshotProgress_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Screenshot_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Screenshot_t.java deleted file mode 100644 index d34f6e0898..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Screenshot_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1366
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Screenshot_t extends Structure { - public int handle; - public int type; - public VREvent_Screenshot_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("handle", "type"); - } - public VREvent_Screenshot_t(int handle, int type) { - super(); - this.handle = handle; - this.type = type; - } - public VREvent_Screenshot_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Screenshot_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Screenshot_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Scroll_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Scroll_t.java deleted file mode 100644 index 8a428e8bb5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Scroll_t.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1314
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Scroll_t extends Structure { - public float xdelta; - public float ydelta; - public int repeatCount; - public VREvent_Scroll_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("xdelta", "ydelta", "repeatCount"); - } - public VREvent_Scroll_t(float xdelta, float ydelta, int repeatCount) { - super(); - this.xdelta = xdelta; - this.ydelta = ydelta; - this.repeatCount = repeatCount; - } - public VREvent_Scroll_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Scroll_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Scroll_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SeatedZeroPoseReset_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SeatedZeroPoseReset_t.java deleted file mode 100644 index fb8edda62a..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SeatedZeroPoseReset_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1362
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_SeatedZeroPoseReset_t extends Structure { - public byte bResetBySystemMenu; - public VREvent_SeatedZeroPoseReset_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bResetBySystemMenu"); - } - public VREvent_SeatedZeroPoseReset_t(byte bResetBySystemMenu) { - super(); - this.bResetBySystemMenu = bResetBySystemMenu; - } - public VREvent_SeatedZeroPoseReset_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_SeatedZeroPoseReset_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_SeatedZeroPoseReset_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SpatialAnchor_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SpatialAnchor_t.java deleted file mode 100644 index 5e26d5b4cf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_SpatialAnchor_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1420
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_SpatialAnchor_t extends Structure { - /** C type : SpatialAnchorHandle_t */ - public int unHandle; - public VREvent_SpatialAnchor_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("unHandle"); - } - /** @param unHandle C type : SpatialAnchorHandle_t */ - public VREvent_SpatialAnchor_t(int unHandle) { - super(); - this.unHandle = unHandle; - } - public VREvent_SpatialAnchor_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_SpatialAnchor_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_SpatialAnchor_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Status_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Status_t.java deleted file mode 100644 index 054b72f682..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_Status_t.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1338
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_Status_t extends Structure { - public int statusState; - public VREvent_Status_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("statusState"); - } - public VREvent_Status_t(int statusState) { - super(); - this.statusState = statusState; - } - public VREvent_Status_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_Status_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_Status_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_TouchPadMove_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_TouchPadMove_t.java deleted file mode 100644 index 58f4af1122..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_TouchPadMove_t.java +++ /dev/null @@ -1,43 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1322
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_TouchPadMove_t extends Structure { - public byte bFingerDown; - public float flSecondsFingerDown; - public float fValueXFirst; - public float fValueYFirst; - public float fValueXRaw; - public float fValueYRaw; - public VREvent_TouchPadMove_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("bFingerDown", "flSecondsFingerDown", "fValueXFirst", "fValueYFirst", "fValueXRaw", "fValueYRaw"); - } - public VREvent_TouchPadMove_t(byte bFingerDown, float flSecondsFingerDown, float fValueXFirst, float fValueYFirst, float fValueXRaw, float fValueYRaw) { - super(); - this.bFingerDown = bFingerDown; - this.flSecondsFingerDown = flSecondsFingerDown; - this.fValueXFirst = fValueXFirst; - this.fValueYFirst = fValueYFirst; - this.fValueXRaw = fValueXRaw; - this.fValueYRaw = fValueYRaw; - } - public VREvent_TouchPadMove_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_TouchPadMove_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_TouchPadMove_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_WebConsole_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_WebConsole_t.java deleted file mode 100644 index bcde8c67c3..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_WebConsole_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1405
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_WebConsole_t extends Structure { - /** C type : WebConsoleHandle_t */ - public long webConsoleHandle; - public VREvent_WebConsole_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("webConsoleHandle"); - } - /** @param webConsoleHandle C type : WebConsoleHandle_t */ - public VREvent_WebConsole_t(long webConsoleHandle) { - super(); - this.webConsoleHandle = webConsoleHandle; - } - public VREvent_WebConsole_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_WebConsole_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_WebConsole_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_t.java deleted file mode 100644 index e22ad5687e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VREvent_t.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * An event posted by the server to all running applications
                  - * native declaration : headers\openvr_capi.h:1694
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VREvent_t extends Structure { - /** EVREventType enum */ - public int eventType; - /** C type : TrackedDeviceIndex_t */ - public int trackedDeviceIndex; - public float eventAgeSeconds; - /** C type : VREvent_Data_t */ - public VREvent_Data_t data; - public VREvent_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("eventType", "trackedDeviceIndex", "eventAgeSeconds", "data"); - } - /** - * @param eventType EVREventType enum
                  - * @param trackedDeviceIndex C type : TrackedDeviceIndex_t
                  - * @param data C type : VREvent_Data_t - */ - public VREvent_t(int eventType, int trackedDeviceIndex, float eventAgeSeconds, VREvent_Data_t data) { - super(); - this.eventType = eventType; - this.trackedDeviceIndex = trackedDeviceIndex; - this.eventAgeSeconds = eventAgeSeconds; - this.data = data; - } - public VREvent_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VREvent_t implements Structure.ByReference { - - }; - public static class ByValue extends VREvent_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_Data_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_Data_t.java deleted file mode 100644 index 9020bf2852..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_Data_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Union; -/** - * native declaration : headers\openvr_capi.h:1698
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VROverlayIntersectionMaskPrimitive_Data_t extends Union { - /** C type : IntersectionMaskRectangle_t */ - public IntersectionMaskRectangle_t m_Rectangle; - /** C type : IntersectionMaskCircle_t */ - public IntersectionMaskCircle_t m_Circle; - public VROverlayIntersectionMaskPrimitive_Data_t() { - super(); - } - /** @param m_Rectangle C type : IntersectionMaskRectangle_t */ - public VROverlayIntersectionMaskPrimitive_Data_t(IntersectionMaskRectangle_t m_Rectangle) { - super(); - this.m_Rectangle = m_Rectangle; - setType(IntersectionMaskRectangle_t.class); - } - /** @param m_Circle C type : IntersectionMaskCircle_t */ - public VROverlayIntersectionMaskPrimitive_Data_t(IntersectionMaskCircle_t m_Circle) { - super(); - this.m_Circle = m_Circle; - setType(IntersectionMaskCircle_t.class); - } - public VROverlayIntersectionMaskPrimitive_Data_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VROverlayIntersectionMaskPrimitive_Data_t implements com.sun.jna.Structure.ByReference { - - }; - public static class ByValue extends VROverlayIntersectionMaskPrimitive_Data_t implements com.sun.jna.Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_t.java deleted file mode 100644 index 63ec6c5b78..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionMaskPrimitive_t.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1702
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VROverlayIntersectionMaskPrimitive_t extends Structure { - /** - * C type : EVROverlayIntersectionMaskPrimitiveType - */ - public int m_nPrimitiveType; - /** C type : VROverlayIntersectionMaskPrimitive_Data_t */ - public VROverlayIntersectionMaskPrimitive_Data_t m_Primitive; - public VROverlayIntersectionMaskPrimitive_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nPrimitiveType", "m_Primitive"); - } - /** - * @param m_nPrimitiveType @see JOpenVRLibrary.EVROverlayIntersectionMaskPrimitiveType
                  - * C type : EVROverlayIntersectionMaskPrimitiveType
                  - * @param m_Primitive C type : VROverlayIntersectionMaskPrimitive_Data_t - */ - public VROverlayIntersectionMaskPrimitive_t(int m_nPrimitiveType, VROverlayIntersectionMaskPrimitive_Data_t m_Primitive) { - super(); - this.m_nPrimitiveType = m_nPrimitiveType; - this.m_Primitive = m_Primitive; - } - public VROverlayIntersectionMaskPrimitive_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VROverlayIntersectionMaskPrimitive_t implements Structure.ByReference { - - }; - public static class ByValue extends VROverlayIntersectionMaskPrimitive_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionParams_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionParams_t.java deleted file mode 100644 index 872e6609fe..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionParams_t.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1535
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VROverlayIntersectionParams_t extends Structure { - /** C type : HmdVector3_t */ - public HmdVector3_t vSource; - /** C type : HmdVector3_t */ - public HmdVector3_t vDirection; - /** - * C type : ETrackingUniverseOrigin - */ - public int eOrigin; - public VROverlayIntersectionParams_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("vSource", "vDirection", "eOrigin"); - } - /** - * @param vSource C type : HmdVector3_t
                  - * @param vDirection C type : HmdVector3_t
                  - * @param eOrigin @see JOpenVRLibrary.ETrackingUniverseOrigin
                  - * C type : ETrackingUniverseOrigin - */ - public VROverlayIntersectionParams_t(HmdVector3_t vSource, HmdVector3_t vDirection, int eOrigin) { - super(); - this.vSource = vSource; - this.vDirection = vDirection; - this.eOrigin = eOrigin; - } - public VROverlayIntersectionParams_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VROverlayIntersectionParams_t implements Structure.ByReference { - - }; - public static class ByValue extends VROverlayIntersectionParams_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionResults_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionResults_t.java deleted file mode 100644 index 22374baccc..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VROverlayIntersectionResults_t.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1541
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VROverlayIntersectionResults_t extends Structure { - /** C type : HmdVector3_t */ - public HmdVector3_t vPoint; - /** C type : HmdVector3_t */ - public HmdVector3_t vNormal; - /** C type : HmdVector2_t */ - public HmdVector2_t vUVs; - public float fDistance; - public VROverlayIntersectionResults_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("vPoint", "vNormal", "vUVs", "fDistance"); - } - /** - * @param vPoint C type : HmdVector3_t
                  - * @param vNormal C type : HmdVector3_t
                  - * @param vUVs C type : HmdVector2_t - */ - public VROverlayIntersectionResults_t(HmdVector3_t vPoint, HmdVector3_t vNormal, HmdVector2_t vUVs, float fDistance) { - super(); - this.vPoint = vPoint; - this.vNormal = vNormal; - this.vUVs = vUVs; - this.fDistance = fDistance; - } - public VROverlayIntersectionResults_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VROverlayIntersectionResults_t implements Structure.ByReference { - - }; - public static class ByValue extends VROverlayIntersectionResults_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureBounds_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureBounds_t.java deleted file mode 100644 index 3a0ce15df2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureBounds_t.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1263
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRTextureBounds_t extends Structure { - public float uMin; - public float vMin; - public float uMax; - public float vMax; - public VRTextureBounds_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("uMin", "vMin", "uMax", "vMax"); - } - public VRTextureBounds_t(float uMin, float vMin, float uMax, float vMax) { - super(); - this.uMin = uMin; - this.vMin = vMin; - this.uMax = uMax; - this.vMax = vMax; - } - public VRTextureBounds_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRTextureBounds_t implements Structure.ByReference { - - }; - public static class ByValue extends VRTextureBounds_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureDepthInfo_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureDepthInfo_t.java deleted file mode 100644 index 6dc69f3fba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureDepthInfo_t.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1272
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRTextureDepthInfo_t extends Structure { - /** - * void *
                  - * C type : void* - */ - public Pointer handle; - /** C type : HmdMatrix44_t */ - public HmdMatrix44_t mProjection; - /** C type : HmdVector2_t */ - public HmdVector2_t vRange; - public VRTextureDepthInfo_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("handle", "mProjection", "vRange"); - } - /** - * @param handle void *
                  - * C type : void*
                  - * @param mProjection C type : HmdMatrix44_t
                  - * @param vRange C type : HmdVector2_t - */ - public VRTextureDepthInfo_t(Pointer handle, HmdMatrix44_t mProjection, HmdVector2_t vRange) { - super(); - this.handle = handle; - this.mProjection = mProjection; - this.vRange = vRange; - } - public VRTextureDepthInfo_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRTextureDepthInfo_t implements Structure.ByReference { - - }; - public static class ByValue extends VRTextureDepthInfo_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithDepth_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithDepth_t.java deleted file mode 100644 index 2b58cfa601..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithDepth_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1275
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRTextureWithDepth_t extends Structure { - /** C type : VRTextureDepthInfo_t */ - public VRTextureDepthInfo_t depth; - public VRTextureWithDepth_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("depth"); - } - /** @param depth C type : VRTextureDepthInfo_t */ - public VRTextureWithDepth_t(VRTextureDepthInfo_t depth) { - super(); - this.depth = depth; - } - public VRTextureWithDepth_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRTextureWithDepth_t implements Structure.ByReference { - - }; - public static class ByValue extends VRTextureWithDepth_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPoseAndDepth_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPoseAndDepth_t.java deleted file mode 100644 index 1ae24aa8b8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPoseAndDepth_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1278
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRTextureWithPoseAndDepth_t extends Structure { - /** C type : VRTextureDepthInfo_t */ - public VRTextureDepthInfo_t depth; - public VRTextureWithPoseAndDepth_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("depth"); - } - /** @param depth C type : VRTextureDepthInfo_t */ - public VRTextureWithPoseAndDepth_t(VRTextureDepthInfo_t depth) { - super(); - this.depth = depth; - } - public VRTextureWithPoseAndDepth_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRTextureWithPoseAndDepth_t implements Structure.ByReference { - - }; - public static class ByValue extends VRTextureWithPoseAndDepth_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPose_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPose_t.java deleted file mode 100644 index 694562e441..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRTextureWithPose_t.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1266
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRTextureWithPose_t extends Structure { - /** C type : HmdMatrix34_t */ - public HmdMatrix34_t mDeviceToAbsoluteTracking; - public VRTextureWithPose_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("mDeviceToAbsoluteTracking"); - } - /** @param mDeviceToAbsoluteTracking C type : HmdMatrix34_t */ - public VRTextureWithPose_t(HmdMatrix34_t mDeviceToAbsoluteTracking) { - super(); - this.mDeviceToAbsoluteTracking = mDeviceToAbsoluteTracking; - } - public VRTextureWithPose_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRTextureWithPose_t implements Structure.ByReference { - - }; - public static class ByValue extends VRTextureWithPose_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRVulkanTextureData_t.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRVulkanTextureData_t.java deleted file mode 100644 index cf26144bc4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VRVulkanTextureData_t.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jme3.system.jopenvr; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkDevice_T; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkInstance_T; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkPhysicalDevice_T; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkQueue_T; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1294
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VRVulkanTextureData_t extends Structure { - public long m_nImage; - /** - * struct VkDevice_T *
                  - * C type : VkDevice_T* - */ - public VkDevice_T m_pDevice; - /** - * struct VkPhysicalDevice_T *
                  - * C type : VkPhysicalDevice_T* - */ - public VkPhysicalDevice_T m_pPhysicalDevice; - /** - * struct VkInstance_T *
                  - * C type : VkInstance_T* - */ - public VkInstance_T m_pInstance; - /** - * struct VkQueue_T *
                  - * C type : VkQueue_T* - */ - public VkQueue_T m_pQueue; - public int m_nQueueFamilyIndex; - public int m_nWidth; - public int m_nHeight; - public int m_nFormat; - public int m_nSampleCount; - public VRVulkanTextureData_t() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("m_nImage", "m_pDevice", "m_pPhysicalDevice", "m_pInstance", "m_pQueue", "m_nQueueFamilyIndex", "m_nWidth", "m_nHeight", "m_nFormat", "m_nSampleCount"); - } - public VRVulkanTextureData_t(Pointer peer) { - super(peer); - } - public static class ByReference extends VRVulkanTextureData_t implements Structure.ByReference { - - }; - public static class ByValue extends VRVulkanTextureData_t implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRApplications_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRApplications_FnTable.java deleted file mode 100644 index 8c42698940..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRApplications_FnTable.java +++ /dev/null @@ -1,216 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1897
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRApplications_FnTable extends Structure { - /** C type : AddApplicationManifest_callback* */ - public VR_IVRApplications_FnTable.AddApplicationManifest_callback AddApplicationManifest; - /** C type : RemoveApplicationManifest_callback* */ - public VR_IVRApplications_FnTable.RemoveApplicationManifest_callback RemoveApplicationManifest; - /** C type : IsApplicationInstalled_callback* */ - public VR_IVRApplications_FnTable.IsApplicationInstalled_callback IsApplicationInstalled; - /** C type : GetApplicationCount_callback* */ - public VR_IVRApplications_FnTable.GetApplicationCount_callback GetApplicationCount; - /** C type : GetApplicationKeyByIndex_callback* */ - public VR_IVRApplications_FnTable.GetApplicationKeyByIndex_callback GetApplicationKeyByIndex; - /** C type : GetApplicationKeyByProcessId_callback* */ - public VR_IVRApplications_FnTable.GetApplicationKeyByProcessId_callback GetApplicationKeyByProcessId; - /** C type : LaunchApplication_callback* */ - public VR_IVRApplications_FnTable.LaunchApplication_callback LaunchApplication; - /** C type : LaunchTemplateApplication_callback* */ - public VR_IVRApplications_FnTable.LaunchTemplateApplication_callback LaunchTemplateApplication; - /** C type : LaunchApplicationFromMimeType_callback* */ - public VR_IVRApplications_FnTable.LaunchApplicationFromMimeType_callback LaunchApplicationFromMimeType; - /** C type : LaunchDashboardOverlay_callback* */ - public VR_IVRApplications_FnTable.LaunchDashboardOverlay_callback LaunchDashboardOverlay; - /** C type : CancelApplicationLaunch_callback* */ - public VR_IVRApplications_FnTable.CancelApplicationLaunch_callback CancelApplicationLaunch; - /** C type : IdentifyApplication_callback* */ - public VR_IVRApplications_FnTable.IdentifyApplication_callback IdentifyApplication; - /** C type : GetApplicationProcessId_callback* */ - public VR_IVRApplications_FnTable.GetApplicationProcessId_callback GetApplicationProcessId; - /** C type : GetApplicationsErrorNameFromEnum_callback* */ - public VR_IVRApplications_FnTable.GetApplicationsErrorNameFromEnum_callback GetApplicationsErrorNameFromEnum; - /** C type : GetApplicationPropertyString_callback* */ - public VR_IVRApplications_FnTable.GetApplicationPropertyString_callback GetApplicationPropertyString; - /** C type : GetApplicationPropertyBool_callback* */ - public VR_IVRApplications_FnTable.GetApplicationPropertyBool_callback GetApplicationPropertyBool; - /** C type : GetApplicationPropertyUint64_callback* */ - public VR_IVRApplications_FnTable.GetApplicationPropertyUint64_callback GetApplicationPropertyUint64; - /** C type : SetApplicationAutoLaunch_callback* */ - public VR_IVRApplications_FnTable.SetApplicationAutoLaunch_callback SetApplicationAutoLaunch; - /** C type : GetApplicationAutoLaunch_callback* */ - public VR_IVRApplications_FnTable.GetApplicationAutoLaunch_callback GetApplicationAutoLaunch; - /** C type : SetDefaultApplicationForMimeType_callback* */ - public VR_IVRApplications_FnTable.SetDefaultApplicationForMimeType_callback SetDefaultApplicationForMimeType; - /** C type : GetDefaultApplicationForMimeType_callback* */ - public VR_IVRApplications_FnTable.GetDefaultApplicationForMimeType_callback GetDefaultApplicationForMimeType; - /** C type : GetApplicationSupportedMimeTypes_callback* */ - public VR_IVRApplications_FnTable.GetApplicationSupportedMimeTypes_callback GetApplicationSupportedMimeTypes; - /** C type : GetApplicationsThatSupportMimeType_callback* */ - public VR_IVRApplications_FnTable.GetApplicationsThatSupportMimeType_callback GetApplicationsThatSupportMimeType; - /** C type : GetApplicationLaunchArguments_callback* */ - public VR_IVRApplications_FnTable.GetApplicationLaunchArguments_callback GetApplicationLaunchArguments; - /** C type : GetStartingApplication_callback* */ - public VR_IVRApplications_FnTable.GetStartingApplication_callback GetStartingApplication; - /** C type : GetTransitionState_callback* */ - public VR_IVRApplications_FnTable.GetTransitionState_callback GetTransitionState; - /** C type : PerformApplicationPrelaunchCheck_callback* */ - public VR_IVRApplications_FnTable.PerformApplicationPrelaunchCheck_callback PerformApplicationPrelaunchCheck; - /** C type : GetApplicationsTransitionStateNameFromEnum_callback* */ - public VR_IVRApplications_FnTable.GetApplicationsTransitionStateNameFromEnum_callback GetApplicationsTransitionStateNameFromEnum; - /** C type : IsQuitUserPromptRequested_callback* */ - public VR_IVRApplications_FnTable.IsQuitUserPromptRequested_callback IsQuitUserPromptRequested; - /** C type : LaunchInternalProcess_callback* */ - public VR_IVRApplications_FnTable.LaunchInternalProcess_callback LaunchInternalProcess; - /** C type : GetCurrentSceneProcessId_callback* */ - public VR_IVRApplications_FnTable.GetCurrentSceneProcessId_callback GetCurrentSceneProcessId; - /** native declaration : headers\openvr_capi.h:1866 */ - public interface AddApplicationManifest_callback extends Callback { - int apply(Pointer pchApplicationManifestFullPath, byte bTemporary); - }; - /** native declaration : headers\openvr_capi.h:1867 */ - public interface RemoveApplicationManifest_callback extends Callback { - int apply(Pointer pchApplicationManifestFullPath); - }; - /** native declaration : headers\openvr_capi.h:1868 */ - public interface IsApplicationInstalled_callback extends Callback { - byte apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1869 */ - public interface GetApplicationCount_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:1870 */ - public interface GetApplicationKeyByIndex_callback extends Callback { - int apply(int unApplicationIndex, Pointer pchAppKeyBuffer, int unAppKeyBufferLen); - }; - /** native declaration : headers\openvr_capi.h:1871 */ - public interface GetApplicationKeyByProcessId_callback extends Callback { - int apply(int unProcessId, Pointer pchAppKeyBuffer, int unAppKeyBufferLen); - }; - /** native declaration : headers\openvr_capi.h:1872 */ - public interface LaunchApplication_callback extends Callback { - int apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1873 */ - public interface LaunchTemplateApplication_callback extends Callback { - int apply(Pointer pchTemplateAppKey, Pointer pchNewAppKey, AppOverrideKeys_t pKeys, int unKeys); - }; - /** native declaration : headers\openvr_capi.h:1874 */ - public interface LaunchApplicationFromMimeType_callback extends Callback { - int apply(Pointer pchMimeType, Pointer pchArgs); - }; - /** native declaration : headers\openvr_capi.h:1875 */ - public interface LaunchDashboardOverlay_callback extends Callback { - int apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1876 */ - public interface CancelApplicationLaunch_callback extends Callback { - byte apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1877 */ - public interface IdentifyApplication_callback extends Callback { - int apply(int unProcessId, Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1878 */ - public interface GetApplicationProcessId_callback extends Callback { - int apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1879 */ - public interface GetApplicationsErrorNameFromEnum_callback extends Callback { - Pointer apply(int error); - }; - /** native declaration : headers\openvr_capi.h:1880 */ - public interface GetApplicationPropertyString_callback extends Callback { - int apply(Pointer pchAppKey, int eProperty, Pointer pchPropertyValueBuffer, int unPropertyValueBufferLen, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:1881 */ - public interface GetApplicationPropertyBool_callback extends Callback { - byte apply(Pointer pchAppKey, int eProperty, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:1882 */ - public interface GetApplicationPropertyUint64_callback extends Callback { - long apply(Pointer pchAppKey, int eProperty, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:1883 */ - public interface SetApplicationAutoLaunch_callback extends Callback { - int apply(Pointer pchAppKey, byte bAutoLaunch); - }; - /** native declaration : headers\openvr_capi.h:1884 */ - public interface GetApplicationAutoLaunch_callback extends Callback { - byte apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1885 */ - public interface SetDefaultApplicationForMimeType_callback extends Callback { - int apply(Pointer pchAppKey, Pointer pchMimeType); - }; - /** native declaration : headers\openvr_capi.h:1886 */ - public interface GetDefaultApplicationForMimeType_callback extends Callback { - byte apply(Pointer pchMimeType, Pointer pchAppKeyBuffer, int unAppKeyBufferLen); - }; - /** native declaration : headers\openvr_capi.h:1887 */ - public interface GetApplicationSupportedMimeTypes_callback extends Callback { - byte apply(Pointer pchAppKey, Pointer pchMimeTypesBuffer, int unMimeTypesBuffer); - }; - /** native declaration : headers\openvr_capi.h:1888 */ - public interface GetApplicationsThatSupportMimeType_callback extends Callback { - int apply(Pointer pchMimeType, Pointer pchAppKeysThatSupportBuffer, int unAppKeysThatSupportBuffer); - }; - /** native declaration : headers\openvr_capi.h:1889 */ - public interface GetApplicationLaunchArguments_callback extends Callback { - int apply(int unHandle, Pointer pchArgs, int unArgs); - }; - /** native declaration : headers\openvr_capi.h:1890 */ - public interface GetStartingApplication_callback extends Callback { - int apply(Pointer pchAppKeyBuffer, int unAppKeyBufferLen); - }; - /** native declaration : headers\openvr_capi.h:1891 */ - public interface GetTransitionState_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:1892 */ - public interface PerformApplicationPrelaunchCheck_callback extends Callback { - int apply(Pointer pchAppKey); - }; - /** native declaration : headers\openvr_capi.h:1893 */ - public interface GetApplicationsTransitionStateNameFromEnum_callback extends Callback { - Pointer apply(int state); - }; - /** native declaration : headers\openvr_capi.h:1894 */ - public interface IsQuitUserPromptRequested_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1895 */ - public interface LaunchInternalProcess_callback extends Callback { - int apply(Pointer pchBinaryPath, Pointer pchArguments, Pointer pchWorkingDirectory); - }; - /** native declaration : headers\openvr_capi.h:1896 */ - public interface GetCurrentSceneProcessId_callback extends Callback { - int apply(); - }; - public VR_IVRApplications_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("AddApplicationManifest", "RemoveApplicationManifest", "IsApplicationInstalled", "GetApplicationCount", "GetApplicationKeyByIndex", "GetApplicationKeyByProcessId", "LaunchApplication", "LaunchTemplateApplication", "LaunchApplicationFromMimeType", "LaunchDashboardOverlay", "CancelApplicationLaunch", "IdentifyApplication", "GetApplicationProcessId", "GetApplicationsErrorNameFromEnum", "GetApplicationPropertyString", "GetApplicationPropertyBool", "GetApplicationPropertyUint64", "SetApplicationAutoLaunch", "GetApplicationAutoLaunch", "SetDefaultApplicationForMimeType", "GetDefaultApplicationForMimeType", "GetApplicationSupportedMimeTypes", "GetApplicationsThatSupportMimeType", "GetApplicationLaunchArguments", "GetStartingApplication", "GetTransitionState", "PerformApplicationPrelaunchCheck", "GetApplicationsTransitionStateNameFromEnum", "IsQuitUserPromptRequested", "LaunchInternalProcess", "GetCurrentSceneProcessId"); - } - public VR_IVRApplications_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRApplications_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRApplications_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperoneSetup_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperoneSetup_FnTable.java deleted file mode 100644 index 36be54ea5b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperoneSetup_FnTable.java +++ /dev/null @@ -1,151 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1957
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRChaperoneSetup_FnTable extends Structure { - /** C type : CommitWorkingCopy_callback* */ - public VR_IVRChaperoneSetup_FnTable.CommitWorkingCopy_callback CommitWorkingCopy; - /** C type : RevertWorkingCopy_callback* */ - public VR_IVRChaperoneSetup_FnTable.RevertWorkingCopy_callback RevertWorkingCopy; - /** C type : GetWorkingPlayAreaSize_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetWorkingPlayAreaSize_callback GetWorkingPlayAreaSize; - /** C type : GetWorkingPlayAreaRect_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetWorkingPlayAreaRect_callback GetWorkingPlayAreaRect; - /** C type : GetWorkingCollisionBoundsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetWorkingCollisionBoundsInfo_callback GetWorkingCollisionBoundsInfo; - /** C type : GetLiveCollisionBoundsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetLiveCollisionBoundsInfo_callback GetLiveCollisionBoundsInfo; - /** C type : GetWorkingSeatedZeroPoseToRawTrackingPose_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetWorkingSeatedZeroPoseToRawTrackingPose_callback GetWorkingSeatedZeroPoseToRawTrackingPose; - /** C type : GetWorkingStandingZeroPoseToRawTrackingPose_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetWorkingStandingZeroPoseToRawTrackingPose_callback GetWorkingStandingZeroPoseToRawTrackingPose; - /** C type : SetWorkingPlayAreaSize_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingPlayAreaSize_callback SetWorkingPlayAreaSize; - /** C type : SetWorkingCollisionBoundsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingCollisionBoundsInfo_callback SetWorkingCollisionBoundsInfo; - /** C type : SetWorkingSeatedZeroPoseToRawTrackingPose_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingSeatedZeroPoseToRawTrackingPose_callback SetWorkingSeatedZeroPoseToRawTrackingPose; - /** C type : SetWorkingStandingZeroPoseToRawTrackingPose_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingStandingZeroPoseToRawTrackingPose_callback SetWorkingStandingZeroPoseToRawTrackingPose; - /** C type : ReloadFromDisk_callback* */ - public VR_IVRChaperoneSetup_FnTable.ReloadFromDisk_callback ReloadFromDisk; - /** C type : GetLiveSeatedZeroPoseToRawTrackingPose_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetLiveSeatedZeroPoseToRawTrackingPose_callback GetLiveSeatedZeroPoseToRawTrackingPose; - /** C type : SetWorkingCollisionBoundsTagsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingCollisionBoundsTagsInfo_callback SetWorkingCollisionBoundsTagsInfo; - /** C type : GetLiveCollisionBoundsTagsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetLiveCollisionBoundsTagsInfo_callback GetLiveCollisionBoundsTagsInfo; - /** C type : SetWorkingPhysicalBoundsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.SetWorkingPhysicalBoundsInfo_callback SetWorkingPhysicalBoundsInfo; - /** C type : GetLivePhysicalBoundsInfo_callback* */ - public VR_IVRChaperoneSetup_FnTable.GetLivePhysicalBoundsInfo_callback GetLivePhysicalBoundsInfo; - /** C type : ExportLiveToBuffer_callback* */ - public VR_IVRChaperoneSetup_FnTable.ExportLiveToBuffer_callback ExportLiveToBuffer; - /** C type : ImportFromBufferToWorking_callback* */ - public VR_IVRChaperoneSetup_FnTable.ImportFromBufferToWorking_callback ImportFromBufferToWorking; - /** native declaration : headers\openvr_capi.h:1937 */ - public interface CommitWorkingCopy_callback extends Callback { - byte apply(int configFile); - }; - /** native declaration : headers\openvr_capi.h:1938 */ - public interface RevertWorkingCopy_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:1939 */ - public interface GetWorkingPlayAreaSize_callback extends Callback { - byte apply(FloatByReference pSizeX, FloatByReference pSizeZ); - }; - /** native declaration : headers\openvr_capi.h:1940 */ - public interface GetWorkingPlayAreaRect_callback extends Callback { - byte apply(HmdQuad_t rect); - }; - /** native declaration : headers\openvr_capi.h:1941 */ - public interface GetWorkingCollisionBoundsInfo_callback extends Callback { - byte apply(HmdQuad_t pQuadsBuffer, IntByReference punQuadsCount); - }; - /** native declaration : headers\openvr_capi.h:1942 */ - public interface GetLiveCollisionBoundsInfo_callback extends Callback { - byte apply(HmdQuad_t pQuadsBuffer, IntByReference punQuadsCount); - }; - /** native declaration : headers\openvr_capi.h:1943 */ - public interface GetWorkingSeatedZeroPoseToRawTrackingPose_callback extends Callback { - byte apply(HmdMatrix34_t pmatSeatedZeroPoseToRawTrackingPose); - }; - /** native declaration : headers\openvr_capi.h:1944 */ - public interface GetWorkingStandingZeroPoseToRawTrackingPose_callback extends Callback { - byte apply(HmdMatrix34_t pmatStandingZeroPoseToRawTrackingPose); - }; - /** native declaration : headers\openvr_capi.h:1945 */ - public interface SetWorkingPlayAreaSize_callback extends Callback { - void apply(float sizeX, float sizeZ); - }; - /** native declaration : headers\openvr_capi.h:1946 */ - public interface SetWorkingCollisionBoundsInfo_callback extends Callback { - void apply(HmdQuad_t pQuadsBuffer, int unQuadsCount); - }; - /** native declaration : headers\openvr_capi.h:1947 */ - public interface SetWorkingSeatedZeroPoseToRawTrackingPose_callback extends Callback { - void apply(HmdMatrix34_t pMatSeatedZeroPoseToRawTrackingPose); - }; - /** native declaration : headers\openvr_capi.h:1948 */ - public interface SetWorkingStandingZeroPoseToRawTrackingPose_callback extends Callback { - void apply(HmdMatrix34_t pMatStandingZeroPoseToRawTrackingPose); - }; - /** native declaration : headers\openvr_capi.h:1949 */ - public interface ReloadFromDisk_callback extends Callback { - void apply(int configFile); - }; - /** native declaration : headers\openvr_capi.h:1950 */ - public interface GetLiveSeatedZeroPoseToRawTrackingPose_callback extends Callback { - byte apply(HmdMatrix34_t pmatSeatedZeroPoseToRawTrackingPose); - }; - /** native declaration : headers\openvr_capi.h:1951 */ - public interface SetWorkingCollisionBoundsTagsInfo_callback extends Callback { - void apply(Pointer pTagsBuffer, int unTagCount); - }; - /** native declaration : headers\openvr_capi.h:1952 */ - public interface GetLiveCollisionBoundsTagsInfo_callback extends Callback { - byte apply(Pointer pTagsBuffer, IntByReference punTagCount); - }; - /** native declaration : headers\openvr_capi.h:1953 */ - public interface SetWorkingPhysicalBoundsInfo_callback extends Callback { - byte apply(HmdQuad_t pQuadsBuffer, int unQuadsCount); - }; - /** native declaration : headers\openvr_capi.h:1954 */ - public interface GetLivePhysicalBoundsInfo_callback extends Callback { - byte apply(HmdQuad_t pQuadsBuffer, IntByReference punQuadsCount); - }; - /** native declaration : headers\openvr_capi.h:1955 */ - public interface ExportLiveToBuffer_callback extends Callback { - byte apply(Pointer pBuffer, IntByReference pnBufferLength); - }; - /** native declaration : headers\openvr_capi.h:1956 */ - public interface ImportFromBufferToWorking_callback extends Callback { - byte apply(Pointer pBuffer, int nImportFlags); - }; - public VR_IVRChaperoneSetup_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("CommitWorkingCopy", "RevertWorkingCopy", "GetWorkingPlayAreaSize", "GetWorkingPlayAreaRect", "GetWorkingCollisionBoundsInfo", "GetLiveCollisionBoundsInfo", "GetWorkingSeatedZeroPoseToRawTrackingPose", "GetWorkingStandingZeroPoseToRawTrackingPose", "SetWorkingPlayAreaSize", "SetWorkingCollisionBoundsInfo", "SetWorkingSeatedZeroPoseToRawTrackingPose", "SetWorkingStandingZeroPoseToRawTrackingPose", "ReloadFromDisk", "GetLiveSeatedZeroPoseToRawTrackingPose", "SetWorkingCollisionBoundsTagsInfo", "GetLiveCollisionBoundsTagsInfo", "SetWorkingPhysicalBoundsInfo", "GetLivePhysicalBoundsInfo", "ExportLiveToBuffer", "ImportFromBufferToWorking"); - } - public VR_IVRChaperoneSetup_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRChaperoneSetup_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRChaperoneSetup_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperone_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperone_FnTable.java deleted file mode 100644 index 6d71c06af7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRChaperone_FnTable.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.FloatByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1915
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRChaperone_FnTable extends Structure { - /** C type : GetCalibrationState_callback* */ - public VR_IVRChaperone_FnTable.GetCalibrationState_callback GetCalibrationState; - /** C type : GetPlayAreaSize_callback* */ - public VR_IVRChaperone_FnTable.GetPlayAreaSize_callback GetPlayAreaSize; - /** C type : GetPlayAreaRect_callback* */ - public VR_IVRChaperone_FnTable.GetPlayAreaRect_callback GetPlayAreaRect; - /** C type : ReloadInfo_callback* */ - public VR_IVRChaperone_FnTable.ReloadInfo_callback ReloadInfo; - /** C type : SetSceneColor_callback* */ - public VR_IVRChaperone_FnTable.SetSceneColor_callback SetSceneColor; - /** C type : GetBoundsColor_callback* */ - public VR_IVRChaperone_FnTable.GetBoundsColor_callback GetBoundsColor; - /** C type : AreBoundsVisible_callback* */ - public VR_IVRChaperone_FnTable.AreBoundsVisible_callback AreBoundsVisible; - /** C type : ForceBoundsVisible_callback* */ - public VR_IVRChaperone_FnTable.ForceBoundsVisible_callback ForceBoundsVisible; - /** native declaration : headers\openvr_capi.h:1907 */ - public interface GetCalibrationState_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:1908 */ - public interface GetPlayAreaSize_callback extends Callback { - byte apply(FloatByReference pSizeX, FloatByReference pSizeZ); - }; - /** native declaration : headers\openvr_capi.h:1909 */ - public interface GetPlayAreaRect_callback extends Callback { - byte apply(HmdQuad_t rect); - }; - /** native declaration : headers\openvr_capi.h:1910 */ - public interface ReloadInfo_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:1911 */ - public interface SetSceneColor_callback extends Callback { - void apply(HmdColor_t.ByValue color); - }; - /** native declaration : headers\openvr_capi.h:1912 */ - public interface GetBoundsColor_callback extends Callback { - void apply(HmdColor_t pOutputColorArray, int nNumOutputColors, float flCollisionBoundsFadeDistance, HmdColor_t pOutputCameraColor); - }; - /** native declaration : headers\openvr_capi.h:1913 */ - public interface AreBoundsVisible_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1914 */ - public interface ForceBoundsVisible_callback extends Callback { - void apply(byte bForce); - }; - public VR_IVRChaperone_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetCalibrationState", "GetPlayAreaSize", "GetPlayAreaRect", "ReloadInfo", "SetSceneColor", "GetBoundsColor", "AreBoundsVisible", "ForceBoundsVisible"); - } - /** - * @param GetCalibrationState C type : GetCalibrationState_callback*
                  - * @param GetPlayAreaSize C type : GetPlayAreaSize_callback*
                  - * @param GetPlayAreaRect C type : GetPlayAreaRect_callback*
                  - * @param ReloadInfo C type : ReloadInfo_callback*
                  - * @param SetSceneColor C type : SetSceneColor_callback*
                  - * @param GetBoundsColor C type : GetBoundsColor_callback*
                  - * @param AreBoundsVisible C type : AreBoundsVisible_callback*
                  - * @param ForceBoundsVisible C type : ForceBoundsVisible_callback* - */ - public VR_IVRChaperone_FnTable(VR_IVRChaperone_FnTable.GetCalibrationState_callback GetCalibrationState, VR_IVRChaperone_FnTable.GetPlayAreaSize_callback GetPlayAreaSize, VR_IVRChaperone_FnTable.GetPlayAreaRect_callback GetPlayAreaRect, VR_IVRChaperone_FnTable.ReloadInfo_callback ReloadInfo, VR_IVRChaperone_FnTable.SetSceneColor_callback SetSceneColor, VR_IVRChaperone_FnTable.GetBoundsColor_callback GetBoundsColor, VR_IVRChaperone_FnTable.AreBoundsVisible_callback AreBoundsVisible, VR_IVRChaperone_FnTable.ForceBoundsVisible_callback ForceBoundsVisible) { - super(); - this.GetCalibrationState = GetCalibrationState; - this.GetPlayAreaSize = GetPlayAreaSize; - this.GetPlayAreaRect = GetPlayAreaRect; - this.ReloadInfo = ReloadInfo; - this.SetSceneColor = SetSceneColor; - this.GetBoundsColor = GetBoundsColor; - this.AreBoundsVisible = AreBoundsVisible; - this.ForceBoundsVisible = ForceBoundsVisible; - } - public VR_IVRChaperone_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRChaperone_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRChaperone_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRCompositor_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRCompositor_FnTable.java deleted file mode 100644 index f4154dfdac..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRCompositor_FnTable.java +++ /dev/null @@ -1,290 +0,0 @@ -package com.jme3.system.jopenvr; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkPhysicalDevice_T; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2045
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRCompositor_FnTable extends Structure { - /** C type : SetTrackingSpace_callback* */ - public VR_IVRCompositor_FnTable.SetTrackingSpace_callback SetTrackingSpace; - /** C type : GetTrackingSpace_callback* */ - public VR_IVRCompositor_FnTable.GetTrackingSpace_callback GetTrackingSpace; - /** C type : WaitGetPoses_callback* */ - public VR_IVRCompositor_FnTable.WaitGetPoses_callback WaitGetPoses; - /** C type : GetLastPoses_callback* */ - public VR_IVRCompositor_FnTable.GetLastPoses_callback GetLastPoses; - /** C type : GetLastPoseForTrackedDeviceIndex_callback* */ - public VR_IVRCompositor_FnTable.GetLastPoseForTrackedDeviceIndex_callback GetLastPoseForTrackedDeviceIndex; - /** C type : Submit_callback* */ - public VR_IVRCompositor_FnTable.Submit_callback Submit; - /** C type : ClearLastSubmittedFrame_callback* */ - public VR_IVRCompositor_FnTable.ClearLastSubmittedFrame_callback ClearLastSubmittedFrame; - /** C type : PostPresentHandoff_callback* */ - public VR_IVRCompositor_FnTable.PostPresentHandoff_callback PostPresentHandoff; - /** C type : GetFrameTiming_callback* */ - public VR_IVRCompositor_FnTable.GetFrameTiming_callback GetFrameTiming; - /** C type : GetFrameTimings_callback* */ - public VR_IVRCompositor_FnTable.GetFrameTimings_callback GetFrameTimings; - /** C type : GetFrameTimeRemaining_callback* */ - public VR_IVRCompositor_FnTable.GetFrameTimeRemaining_callback GetFrameTimeRemaining; - /** C type : GetCumulativeStats_callback* */ - public VR_IVRCompositor_FnTable.GetCumulativeStats_callback GetCumulativeStats; - /** C type : FadeToColor_callback* */ - public VR_IVRCompositor_FnTable.FadeToColor_callback FadeToColor; - /** C type : GetCurrentFadeColor_callback* */ - public VR_IVRCompositor_FnTable.GetCurrentFadeColor_callback GetCurrentFadeColor; - /** C type : FadeGrid_callback* */ - public VR_IVRCompositor_FnTable.FadeGrid_callback FadeGrid; - /** C type : GetCurrentGridAlpha_callback* */ - public VR_IVRCompositor_FnTable.GetCurrentGridAlpha_callback GetCurrentGridAlpha; - /** C type : SetSkyboxOverride_callback* */ - public VR_IVRCompositor_FnTable.SetSkyboxOverride_callback SetSkyboxOverride; - /** C type : ClearSkyboxOverride_callback* */ - public VR_IVRCompositor_FnTable.ClearSkyboxOverride_callback ClearSkyboxOverride; - /** C type : CompositorBringToFront_callback* */ - public VR_IVRCompositor_FnTable.CompositorBringToFront_callback CompositorBringToFront; - /** C type : CompositorGoToBack_callback* */ - public VR_IVRCompositor_FnTable.CompositorGoToBack_callback CompositorGoToBack; - /** C type : CompositorQuit_callback* */ - public VR_IVRCompositor_FnTable.CompositorQuit_callback CompositorQuit; - /** C type : IsFullscreen_callback* */ - public VR_IVRCompositor_FnTable.IsFullscreen_callback IsFullscreen; - /** C type : GetCurrentSceneFocusProcess_callback* */ - public VR_IVRCompositor_FnTable.GetCurrentSceneFocusProcess_callback GetCurrentSceneFocusProcess; - /** C type : GetLastFrameRenderer_callback* */ - public VR_IVRCompositor_FnTable.GetLastFrameRenderer_callback GetLastFrameRenderer; - /** C type : CanRenderScene_callback* */ - public VR_IVRCompositor_FnTable.CanRenderScene_callback CanRenderScene; - /** C type : ShowMirrorWindow_callback* */ - public VR_IVRCompositor_FnTable.ShowMirrorWindow_callback ShowMirrorWindow; - /** C type : HideMirrorWindow_callback* */ - public VR_IVRCompositor_FnTable.HideMirrorWindow_callback HideMirrorWindow; - /** C type : IsMirrorWindowVisible_callback* */ - public VR_IVRCompositor_FnTable.IsMirrorWindowVisible_callback IsMirrorWindowVisible; - /** C type : CompositorDumpImages_callback* */ - public VR_IVRCompositor_FnTable.CompositorDumpImages_callback CompositorDumpImages; - /** C type : ShouldAppRenderWithLowResources_callback* */ - public VR_IVRCompositor_FnTable.ShouldAppRenderWithLowResources_callback ShouldAppRenderWithLowResources; - /** C type : ForceInterleavedReprojectionOn_callback* */ - public VR_IVRCompositor_FnTable.ForceInterleavedReprojectionOn_callback ForceInterleavedReprojectionOn; - /** C type : ForceReconnectProcess_callback* */ - public VR_IVRCompositor_FnTable.ForceReconnectProcess_callback ForceReconnectProcess; - /** C type : SuspendRendering_callback* */ - public VR_IVRCompositor_FnTable.SuspendRendering_callback SuspendRendering; - /** C type : GetMirrorTextureD3D11_callback* */ - public VR_IVRCompositor_FnTable.GetMirrorTextureD3D11_callback GetMirrorTextureD3D11; - /** C type : ReleaseMirrorTextureD3D11_callback* */ - public VR_IVRCompositor_FnTable.ReleaseMirrorTextureD3D11_callback ReleaseMirrorTextureD3D11; - /** C type : GetMirrorTextureGL_callback* */ - public VR_IVRCompositor_FnTable.GetMirrorTextureGL_callback GetMirrorTextureGL; - /** C type : ReleaseSharedGLTexture_callback* */ - public VR_IVRCompositor_FnTable.ReleaseSharedGLTexture_callback ReleaseSharedGLTexture; - /** C type : LockGLSharedTextureForAccess_callback* */ - public VR_IVRCompositor_FnTable.LockGLSharedTextureForAccess_callback LockGLSharedTextureForAccess; - /** C type : UnlockGLSharedTextureForAccess_callback* */ - public VR_IVRCompositor_FnTable.UnlockGLSharedTextureForAccess_callback UnlockGLSharedTextureForAccess; - /** C type : GetVulkanInstanceExtensionsRequired_callback* */ - public VR_IVRCompositor_FnTable.GetVulkanInstanceExtensionsRequired_callback GetVulkanInstanceExtensionsRequired; - /** C type : GetVulkanDeviceExtensionsRequired_callback* */ - public VR_IVRCompositor_FnTable.GetVulkanDeviceExtensionsRequired_callback GetVulkanDeviceExtensionsRequired; - /** C type : SetExplicitTimingMode_callback* */ - public VR_IVRCompositor_FnTable.SetExplicitTimingMode_callback SetExplicitTimingMode; - /** C type : SubmitExplicitTimingData_callback* */ - public VR_IVRCompositor_FnTable.SubmitExplicitTimingData_callback SubmitExplicitTimingData; - /** native declaration : headers\openvr_capi.h:2002 */ - public interface SetTrackingSpace_callback extends Callback { - void apply(int eOrigin); - }; - /** native declaration : headers\openvr_capi.h:2003 */ - public interface GetTrackingSpace_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2004 */ - public interface WaitGetPoses_callback extends Callback { - int apply(TrackedDevicePose_t pRenderPoseArray, int unRenderPoseArrayCount, TrackedDevicePose_t pGamePoseArray, int unGamePoseArrayCount); - }; - /** native declaration : headers\openvr_capi.h:2005 */ - public interface GetLastPoses_callback extends Callback { - int apply(TrackedDevicePose_t pRenderPoseArray, int unRenderPoseArrayCount, TrackedDevicePose_t pGamePoseArray, int unGamePoseArrayCount); - }; - /** native declaration : headers\openvr_capi.h:2006 */ - public interface GetLastPoseForTrackedDeviceIndex_callback extends Callback { - int apply(int unDeviceIndex, TrackedDevicePose_t pOutputPose, TrackedDevicePose_t pOutputGamePose); - }; - /** native declaration : headers\openvr_capi.h:2007 */ - public interface Submit_callback extends Callback { - int apply(int eEye, Texture_t pTexture, VRTextureBounds_t pBounds, int nSubmitFlags); - }; - /** native declaration : headers\openvr_capi.h:2008 */ - public interface ClearLastSubmittedFrame_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2009 */ - public interface PostPresentHandoff_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2010 */ - public interface GetFrameTiming_callback extends Callback { - byte apply(Compositor_FrameTiming pTiming, int unFramesAgo); - }; - /** native declaration : headers\openvr_capi.h:2011 */ - public interface GetFrameTimings_callback extends Callback { - int apply(Compositor_FrameTiming pTiming, int nFrames); - }; - /** native declaration : headers\openvr_capi.h:2012 */ - public interface GetFrameTimeRemaining_callback extends Callback { - float apply(); - }; - /** native declaration : headers\openvr_capi.h:2013 */ - public interface GetCumulativeStats_callback extends Callback { - void apply(Compositor_CumulativeStats pStats, int nStatsSizeInBytes); - }; - /** native declaration : headers\openvr_capi.h:2014 */ - public interface FadeToColor_callback extends Callback { - void apply(float fSeconds, float fRed, float fGreen, float fBlue, float fAlpha, byte bBackground); - }; - /** native declaration : headers\openvr_capi.h:2015 */ - public interface GetCurrentFadeColor_callback extends Callback { - com.jme3.system.jopenvr.HmdColor_t.ByValue apply(byte bBackground); - }; - /** native declaration : headers\openvr_capi.h:2016 */ - public interface FadeGrid_callback extends Callback { - void apply(float fSeconds, byte bFadeIn); - }; - /** native declaration : headers\openvr_capi.h:2017 */ - public interface GetCurrentGridAlpha_callback extends Callback { - float apply(); - }; - /** native declaration : headers\openvr_capi.h:2018 */ - public interface SetSkyboxOverride_callback extends Callback { - int apply(Texture_t pTextures, int unTextureCount); - }; - /** native declaration : headers\openvr_capi.h:2019 */ - public interface ClearSkyboxOverride_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2020 */ - public interface CompositorBringToFront_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2021 */ - public interface CompositorGoToBack_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2022 */ - public interface CompositorQuit_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2023 */ - public interface IsFullscreen_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:2024 */ - public interface GetCurrentSceneFocusProcess_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2025 */ - public interface GetLastFrameRenderer_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2026 */ - public interface CanRenderScene_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:2027 */ - public interface ShowMirrorWindow_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2028 */ - public interface HideMirrorWindow_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2029 */ - public interface IsMirrorWindowVisible_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:2030 */ - public interface CompositorDumpImages_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2031 */ - public interface ShouldAppRenderWithLowResources_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:2032 */ - public interface ForceInterleavedReprojectionOn_callback extends Callback { - void apply(byte bOverride); - }; - /** native declaration : headers\openvr_capi.h:2033 */ - public interface ForceReconnectProcess_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2034 */ - public interface SuspendRendering_callback extends Callback { - void apply(byte bSuspend); - }; - /** native declaration : headers\openvr_capi.h:2035 */ - public interface GetMirrorTextureD3D11_callback extends Callback { - int apply(int eEye, Pointer pD3D11DeviceOrResource, PointerByReference ppD3D11ShaderResourceView); - }; - /** native declaration : headers\openvr_capi.h:2036 */ - public interface ReleaseMirrorTextureD3D11_callback extends Callback { - void apply(Pointer pD3D11ShaderResourceView); - }; - /** native declaration : headers\openvr_capi.h:2037 */ - public interface GetMirrorTextureGL_callback extends Callback { - int apply(int eEye, IntByReference pglTextureId, PointerByReference pglSharedTextureHandle); - }; - /** native declaration : headers\openvr_capi.h:2038 */ - public interface ReleaseSharedGLTexture_callback extends Callback { - byte apply(int glTextureId, Pointer glSharedTextureHandle); - }; - /** native declaration : headers\openvr_capi.h:2039 */ - public interface LockGLSharedTextureForAccess_callback extends Callback { - void apply(Pointer glSharedTextureHandle); - }; - /** native declaration : headers\openvr_capi.h:2040 */ - public interface UnlockGLSharedTextureForAccess_callback extends Callback { - void apply(Pointer glSharedTextureHandle); - }; - /** native declaration : headers\openvr_capi.h:2041 */ - public interface GetVulkanInstanceExtensionsRequired_callback extends Callback { - int apply(Pointer pchValue, int unBufferSize); - }; - /** native declaration : headers\openvr_capi.h:2042 */ - public interface GetVulkanDeviceExtensionsRequired_callback extends Callback { - int apply(VkPhysicalDevice_T pPhysicalDevice, Pointer pchValue, int unBufferSize); - }; - /** native declaration : headers\openvr_capi.h:2043 */ - public interface SetExplicitTimingMode_callback extends Callback { - void apply(int eTimingMode); - }; - /** native declaration : headers\openvr_capi.h:2044 */ - public interface SubmitExplicitTimingData_callback extends Callback { - int apply(); - }; - public VR_IVRCompositor_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("SetTrackingSpace", "GetTrackingSpace", "WaitGetPoses", "GetLastPoses", "GetLastPoseForTrackedDeviceIndex", "Submit", "ClearLastSubmittedFrame", "PostPresentHandoff", "GetFrameTiming", "GetFrameTimings", "GetFrameTimeRemaining", "GetCumulativeStats", "FadeToColor", "GetCurrentFadeColor", "FadeGrid", "GetCurrentGridAlpha", "SetSkyboxOverride", "ClearSkyboxOverride", "CompositorBringToFront", "CompositorGoToBack", "CompositorQuit", "IsFullscreen", "GetCurrentSceneFocusProcess", "GetLastFrameRenderer", "CanRenderScene", "ShowMirrorWindow", "HideMirrorWindow", "IsMirrorWindowVisible", "CompositorDumpImages", "ShouldAppRenderWithLowResources", "ForceInterleavedReprojectionOn", "ForceReconnectProcess", "SuspendRendering", "GetMirrorTextureD3D11", "ReleaseMirrorTextureD3D11", "GetMirrorTextureGL", "ReleaseSharedGLTexture", "LockGLSharedTextureForAccess", "UnlockGLSharedTextureForAccess", "GetVulkanInstanceExtensionsRequired", "GetVulkanDeviceExtensionsRequired", "SetExplicitTimingMode", "SubmitExplicitTimingData"); - } - public VR_IVRCompositor_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRCompositor_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRCompositor_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRDriverManager_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRDriverManager_FnTable.java deleted file mode 100644 index f8e926a1c7..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRDriverManager_FnTable.java +++ /dev/null @@ -1,58 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2313
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRDriverManager_FnTable extends Structure { - /** C type : GetDriverCount_callback* */ - public VR_IVRDriverManager_FnTable.GetDriverCount_callback GetDriverCount; - /** C type : GetDriverName_callback* */ - public VR_IVRDriverManager_FnTable.GetDriverName_callback GetDriverName; - /** C type : GetDriverHandle_callback* */ - public VR_IVRDriverManager_FnTable.GetDriverHandle_callback GetDriverHandle; - /** native declaration : headers\openvr_capi.h:2310 */ - public interface GetDriverCount_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2311 */ - public interface GetDriverName_callback extends Callback { - int apply(int nDriver, Pointer pchValue, int unBufferSize); - }; - /** native declaration : headers\openvr_capi.h:2312 */ - public interface GetDriverHandle_callback extends Callback { - long apply(Pointer pchDriverName); - }; - public VR_IVRDriverManager_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetDriverCount", "GetDriverName", "GetDriverHandle"); - } - /** - * @param GetDriverCount C type : GetDriverCount_callback*
                  - * @param GetDriverName C type : GetDriverName_callback*
                  - * @param GetDriverHandle C type : GetDriverHandle_callback* - */ - public VR_IVRDriverManager_FnTable(VR_IVRDriverManager_FnTable.GetDriverCount_callback GetDriverCount, VR_IVRDriverManager_FnTable.GetDriverName_callback GetDriverName, VR_IVRDriverManager_FnTable.GetDriverHandle_callback GetDriverHandle) { - super(); - this.GetDriverCount = GetDriverCount; - this.GetDriverName = GetDriverName; - this.GetDriverHandle = GetDriverHandle; - } - public VR_IVRDriverManager_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRDriverManager_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRDriverManager_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRExtendedDisplay_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRExtendedDisplay_FnTable.java deleted file mode 100644 index de9b54af2e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRExtendedDisplay_FnTable.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1807
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRExtendedDisplay_FnTable extends Structure { - /** C type : GetWindowBounds_callback* */ - public VR_IVRExtendedDisplay_FnTable.GetWindowBounds_callback GetWindowBounds; - /** C type : GetEyeOutputViewport_callback* */ - public VR_IVRExtendedDisplay_FnTable.GetEyeOutputViewport_callback GetEyeOutputViewport; - /** C type : GetDXGIOutputInfo_callback* */ - public VR_IVRExtendedDisplay_FnTable.GetDXGIOutputInfo_callback GetDXGIOutputInfo; - /** native declaration : headers\openvr_capi.h:1804 */ - public interface GetWindowBounds_callback extends Callback { - void apply(IntByReference pnX, IntByReference pnY, IntByReference pnWidth, IntByReference pnHeight); - }; - /** native declaration : headers\openvr_capi.h:1805 */ - public interface GetEyeOutputViewport_callback extends Callback { - void apply(int eEye, IntByReference pnX, IntByReference pnY, IntByReference pnWidth, IntByReference pnHeight); - }; - /** native declaration : headers\openvr_capi.h:1806 */ - public interface GetDXGIOutputInfo_callback extends Callback { - void apply(IntByReference pnAdapterIndex, IntByReference pnAdapterOutputIndex); - }; - public VR_IVRExtendedDisplay_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetWindowBounds", "GetEyeOutputViewport", "GetDXGIOutputInfo"); - } - /** - * @param GetWindowBounds C type : GetWindowBounds_callback*
                  - * @param GetEyeOutputViewport C type : GetEyeOutputViewport_callback*
                  - * @param GetDXGIOutputInfo C type : GetDXGIOutputInfo_callback* - */ - public VR_IVRExtendedDisplay_FnTable(VR_IVRExtendedDisplay_FnTable.GetWindowBounds_callback GetWindowBounds, VR_IVRExtendedDisplay_FnTable.GetEyeOutputViewport_callback GetEyeOutputViewport, VR_IVRExtendedDisplay_FnTable.GetDXGIOutputInfo_callback GetDXGIOutputInfo) { - super(); - this.GetWindowBounds = GetWindowBounds; - this.GetEyeOutputViewport = GetEyeOutputViewport; - this.GetDXGIOutputInfo = GetDXGIOutputInfo; - } - public VR_IVRExtendedDisplay_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRExtendedDisplay_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRExtendedDisplay_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRIOBuffer_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRIOBuffer_FnTable.java deleted file mode 100644 index 7237387299..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRIOBuffer_FnTable.java +++ /dev/null @@ -1,76 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2363
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRIOBuffer_FnTable extends Structure { - /** C type : Open_callback* */ - public VR_IVRIOBuffer_FnTable.Open_callback Open; - /** C type : Close_callback* */ - public VR_IVRIOBuffer_FnTable.Close_callback Close; - /** C type : Read_callback* */ - public VR_IVRIOBuffer_FnTable.Read_callback Read; - /** C type : Write_callback* */ - public VR_IVRIOBuffer_FnTable.Write_callback Write; - /** C type : PropertyContainer_callback* */ - public VR_IVRIOBuffer_FnTable.PropertyContainer_callback PropertyContainer; - /** native declaration : headers\openvr_capi.h:2358 */ - public interface Open_callback extends Callback { - int apply(Pointer pchPath, int mode, int unElementSize, int unElements, LongByReference pulBuffer); - }; - /** native declaration : headers\openvr_capi.h:2359 */ - public interface Close_callback extends Callback { - int apply(long ulBuffer); - }; - /** native declaration : headers\openvr_capi.h:2360 */ - public interface Read_callback extends Callback { - int apply(long ulBuffer, Pointer pDst, int unBytes, IntByReference punRead); - }; - /** native declaration : headers\openvr_capi.h:2361 */ - public interface Write_callback extends Callback { - int apply(long ulBuffer, Pointer pSrc, int unBytes); - }; - /** native declaration : headers\openvr_capi.h:2362 */ - public interface PropertyContainer_callback extends Callback { - long apply(long ulBuffer); - }; - public VR_IVRIOBuffer_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("Open", "Close", "Read", "Write", "PropertyContainer"); - } - /** - * @param Open C type : Open_callback*
                  - * @param Close C type : Close_callback*
                  - * @param Read C type : Read_callback*
                  - * @param Write C type : Write_callback*
                  - * @param PropertyContainer C type : PropertyContainer_callback* - */ - public VR_IVRIOBuffer_FnTable(VR_IVRIOBuffer_FnTable.Open_callback Open, VR_IVRIOBuffer_FnTable.Close_callback Close, VR_IVRIOBuffer_FnTable.Read_callback Read, VR_IVRIOBuffer_FnTable.Write_callback Write, VR_IVRIOBuffer_FnTable.PropertyContainer_callback PropertyContainer) { - super(); - this.Open = Open; - this.Close = Close; - this.Read = Read; - this.Write = Write; - this.PropertyContainer = PropertyContainer; - } - public VR_IVRIOBuffer_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRIOBuffer_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRIOBuffer_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRInput_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRInput_FnTable.java deleted file mode 100644 index c7fcb0aabd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRInput_FnTable.java +++ /dev/null @@ -1,139 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2351
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRInput_FnTable extends Structure { - /** C type : SetActionManifestPath_callback* */ - public VR_IVRInput_FnTable.SetActionManifestPath_callback SetActionManifestPath; - /** C type : GetActionSetHandle_callback* */ - public VR_IVRInput_FnTable.GetActionSetHandle_callback GetActionSetHandle; - /** C type : GetActionHandle_callback* */ - public VR_IVRInput_FnTable.GetActionHandle_callback GetActionHandle; - /** C type : GetInputSourceHandle_callback* */ - public VR_IVRInput_FnTable.GetInputSourceHandle_callback GetInputSourceHandle; - /** C type : UpdateActionState_callback* */ - public VR_IVRInput_FnTable.UpdateActionState_callback UpdateActionState; - /** C type : GetDigitalActionData_callback* */ - public VR_IVRInput_FnTable.GetDigitalActionData_callback GetDigitalActionData; - /** C type : GetAnalogActionData_callback* */ - public VR_IVRInput_FnTable.GetAnalogActionData_callback GetAnalogActionData; - /** C type : GetPoseActionData_callback* */ - public VR_IVRInput_FnTable.GetPoseActionData_callback GetPoseActionData; - /** C type : GetSkeletalActionData_callback* */ - public VR_IVRInput_FnTable.GetSkeletalActionData_callback GetSkeletalActionData; - /** C type : GetSkeletalBoneData_callback* */ - public VR_IVRInput_FnTable.GetSkeletalBoneData_callback GetSkeletalBoneData; - /** C type : GetSkeletalBoneDataCompressed_callback* */ - public VR_IVRInput_FnTable.GetSkeletalBoneDataCompressed_callback GetSkeletalBoneDataCompressed; - /** C type : DecompressSkeletalBoneData_callback* */ - public VR_IVRInput_FnTable.DecompressSkeletalBoneData_callback DecompressSkeletalBoneData; - /** C type : TriggerHapticVibrationAction_callback* */ - public VR_IVRInput_FnTable.TriggerHapticVibrationAction_callback TriggerHapticVibrationAction; - /** C type : GetActionOrigins_callback* */ - public VR_IVRInput_FnTable.GetActionOrigins_callback GetActionOrigins; - /** C type : GetOriginLocalizedName_callback* */ - public VR_IVRInput_FnTable.GetOriginLocalizedName_callback GetOriginLocalizedName; - /** C type : GetOriginTrackedDeviceInfo_callback* */ - public VR_IVRInput_FnTable.GetOriginTrackedDeviceInfo_callback GetOriginTrackedDeviceInfo; - /** C type : ShowActionOrigins_callback* */ - public VR_IVRInput_FnTable.ShowActionOrigins_callback ShowActionOrigins; - /** C type : ShowBindingsForActionSet_callback* */ - public VR_IVRInput_FnTable.ShowBindingsForActionSet_callback ShowBindingsForActionSet; - /** native declaration : headers\openvr_capi.h:2333 */ - public interface SetActionManifestPath_callback extends Callback { - int apply(Pointer pchActionManifestPath); - }; - /** native declaration : headers\openvr_capi.h:2334 */ - public interface GetActionSetHandle_callback extends Callback { - int apply(Pointer pchActionSetName, LongByReference pHandle); - }; - /** native declaration : headers\openvr_capi.h:2335 */ - public interface GetActionHandle_callback extends Callback { - int apply(Pointer pchActionName, LongByReference pHandle); - }; - /** native declaration : headers\openvr_capi.h:2336 */ - public interface GetInputSourceHandle_callback extends Callback { - int apply(Pointer pchInputSourcePath, LongByReference pHandle); - }; - /** native declaration : headers\openvr_capi.h:2337 */ - public interface UpdateActionState_callback extends Callback { - int apply(VRActiveActionSet_t pSets, int unSizeOfVRSelectedActionSet_t, int unSetCount); - }; - /** native declaration : headers\openvr_capi.h:2338 */ - public interface GetDigitalActionData_callback extends Callback { - int apply(long action, InputDigitalActionData_t pActionData, int unActionDataSize, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2339 */ - public interface GetAnalogActionData_callback extends Callback { - int apply(long action, InputAnalogActionData_t pActionData, int unActionDataSize, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2340 */ - public interface GetPoseActionData_callback extends Callback { - int apply(long action, int eOrigin, float fPredictedSecondsFromNow, InputPoseActionData_t pActionData, int unActionDataSize, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2341 */ - public interface GetSkeletalActionData_callback extends Callback { - int apply(long action, InputSkeletalActionData_t pActionData, int unActionDataSize, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2342 */ - public interface GetSkeletalBoneData_callback extends Callback { - int apply(long action, int eTransformSpace, int eMotionRange, VRBoneTransform_t pTransformArray, int unTransformArrayCount, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2343 */ - public interface GetSkeletalBoneDataCompressed_callback extends Callback { - int apply(long action, int eTransformSpace, int eMotionRange, Pointer pvCompressedData, int unCompressedSize, IntByReference punRequiredCompressedSize, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2344 */ - public interface DecompressSkeletalBoneData_callback extends Callback { - int apply(Pointer pvCompressedBuffer, int unCompressedBufferSize, IntByReference peTransformSpace, VRBoneTransform_t pTransformArray, int unTransformArrayCount); - }; - /** native declaration : headers\openvr_capi.h:2345 */ - public interface TriggerHapticVibrationAction_callback extends Callback { - int apply(long action, float fStartSecondsFromNow, float fDurationSeconds, float fFrequency, float fAmplitude, long ulRestrictToDevice); - }; - /** native declaration : headers\openvr_capi.h:2346 */ - public interface GetActionOrigins_callback extends Callback { - int apply(long actionSetHandle, long digitalActionHandle, LongByReference originsOut, int originOutCount); - }; - /** native declaration : headers\openvr_capi.h:2347 */ - public interface GetOriginLocalizedName_callback extends Callback { - int apply(long origin, Pointer pchNameArray, int unNameArraySize); - }; - /** native declaration : headers\openvr_capi.h:2348 */ - public interface GetOriginTrackedDeviceInfo_callback extends Callback { - int apply(long origin, InputOriginInfo_t pOriginInfo, int unOriginInfoSize); - }; - /** native declaration : headers\openvr_capi.h:2349 */ - public interface ShowActionOrigins_callback extends Callback { - int apply(long actionSetHandle, long ulActionHandle); - }; - /** native declaration : headers\openvr_capi.h:2350 */ - public interface ShowBindingsForActionSet_callback extends Callback { - int apply(VRActiveActionSet_t pSets, int unSizeOfVRSelectedActionSet_t, int unSetCount, long originToHighlight); - }; - public VR_IVRInput_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("SetActionManifestPath", "GetActionSetHandle", "GetActionHandle", "GetInputSourceHandle", "UpdateActionState", "GetDigitalActionData", "GetAnalogActionData", "GetPoseActionData", "GetSkeletalActionData", "GetSkeletalBoneData", "GetSkeletalBoneDataCompressed", "DecompressSkeletalBoneData", "TriggerHapticVibrationAction", "GetActionOrigins", "GetOriginLocalizedName", "GetOriginTrackedDeviceInfo", "ShowActionOrigins", "ShowBindingsForActionSet"); - } - public VR_IVRInput_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRInput_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRInput_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRNotifications_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRNotifications_FnTable.java deleted file mode 100644 index 5456f6f081..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRNotifications_FnTable.java +++ /dev/null @@ -1,51 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2257
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRNotifications_FnTable extends Structure { - /** C type : CreateNotification_callback* */ - public VR_IVRNotifications_FnTable.CreateNotification_callback CreateNotification; - /** C type : RemoveNotification_callback* */ - public VR_IVRNotifications_FnTable.RemoveNotification_callback RemoveNotification; - /** native declaration : headers\openvr_capi.h:2255 */ - public interface CreateNotification_callback extends Callback { - int apply(long ulOverlayHandle, long ulUserValue, int type, Pointer pchText, int style, NotificationBitmap_t pImage, IntByReference pNotificationId); - }; - /** native declaration : headers\openvr_capi.h:2256 */ - public interface RemoveNotification_callback extends Callback { - int apply(int notificationId); - }; - public VR_IVRNotifications_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("CreateNotification", "RemoveNotification"); - } - /** - * @param CreateNotification C type : CreateNotification_callback*
                  - * @param RemoveNotification C type : RemoveNotification_callback* - */ - public VR_IVRNotifications_FnTable(VR_IVRNotifications_FnTable.CreateNotification_callback CreateNotification, VR_IVRNotifications_FnTable.RemoveNotification_callback RemoveNotification) { - super(); - this.CreateNotification = CreateNotification; - this.RemoveNotification = RemoveNotification; - } - public VR_IVRNotifications_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRNotifications_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRNotifications_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVROverlay_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVROverlay_FnTable.java deleted file mode 100644 index 7dc6a2d408..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVROverlay_FnTable.java +++ /dev/null @@ -1,525 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; -import com.sun.jna.ptr.PointerByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2211
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVROverlay_FnTable extends Structure { - /** C type : FindOverlay_callback* */ - public VR_IVROverlay_FnTable.FindOverlay_callback FindOverlay; - /** C type : CreateOverlay_callback* */ - public VR_IVROverlay_FnTable.CreateOverlay_callback CreateOverlay; - /** C type : DestroyOverlay_callback* */ - public VR_IVROverlay_FnTable.DestroyOverlay_callback DestroyOverlay; - /** C type : SetHighQualityOverlay_callback* */ - public VR_IVROverlay_FnTable.SetHighQualityOverlay_callback SetHighQualityOverlay; - /** C type : GetHighQualityOverlay_callback* */ - public VR_IVROverlay_FnTable.GetHighQualityOverlay_callback GetHighQualityOverlay; - /** C type : GetOverlayKey_callback* */ - public VR_IVROverlay_FnTable.GetOverlayKey_callback GetOverlayKey; - /** C type : GetOverlayName_callback* */ - public VR_IVROverlay_FnTable.GetOverlayName_callback GetOverlayName; - /** C type : SetOverlayName_callback* */ - public VR_IVROverlay_FnTable.SetOverlayName_callback SetOverlayName; - /** C type : GetOverlayImageData_callback* */ - public VR_IVROverlay_FnTable.GetOverlayImageData_callback GetOverlayImageData; - /** C type : GetOverlayErrorNameFromEnum_callback* */ - public VR_IVROverlay_FnTable.GetOverlayErrorNameFromEnum_callback GetOverlayErrorNameFromEnum; - /** C type : SetOverlayRenderingPid_callback* */ - public VR_IVROverlay_FnTable.SetOverlayRenderingPid_callback SetOverlayRenderingPid; - /** C type : GetOverlayRenderingPid_callback* */ - public VR_IVROverlay_FnTable.GetOverlayRenderingPid_callback GetOverlayRenderingPid; - /** C type : SetOverlayFlag_callback* */ - public VR_IVROverlay_FnTable.SetOverlayFlag_callback SetOverlayFlag; - /** C type : GetOverlayFlag_callback* */ - public VR_IVROverlay_FnTable.GetOverlayFlag_callback GetOverlayFlag; - /** C type : SetOverlayColor_callback* */ - public VR_IVROverlay_FnTable.SetOverlayColor_callback SetOverlayColor; - /** C type : GetOverlayColor_callback* */ - public VR_IVROverlay_FnTable.GetOverlayColor_callback GetOverlayColor; - /** C type : SetOverlayAlpha_callback* */ - public VR_IVROverlay_FnTable.SetOverlayAlpha_callback SetOverlayAlpha; - /** C type : GetOverlayAlpha_callback* */ - public VR_IVROverlay_FnTable.GetOverlayAlpha_callback GetOverlayAlpha; - /** C type : SetOverlayTexelAspect_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTexelAspect_callback SetOverlayTexelAspect; - /** C type : GetOverlayTexelAspect_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTexelAspect_callback GetOverlayTexelAspect; - /** C type : SetOverlaySortOrder_callback* */ - public VR_IVROverlay_FnTable.SetOverlaySortOrder_callback SetOverlaySortOrder; - /** C type : GetOverlaySortOrder_callback* */ - public VR_IVROverlay_FnTable.GetOverlaySortOrder_callback GetOverlaySortOrder; - /** C type : SetOverlayWidthInMeters_callback* */ - public VR_IVROverlay_FnTable.SetOverlayWidthInMeters_callback SetOverlayWidthInMeters; - /** C type : GetOverlayWidthInMeters_callback* */ - public VR_IVROverlay_FnTable.GetOverlayWidthInMeters_callback GetOverlayWidthInMeters; - /** C type : SetOverlayAutoCurveDistanceRangeInMeters_callback* */ - public VR_IVROverlay_FnTable.SetOverlayAutoCurveDistanceRangeInMeters_callback SetOverlayAutoCurveDistanceRangeInMeters; - /** C type : GetOverlayAutoCurveDistanceRangeInMeters_callback* */ - public VR_IVROverlay_FnTable.GetOverlayAutoCurveDistanceRangeInMeters_callback GetOverlayAutoCurveDistanceRangeInMeters; - /** C type : SetOverlayTextureColorSpace_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTextureColorSpace_callback SetOverlayTextureColorSpace; - /** C type : GetOverlayTextureColorSpace_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTextureColorSpace_callback GetOverlayTextureColorSpace; - /** C type : SetOverlayTextureBounds_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTextureBounds_callback SetOverlayTextureBounds; - /** C type : GetOverlayTextureBounds_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTextureBounds_callback GetOverlayTextureBounds; - /** C type : GetOverlayRenderModel_callback* */ - public VR_IVROverlay_FnTable.GetOverlayRenderModel_callback GetOverlayRenderModel; - /** C type : SetOverlayRenderModel_callback* */ - public VR_IVROverlay_FnTable.SetOverlayRenderModel_callback SetOverlayRenderModel; - /** C type : GetOverlayTransformType_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTransformType_callback GetOverlayTransformType; - /** C type : SetOverlayTransformAbsolute_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTransformAbsolute_callback SetOverlayTransformAbsolute; - /** C type : GetOverlayTransformAbsolute_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTransformAbsolute_callback GetOverlayTransformAbsolute; - /** C type : SetOverlayTransformTrackedDeviceRelative_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTransformTrackedDeviceRelative_callback SetOverlayTransformTrackedDeviceRelative; - /** C type : GetOverlayTransformTrackedDeviceRelative_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTransformTrackedDeviceRelative_callback GetOverlayTransformTrackedDeviceRelative; - /** C type : SetOverlayTransformTrackedDeviceComponent_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTransformTrackedDeviceComponent_callback SetOverlayTransformTrackedDeviceComponent; - /** C type : GetOverlayTransformTrackedDeviceComponent_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTransformTrackedDeviceComponent_callback GetOverlayTransformTrackedDeviceComponent; - /** C type : GetOverlayTransformOverlayRelative_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTransformOverlayRelative_callback GetOverlayTransformOverlayRelative; - /** C type : SetOverlayTransformOverlayRelative_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTransformOverlayRelative_callback SetOverlayTransformOverlayRelative; - /** C type : ShowOverlay_callback* */ - public VR_IVROverlay_FnTable.ShowOverlay_callback ShowOverlay; - /** C type : HideOverlay_callback* */ - public VR_IVROverlay_FnTable.HideOverlay_callback HideOverlay; - /** C type : IsOverlayVisible_callback* */ - public VR_IVROverlay_FnTable.IsOverlayVisible_callback IsOverlayVisible; - /** C type : GetTransformForOverlayCoordinates_callback* */ - public VR_IVROverlay_FnTable.GetTransformForOverlayCoordinates_callback GetTransformForOverlayCoordinates; - /** C type : PollNextOverlayEvent_callback* */ - public VR_IVROverlay_FnTable.PollNextOverlayEvent_callback PollNextOverlayEvent; - /** C type : GetOverlayInputMethod_callback* */ - public VR_IVROverlay_FnTable.GetOverlayInputMethod_callback GetOverlayInputMethod; - /** C type : SetOverlayInputMethod_callback* */ - public VR_IVROverlay_FnTable.SetOverlayInputMethod_callback SetOverlayInputMethod; - /** C type : GetOverlayMouseScale_callback* */ - public VR_IVROverlay_FnTable.GetOverlayMouseScale_callback GetOverlayMouseScale; - /** C type : SetOverlayMouseScale_callback* */ - public VR_IVROverlay_FnTable.SetOverlayMouseScale_callback SetOverlayMouseScale; - /** C type : ComputeOverlayIntersection_callback* */ - public VR_IVROverlay_FnTable.ComputeOverlayIntersection_callback ComputeOverlayIntersection; - /** C type : IsHoverTargetOverlay_callback* */ - public VR_IVROverlay_FnTable.IsHoverTargetOverlay_callback IsHoverTargetOverlay; - /** C type : GetGamepadFocusOverlay_callback* */ - public VR_IVROverlay_FnTable.GetGamepadFocusOverlay_callback GetGamepadFocusOverlay; - /** C type : SetGamepadFocusOverlay_callback* */ - public VR_IVROverlay_FnTable.SetGamepadFocusOverlay_callback SetGamepadFocusOverlay; - /** C type : SetOverlayNeighbor_callback* */ - public VR_IVROverlay_FnTable.SetOverlayNeighbor_callback SetOverlayNeighbor; - /** C type : MoveGamepadFocusToNeighbor_callback* */ - public VR_IVROverlay_FnTable.MoveGamepadFocusToNeighbor_callback MoveGamepadFocusToNeighbor; - /** C type : SetOverlayDualAnalogTransform_callback* */ - public VR_IVROverlay_FnTable.SetOverlayDualAnalogTransform_callback SetOverlayDualAnalogTransform; - /** C type : GetOverlayDualAnalogTransform_callback* */ - public VR_IVROverlay_FnTable.GetOverlayDualAnalogTransform_callback GetOverlayDualAnalogTransform; - /** C type : SetOverlayTexture_callback* */ - public VR_IVROverlay_FnTable.SetOverlayTexture_callback SetOverlayTexture; - /** C type : ClearOverlayTexture_callback* */ - public VR_IVROverlay_FnTable.ClearOverlayTexture_callback ClearOverlayTexture; - /** C type : SetOverlayRaw_callback* */ - public VR_IVROverlay_FnTable.SetOverlayRaw_callback SetOverlayRaw; - /** C type : SetOverlayFromFile_callback* */ - public VR_IVROverlay_FnTable.SetOverlayFromFile_callback SetOverlayFromFile; - /** C type : GetOverlayTexture_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTexture_callback GetOverlayTexture; - /** C type : ReleaseNativeOverlayHandle_callback* */ - public VR_IVROverlay_FnTable.ReleaseNativeOverlayHandle_callback ReleaseNativeOverlayHandle; - /** C type : GetOverlayTextureSize_callback* */ - public VR_IVROverlay_FnTable.GetOverlayTextureSize_callback GetOverlayTextureSize; - /** C type : CreateDashboardOverlay_callback* */ - public VR_IVROverlay_FnTable.CreateDashboardOverlay_callback CreateDashboardOverlay; - /** C type : IsDashboardVisible_callback* */ - public VR_IVROverlay_FnTable.IsDashboardVisible_callback IsDashboardVisible; - /** C type : IsActiveDashboardOverlay_callback* */ - public VR_IVROverlay_FnTable.IsActiveDashboardOverlay_callback IsActiveDashboardOverlay; - /** C type : SetDashboardOverlaySceneProcess_callback* */ - public VR_IVROverlay_FnTable.SetDashboardOverlaySceneProcess_callback SetDashboardOverlaySceneProcess; - /** C type : GetDashboardOverlaySceneProcess_callback* */ - public VR_IVROverlay_FnTable.GetDashboardOverlaySceneProcess_callback GetDashboardOverlaySceneProcess; - /** C type : ShowDashboard_callback* */ - public VR_IVROverlay_FnTable.ShowDashboard_callback ShowDashboard; - /** C type : GetPrimaryDashboardDevice_callback* */ - public VR_IVROverlay_FnTable.GetPrimaryDashboardDevice_callback GetPrimaryDashboardDevice; - /** C type : ShowKeyboard_callback* */ - public VR_IVROverlay_FnTable.ShowKeyboard_callback ShowKeyboard; - /** C type : ShowKeyboardForOverlay_callback* */ - public VR_IVROverlay_FnTable.ShowKeyboardForOverlay_callback ShowKeyboardForOverlay; - /** C type : GetKeyboardText_callback* */ - public VR_IVROverlay_FnTable.GetKeyboardText_callback GetKeyboardText; - /** C type : HideKeyboard_callback* */ - public VR_IVROverlay_FnTable.HideKeyboard_callback HideKeyboard; - /** C type : SetKeyboardTransformAbsolute_callback* */ - public VR_IVROverlay_FnTable.SetKeyboardTransformAbsolute_callback SetKeyboardTransformAbsolute; - /** C type : SetKeyboardPositionForOverlay_callback* */ - public VR_IVROverlay_FnTable.SetKeyboardPositionForOverlay_callback SetKeyboardPositionForOverlay; - /** C type : SetOverlayIntersectionMask_callback* */ - public VR_IVROverlay_FnTable.SetOverlayIntersectionMask_callback SetOverlayIntersectionMask; - /** C type : GetOverlayFlags_callback* */ - public VR_IVROverlay_FnTable.GetOverlayFlags_callback GetOverlayFlags; - /** C type : ShowMessageOverlay_callback* */ - public VR_IVROverlay_FnTable.ShowMessageOverlay_callback ShowMessageOverlay; - /** C type : CloseMessageOverlay_callback* */ - public VR_IVROverlay_FnTable.CloseMessageOverlay_callback CloseMessageOverlay; - /** native declaration : headers\openvr_capi.h:2129 */ - public interface FindOverlay_callback extends Callback { - int apply(Pointer pchOverlayKey, LongByReference pOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2130 */ - public interface CreateOverlay_callback extends Callback { - int apply(Pointer pchOverlayKey, Pointer pchOverlayName, LongByReference pOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2131 */ - public interface DestroyOverlay_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2132 */ - public interface SetHighQualityOverlay_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2133 */ - public interface GetHighQualityOverlay_callback extends Callback { - long apply(); - }; - /** native declaration : headers\openvr_capi.h:2134 */ - public interface GetOverlayKey_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchValue, int unBufferSize, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:2135 */ - public interface GetOverlayName_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchValue, int unBufferSize, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:2136 */ - public interface SetOverlayName_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchName); - }; - /** native declaration : headers\openvr_capi.h:2137 */ - public interface GetOverlayImageData_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pvBuffer, int unBufferSize, IntByReference punWidth, IntByReference punHeight); - }; - /** native declaration : headers\openvr_capi.h:2138 */ - public interface GetOverlayErrorNameFromEnum_callback extends Callback { - Pointer apply(int error); - }; - /** native declaration : headers\openvr_capi.h:2139 */ - public interface SetOverlayRenderingPid_callback extends Callback { - int apply(long ulOverlayHandle, int unPID); - }; - /** native declaration : headers\openvr_capi.h:2140 */ - public interface GetOverlayRenderingPid_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2141 */ - public interface SetOverlayFlag_callback extends Callback { - int apply(long ulOverlayHandle, int eOverlayFlag, byte bEnabled); - }; - /** native declaration : headers\openvr_capi.h:2142 */ - public interface GetOverlayFlag_callback extends Callback { - int apply(long ulOverlayHandle, int eOverlayFlag, Pointer pbEnabled); - }; - /** native declaration : headers\openvr_capi.h:2143 */ - public interface SetOverlayColor_callback extends Callback { - int apply(long ulOverlayHandle, float fRed, float fGreen, float fBlue); - }; - /** native declaration : headers\openvr_capi.h:2144 */ - public interface GetOverlayColor_callback extends Callback { - int apply(long ulOverlayHandle, FloatByReference pfRed, FloatByReference pfGreen, FloatByReference pfBlue); - }; - /** native declaration : headers\openvr_capi.h:2145 */ - public interface SetOverlayAlpha_callback extends Callback { - int apply(long ulOverlayHandle, float fAlpha); - }; - /** native declaration : headers\openvr_capi.h:2146 */ - public interface GetOverlayAlpha_callback extends Callback { - int apply(long ulOverlayHandle, FloatByReference pfAlpha); - }; - /** native declaration : headers\openvr_capi.h:2147 */ - public interface SetOverlayTexelAspect_callback extends Callback { - int apply(long ulOverlayHandle, float fTexelAspect); - }; - /** native declaration : headers\openvr_capi.h:2148 */ - public interface GetOverlayTexelAspect_callback extends Callback { - int apply(long ulOverlayHandle, FloatByReference pfTexelAspect); - }; - /** native declaration : headers\openvr_capi.h:2149 */ - public interface SetOverlaySortOrder_callback extends Callback { - int apply(long ulOverlayHandle, int unSortOrder); - }; - /** native declaration : headers\openvr_capi.h:2150 */ - public interface GetOverlaySortOrder_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference punSortOrder); - }; - /** native declaration : headers\openvr_capi.h:2151 */ - public interface SetOverlayWidthInMeters_callback extends Callback { - int apply(long ulOverlayHandle, float fWidthInMeters); - }; - /** native declaration : headers\openvr_capi.h:2152 */ - public interface GetOverlayWidthInMeters_callback extends Callback { - int apply(long ulOverlayHandle, FloatByReference pfWidthInMeters); - }; - /** native declaration : headers\openvr_capi.h:2153 */ - public interface SetOverlayAutoCurveDistanceRangeInMeters_callback extends Callback { - int apply(long ulOverlayHandle, float fMinDistanceInMeters, float fMaxDistanceInMeters); - }; - /** native declaration : headers\openvr_capi.h:2154 */ - public interface GetOverlayAutoCurveDistanceRangeInMeters_callback extends Callback { - int apply(long ulOverlayHandle, FloatByReference pfMinDistanceInMeters, FloatByReference pfMaxDistanceInMeters); - }; - /** native declaration : headers\openvr_capi.h:2155 */ - public interface SetOverlayTextureColorSpace_callback extends Callback { - int apply(long ulOverlayHandle, int eTextureColorSpace); - }; - /** native declaration : headers\openvr_capi.h:2156 */ - public interface GetOverlayTextureColorSpace_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference peTextureColorSpace); - }; - /** native declaration : headers\openvr_capi.h:2157 */ - public interface SetOverlayTextureBounds_callback extends Callback { - int apply(long ulOverlayHandle, VRTextureBounds_t pOverlayTextureBounds); - }; - /** native declaration : headers\openvr_capi.h:2158 */ - public interface GetOverlayTextureBounds_callback extends Callback { - int apply(long ulOverlayHandle, VRTextureBounds_t pOverlayTextureBounds); - }; - /** native declaration : headers\openvr_capi.h:2159 */ - public interface GetOverlayRenderModel_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchValue, int unBufferSize, HmdColor_t pColor, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:2160 */ - public interface SetOverlayRenderModel_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchRenderModel, HmdColor_t pColor); - }; - /** native declaration : headers\openvr_capi.h:2161 */ - public interface GetOverlayTransformType_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference peTransformType); - }; - /** native declaration : headers\openvr_capi.h:2162 */ - public interface SetOverlayTransformAbsolute_callback extends Callback { - int apply(long ulOverlayHandle, int eTrackingOrigin, HmdMatrix34_t pmatTrackingOriginToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2163 */ - public interface GetOverlayTransformAbsolute_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference peTrackingOrigin, HmdMatrix34_t pmatTrackingOriginToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2164 */ - public interface SetOverlayTransformTrackedDeviceRelative_callback extends Callback { - int apply(long ulOverlayHandle, int unTrackedDevice, HmdMatrix34_t pmatTrackedDeviceToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2165 */ - public interface GetOverlayTransformTrackedDeviceRelative_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference punTrackedDevice, HmdMatrix34_t pmatTrackedDeviceToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2166 */ - public interface SetOverlayTransformTrackedDeviceComponent_callback extends Callback { - int apply(long ulOverlayHandle, int unDeviceIndex, Pointer pchComponentName); - }; - /** native declaration : headers\openvr_capi.h:2167 */ - public interface GetOverlayTransformTrackedDeviceComponent_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference punDeviceIndex, Pointer pchComponentName, int unComponentNameSize); - }; - /** native declaration : headers\openvr_capi.h:2168 */ - public interface GetOverlayTransformOverlayRelative_callback extends Callback { - int apply(long ulOverlayHandle, LongByReference ulOverlayHandleParent, HmdMatrix34_t pmatParentOverlayToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2169 */ - public interface SetOverlayTransformOverlayRelative_callback extends Callback { - int apply(long ulOverlayHandle, long ulOverlayHandleParent, HmdMatrix34_t pmatParentOverlayToOverlayTransform); - }; - /** native declaration : headers\openvr_capi.h:2170 */ - public interface ShowOverlay_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2171 */ - public interface HideOverlay_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2172 */ - public interface IsOverlayVisible_callback extends Callback { - byte apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2173 */ - public interface GetTransformForOverlayCoordinates_callback extends Callback { - int apply(long ulOverlayHandle, int eTrackingOrigin, HmdVector2_t.ByValue coordinatesInOverlay, HmdMatrix34_t pmatTransform); - }; - /** native declaration : headers\openvr_capi.h:2174 */ - public interface PollNextOverlayEvent_callback extends Callback { - byte apply(long ulOverlayHandle, VREvent_t pEvent, int uncbVREvent); - }; - /** native declaration : headers\openvr_capi.h:2175 */ - public interface GetOverlayInputMethod_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference peInputMethod); - }; - /** native declaration : headers\openvr_capi.h:2176 */ - public interface SetOverlayInputMethod_callback extends Callback { - int apply(long ulOverlayHandle, int eInputMethod); - }; - /** native declaration : headers\openvr_capi.h:2177 */ - public interface GetOverlayMouseScale_callback extends Callback { - int apply(long ulOverlayHandle, HmdVector2_t pvecMouseScale); - }; - /** native declaration : headers\openvr_capi.h:2178 */ - public interface SetOverlayMouseScale_callback extends Callback { - int apply(long ulOverlayHandle, HmdVector2_t pvecMouseScale); - }; - /** native declaration : headers\openvr_capi.h:2179 */ - public interface ComputeOverlayIntersection_callback extends Callback { - byte apply(long ulOverlayHandle, VROverlayIntersectionParams_t pParams, VROverlayIntersectionResults_t pResults); - }; - /** native declaration : headers\openvr_capi.h:2180 */ - public interface IsHoverTargetOverlay_callback extends Callback { - byte apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2181 */ - public interface GetGamepadFocusOverlay_callback extends Callback { - long apply(); - }; - /** native declaration : headers\openvr_capi.h:2182 */ - public interface SetGamepadFocusOverlay_callback extends Callback { - int apply(long ulNewFocusOverlay); - }; - /** native declaration : headers\openvr_capi.h:2183 */ - public interface SetOverlayNeighbor_callback extends Callback { - int apply(int eDirection, long ulFrom, long ulTo); - }; - /** native declaration : headers\openvr_capi.h:2184 */ - public interface MoveGamepadFocusToNeighbor_callback extends Callback { - int apply(int eDirection, long ulFrom); - }; - /** native declaration : headers\openvr_capi.h:2185 */ - public interface SetOverlayDualAnalogTransform_callback extends Callback { - int apply(long ulOverlay, int eWhich, HmdVector2_t vCenter, float fRadius); - }; - /** native declaration : headers\openvr_capi.h:2186 */ - public interface GetOverlayDualAnalogTransform_callback extends Callback { - int apply(long ulOverlay, int eWhich, HmdVector2_t pvCenter, FloatByReference pfRadius); - }; - /** native declaration : headers\openvr_capi.h:2187 */ - public interface SetOverlayTexture_callback extends Callback { - int apply(long ulOverlayHandle, Texture_t pTexture); - }; - /** native declaration : headers\openvr_capi.h:2188 */ - public interface ClearOverlayTexture_callback extends Callback { - int apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2189 */ - public interface SetOverlayRaw_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pvBuffer, int unWidth, int unHeight, int unDepth); - }; - /** native declaration : headers\openvr_capi.h:2190 */ - public interface SetOverlayFromFile_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pchFilePath); - }; - /** native declaration : headers\openvr_capi.h:2191 */ - public interface GetOverlayTexture_callback extends Callback { - int apply(long ulOverlayHandle, PointerByReference pNativeTextureHandle, Pointer pNativeTextureRef, IntByReference pWidth, IntByReference pHeight, IntByReference pNativeFormat, IntByReference pAPIType, IntByReference pColorSpace, VRTextureBounds_t pTextureBounds); - }; - /** native declaration : headers\openvr_capi.h:2192 */ - public interface ReleaseNativeOverlayHandle_callback extends Callback { - int apply(long ulOverlayHandle, Pointer pNativeTextureHandle); - }; - /** native declaration : headers\openvr_capi.h:2193 */ - public interface GetOverlayTextureSize_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference pWidth, IntByReference pHeight); - }; - /** native declaration : headers\openvr_capi.h:2194 */ - public interface CreateDashboardOverlay_callback extends Callback { - int apply(Pointer pchOverlayKey, Pointer pchOverlayFriendlyName, LongByReference pMainHandle, LongByReference pThumbnailHandle); - }; - /** native declaration : headers\openvr_capi.h:2195 */ - public interface IsDashboardVisible_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:2196 */ - public interface IsActiveDashboardOverlay_callback extends Callback { - byte apply(long ulOverlayHandle); - }; - /** native declaration : headers\openvr_capi.h:2197 */ - public interface SetDashboardOverlaySceneProcess_callback extends Callback { - int apply(long ulOverlayHandle, int unProcessId); - }; - /** native declaration : headers\openvr_capi.h:2198 */ - public interface GetDashboardOverlaySceneProcess_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference punProcessId); - }; - /** native declaration : headers\openvr_capi.h:2199 */ - public interface ShowDashboard_callback extends Callback { - void apply(Pointer pchOverlayToShow); - }; - /** native declaration : headers\openvr_capi.h:2200 */ - public interface GetPrimaryDashboardDevice_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2201 */ - public interface ShowKeyboard_callback extends Callback { - int apply(int eInputMode, int eLineInputMode, Pointer pchDescription, int unCharMax, Pointer pchExistingText, byte bUseMinimalMode, long uUserValue); - }; - /** native declaration : headers\openvr_capi.h:2202 */ - public interface ShowKeyboardForOverlay_callback extends Callback { - int apply(long ulOverlayHandle, int eInputMode, int eLineInputMode, Pointer pchDescription, int unCharMax, Pointer pchExistingText, byte bUseMinimalMode, long uUserValue); - }; - /** native declaration : headers\openvr_capi.h:2203 */ - public interface GetKeyboardText_callback extends Callback { - int apply(Pointer pchText, int cchText); - }; - /** native declaration : headers\openvr_capi.h:2204 */ - public interface HideKeyboard_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:2205 */ - public interface SetKeyboardTransformAbsolute_callback extends Callback { - void apply(int eTrackingOrigin, HmdMatrix34_t pmatTrackingOriginToKeyboardTransform); - }; - /** native declaration : headers\openvr_capi.h:2206 */ - public interface SetKeyboardPositionForOverlay_callback extends Callback { - void apply(long ulOverlayHandle, com.jme3.system.jopenvr.HmdRect2_t.ByValue avoidRect); - }; - /** native declaration : headers\openvr_capi.h:2207 */ - public interface SetOverlayIntersectionMask_callback extends Callback { - int apply(long ulOverlayHandle, VROverlayIntersectionMaskPrimitive_t pMaskPrimitives, int unNumMaskPrimitives, int unPrimitiveSize); - }; - /** native declaration : headers\openvr_capi.h:2208 */ - public interface GetOverlayFlags_callback extends Callback { - int apply(long ulOverlayHandle, IntByReference pFlags); - }; - /** native declaration : headers\openvr_capi.h:2209 */ - public interface ShowMessageOverlay_callback extends Callback { - int apply(Pointer pchText, Pointer pchCaption, Pointer pchButton0Text, Pointer pchButton1Text, Pointer pchButton2Text, Pointer pchButton3Text); - }; - /** native declaration : headers\openvr_capi.h:2210 */ - public interface CloseMessageOverlay_callback extends Callback { - void apply(); - }; - public VR_IVROverlay_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("FindOverlay", "CreateOverlay", "DestroyOverlay", "SetHighQualityOverlay", "GetHighQualityOverlay", "GetOverlayKey", "GetOverlayName", "SetOverlayName", "GetOverlayImageData", "GetOverlayErrorNameFromEnum", "SetOverlayRenderingPid", "GetOverlayRenderingPid", "SetOverlayFlag", "GetOverlayFlag", "SetOverlayColor", "GetOverlayColor", "SetOverlayAlpha", "GetOverlayAlpha", "SetOverlayTexelAspect", "GetOverlayTexelAspect", "SetOverlaySortOrder", "GetOverlaySortOrder", "SetOverlayWidthInMeters", "GetOverlayWidthInMeters", "SetOverlayAutoCurveDistanceRangeInMeters", "GetOverlayAutoCurveDistanceRangeInMeters", "SetOverlayTextureColorSpace", "GetOverlayTextureColorSpace", "SetOverlayTextureBounds", "GetOverlayTextureBounds", "GetOverlayRenderModel", "SetOverlayRenderModel", "GetOverlayTransformType", "SetOverlayTransformAbsolute", "GetOverlayTransformAbsolute", "SetOverlayTransformTrackedDeviceRelative", "GetOverlayTransformTrackedDeviceRelative", "SetOverlayTransformTrackedDeviceComponent", "GetOverlayTransformTrackedDeviceComponent", "GetOverlayTransformOverlayRelative", "SetOverlayTransformOverlayRelative", "ShowOverlay", "HideOverlay", "IsOverlayVisible", "GetTransformForOverlayCoordinates", "PollNextOverlayEvent", "GetOverlayInputMethod", "SetOverlayInputMethod", "GetOverlayMouseScale", "SetOverlayMouseScale", "ComputeOverlayIntersection", "IsHoverTargetOverlay", "GetGamepadFocusOverlay", "SetGamepadFocusOverlay", "SetOverlayNeighbor", "MoveGamepadFocusToNeighbor", "SetOverlayDualAnalogTransform", "GetOverlayDualAnalogTransform", "SetOverlayTexture", "ClearOverlayTexture", "SetOverlayRaw", "SetOverlayFromFile", "GetOverlayTexture", "ReleaseNativeOverlayHandle", "GetOverlayTextureSize", "CreateDashboardOverlay", "IsDashboardVisible", "IsActiveDashboardOverlay", "SetDashboardOverlaySceneProcess", "GetDashboardOverlaySceneProcess", "ShowDashboard", "GetPrimaryDashboardDevice", "ShowKeyboard", "ShowKeyboardForOverlay", "GetKeyboardText", "HideKeyboard", "SetKeyboardTransformAbsolute", "SetKeyboardPositionForOverlay", "SetOverlayIntersectionMask", "GetOverlayFlags", "ShowMessageOverlay", "CloseMessageOverlay"); - } - public VR_IVROverlay_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVROverlay_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVROverlay_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRRenderModels_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRRenderModels_FnTable.java deleted file mode 100644 index a025a415ec..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRRenderModels_FnTable.java +++ /dev/null @@ -1,145 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2251
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRRenderModels_FnTable extends Structure { - /** C type : LoadRenderModel_Async_callback* */ - public VR_IVRRenderModels_FnTable.LoadRenderModel_Async_callback LoadRenderModel_Async; - /** C type : FreeRenderModel_callback* */ - public VR_IVRRenderModels_FnTable.FreeRenderModel_callback FreeRenderModel; - /** C type : LoadTexture_Async_callback* */ - public VR_IVRRenderModels_FnTable.LoadTexture_Async_callback LoadTexture_Async; - /** C type : FreeTexture_callback* */ - public VR_IVRRenderModels_FnTable.FreeTexture_callback FreeTexture; - /** C type : LoadTextureD3D11_Async_callback* */ - public VR_IVRRenderModels_FnTable.LoadTextureD3D11_Async_callback LoadTextureD3D11_Async; - /** C type : LoadIntoTextureD3D11_Async_callback* */ - public VR_IVRRenderModels_FnTable.LoadIntoTextureD3D11_Async_callback LoadIntoTextureD3D11_Async; - /** C type : FreeTextureD3D11_callback* */ - public VR_IVRRenderModels_FnTable.FreeTextureD3D11_callback FreeTextureD3D11; - /** C type : GetRenderModelName_callback* */ - public VR_IVRRenderModels_FnTable.GetRenderModelName_callback GetRenderModelName; - /** C type : GetRenderModelCount_callback* */ - public VR_IVRRenderModels_FnTable.GetRenderModelCount_callback GetRenderModelCount; - /** C type : GetComponentCount_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentCount_callback GetComponentCount; - /** C type : GetComponentName_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentName_callback GetComponentName; - /** C type : GetComponentButtonMask_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentButtonMask_callback GetComponentButtonMask; - /** C type : GetComponentRenderModelName_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentRenderModelName_callback GetComponentRenderModelName; - /** C type : GetComponentStateForDevicePath_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentStateForDevicePath_callback GetComponentStateForDevicePath; - /** C type : GetComponentState_callback* */ - public VR_IVRRenderModels_FnTable.GetComponentState_callback GetComponentState; - /** C type : RenderModelHasComponent_callback* */ - public VR_IVRRenderModels_FnTable.RenderModelHasComponent_callback RenderModelHasComponent; - /** C type : GetRenderModelThumbnailURL_callback* */ - public VR_IVRRenderModels_FnTable.GetRenderModelThumbnailURL_callback GetRenderModelThumbnailURL; - /** C type : GetRenderModelOriginalPath_callback* */ - public VR_IVRRenderModels_FnTable.GetRenderModelOriginalPath_callback GetRenderModelOriginalPath; - /** C type : GetRenderModelErrorNameFromEnum_callback* */ - public VR_IVRRenderModels_FnTable.GetRenderModelErrorNameFromEnum_callback GetRenderModelErrorNameFromEnum; - /** native declaration : headers\openvr_capi.h:2232 */ - public interface LoadRenderModel_Async_callback extends Callback { - int apply(Pointer pchRenderModelName, PointerByReference ppRenderModel); - }; - /** native declaration : headers\openvr_capi.h:2233 */ - public interface FreeRenderModel_callback extends Callback { - void apply(RenderModel_t pRenderModel); - }; - /** native declaration : headers\openvr_capi.h:2234 */ - public interface LoadTexture_Async_callback extends Callback { - int apply(int textureId, PointerByReference ppTexture); - }; - /** native declaration : headers\openvr_capi.h:2235 */ - public interface FreeTexture_callback extends Callback { - void apply(RenderModel_TextureMap_t pTexture); - }; - /** native declaration : headers\openvr_capi.h:2236 */ - public interface LoadTextureD3D11_Async_callback extends Callback { - int apply(int textureId, Pointer pD3D11Device, PointerByReference ppD3D11Texture2D); - }; - /** native declaration : headers\openvr_capi.h:2237 */ - public interface LoadIntoTextureD3D11_Async_callback extends Callback { - int apply(int textureId, Pointer pDstTexture); - }; - /** native declaration : headers\openvr_capi.h:2238 */ - public interface FreeTextureD3D11_callback extends Callback { - void apply(Pointer pD3D11Texture2D); - }; - /** native declaration : headers\openvr_capi.h:2239 */ - public interface GetRenderModelName_callback extends Callback { - int apply(int unRenderModelIndex, Pointer pchRenderModelName, int unRenderModelNameLen); - }; - /** native declaration : headers\openvr_capi.h:2240 */ - public interface GetRenderModelCount_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:2241 */ - public interface GetComponentCount_callback extends Callback { - int apply(Pointer pchRenderModelName); - }; - /** native declaration : headers\openvr_capi.h:2242 */ - public interface GetComponentName_callback extends Callback { - int apply(Pointer pchRenderModelName, int unComponentIndex, Pointer pchComponentName, int unComponentNameLen); - }; - /** native declaration : headers\openvr_capi.h:2243 */ - public interface GetComponentButtonMask_callback extends Callback { - long apply(Pointer pchRenderModelName, Pointer pchComponentName); - }; - /** native declaration : headers\openvr_capi.h:2244 */ - public interface GetComponentRenderModelName_callback extends Callback { - int apply(Pointer pchRenderModelName, Pointer pchComponentName, Pointer pchComponentRenderModelName, int unComponentRenderModelNameLen); - }; - /** native declaration : headers\openvr_capi.h:2245 */ - public interface GetComponentStateForDevicePath_callback extends Callback { - byte apply(Pointer pchRenderModelName, Pointer pchComponentName, long devicePath, RenderModel_ControllerMode_State_t pState, RenderModel_ComponentState_t pComponentState); - }; - /** native declaration : headers\openvr_capi.h:2246 */ - public interface GetComponentState_callback extends Callback { - byte apply(Pointer pchRenderModelName, Pointer pchComponentName, VRControllerState_t pControllerState, RenderModel_ControllerMode_State_t pState, RenderModel_ComponentState_t pComponentState); - }; - /** native declaration : headers\openvr_capi.h:2247 */ - public interface RenderModelHasComponent_callback extends Callback { - byte apply(Pointer pchRenderModelName, Pointer pchComponentName); - }; - /** native declaration : headers\openvr_capi.h:2248 */ - public interface GetRenderModelThumbnailURL_callback extends Callback { - int apply(Pointer pchRenderModelName, Pointer pchThumbnailURL, int unThumbnailURLLen, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2249 */ - public interface GetRenderModelOriginalPath_callback extends Callback { - int apply(Pointer pchRenderModelName, Pointer pchOriginalPath, int unOriginalPathLen, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2250 */ - public interface GetRenderModelErrorNameFromEnum_callback extends Callback { - Pointer apply(int error); - }; - public VR_IVRRenderModels_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("LoadRenderModel_Async", "FreeRenderModel", "LoadTexture_Async", "FreeTexture", "LoadTextureD3D11_Async", "LoadIntoTextureD3D11_Async", "FreeTextureD3D11", "GetRenderModelName", "GetRenderModelCount", "GetComponentCount", "GetComponentName", "GetComponentButtonMask", "GetComponentRenderModelName", "GetComponentStateForDevicePath", "GetComponentState", "RenderModelHasComponent", "GetRenderModelThumbnailURL", "GetRenderModelOriginalPath", "GetRenderModelErrorNameFromEnum"); - } - public VR_IVRRenderModels_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRRenderModels_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRRenderModels_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRResources_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRResources_FnTable.java deleted file mode 100644 index 889feb9822..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRResources_FnTable.java +++ /dev/null @@ -1,50 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2305
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRResources_FnTable extends Structure { - /** C type : LoadSharedResource_callback* */ - public VR_IVRResources_FnTable.LoadSharedResource_callback LoadSharedResource; - /** C type : GetResourceFullPath_callback* */ - public VR_IVRResources_FnTable.GetResourceFullPath_callback GetResourceFullPath; - /** native declaration : headers\openvr_capi.h:2303 */ - public interface LoadSharedResource_callback extends Callback { - int apply(Pointer pchResourceName, Pointer pchBuffer, int unBufferLen); - }; - /** native declaration : headers\openvr_capi.h:2304 */ - public interface GetResourceFullPath_callback extends Callback { - int apply(Pointer pchResourceName, Pointer pchResourceTypeDirectory, Pointer pchPathBuffer, int unBufferLen); - }; - public VR_IVRResources_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("LoadSharedResource", "GetResourceFullPath"); - } - /** - * @param LoadSharedResource C type : LoadSharedResource_callback*
                  - * @param GetResourceFullPath C type : GetResourceFullPath_callback* - */ - public VR_IVRResources_FnTable(VR_IVRResources_FnTable.LoadSharedResource_callback LoadSharedResource, VR_IVRResources_FnTable.GetResourceFullPath_callback GetResourceFullPath) { - super(); - this.LoadSharedResource = LoadSharedResource; - this.GetResourceFullPath = GetResourceFullPath; - } - public VR_IVRResources_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRResources_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRResources_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRScreenshots_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRScreenshots_FnTable.java deleted file mode 100644 index 888c3ce2d9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRScreenshots_FnTable.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2299
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRScreenshots_FnTable extends Structure { - /** C type : RequestScreenshot_callback* */ - public VR_IVRScreenshots_FnTable.RequestScreenshot_callback RequestScreenshot; - /** C type : HookScreenshot_callback* */ - public VR_IVRScreenshots_FnTable.HookScreenshot_callback HookScreenshot; - /** C type : GetScreenshotPropertyType_callback* */ - public VR_IVRScreenshots_FnTable.GetScreenshotPropertyType_callback GetScreenshotPropertyType; - /** C type : GetScreenshotPropertyFilename_callback* */ - public VR_IVRScreenshots_FnTable.GetScreenshotPropertyFilename_callback GetScreenshotPropertyFilename; - /** C type : UpdateScreenshotProgress_callback* */ - public VR_IVRScreenshots_FnTable.UpdateScreenshotProgress_callback UpdateScreenshotProgress; - /** C type : TakeStereoScreenshot_callback* */ - public VR_IVRScreenshots_FnTable.TakeStereoScreenshot_callback TakeStereoScreenshot; - /** C type : SubmitScreenshot_callback* */ - public VR_IVRScreenshots_FnTable.SubmitScreenshot_callback SubmitScreenshot; - /** native declaration : headers\openvr_capi.h:2292 */ - public interface RequestScreenshot_callback extends Callback { - int apply(IntByReference pOutScreenshotHandle, int type, Pointer pchPreviewFilename, Pointer pchVRFilename); - }; - /** native declaration : headers\openvr_capi.h:2293 */ - public interface HookScreenshot_callback extends Callback { - int apply(IntByReference pSupportedTypes, int numTypes); - }; - /** native declaration : headers\openvr_capi.h:2294 */ - public interface GetScreenshotPropertyType_callback extends Callback { - int apply(int screenshotHandle, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:2295 */ - public interface GetScreenshotPropertyFilename_callback extends Callback { - int apply(int screenshotHandle, int filenameType, Pointer pchFilename, int cchFilename, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:2296 */ - public interface UpdateScreenshotProgress_callback extends Callback { - int apply(int screenshotHandle, float flProgress); - }; - /** native declaration : headers\openvr_capi.h:2297 */ - public interface TakeStereoScreenshot_callback extends Callback { - int apply(IntByReference pOutScreenshotHandle, Pointer pchPreviewFilename, Pointer pchVRFilename); - }; - /** native declaration : headers\openvr_capi.h:2298 */ - public interface SubmitScreenshot_callback extends Callback { - int apply(int screenshotHandle, int type, Pointer pchSourcePreviewFilename, Pointer pchSourceVRFilename); - }; - public VR_IVRScreenshots_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("RequestScreenshot", "HookScreenshot", "GetScreenshotPropertyType", "GetScreenshotPropertyFilename", "UpdateScreenshotProgress", "TakeStereoScreenshot", "SubmitScreenshot"); - } - /** - * @param RequestScreenshot C type : RequestScreenshot_callback*
                  - * @param HookScreenshot C type : HookScreenshot_callback*
                  - * @param GetScreenshotPropertyType C type : GetScreenshotPropertyType_callback*
                  - * @param GetScreenshotPropertyFilename C type : GetScreenshotPropertyFilename_callback*
                  - * @param UpdateScreenshotProgress C type : UpdateScreenshotProgress_callback*
                  - * @param TakeStereoScreenshot C type : TakeStereoScreenshot_callback*
                  - * @param SubmitScreenshot C type : SubmitScreenshot_callback* - */ - public VR_IVRScreenshots_FnTable(VR_IVRScreenshots_FnTable.RequestScreenshot_callback RequestScreenshot, VR_IVRScreenshots_FnTable.HookScreenshot_callback HookScreenshot, VR_IVRScreenshots_FnTable.GetScreenshotPropertyType_callback GetScreenshotPropertyType, VR_IVRScreenshots_FnTable.GetScreenshotPropertyFilename_callback GetScreenshotPropertyFilename, VR_IVRScreenshots_FnTable.UpdateScreenshotProgress_callback UpdateScreenshotProgress, VR_IVRScreenshots_FnTable.TakeStereoScreenshot_callback TakeStereoScreenshot, VR_IVRScreenshots_FnTable.SubmitScreenshot_callback SubmitScreenshot) { - super(); - this.RequestScreenshot = RequestScreenshot; - this.HookScreenshot = HookScreenshot; - this.GetScreenshotPropertyType = GetScreenshotPropertyType; - this.GetScreenshotPropertyFilename = GetScreenshotPropertyFilename; - this.UpdateScreenshotProgress = UpdateScreenshotProgress; - this.TakeStereoScreenshot = TakeStereoScreenshot; - this.SubmitScreenshot = SubmitScreenshot; - } - public VR_IVRScreenshots_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRScreenshots_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRScreenshots_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSettings_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSettings_FnTable.java deleted file mode 100644 index a3704ec539..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSettings_FnTable.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2283
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRSettings_FnTable extends Structure { - /** C type : GetSettingsErrorNameFromEnum_callback* */ - public VR_IVRSettings_FnTable.GetSettingsErrorNameFromEnum_callback GetSettingsErrorNameFromEnum; - /** C type : Sync_callback* */ - public VR_IVRSettings_FnTable.Sync_callback Sync; - /** C type : SetBool_callback* */ - public VR_IVRSettings_FnTable.SetBool_callback SetBool; - /** C type : SetInt32_callback* */ - public VR_IVRSettings_FnTable.SetInt32_callback SetInt32; - /** C type : SetFloat_callback* */ - public VR_IVRSettings_FnTable.SetFloat_callback SetFloat; - /** C type : SetString_callback* */ - public VR_IVRSettings_FnTable.SetString_callback SetString; - /** C type : GetBool_callback* */ - public VR_IVRSettings_FnTable.GetBool_callback GetBool; - /** C type : GetInt32_callback* */ - public VR_IVRSettings_FnTable.GetInt32_callback GetInt32; - /** C type : GetFloat_callback* */ - public VR_IVRSettings_FnTable.GetFloat_callback GetFloat; - /** C type : GetString_callback* */ - public VR_IVRSettings_FnTable.GetString_callback GetString; - /** C type : RemoveSection_callback* */ - public VR_IVRSettings_FnTable.RemoveSection_callback RemoveSection; - /** C type : RemoveKeyInSection_callback* */ - public VR_IVRSettings_FnTable.RemoveKeyInSection_callback RemoveKeyInSection; - /** native declaration : headers\openvr_capi.h:2271 */ - public interface GetSettingsErrorNameFromEnum_callback extends Callback { - Pointer apply(int eError); - }; - /** native declaration : headers\openvr_capi.h:2272 */ - public interface Sync_callback extends Callback { - byte apply(byte bForce, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2273 */ - public interface SetBool_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, byte bValue, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2274 */ - public interface SetInt32_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, int nValue, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2275 */ - public interface SetFloat_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, float flValue, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2276 */ - public interface SetString_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, Pointer pchValue, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2277 */ - public interface GetBool_callback extends Callback { - byte apply(Pointer pchSection, Pointer pchSettingsKey, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2278 */ - public interface GetInt32_callback extends Callback { - int apply(Pointer pchSection, Pointer pchSettingsKey, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2279 */ - public interface GetFloat_callback extends Callback { - float apply(Pointer pchSection, Pointer pchSettingsKey, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2280 */ - public interface GetString_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, Pointer pchValue, int unValueLen, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2281 */ - public interface RemoveSection_callback extends Callback { - void apply(Pointer pchSection, IntByReference peError); - }; - /** native declaration : headers\openvr_capi.h:2282 */ - public interface RemoveKeyInSection_callback extends Callback { - void apply(Pointer pchSection, Pointer pchSettingsKey, IntByReference peError); - }; - public VR_IVRSettings_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetSettingsErrorNameFromEnum", "Sync", "SetBool", "SetInt32", "SetFloat", "SetString", "GetBool", "GetInt32", "GetFloat", "GetString", "RemoveSection", "RemoveKeyInSection"); - } - public VR_IVRSettings_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRSettings_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRSettings_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSpatialAnchors_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSpatialAnchors_FnTable.java deleted file mode 100644 index 17d7892695..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSpatialAnchors_FnTable.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:2373
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRSpatialAnchors_FnTable extends Structure { - /** C type : CreateSpatialAnchorFromDescriptor_callback* */ - public VR_IVRSpatialAnchors_FnTable.CreateSpatialAnchorFromDescriptor_callback CreateSpatialAnchorFromDescriptor; - /** C type : CreateSpatialAnchorFromPose_callback* */ - public VR_IVRSpatialAnchors_FnTable.CreateSpatialAnchorFromPose_callback CreateSpatialAnchorFromPose; - /** C type : GetSpatialAnchorPose_callback* */ - public VR_IVRSpatialAnchors_FnTable.GetSpatialAnchorPose_callback GetSpatialAnchorPose; - /** C type : GetSpatialAnchorDescriptor_callback* */ - public VR_IVRSpatialAnchors_FnTable.GetSpatialAnchorDescriptor_callback GetSpatialAnchorDescriptor; - /** native declaration : headers\openvr_capi.h:2369 */ - public interface CreateSpatialAnchorFromDescriptor_callback extends Callback { - int apply(Pointer pchDescriptor, IntByReference pHandleOut); - }; - /** native declaration : headers\openvr_capi.h:2370 */ - public interface CreateSpatialAnchorFromPose_callback extends Callback { - int apply(int unDeviceIndex, int eOrigin, SpatialAnchorPose_t pPose, IntByReference pHandleOut); - }; - /** native declaration : headers\openvr_capi.h:2371 */ - public interface GetSpatialAnchorPose_callback extends Callback { - int apply(int unHandle, int eOrigin, SpatialAnchorPose_t pPoseOut); - }; - /** native declaration : headers\openvr_capi.h:2372 */ - public interface GetSpatialAnchorDescriptor_callback extends Callback { - int apply(int unHandle, Pointer pchDescriptorOut, IntByReference punDescriptorBufferLenInOut); - }; - public VR_IVRSpatialAnchors_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("CreateSpatialAnchorFromDescriptor", "CreateSpatialAnchorFromPose", "GetSpatialAnchorPose", "GetSpatialAnchorDescriptor"); - } - /** - * @param CreateSpatialAnchorFromDescriptor C type : CreateSpatialAnchorFromDescriptor_callback*
                  - * @param CreateSpatialAnchorFromPose C type : CreateSpatialAnchorFromPose_callback*
                  - * @param GetSpatialAnchorPose C type : GetSpatialAnchorPose_callback*
                  - * @param GetSpatialAnchorDescriptor C type : GetSpatialAnchorDescriptor_callback* - */ - public VR_IVRSpatialAnchors_FnTable(VR_IVRSpatialAnchors_FnTable.CreateSpatialAnchorFromDescriptor_callback CreateSpatialAnchorFromDescriptor, VR_IVRSpatialAnchors_FnTable.CreateSpatialAnchorFromPose_callback CreateSpatialAnchorFromPose, VR_IVRSpatialAnchors_FnTable.GetSpatialAnchorPose_callback GetSpatialAnchorPose, VR_IVRSpatialAnchors_FnTable.GetSpatialAnchorDescriptor_callback GetSpatialAnchorDescriptor) { - super(); - this.CreateSpatialAnchorFromDescriptor = CreateSpatialAnchorFromDescriptor; - this.CreateSpatialAnchorFromPose = CreateSpatialAnchorFromPose; - this.GetSpatialAnchorPose = GetSpatialAnchorPose; - this.GetSpatialAnchorDescriptor = GetSpatialAnchorDescriptor; - } - public VR_IVRSpatialAnchors_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRSpatialAnchors_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRSpatialAnchors_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSystem_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSystem_FnTable.java deleted file mode 100644 index 315ca22cdc..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRSystem_FnTable.java +++ /dev/null @@ -1,316 +0,0 @@ -package com.jme3.system.jopenvr; -import com.jme3.system.jopenvr.JOpenVRLibrary.VkInstance_T; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; -import java.util.Arrays; -import java.util.List; -/** - * OpenVR Function Pointer Tables
                  - * native declaration : headers\openvr_capi.h:1799
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRSystem_FnTable extends Structure { - /** C type : GetRecommendedRenderTargetSize_callback* */ - public VR_IVRSystem_FnTable.GetRecommendedRenderTargetSize_callback GetRecommendedRenderTargetSize; - /** C type : GetProjectionMatrix_callback* */ - public VR_IVRSystem_FnTable.GetProjectionMatrix_callback GetProjectionMatrix; - /** C type : GetProjectionRaw_callback* */ - public VR_IVRSystem_FnTable.GetProjectionRaw_callback GetProjectionRaw; - /** C type : ComputeDistortion_callback* */ - public VR_IVRSystem_FnTable.ComputeDistortion_callback ComputeDistortion; - /** C type : GetEyeToHeadTransform_callback* */ - public VR_IVRSystem_FnTable.GetEyeToHeadTransform_callback GetEyeToHeadTransform; - /** C type : GetTimeSinceLastVsync_callback* */ - public VR_IVRSystem_FnTable.GetTimeSinceLastVsync_callback GetTimeSinceLastVsync; - /** C type : GetD3D9AdapterIndex_callback* */ - public VR_IVRSystem_FnTable.GetD3D9AdapterIndex_callback GetD3D9AdapterIndex; - /** C type : GetDXGIOutputInfo_callback* */ - public com.jme3.system.jopenvr.VR_IVRExtendedDisplay_FnTable.GetDXGIOutputInfo_callback GetDXGIOutputInfo; - /** C type : GetOutputDevice_callback* */ - public VR_IVRSystem_FnTable.GetOutputDevice_callback GetOutputDevice; - /** C type : IsDisplayOnDesktop_callback* */ - public VR_IVRSystem_FnTable.IsDisplayOnDesktop_callback IsDisplayOnDesktop; - /** C type : SetDisplayVisibility_callback* */ - public VR_IVRSystem_FnTable.SetDisplayVisibility_callback SetDisplayVisibility; - /** C type : GetDeviceToAbsoluteTrackingPose_callback* */ - public VR_IVRSystem_FnTable.GetDeviceToAbsoluteTrackingPose_callback GetDeviceToAbsoluteTrackingPose; - /** C type : ResetSeatedZeroPose_callback* */ - public VR_IVRSystem_FnTable.ResetSeatedZeroPose_callback ResetSeatedZeroPose; - /** C type : GetSeatedZeroPoseToStandingAbsoluteTrackingPose_callback* */ - public VR_IVRSystem_FnTable.GetSeatedZeroPoseToStandingAbsoluteTrackingPose_callback GetSeatedZeroPoseToStandingAbsoluteTrackingPose; - /** C type : GetRawZeroPoseToStandingAbsoluteTrackingPose_callback* */ - public VR_IVRSystem_FnTable.GetRawZeroPoseToStandingAbsoluteTrackingPose_callback GetRawZeroPoseToStandingAbsoluteTrackingPose; - /** C type : GetSortedTrackedDeviceIndicesOfClass_callback* */ - public VR_IVRSystem_FnTable.GetSortedTrackedDeviceIndicesOfClass_callback GetSortedTrackedDeviceIndicesOfClass; - /** C type : GetTrackedDeviceActivityLevel_callback* */ - public VR_IVRSystem_FnTable.GetTrackedDeviceActivityLevel_callback GetTrackedDeviceActivityLevel; - /** C type : ApplyTransform_callback* */ - public VR_IVRSystem_FnTable.ApplyTransform_callback ApplyTransform; - /** C type : GetTrackedDeviceIndexForControllerRole_callback* */ - public VR_IVRSystem_FnTable.GetTrackedDeviceIndexForControllerRole_callback GetTrackedDeviceIndexForControllerRole; - /** C type : GetControllerRoleForTrackedDeviceIndex_callback* */ - public VR_IVRSystem_FnTable.GetControllerRoleForTrackedDeviceIndex_callback GetControllerRoleForTrackedDeviceIndex; - /** C type : GetTrackedDeviceClass_callback* */ - public VR_IVRSystem_FnTable.GetTrackedDeviceClass_callback GetTrackedDeviceClass; - /** C type : IsTrackedDeviceConnected_callback* */ - public VR_IVRSystem_FnTable.IsTrackedDeviceConnected_callback IsTrackedDeviceConnected; - /** C type : GetBoolTrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetBoolTrackedDeviceProperty_callback GetBoolTrackedDeviceProperty; - /** C type : GetFloatTrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetFloatTrackedDeviceProperty_callback GetFloatTrackedDeviceProperty; - /** C type : GetInt32TrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetInt32TrackedDeviceProperty_callback GetInt32TrackedDeviceProperty; - /** C type : GetUint64TrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetUint64TrackedDeviceProperty_callback GetUint64TrackedDeviceProperty; - /** C type : GetMatrix34TrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetMatrix34TrackedDeviceProperty_callback GetMatrix34TrackedDeviceProperty; - /** C type : GetArrayTrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetArrayTrackedDeviceProperty_callback GetArrayTrackedDeviceProperty; - /** C type : GetStringTrackedDeviceProperty_callback* */ - public VR_IVRSystem_FnTable.GetStringTrackedDeviceProperty_callback GetStringTrackedDeviceProperty; - /** C type : GetPropErrorNameFromEnum_callback* */ - public VR_IVRSystem_FnTable.GetPropErrorNameFromEnum_callback GetPropErrorNameFromEnum; - /** C type : PollNextEvent_callback* */ - public VR_IVRSystem_FnTable.PollNextEvent_callback PollNextEvent; - /** C type : PollNextEventWithPose_callback* */ - public VR_IVRSystem_FnTable.PollNextEventWithPose_callback PollNextEventWithPose; - /** C type : GetEventTypeNameFromEnum_callback* */ - public VR_IVRSystem_FnTable.GetEventTypeNameFromEnum_callback GetEventTypeNameFromEnum; - /** C type : GetHiddenAreaMesh_callback* */ - public VR_IVRSystem_FnTable.GetHiddenAreaMesh_callback GetHiddenAreaMesh; - /** C type : GetControllerState_callback* */ - public VR_IVRSystem_FnTable.GetControllerState_callback GetControllerState; - /** C type : GetControllerStateWithPose_callback* */ - public VR_IVRSystem_FnTable.GetControllerStateWithPose_callback GetControllerStateWithPose; - /** C type : TriggerHapticPulse_callback* */ - public VR_IVRSystem_FnTable.TriggerHapticPulse_callback TriggerHapticPulse; - /** C type : GetButtonIdNameFromEnum_callback* */ - public VR_IVRSystem_FnTable.GetButtonIdNameFromEnum_callback GetButtonIdNameFromEnum; - /** C type : GetControllerAxisTypeNameFromEnum_callback* */ - public VR_IVRSystem_FnTable.GetControllerAxisTypeNameFromEnum_callback GetControllerAxisTypeNameFromEnum; - /** C type : IsInputAvailable_callback* */ - public VR_IVRSystem_FnTable.IsInputAvailable_callback IsInputAvailable; - /** C type : IsSteamVRDrawingControllers_callback* */ - public VR_IVRSystem_FnTable.IsSteamVRDrawingControllers_callback IsSteamVRDrawingControllers; - /** C type : ShouldApplicationPause_callback* */ - public VR_IVRSystem_FnTable.ShouldApplicationPause_callback ShouldApplicationPause; - /** C type : ShouldApplicationReduceRenderingWork_callback* */ - public VR_IVRSystem_FnTable.ShouldApplicationReduceRenderingWork_callback ShouldApplicationReduceRenderingWork; - /** C type : DriverDebugRequest_callback* */ - public VR_IVRSystem_FnTable.DriverDebugRequest_callback DriverDebugRequest; - /** C type : PerformFirmwareUpdate_callback* */ - public VR_IVRSystem_FnTable.PerformFirmwareUpdate_callback PerformFirmwareUpdate; - /** C type : AcknowledgeQuit_Exiting_callback* */ - public VR_IVRSystem_FnTable.AcknowledgeQuit_Exiting_callback AcknowledgeQuit_Exiting; - /** C type : AcknowledgeQuit_UserPrompt_callback* */ - public VR_IVRSystem_FnTable.AcknowledgeQuit_UserPrompt_callback AcknowledgeQuit_UserPrompt; - /** native declaration : headers\openvr_capi.h:1752 */ - public interface GetRecommendedRenderTargetSize_callback extends Callback { - void apply(IntByReference pnWidth, IntByReference pnHeight); - }; - /** native declaration : headers\openvr_capi.h:1753 */ - public interface GetProjectionMatrix_callback extends Callback { - com.jme3.system.jopenvr.HmdMatrix44_t.ByValue apply(int eEye, float fNearZ, float fFarZ); - }; - /** native declaration : headers\openvr_capi.h:1754 */ - public interface GetProjectionRaw_callback extends Callback { - void apply(int eEye, FloatByReference pfLeft, FloatByReference pfRight, FloatByReference pfTop, FloatByReference pfBottom); - }; - /** native declaration : headers\openvr_capi.h:1755 */ - public interface ComputeDistortion_callback extends Callback { - byte apply(int eEye, float fU, float fV, DistortionCoordinates_t pDistortionCoordinates); - }; - /** native declaration : headers\openvr_capi.h:1756 */ - public interface GetEyeToHeadTransform_callback extends Callback { - HmdMatrix34_t.ByValue apply(int eEye); - }; - /** native declaration : headers\openvr_capi.h:1757 */ - public interface GetTimeSinceLastVsync_callback extends Callback { - byte apply(FloatByReference pfSecondsSinceLastVsync, LongByReference pulFrameCounter); - }; - /** native declaration : headers\openvr_capi.h:1758 */ - public interface GetD3D9AdapterIndex_callback extends Callback { - int apply(); - }; - /** native declaration : headers\openvr_capi.h:1759 */ - public interface GetDXGIOutputInfo_callback extends Callback { - void apply(IntByReference pnAdapterIndex); - }; - /** native declaration : headers\openvr_capi.h:1760 */ - public interface GetOutputDevice_callback extends Callback { - void apply(LongByReference pnDevice, int textureType, VkInstance_T pInstance); - }; - /** native declaration : headers\openvr_capi.h:1761 */ - public interface IsDisplayOnDesktop_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1762 */ - public interface SetDisplayVisibility_callback extends Callback { - byte apply(byte bIsVisibleOnDesktop); - }; - /** native declaration : headers\openvr_capi.h:1763 */ - public interface GetDeviceToAbsoluteTrackingPose_callback extends Callback { - void apply(int eOrigin, float fPredictedSecondsToPhotonsFromNow, TrackedDevicePose_t pTrackedDevicePoseArray, int unTrackedDevicePoseArrayCount); - }; - /** native declaration : headers\openvr_capi.h:1764 */ - public interface ResetSeatedZeroPose_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:1765 */ - public interface GetSeatedZeroPoseToStandingAbsoluteTrackingPose_callback extends Callback { - HmdMatrix34_t.ByValue apply(); - }; - /** native declaration : headers\openvr_capi.h:1766 */ - public interface GetRawZeroPoseToStandingAbsoluteTrackingPose_callback extends Callback { - HmdMatrix34_t.ByValue apply(); - }; - /** native declaration : headers\openvr_capi.h:1767 */ - public interface GetSortedTrackedDeviceIndicesOfClass_callback extends Callback { - int apply(int eTrackedDeviceClass, IntByReference punTrackedDeviceIndexArray, int unTrackedDeviceIndexArrayCount, int unRelativeToTrackedDeviceIndex); - }; - /** native declaration : headers\openvr_capi.h:1768 */ - public interface GetTrackedDeviceActivityLevel_callback extends Callback { - int apply(int unDeviceId); - }; - /** native declaration : headers\openvr_capi.h:1769 */ - public interface ApplyTransform_callback extends Callback { - void apply(TrackedDevicePose_t pOutputPose, TrackedDevicePose_t pTrackedDevicePose, HmdMatrix34_t pTransform); - }; - /** native declaration : headers\openvr_capi.h:1770 */ - public interface GetTrackedDeviceIndexForControllerRole_callback extends Callback { - int apply(int unDeviceType); - }; - /** native declaration : headers\openvr_capi.h:1771 */ - public interface GetControllerRoleForTrackedDeviceIndex_callback extends Callback { - int apply(int unDeviceIndex); - }; - /** native declaration : headers\openvr_capi.h:1772 */ - public interface GetTrackedDeviceClass_callback extends Callback { - int apply(int unDeviceIndex); - }; - /** native declaration : headers\openvr_capi.h:1773 */ - public interface IsTrackedDeviceConnected_callback extends Callback { - byte apply(int unDeviceIndex); - }; - /** native declaration : headers\openvr_capi.h:1774 */ - public interface GetBoolTrackedDeviceProperty_callback extends Callback { - byte apply(int unDeviceIndex, int prop, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1775 */ - public interface GetFloatTrackedDeviceProperty_callback extends Callback { - float apply(int unDeviceIndex, int prop, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1776 */ - public interface GetInt32TrackedDeviceProperty_callback extends Callback { - int apply(int unDeviceIndex, int prop, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1777 */ - public interface GetUint64TrackedDeviceProperty_callback extends Callback { - long apply(int unDeviceIndex, int prop, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1778 */ - public interface GetMatrix34TrackedDeviceProperty_callback extends Callback { - HmdMatrix34_t.ByValue apply(int unDeviceIndex, int prop, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1779 */ - public interface GetArrayTrackedDeviceProperty_callback extends Callback { - int apply(int unDeviceIndex, int prop, int propType, Pointer pBuffer, int unBufferSize, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1780 */ - public interface GetStringTrackedDeviceProperty_callback extends Callback { - int apply(int unDeviceIndex, int prop, Pointer pchValue, int unBufferSize, IntByReference pError); - }; - /** native declaration : headers\openvr_capi.h:1781 */ - public interface GetPropErrorNameFromEnum_callback extends Callback { - Pointer apply(int error); - }; - /** native declaration : headers\openvr_capi.h:1782 */ - public interface PollNextEvent_callback extends Callback { - byte apply(VREvent_t pEvent, int uncbVREvent); - }; - /** native declaration : headers\openvr_capi.h:1783 */ - public interface PollNextEventWithPose_callback extends Callback { - byte apply(int eOrigin, VREvent_t pEvent, int uncbVREvent, TrackedDevicePose_t pTrackedDevicePose); - }; - /** native declaration : headers\openvr_capi.h:1784 */ - public interface GetEventTypeNameFromEnum_callback extends Callback { - Pointer apply(int eType); - }; - /** native declaration : headers\openvr_capi.h:1785 */ - public interface GetHiddenAreaMesh_callback extends Callback { - com.jme3.system.jopenvr.HiddenAreaMesh_t.ByValue apply(int eEye, int type); - }; - /** native declaration : headers\openvr_capi.h:1786 */ - public interface GetControllerState_callback extends Callback { - byte apply(int unControllerDeviceIndex, VRControllerState_t pControllerState, int unControllerStateSize); - }; - /** native declaration : headers\openvr_capi.h:1787 */ - public interface GetControllerStateWithPose_callback extends Callback { - byte apply(int eOrigin, int unControllerDeviceIndex, VRControllerState_t pControllerState, int unControllerStateSize, TrackedDevicePose_t pTrackedDevicePose); - }; - /** native declaration : headers\openvr_capi.h:1788 */ - public interface TriggerHapticPulse_callback extends Callback { - void apply(int unControllerDeviceIndex, int unAxisId, short usDurationMicroSec); - }; - /** native declaration : headers\openvr_capi.h:1789 */ - public interface GetButtonIdNameFromEnum_callback extends Callback { - Pointer apply(int eButtonId); - }; - /** native declaration : headers\openvr_capi.h:1790 */ - public interface GetControllerAxisTypeNameFromEnum_callback extends Callback { - Pointer apply(int eAxisType); - }; - /** native declaration : headers\openvr_capi.h:1791 */ - public interface IsInputAvailable_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1792 */ - public interface IsSteamVRDrawingControllers_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1793 */ - public interface ShouldApplicationPause_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1794 */ - public interface ShouldApplicationReduceRenderingWork_callback extends Callback { - byte apply(); - }; - /** native declaration : headers\openvr_capi.h:1795 */ - public interface DriverDebugRequest_callback extends Callback { - int apply(int unDeviceIndex, Pointer pchRequest, Pointer pchResponseBuffer, int unResponseBufferSize); - }; - /** native declaration : headers\openvr_capi.h:1796 */ - public interface PerformFirmwareUpdate_callback extends Callback { - int apply(int unDeviceIndex); - }; - /** native declaration : headers\openvr_capi.h:1797 */ - public interface AcknowledgeQuit_Exiting_callback extends Callback { - void apply(); - }; - /** native declaration : headers\openvr_capi.h:1798 */ - public interface AcknowledgeQuit_UserPrompt_callback extends Callback { - void apply(); - }; - public VR_IVRSystem_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetRecommendedRenderTargetSize", "GetProjectionMatrix", "GetProjectionRaw", "ComputeDistortion", "GetEyeToHeadTransform", "GetTimeSinceLastVsync", "GetD3D9AdapterIndex", "GetDXGIOutputInfo", "GetOutputDevice", "IsDisplayOnDesktop", "SetDisplayVisibility", "GetDeviceToAbsoluteTrackingPose", "ResetSeatedZeroPose", "GetSeatedZeroPoseToStandingAbsoluteTrackingPose", "GetRawZeroPoseToStandingAbsoluteTrackingPose", "GetSortedTrackedDeviceIndicesOfClass", "GetTrackedDeviceActivityLevel", "ApplyTransform", "GetTrackedDeviceIndexForControllerRole", "GetControllerRoleForTrackedDeviceIndex", "GetTrackedDeviceClass", "IsTrackedDeviceConnected", "GetBoolTrackedDeviceProperty", "GetFloatTrackedDeviceProperty", "GetInt32TrackedDeviceProperty", "GetUint64TrackedDeviceProperty", "GetMatrix34TrackedDeviceProperty", "GetArrayTrackedDeviceProperty", "GetStringTrackedDeviceProperty", "GetPropErrorNameFromEnum", "PollNextEvent", "PollNextEventWithPose", "GetEventTypeNameFromEnum", "GetHiddenAreaMesh", "GetControllerState", "GetControllerStateWithPose", "TriggerHapticPulse", "GetButtonIdNameFromEnum", "GetControllerAxisTypeNameFromEnum", "IsInputAvailable", "IsSteamVRDrawingControllers", "ShouldApplicationPause", "ShouldApplicationReduceRenderingWork", "DriverDebugRequest", "PerformFirmwareUpdate", "AcknowledgeQuit_Exiting", "AcknowledgeQuit_UserPrompt"); - } - public VR_IVRSystem_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRSystem_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRSystem_FnTable implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRTrackedCamera_FnTable.java b/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRTrackedCamera_FnTable.java deleted file mode 100644 index 1fc5d469a6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/jopenvr/VR_IVRTrackedCamera_FnTable.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.jme3.system.jopenvr; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.LongByReference; -import com.sun.jna.ptr.PointerByReference; -import java.util.Arrays; -import java.util.List; -/** - * native declaration : headers\openvr_capi.h:1833
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class VR_IVRTrackedCamera_FnTable extends Structure { - /** C type : GetCameraErrorNameFromEnum_callback* */ - public VR_IVRTrackedCamera_FnTable.GetCameraErrorNameFromEnum_callback GetCameraErrorNameFromEnum; - /** C type : HasCamera_callback* */ - public VR_IVRTrackedCamera_FnTable.HasCamera_callback HasCamera; - /** C type : GetCameraFrameSize_callback* */ - public VR_IVRTrackedCamera_FnTable.GetCameraFrameSize_callback GetCameraFrameSize; - /** C type : GetCameraIntrinsics_callback* */ - public VR_IVRTrackedCamera_FnTable.GetCameraIntrinsics_callback GetCameraIntrinsics; - /** C type : GetCameraProjection_callback* */ - public VR_IVRTrackedCamera_FnTable.GetCameraProjection_callback GetCameraProjection; - /** C type : AcquireVideoStreamingService_callback* */ - public VR_IVRTrackedCamera_FnTable.AcquireVideoStreamingService_callback AcquireVideoStreamingService; - /** C type : ReleaseVideoStreamingService_callback* */ - public VR_IVRTrackedCamera_FnTable.ReleaseVideoStreamingService_callback ReleaseVideoStreamingService; - /** C type : GetVideoStreamFrameBuffer_callback* */ - public VR_IVRTrackedCamera_FnTable.GetVideoStreamFrameBuffer_callback GetVideoStreamFrameBuffer; - /** C type : GetVideoStreamTextureSize_callback* */ - public VR_IVRTrackedCamera_FnTable.GetVideoStreamTextureSize_callback GetVideoStreamTextureSize; - /** C type : GetVideoStreamTextureD3D11_callback* */ - public VR_IVRTrackedCamera_FnTable.GetVideoStreamTextureD3D11_callback GetVideoStreamTextureD3D11; - /** C type : GetVideoStreamTextureGL_callback* */ - public VR_IVRTrackedCamera_FnTable.GetVideoStreamTextureGL_callback GetVideoStreamTextureGL; - /** C type : ReleaseVideoStreamTextureGL_callback* */ - public VR_IVRTrackedCamera_FnTable.ReleaseVideoStreamTextureGL_callback ReleaseVideoStreamTextureGL; - /** native declaration : headers\openvr_capi.h:1821 */ - public interface GetCameraErrorNameFromEnum_callback extends Callback { - Pointer apply(int eCameraError); - }; - /** native declaration : headers\openvr_capi.h:1822 */ - public interface HasCamera_callback extends Callback { - int apply(int nDeviceIndex, Pointer pHasCamera); - }; - /** native declaration : headers\openvr_capi.h:1823 */ - public interface GetCameraFrameSize_callback extends Callback { - int apply(int nDeviceIndex, int eFrameType, IntByReference pnWidth, IntByReference pnHeight, IntByReference pnFrameBufferSize); - }; - /** native declaration : headers\openvr_capi.h:1824 */ - public interface GetCameraIntrinsics_callback extends Callback { - int apply(int nDeviceIndex, int eFrameType, HmdVector2_t pFocalLength, HmdVector2_t pCenter); - }; - /** native declaration : headers\openvr_capi.h:1825 */ - public interface GetCameraProjection_callback extends Callback { - int apply(int nDeviceIndex, int eFrameType, float flZNear, float flZFar, HmdMatrix44_t pProjection); - }; - /** native declaration : headers\openvr_capi.h:1826 */ - public interface AcquireVideoStreamingService_callback extends Callback { - int apply(int nDeviceIndex, LongByReference pHandle); - }; - /** native declaration : headers\openvr_capi.h:1827 */ - public interface ReleaseVideoStreamingService_callback extends Callback { - int apply(long hTrackedCamera); - }; - /** native declaration : headers\openvr_capi.h:1828 */ - public interface GetVideoStreamFrameBuffer_callback extends Callback { - int apply(long hTrackedCamera, int eFrameType, Pointer pFrameBuffer, int nFrameBufferSize, CameraVideoStreamFrameHeader_t pFrameHeader, int nFrameHeaderSize); - }; - /** native declaration : headers\openvr_capi.h:1829 */ - public interface GetVideoStreamTextureSize_callback extends Callback { - int apply(int nDeviceIndex, int eFrameType, VRTextureBounds_t pTextureBounds, IntByReference pnWidth, IntByReference pnHeight); - }; - /** native declaration : headers\openvr_capi.h:1830 */ - public interface GetVideoStreamTextureD3D11_callback extends Callback { - int apply(long hTrackedCamera, int eFrameType, Pointer pD3D11DeviceOrResource, PointerByReference ppD3D11ShaderResourceView, CameraVideoStreamFrameHeader_t pFrameHeader, int nFrameHeaderSize); - }; - /** native declaration : headers\openvr_capi.h:1831 */ - public interface GetVideoStreamTextureGL_callback extends Callback { - int apply(long hTrackedCamera, int eFrameType, IntByReference pglTextureId, CameraVideoStreamFrameHeader_t pFrameHeader, int nFrameHeaderSize); - }; - /** native declaration : headers\openvr_capi.h:1832 */ - public interface ReleaseVideoStreamTextureGL_callback extends Callback { - int apply(long hTrackedCamera, int glTextureId); - }; - public VR_IVRTrackedCamera_FnTable() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("GetCameraErrorNameFromEnum", "HasCamera", "GetCameraFrameSize", "GetCameraIntrinsics", "GetCameraProjection", "AcquireVideoStreamingService", "ReleaseVideoStreamingService", "GetVideoStreamFrameBuffer", "GetVideoStreamTextureSize", "GetVideoStreamTextureD3D11", "GetVideoStreamTextureGL", "ReleaseVideoStreamTextureGL"); - } - public VR_IVRTrackedCamera_FnTable(Pointer peer) { - super(peer); - } - public static class ByReference extends VR_IVRTrackedCamera_FnTable implements Structure.ByReference { - - }; - public static class ByValue extends VR_IVRTrackedCamera_FnTable implements Structure.ByValue { - - }; -} 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 deleted file mode 100644 index 1c461e6f36..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglContextVR.java +++ /dev/null @@ -1,299 +0,0 @@ -package com.jme3.system.lwjgl; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -import com.jme3.input.lwjgl.GlfwJoystickInput; -import com.jme3.input.lwjgl.GlfwKeyInputVR; -import com.jme3.input.lwjgl.GlfwMouseInputVR; -import com.jme3.renderer.Renderer; -import com.jme3.renderer.RendererException; -import com.jme3.renderer.lwjgl.LwjglGL; -import com.jme3.renderer.lwjgl.LwjglGLExt; -import com.jme3.renderer.lwjgl.LwjglGLFboEXT; -import com.jme3.renderer.lwjgl.LwjglGLFboGL3; -import com.jme3.renderer.opengl.*; -import com.jme3.system.*; - -import org.lwjgl.glfw.GLFW; -import org.lwjgl.opengl.ARBDebugOutput; -import org.lwjgl.opengl.ARBFramebufferObject; -import org.lwjgl.opengl.EXTFramebufferMultisample; -import org.lwjgl.opengl.GLCapabilities; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; - -import static org.lwjgl.opengl.GL.createCapabilities; -import static org.lwjgl.opengl.GL11.glGetInteger; - -/** - * A VR oriented LWJGL implementation of a graphics context. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public abstract class LwjglContextVR implements JmeContext { - - private static final Logger logger = Logger.getLogger(LwjglContextVR.class.getName()); - - protected static final String THREAD_NAME = "jME3 Main"; - - protected AtomicBoolean created = new AtomicBoolean(false); - protected AtomicBoolean renderable = new AtomicBoolean(false); - protected final Object createdLock = new Object(); - - protected AppSettings settings = new AppSettings(true); - protected Renderer renderer; - protected GlfwKeyInputVR keyInput; - protected GlfwMouseInputVR mouseInput; - protected GlfwJoystickInput joyInput; - protected Timer timer; - protected SystemListener listener; - - public void setSystemListener(SystemListener listener) { - this.listener = listener; - } - - protected void printContextInitInfo() { - logger.log(Level.INFO, "LWJGL {0} context running on thread {1}\n" - + " * Graphics Adapter: GLFW {2}", - new Object[]{Integer.toString(org.lwjgl.Version.VERSION_MAJOR), Thread.currentThread().getName(), GLFW.glfwGetVersionString()}); - } - - protected int determineMaxSamples() { - // If we already have a valid context, determine samples using current context. - logger.log(Level.SEVERE, "glfwExtensionSupported(\"GL_ARB_framebuffer_object\"): "+GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object")); - logger.log(Level.SEVERE, "glfwExtensionSupported(\"GL_EXT_framebuffer_multisample\"): "+GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object")); - - if (GLFW.glfwExtensionSupported("GL_ARB_framebuffer_object")) { - return glGetInteger(ARBFramebufferObject.GL_MAX_SAMPLES); - } else if (GLFW.glfwExtensionSupported("GL_EXT_framebuffer_multisample")) { - return glGetInteger(EXTFramebufferMultisample.GL_MAX_SAMPLES_EXT); - } - - return Integer.MAX_VALUE; - } - - protected void loadNatives() { - if (JmeSystem.isLowPermissions()) { - return; - } - - if ("LWJGL".equals(settings.getAudioRenderer())) { - NativeLibraryLoader.loadNativeLibrary("openal-lwjgl3", true); - } - - if (NativeLibraryLoader.isUsingNativeBullet()) { - NativeLibraryLoader.loadNativeLibrary("bulletjme", true); - } - - NativeLibraryLoader.loadNativeLibrary("glfw-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("jemalloc-lwjgl3", true); - NativeLibraryLoader.loadNativeLibrary("lwjgl3", true); - } - - /** - * Check if the display is a retina display. - * @return true if the display is a retina display and false otherwise. - */ - public boolean isRetinaDisplay() { - return GLFW.glfwGetVersionString().contains("retina"); - } - - protected int getNumSamplesToUse() { - int samples = 0; - if (settings.getSamples() > 1) { - samples = settings.getSamples(); - final int supportedSamples = determineMaxSamples(); - if (supportedSamples < samples) { - logger.log(Level.WARNING, - "Couldn't satisfy antialiasing samples requirement: x{0}. " - + "Video hardware only supports: x{1}", - new Object[]{samples, supportedSamples}); - - samples = supportedSamples; - } - } - return samples; - } - - protected void initContextFirstTime() { - final GLCapabilities capabilities = createCapabilities(settings.getRenderer().equals(AppSettings.LWJGL_OPENGL32)); - - if (!capabilities.OpenGL20) { - throw new RendererException("OpenGL 2.0 or higher is required for jMonkeyEngine"); - } - - if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL2) - || settings.getRenderer().equals(AppSettings.LWJGL_OPENGL32)) { - GL gl = new LwjglGL(); - GLExt glext = new LwjglGLExt(); - GLFbo glfbo; - - if (capabilities.OpenGL30) { - glfbo = new LwjglGLFboGL3(); - } else { - glfbo = new LwjglGLFboEXT(); - } - - if (settings.getBoolean("GraphicsDebug")) { - gl = new GLDebugDesktop(gl, glext, glfbo); - glext = (GLExt) gl; - glfbo = (GLFbo) gl; - } - - if (settings.getBoolean("GraphicsTiming")) { - GLTimingState timingState = new GLTimingState(); - gl = (GL) GLTiming.createGLTiming(gl, timingState, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTiming.createGLTiming(glext, timingState, GLExt.class); - glfbo = (GLFbo) GLTiming.createGLTiming(glfbo, timingState, GLFbo.class); - } - - if (settings.getBoolean("GraphicsTrace")) { - gl = (GL) GLTracer.createDesktopGlTracer(gl, GL.class, GL2.class, GL3.class, GL4.class); - glext = (GLExt) GLTracer.createDesktopGlTracer(glext, GLExt.class); - glfbo = (GLFbo) GLTracer.createDesktopGlTracer(glfbo, GLFbo.class); - } - - renderer = new GLRenderer(gl, glext, glfbo); - renderer.initialize(); - } else { - throw new UnsupportedOperationException("Unsupported renderer: " + settings.getRenderer()); - } - - if (capabilities.GL_ARB_debug_output && settings.getBoolean("GraphicsDebug")) { - ARBDebugOutput.glDebugMessageCallbackARB(new LwjglGLDebugOutputHandler(), 0); - } - - renderer.setMainFrameBufferSrgb(settings.isGammaCorrection()); - renderer.setLinearizeSrgbImages(settings.isGammaCorrection()); - - // Init input - if (keyInput != null) { - keyInput.initialize(); - } - - if (mouseInput != null) { - mouseInput.initialize(); - } - - if (joyInput != null) { - joyInput.initialize(); - } - renderable.set(true); - } - - /** - * Context internal destroy. - */ - public void internalDestroy() { - renderer = null; - timer = null; - renderable.set(false); - synchronized (createdLock) { - created.set(false); - createdLock.notifyAll(); - } - } - - /** - * Context internal create. - */ - public void internalCreate() { - synchronized (createdLock) { - created.set(true); - createdLock.notifyAll(); - } - - initContextFirstTime(); - } - - /** - * Create the context. - */ - public void create() { - create(false); - } - - /** - * Destroy the context. - */ - public void destroy() { - destroy(false); - } - - /** - * - * @param createdVal - */ - protected void waitFor(boolean createdVal) { - synchronized (createdLock) { - while (created.get() != createdVal) { - try { - createdLock.wait(); - } catch (InterruptedException ignored) { - } - } - } - } - - @Override - public boolean isCreated() { - return created.get(); - } - - @Override - public boolean isRenderable() { - return renderable.get(); - } - - @Override - public void setSettings(AppSettings settings) { - this.settings.copyFrom(settings); - } - - @Override - public AppSettings getSettings() { - return settings; - } - - @Override - public Renderer getRenderer() { - return renderer; - } - - @Override - public Timer getTimer() { - return timer; - } - -} diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java deleted file mode 100644 index 8915940f77..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglDisplayVR.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ -package com.jme3.system.lwjgl; - -import com.jme3.opencl.Context; - -/** - * A VR oriented LWJGL display. - * @author Daniel Johansson - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public class LwjglDisplayVR extends LwjglWindowVR { - - /** - * Create a new VR oriented LWJGL display. - */ - public LwjglDisplayVR() { - super(Type.Display); - } - - @Override - public Context getOpenCLContext() { - return null; - } - - -} diff --git a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java b/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java deleted file mode 100644 index 9fc51e805c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglOffscreenBufferVR.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.jme3.system.lwjgl; - -/* - * Copyright (c) 2009-2012 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.system.JmeContext; - -/** - * A VR oriented LWJGL offscreen buffer. - * @author Daniel Johansson - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public class LwjglOffscreenBufferVR extends LwjglWindow { - - /** - * Create a new VR oriented LWJGL offscreen buffer. - */ - public LwjglOffscreenBufferVR() { - super(JmeContext.Type.OffscreenSurface); - } - - -} 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 deleted file mode 100644 index 4bcad18766..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/lwjgl/LwjglWindowVR.java +++ /dev/null @@ -1,570 +0,0 @@ -package com.jme3.system.lwjgl; - -/* - * Copyright (c) 2009-2018 jMonkeyEngine - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in the - * documentation and/or other materials provided with the distribution. - * - * * Neither the name of 'jMonkeyEngine' nor the names of its contributors - * may be used to endorse or promote products derived from this software - * without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED - * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR - * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR - * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, - * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, - * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR - * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF - * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING - * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS - * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -import com.jme3.input.JoyInput; -import com.jme3.input.KeyInput; -import com.jme3.input.MouseInput; -import com.jme3.input.TouchInput; -import com.jme3.input.lwjgl.GlfwJoystickInput; -import com.jme3.input.lwjgl.GlfwKeyInputVR; -import com.jme3.input.lwjgl.GlfwMouseInputVR; -import com.jme3.system.AppSettings; -import com.jme3.system.JmeContext; -import com.jme3.system.JmeSystem; -import com.jme3.system.NanoTimer; - -import org.lwjgl.glfw.*; - -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; -import java.util.logging.Logger; -import org.lwjgl.Version; - -import static org.lwjgl.glfw.GLFW.*; -import static org.lwjgl.opengl.GL11.GL_FALSE; -import static org.lwjgl.system.MemoryUtil.NULL; - -/** - * A wrapper class over the GLFW framework in LWJGL 3. - * - * @author Daniel Johansson - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - (c) 2016 - JOrigin project - http:/www.jorigin.org - */ -public abstract class LwjglWindowVR extends LwjglContextVR implements Runnable { - - private static final Logger LOGGER = Logger.getLogger(LwjglWindowVR.class.getName()); - - protected AtomicBoolean needClose = new AtomicBoolean(false); - protected final AtomicBoolean needRestart = new AtomicBoolean(false); - protected boolean wasActive = false; - protected boolean autoFlush = true; - protected boolean allowSwapBuffers = false; - private long window = NULL; - private final JmeContext.Type type; - private int frameRateLimit = -1; - private double frameSleepTime; - - private GLFWErrorCallback errorCallback; - private GLFWWindowSizeCallback windowSizeCallback; - private GLFWWindowFocusCallback windowFocusCallback; - private Thread mainThread; - - /** - * 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. - */ - public LwjglWindowVR(final JmeContext.Type type) { - if (!JmeContext.Type.Display.equals(type) && !JmeContext.Type.OffscreenSurface.equals(type) && !JmeContext.Type.Canvas.equals(type)) { - throw new IllegalArgumentException("Unsupported type '" + type.name() + "' provided"); - } - - this.type = type; - } - - /** - * @return Type.Display or Type.Canvas - */ - public JmeContext.Type getType() { - return type; - } - - /** - * Set the title if it's a windowed display - * - * @param title the title to set - */ - public void setTitle(final String title) { - if (created.get() && window != NULL) { - glfwSetWindowTitle(window, title); - } - } - - /** - * Restart if it's a windowed or full-screen display. - */ - public void restart() { - if (created.get()) { - needRestart.set(true); - } else { - LOGGER.warning("Display is not created, cannot restart window."); - } - } - - /** - * Apply the settings, changing resolution, etc. - * - * @param settings the settings to apply when creating the context. - */ - protected void createContext(final AppSettings settings) { - glfwSetErrorCallback(errorCallback = new GLFWErrorCallback() { - @Override - public void invoke(int error, long description) { - final String message = GLFWErrorCallback.getDescription(description); - listener.handleError(message, new Exception(message)); - } - }); - - if ( glfwInit() == false ) { - throw new IllegalStateException("Unable to initialize GLFW"); - } - - glfwDefaultWindowHints(); - - // just use defaults, which should provide the best compatibility - /*if (settings.getRenderer().equals(AppSettings.LWJGL_OPENGL3)) { - glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); - glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GLFW_FALSE); - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); - } else { - glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2); - glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); - }*/ - - if (settings.getBoolean("RendererDebug")) { - glfwWindowHint(GLFW_OPENGL_DEBUG_CONTEXT, GLFW_TRUE); - } - - if (settings.isGammaCorrection()) { - glfwWindowHint(GLFW_SRGB_CAPABLE, GLFW_TRUE); - } - - glfwWindowHint(GLFW_VISIBLE, GL_FALSE); - glfwWindowHint(GLFW_RESIZABLE, settings.isResizable() ? GLFW_TRUE : GLFW_FALSE); - glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); - glfwWindowHint(GLFW_DEPTH_BITS, settings.getDepthBits()); - glfwWindowHint(GLFW_STENCIL_BITS, settings.getStencilBits()); - glfwWindowHint(GLFW_SAMPLES, settings.getSamples()); - glfwWindowHint(GLFW_STEREO, settings.useStereo3D() ? GLFW_TRUE : GLFW_FALSE); - glfwWindowHint(GLFW_REFRESH_RATE, settings.getFrequency()); - - if (settings.getBitsPerPixel() == 24) { - glfwWindowHint(GLFW_RED_BITS, 8); - glfwWindowHint(GLFW_GREEN_BITS, 8); - glfwWindowHint(GLFW_BLUE_BITS, 8); - } else if (settings.getBitsPerPixel() == 16) { - glfwWindowHint(GLFW_RED_BITS, 5); - glfwWindowHint(GLFW_GREEN_BITS, 6); - glfwWindowHint(GLFW_BLUE_BITS, 5); - } - - glfwWindowHint(GLFW_ALPHA_BITS, settings.getAlphaBits()); - - // TODO: Add support for monitor selection - long monitor = NULL; - - if (settings.isFullscreen()) { - monitor = glfwGetPrimaryMonitor(); - } - - final GLFWVidMode videoMode = glfwGetVideoMode(glfwGetPrimaryMonitor()); - - if (settings.getWidth() <= 0 || settings.getHeight() <= 0) { - settings.setResolution(videoMode.width(), videoMode.height()); - } - - window = glfwCreateWindow(settings.getWidth(), settings.getHeight(), settings.getTitle(), monitor, NULL); - - if (window == NULL) { - throw new RuntimeException("Failed to create the GLFW window"); - } - - glfwSetWindowFocusCallback(window, windowFocusCallback = new GLFWWindowFocusCallback() { - - @Override - public void invoke(long window, boolean focused) { - if (wasActive != focused) { - if (wasActive == false) { - listener.gainFocus(); - timer.reset(); - wasActive = true; - } else { - listener.loseFocus(); - wasActive = false; - } - - - } - } - }); - - // Center the window - if( Type.Display.equals(type) ) { - if (!settings.isFullscreen()) { - glfwSetWindowPos(window, - (videoMode.width() - settings.getWidth()) / 2, - (videoMode.height() - settings.getHeight()) / 2); - } - } - - // Make the OpenGL context current - glfwMakeContextCurrent(window); - - // Enable vsync - if (settings.isVSync()) { - glfwSwapInterval(1); - } else { - glfwSwapInterval(0); - } - - // Make the window visible - if (Type.Display.equals(type)) { - glfwShowWindow(window); - - glfwFocusWindow(window); - } - - // Add a resize callback which delegates to the listener - glfwSetWindowSizeCallback(window, windowSizeCallback = new GLFWWindowSizeCallback() { - @Override - public void invoke(final long window, final int width, final int height) { - settings.setResolution(width, height); - listener.reshape(width, height); - } - }); - - allowSwapBuffers = settings.isSwapBuffers(); - - // TODO: When GLFW 3.2 is released and included in LWJGL 3.x then we should hopefully be able to set the window icon. - } - - /** - * Destroy the context. - */ - protected void destroyContext() { - try { - if (renderer != null) { - renderer.cleanup(); - } - - if (errorCallback != null) { - errorCallback.free(); - errorCallback = null; - } - - if (windowSizeCallback != null) { - - windowSizeCallback.free(); - - windowSizeCallback = null; - } - - if (windowFocusCallback != null) { - - windowFocusCallback.free(); - - windowFocusCallback = null; - } - - if (window != NULL) { - glfwDestroyWindow(window); - window = NULL; - } - } catch (Exception ex) { - listener.handleError("Failed to destroy context", ex); - } - } - - @Override - public void create(boolean waitFor) { - if (created.get()) { - LOGGER.warning("create() called when display is already created!"); - return; - } - - mainThread = Thread.currentThread(); - run(); - } - - /** - * Does LWJGL display initialization in the OpenGL thread - */ - protected boolean initInThread() { - try { - if (!JmeSystem.isLowPermissions()) { - // Enable uncaught exception handler only for current thread - Thread.currentThread().setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { - @Override - public void uncaughtException(Thread thread, Throwable thrown) { - listener.handleError("Uncaught exception thrown in " + thread.toString(), thrown); - if (needClose.get()) { - // listener.handleError() has requested the - // context to close. Satisfy request. - deinitInThread(); - } - } - }); - } - - loadNatives(); - timer = new NanoTimer(); - - // For canvas, this will create a pbuffer, - // allowing us to query information. - // When the canvas context becomes available, it will - // be replaced seamlessly. - createContext(settings); - printContextInitInfo(); - - created.set(true); - super.internalCreate(); - } catch (Exception ex) { - try { - if (window != NULL) { - glfwDestroyWindow(window); - window = NULL; - } - } catch (Exception ex2) { - LOGGER.log(Level.WARNING, null, ex2); - } - - listener.handleError("Failed to create display", ex); - return false; // if we failed to create display, do not continue - } - - listener.initialize(); - return true; - } - - /** - * execute one iteration of the render loop in the OpenGL thread - */ - protected void runLoop() { - // If a restart is required, lets recreate the context. - if (needRestart.getAndSet(false)) { - try { - destroyContext(); - createContext(settings); - } catch (Exception ex) { - LOGGER.log(Level.SEVERE, "Failed to set display settings!", ex); - } - - LOGGER.fine("Display restarted."); - } - - if (!created.get()) { - throw new IllegalStateException(); - } - - listener.update(); - - // All this does is call swap buffers - // If the canvas is not active, there's no need to waste time - // doing that .. - if (renderable.get()) { - // calls swap buffers, etc. - try { - if (allowSwapBuffers && autoFlush) { - glfwSwapBuffers(window); - } - } catch (Throwable ex) { - listener.handleError("Error while swapping buffers", ex); - } - } - - // Subclasses just call GLObjectManager clean up objects here - // it is safe .. for now. - if (renderer != null) { - renderer.postFrame(); - } - - if (autoFlush) { - if (frameRateLimit != getSettings().getFrameRate()) { - setFrameRateLimit(getSettings().getFrameRate()); - } - } else if (frameRateLimit != 20) { - setFrameRateLimit(20); - } - - // If software frame rate limiting has been asked for, lets calculate sleep time based on a base value calculated - // from 1000 / frameRateLimit in milliseconds subtracting the time it has taken to render last frame. - // This gives an approximate limit within 3 fps of the given frame rate limit. - if (frameRateLimit > 0) { - final double sleep = frameSleepTime - (timer.getTimePerFrame() / 1000.0); - final long sleepMillis = (long) sleep; - final int additionalNanos = (int) ((sleep - sleepMillis) * 1000000.0); - - if (sleepMillis >= 0 && additionalNanos >= 0) { - try { - Thread.sleep(sleepMillis, additionalNanos); - } catch (InterruptedException ignored) { - } - } - } - - glfwPollEvents(); - } - - private void setFrameRateLimit(int frameRateLimit) { - this.frameRateLimit = frameRateLimit; - frameSleepTime = 1000.0 / this.frameRateLimit; - } - - /** - * De-initialize in the OpenGL thread. - */ - - protected void deinitInThread() { - listener.destroy(); - - destroyContext(); - super.internalDestroy(); - - LOGGER.fine("Display destroyed."); - } - - @Override - public void run() { - if (listener == null) { - throw new IllegalStateException("SystemListener is not set on context!" - + "Must set with JmeContext.setSystemListener()."); - } - - - LOGGER.log(Level.FINE, "Using LWJGL {0}", Version.getVersion()); - - if (!initInThread()) { - LOGGER.log(Level.SEVERE, "Display initialization failed. Cannot continue."); - return; - } - - while (true) { - - runLoop(); - - if (needClose.get()) { - break; - } - - if (glfwWindowShouldClose(window) == true) { - listener.requestClose(false); - } - } - - deinitInThread(); - } - - public JoyInput getJoyInput() { - if (joyInput == null) { - joyInput = new GlfwJoystickInput(); - } - return joyInput; - } - - public MouseInput getMouseInput() { - if (mouseInput == null) { - mouseInput = new GlfwMouseInputVR(this); - } - return mouseInput; - } - - public KeyInput getKeyInput() { - if (keyInput == null) { - keyInput = new GlfwKeyInputVR(this); - } - - return keyInput; - } - - public TouchInput getTouchInput() { - return null; - } - - public void setAutoFlushFrames(boolean enabled) { - this.autoFlush = enabled; - } - - public void destroy(boolean waitFor) { - needClose.set(true); - if (mainThread == Thread.currentThread()) { - // Ignore waitFor. - return; - } - - if (waitFor) { - waitFor(false); - } - } - - /** - * Get the window handle. - * @return the window handle as an internal GLFW identifier. - */ - public long getWindowHandle() { - return window; - } - - - // TODO: Implement support for window icon when GLFW supports it. - /* - private ByteBuffer[] imagesToByteBuffers(Object[] images) { - ByteBuffer[] out = new ByteBuffer[images.length]; - for (int i = 0; i < images.length; i++) { - BufferedImage image = (BufferedImage) images[i]; - out[i] = imageToByteBuffer(image); - } - return out; - } - - - private ByteBuffer imageToByteBuffer(BufferedImage image) { - if (image.getType() != BufferedImage.TYPE_INT_ARGB_PRE) { - BufferedImage convertedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB_PRE); - Graphics2D g = convertedImage.createGraphics(); - double width = image.getWidth() * (double) 1; - double height = image.getHeight() * (double) 1; - g.drawImage(image, (int) ((convertedImage.getWidth() - width) / 2), - (int) ((convertedImage.getHeight() - height) / 2), - (int) (width), (int) (height), null); - g.dispose(); - image = convertedImage; - } - - byte[] imageBuffer = new byte[image.getWidth() * image.getHeight() * 4]; - int counter = 0; - for (int i = 0; i < image.getHeight(); i++) { - for (int j = 0; j < image.getWidth(); j++) { - int colorSpace = image.getRGB(j, i); - imageBuffer[counter + 0] = (byte) ((colorSpace << 8) >> 24); - imageBuffer[counter + 1] = (byte) ((colorSpace << 16) >> 24); - imageBuffer[counter + 2] = (byte) ((colorSpace << 24) >> 24); - imageBuffer[counter + 3] = (byte) (colorSpace >> 24); - counter += 4; - } - } - return ByteBuffer.wrap(imageBuffer); - } - */ -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientkit/OsvrClientKitLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientkit/OsvrClientKitLibrary.java deleted file mode 100644 index dd8792e399..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientkit/OsvrClientKitLibrary.java +++ /dev/null @@ -1,380 +0,0 @@ -package com.jme3.system.osvr.osvrclientkit; -import com.sun.jna.Callback; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -import com.sun.jna.ptr.PointerByReference; -/** - * JNA Wrapper for library osvrClientKit
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrClientKitLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrClientKit"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrClientKitLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrClientKitLibrary.class, OsvrClientKitLibrary.JNA_NATIVE_LIB); - } - /** - * Initialize the library. - * @param applicationIdentifier A null terminated string identifying your
                  - * application. Reverse DNS format strongly suggested.
                  - * @param flags initialization options (reserved) - pass 0 for now.
                  - * @return Client context - will be needed for subsequent calls
                  - * Original signature : OSVR_ClientContext osvrClientInit(const char[], uint32_t)
                  - * @deprecated use the safer method - * {@link #osvrClientInit(byte[], int)} - * instead - */ - @Deprecated - public static native OsvrClientKitLibrary.OSVR_ClientContext osvrClientInit(Pointer applicationIdentifier, int flags); - /** - * Initialize the library. - * @param applicationIdentifier A null terminated string identifying your
                  - * application. Reverse DNS format strongly suggested.
                  - * @param flags initialization options (reserved) - pass 0 for now.
                  - * @return Client context - will be needed for subsequent calls
                  - * Original signature : OSVR_ClientContext osvrClientInit(const char[], uint32_t) - */ - public static native OsvrClientKitLibrary.OSVR_ClientContext osvrClientInit(byte applicationIdentifier[], int flags); - /** - * Updates the state of the context - call regularly in your mainloop. - * @param ctx Client context
                  - * Original signature : OSVR_ReturnCode osvrClientUpdate(OSVR_ClientContext)
                  - * @deprecated use the safer method - * {@link #osvrClientUpdate(OsvrClientKitLibrary.OSVR_ClientContext)} - * instead - */ - @Deprecated - public static native byte osvrClientUpdate(Pointer ctx); - /** - * Updates the state of the context - call regularly in your mainloop. - * @param ctx Client context
                  - * Original signature : OSVR_ReturnCode osvrClientUpdate(OSVR_ClientContext) - */ - public static native byte osvrClientUpdate(OsvrClientKitLibrary.OSVR_ClientContext ctx); - /** - * Checks to see if the client context is fully started up and connected - * properly to a server.
                  - * If this reports that the client context is not OK, there may not be a server
                  - * running, or you may just have to call osvrClientUpdate() a few times to
                  - * permit startup to finish. The return value of this call will not change from
                  - * failure to success without calling osvrClientUpdate().
                  - * @param ctx Client context
                  - * @return OSVR_RETURN_FAILURE if not yet fully connected/initialized, or if
                  - * some other error (null context) occurs.
                  - * Original signature : OSVR_ReturnCode osvrClientCheckStatus(OSVR_ClientContext)
                  - * @deprecated use the safer method - * {@link #osvrClientCheckStatus(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext)} - * instead - */ - @Deprecated - public static native byte osvrClientCheckStatus(Pointer ctx); - /** - * Checks to see if the client context is fully started up and connected - * properly to a server.
                  - * If this reports that the client context is not OK, there may not be a server
                  - * running, or you may just have to call osvrClientUpdate() a few times to
                  - * permit startup to finish. The return value of this call will not change from
                  - * failure to success without calling osvrClientUpdate().
                  - * @param ctx Client context
                  - * @return OSVR_RETURN_FAILURE if not yet fully connected/initialized, or if
                  - * some other error (null context) occurs.
                  - * Original signature : OSVR_ReturnCode osvrClientCheckStatus(OSVR_ClientContext) - */ - public static native byte osvrClientCheckStatus(OsvrClientKitLibrary.OSVR_ClientContext ctx); - /** - * Shut down the library. - * @param ctx Client context
                  - * Original signature : OSVR_ReturnCode osvrClientShutdown(OSVR_ClientContext)
                  - * @deprecated use the safer method - * {@link #osvrClientShutdown(OsvrClientKitLibrary.OSVR_ClientContext)} - * instead - */ - @Deprecated - public static native byte osvrClientShutdown(Pointer ctx); - /** - * Shut down the library. - * @param ctx Client context
                  - * Original signature : OSVR_ReturnCode osvrClientShutdown(OSVR_ClientContext) - */ - public static native byte osvrClientShutdown(OsvrClientKitLibrary.OSVR_ClientContext ctx); - /** - * Log a message from the client. - * Original signature : void osvrClientLog(OSVR_ClientContext, OSVR_LogLevel, const char*)
                  - * @deprecated use the safer method - * {@link #osvrClientLog(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext, int, java.lang.String)} - * instead - */ - @Deprecated - public static native void osvrClientLog(Pointer ctx, int severity, Pointer message); - /** - * Log a message from the client. - * Original signature : void osvrClientLog(OSVR_ClientContext, OSVR_LogLevel, const char*) - */ - public static native void osvrClientLog(OsvrClientKitLibrary.OSVR_ClientContext ctx, int severity, String message); - /** - * Get the interface associated with the given path. - * @param ctx Client context
                  - * @param path A resource path (null-terminated string)
                  - * @param iface The interface object. May be freed when no longer needed,
                  - * otherwise it will be freed when the context is closed.
                  - * Original signature : OSVR_ReturnCode osvrClientGetInterface(OSVR_ClientContext, const char[], OSVR_ClientInterface*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetInterface(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext, byte[], com.sun.jna.ptr.PointerByReference)} - * instead - */ - @Deprecated - public static native byte osvrClientGetInterface(Pointer ctx, Pointer path, Pointer iface); - /** - * Get the interface associated with the given path. - * @param ctx Client context
                  - * @param path A resource path (null-terminated string)
                  - * @param iface The interface object. May be freed when no longer needed,
                  - * otherwise it will be freed when the context is closed.
                  - * Original signature : OSVR_ReturnCode osvrClientGetInterface(OSVR_ClientContext, const char[], OSVR_ClientInterface*) - */ - public static native byte osvrClientGetInterface(OsvrClientKitLibrary.OSVR_ClientContext ctx, byte path[], PointerByReference iface); - /** - * Get the interface associated with the given path. - * @param ctx Client context
                  - * @param path A resource path (null-terminated string)
                  - * @param iface The interface object. May be freed when no longer needed,
                  - * otherwise it will be freed when the context is closed.
                  - * Original signature : OSVR_ReturnCode osvrClientGetInterface(OSVR_ClientContext, const char[], OSVR_ClientInterface*) - */ - public static native byte osvrClientGetInterface(Pointer ctx, Pointer path, PointerByReference iface); - /** - * Free an interface object before context closure. - * @param ctx Client context
                  - * @param iface The interface object
                  - * @return OSVR_RETURN_SUCCESS unless a null context or interface was passed
                  - * or the given interface was not found in the context (i.e. had already been
                  - * freed)
                  - * Original signature : OSVR_ReturnCode osvrClientFreeInterface(OSVR_ClientContext, OSVR_ClientInterface)
                  - * @deprecated use the safer method - * {@link #osvrClientFreeInterface(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext, com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientInterface)} - * instead - */ - @Deprecated - public static native byte osvrClientFreeInterface(Pointer ctx, Pointer iface); - /** - * Free an interface object before context closure. - * @param ctx Client context
                  - * @param iface The interface object
                  - * @return OSVR_RETURN_SUCCESS unless a null context or interface was passed
                  - * or the given interface was not found in the context (i.e. had already been
                  - * freed)
                  - * Original signature : OSVR_ReturnCode osvrClientFreeInterface(OSVR_ClientContext, OSVR_ClientInterface) - */ - public static native byte osvrClientFreeInterface(OsvrClientKitLibrary.OSVR_ClientContext ctx, OsvrClientKitLibrary.OSVR_ClientInterface iface); - /** - * Original signature : OSVR_ReturnCode osvrRegisterPoseCallback(OSVR_ClientInterface, OSVR_PoseCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterPoseCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterPoseCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterPoseCallback(OSVR_ClientInterface, OSVR_PoseCallback, void*) */ - public static native byte osvrRegisterPoseCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterPositionCallback(OSVR_ClientInterface, OSVR_PositionCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterPositionCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterPositionCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterPositionCallback(OSVR_ClientInterface, OSVR_PositionCallback, void*) */ - public static native byte osvrRegisterPositionCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterOrientationCallback(OSVR_ClientInterface, OSVR_OrientationCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterOrientationCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterOrientationCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterOrientationCallback(OSVR_ClientInterface, OSVR_OrientationCallback, void*) */ - public static native byte osvrRegisterOrientationCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterVelocityCallback(OSVR_ClientInterface, OSVR_VelocityCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterVelocityCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterVelocityCallback(OSVR_ClientInterface, OSVR_VelocityCallback, void*) */ - public static native byte osvrRegisterVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterLinearVelocityCallback(OSVR_ClientInterface, OSVR_LinearVelocityCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterLinearVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterLinearVelocityCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterLinearVelocityCallback(OSVR_ClientInterface, OSVR_LinearVelocityCallback, void*) */ - public static native byte osvrRegisterLinearVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterAngularVelocityCallback(OSVR_ClientInterface, OSVR_AngularVelocityCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterAngularVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterAngularVelocityCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterAngularVelocityCallback(OSVR_ClientInterface, OSVR_AngularVelocityCallback, void*) */ - public static native byte osvrRegisterAngularVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterAccelerationCallback(OSVR_ClientInterface, OSVR_AccelerationCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterAccelerationCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterAccelerationCallback(OSVR_ClientInterface, OSVR_AccelerationCallback, void*) */ - public static native byte osvrRegisterAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterLinearAccelerationCallback(OSVR_ClientInterface, OSVR_LinearAccelerationCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterLinearAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterLinearAccelerationCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterLinearAccelerationCallback(OSVR_ClientInterface, OSVR_LinearAccelerationCallback, void*) */ - public static native byte osvrRegisterLinearAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterAngularAccelerationCallback(OSVR_ClientInterface, OSVR_AngularAccelerationCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterAngularAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterAngularAccelerationCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterAngularAccelerationCallback(OSVR_ClientInterface, OSVR_AngularAccelerationCallback, void*) */ - public static native byte osvrRegisterAngularAccelerationCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterButtonCallback(OSVR_ClientInterface, OSVR_ButtonCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterButtonCallback(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Callback, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterButtonCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterButtonCallback(OSVR_ClientInterface, OSVR_ButtonCallback, void*) */ - public static native byte osvrRegisterButtonCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Callback cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterAnalogCallback(OSVR_ClientInterface, OSVR_AnalogCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterAnalogCallback(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Callback, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterAnalogCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterAnalogCallback(OSVR_ClientInterface, OSVR_AnalogCallback, void*) */ - public static native byte osvrRegisterAnalogCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Callback cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterImagingCallback(OSVR_ClientInterface, OSVR_ImagingCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterImagingCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterImagingCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterImagingCallback(OSVR_ClientInterface, OSVR_ImagingCallback, void*) */ - public static native byte osvrRegisterImagingCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterLocation2DCallback(OSVR_ClientInterface, OSVR_Location2DCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterLocation2DCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterLocation2DCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterLocation2DCallback(OSVR_ClientInterface, OSVR_Location2DCallback, void*) */ - public static native byte osvrRegisterLocation2DCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterDirectionCallback(OSVR_ClientInterface, OSVR_DirectionCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterDirectionCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterDirectionCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterDirectionCallback(OSVR_ClientInterface, OSVR_DirectionCallback, void*) */ - public static native byte osvrRegisterDirectionCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterEyeTracker2DCallback(OSVR_ClientInterface, OSVR_EyeTracker2DCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterEyeTracker2DCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterEyeTracker2DCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterEyeTracker2DCallback(OSVR_ClientInterface, OSVR_EyeTracker2DCallback, void*) */ - public static native byte osvrRegisterEyeTracker2DCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterEyeTracker3DCallback(OSVR_ClientInterface, OSVR_EyeTracker3DCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterEyeTracker3DCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterEyeTracker3DCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterEyeTracker3DCallback(OSVR_ClientInterface, OSVR_EyeTracker3DCallback, void*) */ - public static native byte osvrRegisterEyeTracker3DCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterEyeTrackerBlinkCallback(OSVR_ClientInterface, OSVR_EyeTrackerBlinkCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterEyeTrackerBlinkCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterEyeTrackerBlinkCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterEyeTrackerBlinkCallback(OSVR_ClientInterface, OSVR_EyeTrackerBlinkCallback, void*) */ - public static native byte osvrRegisterEyeTrackerBlinkCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterNaviVelocityCallback(OSVR_ClientInterface, OSVR_NaviVelocityCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterNaviVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterNaviVelocityCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterNaviVelocityCallback(OSVR_ClientInterface, OSVR_NaviVelocityCallback, void*) */ - public static native byte osvrRegisterNaviVelocityCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - /** - * Original signature : OSVR_ReturnCode osvrRegisterNaviPositionCallback(OSVR_ClientInterface, OSVR_NaviPositionCallback, void*)
                  - * @deprecated use the safer method - * {@link #osvrRegisterNaviPositionCallback(OsvrClientKitLibrary.OSVR_ClientInterface, com.sun.jna.Pointer, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrRegisterNaviPositionCallback(Pointer iface, Pointer cb, Pointer userdata); - /** Original signature : OSVR_ReturnCode osvrRegisterNaviPositionCallback(OSVR_ClientInterface, OSVR_NaviPositionCallback, void*) */ - public static native byte osvrRegisterNaviPositionCallback(OsvrClientKitLibrary.OSVR_ClientInterface iface, Pointer cb, Pointer userdata); - public static class OSVR_ClientContext extends PointerType { - public OSVR_ClientContext(Pointer address) { - super(address); - } - public OSVR_ClientContext() { - super(); - } - }; - public static class OSVR_ClientInterface extends PointerType { - public OSVR_ClientInterface(Pointer address) { - super(address); - } - public OSVR_ClientInterface() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationReport.java deleted file mode 100644 index 61592a5b3e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_AccelerationReport extends Structure { - public int sensor; - /** C type : OSVR_AccelerationState */ - public OSVR_AccelerationState state; - public OSVR_AccelerationReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_AccelerationState */ - public OSVR_AccelerationReport(int sensor, OSVR_AccelerationState state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_AccelerationReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_AccelerationReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_AccelerationReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationState.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationState.java deleted file mode 100644 index ccf4837ced..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AccelerationState.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_AccelerationState extends Structure { - /** C type : OSVR_LinearAccelerationState */ - public OSVR_Vec3 linearAcceleration; - /** C type : OSVR_CBool */ - public byte linearAccelerationValid; - /** C type : OSVR_AngularAccelerationState */ - public OSVR_IncrementalQuaternion angularAcceleration; - /** C type : OSVR_CBool */ - public byte angularAccelerationValid; - public OSVR_AccelerationState() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("linearAcceleration", "linearAccelerationValid", "angularAcceleration", "angularAccelerationValid"); - } - /** - * @param linearAcceleration C type : OSVR_LinearAccelerationState
                  - * @param linearAccelerationValid C type : OSVR_CBool
                  - * @param angularAcceleration C type : OSVR_AngularAccelerationState
                  - * @param angularAccelerationValid C type : OSVR_CBool - */ - public OSVR_AccelerationState(OSVR_Vec3 linearAcceleration, byte linearAccelerationValid, OSVR_IncrementalQuaternion angularAcceleration, byte angularAccelerationValid) { - super(); - this.linearAcceleration = linearAcceleration; - this.linearAccelerationValid = linearAccelerationValid; - this.angularAcceleration = angularAcceleration; - this.angularAccelerationValid = angularAccelerationValid; - } - public OSVR_AccelerationState(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_AccelerationState implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_AccelerationState implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AnalogReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AnalogReport.java deleted file mode 100644 index 0f84cdfeac..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AnalogReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_AnalogReport extends Structure { - public int sensor; - /** C type : OSVR_AnalogState */ - public double state; - public OSVR_AnalogReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_AnalogState */ - public OSVR_AnalogReport(int sensor, double state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_AnalogReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_AnalogReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_AnalogReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularAccelerationReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularAccelerationReport.java deleted file mode 100644 index 1afbe795b9..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularAccelerationReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_AngularAccelerationReport extends Structure { - public int sensor; - /** C type : OSVR_AngularAccelerationState */ - public OSVR_IncrementalQuaternion state; - public OSVR_AngularAccelerationReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_AngularAccelerationState */ - public OSVR_AngularAccelerationReport(int sensor, OSVR_IncrementalQuaternion state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_AngularAccelerationReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_AngularAccelerationReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_AngularAccelerationReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularVelocityReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularVelocityReport.java deleted file mode 100644 index 5926ec7456..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_AngularVelocityReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_AngularVelocityReport extends Structure { - public int sensor; - /** C type : OSVR_AngularVelocityState */ - public OSVR_IncrementalQuaternion state; - public OSVR_AngularVelocityReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_AngularVelocityState */ - public OSVR_AngularVelocityReport(int sensor, OSVR_IncrementalQuaternion state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_AngularVelocityReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_AngularVelocityReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_AngularVelocityReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_ButtonReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_ButtonReport.java deleted file mode 100644 index ad3ab53be6..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_ButtonReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_ButtonReport extends Structure { - public int sensor; - /** C type : OSVR_ButtonState */ - public byte state; - public OSVR_ButtonReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_ButtonState */ - public OSVR_ButtonReport(int sensor, byte state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_ButtonReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_ButtonReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_ButtonReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_DirectionReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_DirectionReport.java deleted file mode 100644 index 78ca84a396..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_DirectionReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_DirectionReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_DirectionState */ - public OSVR_Vec3 direction; - public OSVR_DirectionReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "direction"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param direction C type : OSVR_DirectionState - */ - public OSVR_DirectionReport(int sensor, OSVR_Vec3 direction) { - super(); - this.sensor = sensor; - this.direction = direction; - } - public OSVR_DirectionReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_DirectionReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_DirectionReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker2DReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker2DReport.java deleted file mode 100644 index fab1548e2b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker2DReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_EyeTracker2DReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_EyeTracker2DState */ - public OSVR_Vec2 state; - public OSVR_EyeTracker2DReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param state C type : OSVR_EyeTracker2DState - */ - public OSVR_EyeTracker2DReport(int sensor, OSVR_Vec2 state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_EyeTracker2DReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_EyeTracker2DReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_EyeTracker2DReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DReport.java deleted file mode 100644 index a42192be8b..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_EyeTracker3DReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_EyeTracker3DState */ - public OSVR_EyeTracker3DState state; - public OSVR_EyeTracker3DReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param state C type : OSVR_EyeTracker3DState - */ - public OSVR_EyeTracker3DReport(int sensor, OSVR_EyeTracker3DState state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_EyeTracker3DReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_EyeTracker3DReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_EyeTracker3DReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DState.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DState.java deleted file mode 100644 index 07eb1d8806..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTracker3DState.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_EyeTracker3DState extends Structure { - /** C type : OSVR_CBool */ - public byte directionValid; - /** C type : OSVR_DirectionState */ - public OSVR_Vec3 direction; - /** C type : OSVR_CBool */ - public byte basePointValid; - /** C type : OSVR_PositionState */ - public OSVR_Vec3 basePoint; - public OSVR_EyeTracker3DState() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("directionValid", "direction", "basePointValid", "basePoint"); - } - /** - * @param directionValid C type : OSVR_CBool
                  - * @param direction C type : OSVR_DirectionState
                  - * @param basePointValid C type : OSVR_CBool
                  - * @param basePoint C type : OSVR_PositionState - */ - public OSVR_EyeTracker3DState(byte directionValid, OSVR_Vec3 direction, byte basePointValid, OSVR_Vec3 basePoint) { - super(); - this.directionValid = directionValid; - this.direction = direction; - this.basePointValid = basePointValid; - this.basePoint = basePoint; - } - public OSVR_EyeTracker3DState(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_EyeTracker3DState implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_EyeTracker3DState implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTrackerBlinkReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTrackerBlinkReport.java deleted file mode 100644 index 4d051bbd23..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_EyeTrackerBlinkReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_EyeTrackerBlinkReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_EyeTrackerBlinkState */ - public byte state; - public OSVR_EyeTrackerBlinkReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param state C type : OSVR_EyeTrackerBlinkState - */ - public OSVR_EyeTrackerBlinkReport(int sensor, byte state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_EyeTrackerBlinkReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_EyeTrackerBlinkReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_EyeTrackerBlinkReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_IncrementalQuaternion.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_IncrementalQuaternion.java deleted file mode 100644 index 66f04ba4e4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_IncrementalQuaternion.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_IncrementalQuaternion extends Structure { - /** C type : OSVR_Quaternion */ - public OSVR_Quaternion incrementalRotation; - public double dt; - public OSVR_IncrementalQuaternion() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("incrementalRotation", "dt"); - } - /** @param incrementalRotation C type : OSVR_Quaternion */ - public OSVR_IncrementalQuaternion(OSVR_Quaternion incrementalRotation, double dt) { - super(); - this.incrementalRotation = incrementalRotation; - this.dt = dt; - } - public OSVR_IncrementalQuaternion(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_IncrementalQuaternion implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_IncrementalQuaternion implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearAccelerationReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearAccelerationReport.java deleted file mode 100644 index e93333f59d..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearAccelerationReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_LinearAccelerationReport extends Structure { - public int sensor; - /** C type : OSVR_LinearAccelerationState */ - public OSVR_Vec3 state; - public OSVR_LinearAccelerationReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_LinearAccelerationState */ - public OSVR_LinearAccelerationReport(int sensor, OSVR_Vec3 state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_LinearAccelerationReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_LinearAccelerationReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_LinearAccelerationReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearVelocityReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearVelocityReport.java deleted file mode 100644 index daca276bc4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_LinearVelocityReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_LinearVelocityReport extends Structure { - public int sensor; - /** C type : OSVR_LinearVelocityState */ - public OSVR_Vec3 state; - public OSVR_LinearVelocityReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_LinearVelocityState */ - public OSVR_LinearVelocityReport(int sensor, OSVR_Vec3 state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_LinearVelocityReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_LinearVelocityReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_LinearVelocityReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Location2DReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Location2DReport.java deleted file mode 100644 index 75e9ea1e36..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Location2DReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Location2DReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_Location2DState */ - public OSVR_Vec2 location; - public OSVR_Location2DReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "location"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param location C type : OSVR_Location2DState - */ - public OSVR_Location2DReport(int sensor, OSVR_Vec2 location) { - super(); - this.sensor = sensor; - this.location = location; - } - public OSVR_Location2DReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Location2DReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Location2DReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviPositionReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviPositionReport.java deleted file mode 100644 index 641018a972..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviPositionReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_NaviPositionReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_NaviPositionState */ - public OSVR_Vec2 state; - public OSVR_NaviPositionReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param state C type : OSVR_NaviPositionState - */ - public OSVR_NaviPositionReport(int sensor, OSVR_Vec2 state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_NaviPositionReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_NaviPositionReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_NaviPositionReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviVelocityReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviVelocityReport.java deleted file mode 100644 index 89dcef2508..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_NaviVelocityReport.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_NaviVelocityReport extends Structure { - /** C type : OSVR_ChannelCount */ - public int sensor; - /** C type : OSVR_NaviVelocityState */ - public OSVR_Vec2 state; - public OSVR_NaviVelocityReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** - * @param sensor C type : OSVR_ChannelCount
                  - * @param state C type : OSVR_NaviVelocityState - */ - public OSVR_NaviVelocityReport(int sensor, OSVR_Vec2 state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_NaviVelocityReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_NaviVelocityReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_NaviVelocityReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_OrientationReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_OrientationReport.java deleted file mode 100644 index 2699487f15..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_OrientationReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_OrientationReport extends Structure { - public int sensor; - /** C type : OSVR_OrientationState */ - public OSVR_Quaternion rotation; - public OSVR_OrientationReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "rotation"); - } - /** @param rotation C type : OSVR_OrientationState */ - public OSVR_OrientationReport(int sensor, OSVR_Quaternion rotation) { - super(); - this.sensor = sensor; - this.rotation = rotation; - } - public OSVR_OrientationReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_OrientationReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_OrientationReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Pose3.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Pose3.java deleted file mode 100644 index a9ce1c2021..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Pose3.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Pose3 extends Structure { - /** C type : OSVR_Vec3 */ - public OSVR_Vec3 translation; - /** C type : OSVR_Quaternion */ - public OSVR_Quaternion rotation; - public OSVR_Pose3() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("translation", "rotation"); - } - /** - * @param translation C type : OSVR_Vec3
                  - * @param rotation C type : OSVR_Quaternion - */ - public OSVR_Pose3(OSVR_Vec3 translation, OSVR_Quaternion rotation) { - super(); - this.translation = translation; - this.rotation = rotation; - } - public OSVR_Pose3(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Pose3 implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Pose3 implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PoseReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PoseReport.java deleted file mode 100644 index f80bfd06b1..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PoseReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_PoseReport extends Structure { - public int sensor; - /** C type : OSVR_PoseState */ - public OSVR_Pose3 pose; - public OSVR_PoseReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "pose"); - } - /** @param pose C type : OSVR_PoseState */ - public OSVR_PoseReport(int sensor, OSVR_Pose3 pose) { - super(); - this.sensor = sensor; - this.pose = pose; - } - public OSVR_PoseReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_PoseReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_PoseReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PositionReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PositionReport.java deleted file mode 100644 index 7186828354..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_PositionReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_PositionReport extends Structure { - public int sensor; - /** C type : OSVR_PositionState */ - public OSVR_Vec3 xyz; - public OSVR_PositionReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "xyz"); - } - /** @param xyz C type : OSVR_PositionState */ - public OSVR_PositionReport(int sensor, OSVR_Vec3 xyz) { - super(); - this.sensor = sensor; - this.xyz = xyz; - } - public OSVR_PositionReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_PositionReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_PositionReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Quaternion.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Quaternion.java deleted file mode 100644 index 48f83f6077..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Quaternion.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Quaternion extends Structure { - /** C type : double[4] */ - public double[] data = new double[4]; - public OSVR_Quaternion() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("data"); - } - /** @param data C type : double[4] */ - public OSVR_Quaternion(double data[]) { - super(); - if ((data.length != this.data.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.data = data; - } - public OSVR_Quaternion(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Quaternion implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Quaternion implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec2.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec2.java deleted file mode 100644 index 28a5453a23..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec2.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Vec2 extends Structure { - /** C type : double[2] */ - public double[] data = new double[2]; - public OSVR_Vec2() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("data"); - } - /** @param data C type : double[2] */ - public OSVR_Vec2(double data[]) { - super(); - if ((data.length != this.data.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.data = data; - } - public OSVR_Vec2(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Vec2 implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Vec2 implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec3.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec3.java deleted file mode 100644 index 96c81a65c4..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_Vec3.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Vec3 extends Structure { - /** C type : double[3] */ - public double[] data = new double[3]; - public OSVR_Vec3() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("data"); - } - /** @param data C type : double[3] */ - public OSVR_Vec3(double data[]) { - super(); - if ((data.length != this.data.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.data = data; - } - public OSVR_Vec3(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Vec3 implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Vec3 implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityReport.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityReport.java deleted file mode 100644 index 103f351d81..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityReport.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_VelocityReport extends Structure { - public int sensor; - /** C type : OSVR_VelocityState */ - public OSVR_VelocityState state; - public OSVR_VelocityReport() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("sensor", "state"); - } - /** @param state C type : OSVR_VelocityState */ - public OSVR_VelocityReport(int sensor, OSVR_VelocityState state) { - super(); - this.sensor = sensor; - this.state = state; - } - public OSVR_VelocityReport(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_VelocityReport implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_VelocityReport implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityState.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityState.java deleted file mode 100644 index de34de0cd2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OSVR_VelocityState.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_VelocityState extends Structure { - /** C type : OSVR_LinearVelocityState */ - public OSVR_Vec3 linearVelocity; - /** C type : OSVR_CBool */ - public byte linearVelocityValid; - /** C type : OSVR_AngularVelocityState */ - public OSVR_IncrementalQuaternion angularVelocity; - /** C type : OSVR_CBool */ - public byte angularVelocityValid; - public OSVR_VelocityState() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("linearVelocity", "linearVelocityValid", "angularVelocity", "angularVelocityValid"); - } - /** - * @param linearVelocity C type : OSVR_LinearVelocityState
                  - * @param linearVelocityValid C type : OSVR_CBool
                  - * @param angularVelocity C type : OSVR_AngularVelocityState
                  - * @param angularVelocityValid C type : OSVR_CBool - */ - public OSVR_VelocityState(OSVR_Vec3 linearVelocity, byte linearVelocityValid, OSVR_IncrementalQuaternion angularVelocity, byte angularVelocityValid) { - super(); - this.linearVelocity = linearVelocity; - this.linearVelocityValid = linearVelocityValid; - this.angularVelocity = angularVelocity; - this.angularVelocityValid = angularVelocityValid; - } - public OSVR_VelocityState(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_VelocityState implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_VelocityState implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OsvrClientReportTypesLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OsvrClientReportTypesLibrary.java deleted file mode 100644 index 9f8f1ea765..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrclientreporttypes/OsvrClientReportTypesLibrary.java +++ /dev/null @@ -1,79 +0,0 @@ -package com.jme3.system.osvr.osvrclientreporttypes; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -/** - * JNA Wrapper for library osvrClientReportTypes
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrClientReportTypesLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrClientKit"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrClientReportTypesLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrClientReportTypesLibrary.class, OsvrClientReportTypesLibrary.JNA_NATIVE_LIB); - } - public static final int OSVR_TRUE = (int)(1); - public static final int OSVR_FALSE = (int)(0); - public static final int OSVR_BUTTON_PRESSED = (int)(1); - public static final int OSVR_BUTTON_NOT_PRESSED = (int)(0); - public static final int OSVR_EYE_BLINK = (int)(1); - public static final int OSVR_EYE_NO_BLINK = (int)(0); - /** Original signature : double osvrVec3GetX(const OSVR_Vec3*) */ - public static native double osvrVec3GetX(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetX(OSVR_Vec3*, double) */ - public static native void osvrVec3SetX(OSVR_Vec3 v, double val); - /** Original signature : double osvrVec3GetY(const OSVR_Vec3*) */ - public static native double osvrVec3GetY(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetY(OSVR_Vec3*, double) */ - public static native void osvrVec3SetY(OSVR_Vec3 v, double val); - /** Original signature : double osvrVec3GetZ(const OSVR_Vec3*) */ - public static native double osvrVec3GetZ(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetZ(OSVR_Vec3*, double) */ - public static native void osvrVec3SetZ(OSVR_Vec3 v, double val); - /** - * Set a Vec3 to the zero vector. - * Original signature : void osvrVec3Zero(OSVR_Vec3*) - */ - public static native void osvrVec3Zero(OSVR_Vec3 v); - /** Original signature : double osvrQuatGetW(const OSVR_Quaternion*) */ - public static native double osvrQuatGetW(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetW(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetW(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetX(const OSVR_Quaternion*) */ - public static native double osvrQuatGetX(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetX(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetX(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetY(const OSVR_Quaternion*) */ - public static native double osvrQuatGetY(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetY(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetY(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetZ(const OSVR_Quaternion*) */ - public static native double osvrQuatGetZ(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetZ(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetZ(OSVR_Quaternion q, double val); - /** - * Set a quaternion to the identity rotation. - * Original signature : void osvrQuatSetIdentity(OSVR_Quaternion*) - */ - public static native void osvrQuatSetIdentity(OSVR_Quaternion q); - /** - * Set a pose to identity. - * Original signature : void osvrPose3SetIdentity(OSVR_Pose3*) - */ - public static native void osvrPose3SetIdentity(OSVR_Pose3 pose); - /** Original signature : double osvrVec2GetX(const OSVR_Vec2*) */ - public static native double osvrVec2GetX(OSVR_Vec2 v); - /** Original signature : void osvrVec2SetX(OSVR_Vec2*, double) */ - public static native void osvrVec2SetX(OSVR_Vec2 v, double val); - /** Original signature : double osvrVec2GetY(const OSVR_Vec2*) */ - public static native double osvrVec2GetY(OSVR_Vec2 v); - /** Original signature : void osvrVec2SetY(OSVR_Vec2*, double) */ - public static native void osvrVec2SetY(OSVR_Vec2 v, double val); - /** - * Set a Vec2 to the zero vector. - * Original signature : void osvrVec2Zero(OSVR_Vec2*) - */ - public static native void osvrVec2Zero(OSVR_Vec2 v); -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrdisplay/OsvrDisplayLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrdisplay/OsvrDisplayLibrary.java deleted file mode 100644 index f3ef251407..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrdisplay/OsvrDisplayLibrary.java +++ /dev/null @@ -1,851 +0,0 @@ -package com.jme3.system.osvr.osvrdisplay; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -import com.sun.jna.ptr.DoubleByReference; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; -import java.nio.ByteBuffer; -import java.nio.DoubleBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; - -/** - * JNA Wrapper for library osvrDisplay
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrDisplayLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrClientKit"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrDisplayLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrDisplayLibrary.class, OsvrDisplayLibrary.JNA_NATIVE_LIB); - } - /** - * Allocates a display configuration object populated with data from the
                  - * OSVR system.
                  - * Before this call will succeed, your application will need to be correctly
                  - * and fully connected to an OSVR server. You may consider putting this call in
                  - * a loop alternating with osvrClientUpdate() until this call succeeds.
                  - * Data provided by a display configuration object:
                  - * - The logical display topology (number and relationship of viewers, eyes,
                  - * and surfaces), which remains constant throughout the life of the
                  - * configuration object. (A method of notification of change here is TBD).
                  - * - Pose data for viewers (not required for rendering) and pose/view data for
                  - * eyes (used for rendering) which is based on tracker data: if used, these
                  - * should be queried every frame.
                  - * - Projection matrix data for surfaces, which while in current practice may
                  - * be relatively unchanging, we are not guaranteeing them to be constant:
                  - * these should be queried every frame.
                  - * - Video-input-relative viewport size/location for a surface: would like this
                  - * to be variable, but probably not feasible. If you have input, please
                  - * comment on the dev mailing list.
                  - * - Per-surface distortion strategy priorities/availabilities: constant. Note
                  - * the following, though...
                  - * - Per-surface distortion strategy parameters: variable, request each frame.
                  - * (Could make constant with a notification if needed?)
                  - * Important note: While most of this data is immediately available if you are
                  - * successful in getting a display config object, the pose-based data (viewer
                  - * pose, eye pose, eye view matrix) needs tracker state, so at least one (and in
                  - * practice, typically more) osvrClientUpdate() must be performed before a new
                  - * tracker report is available to populate that state. See
                  - * osvrClientCheckDisplayStartup() to query if all startup data is available.
                  - * todo Decide if relative viewport should be constant in a display config,
                  - * and update docs accordingly.
                  - * todo Decide if distortion params should be constant in a display config,
                  - * and update docs accordingly.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or some other
                  - * error occurred, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetDisplay(OSVR_ClientContext, OSVR_DisplayConfig*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetDisplay(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext, com.sun.jna.ptr.PointerByReference)} - * instead - */ - @Deprecated - public static native byte osvrClientGetDisplay(Pointer ctx, Pointer disp); - /** - * Allocates a display configuration object populated with data from the
                  - * OSVR system.
                  - * Before this call will succeed, your application will need to be correctly
                  - * and fully connected to an OSVR server. You may consider putting this call in
                  - * a loop alternating with osvrClientUpdate() until this call succeeds.
                  - * Data provided by a display configuration object:
                  - * - The logical display topology (number and relationship of viewers, eyes,
                  - * and surfaces), which remains constant throughout the life of the
                  - * configuration object. (A method of notification of change here is TBD).
                  - * - Pose data for viewers (not required for rendering) and pose/view data for
                  - * eyes (used for rendering) which is based on tracker data: if used, these
                  - * should be queried every frame.
                  - * - Projection matrix data for surfaces, which while in current practice may
                  - * be relatively unchanging, we are not guaranteeing them to be constant:
                  - * these should be queried every frame.
                  - * - Video-input-relative viewport size/location for a surface: would like this
                  - * to be variable, but probably not feasible. If you have input, please
                  - * comment on the dev mailing list.
                  - * - Per-surface distortion strategy priorities/availabilities: constant. Note
                  - * the following, though...
                  - * - Per-surface distortion strategy parameters: variable, request each frame.
                  - * (Could make constant with a notification if needed?)
                  - * Important note: While most of this data is immediately available if you are
                  - * successful in getting a display config object, the pose-based data (viewer
                  - * pose, eye pose, eye view matrix) needs tracker state, so at least one (and in
                  - * practice, typically more) osvrClientUpdate() must be performed before a new
                  - * tracker report is available to populate that state. See
                  - * osvrClientCheckDisplayStartup() to query if all startup data is available.
                  - * todo Decide if relative viewport should be constant in a display config,
                  - * and update docs accordingly.
                  - * todo Decide if distortion params should be constant in a display config,
                  - * and update docs accordingly.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or some other
                  - * error occurred, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetDisplay(OSVR_ClientContext, OSVR_DisplayConfig*) - */ - public static native byte osvrClientGetDisplay(OsvrClientKitLibrary.OSVR_ClientContext ctx, PointerByReference disp); - /** - * Allocates a display configuration object populated with data from the
                  - * OSVR system.
                  - * Before this call will succeed, your application will need to be correctly
                  - * and fully connected to an OSVR server. You may consider putting this call in
                  - * a loop alternating with osvrClientUpdate() until this call succeeds.
                  - * Data provided by a display configuration object:
                  - * - The logical display topology (number and relationship of viewers, eyes,
                  - * and surfaces), which remains constant throughout the life of the
                  - * configuration object. (A method of notification of change here is TBD).
                  - * - Pose data for viewers (not required for rendering) and pose/view data for
                  - * eyes (used for rendering) which is based on tracker data: if used, these
                  - * should be queried every frame.
                  - * - Projection matrix data for surfaces, which while in current practice may
                  - * be relatively unchanging, we are not guaranteeing them to be constant:
                  - * these should be queried every frame.
                  - * - Video-input-relative viewport size/location for a surface: would like this
                  - * to be variable, but probably not feasible. If you have input, please
                  - * comment on the dev mailing list.
                  - * - Per-surface distortion strategy priorities/availabilities: constant. Note
                  - * the following, though...
                  - * - Per-surface distortion strategy parameters: variable, request each frame.
                  - * (Could make constant with a notification if needed?)
                  - * Important note: While most of this data is immediately available if you are
                  - * successful in getting a display config object, the pose-based data (viewer
                  - * pose, eye pose, eye view matrix) needs tracker state, so at least one (and in
                  - * practice, typically more) osvrClientUpdate() must be performed before a new
                  - * tracker report is available to populate that state. See
                  - * osvrClientCheckDisplayStartup() to query if all startup data is available.
                  - * todo Decide if relative viewport should be constant in a display config,
                  - * and update docs accordingly.
                  - * todo Decide if distortion params should be constant in a display config,
                  - * and update docs accordingly.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or some other
                  - * error occurred, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetDisplay(OSVR_ClientContext, OSVR_DisplayConfig*) - */ - public static native byte osvrClientGetDisplay(Pointer ctx, PointerByReference disp); - /** - * Frees a display configuration object. The corresponding context must
                  - * still be open.
                  - * If you fail to call this, it will be automatically called as part of
                  - * clean-up when the corresponding context is closed.
                  - * @return OSVR_RETURN_FAILURE if a null config was passed, or if the given
                  - * display object was already freed.
                  - * Original signature : OSVR_ReturnCode osvrClientFreeDisplay(OSVR_DisplayConfig)
                  - * @deprecated use the safer method - * {@link #osvrClientFreeDisplay(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig)} - * instead - */ - @Deprecated - public static native byte osvrClientFreeDisplay(Pointer disp); - /** - * Frees a display configuration object. The corresponding context must
                  - * still be open.
                  - * If you fail to call this, it will be automatically called as part of
                  - * clean-up when the corresponding context is closed.
                  - * @return OSVR_RETURN_FAILURE if a null config was passed, or if the given
                  - * display object was already freed.
                  - * Original signature : OSVR_ReturnCode osvrClientFreeDisplay(OSVR_DisplayConfig) - */ - public static native byte osvrClientFreeDisplay(OsvrDisplayLibrary.OSVR_DisplayConfig disp); - /** - * Checks to see if a display is fully configured and ready, including
                  - * having received its first pose update.
                  - * Once this first succeeds, it will continue to succeed for the lifetime of
                  - * the display config object, so it is not necessary to keep calling once you
                  - * get a successful result.
                  - * @return OSVR_RETURN_FAILURE if a null config was passed, or if the given
                  - * display config object was otherwise not ready for full use.
                  - * Original signature : OSVR_ReturnCode osvrClientCheckDisplayStartup(OSVR_DisplayConfig)
                  - * @deprecated use the safer method - * {@link #osvrClientCheckDisplayStartup(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig)} - * instead - */ - @Deprecated - public static native byte osvrClientCheckDisplayStartup(Pointer disp); - /** - * Checks to see if a display is fully configured and ready, including
                  - * having received its first pose update.
                  - * Once this first succeeds, it will continue to succeed for the lifetime of
                  - * the display config object, so it is not necessary to keep calling once you
                  - * get a successful result.
                  - * @return OSVR_RETURN_FAILURE if a null config was passed, or if the given
                  - * display config object was otherwise not ready for full use.
                  - * Original signature : OSVR_ReturnCode osvrClientCheckDisplayStartup(OSVR_DisplayConfig) - */ - public static native byte osvrClientCheckDisplayStartup(OsvrDisplayLibrary.OSVR_DisplayConfig disp); - /** - * A display config can have one or more display inputs to pass pixels
                  - * over (HDMI/DVI connections, etc): retrieve the number of display inputs in
                  - * the current configuration.
                  - * @param disp Display config object.
                  - * @param numDisplayInputs Number of display inputs in the logical display
                  - * topology, **constant** throughout the active, valid lifetime of a display
                  - * config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in
                  - * which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumDisplayInputs(OSVR_DisplayConfig, OSVR_DisplayInputCount*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetNumDisplayInputs(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, java.nio.ByteBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetNumDisplayInputs(Pointer disp, Pointer numDisplayInputs); - /** - * A display config can have one or more display inputs to pass pixels
                  - * over (HDMI/DVI connections, etc): retrieve the number of display inputs in
                  - * the current configuration.
                  - * @param disp Display config object.
                  - * @param numDisplayInputs Number of display inputs in the logical display
                  - * topology, **constant** throughout the active, valid lifetime of a display
                  - * config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in
                  - * which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumDisplayInputs(OSVR_DisplayConfig, OSVR_DisplayInputCount*) - */ - public static native byte osvrClientGetNumDisplayInputs(OsvrDisplayLibrary.OSVR_DisplayConfig disp, ByteBuffer numDisplayInputs); - /** - * Retrieve the pixel dimensions of a given display input for a display
                  - * config
                  - * @param disp Display config object.
                  - * @param displayInputIndex The zero-based index of the display input.
                  - * @param width Width (in pixels) of the display input.
                  - * @param height Height (in pixels) of the display input.
                  - * The out parameters are **constant** throughout the active, valid lifetime of
                  - * a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in
                  - * which case the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetDisplayDimensions(OSVR_DisplayConfig, OSVR_DisplayInputCount, OSVR_DisplayDimension*, OSVR_DisplayDimension*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetDisplayDimensions(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, byte, java.nio.IntBuffer, java.nio.IntBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetDisplayDimensions(Pointer disp, byte displayInputIndex, IntByReference width, IntByReference height); - /** - * Retrieve the pixel dimensions of a given display input for a display
                  - * config
                  - * @param disp Display config object.
                  - * @param displayInputIndex The zero-based index of the display input.
                  - * @param width Width (in pixels) of the display input.
                  - * @param height Height (in pixels) of the display input.
                  - * The out parameters are **constant** throughout the active, valid lifetime of
                  - * a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in
                  - * which case the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetDisplayDimensions(OSVR_DisplayConfig, OSVR_DisplayInputCount, OSVR_DisplayDimension*, OSVR_DisplayDimension*) - */ - public static native byte osvrClientGetDisplayDimensions(OsvrDisplayLibrary.OSVR_DisplayConfig disp, byte displayInputIndex, IntBuffer width, IntBuffer height); - /** - * A display config can have one (or theoretically more) viewers:
                  - * retrieve the viewer count.
                  - * @param disp Display config object.
                  - * @param viewers Number of viewers in the logical display topology,
                  - * *constant** throughout the active, valid lifetime of a display config
                  - * object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumViewers(OSVR_DisplayConfig, OSVR_ViewerCount*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetNumViewers(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, java.nio.IntBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetNumViewers(Pointer disp, IntByReference viewers); - /** - * A display config can have one (or theoretically more) viewers:
                  - * retrieve the viewer count.
                  - * @param disp Display config object.
                  - * @param viewers Number of viewers in the logical display topology,
                  - * *constant** throughout the active, valid lifetime of a display config
                  - * object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumViewers(OSVR_DisplayConfig, OSVR_ViewerCount*) - */ - public static native byte osvrClientGetNumViewers(OsvrDisplayLibrary.OSVR_DisplayConfig disp, IntBuffer viewers); - /** - * Get the pose of a viewer in a display config.
                  - * Note that there may not necessarily be any surfaces rendered from this pose
                  - * (it's the unused "center" eye in a stereo configuration, for instance) so
                  - * only use this if it makes integration into your engine or existing
                  - * applications (not originally designed for stereo) easier.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the pose argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerPose(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_Pose3*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerPose(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerPose(Pointer disp, int viewer, Pointer pose); - /** - * Get the pose of a viewer in a display config.
                  - * Note that there may not necessarily be any surfaces rendered from this pose
                  - * (it's the unused "center" eye in a stereo configuration, for instance) so
                  - * only use this if it makes integration into your engine or existing
                  - * applications (not originally designed for stereo) easier.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the pose argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerPose(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_Pose3*) - */ - public static native byte osvrClientGetViewerPose(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, Pointer pose); - /** - * Each viewer in a display config can have one or more "eyes" which
                  - * have a substantially similar pose: get the count.
                  - * @param disp Display config object.
                  - * @param viewer Viewer ID
                  - * @param eyes Number of eyes for this viewer in the logical display
                  - * topology, **constant** throughout the active, valid lifetime of a display
                  - * config object
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumEyesForViewer(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetNumEyesForViewer(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, java.nio.ByteBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetNumEyesForViewer(Pointer disp, int viewer, Pointer eyes); - /** - * Each viewer in a display config can have one or more "eyes" which
                  - * have a substantially similar pose: get the count.
                  - * @param disp Display config object.
                  - * @param viewer Viewer ID
                  - * @param eyes Number of eyes for this viewer in the logical display
                  - * topology, **constant** throughout the active, valid lifetime of a display
                  - * config object
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumEyesForViewer(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount*) - */ - public static native byte osvrClientGetNumEyesForViewer(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, ByteBuffer eyes); - /** - * Get the "viewpoint" for the given eye of a viewer in a display
                  - * config.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param pose Room-space pose (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the pose argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyePose(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_Pose3*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyePose(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyePose(Pointer disp, int viewer, byte eye, Pointer pose); - /** - * Get the "viewpoint" for the given eye of a viewer in a display
                  - * config.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param pose Room-space pose (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the pose argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyePose(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_Pose3*) - */ - public static native byte osvrClientGetViewerEyePose(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, Pointer pose); - /** - * Get the view matrix (inverse of pose) for the given eye of a
                  - * viewer in a display config - matrix of **doubles**.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param mat Pass a double[::OSVR_MATRIX_SIZE] to get the transformation
                  - * matrix from room space to eye space (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixd(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_MatrixConventions, double*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeViewMatrixd(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, short, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeViewMatrixd(Pointer disp, int viewer, byte eye, short flags, DoubleByReference mat); - /** - * Get the view matrix (inverse of pose) for the given eye of a
                  - * viewer in a display config - matrix of **doubles**.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param mat Pass a double[::OSVR_MATRIX_SIZE] to get the transformation
                  - * matrix from room space to eye space (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixd(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_MatrixConventions, double*) - */ - public static native byte osvrClientGetViewerEyeViewMatrixd(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, short flags, DoubleBuffer mat); - /** - * Get the view matrix (inverse of pose) for the given eye of a
                  - * viewer in a display config - matrix of **floats**.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param mat Pass a float[::OSVR_MATRIX_SIZE] to get the transformation
                  - * matrix from room space to eye space (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixf(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_MatrixConventions, float*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeViewMatrixf(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, short, java.nio.FloatBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeViewMatrixf(Pointer disp, int viewer, byte eye, short flags, FloatByReference mat); - /** - * Get the view matrix (inverse of pose) for the given eye of a
                  - * viewer in a display config - matrix of **floats**.
                  - * Will only succeed if osvrClientCheckDisplayStartup() succeeds.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param mat Pass a float[::OSVR_MATRIX_SIZE] to get the transformation
                  - * matrix from room space to eye space (not relative to pose of the viewer)
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed or no pose was
                  - * yet available, in which case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeViewMatrixf(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_MatrixConventions, float*) - */ - public static native byte osvrClientGetViewerEyeViewMatrixf(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, short flags, FloatBuffer mat); - /** - * Each eye of each viewer in a display config has one or more surfaces
                  - * (aka "screens") on which content should be rendered.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surfaces Number of surfaces (numbered [0, surfaces - 1]) for the
                  - * given viewer and eye. **Constant** throughout the active, valid lifetime of
                  - * a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumSurfacesForViewerEye(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetNumSurfacesForViewerEye(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, java.nio.IntBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetNumSurfacesForViewerEye(Pointer disp, int viewer, byte eye, IntByReference surfaces); - /** - * Each eye of each viewer in a display config has one or more surfaces
                  - * (aka "screens") on which content should be rendered.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surfaces Number of surfaces (numbered [0, surfaces - 1]) for the
                  - * given viewer and eye. **Constant** throughout the active, valid lifetime of
                  - * a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetNumSurfacesForViewerEye(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount*) - */ - public static native byte osvrClientGetNumSurfacesForViewerEye(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, IntBuffer surfaces); - /** - * Get the dimensions/location of the viewport **within the display
                  - * input** for a surface seen by an eye of a viewer in a display config. (This
                  - * does not include other video inputs that may be on a single virtual desktop,
                  - * etc. or explicitly account for display configurations that use multiple
                  - * video inputs. It does not necessarily indicate that a viewport in the sense
                  - * of glViewport must be created with these parameters, though the parameter
                  - * order matches for convenience.)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param left Output: Distance in pixels from the left of the video input
                  - * to the left of the viewport.
                  - * @param bottom Output: Distance in pixels from the bottom of the video
                  - * input to the bottom of the viewport.
                  - * @param width Output: Width of viewport in pixels.
                  - * @param height Output: Height of viewport in pixels.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetRelativeViewportForViewerEyeSurface(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_ViewportDimension*, OSVR_ViewportDimension*, OSVR_ViewportDimension*, OSVR_ViewportDimension*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetRelativeViewportForViewerEyeSurface(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetRelativeViewportForViewerEyeSurface(Pointer disp, int viewer, byte eye, int surface, IntByReference left, IntByReference bottom, IntByReference width, IntByReference height); - /** - * Get the dimensions/location of the viewport **within the display
                  - * input** for a surface seen by an eye of a viewer in a display config. (This
                  - * does not include other video inputs that may be on a single virtual desktop,
                  - * etc. or explicitly account for display configurations that use multiple
                  - * video inputs. It does not necessarily indicate that a viewport in the sense
                  - * of glViewport must be created with these parameters, though the parameter
                  - * order matches for convenience.)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param left Output: Distance in pixels from the left of the video input
                  - * to the left of the viewport.
                  - * @param bottom Output: Distance in pixels from the bottom of the video
                  - * input to the bottom of the viewport.
                  - * @param width Output: Width of viewport in pixels.
                  - * @param height Output: Height of viewport in pixels.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetRelativeViewportForViewerEyeSurface(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_ViewportDimension*, OSVR_ViewportDimension*, OSVR_ViewportDimension*, OSVR_ViewportDimension*) - */ - public static native byte osvrClientGetRelativeViewportForViewerEyeSurface(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, IntBuffer left, IntBuffer bottom, IntBuffer width, IntBuffer height); - /** - * Get the index of the display input for a surface seen by an eye of a
                  - * viewer in a display config.
                  - * This is the OSVR-assigned display input: it may not (and in practice,
                  - * usually will not) match any platform-specific display indices. This function
                  - * exists to associate surfaces with video inputs as enumerated by
                  - * osvrClientGetNumDisplayInputs().
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param displayInput Zero-based index of the display input pixels for
                  - * this surface are tranmitted over.
                  - * This association is **constant** throughout the active, valid lifetime of a
                  - * display config object.
                  - * @see #osvrClientGetNumDisplayInputs(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, java.nio.ByteBuffer) - * @see #osvrClientGetRelativeViewportForViewerEyeSurface(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer) - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which
                  - * case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceDisplayInputIndex(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_DisplayInputCount*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceDisplayInputIndex(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.ByteBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceDisplayInputIndex(Pointer disp, int viewer, byte eye, int surface, Pointer displayInput); - /** - * Get the index of the display input for a surface seen by an eye of a
                  - * viewer in a display config.
                  - * This is the OSVR-assigned display input: it may not (and in practice,
                  - * usually will not) match any platform-specific display indices. This function
                  - * exists to associate surfaces with video inputs as enumerated by
                  - * osvrClientGetNumDisplayInputs().
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param displayInput Zero-based index of the display input pixels for
                  - * this surface are tranmitted over.
                  - * This association is **constant** throughout the active, valid lifetime of a
                  - * display config object.
                  - * @see #osvrClientGetNumDisplayInputs(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, java.nio.ByteBuffer) - * @see #osvrClientGetRelativeViewportForViewerEyeSurface(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer, java.nio.IntBuffer) - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which
                  - * case the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceDisplayInputIndex(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_DisplayInputCount*) - */ - public static native byte osvrClientGetViewerEyeSurfaceDisplayInputIndex(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, ByteBuffer displayInput); - /** - * Get the projection matrix for a surface seen by an eye of a viewer
                  - * in a display config. (double version)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param near Distance from viewpoint to near clipping plane - must be
                  - * positive.
                  - * @param far Distance from viewpoint to far clipping plane - must be positive
                  - * and not equal to near, typically greater than near.
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param matrix Output projection matrix: supply an array of 16
                  - * (::OSVR_MATRIX_SIZE) doubles.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionMatrixd(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, double, double, OSVR_MatrixConventions, double*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceProjectionMatrixd(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, double, double, short, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceProjectionMatrixd(Pointer disp, int viewer, byte eye, int surface, double near, double far, short flags, DoubleByReference matrix); - /** - * Get the projection matrix for a surface seen by an eye of a viewer
                  - * in a display config. (double version)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param near Distance from viewpoint to near clipping plane - must be
                  - * positive.
                  - * @param far Distance from viewpoint to far clipping plane - must be positive
                  - * and not equal to near, typically greater than near.
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param matrix Output projection matrix: supply an array of 16
                  - * (::OSVR_MATRIX_SIZE) doubles.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionMatrixd(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, double, double, OSVR_MatrixConventions, double*) - */ - public static native byte osvrClientGetViewerEyeSurfaceProjectionMatrixd(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, double near, double far, short flags, DoubleBuffer matrix); - /** - * Get the projection matrix for a surface seen by an eye of a viewer
                  - * in a display config. (float version)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param near Distance to near clipping plane - must be nonzero, typically
                  - * positive.
                  - * @param far Distance to far clipping plane - must be nonzero, typically
                  - * positive and greater than near.
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param matrix Output projection matrix: supply an array of 16
                  - * (::OSVR_MATRIX_SIZE) floats.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionMatrixf(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, float, float, OSVR_MatrixConventions, float*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceProjectionMatrixf(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, float, float, short, java.nio.FloatBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceProjectionMatrixf(Pointer disp, int viewer, byte eye, int surface, float near, float far, short flags, FloatByReference matrix); - /** - * Get the projection matrix for a surface seen by an eye of a viewer
                  - * in a display config. (float version)
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param near Distance to near clipping plane - must be nonzero, typically
                  - * positive.
                  - * @param far Distance to far clipping plane - must be nonzero, typically
                  - * positive and greater than near.
                  - * @param flags Bitwise OR of matrix convention flags (see @ref MatrixFlags)
                  - * @param matrix Output projection matrix: supply an array of 16
                  - * (::OSVR_MATRIX_SIZE) floats.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionMatrixf(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, float, float, OSVR_MatrixConventions, float*) - */ - public static native byte osvrClientGetViewerEyeSurfaceProjectionMatrixf(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, float near, float far, short flags, FloatBuffer matrix); - /** - * Get the clipping planes (positions at unit distance) for a surface
                  - * seen by an eye of a viewer
                  - * in a display config.
                  - * This is only for use in integrations that cannot accept a fully-formulated
                  - * projection matrix as returned by
                  - * osvrClientGetViewerEyeSurfaceProjectionMatrixf() or
                  - * osvrClientGetViewerEyeSurfaceProjectionMatrixd(), and may not necessarily
                  - * provide the same optimizations.
                  - * As all the planes are given at unit (1) distance, before passing these
                  - * planes to a consuming function in your application/engine, you will typically
                  - * divide them by your near clipping plane distance.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param left Distance to left clipping plane
                  - * @param right Distance to right clipping plane
                  - * @param bottom Distance to bottom clipping plane
                  - * @param top Distance to top clipping plane
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionClippingPlanes(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, double*, double*, double*, double*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceProjectionClippingPlanes(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.DoubleBuffer, java.nio.DoubleBuffer, java.nio.DoubleBuffer, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceProjectionClippingPlanes(Pointer disp, int viewer, byte eye, int surface, DoubleByReference left, DoubleByReference right, DoubleByReference bottom, DoubleByReference top); - /** - * Get the clipping planes (positions at unit distance) for a surface
                  - * seen by an eye of a viewer
                  - * in a display config.
                  - * This is only for use in integrations that cannot accept a fully-formulated
                  - * projection matrix as returned by
                  - * osvrClientGetViewerEyeSurfaceProjectionMatrixf() or
                  - * osvrClientGetViewerEyeSurfaceProjectionMatrixd(), and may not necessarily
                  - * provide the same optimizations.
                  - * As all the planes are given at unit (1) distance, before passing these
                  - * planes to a consuming function in your application/engine, you will typically
                  - * divide them by your near clipping plane distance.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param left Distance to left clipping plane
                  - * @param right Distance to right clipping plane
                  - * @param bottom Distance to bottom clipping plane
                  - * @param top Distance to top clipping plane
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output arguments are unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceProjectionClippingPlanes(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, double*, double*, double*, double*) - */ - public static native byte osvrClientGetViewerEyeSurfaceProjectionClippingPlanes(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, DoubleBuffer left, DoubleBuffer right, DoubleBuffer bottom, DoubleBuffer top); - /** - * Determines if a surface seen by an eye of a viewer in a display
                  - * config requests some distortion to be performed.
                  - * This simply reports true or false, and does not specify which kind of
                  - * distortion implementations have been parameterized for this display. For
                  - * each distortion implementation your application supports, you'll want to
                  - * call the corresponding priority function to find out if it is available.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param distortionRequested Output parameter: whether distortion is
                  - * requested. **Constant** throughout the active, valid lifetime of a display
                  - * config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientDoesViewerEyeSurfaceWantDistortion(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_CBool*)
                  - * @deprecated use the safer method - * {@link #osvrClientDoesViewerEyeSurfaceWantDistortion(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.ByteBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientDoesViewerEyeSurfaceWantDistortion(Pointer disp, int viewer, byte eye, int surface, Pointer distortionRequested); - /** - * Determines if a surface seen by an eye of a viewer in a display
                  - * config requests some distortion to be performed.
                  - * This simply reports true or false, and does not specify which kind of
                  - * distortion implementations have been parameterized for this display. For
                  - * each distortion implementation your application supports, you'll want to
                  - * call the corresponding priority function to find out if it is available.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param distortionRequested Output parameter: whether distortion is
                  - * requested. **Constant** throughout the active, valid lifetime of a display
                  - * config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientDoesViewerEyeSurfaceWantDistortion(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_CBool*) - */ - public static native byte osvrClientDoesViewerEyeSurfaceWantDistortion(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, ByteBuffer distortionRequested); - /** - * Returns the priority/availability of radial distortion parameters for
                  - * a surface seen by an eye of a viewer in a display config.
                  - * If osvrClientDoesViewerEyeSurfaceWantDistortion() reports false, then the
                  - * display does not request distortion of any sort, and thus neither this nor
                  - * any other distortion strategy priority function will report an "available"
                  - * priority.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param priority Output: the priority level. Negative values
                  - * (canonically OSVR_DISTORTION_PRIORITY_UNAVAILABLE) indicate this technique
                  - * not available, higher values indicate higher preference for the given
                  - * technique based on the device's description. **Constant** throughout the
                  - * active, valid lifetime of a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceRadialDistortionPriority(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_DistortionPriority*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceRadialDistortionPriority(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, java.nio.IntBuffer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceRadialDistortionPriority(Pointer disp, int viewer, byte eye, int surface, IntByReference priority); - /** - * Returns the priority/availability of radial distortion parameters for
                  - * a surface seen by an eye of a viewer in a display config.
                  - * If osvrClientDoesViewerEyeSurfaceWantDistortion() reports false, then the
                  - * display does not request distortion of any sort, and thus neither this nor
                  - * any other distortion strategy priority function will report an "available"
                  - * priority.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param priority Output: the priority level. Negative values
                  - * (canonically OSVR_DISTORTION_PRIORITY_UNAVAILABLE) indicate this technique
                  - * not available, higher values indicate higher preference for the given
                  - * technique based on the device's description. **Constant** throughout the
                  - * active, valid lifetime of a display config object.
                  - * @return OSVR_RETURN_FAILURE if invalid parameters were passed, in which case
                  - * the output argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceRadialDistortionPriority(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_DistortionPriority*) - */ - public static native byte osvrClientGetViewerEyeSurfaceRadialDistortionPriority(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, IntBuffer priority); - /** - * Returns the radial distortion parameters, if known/requested, for a
                  - * surface seen by an eye of a viewer in a display config.
                  - * Will only succeed if osvrClientGetViewerEyeSurfaceRadialDistortionPriority()
                  - * reports a non-negative priority.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param params Output: the parameters for radial distortion
                  - * @return OSVR_RETURN_FAILURE if this surface does not have these parameters
                  - * described, or if invalid parameters were passed, in which case the output
                  - * argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceRadialDistortion(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_RadialDistortionParameters*)
                  - * @deprecated use the safer method - * {@link #osvrClientGetViewerEyeSurfaceRadialDistortion(com.jme3.system.osvr.osvrdisplay.OsvrDisplayLibrary.OSVR_DisplayConfig, int, byte, int, com.sun.jna.Pointer)} - * instead - */ - @Deprecated - public static native byte osvrClientGetViewerEyeSurfaceRadialDistortion(Pointer disp, int viewer, byte eye, int surface, Pointer params); - /** - * Returns the radial distortion parameters, if known/requested, for a
                  - * surface seen by an eye of a viewer in a display config.
                  - * Will only succeed if osvrClientGetViewerEyeSurfaceRadialDistortionPriority()
                  - * reports a non-negative priority.
                  - * @param disp Display config object
                  - * @param viewer Viewer ID
                  - * @param eye Eye ID
                  - * @param surface Surface ID
                  - * @param params Output: the parameters for radial distortion
                  - * @return OSVR_RETURN_FAILURE if this surface does not have these parameters
                  - * described, or if invalid parameters were passed, in which case the output
                  - * argument is unmodified.
                  - * Original signature : OSVR_ReturnCode osvrClientGetViewerEyeSurfaceRadialDistortion(OSVR_DisplayConfig, OSVR_ViewerCount, OSVR_EyeCount, OSVR_SurfaceCount, OSVR_RadialDistortionParameters*) - */ - public static native byte osvrClientGetViewerEyeSurfaceRadialDistortion(OsvrDisplayLibrary.OSVR_DisplayConfig disp, int viewer, byte eye, int surface, Pointer params); - public static class OSVR_ClientContext extends PointerType { - public OSVR_ClientContext(Pointer address) { - super(address); - } - public OSVR_ClientContext() { - super(); - } - }; - public static class OSVR_DisplayConfig extends PointerType { - public OSVR_DisplayConfig(Pointer address) { - super(address); - } - public OSVR_DisplayConfig() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrinterface/OsvrInterfaceLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrinterface/OsvrInterfaceLibrary.java deleted file mode 100644 index 2a1c69476f..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrinterface/OsvrInterfaceLibrary.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.jme3.system.osvr.osvrinterface; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientInterface; -import com.jme3.system.osvr.osvrclientreporttypes.OSVR_Pose3; -import com.jme3.system.osvr.osvrtimevalue.OSVR_TimeValue; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -/** - * JNA Wrapper for library osvrInterface
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrInterfaceLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrClientKit"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrInterfaceLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrInterfaceLibrary.class, OsvrInterfaceLibrary.JNA_NATIVE_LIB); - } - - /** Manually added */ - public static native byte osvrGetPoseState(OSVR_ClientInterface iface, OSVR_TimeValue timestamp, OSVR_Pose3 state); - -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Pose3.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Pose3.java deleted file mode 100644 index 8925e2c6e2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Pose3.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrmatrixconventions; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Pose3 extends Structure { - /** C type : OSVR_Vec3 */ - public OSVR_Vec3 translation; - /** C type : OSVR_Quaternion */ - public OSVR_Quaternion rotation; - public OSVR_Pose3() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("translation", "rotation"); - } - /** - * @param translation C type : OSVR_Vec3
                  - * @param rotation C type : OSVR_Quaternion - */ - public OSVR_Pose3(OSVR_Vec3 translation, OSVR_Quaternion rotation) { - super(); - this.translation = translation; - this.rotation = rotation; - } - public OSVR_Pose3(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Pose3 implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Pose3 implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Quaternion.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Quaternion.java deleted file mode 100644 index 04c9145941..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Quaternion.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrmatrixconventions; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Quaternion extends Structure { - /** C type : double[4] */ - public double[] data = new double[4]; - public OSVR_Quaternion() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("data"); - } - /** @param data C type : double[4] */ - public OSVR_Quaternion(double data[]) { - super(); - if ((data.length != this.data.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.data = data; - } - public OSVR_Quaternion(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Quaternion implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Quaternion implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Vec3.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Vec3.java deleted file mode 100644 index 31db1607ae..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OSVR_Vec3.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrmatrixconventions; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_Vec3 extends Structure { - /** C type : double[3] */ - public double[] data = new double[3]; - public OSVR_Vec3() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("data"); - } - /** @param data C type : double[3] */ - public OSVR_Vec3(double data[]) { - super(); - if ((data.length != this.data.length)) - throw new IllegalArgumentException("Wrong array size !"); - this.data = data; - } - public OSVR_Vec3(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_Vec3 implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_Vec3 implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OsvrMatrixConventionsLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OsvrMatrixConventionsLibrary.java deleted file mode 100644 index dde9fe4102..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrmatrixconventions/OsvrMatrixConventionsLibrary.java +++ /dev/null @@ -1,192 +0,0 @@ -package com.jme3.system.osvr.osvrmatrixconventions; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -import com.sun.jna.ptr.DoubleByReference; -import com.sun.jna.ptr.FloatByReference; -import java.nio.DoubleBuffer; -import java.nio.FloatBuffer; -/** - * JNA Wrapper for library osvrMatrixConventions
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrMatrixConventionsLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrUtil"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrMatrixConventionsLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrMatrixConventionsLibrary.class, OsvrMatrixConventionsLibrary.JNA_NATIVE_LIB); - } - /** enum values */ - public static interface OSVR_MatrixMasks { - public static final int OSVR_MATRIX_MASK_ROWMAJOR = 0x1; - public static final int OSVR_MATRIX_MASK_ROWVECTORS = 0x2; - public static final int OSVR_MATRIX_MASK_LHINPUT = 0x4; - public static final int OSVR_MATRIX_MASK_UNSIGNEDZ = 0x8; - }; - /** enum values */ - public static interface OSVR_MatrixOrderingFlags { - public static final int OSVR_MATRIX_COLMAJOR = 0x0; - public static final int OSVR_MATRIX_ROWMAJOR = (int)OsvrMatrixConventionsLibrary.OSVR_MatrixMasks.OSVR_MATRIX_MASK_ROWMAJOR; - }; - /** enum values */ - public static interface OSVR_MatrixVectorFlags { - public static final int OSVR_MATRIX_COLVECTORS = 0x0; - public static final int OSVR_MATRIX_ROWVECTORS = (int)OsvrMatrixConventionsLibrary.OSVR_MatrixMasks.OSVR_MATRIX_MASK_ROWVECTORS; - }; - /** enum values */ - public static interface OSVR_ProjectionMatrixInputFlags { - public static final int OSVR_MATRIX_RHINPUT = 0x0; - public static final int OSVR_MATRIX_LHINPUT = (int)OsvrMatrixConventionsLibrary.OSVR_MatrixMasks.OSVR_MATRIX_MASK_LHINPUT; - }; - /** enum values */ - public static interface OSVR_ProjectionMatrixZFlags { - public static final int OSVR_MATRIX_SIGNEDZ = 0x0; - public static final int OSVR_MATRIX_UNSIGNEDZ = (int)OsvrMatrixConventionsLibrary.OSVR_MatrixMasks.OSVR_MATRIX_MASK_UNSIGNEDZ; - }; - public static final int OSVR_MATRIX_SIZE = 16; - public static final int OSVR_RETURN_SUCCESS = (int)(0); - public static final int OSVR_RETURN_FAILURE = (int)(1); - /** Original signature : double osvrVec3GetX(const OSVR_Vec3*) */ - public static native double osvrVec3GetX(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetX(OSVR_Vec3*, double) */ - public static native void osvrVec3SetX(OSVR_Vec3 v, double val); - /** Original signature : double osvrVec3GetY(const OSVR_Vec3*) */ - public static native double osvrVec3GetY(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetY(OSVR_Vec3*, double) */ - public static native void osvrVec3SetY(OSVR_Vec3 v, double val); - /** Original signature : double osvrVec3GetZ(const OSVR_Vec3*) */ - public static native double osvrVec3GetZ(OSVR_Vec3 v); - /** Original signature : void osvrVec3SetZ(OSVR_Vec3*, double) */ - public static native void osvrVec3SetZ(OSVR_Vec3 v, double val); - /** - * Set a Vec3 to the zero vector
                  - * Original signature : void osvrVec3Zero(OSVR_Vec3*) - */ - public static native void osvrVec3Zero(OSVR_Vec3 v); - /** Original signature : double osvrQuatGetW(const OSVR_Quaternion*) */ - public static native double osvrQuatGetW(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetW(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetW(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetX(const OSVR_Quaternion*) */ - public static native double osvrQuatGetX(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetX(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetX(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetY(const OSVR_Quaternion*) */ - public static native double osvrQuatGetY(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetY(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetY(OSVR_Quaternion q, double val); - /** Original signature : double osvrQuatGetZ(const OSVR_Quaternion*) */ - public static native double osvrQuatGetZ(OSVR_Quaternion q); - /** Original signature : void osvrQuatSetZ(OSVR_Quaternion*, double) */ - public static native void osvrQuatSetZ(OSVR_Quaternion q, double val); - /** - * Set a quaternion to the identity rotation
                  - * Original signature : void osvrQuatSetIdentity(OSVR_Quaternion*) - */ - public static native void osvrQuatSetIdentity(OSVR_Quaternion q); - /** - * Set a pose to identity
                  - * Original signature : void osvrPose3SetIdentity(OSVR_Pose3*) - */ - public static native void osvrPose3SetIdentity(OSVR_Pose3 pose); - /** - * Set a matrix of doubles based on a Pose3.
                  - * @param pose The Pose3 to convert
                  - * @param flags Memory ordering flag - see @ref MatrixFlags
                  - * @param mat an array of 16 doubles
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrixd(const OSVR_Pose3*, OSVR_MatrixConventions, double*)
                  - * @deprecated use the safer method - * {@link #osvrPose3ToMatrixd(com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3, short, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrPose3ToMatrixd(OSVR_Pose3 pose, short flags, DoubleByReference mat); - /** - * Set a matrix of doubles based on a Pose3.
                  - * @param pose The Pose3 to convert
                  - * @param flags Memory ordering flag - see @ref MatrixFlags
                  - * @param mat an array of 16 doubles
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrixd(const OSVR_Pose3*, OSVR_MatrixConventions, double*) - */ - public static native byte osvrPose3ToMatrixd(OSVR_Pose3 pose, short flags, DoubleBuffer mat); - /** - * Set a matrix of floats based on a Pose3.
                  - * @param pose The Pose3 to convert
                  - * @param flags Memory ordering flag - see @ref MatrixFlags
                  - * @param mat an array of 16 floats
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrixf(const OSVR_Pose3*, OSVR_MatrixConventions, float*)
                  - * @deprecated use the safer method - * {@link #osvrPose3ToMatrixf(com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3, short, java.nio.FloatBuffer)} - * instead - */ - @Deprecated - public static native byte osvrPose3ToMatrixf(OSVR_Pose3 pose, short flags, FloatByReference mat); - /** - * Set a matrix of floats based on a Pose3.
                  - * @param pose The Pose3 to convert
                  - * @param flags Memory ordering flag - see @ref MatrixFlags
                  - * @param mat an array of 16 floats
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrixf(const OSVR_Pose3*, OSVR_MatrixConventions, float*) - */ - public static native byte osvrPose3ToMatrixf(OSVR_Pose3 pose, short flags, FloatBuffer mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detecting scalar
                  - * type)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, double*)
                  - * @deprecated use the safer method - * {@link #osvrPose3ToMatrix(com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3, short, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, DoubleByReference mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detecting scalar
                  - * type)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, double*) - */ - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, DoubleBuffer mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detecting scalar
                  - * type)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, float*)
                  - * @deprecated use the safer method - * {@link #osvrPose3ToMatrix(com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3, short, java.nio.FloatBuffer)} - * instead - */ - @Deprecated - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, FloatByReference mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detecting scalar
                  - * type)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, float*) - */ - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, FloatBuffer mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detects scalar
                  - * and takes array rather than pointer)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, Scalar[OSVR_MATRIX_SIZE])
                  - * @deprecated use the safer method - * {@link #osvrPose3ToMatrix(com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3, short, java.nio.DoubleBuffer)} - * instead - */ - @Deprecated - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, Pointer mat); - /** - * Set a matrix based on a Pose3. (C++-only overload - detects scalar
                  - * and takes array rather than pointer)
                  - * Original signature : OSVR_ReturnCode osvrPose3ToMatrix(const OSVR_Pose3*, OSVR_MatrixConventions, Scalar[OSVR_MATRIX_SIZE]) - */ - public static native byte osvrPose3ToMatrix(OSVR_Pose3 pose, short flags, OsvrMatrixConventionsLibrary.Scalar mat[]); - public static class Scalar extends PointerType { - public Scalar(Pointer address) { - super(address); - } - public Scalar() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ProjectionMatrix.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ProjectionMatrix.java deleted file mode 100644 index 7b9d66e3fe..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ProjectionMatrix.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanager; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_ProjectionMatrix extends Structure { - public double left; - public double right; - public double top; - public double bottom; - /** Cannot name "near" because Visual Studio keyword */ - public double nearClip; - public double farClip; - public OSVR_ProjectionMatrix() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("left", "right", "top", "bottom", "nearClip", "farClip"); - } - /** @param nearClip Cannot name "near" because Visual Studio keyword */ - public OSVR_ProjectionMatrix(double left, double right, double top, double bottom, double nearClip, double farClip) { - super(); - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - this.nearClip = nearClip; - this.farClip = farClip; - } - public OSVR_ProjectionMatrix(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_ProjectionMatrix implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_ProjectionMatrix implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RGB.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RGB.java deleted file mode 100644 index 09695a57b8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RGB.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanager; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_RGB extends Structure { - public float r; - public float g; - public float b; - public OSVR_RGB() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("r", "g", "b"); - } - public OSVR_RGB(float r, float g, float b) { - super(); - this.r = r; - this.g = g; - this.b = b; - } - public OSVR_RGB(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_RGB implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_RGB implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RenderParams.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RenderParams.java deleted file mode 100644 index 262b7ddb28..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_RenderParams.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanager; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_RenderParams extends Structure { - /** - * Room space to insert
                  - * C type : OSVR_PoseState* - */ - public Pointer worldFromRoomAppend; - /** - * Overrides head space
                  - * C type : OSVR_PoseState* - */ - public Pointer roomFromHeadReplace; - public double nearClipDistanceMeters; - public double farClipDistanceMeters; - public OSVR_RenderParams() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("worldFromRoomAppend", "roomFromHeadReplace", "nearClipDistanceMeters", "farClipDistanceMeters"); - } - /** - * @param worldFromRoomAppend Room space to insert
                  - * C type : OSVR_PoseState*
                  - * @param roomFromHeadReplace Overrides head space
                  - * C type : OSVR_PoseState* - */ - public OSVR_RenderParams(Pointer worldFromRoomAppend, Pointer roomFromHeadReplace, double nearClipDistanceMeters, double farClipDistanceMeters) { - super(); - this.worldFromRoomAppend = worldFromRoomAppend; - this.roomFromHeadReplace = roomFromHeadReplace; - this.nearClipDistanceMeters = nearClipDistanceMeters; - this.farClipDistanceMeters = farClipDistanceMeters; - } - public OSVR_RenderParams(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_RenderParams implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_RenderParams implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ViewportDescription.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ViewportDescription.java deleted file mode 100644 index 11f05bda7c..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OSVR_ViewportDescription.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanager; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_ViewportDescription extends Structure { - /** Left side of the viewport in pixels */ - public double left; - /** First pixel in the viewport at the bottom. */ - public double lower; - /** Last pixel in the viewport at the top */ - public double width; - /** Last pixel on the right of the viewport in pixels */ - public double height; - public OSVR_ViewportDescription() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("left", "lower", "width", "height"); - } - /** - * @param left Left side of the viewport in pixels
                  - * @param lower First pixel in the viewport at the bottom.
                  - * @param width Last pixel in the viewport at the top
                  - * @param height Last pixel on the right of the viewport in pixels - */ - public OSVR_ViewportDescription(double left, double lower, double width, double height) { - super(); - this.left = left; - this.lower = lower; - this.width = width; - this.height = height; - } - public OSVR_ViewportDescription(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_ViewportDescription implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_ViewportDescription implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OsvrRenderManagerLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OsvrRenderManagerLibrary.java deleted file mode 100644 index 649c222d26..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanager/OsvrRenderManagerLibrary.java +++ /dev/null @@ -1,117 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanager; -import com.ochafik.lang.jnaerator.runtime.NativeSizeByReference; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.ptr.DoubleByReference; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.PointerByReference; -import java.nio.DoubleBuffer; -import java.nio.FloatBuffer; -/** - * JNA Wrapper for library osvrRenderManager
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrRenderManagerLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrRenderManager"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrRenderManagerLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrRenderManagerLibrary.class, OsvrRenderManagerLibrary.JNA_NATIVE_LIB); - } - /** enum values */ - public static interface OSVR_OpenStatus { - public static final int OSVR_OPEN_STATUS_FAILURE = 0; - public static final int OSVR_OPEN_STATUS_PARTIAL = 1; - public static final int OSVR_OPEN_STATUS_COMPLETE = 2; - }; - /** - * todo OSVR_RenderTimingInfo. - * Original signature : OSVR_ReturnCode osvrDestroyRenderManager(OSVR_RenderManager) - */ - public static native byte osvrDestroyRenderManager(Pointer renderManager); - /** - * todo Make this actually cache, for now it does not.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetNumRenderInfo(OSVR_RenderManager, OSVR_RenderParams, OSVR_RenderInfoCount*) - */ - public static native byte osvrRenderManagerGetNumRenderInfo(Pointer renderManager, OSVR_RenderParams.ByValue renderParams, NativeSizeByReference numRenderInfoOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerGetDoingOkay(OSVR_RenderManager) */ - public static native byte osvrRenderManagerGetDoingOkay(Pointer renderManager); - /** Original signature : OSVR_ReturnCode osvrRenderManagerGetDefaultRenderParams(OSVR_RenderParams*) */ - public static native byte osvrRenderManagerGetDefaultRenderParams(OSVR_RenderParams renderParamsOut); - /** - * must be registered before they are presented.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerStartPresentRenderBuffers(OSVR_RenderManagerPresentState*) - */ - public static native byte osvrRenderManagerStartPresentRenderBuffers(PointerByReference presentStateOut); - /** - * buffers for a single frame. This sequence starts with the Start function.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerFinishPresentRenderBuffers(OSVR_RenderManager, OSVR_RenderManagerPresentState, OSVR_RenderParams, OSVR_CBool) - */ - public static native byte osvrRenderManagerFinishPresentRenderBuffers(Pointer renderManager, Pointer presentState, OSVR_RenderParams.ByValue renderParams, byte shouldFlipY); - /** - * must be registered before they are presented.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerStartRegisterRenderBuffers(OSVR_RenderManagerRegisterBufferState*) - */ - public static native byte osvrRenderManagerStartRegisterRenderBuffers(PointerByReference registerBufferStateOut); - /** - * buffers for a single frame. This sequence starts with the Start function.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerFinishRegisterRenderBuffers(OSVR_RenderManager, OSVR_RenderManagerRegisterBufferState, OSVR_CBool) - */ - public static native byte osvrRenderManagerFinishRegisterRenderBuffers(Pointer renderManager, Pointer registerBufferState, byte appWillNotOverwriteBeforeNewPresent); - /** Original signature : OSVR_ReturnCode osvrRenderManagerPresentSolidColorf(OSVR_RenderManager, OSVR_RGB_FLOAT) */ - public static native byte osvrRenderManagerPresentSolidColorf(Pointer renderManager, com.jme3.system.osvr.osvrrendermanager.OSVR_RGB.ByValue rgb); - /** - * when you're done.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetRenderInfoCollection(OSVR_RenderManager, OSVR_RenderParams, OSVR_RenderInfoCollection*) - */ - public static native byte osvrRenderManagerGetRenderInfoCollection(Pointer renderManager, OSVR_RenderParams.ByValue renderParams, PointerByReference renderInfoCollectionOut); - /** - * Releases the OSVR_RenderInfoCollection.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerReleaseRenderInfoCollection(OSVR_RenderInfoCollection) - */ - public static native byte osvrRenderManagerReleaseRenderInfoCollection(Pointer renderInfoCollection); - /** - * Get the size of the OSVR_RenderInfoCollection.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetNumRenderInfoInCollection(OSVR_RenderInfoCollection, OSVR_RenderInfoCount*) - */ - public static native byte osvrRenderManagerGetNumRenderInfoInCollection(Pointer renderInfoCollection, NativeSizeByReference countOut); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_OpenGL(double*, OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_OpenGL(java.nio.DoubleBuffer, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_OpenGL(com.sun.jna.ptr.DoubleByReference, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_OpenGL(DoubleByReference OpenGL_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_OpenGL(double*, OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_OpenGL(DoubleBuffer OpenGL_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_D3D(float[16], OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_D3D(java.nio.FloatBuffer, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_D3D(com.sun.jna.ptr.FloatByReference, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_D3D(FloatByReference D3D_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_D3D(float[16], OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_D3D(FloatBuffer D3D_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_Unreal(float[16], OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_Unreal(java.nio.FloatBuffer, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_Unreal(com.sun.jna.ptr.FloatByReference, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_Unreal(FloatByReference Unreal_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_Unreal(float[16], OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_Unreal(FloatBuffer Unreal_out, com.jme3.system.osvr.osvrrendermanager.OSVR_ProjectionMatrix.ByValue projection_in); -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_GraphicsLibraryOpenGL.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_GraphicsLibraryOpenGL.java deleted file mode 100644 index 626609e081..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_GraphicsLibraryOpenGL.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_GraphicsLibraryOpenGL extends Structure { - /** C type : const OSVR_OpenGLToolkitFunctions* */ - public com.jme3.system.osvr.osvrrendermanageropengl.OSVR_OpenGLToolkitFunctions.ByReference toolkit; - public OSVR_GraphicsLibraryOpenGL() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("toolkit"); - } - /** @param toolkit C type : const OSVR_OpenGLToolkitFunctions* */ - public OSVR_GraphicsLibraryOpenGL(com.jme3.system.osvr.osvrrendermanageropengl.OSVR_OpenGLToolkitFunctions.ByReference toolkit) { - super(); - this.toolkit = toolkit; - } - public OSVR_GraphicsLibraryOpenGL(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_GraphicsLibraryOpenGL implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_GraphicsLibraryOpenGL implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLContextParams.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLContextParams.java deleted file mode 100644 index 71a67ee1a0..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLContextParams.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_OpenGLContextParams extends Structure { - /** C type : const char* */ - public Pointer windowTitle; - /** C type : OSVR_CBool */ - public byte fullScreen; - public int width; - public int height; - public int xPos; - public int yPos; - public int bitsPerPixel; - public int numBuffers; - /** C type : OSVR_CBool */ - public byte visible; - public OSVR_OpenGLContextParams() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("windowTitle", "fullScreen", "width", "height", "xPos", "yPos", "bitsPerPixel", "numBuffers", "visible"); - } - /** - * @param windowTitle C type : const char*
                  - * @param fullScreen C type : OSVR_CBool
                  - * @param visible C type : OSVR_CBool - */ - public OSVR_OpenGLContextParams(Pointer windowTitle, byte fullScreen, int width, int height, int xPos, int yPos, int bitsPerPixel, int numBuffers, byte visible) { - super(); - this.windowTitle = windowTitle; - this.fullScreen = fullScreen; - this.width = width; - this.height = height; - this.xPos = xPos; - this.yPos = yPos; - this.bitsPerPixel = bitsPerPixel; - this.numBuffers = numBuffers; - this.visible = visible; - } - public OSVR_OpenGLContextParams(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_OpenGLContextParams implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_OpenGLContextParams implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLToolkitFunctions.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLToolkitFunctions.java deleted file mode 100644 index 0b996bcecd..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenGLToolkitFunctions.java +++ /dev/null @@ -1,91 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.ochafik.lang.jnaerator.runtime.NativeSize; -import com.sun.jna.Callback; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import com.sun.jna.ptr.IntByReference; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_OpenGLToolkitFunctions extends Structure { - public NativeSize size; - /** C type : void* */ - public Pointer data; - /** C type : create_callback* */ - public OSVR_OpenGLToolkitFunctions.create_callback create; - /** C type : destroy_callback* */ - public OSVR_OpenGLToolkitFunctions.destroy_callback destroy; - /** C type : handleEvents_callback* */ - public OSVR_OpenGLToolkitFunctions.handleEvents_callback handleEvents; - /** C type : getDisplayFrameBuffer_callback* */ - public OSVR_OpenGLToolkitFunctions.getDisplayFrameBuffer_callback getDisplayFrameBuffer; - /** C type : getDisplaySizeOverride_callback* */ - public OSVR_OpenGLToolkitFunctions.getDisplaySizeOverride_callback getDisplaySizeOverride; - public interface create_callback extends Callback { - void apply(Pointer data); - }; - public interface destroy_callback extends Callback { - void apply(Pointer data); - }; - public interface OSVR_CBool_callback extends Callback { - int apply(Pointer data, OSVR_OpenGLContextParams p); - }; - public interface OSVR_CBool_callback2 extends Callback { - int apply(Pointer data); - }; - public interface OSVR_CBool_callback3 extends Callback { - int apply(Pointer data, NativeSize display); - }; - public interface OSVR_CBool_callback4 extends Callback { - int apply(Pointer data, NativeSize display); - }; - public interface OSVR_CBool_callback5 extends Callback { - int apply(Pointer data, byte verticalSync); - }; - public interface handleEvents_callback extends Callback { - byte apply(Pointer data); - }; - public interface getDisplayFrameBuffer_callback extends Callback { - byte apply(Pointer data, NativeSize display, IntByReference frameBufferOut); - }; - public interface getDisplaySizeOverride_callback extends Callback { - byte apply(Pointer data, NativeSize display, IntByReference width, IntByReference height); - }; - public OSVR_OpenGLToolkitFunctions() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("size", "data", "create", "destroy", "handleEvents", "getDisplayFrameBuffer", "getDisplaySizeOverride"); - } - /** - * @param data C type : void*
                  - * @param create C type : create_callback*
                  - * @param destroy C type : destroy_callback*
                  - * @param handleEvents C type : handleEvents_callback*
                  - * @param getDisplayFrameBuffer C type : getDisplayFrameBuffer_callback*
                  - * @param getDisplaySizeOverride C type : getDisplaySizeOverride_callback* - */ - public OSVR_OpenGLToolkitFunctions(NativeSize size, Pointer data, OSVR_OpenGLToolkitFunctions.create_callback create, OSVR_OpenGLToolkitFunctions.destroy_callback destroy, OSVR_OpenGLToolkitFunctions.handleEvents_callback handleEvents, OSVR_OpenGLToolkitFunctions.getDisplayFrameBuffer_callback getDisplayFrameBuffer, OSVR_OpenGLToolkitFunctions.getDisplaySizeOverride_callback getDisplaySizeOverride) { - super(); - this.size = size; - this.data = data; - this.create = create; - this.destroy = destroy; - this.handleEvents = handleEvents; - this.getDisplayFrameBuffer = getDisplayFrameBuffer; - this.getDisplaySizeOverride = getDisplaySizeOverride; - } - public OSVR_OpenGLToolkitFunctions(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_OpenGLToolkitFunctions implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_OpenGLToolkitFunctions implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenResultsOpenGL.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenResultsOpenGL.java deleted file mode 100644 index e4da1e6da8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_OpenResultsOpenGL.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_OpenResultsOpenGL extends Structure { - /** - * C type : OSVR_OpenStatus - */ - public int status; - /** C type : OSVR_GraphicsLibraryOpenGL */ - public OSVR_GraphicsLibraryOpenGL library; - /** C type : OSVR_RenderBufferOpenGL */ - public OSVR_RenderBufferOpenGL buffers; - public OSVR_OpenResultsOpenGL() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("status", "library", "buffers"); - } - /** - * @param status @see OsvrRenderManagerOpenGLLibrary.OSVR_OpenStatus
                  - * C type : OSVR_OpenStatus
                  - * @param library C type : OSVR_GraphicsLibraryOpenGL
                  - * @param buffers C type : OSVR_RenderBufferOpenGL - */ - public OSVR_OpenResultsOpenGL(int status, OSVR_GraphicsLibraryOpenGL library, OSVR_RenderBufferOpenGL buffers) { - super(); - this.status = status; - this.library = library; - this.buffers = buffers; - } - public OSVR_OpenResultsOpenGL(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_OpenResultsOpenGL implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_OpenResultsOpenGL implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ProjectionMatrix.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ProjectionMatrix.java deleted file mode 100644 index 21df22bdba..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ProjectionMatrix.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_ProjectionMatrix extends Structure { - public double left; - public double right; - public double top; - public double bottom; - /** Cannot name "near" because Visual Studio keyword */ - public double nearClip; - public double farClip; - public OSVR_ProjectionMatrix() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("left", "right", "top", "bottom", "nearClip", "farClip"); - } - /** @param nearClip Cannot name "near" because Visual Studio keyword */ - public OSVR_ProjectionMatrix(double left, double right, double top, double bottom, double nearClip, double farClip) { - super(); - this.left = left; - this.right = right; - this.top = top; - this.bottom = bottom; - this.nearClip = nearClip; - this.farClip = farClip; - } - public OSVR_ProjectionMatrix(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_ProjectionMatrix implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_ProjectionMatrix implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RGB.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RGB.java deleted file mode 100644 index fb5a925b35..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RGB.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_RGB extends Structure { - public float r; - public float g; - public float b; - public OSVR_RGB() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("r", "g", "b"); - } - public OSVR_RGB(float r, float g, float b) { - super(); - this.r = r; - this.g = g; - this.b = b; - } - public OSVR_RGB(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_RGB implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_RGB implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderBufferOpenGL.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderBufferOpenGL.java deleted file mode 100644 index d2488eff9e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderBufferOpenGL.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_RenderBufferOpenGL extends Structure { - public int colorBufferName; - public int depthStencilBufferName; - public OSVR_RenderBufferOpenGL() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("colorBufferName", "depthStencilBufferName"); - } - public OSVR_RenderBufferOpenGL(int colorBufferName, int depthStencilBufferName) { - super(); - this.colorBufferName = colorBufferName; - this.depthStencilBufferName = depthStencilBufferName; - } - public OSVR_RenderBufferOpenGL(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_RenderBufferOpenGL implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_RenderBufferOpenGL implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderInfoOpenGL.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderInfoOpenGL.java deleted file mode 100644 index 3b0e1a79f2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderInfoOpenGL.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.jme3.system.osvr.osvrmatrixconventions.OSVR_Pose3; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public /*abstract*/ class OSVR_RenderInfoOpenGL extends Structure { - /** C type : OSVR_GraphicsLibraryOpenGL */ - public OSVR_GraphicsLibraryOpenGL library; - /** C type : OSVR_ViewportDescription */ - public OSVR_ViewportDescription viewport; - public OSVR_Pose3 pose; - /** C type : OSVR_ProjectionMatrix */ - public OSVR_ProjectionMatrix projection; - public OSVR_RenderInfoOpenGL() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("library", "viewport", "pose", "projection"); - } - /** - * @param library C type : OSVR_GraphicsLibraryOpenGL
                  - * @param viewport C type : OSVR_ViewportDescription
                  - * @param projection C type : OSVR_ProjectionMatrix - */ - public OSVR_RenderInfoOpenGL(OSVR_GraphicsLibraryOpenGL library, OSVR_ViewportDescription viewport, OSVR_Pose3 pose, OSVR_ProjectionMatrix projection) { - super(); - this.library = library; - this.viewport = viewport; - this.pose = pose; - this.projection = projection; - } - public OSVR_RenderInfoOpenGL(Pointer peer) { - super(peer); - } - public static /*abstract*/ class ByReference extends OSVR_RenderInfoOpenGL implements Structure.ByReference { - - }; - public static /*abstract*/ class ByValue extends OSVR_RenderInfoOpenGL implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderParams.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderParams.java deleted file mode 100644 index 2b63b7b3cf..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_RenderParams.java +++ /dev/null @@ -1,52 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_RenderParams extends Structure { - /** - * Room space to insert
                  - * C type : OSVR_PoseState* - */ - public Pointer worldFromRoomAppend; - /** - * Overrides head space
                  - * C type : OSVR_PoseState* - */ - public Pointer roomFromHeadReplace; - public double nearClipDistanceMeters; - public double farClipDistanceMeters; - public OSVR_RenderParams() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("worldFromRoomAppend", "roomFromHeadReplace", "nearClipDistanceMeters", "farClipDistanceMeters"); - } - /** - * @param worldFromRoomAppend Room space to insert
                  - * C type : OSVR_PoseState*
                  - * @param roomFromHeadReplace Overrides head space
                  - * C type : OSVR_PoseState* - */ - public OSVR_RenderParams(Pointer worldFromRoomAppend, Pointer roomFromHeadReplace, double nearClipDistanceMeters, double farClipDistanceMeters) { - super(); - this.worldFromRoomAppend = worldFromRoomAppend; - this.roomFromHeadReplace = roomFromHeadReplace; - this.nearClipDistanceMeters = nearClipDistanceMeters; - this.farClipDistanceMeters = farClipDistanceMeters; - } - public OSVR_RenderParams(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_RenderParams implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_RenderParams implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ViewportDescription.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ViewportDescription.java deleted file mode 100644 index 0df38e45f5..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OSVR_ViewportDescription.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_ViewportDescription extends Structure { - /** Left side of the viewport in pixels */ - public double left; - /** First pixel in the viewport at the bottom. */ - public double lower; - /** Last pixel in the viewport at the top */ - public double width; - /** Last pixel on the right of the viewport in pixels */ - public double height; - public OSVR_ViewportDescription() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("left", "lower", "width", "height"); - } - /** - * @param left Left side of the viewport in pixels
                  - * @param lower First pixel in the viewport at the bottom.
                  - * @param width Last pixel in the viewport at the top
                  - * @param height Last pixel on the right of the viewport in pixels - */ - public OSVR_ViewportDescription(double left, double lower, double width, double height) { - super(); - this.left = left; - this.lower = lower; - this.width = width; - this.height = height; - } - public OSVR_ViewportDescription(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_ViewportDescription implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_ViewportDescription implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OsvrRenderManagerOpenGLLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OsvrRenderManagerOpenGLLibrary.java deleted file mode 100644 index 6b3a02f065..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrrendermanageropengl/OsvrRenderManagerOpenGLLibrary.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.jme3.system.osvr.osvrrendermanageropengl; -import com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary; -import com.ochafik.lang.jnaerator.runtime.NativeSize; -import com.ochafik.lang.jnaerator.runtime.NativeSizeByReference; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -import com.sun.jna.ptr.DoubleByReference; -import com.sun.jna.ptr.FloatByReference; -import com.sun.jna.ptr.IntByReference; -import com.sun.jna.ptr.PointerByReference; -import java.nio.DoubleBuffer; -import java.nio.FloatBuffer; -import java.nio.IntBuffer; -/** - * JNA Wrapper for library osvrRenderManagerOpenGL
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrRenderManagerOpenGLLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrRenderManager"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrRenderManagerOpenGLLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrRenderManagerOpenGLLibrary.class, OsvrRenderManagerOpenGLLibrary.JNA_NATIVE_LIB); - } - /** enum values */ - public static interface OSVR_OpenStatus { - public static final int OSVR_OPEN_STATUS_FAILURE = 0; - public static final int OSVR_OPEN_STATUS_PARTIAL = 1; - public static final int OSVR_OPEN_STATUS_COMPLETE = 2; - }; - /** - * todo OSVR_RenderTimingInfo
                  - * Original signature : OSVR_ReturnCode osvrDestroyRenderManager(OSVR_RenderManager) - */ - public static native byte osvrDestroyRenderManager(Pointer renderManager); - /** - * todo Make this actually cache, for now it does not.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetNumRenderInfo(OSVR_RenderManager, OSVR_RenderParams, OSVR_RenderInfoCount*) - */ - public static native byte osvrRenderManagerGetNumRenderInfo(Pointer renderManager, OSVR_RenderParams.ByValue renderParams, NativeSizeByReference numRenderInfoOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerGetDoingOkay(OSVR_RenderManager) */ - public static native byte osvrRenderManagerGetDoingOkay(Pointer renderManager); - /** Original signature : OSVR_ReturnCode osvrRenderManagerGetDefaultRenderParams(OSVR_RenderParams*) */ - public static native byte osvrRenderManagerGetDefaultRenderParams(OSVR_RenderParams renderParamsOut); - /** - * must be registered before they are presented.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerStartPresentRenderBuffers(OSVR_RenderManagerPresentState*) - */ - public static native byte osvrRenderManagerStartPresentRenderBuffers(PointerByReference presentStateOut); - /** - * buffers for a single frame. This sequence starts with the Start function.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerFinishPresentRenderBuffers(OSVR_RenderManager, OSVR_RenderManagerPresentState, OSVR_RenderParams, OSVR_CBool) - */ - public static native byte osvrRenderManagerFinishPresentRenderBuffers(Pointer renderManager, Pointer presentState, OSVR_RenderParams.ByValue renderParams, byte shouldFlipY); - /** - * must be registered before they are presented.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerStartRegisterRenderBuffers(OSVR_RenderManagerRegisterBufferState*) - */ - public static native byte osvrRenderManagerStartRegisterRenderBuffers(PointerByReference registerBufferStateOut); - /** - * buffers for a single frame. This sequence starts with the Start function.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerFinishRegisterRenderBuffers(OSVR_RenderManager, OSVR_RenderManagerRegisterBufferState, OSVR_CBool) - */ - public static native byte osvrRenderManagerFinishRegisterRenderBuffers(Pointer renderManager, Pointer registerBufferState, byte appWillNotOverwriteBeforeNewPresent); - /** Original signature : OSVR_ReturnCode osvrRenderManagerPresentSolidColorf(OSVR_RenderManager, OSVR_RGB_FLOAT) */ - public static native byte osvrRenderManagerPresentSolidColorf(Pointer renderManager, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RGB.ByValue rgb); - /** - * when you're done.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetRenderInfoCollection(OSVR_RenderManager, OSVR_RenderParams, OSVR_RenderInfoCollection*) - */ - public static native byte osvrRenderManagerGetRenderInfoCollection(Pointer renderManager, OSVR_RenderParams.ByValue renderParams, PointerByReference renderInfoCollectionOut); - /** - * Releases the OSVR_RenderInfoCollection.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerReleaseRenderInfoCollection(OSVR_RenderInfoCollection) - */ - public static native byte osvrRenderManagerReleaseRenderInfoCollection(Pointer renderInfoCollection); - /** - * Get the size of the OSVR_RenderInfoCollection.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetNumRenderInfoInCollection(OSVR_RenderInfoCollection, OSVR_RenderInfoCount*) - */ - public static native byte osvrRenderManagerGetNumRenderInfoInCollection(Pointer renderInfoCollection, NativeSizeByReference countOut); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_OpenGL(double*, OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_OpenGL(java.nio.DoubleBuffer, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_OpenGL(com.sun.jna.ptr.DoubleByReference, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_OpenGL(DoubleByReference OpenGL_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_OpenGL(double*, OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_OpenGL(DoubleBuffer OpenGL_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_D3D(float[16], OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_D3D(java.nio.FloatBuffer, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_D3D(com.sun.jna.ptr.FloatByReference, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_D3D(FloatByReference D3D_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_D3D(float[16], OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_D3D(FloatBuffer D3D_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_Unreal(float[16], OSVR_ProjectionMatrix)
                  - * @deprecated use the safer methods {@link #OSVR_Projection_to_Unreal(java.nio.FloatBuffer, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} and {@link #OSVR_Projection_to_Unreal(com.sun.jna.ptr.FloatByReference, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue)} instead - */ - @Deprecated - public static native byte OSVR_Projection_to_Unreal(FloatByReference Unreal_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * @return True on success, false on failure (null pointer).
                  - * Original signature : OSVR_ReturnCode OSVR_Projection_to_Unreal(float[16], OSVR_ProjectionMatrix) - */ - public static native byte OSVR_Projection_to_Unreal(FloatBuffer Unreal_out, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ProjectionMatrix.ByValue projection_in); - /** - * Original signature : OSVR_ReturnCode osvrCreateRenderManagerOpenGL(OSVR_ClientContext, const char[], OSVR_GraphicsLibraryOpenGL, OSVR_RenderManager*, OSVR_RenderManagerOpenGL*)
                  - * @deprecated use the safer method - * {@link #osvrCreateRenderManagerOpenGL(com.jme3.system.osvr.osvrclientkit.OsvrClientKitLibrary.OSVR_ClientContext, byte[], com.jme3.system.osvr.osvrrendermanageropengl.OSVR_GraphicsLibraryOpenGL.ByValue, com.sun.jna.ptr.PointerByReference, com.sun.jna.ptr.PointerByReference)} - * instead - */ - @Deprecated - public static native byte osvrCreateRenderManagerOpenGL(Pointer clientContext, Pointer graphicsLibraryName, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_GraphicsLibraryOpenGL.ByValue graphicsLibrary, PointerByReference renderManagerOut, PointerByReference renderManagerOpenGLOut); - /** Original signature : OSVR_ReturnCode osvrCreateRenderManagerOpenGL(OSVR_ClientContext, const char[], OSVR_GraphicsLibraryOpenGL, OSVR_RenderManager*, OSVR_RenderManagerOpenGL*) */ - public static native byte osvrCreateRenderManagerOpenGL(OsvrClientKitLibrary.OSVR_ClientContext clientContext, byte graphicsLibraryName[], com.jme3.system.osvr.osvrrendermanageropengl.OSVR_GraphicsLibraryOpenGL.ByValue graphicsLibrary, PointerByReference renderManagerOut, PointerByReference renderManagerOpenGLOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerGetRenderInfoOpenGL(OSVR_RenderManagerOpenGL, OSVR_RenderInfoCount, OSVR_RenderParams, OSVR_RenderInfoOpenGL*) */ - public static native byte osvrRenderManagerGetRenderInfoOpenGL(Pointer renderManager, NativeSize renderInfoIndex, OSVR_RenderParams.ByValue renderParams, OSVR_RenderInfoOpenGL renderInfoOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerOpenDisplayOpenGL(OSVR_RenderManagerOpenGL, OSVR_OpenResultsOpenGL*) */ - public static native byte osvrRenderManagerOpenDisplayOpenGL(Pointer renderManager, OSVR_OpenResultsOpenGL openResultsOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerPresentRenderBufferOpenGL(OSVR_RenderManagerPresentState, OSVR_RenderBufferOpenGL, OSVR_RenderInfoOpenGL, OSVR_ViewportDescription) */ - public static native byte osvrRenderManagerPresentRenderBufferOpenGL(Pointer presentState, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderBufferOpenGL.ByValue buffer, OSVR_RenderInfoOpenGL.ByValue renderInfoUsed, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_ViewportDescription.ByValue normalizedCroppingViewport); - /** Original signature : OSVR_ReturnCode osvrRenderManagerRegisterRenderBufferOpenGL(OSVR_RenderManagerRegisterBufferState, OSVR_RenderBufferOpenGL) */ - public static native byte osvrRenderManagerRegisterRenderBufferOpenGL(Pointer registerBufferState, com.jme3.system.osvr.osvrrendermanageropengl.OSVR_RenderBufferOpenGL.ByValue renderBuffer); - /** - * Gets a given OSVR_RenderInfoOpenGL from an OSVR_RenderInfoCollection.
                  - * Original signature : OSVR_ReturnCode osvrRenderManagerGetRenderInfoFromCollectionOpenGL(OSVR_RenderInfoCollection, OSVR_RenderInfoCount, OSVR_RenderInfoOpenGL*) - */ - public static native byte osvrRenderManagerGetRenderInfoFromCollectionOpenGL(Pointer renderInfoCollection, NativeSize index, OSVR_RenderInfoOpenGL renderInfoOut); - /** - * Original signature : OSVR_ReturnCode osvrRenderManagerCreateColorBufferOpenGL(GLsizei, GLsizei, GLenum, GLuint*)
                  - * @deprecated use the safer methods {@link #osvrRenderManagerCreateColorBufferOpenGL(int, int, int, java.nio.IntBuffer)} and {@link #osvrRenderManagerCreateColorBufferOpenGL(int, int, int, com.sun.jna.ptr.IntByReference)} instead - */ - @Deprecated - public static native byte osvrRenderManagerCreateColorBufferOpenGL(int width, int height, int format, IntByReference colorBufferNameOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerCreateColorBufferOpenGL(GLsizei, GLsizei, GLenum, GLuint*) */ - public static native byte osvrRenderManagerCreateColorBufferOpenGL(int width, int height, int format, IntBuffer colorBufferNameOut); - /** - * Original signature : OSVR_ReturnCode osvrRenderManagerCreateDepthBufferOpenGL(GLsizei, GLsizei, GLuint*)
                  - * @deprecated use the safer methods {@link #osvrRenderManagerCreateDepthBufferOpenGL(int, int, java.nio.IntBuffer)} and {@link #osvrRenderManagerCreateDepthBufferOpenGL(int, int, com.sun.jna.ptr.IntByReference)} instead - */ - @Deprecated - public static native byte osvrRenderManagerCreateDepthBufferOpenGL(int width, int height, IntByReference depthBufferNameOut); - /** Original signature : OSVR_ReturnCode osvrRenderManagerCreateDepthBufferOpenGL(GLsizei, GLsizei, GLuint*) */ - public static native byte osvrRenderManagerCreateDepthBufferOpenGL(int width, int height, IntBuffer depthBufferNameOut); - public static class OSVR_ClientContext extends PointerType { - public OSVR_ClientContext(Pointer address) { - super(address); - } - public OSVR_ClientContext() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OSVR_TimeValue.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OSVR_TimeValue.java deleted file mode 100644 index db60f043d8..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OSVR_TimeValue.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.jme3.system.osvr.osvrtimevalue; -import com.sun.jna.Pointer; -import com.sun.jna.Structure; -import java.util.Arrays; -import java.util.List; -/** - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OSVR_TimeValue extends Structure { - /** C type : OSVR_TimeValue_Seconds */ - public long seconds; - /** C type : OSVR_TimeValue_Microseconds */ - public int microseconds; - public OSVR_TimeValue() { - super(); - } - protected List getFieldOrder() { - return Arrays.asList("seconds", "microseconds"); - } - /** - * @param seconds C type : OSVR_TimeValue_Seconds
                  - * @param microseconds C type : OSVR_TimeValue_Microseconds - */ - public OSVR_TimeValue(long seconds, int microseconds) { - super(); - this.seconds = seconds; - this.microseconds = microseconds; - } - public OSVR_TimeValue(Pointer peer) { - super(peer); - } - public static class ByReference extends OSVR_TimeValue implements Structure.ByReference { - - }; - public static class ByValue extends OSVR_TimeValue implements Structure.ByValue { - - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OsvrTimeValueLibrary.java b/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OsvrTimeValueLibrary.java deleted file mode 100644 index 9af6a39af2..0000000000 --- a/jme3-vr/src/main/java/com/jme3/system/osvr/osvrtimevalue/OsvrTimeValueLibrary.java +++ /dev/null @@ -1,119 +0,0 @@ -package com.jme3.system.osvr.osvrtimevalue; -import com.sun.jna.Library; -import com.sun.jna.Native; -import com.sun.jna.NativeLibrary; -import com.sun.jna.Pointer; -import com.sun.jna.PointerType; -/** - * JNA Wrapper for library osvrTimeValue
                  - * This file was autogenerated by JNAerator,
                  - * a tool written by Olivier Chafik that uses a few opensource projects..
                  - * For help, please visit NativeLibs4Java , Rococoa, or JNA. - */ -public class OsvrTimeValueLibrary implements Library { - public static final String JNA_LIBRARY_NAME = "osvrClientKit"; - public static final NativeLibrary JNA_NATIVE_LIB = NativeLibrary.getInstance(OsvrTimeValueLibrary.JNA_LIBRARY_NAME); - static { - Native.register(OsvrTimeValueLibrary.class, OsvrTimeValueLibrary.JNA_NATIVE_LIB); - } - public static final int OSVR_TRUE = (int)(1); - public static final int OSVR_FALSE = (int)(0); - /** - * Gets the current time in the TimeValue. Parallel to gettimeofday. - * Original signature : void osvrTimeValueGetNow(OSVR_TimeValue*) - */ - public static native void osvrTimeValueGetNow(OSVR_TimeValue dest); - /** - * Converts from a TimeValue struct to your system's struct timeval. - * @param dest Pointer to an empty struct timeval for your platform.
                  - * @param src A pointer to an OSVR_TimeValue you'd like to convert from.
                  - * If either parameter is NULL, the function will return without doing
                  - * anything.
                  - * Original signature : void osvrTimeValueToStructTimeval(timeval*, const OSVR_TimeValue*) - */ - public static native void osvrTimeValueToStructTimeval(OsvrTimeValueLibrary.timeval dest, OSVR_TimeValue src); - /** - * Converts from a TimeValue struct to your system's struct timeval. - * @param dest An OSVR_TimeValue destination pointer.
                  - * @param src Pointer to a struct timeval you'd like to convert from.
                  - * The result is normalized.
                  - * If either parameter is NULL, the function will return without doing
                  - * anything.
                  - * Original signature : void osvrStructTimevalToTimeValue(OSVR_TimeValue*, timeval*) - */ - public static native void osvrStructTimevalToTimeValue(OSVR_TimeValue dest, OsvrTimeValueLibrary.timeval src); - /** - * "Normalizes" a time value so that the absolute number of microseconds - * is less than 1,000,000, and that the sign of both components is the same.
                  - * @param tv Address of a struct TimeValue to normalize in place.
                  - * If the given pointer is NULL, this function returns without doing anything.
                  - * Original signature : void osvrTimeValueNormalize(OSVR_TimeValue*) - */ - public static native void osvrTimeValueNormalize(OSVR_TimeValue tv); - /** - * Sums two time values, replacing the first with the result. - * @param tvA Destination and first source.
                  - * @param tvB second source
                  - * If a given pointer is NULL, this function returns without doing anything.
                  - * Both parameters are expected to be in normalized form.
                  - * Original signature : void osvrTimeValueSum(OSVR_TimeValue*, const OSVR_TimeValue*) - */ - public static native void osvrTimeValueSum(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * Computes the difference between two time values, replacing the first - * with the result.
                  - * Effectively, `*tvA = *tvA - *tvB`
                  - * @param tvA Destination and first source.
                  - * @param tvB second source
                  - * If a given pointer is NULL, this function returns without doing anything.
                  - * Both parameters are expected to be in normalized form.
                  - * Original signature : void osvrTimeValueDifference(OSVR_TimeValue*, const OSVR_TimeValue*) - */ - public static native void osvrTimeValueDifference(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * Compares two time values (assumed to be normalized), returning - * the same values as strcmp
                  - * @return <0 if A is earlier than B, 0 if they are the same, and >0 if A
                  - * is later than B.
                  - * Original signature : int osvrTimeValueCmp(const OSVR_TimeValue*, const OSVR_TimeValue*) - */ - public static native int osvrTimeValueCmp(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * Compute the difference between the two time values, returning the - * duration as a double-precision floating-point number of seconds.
                  - * Effectively, `ret = *tvA - *tvB`
                  - * @param tvA first source.
                  - * @param tvB second source
                  - * @return Duration of timespan in seconds (floating-point)
                  - * Original signature : double osvrTimeValueDurationSeconds(const OSVR_TimeValue*, const OSVR_TimeValue*) - */ - public static native double osvrTimeValueDurationSeconds(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * True if A is later than B. - * Original signature : OSVR_CBool osvrTimeValueGreater(const OSVR_TimeValue*, const OSVR_TimeValue*) - */ - public static native byte osvrTimeValueGreater(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * Returns true if the time value is normalized. Typically used in assertions.
                  - * Original signature : bool osvrTimeValueIsNormalized(const OSVR_TimeValue&) - */ - public static native byte osvrTimeValueIsNormalized(OSVR_TimeValue tv); - /** - * Operator > overload for time values
                  - * Original signature : bool operator>(const OSVR_TimeValue&, const OSVR_TimeValue&) - */ - public static native byte operatorGreater(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - /** - * Operator == overload for time values
                  - * Original signature : bool operator==(const OSVR_TimeValue&, const OSVR_TimeValue&) - */ - public static native byte operatorIsEqual(OSVR_TimeValue tvA, OSVR_TimeValue tvB); - public static class timeval extends PointerType { - public timeval(Pointer address) { - super(address); - } - public timeval() { - super(); - } - }; -} diff --git a/jme3-vr/src/main/java/com/jme3/util/VRGUIPositioningMode.java b/jme3-vr/src/main/java/com/jme3/util/VRGUIPositioningMode.java deleted file mode 100644 index 570a9be683..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRGUIPositioningMode.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.jme3.util; - -/** - * A enumeration that describes the GUI display positioning modes. - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public enum VRGUIPositioningMode { - MANUAL, - AUTO_CAM_ALL, - AUTO_CAM_ALL_SKIP_PITCH, - AUTO_OBSERVER_POS_CAM_ROTATION, - AUTO_OBSERVER_ALL, - AUTO_OBSERVER_ALL_CAMHEIGHT -} diff --git a/jme3-vr/src/main/java/com/jme3/util/VRGuiManager.java b/jme3-vr/src/main/java/com/jme3/util/VRGuiManager.java deleted file mode 100644 index a5a4d89e8e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRGuiManager.java +++ /dev/null @@ -1,482 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.util; - -import com.jme3.app.VREnvironment; -import com.jme3.material.Material; -import com.jme3.material.RenderState.BlendMode; -import com.jme3.math.ColorRGBA; -import com.jme3.math.Matrix3f; -import com.jme3.math.Quaternion; -import com.jme3.math.Vector2f; -import com.jme3.math.Vector3f; -import com.jme3.renderer.Camera; -import com.jme3.renderer.ViewPort; -import com.jme3.renderer.queue.RenderQueue.Bucket; -import com.jme3.scene.Spatial; -import com.jme3.scene.CenterQuad; -import com.jme3.scene.Geometry; -import com.jme3.scene.Node; -import com.jme3.system.AppSettings; -import com.jme3.texture.FrameBuffer; -import com.jme3.texture.Image.Format; -import com.jme3.texture.Texture; -import com.jme3.texture.Texture2D; -import java.awt.GraphicsEnvironment; -import java.util.Iterator; - -/** - * A class dedicated to the management and the display of a Graphical User Interface (GUI) within a VR environment. - * @author reden - phr00t - https://github.com/phr00t - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class VRGuiManager { - - private Camera camLeft, camRight; - private float guiDistance = 1.5f; - private float guiScale = 1f; - private float guiPositioningElastic; - - private VRGUIPositioningMode posMode = VRGUIPositioningMode.AUTO_CAM_ALL; - - private final Matrix3f orient = new Matrix3f(); - private Vector2f screenSize; - protected boolean wantsReposition; - - private Vector2f ratio; - - private final Vector3f EoldPos = new Vector3f(); - - private final Quaternion EoldDir = new Quaternion(); - - private final Vector3f look = new Vector3f(); - private final Vector3f left = new Vector3f(); - private final Vector3f temppos = new Vector3f(); - private final Vector3f up = new Vector3f(); - - private boolean useCurvedSurface = false; - private boolean overdraw = false; - private Geometry guiQuad; - private Node guiQuadNode; - private ViewPort offView; - private Texture2D guiTexture; - - private final Quaternion tempq = new Quaternion(); - - private VREnvironment environment = null; - - /** - * Create a new GUI manager attached to the given app state. - * @param environment the VR environment to which this manager is attached to. - */ - public VRGuiManager(VREnvironment environment){ - this.environment = environment; - } - - public boolean isWantsReposition() { - return wantsReposition; - } - - public void setWantsReposition(boolean wantsReposition) { - this.wantsReposition = wantsReposition; - } - - /** - * - * Makes auto GUI positioning happen not immediately, but like an - * elastic connected to the headset. Setting to 0 disables (default) - * Higher settings make it track the headset quicker. - * - * @param elastic amount of elasticity - */ - public void setPositioningElasticity(float elastic) { - guiPositioningElastic = elastic; - } - - public float getPositioningElasticity() { - return guiPositioningElastic; - } - - /** - * Get the GUI {@link VRGUIPositioningMode positioning mode}. - * @return the GUI {@link VRGUIPositioningMode positioning mode}. - * @see #setPositioningMode(VRGUIPositioningMode) - */ - public VRGUIPositioningMode getPositioningMode() { - return posMode; - } - - /** - * Set the GUI {@link VRGUIPositioningMode positioning mode}. - * @param mode the GUI {@link VRGUIPositioningMode positioning mode}. - * @see #getPositioningMode() - */ - public void setPositioningMode(VRGUIPositioningMode mode) { - posMode = mode; - } - - /** - * Get the GUI canvas size. This method return the size in pixels of the GUI available area within the VR view. - * @return the GUI canvas size. This method return the size in pixels of the GUI available area within the VR view. - */ - public Vector2f getCanvasSize() { - - if (environment != null){ - - if (environment.getApplication() != null){ - if( screenSize == null ) { - if( environment.isInVR() && environment.getVRHardware() != null ) { - screenSize = new Vector2f(); - environment.getVRHardware().getRenderSize(screenSize); - screenSize.multLocal(environment.getVRViewManager().getResolutionMuliplier()); - } else { - AppSettings as = environment.getApplication().getContext().getSettings(); - screenSize = new Vector2f(as.getWidth(), as.getHeight()); - } - } - return screenSize; - } else { - throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application."); - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - - } - - /** - * Get the ratio between the {@link #getCanvasSize() GUI canvas size} and the application main windows (if available) or the screen size. - * @return the ratio between the {@link #getCanvasSize() GUI canvas size} and the application main windows (if available). - * @see #getCanvasSize() - */ - public Vector2f getCanvasToWindowRatio() { - - if (environment != null){ - - if (environment.getApplication() != null){ - if( ratio == null ) { - ratio = new Vector2f(); - Vector2f canvas = getCanvasSize(); - int width = Integer.min(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getWidth(), - environment.getApplication().getContext().getSettings().getWidth()); - int height = Integer.min(GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDisplayMode().getHeight(), - environment.getApplication().getContext().getSettings().getHeight()); - ratio.x = Float.max(1f, canvas.x / width); - ratio.y = Float.max(1f, canvas.y / height); - } - return ratio; - - } else { - throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application."); - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - } - - /** - * Inform this manager that it has to position the GUI. - */ - public void positionGui() { - wantsReposition = true; - } - - /** - * Position the GUI to the given location. - * @param pos the position of the GUI. - * @param dir the rotation of the GUI. - * @param tpf the time per frame. - */ - private void positionTo(Vector3f pos, Quaternion dir, float tpf) { - - if (environment != null){ - Vector3f guiPos = guiQuadNode.getLocalTranslation(); - guiPos.set(0f, 0f, guiDistance); - dir.mult(guiPos, guiPos); - guiPos.x += pos.x; - guiPos.y += pos.y + environment.getVRHeightAdjustment(); - guiPos.z += pos.z; - if( guiPositioningElastic > 0f && posMode != VRGUIPositioningMode.MANUAL ) { - // mix pos & dir with current pos & dir - guiPos.interpolateLocal(EoldPos, guiPos, Float.min(1f, tpf * guiPositioningElastic)); - EoldPos.set(guiPos); - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - } - - /** - * Update the GUI geometric state. This method should be called after GUI modification. - */ - public void updateGuiQuadGeometricState() { - guiQuadNode.updateGeometricState(); - } - - /** - * Position the GUI without delay. - * @param tpf the time per frame. - */ - public void positionGuiNow(float tpf) { - - if (environment != null){ - wantsReposition = false; - if( environment.isInVR() == false ){ - return; - } - - guiQuadNode.setLocalScale(guiDistance * guiScale * 4f, 4f * guiDistance * guiScale, 1f); - - switch( posMode ) { - case MANUAL: - case AUTO_CAM_ALL_SKIP_PITCH: - case AUTO_CAM_ALL: - if( camLeft != null && camRight != null ) { - // get middle point - temppos.set(camLeft.getLocation()).interpolateLocal(camRight.getLocation(), 0.5f); - positionTo(temppos, camLeft.getRotation(), tpf); - } - rotateScreenTo(camLeft.getRotation(), tpf); - - break; - case AUTO_OBSERVER_POS_CAM_ROTATION: - Object obs = environment.getObserver(); - if( obs != null ) { - if( obs instanceof Camera ) { - positionTo(((Camera)obs).getLocation(), camLeft.getRotation(), tpf); - } else { - positionTo(((Spatial)obs).getWorldTranslation(), camLeft.getRotation(), tpf); - } - } - rotateScreenTo(camLeft.getRotation(), tpf); - - break; - case AUTO_OBSERVER_ALL: - case AUTO_OBSERVER_ALL_CAMHEIGHT: - obs = environment.getObserver(); - if( obs != null ) { - Quaternion q; - if( obs instanceof Camera ) { - q = ((Camera)obs).getRotation(); - temppos.set(((Camera)obs).getLocation()); - } else { - q = ((Spatial)obs).getWorldRotation(); - temppos.set(((Spatial)obs).getWorldTranslation()); - } - if( posMode == VRGUIPositioningMode.AUTO_OBSERVER_ALL_CAMHEIGHT ) { - temppos.y = camLeft.getLocation().y; - } - positionTo(temppos, q, tpf); - rotateScreenTo(q, tpf); - - } - break; - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - } - - /** - * Rotate the GUI to the given direction. - * @param dir the direction to rotate to. - * @param tpf the time per frame. - */ - private void rotateScreenTo(Quaternion dir, float tpf) { - dir.getRotationColumn(2, look).negateLocal(); - dir.getRotationColumn(0, left).negateLocal(); - orient.fromAxes(left, dir.getRotationColumn(1, up), look); - Quaternion rot = tempq.fromRotationMatrix(orient); - if( posMode == VRGUIPositioningMode.AUTO_CAM_ALL_SKIP_PITCH ){ - VRUtil.stripToYaw(rot); - } - - if( guiPositioningElastic > 0f && posMode != VRGUIPositioningMode.MANUAL ) { - // mix pos & dir with current pos & dir - EoldDir.nlerp(rot, tpf * guiPositioningElastic); - guiQuadNode.setLocalRotation(EoldDir); - } else { - guiQuadNode.setLocalRotation(rot); - } - } - - /** - * Get the GUI distance from the observer. - * @return the GUI distance from the observer. - * @see #setGuiDistance(float) - */ - public float getGuiDistance() { - return guiDistance; - } - - /** - * Set the GUI distance from the observer. - * @param newGuiDistance the GUI distance from the observer. - * @see #getGuiDistance() - */ - public void setGuiDistance(float newGuiDistance) { - guiDistance = newGuiDistance; - } - - /** - * Get the GUI scale. - * @return the GUI scale. - * @see #setGuiScale(float) - */ - public float getGUIScale(){ - return guiScale; - } - - /** - * Set the GUI scale. - * @param scale the GUI scale. - * @see #getGUIScale() - */ - public void setGuiScale(float scale) { - guiScale = scale; - } - - /** - * Adjust the GUI distance from the observer. - * This method increment / decrement the {@link #getGuiDistance() GUI distance} by the given value. - * @param adjustAmount the increment (if positive) / decrement (if negative) value of the GUI distance. - */ - public void adjustGuiDistance(float adjustAmount) { - guiDistance += adjustAmount; - } - - /** - * Set up the GUI. - * @param leftcam the left eye camera. - * @param rightcam the right eye camera. - * @param left the left eye viewport. - * @param right the right eye viewport. - */ - public void setupGui(Camera leftcam, Camera rightcam, ViewPort left, ViewPort right) { - - if (environment != null){ - if( environment.hasTraditionalGUIOverlay() ) { - camLeft = leftcam; - camRight = rightcam; - Spatial guiScene = getGuiQuad(camLeft); - left.attachScene(guiScene); - if( right != null ) right.attachScene(guiScene); - setPositioningMode(posMode); - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - } - - /** - * Get if the GUI has to use curved surface. - * @return true if the GUI has to use curved surface and false otherwise. - * @see #setCurvedSurface(boolean) - */ - public boolean isCurverSurface(){ - return useCurvedSurface; - } - - /** - * Set if the GUI has to use curved surface. - * @param set true if the GUI has to use curved surface and false otherwise. - * @see #isCurverSurface() - */ - public void setCurvedSurface(boolean set) { - useCurvedSurface = set; - } - - /** - * Get if the GUI has to be displayed even if it is behind objects. - * @return true if the GUI has to use curved surface and false otherwise. - * @see #setGuiOverdraw(boolean) - */ - public boolean isGuiOverdraw(){ - return overdraw; - } - - /** - * Set if the GUI has to be displayed even if it is behind objects. - * @param set true if the GUI has to use curved surface and false otherwise. - * @see #isGuiOverdraw() - */ - public void setGuiOverdraw(boolean set) { - overdraw = set; - } - - /** - * Create a GUI quad for the given camera. - * @param sourceCam the camera - * @return a GUI quad for the given camera. - */ - private Spatial getGuiQuad(Camera sourceCam){ - - if (environment != null){ - - if (environment.getApplication() != null){ - if( guiQuadNode == null ) { - Vector2f guiCanvasSize = getCanvasSize(); - Camera offCamera = sourceCam.clone(); - offCamera.setParallelProjection(true); - offCamera.setLocation(Vector3f.ZERO); - offCamera.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y); - - offView = environment.getApplication().getRenderManager().createPreView("GUI View", offCamera); - offView.setClearFlags(true, true, true); - offView.setBackgroundColor(ColorRGBA.BlackNoAlpha); - - // create offscreen framebuffer - FrameBuffer offBuffer = new FrameBuffer((int)guiCanvasSize.x, (int)guiCanvasSize.y, 1); - - //setup framebuffer's texture - guiTexture = new Texture2D((int)guiCanvasSize.x, (int)guiCanvasSize.y, Format.RGBA8); - guiTexture.setMinFilter(Texture.MinFilter.BilinearNoMipMaps); - guiTexture.setMagFilter(Texture.MagFilter.Bilinear); - - //setup framebuffer to use texture - offBuffer.setDepthBuffer(Format.Depth); - offBuffer.setColorTexture(guiTexture); - - //set viewport to render to offscreen framebuffer - offView.setOutputFrameBuffer(offBuffer); - - // setup framebuffer's scene - Iterator spatialIter = environment.getApplication().getGuiViewPort().getScenes().iterator(); - while(spatialIter.hasNext()){ - offView.attachScene(spatialIter.next()); - } - - - if( useCurvedSurface ) { - guiQuad = (Geometry)environment.getApplication().getAssetManager().loadModel("Common/Util/gui_mesh.j3o"); - } else { - guiQuad = new Geometry("guiQuad", new CenterQuad(1f, 1f)); - } - - Material mat = new Material(environment.getApplication().getAssetManager(), "Common/MatDefs/VR/GuiOverlay.j3md"); - mat.getAdditionalRenderState().setDepthTest(!overdraw); - mat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha); - mat.getAdditionalRenderState().setDepthWrite(false); - mat.setTexture("ColorMap", guiTexture); - guiQuad.setQueueBucket(Bucket.Translucent); - guiQuad.setMaterial(mat); - - guiQuadNode = new Node("gui-quad-node"); - guiQuadNode.setQueueBucket(Bucket.Translucent); - guiQuadNode.attachChild(guiQuad); - } - return guiQuadNode; - } else { - throw new IllegalStateException("VR GUI manager underlying environment is not attached to any application."); - } - } else { - throw new IllegalStateException("VR GUI manager is not attached to any environment."); - } - - - - } -} diff --git a/jme3-vr/src/main/java/com/jme3/util/VRUtil.java b/jme3-vr/src/main/java/com/jme3/util/VRUtil.java deleted file mode 100644 index abc8d3ca9e..0000000000 --- a/jme3-vr/src/main/java/com/jme3/util/VRUtil.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -package com.jme3.util; - -import com.jme3.math.FastMath; -import com.jme3.math.Matrix4f; -import com.jme3.math.Quaternion; - - -import java.util.concurrent.TimeUnit; - -/** - * - * @author Julien Seinturier - COMEX SA - http://www.seinturier.fr - * - */ -public class VRUtil { - - private static final long SLEEP_PRECISION = TimeUnit.MILLISECONDS.toNanos(4); - private static final long SPIN_YIELD_PRECISION = TimeUnit.MILLISECONDS.toNanos(2); - - public static void sleepNanos(long nanoDuration) { - final long end = System.nanoTime() + nanoDuration; - long timeLeft = nanoDuration; - do { - try { - if (timeLeft > SLEEP_PRECISION) { - Thread.sleep(1); - } else if (timeLeft > SPIN_YIELD_PRECISION) { - Thread.sleep(0); - } - } catch(Exception e) { } - timeLeft = end - System.nanoTime(); - } while (timeLeft > 0); - } - - - - - public static void convertMatrix4toQuat(Matrix4f in, Quaternion out) { - // convert rotation matrix to quat - out.fromRotationMatrix(in.m00, in.m01, in.m02, in.m10, in.m11, in.m12, in.m20, in.m21, in.m22); - // flip the pitch - out.set(-out.getX(), out.getY(), -out.getZ(), out.getW()); - } - - public static Quaternion FastFullAngles(Quaternion use, float yaw, float pitch, float roll) { - float angle; - float sinRoll, sinPitch, sinYaw, cosRoll, cosPitch, cosYaw; - angle = roll * 0.5f; - sinPitch = (float)Math.sin(angle); - cosPitch = (float)Math.cos(angle); - angle = yaw * 0.5f; - sinRoll = (float)Math.sin(angle); - cosRoll = (float)Math.cos(angle); - angle = pitch * 0.5f; - sinYaw = (float)Math.sin(angle); - cosYaw = (float)Math.cos(angle); - - // variables used to reduce multiplication calls. - float cosRollXcosPitch = cosRoll * cosPitch; - float sinRollXsinPitch = sinRoll * sinPitch; - float cosRollXsinPitch = cosRoll * sinPitch; - float sinRollXcosPitch = sinRoll * cosPitch; - - use.set((cosRollXcosPitch * sinYaw + sinRollXsinPitch * cosYaw), - (sinRollXcosPitch * cosYaw + cosRollXsinPitch * sinYaw), - (cosRollXsinPitch * cosYaw - sinRollXcosPitch * sinYaw), - (cosRollXcosPitch * cosYaw - sinRollXsinPitch * sinYaw)); - - return use; - } - - public static Quaternion stripToYaw(Quaternion q) { - float yaw; - float w = q.getW(); - float x = q.getX(); - float y = q.getY(); - float z = q.getZ(); - float sqx = x*x; - float sqy = y*y; - float sqz = z*z; - float sqw = w*w; - float unit = sqx + sqy + sqz + sqw; // if normalized is one, otherwise - // is correction factor - float test = x * y + z * w; - if (test > 0.499 * unit) { // singularity at north pole - yaw = 2 * FastMath.atan2(x, w); - } else if (test < -0.499 * unit) { // singularity at south pole - yaw = -2 * FastMath.atan2(x, w); - } else { - yaw = FastMath.atan2(2 * y * w - 2 * x * z, sqx - sqy - sqz + sqw); // roll or heading - } - FastFullAngles(q, yaw, 0f, 0f); - return q; - } -} diff --git a/jme3-vr/src/main/java/test/TestInitHmd.java b/jme3-vr/src/main/java/test/TestInitHmd.java deleted file mode 100644 index a15ea732d1..0000000000 --- a/jme3-vr/src/main/java/test/TestInitHmd.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (c) 2009-2018 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 test; - -import com.jme3.app.VREnvironment; -import com.jme3.input.vr.openvr.OpenVR; -import com.jme3.system.AppSettings; - -/** - * Testing OpenVR environment. - * @author Rickard (neph1 @ github) - */ -public class TestInitHmd { - - public static void main(String... args){ - testInitHmd(); - } - - public static void testInitHmd(){ - VREnvironment environment = new VREnvironment(new AppSettings(true)); - environment.initialize(); - OpenVR openVr = (OpenVR) environment.getVRHardware(); - System.out.println(openVr.getName()); - - openVr.updatePose(); - - openVr.destroy(); - - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.frag deleted file mode 100644 index 5d121019fc..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.frag +++ /dev/null @@ -1,109 +0,0 @@ -//#define FRAGMENT_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -uniform vec2 g_ResolutionInverse; -uniform vec2 m_FrustumNearFar; -uniform sampler2D m_Texture; -uniform sampler2D m_Normals; -uniform sampler2D m_DepthTexture; -uniform vec3 m_FrustumCorner; -uniform float m_Distance; - -varying vec2 texCoord; - -#define m_Scale 3.15 -#define m_Bias 0.025 -#define m_SampleRadius 200.0 - -vec4 fetchNormalDepth(vec2 tc){ - vec4 nd; - nd.xyz = texture2D(m_Normals, tc).rgb; - nd.w = 150.0 * texture2D(m_DepthTexture, tc).r; - return nd; -} - -vec3 getPosition(in vec2 uv){ - float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - texture2D(m_DepthTexture,uv).r * (m_FrustumNearFar.y-m_FrustumNearFar.x)); -#ifdef INSTANCING - float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, (uv.x - (uv.x > 0.5 ? 0.5 : 0.0)) * 2.0); -#else - float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, uv.x); -#endif - float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, uv.y); - return depth* vec3(x, y, m_FrustumCorner.z); -} - -vec3 getPosition(in vec2 uv, in float indepth){ - float depth= (2.0 * m_FrustumNearFar.x) / (m_FrustumNearFar.y + m_FrustumNearFar.x - indepth * (m_FrustumNearFar.y-m_FrustumNearFar.x)); -#ifdef INSTANCING - float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, (uv.x - (uv.x > 0.5 ? 0.5 : 0.0)) * 2.0); -#else - float x = mix(-m_FrustumCorner.x, m_FrustumCorner.x, uv.x); -#endif - float y = mix(-m_FrustumCorner.y, m_FrustumCorner.y, uv.y); - return depth* vec3(x, y, m_FrustumCorner.z); -} - -float doAmbientOcclusion(in vec2 tc, in vec3 pos, in vec3 norm){ - vec3 diff = getPosition(tc)- pos; - float d = length(diff) * m_Scale; - vec3 v = normalize(diff); - return step(0.00002,d)*max(0.0, dot(norm, v) - m_Bias) * ( 1.0/(1.0 + d) ) * smoothstep(0.00002,0.0027,d); -} - -void main(){ - float result; - - float firstdepth = texture2D(m_DepthTexture,texCoord).r; - vec4 color = texture2D(m_Texture, texCoord); - - if( firstdepth == 1.0 ) { - gl_FragColor = color; - return; - } - - vec3 position = getPosition(texCoord, firstdepth); - vec3 normal = texture2D(m_Normals, texCoord).xyz * 2.0 - 1.0; - - vec2 rad = m_SampleRadius * g_ResolutionInverse / max(16.0, position.z); - - float ao = doAmbientOcclusion(texCoord + vec2( rad.x, rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2(-rad.x, rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2( rad.x, -rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2(-rad.x, -rad.y), position, normal); - - ao += doAmbientOcclusion(texCoord + vec2(-rad.x, 0.0), position, normal); - ao += doAmbientOcclusion(texCoord + vec2( rad.x, 0.0), position, normal); - ao += doAmbientOcclusion(texCoord + vec2(0.0, -rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2(0.0, rad.y), position, normal); - - rad *= 0.7; - - ao += doAmbientOcclusion(texCoord + vec2(-rad.x, -rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2( rad.x, -rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2(-rad.x, rad.y), position, normal); - ao += doAmbientOcclusion(texCoord + vec2( rad.x, rad.y), position, normal); - - result = 1.0 - clamp(ao * 0.4 - position.z * m_Distance * 2.5, 0.0, 0.6); - -#ifndef NO_OUTLINE - // ok, done with ambient occlusion, do cartoon edge - - vec2 mv = 0.5 * g_ResolutionInverse; - - vec4 n1 = fetchNormalDepth(texCoord + vec2(-mv.x, -mv.y)); - vec4 n2 = fetchNormalDepth(texCoord + vec2( mv.x, mv.y)); - vec4 n3 = fetchNormalDepth(texCoord + vec2(-mv.x, mv.y)); - vec4 n4 = fetchNormalDepth(texCoord + vec2( mv.x, -mv.y)); - - // Work out how much the normal and depth values are changing. - vec4 diagonalDelta = abs(n1 - n2) + abs(n3 - n4); - - float normalDelta = dot(diagonalDelta.xyz, vec3(1.0)); - float totalDelta = (diagonalDelta.w + normalDelta * 0.4) - position.z * m_Distance; - - gl_FragColor = color * vec4(result, result, result, 1.0) * (1.0 - clamp(totalDelta, 0.0, 1.0)); -#else - gl_FragColor = color * vec4(result, result, result, 1.0); -#endif -} diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.j3md b/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.j3md deleted file mode 100644 index 5faaf9260b..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/CartoonSSAO.j3md +++ /dev/null @@ -1,47 +0,0 @@ -MaterialDef CartoonSSAO { - - MaterialParameters { - Int NumSamples - Int NumSamplesDepth - Texture2D DepthTexture - Texture2D Texture - Texture2D Normals - Vector3 FrustumCorner - Float Distance - Boolean disableOutline - Boolean useInstancing - Vector2 FrustumNearFar - } - - Technique { - VertexShader GLSL150: Common/MatDefs/Post/Post15.vert - FragmentShader GLSL150: Common/MatDefs/VR/CartoonSSAO.frag - - WorldParameters { - WorldViewProjectionMatrix - WorldViewMatrix - ResolutionInverse - } - - Defines { - NO_OUTLINE : disableOutline - INSTANCING : useInstancing - } - } - - Technique { - VertexShader GLSL100: Common/MatDefs/Post/Post.vert - FragmentShader GLSL100: Common/MatDefs/VR/CartoonSSAO.frag - - WorldParameters { - WorldViewProjectionMatrix - WorldViewMatrix - ResolutionInverse - } - - Defines { - NO_OUTLINE : disableOutline - INSTANCING : useInstancing - } - } -} diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.frag deleted file mode 100644 index 555ef3eb49..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.frag +++ /dev/null @@ -1,10 +0,0 @@ -#import "Common/ShaderLib/GLSLCompat.glsllib" - -uniform sampler2D m_ColorMap; - -varying vec2 texCoord1; - -void main(){ - gl_FragColor = texture2D(m_ColorMap, texCoord1); - gl_FragColor.a *= 12.0 / (1.0 + gl_FragColor.a * 11.0 ); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.j3md b/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.j3md deleted file mode 100644 index 79b7759237..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.j3md +++ /dev/null @@ -1,38 +0,0 @@ -MaterialDef GuiOverlay { - - MaterialParameters { - Texture2D ColorMap - - // For VR instancing - Matrix4 RightEyeViewProjectionMatrix - } - - Technique { - VertexShader GLSL150: Common/MatDefs/VR/GuiOverlay15.vert - FragmentShader GLSL150: Common/MatDefs/VR/GuiOverlay15.frag - - WorldParameters { - WorldViewProjectionMatrix - ViewProjectionMatrix // needed for VR instancing - } - - Defines { - INSTANCING : RightEyeViewProjectionMatrix // For VR instancing - } - } - - Technique { - VertexShader GLSL100: Common/MatDefs/VR/GuiOverlay.vert - FragmentShader GLSL100: Common/MatDefs/VR/GuiOverlay.frag - - WorldParameters { - WorldViewProjectionMatrix - ViewProjectionMatrix // needed for VR instancing - } - - Defines { - INSTANCING : RightEyeViewProjectionMatrix // For VR instancing - } - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.vert deleted file mode 100644 index 8b05071961..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay.vert +++ /dev/null @@ -1,12 +0,0 @@ -// import the following for VR instancing -#import "Common/ShaderLib/InstanceVR.glsllib" - -attribute vec3 inPosition; -attribute vec2 inTexCoord; -varying vec2 texCoord1; - -void main(){ - texCoord1 = inTexCoord; - vec4 modelSpacePos = vec4(inPosition, 1.0); - gl_Position = TransformWorldViewProjectionVR(modelSpacePos); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.frag deleted file mode 100644 index efaca8436c..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.frag +++ /dev/null @@ -1,13 +0,0 @@ -//#define FRAGMENT_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -uniform sampler2D m_ColorMap; - -in vec2 texCoord1; - -out vec4 outColor; - -void main(){ - outColor = texture2D(m_ColorMap, texCoord1); - outColor.a *= 12.0 / (1.0 + outColor.a * 11.0 ); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.vert deleted file mode 100644 index cb505d5cfe..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/GuiOverlay15.vert +++ /dev/null @@ -1,14 +0,0 @@ -// import the following for VR instancing -//#define VERTEX_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" -#import "Common/ShaderLib/InstanceVR.glsllib" - -in vec3 inPosition; -in vec2 inTexCoord; -out vec2 texCoord1; - -void main(){ - texCoord1 = inTexCoord; - vec4 modelSpacePos = vec4(inPosition, 1.0); - gl_Position = TransformWorldViewProjectionVR(modelSpacePos); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.frag deleted file mode 100644 index 03a81aa322..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.frag +++ /dev/null @@ -1,21 +0,0 @@ -uniform sampler2D m_Texture; - -varying vec2 UVred; -varying vec2 UVgreen; -varying vec2 UVblue; - -void main() { - // performance & FOV experiment by removing bounds check - //float fBoundsCheck = ( (dot( vec2( lessThan( UVgreen.xy, vec2(0.05, 0.05)) ), vec2(1.0, 1.0))+dot( vec2( greaterThan( UVgreen.xy, vec2( 0.95, 0.95)) ), vec2(1.0, 1.0))) ); - // - //if( fBoundsCheck > 1.0 ) { - // gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - //} else { - - float red = texture2D(m_Texture, UVred).x; - float green = texture2D(m_Texture, UVgreen).y; - float blue = texture2D(m_Texture, UVblue).z; - gl_FragColor = vec4( red, green, blue, 1.0 ); - - //} -} diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.j3md b/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.j3md deleted file mode 100644 index 7cadd0af08..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.j3md +++ /dev/null @@ -1,33 +0,0 @@ -MaterialDef OpenVR { - MaterialParameters { - Int NumSamples - Texture2D Texture - - Vector2 inUVred - Vector2 inUVblue - Vector2 inUVgreen - } - - Technique { - VertexShader GLSL150: Common/MatDefs/VR/OpenVR15.vert - FragmentShader GLSL150: Common/MatDefs/VR/OpenVR15.frag - - WorldParameters { - } - - Defines { - } - - } - - Technique { - VertexShader GLSL100: Common/MatDefs/VR/OpenVR.vert - FragmentShader GLSL100: Common/MatDefs/VR/OpenVR.frag - - WorldParameters { - } - - Defines { - } - } -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.vert deleted file mode 100644 index 080df41a1a..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR.vert +++ /dev/null @@ -1,16 +0,0 @@ -attribute vec4 inPosition; - -attribute vec2 inTexCoord; // m_inUVred -attribute vec2 inTexCoord2; // m_inUVgreen -attribute vec2 inTexCoord3; // m_inUVblue - -varying vec2 UVred; -varying vec2 UVgreen; -varying vec2 UVblue; - -void main() { - gl_Position = inPosition; - UVred = inTexCoord; - UVgreen = inTexCoord2; - UVblue = inTexCoord3; -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.frag deleted file mode 100644 index f5d6e7da01..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.frag +++ /dev/null @@ -1,23 +0,0 @@ -uniform sampler2D m_Texture; - -in vec2 UVred; -in vec2 UVgreen; -in vec2 UVblue; - -out vec4 outColor; - -void main() { - // performance & FOV improvement by removing bounds check - //float fBoundsCheck = ( (dot( vec2( lessThan( UVgreen.xy, vec2(0.05, 0.05)) ), vec2(1.0, 1.0))+dot( vec2( greaterThan( UVgreen.xy, vec2( 0.95, 0.95)) ), vec2(1.0, 1.0))) ); - // - //if( fBoundsCheck > 1.0 ) { - // gl_FragColor = vec4( 0.0, 0.0, 0.0, 1.0 ); - //} else { - - float red = texture2D(m_Texture, UVred).x; - float green = texture2D(m_Texture, UVgreen).y; - float blue = texture2D(m_Texture, UVblue).z; - outColor = vec4( red, green, blue, 1.0 ); - - //} -} diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.vert deleted file mode 100644 index b8627c0320..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/OpenVR15.vert +++ /dev/null @@ -1,16 +0,0 @@ -in vec4 inPosition; - -in vec2 inTexCoord; // m_inUVred -in vec2 inTexCoord2; // m_inUVgreen -in vec2 inTexCoord3; // m_inUVblue - -out vec2 UVred; -out vec2 UVgreen; -out vec2 UVblue; - -void main() { - gl_Position = inPosition; - UVred = inTexCoord; - UVgreen = inTexCoord2; - UVblue = inTexCoord3; -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.frag deleted file mode 100644 index 09b4c9fb9c..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.frag +++ /dev/null @@ -1,183 +0,0 @@ -#import "Common/ShaderLib/MultiSample.glsllib" -#import "Common/ShaderLib/Shadows.glsllib" - - -uniform COLORTEXTURE m_Texture; -uniform DEPTHTEXTURE m_DepthTexture; -uniform mat4 m_ViewProjectionMatrixInverse; -uniform vec4 m_ViewProjectionMatrixRow2; - -#ifdef INSTANCING - uniform mat4 m_ViewProjectionMatrixInverseRight; - uniform vec4 m_ViewProjectionMatrixRow2Right; -#endif - -in vec2 texCoord; -out vec4 outFragColor; - -const mat4 biasMat = mat4(0.5, 0.0, 0.0, 0.0, - 0.0, 0.5, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.0, - 0.5, 0.5, 0.5, 1.0); - -uniform mat4 m_LightViewProjectionMatrix0; -uniform mat4 m_LightViewProjectionMatrix1; -uniform mat4 m_LightViewProjectionMatrix2; -uniform mat4 m_LightViewProjectionMatrix3; - -uniform vec2 g_ResolutionInverse; - -#ifdef POINTLIGHT - uniform vec3 m_LightPos; - uniform mat4 m_LightViewProjectionMatrix4; - uniform mat4 m_LightViewProjectionMatrix5; -#else - uniform vec3 m_LightDir; - #ifndef PSSM - uniform vec3 m_LightPos; - #endif -#endif - -#ifdef FADE -uniform vec2 m_FadeInfo; -#endif - -vec3 getPosition(in float depth, in vec2 uv){ - #ifdef INSTANCING - vec4 pos; - mat4 usemat; - uv.x *= 2.0; - if( uv.x > 1.0 ) { - // right eye - uv.x -= 1.0; - usemat = m_ViewProjectionMatrixInverseRight; - } else { - // left eye - usemat = m_ViewProjectionMatrixInverse; - } - pos = vec4(uv, depth, 1.0) * 2.0 - 1.0; - pos = usemat * pos; - #else - vec4 pos = vec4(uv, depth, 1.0) * 2.0 - 1.0; - pos = m_ViewProjectionMatrixInverse * pos; - #endif - return pos.xyz / pos.w; -} - -#ifndef BACKFACE_SHADOWS - vec3 approximateNormal(in float depth,in vec4 worldPos,in vec2 texCoord, in int numSample){ - float step = g_ResolutionInverse.x ; - float stepy = g_ResolutionInverse.y ; - float depth1 = fetchTextureSample(m_DepthTexture,texCoord + vec2(-step,stepy),numSample).r; - float depth2 = fetchTextureSample(m_DepthTexture,texCoord + vec2(step,stepy),numSample).r; - vec3 v1, v2; - vec4 worldPos1 = vec4(getPosition(depth1,texCoord + vec2(-step,stepy)),1.0); - vec4 worldPos2 = vec4(getPosition(depth2,texCoord + vec2(step,stepy)),1.0); - - v1 = normalize((worldPos1 - worldPos)).xyz; - v2 = normalize((worldPos2 - worldPos)).xyz; - return normalize(cross(v2, v1)); - - } -#endif - -vec4 main_multiSample(in int numSample){ - float depth = fetchTextureSample(m_DepthTexture,texCoord,numSample).r;//getDepth(m_DepthTexture,texCoord).r; - vec4 color = fetchTextureSample(m_Texture,texCoord,numSample); - - //Discard shadow computation on the sky - if(depth == 1.0){ - return color; - } - - // get the vertex in world space - vec4 worldPos = vec4(getPosition(depth,texCoord),1.0); - - - vec3 lightDir; - #ifdef PSSM - lightDir = m_LightDir; - #else - lightDir = worldPos.xyz - m_LightPos; - #endif - - #ifndef BACKFACE_SHADOWS - vec3 normal = approximateNormal(depth, worldPos, texCoord, numSample); - float ndotl = dot(normal, lightDir); - if(ndotl > 0.0){ - return color; - } - #endif - - #if (!defined(POINTLIGHT) && !defined(PSSM)) - if( dot(m_LightDir,lightDir)<0){ - return color; - } - #endif - - // populate the light view matrices array and convert vertex to light viewProj space - vec4 projCoord0 = biasMat * m_LightViewProjectionMatrix0 * worldPos; - vec4 projCoord1 = biasMat * m_LightViewProjectionMatrix1 * worldPos; - vec4 projCoord2 = biasMat * m_LightViewProjectionMatrix2 * worldPos; - vec4 projCoord3 = biasMat * m_LightViewProjectionMatrix3 * worldPos; - #ifdef POINTLIGHT - vec4 projCoord4 = biasMat * m_LightViewProjectionMatrix4 * worldPos; - vec4 projCoord5 = biasMat * m_LightViewProjectionMatrix5 * worldPos; - #endif - - float shadow = 1.0; - - #if defined(PSSM) || defined(FADE) - #ifdef INSTANCING - vec4 useMat = (texCoord.x > 0.5 ? m_ViewProjectionMatrixRow2Right : m_ViewProjectionMatrixRow2); - float shadowPosition = useMat.x * worldPos.x + useMat.y * worldPos.y + useMat.z * worldPos.z + useMat.w; - #else - float shadowPosition = m_ViewProjectionMatrixRow2.x * worldPos.x + m_ViewProjectionMatrixRow2.y * worldPos.y + m_ViewProjectionMatrixRow2.z * worldPos.z + m_ViewProjectionMatrixRow2.w; - #endif - #endif - - #ifdef POINTLIGHT - shadow = getPointLightShadows(worldPos, m_LightPos, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3,m_ShadowMap4,m_ShadowMap5, - projCoord0, projCoord1, projCoord2, projCoord3, projCoord4, projCoord5); - #else - #ifdef PSSM - shadow = getDirectionalLightShadows(m_Splits, shadowPosition, - m_ShadowMap0,m_ShadowMap1,m_ShadowMap2,m_ShadowMap3, - projCoord0, projCoord1, projCoord2, projCoord3); - #else - //spotlight - shadow = getSpotLightShadows(m_ShadowMap0,projCoord0); - #endif - #endif - - - #ifdef FADE - shadow = clamp(max(0.0,mix(shadow, 1.0 ,(shadowPosition - m_FadeInfo.x) * m_FadeInfo.y)),0.0,1.0); - #endif - - shadow= shadow * m_ShadowIntensity + (1.0 - m_ShadowIntensity); - return color * vec4(shadow, shadow, shadow, 1.0); -} - -void main(){ - - #if !defined( RENDER_SHADOWS ) - outFragColor = fetchTextureSample(m_Texture,texCoord,0); - return; - #endif - - #ifdef RESOLVE_MS - vec4 color = vec4(0.0); - for (int i = 0; i < m_NumSamples; i++){ - color += main_multiSample(i); - } - outFragColor = color / m_NumSamples; - #else - outFragColor = main_multiSample(0); - #endif - -} - - - diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.j3md b/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.j3md deleted file mode 100644 index 3bdd5b2ea1..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.j3md +++ /dev/null @@ -1,99 +0,0 @@ -MaterialDef Post Shadow { - - MaterialParameters { - Int FilterMode - Boolean HardwareShadows - - Texture2D ShadowMap0 - Texture2D ShadowMap1 - Texture2D ShadowMap2 - Texture2D ShadowMap3 - //pointLights - Texture2D ShadowMap4 - Texture2D ShadowMap5 - - Float ShadowIntensity - Vector4 Splits - Vector2 FadeInfo - - Matrix4 LightViewProjectionMatrix0 - Matrix4 LightViewProjectionMatrix1 - Matrix4 LightViewProjectionMatrix2 - Matrix4 LightViewProjectionMatrix3 - //pointLight - Matrix4 LightViewProjectionMatrix4 - Matrix4 LightViewProjectionMatrix5 - Vector3 LightPos - Vector3 LightDir - - Float PCFEdge - - Float ShadowMapSize - - Matrix4 ViewProjectionMatrixInverse - Vector4 ViewProjectionMatrixRow2 - - Int NumSamples - Int NumSamplesDepth - Texture2D Texture - Texture2D DepthTexture - - // is VR instancing? - Matrix4 ViewProjectionMatrixInverseRight - Vector4 ViewProjectionMatrixRow2Right - - Boolean BackfaceShadows: true - } - - Technique { - VertexShader GLSL150: Common/MatDefs/VR/PostShadowFilter.vert - FragmentShader GLSL150: Common/MatDefs/VR/PostShadowFilter.frag - - WorldParameters { - ResolutionInverse - } - - Defines { - RESOLVE_MS : NumSamples - RESOLVE_DEPTH_MS : NumSamplesDepth - HARDWARE_SHADOWS : HardwareShadows - FILTER_MODE : FilterMode - PCFEDGE : PCFEdge - SHADOWMAP_SIZE : ShadowMapSize - FADE : FadeInfo - PSSM : Splits - POINTLIGHT : LightViewProjectionMatrix5 - //if no shadow map don't render shadows - RENDER_SHADOWS : ShadowMap0 - INSTANCING : ViewProjectionMatrixInverseRight - BACKFACE_SHADOWS : BackfaceShadows - } - - } - - Technique { - VertexShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.vert - FragmentShader GLSL100: Common/MatDefs/Shadow/PostShadowFilter.frag - - WorldParameters { - ResolutionInverse - } - - Defines { - HARDWARE_SHADOWS : HardwareShadows - FILTER_MODE : FilterMode - PCFEDGE : PCFEdge - SHADOWMAP_SIZE : ShadowMapSize - FADE : FadeInfo - PSSM : Splits - POINTLIGHT : LightViewProjectionMatrix5 - INSTANCING : ViewProjectionMatrixInverseRight - BACKFACE_SHADOWS : BackfaceShadows - } - - } - - - - -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.vert deleted file mode 100644 index d8ffbbfcb9..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/PostShadowFilter.vert +++ /dev/null @@ -1,11 +0,0 @@ -//#define VERTEX_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -attribute vec4 inPosition; -attribute vec2 inTexCoord; -varying vec2 texCoord; - -void main() { - gl_Position = vec4(inPosition.xy * 2.0 - 1.0, 0.0, 1.0); - texCoord = inTexCoord; -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.frag deleted file mode 100644 index 14217abc4a..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.frag +++ /dev/null @@ -1,21 +0,0 @@ -//#define FRAGMENT_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -uniform vec4 m_Color; -uniform sampler2D m_ColorMap; - -varying vec2 texCoord1; - -void main(){ - vec4 color = vec4(1.0); - - #ifdef HAS_COLORMAP - color *= texture2D(m_ColorMap, texCoord1); - #endif - - #ifdef HAS_COLOR - color *= m_Color; - #endif - - gl_FragColor = color; -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.j3md b/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.j3md deleted file mode 100644 index 81e1ebecc4..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.j3md +++ /dev/null @@ -1,45 +0,0 @@ -MaterialDef Unshaded { - - MaterialParameters { - Texture2D ColorMap - Color Color (Color) - - // For VR instancing - Matrix4 RightEyeViewProjectionMatrix - } - - Technique { - VertexShader GLSL150: Common/MatDefs/VR/Unshaded.vert - FragmentShader GLSL150: Common/MatDefs/VR/Unshaded.frag - - WorldParameters { - WorldViewProjectionMatrix - ViewProjectionMatrix // needed for VR instancing - ViewMatrix - } - - Defines { - INSTANCING : RightEyeViewProjectionMatrix // For VR instancing - HAS_COLORMAP : ColorMap - HAS_COLOR : Color - } - } - - Technique { - VertexShader GLSL100: Common/MatDefs/VR/Unshaded.vert - FragmentShader GLSL100: Common/MatDefs/VR/Unshaded.frag - - WorldParameters { - WorldViewProjectionMatrix - ViewProjectionMatrix // needed for VR instancing - ViewMatrix - } - - Defines { - INSTANCING : RightEyeViewProjectionMatrix // For VR instancing - HAS_COLORMAP : ColorMap - HAS_COLOR : Color - } - } - -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.vert deleted file mode 100644 index 6e72b10e7f..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/Unshaded.vert +++ /dev/null @@ -1,27 +0,0 @@ -//#define VERTEX_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -// import the following for VR instancing -#import "Common/ShaderLib/InstanceVR.glsllib" - -attribute vec3 inPosition; - -#if defined(HAS_COLORMAP) || (defined(HAS_LIGHTMAP) && !defined(SEPARATE_TEXCOORD)) - #define NEED_TEXCOORD1 -#endif - -attribute vec2 inTexCoord; -attribute vec4 inColor; - -varying vec2 texCoord1; - -void main(){ - #ifdef NEED_TEXCOORD1 - texCoord1 = inTexCoord; - #endif - - vec4 modelSpacePos = vec4(inPosition, 1.0); - - // use the following transform function for VR instancing - gl_Position = TransformWorldViewProjectionVR(modelSpacePos); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.frag b/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.frag deleted file mode 100644 index 16d32cedcd..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.frag +++ /dev/null @@ -1,9 +0,0 @@ -//#define FRAGMENT_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -varying vec3 normal; - -void main(void) -{ - gl_FragColor = vec4(normal.xy* 0.5 + 0.5,-normal.z* 0.5 + 0.5, 1.0); -} diff --git a/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.vert b/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.vert deleted file mode 100644 index 3f590101f5..0000000000 --- a/jme3-vr/src/main/resources/Common/MatDefs/VR/normal.vert +++ /dev/null @@ -1,19 +0,0 @@ -//#define VERTEX_SHADER -#import "Common/ShaderLib/GLSLCompat.glsllib" - -// import the following for VR instancing -#import "Common/ShaderLib/InstanceVR.glsllib" - -attribute vec3 inPosition; -attribute vec3 inNormal; - -varying vec3 normal; - -void main(void) -{ - vec4 modelSpacePos = vec4(inPosition, 1.0); - normal = normalize(TransformNormal(inNormal)); - - // use the following transform function for VR instancing - gl_Position = TransformWorldViewProjectionVR(modelSpacePos); -} \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/ShaderLib/InstanceVR.glsllib b/jme3-vr/src/main/resources/Common/ShaderLib/InstanceVR.glsllib deleted file mode 100644 index 99629173f9..0000000000 --- a/jme3-vr/src/main/resources/Common/ShaderLib/InstanceVR.glsllib +++ /dev/null @@ -1,126 +0,0 @@ -// Instancing GLSL library. Modified for VR use. -// -// When the INSTANCING define is set in the shader, -// all global matrices are replaced with "instanced" versions. -// One exception is g_NormalMatrix which becomes unusable, -// instead the function ApplyNormalTransform is used to transform -// the normal and tangent vectors into world view space. - -// The world matrix and normal transform quaternion need to be passed -// as vertex attributes "inWorldMatrix" and "inNormalRotationQuaternion" -// respectively. -// The VertexBuffers for those two attributes -// need to be configured into instanced mode (VertexBuffer.setInstanced(true)). -// - inWorldMatrix should have 12 * numInstances floats. -// - inNormalRotationQuaternion should have 4 * numInstances. -// Thus, instancing data occupies 4 vertex attributes (16 / 4 = 4). -// -// The GL_ARB_draw_instanced and GL_ARB_instanced_arrays extensions -// are required (OGL 3.3). - -#if defined INSTANCING - -uniform mat4 g_ViewProjectionMatrix; -uniform mat4 g_ViewMatrix; -uniform mat4 m_RightEyeViewProjectionMatrix; - -// World Matrix + Normal Rotation Quaternion. -// The World Matrix is the top 3 rows - -// since the bottom row is always 0,0,0,1 for this transform. -// The bottom row is the transpose of the inverse of WorldView Transform -// as a quaternion. i.e. g_NormalMatrix converted to a quaternion. -// -// Using a quaternion instead of a matrix here allows saving approximately -// 2 vertex attributes which now can be used for additional per-vertex data. -attribute mat4 inInstanceData; - -// Extract the world matrix out of the instance data, leaving out the -// quaternion at the end. -mat4 worldMatrix = mat4(vec4(inInstanceData[0].xyz, 0.0), - vec4(inInstanceData[1].xyz, 0.0), - vec4(inInstanceData[2].xyz, 0.0), - vec4(inInstanceData[3].xyz, 1.0)); - -vec4 TransformWorld(vec4 position) -{ - return (worldMatrix * position); -} - -vec4 TransformWorldView(vec4 position) -{ - return g_ViewMatrix * TransformWorld(position); -} - -vec4 TransformWorldViewProjection(vec4 position) -{ - return g_ViewProjectionMatrix * TransformWorld(position); -} - -// VR specific variables -const float EyeOffsetScale[2] = float[](-0.5, 0.5); -const vec4 EyeClipEdge[2] = vec4[](vec4(-1.0,0.0,0.0,1.0), vec4(1.0,0.0,0.0,1.0)); -out float gl_ClipDistance[1]; -in int gl_InstanceID; - -vec4 TransformWorldViewProjectionVR(vec4 position) -{ - vec4 clipPos = (gl_InstanceID == 0 ? g_ViewProjectionMatrix : m_RightEyeViewProjectionMatrix) * TransformWorld(position); - gl_ClipDistance[0] = dot(clipPos, EyeClipEdge[gl_InstanceID]); - clipPos.x *= 0.5; // shrink to half of the screen - clipPos.x += EyeOffsetScale[gl_InstanceID] * clipPos.w; // scoot left or right. - return clipPos; -} - -vec3 TransformNormal(vec3 vec) -{ - vec4 quat = vec4(inInstanceData[0].w, inInstanceData[1].w, - inInstanceData[2].w, inInstanceData[3].w); - - vec3 worldNormal = vec + vec3(2.0) * cross(cross(vec, quat.xyz) + vec3(quat.w) * vec, quat.xyz); - - return (g_ViewMatrix * vec4(worldNormal, 0.0)).xyz; -} - -// Prevent user from using g_** matrices which will have invalid data in this case. -#define g_WorldMatrix use_TransformWorld_not_gWorldMatrix -#define g_WorldViewMatrix use_TransformWorldView_not_gWorldMatrix -#define g_WorldViewProjectionMatrix use_TransformWorldViewProjectionVR_not_gWorldViewProjectionMatrix -#define g_NormalMatrix use_TransformNormal_not_gNormalMatrix - -#else - -uniform mat4 g_WorldMatrix; -uniform mat4 g_WorldViewMatrix; -uniform mat4 g_WorldViewProjectionMatrix; -uniform mat3 g_NormalMatrix; -uniform mat3 g_WorldNormalMatrix; - -vec4 TransformWorld(vec4 position) -{ - return g_WorldMatrix * position; -} - -vec4 TransformWorldView(vec4 position) -{ - return g_WorldViewMatrix * position; -} - -vec4 TransformWorldViewProjection(vec4 position) -{ - return g_WorldViewProjectionMatrix * position; -} - -vec4 TransformWorldViewProjectionVR(vec4 position) -{ - return g_WorldViewProjectionMatrix * position; -} - -vec3 TransformNormal(vec3 normal) { - return g_NormalMatrix * normal; -} - -vec3 TransformWorldNormal(vec3 normal) { - return normalize(g_WorldNormalMatrix * normal); -} - -#endif \ No newline at end of file diff --git a/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3o b/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3o deleted file mode 100644 index 728a706020..0000000000 Binary files a/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3o and /dev/null differ diff --git a/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3odata b/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3odata deleted file mode 100644 index c5df9acd61..0000000000 --- a/jme3-vr/src/main/resources/Common/Util/gui_mesh.j3odata +++ /dev/null @@ -1,3 +0,0 @@ -# -#Thu Oct 01 10:33:02 EDT 2015 -ORIGINAL_PATH=Models/gui_mesh.obj diff --git a/jme3-vr/src/main/resources/Common/Util/mouse.png b/jme3-vr/src/main/resources/Common/Util/mouse.png deleted file mode 100644 index 8ca6edbe1e..0000000000 Binary files a/jme3-vr/src/main/resources/Common/Util/mouse.png and /dev/null differ diff --git a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib b/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib deleted file mode 100644 index b3da84f1ef..0000000000 Binary files a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib and /dev/null differ diff --git a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Info.plist b/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Info.plist deleted file mode 100644 index 60ac3d69bc..0000000000 --- a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Info.plist +++ /dev/null @@ -1,20 +0,0 @@ - - - - - CFBundleDevelopmentRegion - English - CFBundleIdentifier - com.apple.xcode.dsym.libopenvr_api.dylib - CFBundleInfoDictionaryVersion - 6.0 - CFBundlePackageType - dSYM - CFBundleSignature - ???? - CFBundleShortVersionString - 1.0 - CFBundleVersion - 1 - - diff --git a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Resources/DWARF/libopenvr_api.dylib b/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Resources/DWARF/libopenvr_api.dylib deleted file mode 100644 index 8ca6951d9d..0000000000 Binary files a/jme3-vr/src/main/resources/darwin/libopenvr_api.dylib.dSYM/Contents/Resources/DWARF/libopenvr_api.dylib and /dev/null differ diff --git a/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so b/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so deleted file mode 100644 index eafad11ec6..0000000000 Binary files a/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so and /dev/null differ diff --git a/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so.dbg b/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so.dbg deleted file mode 100644 index 877c284088..0000000000 Binary files a/jme3-vr/src/main/resources/linux-x86-64/libopenvr_api.so.dbg and /dev/null differ diff --git a/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so b/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so deleted file mode 100644 index a250545083..0000000000 Binary files a/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so and /dev/null differ diff --git a/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so.dbg b/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so.dbg deleted file mode 100644 index a26ac1c1f9..0000000000 Binary files a/jme3-vr/src/main/resources/linux-x86/libopenvr_api.so.dbg and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/LICENSE.txt b/jme3-vr/src/main/resources/win32-x86-64/LICENSE.txt deleted file mode 100644 index ee83337d7f..0000000000 --- a/jme3-vr/src/main/resources/win32-x86-64/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2015, Valve Corporation -All rights reserved. - -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. diff --git a/jme3-vr/src/main/resources/win32-x86-64/Qt5Core.dll b/jme3-vr/src/main/resources/win32-x86-64/Qt5Core.dll deleted file mode 100644 index b1a4a24e73..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/Qt5Core.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/Qt5Gui.dll b/jme3-vr/src/main/resources/win32-x86-64/Qt5Gui.dll deleted file mode 100644 index 9be46e08a0..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/Qt5Gui.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/Qt5Widgets.dll b/jme3-vr/src/main/resources/win32-x86-64/Qt5Widgets.dll deleted file mode 100644 index a70f8c84ce..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/Qt5Widgets.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/SDL2.dll b/jme3-vr/src/main/resources/win32-x86-64/SDL2.dll deleted file mode 100644 index a65b22ce9d..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/SDL2.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/d3dcompiler_47.dll b/jme3-vr/src/main/resources/win32-x86-64/d3dcompiler_47.dll deleted file mode 100644 index 4f3fc16106..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/d3dcompiler_47.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/functionality.dll b/jme3-vr/src/main/resources/win32-x86-64/functionality.dll deleted file mode 100644 index c25975aaeb..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/functionality.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/glew32.dll b/jme3-vr/src/main/resources/win32-x86-64/glew32.dll deleted file mode 100644 index a6cc6fd17f..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/glew32.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/msvcp120.dll b/jme3-vr/src/main/resources/win32-x86-64/msvcp120.dll deleted file mode 100644 index 4ea1efa734..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/msvcp120.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/msvcr120.dll b/jme3-vr/src/main/resources/win32-x86-64/msvcr120.dll deleted file mode 100644 index d711c92232..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/msvcr120.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_calib3d2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_calib3d2410.dll deleted file mode 100644 index af147e0d1f..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_calib3d2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_core2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_core2410.dll deleted file mode 100644 index d3a98f26a6..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_core2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_features2d2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_features2d2410.dll deleted file mode 100644 index bb0d3c635b..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_features2d2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_flann2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_flann2410.dll deleted file mode 100644 index d8e465a8e1..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_flann2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_highgui2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_highgui2410.dll deleted file mode 100644 index cd28635211..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_highgui2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/opencv_imgproc2410.dll b/jme3-vr/src/main/resources/win32-x86-64/opencv_imgproc2410.dll deleted file mode 100644 index 548567b613..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/opencv_imgproc2410.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/openvr_api.dll b/jme3-vr/src/main/resources/win32-x86-64/openvr_api.dll deleted file mode 100644 index 5767d725f4..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/openvr_api.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/openvr_api.pdb b/jme3-vr/src/main/resources/win32-x86-64/openvr_api.pdb deleted file mode 100644 index 082d4780d9..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/openvr_api.pdb and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osg.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osg.dll deleted file mode 100644 index cab66c897e..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osg.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgDB.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osgDB.dll deleted file mode 100644 index 1ad86ca487..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgDB.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgGA.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osgGA.dll deleted file mode 100644 index 32643f05ee..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgGA.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgText.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osgText.dll deleted file mode 100644 index 9b8bdd7e4a..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgText.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgUtil.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osgUtil.dll deleted file mode 100644 index 75f620864c..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgUtil.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgViewer.dll b/jme3-vr/src/main/resources/win32-x86-64/osg100-osgViewer.dll deleted file mode 100644 index eab07f1976..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osg100-osgViewer.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osgdb_deprecated_osg.dll b/jme3-vr/src/main/resources/win32-x86-64/osgdb_deprecated_osg.dll deleted file mode 100644 index 1b7c8cc042..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osgdb_deprecated_osg.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osgdb_osg.dll b/jme3-vr/src/main/resources/win32-x86-64/osgdb_osg.dll deleted file mode 100644 index a391446554..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osgdb_osg.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrAnalysisPluginKit.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrAnalysisPluginKit.dll deleted file mode 100644 index 1652fb9515..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrAnalysisPluginKit.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrClient.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrClient.dll deleted file mode 100644 index 43c9eeb85d..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrClient.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrClientKit.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrClientKit.dll deleted file mode 100644 index 4de48879c4..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrClientKit.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrCommon.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrCommon.dll deleted file mode 100644 index 474abf024a..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrCommon.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrConnection.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrConnection.dll deleted file mode 100644 index b4aec55ce0..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrConnection.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrJointClientKit.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrJointClientKit.dll deleted file mode 100644 index 9e8a8cc655..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrJointClientKit.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrPluginHost.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrPluginHost.dll deleted file mode 100644 index c39715e28f..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrPluginHost.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrPluginKit.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrPluginKit.dll deleted file mode 100644 index c0009b682d..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrPluginKit.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrRenderManager.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrRenderManager.dll deleted file mode 100644 index f97df681e8..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrRenderManager.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrServer.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrServer.dll deleted file mode 100644 index 9d925ac03a..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrServer.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrUSBSerial.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrUSBSerial.dll deleted file mode 100644 index b81542225f..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrUSBSerial.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrUtil.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrUtil.dll deleted file mode 100644 index 417c04d462..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrUtil.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/osvrVRPNServer.dll b/jme3-vr/src/main/resources/win32-x86-64/osvrVRPNServer.dll deleted file mode 100644 index 0720deb3d9..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/osvrVRPNServer.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86-64/ot20-OpenThreads.dll b/jme3-vr/src/main/resources/win32-x86-64/ot20-OpenThreads.dll deleted file mode 100644 index a50ba7d58b..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86-64/ot20-OpenThreads.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86/openvr_api.dll b/jme3-vr/src/main/resources/win32-x86/openvr_api.dll deleted file mode 100644 index 83ead53b82..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86/openvr_api.dll and /dev/null differ diff --git a/jme3-vr/src/main/resources/win32-x86/openvr_api.pdb b/jme3-vr/src/main/resources/win32-x86/openvr_api.pdb deleted file mode 100644 index a7bb091e62..0000000000 Binary files a/jme3-vr/src/main/resources/win32-x86/openvr_api.pdb and /dev/null differ diff --git a/lib/jbullet.jar b/lib/jbullet.jar deleted file mode 100644 index 43926d5df8..0000000000 Binary files a/lib/jbullet.jar and /dev/null differ diff --git a/lib/stack-alloc.jar b/lib/stack-alloc.jar deleted file mode 100644 index ab1d988ced..0000000000 Binary files a/lib/stack-alloc.jar and /dev/null differ diff --git a/license.txt b/license.txt deleted file mode 100644 index d81f177ae7..0000000000 --- a/license.txt +++ /dev/null @@ -1,35 +0,0 @@ -<#if licenseFirst??> -${licenseFirst} - -${licensePrefix}Copyright (c) 2009-${date?date?string("yyyy")} ${project.organization!user} -${licensePrefix}All rights reserved. -${licensePrefix?replace(" +$", "", "r")} -${licensePrefix}Redistribution and use in source and binary forms, with or without -${licensePrefix}modification, are permitted provided that the following conditions are -${licensePrefix}met: -${licensePrefix?replace(" +$", "", "r")} -${licensePrefix}* Redistributions of source code must retain the above copyright -${licensePrefix} notice, this list of conditions and the following disclaimer. -${licensePrefix?replace(" +$", "", "r")} -${licensePrefix}* Redistributions in binary form must reproduce the above copyright -${licensePrefix} notice, this list of conditions and the following disclaimer in the -${licensePrefix} documentation and/or other materials provided with the distribution. -${licensePrefix?replace(" +$", "", "r")} -${licensePrefix}* Neither the name of '${project.organization!user}' nor the names of its contributors -${licensePrefix} may be used to endorse or promote products derived from this software -${licensePrefix} without specific prior written permission. -${licensePrefix?replace(" +$", "", "r")} -${licensePrefix}THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -${licensePrefix}"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED -${licensePrefix}TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -${licensePrefix}PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR -${licensePrefix}CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, -${licensePrefix}EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, -${licensePrefix}PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -${licensePrefix}PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -${licensePrefix}LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -${licensePrefix}NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -${licensePrefix}SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -<#if licenseLast??> -${licenseLast} - \ No newline at end of file diff --git a/local.properties b/local.properties deleted file mode 100644 index 27e823fffd..0000000000 --- a/local.properties +++ /dev/null @@ -1 +0,0 @@ -sdk.dir=C:\\AndroidSDK \ No newline at end of file diff --git a/natives-snapshot.properties b/natives-snapshot.properties index 5871ad7fac..b4fc1678a3 100644 --- a/natives-snapshot.properties +++ b/natives-snapshot.properties @@ -1 +1 @@ -natives.snapshot=75001d8abda394a320662e63466245fd02b344f3 +natives.snapshot=a2471007ca569d6ce0f8fc26b92366aa7dcf4a61 diff --git a/settings.gradle b/settings.gradle index e6298b4826..20b3f4f312 100644 --- a/settings.gradle +++ b/settings.gradle @@ -8,12 +8,13 @@ include 'jme3-core' include 'jme3-effects' include 'jme3-networking' include 'jme3-plugins' +include 'jme3-plugins-json' +include 'jme3-plugins-json-gson' + include 'jme3-terrain' // Desktop dependent java classes include 'jme3-desktop' -include 'jme3-blender' -include 'jme3-jogl' include 'jme3-lwjgl' if (JavaVersion.current().isJava8Compatible()) { include 'jme3-lwjgl3' @@ -28,17 +29,18 @@ include 'jme3-android' include 'jme3-ios' //native builds -include 'jme3-bullet' //java -include 'jme3-bullet-native' //cpp -include 'jme3-bullet-native-android' //cpp include 'jme3-android-native' //cpp +include 'jme3-ios-native' //cpp // Test Data project include 'jme3-testdata' // Example projects include 'jme3-examples' +include 'jme3-awt-dialogs' if(buildAndroidExamples == "true"){ include 'jme3-android-examples' } +include 'jme3-screenshot-tests' + diff --git a/source-file-header-template.txt b/source-file-header-template.txt new file mode 100644 index 0000000000..be93175706 --- /dev/null +++ b/source-file-header-template.txt @@ -0,0 +1,35 @@ +<#if licenseFirst??> +${licenseFirst} + +${licensePrefix}Copyright (c) 2009-${date?date?string("yyyy")} jMonkeyEngine +${licensePrefix}All rights reserved. +${licensePrefix?replace(" +$", "", "r")} +${licensePrefix}Redistribution and use in source and binary forms, with or without +${licensePrefix}modification, are permitted provided that the following conditions are +${licensePrefix}met: +${licensePrefix?replace(" +$", "", "r")} +${licensePrefix}* Redistributions of source code must retain the above copyright +${licensePrefix} notice, this list of conditions and the following disclaimer. +${licensePrefix?replace(" +$", "", "r")} +${licensePrefix}* Redistributions in binary form must reproduce the above copyright +${licensePrefix} notice, this list of conditions and the following disclaimer in the +${licensePrefix} documentation and/or other materials provided with the distribution. +${licensePrefix?replace(" +$", "", "r")} +${licensePrefix}* Neither the name of 'jMonkeyEngine' nor the names of its contributors +${licensePrefix} may be used to endorse or promote products derived from this software +${licensePrefix} without specific prior written permission. +${licensePrefix?replace(" +$", "", "r")} +${licensePrefix}THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +${licensePrefix}"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +${licensePrefix}TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +${licensePrefix}PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +${licensePrefix}CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +${licensePrefix}EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +${licensePrefix}PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +${licensePrefix}PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +${licensePrefix}LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +${licensePrefix}NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +${licensePrefix}SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +<#if licenseLast??> +${licenseLast} + \ No newline at end of file diff --git a/version.gradle b/version.gradle index 637b5ff72a..fbed055bf9 100644 --- a/version.gradle +++ b/version.gradle @@ -7,7 +7,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'org.ajoberstar:gradle-git:1.2.0' + classpath libs.gradle.git } }